diff --git a/Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m b/Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m index 305b2ba..8cfd141 100644 --- a/Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m +++ b/Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m @@ -231,10 +231,10 @@ classdef FFE_A2TrackedLevels < handle dc_level_symbol_idx = symbol_idx; if dc_level_enabled - dc_level_offset = dc_level_offset_by_level(symbol_idx); - dc_level_valid_count = dc_level_valid_count_by_level(symbol_idx); - dc_level_weight = dc_level_weight_by_level(symbol_idx) * ... - min(dc_level_valid_count / dc_level_buffer_len,1); + dc_level_offset = dc_level_offset_by_level(symbol_idx); + dc_level_valid_count = dc_level_valid_count_by_level(symbol_idx); + dc_level_weight = dc_level_weight_by_level(symbol_idx) * ... + min(dc_level_valid_count / dc_level_buffer_len,1); [dc_level_buffer,dc_level_buffer_pos_by_level,dc_level_sum_by_level, ... dc_level_buffer_valid_count_by_level,dc_level_offset_by_level, ... diff --git a/Functions/EQ_recipes/mpi_recipe_dev.m b/Functions/EQ_recipes/mpi_recipe_dev.m index bd7413a..962ec62 100644 --- a/Functions/EQ_recipes/mpi_recipe_dev.m +++ b/Functions/EQ_recipes/mpi_recipe_dev.m @@ -15,11 +15,12 @@ end %% Execution toggles -run_conventional_ffe = 1; -run_a2_tracked_levels = 1; -run_a2_residual = 1; -run_a1 = 1; -run_tracking_adaptive = 1; +run_conventional_ffe = 0; +run_hpf = 1; +run_a2_tracked_levels = 0; +run_a2_residual = 0; +run_a1 = 0; +run_tracking_adaptive = 0; plot_output_signals = 0; %% Shared fixed EQ settings @@ -49,6 +50,7 @@ if plot_output_signals "fignum", 400, ... "normalize", true); end + %% Conventional FFE if run_conventional_ffe eq_ffe = FFE_plain( ... @@ -78,6 +80,41 @@ if run_conventional_ffe end end +%% Conventional FFE +if run_hpf + eq_ffe = FFE_plain( ... + "sps", eq_sps, ... + "order", eq_order, ... + "decide", false, ... + "adaption_technique", eq_adaption, ... + "len_tr", eq_len_tr, ... + "epochs_tr", eq_epochs_tr, ... + "mu_tr", eq_mu_tr, ... + "dd_mode", true, ... + "epochs_dd", eq_epochs_dd, ... + "mu_dd", 0.0012, ... + "optmize_mus", false, ... + "plot_mu_optimization", options.debug_plots, ... + "save_debug", eq_save_debug); + + %%% APPLY HPF in MHZ order + f = Filter("f_cutoff",options.userParameters.hpf,"filterType","butterworth","lowpass",0,"filtdegree",4,"fs",options.fsym*2); + Scpe_sig = f.process(Scpe_sig); + + storageName = "HPF_plus_conventional_FFE"; + [ffe_results,equalized_signal] = runFfe(eq_ffe, "Conventional FFE", ... + Scpe_sig, Symbols, Tx_bits, options); + ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ... + "HPF_plus_conventional_FFE", "hpf", block_update); + output.(char(storageName)) = ffe_results; + + if plot_output_signals + plotEqSignals(equalized_signal,Symbols,options,400,-1); + end +end + + + %% A2 tracked-level decision if run_a2_tracked_levels eq_ffe = FFE_A2TrackedLevels( ... @@ -92,8 +129,8 @@ if run_a2_tracked_levels "epochs_dd", eq_epochs_dd, ... "mu_dd", 0.012, ... "dc_smoothing_a2", 1, ... - "dc_level_avg_bufferlength_a2", 112, ... - "dc_level_update_blocklength_a2", block_update, ... + "dc_level_avg_bufferlength_a2", 112, ... %war 112 + "dc_level_update_blocklength_a2", block_update, ... % war block_update "dc_level_weights_a2", [1], ... "save_debug", eq_save_debug); @@ -190,7 +227,7 @@ if run_tracking_adaptive "optmize_mus", false, ... "optimize_dc_tracking_params", true, ... "dc_tracking_optimization_len", 2^15, ... - "dc_tracking_optimization_max_evals", 20, ... + "dc_tracking_optimization_max_evals", 30, ... "plot_mu_optimization", options.debug_plots, ... "save_debug", eq_save_debug); diff --git a/Functions/beautifyBERplot.m b/Functions/beautifyBERplot.m index 5f84673..3c3764b 100644 --- a/Functions/beautifyBERplot.m +++ b/Functions/beautifyBERplot.m @@ -6,10 +6,12 @@ function beautifyBERplot(options) % beautifyBERplot % beautifyBERplot("polyfit",true,"polyorder",1) % beautifyBERplot("polyfit",true,"fitmethod","pchip") +% beautifyBERplot("changemarkers",true) arguments options.logscale (1,1) logical = 1 options.setmarkers (1,1) logical = 1 + options.changemarkers (1,1) logical = 0 options.setcolors (1,1) logical = 1 options.polyfit (1,1) logical = 0 options.polyorder (1,1) double = 2 @@ -42,9 +44,11 @@ if n > 0 end end -markers = {'o','s','o','o','^','v','d','>'}; -lw = 0.8; -ms = 2; +% Simple marker set that exports cleanly with common MATLAB-to-TikZ workflows. +markers = {'o','square','diamond','^','v','>','<','pentagram'}; +lw = 1; +ms = 5; +applyMarkers = options.setmarkers || options.changemarkers; % --------------------------------------------------------- % Apply line/scatter + marker styling @@ -59,8 +63,8 @@ for i = 1:n dataObj.Color = cmap(i,:); end - if options.setmarkers - if strcmp(dataObj.Marker,'none') + if applyMarkers + if options.changemarkers || strcmp(dataObj.Marker,'none') dataObj.Marker = markers{mod(i-1,numel(markers))+1}; end dataObj.MarkerSize = ms; @@ -80,11 +84,11 @@ for i = 1:n end end - if options.setmarkers - if strcmp(dataObj.Marker,'none') + if applyMarkers + if options.changemarkers || strcmp(dataObj.Marker,'none') dataObj.Marker = markers{mod(i-1,numel(markers))+1}; end - dataObj.SizeData = ms^2; + dataObj.SizeData = ms; end end end diff --git a/Functions/mat2tikz_improved.m b/Functions/mat2tikz_improved.m index 0ba1c6d..bc0b040 100644 --- a/Functions/mat2tikz_improved.m +++ b/Functions/mat2tikz_improved.m @@ -22,24 +22,8 @@ matlab2tikz(char(filename), ... 'tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}}', ... 'every axis/.append style={font=\scriptsize}', ... 'legend columns=1', ... - 'legend style={at={(0.02,0.98)}, anchor=north west, font=\scriptsize, draw=black!100, rounded corners=2pt, inner sep=1pt, fill=white, column sep=2pt}' ... + 'legend style={at={(0.98,0.98)}, anchor=north east, font=\scriptsize, draw=black!100, rounded corners=2pt, inner sep=1pt, fill=white, column sep=2pt}' ... }); -% matlab2tikz(char(filename), ... -% 'width','\fwidth', ... -% 'height','\fheight', ... -% 'showInfo',false, ... -% 'extraAxisOptions',{ ... -% 'legend style={font=\footnotesize}', ... -% 'xlabel style={font=\color{white!15!black},font=\small},',... -% 'ylabel style={font=\color{white!15!black},font=\small},',... -% 'legend columns=1', ... -% 'every axis/.append style={font=\scriptsize}',... -% 'legend columns=1',... -% 'legend style={at={(0.02,0.98)},font=\footnotesize,draw=black!60,rounded corners=2pt,inner sep=1pt,fill=white,column sep=6pt,anchor= north west}',... -% 'legend style={at={(0.02,0.98)},draw=white!0!white,font=\scriptsize,inner sep=0.1pt,fill=white,column sep=1pt,anchor= north west}',... -% 'every axis/.append style={font=\scriptsize}',... -% 'scaled ticks=false', ... -% 'tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}}' ... -% }); + end diff --git a/Libs/mat2tikz/src/matlab2tikz.m b/Libs/mat2tikz/src/matlab2tikz.m index 9c25805..4cf3758 100644 --- a/Libs/mat2tikz/src/matlab2tikz.m +++ b/Libs/mat2tikz/src/matlab2tikz.m @@ -2234,12 +2234,12 @@ function [tikzMarkerSize, isDefault] = ... % the acute angle (at the top and the bottom of the diamond) % is a manually measured 75 degrees (in TikZ, and MATLAB % probably very similar); use this as a base for calculations - tikzMarkerSize = matlabMarkerSize(:) / 2 / atan(75/2 *pi/180); + tikzMarkerSize = matlabMarkerSize(:) / 1.7 ;%/ atan(75/2 *pi/180); case {'^','v','<','>'} % for triangles, matlab takes the height % and tikz the circumcircle radius; % the triangles are always equiangular - tikzMarkerSize = matlabMarkerSize(:) / 2 * (2/3); + tikzMarkerSize = matlabMarkerSize(:) / 1.7;% * (2/3); otherwise error('matlab2tikz:translateMarkerSize', ... 'Unknown matlabMarker ''%s''.', matlabMarker); diff --git a/mpiana_13_07_2026.zip b/mpiana_13_07_2026.zip new file mode 100644 index 0000000..2e700f2 Binary files /dev/null and b/mpiana_13_07_2026.zip differ diff --git a/projects/Diss/MPI_revisit/algorithms/PLOT_ber_vs_sir.m b/projects/Diss/MPI_revisit/algorithms/PLOT_ber_vs_sir.m deleted file mode 100644 index e8a08d9..0000000 --- a/projects/Diss/MPI_revisit/algorithms/PLOT_ber_vs_sir.m +++ /dev/null @@ -1,243 +0,0 @@ -%% Simple BER over SIR plot from the recombined warehouses -% This script intentionally keeps the mechanics visible: -% 1) load the run_id warehouse and the grouped config warehouse -% 2) loop over occupied config cells -% 3) read the physical coordinates of the cell -% 4) extract all BER values stored in that cell -% 5) plot individual BER dots and one larger mean marker per SIR - -clear; clc; - -scriptDir = fileparts("C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\Diss\MPI_revisit\algorithms\"); - - -runWhFile = fullfile(scriptDir, "combined_by_run_id_results.mat"); -configWhFile = fullfile(scriptDir, "combined_by_config_results.mat"); - -runWhData = load(runWhFile); -configWhData = load(configWhFile); - -wh_run_id_combined = runWhData.wh_run_id_combined; -wh_config_combined = configWhData.wh_config_combined; -algorithmStorageNames = configWhData.algorithmStorageNames; - -fprintf("Loaded run_id warehouse:\n %s\n", runWhFile); -wh_run_id_combined.showInfo; - -fprintf("\nLoaded config-grouped warehouse:\n %s\n", configWhFile); -wh_config_combined.showInfo; - -%% Plot settings - -pathLengthToPlot = 1000; % use 300 to see duplicate run_ids per config; set 1000 for the previous path -selectedPamLevels = 8; %wh_config_combined.parameter.pam_level.values; -selectedAlgorithms = algorithmStorageNames([1,2,3,5]); - -useBoundedLines = true; % switch uncertainty bands on/off here -usePolyfit = true; % switch fitted dashed trend lines on/off here -polyfitOrderMax = 4; - - -% SIR vs BER: one subplot per PAM level, all algorithms compared - -if exist("linspecer", "file") - algColors = linspecer(numel(selectedAlgorithms)); -else - algColors = lines(numel(selectedAlgorithms)); -end - -figure(); clf; -tiledlayout(numel(selectedPamLevels), 1, "TileSpacing", "compact"); - -for pamIdx = 1:numel(selectedPamLevels) - pamLevel = selectedPamLevels(pamIdx); - nexttile; hold on; - - for algIdx = 1:numel(selectedAlgorithms) - algorithmName = selectedAlgorithms{algIdx}; - algColor = algColors(algIdx, :); - - berTable = buildBerTable(wh_config_combined, algorithmName, pathLengthToPlot); - berTable = berTable(berTable.pam_level == pamLevel, :); - - % berTable(berTable.sir == 23,:) = []; - - if isempty(berTable) - continue - end - - sirValues = unique(berTable.sir(:).'); - sirValues = sort(sirValues); - - meanBer = nan(size(sirValues)); - minBer = nan(size(sirValues)); - maxBer = nan(size(sirValues)); - - for sirIdx = 1:numel(sirValues) - sirValue = sirValues(sirIdx); - sirRows = berTable(berTable.sir == sirValue, :); - berValues = [sirRows.ber_values{:}]; - berValues = berValues(isfinite(berValues)); - berValues = berValues(berValues<0.1); - berValues = rmoutliers(berValues); - - - if isempty(berValues) - continue - end - - scatter(repmat(sirValue, size(berValues)), berValues, ... - 30, ... - "Marker", ".", ... - "MarkerEdgeColor", algColor, ... - "MarkerFaceColor", algColor, ... - "HandleVisibility", "off"); - - meanBer(sirIdx) = mean(berValues, "omitnan"); - minBer(sirIdx) = min(berValues); - maxBer(sirIdx) = max(berValues); - end - - valid = isfinite(sirValues) & isfinite(meanBer); - if ~any(valid) - continue - end - - if useBoundedLines && exist("boundedline", "file") - yLower = max(meanBer - minBer, 0); - yUpper = max(maxBer - meanBer, 0); - yBounds = [yLower(:), yUpper(:)]; - - [hl, hp] = boundedline(sirValues(valid).', meanBer(valid).', yBounds(valid, :), ... - 'alpha', 'transparency', 0.08, ... - 'cmap', algColor, ... - 'nan', 'fill', ... - 'orientation', 'vert'); - set(hl, "LineStyle", "none", "Marker", "none", "HandleVisibility", "off"); - set(hp, "LineStyle", "none", "HandleVisibility", "off"); - end - - scatter(sirValues(valid), meanBer(valid), ... - 20, ... - "Marker", "o", ... - "MarkerEdgeColor", algColor, ... - "MarkerFaceColor", algColor, ... - "LineWidth", 1, ... - "DisplayName", algorithmName); - - if usePolyfit - fitMask = valid & meanBer > 0; - if nnz(fitMask) >= 2 - fitOrder = min(polyfitOrderMax, nnz(fitMask) - 1); - fitCoeff = polyfit(sirValues(fitMask), log10(meanBer(fitMask)), fitOrder); - xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); - yFit = 10 .^ polyval(fitCoeff, xFit); - - plot(xFit, yFit, ... - "LineStyle", "--", ... - "LineWidth", 1.1, ... - "Color", algColor, ... - "HandleVisibility", "off"); - end - end - end - - yline(2.2e-4, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - yline(3.8e-3, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - yline(2e-2, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - ylim([9e-5, 0.1]); - - title(sprintf("PAM %.0f, path %.0f m", pamLevel, pathLengthToPlot)); - xlabel("SIR (dB)"); - ylabel("BER"); - set(gca, "YScale", "log"); - xlim([15,45]); - grid on; - box on; - legend("Location", "best", "Interpreter", "none"); -end - -%% Local helpers - -function berTable = buildBerTable(wh, storageName, pathLength) -rows = struct( ... - "pam_level", {}, ... - "symbolrate", {}, ... - "interference_path_length", {}, ... - "sir", {}, ... - "block_update", {}, ... - "run_ids", {}, ... - "n_run_ids", {}, ... - "ber_values", {}, ... - "n_ber_values", {}); - -for linIdx = 1:numel(wh.sto.(storageName)) - storedValue = wh.sto.(storageName){linIdx}; - if isempty(storedValue) - continue - end - - phys = linearIndexToStruct(wh, linIdx); - if phys.interference_path_length ~= pathLength - continue - end - - berValues = extractBerValues(storedValue); - if isempty(berValues) - continue - end - - runIds = wh.sto.run_id{linIdx}; - if isempty(runIds) - runIds = NaN; - end - - row = struct(); - row.pam_level = phys.pam_level; - row.symbolrate = phys.symbolrate; - row.interference_path_length = phys.interference_path_length; - row.sir = phys.sir; - row.block_update = phys.block_update; - row.run_ids = {runIds}; - row.n_run_ids = numel(runIds); - row.ber_values = {berValues}; - row.n_ber_values = numel(berValues); - - rows(end+1) = row; %#ok -end - -if isempty(rows) - berTable = struct2table(rows); -else - berTable = struct2table(rows); - berTable = sortrows(berTable, ["pam_level", "sir", "symbolrate"]); -end -end - -function physStruct = linearIndexToStruct(wh, linIdx) -[physValues, physNames] = wh.getPhysIndicesByLinIndex(linIdx); -physStruct = struct(); - -for paramIdx = 1:numel(physNames) - physStruct.(char(physNames{paramIdx})) = physValues{paramIdx}; -end -end - -function berValues = extractBerValues(value) -berValues = []; - -if isstruct(value) - if isfield(value, "metrics") && hasBer(value.metrics) - berValues(end+1) = value.metrics.BER; - end -elseif iscell(value) - for valueIdx = 1:numel(value) - berValues = [berValues, extractBerValues(value{valueIdx})]; %#ok - end -end -end - -function tf = hasBer(metrics) -tf = (isstruct(metrics) && isfield(metrics, "BER")) || ... - (isobject(metrics) && isprop(metrics, "BER")); -end diff --git a/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_ber_vs_sir.m b/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_ber_vs_sir.m deleted file mode 100644 index 81daa33..0000000 --- a/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_ber_vs_sir.m +++ /dev/null @@ -1,254 +0,0 @@ -%% BER over SIR from MpiReductionResults -% 1) gather data from the database -% 2) clean data and remove per-curve outliers -% 3) plot BER over SIR grouped by algorithm - -clear; clc; - -%% 1) Gather data - -studyName = "pam4_112_greater_3153"; -pathLengthToPlot = 0; -selectedBlockUpdate = 1; % set [] to pool all block_update values -selectedPamLevels = []; % set [] to use all PAM levels in the query result -selectedAlgorithms = []; % set [] to use all algorithms in the query result - -useBoundedLines = true; -usePolyfit = true; -polyfitOrderMax = 4; -maxBerForPlot = 0.1; - -db = DBHandler( ... - "dataBase", "labor", ... - "type", "mysql", ... - "server", "192.168.178.192", ... - "user", "silas", ... - "password", "silas"); -db.refresh(); - -fp = QueryFilter(); -fp.where('Runs', 'interference_path_length', 'EQUALS', pathLengthToPlot); -fp.where('Runs', 'fiber_length', 'EQUALS', 0); -fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); -fp.where('MpiReductionResults', 'study_name', 'EQUALS', char(studyName)); -if ~isempty(selectedBlockUpdate) - fp.where('MpiReductionResults', 'block_update', 'EQUALS', selectedBlockUpdate); -end - -selectedFields = { ... - 'Runs.run_id', ... - 'Runs.sir', ... - 'Runs.pam_level', ... - 'Runs.symbolrate', ... - 'Runs.interference_path_length', ... - 'Runs.power_mpi_interference', ... - 'MpiReductionResults.occurrence_idx', ... - 'MpiReductionResults.storage_name', ... - 'MpiReductionResults.algorithm', ... - 'MpiReductionResults.algorithm_variant', ... - 'MpiReductionResults.eq_class', ... - 'MpiReductionResults.block_update', ... - 'MpiReductionResults.BER', ... - 'MpiReductionResults.BER_precoded', ... - 'MpiReductionResults.SNR', ... - 'MpiReductionResults.GMI', ... - 'MpiReductionResults.AIR'}; -selectedFields = selectedFields(:); - -[rawData, query] = db.queryDB(fp, selectedFields); -disp(query); -fprintf("Fetched %d MPI reduction rows.\n", height(rawData)); - -%% 2) Clean data - -data = rawData; -numericFields = ["run_id", "sir", "pam_level", "symbolrate", ... - "interference_path_length", "occurrence_idx", "block_update", ... - "power_mpi_interference", "BER", "BER_precoded", "SNR", "GMI", "AIR"]; -for fieldIdx = 1:numel(numericFields) - fieldName = numericFields(fieldIdx); - if ismember(fieldName, string(data.Properties.VariableNames)) - data.(char(fieldName)) = numericColumn(data.(char(fieldName))); - end -end - -stringFields = ["storage_name", "algorithm", "algorithm_variant", "eq_class"]; -for fieldIdx = 1:numel(stringFields) - fieldName = stringFields(fieldIdx); - if ismember(fieldName, string(data.Properties.VariableNames)) - data.(char(fieldName)) = stringColumn(data.(char(fieldName))); - end -end - -data = data(isfinite(data.BER) & data.BER > 0 & data.BER < maxBerForPlot, :); -data.sir_exact = -7 - data.power_mpi_interference; -if isempty(data) - warning("PLOT_mpi_reduction_db_ber_vs_sir:NoRows", ... - "No rows remain after initial BER/path/study/block filtering."); - return -end - -if isempty(selectedPamLevels) - selectedPamLevels = unique(data.pam_level(isfinite(data.pam_level))).'; -else - data = data(ismember(data.pam_level, selectedPamLevels), :); -end - -if isempty(selectedAlgorithms) - selectedAlgorithms = unique(data.algorithm, "stable").'; -else - selectedAlgorithms = string(selectedAlgorithms); - data = data(ismember(data.algorithm, selectedAlgorithms), :); -end - -if isempty(data) - warning("PLOT_mpi_reduction_db_ber_vs_sir:NoSelectedRows", ... - "No rows remain after selectedPamLevels/selectedAlgorithms filtering."); - return -end - -data.clean_keep = true(height(data), 1); -[groupId, groupPam, groupAlgorithm, groupSir] = findgroups(data.pam_level, data.algorithm, data.sir); -for groupIdx = 1:max(groupId) - rowMask = groupId == groupIdx; - berValues = data.BER(rowMask); - if nnz(rowMask) > 3 - data.clean_keep(rowMask) = ~isoutlier(berValues); - end -end -cleanData = data(data.clean_keep, :); - -summaryTable = groupsummary(cleanData, ["pam_level", "algorithm", "sir"], ... - {"mean", "min", "max"}, "BER"); -sirExactTable = groupsummary(cleanData, ["pam_level", "algorithm", "sir"], ... - "median", "sir_exact"); -summaryTable.sir_exact = sirExactTable.median_sir_exact; -summaryTable = sortrows(summaryTable, ["pam_level", "algorithm", "sir"]); - -fprintf("Cleaned to %d rows across %d PAM/algorithm/SIR groups.\n", ... - height(cleanData), height(summaryTable)); -disp(groupcounts(cleanData, ["pam_level", "algorithm"])); - -%% 3) Plot data - -if exist("linspecer", "file") - algColors = linspecer(numel(selectedAlgorithms)); -else - algColors = lines(numel(selectedAlgorithms)); -end - -figure(); clf; -tiledlayout(numel(selectedPamLevels), 1, "TileSpacing", "compact"); - -for pamIdx = 1:numel(selectedPamLevels) - pamLevel = selectedPamLevels(pamIdx); - nexttile; hold on; - - for algIdx = 1:numel(selectedAlgorithms) - algorithmName = selectedAlgorithms(algIdx); - algColor = algColors(algIdx, :); - rawMask = cleanData.pam_level == pamLevel & cleanData.algorithm == algorithmName; - curveMask = summaryTable.pam_level == pamLevel & summaryTable.algorithm == algorithmName; - - if ~any(curveMask) - continue - end - scatter(cleanData.sir_exact(rawMask), cleanData.BER(rawMask), ... - 30, ... - "Marker", ".", ... - "MarkerEdgeColor", algColor, ... - "HandleVisibility", "off"); - - sirValues = summaryTable.sir_exact(curveMask).'; - meanBer = summaryTable.mean_BER(curveMask).'; - minBer = summaryTable.min_BER(curveMask).'; - maxBer = summaryTable.max_BER(curveMask).'; - valid = isfinite(sirValues) & isfinite(meanBer) & meanBer > 0; - - if useBoundedLines && exist("boundedline", "file") && any(valid) - yLower = max(meanBer - minBer, 0); - yUpper = max(maxBer - meanBer, 0); - yBounds = [yLower(:), yUpper(:)]; - - [hl, hp] = boundedline(sirValues(valid).', meanBer(valid).', yBounds(valid, :), ... - 'alpha', 'transparency', 0.08, ... - 'cmap', algColor, ... - 'nan', 'fill', ... - 'orientation', 'vert'); - set(hl, "LineStyle", "none", "Marker", "none", "HandleVisibility", "off"); - set(hp, "LineStyle", "none", "HandleVisibility", "off"); - end - - plot(sirValues(valid), meanBer(valid), ... - "LineStyle", "none", ... - "Marker", "o", ... - "MarkerSize", 5, ... - "LineWidth", 1.2, ... - "Color", algColor, ... - "MarkerFaceColor", algColor, ... - "DisplayName", char(algorithmName)); - - if usePolyfit - fitMask = valid & meanBer > 0; - if nnz(fitMask) >= 2 - fitOrder = min(polyfitOrderMax, nnz(fitMask) - 1); - fitCoeff = polyfit(sirValues(fitMask), log10(meanBer(fitMask)), fitOrder); - xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); - yFit = 10 .^ polyval(fitCoeff, xFit); - - plot(xFit, yFit, ... - "LineStyle", "--", ... - "LineWidth", 1.1, ... - "Color", algColor, ... - "HandleVisibility", "off"); - end - end - end - - yline(2.2e-4, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); - yline(3.8e-3, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); - yline(2e-2, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); - - title(sprintf("PAM %.0f, path %.0f m, block update %s", ... - pamLevel, pathLengthToPlot, blockUpdateLabel(selectedBlockUpdate))); - xlabel("SIR (dB)"); - ylabel("BER"); - set(gca, "YScale", "log"); - ylim([9e-5, maxBerForPlot]); - grid on; - box on; - legend("Location", "best", "Interpreter", "none"); -end - -if exist("beautifyBERplot", "file") - beautifyBERplot("logscale", true, "setcolors", false, "setmarkers", false); -end - -%% Local helpers - -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 = stringColumn(values) - if iscell(values) - values = string(values); - elseif ischar(values) - values = string(values); - end - values = strip(string(values)); -end - -function label = blockUpdateLabel(selectedBlockUpdate) - if isempty(selectedBlockUpdate) - label = "pooled"; - else - label = string(selectedBlockUpdate); - end -end diff --git a/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes.m b/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes.m new file mode 100644 index 0000000..dfb99a3 --- /dev/null +++ b/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes.m @@ -0,0 +1,534 @@ +%% BER over SIR by MPI delay/coherence regime from MpiReductionResults +% 1) gather data from the database +% 2) clean data and assign path-length regimes +% 3) plot one BER-over-SIR figure per regime grouped by algorithm + +clear; clc; + +%% 1) Gather data + +studyName = "block_update_sweep"; +selectedBlockUpdate = 1; % set [] to pool all block_update values +selectedPamLevels = 6; % set [] to use all PAM levels in the query result + +algorithmSelection = table( ... + ["plain_ffe"; ... + "a2_tracked_levels"; ... + "a2_residual"; ... + "a1_moving_average"; ... + "dc_tracking"], ... + [true; true; true; true; true], ... + 'VariableNames', ["algorithm", "enabled"]); +selectedAlgorithms = algorithmSelection.algorithm(algorithmSelection.enabled); + +useBoundedLines = true; +usePolyfit = true; +polyfitOrderMax = 4; +boundaryPolyfitOrderMax = 4; +maxBerForPlot = 0.1; +scatterKeepFraction = 0.2; +boundMode = "fitStd"; % "fitStd", "directStd", "fitCI", or "directCI" +confidenceLevel = 0.95; +regimeNames = ["0-1 m", "10-100 m", "300 m", "1000 m"]; +algorithmMarkers = {'o','square','diamond','^','v','>','<','pentagram'}; + +db = DBHandler( ... + "dataBase", "labor", ... + "type", "mysql", ... + "server", "192.168.178.192", ... + "user", "silas", ... + "password", "silas"); +db.refresh(); + +fp = QueryFilter(); +% fp.where('Runs','run_id','GREATER_EQUAL',3153); +fp.where('Runs', 'fiber_length', 'EQUALS', 0); +fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); +fp.where('Runs', 'v_bias', 'EQUALS', 2.65); +% fp.where('MpiReductionResults', 'study_name', 'EQUALS', char(studyName)); +if ~isempty(selectedBlockUpdate) + fp.where('MpiReductionResults', 'block_update', 'EQUALS', selectedBlockUpdate); +end +if isscalar(selectedPamLevels) && ~isempty(selectedPamLevels) + fp.where('Runs', 'pam_level', 'EQUALS', selectedPamLevels); +end + +selectedFields = { ... + 'Runs.run_id', ... + 'Runs.sir', ... + 'Runs.pam_level', ... + 'Runs.symbolrate', ... + 'Runs.interference_path_length', ... + 'Runs.power_mpi_interference', ... + 'Runs.power_pd_in', ... + 'Runs.v_bias', ... + 'Runs.loop_id', ... + 'MpiReductionResults.occurrence_idx', ... + 'MpiReductionResults.storage_name', ... + 'MpiReductionResults.algorithm', ... + 'MpiReductionResults.algorithm_variant', ... + 'MpiReductionResults.eq_class', ... + 'MpiReductionResults.block_update', ... + 'MpiReductionResults.BER', ... + 'MpiReductionResults.BER_precoded', ... + 'MpiReductionResults.SNR', ... + 'MpiReductionResults.GMI', ... + 'MpiReductionResults.AIR'}; +selectedFields = selectedFields(:); + +[rawData, query] = db.queryDB(fp, selectedFields); +disp(query); +fprintf("Fetched %d MPI reduction rows.\n", height(rawData)); + +%% 2) Clean data and assign regimes + +data = rawData; +numericFields = ["run_id", "sir", "pam_level", "symbolrate", ... + "interference_path_length", "occurrence_idx", "block_update", ... + "power_mpi_interference", "BER", "BER_precoded", "SNR", "GMI", "AIR"]; +for fieldIdx = 1:numel(numericFields) + fieldName = numericFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = numericColumn(data.(char(fieldName))); + end +end + +stringFields = ["storage_name", "algorithm", "algorithm_variant", "eq_class"]; +for fieldIdx = 1:numel(stringFields) + fieldName = stringFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = stringColumn(data.(char(fieldName))); + end +end + +% data = data(data.run_id>3153,:); +data(data.run_id == 3866, :) = []; +data(data.run_id == 3865, :) = []; +data(data.run_id == 3796, :) = []; +data(data.run_id == 3797, :) = []; +data(data.run_id == 3798, :) = []; +data(data.run_id == 4002, :) = []; +data(data.run_id == 4200, :) = []; +data(data.run_id == 4199, :) = []; +data(data.loop_id == 91, :) = []; +data(data.loop_id == 59, :) = []; + +data = data(isfinite(data.BER) & data.BER > 0 & data.BER < maxBerForPlot, :); +data.sir_exact = -7 - data.power_mpi_interference; + +data.path_regime = strings(height(data), 1); +data.path_regime(data.interference_path_length == 0 | ... + data.interference_path_length == 1) = "0-1 m"; +data.path_regime(data.interference_path_length >= 10 & ... + data.interference_path_length <= 100) = "10-100 m"; +data.path_regime(data.interference_path_length == 300) = "300 m"; +data.path_regime(data.interference_path_length == 1000) = "1000 m"; +data = data(data.path_regime ~= "", :); + +if isempty(data) + warning("PLOT_mpi_reduction_db_delay_regimes:NoRows", ... + "No rows remain after BER/study/block/regime filtering."); + return +end + +if isempty(selectedPamLevels) + selectedPamLevels = unique(data.pam_level(isfinite(data.pam_level))).'; +else + data = data(ismember(data.pam_level, selectedPamLevels), :); +end + +data = data(ismember(data.algorithm, selectedAlgorithms), :); +selectedAlgorithms = selectedAlgorithms(ismember(selectedAlgorithms, unique(data.algorithm, "stable"))); + +if isempty(data) + warning("PLOT_mpi_reduction_db_delay_regimes:NoSelectedRows", ... + "No rows remain after selectedPamLevels/selectedAlgorithms filtering."); + return +end + +data.clean_keep = true(height(data), 1); +groupId = findgroups(data.path_regime, data.pam_level, data.algorithm, data.sir); +for curGroup = unique(groupId(isfinite(groupId))).' + rowMask = groupId == curGroup; + berValues = data.BER(rowMask); + if nnz(rowMask) > 3 + data.clean_keep(rowMask) = ~isoutlier(berValues); + end +end +cleanData = data(data.clean_keep, :); + +groupVars = ["path_regime", "pam_level", "algorithm", "sir"]; +summaryTable = groupsummary(cleanData, groupVars, ... + {"mean", "min", "max"}, "BER"); +sirExactTable = groupsummary(cleanData, groupVars, "median", "sir_exact"); +summaryTable = sortrows(summaryTable, groupVars); +sirExactTable = sortrows(sirExactTable, groupVars); +summaryTable.sir_exact = sirExactTable.median_sir_exact; +summaryTable = addBerIntervalBounds(summaryTable, cleanData, groupVars, confidenceLevel); + +fprintf("Cleaned to %d rows across %d regime/PAM/algorithm/SIR groups.\n", ... + height(cleanData), height(summaryTable)); +disp(groupcounts(cleanData, ["path_regime", "pam_level", "algorithm"])); + +%% 3) Plot one BER-over-SIR figure per delay/coherence regime + +for regimeIdx = 1:numel(regimeNames) + regimeName = regimeNames(regimeIdx); + regimeDataMask = cleanData.path_regime == regimeName; + if ~any(regimeDataMask) + fprintf("Skipping regime %s: no rows.\n", regimeName); + continue + end + + figure(); clf; + tiledlayout(numel(selectedPamLevels), 1, "TileSpacing", "compact"); + + for pamIdx = 1:numel(selectedPamLevels) + pamLevel = selectedPamLevels(pamIdx); + nexttile; hold on; + + for algIdx = 1:numel(selectedAlgorithms) + algorithmName = selectedAlgorithms(algIdx); + algColor = algorithmColor(algorithmName); + marker = algorithmMarker(algorithmName, algorithmMarkers); + displayName = algorithmDisplayName(algorithmName); + + rawMask = cleanData.path_regime == regimeName & ... + cleanData.pam_level == pamLevel & ... + cleanData.algorithm == algorithmName; + curveMask = summaryTable.path_regime == regimeName & ... + summaryTable.pam_level == pamLevel & ... + summaryTable.algorithm == algorithmName; + + if ~any(curveMask) + continue + end + + if regimeIdx == 2 || regimeIdx == 1 + scatterKeepFraction = 0.2; + else + scatterKeepFraction = 1; + end + + scatterRows = downsampleRows(cleanData(rawMask, :), scatterKeepFraction); + scatter(scatterRows.sir_exact, scatterRows.BER, ... + 26, ... + "Marker", ".", ... + "MarkerEdgeColor", algColor, ... + "MarkerFaceColor", algColor, ... + "HandleVisibility", "off"); + + sirValues = summaryTable.sir_exact(curveMask).'; + meanBer = summaryTable.mean_BER(curveMask).'; + [boundCenterBer, boundLowerBer, boundUpperBer] = ... + selectBerBounds(summaryTable(curveMask, :), boundMode); + valid = isfinite(sirValues) & isfinite(meanBer) & meanBer > 0; + + if useBoundedLines && exist("boundedline", "file") && any(valid) + [xBand, centerBand, yBounds] = berIntervalBounds( ... + sirValues, boundCenterBer, boundLowerBer, boundUpperBer, ... + boundMode, boundaryPolyfitOrderMax); + + [hl, hp] = boundedline(xBand, centerBand, yBounds, ... + 'alpha', 'transparency', 0.18, ... + 'cmap', algColor, ... + 'nan', 'fill', ... + 'orientation', 'vert'); + set(hl, "LineStyle", "-",'LineWidth',1, "Marker", "none", "HandleVisibility", "on","DisplayName", char(displayName)); + set(hp, "LineStyle", "-", "HandleVisibility", "off","Marker", "none"); + end + + plot(sirValues(valid), meanBer(valid), ... + "LineStyle", "none", ... + "Marker", marker, ... + "MarkerSize", 3, ... + "LineWidth", 1, ... + "Color", algColor, ... + "MarkerFaceColor", "w", ... + "MarkerEdgeColor", algColor, ... + "DisplayName", char(displayName),'HandleVisibility','off'); + + if usePolyfit + fitMask = valid & meanBer > 0; + if nnz(fitMask) >= 2 + fitOrder = min(polyfitOrderMax, nnz(fitMask) - 1); + fitCoeff = polyfit(sirValues(fitMask), log10(meanBer(fitMask)), fitOrder); + xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); + yFit = 10 .^ polyval(fitCoeff, xFit); + + % plot(xFit, yFit, ... + % "LineStyle", "--", ... + % "LineWidth", 1.1, ... + % "Color", algColor, ... + % "HandleVisibility", "off"); + end + end + end + + yline(2.2e-4, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(3.8e-3, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(2e-2, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + + title(sprintf("PAM %.0f, interference path regime %s", pamLevel, regimeName)); + xlabel("SIR (dB)"); + ylabel("BER"); + set(gca, "YScale", "log"); + ylim([9e-5, maxBerForPlot]); + xlim([15, 45]); + grid on; + box on; + legend("Location", "northeast", "Interpreter", "none"); + + if exist("beautifyBERplot", "file") + beautifyBERplot("logscale", true, "setcolors", false, "setmarkers", false); + end + end + % mat2tikz_improved("C:/Users/Silas/Documents/6971e0b65b380ca6d71c837f/04_Experimental_Evaluation/tikz/mpi/ber_vs_sir_" + regimeName + ".tikz","cleanfigure",1); +end + +%% Local helpers + +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 = stringColumn(values) + if iscell(values) + values = string(values); + elseif ischar(values) + values = string(values); + end + values = strip(string(values)); +end + +function rows = downsampleRows(rows, keepFraction) + if keepFraction >= 1 || height(rows) <= 1 + return + end + + keepEvery = max(1, round(1 / keepFraction)); + keepIdx = 1:keepEvery:height(rows); + rows = rows(keepIdx, :); +end + +function summaryTable = addBerIntervalBounds(summaryTable, cleanData, groupVars, confidenceLevel) + nGroups = height(summaryTable); + ciCenter = NaN(nGroups, 1); + ciLower = NaN(nGroups, 1); + ciUpper = NaN(nGroups, 1); + stdCenter = NaN(nGroups, 1); + stdLower = NaN(nGroups, 1); + stdUpper = NaN(nGroups, 1); + + for groupIdx = 1:nGroups + rowMask = true(height(cleanData), 1); + for varIdx = 1:numel(groupVars) + varName = groupVars(varIdx); + rowMask = rowMask & cleanData.(char(varName)) == summaryTable.(char(varName))(groupIdx); + end + + [ciCenter(groupIdx), ciLower(groupIdx), ciUpper(groupIdx)] = ... + logBerMeanConfidenceInterval(cleanData.BER(rowMask), confidenceLevel); + [stdCenter(groupIdx), stdLower(groupIdx), stdUpper(groupIdx)] = ... + logBerMeanStdInterval(cleanData.BER(rowMask)); + end + + summaryTable.ci_center_BER = ciCenter; + summaryTable.ci_lower_BER = ciLower; + summaryTable.ci_upper_BER = ciUpper; + summaryTable.std_center_BER = stdCenter; + summaryTable.std_lower_BER = stdLower; + summaryTable.std_upper_BER = stdUpper; +end + +function [centerBer, lowerBer, upperBer] = logBerMeanConfidenceInterval(berValues, confidenceLevel) + berValues = berValues(isfinite(berValues) & berValues > 0); + if isempty(berValues) + centerBer = NaN; + lowerBer = NaN; + upperBer = NaN; + return + end + + logBer = log10(berValues(:)); + centerLog = mean(logBer, "omitnan"); + + if numel(logBer) < 2 + centerBer = 10 .^ centerLog; + lowerBer = centerBer; + upperBer = centerBer; + return + end + + alpha = 1 - confidenceLevel; + if exist("tinv", "file") + tCritical = tinv(1 - alpha/2, numel(logBer) - 1); + else + tCritical = 1.96; + end + + halfWidth = tCritical * std(logBer, 0, "omitnan") / sqrt(numel(logBer)); + centerBer = 10 .^ centerLog; + lowerBer = 10 .^ (centerLog - halfWidth); + upperBer = 10 .^ (centerLog + halfWidth); +end + +function [centerBer, lowerBer, upperBer] = logBerMeanStdInterval(berValues) + berValues = berValues(isfinite(berValues) & berValues > 0); + if isempty(berValues) + centerBer = NaN; + lowerBer = NaN; + upperBer = NaN; + return + end + + logBer = log10(berValues(:)); + centerLog = mean(logBer, "omitnan"); + stdLog = std(logBer, 0, "omitnan"); + + centerBer = 10 .^ centerLog; + lowerBer = 10 .^ (centerLog - stdLog); + upperBer = 10 .^ (centerLog + stdLog); +end + +function [centerBer, lowerBer, upperBer] = selectBerBounds(curveSummary, boundMode) + if boundMode == "fitCI" || boundMode == "directCI" + centerBer = curveSummary.ci_center_BER.'; + lowerBer = curveSummary.ci_lower_BER.'; + upperBer = curveSummary.ci_upper_BER.'; + else + centerBer = curveSummary.std_center_BER.'; + lowerBer = curveSummary.std_lower_BER.'; + upperBer = curveSummary.std_upper_BER.'; + end +end + +function [xBand, centerBand, yBounds] = berIntervalBounds(sirValues, centerBer, lowerBer, upperBer, boundMode, maxOrder) + if boundMode == "directCI" || boundMode == "directStd" + [xBand, centerBand, yBounds] = directBerBounds(sirValues, centerBer, lowerBer, upperBer); + return + end + + [xBand, centerBand, yBounds] = fittedBerBounds(sirValues, centerBer, lowerBer, upperBer, maxOrder); +end + +function [xBand, centerBand, yBounds] = directBerBounds(sirValues, centerBer, lowerBer, upperBer) + valid = isfinite(sirValues) & isfinite(centerBer) & isfinite(lowerBer) & ... + isfinite(upperBer) & centerBer > 0 & lowerBer > 0 & upperBer > 0; + + xBand = sirValues(valid).'; + centerBand = centerBer(valid).'; + lowerBand = lowerBer(valid).'; + upperBand = upperBer(valid).'; + + [xBand, orderIdx] = sort(xBand(:)); + centerBand = centerBand(orderIdx); + lowerBand = lowerBand(orderIdx); + upperBand = upperBand(orderIdx); + + lowerTmp = min(lowerBand, upperBand); + upperBand = max(lowerBand, upperBand); + lowerBand = lowerTmp; + + centerBand = min(max(centerBand, lowerBand), upperBand); + yBounds = [max(centerBand - lowerBand, 0), max(upperBand - centerBand, 0)]; +end + +function [xBand, centerBand, yBounds] = fittedBerBounds(sirValues, meanBer, minBer, maxBer, maxOrder) + valid = isfinite(sirValues) & isfinite(meanBer) & isfinite(minBer) & ... + isfinite(maxBer) & meanBer > 0 & minBer > 0 & maxBer > 0; + + x = sirValues(valid); + yMean = meanBer(valid); + yMin = minBer(valid); + yMax = maxBer(valid); + + if numel(x) < 2 + xBand = x(:); + centerBand = yMean(:); + yBounds = [max(yMean(:) - yMin(:), 0), max(yMax(:) - yMean(:), 0)]; + return + end + + [x, orderIdx] = sort(x(:)); + yMean = yMean(orderIdx); + yMin = yMin(orderIdx); + yMax = yMax(orderIdx); + + xBand = linspace(min(x), max(x), 300).'; + fitOrder = min(maxOrder, numel(unique(x)) - 1); + + if fitOrder < 1 + centerBand = interp1(x, yMean, xBand, "linear", "extrap"); + lowerBand = interp1(x, yMin, xBand, "linear", "extrap"); + upperBand = interp1(x, yMax, xBand, "linear", "extrap"); + else + centerBand = fitLogBer(x, yMean, xBand, fitOrder); + lowerBand = fitLogBer(x, yMin, xBand, fitOrder); + upperBand = fitLogBer(x, yMax, xBand, fitOrder); + end + + lowerTmp = min(lowerBand, upperBand); + upperBand = max(lowerBand, upperBand); + lowerBand = lowerTmp; + + centerBand = min(max(centerBand, lowerBand), upperBand); + yBounds = [max(centerBand - lowerBand, 0), max(upperBand - centerBand, 0)]; +end + +function yFit = fitLogBer(x, y, xFit, fitOrder) + coeff = polyfit(x, log10(y), fitOrder); + yFit = 10 .^ polyval(coeff, xFit); +end + +function label = algorithmDisplayName(algorithmName) + algorithmName = string(algorithmName); + switch algorithmName + case "plain_ffe" + label = "FFE only"; + case "a2_tracked_levels" + label = "ACT"; + case "a2_residual" + label = "L-DCA"; + case "a1_moving_average" + label = "DCA"; + case "dc_tracking" + label = "DCT"; + otherwise + label = algorithmName; + end +end + +function color = algorithmColor(algorithmName) + algorithmName = string(algorithmName); + switch algorithmName + case "plain_ffe" + color = [0.3467 0.5360 0.6907]; + case "a2_tracked_levels" + color = [0.9153 0.2816 0.2878]; + case "a2_residual" + color = [0.4416 0.7490 0.4322]; + case "a1_moving_average" + color = [1.0000 0.5984 0.2000]; + case "dc_tracking" + color = [0.6769 0.4447 0.7114]; + otherwise + color = [0 0 0]; + end +end + +function marker = algorithmMarker(algorithmName, algorithmMarkers) + algorithmOrder = ["plain_ffe", "a2_tracked_levels", "a2_residual", ... + "a1_moving_average", "dc_tracking"]; + markerIdx = find(algorithmOrder == string(algorithmName), 1); + if isempty(markerIdx) + markerIdx = 1; + end + marker = algorithmMarkers{mod(markerIdx - 1, numel(algorithmMarkers)) + 1}; +end diff --git a/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes_v_bias.m b/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes_v_bias.m new file mode 100644 index 0000000..eafd83e --- /dev/null +++ b/projects/Diss/MPI_revisit/algorithms/PLOT_mpi_reduction_db_delay_regimes_v_bias.m @@ -0,0 +1,346 @@ +%% BER over SIR by MPI delay/coherence regime and loop id +% 1) gather dc_tracking data from the database +% 2) clean data and assign path-length regimes +% 3) plot one BER-over-SIR figure per regime grouped by loop_id + +clear; clc; + +%% 1) Gather data + +studyName = "pam4_112_greater_3153"; +selectedBlockUpdate = 1; % set [] to pool all block_update values +selectedPamLevels = 4; % set [] to use all PAM levels in the query result +selectedAlgorithm = "plain_ffe"; + +useBoundedLines = defaultUseBoundedLines(); +usePolyfit = true; +polyfitOrderMax = 4; +maxBerForPlot = 0.1; +regimeNames = ["0-1 m", "10-100 m", "300 m", "1000 m"]; +loopMarkers = {'o','square','diamond','^','v','>','<','pentagram'}; + +db = DBHandler( ... + "dataBase", "labor", ... + "type", "mysql", ... + "server", "192.168.178.192", ... + "user", "silas", ... + "password", "silas"); +db.refresh(); + +fp = QueryFilter(); +fp.where('Runs', 'fiber_length', 'EQUALS', 0); +fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); +fp.where('Runs', 'v_bias', 'EQUALS', 2.65); +fp.where('MpiReductionResults', 'study_name', 'EQUALS', char(studyName)); +fp.where('MpiReductionResults', 'algorithm', 'EQUALS', char(selectedAlgorithm)); +if ~isempty(selectedBlockUpdate) + fp.where('MpiReductionResults', 'block_update', 'EQUALS', selectedBlockUpdate); +end +if isscalar(selectedPamLevels) && ~isempty(selectedPamLevels) + fp.where('Runs', 'pam_level', 'EQUALS', selectedPamLevels); +end + +selectedFields = { ... + 'Runs.run_id', ... + 'Runs.sir', ... + 'Runs.pam_level', ... + 'Runs.symbolrate', ... + 'Runs.interference_path_length', ... + 'Runs.power_mpi_interference', ... + 'Runs.power_pd_in', ... + 'Runs.v_bias', ... + 'Runs.loop_id', ... + 'MpiReductionResults.occurrence_idx', ... + 'MpiReductionResults.storage_name', ... + 'MpiReductionResults.algorithm', ... + 'MpiReductionResults.algorithm_variant', ... + 'MpiReductionResults.eq_class', ... + 'MpiReductionResults.block_update', ... + 'MpiReductionResults.BER', ... + 'MpiReductionResults.BER_precoded', ... + 'MpiReductionResults.SNR', ... + 'MpiReductionResults.GMI', ... + 'MpiReductionResults.AIR'}; +selectedFields = selectedFields(:); + +[rawData, query] = db.queryDB(fp, selectedFields); +disp(query); +fprintf("Fetched %d %s MPI reduction rows.\n", ... + height(rawData), algorithmDisplayName(selectedAlgorithm)); + +%% 2) Clean data and assign regimes + +data = rawData; +numericFields = ["run_id", "sir", "pam_level", "symbolrate", ... + "interference_path_length", "occurrence_idx", "block_update", ... + "power_mpi_interference", "power_pd_in", "v_bias", "loop_id", "BER", "BER_precoded", ... + "SNR", "GMI", "AIR"]; +for fieldIdx = 1:numel(numericFields) + fieldName = numericFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = numericColumn(data.(char(fieldName))); + end +end + +stringFields = ["storage_name", "algorithm", "algorithm_variant", "eq_class"]; +for fieldIdx = 1:numel(stringFields) + fieldName = stringFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = stringColumn(data.(char(fieldName))); + end +end + +data(data.run_id == 3866, :) = []; +data(data.run_id == 3865, :) = []; +data(data.run_id == 3796, :) = []; +data(data.run_id == 3797, :) = []; +data(data.run_id == 3798, :) = []; +data(data.run_id == 4002, :) = []; +data(data.run_id == 4200, :) = []; +data(data.run_id == 4199, :) = []; +data(data.loop_id == 91, :) = []; + + +data = data(isfinite(data.BER) & data.BER > 0 & data.BER < maxBerForPlot, :); +data = data(isfinite(data.v_bias), :); +data.sir_exact = -7 - data.power_mpi_interference; + +data.path_regime = strings(height(data), 1); +data.path_regime(data.interference_path_length == 0 | ... + data.interference_path_length == 1) = "0-1 m"; +data.path_regime(data.interference_path_length >= 10 & ... + data.interference_path_length <= 100) = "10-100 m"; +data.path_regime(data.interference_path_length == 300) = "300 m"; +data.path_regime(data.interference_path_length == 1000) = "1000 m"; +data = data(data.path_regime ~= "", :); + +if isempty(data) + warning("PLOT_mpi_reduction_db_delay_regimes_v_bias:NoRows", ... + "No rows remain after BER/study/block/regime/v_bias filtering."); + return +end + +if isempty(selectedPamLevels) + selectedPamLevels = unique(data.pam_level(isfinite(data.pam_level))).'; +else + data = data(ismember(data.pam_level, selectedPamLevels), :); +end + +if isempty(data) + warning("PLOT_mpi_reduction_db_delay_regimes_v_bias:NoSelectedRows", ... + "No rows remain after selectedPamLevels filtering."); + return +end + +data.loop_id_group = round(data.loop_id); +selectedLoopIds = unique(data.loop_id_group(isfinite(data.loop_id_group))).'; + +data.clean_keep = true(height(data), 1); +groupId = findgroups(data.path_regime, data.pam_level, data.loop_id_group, data.sir); +for curGroup = unique(groupId(isfinite(groupId))).' + rowMask = groupId == curGroup; + berValues = data.BER(rowMask); + if nnz(rowMask) > 3 + data.clean_keep(rowMask) = ~isoutlier(berValues); + end +end +cleanData = data(data.clean_keep, :); + +groupVars = ["path_regime", "pam_level", "loop_id_group", "sir"]; +summaryTable = groupsummary(cleanData, groupVars, ... + {"mean", "min", "max"}, "BER"); +sirExactTable = groupsummary(cleanData, groupVars, "mean", "sir_exact"); +biasTable = groupsummary(cleanData, groupVars, "median", "v_bias"); +loopTable = groupsummary(cleanData, groupVars, "median", "loop_id"); +pathLengthTable = groupsummary(cleanData, groupVars, "median", "interference_path_length"); +summaryTable = sortrows(summaryTable, groupVars); +sirExactTable = sortrows(sirExactTable, groupVars); +biasTable = sortrows(biasTable, groupVars); +loopTable = sortrows(loopTable, groupVars); +pathLengthTable = sortrows(pathLengthTable, groupVars); +summaryTable.sir_exact = sirExactTable.mean_sir_exact; +summaryTable.v_bias = biasTable.median_v_bias; +summaryTable.loop_id = loopTable.median_loop_id; +summaryTable.interference_path_length = pathLengthTable.median_interference_path_length; + +fprintf("Cleaned to %d rows across %d regime/PAM/loop_id/SIR groups.\n", ... + height(cleanData), height(summaryTable)); +disp(groupcounts(cleanData, ["path_regime", "pam_level", "loop_id_group"])); + + + +%% 3) Plot one BER-over-SIR figure per delay/coherence regime + +if exist("linspecer", "file") + loopColors = linspecer(numel(selectedLoopIds)); +else + loopColors = lines(numel(selectedLoopIds)); +end + +for regimeIdx = 1:numel(regimeNames) + regimeName = regimeNames(regimeIdx); + regimeDataMask = cleanData.path_regime == regimeName; + if ~any(regimeDataMask) + fprintf("Skipping regime %s: no rows.\n", regimeName); + continue + end + + figure(); clf; + tiledlayout(numel(selectedPamLevels), 1, "TileSpacing", "compact"); + + for pamIdx = 1:numel(selectedPamLevels) + pamLevel = selectedPamLevels(pamIdx); + nexttile; hold on; + + for loopIdx = 1:numel(selectedLoopIds) + loopId = selectedLoopIds(loopIdx); + loopColor = loopColors(loopIdx, :); + marker = loopMarkers{mod(loopIdx - 1, numel(loopMarkers)) + 1}; + + rawMask = cleanData.path_regime == regimeName & ... + cleanData.pam_level == pamLevel & ... + cleanData.loop_id_group == loopId; + curveMask = summaryTable.path_regime == regimeName & ... + summaryTable.pam_level == pamLevel & ... + summaryTable.loop_id_group == loopId; + + if ~any(curveMask) + continue + end + + rawRows = cleanData(rawMask, :); + hScatter = scatter(rawRows.sir_exact, rawRows.BER, ... + 26, ... + "Marker", ".", ... + "MarkerEdgeColor", loopColor, ... + "HandleVisibility", "off"); + addRawDataTips(hScatter, rawRows); + + sirValues = summaryTable.sir_exact(curveMask).'; + meanBer = summaryTable.mean_BER(curveMask).'; + minBer = summaryTable.min_BER(curveMask).'; + maxBer = summaryTable.max_BER(curveMask).'; + curveLoopIds = summaryTable.loop_id(curveMask).'; + curvePathLengths = summaryTable.interference_path_length(curveMask).'; + valid = isfinite(sirValues) & isfinite(meanBer) & meanBer > 0; + + if useBoundedLines && exist("boundedline", "file") && any(valid) + yLower = max(meanBer - minBer, 0); + yUpper = max(maxBer - meanBer, 0); + yBounds = [yLower(:), yUpper(:)]; + + [hl, hp] = boundedline(sirValues(valid).', meanBer(valid).', yBounds(valid, :), ... + 'alpha', 'transparency', 0.08, ... + 'cmap', loopColor, ... + 'nan', 'fill', ... + 'orientation', 'vert'); + set(hl, "LineStyle", "none", "Marker", "none", "HandleVisibility", "off"); + set(hp, "LineStyle", "none", "HandleVisibility", "off"); + end + + plot(sirValues(valid), meanBer(valid), ... + "LineStyle", "-", ... + "Marker", marker, ... + "MarkerSize", 5, ... + "LineWidth", 1.2, ... + "Color", loopColor, ... + "MarkerFaceColor", "w", ... + "MarkerEdgeColor", loopColor, ... + "DisplayName", sprintf("loop %.0f, %.0f m", ... + median(curveLoopIds, "omitnan"), ... + median(curvePathLengths, "omitnan"))); + + if usePolyfit + fitMask = valid & meanBer > 0; + if nnz(fitMask) >= 2 + fitOrder = min(polyfitOrderMax, nnz(fitMask) - 1); + fitCoeff = polyfit(sirValues(fitMask), log10(meanBer(fitMask)), fitOrder); + xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); + yFit = 10 .^ polyval(fitCoeff, xFit); + + plot(xFit, yFit, ... + "LineStyle", "--", ... + "LineWidth", 1.1, ... + "Color", loopColor, ... + "HandleVisibility", "off"); + end + end + end + + yline(2.2e-4, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(3.8e-3, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(2e-2, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + + title(sprintf("PAM %.0f, %s, %s", ... + pamLevel, regimeName, algorithmDisplayName(selectedAlgorithm))); + xlabel("SIR (dB)"); + ylabel("BER"); + set(gca, "YScale", "log"); + ylim([9e-5, maxBerForPlot]); + xlim([15, 45]); + grid on; + box on; + legend("Location", "best", "Interpreter", "tex"); + + if exist("beautifyBERplot", "file") + beautifyBERplot("logscale", true, "setcolors", false, "setmarkers", false); + end + end +end + +%% Local helpers + +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 = stringColumn(values) + if iscell(values) + values = string(values); + elseif ischar(values) + values = string(values); + end + values = strip(string(values)); +end + +function tf = defaultUseBoundedLines() + tf = false; +end + +function label = algorithmDisplayName(algorithmName) + algorithmName = string(algorithmName); + switch algorithmName + case "plain_ffe" + label = "FFE only"; + case "a2_tracked_levels" + label = "ACT"; + case "a2_residual" + label = "L-DCA"; + case "a1_moving_average" + label = "DCA"; + case "dc_tracking" + label = "DCT"; + otherwise + label = algorithmName; + end +end + +function addRawDataTips(hScatter, rawRows) + hScatter.DataTipTemplate.DataTipRows(1).Label = "SIR"; + hScatter.DataTipTemplate.DataTipRows(1).Format = "%.2f dB"; + hScatter.DataTipTemplate.DataTipRows(2).Label = "BER"; + hScatter.DataTipTemplate.DataTipRows(2).Format = "%.2e"; + hScatter.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("loop", rawRows.loop_id); + hScatter.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("run_id", rawRows.run_id); + hScatter.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("P_{rx}", rawRows.power_pd_in); + hScatter.DataTipTemplate.DataTipRows(end).Format = "%.2f dBm"; + hScatter.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("delay", rawRows.interference_path_length); + hScatter.DataTipTemplate.FontSize = 9; + hScatter.DataTipTemplate.FontName = "arial"; +end diff --git a/projects/Diss/MPI_revisit/algorithms/ffe_baseline.fig b/projects/Diss/MPI_revisit/algorithms/ffe_baseline.fig deleted file mode 100644 index b0f2e06..0000000 Binary files a/projects/Diss/MPI_revisit/algorithms/ffe_baseline.fig and /dev/null differ diff --git a/projects/Diss/MPI_revisit/algorithms/recombine_warehouses.m b/projects/Diss/MPI_revisit/algorithms/recombine_warehouses.m deleted file mode 100644 index 85b13b0..0000000 --- a/projects/Diss/MPI_revisit/algorithms/recombine_warehouses.m +++ /dev/null @@ -1,539 +0,0 @@ -% === DSP settings === -dsp_options = struct(); -dsp_options.mode = "run_id"; -dsp_options.recipe = @mpi_recipe_dev; -dsp_options.append_to_db = false; -dsp_options.start_occurence = 1; -dsp_options.max_occurences = 15; -dsp_options.debug_plots = false; - -dsp_options.database_type = "mysql"; - -dsp_options.dataBase = "labor"; -dsp_options.storage_path = "W:\labdata\ECOC Silas\ecoc_2025"; - -dsp_options.server = "192.168.178.192"; -dsp_options.port = 3306; -dsp_options.user = "silas"; -dsp_options.password = "silas"; - -db = DBHandler("dataBase", [dsp_options.dataBase], ... - "type", dsp_options.database_type, ... - "server", dsp_options.server, ... - "user", dsp_options.user, "password", dsp_options.password); - -scriptDir = fileparts(mfilename("fullpath")); - -whpam4_loaded = load(fullfile(scriptDir, "pam_4_results.mat")); -whpam6_loaded = load(fullfile(scriptDir, "pam_6_results.mat")); -whpam8_loaded = load(fullfile(scriptDir, "pam_8_results.mat")); - -whpam4 = whpam4_loaded.obj; -whpam6 = whpam6_loaded.obj; -whpam8 = whpam8_loaded.obj; - -whList = {whpam4, whpam6, whpam8}; -algorithmStorageNames = fieldnames(whpam4.sto); - -%% Build a lossless run_id warehouse and cache run metadata - -pamformats = [4, 6, 8]; -baudrates = [112e9, 96e9, 72e9]; -runMetaTable = queryRunMetadata(db, whList, pamformats, baudrates); - -wh_run_id_combined = mergeDataStoragesByUnion(whList); -wh_run_id_combined = addRunMetadataStorages(wh_run_id_combined, runMetaTable); - -runIdSavePath = fullfile(scriptDir, "combined_by_run_id_results.mat"); -save(runIdSavePath, "wh_run_id_combined", "runMetaTable", "algorithmStorageNames", '-v7.3'); - -fprintf("Saved run_id-combined warehouse:\n %s\n", runIdSavePath); -wh_run_id_combined.showInfo; - -%% Build a grouped config warehouse for analysis views - -configFields = ["pam_level", "symbolrate", "interference_path_length", "sir"]; -wh_config_combined = buildConfigWarehouse( ... - wh_run_id_combined, runMetaTable, algorithmStorageNames, configFields); - -configSavePath = fullfile(scriptDir, "combined_by_config_results.mat"); -save(configSavePath, "wh_config_combined", "runMetaTable", "algorithmStorageNames", '-v7.3'); - -fprintf("Saved config-grouped warehouse:\n %s\n", configSavePath); -wh_config_combined.showInfo; - -%% BER over SIR at fixed path length - -plot_options = struct(); -plot_options.path_length = 1000; -plot_options.use_boundedlines = true; -plot_options.boundedline_alpha = 0.10; -plot_options.polyfit_order_max = 3; -plot_options.fec_ber_threshold = 2e-2; -plot_options.figure_base = 7100; -plot_options.xlim = [15 35]; - -plotBerOverSirForPath(wh_config_combined, algorithmStorageNames, plot_options); - -%% Local functions - -function runMetaTable = queryRunMetadata(db, whList, pamformats, baudrates) -metaTables = {}; -metadataFields = db.getTableFieldNames('Runs'); - -for whIdx = 1:numel(whList) - curWh = whList{whIdx}; - curRunIds = curWh.parameter.run_id.values(:); - - fp = QueryFilter(); - fp.where('Runs', 'run_id', 'GREATER_EQUAL', 3153); - fp.where('Runs', 'symbolrate', 'EQUALS', baudrates(whIdx)); - fp.where('Runs', 'fiber_length', 'EQUALS', 0); - fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); - fp.where('Runs', 'pam_level', 'EQUALS', pamformats(whIdx)); - - [dataTable, ~] = db.queryDB(fp, metadataFields); - dataTable.run_id = numericColumn(dataTable.run_id); - dataTable = dataTable(ismember(dataTable.run_id, curRunIds), :); - - missingRunIds = setdiff(curRunIds, dataTable.run_id); - if ~isempty(missingRunIds) - warning("recombine_warehouses:MissingMetadata", ... - "%d run_id(s) from PAM %.0f warehouse were not found in the metadata query.", ... - numel(missingRunIds), pamformats(whIdx)); - end - - metaTables{end+1} = dataTable; %#ok -end - -runMetaTable = vertcat(metaTables{:}); -[~, uniqueIdx] = unique(numericColumn(runMetaTable.run_id), "stable"); -runMetaTable = runMetaTable(uniqueIdx, :); - -numericFields = ["run_id", "pam_level", "symbolrate", "interference_path_length", "sir"]; -for fieldIdx = 1:numel(numericFields) - fieldName = numericFields(fieldIdx); - if ismember(fieldName, string(runMetaTable.Properties.VariableNames)) - runMetaTable.(char(fieldName)) = numericColumn(runMetaTable.(char(fieldName))); - end -end - -[~, sortIdx] = sort(runMetaTable.run_id); -runMetaTable = runMetaTable(sortIdx, :); -end - -function x = numericColumn(x) -if isnumeric(x) - x = double(x); -elseif iscell(x) - x = str2double(string(x)); -elseif isstring(x) || ischar(x) || iscategorical(x) - x = str2double(string(x)); -else - x = double(x); -end -x = x(:); -end - -function whMerged = mergeDataStoragesByUnion(whList) -templateWh = whList{1}; -paramNames = cellstr(templateWh.fn); - -for whIdx = 2:numel(whList) - if ~isequal(sort(cellstr(whList{whIdx}.fn)), sort(paramNames)) - error("recombine_warehouses:ParameterMismatch", ... - "All input warehouses must use the same parameter names."); - end -end - -mergedParams = struct(); -for paramIdx = 1:numel(paramNames) - paramName = paramNames{paramIdx}; - values = []; - - for whIdx = 1:numel(whList) - newValues = whList{whIdx}.parameter.(paramName).values(:).'; - values = [values, newValues]; %#ok - end - - values = unique(values, "stable"); - if isnumeric(values) - values = sort(values); - end - mergedParams.(paramName) = values; -end - -whMerged = DataStorage(mergedParams); - -for whIdx = 1:numel(whList) - curWh = whList{whIdx}; - curStorageNames = fieldnames(curWh.sto); - - for storageIdx = 1:numel(curStorageNames) - storageName = curStorageNames{storageIdx}; - if ~isfield(whMerged.sto, storageName) - whMerged.addStorage(storageName); - end - - for linIdx = 1:numel(curWh.sto.(storageName)) - storedValue = curWh.sto.(storageName){linIdx}; - if isempty(storedValue) - continue - end - - targetLinIdx = mapLinearIndex(curWh, whMerged, linIdx); - existingValue = whMerged.sto.(storageName){targetLinIdx}; - whMerged.sto.(storageName){targetLinIdx} = mergeStoredValue(existingValue, storedValue); - end - end -end -end - -function wh = addRunMetadataStorages(wh, runMetaTable) -metadataStorageNames = ["meta_pam_level", "meta_symbolrate", ... - "meta_interference_path_length", "meta_sir"]; -metadataTableFields = ["pam_level", "symbolrate", "interference_path_length", "sir"]; - -for fieldIdx = 1:numel(metadataStorageNames) - if ~isfield(wh.sto, metadataStorageNames(fieldIdx)) - wh.addStorage(char(metadataStorageNames(fieldIdx))); - end -end - -for linIdx = 1:wh.getLastLinIndice() - physStruct = linearIndexToStruct(wh, linIdx); - if ~isfield(physStruct, "run_id") - continue - end - - metaIdx = find(runMetaTable.run_id == physStruct.run_id, 1); - if isempty(metaIdx) - continue - end - - for fieldIdx = 1:numel(metadataStorageNames) - wh.sto.(char(metadataStorageNames(fieldIdx))){linIdx} = ... - runMetaTable.(char(metadataTableFields(fieldIdx)))(metaIdx); - end -end -end - -function whConfig = buildConfigWarehouse(whRun, runMetaTable, algorithmStorageNames, configFields) -runParamNames = cellstr(whRun.fn); -extraParamNames = setdiff(string(runParamNames), "run_id", "stable"); - -configParams = struct(); -for fieldIdx = 1:numel(configFields) - fieldName = configFields(fieldIdx); - values = unique(runMetaTable.(char(fieldName))(:).', "stable"); - if isnumeric(values) - values = sort(values); - end - configParams.(char(fieldName)) = values; -end - -for paramIdx = 1:numel(extraParamNames) - paramName = extraParamNames(paramIdx); - configParams.(char(paramName)) = whRun.parameter.(char(paramName)).values; -end - -whConfig = DataStorage(configParams); -for storageIdx = 1:numel(algorithmStorageNames) - whConfig.addStorage(algorithmStorageNames{storageIdx}); -end -whConfig.addStorage("run_id"); - -for storageIdx = 1:numel(algorithmStorageNames) - storageName = algorithmStorageNames{storageIdx}; - - for linIdx = 1:numel(whRun.sto.(storageName)) - storedValue = whRun.sto.(storageName){linIdx}; - if isempty(storedValue) - continue - end - - sourcePhys = linearIndexToStruct(whRun, linIdx); - metaIdx = find(runMetaTable.run_id == sourcePhys.run_id, 1); - if isempty(metaIdx) - continue - end - - targetArgs = configTargetArgs(whConfig, sourcePhys, runMetaTable, metaIdx, configFields); - targetLinIdx = whConfig.getIndicesByPhys(targetArgs); - - existingValue = whConfig.sto.(storageName){targetLinIdx}; - whConfig.sto.(storageName){targetLinIdx} = mergeStoredValue(existingValue, storedValue); - end -end - -for linIdx = 1:whRun.getLastLinIndice() - sourcePhys = linearIndexToStruct(whRun, linIdx); - metaIdx = find(runMetaTable.run_id == sourcePhys.run_id, 1); - if isempty(metaIdx) - continue - end - - targetArgs = configTargetArgs(whConfig, sourcePhys, runMetaTable, metaIdx, configFields); - targetLinIdx = whConfig.getIndicesByPhys(targetArgs); - whConfig.sto.run_id{targetLinIdx} = mergeUniqueNumeric( ... - whConfig.sto.run_id{targetLinIdx}, sourcePhys.run_id); -end -end - -function targetArgs = configTargetArgs(whConfig, sourcePhys, runMetaTable, metaIdx, configFields) -targetArgs = cell(1, numel(whConfig.fn)); - -for paramIdx = 1:numel(whConfig.fn) - paramName = string(whConfig.fn(paramIdx)); - if any(configFields == paramName) - targetArgs{paramIdx} = runMetaTable.(char(paramName))(metaIdx); - else - targetArgs{paramIdx} = sourcePhys.(char(paramName)); - end -end -end - -function targetLinIdx = mapLinearIndex(sourceWh, targetWh, sourceLinIdx) -sourcePhys = linearIndexToStruct(sourceWh, sourceLinIdx); -targetArgs = cell(1, numel(targetWh.fn)); - -for paramIdx = 1:numel(targetWh.fn) - paramName = char(targetWh.fn(paramIdx)); - targetArgs{paramIdx} = sourcePhys.(paramName); -end - -targetLinIdx = targetWh.getIndicesByPhys(targetArgs); -end - -function physStruct = linearIndexToStruct(wh, linIdx) -[physValues, physNames] = wh.getPhysIndicesByLinIndex(linIdx); -physStruct = struct(); - -for paramIdx = 1:numel(physNames) - physStruct.(char(physNames{paramIdx})) = physValues{paramIdx}; -end -end - -function out = mergeStoredValue(existingValue, newValue) -if isempty(existingValue) - out = newValue; - return -end - -existingPackages = asPackageCell(existingValue); -newPackages = asPackageCell(newValue); -out = [existingPackages(:); newPackages(:)].'; -end - -function packages = asPackageCell(value) -if isempty(value) - packages = {}; -elseif iscell(value) - packages = value(:).'; -else - packages = {value}; -end -end - -function out = mergeUniqueNumeric(existingValue, newValue) -if isempty(existingValue) - out = newValue; -else - out = unique([existingValue(:); newValue(:)].', "stable"); -end -end - -function plotBerOverSirForPath(whConfig, algorithmStorageNames, plotOptions) -paramNames = string(whConfig.fn); - -if ~any(paramNames == "pam_level") || ~any(paramNames == "interference_path_length") || ~any(paramNames == "sir") - error("recombine_warehouses:MissingPlotParameters", ... - "The config warehouse must contain pam_level, interference_path_length, and sir parameters."); -end - -pamLevels = whConfig.parameter.pam_level.values; -hasBlockUpdate = any(paramNames == "block_update"); -if hasBlockUpdate - blockUpdates = whConfig.parameter.block_update.values; -else - blockUpdates = NaN; -end - -if exist("linspecer", "file") - curveColors = linspecer(max(numel(algorithmStorageNames), 1)); -else - curveColors = lines(max(numel(algorithmStorageNames), 1)); -end - -figureIdx = plotOptions.figure_base; -for blockIdx = 1:numel(blockUpdates) - for pamIdx = 1:numel(pamLevels) - pamLevel = pamLevels(pamIdx); - figureIdx = figureIdx + 1; - figure(figureIdx); clf; hold on - - for storageIdx = 1:numel(algorithmStorageNames) - storageName = algorithmStorageNames{storageIdx}; - curveColor = curveColors(storageIdx, :); - - filter = struct(); - filter.pam_level = pamLevel; - filter.interference_path_length = plotOptions.path_length; - if hasBlockUpdate - filter.block_update = blockUpdates(blockIdx); - end - - [sirValues, berGroups] = collectBerBySir(whConfig, storageName, filter); - if isempty(sirValues) - continue - end - - [sirValues, sortIdx] = sort(sirValues); - berGroups = berGroups(sortIdx); - - berAvg = nan(1, numel(sirValues)); - berMin = nan(1, numel(sirValues)); - berMax = nan(1, numel(sirValues)); - - for sirIdx = 1:numel(sirValues) - rawBer = berGroups{sirIdx}; - rawBer = rawBer(isfinite(rawBer)); - if isempty(rawBer) - continue - end - - scatter(repmat(sirValues(sirIdx), size(rawBer)), rawBer, ... - 12, ... - "Marker", ".", ... - "MarkerEdgeColor", curveColor, ... - "MarkerFaceColor", curveColor, ... - "HandleVisibility", "off"); - - berAvg(sirIdx) = mean(rawBer, "omitnan"); - berMin(sirIdx) = min(rawBer); - berMax(sirIdx) = max(rawBer); - end - - validAvg = isfinite(sirValues) & isfinite(berAvg); - if ~any(validAvg) - continue - end - - if plotOptions.use_boundedlines && exist("boundedline", "file") - yLower = max(berAvg - berMin, 0); - yUpper = max(berMax - berAvg, 0); - yBounds = [yLower(:), yUpper(:)]; - - [hl, hp] = boundedline(sirValues(:), berAvg(:), yBounds, ... - "alpha", "transparency", plotOptions.boundedline_alpha, ... - "cmap", curveColor, ... - "nan", "fill", ... - "orientation", "vert"); - set(hl, "LineStyle", "none", "Marker", "none", "HandleVisibility", "off"); - set(hp, "HandleVisibility", "off", "LineStyle", "none"); - end - - scatter(sirValues(validAvg), berAvg(validAvg), ... - 54, ... - "Marker", "o", ... - "MarkerEdgeColor", curveColor, ... - "MarkerFaceColor", "none", ... - "LineWidth", 1.2, ... - "DisplayName", storageName); - - fitMask = validAvg & berAvg > 0; - if nnz(fitMask) >= 2 - fitOrder = min(plotOptions.polyfit_order_max, nnz(fitMask) - 1); - fitCoeff = polyfit(sirValues(fitMask), log10(berAvg(fitMask)), fitOrder); - xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); - yFit = 10 .^ polyval(fitCoeff, xFit); - - plot(xFit, yFit, ... - "LineWidth", 1.2, ... - "LineStyle", "--", ... - "Marker", "none", ... - "Color", curveColor, ... - "HandleVisibility", "off"); - end - end - - if hasBlockUpdate - title(sprintf("BER over SIR, PAM %.0f, path %.0f m, block update = %g", ... - pamLevel, plotOptions.path_length, blockUpdates(blockIdx))); - else - title(sprintf("BER over SIR, PAM %.0f, path %.0f m", ... - pamLevel, plotOptions.path_length)); - end - xlabel("SIR (dB)"); - ylabel("BER"); - xlim(plotOptions.xlim); - grid on - box on - - if exist("beautifyBERplot", "file") - beautifyBERplot("logscale", true, "setcolors", false, "setmarkers", false); - else - set(gca, "YScale", "log"); - end - legend("Location", "best", "Interpreter", "none"); - end -end -end - -function [sirValues, berGroups] = collectBerBySir(whConfig, storageName, filter) -sirValues = []; -berGroups = {}; - -for linIdx = 1:numel(whConfig.sto.(storageName)) - storedValue = whConfig.sto.(storageName){linIdx}; - if isempty(storedValue) - continue - end - - physStruct = linearIndexToStruct(whConfig, linIdx); - if ~matchesFilter(physStruct, filter) - continue - end - - berValues = extractBerValues(storedValue); - if isempty(berValues) - continue - end - - sirValue = physStruct.sir; - sirIdx = find(sirValues == sirValue, 1); - if isempty(sirIdx) - sirValues(end+1) = sirValue; %#ok - berGroups{end+1} = berValues; %#ok - else - berGroups{sirIdx} = [berGroups{sirIdx}, berValues]; %#ok - end -end -end - -function tf = matchesFilter(physStruct, filter) -tf = true; -filterFields = fieldnames(filter); - -for fieldIdx = 1:numel(filterFields) - fieldName = filterFields{fieldIdx}; - if ~isfield(physStruct, fieldName) || physStruct.(fieldName) ~= filter.(fieldName) - tf = false; - return - end -end -end - -function berValues = extractBerValues(value) -berValues = []; - -if isstruct(value) - if isfield(value, "metrics") && isfield(value.metrics, "BER") - berValues(end+1) = value.metrics.BER; - end -elseif iscell(value) - for valueIdx = 1:numel(value) - berValues = [berValues, extractBerValues(value{valueIdx})]; %#ok - end -end -end diff --git a/projects/Diss/MPI_revisit/algorithms/wh_ffe_baseline.mat b/projects/Diss/MPI_revisit/algorithms/wh_ffe_baseline.mat deleted file mode 100644 index 132cedc..0000000 Binary files a/projects/Diss/MPI_revisit/algorithms/wh_ffe_baseline.mat and /dev/null differ diff --git a/projects/Diss/MPI_revisit/investigate_mpi_algorithms.m b/projects/Diss/MPI_revisit/investigate_mpi_algorithms.m index 61e4fac..b7db98d 100644 --- a/projects/Diss/MPI_revisit/investigate_mpi_algorithms.m +++ b/projects/Diss/MPI_revisit/investigate_mpi_algorithms.m @@ -3,13 +3,13 @@ dsp_options = struct(); dsp_options.mode = "run_id"; dsp_options.recipe = @mpi_recipe_dev; dsp_options.append_to_db = false; -dsp_options.start_occurence = 1; -dsp_options.max_occurences = 1; -dsp_options.debug_plots = true; +dsp_options.start_occurence = 5; +dsp_options.max_occurences = 15; +dsp_options.debug_plots = false; write_mpi_reduction_db = 0; -stream_mpi_reduction_db = 0; -mpi_reduction_study_name = "block_update_sweep"; +stream_mpi_reduction_db = write_mpi_reduction_db; +mpi_reduction_study_name = "pam6_8_sweep"; mpi_reduction_writer_path = fullfile(fileparts(mfilename('fullpath')),"db"); addpath(mpi_reduction_writer_path); @@ -51,7 +51,7 @@ for i = 1 fp.where('Runs', 'symbolrate', 'EQUALS', B); % 72 96 112 fp.where('Runs', 'fiber_length', 'EQUALS', 0); fp.where('Runs', 'interference_path_length', 'EQUALS', 1000); - % fp.where('Runs', 'sir', 'LESS_EQUAL', 20); + fp.where('Runs', 'sir', 'LESS_EQUAL', 20); fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); % fp.where('Runs', 'is_mpi', 'EQUALS', 1); fp.where('Runs', 'pam_level', 'EQUALS', M); @@ -62,14 +62,14 @@ for i = 1 [~, sortIdx] = sort(dataTable.sir, 'descend'); dataTable = dataTable(sortIdx, :); - % dataTable = dataTable(1,:); + dataTable = dataTable(1,:); run_ids = dataTable.run_id; %% === Warehouse setup === dsp_options.userParameters = struct(); dsp_options.userParameters.block_update = 1;%[1,2,4,8,16,32,64,112,224,448,448*2,1024,2048,4096,8192,16384];%%logspace(-3.8,-1,22);%[linspace(2,4096,22)]; - % dsp_options.userParameters.block_update = linspace(1,224,22); + dsp_options.userParameters.hpf = [2e6:2e6:10e6,20e6:10e6:100e6]; wh = DataStorage(dsp_options.userParameters); %% @@ -108,7 +108,8 @@ end storageNames = fieldnames(wh.sto); x_base = dataTable.sir(:).'; -% x_base = dsp_options.userParameters.block_update; +x_base = dsp_options.userParameters.block_update; +x_base = dsp_options.userParameters.hpf; figure(2026); clf; hold on for storage_idx = 1:numel(storageNames) diff --git a/projects/Diss/MPI_revisit/parallelization_analysis/PLOT_mpi_reduction_db_parallelization_vs_sir.m b/projects/Diss/MPI_revisit/parallelization_analysis/PLOT_mpi_reduction_db_parallelization_vs_sir.m new file mode 100644 index 0000000..ff04768 --- /dev/null +++ b/projects/Diss/MPI_revisit/parallelization_analysis/PLOT_mpi_reduction_db_parallelization_vs_sir.m @@ -0,0 +1,564 @@ +%% MPI reduction parallelization analysis from MpiReductionResults +% A) Helper BER-over-SIR plots for first and last block_update. +% B) Required SIR at FEC threshold over block_update. + +clear; clc; + +%% 1) Gather data + +studyName = "block_update_sweep"; +pathLengthToPlot = 1000; +selectedPamLevels = 6; % set [] to use all PAM levels in the query result +selectedAlgorithms = []; % set [] to use all algorithms in the query result + +useBoundedLines = true; +usePolyfit = true; +polyfitOrderMax = 4; +boundaryPolyfitOrderMax = 4; +fecBerThreshold = 3.8e-3; +maxBerForPlot = 0.1; +scatterKeepFraction = 0.2; +boundMode = "fitStd"; % "fitStd" or "directStd" +algorithmMarkers = {'o','square','diamond','^','v','>','<','pentagram'}; + +db = DBHandler( ... + "dataBase", "labor", ... + "type", "mysql", ... + "server", "192.168.178.192", ... + "user", "silas", ... + "password", "silas"); +db.refresh(); + +fp = QueryFilter(); +fp.where('Runs', 'interference_path_length', 'EQUALS', pathLengthToPlot); +fp.where('Runs', 'fiber_length', 'EQUALS', 0); +fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); +fp.where('MpiReductionResults', 'study_name', 'EQUALS', char(studyName)); +if isscalar(selectedPamLevels) && ~isempty(selectedPamLevels) + fp.where('Runs', 'pam_level', 'EQUALS', selectedPamLevels); +end + +selectedFields = { ... + 'Runs.run_id', ... + 'Runs.sir', ... + 'Runs.pam_level', ... + 'Runs.symbolrate', ... + 'Runs.interference_path_length', ... + 'Runs.power_mpi_interference', ... + 'MpiReductionResults.occurrence_idx', ... + 'MpiReductionResults.storage_name', ... + 'MpiReductionResults.algorithm', ... + 'MpiReductionResults.algorithm_variant', ... + 'MpiReductionResults.eq_class', ... + 'MpiReductionResults.block_update', ... + 'MpiReductionResults.BER', ... + 'MpiReductionResults.BER_precoded', ... + 'MpiReductionResults.SNR', ... + 'MpiReductionResults.GMI', ... + 'MpiReductionResults.AIR'}; +selectedFields = selectedFields(:); + +[rawData, query] = db.queryDB(fp, selectedFields); +disp(query); +fprintf("Fetched %d MPI reduction rows.\n", height(rawData)); + +%% 2) Clean data and calculate required SIR + +data = rawData; +numericFields = ["run_id", "sir", "pam_level", "symbolrate", ... + "interference_path_length", "occurrence_idx", "block_update", ... + "power_mpi_interference", "BER", "BER_precoded", "SNR", "GMI", "AIR"]; +for fieldIdx = 1:numel(numericFields) + fieldName = numericFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = numericColumn(data.(char(fieldName))); + end +end + +stringFields = ["storage_name", "algorithm", "algorithm_variant", "eq_class"]; +for fieldIdx = 1:numel(stringFields) + fieldName = stringFields(fieldIdx); + if ismember(fieldName, string(data.Properties.VariableNames)) + data.(char(fieldName)) = stringColumn(data.(char(fieldName))); + end +end + +data = data(isfinite(data.BER) & data.BER > 0 & data.BER < maxBerForPlot, :); +data.sir_exact = -7 - data.power_mpi_interference; +if isempty(data) + warning("PLOT_mpi_reduction_db_parallelization_vs_sir:NoRows", ... + "No rows remain after initial BER/path/study filtering."); + return +end + +if isempty(selectedPamLevels) + selectedPamLevels = unique(data.pam_level(isfinite(data.pam_level))).'; +else + data = data(ismember(data.pam_level, selectedPamLevels), :); +end + +if isempty(selectedAlgorithms) + selectedAlgorithms = unique(data.algorithm, "stable").'; +else + selectedAlgorithms = string(selectedAlgorithms); + data = data(ismember(data.algorithm, selectedAlgorithms), :); +end + +if isempty(data) + warning("PLOT_mpi_reduction_db_parallelization_vs_sir:NoSelectedRows", ... + "No rows remain after selectedPamLevels/selectedAlgorithms filtering."); + return +end + +data.clean_keep = true(height(data), 1); +groupId = findgroups(data.pam_level, data.algorithm, data.block_update, data.sir); +for curGroup = unique(groupId(isfinite(groupId))).' + rowMask = groupId == curGroup; + berValues = data.BER(rowMask); + if nnz(rowMask) > 3 + data.clean_keep(rowMask) = ~isoutlier(berValues); + end +end +cleanData = data(data.clean_keep, :); + +groupVars = ["pam_level", "algorithm", "block_update", "sir"]; +summaryTable = groupsummary(cleanData, groupVars, ... + {"mean", "min", "max"}, "BER"); +sirExactTable = groupsummary(cleanData, groupVars, "median", "sir_exact"); +summaryTable = sortrows(summaryTable, groupVars); +sirExactTable = sortrows(sirExactTable, groupVars); +summaryTable.sir_exact = sirExactTable.median_sir_exact; +summaryTable = addBerStdBounds(summaryTable, cleanData, groupVars); + +blockUpdates = unique(cleanData.block_update(isfinite(cleanData.block_update))).'; +blockUpdates = sort(blockUpdates); +helperBlockUpdates = unique(blockUpdates([1, end]), "stable"); + +requiredSirRows = table(); +for pamIdx = 1:numel(selectedPamLevels) + pamLevel = selectedPamLevels(pamIdx); + for algIdx = 1:numel(selectedAlgorithms) + algorithmName = selectedAlgorithms(algIdx); + for blockIdx = 1:numel(blockUpdates) + blockUpdate = blockUpdates(blockIdx); + curveMask = summaryTable.pam_level == pamLevel & ... + summaryTable.algorithm == algorithmName & ... + summaryTable.block_update == blockUpdate; + + sirValues = summaryTable.sir_exact(curveMask).'; + meanBer = summaryTable.mean_BER(curveMask).'; + [~, ~, requiredSir, fitOrder] = fitBerAtFec( ... + sirValues, meanBer, polyfitOrderMax, fecBerThreshold); + + newRow = table(pamLevel, algorithmName, blockUpdate, ... + requiredSir, fitOrder, nnz(isfinite(sirValues) & isfinite(meanBer)), ... + 'VariableNames', ["pam_level", "algorithm", "block_update", ... + "required_sir", "fit_order", "n_points"]); + requiredSirRows = [requiredSirRows; newRow]; %#ok + end + end +end + +fprintf("Cleaned to %d rows across %d PAM/algorithm/block/SIR groups.\n", ... + height(cleanData), height(summaryTable)); +disp(groupcounts(cleanData, ["pam_level", "algorithm", "block_update"])); +disp(requiredSirRows); + +%% 3A) Helper BER-over-SIR plots for first and last block updates + +% tiledlayout(numel(selectedPamLevels), numel(helperBlockUpdates), ... +% "TileSpacing", "compact"); + +for pamIdx = 1:numel(selectedPamLevels) + pamLevel = selectedPamLevels(pamIdx); + for helperIdx = 1:numel(helperBlockUpdates) + figure(); clf; + blockUpdate = helperBlockUpdates(helperIdx); + nexttile; hold on; + + for algIdx = 1:numel(selectedAlgorithms) + algorithmName = selectedAlgorithms(algIdx); + algColor = algorithmColor(algorithmName); + marker = algorithmMarker(algorithmName, algorithmMarkers); + displayName = algorithmDisplayName(algorithmName); + + rawMask = cleanData.pam_level == pamLevel & ... + cleanData.algorithm == algorithmName & ... + cleanData.block_update == blockUpdate; + curveMask = summaryTable.pam_level == pamLevel & ... + summaryTable.algorithm == algorithmName & ... + summaryTable.block_update == blockUpdate; + + if ~any(curveMask) + continue + end + + scatterKeepFraction = 1; + scatterRows = downsampleRows(cleanData(rawMask, :), scatterKeepFraction); + scatter(scatterRows.sir_exact, scatterRows.BER, ... + 26, ... + "Marker", ".", ... + "MarkerEdgeColor", algColor, ... + "MarkerFaceColor", algColor, ... + "HandleVisibility", "off"); + + sirValues = summaryTable.sir_exact(curveMask).'; + meanBer = summaryTable.mean_BER(curveMask).'; + boundCenterBer = summaryTable.std_center_BER(curveMask).'; + boundLowerBer = summaryTable.std_lower_BER(curveMask).'; + boundUpperBer = summaryTable.std_upper_BER(curveMask).'; + valid = isfinite(sirValues) & isfinite(meanBer) & meanBer > 0; + + if useBoundedLines && exist("boundedline", "file") && any(valid) + [xBand, centerBand, yBounds] = berStdBounds( ... + sirValues, boundCenterBer, boundLowerBer, boundUpperBer, ... + boundMode, boundaryPolyfitOrderMax); + + [hl, hp] = boundedline(xBand, centerBand, yBounds, ... + 'alpha', 'transparency', 0.1, ... + 'cmap', algColor, ... + 'nan', 'fill', ... + 'orientation', 'vert'); + set(hl, "LineStyle", "none", 'LineWidth', 1, "Marker", "none", ... + "HandleVisibility", "off", "DisplayName", char(displayName)); + set(hp, "LineStyle", "-", "HandleVisibility", "off", "Marker", "none"); + end + + plot(sirValues(valid), meanBer(valid), ... + "LineStyle", "-", ... + "Marker", marker, ... + "MarkerSize", 3, ... + "LineWidth", 1, ... + "Color", algColor, ... + "MarkerFaceColor", "w", ... + "MarkerEdgeColor", algColor, ... + "DisplayName", char(displayName), 'HandleVisibility', 'on'); + + if usePolyfit + fitMask = valid & meanBer > 0; + if nnz(fitMask) >= 2 + fitOrder = min(polyfitOrderMax, nnz(fitMask) - 1); + fitCoeff = polyfit(sirValues(fitMask), log10(meanBer(fitMask)), fitOrder); + xFit = linspace(min(sirValues(fitMask)), max(sirValues(fitMask)), 300); + yFit = 10 .^ polyval(fitCoeff, xFit); + + % plot(xFit, yFit, ... + % "LineStyle", "--", ... + % "LineWidth", 1.1, ... + % "Color", algColor, ... + % "HandleVisibility", "off"); + end + end + end + + yline(2.2e-4, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(fecBerThreshold, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + yline(2e-2, "LineWidth", 1, "LineStyle", "--", "HandleVisibility", "off"); + + title(sprintf("PAM %.0f, block update %.0f", pamLevel, blockUpdate)); + xlabel("SIR (dB)"); + ylabel("BER"); + set(gca, "YScale", "log"); + ylim([9e-5, 0.05]); + xlim([15, 35]); + grid on; + box on; + legend("Location", "northeast", "Interpreter", "none"); + if exist("beautifyBERplot", "file") + beautifyBERplot("logscale", true, "setcolors", false, "setmarkers", false); + end + % mat2tikz_improved(sprintf("C:/Users/Silas/Documents/6971e0b65b380ca6d71c837f/04_Experimental_Evaluation/tikz/mpi/ber_vs_sir_blockupdate_v2_%d.tikz",blockUpdate)); + end +end + +%% 3B) Required SIR over block update + +figure(); clf; +tiledlayout(numel(selectedPamLevels), 1, "TileSpacing", "compact"); + +for pamIdx = 1:numel(selectedPamLevels) + pamLevel = selectedPamLevels(pamIdx); + nexttile; hold on; + + for algIdx = 1:numel(selectedAlgorithms) + algorithmName = selectedAlgorithms(algIdx); + algColor = algorithmColor(algorithmName); + marker = algorithmMarker(algorithmName, algorithmMarkers); + displayName = algorithmDisplayName(algorithmName); + rowMask = requiredSirRows.pam_level == pamLevel & ... + requiredSirRows.algorithm == algorithmName; + + x = requiredSirRows.block_update(rowMask).'; + y = requiredSirRows.required_sir(rowMask).'; + [x, sortIdx] = sort(x); + y = y(sortIdx); + valid = isfinite(x) & isfinite(y); + + if ~any(valid) + continue + end + + plot(x(valid), y(valid), ... + "LineWidth", 1.4, ... + "LineStyle", "-", ... + "Marker", marker, ... + "MarkerSize", 5, ... + "Color", algColor, ... + "MarkerFaceColor", "w", ... + "MarkerEdgeColor", algColor, ... + "DisplayName", char(displayName)); + end + + set(gca, "XScale", "log"); + xticks(blockUpdates); + xticklabels(string(blockUpdates)); + xlim([x(1) x(end)]); + grid on; + box on; + xlabel("Parallelization / block update"); + ylabel(sprintf("Required SIR at BER = %.1e (dB)", fecBerThreshold)); + title(sprintf("PAM %.0f, path %.0f m", pamLevel, pathLengthToPlot)); + legend("Location","southeast", "Interpreter", "none"); + if exist("beautifyBERplot", "file") + beautifyBERplot("logscale", false, "setcolors", false, "setmarkers", false); + end +end + +%% Local helpers + +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 = stringColumn(values) + if iscell(values) + values = string(values); + elseif ischar(values) + values = string(values); + end + values = strip(string(values)); +end + +function rows = downsampleRows(rows, keepFraction) + if keepFraction >= 1 || height(rows) <= 1 + return + end + + keepEvery = max(1, round(1 / keepFraction)); + keepIdx = 1:keepEvery:height(rows); + rows = rows(keepIdx, :); +end + +function summaryTable = addBerStdBounds(summaryTable, cleanData, groupVars) + nGroups = height(summaryTable); + stdCenter = NaN(nGroups, 1); + stdLower = NaN(nGroups, 1); + stdUpper = NaN(nGroups, 1); + + for groupIdx = 1:nGroups + rowMask = true(height(cleanData), 1); + for varIdx = 1:numel(groupVars) + varName = groupVars(varIdx); + rowMask = rowMask & cleanData.(char(varName)) == summaryTable.(char(varName))(groupIdx); + end + + [stdCenter(groupIdx), stdLower(groupIdx), stdUpper(groupIdx)] = ... + logBerMeanStdInterval(cleanData.BER(rowMask)); + end + + summaryTable.std_center_BER = stdCenter; + summaryTable.std_lower_BER = stdLower; + summaryTable.std_upper_BER = stdUpper; +end + +function [centerBer, lowerBer, upperBer] = logBerMeanStdInterval(berValues) + berValues = berValues(isfinite(berValues) & berValues > 0); + if isempty(berValues) + centerBer = NaN; + lowerBer = NaN; + upperBer = NaN; + return + end + + logBer = log10(berValues(:)); + centerLog = mean(logBer, "omitnan"); + stdLog = std(logBer, 0, "omitnan"); + + centerBer = 10 .^ centerLog; + lowerBer = 10 .^ (centerLog - stdLog); + upperBer = 10 .^ (centerLog + stdLog); +end + +function [xBand, centerBand, yBounds] = berStdBounds(sirValues, centerBer, lowerBer, upperBer, boundMode, maxOrder) + if boundMode == "directStd" + [xBand, centerBand, yBounds] = directBerBounds(sirValues, centerBer, lowerBer, upperBer); + return + end + + [xBand, centerBand, yBounds] = fittedBerBounds(sirValues, centerBer, lowerBer, upperBer, maxOrder); +end + +function [xBand, centerBand, yBounds] = directBerBounds(sirValues, centerBer, lowerBer, upperBer) + valid = isfinite(sirValues) & isfinite(centerBer) & isfinite(lowerBer) & ... + isfinite(upperBer) & centerBer > 0 & lowerBer > 0 & upperBer > 0; + + xBand = sirValues(valid).'; + centerBand = centerBer(valid).'; + lowerBand = lowerBer(valid).'; + upperBand = upperBer(valid).'; + + [xBand, orderIdx] = sort(xBand(:)); + centerBand = centerBand(orderIdx); + lowerBand = lowerBand(orderIdx); + upperBand = upperBand(orderIdx); + + lowerTmp = min(lowerBand, upperBand); + upperBand = max(lowerBand, upperBand); + lowerBand = lowerTmp; + + centerBand = min(max(centerBand, lowerBand), upperBand); + yBounds = [max(centerBand - lowerBand, 0), max(upperBand - centerBand, 0)]; +end + +function [xBand, centerBand, yBounds] = fittedBerBounds(sirValues, meanBer, minBer, maxBer, maxOrder) + valid = isfinite(sirValues) & isfinite(meanBer) & isfinite(minBer) & ... + isfinite(maxBer) & meanBer > 0 & minBer > 0 & maxBer > 0; + + x = sirValues(valid); + yMean = meanBer(valid); + yMin = minBer(valid); + yMax = maxBer(valid); + + if numel(x) < 2 + xBand = x(:); + centerBand = yMean(:); + yBounds = [max(yMean(:) - yMin(:), 0), max(yMax(:) - yMean(:), 0)]; + return + end + + [x, orderIdx] = sort(x(:)); + yMean = yMean(orderIdx); + yMin = yMin(orderIdx); + yMax = yMax(orderIdx); + + xBand = linspace(min(x), max(x), 300).'; + fitOrder = min(maxOrder, numel(unique(x)) - 1); + + if fitOrder < 1 + centerBand = interp1(x, yMean, xBand, "linear", "extrap"); + lowerBand = interp1(x, yMin, xBand, "linear", "extrap"); + upperBand = interp1(x, yMax, xBand, "linear", "extrap"); + else + centerBand = fitLogBer(x, yMean, xBand, fitOrder); + lowerBand = fitLogBer(x, yMin, xBand, fitOrder); + upperBand = fitLogBer(x, yMax, xBand, fitOrder); + end + + lowerTmp = min(lowerBand, upperBand); + upperBand = max(lowerBand, upperBand); + lowerBand = lowerTmp; + + centerBand = min(max(centerBand, lowerBand), upperBand); + yBounds = [max(centerBand - lowerBand, 0), max(upperBand - centerBand, 0)]; +end + +function yFit = fitLogBer(x, y, xFit, fitOrder) + coeff = polyfit(x, log10(y), fitOrder); + yFit = 10 .^ polyval(coeff, xFit); +end + +function label = algorithmDisplayName(algorithmName) + algorithmName = string(algorithmName); + switch algorithmName + case "plain_ffe" + label = "FFE only"; + case "a2_tracked_levels" + label = "ACT"; + case "a2_residual" + label = "L-DCA"; + case "a1_moving_average" + label = "DCA"; + case "dc_tracking" + label = "DCT"; + otherwise + label = algorithmName; + end +end + +function color = algorithmColor(algorithmName) + algorithmName = string(algorithmName); + switch algorithmName + case "plain_ffe" + color = [0.3467 0.5360 0.6907]; + case "a2_tracked_levels" + color = [0.9153 0.2816 0.2878]; + case "a2_residual" + color = [0.4416 0.7490 0.4322]; + case "a1_moving_average" + color = [1.0000 0.5984 0.2000]; + case "dc_tracking" + color = [0.6769 0.4447 0.7114]; + otherwise + color = [0 0 0]; + end +end + +function marker = algorithmMarker(algorithmName, algorithmMarkers) + algorithmOrder = ["plain_ffe", "a2_tracked_levels", "a2_residual", ... + "a1_moving_average", "dc_tracking"]; + markerIdx = find(algorithmOrder == string(algorithmName), 1); + if isempty(markerIdx) + markerIdx = 1; + end + marker = algorithmMarkers{mod(markerIdx - 1, numel(algorithmMarkers)) + 1}; +end + +function [xFit, yFit, requiredSir, fitOrder] = fitBerAtFec( ... + sirValues, meanBer, polyfitOrderMax, fecBerThreshold) + + xFit = NaN; + yFit = NaN; + requiredSir = NaN; + fitOrder = NaN; + + valid = isfinite(sirValues) & isfinite(meanBer) & meanBer > 0; + if nnz(valid) < 2 + return + end + + sirValues = sirValues(valid); + meanBer = meanBer(valid); + [sirValues, sortIdx] = sort(sirValues); + meanBer = meanBer(sortIdx); + + fitOrder = min(polyfitOrderMax, nnz(valid) - 1); + fitCoeff = polyfit(sirValues, log10(meanBer), fitOrder); + xFit = linspace(min(sirValues), max(sirValues), 300); + yFit = 10 .^ polyval(fitCoeff, xFit); + + thresholdMask = isfinite(yFit) & yFit <= fecBerThreshold; + if ~any(thresholdMask) + return + end + + firstThresholdIdx = find(thresholdMask, 1, "first"); + if firstThresholdIdx == 1 + requiredSir = xFit(firstThresholdIdx); + return + end + + xPair = xFit(firstThresholdIdx - 1:firstThresholdIdx); + yPair = log10(yFit(firstThresholdIdx - 1:firstThresholdIdx)); + if all(isfinite(yPair)) && diff(yPair) ~= 0 + requiredSir = interp1(yPair, xPair, log10(fecBerThreshold), ... + "linear", "extrap"); + else + requiredSir = xFit(firstThresholdIdx); + end +end diff --git a/projects/Diss/MPI_revisit/parallelization_analysis/parallelization_pam4.fig b/projects/Diss/MPI_revisit/parallelization_analysis/parallelization_pam4.fig deleted file mode 100644 index b2482da..0000000 Binary files a/projects/Diss/MPI_revisit/parallelization_analysis/parallelization_pam4.fig and /dev/null differ diff --git a/projects/Diss/MPI_revisit/parallelization_analysis/plot_parallelization_vs_sir_pam4.m b/projects/Diss/MPI_revisit/parallelization_analysis/plot_parallelization_vs_sir_pam4.m deleted file mode 100644 index d79cf2b..0000000 --- a/projects/Diss/MPI_revisit/parallelization_analysis/plot_parallelization_vs_sir_pam4.m +++ /dev/null @@ -1,389 +0,0 @@ -%% Analyze -% === DSP settings === -dsp_options = struct(); -dsp_options.mode = "run_id"; -dsp_options.recipe = @mpi_recipe_dev; -dsp_options.append_to_db = false; -dsp_options.start_occurence = 1; -dsp_options.max_occurences = 15; -dsp_options.debug_plots = false; - -dsp_options.database_type = "mysql"; - -dsp_options.dataBase = "labor"; -dsp_options.storage_path = "W:\labdata\ECOC Silas\ecoc_2025"; - -dsp_options.server = "192.168.178.192"; -dsp_options.port = 3306; -dsp_options.user = "silas"; -dsp_options.password = "silas"; - -db = DBHandler("dataBase", [dsp_options.dataBase],... - "type", dsp_options.database_type,... - "server", dsp_options.server,... - "user", dsp_options.user, "password", dsp_options.password); - -%% - -fp = QueryFilter(); -fp.where('Runs','run_id','GREATER_EQUAL',3153); -fp.where('Runs', 'symbolrate', 'EQUALS', 112e9); % 72 96 112 -fp.where('Runs', 'fiber_length', 'EQUALS', 0); -fp.where('Runs', 'interference_path_length', 'EQUALS', 1000); -% fp.where('Runs', 'sir', 'LESS_EQUAL', 50); -fp.where('Runs', 'db_mode', 'EQUALS', '"no_db"'); -% fp.where('Runs', 'is_mpi', 'EQUALS', 1); -fp.where('Runs', 'pam_level', 'EQUALS', 4); -% fp.where('Runs', 'v_bias', 'EQUALS', 2.65); - -[dataTable,~] = db.queryDB(fp, db.getTableFieldNames('Runs')); - -% [~, uniqueSirRows] = unique(dataTable.sir, "stable"); -% dataTable = dataTable(uniqueSirRows, :); - -[~, sortIdx] = sort(dataTable.sir, 'descend'); -dataTable = dataTable(sortIdx, :); -% dataTable = dataTable(1, :); -run_ids = dataTable.run_id; -%% - -wh_ = load("C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\Diss\MPI_revisit\parallelization_analysis\wh_block_update_pam4_combined.mat"); -wh = wh_.wh; - -% wh_ = load("C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\Diss\MPI_revisit\parallelization_analysis\wh_block_update_pam4_short_blocks.mat"); -% wh2 = wh_.wh; -% -% wh = mergeDataStorages(wh1,wh2); - -%% - - -storageNames = fieldnames(wh.sto); -storageNames = storageNames([1,2,3,5]); -ber_by_storage = struct(); -ber_mat_by_storage = struct(); - -sir_values = dataTable.sir(:).'; -block_updates = wh.parameter.block_update.values; -storage_parameter_names = cellstr(wh.fn); -block_axis = find(strcmp(storage_parameter_names,"block_update"),1); - -if exist('linspecer','file') - curve_colors = linspecer(max(numel(storageNames),1)); -else - curve_colors = lines(max(numel(storageNames),1)); -end - -useBoundedLines = true; -usePolyfit = true; -polyfitOrderMax = 4; -fec_ber_threshold = 3.8e-3;%3.8e-3; -fit_coeff_by_storage = struct(); -required_sir_fec_by_storage = struct(); -for storage_idx = 1:numel(storageNames) - storageName = storageNames{storage_idx}; - fit_coeff_by_storage.(storageName) = cell(1,numel(block_updates)); - required_sir_fec_by_storage.(storageName) = nan(1,numel(block_updates)); -end - -for block_idx = 1:numel(block_updates) - block_update = block_updates(block_idx); - - figure(3000 + block_idx); clf; hold on - for storage_idx = 1:numel(storageNames) - storageName = storageNames{storage_idx}; - curve_color = curve_colors(storage_idx,:); - result = wh.sto.(storageName); - - if ~isempty(block_axis) && size(result,block_axis) == numel(block_updates) - result_subs = repmat({':'},1,ndims(result)); - result_subs{block_axis} = block_idx; - result_for_block = result(result_subs{:}); - result_for_block = result_for_block(:).'; - else - result_for_block = result(:).'; - end - - % Each stored entry is a package cell containing one or more occurrences. - ber_all = cell(1,numel(result_for_block)); - for result_idx = 1:numel(result_for_block) - packageCell = result_for_block{result_idx}; - if isempty(packageCell) - ber_all{result_idx} = NaN; - continue - end - - ber_values = nan(1,numel(packageCell)); - for package_idx = 1:numel(packageCell) - if isstruct(packageCell{package_idx}) && isfield(packageCell{package_idx},"metrics") - ber_values(package_idx) = packageCell{package_idx}.metrics.BER; - end - end - ber_all{result_idx} = ber_values; - end - - maxPackages = max(cellfun(@numel,ber_all)); - ber_mat = nan(maxPackages,numel(ber_all)); - for k = 1:numel(ber_all) - ber_mat(1:numel(ber_all{k}),k) = ber_all{k}; - end - - % Match the BER-over-SIR plot cleanup: remove high BER points and outliers per SIR. - for col = 1:size(ber_mat,2) - colData = ber_mat(:,col); - colData(~isfinite(colData) | colData >= 0.1) = NaN; - validColData = colData(isfinite(colData)); - if isempty(validColData) - ber_mat(:,col) = NaN; - continue - end - outliers = isoutlier(validColData); - validColData(outliers) = NaN; - colData(isfinite(colData)) = validColData; - ber_mat(:,col) = colData; - end - - ber = mean(ber_mat,1,"omitnan"); - ber_min = nan(1,numel(ber)); - ber_max = nan(1,numel(ber)); - for k = 1:numel(ber) - ber_here = ber_mat(:,k); - ber_here = ber_here(isfinite(ber_here)); - if isempty(ber_here) - continue - end - ber_min(k) = min(ber_here); - ber_max(k) = max(ber_here); - end - - x = sir_values; - if numel(x) ~= numel(ber) - x = 1:numel(ber); - end - - ber_by_storage.(storageName){block_idx} = ber; - ber_mat_by_storage.(storageName){block_idx} = ber_mat; - - for k = 1:numel(x) - scatter(repmat(x(k),maxPackages,1),ber_mat(:,k), ... - 30, ... - 'Marker','.', ... - 'MarkerEdgeColor',curve_color, ... - 'MarkerFaceColor',curve_color, ... - 'HandleVisibility','off'); - end - - valid = isfinite(x) & isfinite(ber); - if ~any(valid) - continue - end - - % if useBoundedLines && exist("boundedline","file") - % y_lower = max(ber - ber_min,0); - % y_upper = max(ber_max - ber,0); - % y_bounds = [y_lower(:), y_upper(:)]; - % - % [hl, hp] = boundedline(x(valid).',ber(valid).',y_bounds(valid,:), ... - % 'alpha', 'transparency', 0.08, ... - % 'cmap', curve_color, ... - % 'nan', 'fill', ... - % 'orientation', 'vert'); - % set(hl, ... - % 'LineStyle','none', ... - % 'Marker','none', ... - % 'HandleVisibility','off'); - % set(hp, ... - % 'HandleVisibility','off', ... - % 'LineStyle','none'); - % end - - scatter(x(valid),ber(valid), ... - 20, ... - 'Marker','o', ... - 'MarkerEdgeColor',curve_color, ... - 'MarkerFaceColor',curve_color, ... - 'LineWidth',1, ... - 'DisplayName',storageName); - - fit_mask = valid & ber > 0; - if usePolyfit && nnz(fit_mask) >= 2 - fit_order = min(polyfitOrderMax,nnz(fit_mask)-1); - fit_coeff = polyfit(x(fit_mask),log10(ber(fit_mask)),fit_order); - x_fit = linspace(min(x(fit_mask)),max(x(fit_mask)),300); - y_fit = 10.^polyval(fit_coeff,x_fit); - fit_coeff_by_storage.(storageName){block_idx} = fit_coeff; - - sir_req = NaN; - threshold_mask = isfinite(y_fit) & y_fit <= fec_ber_threshold; - if any(threshold_mask) - first_threshold_idx = find(threshold_mask,1,"first"); - if first_threshold_idx == 1 - sir_req = x_fit(first_threshold_idx); - else - x_pair = x_fit(first_threshold_idx-1:first_threshold_idx); - y_pair = log10(y_fit(first_threshold_idx-1:first_threshold_idx)); - if all(isfinite(y_pair)) && diff(y_pair) ~= 0 - sir_req = interp1(y_pair,x_pair,log10(fec_ber_threshold), ... - "linear","extrap"); - else - sir_req = x_fit(first_threshold_idx); - end - end - end - required_sir_fec_by_storage.(storageName)(block_idx) = sir_req; - - plot(x_fit,y_fit, ... - 'LineWidth',1.1, ... - 'LineStyle','--', ... - 'Color',curve_color, ... - 'HandleVisibility','off'); - end - end - - title(sprintf("BER over SIR, block update = %g",block_update)); - xlabel("SIR (dB)"); - ylabel("BER"); - - yline(2.2e-4, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - yline(3.8e-3, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - yline(2e-2, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - ylim([9e-5, 0.1]); - - set(gca, "YScale", "log"); - xlim([15,45]); - grid on; - box on; - legend("Location", "best", "Interpreter", "none"); -end - -%% Required SIR to reach FEC over parallelization - -figure(5000); clf; hold on -for storage_idx = 1:numel(storageNames) - storageName = storageNames{storage_idx}; - curve_color = curve_colors(storage_idx,:); - required_sir = required_sir_fec_by_storage.(storageName); - valid = isfinite(block_updates) & isfinite(required_sir); - - if ~any(valid) - continue - end - - plot(block_updates(valid),required_sir(valid), ... - 'LineWidth',1.4, ... - 'LineStyle','-', ... - 'Marker','o', ... - 'MarkerSize',5, ... - 'Color',curve_color, ... - 'DisplayName',storageName); -end - -set(gca,'XScale','log'); -xticks(block_updates); -xticklabels(string(block_updates)); -grid on -box on -xlabel("Parallelization / block update"); -ylabel(sprintf("Required SIR at BER = %.1e (dB)",fec_ber_threshold)); -title("Required SIR to reach FEC over parallelization"); -legend("Location","best","Interpreter","none"); - - -%% - - -function whMerged = mergeDataStorages(varargin) -% mergeDataStorages merges compatible DataStorage objects by physical indices. -% Existing duplicate entries are concatenated when both values are package cells. - -whList = varargin; -templateWh = whList{1}; -paramNames = cellstr(templateWh.fn); - -mergedParams = struct(); -for param_idx = 1:numel(paramNames) - paramName = paramNames{param_idx}; - values = templateWh.parameter.(paramName).values(:).'; - - for wh_idx = 2:numel(whList) - curWh = whList{wh_idx}; - if ~isequal(sort(cellstr(curWh.fn)),sort(paramNames)) - error("mergeDataStorages:ParameterMismatch", ... - "All warehouses must use the same parameter names."); - end - if ~isfield(curWh.parameter,paramName) - error("mergeDataStorages:ParameterMismatch", ... - "Warehouse %d does not contain parameter '%s'.",wh_idx,paramName); - end - - newValues = curWh.parameter.(paramName).values(:).'; - for value_idx = 1:numel(newValues) - if ~ismember(newValues(value_idx),values) - values(end+1) = newValues(value_idx); %#ok - end - end - end - - if strcmp(paramName,"block_update") && isnumeric(values) - values = sort(values); - end - - mergedParams.(paramName) = values; -end - -whMerged = DataStorage(mergedParams); - -for wh_idx = 1:numel(whList) - curWh = whList{wh_idx}; - curStorageNames = fieldnames(curWh.sto); - - for storage_idx = 1:numel(curStorageNames) - storageName = curStorageNames{storage_idx}; - if ~isfield(whMerged.sto,storageName) - whMerged.addStorage(storageName); - end - - for lin_idx = 1:numel(curWh.sto.(storageName)) - storedValue = curWh.sto.(storageName){lin_idx}; - if isempty(storedValue) - continue - end - - [physValues,physNames] = curWh.getPhysIndicesByLinIndex(lin_idx); - physNames = cellfun(@char,physNames,'UniformOutput',false); - targetSubscripts = cell(1,numel(whMerged.fn)); - - for param_idx = 1:numel(whMerged.fn) - paramName = char(whMerged.fn(param_idx)); - sourceParamIdx = find(strcmp(physNames,paramName),1); - if isempty(sourceParamIdx) - error("mergeDataStorages:ParameterMismatch", ... - "Storage entry is missing parameter '%s'.",paramName); - end - targetSubscripts{param_idx} = whMerged.getIndexByPhys(paramName,physValues{sourceParamIdx}); - end - - if isscalar(targetSubscripts) - targetLinIdx = targetSubscripts{1}; - else - targetLinIdx = sub2ind(whMerged.getStorageSize(),targetSubscripts{:}); - end - - existingValue = whMerged.sto.(storageName){targetLinIdx}; - whMerged.sto.(storageName){targetLinIdx} = mergeStoredValue(existingValue,storedValue); - end - end -end -end - -function out = mergeStoredValue(existingValue,newValue) -if isempty(existingValue) - out = newValue; -elseif iscell(existingValue) && iscell(newValue) - out = [existingValue(:); newValue(:)].'; -else - out = newValue; -end -end - - diff --git a/projects/Diss/MPI_revisit/plot_mpi_run_id_timesignal.m b/projects/Diss/MPI_revisit/plot_mpi_run_id_timesignal.m new file mode 100644 index 0000000..bf10f60 --- /dev/null +++ b/projects/Diss/MPI_revisit/plot_mpi_run_id_timesignal.m @@ -0,0 +1,55 @@ +%% Quick plot of one MPI run_id time signal + +clear; clc; + +run_id = 3933; +savePath = "W:\labdata\ECOC Silas\ecoc_2025\"; + +db = DBHandler("type", "mysql", "dataBase", "labor"); + +fp = QueryFilter(); +fp.where('Runs', 'run_id', 'EQUALS', run_id); + +[dataTable, sql_query] = db.queryDB(fp, db.getTableFieldNames('Runs')); + +if isempty(dataTable) + error("No run found for run_id %d.", run_id); +end + +dataTable = dataTable(1, :); +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); + +Symbols = load(char(savePath + string(dataTable.tx_symbols_path))); +Symbols = Symbols.Symbols; + +Scpe_sig_raw = load(char(savePath + string(dataTable.rx_raw_path))); +Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; +Scpe_sig_raw = Scpe_sig_raw.normalize("mode", "rms"); + +figure(run_id); clf; +showLevelScatter(Scpe_sig_raw, Symbols, ... + "fsym", fsym, ... + "syncFs", 2*fsym, ... + "fignum", run_id, ... + "normalize", true, ... + "debug_plots", false, ... + "showPlot", true, ... + "showStdAnnotations", true, ... + "yLimits", [-2.5 2.5], ... + "xLimits", [0 25], ... + "scatterAlpha", 0.25, ... + "scatterSize", 1, ... + "avgLineMaxPoints", 500, ... + "avgLineSmoothWindow", 5); + +title(sprintf("run\\_id %d, PAM %.0f, %.0f m, SIR %.2f dB", ... + run_id, M, dataTable.interference_path_length, dataTable.sir)); + +fprintf("run_id: %d\n", run_id); +fprintf("PAM: %.0f\n", M); +fprintf("symbolrate: %.2f GBd\n", fsym*1e-9); +fprintf("path length: %.0f m\n", dataTable.interference_path_length); +fprintf("SIR: %.2f dB\n", dataTable.sir); +fprintf("v_bias: %.3g V\n", dataTable.v_bias); +fprintf("P_rx: %.2f dBm\n", dataTable.power_pd_in); diff --git a/workerError.mat b/workerError.mat index fe91e0e..a369927 100644 Binary files a/workerError.mat and b/workerError.mat differ