diff --git a/Classes/00_signals/Opticalsignal.m b/Classes/00_signals/Opticalsignal.m index 25900a4..b39511e 100644 --- a/Classes/00_signals/Opticalsignal.m +++ b/Classes/00_signals/Opticalsignal.m @@ -5,6 +5,7 @@ classdef Opticalsignal < Signal properties nase lambda + polrot end @@ -18,6 +19,7 @@ classdef Opticalsignal < Signal options.logbook options.lambda options.nase + options.polrot end obj = obj@Signal(signal); diff --git a/Classes/00_signals/Signal.m b/Classes/00_signals/Signal.m index f212a02..a21e71c 100644 --- a/Classes/00_signals/Signal.m +++ b/Classes/00_signals/Signal.m @@ -108,6 +108,7 @@ classdef Signal options.logbook options.nase options.lambda + options.polrot end fn = fieldnames(options); @@ -122,7 +123,7 @@ classdef Signal %convert to optical - o_sig = Opticalsignal(obj.signal,"fs",obj.fs,"lambda",options.lambda,"logbook",obj.logbook,"nase",options.nase); + o_sig = Opticalsignal(obj.signal,"fs",obj.fs,"lambda",options.lambda,"logbook",obj.logbook,"nase",options.nase,"polrot",options.polrot); elseif isa(obj,'Informationsignal') @@ -141,7 +142,7 @@ classdef Signal arguments obj - options.fignum + options.fignum = randi(1000) options.displayname = []; options.timeframe = 0; options.clear = 0; @@ -187,7 +188,7 @@ classdef Signal ylabel('Amplitude'); end - + % Add legend if not already present if isempty(get(gca, 'Legend')) legend; @@ -269,6 +270,7 @@ classdef Signal SignalPower = [obj.power]; Nase = [0]; SignalCopy = obj.signal; + SignalCopy = []; ModifierName = class(CallingModifier); @@ -342,83 +344,151 @@ classdef Signal arguments obj - options.fignum + options.fignum = 2025 options.displayname = ""; options.color = []; + options.linestyle = '-'; options.normalizeToNyquist = 0; + options.addDCoffset = 0; + options.normalizeToDC = 0; options.normalizeTo0dB = 0; options.max_num_lines = []; % Leave empty or omit to disable line rotation options.fft_length = []; + % --- NEW options --- + options.useWavelengthAxis (1,1) logical = false % plot x-axis in wavelength + options.lambda0_nm (1,1) double = 1310 % center wavelength [nm] end if isempty(options.fft_length) options.fft_length = 2^(nextpow2(length(obj.signal))-9); end + + if options.normalizeToNyquist == 0 - [p_lin,w] = pwelch(obj.signal,hanning(options.fft_length),options.fft_length/2,options.fft_length,obj.fs,"centered","power","mean"); - w = w.*1e-9; + [p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ... + options.fft_length/2, options.fft_length, ... + obj.fs, "centered", "power", "mean"); + f_GHz = f_Hz*1e-9; % keep frequency vector for frequency axis else - [p_lin,w] = pwelch(obj.signal,hanning(options.fft_length),options.fft_length/2,options.fft_length,"centered","power","mean"); + [p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ... + options.fft_length/2, options.fft_length, ... + "centered", "power", "mean"); + % In normalized mode, pwelch returns rad/sample centered on 0. + % We'll keep f_rad for the x-axis in that mode. end + % p_lin = movmean(p_lin,4); + if options.normalizeTo0dB - p_lin = p_lin./ max(p_lin); + p_lin = p_lin ./ max(p_lin); p_dbm = 10*log10(p_lin); % normalized to 0 dB - ylab = "normalized to 0 dB"; + ylab = "Normalized PSD"; else p_dbm = 10*log10(p_lin); ylab = "Power (dB/Hz)"; end + % --- If requested, build wavelength axis from frequency offset --- + if options.useWavelengthAxis && options.normalizeToNyquist == 0 + c = physconst('LightSpeed'); % [m/s] + lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m] + f_c = c / lambda0_m; % carrier frequency [Hz] + + % exact mapping + f_abs = f_c + f_Hz; % absolute frequency [Hz] + lambda_m = c ./ f_abs; % wavelength [m] + lambda_nm = lambda_m * 1e9; % wavelength [nm] + + % assign axis + x_vec = lambda_nm(:); + x_label = "Wavelength [nm]"; + + % Sort to ensure axis is ascending + [x_vec, sortIdx] = sort(x_vec, 'ascend'); + p_dbm = p_dbm(sortIdx, :); + else + % Frequency or normalized axes + if options.normalizeToNyquist == 0 + x_vec = f_GHz; + x_label = "Frequency in GHz"; + else + x_vec = f_rad; % normalized frequency in rad/sample + x_label = "Normalized Frequency"; + end + end + figure(options.fignum); ax = gca; hold on - if isempty(options.color) - hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1); - else - hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color); + p_dbm = p_dbm+options.addDCoffset; + + if options.normalizeToDC + [~,min_idx]=min(abs(f_GHz)); + pow_at_dc = p_dbm(min_idx); + p_dbm = p_dbm-pow_at_dc; + end + % p_dbm = movmean(p_dbm,10); + + for s = 1:min(size(p_dbm)) + if isempty(options.color) + plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1); + else + plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle); + end end - % If user wants to limit the number of lines, check and remove old lines + % Limit number of lines if requested if ~isempty(options.max_num_lines) && options.max_num_lines > 0 allLines = findall(ax, 'Type', 'Line'); if length(allLines) > options.max_num_lines - % Sort lines by creation order. Usually, the oldest lines appear first in allLines. - % If needed, you can sort by UserData or other criteria. numToRemove = length(allLines) - options.max_num_lines; delete(allLines(1:numToRemove)); end end - if options.normalizeToNyquist == 0 - xlabel("Frequency in GHz"); - edgetick = 2^(nextpow2(obj.fs*1e-9)); - xticks(-edgetick:16:edgetick); - xlim([100*round(min(w)/100,1)-10, 100*round(max(w)/100,1)+10]) - xlim([-128 128]);%256GSa/s + % Axis labels and limits + xlabel(x_label); + + if options.useWavelengthAxis && options.normalizeToNyquist == 0 + xlim([min(x_vec) max(x_vec)]); else - xlabel("Normalized Frequency"); - xlim([-pi, pi]); + if options.normalizeToNyquist == 0 + % Keep your existing freq handling (you can fine-tune as needed) + % xlim([-128 128]); % example for 256 GSa/s if desired + xlim([min(x_vec) max(x_vec)]); + else + xlim([-pi, pi]); + end end ylabel(ylab); - try - ylim([max(min(floor(min(p_dbm))-3, ax.YLim(1)),-40), min(max(ceil(max(p_dbm))+3, ax.YLim(2)),10)]); - catch - ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]); + % --- Y-Axis scaling (auto with margin) --- + y_min = min(p_dbm(:)); + y_max = max(p_dbm(:)); + + % Add 5% dynamic range margin on both sides + y_range = y_max - y_min; + if y_range == 0 + y_range = 10; % fallback if flat end - ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]); - yticks(-200:10:10); - grid on; grid minor; - legend - % legend('Interpreter','none'); + + y_margin = 0.05 * y_range; + ylim([y_min - y_margin, y_max + y_margin]); + + % Set ticks automatically, avoid overpopulation + try + yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10)))))); + end + grid on; + end + function move_it_spectrum(obj,options) arguments obj @@ -546,7 +616,7 @@ classdef Signal options.unit power_notation = power_notation.dBm end - pow = mean(abs(obj.signal).^2); + pow = sum(mean(abs(obj.signal).^2)); switch options.unit case power_notation.dBm @@ -661,9 +731,9 @@ classdef Signal options.fs_ref = 0; options.debug_plots = 0; end - - - + + + S = {}; inverted = -1; sequenceFound = 0; @@ -693,10 +763,10 @@ classdef Signal return end - if mean(w) > 10 - warning('No sync possible, check code of findpeaks! Look at correlation') + pkpos = sort(pkpos); + + if mean(w) > 10 || mean(p) > 10 return - else sequenceFound = 1; end @@ -707,33 +777,33 @@ classdef Signal findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum,'SortStr','descend') end - + shifts = lags(pkpos); sequenceStarts = shifts; - % shifts = shifts(shifts>=0); + shifts = shifts(shifts>=0); if numel(shifts) > 0 - + %Cut occurences of ref signal from signal (only positive shifts) if all(sign(co(pkpos))==-1) inverted = 1; end - + for c = shifts sig = obj.delay(-c,'mode','samples'); - sig.signal = sig.signal(1:length(b)) .* -inverted; + sig.signal = sig.signal(1:length(b));% .* -inverted; S{end+1,1} = sig; end - - %return/keep the sinal with the highest correlation (only within positive shifts) - [~,idx]=max(pks); - obj.signal = S{idx}.signal; - %put signal with highest corr. to first index in S array - swap = S{1}; - S{1} = S{idx}; - S{idx} = swap; - + + % %return/keep the sinal with the highest correlation (only within positive shifts) + % [~,idx]=max(pks); + % obj.signal = S{idx}.signal; + % %put signal with highest corr. to first index in S array + % swap = S{1}; + % S{1} = S{idx}; + % S{idx} = swap; + for c = 1:numel(shifts) S{c}.logbook = []; end @@ -762,10 +832,6 @@ classdef Signal end - - - - %% function obj = filter(obj,a,b) @@ -912,11 +978,11 @@ classdef Signal elseif mode == 1 % generate eye diagram using histogram - maxA = max(sig(100:end-100))*2; - minA = min(sig(100:end-100))*2; + maxA = max(sig(100:end-100))*1.3; + minA = min(sig(100:end-100))*1.3; - % maxA = 0.0015; - % minA = 0; + maxA = 0.12; + minA = -0.08; difference= maxA-minA; @@ -927,7 +993,7 @@ classdef Signal hist_data(:,n)=flip(nn.'); %without flip, the eye is upside down :-( end - ax = gca; + plot_data = 20*log10(hist_data); plot_data(plot_data==-Inf) = 0; @@ -944,27 +1010,29 @@ classdef Signal if isa(obj,'Opticalsignal') title(['Optical Eye ',options.displayname]) ylabel("Power in mW"); - y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,16)); + y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,6)); min_ = min(abs(obj.signal(100:end-100)).^2); max_ = abs(max(obj.signal(100:end-100)).^2); elseif isa(obj,'Electricalsignal') title(['Electrical Eye ',options.displayname]) ylabel("Voltage in V"); - y_tickstring = string(linspace(maxA,minA,16)); + y_tickstring = string(linspace(maxA,minA,6)); min_ = min(obj.signal(100:end-100)); max_ = abs(max(obj.signal(100:end-100))); else title(['Digital Eye ',options.displayname]) ylabel("Digital Signal Amplitude"); - y_tickstring = string(linspace(maxA,minA,16)); + y_tickstring = string(linspace(maxA,minA,6)); min_ = min(obj.signal(100:end-100)); max_ = abs(max(obj.signal(100:end-100))); end xlabel('Time in ps') + + % add information - if 1 + if 0 pwr_dbm = round(obj.power,3); pwr_lin = obj.power("unit",power_notation.W); @@ -1073,18 +1141,20 @@ classdef Signal end - yticks(linspace(0,histpoints,16)); - y_tickstring = sprintfc('%.2f', y_tickstring); - yticklabels(y_tickstring); + grid off - xticks(linspace(0,histpoints_horizontal,8)) - x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12); - xticklabels(x_tickstring); end + yticks(linspace(0,histpoints,6)); + y_tickstring = sprintfc('%.2f', y_tickstring); + yticklabels(y_tickstring); + xticks(linspace(0,histpoints_horizontal,6)) + x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12); + xticklabels(x_tickstring); + % end % disp('h'); diff --git a/Classes/01_transmit/ChannelFreqResp.m b/Classes/01_transmit/ChannelFreqResp.m index 0c9fda8..e6c8a2a 100644 --- a/Classes/01_transmit/ChannelFreqResp.m +++ b/Classes/01_transmit/ChannelFreqResp.m @@ -139,6 +139,8 @@ classdef ChannelFreqResp < handle fnew = linspace(0,fstarget/2,length(Target)/2+1); fnew = fnew(2:end-1); + + % Old frequency axis (should be much coarser) idx_old = find((obj.faxis > 0) .* (obj.faxis < fstarget/2)); %positions of all Frequencies smaller than fs/2 int_fold = obj.faxis(idx_old); %old frequencies from 0 to fs/2 @@ -149,6 +151,10 @@ classdef ChannelFreqResp < handle % interpolate the frequency response that had a coarse frequency resolution (e.g. 256 bins) to the current frequency resolution (e.g. 21843 bins) iH = interp1(int_fold, real(H_inv(idx_old)) ,fnew, 'linear') + 1i*interp1(int_fold, imag(H_inv(idx_old)) ,fnew, 'linear'); + if 0 + figure(7);hold on;plot(fnew,20*log10(abs(iH))) + end + % set all NaN values to the fist/ last non-NaN value nH = find(~isnan(iH),1,'first'); iH(1:nH)=iH(nH); @@ -171,6 +177,8 @@ classdef ChannelFreqResp < handle % five frequencies -> should be the vaue at f=0=DC component? iH = iH./mean(abs(iH)); %why 1:5?? + dc = 20*log10(abs(mean(abs(iH(1:5))))); + % set maximum amplification % set als values higher than hmax to hmax and keep the % phase information by multiplication with respective @@ -206,7 +214,7 @@ classdef ChannelFreqResp < handle function plot(obj) - figure(55); + figure(); clf; Havg = obj.H; @@ -225,7 +233,7 @@ classdef ChannelFreqResp < handle xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on; - figure(56); + figure(); clf; @@ -246,7 +254,7 @@ classdef ChannelFreqResp < handle xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on; %%% plot for publication - figure(1234); + figure(101); hold all; box on; title('Magnitude Freq. Response'); @@ -269,6 +277,25 @@ classdef ChannelFreqResp < handle legend('Interpreter','latex') grid on; + % + figure(100); hold on; + Havg = obj.H; + Havg = Havg./max(Havg); + Hall = obj.H_all; + + for i = 1:size(Hall,1) + Hall(i,:) = Hall(i,:)./max(Hall(i,:)); + end + + %1) + col = cbrewer2('Paired',8); + hold all;box on;title('Magnitude Freq. Response'); + plot(obj.faxis/1e9, 20*log10(abs(Hall)),'linewidth',0.1,'LineStyle','-','Color',col(1,:),'HandleVisibility','off') ; + xlim([0.2 .5*max(obj.faxis)*1e-9]); + plot(obj.faxis/1e9, 20*log10(abs(Havg)),'LineWidth',2,'Color',col(2,:)); + grid on; + + end diff --git a/Classes/01_transmit/M8199B.m b/Classes/01_transmit/M8199B.m index 2519453..7a2a34e 100644 --- a/Classes/01_transmit/M8199B.m +++ b/Classes/01_transmit/M8199B.m @@ -17,7 +17,7 @@ classdef M8199B < AWG fdac = 256e9; - Lp_awg = Filter('filtdegree',4,"f_cutoff",75e9,"fs",fdac*options.kover,"filterType",filtertypes.butterworth,"active",true); + Lp_awg = Filter('filtdegree',3,"f_cutoff",75e9,"fs",fdac*options.kover,"filterType",filtertypes.gaussian,"active",true); obj = obj@AWG("fdac",fdac,"dac_min",dac_min,"dac_max",dac_max,"lpf_active",1,"H_lpf",Lp_awg,"kover",options.kover,... "bit_resolution",5.5,"normalize2dac",1,"upsampling_method","samplehold"); diff --git a/Classes/01_transmit/PAMmapper.m b/Classes/01_transmit/PAMmapper.m index 6ba8558..a44d8cd 100644 --- a/Classes/01_transmit/PAMmapper.m +++ b/Classes/01_transmit/PAMmapper.m @@ -29,7 +29,7 @@ classdef PAMmapper obj.levels = obj.get_levels(); - obj.scaling = rms(obj.get_levels()); + obj.scaling = obj.get_scaling;%rms(obj.get_levels()); obj.eth_style = options.eth_style; @@ -45,7 +45,7 @@ classdef PAMmapper signal_in = signal_in.logbookentry(lbdesc,obj); out = signal_in; else - out = signal_in; + out = obj.map_(signal_in); end end @@ -283,6 +283,21 @@ classdef PAMmapper end end + function scaling = get_scaling(obj) + switch obj.M + case 2 + scaling = 1; + case 4 + scaling = sqrt(5); + case 6 + scaling = sqrt(10); + case 8 + scaling = sqrt(21); + case 16 + scaling = 1; + end + end + function [data_out] = demap_(obj,data_in) data_in= data_in'; @@ -328,8 +343,9 @@ classdef PAMmapper if ~obj.eth_style - data_in = data_in/(sqrt(mean(abs(data_in).^2))); + % data_in = data_in/(sqrt(mean(abs(data_in).^2))); data_in = data_in*sqrt(10); + %data_in = data_in*sqrt((5^2 + 3^2 + 1^2 + 5^2 + 3^2 + 1^2)/6); if size(data_in,2) > 1 data_in = data_in.'; @@ -449,7 +465,7 @@ classdef PAMmapper function [Signal_out] = quantize(obj,Signal_in) constellation = obj.get_levels(); - constellation = constellation ./ rms(constellation); + constellation = constellation ./ obj.scaling; issignalclass = 0; if isa(Signal_in,'Signal') @@ -480,7 +496,9 @@ classdef PAMmapper end function bitmap = showBitMapping(obj) - bitmap = obj.demap([obj.levels ./ obj.scaling]'); + + bitmap = obj.demap((obj.levels ./ obj.scaling)'); + end end diff --git a/Classes/01_transmit/PAMsource.m b/Classes/01_transmit/PAMsource.m index f1f8e5a..d29f7f7 100644 --- a/Classes/01_transmit/PAMsource.m +++ b/Classes/01_transmit/PAMsource.m @@ -136,6 +136,13 @@ classdef PAMsource %%%%%% Duobinary %%%%%%%%%%% + if obj.db_precode + obj.duobinary_mode = db_mode.db_precoded; + end + if obj.db_encode + obj.duobinary_mode = db_mode.db_encoded; + end + switch obj.duobinary_mode case db_mode.no_db @@ -164,7 +171,7 @@ classdef PAMsource %%%%% Pulse-forming %%%%%% if obj.applypulseform %%% MY CODE - if 0 + if 1 digi_sig = obj.pulseformer.process(symbols); else diff --git a/Classes/02_etc/Amplifier.m b/Classes/02_etc/Amplifier.m index 31324e2..71db478 100644 --- a/Classes/02_etc/Amplifier.m +++ b/Classes/02_etc/Amplifier.m @@ -108,7 +108,7 @@ classdef Amplifier if obj.gain_mode == gain_mode.output_power %get linear gain for output power mode - pow_in = mean(abs(xin.^2)) ; % lin input power + pow_in = sum(mean(abs(xin.^2)),2) ; % lin input power pow_out = 10^(obj.amplification_db/10 - 3) ; % dBm to lin a_lin = sqrt(pow_out/pow_in) ; diff --git a/Classes/02_optical/EML.m b/Classes/02_optical/EML.m index 96765da..32a902c 100644 --- a/Classes/02_optical/EML.m +++ b/Classes/02_optical/EML.m @@ -70,7 +70,7 @@ classdef EML [signalclass_in.signal,obj] = obj.process_(signalclass_in.signal); % cast the inform. signal to electrical signal - signalclass_in = Opticalsignal(signalclass_in,"fs",obj.fsimu,"logbook",signalclass_in.logbook,"lambda",obj.lambda*1e-9,"nase",0); + signalclass_in = Opticalsignal(signalclass_in,"fs",obj.fsimu,"logbook",signalclass_in.logbook,"lambda",obj.lambda*1e-9,"nase",0,"polrot",0); % append to logbook lbdesc = [num2str(obj.lambda),' nm Laser with ',num2str(obj.power),' dBm P_out. Linew.=',num2str(obj.linewidth*1e-6),' MHz. Modulation mode: ',char(obj.mode) ]; diff --git a/Classes/02_optical/Fiber.m b/Classes/02_optical/Fiber.m index 55f8ec2..fbdca1b 100644 --- a/Classes/02_optical/Fiber.m +++ b/Classes/02_optical/Fiber.m @@ -1,189 +1,171 @@ classdef Fiber - %FIBER Summary of this class goes here - % Detailed explanation goes here + % Fiber: Simulate optical fiber signal propagation using + % the split-step Fourier method (SSFM). properties + % Simulation sampling frequency [Hz] fsimu + % Fiber length [m] fiber_length + % Attenuation coefficient [dB/km] alpha + % Dispersion parameter D [s/m^2] D + % Dispersion slope [s/m^3] Dslope + % Reference wavelength [m] lambda0 + % Nonlinear coefficient [1/(W·m)] gamma + % Maximum allowed nonlinear phase change per step [rad] dphimax + % Derived parameters: + % Second-order dispersion coefficient β2 [s^2/m] b2 + % Third-order dispersion coefficient β3 [s^3/m] b3 + % Linear attenuation constant [1/m] alpha_lin + % Frequency-domain linear operator per unit length linstep end methods function obj = Fiber(options) - %FIBER Construct an instance of this class - % Detailed explanation goes here + % Constructor: initialize fiber physical and simulation parameters. arguments - options.fsimu - options.fiber_length = 0 - options.alpha = 0.2 - options.D = 17 - options.Dslope = 0.06 - options.lambda0 = 1550 - options.gamma = 0 - options.dphimax = 5e-3 + options.fsimu % Sampling frequency [Hz] + options.fiber_length = 0 % Fiber length [km] + options.alpha = 0.2 % Attenuation [dB/km] + options.D = 17 % Dispersion D parameter [ps/(nm·km)] + options.Dslope = 0.06 % Dispersion slope [ps/(nm^2·km)] + options.lambda0 = 1550 % Reference wavelength [nm] + options.gamma = 0 % Nonlinear coefficient [1/(W·km)] + options.dphimax = 5e-3 % Max phase step [rad] end - obj.fsimu = options.fsimu; - obj.fiber_length = options.fiber_length*1000; %km - obj.alpha = options.alpha; - obj.D = options.D*1e-6; - obj.Dslope = options.Dslope*1e3; - obj.lambda0 = options.lambda0*1e-9; - obj.gamma = options.gamma; - obj.dphimax = options.dphimax; - - - + % Assign inputs to object properties, converting units to SI. + obj.fsimu = options.fsimu; + obj.fiber_length = options.fiber_length * 1e3; % km → m + obj.alpha = options.alpha; + obj.D = options.D * 1e-6; % ps/(nm·km) → s/(m·m) + obj.Dslope = options.Dslope * 1e3; % ps/(nm^2·km) → s/m^2 + obj.lambda0 = options.lambda0 * 1e-9; % nm → m + obj.gamma = options.gamma; % 1/(W·km) (assumed → 1/(W·m) internally) + obj.dphimax = options.dphimax; end - function signalclass_out = process(obj,signalclass_in) + function signalclass_out = process(obj, signalclass_in) + % process: Apply fiber propagation to input signal class. + % Calls the internal SSFM routine and logs the operation. - % actual processing of the signal (steps 1. - 3.) - signalclass_in = obj.process_(signalclass_in); + % Run internal split-step propagation + signalclass = obj.process_(signalclass_in); - % append to logbook - lbdesc = 'Fiber '; - signalclass_in = signalclass_in.logbookentry(lbdesc); - - % write to output - signalclass_out = signalclass_in; + % Add entry to logbook for tracking + signalclass = signalclass.logbookentry('Fiber '); + % Return processed signal class + signalclass_out = signalclass; end - function opt_sig = process_(obj,opt_sig) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here + function opt_sig = process_(obj, opt_sig) + % process_: Internal routine for one-step fiber propagation. + % Computes linear and (optionally) nonlinear effects. - signal = opt_sig.signal; + % Extract time-domain field and wavelength + signal = opt_sig.signal; lambda_signal = opt_sig.lambda; - obj.D = obj.D + (lambda_signal-obj.lambda0)*obj.Dslope; - - obj.b2 = -obj.D*lambda_signal^2/(2*pi*Constant.LightSpeed); - obj.b3 = ((lambda_signal.^2/(2*pi*Constant.LightSpeed)).^2*obj.Dslope); - obj.alpha_lin = obj.alpha/10*log(10)/1000; - + % Update dispersion parameter D for current wavelength + obj.D = obj.D + (lambda_signal - obj.lambda0) * obj.Dslope; + + % Compute dispersion coefficients (β2, β3) + obj.b2 = -obj.D * lambda_signal^2 / (2 * pi * Constant.LightSpeed); + obj.b3 = (lambda_signal^2 / (2 * pi * Constant.LightSpeed))^2 * obj.Dslope; + + % Convert attenuation from dB/km to linear 1/m + obj.alpha_lin = obj.alpha / 10 * log(10) / 1e3; + + % Build frequency axis for FFT operations N = length(signal); - faxis = linspace(-obj.fsimu/2,obj.fsimu/2,N+1); - faxis = ifftshift(faxis(:,1:end-1)); - faxis = faxis'; - - obj.linstep = -obj.alpha_lin/2 - 2*1j*pi^2*obj.b2*faxis.^2 - 4/3*1j*pi^3*obj.b3*faxis.^3; + faxis = linspace(-obj.fsimu/2, obj.fsimu/2, N+1); + faxis = faxis(1:end-1); % drop redundant endpoint + faxis = ifftshift(faxis)'; % center zero frequency - if 0 - H = exp((obj.linstep)*obj.fiber_length); - - figure(222) - hold on - plot(faxis.*1e-9,abs(real(Y)),'LineStyle','-','DisplayName','Abs(real) part of complex TF'); - xlabel("Frequency in GHz") - ylabel("$ R|(H(\omega, L))|$") - end + % Define linear operator per meter in frequency domain + obj.linstep = -obj.alpha_lin/2 ... % half-step loss + - 1j*2*pi^2*obj.b2 .* faxis.^2 ... % second-order dispersion + - 1j*(4/3)*pi^3*obj.b3 .* faxis.^3; % third-order dispersion + % Choose linear-only or nonlinear SSFM based on gamma if obj.gamma ~= 0 + % Full adaptive SSFM with nonlinear Schrödinger solver opt_out = obj.NLSE(signal); else - opt_out = ifft( fft(signal) .* exp(obj.linstep*obj.fiber_length) ); % only one linear step + % Single-step linear propagation in frequency domain + opt_out = ifft( fft(signal) .* exp(obj.linstep * obj.fiber_length) ); end - %TODO: attenuate nase ... - + % Update output field in signal class opt_sig.signal = opt_out; - end - function [yout] = NLSE(obj, xin) - - maxPow = obj.gamma.*max(abs(xin).^2); - - Leff = obj.dphimax / maxPow ; - - dz = Leff; + function yout = NLSE(obj, xin) + % NLSE: Solve nonlinear Schrödinger equation by adaptive split-step + % xin: input time-domain field + % Returns yout: output time-domain field after propagation + % Initial estimate of effective length per step + maxPow = obj.gamma * max(abs(xin).^2); + Leff = obj.dphimax / maxPow; + dz = Leff; z_prop = 0; - yout = fft(xin); - - yout = ((yout).*exp(obj.linstep*dz/2)); + % Apply initial half-step linear operator + yout = fft(xin) .* exp(obj.linstep * dz/2); + % Loop until full fiber length is reached while true - + % Inverse FFT to time domain for nonlinear phase shift yout = ifft(yout); - Leff = dz; - + % Nonlinear phase rotation per segment power = abs(yout).^2; - Hnl = exp( -1j*obj.gamma*power*Leff); - yout = yout .* Hnl; + Hnl = exp(-1j * obj.gamma * power * dz); + yout = yout .* Hnl; + % Accumulate propagation distance z_prop = z_prop + dz; - maxPow = obj.gamma*max(abs(yout).^2); - Leff = obj.dphimax/maxPow; - - dz_new = Leff; + % Compute adaptive step for next interval + maxPow = obj.gamma * max(abs(yout).^2); + dz_new = obj.dphimax / maxPow; + % If remaining length shorter than new step, finish loop if z_prop + dz_new > obj.fiber_length dz_new = obj.fiber_length - z_prop; - break + break; end - yout = fft(yout); - - yout = ((yout).*exp(obj.linstep*(dz/2+dz_new/2))); - - dz = dz_new; - + % Half-step linear operator bridging segments + yout = fft(yout) .* exp(obj.linstep * ((dz/2) + (dz_new/2))); + dz = dz_new; end - yout = fft(yout); - - yout = ((yout).*exp(obj.linstep*(dz/2+dz_new/2))); - + % Final propagation segment: combine half-steps and nonlinear + yout = fft(yout) .* exp(obj.linstep * ((dz/2) + (dz_new/2))); yout = ifft(yout); - Leff = dz; - + % Last nonlinear phase shift power = abs(yout).^2; + Hnl = exp(-1j * obj.gamma * power * dz_new); + yout = yout .* Hnl; - Hnl = exp( -1j*obj.gamma*power*Leff); - - yout = yout .* Hnl; - - yout = fft(yout); - - yout = ((yout).*exp(obj.linstep*(dz/2))); - + % Final half-step linear operator to complete SSFM + yout = fft(yout) .* exp(obj.linstep * (dz_new/2)); yout = ifft(yout); - - end - - end end - - - - - - - - - - - - - - - diff --git a/Classes/04_DSP/Coding/Duobinary.m b/Classes/04_DSP/Coding/Duobinary.m index 6f3460b..8590596 100644 --- a/Classes/04_DSP/Coding/Duobinary.m +++ b/Classes/04_DSP/Coding/Duobinary.m @@ -53,6 +53,7 @@ classdef Duobinary % bk(k+1) =mod(data(k)-bk(k),M); % end + % THIS WAS USED! for k = 2:numel(data) bk(k) = mod(data(k)-bk(k-1),M); end diff --git a/Classes/04_DSP/Equalizer/FFE.m b/Classes/04_DSP/Equalizer/FFE.m index 54d0b57..18442d6 100644 --- a/Classes/04_DSP/Equalizer/FFE.m +++ b/Classes/04_DSP/Equalizer/FFE.m @@ -3,24 +3,34 @@ classdef FFE < handle % 1) Training mode (stable performance when you use NLMS) % 2) Decision directed mode - % Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0); + %LMS: mu in order of 0.001 for acceptable convergence speed + %NLMS: mu in order of 0.01 for acceptable convergence speed + %RLS: mu is lambda -> 0.99 -> 1 (has a strong dependency on this! use a loop to find out best values) + + % FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode); properties sps % usually 2 order e + e_tr error + debug_struct len_tr mu_tr epochs_tr - mu_dd + adaption_technique % nlms, lms, rls + dd_mode % 1 or 0 to set DD-mode on or off + mu_dd %weight update in dd mode epochs_dd - constellation + P % covariance matrix of rls - decide + constellation % symbol constellation + + decide %wether to return the (hard) decisions or the result after FFE (soft) end methods @@ -34,6 +44,8 @@ classdef FFE < handle options.mu_tr = 0; options.epochs_tr = 5; + options.adaption_technique adaption_method = adaption_method.lms; + options.dd_mode = 1; options.mu_dd = 1e-5; options.epochs_dd = 5; @@ -59,10 +71,15 @@ classdef FFE < handle obj.constellation = unique(D.signal); + + delta = 0.05; + obj.P = (1/delta) * eye(obj.order); + % Training Mode training = 1; showviz = 0; obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz); + obj.e_tr = obj.e; % Decision Directed Mode n = X.length; @@ -71,7 +88,7 @@ classdef FFE < handle [signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz); % Output Signal - if obj.decide + if obj.decide X.signal = decision; else X.signal = signal; @@ -83,24 +100,25 @@ classdef FFE < handle Noi = X; Noi = X - D; - + end - function [y,d_hat] = equalize(obj,x,d,mio,epochs,N,training,showviz) + function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz) arguments - obj - x - d - mio - epochs - N - training + obj + x + d + mu + epochs + N + training showviz end x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)]; + lambda = mu; if training mask = ones(obj.order,1); @@ -111,8 +129,17 @@ classdef FFE < handle end mask = ones(obj.order,1); + maincursor_pos=ceil(length(obj.e)/2); + always_ideal_decision = 0; + save_debug = 0; + grad =0; + weight = 0; + update = 0; + + if mu == 0 || (~obj.dd_mode && ~training) + epochs = 1; + end - for epoch = 1 : epochs symbol = 0; for sample = 1 : obj.sps : N @@ -124,30 +151,75 @@ classdef FFE < handle y(symbol,1) = (obj.e.*mask).' * U; % Calculating output of LMS __ * | if training - d_hat(symbol,1) = d(symbol); + d_hat(symbol,1) = d(symbol); else - [~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point - d_hat(symbol,1) = obj.constellation(symbol_idx); + if ~always_ideal_decision + [~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point + d_hat(symbol,1) = obj.constellation(symbol_idx); + else + d_hat(symbol,1) = d(symbol); + end end - err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error + % err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error + err(symbol) = d_hat(symbol) - y(symbol); % Instantaneous error true_err(symbol) = y(symbol) - d(symbol); % Instantaneous error - if mio ~= 0 - obj.e = obj.e - (mio * err(symbol) * U) ; % Weight update rule of LMS - else - normalizationfactor = (U.' * U); - obj.e = obj.e - err(symbol) * U / normalizationfactor; % Weight update rule of NLMS + if training || obj.dd_mode + switch obj.adaption_technique + + case adaption_method.lms + + % mu used as update weight (suggestion: 0.001) + weight = mu; + grad = err(symbol) * U; + update = grad * weight; + obj.e = obj.e + update; + + case adaption_method.nlms + + % mu used as update weight (suggestion: 0.01-0.05; bit higher during tr) + normU = ((U.'*U)) + eps; + weight = mu / normU; + grad = err(symbol) * U; + update = grad * weight; + obj.e = obj.e + update; + + case adaption_method.rls + + + % RLS‐Gain: + denom = lambda + U.' * obj.P * U; + k = (obj.P * U) / denom; + + % Gewichtsupdate: + update = k * err(symbol); + obj.e = obj.e + update; + + % P-Matrix‐Update: + obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P)); + + + end end - obj.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error + + if save_debug + obj.debug_struct.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error + if training + obj.debug_struct.error_tr(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error + obj.debug_struct.update_tr(epoch,symbol) = update.'*update ./ rms(obj.e); + end + obj.debug_struct.main_cursor(epoch,symbol) = abs(obj.e(maincursor_pos)); + obj.debug_struct.mu_nlms(epoch,symbol) = weight; + obj.debug_struct.update_gradient(epoch,symbol) = grad.'*grad; + obj.debug_struct.update(epoch,symbol) = update.'*update ./ rms(obj.e); + end end end end - end -end - +end \ No newline at end of file diff --git a/Classes/04_DSP/Equalizer/FFE_DCremoval.m b/Classes/04_DSP/Equalizer/FFE_DCremoval.m index dafe65c..06d50c6 100644 --- a/Classes/04_DSP/Equalizer/FFE_DCremoval.m +++ b/Classes/04_DSP/Equalizer/FFE_DCremoval.m @@ -57,6 +57,8 @@ classdef FFE_DCremoval < handle obj.e = zeros(obj.order,1); obj.error = 0; + obj.dc_buffer_len = floor(obj.dc_buffer_len); + end function [X,Noi] = process(obj, X, D) @@ -155,16 +157,15 @@ classdef FFE_DCremoval < handle end end - if ~training - figure(1122) - hold on - scatter(1:numel(e_dc_save),e_dc_save,1,'.'); - scatter(1:numel(y),y,1,'.'); - - end + % if ~training + % figure(1122) + % hold on + % scatter(1:numel(e_dc_save),e_dc_save,1,'.'); + % % scatter(1:numel(y),y,1,'.'); + % + % end end end -end - +end \ No newline at end of file diff --git a/Classes/04_DSP/Equalizer/FFE_DFE.m b/Classes/04_DSP/Equalizer/FFE_DFE.m index 0841f8d..e896500 100644 --- a/Classes/04_DSP/Equalizer/FFE_DFE.m +++ b/Classes/04_DSP/Equalizer/FFE_DFE.m @@ -60,7 +60,7 @@ classdef FFE_DFE < handle end - function [X] = process(obj, X, D) + function [X,Noi] = process(obj, X, D) % actual processing of the signal (steps 1. - 3.) % 1 normalize RMS @@ -88,6 +88,9 @@ classdef FFE_DFE < handle X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym lbdesc = [num2str(obj.ffe_order),' tap FFE']; X = X.logbookentry(lbdesc); % append to logbook + + Noi = X; + Noi = X - D; end @@ -185,8 +188,6 @@ classdef FFE_DFE < handle obj.e = coeff(1:obj.ffe_order); obj.b = coeff(obj.ffe_order+1:end); - - end end diff --git a/Classes/04_DSP/Equalizer/Postfilter.m b/Classes/04_DSP/Equalizer/Postfilter.m index 8d09ef1..659801e 100644 --- a/Classes/04_DSP/Equalizer/Postfilter.m +++ b/Classes/04_DSP/Equalizer/Postfilter.m @@ -31,7 +31,7 @@ classdef Postfilter < handle end - function signalclass_out = process(obj,signalclass_in,noiseclass_in,options) + function [signalclass_out,noiseclass_out] = process(obj,signalclass_in,noiseclass_in,options) arguments obj @@ -55,7 +55,8 @@ classdef Postfilter < handle else end - + + noiseclass_in = noiseclass_in.filter(obj.coefficients,1); signalclass_in = signalclass_in.filter(obj.coefficients,1); % append to logbook @@ -64,7 +65,7 @@ classdef Postfilter < handle % write to output signalclass_out = signalclass_in; - + noiseclass_out = noiseclass_in; end function showFilter(obj,options) diff --git a/Classes/04_DSP/Sequence Detection/MLSE.m b/Classes/04_DSP/Sequence Detection/MLSE.m index 9576524..59ef211 100644 --- a/Classes/04_DSP/Sequence Detection/MLSE.m +++ b/Classes/04_DSP/Sequence Detection/MLSE.m @@ -6,6 +6,10 @@ classdef MLSE < handle DIR trellis_states duobinary_output + trellis_state_mode + trellis_exclusion + debug + scale_mode end methods (Access=public) @@ -19,7 +23,10 @@ classdef MLSE < handle options.DIR double = [1]; options.trellis_states double = [-3 -1 1 3]; options.duobinary_output logical = false; - + options.trellis_state_mode = 2; + options.trellis_exclusion = 0; + options.scale_mode = 2; + options.debug = 0; end % @@ -29,28 +36,64 @@ classdef MLSE < handle obj.(fn{n}) = options.(fn{n}); end end - - % do more stuff - end - function [signalclass_hd,signalclass_sd] = process(obj,signalclass,ref_symbolclass) + function [signalclass_hd,LLR,GMI] = process(obj,signalclass,ref_symbolclass) + + arguments + obj + signalclass + ref_symbolclass + end data_in = signalclass.signal; + shape_in = size(data_in); data_ref = ref_symbolclass.signal; - [data_out_hd,data_out_sd] = obj.process_(data_in,data_ref); + [data_out_hd,LLR,GMI] = obj.process_(data_in,data_ref); + try + data_out_hd = reshape(data_out_hd,shape_in(1),shape_in(2)); + catch + warning('output reshaping failed after MLSE'); + end signalclass_hd = signalclass; signalclass_hd.signal = data_out_hd; - signalclass_sd = signalclass; - signalclass_sd.signal = data_out_sd; + % signalclass_sd = signalclass; + % signalclass_sd.signal = data_out_sd; end - function [VITERBI_ESTIMATION_SYMBOLS,soft_decisions] = process_(obj,data_in,data_ref) + function [VITERBI_ESTIMATION_SYMBOLS,LLR_maxlogmap,GMI] = process_(obj,data_in,data_ref) + + arguments + obj + data_in + data_ref + end + + debug = obj.debug; + + trellis_state_mode = obj.trellis_state_mode; % General: States should match the target states of the prev. EQ (EQ's job was to reduce the error between signal and the target) + % 0 = use provided states (MUST provide the correct states); + % 1 = normalize to = 1 rms; + % 2 = use target symbols; + % 3 = use statistical levels + % 3 analyzes avg of rx signal levels - can help with nonlinear impairments + + trellis_exclusion = obj.trellis_exclusion; % PAM-6 only (only if data is NOT precoded!) + + scale_mode = obj.scale_mode; % scale_mode: + % 0 = no scaling, + % 1 = RMS→scale MODEL, + % 2 = MMSE/time-corr→scale MODEL, + % 3 = RMS→scale DATA, + % 4 = MMSE/time-corr→scale DATA + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%% PREPARATIONS %%%%%%%% % remove unnecessary zeros at start of impulse response to keep % number of trellis states minimal @@ -63,399 +106,557 @@ classdef MLSE < handle obj.DIR = [0 obj.DIR]; end + % impulse respnse to remove from signal + obj.DIR = flip(obj.DIR); %i.e. -0.2676 -0.0478 1.0000 - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %%%%%% PREPARATIONS %%%%%%%% + tx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(data_ref); - %%%% Separate the equalized signal into the respective levels based on the actually transmitted level - constellation = unique(data_ref); - decisionLevels = (constellation(1:end-1) + constellation(2:end)) / 2; - tx_bits = PAMmapper(numel(constellation),0,"eth_style",1).demap(data_ref); + % Trellis States + if trellis_state_mode == 1 % Normalize the Trellis states to =1 RMS + obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states); - % impulse respnse i.e. [0.5, 1.0000] - obj.DIR = flip(obj.DIR); + elseif trellis_state_mode == 2 %simply use the states from the ref signal (should be a robust option) + + obj.trellis_states = reshape(unique(data_ref),1,length(unique(data_ref))); + + elseif trellis_state_mode == 3 %use_statistical_levels + + %%%% Separate the equalized signal into the respective levels based on the actually transmitted level + constellation = unique(data_ref); + + % find actual levels from rx signal + symbols_for_lvl = NaN(numel(constellation),length(data_ref)); + for l = 1:numel(constellation) + level_amplitude = constellation(l); + symbols_for_lvl(l,data_ref==level_amplitude) = data_in(data_ref==level_amplitude); + end + + %replace the trellis states + avg_levels = mean(symbols_for_lvl,2,'omitnan'); + obj.trellis_states = sort(avg_levels)'; + + %also replace the whole ref signal (PAM-M) levels + [~, idx] = ismember(data_ref, unique(data_ref)); + data_ref = avg_levels(idx); + + end - % RMS normalization of input data - data_in = data_in ./ rms(data_in); % seems to be the only way to use combvec for a flexible amount % of vectors. 'combs' contains all trellis states pre_comb_mat = repmat(obj.trellis_states,length(obj.DIR)-1,1); pre_comb_cell = mat2cell(pre_comb_mat,ones(1,size(pre_comb_mat,1)),size(pre_comb_mat,2)); combs = fliplr(combvec(pre_comb_cell{:}).'); + first_sym = combs(:,1); % das ist das älteste/ trailing Symbol aus der sequenz + last_sym = combs(:,end); %hiermit wird entschieden/ das ist das cursor symbol am ende der sequenz - % Save first and last symbol of each state - first_sym = combs(:,1); - last_sym = combs(:,end); states = sum(combs,2); + nStates = length(last_sym); - % Calculate all possible input symbols for the desired impulse - % response. Row number is the index of the previous state, - % column number is the index of the next state - % noise free received == branch metrics - noise_free_received = zeros(length(states),length(states)); - count_row = 1; - count_col = 1; - for l1 = 1:length(states) - for l2 = 1:length(states) - if sum(combs(l2,2:end) == combs(l1,1:end-1)) == size(combs,2)-1 - noise_free_received(count_row,count_col) = sum(combs(l2,:).*obj.DIR(end:-1:2)) + last_sym(l1)*obj.DIR(1); - else - noise_free_received(count_row,count_col) = inf; + % % Calculate all possible input symbols for the desired impulse + % % response. Row number is the index of the previous state, + % % column number is the index of the next state + % % noise free received == branch metrics + % assumes: last_sym = combs(:,end); % already defined earlier + levels = sort(unique(obj.trellis_states(:)).'); + edges = [levels(1) levels(end)]; % edge levels (0 and 5 in PAM6) + + noise_free_received = inf(nStates,nStates); % rows: to, cols: from + edge_edge_mask = false(nStates,nStates); % rows: to, cols: from + + for from = 1:nStates + for to = 1:nStates + % valid transition if shift-register overlap holds + if all(combs(to,2:end) == combs(from,1:end-1)) + % noiseless sample for the 'to' state reached from 'from' + noise_free_received(to,from) = ... + dot(combs(to,:), obj.DIR(end:-1:2)) + last_sym(from)*obj.DIR(1); + + % mark edge→edge candidate (to be excluded only on even→odd steps) + edge_edge_mask(to,from) = ... + (last_sym(from)==edges(1) || last_sym(from)==edges(2)) && ... + (last_sym(to) ==edges(1) || last_sym(to) ==edges(2)); end - count_row = count_row + 1; end - count_col = count_col + 1; - count_row = 1; end - % match amplitude levels of input signal to those of the calculated ideal symbols - % i.e. match the rms values of data_in to noise_free_received - if isreal(data_in) - if obj.M == round(obj.M) - data_in = data_in * rms(noise_free_received(noise_free_received ~= inf),'all','omitnan'); - end + h = flip(obj.DIR(:)).'; + data_in = data_in(:); + y_ideal = conv(data_ref(:), h, "same"); + + switch scale_mode + case 0 + g = 1; b = 0; + case 1 % RMS: scale model to data + g = rms(data_in)/rms(y_ideal); b = mean(data_in) - g*mean(y_ideal); + case 2 % MMSE/time-corr: scale states to data + [c,lags] = xcorr(data_in(:), y_ideal, 64); + [~,ix] = max(abs(c)); + lag = lags(ix); + y_ideal = circshift(y_ideal, lag); + mu_y = mean(data_in(:)); + mu_i = mean(y_ideal); + y_c = data_in(:)-mu_y; + yi_c = y_ideal-mu_i; + g = (yi_c'*y_c)/(yi_c'*yi_c); + b = mu_y - g*mu_i; + case 3 % RMS flipped: scale data to model + gd = rms(y_ideal)/rms(data_in); bd = mean(y_ideal) - gd*mean(data_in); + data_in = gd*data_in + bd; + g = 1; b = 0; + case 4 % MMSE/time-corr flipped: scale data to states + [c,lags] = xcorr(data_in(:), y_ideal(:), 64); + [~,ix] = max(abs(c)); + lag = lags(ix); + y_ideal = circshift(y_ideal(:), lag); + mu_y = mean(data_in(:)); + mu_i = mean(y_ideal); + y_c = data_in(:) - mu_y; % data_in centered + yi_c = y_ideal - mu_i; % ideal centered + g = (y_c' * yi_c) / (y_c' * y_c); + b = mu_i - g * mu_y; + data_in = g * data_in(:) + b; + g = 1; b = 0; + end + + % apply (g,b) to model and compute common sigma + noise_free_received = g*noise_free_received + b; + last_sym = g*last_sym + b; + sigma2 = mean(abs(data_in - (g*y_ideal + b)).^2); + inv2s2 = 1/(2*sigma2); + + if debug + figure(100); clf; hold on + showLevelScatter(data_in, data_ref, "fignum", 100); + yline(noise_free_received(:), 'DisplayName','Transition States','Color','red','HandleVisibility','off'); + yline(obj.trellis_states(:), 'DisplayName','Transition States','Color','green','LineWidth',2,'HandleVisibility','off') end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %%%%% FORWARD PASS %%%%% + %%%%% FORWARD PASS (VITERBI -Alpha's) %%%%% % Initialize the output vector - pm = zeros(length(states),length(states)); - bm_fw = zeros(length(states),length(states),length(data_in)); + pm = zeros(nStates,nStates); + bm_fw = zeros(nStates,nStates,length(data_in)); + + % first start is evaluated without ISI/ wihout the full Impulse response + % so simply use the constellation here + bm = -(data_in(1) - last_sym).^2 * inv2s2; + pm = pm + bm; + [alpha(:,1),pm_survivor_fw_idx(:,1)] = max(pm,[],2); + pm = repmat(alpha(:,1).',nStates,1); + bm_fw(:,:,1) = pm; % Forward Recursion (FSM Computation) - for n = 1:length(data_in) + for n = 2:length(data_in) + bm = -(data_in(n) - noise_free_received).^2 * inv2s2; + + % exclude edge→edge transitions only for even->odd steps && PAM-6 + if mod(n,2) == 0 && obj.M == 6 && trellis_exclusion + bm(edge_edge_mask) = -Inf; + end - bm = abs(data_in(n) - noise_free_received).^2; pm = pm + bm; - [pm_survivor_fw(:,n),pm_survivor_fw_idx(:,n)] = min(pm,[],2); % choose lowest path metric as new state - pm = repmat(pm_survivor_fw(:,n).',length(states),1); % update pm (chosen state to 2nd dimension -> FROM state) + [alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state (get min distance for all state transitions towards a new state) + pm = repmat(alpha(:,n).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state) bm_fw(:,:,n) = bm; end % we can now get the best path as min - best_fw_path = NaN(1,length(data_in)+1); + viterbi_path = NaN(1,length(data_in)); % find ideal trellis path by going through the trellis backwards - [~,best_fw_path(length(data_in)+1)] = min(pm_survivor_fw(:,length(data_in))); + [~,viterbi_path(length(data_in))] = max(alpha(:,length(data_in))); + for n = length(data_in):-1:2 + viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n); + end + + + if debug + % alpha_ = alpha - min(alpha) + eps; + % figure();hold on; + % n = 10; + % scatter(1:n,obj.trellis_states(repmat([1:numel(obj.trellis_states)]',1,n)),abs(alpha_(:,end-n+1:end)),'Marker','o','LineWidth',1); + % scatter(1:n,obj.trellis_states(viterbi_path(end-n+1:end)),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','green'); + % % scatter(1:n,data_ref(end-n+1:end),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','red'); + % yticks(obj.trellis_states); + % ylim([min(obj.trellis_states)-1 max(obj.trellis_states)+1]); + + %% ----- Build true path from known sequence (data_ref) ----- + % Memory length used by VA (L = length(obj.DIR)-1 symbols stored in state) + L = size(combs,2); % each row of combs is the L-tap state vector + N = length(data_in); + + % Map each trellis level to an index (1..M) + [~, level_to_idx] = ismember(levels, levels); %#ok % identity map + [ok_ref, ref_idx] = ismember(data_ref(:).', levels); + if ~all(ok_ref) + warning('Some data_ref symbols are not in "levels". True-path build may fail.'); + end + + % Precompute next_state(from_state, u_idx) LUT such that: + % combs(next_state,:) == [ combs(from_state,2:end) , levels(u_idx) ] + next_state = zeros(size(combs,1), numel(levels), 'uint32'); + for from = 1:size(combs,1) + prefix = combs(from,2:end); % what must match in 'to' for a valid transition + for ui = 1:numel(levels) + target = [prefix, levels(ui)]; + % find the unique 'to' whose state vector equals target + to = find(all(bsxfun(@eq, combs, target), 2), 1, 'first'); + if isempty(to), to = 0; end + next_state(from, ui) = to; + end + end + + % Initialize true path at n=1: pick any state with last_sym == data_ref(1) + % Prefer one whose suffix matches the first available history if L>1. + cand = find(last_sym == data_ref(1)); + if isempty(cand) + % fallback: choose closest in amplitude (should not happen if levels match) + [~,ix] = min(abs(last_sym - data_ref(1))); + cand = ix; + end + true_state_path = zeros(1,N,'uint32'); + true_state_path(1) = cand(1); + + % Propagate forward using the known inputs data_ref(n) + for n = 2:N + ui = ref_idx(n); % index of the actual transmitted level at time n + from = true_state_path(n-1); + if from==0 || ui==0 + true_state_path(n) = 0; + else + true_state_path(n) = next_state(from, ui); + if true_state_path(n)==0 + % Safety fallback: if no valid transition found (should not happen) + % choose any to-state whose vector matches shift+current symbol + target = [combs(from,2:end), levels(ui)]; + to = find(all(bsxfun(@eq, combs, target), 2), 1, 'first'); + if isempty(to), to = from; end + true_state_path(n) = to; + end + end + end + + %% ----- Collect branch metrics along decoded vs. true path ----- + bm_decoded = nan(1,N); + bm_true = nan(1,N); + + % n=1 in your code stores pm into bm_fw(:,:,1); real BMs start at n>=2 + for n = 2:N + % Decoded path: to = viterbi_path(n), from = survivor that fed it + to_d = viterbi_path(n); + from_d = pm_survivor_fw_idx(to_d, n); + bm_decoded(n) = bm_fw(to_d, from_d, n); + + % True path: transition true_state_path(n-1) -> true_state_path(n) + to_t = true_state_path(n); + from_t = true_state_path(n-1); + if to_t>0 && from_t>0 + bm_true(n) = bm_fw(to_t, from_t, n); + end + end + + % Convert to "cost" for intuitive plotting (your BM is a log-likelihood) + cost_dec = -bm_decoded; + cost_true= -bm_true; + + %% ----- Plot a short window for clarity ----- + win = max(2, N-20000):N; % last 200 samples (adjust as needed) + figure('Color','w'); hold on; grid on; box on; + plot(win, cost_true(win), 'LineWidth',1.2, 'DisplayName','True path cost (−BM)'); + plot(win, cost_dec(win), 'LineWidth',1.2, 'DisplayName','Decoded path cost (−BM)'); + xlabel('Time index n'); ylabel('Branch cost'); title('Branch metrics along true vs decoded path'); + legend('Location','best'); + + %% ----- Optional: overlay symbol levels for the same window ----- + yyaxis right + plot(win, data_in(win), ':', 'LineWidth',0.8, 'DisplayName','y(n)'); + ylabel('Amplitude'); + + end + + VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path); - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %%%%% BACKWARD PASS %%%%% + %%%%% BACKWARD PASS (Beta's) %%%%% % Initialize the output vector - pm = zeros(length(states),length(states)); - pm_survivor_bw = zeros(length(states),length(data_in)+1); - pm_survivor_bw_idx = zeros(length(states),length(data_in)+1); - bm_bw = zeros(length(states),length(states),length(data_in)+1); + pm = zeros(nStates,nStates); + beta = zeros(nStates,length(data_in)); + pm_survivor_bw_idx = zeros(nStates,length(data_in)); + bm_bw = zeros(nStates,nStates,length(data_in)); % starting with the state that has the lowest sum path % metric, follow the stored information about the % predecessor - for h = length(data_in):-1:1 + for h = length(data_in)-1:-1:1 - best_fw_path(h) = pm_survivor_fw_idx(best_fw_path(h+1),h); + bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2; + + % exclude edge→edge transitions only for even->odd steps && PAM-6 + if mod(h+1, 2) == 0 && obj.M == 6 && trellis_exclusion + bm(edge_edge_mask) = -Inf; + end - bm = abs(data_in(h) - noise_free_received).^2; pm = pm + bm.'; - [pm_survivor_bw(:,h),pm_survivor_bw_idx(:,h)] = min(pm,[],2); % choose lowest path metric as new state - pm = repmat(pm_survivor_bw(:,h).',length(states),1); % update pm (chosen state to 2nd dimension -> FROM state) + [beta(:,h),pm_survivor_bw_idx(:,h)] = max(pm,[],2); % choose lowest path metric as new state + pm = repmat(beta(:,h).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state) bm_bw(:,:,h) = bm; end - VITERBI_ESTIMATION_IDX(1:length(data_in)) = first_sym(best_fw_path(2:end)); - VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = constellation(best_fw_path(2:end)); - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %%%%% FORWARD PASS %%%%% - + %%%%% FORWARD PASS PAM 2,4,8 (Combine Alpha and Beta to yield LLP's) %%%%% + %calc the log probabilities (llp's) - for k = 2:length(data_in) - - fsm = repmat(pm_survivor_fw(:,k-1)',[length(states),1]); %pm_survivor_fw size: length(states)xlength(sequence) - bm = bm_fw(:,:,k); %bm_fw size: length(states)xlength(states)xlength(sequence) - bsm = repmat(pm_survivor_bw(:,k)',[length(states),1]); %pm_survivor_bw size: length(states)xlength(sequence)+1 + for k = 1:length(data_in) + + if k == 1 + + alpha_ = repmat(alpha(:,k)',[nStates,1])'; + beta_ = beta(:,k); + + LLP(:,k) = max(alpha_ + beta_,[],2); + + else + + alpha_ = repmat(alpha(:,k-1)',[nStates,1])'; + gamma_ = bm_fw(:,:,k)'; + beta_ = beta(:,k); + + LLP(:,k) = max(alpha_ + gamma_,[],1) + beta_'; + end - llp(:,k) = min(fsm+bm+bsm,[],2); - end - % Compute soft output PAM4 stream from the metric_sym - soft_output = zeros(length(data_in),1); % Expected symbol value per stage - symbol_prob = zeros(length(data_in), length(states)); % Store full probability distribution (optional) - llp = llp'; - for n = 1:length(data_in) - metrics = llp(n, :); % A posteriori metric for each PAM4 candidate - % For numerical stability, subtract the maximum metric before exponentiating - maxMetric = min(metrics); - expMetrics = exp(metrics - maxMetric); - probs = expMetrics / sum(expMetrics); % Normalize to get probabilities - symbol_prob(n, :) = probs; % (Optional) store distribution for analysis - % Compute the soft output as the expected value of the PAM4 symbols - soft_output(n) = sum(probs .* constellation'); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% FORWARD PASS PAM2,4,8 %%%%% + + % These are interchangeable... second is chatgpt: + nml_LLP = LLP - max(LLP); %subtract highest value for better numerical stability, LLP's are not always close to zero + expLLP = exp(nml_LLP); + state_prob = expLLP ./ sum(expLLP); % sums to one (or numerically close to one) + + % compute symbol‐posteriors from LLP in the log‐domain: + amax = max(LLP,[],1); + logZ = amax + log(sum(exp(LLP - amax), 1)); + logPstate = LLP - logZ; % still in log‐domain + state_prob = exp(logPstate); % exact, sums to 1 + + + if obj.M == 6 + + num_bits = 5; + + % all possible transitions (for now 36, including the "edges" + % of the QAM 32 constellation) + states = PAMmapper(6,0,"eth_style",0).levels; + pam6transitions = combvec(states,states)'; % pam6transitions = + % [-5 -5; + % -3 -5; + % -1 -5; ... + + pam6bits = PAMmapper(6,0,"eth_style",0).demap(reshape(pam6transitions',[],1)./sqrt(10)); + + pam6bits = reshape(pam6bits',5,[])'; + + [ok1, idx_sym_1] = ismember(pam6transitions(:,1), states); + [ok2, idx_sym_2] = ismember(pam6transitions(:,2), states); + assert(all(ok1)&all(ok2), 'Some transition amplitude not found in trellis_states') + pam6ind = [idx_sym_1, idx_sym_2]; + + tx_bits_pam6_reshaped = reshape(tx_bits,5,[])'; % N x 5 + + numPairs = floor(size(LLP,2)/2); + LLR_exact = zeros(numPairs,5); + LLR_maxlogmap = zeros(numPairs,5); + + for k = 1:numPairs + symbol1 = 2*k-1; + symbol2 = 2*k; + + LLP1 = LLP (:,symbol1); + LLP2 = LLP (:,symbol2); + prob1 = state_prob(:,symbol1); + prob2 = state_prob(:,symbol2); + + % 36 joint‐metrics M = log P(i)*P(j) = L1(i)+L2(j) + Mij = LLP1(pam6ind(:,1)) + LLP2(pam6ind(:,2)); + pij = prob1(pam6ind(:,1)) .* prob2(pam6ind(:,2)); + + % now for each of the 5 bits do exact-LLR or max-log + for b = 1:num_bits + idx_sym_1 = pam6bits(:,b)==1; + idx_bit_1 = pam6bits(:,b)==0; + + %--- exact LLR from probabilities + P1 = sum(pij(idx_sym_1)); + P0 = sum(pij(idx_bit_1)); + LLR_exact(k,b) = log(P1./P0); + + %--- max-log: + LLR_maxlogmap(k,b) = max( Mij(idx_sym_1) ) - max( Mij(idx_bit_1) ); % N x num_bits + end + end + + MI = zeros(1, num_bits); + for k = 1:num_bits + + idx_bit_1 = (tx_bits_pam6_reshaped(:,k) == 0); %wo sind die 1en + idx_sym_1 = (tx_bits_pam6_reshaped(:,k) == 1); %wo sind die 0en + + %LLR's for all actually transmitted ones or zeros + llr0 = LLR_exact(idx_bit_1,k); + llr1 = LLR_exact(idx_sym_1,k); + + % Calculate mutual information for bit position k + I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1 + I1 = mean(log2(1 + exp(-llr1))); % exp(-+LLR) = exp(negative) < 1 + MI(k) = 1 - 0.5 * (I0 + I1); + end + + GMI = sum(MI); % Total mutual information per symbol + GMI = GMI/2; + + else + + % Number of symbols and bits per symbol + num_bits = log2(length(obj.trellis_states)); % 2 bits per symbol + % bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).demap(first_sym./rms(first_sym)); + bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).showBitMapping; + % Initialize LLR storage + LLR_maxlogmap = zeros(length(data_in),num_bits); + LLR_exact = zeros(length(data_in),num_bits); + + % Compute bit-wise LLRs + for bit_idx = 1:num_bits + + % Find indices where bit is 0 and where it is 1 + idx_bit_0 = bit_mapping(:,bit_idx) == 0; + idx_bit_1 = bit_mapping(:,bit_idx) == 1; + + % Sum over log-probabilities + % Max-Log approximation: using max instead of sum) + LLR_maxlogmap(:,bit_idx) = max(LLP(idx_bit_1,:), [], 1) - max(LLP(idx_bit_0,:), [], 1); + + % Sum probabilities over states for which the bit is 1 and 0, respectively. + P0 = sum(state_prob(idx_bit_0, :),1); + P1 = sum(state_prob(idx_bit_1, :),1); + LLR_exact(:,bit_idx) = log(P1./P0); % N x num_bits + + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% CALC NGMI %%%%% + + MI = zeros(1, num_bits); + LLR_exact = LLR_exact; + for k = 1:num_bits + + idx_bit_0 = (tx_bits(:,k) == 0); %wo sind die 1en + idx_bit_1 = (tx_bits(:,k) == 1); %wo sind die 0en + + %LLR's for all actually transmitted ones or zeros + llr0 = LLR_exact(idx_bit_0,k); + llr1 = LLR_exact(idx_bit_1,k); + + % Calculate mutual information for bit position k + I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1 + I1 = mean(log2(1 + exp(-llr1))); % exp(-+LLR) = exp(negative) < 1 + MI(k) = 1 - 0.5 * (I0 + I1); + end + + GMI = sum(MI); % Total mutual information for 2 symbols + end - % Number of symbols and bits per symbol - num_symbols = constellation; - num_bits = 2; % 2 bits per symbol + VITERBI_ESTIMATION_SYMBOLS = VITERBI_ESTIMATION_SYMBOLS./rms(VITERBI_ESTIMATION_SYMBOLS); - bit_mapping = PAMmapper(4,0,"eth_style",1).showBitMapping; % Each row corresponds to the symbol above + if debug + %%% DEBUG PLOT LIKELIHOOD RATIOS %%% + figure(115);clf + subplot(2,1,1) + for bit = 1:num_bits + hold on; + histogram(LLR_exact(:,bit),1000,"DisplayName",sprintf('Actual LLR of Bit Pos %d',bit),'LineStyle','none','FaceAlpha',0.4); + end + legend - % Initialize LLR storage - llr = zeros(num_bits, length(data_in)); - - % Compute bit-wise LLRs - for bit_idx = 1:num_bits - % Find indices where bit is 0 and where it is 1 - idx_bit_0 = find(bit_mapping(:,bit_idx) == 0); - idx_bit_1 = find(bit_mapping(:,bit_idx) == 1); - - % Sum over log-probabilities (Max-Log approximation: using min instead of sum) - llr(:,bit_idx) = min(llp(:,idx_bit_1), [], 1) - min(llp(:,idx_bit_0), [], 1); - + subplot(2,1,2) + for bit = 1:num_bits + hold on; + histogram(LLR_maxlogmap(:,bit),1000,"DisplayName",sprintf('Max Log LLR of Bit Pos %d',bit),'LineStyle','none','FaceAlpha',0.4); + end + legend end - % Convert LLR values to a hard-decision bit stream - bit_stream = llr < 0; - [~,~,ber_llr,~] = calc_ber(bit_stream',tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('LLR BER : %.2e \n',ber_llr); - - % directly decide based on lowest LLP index - [~,llp_based_state_seq]=min(llp); - LLP_EST(1:length(data_in)) = constellation(llp_based_state_seq); - rx_bits = PAMmapper(numel(constellation),0,"eth_style",1).demap(LLP_EST'); - [~,~,ber_llp,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('LLP BER : %.2e \n',ber_llp); - % [~,~,ber_llp,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - % fprintf('LLP BER +1: %.2e \n',ber_llp); - % [~,~,ber_llp,~] = calc_ber(circshift(rx_bits,-1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - % fprintf('LLP BER -1: %.2e \n',ber_llp); - - %%%% DECIDE based on Viterbi traceback - rx_bits = PAMmapper(numel(constellation),0,"eth_style",1).demap(VITERBI_ESTIMATION_SYMBOLS'); - [~,~,ber_viterbi,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('Viterbi BER: %.2e \n',ber_viterbi); - % [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - % fprintf('Viterbi BER: %.2e \n',ber_viterbi); - - % directly decide based on the FW path metrics - [~,fw_direct_state_seq]=min(pm_survivor_fw); - FW_EST(1:length(data_in)) = constellation(fw_direct_state_seq); - rx_bits = PAMmapper(numel(constellation),0,"eth_style",1).demap(FW_EST'); - [~,~,ber_fw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('FW BER: %.2e \n',ber_fw); - - % directly decide based on the BW path metrics - [~,bw_direct_state_seq]=min(pm_survivor_bw); - BW_EST(1:length(data_in)) = constellation(bw_direct_state_seq(2:end)); - rx_bits = PAMmapper(numel(constellation),0,"eth_style",1).demap(BW_EST'); - [~,~,ber_bw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('BW BER: %.2e \n',ber_bw); - % [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - % fprintf('BW BER: %.2e \n',ber_viterbi); - % [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,-1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - % fprintf('BW BER: %.2e \n',ber_viterbi); - - PAMmapper(4,0,"eth_style",1).showBitMapping %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - tx_symbolpos = zeros(numel(constellation),length(data_ref)); - est_symbolpos = zeros(numel(constellation),length(data_ref)); - for lvl = 1:numel(constellation) - tx_symbolpos(lvl,data_ref==constellation(lvl)) = 1; - est_symbolpos(lvl,VITERBI_ESTIMATION_IDX==first_sym(lvl)) = 1; - est_symbolpos_llm(lvl,llp_based_state_seq==lvl) = 1; + %%%%% CHECK BER's %%%%% + + if debug + tx_bits = reshape(tx_bits',[],1); + fprintf('\n') + disp('Start DEBUG MLSE:') + % DECIDE based on Viterbi traceback + VITERBI_ESTIMATION_SYMBOLS = VITERBI_ESTIMATION_SYMBOLS./rms(VITERBI_ESTIMATION_SYMBOLS); + rx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(VITERBI_ESTIMATION_SYMBOLS'); + rx_bits = reshape(rx_bits',[],1); + [~,numErr,ber_viterbi,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + fprintf('Viterbi BER: %.2e \n',ber_viterbi); + fprintf('Viterbi Errors = %d\n', numErr); + + pairs = reshape(VITERBI_ESTIMATION_SYMBOLS,2,[]).'; + levels = sort(unique(VITERBI_ESTIMATION_SYMBOLS)); + isedge = ismember(pairs, [levels(1) levels(end)]); + isforbidden = sum(isedge,2)==2; + fprintf('Found %d forbidden transitions (even→odd edges).\n', nnz(isforbidden)); + + + % Convert LLR values to a hard-decision bit stream + bit_stream = LLR_maxlogmap > 0; %ratio separates lower or higher than =0 -> simply decode for the negative values + bit_stream = reshape(bit_stream',[],1); + [~,~,ber_llr,~] = calc_ber(bit_stream,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + fprintf('LLR MaxLogMAP BER : %.2e \n',ber_llr); + + % Convert LLR values to a hard-decision bit stream + bit_stream = LLR_exact > 0; %ratio separates lower or higher than =0 -> simply decode for the negative value + bit_stream = reshape(bit_stream',[],1); + [~,~,ber_llr,~] = calc_ber(bit_stream,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + fprintf('LLR BER : %.2e \n',ber_llr); + + % DECIDE based on lowest LLP index and check BER + [~,llp_based_state_seq]=max(LLP); + LLP_EST(1:length(data_in)) = first_sym(llp_based_state_seq); + LLP_EST = LLP_EST./rms(LLP_EST); + rx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(LLP_EST'); + rx_bits = reshape(rx_bits',[],1); + [~,~,ber_llp,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + fprintf('LLP BER : %.2e \n',ber_llp); + + % directly decide based on the FW path metrics + [~,fw_direct_state_seq]=max(alpha); + FW_EST(1:length(data_in)) = first_sym(fw_direct_state_seq); + FW_EST = FW_EST./rms(FW_EST); + rx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(FW_EST'); + rx_bits = reshape(rx_bits',[],1); + [~,~,ber_fw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + fprintf('FW BER : %.2e \n',ber_fw); + disp('Stop DEBUG MLSE:') + fprintf('\n') end - est_symbolpos= logical(est_symbolpos); - tx_symbolpos = logical(tx_symbolpos); - est_symbolpos_llm = logical(est_symbolpos_llm); - - figure(299);clf;hold on; - llp_filt = NaN(length(states),length(data_in)); - llp_ = llp - min(llp,[],1); - for c = 2%:numel(constellation) - for c2 = 1:3%numel(constellation) - - idx = est_symbolpos_llm(c,:); - llp_filt(c2,idx) = (llp(c2,idx)); - - plotidx = 2:200000; - scatter(plotidx,llp_filt(c2,plotidx),1,'o','LineWidth',1,'DisplayName',sprintf('LLP of Symbol %d | %d was transmitted',c2,c)); - - end - end - - % Assume llp is a 4 x N matrix (4 PAM-4 symbols, N time steps) - [numSymbols, numTimeSteps] = size(llp); - P_symbols = zeros(numSymbols, numTimeSteps); % To hold the soft output probabilities - - for k = 1:numTimeSteps - - % Compute the exponentials. (Use -llp_shifted if llp's are costs.) - exponents = -llp_(:, k); - - % Normalize to form a probability vector. - P_symbols(:, k) = exponents / sum(exponents); - end - - % Optionally, if you prefer a single soft output value per symbol (an expectation), - % define your PAM-4 constellation levels, e.g.: - soft_output = sum(P_symbols .* constellation, 1); % 1 x N vector of soft outputs - - - %%%% BITWISE LLR's ??? %%%%% - figure(100); - clf - hold on - title('LLR between inner and outer bits') - bitmapping = PAMmapper(numel(constellation),0).demap(constellation); - for bp = 1:size(bitmapping,2) - pos_bitone = find(bitmapping(:,bp)==1); - pos_bitzero = find(bitmapping(:,bp)~=1); - - llr_bits(bp,:) = min(llp(pos_bitone,:),[],1) - min(llp(pos_bitzero,:),[],1); - - subplot(size(bitmapping,2),1,bp) - scatter(1:length(data_ref),llr_bits(bp,:),1,'.'); - end - %%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - % From: Log-Likelihood Probabilities LLP --> To: Log-Likelihood Ratios LLR - for s = 1:length(states)-1 - llr(s,:) = llp_(s,:) - llp_(s+1,:); % subtract llp's of successive const. points to get llr's - end - - % Build Sets of LLR's that contain only those values at - % timepoint k, where the symbols: - % a) were actually transmitted: set "S" - % b) were decoded by viterbi: set "SC" - S = NaN(length(states)-1,length(data_in)); - SC = NaN(length(states)-1,length(data_in)); - - llp_inf = llp_; - llp_inf(llp_==0) = Inf; - [~,scnd_idx] = min(llp_inf,[],1) ; - for s = 1:length(states)-1 - - %%% SET "S" - % find all time indices k, where const point s was actually transmitted - indice = tx_symbolpos(s,:)==1; - S(s,indice) = llr(s,indice); - %calc mean of all llr's where symbol s was transmitted - K(s,1) = mean(S(s,indice),'omitnan'); - - % find all time indices k, where const. point s+1 was actually transmitted - indice_plusone = tx_symbolpos(s+1,:)==1; - S(s,indice_plusone) = llr(s,indice_plusone); - K(s,2) = mean(S(s,indice_plusone),'omitnan'); - - - %%% SET "SC" - % find all time indices k, where symbol s was decoded - % idx: find positions in time where a symbol s was decoded - idx = est_symbolpos_llm(s,:); - % idx 2: find positions where the strongest competitor is - % s+1, i.e. the second best llp is at s+1 - idx2 = scnd_idx == s+1; - SC(s,idx&idx2) = llr(s,idx&idx2); - KC(s,1) = mean(SC(s,idx&idx2),'omitnan'); - - % find all time indices k, where symbol s+1 was decoded - % idx: find positions in time where a symbol s+1 was decoded - idx = est_symbolpos_llm(s+1,:); - idx2 = scnd_idx == s; - % idx 2: find positions where the strongest competitor is - % s, i.e. the second best llp is at s - SC(s,idx&idx2) = llr(s,idx&idx2); - KC(s,2) = mean(SC(s,idx&idx2),'omitnan'); - - % scale the sets, using the average (?) of the - llrcn(s,:) = SC(s,:) * ( constellation(s+1)-constellation(s) ) ./ ( K(s,2)-K(s,1)) + decisionLevels(s); - - end - - figure(111) - title("Bla") - clf - for s = 1:length(states)-1 - figure(1111) - hold on - title(sprintf('llrcn = llp %d - llp %d',s, s+1,s, s+1)); - scatter(1:length(llrcn),llrcn(s,:),1,'.'); - - - % STUFENARTIGE LLR'S UND LLP'S %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - figure(111) - - subplot(1,length(states),s) - hold on - title(sprintf('llr = llp %d - llp %d \n SC=llr(tx sym = %d or %d)',s, s+1,s, s+1)); - scatter(1:length(llr),llr(s,:),1,'.'); - scatter(1:length(SC),SC(s,:),1,'.'); - yline([K(s,1),K(s,2)]); - low = min(min(llr)); - hi = max(max(llr)); - ylim([low,hi]); - - subplot(1,length(states),length(states)) - hold on - scatter(1:length(llr),llr(s,:),1,'.'); - ylim([low,hi]); - - - % HISTOGRAMME %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - figure(112) - subplot(length(states),1,s) - hold on - title(sprintf('histogram of llr; between const point %d - %d',s, s+1)); - histogram(llrcn(s,:),10000,'EdgeAlpha',0) - % histogram(SC(s,:),10000,'EdgeAlpha',0) - xlim([-20, 20]); - subplot(length(states),1,length(states)) - hold on - histogram(llrcn(s,:),10000,'EdgeAlpha',0) - % histogram(SC(s,:),10000,'EdgeAlpha',0) - % xlim([-20, 20]); - - end - - softdecisions = mean(llrcn,1,'omitnan'); - - distance = abs(VITERBI_ESTIMATION_SYMBOLS-llrcn); - [win_cost,win_idx] = min(distance,[],1); - - for i = 1:length(data_in) - soft_decisions(i) = llrcn(win_idx(i),i); - end - - soft_decisions = max(llrcn,[],1); - - showLevelHistogram(soft_decisions,data_ref) - - figure() - % scatter(1:length(data_in),VITERBI_ESTIMATION_SYMBOLS,1,'.'); - scatter(1:length(data_in),soft_decisions,1,'.'); - - - MLM_ESTIMATION(1:length(data_in)) = PAMmapper(numel(constellation),0).quantize(soft_decisions)'; - rx_bits = PAMmapper(numel(constellation),0).demap(MLM_ESTIMATION'); - [~,~,ber_mlm,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - fprintf('MLM BER: %.2e \n',ber_mlm); - end - end - methods (Access=private) end end diff --git a/Classes/04_DSP/TransmissionPerformance.m b/Classes/04_DSP/TransmissionPerformance.m index 1550e40..d87d691 100644 --- a/Classes/04_DSP/TransmissionPerformance.m +++ b/Classes/04_DSP/TransmissionPerformance.m @@ -42,6 +42,7 @@ classdef TransmissionPerformance 0.8733, 0.8790, 0.8848, 0.8905, 0.8962, ... 0.9019, 0.9077, 0.9134, 0.9191, 0.9248, ... 0.9306, 0.9363, 0.9420, 0.9477, 0.9535]; + NGMITHRESHOLDS_SDHD = [0.8116, 0.8167, 0.8241, 0.8317, 0.8401, ... 0.8459, 0.8512, 0.8574, 0.8685, 0.8746, ... 0.8829, 0.8892, 0.8958, 0.9022, 0.9090,... @@ -60,7 +61,7 @@ classdef TransmissionPerformance %% LUT for KP4-FEC and Inner Code https://grouper.ieee.org/groups/802/3/dj/public/23_03/patra_3dj_01b_2303.pdf - CODE_RATE_KP4_AND_INNER = [0.885799]; + CODE_RATE_KP4_AND_INNER = [0.885799]; %https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9979198 -> 199.8 / 224 = 0.891 als code rate BERTHRESHOLDS_KP4_AND_INNER = 4.85e-3; % https://www.ieee802.org/3/bs/public/14_11/parthasarathy_3bs_01a_1114.pdf @@ -153,11 +154,21 @@ classdef TransmissionPerformance netrates.SDHD.Threshold = NaN(1, numMeasurements); end if ~isempty(ber) + netrates.STAIR.GrossRate = NaN(1, numMeasurements); + netrates.STAIR.NetRate = NaN(1, numMeasurements); + netrates.STAIR.CodeRate = NaN(1, numMeasurements); + netrates.STAIR.Threshold = NaN(1, numMeasurements); + netrates.HD.GrossRate = NaN(1, numMeasurements); netrates.HD.NetRate = NaN(1, numMeasurements); netrates.HD.CodeRate = NaN(1, numMeasurements); netrates.HD.Threshold = NaN(1, numMeasurements); + netrates.KP4.GrossRate = NaN(1, numMeasurements); + netrates.KP4.NetRate = NaN(1, numMeasurements); + netrates.KP4.CodeRate = NaN(1, numMeasurements); + netrates.KP4.Threshold = NaN(1, numMeasurements); + netrates.KP4_hamming.GrossRate = NaN(1, numMeasurements); netrates.KP4_hamming.NetRate = NaN(1, numMeasurements); netrates.KP4_hamming.CodeRate = NaN(1, numMeasurements); @@ -200,11 +211,47 @@ classdef TransmissionPerformance end if ~isempty(idxBER) codeRate = obj.CODE_RATES_HD(idxBER); + netrates.STAIR.NetRate(i) = grossRate(i) * codeRate; + netrates.STAIR.GrossRate(i) = grossRate(i) ; + netrates.STAIR.CodeRate(i) = codeRate; + netrates.STAIR.Threshold(i) = obj.BERTHRESHOLDS_HD(idxBER); + end + + % HD FEC 3,8e-3 + idxBER = []; + for j = length(obj.BERTHRESHOLDS_HDFEC):-1:1 + if ber(i) <= obj.BERTHRESHOLDS_HDFEC(j) + idxBER = j; + break; + end + end + if ~isempty(idxBER) + codeRate = obj.CODE_RATE_HDFEC(idxBER); netrates.HD.NetRate(i) = grossRate(i) * codeRate; netrates.HD.GrossRate(i) = grossRate(i) ; netrates.HD.CodeRate(i) = codeRate; - netrates.HD.Threshold(i) = obj.BERTHRESHOLDS_HD(idxBER); + netrates.HD.Threshold(i) = obj.CODE_RATE_HDFEC(idxBER); end + + + % KP4 + idxBER = []; + for j = length(obj.BERTHRESHOLDS_KP4):-1:1 + if ber(i) <= obj.BERTHRESHOLDS_KP4(j) + idxBER = j; + break; + end + end + if ~isempty(idxBER) + codeRate = obj.CODE_RATE_KP4(idxBER); + netrates.KP4.NetRate(i) = grossRate(i) * codeRate; + netrates.KP4.GrossRate(i) = grossRate(i) ; + netrates.KP4.CodeRate(i) = codeRate; + netrates.KP4.Threshold(i) = obj.CODE_RATE_KP4(idxBER); + end + + + % KP4 + inner Hamming idxBER = []; for j = length(obj.BERTHRESHOLDS_KP4_AND_INNER):-1:1 if ber(i) <= obj.BERTHRESHOLDS_KP4_AND_INNER(j) @@ -220,6 +267,7 @@ classdef TransmissionPerformance netrates.KP4_hamming.Threshold(i) = obj.BERTHRESHOLDS_KP4_AND_INNER(idxBER); end + % O FEC idxBER = []; for j = length(obj.BERTHRESHOLDS_O_FEC):-1:1 if ber(i) <= obj.BERTHRESHOLDS_O_FEC(j) diff --git a/Classes/DataBaseHandler/DBHandler.m b/Classes/DataBaseHandler/DBHandler.m index 368dd2a..0cb5245 100644 --- a/Classes/DataBaseHandler/DBHandler.m +++ b/Classes/DataBaseHandler/DBHandler.m @@ -5,7 +5,7 @@ classdef DBHandler < handle properties conn % Database connection object - pathToDB % Path to the SQLite database + dataBase % Path to the SQLite database tableNames % Cell array containing names of all tables in the database tables = struct(); % Structure containing MATLAB tables for each database table distinctValues @@ -21,7 +21,7 @@ classdef DBHandler < handle % obj = DBHandler('pathToDB', 'path/to/database.db'); arguments - options.pathToDB = ""; % Default value for pathToDB if not provided + options.dataBase = ""; % Default value for pathToDB if not provided options.type = "mysql"; end @@ -37,12 +37,15 @@ classdef DBHandler < handle try if options.type == "sqlite" - obj.conn = sqlite(obj.pathToDB); + + obj.conn = sqlite(obj.dataBase); + elseif options.type == "mysql" - datasource = "jdbc:mysql://134.245.243.254:3306/labor"; + + % datasource = "jdbc:mysql://134.245.243.254:3306/labor"; obj.conn = database( ... - "labor", ... % Database name + string(obj.dataBase), ... % Database name "silas", ... % Username "silas", ... % Password (or getSecret) "Vendor", "MySQL", ... @@ -60,7 +63,9 @@ classdef DBHandler < handle obj.refresh(); else + error('DB seems to be corrupt') + end end @@ -68,9 +73,11 @@ classdef DBHandler < handle function obj = refresh(obj) % Get table names and the first rows of each table to understand the structure + warning off obj.getTableNames(); obj.getTables(); - obj.getDistinctValues(); + warning on + % obj.getDistinctValues(); end function obj = getTableNames(obj) @@ -83,7 +90,7 @@ classdef DBHandler < handle result = fetch(obj.conn, 'SELECT name FROM sqlite_master WHERE type="table"'); obj.tableNames = result.name; end - + catch e error('Failed to retrieve table names: %s', e.message); end @@ -169,14 +176,6 @@ classdef DBHandler < handle function healthyDB = dbIsHealthy(obj) healthyDB = false; - num_runs = obj.fetch('SELECT COUNT(*) AS total_runs FROM Runs'); - - num_configs = obj.fetch('SELECT COUNT(*) AS total_configurations FROM Configurations'); - - num_meas = obj.fetch('SELECT COUNT(*) AS total_measurements FROM Measurements'); - - assert((num_runs{1,1}==num_configs{1,1})&&(num_configs{1,1}==num_meas{1,1}),'Different num of entries per table'); - %Check for any duplicate paths duplictae_raw = obj.fetch("SELECT COALESCE(Runs.rx_raw_path,'NaN') AS rx_raw_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_raw_path HAVING COUNT(*) > 1"); duplictae_sync = obj.fetch("SELECT COALESCE(Runs.rx_sync_path,'NaN') AS rx_sync_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_sync_path HAVING COUNT(*) > 1"); @@ -186,11 +185,11 @@ classdef DBHandler < handle fprintf('Raw Rx Paths: Found %d duplictaes of %s \n',duplictae_raw.occurrences(i),duplictae_raw.rx_raw_path(i)); end end + healthyDB = true; end - function lastID = appendToTable(obj, tableName, newRow) % appendToTable Appends a new row to the specified table % @@ -206,40 +205,50 @@ classdef DBHandler < handle error('Table %s does not exist in the database or has not been fetched.', tableName); end - % Convert newRow to a table if it is a struct + % Handle struct preprocessing before table conversion if isstruct(newRow) fields = fieldnames(newRow); - emptyFields = structfun(@isempty,newRow); - if sum(emptyFields) > 0 - emptyFieldNames = fields(emptyFields); % use () to get a cell array - for idx = 1:numel(emptyFieldNames) - newRow.(emptyFieldNames{idx}) = NaN; + + % % Handle empty fields + % emptyFields = structfun(@isempty, newRow); + % if sum(emptyFields) > 0 + % emptyFieldNames = fields(emptyFields); + % for idx = 1:numel(emptyFieldNames) + % newRow.(emptyFieldNames{idx}) = NaN; + % end + % end + + % Convert any non-scalar numeric (including [] and vectors) into JSON + for i = 1:numel(fields) + name = fields{i}; + value = newRow.(name); + + if isnumeric(value) && ~isscalar(value) + % jsonencode([]) -> "[]" + % jsonencode([a,b,c]) -> "[a,b,c]" + newRow.(name) = jsonencode(value); end end + + % Convert to table newRow = struct2table(newRow); end % Ensure the new row matches the structure of the existing table existingTableStructure = obj.tables.(tableName); - % Perform data type checks and conversions + % Perform remaining data type checks and conversions for colName = newRow.Properties.VariableNames - % Extract the value and its intended column type value = newRow.(colName{1}); - existingValue = existingTableStructure.(colName{1}); - % If the value is a class object, convert it to JSON format - if isobject(value) && ~isdatetime(value) && ~isa(value,"string") + if iscell(value) && ~isempty(value) + if ischar(value{1}) || isstring(value{1}) + newRow.(colName{1}) = value{1}; + end + elseif isobject(value) && ~isdatetime(value) && ~isa(value, "string") newRow.(colName{1}) = string(jsonencode(value)); - - % If the value is a character array, convert it to a string elseif ischar(value) newRow.(colName{1}) = string(value); - - elseif isdatetime(value) - - % newRow.(colName{1}) = string(value); - end end @@ -247,7 +256,6 @@ classdef DBHandler < handle % Parameters for retry logic maxRetries = 50; basePause = 0.05; % seconds - attempt = 0; success = false; @@ -256,37 +264,41 @@ classdef DBHandler < handle if obj.type == "mysql" newRow_ = obj.convertTableToCellStrings(newRow); end - sqlwrite(obj.conn, tableName, newRow); - success = true; % Write successful + sqlwrite(obj.conn, tableName, newRow,"Catalog",obj.dataBase); + success = true; catch e if contains(e.message, 'database is locked') || contains(e.message, 'cannot rollback transaction') attempt = attempt + 1; - pauseTime = basePause * (1 + rand()); % Add random jitter to reduce collision chance - fprintf('Database locked. Retry %d/%d after %.3f seconds...\n', attempt, maxRetries, pauseTime); + pauseTime = basePause * (1 + rand()); + fprintf('Database locked. Retry %d/%d after %.3f seconds...\n', ... + attempt, maxRetries, pauseTime); pause(pauseTime); else + fprintf('Error details:\n'); + fprintf('Column values:\n'); + disp(newRow); error('Failed to append to the table %s: %s', tableName, e.message); end end end if ~success - error('Failed to append to table %s after %d retries due to database lock.', tableName, maxRetries); + error('Failed to append to table %s after %d retries due to database lock.', ... + tableName, maxRetries); end - % Retrieve the measurement_id of the newly inserted row for linking other tables - + % Retrieve the ID of the newly inserted row if obj.type == "mysql" - queue = "SELECT LAST_INSERT_ID()"; + query = "SELECT LAST_INSERT_ID()"; elseif obj.type == "sqlite" - queue = "SELECT last_insert_rowid()"; + query = "SELECT last_insert_rowid()"; end - result = fetch(obj.conn, queue); - lastID = result{1, 1}; % Access the value directly from the table + result = fetch(obj.conn, query); + lastID = result{1, 1}; end - function exists = checkIfRunExists(obj, table2check, column2check, value2check) + function [exists, count] = checkIfRunExists(obj, table2check, column2check, value2check) % checkIfRunExists Checks if a specific value exists in a specified column of a table % % Usage: @@ -311,7 +323,11 @@ classdef DBHandler < handle end % Construct the query to check for the value in the specified column - query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = "%s"', table2check, column2check, value2check); + if isnumeric(value2check) + query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = %d', table2check, column2check, value2check); + else + query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = "%s"', table2check, column2check, value2check); + end % Execute the query and pass the value2check to avoid SQL injection issues try @@ -325,12 +341,21 @@ classdef DBHandler < handle exists = count > 0; if exists - disp(['The value "', value2check, '" already exists in the column "', column2check, '" of the table "', table2check, '".']); + % disp(['The value "', num2str(value2check), '" already exists in the column "', column2check, '" of the table "', table2check, '".']); else % disp(['The value "', value2check, '" does not exist in the column "', column2check, '" of the table "', table2check, '".']); end end + function hashStr = calcHash(~,object) + jsonStr = jsonencode(object); + md = java.security.MessageDigest.getInstance('MD5'); + md.update(uint8(jsonStr)); + hashBytes = typecast(md.digest, 'uint8'); + hashStr = lower(dec2hex(hashBytes)'); + hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string + end + function resultID = addProcessingResult(obj, run_id, resultData, eqParamsData) % addProcessingResult Adds a processing result and links it to an EqualizerParameters entry. @@ -348,18 +373,19 @@ classdef DBHandler < handle % resultID: The result_id of the newly inserted ProcessingResults entry. % 1. Compute hash for equalizer parameters - jsonStr = jsonencode(eqParamsData); - md = java.security.MessageDigest.getInstance('MD5'); - md.update(uint8(jsonStr)); - hashBytes = typecast(md.digest, 'uint8'); - hashStr = lower(dec2hex(hashBytes)'); - hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string + % jsonStr = jsonencode(eqParamsData); + % md = java.security.MessageDigest.getInstance('MD5'); + % md.update(uint8(jsonStr)); + % hashBytes = typecast(md.digest, 'uint8'); + % hashStr = lower(dec2hex(hashBytes)'); + % hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string + hashStr = obj.calcHash(eqParamsData); % Add hash to equalizer parameters - eqParamsData.config_hash = hashStr; + eqParamsData.hash = hashStr; % 2. Check if an equalizer configuration with the same hash exists - queryStr = sprintf('SELECT eq_id FROM EqualizerParameters WHERE config_hash = ''%s''', eqParamsData.config_hash); + queryStr = sprintf('SELECT eq_id FROM Equalizer WHERE hash = ''%s''', eqParamsData.hash); existingEntry = obj.fetch(queryStr); if ~isempty(existingEntry) @@ -367,27 +393,30 @@ classdef DBHandler < handle eq_id = existingEntry{1,1}; else % Insert the new equalizer configuration and get its eq_id - eq_id = obj.appendToTable('EqualizerParameters', eqParamsData); + eqParamsData = eqParamsData.toStruct; + eq_id = obj.appendToTable('Equalizer', eqParamsData); end % 3. Add the equalizer configuration reference and run_id to resultData - resultData.eqParam_id = eq_id; + resultData = resultData.toStruct; + resultData.eq_id = eq_id; resultData.run_id = run_id; % 4. Compute hash for the processing result tempResultData = rmfield(resultData, 'date_of_processing'); - resultJsonStr = jsonencode(tempResultData); - md2 = java.security.MessageDigest.getInstance('MD5'); % Create a new MD5 instance - md2.update(uint8(resultJsonStr)); - resultHashBytes = typecast(md2.digest, 'uint8'); - resultHashStr = lower(dec2hex(resultHashBytes)'); - resultHashStr = lower(strtrim(resultHashStr(:)')); % Convert to a lowercase string + % resultJsonStr = jsonencode(tempResultData); + % md2 = java.security.MessageDigest.getInstance('MD5'); % Create a new MD5 instance + % md2.update(uint8(resultJsonStr)); + % resultHashBytes = typecast(md2.digest, 'uint8'); + % resultHashStr = lower(dec2hex(resultHashBytes)'); + % resultHashStr = lower(strtrim(resultHashStr(:)')); % Convert to a lowercase string + resultHashStr = obj.calcHash(tempResultData); % Add the result hash to resultData - resultData.result_hash = resultHashStr; + resultData.hash = resultHashStr; % 5. Check if an identical processing result already exists - queryStr2 = sprintf('SELECT result_id FROM Results WHERE result_hash = ''%s''', resultData.result_hash); + queryStr2 = sprintf('SELECT result_id FROM Results WHERE hash = ''%s''', resultData.hash); existingResult = obj.fetch(queryStr2); if ~isempty(existingResult) @@ -397,7 +426,66 @@ classdef DBHandler < handle return; end - % 6. Insert the processing result + % 6. check if obj.tables.Results matches Metricstruct + % Fields to exclude from comparison + excludeFields = {'result_id', 'run_id', 'eq_id'}; + + % 7. Chack for new fields in Metric struct and append to + % database if necessary + ms=Metricstruct; + metricFields = setdiff(fieldnames(ms), excludeFields); + tableFields = setdiff(fieldnames(obj.tables.Results), excludeFields); + + % Check matches and find missing fields + matches = all(ismember(metricFields, tableFields)); + + if ~matches + missingFields = setdiff(metricFields, tableFields); + + + % If there are missing fields, add them to the SQL table + if ~isempty(missingFields) + for i = 1:length(missingFields) + fieldName = missingFields{i}; + + % Determine SQL data type based on MATLAB class + fieldValue = ms.(fieldName); + if isnumeric(fieldValue) + if isinteger(fieldValue) + sqlType = 'INTEGER'; + else + sqlType = 'REAL'; + end + elseif ischar(fieldValue) || isstring(fieldValue) + sqlType = 'TEXT'; + elseif isdatetime(fieldValue) + sqlType = 'DATETIME'; + elseif iscell(fieldValue) || isstruct(fieldValue) || islogical(fieldValue) + sqlType = 'TEXT'; % Store as JSON + else + sqlType = 'TEXT'; % Default to TEXT for unknown types + end + + % Create ALTER TABLE query + queryStr = sprintf('ALTER TABLE Results ADD COLUMN %s %s', fieldName, sqlType); + + try + % Execute the query + obj.fetch(queryStr); + fprintf('Added field "%s" of type %s to Results table\n', fieldName, sqlType); + catch ME + fprintf('Error adding field "%s": %s\n', fieldName, ME.message); + end + end + else + fprintf('No missing fields to add.\n'); + end + + end + + + + % 8. Insert the processing result resultID = obj.appendToTable('Results', resultData); end @@ -486,17 +574,32 @@ classdef DBHandler < handle end - - function executeSQL(obj, query) % This method executes an SQL statement using MATLAB's execute function. execute(obj.conn, query); end - function answer = fetch(obj,query) - answer = fetch(obj.conn,query); + + function answer = fetch(obj, query) + maxFast = 20; maxSlow = 30; + for attempt = 1:maxSlow + try + answer = fetch(obj.conn, query); + return + catch ME + if attempt < maxFast + pause(0.1) + else + pause(1) + end + lastErr = ME; + end + end + error('Database fetch failed after %d attempts:\n%s', maxSlow, lastErr.getReport()) end + + function [result,query] = queryDB(obj, filterParams, selectedFields) % getPathsWithFlexibleFilter Retrieves values from Runs table with flexible filtering % and lets the user select which fields to include in the SELECT statement. @@ -518,22 +621,6 @@ classdef DBHandler < handle selectedFields = []; end - % Step 1: Prompt the user to input filter parameters if not provided - if isempty(filterParams) - filterParams = obj.promptFilterParameters(); - end - - % Step 2: Prompt the user to select fields to include in the SELECT statement - if isempty(selectedFields) - selectedFields = obj.promptSelectFields(); - else - if iscell(selectedFields) - - elseif isstruct(selectedFields) - - end - end - % Step 3: Construct the SQL query based on the inputs query = obj.constructSQLQuery(filterParams, selectedFields); @@ -558,7 +645,7 @@ classdef DBHandler < handle cleanedTable.(varNames{i}) = numCol; else % Clean double-quoted SQL literals (e.g., ""no_db"") - cleanedTable.(varNames{i}) = strrep(string(col), '""', '"'); + cleanedTable.(varNames{i}) = strrep(string(col), '"', ''); end end end @@ -619,132 +706,238 @@ classdef DBHandler < handle end end - function query = constructSQLQuery(obj, filterParams, selectedFields) % constructSQLQuery Constructs the SQL query based on filter parameters and selected fields. - % - % If selectedFields is provided as a struct, it is converted to a cell array. - % The conversion takes the field names and creates entries like: - % {'selectedFields.fieldName'} for each field. - % Input check for selectedFields: if it's a struct, convert it to a cell array. - if isstruct(selectedFields) + arguments + obj + filterParams + selectedFields + end + + % -------- Step 1: Normalize selectedFields to {'Table.field', ...} -------- + if isempty(selectedFields) || (ischar(selectedFields) && strcmpi(selectedFields, 'all')) + selectedFields = obj.getTableFieldNames('Runs'); % default + elseif isstruct(selectedFields) newFields = {}; tableNames = fieldnames(selectedFields); for t = 1:numel(tableNames) tableStruct = selectedFields.(tableNames{t}); - fieldNames = fieldnames(tableStruct); - for f = 1:numel(fieldNames) - if isequal(tableStruct.(fieldNames{f}), 1) - newFields{end+1} = sprintf('%s.%s', tableNames{t}, fieldNames{f}); + fns = fieldnames(tableStruct); + for f = 1:numel(fns) + if isequal(tableStruct.(fns{f}), 1) + newFields{end+1} = sprintf('%s.%s', tableNames{t}, fns{f}); %#ok end end end selectedFields = newFields; end - % Construct the SELECT clause dynamically based on user selection. - % (Assuming that when provided as a cell array, each entry is of the form - % 'TableName.fieldName' or, in our conversion case, 'selectedFields.fieldName'.) - selectClause = 'SELECT DISTINCT '; - for i = 1:numel(selectedFields) - fieldParts = strsplit(selectedFields{i}, '.'); - % If the field comes from the struct conversion, its first part is 'selectedFields' - % and the actual field name is in the second part. - if strcmp(fieldParts{1}, 'selectedFields') - tableName = fieldParts{1}; % not used for type checking below - fieldName = fieldParts{2}; - else - tableName = fieldParts{1}; - fieldName = fieldParts{2}; - end + % Parse the table names actually referenced by the SELECT + reqTables = unique(cellfun(@(s) extractBefore(s, '.'), selectedFields, ... + 'UniformOutput', false)); - % Decide on COALESCE depending on the field type. - % If the table is known in obj.tables and the field is numeric, use 'NaN'. - if isfield(obj.tables, tableName) && isfield(obj.tables.(tableName), fieldName) && isnumeric(obj.tables.(tableName).(fieldName)) - selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', ''NaN'') AS ', fieldName]; - else - selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', '''') AS ', fieldName]; - end + % -------- Step 2: Build SELECT with COALESCE wrapper as you already do ---- + selectClause = obj.generateCoalesceString(selectedFields); - if i < numel(selectedFields) - selectClause = [selectClause, ', ']; - else - selectClause = [selectClause, ' ']; + % -------- Step 3: FROM and minimal JOIN plan ------------------------------ + % Decide main table: prefer the first explicitly referenced table, else 'Runs' + if ~isempty(reqTables) + mainTable = reqTables{1}; + else + mainTable = 'Runs'; + end + + % If WHERE references a table not in reqTables (e.g., Runs.*), make sure it’s present. + whereClause = ''; + if ~isempty(filterParams) + whereClause = obj.generateWhereClause(filterParams); + % Heuristic: add 'Runs' if WHERE clause mentions 'Runs.' + if contains(whereClause, 'Runs.') + reqTables = unique([reqTables; {'Runs'}]); %#ok end end + % Ensure main table is included + if ~ismember(mainTable, reqTables) + reqTables = unique([mainTable; reqTables]); %#ok + end - % --- Adaptive FROM Clause --- - mainTable = 'Runs'; - fromClause = ['FROM ', mainTable, ' ']; + % We’ll build joins only for the required tables (minus the main) + otherTables = setdiff(reqTables, {mainTable}); - % Prepare join buffers - normalJoins = ''; - equalizerJoin = ''; + % Keep track of what’s already in the FROM graph (start with main) + present = string(mainTable); + joins = strings(0,1); - tableNamesAll = fieldnames(obj.tables); - for t = 1:numel(tableNamesAll) - tableName = tableNamesAll{t}; + % Helper lambdas + hasField = @(tbl, fld) isfield(obj.tables.(char(tbl)), char(fld)); + canJoinBy = @(left, right, key) hasField(left, key) && hasField(right, key); - if strcmpi(tableName, mainTable) || strcmpi(tableName, 'sqlite_sequence') - continue; + + % A small helper that adds a LEFT JOIN if the right table isn't present yet + function addJoinByKey(rightTbl, key) + if any(present == string(rightTbl)) + return; % already joined end + % Prefer to join against an already-present table that has the key + anchor = ''; + for k = 1:numel(present) + if canJoinBy(char(present(k)), rightTbl, key) + anchor = char(present(k)); + break; + end + end + if isempty(anchor) + % No anchor in current graph; if the right table is 'Equalizer' and key is eq_id, + % try to ensure a bridge table with eq_id exists (Results or a dashboard view). + if strcmpi(rightTbl,'Equalizer') && strcmpi(key,'eq_id') + % Bring in one eq_id-capable table if it is requested + bridgeOrder = {'Results','dashboard_old','dashboard_new','dashboard_ungrouped'}; + for b = 1:numel(bridgeOrder) + br = bridgeOrder{b}; + if ismember(br, reqTables) && ~any(present == string(br)) && hasField(obj.tables.(br),'eq_id') + % Attach bridge by run_id if possible, otherwise leave for eq_id + if any(present == "Runs") && hasField(obj.tables.(br),'run_id') && hasField(obj.tables.('Runs'),'run_id') + joins(end+1,1) = "LEFT JOIN " + br + " ON Runs.run_id = " + br + ".run_id"; + present(end+1,1) = string(br); + anchor = br; % we can now anchor Equalizer on eq_id to this + break; + else + % Fallback: anchor to main if it shares eq_id + for k = 1:numel(present) + pk = char(present(k)); + if canJoinBy(pk, br, 'eq_id') + joins(end+1,1) = "LEFT JOIN " + br + " ON " + pk + ".eq_id = " + br + ".eq_id"; + present(end+1,1) = string(br); + anchor = br; + break; + end + end + if ~isempty(anchor), break; end + end + end + end + end + end + % Re-scan for an anchor (maybe the bridge helped) + if isempty(anchor) + for k = 1:numel(present) + if canJoinBy(char(present(k)), rightTbl, key) + anchor = char(present(k)); + break; + end + end + end + if isempty(anchor) + % As a final fallback, if the main table is Runs and right has run_id, join by run_id + if strcmpi(mainTable,'Runs') && hasField(obj.tables.(rightTbl),'run_id') && hasField(obj.tables.('Runs'),'run_id') + anchor = 'Runs'; + key = 'run_id'; + end + end + if isempty(anchor) + % Could not find a path; skip join silently (or throw if you prefer strict) + return; + end + joins(end+1,1) = "LEFT JOIN " + rightTbl + " ON " + anchor + "." + key + " = " + rightTbl + "." + key; + present(end+1,1) = string(rightTbl); + end - if isfield(obj.tables.(tableName), 'run_id') - % Direct join to Runs - normalJoins = [normalJoins, 'LEFT JOIN ', tableName, ' ON ', mainTable, '.run_id = ', tableName, '.run_id ']; - elseif isfield(obj.tables.(tableName), 'eq_id') - % Equalizer depends on Results, collect this join separately - equalizerJoin = ['LEFT JOIN ', tableName, ' ON Results.eqParam_id = ', tableName, '.eq_id ']; + % First pass: if WHERE uses Runs.* and mainTable isn’t Runs, ensure Runs is in the graph + if contains(string(whereClause), "Runs.") && ~any(present == "Runs") + % Try to join Runs to whatever has run_id (mainTable ideally) + if hasField(mainTable, 'run_id') && hasField('Runs', 'run_id') + joins(end+1,1) = "LEFT JOIN Runs ON " + string(mainTable) + ".run_id = Runs.run_id"; + present(end+1,1) = "Runs"; end end - % Combine joins: normal joins first, Equalizer last - fromClause = [fromClause, normalJoins, equalizerJoin]; + % Join the required tables with minimal edges + for i = 1:numel(otherTables) + tbl = otherTables{i}; + % Prefer run_id join if possible, else eq_id, else skip + if any(present == "Runs") && hasField(tbl,'run_id') + addJoinByKey(tbl, 'run_id'); + elseif hasField(tbl,'run_id') && hasField(mainTable,'run_id') + addJoinByKey(tbl, 'run_id'); + elseif hasField(tbl,'eq_id') + addJoinByKey(tbl, 'eq_id'); + else + % no obvious key; skip + end + end + % Build the FROM clause + fromClause = "FROM " + string(mainTable) + " " + strjoin(joins, " "); + % -------- Step 4: WHERE (unchanged logic) ------------------------------ + if ~isempty(whereClause) + query = char(strjoin([selectClause, fromClause, "WHERE " + string(whereClause)], " ")); + else + query = char(strjoin([selectClause, fromClause], " ")); + end + end - - % --- WHERE Clause Construction --- - baseQuery = [selectClause, ' ', fromClause, 'WHERE ']; + function whereClause = generateWhereClause(obj, filterParams) filterClauses = []; - tableNames_ = fieldnames(filterParams); - for t = 1:numel(tableNames_) - tableName = tableNames_{t}; + + % If filterParams is a DbFilterParameter object, get its internal structure + if isa(filterParams, 'QueryFilter') + filterParams = filterParams.toStruct(); + end + + % Now proceed with the structure + tableNames = fieldnames(filterParams); + + for t = 1:numel(tableNames) + tableName = tableNames{t}; tableParams = filterParams.(tableName); fieldNames = fieldnames(tableParams); + for i = 1:numel(fieldNames) fieldName = fieldNames{i}; value = tableParams.(fieldName); fullName = sprintf('%s.%s', tableName, fieldName); - % Handle various types of values for SQL query construction - if isempty(value) + % Skip empty values + if isempty(value) || (isa(value, 'QueryFilter') && isempty(value.value)) continue; - elseif isnumeric(value) && isnan(value) - filterClause = sprintf('%s IS NULL', fullName); - elseif isnumeric(value) && ~isEnumeration(value) - filterClause = sprintf('%s = %f', fullName, value); - elseif islogical(value) || (isnumeric(value) && ismember(value, [0, 1])) && ~isEnumeration(value) - filterClause = sprintf('%s = %d', fullName, value); - elseif ischar(value) || isstring(value) - filterClause = sprintf('%s = ''%s''', fullName, char(value)); - elseif isEnumeration(value) - filterClause = sprintf('%s = ''%s''', fullName, value); - else - error('Unsupported data type for field "%s".', fullName); end - filterClauses = [filterClauses, filterClause, ' AND ']; + + % Handle Filter class + if isa(value, 'SqlFilter') + if isnumeric(value.value) + filterClause = sprintf('%s %s %f', ... + fullName, value.operator, value.value); + elseif ischar(value.value) || isstring(value.value) + filterClause = sprintf('%s %s ''%s''', ... + fullName, value.operator, char(value.value)); + else + continue; % Skip unsupported types + end + filterClauses = [filterClauses, filterClause, ' AND ']; + else + % Handle direct values (legacy support) + if isnumeric(value) && isnan(value) + filterClause = sprintf('%s IS NULL', fullName); + elseif isnumeric(value) + filterClause = sprintf('%s = %f', fullName, value); + elseif ischar(value) || isstring(value) + filterClause = sprintf('%s = ''%s''', fullName, char(value)); + else + continue; % Skip unsupported types + end + filterClauses = [filterClauses, filterClause, ' AND ']; + end end end - % Remove trailing ' AND ' if any filters were added. + % Remove trailing ' AND ' if any filters were added if ~isempty(filterClauses) - filterClauses = filterClauses(1:end-5); - query = [selectClause, ' ', fromClause, 'WHERE ', filterClauses]; + whereClause = filterClauses(1:end-5); else - query = [selectClause, ' ', fromClause]; + whereClause = ''; end end @@ -980,5 +1173,59 @@ classdef DBHandler < handle end + function fieldNames = getTableFieldNames(obj, tableName) + % Returns all field names for a given table as a cell array in the format {'TableName.fieldName'} + if isfield(obj.tables, tableName) + % Get raw field names + rawFields = fieldnames(obj.tables.(tableName)); + + % Create cell array with table name prefix + fieldNames = cellfun(@(x) [tableName, '.', x], ... + rawFields, ... + 'UniformOutput', false); + else + error('Table "%s" not found in obj.tables.', tableName); + end + end + + + function coalesceStr = generateCoalesceString(obj, selectedFields) + % Generates a COALESCE string for selected fields + % Input: + % selectedFields: cell array of strings in format {'Table.field'} + % e.g., {'Runs.run_id', 'Runs.bitrate'} + % Output: + % coalesceStr: string with COALESCE statements + + arguments + obj + selectedFields cell + end + + % Initialize cell array to store each COALESCE statement + coalesceStatements = cell(length(selectedFields), 1); + + % Generate COALESCE statement for each field + for i = 1:length(selectedFields) + % Split table and field name + parts = strsplit(selectedFields{i}, '.'); + if length(parts) ~= 2 + error('Field name must be in format "Table.field": %s', selectedFields{i}); + end + tableName = parts{1}; + fieldName = parts{2}; + + % Generate COALESCE statement + coalesceStatements{i} = sprintf('COALESCE(%s.%s, ''NaN'') AS %s ', ... + tableName, fieldName, fieldName); + end + + % Join with comma, newline and MATLAB string continuation + coalesceStr = strjoin(coalesceStatements, [', ' sprintf('\n ')]); + % Add initial newline and spacing for formatting + coalesceStr = [sprintf('SELECT DISTINCT \n ') coalesceStr]; + end + + end end diff --git a/Classes/DataBaseHandler/copyStylingFrom.m b/Classes/DataBaseHandler/copyStylingFrom.m deleted file mode 100644 index aaf110f..0000000 --- a/Classes/DataBaseHandler/copyStylingFrom.m +++ /dev/null @@ -1,76 +0,0 @@ -function copyStylingFrom(figNumSource, figNumTgt) - % Get handles to the source and target figures - sourceFig = figure(figNumSource); - targetFig = figure(figNumTgt); - - % Get axes of source and target figures - sourceAxes = findall(sourceFig, 'type', 'axes'); - targetAxes = findall(targetFig, 'type', 'axes'); - - % Ensure the number of axes match - if length(sourceAxes) ~= length(targetAxes) - error('Number of axes in source and target figures must be the same.'); - end - - % Loop through each pair of axes and copy styling properties - for i = 1:length(sourceAxes) - copyAxesProperties(sourceAxes(i), targetAxes(i)); - end - - % Apply general figure properties if desired - targetFig.Color = sourceFig.Color; % Background color -end - -function copyAxesProperties(sourceAx, targetAx) - % List of properties to copy from source to target axes - propsToCopy = {'XColor', 'YColor', 'ZColor', 'FontSize', 'FontName', ... - 'GridColor', 'GridLineStyle', 'MinorGridColor', 'Box', ... - 'XGrid', 'YGrid', 'ZGrid', 'XMinorGrid', 'YMinorGrid', 'ZMinorGrid', ... - 'LineWidth', 'TitleFontSizeMultiplier', 'LabelFontSizeMultiplier'}; - - % Copy properties from source to target - for i = 1:length(propsToCopy) - try - targetAx.(propsToCopy{i}) = sourceAx.(propsToCopy{i}); - catch - % Skip property if it doesn't exist or can't be copied - end - end - - % Copy axis labels and titles - targetAx.Title.String = sourceAx.Title.String; - targetAx.XLabel.String = sourceAx.XLabel.String; - targetAx.YLabel.String = sourceAx.YLabel.String; - targetAx.ZLabel.String = sourceAx.ZLabel.String; - - % Copy children elements like lines, patches, etc. - sourceChildren = allchild(sourceAx); - targetChildren = allchild(targetAx); - - % Ensure the number of children elements match - if length(sourceChildren) ~= length(targetChildren) - warning('Number of elements in source and target axes differ. Styling may not be applied completely.'); - end - - % Copy properties of children (like lines, patches, etc.), except colors and legends - for i = 1:min(length(sourceChildren), length(targetChildren)) - copyObjectProperties(sourceChildren(i), targetChildren(i)); - end -end - -function copyObjectProperties(sourceObj, targetObj) - % List of common properties to copy for plot elements (lines, patches, etc.) - propsToCopy = {'LineStyle', 'LineWidth', 'Marker', 'MarkerSize', ... - 'MarkerEdgeColor', 'MarkerFaceColor', 'DisplayName'}; - - % Copy properties from source to target, excluding colors - for i = 1:length(propsToCopy) - try - if ~contains(propsToCopy{i}, 'Color') % Skip color properties - targetObj.(propsToCopy{i}) = sourceObj.(propsToCopy{i}); - end - catch - % Skip property if it doesn't exist or can't be copied - end - end -end diff --git a/Classes/Warehouse_class/classes/DataStorage.m b/Classes/Warehouse_class/classes/DataStorage.m index 91530cf..394a12f 100644 --- a/Classes/Warehouse_class/classes/DataStorage.m +++ b/Classes/Warehouse_class/classes/DataStorage.m @@ -51,8 +51,9 @@ classdef DataStorage < handle function save(obj,path) try save(path,"obj"); - catch - + catch e + disp(e.message) + disp('Provide save path') end end @@ -136,7 +137,21 @@ classdef DataStorage < handle if ~isempty(tmp) if isa(tmp,'double') - value(i) = tmp ; + try + value(i,:) = tmp ; + catch + a = size(value,2); + b = size(tmp,2); + if a > b + value(i,:) =[tmp,NaN(1,a-b)] ; + elseif a < b + value(i,:) = tmp(1:size(value,2)) ; + else + error('unknwon case...') + end + + + end elseif isa(tmp,'Signal') || isa(tmp,'struct') || isa(tmp,'Exfo_laser') || isa(tmp,'DC_supply') if i == 1 value = {}; @@ -150,7 +165,7 @@ classdef DataStorage < handle end value{i} = tmp{1} ; else - value{i} = tmp{1} ; + value{i} = tmp ; end else diff --git a/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine.m b/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine.m deleted file mode 100644 index 86cbe93..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine.m +++ /dev/null @@ -1,251 +0,0 @@ -% Script, that shows the data management routine :-) - -loadExistingWareHouse = 0; - -if loadExistingWareHouse - - [file, path] = uigetfile(); - wh = load([path filesep file]); - wh = wh.wh; - wh.showInfo; - -else - - % 1) Define all your parameters, best practice directly constructs a - % structure - - params = struct; - - params.l = [2,10]; - - params.dispersion = [0]; - - params.sgm = [0]; - -% params.pol = ["YXYXYXYX","YXXYYXXY","YYYYYYYY"]; - params.pol = ["alternated","paired","copolarized"]; - - params.p_in = [3]; - - params.p_out = [-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2]; - - params.pmd = [0.1]; - - params.gamma = [0.0023]; - - params.realization = [1:20]; - - params.numchannels = [16]; - - params.center_wavelength = floor([getSweepWavelengths(35, 50e9, 1310)] .* 1000) ./ 1000 ; - params.center_wavelength = [1285 1287 1290 1292 1295]; - params.center_wavelength = 1310; - - params.channelspacing = [400e9]; - - params.random_zdw = [0]; - - %wh = warehouse :-) - wh = DataStorage(params); - - wh.showInfo; - - wh.addStorage("ber"); - - wh.addStorage("totalBer"); - -end - - - -%2) Simulate a bunch of data - TO BE IMPLEMENTED HERE - for now use scripts -%from Sebastian - -%3) Once the simulation folder is around, specifiy path and analyze dirs - -path = uigetdir('C:\Users\Silas\Documents\MATLAB\Raw_Cluster_Simulations'); - -allMat = getAllFilesInFolder(path,'.mat'); -allErr = getAllFilesInFolder(path,'.err'); - -%allMat = dir([path filesep '*.mat']); - -%allErr = dir([path filesep '*.err']); - -if numel(allMat) == 0 - warning('You defined an empty folder. Could not locate any .mat file.') -else - fprintf('%-20s', 'Err Files:'); fprintf('%-12s', num2str(numel(allErr))); fprintf('\n'); - fprintf('%-20s', 'Mat Files:'); fprintf('%-12s', num2str(numel(allMat))); fprintf('\n'); - fprintf('%-20s', 'Missing Mat Files:'); fprintf('%-12s', num2str(numel(allErr)-numel(allMat))); fprintf('\n'); -end - -%4) Now load that data - -f = waitbar(0,'Please wait...'); -cnt = 0; - -for num = 1:numel(allMat) - - fileName = allMat(num).name; - fileFolder = allMat(num).path; - fileExt = allMat(num).ext; -% - matFile = load([fileFolder filesep fileName fileExt]); - matFile = matFile.loop_data; - - % ____________________________________ - % FIND THE DATAPOINT CURRENTLY LOADED - zdw = 1310; - - channelplan = "symmetric"; - - channelspacing = str2double(strrep(regexp(fileName,'(_chsp)+([\d]*)','match'),'_chsp','')).*1e9; - - numchannels = str2double(strrep(regexp(fileName,'(ch)+(_)+([\d]*)','match'),'ch_','')); - - center_wavelength = str2double(insertAfter(strrep(regexp(fileName,'(lambda)+([\d]*)','match'),'lambda',''),4,'.')); - - center_wavelength = floor(center_wavelength * 1000) / 1000; - - if center_wavelength == 2192 - continue - end - - center_wavelength = 1310; - - random_zdw = str2double(strrep(regexp(fileName,'(rzwd)+([\d])','match'),'rzwd','')); - - l = str2double(strrep(regexp(fileName,'([L])+(_)+([\d]*)','match'),'L_','')); - - d = str2double(strrep(regexp(fileName,'([D])+(_)+([\d]*)','match'),'D_','')); - - if d == 0 - sgm = false; - else - sgm = true; - end - - - if numel(regexp(fileName,'(YYYY)','match')) > 1 - pol = "copolarized"; - elseif numel(regexp(fileName,'(YXXY)','match')) > 1 - pol = "paired"; - elseif numel(regexp(fileName,'(YXYX)','match')) > 1 - pol = "alternated"; - else - pol = "copolarized"; - end - - p_in = str2double(strrep(regexp(fileName,'(pow_)+([-,\d]{1})','match'),'pow_','')); - - pmd = 0.1; - - gamma = 0.0023; - - realiz = str2double(strrep(regexp(fileName,'(r)+([-,\d]{1,3})','match'),'r','')); - - - - % ____________________________________ - % Get the information you want from current file - rop=[]; - ber = []; - for pow = 2:12 - - module_number = ''; - for p = 1:11 %11 because there are 11 ROP branches in model - - % get ROP - if p == 1 - p_out = matFile.dp_optatten_para.atten; - else - p_out = matFile.("dp_optatten__"+(p)+"_para").atten; - end - - p_out = round(p_out-10*log10(numel(matFile.config.parameters.common.wavelengthPlan))); - - for c = 1:numel(matFile.config.parameters.common.wavelengthPlan) - - ber(c) = matFile.("prms_compare_wdm__"+(p+1)+"_out"){1,c}.ber; - - end - - totalBer = matFile.("prms_compare_wdm__"+(p+1)+"_out"){1,end}.totalBer; - - if totalBer > 0.2 && channelspacing == 400e9 && pol == "alternated" - disp("stopping here"); - pause; - end - - - % ____________________________________ - % Add value to warehouse at the correct position - - - - wh.addValueToStorage(ber,'ber',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels, center_wavelength,channelspacing,random_zdw); - wh.getStoValue('ber',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels, center_wavelength,channelspacing,random_zdw); - wh.addValueToStorage(totalBer,'totalBer',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels,center_wavelength,channelspacing,random_zdw); - - end - - end - waitbar(num/numel(allMat),f,'Loading your data'); -end - -close(f) - - - - - -% 4) Hey! the warehouse is here and (hopefully) filled with data :-) - -% Create a save dialog -defaultDir = 'C:\Users\Silas\Documents\MATLAB\Raw_Cluster_Simulations\'; -defaultExt = '*.mat'; -[filename, pathname] = uiputfile(fullfile(defaultDir, defaultExt),'', 'wh.mat'); - -% Check if the user pressed Cancel -if isequal(filename, 0) || isequal(pathname, 0) - disp('Save operation canceled.'); -else - % Save the variable to the selected file - save(fullfile(pathname, filename), 'wh'); - disp(['Variable "wh" saved to: ', fullfile(pathname, filename)]); -end - - - - -function matFileStructArray = getAllFilesInFolder(folderPath,extension) - % Get a list of all files in the current folder - currentFolderFiles = dir(fullfile(folderPath, '*')); - - % Exclude '.' and '..' directories - currentFolderFiles = currentFolderFiles(~ismember({currentFolderFiles.name}, {'.', '..'})); - - % Initialize the structure array for .mat files - matFileStructArray = struct('path', {}, 'name', {}, 'ext', {}); - - % Loop over each file in the current folder - for i = 1:length(currentFolderFiles) - currentFile = currentFolderFiles(i); - - % Check if the current item is a file and has a .mat extension - if ~currentFile.isdir && endsWith(currentFile.name, extension, 'IgnoreCase', true) - % If it's a .mat file, add it to the structure array - [matFileStructArray(end + 1).path,matFileStructArray(end+1).name, matFileStructArray(end+1).ext] = fileparts(fullfile(folderPath, currentFile.name)); - elseif currentFile.isdir - % If it's a directory, recursively call the function - subfolderPath = fullfile(folderPath, currentFile.name); - subfolderMatFiles = getAllFilesInFolder(subfolderPath,extension); - - % Add .mat files from the subfolder to the structure array - matFileStructArray = [matFileStructArray, subfolderMatFiles]; - end - end -end - - diff --git a/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m b/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m deleted file mode 100644 index aaec117..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m +++ /dev/null @@ -1,250 +0,0 @@ -% Script, that shows the data management routine :-) - -loadExistingWareHouse = 0; - -if loadExistingWareHouse - - [file, path] = uigetfile(); - wh = load([path filesep file]); - wh = wh.wh; - wh.showInfo; - -else - - % 1) Define all your parameters, best practice directly constructs a - % structure - - params = struct; - - params.l = [2, 10]; - - params.dispersion = [0, 3]; - - params.sgm = [0, 1]; - -% params.pol = ["YXYXYXYX","YXXYYXXY","YYYYYYYY"]; - params.pol = ["alternated","paired","copolarized"]; - - params.p_in = [3]; - - params.p_out = [-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2]; - - params.pmd = [0.1]; - - params.gamma = [0.0023]; - - params.realization = [1:20]; - - params.numchannels = [1,2,4,8,16]; - - params.center_wavelength = floor([getSweepWavelengths(35, 50e9, 1310)] .* 1000) ./ 1000 ; - params.center_wavelength = [1285 1287 1290 1292 1295]; - params.center_wavelength = 1310; - - params.channelspacing = [400e9]; - - params.random_zdw = [0,1]; - - %wh = warehouse :-) - wh = DataStorage(params); - - wh.showInfo; - - wh.addStorage("ber"); - - wh.addStorage("totalBer"); - -end - - - -%2) Simulate a bunch of data - TO BE IMPLEMENTED HERE - for now use scripts -%from Sebastian - -%3) Once the simulation folder is around, specifiy path and analyze dirs - -path = uigetdir('C:\Users\Silas\Documents\MATLAB\Raw_Cluster_Simulations'); - -allMat = getAllFilesInFolder(path,'.mat'); -allErr = getAllFilesInFolder(path,'.err'); - -%allMat = dir([path filesep '*.mat']); - -%allErr = dir([path filesep '*.err']); - -if numel(allMat) == 0 - warning('You defined an empty folder. Could not locate any .mat file.') -else - fprintf('%-20s', 'Err Files:'); fprintf('%-12s', num2str(numel(allErr))); fprintf('\n'); - fprintf('%-20s', 'Mat Files:'); fprintf('%-12s', num2str(numel(allMat))); fprintf('\n'); - fprintf('%-20s', 'Missing Mat Files:'); fprintf('%-12s', num2str(numel(allErr)-numel(allMat))); fprintf('\n'); -end - -%4) Now load that data - -f = waitbar(0,'Please wait...'); -cnt = 0; - -for num = 1:numel(allMat) - - fileName = allMat(num).name; - fileFolder = allMat(num).path; - fileExt = allMat(num).ext; -% - matFile = load([fileFolder filesep fileName fileExt]); - matFile = matFile.loop_data; - - % ____________________________________ - % FIND THE DATAPOINT CURRENTLY LOADED - zdw = 1310; - - channelplan = "symmetric"; - - channelspacing = str2double(strrep(regexp(fileName,'(_chsp)+([\d]*)','match'),'_chsp','')).*1e9; - - numchannels = str2double(strrep(regexp(fileName,'(ch)+(_)+([\d]*)','match'),'ch_','')); - - center_wavelength = str2double(insertAfter(strrep(regexp(fileName,'(lambda)+([\d]*)','match'),'lambda',''),4,'.')); - - center_wavelength = floor(center_wavelength * 1000) / 1000; - - if center_wavelength == 2192 - continue - end - - center_wavelength = 1310; - - random_zdw = str2double(strrep(regexp(fileName,'(rzwd)+([\d])','match'),'rzwd','')); - - l = str2double(strrep(regexp(fileName,'([L])+(_)+([\d]*)','match'),'L_','')); - - d = str2double(strrep(regexp(fileName,'([D])+(_)+([\d]*)','match'),'D_','')); - - if d == 0 - sgm = false; - else - sgm = true; - end - - - if numel(regexp(fileName,'(YYYY)','match')) > 1 - pol = "copolarized"; - elseif numel(regexp(fileName,'(YXXY)','match')) > 1 - pol = "paired"; - elseif numel(regexp(fileName,'(YXYX)','match')) > 1 - pol = "alternated"; - else - pol = "copolarized"; - end - - p_in = str2double(strrep(regexp(fileName,'(pow_)+([-,\d]{1})','match'),'pow_','')); - - pmd = 0.1; - - gamma = 0.0023; - - realiz = str2double(strrep(regexp(fileName,'(r)+([-,\d]{1,3})','match'),'r','')); - - - - % ____________________________________ - % Get the information you want from current file - rop=[]; - ber = []; - for pow = 2:12 - - module_number = ''; - for p = 1:11 %11 because there are 11 ROP branches in model - - % get ROP - if p == 1 - p_out = matFile.dp_optatten_para.atten; - else - p_out = matFile.("dp_optatten__"+(p)+"_para").atten; - end - - p_out = round(p_out-10*log10(numel(matFile.config.parameters.common.wavelengthPlan))); - - for c = 1:numel(matFile.config.parameters.common.wavelengthPlan) - - ber(c) = matFile.("prms_compare_wdm__"+(p+1)+"_out"){1,c}.ber; - - end - - totalBer = matFile.("prms_compare_wdm__"+(p+1)+"_out"){1,end}.totalBer; - - if totalBer > 0.2 && channelspacing == 400e9 && pol == "alternated" - disp("stopping here"); - pause; - end - - % ____________________________________ - % Add value to warehouse at the correct position - - - - wh.addValueToStorage(ber,'ber',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels, center_wavelength,channelspacing,random_zdw); - wh.getStoValue('ber',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels, center_wavelength,channelspacing,random_zdw); - wh.addValueToStorage(totalBer,'totalBer',l,d,sgm,pol,p_in,p_out,pmd,gamma,realiz,numchannels,center_wavelength,channelspacing,random_zdw); - - end - - end - waitbar(num/numel(allMat),f,'Loading your data'); -end - -close(f) - - - - - -% 4) Hey! the warehouse is here and (hopefully) filled with data :-) - -% Create a save dialog -defaultDir = 'C:\Users\Silas\Documents\MATLAB\Raw_Cluster_Simulations\'; -defaultExt = '*.mat'; -[filename, pathname] = uiputfile(fullfile(defaultDir, defaultExt),'', 'wh.mat'); - -% Check if the user pressed Cancel -if isequal(filename, 0) || isequal(pathname, 0) - disp('Save operation canceled.'); -else - % Save the variable to the selected file - save(fullfile(pathname, filename), 'wh'); - disp(['Variable "wh" saved to: ', fullfile(pathname, filename)]); -end - - - - -function matFileStructArray = getAllFilesInFolder(folderPath,extension) - % Get a list of all files in the current folder - currentFolderFiles = dir(fullfile(folderPath, '*')); - - % Exclude '.' and '..' directories - currentFolderFiles = currentFolderFiles(~ismember({currentFolderFiles.name}, {'.', '..'})); - - % Initialize the structure array for .mat files - matFileStructArray = struct('path', {}, 'name', {}, 'ext', {}); - - % Loop over each file in the current folder - for i = 1:length(currentFolderFiles) - currentFile = currentFolderFiles(i); - - % Check if the current item is a file and has a .mat extension - if ~currentFile.isdir && endsWith(currentFile.name, extension, 'IgnoreCase', true) - % If it's a .mat file, add it to the structure array - [matFileStructArray(end + 1).path,matFileStructArray(end+1).name, matFileStructArray(end+1).ext] = fileparts(fullfile(folderPath, currentFile.name)); - elseif currentFile.isdir - % If it's a directory, recursively call the function - subfolderPath = fullfile(folderPath, currentFile.name); - subfolderMatFiles = getAllFilesInFolder(subfolderPath,extension); - - % Add .mat files from the subfolder to the structure array - matFileStructArray = [matFileStructArray, subfolderMatFiles]; - end - end -end - - diff --git a/Classes/Warehouse_class/functions/fwm_plots/automate_JLT_plots.m b/Classes/Warehouse_class/functions/fwm_plots/automate_JLT_plots.m deleted file mode 100644 index 68b77b6..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/automate_JLT_plots.m +++ /dev/null @@ -1,394 +0,0 @@ - - -%automate plots -[file, path] = uigetfile("C:\Users\Silas\Documents\MATLAB\Datensätze\Raw_Cluster_Simulations\session_februar_24\wh_mi_nacht.mat"); -wh = load([path filesep file]); -wh = wh.wh; - -plotJob = struct(); -width = 350; -height = 200; -plotJob.Position = [100 100 width 100+height]; -cols = cbrewer2("paired",12); -plotJob.color = cols(1,:); -plotJob.l = 2; -plotJob.ch = 16; -plotJob.d = 0; -plotJob.sgm = 0; -plotJob.pol = "copolarized"; -plotJob.p_in = 3; -plotJob.gamma = 0; -plotJob.pmd = 0; -plotJob.channelspacing = 400e9; -plotJob.randzdw = 0; - - -plotJob.plot_ber_curve = 1; -plotJob.plot_3dber_curve = 0; -plotJob.plot_violin = 0; -plotJob.plot_wavelength_sweep = 0; -plotJob.plot_wavelength_sweep_failure_rate = 0; - - -plotJob.dataStatArg = 'Lineplot with quartiles'; -plotJob.plotTypeArg = 'Lines'; -plotJob.displayname = 'a'; -plotJob.title = 'title'; -plotJob.figName = '16 Chann__'; -plotJob.xAxisLabel = 'ROP per Channel in dBm'; -plotJob.yAxisLabel = 'BER'; - -% createbercurves(wh,plotJob) -createviolinplots(wh,plotJob); -% createsweepplots(wh,plotJob); - - -%% 1 -function createbercurves(wh,plotJob) -width = 1650; -height = 400; -s = 100; -e = 100; - -cols = cbrewer2("paired",12); -numRows = 2; -numCols = 4; - -plotJob.figName = '16 Chann_200G'; -plotJob.channelspacing = 400e9; -plotJob.ch = 16; -Len = [2,2,2,2,10,10,10,10]; -Pol = ["copolarized","alternated","paired","copolarized","copolarized","alternated","paired","copolarized"]; -Title = ["Co Polarized","Alternating Pol. Interl.","Paired Pol. Interl.","Link Segmentation",]; -D = [0,0,0,3,0,0,0,3]; -Sgm = [0,0,0,1,0,0,0,1]; - -colidx = [4,8,6]; -P_launch = [0,3,6]; - -fig = figure('Name',plotJob.figName); -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [0 0 18 7]; -t = tiledlayout(numRows,numCols,'TileSpacing','compact','Padding','compact'); -for idx = 1:(numRows * numCols) - % Create subplot - % sp = subplot(numRows, numCols, idx); - nexttile; - plotJob.l = Len(idx); - plotJob.pol = Pol(idx); - plotJob.d = D(idx); - plotJob.sgm = Sgm(idx); - plotJob.randzdw = 1; - - for i = 1:3 - - plotJob.p_in = P_launch(i); - plotJob.color = cols(colidx(i),:); - hold on - plotCurve(wh, plotJob); - - end - % - if idx ~= 1 && idx ~= 5 % For example, hide y-axis for subplot 1 - set(gca, 'YTickLabel',[]); % Hide y-axis ticks and labels - set(gca,'YGrid','on'); - set(gca, 'YLabel', []); - end - if idx ~= 5 && idx ~= 6 && idx ~= 7 && idx ~= 8 - set(gca, 'XLabel', []); - set(gca, 'XTickLabel', []); - - end - grid on - - g = gca; - pos = g.Position; - if idx <= 4 - title(Title(idx),'FontSize',8); - % a = annotation('textbox', pos-[0.0020 -0.1434 0.0947 0.3121], 'String', "FEC: 3.8e-3","LineStyle","none","FontSize",8,'LineWidth',0.1,'Interpreter','latex',"FontUnits","points",'Vert','middle','FitBoxToText','on'); - % a = annotation('textbox', pos, 'String', "2 km","LineStyle","none","FontSize",8,'LineWidth',0.1,'Interpreter','latex',"FontUnits","points",'Vert','bottom','FitBoxToText','on'); - else - % a = annotation('textbox', pos, 'String', "FEC: 3.8e-3","LineStyle","none","FontSize",8,'LineWidth',0.1,'Interpreter','latex',"FontUnits","points",'Vert','middle','FitBoxToText','on'); - % a = annotation('textbox', pos, 'String', "10 km","LineStyle","none","FontSize",8,'LineWidth',0.1,'Interpreter','latex',"FontUnits","points",'Vert','bottom','FitBoxToText','on'); - end - -end - -% Create textbox -annotation(fig,'textbox',... - [0.0696078431372547 0.246851385390432 0.0656862745098043 0.0453400503778337],... - 'String','10 km',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.288235294117647 0.239294710327459 0.0656862745098043 0.0453400503778338],... - 'String','10 km',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.516666666666666 0.241813602015117 0.0656862745098042 0.0453400503778338],... - 'String','10 km',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.742156862745097 0.236775818639802 0.0656862745098042 0.0453400503778339],... - 'String','10 km',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.071996471804854 0.578899159967702 0.0656862745098039 0.0453400503778341],... - 'String',{'2 km'},... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.29596893566113 0.576253721089996 0.0656862745098041 0.0453400503778341],... - 'String',{'2 km'},... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.524883914268912 0.580785315705112 0.0656862745098041 0.0453400503778341],... - 'String',{'2 km'},... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.753758338909501 0.581291504465311 0.0656862745098037 0.0453400503778341],... - 'String',{'2 km'},... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -a=sgtitle(['N=',num2str(plotJob.ch),'; $\Delta f_{\mathrm{ch}}$= ',num2str(plotJob.channelspacing*1e-9),' GHz'],'FontSIze',10); -a.Interpreter = "latex"; - -lgd = legend('$P_{\mathrm{in}}=0$ dBm','$P_{\mathrm{in}}=3$ dBm','$P_{\mathrm{in}}=6$ dBm','Interpreter','latex'); -lgd.NumColumns = 3; -lgd.Layout.Tile = 'south'; - -copygraphics(t,'BackgroundColor','none'); -end - -%% 2 -function createviolinplots(wh,plotJob) -width = 350; -height = 200; -s = 100; -e = 100; - -cols = cbrewer2("paired",12); -numRows = 1; -numCols = 4; - - -plotJob.ch = 16; -plotJob.p_in = 3; -plotJob.randzdw = 0; - -Pol = ["copolarized","copolarized","alternated","paired",]; -Title = ["Co Pol.","Link Segmentation","Paired Pol. Interl.","Alternating Pol. Interl."]; -D = [0,3,0,0]; -Sgm = [0,1,0,0]; - -colidx = [2]; -Len = [2]; - -plotJob.figName = ['_Violin',num2str(plotJob.ch),' Channels; ',num2str(plotJob.channelspacing*1e-9),' GHz; ',num2str(plotJob.p_in),' dBm; randomized: ', num2str(plotJob.randzdw)]; -fig = figure('Name',plotJob.figName); -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [0 0 18 7]; - -t = tiledlayout(numRows,numCols,'TileSpacing','compact','Padding','compact'); -for idx = 1:(numRows * numCols) - % Create subplot - %subplot(numRows, numCols, idx); - nexttile; - - plotJob.pol = Pol(idx); - plotJob.d = D(idx); - plotJob.sgm = Sgm(idx); - - for i = 1 - - plotJob.color = cols(colidx(i),:); - plotJob.l = Len(i); - hold on - plotViolin(wh, plotJob); - - end - - if idx ~= 1 % For example, hide y-axis for subplot 1 - %set(gca, 'YTickLabel',[]); % Hide y-axis ticks and labels - set(gca, 'YGrid','on'); - set(gca, 'YLabel', []); - end - - if idx <= 4 - title(Title(idx)); - end - - -end - -% Create textbox -annotation(fig,'textbox',... - [0.300019607843137 0.816120906801009 0.108803921568628 0.0906801007556676],... - 'String','$P_{\mathrm{in}}=3$ dBm',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.530411764705882 0.81612090680101 0.108803921568628 0.0906801007556676],... - 'String','$P_{\mathrm{in}}=3$ dBm',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.755901960784313 0.816120906801011 0.108803921568628 0.0906801007556676],... - 'String','$P_{\mathrm{in}}=3$ dBm',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -% Create textbox -annotation(fig,'textbox',... - [0.0696274509803918 0.584382871536529 0.108803921568628 0.0906801007556676],... - 'String','$P_{\mathrm{in}}=3$ dBm',... - 'LineStyle','none',... - 'Interpreter','latex',... - 'FontSize',8,... - 'FitBoxToText','off'); - -copygraphics(t,'BackgroundColor','none'); -% lgd = legend('$P_{\mathrm{in}}=0$ dBm','$P_{\mathrm{in}}=3$ dBm','$P_{\mathrm{in}}=6$ dBm','Interpreter','latex'); -% lgd.NumColumns = 3; -% lgd.Layout.Tile = 'south'; - -end - -%% 3 -function createsweepplots(wh,plotJob) - -width = 650; -height = 200; -s = 100; -e = 100; -plotJob.Position = [0 0 width e+height]; - -cols = cbrewer2("paired",12); -numRows = 1; -numCols = 4; - - -plotJob.channelspacing = 200e9; -plotJob.ch = 16; -plotJob.randzdw = 1; -plotJob.l = 10; - -plotJob.p_in = 3; - -Pol = ["copolarized","alternated","paired","copolarized"]; -Title = ["Co Polarized","Alternating Pol. Interl.","Paired Pol. Interl.","Link Segmentation",]; -D = [0,0,0,3]; -Sgm = [0,0,0,1]; -Channelspacing = [200e9, 200e9]; -PlotTypeArg = ["--","-"]; -colidx = [6,8,2,4]; -Len = [2,10]; - -plotJob.figName = [num2str(plotJob.ch),num2str(plotJob.channelspacing*1e-9),num2str(plotJob.p_in),'...']; -plotJob.figName = "10km 400ghz"; - - - - -fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - -if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on -else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on -end - -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [0 0 18 7]; - -for j = 1 - - plotJob.channelspacing = Channelspacing(j); - plotJob.plotTypeArg = PlotTypeArg(j); - - for idx = 1:4 - - plotJob.color = cols(colidx(idx),:); - plotJob.pol = Pol(idx); - plotJob.d = D(idx); - plotJob.sgm = Sgm(idx); - plotJob.displayname = [char(plotJob.pol)]; - hold on - plotBerVsZdwFailureRate(wh, plotJob); - - end -end -legend('Location', 'southoutside', 'Orientation', 'horizontal'); - - -%plot channel positions -hold on -chpos = calcWavelengthPlan(plotJob.ch, plotJob.channelspacing, 1310); -xline(chpos,'LineWidth',2,'Alpha',0.4,'HandleVisibility','off'); - -chpos = calcWavelengthPlan(plotJob.ch, plotJob.channelspacing, chpos(4)); -xline(chpos,'LineWidth',2,'LineStyle','--','Alpha',0.1,'HandleVisibility','off'); - -title(['N=',num2str(plotJob.ch),' $\Delta f_{\mathrm{ch}}$= ',num2str(plotJob.channelspacing*1e-9),' GHz'],'FontSize',10,'Interpreter','latex'); - - - - -end - - - diff --git a/Classes/Warehouse_class/functions/fwm_plots/dispersion_only.m b/Classes/Warehouse_class/functions/fwm_plots/dispersion_only.m deleted file mode 100644 index a0e8a98..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/dispersion_only.m +++ /dev/null @@ -1,83 +0,0 @@ -%automate plots -[file, path] = uigetfile("C:\Users\Silas\Documents\MATLAB\Datensätze\Raw_Cluster_Simulations\"); -wh = load([path filesep file]); -wh = wh.wh; - -plotJob = struct(); -width = 350; -height = 200; -plotJob.Position = [100 100 width 100+height]; -cols = cbrewer2("paired",12); -plotJob.color = cols(1,:); -plotJob.l = 1; -plotJob.ch = 1; - -plotJob.sgm = 1; -plotJob.pol = "copolarized"; -plotJob.p_in = 3; -plotJob.gamma = 0.0023; -plotJob.pmd = 0.1; -plotJob.channelspacing = 400e9; -plotJob.randzdw = 0; - - -plotJob.plot_ber_curve = 1; -plotJob.plot_3dber_curve = 0; -plotJob.plot_violin = 0; -plotJob.plot_wavelength_sweep = 0; -plotJob.plot_wavelength_sweep_failure_rate = 0; - - -plotJob.dataStatArg = 'Lineplot with quartiles'; -plotJob.plotTypeArg = 'Lines'; -plotJob.displayname = 'a'; -plotJob.title = 'title'; -plotJob.figName = '1 Chann__'; -plotJob.xAxisLabel = 'ROP per Channel in dBm'; -plotJob.yAxisLabel = 'BER'; - - -plotJob.d = 0; - -xAxis = wh.parameter.p_out.values; -D = wh.parameter.dispersion.values; - -figure() -ber_ = []; -for d_ = 0:39 - if d_ == 0 - plotJob.sgm = 0; - ber_(d_+1,:) = wh.getStoValue('ber',plotJob.l,d_,plotJob.sgm,string(plotJob.pol),plotJob.p_in,xAxis,plotJob.pmd,plotJob.gamma,1,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw)'; - else - plotJob.sgm = 1; - ber_(d_+1,:) = wh.getStoValue('ber',plotJob.l,d_,plotJob.sgm,string(plotJob.pol),plotJob.p_in,xAxis,plotJob.pmd,plotJob.gamma,1,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw)'; - end - hold on - plot(xAxis,ber_(d_+1,:)) - set(gca,'yscale','log'); -end -yline(3.8e-3); - - -hdfec = 3.8e-3.*ones(size(xAxis)); -for i = 1:size(ber_,1) - ber_series = ber_(i,:); - a = InterX([hdfec(:)';xAxis],[ber_series;xAxis]); - cross(i) = a(2); -end - -col = cbrewer2('Paired',8); -figure() -plot(D,cross,'Marker','o','MarkerSize',5,'MarkerEdgeColor',[1,1,1],'MarkerFaceColor',col(2,:),'Color',col(1,:),'LineWidth',1); -grid minor -xlabel('Accumulated Dispersion') -ylabel('Required ROP to reach FEC limit in dB') -line([D(16),D(16)],[-10,cross(16)],'linestyle','--') -line([0,D(16)],[cross(16),cross(16)],'linestyle','--') - -line([D(29),D(29)],[-10,cross(29)],'linestyle','--') -line([0,D(29)],[cross(29),cross(29)],'linestyle','--') - - - - diff --git a/Classes/Warehouse_class/functions/fwm_plots/dispersion_validation_miniskript.m b/Classes/Warehouse_class/functions/fwm_plots/dispersion_validation_miniskript.m deleted file mode 100644 index 802db0a..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/dispersion_validation_miniskript.m +++ /dev/null @@ -1,52 +0,0 @@ - -wh = load("C:\Users\Silas\Documents\MATLAB\Datensätze\Raw_Cluster_Simulations\session_dispersion_validation\wh_with_variation.mat"); -wh = wh.wh; - -lambda = 1295; - -figure(3) -plot(xAxis,getber(wh,lambda),'DisplayName',['w:', num2str(lambda), 'variation']); -yline(3.8e-3,'HandleVisibility','off'); -legend -set(gca,'yscale','log'); -grid(gca,'on'); -grid(gca,'minor'); -grid minor -fontsize(gca,8,"points") -fig.Units = "centimeters"; -fig.Position = [2 2 8.5 7]; -set(gca,'TickLabelInterpreter','latex') -ylim([1e-5,0.5]); -xlim([min(xAxis),-3]); - -wh = load("C:\Users\Silas\Documents\MATLAB\Datensätze\Raw_Cluster_Simulations\session_dispersion_validation\wh_no_variation.mat"); -wh = wh.wh; - - -figure(3) -hold on -plot(xAxis,getber(wh,lambda),'DisplayName',['w:', num2str(lambda), 'no variation']); -yline(3.8e-3,'HandleVisibility','off'); -legend -set(gca,'yscale','log'); -grid(gca,'on'); -grid(gca,'minor'); -grid minor -fontsize(gca,8,"points") -fig.Units = "centimeters"; -fig.Position = [2 2 8.5 7]; -set(gca,'TickLabelInterpreter','latex') -ylim([1e-5,0.5]); -xlim([min(xAxis),-3]); - - -function ber = getber(wh,lambda) - realization = wh.parameter.realization.values(1:end); - xAxis = wh.parameter.p_out.values; - ber = []; - for xl = 1:numel(xAxis) - p_out = xAxis(xl); - temp = wh.getStoValue('ber',10,0,0,"copolarized",3,p_out,0.1,0.0023,realization,1,lambda,400e9,1); - ber(xl) = mean(temp,'all'); - end -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/generatePlots.m b/Classes/Warehouse_class/functions/fwm_plots/generatePlots.m deleted file mode 100644 index 2e720ff..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/generatePlots.m +++ /dev/null @@ -1,61 +0,0 @@ -function generatePlots(wh,plotJob) - -% 0) Test for valid query: -p_out = wh.parameter.p_out.values(1); -realization = 9; - - -if 1 %~isempty(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310)) - % test violin - - baseName = plotJob.figName; - - width = 350; - height = 200; - s = 100; - e = 100; - - if plotJob.plot_ber_curve - plotJob.Position = [100 100 width e+height]; - plotJob.figName = [baseName, ' zdwvsber']; - plotCurve(wh, plotJob); - end - - if plotJob.plot_3dber_curve - plotJob.Position = [100 100 width e+height]; - plotJob.figName = [baseName, ' zdwvsber']; - plot3dCurve(wh, plotJob); - end - - if plotJob.plot_wavelength_sweep - plotJob.Position = [100 100 width e+height]; - plotJob.figName = [baseName, ' zdwvsber']; - plotBerVsZDW(wh, plotJob); - end - - if plotJob.plot_wavelength_sweep_failure_rate - plotJob.Position = [100 100 width e+height]; - plotJob.figName = [baseName, ' zdwvsber']; - plotBerVsZdwFailureRate(wh, plotJob); - end - - if plotJob.plot_violin - plotJob.Position = [s+width 100 width e+height]; - plotJob.figName = [baseName, ' violin']; - plotViolin(wh, plotJob); - end - - - if 0 - %2) plotHistogram - plotJob.Position = [s+2*width 100 width e+height]; - plotJob.figName = [baseName, ' FEC crossing']; - plotHistogram(wh,plotJob) - end - - -else - warndlg('The requested Datapoint is not available... This can occur for some edgecase constellations... ') -end - -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/plot3dCurve.m b/Classes/Warehouse_class/functions/fwm_plots/plot3dCurve.m deleted file mode 100644 index 37d8fcf..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plot3dCurve.m +++ /dev/null @@ -1,248 +0,0 @@ -function plotCurve(wh,plotJob) -%PLOTCURVE Summary of this function goes here -% Detailed explanation goes here - -fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - -if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on -else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on -end - -col = plotJob.color; - -% we want to fetch all realizations -if plotJob.pmd == 0 - realization = 499; - % realization = 0:7; -else - realization = wh.parameter.realization.values(1:end); -end -realization = wh.parameter.realization.values(1:end); -% get all xAxis values -xAxis = wh.parameter.p_out.values; - -% Fetch Data from Warehouse -for xl = 1:numel(xAxis) - p_out = xAxis(xl); - if string(plotJob.dataStatArg) == "Worst" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = max(temp,[],'all'); - linew = 1.0; - markersz = 3; - linestyle = '-'; - elseif string(plotJob.dataStatArg) == "AVG" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = mean(temp,'all'); - linew = 1.0; - markersz = 3; - linestyle = '-'; - elseif string(plotJob.dataStatArg) == "Best" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = min(temp,[],'all'); - linew = 1.0; - markersz = 3; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "All Channels; mean(PMD Realizations)" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - - ber(:,xl) = mean(temp,1,"omitnan").'; - - linew = 1; - markersz = 3; - linestyle = '--'; - - elseif string(plotJob.dataStatArg) == "Lineplot with quartiles" - - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = mean(temp,"all","omitnan").'; - - - upperq(xl) = quantile(temp,0.9,"all"); - lowerq(xl) = quantile(temp,0.1,"all"); - - if lowerq(xl) == 0 - lowerq(xl) = lowerq(xl-1); - end - % upperq(xl) = 0.5*std(tmp,1,'all','omitnan'); - % lowerq(xl) = 0.5*std(tmp,1,'all','omitnan'); - - % upperq(xl) = max(dataNoNans); - % lowerq(xl) = min(dataNoNans); - - % upperq(xl) = mean(tmp,"all","omitnan") + 1.96 * (std(tmp,1,'all','omitnan')/sqrt(numel(tmp))); - % lowerq(xl) = mean(tmp,"all","omitnan") - 1.96 * (std(tmp,1,'all','omitnan')/sqrt(numel(tmp))); - - linew = 1; - markersz = 1; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "All Channels ;All PMD Realizations" - - tmp = reshape(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw).',[],1); - ber(1:size(tmp,1),xl) = tmp; - - linew = 0.3; - markersz = 2; - linestyle = ':'; - end -end - -xAxis = xAxis; - -% Plot Data -if string(plotJob.plotTypeArg) == "Scatter" - for rlz = 1:size(ber,1) - - if rlz < size(ber,1) - scatter(xAxis,ber(rlz,:),markersz,'MarkerEdgeColor',col,'MarkerFaceColor',col,'Parent', AxesMain(1),'HandleVisibility','off'); - else - scatter(xAxis,ber(rlz,:),markersz,'MarkerEdgeColor',col,'MarkerFaceColor',col,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - end - - end - -elseif string(plotJob.plotTypeArg) == "Lines" - - % if ~anynan(ber) - % [xAxis,ber] = interpCurve(xAxis, ber); - % end - - for rlz = 1:size(ber,1) -% - if (string(plotJob.dataStatArg) == "All Channels ;All PMD Realizations")||(string(plotJob.dataStatArg) == "All Channels; mean(PMD Realizations)") - ch = mod(rlz,plotJob.ch); - if ch == 0; ch = plotJob.ch; end - else - ch = plotJob.dataStatArg; - end - - if rlz <= size(ber,1) - - - s = plot3(xAxis,repmat(ch,1,numel(xAxis)),ber(rlz,:),linestyle,'Marker',"o",'MarkerSize',markersz,'MarkerFaceColor',plotJob.color,'LineWidth',linew,'Color',col,'Parent', AxesMain,'HandleVisibility','off'); - - s.DataTipTemplate.Interpreter = "latex"; - s.DataTipTemplate.DataTipRows(1).Label = "Ch: "; - s.DataTipTemplate.DataTipRows(1).Value = repmat(ch,size(ber)); - s.DataTipTemplate.DataTipRows(1); - s.DataTipTemplate.DataTipRows(2) = []; - - - else - - if string(plotJob.dataStatArg) == "Lineplot with quartiles" - [hl,hp] = boundedline(xAxis,ber(rlz,:),([(ber(rlz,:)-lowerq(rlz,:));upperq(rlz,:)-ber(rlz,:)]'),'-*', 'alpha','Color',col,'transparency', 0.2); - hp.LineWidth = 1.2; - - ho = outlinebounds(hl,hp); - set(ho, 'linestyle', ':', 'color', col); - else - - s = plot(xAxis,ber(rlz,:),linestyle,'Marker',"o",'MarkerFaceColor',col,'MarkerSize',markersz,'LineWidth',linew,'Color',col,'Parent', AxesMain,'DisplayName',[plotJob.displayname]); - - s.DataTipTemplate.Interpreter = "latex"; - s.DataTipTemplate.DataTipRows(1).Label = "Ch: "; - s.DataTipTemplate.DataTipRows(1).Value = repmat(string(ch),size(ber)); - s.DataTipTemplate.DataTipRows(1) - s.DataTipTemplate.DataTipRows(2) = []; - end - - end - - end -end - -% Draw FEC Threshold Line -%get x data of first children: -%get all linear Values -if 0 - linear = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,wh.parameter.p_out.values,0,0,499,"symmetric"); - linear = mean(linear,2); - lincurve = findall(AxesMain, 'Type', 'line','DisplayName','Linear Baseline'); - % - if isempty(lincurve) - xdata = AxesMain.Children(1).XData; - hdfec = 3.8e-3.*ones(size(xdata)); - plot(xdata,linear,'-','Marker',"o",'MarkerSize',3,'LineWidth',4,'Color',[0.6400 0.6400 0.6400],'MarkerFaceColor',[0.6400 0.6400 0.6400],'Parent', AxesMain,'DisplayName','Linear Baseline'); - %h = get(gca,'Children'); - %set(gca,'Children',[h(2) h(1)]) - end -end - -feccurve = findall(AxesMain, 'Type', 'line','DisplayName','FEC $3.8*10^{-3}$'); -% -if isempty(feccurve) - xdata = xAxis; - hdfec = 3.8e-3.*ones(size(xdata)); - for ch = 1:plotJob.ch - plot3(xdata,repmat(ch,1,numel(xAxis)),hdfec,':','MarkerSize',4,'Color','black','MarkerFaceColor','black','LineWidth',0.5,'Parent', AxesMain,'DisplayName','FEC $3.8*10^{-3}$','HandleVisibility','off'); - end - %h = get(gca,'Children'); - %set(gca,'Children',[h(2) h(1)]) -end - -% Figure Settings -%title(AxesMain,plotJob.title,"Interpreter","none"); - -xlabel(AxesMain,plotJob.xAxisLabel,"Interpreter","none"); - -ylabel(AxesMain,plotJob.yAxisLabel,"Interpreter","none"); - -set(AxesMain,'zscale','log'); - -grid(AxesMain,'on'); - -grid(AxesMain,'minor'); - -grid minor - -view(AxesMain,[42.0619302949062 23.4176470588235]); -%legend(AxesMain); - -fontsize(AxesMain,8,"points") -fontname(AxesMain,"Arial") - -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [2 2 8.5 7]; - -set(AxesMain,'TickLabelInterpreter','none') - -set(AxesMain.Legend,'Interpreter','none') -% set(gcf,'Units','centimeters') -% set(gcf,'Position',[2 2 9 4.5]) - -zlim([1e-4,0.3]); - -xlim([min(xAxis),-3]); - - -annotation('textbox', [0.125, 0.32, 0.1, 0.1], 'String', "FEC 3.8e-3","LineStyle","none","FontSize",8,"FontUnits","points") - - -hold off - - -end - - -function vec = removeZeros(vec) - % Find rows that contain only zeros - rows_to_remove = all(vec == 0, 2); - - % Remove rows with only zeros - vec(rows_to_remove, :) = []; -end diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZDW.m b/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZDW.m deleted file mode 100644 index bebc5b5..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZDW.m +++ /dev/null @@ -1,300 +0,0 @@ -function plotBerVsZDW(wh,plotJob) - - fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - - if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on - else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on - end - - - col = plotJob.color; - - % we want to fetch all realizations - realization = wh.parameter.realization.values(1:end); - - % get all xAxis values - xAxis = wh.parameter.p_out.values; - - % get all center wavelengths - wavelengths = wh.parameter.center_wavelength.values; - -% totber = NaN(numel(realization),1,numel(xAxis),numel(wavelengths)); -% ber = zeros(numel(realization),plotJob.ch,numel(xAxis),numel(wavelengths)); - - %get BER values for query - for w = 2:numel(wavelengths) - - for xl = 1:numel(xAxis) - - c_wavelen = wavelengths(w); - p_out = xAxis(xl); - - % dim1 : realiz; dim2: channels, dim3: rop, dim4, c_wavelength - temp = removeZeros(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,c_wavelen,plotJob.channelspacing,plotJob.randzdw)); - ber(1:size(temp,1),:,xl,w) = temp; - - end - end - - hdfec = 3.8e-3.*ones(size(xAxis)); - zdw_ = []; - zdw_chann = []; - zdw_tot = []; - cf_tot = []; - cf_ = []; - S = []; - Stot = []; - S_chann = []; - cf_chann = []; - - cnt = 0; - %get fec thresholds - %linear = squeeze(linear); - for c_wavelen = 1:size(ber,4) - for realiz = 1:size(ber,1) - for chann = 1:size(ber,2) - - %finde Schnittpunkt zwischen FEC und BER Kurve - temp_ber = squeeze(ber(realiz,chann,:,c_wavelen)).'; - if ~all(temp_ber == 0) - %nur wenn nicht alles nullen sind - crossing_ch = InterX([hdfec;xAxis],[temp_ber;xAxis]); - else - continue - end - - %Req. FEC Ergebnis einsortieren - if ~isempty(crossing_ch) - if crossing_ch(2) == 0 - print("d") - end - S(realiz,chann,c_wavelen) = crossing_ch(2); - else - S(realiz,chann,c_wavelen) = -1; - cnt = cnt +1; - end - - - end - end - end - - temp_max = -inf; - for i = 1:plotJob.ch - hold on - %S:: 1.dim: realiz; 2.dim: channel; 3.dim: center wavelength - %squeeze a channel: - temp_data = squeeze(S(:,i,:)); - - %remove realizations that have no entry (only zero) - temp_data = removeZeros(temp_data); - - %replace zeros with NAN (e.g. for the wavelengths that have missing realizations) - temp_data(temp_data==0) = NaN; - - %plot required ROP for channel and all realizations that cross the - %FEC limit - scatter(wavelengths,temp_data ,5,plotJob.color,'Marker','.'); - -% %plot mean per channel -% temp_mean = mean(temp_data,'omitnan'); -% hold on -% plot(wavelengths,temp_mean,'Marker','*'); - - %get max overall value - temp_max = max(temp_max,max(temp_data)); - end - - - - %plot mean overall - mean_overall = squeeze(mean(S,2)); - mean_overall(mean_overall==0) = NaN; - %mean_overall(mean_overall==-1) = NaN; - mean_overall=mean(mean_overall,1,'omitnan'); - plot(wavelengths,mean_overall,'Color',plotJob.color); - - %plot max overall - scatter(wavelengths,temp_max ,35,plotJob.color,'Marker','v'); - - - %plot channel positions - hold on - chpos = calcWavelengthPlan(plotJob.ch, 400e9, 1310); - xline(chpos,'LineWidth',2,'Alpha',0.2); - chpos = calcWavelengthPlan(plotJob.ch, 400e9, chpos(4)); - xline(chpos,'LineWidth',2,'Alpha',0.2); - - fig.Position = plotJob.Position; - - ylabel('Penalty in dB'); - xlabel('Wavelength in nm'); - - xlim([min(wavelengths),max(wavelengths) ]); - - grid minor; - set(gca, 'color', 'none'); - legend = []; - - fontsize(AxesMain,8,"points") - - fig.Position = plotJob.Position; - fig.Units = "centimeters"; - fig.Position = [2 2 8.5 7]; - - set(AxesMain,'TickLabelInterpreter','latex') - - set(AxesMain.Legend,'Interpreter','latex') - - - - - - - % - % - % - % - % - % distinct_cf = unique(cf_chann); - % - % for i = 1:length(distinct_cf) - % indices = find(cf_chann==distinct_cf(i)); - % cf(i) = distinct_cf(i); - % worst_fec_cross(i) = max(S_chann(indices)); - % avg_fec_cross(i) = mean(S_chann(indices)); - % end - % - % avg_fec_cross = smooth(avg_fec_cross,5); - % - % figure(224) - % hold on - % scatter(cf_,S,10.*abs(S-mean(S)).*ones(size(S)),'DisplayName',['AVG'],'MarkerEdgeColor',col,'MarkerFaceColor',col,'Marker','.'); - % hold on - % scatter(cf(2:end),worst_fec_cross(2:end),15,'DisplayName',['Worst'],'MarkerEdgeColor',col,'MarkerFaceColor',col,'Marker','.','HandleVisibility','off'); - % plot(cf(2:end),avg_fec_cross(2:end),'DisplayName',['AVG'],'LineWidth',1,'LineStyle','-','Color',col,'Marker','none','MarkerFaceColor',col,'MarkerSize',2); - % plot(cf(2:end),worst_fec_cross(2:end),'DisplayName',['AVG'],'LineWidth',0.5,'LineStyle','-','Color',col,'Marker','none','MarkerFaceColor',col,'MarkerSize',2); - % ghzgrid = hz2nm(nm2hz(1310)+[0:1:20].*400e9); - % set(gca,'xtick',sort(ghzgrid)) - % xlim([min(cf(cf~=0)), 1310.1]); - % - % - % % With matlab internal errorbar function... - % figure(221) - % %plot(cf,avg_fec_cross,'DisplayName',['AVG'],'LineWidth',1,'Color',col,'Marker','none','MarkerFaceColor',col,'MarkerSize',2); - % hold on - % %plot(cf_tot,min(S_chann(1:length(cf_tot),:),[],2),'DisplayName',['AVG'],'LineWidth',1,'LineStyle',':','Color',col,'Marker','^','MarkerFaceColor',col,'MarkerSize',2); - % plot(cf,worst_fec_cross,'DisplayName',['AVG'],'LineWidth',2,'LineStyle','-','Color',col,'Marker','none','MarkerFaceColor',col,'MarkerSize',2); - % ghzgrid = hz2nm(nm2hz(1310)+[0:1:20].*400e9); - % set(gca,'xtick',sort(ghzgrid)) - % xlim([1302, 1310.1]); - % xline(ghzgrid,'LineStyle',':','Color',[.7 .7 .7]); - - - %with - % Stot = movmean(Stot,5); - % figure(222) - % [hl,hp] = boundedline(cf_tot,Stot,[(Stot'-min(S_chann(1:length(cf_tot),:),[],2)),(max(S_chann(1:length(cf_tot),:),[],2)-Stot')], 'alpha','Color',col,'transparency', 0.05); - % ho = outlinebounds(hl,hp); - % set(ho, 'linestyle', ':', 'color', col, 'marker', '.','linewidth',0.5); - % hold on - % - % ghzgrid = hz2nm(nm2hz(1310)+[0:1:12].*400e9); - % xline(ghzgrid); - - - - - %plot the total ber - - % %figure(22); - % hold on; - % b= movmean(Stot,3); - % plot(AxesMain,cf_tot,b,'LineWidth',2,'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname],'Color',col,'Marker','o'); - % hold on - - - - - - %scatter(AxesMain,zdw_,S,'Marker','+','MarkerEdgeColor',col,'MarkerFaceAlpha',0.4,'MarkerEdgeAlpha',0.4,'LineWidth',0.5,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - %ylim(AxesMain,[-9.3 -7]); - - % for i = 1:numel(chp) - % hold on - % xline(AxesMain,chp(i),'Color',colr(i,:),'DisplayName',['CH: ', num2str(i)],'LineWidth',1.5); - % hold off - % end - - - - - % - % a = movmean(sortrows([zdw_; S]'),10,'Endpoints','discard'); - % - % sorted = sortrows([zdw_; S]'); - % figure(2) - % scatter(sorted(:,1),sorted(:,2)) - % - % ber_sorted = sort(S); - % mean(ber_sorted); - % std(ber_sorted); - % z1 = []; - % penalty = mean(ber_sorted):0.01:mean(ber_sorted)+2; - % for i = 1:length(penalty) - % l = penalty(i); - % if i == 1 - % z1 = [z1 sum(ber_sorted(1,:)l-0.01) / length(ber_sorted) ]; - % end - % - % end - % - % penalty_higherthan = 0.5; - % probability = sum(z1(find(penalty>mean(ber_sorted)+penalty_higherthan))); - % disp(['A penalty of more than 1dB has a probability of: ', num2str(probability)]); - % % - % stem(AxesMain,penalty,z1,"filled",'Marker','o','MarkerSize',2,'Color',col); - % - % - % [f1,x1]=ecdf(S(end,:)); - % %figure(23);plot(AxesMain,x1,f1,'r','LineWidth',3, 'Color',col); - % - % %plot(AxesMain,a(:,1),a(:,2),'Color',col+1,'Parent', AxesMain(1)); - % - % % histogram(AxesMain,S,1000,'EdgeColor','none','FaceAlpha',0.4); - % - % - % - % % - % %scatter(AxesMain,zdw_,S,'Marker','+','MarkerEdgeColor',col,'MarkerFaceAlpha',0.4,'MarkerEdgeAlpha',0.4,'LineWidth',0.5,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - % hold on - % %scatter(AxesMain,zdw_chann(:,1),mean(S_chann,2),'Marker','diamond','MarkerEdgeColor',col,'MarkerFaceAlpha',0.6,'LineWidth',7,'MarkerFaceColor',col,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - % hold off - % % - % % for rlz = 1:size(S,2) - % % zdwval = zdw_(rlz); - % % feccrossing = S(rlz); - % % scatter(AxesMain,zdwval,feccrossing,10,'MarkerEdgeColor',col,'MarkerFaceColor',col,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - % % end - % - % xline([1302.03471732576,1304.30061112288,1306.57440520904,1308.85614097425,1311.14586009814,1313.44360455251,1315.74941660391,1318.06333881618]); - - -end - -function vec = removeZeros(vec) - % Find rows that contain only zeros - rows_to_remove = all(vec == 0, 2); - - % Remove rows with only zeros - vec(rows_to_remove, :) = []; -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZdwFailureRate.m b/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZdwFailureRate.m deleted file mode 100644 index 480641f..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotBerVsZdwFailureRate.m +++ /dev/null @@ -1,157 +0,0 @@ -function plotBerVsZdwFailureRate(wh,plotJob) - - fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - - if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on - else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on - end - - - col = plotJob.color; - - % we want to fetch all realizations - realization = wh.parameter.realization.values(1:end); - - % get all xAxis values - xAxis = wh.parameter.p_out.values; - - % get all center wavelengths - wavelengths = wh.parameter.center_wavelength.values; - %wavelengths = wavelengths(2:end); -% totber = NaN(numel(realization),1,numel(xAxis),numel(wavelengths)); -% ber = zeros(numel(realization),plotJob.ch,numel(xAxis),numel(wavelengths)); - - %get BER values for query - for w = 1:numel(wavelengths) - - for xl = 1:numel(xAxis) - - c_wavelen = wavelengths(w); - p_out = xAxis(xl); - - % dim1 : realiz; dim2: channels, dim3: rop, dim4, c_wavelength - temp = removeZeros(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,c_wavelen,plotJob.channelspacing,plotJob.randzdw)); - ber(1:size(temp,1),:,xl,w) = temp; - - end - end - - hdfec = 3.8e-3.*ones(size(xAxis)); - zdw_ = []; - zdw_chann = []; - zdw_tot = []; - cf_tot = []; - cf_ = []; - S = []; - Stot = []; - S_chann = []; - cf_chann = []; - - cnt = 0; - %get fec thresholds - %linear = squeeze(linear); - for c_wavelen = 1:size(ber,4) - for realiz = 1:size(ber,1) - for chann = 1:size(ber,2) - - %finde Schnittpunkt zwischen FEC und BER Kurve - temp_ber = squeeze(ber(realiz,chann,:,c_wavelen)).'; - if ~all(temp_ber == 0) - %nur wenn nicht alles nullen sind - crossing_ch = InterX([hdfec;xAxis],[temp_ber;xAxis]); - else - continue - end - - %Req. FEC Ergebnis einsortieren - if ~isempty(crossing_ch) - if crossing_ch(2) == 0 - print("d") - end - S(realiz,chann,c_wavelen) = crossing_ch(2); - else - S(realiz,chann,c_wavelen) = -1; - cnt = cnt +1; - end - - - end - end - end - - temp_max = -inf; - sum_FEC_not_crossed=[]; - sum_FEC_crossed=[]; - - threshold = plotJob.p_in - 10; - - for i = 1:plotJob.ch - hold on - %S:: 1.dim: realiz; 2.dim: channel; 3.dim: center wavelength - %squeeze a channel: - temp_data = squeeze(S(:,i,:)); - - %remove realizations that have no entry (only zero) - temp_data = removeZeros(temp_data); - - %replace zeros with NAN (e.g. for the wavelengths that have missing realizations) - temp_data(temp_data==0) = NaN; - - %for current channel - FEC_crossed = temp_data < threshold & ~isnan(temp_data); - FEC_not_crossed = temp_data >= threshold & ~isnan(temp_data); - - %sum over channels for overall picture - sum_FEC_not_crossed(i,:) = sum(FEC_not_crossed); - sum_FEC_crossed(i,:) = sum(FEC_crossed); - - failure_rate_channelwise(i,:) = sum_FEC_not_crossed(i,:)./ ( sum_FEC_crossed(i,:) + sum_FEC_not_crossed(i,:)); - - end - - failure_rate_total = sum(sum_FEC_not_crossed,1) ./ ( sum(sum_FEC_crossed,1) + sum(sum_FEC_not_crossed,1) ); - % plot failure rate (nbetween 0 and 1) - - plot(wavelengths,failure_rate_total,'Color',plotJob.color,'LineWidth',1,'LineStyle',plotJob.plotTypeArg,'Marker','x','MarkerSize',5,'MarkerFaceColor',plotJob.color,'DisplayName',plotJob.displayname); - - %plot max overall - %scatter(wavelengths,failure_rate_channelwise ,35,plotJob.color,'Marker','.'); - - ylabel('Failure Rate of Link'); - xlabel('Wavelength in nm'); - - xlim([min(wavelengths),max(wavelengths) ]); - ylim([0,1]); - - grid minor; - set(gca, 'color', 'none'); - legend = []; - -% fontsize(AxesMain,8,"points") - - fig.Position = plotJob.Position; - fig.Units = "centimeters"; - fig.Position = [0 0 12 5 7]; - - try - set(AxesMain,'TickLabelInterpreter','latex') - - set(AxesMain.Legend,'Interpreter','latex') - end - -end - -function vec = removeZeros(vec) - % Find rows that contain only zeros - rows_to_remove = all(vec == 0, 2); - - % Remove rows with only zeros - vec(rows_to_remove, :) = []; -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotChannelSpacingAna.m b/Classes/Warehouse_class/functions/fwm_plots/plotChannelSpacingAna.m deleted file mode 100644 index 44febc6..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotChannelSpacingAna.m +++ /dev/null @@ -1,51 +0,0 @@ -function plotChannelSpacingAna(wh,plotJob) - -xAxis = wh.parameter.p_out.values; - - -realization = wh.parameter.realization.values(1:end); - -channelsp = wh.parameter.channelspacing.values(1:end); -channelsp = [200 400].*1e9; -for ch = 1:2 - channspacing = channelsp(ch); - for xl = 1:numel(xAxis) - p_out = xAxis(xl); - - curber = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.numchannels,channspacing); - curzdw = wh.getStoValue('zdw',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.numchannels,channspacing); - - ber(1:size(curber,1),1:size(curber,2),xl) = curber; - zdw(1:size(curber,1),1,xl) = curzdw; - - end - - ber = squeeze(mean(ber,1)); - zdw = squeeze(mean(zdw,1)); - - hdfec = 3.8e-3.*ones(size(xAxis)); - S = []; - wavelength={}; - - zdw_ = []; - zdw_chann = []; - zdw_tot = []; - S = []; - S_chann = []; - wl = round([1302.03471732576,1304.30061112288,1306.57440520904,1308.85614097425,1311.14586009814,1313.44360455251,1315.74941660391,1318.06333881618],2); - wl = 1:16; - wl = [1.2930 1.2953 1.2975 1.2998 1.3020 1.3043 1.3066 1.3089 1.3111 1.3134 1.3157 1.3181 1.3204 1.3227 1.3251 1.3274]; - - - a = InterX([hdfec(:)';xAxis],[mean(ber,1);xAxis]); - if ~isempty(a) - s(ch) = a(2); - else - s(ch) = NaN; - end -end - -figure(2224) -hold on -plot(channelsp,s,'LineWidth',1,'Color',plotJob.color,'Marker','o'); - diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotCurve.m b/Classes/Warehouse_class/functions/fwm_plots/plotCurve.m deleted file mode 100644 index a7134c9..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotCurve.m +++ /dev/null @@ -1,278 +0,0 @@ -function plotCurve(wh,plotJob) -%PLOTCURVE Summary of this function goes here -% Detailed explanation goes here - -fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - -if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on -else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on -end - -col = plotJob.color; - -% we want to fetch all realizations -if plotJob.pmd == 0 - realization = 499; - % realization = 0:7; -else - realization = wh.parameter.realization.values(1:end); -end -realization = wh.parameter.realization.values(1:end); -% get all xAxis values -xAxis = wh.parameter.p_out.values; - -% Fetch Data from Warehouse -for xl = 1:numel(xAxis) - p_out = xAxis(xl); - if string(plotJob.dataStatArg) == "Worst" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = max(temp,[],'all'); - linew = 1.0; - markersz = 3; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "AVG" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = mean(temp,'all'); - linew = 1.0; - markersz = 3; - linestyle = '--'; - - elseif string(plotJob.dataStatArg) == "Best" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = min(temp,[],'all'); - linew = 1.0; - markersz = 3; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "All Channels; mean(PMD Realizations)" - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - - ber(:,xl) = mean(temp,1,"omitnan").'; - - linew = 1; - markersz = 1; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "Lineplot with quartiles" - - temp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - temp = removeZeros(temp); - ber(xl) = mean(temp,"all","omitnan").'; - - - upperq(xl) = quantile(temp,0.99,"all"); - lowerq(xl) = quantile(temp,0.04,"all"); - - if lowerq(xl) == 0 - lowerq(xl) = lowerq(xl-1); - end - % upperq(xl) = 0.5*std(tmp,1,'all','omitnan'); - % lowerq(xl) = 0.5*std(tmp,1,'all','omitnan'); - - % upperq(xl) = max(dataNoNans); - % lowerq(xl) = min(dataNoNans); - - % upperq(xl) = mean(tmp,"all","omitnan") + 1.96 * (std(tmp,1,'all','omitnan')/sqrt(numel(tmp))); - % lowerq(xl) = mean(tmp,"all","omitnan") - 1.96 * (std(tmp,1,'all','omitnan')/sqrt(numel(tmp))); - - linew = 1; - markersz = 1; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "All Channels ;All PMD Realizations" - - raw_fetch = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw).'; - tmp = reshape(raw_fetch,[],1); - ber(1:size(tmp,1),xl) = tmp; - - ber_per_chann(:,:,xl) = raw_fetch; - - linew = 0.3; - markersz = 2; - linestyle = ':'; - - end -end - - -%routine to remove total outliers (here those wehere the rop curve has a mean BER greater than 0.1) -% ber_per_chann_clean = NaN(size(ber_per_chann)); -% for ch = 1:size(ber_per_chann,1) -% bla = squeeze(ber_per_chann(ch,:,:)); -% ber_per_chann(ch,find(mean(bla,2)>0.25),:) = NaN; -% cleaned = rmoutliers(bla,"mean",'ThresholdFactor',2); -% -% ber_per_chann_clean(ch,1:size(cleaned,1),1:size(cleaned,2)) = cleaned; -% -% end -% -% ber = []; -% for rop = 1:size(ber_per_chann,3) -% temp = squeeze(ber_per_chann_clean(:,:,rop)); -% ber(rop) = mean(temp,"all","omitnan").'; -% upperq(rop) = quantile(temp,0.9,"all"); -% lowerq(rop) = quantile(temp,0.1,"all"); -% end - - - - -xAxis = xAxis; - -% Plot Data -if string(plotJob.plotTypeArg) == "Scatter" - for rlz = 1:size(ber,1) - - if rlz < size(ber,1) - scatter(xAxis,ber(rlz,:),markersz,'MarkerEdgeColor',col,'MarkerFaceColor',col,'Parent', AxesMain(1),'HandleVisibility','off'); - else - scatter(xAxis,ber(rlz,:),markersz,'MarkerEdgeColor',col,'MarkerFaceColor',col,'Parent', AxesMain(1),'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); - end - - end - -elseif string(plotJob.plotTypeArg) == "Lines" - - % if ~anynan(ber) - % [xAxis,ber] = interpCurve(xAxis, ber); - % end - - for rlz = 1:size(ber,1) -% - if (string(plotJob.dataStatArg) == "All Channels ;All PMD Realizations")||(string(plotJob.dataStatArg) == "All Channels; mean(PMD Realizations)") - ch = mod(rlz,plotJob.ch); - if ch == 0; ch = plotJob.ch; end - else - ch = plotJob.dataStatArg; - end - - if rlz < size(ber,1) - - - s = plot(xAxis,ber(rlz,:),linestyle,'Marker',"none",'MarkerSize',markersz,'LineWidth',linew,'Color',col,'Parent', AxesMain,'HandleVisibility','off'); - - s.DataTipTemplate.Interpreter = "latex"; - s.DataTipTemplate.DataTipRows(1).Label = "Ch: "; - s.DataTipTemplate.DataTipRows(1).Value = repmat(ch,size(ber)); - s.DataTipTemplate.DataTipRows(1); - s.DataTipTemplate.DataTipRows(2) = []; - - - else - - if string(plotJob.dataStatArg) == "Lineplot with quartiles" - [hl,hp] = boundedline(xAxis,ber(rlz,:),([(ber(rlz,:)-lowerq(rlz,:));upperq(rlz,:)-ber(rlz,:)]'),'-o','alpha','Color',col,'transparency', 0.1,'linewidth',0.7); - hl.MarkerFaceColor = col; - hl.MarkerSize = 3; - set(hp,'HandleVisibility','off'); - %hp.LineWidth = 1.2; - - ho = outlinebounds(hl,hp); - set(ho, 'linestyle', ':', 'color', col,'Linewidth',0.5); - set(ho,'HandleVisibility','off'); - else - - s = plot(xAxis,ber(rlz,:),linestyle,'Marker',"o",'MarkerFaceColor',col,'MarkerSize',markersz,'LineWidth',linew,'Color',col,'Parent', AxesMain,'DisplayName',[plotJob.displayname]); - - s.DataTipTemplate.Interpreter = "latex"; - s.DataTipTemplate.DataTipRows(1).Label = "Ch: "; - s.DataTipTemplate.DataTipRows(1).Value = repmat(string(ch),size(ber)); - s.DataTipTemplate.DataTipRows(1) - s.DataTipTemplate.DataTipRows(2) = []; - end - - end - - end -end - -% Draw FEC Threshold Line -%get x data of first children: -%get all linear Values -if 0 - linear = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,wh.parameter.p_out.values,0,0,499,"symmetric"); - linear = mean(linear,2); - lincurve = findall(AxesMain, 'Type', 'line','DisplayName','Linear Baseline'); - % - if isempty(lincurve) - xdata = AxesMain.Children(1).XData; - hdfec = 3.8e-3.*ones(size(xdata)); - plot(xdata,linear,'-','Marker',"o",'MarkerSize',3,'LineWidth',4,'Color',[0.6400 0.6400 0.6400],'MarkerFaceColor',[0.6400 0.6400 0.6400],'Parent', AxesMain,'DisplayName','Linear Baseline'); - %h = get(gca,'Children'); - %set(gca,'Children',[h(2) h(1)]) - end -end - -feccurve = findall(AxesMain, 'Type', 'line','DisplayName','FEC $3.8*10^{-3}$'); -% -if isempty(feccurve) - xdata = AxesMain.Children(1).XData; - hdfec = 3.8e-3.*ones(size(xdata)); - plot(xdata,hdfec,'--','MarkerSize',4,'Color','black','MarkerFaceColor','black','LineWidth',1,'Parent', AxesMain,'DisplayName','FEC $3.8*10^{-3}$','HandleVisibility','off'); - %h = get(gca,'Children'); - %set(gca,'Children',[h(2) h(1)]) -end - -% Figure Settings -%title(AxesMain,plotJob.title,"Interpreter","none"); - -xlabel(AxesMain,plotJob.xAxisLabel,"Interpreter","latex"); - -ylabel(AxesMain,plotJob.yAxisLabel,"Interpreter","latex"); - -set(AxesMain,'yscale','log'); - -grid(AxesMain,'on'); - -grid(AxesMain,'minor'); - -grid minor - -%legend(AxesMain); - -fontsize(AxesMain,8,"points") -% fontname(AxesMain,"Arial") - -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [2 2 8.5 7]; - -set(AxesMain,'TickLabelInterpreter','latex') - -set(AxesMain.Legend,'Interpreter','latex') -% set(gcf,'Units','centimeters') -% set(gcf,'Position',[2 2 9 4.5]) - -ylim([1e-5,0.3]); - -xlim([min(xAxis),-3]); - - -% annotation('textbox', [0.125, 0.32, 0.1, 0.1], 'String', "FEC 3.8e-3","LineStyle","none","FontSize",8,"FontUnits","points") - - -hold off - - -end - - -function vec = removeZeros(vec) - % Find rows that contain only zeros - rows_to_remove = all(vec == 0, 2); - - % Remove rows with only zeros - vec(rows_to_remove, :) = []; -end diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotHistogram.m b/Classes/Warehouse_class/functions/fwm_plots/plotHistogram.m deleted file mode 100644 index 023a3a1..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotHistogram.m +++ /dev/null @@ -1,151 +0,0 @@ -function plotHistogram(wh,plotJob) - - -fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - -if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on -else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on -end - -col = plotJob.color; - -% we want to fetch all realizations -realization = wh.parameter.realization.values(1:end); - -% get all xAxis values -xAxis = wh.parameter.p_out.values; - - -% Fetch Data from Warehouse -for xl = 1:numel(xAxis) - p_out = xAxis(xl); - if string(plotJob.dataStatArg) == "Worst" - ber(xl) = max(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,string(plotJob.channelplan),string(plotJob.dsp)),[],'all'); - linew = 2.0; - markersz = 3; - linestyle = '-'; - elseif string(plotJob.dataStatArg) == "AVG" - ber(xl) = mean(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,string(plotJob.channelplan),string(plotJob.dsp)),'all'); - linew = 2.0; - markersz = 3; - linestyle = ':'; - elseif string(plotJob.dataStatArg) == "Best" - ber(xl) = min(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,string(plotJob.channelplan),string(plotJob.dsp)),[],'all'); - linew = 2.0; - markersz = 3; - linestyle = ':'; - - elseif string(plotJob.dataStatArg) == "All Channels; mean(PMD Realizations)" - tmp = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,string(plotJob.channelplan),string(plotJob.dsp)); - - if numel(tmp(tmp==0)) ~= 0 - disp('Removed all zero values!'); - tmp(tmp==0) = NaN; - end - ber(:,xl) = mean(tmp,1,"omitnan").'; - - linew = 0.7; - markersz = 2; - linestyle = '-'; - - elseif string(plotJob.dataStatArg) == "All Channels ;All PMD Realizations" - - - - tmp = reshape(wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,string(plotJob.channelplan),string(plotJob.dsp)).',[],1); - ber(1:size(tmp,1),xl) = tmp; - - linew = 0.3; - markersz = 2; - linestyle = ':'; - end -end - -disp('Removed all zero values!'); -ber(ber==0) = NaN; -if ~anynan(ber) - [xAxis,ber] = interpCurve(xAxis, ber); -end - -% plot FEC Crossing as histogram - -hdfec = 3.8e-3.*ones(size(xAxis)); -S = []; -for i = 1:size(ber,1) - a = InterX([hdfec;xAxis],[ber(i,:);xAxis]); - if ~isempty(a) - S(:,i) = a; - end -end - - -%% SUB 1 -AxesMain = subplot(2,1,1); - -hold on - -if ~isempty(S) - histogram(S(end,:),300,'Normalization','probability','FaceColor',col,'EdgeColor',col,'Parent',AxesMain,'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname],'FaceAlpha',0.4,'EdgeAlpha',0.4); -end - -xlim([-3 ,9 ]); -ylim([0 .10]); - -% Figure Settings -title(AxesMain,['$P_{in}:$ ',num2str(plotJob.p_in-9), 'dBm; L : ', num2str(plotJob.l),'Km'],'Interpreter','latex'); - -xlabel(AxesMain,plotJob.xAxisLabel,'Interpreter','latex'); - -ylabel('PDF') - -grid(AxesMain,'on'); - -grid(AxesMain,'minor'); - -%legend(AxesMain,'Interpreter','latex'); - -fontsize(AxesMain,24,"pixels") - -hold off - - -%% SUB 2 -AxesMain = subplot(2,1,2); - -if ~isempty(S) -hold on - -[f1,x1]=ecdf(S(end,:)); - plot(x1,f1,'r','LineWidth',3, 'Color',col,'DisplayName',[plotJob.dataStatArg,' Ch.: ', plotJob.displayname]); -end -xlim([-3 ,9 ]); -ylim([0 1]); -% Figure Settings -title(AxesMain,['$P_{in}:$ ',num2str(plotJob.p_in-9), 'dBm; L : ', num2str(plotJob.l),'Km'],'Interpreter','latex'); - -xlabel(AxesMain,plotJob.xAxisLabel,'Interpreter','latex'); - -ylabel('CDF') - -grid(AxesMain,'on'); - -grid(AxesMain,'minor'); - -fontsize(AxesMain,24,"pixels") - -%legend(AxesMain,'Interpreter','latex'); - -fig.Position = plotJob.Position; - -hold off - - - -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/plotViolin.m b/Classes/Warehouse_class/functions/fwm_plots/plotViolin.m deleted file mode 100644 index 2453744..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plotViolin.m +++ /dev/null @@ -1,206 +0,0 @@ -function plotViolin(wh,plotJob) - -fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); - -if isvalid(fig) - figure(fig) - fig = get(fig); - AxesMain = fig.CurrentAxes; - hold on -else - fig = figure('name',char(plotJob.figName)); - AxesMain = gca; - hold on -end - -%% Violin -col = plotJob.color; - -% we want to fetch all realizations -if plotJob.pmd == 0 - realization = 1; -else - realization = wh.parameter.realization.values(1:end); -end - -%realization = 0:8; - -% get all xAxis values -xAxis = wh.parameter.p_out.values; - -% ber = NaN(500,16,10); -% zdw = NaN(500,1,10); - -%get BER values for query -for xl = 1:numel(xAxis) - p_out = xAxis(xl); - - curber = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw); - curber= removeZeros(curber); - ber(1:size(curber,1),1:size(curber,2),xl) = curber; - -end - -% to remove outliers set the percentile range -% a = squeeze(mean(ber,2)); -% out = isoutlier(mean(a,2),"percentiles",[0 100]); -% ber = ber(~out,:,:); -% disp(sum(out)); - -hdfec = 3.8e-3.*ones(size(xAxis)); -S = []; -wavelength={}; - - -S = []; -S_chann = []; - -wl = calcWavelengthPlan(plotJob.ch,plotJob.channelspacing,1310); -%get fec thresholds -% [C,ia,ib] =intersect(linx,xAxis); -% linear = squeeze(linear); - -%ber(ber==0) = NaN; -S_chann_no_crossing = zeros(1,plotJob.ch); -for chann = 1:size(ber,2) - - for realiz = 1:size(ber,1) - - ber_series = squeeze(ber(realiz,chann,:)).'; - if mean(ber_series) > 0.1 - continue - end - - a = InterX([hdfec(:)';xAxis],[ber_series;xAxis]); - - if ~isempty(a) - S_chann(realiz,chann) = a(2); -% if a(2) > -7 && string(plotJob.pol) == "copolarized" -% continue -% end - S(end+1) = a(2); - wavelength{end+1} = num2str(wl(chann)); - else - S(end+1) = 0; - wavelength{end+1} = num2str(wl(chann)); - S_chann_no_crossing(realiz,chann) = 1; - S_chann(realiz,chann) = -1; - end - - end -end - -threshold = -6; -FEC_crossed = sum(S_chann < threshold & ~isnan(S_chann),1); -FEC_not_crossed = sum(S_chann >= threshold & ~isnan(S_chann),1); -failure_rate = FEC_not_crossed ./ (FEC_crossed + FEC_not_crossed) ; - - -S_chann(S_chann==0) = NaN; - -total_avg = mean(S_chann,"all","omitnan"); - -%figure(2024) -%C = flip(cbrewer2('Spectral',8)); -if numel(S) <= numel(wl) - vs = scatter(1:numel(S),S,50,'o','MarkerEdgeColor','black','MarkerFaceColor',plotJob.color,'LineWidth',1,'HandleVisibility','off'); - %vs = scatter(1,mean(S),50,'o','MarkerEdgeColor','black','MarkerFaceColor',plotJob.color,'LineWidth',1); - -else - - vs = violinplot(S,wavelength,... - 'ViolinColor',plotJob.color,... - 'ViolinAlpha',0.1,... - 'MarkerSize',1,... - 'ShowMedian',false,... - 'EdgeColor',plotJob.color,... - 'ShowWhiskers',false,... - 'ShowData',false,... - 'ShowBox',false,... - 'Bandwidth',0.051 ... - ); - - - hold on - - partly_failed = boolean(ceil(failure_rate)); - - avg = mean(S_chann,1,"omitnan"); - - notfailed = ~partly_failed .* avg; - notfailed(notfailed==0) = NaN; - scatter(1:size(S_chann,2),notfailed,10,'Marker','x','MarkerEdgeColor','black','MarkerFaceColor',plotJob.color,'LineWidth',0.5,'HandleVisibility','off'); - - - hold on - partly_failed = partly_failed.*avg; - partly_failed(partly_failed==0) = NaN; - s=scatter(1:numel(failure_rate),partly_failed,10,'Marker','x','LineWidth',0.5,'HandleVisibility','off','MarkerEdgeColor','red'); - s.DataTipTemplate.Interpreter = "latex"; - s.DataTipTemplate.DataTipRows(1).Label = "Fail Rate: "; - s.DataTipTemplate.DataTipRows(1).Value = failure_rate; - s.DataTipTemplate.DataTipRows(2) = []; - hold off -end - -% ax = gca; -% ax.XTicks - -hold on -yline(total_avg,'LineWidth',1,'LineStyle','--','DisplayName','System Avg.') -fig.Position = plotJob.Position; - -xticklabels(1:16); -ylabel('Penalty in dB'); -xlabel('Channel Number'); -ylim([-9.3,-3]); -xlim([0,plotJob.ch+1]); -grid minor; -set(gca, 'color', 'none'); -legend = []; - -fontsize(AxesMain,8,"points") - -fig.Position = plotJob.Position; -fig.Units = "centimeters"; -fig.Position = [2 2 8.5 7]; - -set(AxesMain,'TickLabelInterpreter','latex') - -set(AxesMain.Legend,'Interpreter','latex') - - - -if 0 -ber_sorted = sort(S); - -z1 = []; -penalty = mean(ber_sorted):0.01:mean(ber_sorted)+2; - -for i = 1:length(penalty) - l = penalty(i); - if i == 1 - z1 = [z1 sum(ber_sorted(1,:)l-0.01) / length(ber_sorted) ]; - end - -end - -penalty_higherthan = 0.5; -probability = sum(z1(find(penalty>mean(ber_sorted)+penalty_higherthan))); -disp(['A penalty of more than 0.5 dB has a probability of: ', num2str(probability)]); -end - -end - - -function vec = removeZeros(vec) - % Find rows that contain only zeros - rows_to_remove = all(vec == 0, 2); - - % Remove rows with only zeros - vec(rows_to_remove, :) = []; - - -end \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/fwm_plots/plot_ber_distribution.m b/Classes/Warehouse_class/functions/fwm_plots/plot_ber_distribution.m deleted file mode 100644 index 1d43674..0000000 --- a/Classes/Warehouse_class/functions/fwm_plots/plot_ber_distribution.m +++ /dev/null @@ -1,58 +0,0 @@ - -%automate plots -% [file, path] = uigetfile("C:\Users\Silas\Documents\MATLAB\Raw_Cluster_Simulations\session_januar24\wh_complete_at_1310.mat"); -% wh = load([path filesep file]); -% wh = wh.wh; - -plotJob = struct(); - -plotJob.l = 10; -plotJob.ch = 16; -plotJob.d = 3; -plotJob.sgm = 1; -plotJob.pol = "copolarized"; -plotJob.p_in = 3; -plotJob.gamma = 0.0023; -plotJob.pmd = 0.1; -plotJob.channelspacing = 400e9; -plotJob.randzdw = 0; - -ber_per_chann = []; -% get all xAxis values -xAxis = wh.parameter.p_out.values; -realization = wh.parameter.realization.values(1:end); -% Fetch Data from Warehouse -for xl = 1:numel(xAxis) - p_out = xAxis(xl); - raw_fetch = wh.getStoValue('ber',plotJob.l,plotJob.d,plotJob.sgm,string(plotJob.pol),plotJob.p_in,p_out,plotJob.pmd,plotJob.gamma,realization,plotJob.ch,1310,plotJob.channelspacing,plotJob.randzdw).'; - - ber_per_chann(:,:,xl) = raw_fetch; -end -%% - -figure(2023); - -for ch = [1,floor(plotJob.ch/2),ceil(plotJob.ch/2)+1,plotJob.ch] %1:15:size(ber_per_chann,1) - - for p = 5%1:size(ber_per_chann,3) - - % Extract data for the current row - row_data = squeeze(ber_per_chann(ch,:,p)); - [f, xi] = ksdensity(row_data); - % Identify the peak point - [max_density, max_index] = max(f); - peak_x = xi(max_index); - - end - % Create a histogram plot for the current row with a unique color - plot(xi, f, 'LineWidth', 2, 'DisplayName', ['Ch. ', num2str(ch)],'LineStyle','--'); - %histogram(row_data,100, 'DisplayName', ['Channel ', num2str(ch)], 'EdgeColor', 'none'); - - hold on; % Hold the plot for the next iteration - text(peak_x, max_density, ['Ch ', num2str(ch)], 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'left'); -end - - -legend show - -%% \ No newline at end of file diff --git a/Datatypes/clr.m b/Datatypes/clr.m index b9aa515..bda20d1 100644 --- a/Datatypes/clr.m +++ b/Datatypes/clr.m @@ -49,22 +49,5 @@ classdef clr hold off; end - function showSet(colorStruct) - % Show colors from a structure in a bar plot - names = fieldnames(colorStruct); - colors = cell2mat(struct2cell(colorStruct)'); - - figure; - hold on; - for i = 1:size(colors, 1) - fill([0 1 1 0], [i-1 i-1 i i], colors(i, :), 'EdgeColor', 'k'); - text(1.1, i-0.5, names{i}, 'FontSize', 12, 'Interpreter', 'none'); - end - ylim([0, size(colors, 1)]); - xlim([0, 1.5]); - axis off; - title('Color Preview'); - hold off; - end end end diff --git a/Datatypes/equalizer_structure.m b/Datatypes/equalizer_structure.m index 7e338bd..b94e0c0 100644 --- a/Datatypes/equalizer_structure.m +++ b/Datatypes/equalizer_structure.m @@ -2,11 +2,13 @@ classdef equalizer_structure < int32 enumeration ffe (0) - vnle (1) - vnle_pf_mlse (2) + dfe (1) + vnle (2) + vnle_pf_mlse (3) % db_precoded (3) - vnle_db_mlse (3) - db_encoded (4) + vnle_db_mlse (4) + db_encoded (5) + ml_mlse (6) end end \ No newline at end of file diff --git a/Functions/EQ_structures/duobinary_signaling.m b/Functions/EQ_structures/duobinary_signaling.m index 0d69311..7f13c35 100644 --- a/Functions/EQ_structures/duobinary_signaling.m +++ b/Functions/EQ_structures/duobinary_signaling.m @@ -1,97 +1,149 @@ -function [eq_package] = duobinary_signaling(eq_, mlse_,M ,rx_signal, tx_symbols, tx_bits,options) -%Duobinary Signaling +function [db_results] = duobinary_signaling(eq_, mlse_, M, rx_signal, tx_symbols, tx_bits, options) +% DUOBINARY_SIGNALING Processes signals through duobinary signaling +% +% Inputs: +% eq_ - Equalizer object +% mlse_ - MLSE object +% M - Modulation order +% rx_signal - Received signal +% tx_symbols - Transmitted symbols +% tx_bits - Transmitted bits +% options - Optional parameters +% +% Outputs: +% db_results - Results from duobinary signaling processing + arguments - eq_ - mlse_ - M - rx_signal - tx_symbols - tx_bits - options.postFFE = []; + eq_ + mlse_ + M + rx_signal + tx_symbols + tx_bits + options.precode_mode db_mode + options.showAnalysis = 0 + options.eth_style_symbol_mapping = 0 + options.postFFE = [] + options.database = [] end +%% Process signals through equalizer +[eq_signal, eq_noise] = eq_.process(rx_signal, tx_symbols); -[eq_signal, eq_noise] = eq_.process(rx_signal,tx_symbols); - +% Apply post-FFE if provided if ~isempty(options.postFFE) - [eq_signal,eq_noise] = options.postFFE.process(eq_signal,tx_symbols); + [eq_signal, eq_noise] = options.postFFE.process(eq_signal, tx_symbols); end -eq_signal.eye(eq_signal.fs,M,"fignum",340); +if isa(mlse_,'MLSE_viterbi') + [mlse_signal] = mlse_.process(eq_signal); +else -eq_signal = mlse_.process(eq_signal); + % Aufpassen mit welcher Sequenz man hier vergleicht für LLR stuff... + % gespeichtere "Symbols" sind schon DB codiert, das wollen wir hier + % nicht! Sondern die precoded aber nicht db-encoded müssen als ref in + % die LLR berechnung gehen! + ref_sym = PAMmapper(M,0).map(tx_bits); %ist klar + ref_sym_dpc = Duobinary().precode(ref_sym); % precoded + % ref_sym_dbenc = Duobinary().encode(ref_sym_dpc); %encoded - das wurde gesendet! + % ref_sym_dec = Duobinary().decode(ref_sym_dbenc); %ref_sym wieder zurück! -eq_signal = Duobinary().encode(eq_signal); -eq_signal = Duobinary().decode(eq_signal); + mlse_.trellis_states = PAMmapper(M,0).levels; + mlse_.trellis_state_mode = 1; + [mlse_signal,LLR,GMI_MLSE] = mlse_.process(eq_signal,ref_sym_dpc); +end -% M = numel(unique(eq_signal.signal)); -rx_bits = PAMmapper(M,0).demap(eq_signal); -[bits_db,errors_db,ber_db,~] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); -eq_package.ber = ber_db; +% tx_symbols_ = Duobinary().decode(tx_symbols); +% [mlse_signal,~,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols); -resultsDBsignaling = struct( ... - 'result_id', NaN, ... % - 'run_id', NaN, ... % Beispielhafte Run-ID - 'eqParam_id', NaN, ... % Beispielhafter Fremdschlüssel zur EqualizerParameters-Tabelle - 'date_of_processing', datetime('now'), ... % Aktuelles Datum und Uhrzeit - 'numBits', bits_db, ... % Beispiel: 1.000.000 Bits - 'numBitErr', errors_db, ... % Beispiel: 120 Bitfehler - 'BER_precoded', [], ... % BER = 120 / 1.000.000 - 'numBitErr_precoded', [], ... % Beispiel: 120 Bitfehler - 'BER', ber_db, ... % BER = 120 / 1.000.000 - 'SNR', [], ... % Beispielhafte SNR - 'SNR_level', jsonencode([]), ... % SNR-Level als JSON-codiertes Array - 'GMI', [], ... % Beispielhafter GMI-Wert - 'AIR', [], ... % Beispielhafter AIR-Wert - 'EVM', [], ... % Beispielhafte EVM - 'EVM_level', jsonencode([]), ... % EVM-Level als JSON-codiertes Array - 'Alpha', [] ... % Beispielhafter Alpha-Wert - ); +% Apply duobinary encoding and decoding +mlse_signal = Duobinary().encode(mlse_signal); +mlse_signal = Duobinary().decode(mlse_signal); +% Demap symbols to bits +rx_bits = PAMmapper(M, 0, "eth_style", options.eth_style_symbol_mapping).demap(mlse_signal); + +%% Calculate BER and metrics +[bits_db, errors_db, ber_db, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + +% Calculate performance metrics after duobinary FFE! +[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal); %SNR of duobinary sequence - not directly comparable to +[gmi] = calc_air(eq_signal, tx_symbols, "skip_front", 10000, "skip_end", 10000); +air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M)); +[evm_total, evm_lvl] = calc_evm(eq_signal, tx_symbols); +[std_total, std_lvl] = calc_std(eq_signal, tx_symbols); +[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols); + +%% Prepare output structure +% Determine postFFE order if ~isempty(options.postFFE) npostFFE = options.postFFE.order; else npostFFE = 0; end -equalizerConfigDBsignaling = struct( ... - 'eq_id', NaN, ... % Auto-Inkrement, wird in der DB gesetzt - 'equalizer_structure', int32(equalizer_structure.db_encoded), ... % Beispiel: 1 (z.B. für vnle) - 'M', M, ... % Ordnung der PAM-Konstellation - 'target_constellation', jsonencode(round(unique(tx_symbols.signal),5)), ... % Beispielhafter Target-String - 'db_target', 1, ... % 0 oder 1 - 'diff_precode', 1, ... % 0 oder 1 - 'postFFE', double(~isempty(options.postFFE)), ... % Beispielwert - 'NpostFFE', npostFFE, ... % Beispielwert - 'Ne1', eq_.Ne(1), ... % Feedforward Koeffizienten 1. Ordnung - 'Ne2', eq_.Ne(2), ... % Feedforward Koeffizienten 2. Ordnung - 'Ne3', eq_.Ne(3), ... % Feedforward Koeffizienten 3. Ordnung - 'Nb1', eq_.Nb(1), ... % Decision Feedback Koeffizienten 1. Ordnung - 'Nb2', eq_.Nb(2), ... % Decision Feedback Koeffizienten 2. Ordnung - 'Nb3', eq_.Nb(3), ... % Decision Feedback Koeffizienten 3. Ordnung - 'K', eq_.K, ... % Samples pro Symbol - 'DCmu', eq_.DCmu, ... % Anpassungsrate für DC-Tap - 'ideal_dfe', eq_.ideal_dfe, ... % Flag für ideal DFE (0 oder 1) - 'training_length', eq_.training_length, ... % Anzahl Trainingssymbole - 'training_loops', eq_.training_loops, ... % Anzahl Trainingsdurchläufe - 'TRmu1', eq_.FFEmu, ... % mu für DD-Modus (1. Ordnung) - 'TRmu2', eq_.FFEmu, ... % mu für DD-Modus (2. Ordnung) - 'TRmu3', eq_.FFEmu, ... % mu für DD-Modus (3. Ordnung) - 'TRmuDFE', eq_.DFEmu, ... % mu für DFE-Modus im DD - 'dd_loops', 5, ... % Anzahl Durchläufe im DD-Modus - 'DDmu1', eq_.DDmu(1), ... % mu für DD-Modus (1. Ordnung) - 'DDmu2', eq_.DDmu(2), ... % mu für DD-Modus (2. Ordnung) - 'DDmu3', eq_.DDmu(3), ... % mu für DD-Modus (3. Ordnung) - 'DDmuDFE', eq_.DDmu(4), ... % mu für DFE-Modus im DD - 'MLSE_mode', 'viterbi', ... % Beispiel: MLSE-Modus als String - 'MLSE_trellis_states', jsonencode(mlse_.trellis_states), ... % Trellis-States, z.B. als JSON-String oder kommasepariert - 'comment', 'function: duobinary_target.m', ... % Zusätzliche Kommentare - 'config_hash', NaN ... - ); +% Create results structure +db_results = struct(); +db_results.metrics = Metricstruct; +db_results.metrics.result_id = NaN; +db_results.metrics.run_id = NaN; +db_results.metrics.eqParam_id = NaN; +db_results.metrics.date_of_processing = datetime('now'); +db_results.metrics.BER = ber_db; +db_results.metrics.numBits = bits_db; +db_results.metrics.numBitErr = errors_db; +db_results.metrics.SNR = snr; +db_results.metrics.SNR_level = snr_lvl; +db_results.metrics.STD = std_total; +db_results.metrics.STD_level = std_lvl; +db_results.metrics.STDrx = std_rxraw_total; +db_results.metrics.STDrx_level = std_rxraw_lvl; +db_results.metrics.GMI = gmi; +db_results.metrics.AIR = air; +db_results.metrics.EVM = evm_total; +db_results.metrics.EVM_level = evm_lvl; +db_results.metrics.MLSE_dir = mlse_.DIR; -eq_package.resultsDBsignaling = resultsDBsignaling; -eq_package.equalizerConfigDBsignaling = equalizerConfigDBsignaling; +% Create configuration structure +eq_.e = []; +eq_.e2 = []; +eq_.e3 = []; +db_results.config = Equalizerstruct(); +db_results.config.eq = jsonencode(eq_); +mlse_.DIR = []; +db_results.config.mlse = jsonencode(mlse_); +db_results.config.equalizer_structure = int32(equalizer_structure.db_encoded); +db_results.config.comment = 'function: duobinary_signaling'; +%% Display analysis if requested +if options.showAnalysis + displayAnalysis(eq_noise, eq_signal, rx_signal, eq_, tx_symbols, M, options.postFFE); +end + +end + +%% Helper Function +function displayAnalysis(eq_noise, eq_signal, rx_signal, eq_, tx_symbols, M, postFFE) +% Display analysis plots and metrics +figure(336); +showEQNoisePSD(eq_noise, "fignum", 336, "displayname", 'Residual Noise after Duobinary'); + +if ~isempty(postFFE) + showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338); +end + +showEQfilter(eq_.e, eq_signal.fs.*2); + +figure(341); clf; +showLevelHistogram(eq_signal, tx_symbols, "fignum", 341); + +warning off +figure(400); clf; +showLevelScatter(eq_signal, tx_symbols, "fignum", 400); + +figure(401); clf; +showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401); +warning on end \ No newline at end of file diff --git a/Functions/EQ_structures/duobinary_target.m b/Functions/EQ_structures/duobinary_target.m index 8d66efb..36150dd 100644 --- a/Functions/EQ_structures/duobinary_target.m +++ b/Functions/EQ_structures/duobinary_target.m @@ -1,4 +1,4 @@ -function [eq_package] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx_bits, options) +function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx_bits, options) arguments eq_ @@ -22,8 +22,14 @@ if ~isempty(options.postFFE) [eq_signal,eq_noise] = options.postFFE.process(eq_signal,db_ref_sequence); end -% dir = [1,1]; -mlse_sig_sd = mlse_.process(eq_signal); +mlse_.DIR = [1,1]; +% + +if isa(mlse_,'MLSE_viterbi') + mlse_sig_sd = mlse_.process(eq_signal); +else + [mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols); +end mlse_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd); @@ -43,8 +49,8 @@ switch options.precode_mode tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded); tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded); - rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_precoded); + [~,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); %B) Just determine BER @@ -59,96 +65,87 @@ switch options.precode_mode mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd,"M",M); mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M); rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_decoded); - [~,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - + [~,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + burst_db_precoded = count_error_bursts(a, 40); % B) Omit the Coding by comparing with demapped TX symbol sequence tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols); rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd); - [bits_db,errors_db,ber_db,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + [bits_db,errors_db,ber_db,a] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + burst_db = count_error_bursts(a, 40); + cols = linspecer(8); + figure();hold on; + stem(1:40,burst_db,'LineWidth',1,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder'); + stem(1:40,burst_db_precoded,'LineWidth',1,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder'); + xlabel('Bit Error Burst Length') + ylabel('Occurence') + set(gca, 'yscale', 'log'); end % M = numel(unique(tx_symbols.signal)); rx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd); [bits_db,errors_db,ber_db,errorIndice_db] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); -eq_package.ber = ber_db; -resultsDBtgt = struct( ... - 'result_id', NaN, ... % - 'run_id', NaN, ... % Beispielhafte Run-ID - 'eqParam_id', NaN, ... % Beispielhafter Fremdschlüssel zur EqualizerParameters-Tabelle - 'date_of_processing', datetime('now'), ... % Aktuelles Datum und Uhrzeit - 'numBits', bits_db, ... % Beispiel: 1.000.000 Bits - 'numBitErr', errors_db, ... % Beispiel: 120 Bitfehler - 'BER_precoded', ber_db_diff_precoded, ... % BER = 120 / 1.000.000 - 'numBitErr_precoded', errors_db_diff_precoded, ... % Beispiel: 120 Bitfehler - 'BER', ber_db, ... % BER = 120 / 1.000.000 - 'SNR', [], ... % Beispielhafte SNR - 'SNR_level', jsonencode([]), ... % SNR-Level als JSON-codiertes Array - 'GMI', [], ... % Beispielhafter GMI-Wert - 'AIR', [], ... % Beispielhafter AIR-Wert - 'EVM', [], ... % Beispielhafte EVM - 'EVM_level', jsonencode([]), ... % EVM-Level als JSON-codiertes Array - 'Alpha', [] ... % Beispielhafter Alpha-Wert - ); + -if ~isempty(options.postFFE) - npostFFE = options.postFFE.order; + + +alpha = arburg(eq_noise.signal,1);%pf_.coefficients(2); +alpha = alpha(2); +if isa(mlse_,'MLSE_viterbi') + gmi_mlse = NaN; + air_mlse = NaN; else - npostFFE = 0; + gmi_mlse = GMI_MLSE; + air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M)); end -equalizerConfigDBtgt = struct( ... - 'eq_id', NaN, ... % Auto-Inkrement, wird in der DB gesetzt - 'equalizer_structure', int32(equalizer_structure.vnle_db_mlse), ... % Beispiel: 1 (z.B. für vnle) - 'M', M, ... % Ordnung der PAM-Konstellation - 'target_constellation', jsonencode(round(db_ref_constellation,5)), ... % Beispielhafter Target-String - 'db_target', 1, ... % 0 oder 1 - 'diff_precode', int32(options.precode_mode), ... % 0 oder 1 - 'postFFE', ~isempty(options.postFFE), ... % Beispielwert - 'NpostFFE', npostFFE, ... % Beispielwert - 'Ne1', eq_.Ne(1), ... % Feedforward Koeffizienten 1. Ordnung - 'Ne2', eq_.Ne(2), ... % Feedforward Koeffizienten 2. Ordnung - 'Ne3', eq_.Ne(3), ... % Feedforward Koeffizienten 3. Ordnung - 'Nb1', eq_.Nb(1), ... % Decision Feedback Koeffizienten 1. Ordnung - 'Nb2', eq_.Nb(2), ... % Decision Feedback Koeffizienten 2. Ordnung - 'Nb3', eq_.Nb(3), ... % Decision Feedback Koeffizienten 3. Ordnung - 'K', eq_.K, ... % Samples pro Symbol - 'DCmu', eq_.DCmu, ... % Anpassungsrate für DC-Tap - 'ideal_dfe', eq_.ideal_dfe, ... % Flag für ideal DFE (0 oder 1) - 'training_length', eq_.training_length, ... % Anzahl Trainingssymbole - 'training_loops', eq_.training_loops, ... % Anzahl Trainingsdurchläufe - 'TRmu1', eq_.FFEmu, ... % mu für DD-Modus (1. Ordnung) - 'TRmu2', eq_.FFEmu, ... % mu für DD-Modus (2. Ordnung) - 'TRmu3', eq_.FFEmu, ... % mu für DD-Modus (3. Ordnung) - 'TRmuDFE', eq_.DFEmu, ... % mu für DFE-Modus im DD - 'dd_loops', 5, ... % Anzahl Durchläufe im DD-Modus - 'DDmu1', eq_.DDmu(1), ... % mu für DD-Modus (1. Ordnung) - 'DDmu2', eq_.DDmu(2), ... % mu für DD-Modus (2. Ordnung) - 'DDmu3', eq_.DDmu(3), ... % mu für DD-Modus (3. Ordnung) - 'DDmuDFE', eq_.DDmu(4), ... % mu für DFE-Modus im DD - 'MLSE_mode', 'viterbi', ... % Beispiel: MLSE-Modus als String - 'MLSE_trellis_states', jsonencode(mlse_.trellis_states), ... % Trellis-States, z.B. als JSON-String oder kommasepariert - 'comment', 'function: duobinary_target.m', ... % Zusätzliche Kommentare - 'config_hash', NaN ... - ); +db_results = struct(); +db_results.metrics = Metricstruct; +db_results.metrics.result_id = NaN; +db_results.metrics.run_id = NaN; +db_results.metrics.eqParam_id = NaN; +db_results.metrics.date_of_processing = datetime('now'); +db_results.metrics.BER = ber_db; +db_results.metrics.numBits = bits_db; +db_results.metrics.numBitErr = errors_db; +db_results.metrics.BER_precoded = ber_db_diff_precoded; +db_results.metrics.numBitErr_precoded = errors_db_diff_precoded; +db_results.metrics.GMI = gmi_mlse; +db_results.metrics.AIR = air_mlse; +db_results.metrics.MLSE_dir = mlse_.DIR; +db_results.metrics.Alpha = alpha; -eq_package.resultsDBtgt = resultsDBtgt; -eq_package.equalizerConfigDBtgt = equalizerConfigDBtgt; +% Create DB results structure +db_results.config = Equalizerstruct(); +eq_.e = []; +eq_.e2 = []; +eq_.e3 = []; +db_results.config.eq = jsonencode(eq_); +% mlse_.DIR = []; +db_results.config.mlse = jsonencode(mlse_); +db_results.config.equalizer_structure = int32(equalizer_structure.vnle_db_mlse); +db_results.config.comment = 'function: Duobinary tgt. (VNLE -> MLSE)'; if options.showAnalysis + + eq_signal.eye(eq_signal.fs,M,"fignum",249); + + eq_noise = eq_noise - mean(eq_noise.signal); rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum"); - Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",250,"displayname","DB encoded reference"); + Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",10,"displayname","DB encoded reference"); showEQNoisePSD(eq_noise,"fignum",250,"displayname",'Duobinary Target Noise after Equalization'); - fprintf('DB tgt BER: %.2e \n',ber); + fprintf('DB tgt BER: %.2e \n',ber_db); + figure(341); clf; + showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341); end diff --git a/Functions/EQ_structures/vnle.m b/Functions/EQ_structures/vnle.m deleted file mode 100644 index 1447351..0000000 --- a/Functions/EQ_structures/vnle.m +++ /dev/null @@ -1,129 +0,0 @@ -function [eq_package] = vnle(eq_,M,rx_signal,tx_symbols,tx_bits,options) - %VNLE Apply an equalization algorithm to the received signal and calculate BER - % This function takes an equalizer object, a received signal, and the - % transmitted symbols to apply equalization, map the received signal back to bits, - % and compute the bit error rate (BER). - % - % Inputs: - % EQ - Equalizer object that provides the equalization method - % rx_signal - Received signal that needs to be equalized - % tx_symbols - Transmitted symbols used as a reference for BER calculation - % - % Outputs: - % eq_signal - Equalized version of the received signal - % ber - Bit error rate after equalization - % numErrors - Number of bit errors detected - - arguments - eq_ - M - rx_signal - tx_symbols - tx_bits - options.precode_mode db_mode - options.showAnalysis = 0 - options.eth_style = 0; - options.postFFE = []; - end - - %FFE or VNLE - if length(rx_signal)/2 == length(tx_symbols)+0.5 - rx_signal.signal = rx_signal.signal(1:end-1); - elseif length(rx_signal)/2 == length(tx_symbols)-1 - end - [eq_signal_sd,eq_noise] = eq_.process(rx_signal,tx_symbols); - - if ~isempty(options.postFFE) - [eq_signal_sd,eq_noise] = options.postFFE.process(eq_signal_sd,tx_symbols); - end - - eq_signal_hd = PAMmapper(M,0).quantize(eq_signal_sd); - - % precoding to mitigate error propagation, most prominently used in - % combination with duobinary signaling to avoid catastrophic error - % behavior (see J.W.M. Bergmans, Digital Baseband Transmission and Recording -> partial response signaling) - - % takes: - % -> eq_signal_hd: hard decision signal after eq - % -> tx_symbols: that where used as reference for eq - - switch options.precode_mode - case db_mode.db_emulate - % re - eq_signal_hd = Duobinary().encode(eq_signal_hd,"M",M); - eq_signal_hd = Duobinary().decode(eq_signal_hd,"M",M); - - tx_symbols_precoded = Duobinary().encode(tx_symbols); - tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded); - - tx_bits = PAMmapper(M,0,"eth_style",options.eth_style).demap(tx_symbols_precoded); - - case db_mode.db_discard - - % normal dsp for precoded sequence == discard/omit/ignore precode - tx_bits = PAMmapper(M,0).demap(tx_symbols); - - case db_mode.db_encoded - - % normal DB encoded data (only for 10KM) - - case db_mode.db_precoded - - eq_signal_hd = Duobinary().encode(eq_signal_hd,"M",M); - eq_signal_hd = Duobinary().decode(eq_signal_hd,"M",M); - - end - - rx_bits = PAMmapper(M,0,"eth_style",options.eth_style).demap(eq_signal_hd); - - [~,numErrors,ber,~] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - [evm_total,evm_lvl] = calc_evm(eq_signal_sd,tx_symbols); - [inf_rate] = calc_air(eq_signal_sd,tx_symbols,"skip_front",10000,"skip_end",10000); - - - eq_package.ber_vnle = ber; - eq_package.evm_total = evm_total; - eq_package.evm_lvl = evm_lvl; - eq_package.inf_rate_vnle = inf_rate; - eq_package.snr = snr(eq_signal_sd.signal,eq_noise.signal); - - eq_package.signal = eq_signal_sd; - - - if options.showAnalysis - - fprintf('SNR: %d dB \n',snr(eq_signal_sd.signal,eq_noise.signal)); - - if M == 6 - logm = 2.5; - else - logm = log2(M); - end - - fprintf('NGMI: %.4f \n', inf_rate/logm); - - fprintf(['VNLE EVM lvl: ',repmat('%.3f ',1,numel(evm_lvl)),' \n'],evm_lvl); - - fprintf('VNLE BER: %.2e \n',ber); - - disp("%%%%%%%%%%%%%%%%%%%%%") - - % showEQcoefficients('n1',eq_.e,'n2',eq_.e2,'n3',eq_.e3,"displayname",'Coefficients'); - % - % if ~isempty(options.postFFE) - % showEQcoefficients('n1',options.postFFE.e,"displayname",'Coefficients'); - % end - % - % showEQNoisePSD(eq_noise); - % - % showEQfilter(eq_.e,eq_signal_sd.fs.*2) - - % noiselessness(tx_symbols,eq_noise,"displayname",'SNR after VNLE','fignum',301); - - % showLevelHistogram(eq_signal_sd,tx_symbols,"fignum",302); - - - - end - -end \ No newline at end of file diff --git a/Functions/EQ_structures/vnle_postfilter_mlse.m b/Functions/EQ_structures/vnle_postfilter_mlse.m index 07ad822..c7f10e1 100644 --- a/Functions/EQ_structures/vnle_postfilter_mlse.m +++ b/Functions/EQ_structures/vnle_postfilter_mlse.m @@ -1,4 +1,19 @@ -function [eq_package] = vnle_postfilter_mlse(eq_,pf_,mlse_,M,rx_signal,tx_symbols,tx_bits,options) +function [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, rx_signal, tx_symbols, tx_bits, options) +% VNLE_POSTFILTER_MLSE Processes signals through VNLE, postfilter, and MLSE +% +% Inputs: +% eq_ - Equalizer object +% pf_ - Postfilter object +% mlse_ - MLSE object +% M - Modulation order +% rx_signal - Received signal +% tx_symbols - Transmitted symbols +% tx_bits - Transmitted bits +% options - Optional parameters +% +% Outputs: +% ffe_results - Results from FFE/VNLE processing +% mlse_results - Results from MLSE processing arguments eq_ @@ -15,271 +30,276 @@ arguments options.database = []; end -%FFE or VNLE -[eq_signal_sd,eq_noise] = eq_.process(rx_signal,tx_symbols); +%% Process signals through equalizers +% FFE or VNLE +[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols); +% Apply post-FFE if provided (does not work properly at the moment...very sad) if ~isempty(options.postFFE) tic - [eq_signal_sd,eq_noise] = options.postFFE.process(eq_signal_sd,tx_symbols); + [eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols); toc end -eq_signal_hd = PAMmapper(M,0).quantize(eq_signal_sd); +% Hard decision on VNLE output +eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd); -mlse_sig_sd = pf_.process(eq_signal_sd,eq_noise); +% Process through postfilter and MLSE -mlse_.DIR = pf_.coefficients; -% [mlse_sig_hd,mlse_sig_sd] = mlse_.process(mlse_sig_sd,tx_symbols); -mlse_sig_sd = mlse_.process(mlse_sig_sd); +[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise); -mlse_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd); - -% precoding to mitigate error propagation, most prominently used in -% combination with duobinary signaling to avoid catastrophic error -% behavior (see J.W.M. Bergmans, Digital Baseband Transmission and Recording -> partial response signaling) - -switch options.precode_mode - - case db_mode.no_db - % TX Data is not precoded: - - % A) Emulate diff precoding - eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd,"M",M); - eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded,"M",M); - - mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd,"M",M); - mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded,"M",M); - - tx_symbols_precoded = Duobinary().encode(tx_symbols); - tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded); - - tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded); - - rx_bits_vnle = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(eq_signal_hd_precoded); - [~,errors_vnle_diff_precoded,ber_vnle_diff_precoded,~] = calc_ber(rx_bits_vnle.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_precoded); - [~,errors_mlse_diff_precoded,ber_mlse_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - %B) Just determine BER - rx_bits_vnle = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(eq_signal_hd); - [bits_vnle,errors_vnle,ber_vnle,~] = calc_ber(rx_bits_vnle.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd); - [bits_mlse,errors_mlse,ber_mlse,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - case db_mode.db_precoded - - % Daten SIND TATSÄCHLICH precoded auf TX Seite: - - % A) Decode at Rx if no DB targeting was applied (we are in VNLE or MLSE EQ structure here! - eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd,"M",M); - eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded,"M",M); - rx_bits_vnle_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(eq_signal_hd_decoded); - [~,errors_vnle_diff_precoded,ber_vnle_diff_precoded,~] = calc_ber(rx_bits_vnle_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd,"M",M); - mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M); - rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_decoded); - [~,errors_mlse_diff_precoded,ber_mlse_diff_precoded,~] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - % B) Omit the Coding by comparing with demapped TX symbol sequence - - tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols); - rx_bits_vnle = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(eq_signal_hd); - [bits_vnle,errors_vnle,ber_vnle,~] = calc_ber(rx_bits_vnle.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); - - rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd); - [bits_mlse,errors_mlse,ber_mlse,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); +if 0 %tx_symbols.fs > 190e9 + if pf_.ncoeff == 1 + if pf_.coefficients(2) < 0 + % coeff is negative for too high/ bad VNLE convergence + pf_.coefficients(2) = 0.9; + + end + else + %long memory / pf respinse - not sure what to set here in a worst + %case :-) + end + %do it again: + pf_.useBurg = 0; + [mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise); end -% METRICS OF VNLE SD Signal: -[snr_vnle,snr_vnle_lvl] = calc_snr(tx_symbols.signal,eq_noise.signal); -[gmi_vnle] = calc_air(eq_signal_sd,tx_symbols,"skip_front",10000,"skip_end",10000); -air_vnle = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_vnle ./ log2(double(M)); -[evm_vnle_total,evm_vnle_lvl] = calc_evm(eq_signal_sd,tx_symbols); -[std_vnle_total,std_vnle_lvl] = calc_std(eq_signal_sd,tx_symbols); -[std_rxraw_total,std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out",tx_symbols.fs),tx_symbols); +mlse_.DIR = pf_.coefficients; -% METRICS OF MLSE (HD-VITERBI) -pf_.ncoeff = 1; -pf_.process(eq_signal_sd,eq_noise); -alpha = pf_.coefficients(2); +GMI_MLSE = NaN; +if isa(mlse_,'MLSE_viterbi') + [mlse_sig_sd] = mlse_.process(mlse_sig_sd); +else + [mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(mlse_sig_sd,tx_symbols); +end -eq_package.ber_mlse = ber_mlse; -eq_package.ber_vnle = ber_vnle; -eq_package.evm_vnle_total = evm_vnle_total; -eq_package.evm_vnle_lvl = evm_vnle_lvl; -eq_package.gmi = gmi_vnle; +mlse_sig_hd = PAMmapper(M, 0, "eth_style", options.eth_style_symbol_mapping).quantize(mlse_sig_sd); -eq_package.eq = eq_; -eq_package.pf = pf_; -eq_package.mlse = mlse_; +%% Calculate BER based on precoding mode +[numbits, errors, bers, ~] = calculateBER(eq_signal_hd, mlse_sig_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping); -resultsVNLE = struct( ... - 'result_id', NaN, ... % - 'run_id', NaN, ... % Beispielhafte Run-ID - 'eqParam_id', NaN, ... % Beispielhafter Fremdschlüssel zur EqualizerParameters-Tabelle - 'date_of_processing', datetime('now'), ... % Aktuelles Datum und Uhrzeit - 'BER', ber_vnle, ... % BER = 120 / 1.000.000 - 'numBits', bits_vnle, ... % Beispiel: 1.000.000 Bits - 'numBitErr', errors_vnle, ... % Beispiel: 120 Bitfehler - 'BER_precoded', ber_vnle_diff_precoded, ... % BER = 120 / 1.000.000 - 'numBitErr_precoded', errors_vnle_diff_precoded, ... % Beispiel: 120 Bitfehler - 'SNR', snr_vnle, ... % Beispielhafte SNR - 'SNR_level', jsonencode(snr_vnle_lvl), ... % SNR-Level als JSON-codiertes Array - 'STD', std_vnle_total, ... - 'STD_level', jsonencode(std_vnle_lvl),... - 'STDrx' , std_rxraw_total, ... - 'STDrx_level', jsonencode(std_rxraw_lvl),... - 'GMI', gmi_vnle, ... % Beispielhafter GMI-Wert - 'AIR', air_vnle, ... % Beispielhafter AIR-Wert - 'EVM', evm_vnle_total, ... % Beispielhafte EVM - 'EVM_level', jsonencode(evm_vnle_lvl), ... % EVM-Level als JSON-codiertes Array - 'Alpha', [] ... % Beispielhafter Alpha-Wert - ); +%% Calculate performance metrics +% VNLE metrics +[snr_vnle, snr_vnle_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal); +% [gmi_vnle] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000); +%calculate bitwise GMI +[gmi_vnle] = calc_ngmi(eq_signal_sd,tx_symbols); +gmi_vnle = max(gmi_vnle,0); + +air_vnle = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_vnle ./ log2(double(M)); +[evm_vnle_total, evm_vnle_lvl] = calc_evm(eq_signal_sd, tx_symbols); +[std_vnle_total, std_vnle_lvl] = calc_std(eq_signal_sd, tx_symbols); +[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols); + +% MLSE metrics +alpha = arburg(eq_noise.signal,1);%pf_.coefficients(2); +alpha = alpha(2); +gmi_mlse = max(GMI_MLSE,0); +air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M)); + +%% Display analysis if requested +if options.showAnalysis + displayAnalysis(eq_noise,whitened_noise, eq_signal_sd, rx_signal, eq_, pf_, mlse_, tx_symbols, M, options.postFFE); +end + + + + + + + +%% Prepare output structures +% Determine postFFE order if ~isempty(options.postFFE) npostFFE = options.postFFE.order; else npostFFE = 0; end -equalizerConfigVNLE = struct( ... - 'eq_id', NaN, ... % Auto-Inkrement, wird in der DB gesetzt - 'equalizer_structure', int32(equalizer_structure.vnle), ... % Beispiel: 1 (z.B. für vnle) - 'M', M, ... % Ordnung der PAM-Konstellation - 'target_constellation', jsonencode(round(unique(tx_symbols.signal),5)), ... % Beispielhafter Target-String - 'db_target', 0, ... % 0 oder 1 - 'diff_precode', int32(options.precode_mode), ... % 0 oder 1 - 'postFFE', double(~isempty(options.postFFE)), ... % Beispielwert - 'NpostFFE', npostFFE, ... % Beispielwert - 'Ne1', eq_.Ne(1), ... % Feedforward Koeffizienten 1. Ordnung - 'Ne2', eq_.Ne(2), ... % Feedforward Koeffizienten 2. Ordnung - 'Ne3', eq_.Ne(3), ... % Feedforward Koeffizienten 3. Ordnung - 'Nb1', eq_.Nb(1), ... % Decision Feedback Koeffizienten 1. Ordnung - 'Nb2', eq_.Nb(2), ... % Decision Feedback Koeffizienten 2. Ordnung - 'Nb3', eq_.Nb(3), ... % Decision Feedback Koeffizienten 3. Ordnung - 'K', eq_.K, ... % Samples pro Symbol - 'DCmu', eq_.DCmu, ... % Anpassungsrate für DC-Tap - 'ideal_dfe', eq_.ideal_dfe, ... % Flag für ideal DFE (0 oder 1) - 'training_length', eq_.training_length, ... % Anzahl Trainingssymbole - 'training_loops', eq_.training_loops, ... % Anzahl Trainingsdurchläufe - 'TRmu1', eq_.FFEmu, ... % mu für DD-Modus (1. Ordnung) - 'TRmu2', eq_.FFEmu, ... % mu für DD-Modus (2. Ordnung) - 'TRmu3', eq_.FFEmu, ... % mu für DD-Modus (3. Ordnung) - 'TRmuDFE', eq_.DFEmu, ... % mu für DFE-Modus im DD - 'dd_loops', 5, ... % Anzahl Durchläufe im DD-Modus - 'DDmu1', eq_.DDmu(1), ... % mu für DD-Modus (1. Ordnung) - 'DDmu2', eq_.DDmu(2), ... % mu für DD-Modus (2. Ordnung) - 'DDmu3', eq_.DDmu(3), ... % mu für DD-Modus (3. Ordnung) - 'DDmuDFE', eq_.DDmu(4), ... % mu für DFE-Modus im DD - 'comment', 'function: vnle_postfilter_mlse', ... % Zusätzliche Kommentare - 'config_hash', NaN ... - ); +ffe_results = struct(); +ffe_results.metrics = Metricstruct; +ffe_results.metrics.result_id = NaN; +ffe_results.metrics.run_id = NaN; +ffe_results.metrics.eqParam_id = NaN; +ffe_results.metrics.date_of_processing = datetime('now'); +ffe_results.metrics.BER = bers.vnle; +ffe_results.metrics.numBits = numbits.vnle; +ffe_results.metrics.numBitErr = errors.vnle; +ffe_results.metrics.BER_precoded = bers.vnle_precoded; +ffe_results.metrics.numBitErr_precoded = errors.vnle_precoded; +ffe_results.metrics.SNR = snr_vnle; +ffe_results.metrics.SNR_level = snr_vnle_lvl; +ffe_results.metrics.STD = std_vnle_total; +ffe_results.metrics.STD_level = std_vnle_lvl; +ffe_results.metrics.STDrx = std_rxraw_total; +ffe_results.metrics.STDrx_level = std_rxraw_lvl; +ffe_results.metrics.GMI = gmi_vnle; +ffe_results.metrics.AIR = air_vnle; +ffe_results.metrics.EVM = evm_vnle_total; +ffe_results.metrics.EVM_level = evm_vnle_lvl; +ffe_results.metrics.Alpha = alpha; +try + eq_.e = []; + eq_.e2 = []; + eq_.e3 = []; +end -resultsMLSE = struct( ... - 'result_id', NaN, ... % - 'run_id', NaN, ... % Beispielhafte Run-ID - 'eqParam_id', NaN, ... % Beispielhafter Fremdschlüssel zur EqualizerParameters-Tabelle - 'date_of_processing', datetime('now'), ... % Aktuelles Datum und Uhrzeit - 'BER', ber_mlse, ... % BER = 120 / 1.000.000 - 'numBits', bits_mlse, ... % Beispiel: 1.000.000 Bits - 'numBitErr', errors_mlse, ... % Beispiel: 120 Bitfehler - 'BER_precoded', ber_mlse_diff_precoded, ... % BER = 120 / 1.000.000 - 'numBitErr_precoded', errors_mlse_diff_precoded, ... % Beispiel: 120 Bitfehler - 'SNR', [], ... % Beispielhafte SNR - 'SNR_level', jsonencode([]), ... % SNR-Level als JSON-codiertes Array - 'GMI', [], ... % Beispielhafter GMI-Wert - 'AIR', [], ... % Beispielhafter AIR-Wert - 'EVM', [], ... % Beispielhafte EVM - 'EVM_level', jsonencode([]), ... % EVM-Level als JSON-codiertes Array - 'Alpha', alpha, ... % Beispielhafter Alpha-Wert - 'MLSE_dir', jsonencode([mlse_.DIR])... - ); +ffe_results.config = Equalizerstruct(); +ffe_results.config.eq = jsonencode(eq_); +ffe_results.config.equalizer_structure = int32(equalizer_structure.vnle); +ffe_results.config.comment = 'function: vnle_postfilter_mlse - FFE part'; +mlse_results = struct(); +mlse_results.metrics = Metricstruct; +mlse_results.metrics.result_id = NaN; +mlse_results.metrics.run_id = NaN; +mlse_results.metrics.eqParam_id = NaN; +mlse_results.metrics.date_of_processing = datetime('now'); +mlse_results.metrics.BER = bers.mlse; +mlse_results.metrics.numBits = numbits.mlse; +mlse_results.metrics.numBitErr = errors.mlse; +mlse_results.metrics.BER_precoded = bers.mlse_precoded; +mlse_results.metrics.numBitErr_precoded = errors.mlse_precoded; +% mlse_results.metrics.SNR = NaN; +mlse_results.metrics.GMI = gmi_mlse; +mlse_results.metrics.AIR = air_mlse; +% mlse_results.metrics.EVM = NaN; +% mlse_results.metrics.EVM_level = NaN; +mlse_results.metrics.Alpha = alpha; +mlse_results.metrics.MLSE_dir = mlse_.DIR; -equalizerConfigMLSE = struct( ... - 'eq_id', NaN, ... % Auto-Inkrement, wird in der DB gesetzt - 'equalizer_structure', int32(equalizer_structure.vnle_pf_mlse), ... % Beispiel: 1 (z.B. für vnle) - 'M', M, ... % Ordnung der PAM-Konstellation - 'target_constellation', jsonencode(round(unique(tx_symbols.signal),5)), ... % Beispielhafter Target-String - 'db_target', 0, ... % 0 oder 1 - 'diff_precode', int32(options.precode_mode), ... % 0 oder 1 - 'postFFE', double(~isempty(options.postFFE)), ... % Beispielwert - 'NpostFFE', npostFFE, ... % Beispielwert - 'Ne1', eq_.Ne(1), ... % Feedforward Koeffizienten 1. Ordnung - 'Ne2', eq_.Ne(2), ... % Feedforward Koeffizienten 2. Ordnung - 'Ne3', eq_.Ne(3), ... % Feedforward Koeffizienten 3. Ordnung - 'Nb1', eq_.Nb(1), ... % Decision Feedback Koeffizienten 1. Ordnung - 'Nb2', eq_.Nb(2), ... % Decision Feedback Koeffizienten 2. Ordnung - 'Nb3', eq_.Nb(3), ... % Decision Feedback Koeffizienten 3. Ordnung - 'K', eq_.K, ... % Samples pro Symbol - 'DCmu', eq_.DCmu, ... % Anpassungsrate für DC-Tap - 'ideal_dfe', eq_.ideal_dfe, ... % Flag für ideal DFE (0 oder 1) - 'training_length', eq_.training_length, ... % Anzahl Trainingssymbole - 'training_loops', eq_.training_loops, ... % Anzahl Trainingsdurchläufe - 'TRmu1', eq_.FFEmu, ... % mu für DD-Modus (1. Ordnung) - 'TRmu2', eq_.FFEmu, ... % mu für DD-Modus (2. Ordnung) - 'TRmu3', eq_.FFEmu, ... % mu für DD-Modus (3. Ordnung) - 'TRmuDFE', eq_.DFEmu, ... % mu für DFE-Modus im DD - 'dd_loops', 5, ... % Anzahl Durchläufe im DD-Modus - 'DDmu1', eq_.DDmu(1), ... % mu für DD-Modus (1. Ordnung) - 'DDmu2', eq_.DDmu(2), ... % mu für DD-Modus (2. Ordnung) - 'DDmu3', eq_.DDmu(3), ... % mu für DD-Modus (3. Ordnung) - 'DDmuDFE', eq_.DDmu(4), ... % mu für DFE-Modus im DD - 'MLSE_mode', 'viterbi', ... % Beispiel: MLSE-Modus als String - 'MLSE_trellis_states', jsonencode(mlse_.trellis_states), ... % Trellis-States, z.B. als JSON-String oder kommasepariert - 'comment', 'function: vnle_postfilter_mlse', ... % Zusätzliche Kommentare - 'config_hash', NaN ... - ); -eq_package.resultsVNLE = resultsVNLE; -eq_package.resultsMLSE = resultsMLSE; -eq_package.equalizerConfigVNLE = equalizerConfigVNLE; -eq_package.equalizerConfigMLSE = equalizerConfigMLSE; +% Create MLSE results structure +mlse_results.config = Equalizerstruct(); +mlse_results.config.eq = jsonencode(eq_); +mlse_.DIR = length(mlse_.DIR)-1; +mlse_results.config.mlse = jsonencode(mlse_); +mlse_results.config.equalizer_structure = int32(equalizer_structure.vnle_pf_mlse); +mlse_results.config.comment = 'function: vnle_postfilter_mlse - MLSE part'; -% eq_package.vnle_out = eq_signal_sd; -if options.showAnalysis - - - % fprintf(['VNLE EVM lvl: ',repmat('%.3f ',1,numel(evm_lvl)),' \n'],evm_lvl); - - % fprintf('VNLE BER: %.2e \n',ber_vnle); - % - % fprintf('MLSE BER: %.2e \n',ber_mlse); - - figure(336);clf; - showEQNoisePSD(eq_noise,"fignum",336,"displayname",'Residual Noise after VNLE','postfilter_taps',pf_.coefficients); - - % figure(337);clf; - % rx_signal.spectrum("normalizeTo0dB",1,"fignum",337,"displayname",'Rx Signal'); - - % figure(338);clf; - % showEQcoefficients('n1',eq_.e,'n2',eq_.e2,'n3',eq_.e3,"displayname",'Coefficients','fignum',338); - - if ~isempty(options.postFFE) - showEQcoefficients('n1',options.postFFE.e,"displayname",'Coefficients','fignum',338); - end - - showEQfilter(eq_.e,eq_signal_sd.fs.*2); - - figure(340);clf; - eq_signal_sd.eye(eq_signal_sd.fs,M,"fignum",340); - - figure(341);clf; - showLevelHistogram(eq_signal_sd,tx_symbols,"fignum",341); - - showLevelScatter(eq_signal_sd,tx_symbols,"fignum",400); - - showLevelScatter(rx_signal.resample("fs_out",tx_symbols.fs),tx_symbols,"fignum",401); - - % autoArrangeFigures(3,3,2) - - end +%% Helper Functions +function [numbits, errors, ber, error_locations] = calculateBER(eq_signal_hd, mlse_sig_hd, tx_symbols, tx_bits, precode_mode, M, eth_style) +% Initialize output structure +numbits = struct('vnle', 0, 'mlse', 0); +errors = struct('vnle', 0, 'mlse', 0, 'vnle_precoded', 0, 'mlse_precoded', 0); +ber = struct('vnle', 0, 'mlse', 0, 'vnle_precoded', 0, 'mlse_precoded', 0); +error_locations = struct('vnle', [], 'mlse', []); + +% PAM mapper for demapping +mapper = PAMmapper(M, 0, "eth_style", eth_style); + +switch precode_mode + case db_mode.no_db + % TX Data is not precoded + % A) Emulate diff precoding + eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M); + eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M); + + mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd, "M", M); + mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded, "M", M); + + tx_symbols_precoded = Duobinary().encode(tx_symbols); + tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded); + + tx_bits_precoded = mapper.demap(tx_symbols_precoded); + + rx_bits_vnle = mapper.demap(eq_signal_hd_precoded); + [~, errors.vnle_precoded, ber.vnle_precoded, ~] = calc_ber(rx_bits_vnle.signal, tx_bits_precoded.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + rx_bits_mlse = mapper.demap(mlse_sig_hd_precoded); + [~, errors.mlse_precoded, ber.mlse_precoded, ~] = calc_ber(rx_bits_mlse.signal, tx_bits_precoded.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + % B) Just determine BER + rx_bits_vnle = mapper.demap(eq_signal_hd); + [numbits.vnle, errors.vnle, ber.vnle, error_locations.vnle] = calc_ber(rx_bits_vnle.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + rx_bits_mlse = mapper.demap(mlse_sig_hd); + [numbits.mlse, errors.mlse, ber.mlse, error_locations.mlse] = calc_ber(rx_bits_mlse.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + case db_mode.db_precoded + % Data is precoded on TX side + % A) Decode at Rx if no DB targeting was applied + eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M); + eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M); + rx_bits_vnle_decoded = mapper.demap(eq_signal_hd_decoded); + [~, errors.vnle_precoded, ber.vnle_precoded, ~] = calc_ber(rx_bits_vnle_decoded.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd, "M", M); + mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded, "M", M); + rx_bits_mlse_decoded = mapper.demap(mlse_sig_hd_decoded); + [~, errors.mlse_precoded, ber.mlse_precoded, err_loc_precoded] = calc_ber(rx_bits_mlse_decoded.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + % B) Omit the Coding by comparing with demapped TX symbol sequence + tx_bits_demapped = mapper.demap(tx_symbols); + rx_bits_vnle = mapper.demap(eq_signal_hd); + [numbits.vnle, errors.vnle, ber.vnle, error_locations.vnle] = calc_ber(rx_bits_vnle.signal, tx_bits_demapped.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); + + rx_bits_mlse = mapper.demap(mlse_sig_hd); + [numbits.mlse, errors.mlse, ber.mlse, error_locations.mlse] = calc_ber(rx_bits_mlse.signal, tx_bits_demapped.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1); +end + +% cols = linspecer(8); +% burst_precoded = count_error_bursts(err_loc_precoded, 40); +% burst_normal = count_error_bursts(error_locations.mlse, 40); +% figure();hold on; +% stem(1:40,burst_normal,'LineWidth',1,'Color',cols(1,:),'Marker','_','DisplayName','w/o diff. precoder'); +% stem(1:40,burst_precoded,'LineWidth',1,'Color',cols(2,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder'); +% xlabel('Bit Error Burst Length') +% ylabel('Occurence') +% set(gca, 'yscale', 'log'); + + + +end + + +function displayAnalysis(eq_noise, whitened_noise, eq_signal_sd, rx_signal, eq_, pf_, mlse_, tx_symbols, M, postFFE) + +% rx_signal.spectrum("displayname",'Rx Signal','fignum',100,'normalizeTo0dB',1); + +% Display analysis plots and metrics +figure(336); +hold on; +eq_signal_sd.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0); +eq_noise.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0); + +showEQNoisePSD(eq_noise, "fignum", 338, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients); + +for t = 1:4 + pf_.ncoeff = t; + [~,~] = pf_.process(eq_signal_sd, eq_noise); + showEQNoisePSD(eq_noise, "fignum", 3388, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients); +end + + +tx_symbols.spectrum("displayname",'Equalized Signal','fignum',1234,'normalizeTo0dB',1); +if ~isempty(postFFE) + showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338); +end + +showEQcoefficients('n1', eq_.e,'n2', eq_.e2,'n3', eq_.e3, "displayname", 'Coefficients', 'fignum', 339); +showEQfilter(eq_.e, eq_signal_sd.fs.*2); + +figure(340); clf; +eq_signal_sd.eye(eq_signal_sd.fs, M, "fignum", 340); + +figure(341); clf; +showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 341); + +warning off +showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 400); +% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401); +drawnow; +warning on + + +whitened_noise.spectrum("displayname",'after postfilter','fignum',342); +eq_noise.spectrum("displayname",'before postfilter','fignum',342); + end \ No newline at end of file diff --git a/Functions/EQ_visuals/showEQNoisePSD.m b/Functions/EQ_visuals/showEQNoisePSD.m index 51b12d7..ed7b440 100644 --- a/Functions/EQ_visuals/showEQNoisePSD.m +++ b/Functions/EQ_visuals/showEQNoisePSD.m @@ -22,7 +22,6 @@ end % Ensure the figure is ready before calling spectrum eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 1,"color",options.color); - title('Noise of soft decision signal (not MLSE)') if ~isnan(options.postfilter_taps) % Hold on to the figure for further plotting @@ -41,4 +40,8 @@ end % Ensure a legend is displayed legend('show'); end + + xlim([-eq_noise.fs/2* 1e-9 eq_noise.fs/2* 1e-9]); + % ylim([-15, 0]); + end diff --git a/Functions/EQ_visuals/showEQcoefficients.m b/Functions/EQ_visuals/showEQcoefficients.m index a844155..cb747b8 100644 --- a/Functions/EQ_visuals/showEQcoefficients.m +++ b/Functions/EQ_visuals/showEQcoefficients.m @@ -53,8 +53,8 @@ function showEQcoefficients(options) for i = 1:numSubplots subplot(1, numSubplots, i); - stem(coeffs{i}, 'Color', options.color, 'LineWidth', 0.1, ... - 'Marker', '.', 'MarkerSize', 1); + stem(coeffs{i}, 'Color', options.color, 'LineWidth', 1, ... + 'Marker', '.', 'MarkerSize', 10); title(titles{i}); ylim([-1, 1]); % Set y-axis limits to [-1, 1] grid on; diff --git a/Functions/EQ_visuals/showEQfilter.m b/Functions/EQ_visuals/showEQfilter.m index 1d13022..4b0f403 100644 --- a/Functions/EQ_visuals/showEQfilter.m +++ b/Functions/EQ_visuals/showEQfilter.m @@ -1,5 +1,12 @@ -function showEQfilter(coefficients,fs) +function showEQfilter(coefficients,fs,options) + +arguments + coefficients + fs + options.fignum (1,1) double = NaN % Default to NaN if not provided + options.displayname (1,:) char = '' % Default to an empty string if not provided +end % Assuming that obj.e contains the final FFE filter coefficients. % Set the number of frequency points and sampling frequency. @@ -12,24 +19,34 @@ function showEQfilter(coefficients,fs) half_nfft = floor(nfft/2) + 1; f = f(1:half_nfft); H = H(1:half_nfft); - - % Plot the magnitude and phase responses. - figure(339); + + H_mag = abs(H); + H_norm = H_mag ./ max(H_mag); + + H_db = 20*log10(H_norm); + H_db = H_db - min(H_db); + + % Determine the figure number to use or create a new figure + if isnan(options.fignum) + fig = figure; % Create a new figure and get its handle + else + fig = figure(options.fignum); % Use the specified figure number + end % Magnitude response (in dB) subplot(2,1,1); hold on - plot(f.*1e-9, 20*log10(abs(1./H))); + plot(f.*1e-9, H_db,'DisplayName',options.displayname); title('(Inverted) Magnitude Response of FFE Filter'); - xlabel('Frequency (Hz)'); + xlabel('Frequency (GHz)'); ylabel('Magnitude (dB)'); grid on; % Phase response subplot(2,1,2); - plot(f.*1e-9, unwrap(angle(H))); + plot(f.*1e-9, unwrap(angle(H)),'DisplayName',options.displayname); title('Phase Response of FFE Filter'); - xlabel('Frequency (Hz)'); + xlabel('Frequency (GHz)'); ylabel('Phase'); grid on; diff --git a/Functions/EQ_visuals/showLevelHistogram.m b/Functions/EQ_visuals/showLevelHistogram.m index 66e5d08..e6e7156 100644 --- a/Functions/EQ_visuals/showLevelHistogram.m +++ b/Functions/EQ_visuals/showLevelHistogram.m @@ -20,10 +20,15 @@ end fig = figure(options.fignum); % Use the specified figure number end + eq_signal = max(min(eq_signal,3),-3); + %%% Separate Classes constellation = unique(ref_symbols); received_sd = NaN(numel(constellation),length(ref_symbols)); - lvlcol = cbrewer2('Set1',numel(constellation)); + lvlcol = cbrewer2('Paired',numel(constellation)*2); + lvlcol = lvlcol(2:2:end,:); + lvlcol = linspecer(numel(constellation)); + % lvlcol = cbrewer2('Set1',numel(constellation)); for lvl = 1:numel(constellation) %Separate the equalized signal into the %respective levels based on the actually @@ -40,12 +45,15 @@ end cnt(lvl) = round(numel(intermediate(~isnan(intermediate)))./length(eq_signal),3).*100; hold on warning off - histogram(received_sd(lvl,:),1000,"EdgeAlpha",0,'DisplayName',['Lvl ',num2str(lvl),' | ',num2str(cnt(lvl)),' %'],'FaceColor',lvlcol(lvl,:),'Normalization','pdf'); + histogram(received_sd(lvl,:),1000,"EdgeAlpha",0,'DisplayName',['Lvl ',num2str(lvl),' ; ',num2str(cnt(lvl)),' '],'FaceColor',lvlcol(lvl,:),'Normalization','pdf'); warning on end + xlim([-3 3]); legend grid on + % view([90 -90]); + end diff --git a/Functions/EQ_visuals/showLevelScatter.m b/Functions/EQ_visuals/showLevelScatter.m index 2d5def9..f39dcb8 100644 --- a/Functions/EQ_visuals/showLevelScatter.m +++ b/Functions/EQ_visuals/showLevelScatter.m @@ -1,31 +1,36 @@ -function showLevelScatter(eq_signal,ref_symbols,options) +function [symbols_for_lvl,avg_for_lvl] = showLevelScatter(eq_signal,ref_symbols,options) arguments eq_signal ref_symbols options.fignum (1,1) double = NaN % Default to NaN if not provided options.displayname (1,:) char = '' % Default to an empty string if not provided - options.f_sym = []; + options.f_sym =1e6; end +plot_shit = 1; + if isa(eq_signal,'Signal') options.f_sym = eq_signal.fs; eq_signal = eq_signal.signal; + assert(~isempty(options.f_sym),'No fsym given'); end if isa(ref_symbols,'Signal') ref_symbols = ref_symbols.signal; end -% Determine the figure number to use or create a new figure -if isnan(options.fignum) - fig = figure; % Create a new figure and get its handle -else - fig = figure(options.fignum); % Use the specified figure number - clf; + +if plot_shit + % Determine the figure number to use or create a new figure + if isnan(options.fignum) + fig = figure; % Create a new figure and get its handle + else + fig = figure(options.fignum); % Use the specified figure number + clf; + end end -assert(~isempty(options.f_sym),'No fsym given'); -rx_symbols = eq_signal ./ rms(eq_signal); +rx_symbols = eq_signal; %./ rms(eq_signal); correct_symbols = ref_symbols; f_sym = options.f_sym; @@ -33,56 +38,54 @@ col = cbrewer2('Paired',numel(unique(correct_symbols))*2); ccnt = -1; levels = unique(correct_symbols); - +symbols_for_lvl = NaN(numel(levels),length(correct_symbols)); start = 1; ende = length(correct_symbols); -% start = 30000; -% ende = 40000; - for l = 1:numel(levels) ccnt = ccnt+2; level_amplitude = levels(l); - symbols_for_lvl = NaN(1,length(correct_symbols)); - symbols_for_lvl(correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); - std_lvl(l) = std(symbols_for_lvl,'omitnan'); + symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); + std_lvl(l) = std(symbols_for_lvl(l,:),'omitnan'); xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6; - % xax_in_sec = 1:length(correct_symbols); - scatter(xax_in_sec(start:ende),symbols_for_lvl(start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:)); - hold on; + if plot_shit + scatter(xax_in_sec(start:ende),symbols_for_lvl(l,start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:)); + hold on; + end + end std_lvl = round(std_lvl,2); ccnt = 0; - +avg_for_lvl = NaN(numel(levels),length(correct_symbols)); % Add the windowed/ smoothed curves for l = 1:numel(levels) ccnt = ccnt+2; level_amplitude = levels(l); - symbols_for_lvl = NaN(1,length(correct_symbols)); + L = 500; + movmean = 1/L .* movsum(rx_symbols(correct_symbols==level_amplitude),[L/2,L/2], 'Endpoints', 'fill'); - movmean = 1/250 .* movsum(rx_symbols(correct_symbols==level_amplitude),[250/2,250/2]); + avg_for_lvl(l,correct_symbols==level_amplitude) = movmean; - symbols_for_lvl(correct_symbols==level_amplitude) = movmean; - - nanx = isnan(symbols_for_lvl); - t = 1:numel(symbols_for_lvl); - symbols_for_lvl(nanx) = interp1(t(~nanx), symbols_for_lvl(~nanx), t(nanx)); + nanx = isnan(avg_for_lvl(l,:)); + t = 1:numel(avg_for_lvl(l,:)); + avg_for_lvl(l,nanx) = interp1(t(~nanx), avg_for_lvl(l,~nanx), t(nanx)); xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6; % xax_in_sec = 1:length(correct_symbols); - plot(xax_in_sec(start:ende),symbols_for_lvl(start:ende),'Color',col(ccnt,:)); + if plot_shit + plot(xax_in_sec(start:ende),avg_for_lvl(l,start:ende),'Color',col(ccnt,:)); + end hold on end -%yline(max(rx_symbols(correct_symbols==levels(2)))) -yline(levels); + if 0 annotation(fig,'textbox',... @@ -121,9 +124,10 @@ if 0 'FitBoxToText','off'); end -% xlim([0, 2.6]) -% ylim([-2 2]) +if plot_shit +% yline(levels); xlabel('Time in $\mu$s'); ylabel('Normalized Amplitude'); - +ylim([-3 3]); +end end diff --git a/Functions/Lab_helper/loadFreqResp.m b/Functions/Lab_helper/loadFreqResp.m index 019ea07..e2c99e8 100644 --- a/Functions/Lab_helper/loadFreqResp.m +++ b/Functions/Lab_helper/loadFreqResp.m @@ -1,5 +1,5 @@ % Define the precomp path -precomp_path = "C:\Users\sioe\Documents\MATLAB\imdd_simulation\projects\ECOC_2025\"; +precomp_path = "C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\precomp"; % Step 1: Find all valid files (assume .mat files for ChannelFreqResp) fileList = dir(fullfile(precomp_path, '*.mat')); diff --git a/Functions/Metrics/calc_air.m b/Functions/Metrics/calc_air.m index c42ab2f..2b10827 100644 --- a/Functions/Metrics/calc_air.m +++ b/Functions/Metrics/calc_air.m @@ -1,5 +1,5 @@ function [ach_inf_rate] = calc_air(test_signal,reference_signal,options) -% Calculation of AIR acc. to J. Kozesnik, „Numerically Computing Achievable Rates of Memoryless Channels“, Francisco Javier Garcıa-Gomez, doi: 10.1007/978-94-009-9857-5. +% „Numerically Computing Achievable Rates of Memoryless Channels“, Francisco Javier Garcıa-Gomez, doi: 10.1007/978-94-009-9857-5. % Implementation is not accessible, I mailed TUM to get the code... arguments(Input) @@ -26,7 +26,7 @@ end % TRIM [test_signal,reference_signal]=trimseq(test_signal,reference_signal,options.skip_front,options.skip_end); -% CALC EVM +% CALC AIR %%% new implementation of AIR constellation = unique(reference_signal); reference_idx = arrayfun(@(x) find(constellation == x, 1), reference_signal); diff --git a/Functions/Metrics/calc_evm.m b/Functions/Metrics/calc_evm.m index c597732..a8e4b7e 100644 --- a/Functions/Metrics/calc_evm.m +++ b/Functions/Metrics/calc_evm.m @@ -28,39 +28,39 @@ end [evm_total,evm_lvl] = calc_evm_(test_signal,reference_signal); -function [evm_total,evm_lvl] = calc_evm_(test_signal,reference_signal) + function [evm_total, evm_lvl] = calc_evm_(test_signal, reference_signal) + % Validate input + assert(length(test_signal) == length(reference_signal), "Sequence length does not match"); - assert(length(test_signal) == length(reference_signal),"Sequence length does not match"); + % Calculate error vector + error_vector = test_signal - reference_signal; - error_vector = (test_signal-reference_signal); + % EVM (RMS) as percentage, per MathWorks definition + evm_total = sqrt(sum(abs(error_vector).^2) / sum(abs(reference_signal).^2)) * 100; - %%% Overall EVM - evm_total = rms(error_vector); - - try - %%% Per Level EVM + % Per-level EVM k = unique(reference_signal); + evm_lvl = NaN(1, length(k)); for lvl = 1:length(k) - lvl_errors = error_vector(reference_signal==k(lvl)); - evm_lvl(lvl) = rms(lvl_errors); + idx = reference_signal == k(lvl); + if any(idx) + lvl_errors = error_vector(idx); + lvl_refs = reference_signal(idx); + evm_lvl(lvl) = sqrt(sum(abs(lvl_errors).^2) / sum(abs(lvl_refs).^2)) * 100; + end end - catch - evm_lvl = NaN; - warning('No EVM per level calculated') end -end + function [data_,reference_]=trimseq(data,reference,skipstart,skip_end) -function [data_,reference_]=trimseq(data,reference,skipstart,skip_end) + data_ = data(skipstart+1:end-skip_end,:); - data_ = data(skipstart+1:end-skip_end,:); + delta_bits = length(reference) - length(data); - delta_bits = length(reference) - length(data); + skip_end = delta_bits + skip_end; - skip_end = delta_bits + skip_end; + reference_ = reference(skipstart+1:end-skip_end,:); - reference_ = reference(skipstart+1:end-skip_end,:); - -end + end end \ No newline at end of file diff --git a/Functions/Metrics/calc_ngmi.m b/Functions/Metrics/calc_ngmi.m index e30121a..5d7363a 100644 --- a/Functions/Metrics/calc_ngmi.m +++ b/Functions/Metrics/calc_ngmi.m @@ -1,123 +1,181 @@ function [GMI,NGMI] = calc_ngmi(test_signal,reference_signal,options) -% Silas implementation of (N)GMI calculation according to: J. Cho, L. Schmalen, und P. J. Winzer, -% „Normalized Generalized Mutual Information as a Forward Error Correction Threshold for Probabilistically Shaped QAM“, -% in 2017 European Conference on Optical Communication (ECOC), Sep. 2017, doi: 10.1109/ECOC.2017.8345872. - -% This implementation assumes the same normal distributed noise for each -% channel (sigma2 is calculated once for the whole signal) +% (N)GMI for AWGN, supports PAM2/4/8 (per-symbol) and PAM6 (pair-based, 5 bits / 2 symbols) arguments(Input) - test_signal; - reference_signal; - options.skip_front = 0; - options.skip_end = 0; - options.returnErrorLocation = 0; + test_signal + reference_signal + options.skip_front (1,1) double = 0 + options.skip_end (1,1) double = 0 + options.returnErrorLocation (1,1) double = 0 %#ok end -options.skip_end = abs(options.skip_end); +options.skip_end = abs(options.skip_end); options.skip_front = abs(options.skip_front); +assert((options.skip_end+options.skip_front) < numel(test_signal), ... + "You can not skip more samples than the overall length."); -assert((options.skip_end+options.skip_front) bit label) and (symbol -> index) + [ok1, idx_s1] = ismember(trans_pairs(:,1), symbols); + [ok2, idx_s2] = ismember(trans_pairs(:,2), symbols); + assert(all(ok1)&all(ok2), 'Transition level not found in symbols.'); + % For fast masking: linear indices of each (i,j) pair in an MxM grid + lin_idx_pairs = sub2ind([M,M], idx_s1, idx_s2); % (M^2) x 1 - error_vector = (test_signal-reference_signal); - sigma2 = var(error_vector); %noise variance + % Build pairwise prior P_pair = P_X(i)*P_X(j) on the MxM grid + P_pair = P_X(:) * P_X(:).'; % MxM + logP_pair = log(P_pair); % for numerical stability - %%% Separate Classes - constellation = unique(reference_signal); - received_sd = NaN(numel(constellation),length(reference_signal)); - lvlcol = cbrewer2('Set1',numel(constellation)); - for lvl = 1:numel(constellation) - %Separate the equalized signal into the - %respective levels based on the actually - %transmitted level! - received_sd(lvl,reference_signal==constellation(lvl)) = test_signal(reference_signal==constellation(lvl)); + % Precompute constant term for log-likelihood + logc = -0.5*log(2*pi*sigma2); + + % Prepare transmitted 5-bit labels per observed pair (to split llr0/llr1) + % Find each (x1(k),x2(k)) in trans_pairs: + [tf, row_in_table] = ismember([x1(:), x2(:)], trans_pairs, 'rows'); + assert(all(tf), 'A reference pair was not found in the transition table.'); + tx_bits_pair = pam6bits(row_in_table, :); % numPairs x 5 + + % Precompute masks for bit=0 / bit=1 on the MxM grid for each bit position + mask1 = false(M,M,5); + mask0 = false(M,M,5); + for b = 1:5 + tmp = false(M,M); + tmp(lin_idx_pairs) = pam6bits(:,b) == 1; + mask1(:,:,b) = tmp; + tmp = false(M,M); + tmp(lin_idx_pairs) = pam6bits(:,b) == 0; + mask0(:,:,b) = tmp; + end + + % Loop pairs: compute exact LLRs via log-sum-exp over the MxM grid + LLR_exact = zeros(numPairs,5); + for k = 1:numPairs + % log-likelihood vectors along each axis + li = logc - ((y1(k) - symbols).^2) / (2*sigma2); % 1xM + lj = logc - ((y2(k) - symbols).^2) / (2*sigma2); % 1xM + % joint log-weights over all (i,j) + logW = (li(:) + lj(:).') + logP_pair; % MxM + + for b = 1:5 + % log P(bit=1 | y) ∝ logsumexp(logW over mask1) + lnum = logsumexp(logW(mask1(:,:,b))); + % log P(bit=0 | y) ∝ logsumexp(logW over mask0) + lden = logsumexp(logW(mask0(:,:,b))); + LLR_exact(k,b) = lnum - lden; % natural-log LLR end + end - N = length(test_signal); % Number of received samples + % --- Bitwise MI via consistency relation + MI = zeros(1,5); + for b = 1:5 + llr_b = LLR_exact(:,b); + idx0 = (tx_bits_pair(:,b)==0); + idx1 = ~idx0; + I0 = mean( log2(1 + exp( llr_b(idx0))) ); % natural LLR inside exp() + I1 = mean( log2(1 + exp(-llr_b(idx1))) ); + MI(b) = 1 - 0.5*(I0 + I1); + end - M = length(constellation); %P - m = log2(M); %bits per symbol + % GMI per symbol (5 bits per 2 symbols) + GMI = sum(MI)/2; + NGMI = GMI / 2.5; - entries = sum(~isnan(received_sd),2)'; - P_X = entries./N; +else + % ===== Generic PAM2/4/8 etc.: per-symbol bit labeling ===== + symbols = constellation; % 1xM + % Gray labels per symbol (your helper) + gray_bits = PAMmapper(M,0).demap_(symbols); % M x log2(M) + m = log2(M); - % Parameters - symbols = constellation'; % PAM-4 symbols + % Likelihood (real AWGN) + q = @(y,x) (1/sqrt(2*pi*sigma2)) * exp(-(y - x).^2/(2*sigma2)); - gray_bits = PAMmapper(M,0).demap_(constellation); % Gray coding (bits per symbol) + % Transmitted bits per sample + tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal); % N x m - % Conditional probability function for AWGN - q_Y_given_X = @(y, x) (1 / sqrt(2 * pi * sigma2)) * exp(-(y - x).^2 / (2 * sigma2)); - - % Entropy term - H_X = -sum(P_X .* log2(P_X)); % Entropy of input distribution - - % GMI computation - noise_impact_term = 0; + % Exact bit-LLRs and MI + LLR_exact = zeros(N,m); + for b = 1:m + mask1 = (gray_bits(:,b)==1); + mask0 = ~mask1; for k = 1:N - y_k = test_signal(k); % Current received sample - [~, closest_symbol_idx] = min(abs(symbols - y_k)); % Closest symbol index - closest_symbol = symbols(closest_symbol_idx); % Closest symbol - - for i = 1:m - % Extract i-th bit for each symbol - bit_mask = gray_bits(:, i); % Binary column for i-th bit of all symbols - - matching_symbols = symbols(bit_mask == gray_bits(closest_symbol_idx, i)); - - % Numerator: Sum over x in x_{b_{k, i}} - numerator = sum(q_Y_given_X(y_k, matching_symbols) .* P_X(ismember(symbols, matching_symbols))); - - % Denominator: Sum over all x - denominator = sum(q_Y_given_X(y_k, symbols) .* P_X); - - % Logarithmic contribution - noise_impact_term = noise_impact_term + log2(numerator / denominator); - end + yk = test_signal(k); + den = sum(q(yk, symbols) .* P_X); + num1 = sum(q(yk, symbols(mask1)) .* P_X(mask1)); + num0 = sum(q(yk, symbols(mask0)) .* P_X(mask0)); + % Use the ratio of posteriors P(bit=1|y)/P(bit=0|y) + LLR_exact(k,b) = log(num1) - log(num0); % natural log end - - % Normalize the noise impact term by N - noise_impact_term = noise_impact_term / N; - - % GMI - GMI = H_X + noise_impact_term; - NGMI = GMI / m; - end - function [data_,reference_]=trimseq(data,reference,skipstart,skip_end) - - data_ = data(skipstart+1:end-skip_end,:); - - delta_bits = length(reference) - length(data); - - skip_end = delta_bits + skip_end; - - reference_ = reference(skipstart+1:end-skip_end,:); - + % Bitwise MI + MI = zeros(1,m); + for b = 1:m + llr_b = LLR_exact(:,b); + idx0 = (tx_bits(:,b)==0); + idx1 = ~idx0; + I0 = mean( log2(1 + exp( llr_b(idx0))) ); + I1 = mean( log2(1 + exp(-llr_b(idx1))) ); + MI(b) = 1 - 0.5*(I0 + I1); end -end \ No newline at end of file + % Per-symbol GMI/NGMI + GMI = sum(MI); + NGMI = GMI / m; +end + +% ---------- helpers ---------- +function s = logsumexp(a) + if isempty(a), s = -inf; return; end + amax = max(a(:)); + s = amax + log(sum(exp(a(:) - amax))); +end + +function [data_,reference_] = trimseq(data,reference,skipstart,skip_end) + data_ = data(skipstart+1:end-skip_end,:); + delta = numel(reference) - numel(data_); + reference_ = reference(skipstart+1:end-(skip_end+delta),:); +end +end diff --git a/Functions/Metrics/calc_snr.m b/Functions/Metrics/calc_snr.m index 2dda8b0..ff25cd9 100644 --- a/Functions/Metrics/calc_snr.m +++ b/Functions/Metrics/calc_snr.m @@ -1,36 +1,43 @@ function [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise) -% CALC_SNR Calculates overall SNR and level-wise SNR for a PAM-M constellation. -% -% [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise) -% -% Inputs: -% tx_signal - Vector of transmitted signal values. -% eq_noise - Vector of corresponding noise samples. -% -% Outputs: -% snr_all - Overall SNR computed using all signal values. -% snr_per_level - A vector where each element is the SNR computed -% for a unique amplitude level in tx_signal. -% -% The function first computes the overall SNR using the full signal vectors. -% Then it uses the unique levels in tx_signal to calculate the SNR for -% the symbols corresponding to each level separately. - + % CALC_SNR Calculates overall SNR and level-wise SNR for a PAM-M constellation. + % + % [snr_all, snr_per_level] = calc_snr(tx_signal, eq_noise) + % + % Inputs: + % tx_signal - Vector of transmitted signal values. + % eq_noise - Vector of corresponding noise samples. + % + % Outputs: + % snr_all - Overall SNR computed using all signal values. + % snr_per_level - A vector where each element is the SNR computed + % for a unique amplitude level in tx_signal. + % + % The function first computes the overall SNR using the full signal vectors. + % Then it uses the unique levels in tx_signal to calculate the SNR for + % the symbols corresponding to each level separately. + + if isa(tx_signal,'Signal') + tx_signal = tx_signal.signal; + end + + if isa(eq_noise,'Signal') + eq_noise = eq_noise.signal; + end + % Calculate overall SNR using the complete signals snr_all = snr(tx_signal, eq_noise); - + % Get the unique amplitude levels in the transmitted signal levels = unique(tx_signal); % Preallocate an array to store the SNR for each unique level - snr_per_level = zeros(size(levels)); - % Loop over each unique level to compute the SNR for that level for i = 1:length(levels) % Find indices where tx_signal equals the current level idx = (tx_signal == levels(i)); - + % Compute the SNR for these indices snr_per_level(i) = snr(tx_signal(idx), eq_noise(idx)); + end end diff --git a/Functions/Metrics/calc_std.m b/Functions/Metrics/calc_std.m index 5f3a2e5..927b18d 100644 --- a/Functions/Metrics/calc_std.m +++ b/Functions/Metrics/calc_std.m @@ -25,34 +25,46 @@ end [test_signal,reference_signal]=trimseq(test_signal,reference_signal,options.skip_front,options.skip_end); % CALC EVM -[std_total,std_lvl] = calc_std_(test_signal,reference_signal); +[std_total, std_lvl, nsd_lvl, d_min]= calc_std_(test_signal,reference_signal); +std_lvl = nsd_lvl; + function [std_total, std_lvl, nsd_lvl, d_min] = calc_std_(test_signal, reference_signal) + + % NSD (Normalized Standard Deviation) expresses the noise spread at each symbol level + % relative to the minimum distance between levels. A low NSD means low error risk, + % while NSD approaching 0.5 indicates a high chance of symbol errors due to noise. -function [std_total,std_lvl] = calc_std_(test_signal,reference_signal) - - assert(length(test_signal) == length(reference_signal),"Sequence length does not match"); - - error_vector = (test_signal-reference_signal); - - %%% Overall EVM - std_total = std(test_signal); - - test_signal = test_signal ./ rms(test_signal); - - try - %%% Per Level EVM + % Ensure input is column vector + test_signal = test_signal(:); + reference_signal = reference_signal(:); + + assert(length(test_signal) == length(reference_signal), "Sequence length does not match"); + + % Find unique levels and their minimum spacing k = unique(reference_signal); + d_min = min(diff(k)); % Minimum distance between adjacent levels + + % Normalize test signal to RMS=1 (if not already) + test_signal = test_signal / rms(test_signal); + + % Overall standard deviation (not normalized) + std_total = std(test_signal); + + % Per-level standard deviation and normalized std + std_lvl = zeros(1, length(k)); + nsd_lvl = zeros(1, length(k)); for lvl = 1:length(k) - % lvl_errors = error_vector(reference_signal==k(lvl)); - std_lvl(lvl) = std(test_signal(reference_signal==k(lvl))); + idx = reference_signal == k(lvl); + if any(idx) + std_lvl(lvl) = std(test_signal(idx)); + nsd_lvl(lvl) = std_lvl(lvl) / d_min; + else + std_lvl(lvl) = NaN; + nsd_lvl(lvl) = NaN; + end end - catch - std_lvl = NaN; - warning('No EVM per level calculated') end -end - function [data_,reference_] = trimseq(data,reference,skipstart,skip_end) data_ = data(skipstart+1:end-skip_end,:); diff --git a/Functions/Theory/Neuer Ordner/duobinary_histogram.m b/Functions/Theory/Neuer Ordner/duobinary_histogram.m deleted file mode 100644 index a71396b..0000000 --- a/Functions/Theory/Neuer Ordner/duobinary_histogram.m +++ /dev/null @@ -1,51 +0,0 @@ - - - -figure(2) -tiledlayout(1,3) -cols = linspecer(5); -cnt = 1; -for m = [4,6,8] - - M = m; - fsym = 112e9; - fdac = 256e9; - - [Digi_sig,Symbols,Tx_bits] = PAMsource(... - "fsym",fsym,"M",M,"order",19,"useprbs",1,... - "fs_out",fdac,... - "applyclipping",0,"clipfactor",1.5,... - "applypulseform",0,"pulseformer",NaN,... - "randkey",33,... - "db_precode",1,"db_encode",0,... - "mrds_code",0,"mrds_blocklength",512).process(); - - Symbols_pre = Duobinary().precode(Symbols); - - Symbols_db = Duobinary().encode(Symbols_pre); - - if M == 4 - Symbols_db.signal = Symbols_db.signal .*sqrt(2.5); - elseif M == 6 - Symbols_db.signal = Symbols_db.signal .*sqrt(5.8); - elseif M == 8 - Symbols_db.signal = Symbols_db.signal .*sqrt(10.5); - end - - % figure(1) - % hold on - % histogram(Symbols_db.signal,"EdgeAlpha",0.3,"Normalization","probability"); - - - % figure(1) - nexttile - hold on - bar(unique(Symbols_db.signal),histcounts(int32(Symbols_db.signal),"Normalization","probability"),"FaceColor",cols(cnt,:),"FaceAlpha",0.6,"BarWidth",1-(0.2*cnt),"LineWidth",0.5,"EdgeColor",'black','DisplayName',['Duobinary PAM-',num2str(M)]); - xticks(unique(Symbols_db.signal)); - ylim([0 0.26]); - xlabel("Symbol") - - - cnt = cnt+1, - -end \ No newline at end of file diff --git a/Functions/Theory/Neuer Ordner/duobinary_transferfunction.m b/Functions/Theory/Neuer Ordner/duobinary_transferfunction.m deleted file mode 100644 index ccc7448..0000000 --- a/Functions/Theory/Neuer Ordner/duobinary_transferfunction.m +++ /dev/null @@ -1,18 +0,0 @@ - - -% Define the filter taps -h_ = {[1],[1 1],[1 2 1],[1 3 3 1]}; - -for i = 1:length(h_) - h = h_{i}; - - [H, w] = freqz(h, 1, 1024, 1); - - figure(1); - hold on - plot(w, 10*log10(abs(H)), 'LineWidth', 2,'DisplayName',['$(1+D)^2$']); %todo - xlabel('Normalized Frequency'); - ylabel('Amplitude in dB'); - grid on; - ylim([-20,10]) -end \ No newline at end of file diff --git a/Functions/beautifyBERplot.m b/Functions/beautifyBERplot.m index e7a02ef..9bc5d0a 100644 --- a/Functions/beautifyBERplot.m +++ b/Functions/beautifyBERplot.m @@ -1,37 +1,113 @@ -function beautifyBERplot() - % BEAUTIFYBERPLOT Enhances a BER plot for publication-quality figures. +function beautifyBERplot(options) +% BEAUTIFYBERPLOT Enhances BER-style plots for publication-quality figures. +% Supports automatic smoothing and trend-line overlay. +% +% Usage examples: +% beautifyBERplot; % default +% beautifyBERplot("polyfit",1); % add polynomial fit +% beautifyBERplot("polyfit",1,"fitmethod","pchip") % piecewise cubic fit +% +% Supported fitmethod options: 'polyfit', 'smoothingspline', 'loess', 'pchip' + +arguments + options.logscale (1,1) logical = 1 + options.setmarkers (1,1) logical = 1; + options.polyfit (1,1) logical = 0 + options.polyorder (1,1) double = 2 + options.fitmethod (1,1) string = "polyfit" % choose fit type +end + +% --- find all line objects in current axes +lines = findall(gca, 'Type', 'Line'); +markers = {'o', 's', 'd', '^', 'v', '>', '<', 'p', 'h'}; +num_markers = length(markers); + +% --- style all lines consistently +for i = 1:length(lines) + lines(i).LineWidth = 1.2; + % lines(i).LineStyle = '-'; + if options.setmarkers == 1 + if string(lines(i).Marker) == "none" + lines(i).Marker = markers{mod(i-1, num_markers) + 1}; + end + lines(i).MarkerSize = 4; + lines(i).MarkerFaceColor = lines(i).Color; + end +end + +% --- optional smoothing/fitting overlay +if options.polyfit + hold on + for i = 1:length(lines) + x = lines(i).XData; + y = lines(i).YData; + valid = isfinite(x) & isfinite(y); + if sum(valid) < options.polyorder + 1 + continue; + end + + xf = linspace(min(x(valid)), max(x(valid)), 200); + + % ----- choose fitting method ----- + switch lower(options.fitmethod) + case "polyfit" + p = polyfit(x(valid), y(valid), options.polyorder); + yf = polyval(p, xf); + + case "smoothingspline" + try + f = fit(x(valid)', y(valid)', 'smoothingspline'); + yf = feval(f, xf); + catch + yf = interp1(x(valid), y(valid), xf, 'pchip'); + end + + case "loess" + yf = smooth(x(valid), y(valid), 0.2, 'loess'); + yf = interp1(x(valid), yf, xf, 'linear', 'extrap'); + + case "pchip" + yf = interp1(x(valid), y(valid), xf, 'pchip'); + + otherwise + warning('Unknown fitmethod "%s". Using polyfit.', options.fitmethod); + p = polyfit(x(valid), y(valid), options.polyorder); + yf = polyval(p, xf); + end + + % --- lightened color for fit overlay + lightcol = lines(i).Color + 0.4 * (1 - lines(i).Color); + lightcol(lightcol > 1) = 1; + + plot(xf, yf, '-', 'Color', lightcol, ... + 'LineWidth', 0.7, 'Marker', 'none', ... + 'HandleVisibility','off'); + end + hold off +end + +% --- axis scaling and cosmetics +if options.logscale + set(gca, 'YScale', 'log'); +end + +% --- Figure size in centimeters --- +% width_pt = 500; +% height_pt = 300; +% +% pt2cm = 0.03514598; % TeX point → cm +% width_cm = width_pt * pt2cm; % = 8.85 cm +% height_cm = height_pt * pt2cm; % = 2.81 cm +% +% set(gcf, 'Units', 'centimeters', 'Position', [2 2 width_cm height_cm]); +% set(gcf, 'PaperUnits', 'centimeters', 'PaperPosition', [0 0 width_cm height_cm]); + +% --- Formatting --- +set(findall(gca, '-property', 'Interpreter'), 'Interpreter', 'latex'); +set(gcf, 'Color', 'w'); +set(gca, 'Box', 'on', 'LineWidth', 0.8); +grid on; +set(gca, 'FontSize', 10, 'FontName', 'Latin Modern Roman'); + - % Set line properties for all current plot lines - lines = findall(gca, 'Type', 'Line'); - markers = {'o', 's', 'd', '^', 'v', '>', '<', 'p', 'h'}; % Define marker styles - num_markers = length(markers); - - for i = 1:length(lines) - lines(i).LineWidth = 1.3; % Thicker line width - %lines(i).LineStyle = '-'; % Solid lines for simplicity - if string(lines(i).Marker) == "none" - lines(i).Marker = markers{mod(i-1, num_markers) + 1}; % Assign markers cyclically - end - lines(i).MarkerSize = 4; % Marker size - lines(i).MarkerFaceColor = 'auto'; % Use line color for marker face - end - - % Change all text interpreters to LaTeX - set(findall(gca, '-property', 'Interpreter'), 'Interpreter', 'latex'); - - % Set figure background to white - set(gcf, 'Color', 'w'); - - - % Set logarithmic scale for y-axis, but only if it makes sense. - % If this is not always desired, you could condition this on the presence of lines or data. - set(gca, 'YScale', 'log'); - - % Customize grid and box appearance - set(gca, 'Box', 'on', 'LineWidth', 0.8); % Thicker border - grid on; - % grid minor; - - % Adjust font size and style for better readability - set(gca, 'FontSize', 10, 'FontName', 'Times New Roman'); end diff --git a/projects/ECOC_2025/dsp_run_id.m b/projects/ECOC_2025/dsp_run_id.m index 52a940d..ae4280b 100644 --- a/projects/ECOC_2025/dsp_run_id.m +++ b/projects/ECOC_2025/dsp_run_id.m @@ -12,13 +12,13 @@ arguments options.storage_path end -% database = DBHandler("pathToDB",[options.database_path,options.database_name],"type","sqlite"); -database = DBHandler("type","mysql"); +database = DBHandler("pathToDB",[options.database_path,options.database_name],"type","sqlite"); +% database = DBHandler("type","mysql"); filterParams = database.tables; filterParams.Configurations = struct('run_id', run_id); -selectedFields = {'Runs.run_id','Runs.tx_bits_path','Runs.tx_signal_path','Runs.tx_symbols_path','Runs.rx_sync_path','Runs.rx_raw_path',... +selectedFields = {'Runs.run_id','Runs.tx_bits_path','Runs.tx_symbols_path','Runs.rx_sync_path','Runs.rx_raw_path',... 'Configurations.db_mode','Configurations.pam_level','Configurations.bitrate','Configurations.symbolrate','Configurations.fiber_length','Configurations.wavelength','Configurations.precomp_amp','Measurements.power_rop','Configurations.v_bias',... 'Configurations.interference_attenuation'}; @@ -28,8 +28,11 @@ dataTable = dataTable(uniqueIdx,:); % Extract unique configurations for each ru fsym = dataTable.symbolrate; M = double(dataTable.pam_level); +try duob_mode = db_mode.(strrep(char(dataTable.db_mode),'"','')); - +catch +duob_mode = db_mode(dataTable.db_mode); +end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% len_tr = 4096*2; @@ -48,15 +51,14 @@ mu_ffe3 = 0.001; mu_dfe = 0.0004; mu_dc = 0.00; -% mu_ffe1 = 0; -% mu_ffe2 = 0; -% mu_ffe3 = 0; -% mu_dfe =0; -% mu_dc = 0.00; - mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3]; vnle_order=[vnle_order1,vnle_order2,vnle_order3]; +dc_buffer_len = 224; +ffe_buffer_len = 1; +smoothing_buffer_length = 4096; +smoothing_buffer_update = 224; + % Overwrite default parameters if given in options.parameters paramStruct = options.parameters; if ~isempty(paramStruct) @@ -78,13 +80,11 @@ eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0 output = struct(); vnle_pf_package = {}; -vnle_dfe_package = {}; +vnle_package = {}; dbtgt_package = {}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -Tx_signal = load([options.storage_path, char(dataTable.tx_signal_path)]); -Tx_signal = Tx_signal.Digi_sig; Tx_bits = load([options.storage_path, char(dataTable.tx_bits_path)]); Tx_bits = Tx_bits.Bits; @@ -99,21 +99,21 @@ found_sync = 0; try Scpe_load = load([options.storage_path, char(dataTable.rx_sync_path)]); Scpe_cell = Scpe_load.S; - [~,~,found_sync] = Scpe_cell{2}.tsynch("reference",Symbols,"fs_ref",dataTable.symbolrate,"debug_plots",0); + [~,~,~,found_sync] = Scpe_cell{2}.tsynch("reference",Symbols,"fs_ref",dataTable.symbolrate,"debug_plots",1); end if ~found_sync Scpe_sig_raw = load([options.storage_path, char(dataTable.rx_raw_path(1))]); Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in",Scpe_sig_raw.fs,"fs_out",2*fsym); - [~,Scpe_cell,found_sync] =Scpe_sig_resampled.tsynch("reference",Symbols,"fs_ref",dataTable.symbolrate,"debug_plots",1); + [~,Scpe_cell,~,found_sync] =Scpe_sig_resampled.tsynch("reference",Symbols,"fs_ref",dataTable.symbolrate,"debug_plots",1); end if ~found_sync if length(Symbols_mapped.signal) == sum(Symbols_mapped.signal == Symbols.signal) warning('Could not synchronize the received signal with the stored symbols!') else - [~,Scpe_cell,found_sync] =Scpe_sig_raw.tsynch("reference",Symbols_mapped,"fs_ref",dataTable.symbolrate,"debug_plots",0); + [~,Scpe_cell,~,found_sync] =Scpe_sig_raw.tsynch("reference",Symbols_mapped,"fs_ref",dataTable.symbolrate,"debug_plots",0); end if ~found_sync warning('Could not synchronize the received signal with the stored symbols!') @@ -142,11 +142,24 @@ for occ = 1:record_realizations % %%%%% VNLE + DFE %%%% if 0 - eq_vnle_dfe = EQ("Ne",vnle_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); - eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0); + eq_ = FFE_DCremoval_adaptive_mu("epochs_tr",5,"epochs_dd",3,"len_tr",4096*2,"mu_dd",... + 0.0002,"mu_tr",0,"order",25,"sps",2,"decide",0,... + "mu_dc",mu_dc,... + "dc_buffer_len",dc_buffer_len, ... + "ffe_buffer_len",ffe_buffer_len,... + "smoothing_buffer_length",smoothing_buffer_length,... + "smoothing_buffer_update",smoothing_buffer_update); - [result] = vnle(eq_vnle_dfe,M,Scpe_sig,Symbols,Tx_bits,"precode_mode",duob_mode,"showAnalysis",1,"postFFE",[]); - vnle_dfe_package{occ} = result; + eq_ = EQ("Ne",vnle_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + + result = vnle(eq_,M,Scpe_sig,Symbols,Tx_bits,"precode_mode",duob_mode,'showAnalysis',1,"postFFE",[],"eth_style_symbol_mapping",0); + vnle_package{occ} = result; + fprintf("FFE Results: %.2e\n", result.ber_vnle); + + + if options.append_to_db + database.addProcessingResult(run_id, result.resultsVNLE, result.equalizerConfigVNLE); + end end @@ -176,12 +189,12 @@ for occ = 1:record_realizations % % fprintf("BER VNLE: %.2e | %.2e; BER MLSE: %.2e | %.2e \n",vnle_pf_package{occ}.resultsVNLE.BER,vnle_pf_package{occ}.resultsVNLE.BER_precoded ,vnle_pf_package{occ}.resultsMLSE.BER,vnle_pf_package{occ}.resultsMLSE.BER_precoded); if vnle_pf - occ = 1; % or whatever your loop index is + % occ = 1; % or whatever your loop index is % Extract VNLE results for readability - vnle = vnle_pf_package{occ}.resultsVNLE; - mlse = vnle_pf_package{occ}.resultsMLSE; + vnle_result = vnle_pf_package{occ}.resultsVNLE; + mlse_result = vnle_pf_package{occ}.resultsMLSE; % Print header @@ -189,19 +202,19 @@ for occ = 1:record_realizations % VNLE Results fprintf(">> VNLE Results:\n"); - fprintf(" BER %.2e\n", vnle.BER); - fprintf(" BER (pre-code) %.2e\n", vnle.BER_precoded); - fprintf(" SNR: %.2f dB\n", vnle.SNR); - fprintf(" GMI: %.4f\n", vnle.GMI); + fprintf(" BER %.2e\n", vnle_result.BER); + fprintf(" BER (pre-code) %.2e\n", vnle_result.BER_precoded); + fprintf(" SNR: %.2f dB\n", vnle_result.SNR); + fprintf(" GMI: %.4f\n", vnle_result.GMI); fprintf(" Linerate: %.2f Gbps\n", Symbols.fs .* floor(log2(M)*10)/10 .*1e-9); - fprintf(" AIR: %.2f Gbps\n", vnle.AIR.*1e-9); + fprintf(" AIR: %.2f Gbps\n", vnle_result.AIR.*1e-9); fprintf("\n"); % MLSE Results fprintf(">> MLSE Results:\n"); - fprintf(" BER : %.2e\n", mlse.BER); - fprintf(" BER (pre-code): %.2e\n", mlse.BER_precoded); - fprintf(" Channel Alpha : %.2f\n", mlse.Alpha); + fprintf(" BER : %.2e\n", mlse_result.BER); + fprintf(" BER (pre-code): %.2e\n", mlse_result.BER_precoded); + fprintf(" Channel Alpha : %.2f\n", mlse_result.Alpha); fprintf("\n"); end @@ -243,6 +256,7 @@ end output.dataTable = dataTable; output.vnle_pf_package = vnle_pf_package; +output.vnle_package = vnle_package; output.dbtgt_package = dbtgt_package; end \ No newline at end of file diff --git a/projects/ECOC_2025/dsp_standalone.m b/projects/ECOC_2025/dsp_standalone.m index 56ba875..b28e6ff 100644 --- a/projects/ECOC_2025/dsp_standalone.m +++ b/projects/ECOC_2025/dsp_standalone.m @@ -4,9 +4,89 @@ savePath = 'Z:\2025\ECOC Silas\ecoc_2025\'; databasePath = 'C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\ECOC_2025\'; database_name = 'ecoc2025_loops.db'; -db = DBHandler("pathToDB", [databasePath, database_name],"type","mysql"); -% db = DBHandler("type","mysql"); -num_occ = 5; -run_par = false; -run_id = 1958; -[out, future] = submit_dsp(run_id, databasePath, database_name, savePath,"parallel",run_par,'max_occurences',num_occ); +db = DBHandler("type","mysql"); +% db = DBHandler("pathToDB", [databasePath, database_name],"type","sqlite"); + +filterParams = db.tables; +% filterParams.Configurations = struct('run_id', 4001); +filterParams.Configurations = struct( ... + 'symbolrate', 112e9, ... %[224,336,360,390,420,448] + 'fiber_length', 0, ... + 'db_mode', '"no_db"', ... + 'interference_attenuation', [], ... + 'interference_path_length', [], ... + 'is_mpi', 1, ... + 'pam_level', 4, ... + 'wavelength', 1310, ... + 'precomp_amp', [], ... + 'signal_attenuation', [], ... + 'v_awg', [], ... + 'v_bias', 2.65 ... + ); + +selectedFields = {'Runs.run_id','Runs.loop_id','Runs.tx_bits_path','Runs.tx_signal_path','Runs.tx_symbols_path','Runs.rx_sync_path','Runs.rx_raw_path',... + 'Configurations.db_mode','Configurations.pam_level','Configurations.bitrate','Configurations.symbolrate','Configurations.fiber_length','Configurations.wavelength','Configurations.precomp_amp','Measurements.power_rop','Configurations.v_bias',... + 'Configurations.interference_attenuation', 'Configurations.interference_path_length'}; + +[dataTable,sql_query] = db.queryDB(filterParams, selectedFields); + +% dataTable(dataTable.loop_id<200,:) = []; + +num_occ = 15; +run_par = true; +run_id = dataTable.run_id; +params = struct(); + +% slow DC tracking +params.dc_buffer_len = 224; +params.ffe_buffer_len = 1; +params.smoothing_buffer_length = 0; +params.smoothing_buffer_update = 0; +params.mu_dc = 0.005; + +futures_list = parallel.FevalFuture.empty(); +for id = 1:length(dataTable.run_id) + run_id = dataTable.run_id(id); + [out, futures_list(id)] = submit_dsp(run_id, databasePath, database_name, savePath,"parallel",run_par,'max_occurences',num_occ,'paramstruct',params); +end + +% ideal DC tracking +params.dc_buffer_len = 1; +params.ffe_buffer_len = 1; +params.smoothing_buffer_length = 0; +params.smoothing_buffer_update = 0; +params.mu_dc = 0.005; + +futures_list = parallel.FevalFuture.empty(); +for id = 1:length(dataTable.run_id) + run_id = dataTable.run_id(id); + [out, futures_list(id)] = submit_dsp(run_id, databasePath, database_name, savePath,"parallel",run_par,'max_occurences',num_occ,'paramstruct',params); +end + +% DC smoothing +params.dc_buffer_len = 1; +params.ffe_buffer_len = 1; +params.smoothing_buffer_length = 4096; +params.smoothing_buffer_update = 224; +params.mu_dc = 0.00; + +futures_list = parallel.FevalFuture.empty(); +for id = 1:length(dataTable.run_id) + run_id = dataTable.run_id(id); + [out, futures_list(id)] = submit_dsp(run_id, databasePath, database_name, savePath,"parallel",run_par,'max_occurences',num_occ,'paramstruct',params); +end + +% only FFE +params.dc_buffer_len = 1; +params.ffe_buffer_len = 1; +params.smoothing_buffer_length = 0; +params.smoothing_buffer_update = 0; +params.mu_dc = 0.00; + +futures_list = parallel.FevalFuture.empty(); +for id = 1:length(dataTable.run_id) + run_id = dataTable.run_id(id); + [out, futures_list(id)] = submit_dsp(run_id, databasePath, database_name, savePath,"parallel",run_par,'max_occurences',num_occ,'paramstruct',params); +end + + diff --git a/projects/ECOC_2025/load_signal_standalone.m b/projects/ECOC_2025/load_signal_standalone.m index 51ae8bd..e54816a 100644 --- a/projects/ECOC_2025/load_signal_standalone.m +++ b/projects/ECOC_2025/load_signal_standalone.m @@ -1,46 +1,89 @@ +db = DBHandler("type","mysql","dataBase",'labor'); + -run_id = 231; +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +M = 4; +fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'symbolrate','EQUALS', 112e9); +fp.where('Runs', 'fiber_length','EQUALS', 0); +fp.where('Runs', 'is_mpi','EQUALS', 1); +fp.where('Runs', 'interference_path_length','EQUALS', 70); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +fp.where('Runs', 'sir','EQUALS',20); -savePath = 'Z:\2025\ECOC Silas\ecoc_2025\'; -databasePath = 'C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\ECOC_2025\'; -database_name = 'ecoc2025_loops.db'; -db = DBHandler("type","mysql"); -% db = DBHandler("pathToDB", [databasePath, database_name],"type","sqlite"); -filterParams = db.tables; -filterParams.Configurations = struct('run_id', run_id); - -selectedFields = {'Runs.run_id','Runs.tx_bits_path','Runs.tx_signal_path','Runs.tx_symbols_path','Runs.rx_sync_path','Runs.rx_raw_path',... - 'Configurations.db_mode','Configurations.pam_level','Configurations.bitrate','Configurations.symbolrate','Configurations.fiber_length','Configurations.wavelength','Configurations.precomp_amp','Measurements.power_rop','Configurations.v_bias',... - 'Configurations.interference_attenuation'}; - -[dataTable,sql_query] = db.queryDB(filterParams, selectedFields); +[dataTable,sql_query] = db.queryDB(fp, db.getTableFieldNames('Runs')); [~, uniqueIdx] = unique(dataTable.run_id); % Get unique run_id indices -dataTable = dataTable(uniqueIdx,:); % Extract unique configurations for each run_id -fsym = dataTable.symbolrate; -M = double(dataTable.pam_level); -duob_mode = db_mode.(strrep(char(dataTable.db_mode),'"','')); +dataTable.SIR = -7 - round(dataTable.power_mpi_interference); -Tx_signal = load([savePath, char(dataTable.tx_signal_path)]); -Tx_signal = Tx_signal.Digi_sig; +%% +for i = 1:size(dataTable,1) + dataTable_ = dataTable(i,:); % Extract unique configurations for each run_id -Tx_bits = load([savePath, char(dataTable.tx_bits_path)]); -Tx_bits = Tx_bits.Bits; -Symbols_mapped = PAMmapper(M,0).map(Tx_bits); -Symbols_mapped.fs = dataTable.symbolrate; + fsym = dataTable_.symbolrate; + M = double(dataTable_.pam_level); + duob_mode = db_mode.(strrep(char(dataTable_.db_mode),'"','')); -Symbols = load([savePath, char(dataTable.tx_symbols_path)]); -Symbols = Symbols.Symbols; + Tx_signal = load([savePath, char(dataTable_.tx_signal_path)]); + Tx_signal = Tx_signal.Digi_sig; -Scpe_sig_raw = load([savePath, char(dataTable.rx_raw_path(1))]); -Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; -Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in",Scpe_sig_raw.fs,"fs_out",2*fsym); -[~,Scpe_cell,found_sync,~,shifts] = Scpe_sig_resampled.tsynch("reference",Symbols,"fs_ref",dataTable.symbolrate,"debug_plots",1); + Tx_bits = load([savePath, char(dataTable_.tx_bits_path)]); + Tx_bits = Tx_bits.Bits; + Symbols_mapped = PAMmapper(M,0).map(Tx_bits); + Symbols_mapped.fs = dataTable_.symbolrate; -shifts_mus = shifts./Scpe_sig_resampled.fs .*1e6; -Scpe_sig_raw.plot("displayname",['Scope Signal (Run ID: ',num2str(run_id)],"fignum",2024,"clear",1); -hold on; -xline(shifts_mus,'HandleVisibility','off'); \ No newline at end of file + Symbols = load([savePath, char(dataTable_.tx_symbols_path)]); + Symbols = Symbols.Symbols; + + Scpe_sig_raw = load([savePath, char(dataTable_.rx_raw_path(1))]); + Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; + Scpe_sig_raw.plot("displayname",['Scope Signal (Run ID: ',num2str(dataTable_.run_id)],"fignum",dataTable_.run_id,"clear",0); + + + Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in",Scpe_sig_raw.fs,"fs_out",fsym); + [~,Scpe_cell,found_sync,test,shifts] = Scpe_sig_resampled.tsynch("reference",Symbols,"fs_ref",dataTable_.symbolrate,"debug_plots",0); + + shifts_mus = shifts./Scpe_sig_resampled.fs .*1e6; + Scpe_sig_resampled = Scpe_sig_resampled.normalize("mode","rms"); + % Scpe_sig_resampled.plot("displayname",['Scope Signal (Run ID: ',num2str(dataTable_.run_id)],"fignum",dataTable_.run_id,"clear",0); + hold on; + % xline(shifts_mus,'HandleVisibility','off'); + + shifts = shifts-shifts(1)+1; + sep_sig = NaN(M,length(Scpe_sig_resampled)); + avg_sig = NaN(M,length(Scpe_sig_resampled)); + for j = 1:size(Scpe_cell,1) + + [sep_sig_,avg_sig_]=showLevelScatter(Scpe_cell{j}.resample("fs_out",Symbols.fs),Symbols,"fignum",400); + s = shifts(j); + sep_sig(:,s+1:s+length(sep_sig_)) = sep_sig_; + avg_sig(:,s+1:s+length(avg_sig_)) = avg_sig_; + + end + + disp(num2str(filterParams.Configurations.interference_path_length)); + var(sep_sig,0,2,'omitnan') +%% + xax_in_sec = ((1:length(avg_sig)) / fsym) * 1e6; + figure();hold on; + cols = cbrewer2('Paired',8); + % for p = 1:size(avg_sig,1) + % sc=scatter(xax_in_sec,sep_sig(p,:),1,'.','MarkerEdgeColor',cols((2*p)-1,:),'MarkerEdgeAlpha',0.1); + % end + for p = 1:size(avg_sig,1) + sc=plot(xax_in_sec,avg_sig(p,:),'LineWidth',1,'Color',cols((2*p),:)); + end + + yline(unique(Symbols.signal),'HandleVisibility','off'); + % xline(shifts./ fsym .*1e6,'HandleVisibility','off'); + xlabel('time in $\mu$s'); + ylabel('Normalized Amplitude'); + xlim([0 25]); + ylim([-2 2]); + + drawnow; +end \ No newline at end of file diff --git a/projects/ECOC_2025/plots_from_database/plot_mpi_trial.m b/projects/ECOC_2025/plots_from_database/plot_mpi_trial.m index 26a85c6..276c7d2 100644 --- a/projects/ECOC_2025/plots_from_database/plot_mpi_trial.m +++ b/projects/ECOC_2025/plots_from_database/plot_mpi_trial.m @@ -1,179 +1,345 @@ -local = 1; - -if local - databasePath = 'C:\Users\sioe\Documents\MATLAB\imdd_simulation\projects\ECOC_2025\'; -else - databasePath = '\\ntserver.tf.uni-kiel.de\scratch\sioe\ECOC_2025\'; -end - -database_name = 'ecoc2025_loops.db'; -database = DBHandler("pathToDB", [databasePath, database_name]); - -figure(); -plotBoundaries = 1; -plotRealizations = 1; -cols = linspecer(3); % Ensure color count matches -% cols = cols(8,:); - -filterParams = database.tables; -filterParams.Configurations = struct( ... - 'symbolrate', 112e9, ... %[224,336,360,390,420,448] - 'fiber_length', 0, ... - 'db_mode', '"no_db"', ... - 'interference_attenuation', [], ... - 'interference_path_length', 10, ... - 'is_mpi', 1, ... - 'pam_level', 4, ... - 'wavelength', 1310, ... - 'precomp_amp', [], ... - 'signal_attenuation', [], ... - 'v_awg', 0.95, ... - 'v_bias', 2.5 ... - ); - -% filterParams.EqualizerParameters.diff_precode = int32(db_mode.db_encoded); -% filterParams.EqualizerParameters.equalizer_structure = int32(equalizer_structure.vnle); - -selectedFields = {'Configurations.run_id' 'Runs.loop_id' 'Runs.date_of_run' 'Runs.rx_raw_path' 'Configurations.bitrate' 'Configurations.v_bias' 'Configurations.v_awg' 'Configurations.precomp_amp' 'Configurations.symbolrate' 'Configurations.pam_level'... - 'Configurations.db_mode' 'Configurations.rop_attenuation' 'Configurations.is_mpi' 'Configurations.interference_attenuation' 'Configurations.interference_path_length' 'Configurations.signal_attenuation' ... - 'EqualizerParameters.equalizer_structure' 'EqualizerParameters.diff_precode' 'EqualizerParameters.eq_id' 'Measurements.power_pd_in' ... - 'Measurements.power_mpi_interference' 'Measurements.power_mpi_signal' 'Results.BER' 'Results.BER_precoded' 'Results.SNR' 'Results.GMI' 'Results.Alpha' 'Results.date_of_processing'}; - -[dataTable,sql_query] = database.queryDB(filterParams, selectedFields); -dataTable.SIR = round(-6 - dataTable.power_mpi_interference); -dataTable = cleanUpTable(dataTable); - -dataTable(dataTable.eq_id==0,:) = []; - -% Filter by time -filter_by_time = 0; -if filter_by_time - startTime = datetime('2025-04-14 13:00:00', 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); - stopTime = datetime('2025-04-14 19:30:00', 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); - dataTable.date_of_run = datetime(dataTable.date_of_run, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); - dataTable.date_of_processing = datetime(dataTable.date_of_processing, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); - dataTable = dataTable(dataTable.date_of_processing > startTime, :); - dataTable = dataTable(dataTable.date_of_processing < stopTime, :); -end -% Group by smth -y_var = 'BER'; -x_var = 'SIR'; - -loop_var = 'eq_id'; -fixedVars = {'eq_id',x_var}; - -[dataTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var); - -dataTableGrpd_mean = groupIt(fixedVars, dataTable, @mean); -dataTableGrpd_min = groupIt(fixedVars, dataTable, @min); -dataTableGrpd_max = groupIt(fixedVars, dataTable, @max); - -% Create a new figure -mkr = '.'; -hold on +% dsp_options.database_type = 'mysql'; +% dsp_options.dataBase = 'labor'; +% dsp_options.storage_path = 'Z:\2025\ECOC Silas\ecoc_2025\'; +% database = DBHandler("dataBase", [dsp_options.dataBase], "type", dsp_options.database_type); +% filterParams = database.tables; +% filterParams.Runs.loop_id = 209; +% % filterParams.Configurations = struct( ... +% % 'symbolrate', 112e9, ... %[224,336,360,390,420,448] +% % 'fiber_length', 0, ... +% % 'db_mode', '"no_db"', ... +% % 'interference_attenuation', [], ... +% % 'interference_path_length', 1000, ... +% % 'is_mpi', 1, ... +% % 'pam_level', 4, ... +% % 'wavelength', 1310, ... +% % 'precomp_amp', [], ... +% % 'signal_attenuation', [], ... +% % 'v_awg', [], ... +% % 'v_bias', [] ... +% % ); +% +% % if 1 +% % % filterParams.EqualizerParameters.dc_buffer_len = 1; +% % filterParams.EqualizerParameters.ffe_buffer_len = 1; +% % filterParams.EqualizerParameters.smoothing_buffer_len = 4096; +% % filterParams.EqualizerParameters.smoothing_buffer_update = 224; +% % filterParams.EqualizerParameters.DCmu = 0; +% % end +% a = database.getTableFieldNames('Runs'); +% b = database.getTableFieldNames('Results'); +% c = database.getTableFieldNames('EqualizerParameters'); +% d = [a;b;c]; +% +% [dataTable,~] = database.queryDB(filterParams, d); +% +% selectedFields = {'Configurations.run_id' 'Runs.loop_id' 'Runs.date_of_run' 'Runs.rx_raw_path' 'Runs.bitrate' 'Runs.v_bias' 'Runs.v_awg' 'Runs.precomp_amp' 'Runs.symbolrate' 'Runs.pam_level'... +% 'Runs.db_mode' 'Runs.rop_attenuation' 'Runs.is_mpi' 'Runs.interference_attenuation' 'Runs.interference_path_length' 'Runs.signal_attenuation' ... +% 'EqualizerParameters.equalizer_structure' 'EqualizerParameters.diff_precode' 'EqualizerParameters.eq_id' 'EqualizerParameters.dc_buffer_len' 'EqualizerParameters.ffe_buffer_len' 'EqualizerParameters.smoothing_buffer_len' 'EqualizerParameters.smoothing_buffer_update' 'EqualizerParameters.DCmu' 'Measurements.power_pd_in' ... +% 'Measurements.power_mpi_interference' 'Measurements.power_mpi_signal' 'Results.BER' 'Results.BER_precoded' 'Results.EVM' 'Results.SNR' 'Results.GMI' 'Results.Alpha' 'Results.date_of_processing'}; -unique_loop_var = unique(dataTable.(loop_var)); +db = DBHandler("type","mysql","dataBase",'labor'); -for i = 1%:numel(unique_loop_var) +fp = QueryFilter(); +% fp.where('mpi_superview', 'loop_id','EQUALS', 209); +fp.where('mpi_superview', 'symbolrate','EQUALS', 112e9); +fp.where('mpi_superview', 'pam_level','EQUALS', 4); +fn = [db.getTableFieldNames('mpi_superview')]; +[dataTable,sql_query] = db.queryDB(fp,fn); - % Prepare filtered data for this loop variable - loopValue = unique_loop_var(i); - loopFiltGrpd = dataTableGrpd_mean.(loop_var) == loopValue; - if ~any(loopFiltGrpd) - continue; % Skip if no data for this loop var - end +%% - % Extract values - x_values = dataTableGrpd_mean.(x_var)(loopFiltGrpd, :); - y_mean = dataTableGrpd_mean.(y_var)(loopFiltGrpd, :); - y_min = dataTableGrpd_min.(y_var)(loopFiltGrpd, :); - y_max = dataTableGrpd_max.(y_var)(loopFiltGrpd, :); +dataTable_clean = dataTable; +dataTable_clean.SIR = -7 - round(dataTable_clean.power_mpi_interference); +dataTable_clean.NGMI = dataTable_clean.GMI ./ log2(dataTable_clean.pam_level); +dataTable_clean = cleanUpTable(dataTable_clean); - % Compute bounds: distance from mean - y_lower = y_mean - y_min; - y_upper = y_max - y_mean; - y_bounds = [y_lower, y_upper]; +dataTable_clean(dataTable_clean.BER>0.2,:) = []; - % Display name (optional) - idx = find(dataTable.(loop_var) == loopValue, 1, 'first'); - dispname = equalizer_structure(dataTable.equalizer_structure(idx)); - dispname = [char(dispname),'; ',num2str(unique(dataTable.interference_path_length)),' m']; +%% - if plotBoundaries - % Plot bounded line - [hl, hp] = boundedline(x_values, y_mean, y_bounds, ... - 'alpha', 'transparency', 0.2, ... - 'cmap', cols(i,:), ... - 'nan', 'fill', ... - 'orientation', 'vert'); - - % Style the main line: thinnest, dotted, no marker - set(hl, 'LineWidth', 1, 'LineStyle', '-', 'Marker', 'none', ... - 'Color', cols(i,:), 'DisplayName', string(dispname)); - - % Hide patch (shaded area) from legend - set(hp, 'HandleVisibility', 'off','LineStyle',':','LineWidth',0.5,'Marker','none'); - - % Add invisible scatter for DataTips - plt = scatter(x_values, y_mean, ... - 'Marker', 'o', 'MarkerEdgeColor', 'none', 'MarkerFaceColor', 'none', ... - 'HandleVisibility', 'off', 'PickableParts', 'all'); - else - plt= plot(x_values,y_mean,'LineWidth', 1, 'LineStyle', '-', 'Marker', 'none', ... - 'Color', cols(i,:), 'DisplayName', string(dispname)); - end - % Add data tips to the invisible scatter - pair_one = {'Run ID', dataTableGrpd_mean.run_id(loopFiltGrpd, :)}; - pair_two = {'Rate', dataTableGrpd_mean.bitrate(loopFiltGrpd, :) * 1e-9}; - pair_three = {'PD in', round(dataTableGrpd_mean.power_pd_in(loopFiltGrpd, :), 2)}; - addDatatips(plt, pair_one, pair_two, pair_three); - +cols = linspecer(8); % Ensure color count matches +figure() +tiledlayout(1, 4, 'TileSpacing', 'compact', 'Padding', 'compact'); +y_here = 0; +figcnt = 0; +for int_len = [0,50,300,1000] + figcnt = figcnt+1; + % figure(int_len+1); + nexttile; + hold on + mode = 4; - % Optionally: outline bounds for better visibility (optional) - % hnew = outlinebounds(hl, hp); - % Tick marks (x-axis) - xticks(round(unique(x_values),2)); + for mode = [1,2] + + hold on; + dataTable = dataTable_clean; - % Optional: scatter realizations - if plotRealizations - loopFiltSingle = dataTable.(loop_var) == loopValue; - x_single = double(dataTable.(x_var)(loopFiltSingle, :)); - y_single = double(dataTable.(y_var)(loopFiltSingle, :)); - sc = scatter(x_single, y_single, 'Marker', mkr, 'MarkerEdgeColor', cols(i, :), ... - 'LineWidth', 0.5, 'HandleVisibility', 'on', 'DisplayName', string(dispname)); + plotBoundaries = 1; + plotRealizations = 1; + cols = linspecer(8); % Ensure color count matches + + if mode == 1 + % No compensation method + dataTable = dataTable(dataTable.dc_buffer_len == 1, :); + dataTable = dataTable(dataTable.ffe_buffer_len == 1, :); + dataTable = dataTable(dataTable.smoothing_buffer_len == 0, :); + dataTable = dataTable(dataTable.smoothing_buffer_update == 0, :); + dataTable = dataTable(dataTable.DCmu == 0, :); + cols = cols(1:1+1,:); + method = 'ffe only'; + + % slow DC smoothing + elseif mode == 2 + + dataTable = dataTable(dataTable.dc_buffer_len == 1, :); + dataTable = dataTable(dataTable.ffe_buffer_len == 1, :); + dataTable = dataTable(dataTable.smoothing_buffer_len == 4096, :); + dataTable = dataTable(dataTable.smoothing_buffer_update == 224, :); + dataTable = dataTable(dataTable.DCmu == 0, :); + + cols = cols(4:4+1,:); + method = 'dc smoothing'; + + + % slow DC tracking + elseif mode == 3 + + dataTable = dataTable(dataTable.dc_buffer_len == 224, :); + dataTable = dataTable(dataTable.ffe_buffer_len == 1, :); + dataTable = dataTable(dataTable.smoothing_buffer_len == 0, :); + dataTable = dataTable(dataTable.smoothing_buffer_update == 0, :); + dataTable = dataTable(dataTable.DCmu == 0.005, :); + + cols = cols(3:3+1,:); + method = 'parallelized dc tracking'; + + elseif mode == 4 + + % ideal DC tracking + dataTable = dataTable(dataTable.dc_buffer_len == 1, :); + dataTable = dataTable(dataTable.ffe_buffer_len == 1, :); + dataTable = dataTable(dataTable.smoothing_buffer_len == 0, :); + dataTable = dataTable(dataTable.smoothing_buffer_update == 0, :); + dataTable = dataTable(dataTable.DCmu == 0.005, :); + + cols = cols(2:2+1,:); + method = 'ideal dc tracking'; + end + + % dataTable(dataTable.eq_id==0,:) = []; + dataTable(dataTable.equalizer_structure~=1,:) = []; + + % Modify values in 'interference_path_length' where the condition is met + dataTable.interference_path_length(dataTable.interference_path_length < 101 & dataTable.interference_path_length > 1) = 50; + + dataTable.interference_path_length(dataTable.interference_path_length == 1) = 0; + dataTable = dataTable(dataTable.interference_path_length == int_len, :); + + + dataTable(dataTable.run_id == 3866, :) = []; + dataTable(dataTable.run_id == 3865, :) = []; + dataTable(dataTable.run_id == 3796, :) = []; + dataTable(dataTable.run_id == 3797, :) = []; + dataTable(dataTable.run_id == 3798, :) = []; + dataTable(dataTable.run_id == 4002, :) = []; + dataTable(dataTable.run_id == 4200, :) = []; + dataTable(dataTable.run_id == 4199, :) = []; + + % 0 + % 1 + % 10 + % 15 + % 20 + % 50 + % 100 + % 300 + % 1000 + + % dataTable(dataTable.interference_path_length ~= 50, :) = []; + % dataTable = dataTable(dataTable.interference_path_length < 51, :); + + % dataTable(dataTable.loop_id<200,:) = []; + + % Filter by time + filter_by_time = 0; + if filter_by_time + startTime = datetime('2025-04-20 18:00:00', 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); + stopTime = datetime('2025-04-30 19:30:00', 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); + dataTable.date_of_run = datetime(dataTable.date_of_run, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); + dataTable.date_of_processing = datetime(dataTable.date_of_processing, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); + dataTable = dataTable(dataTable.date_of_processing > startTime, :); + dataTable = dataTable(dataTable.date_of_processing < stopTime, :); + end + + % Group by smth + y_var = 'BER'; + x_var = 'SIR'; + + loop_var = 'interference_path_length'; + fixedVars = {'equalizer_structure','interference_path_length',x_var}; + + [dataTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var); + + dataTableGrpd_mean = groupIt(fixedVars, dataTable, @mean); + dataTableGrpd_min = groupIt(fixedVars, dataTable, @min); + dataTableGrpd_max = groupIt(fixedVars, dataTable, @max); + + % dataTableGrpd_mean(dataTableGrpd_mean.nRows<50,:) = []; + % dataTableGrpd_min(dataTableGrpd_min.nRows<50,:) = []; + % dataTableGrpd_max(dataTableGrpd_max.nRows<50,:) = []; + + % Create a new figure + + hold on + + unique_loop_var = unique(dataTable.(loop_var)); + + for i = 1:numel(unique_loop_var) + + % Prepare filtered data for this loop variable + loopValue = unique_loop_var(i); + + loopFiltGrpd = dataTableGrpd_mean.(loop_var) == loopValue; + if ~any(loopFiltGrpd) + continue; % Skip if no data for this loop var + end + + % Extract values + x_values = dataTableGrpd_mean.(x_var)(loopFiltGrpd, :); + y_mean = dataTableGrpd_mean.(y_var)(loopFiltGrpd, :); + y_min = dataTableGrpd_min.(y_var)(loopFiltGrpd, :); + y_max = dataTableGrpd_max.(y_var)(loopFiltGrpd, :); + + % Compute bounds: distance from mean + y_lower = y_mean - y_min; + y_upper = y_max - y_mean; + y_bounds = [y_lower, y_upper]; + + % Display name (optional) + try + idx = find(dataTable.(loop_var) == loopValue, 1, 'first'); + % dispname = char(equalizer_structure(dataTable.equalizer_structure(idx))); + dispname = [method]; + dispname = [dispname, '/ ',num2str(unique_loop_var(i)) ,' m']; + % dispname = [dispname,'; ',num2str(unique(dataTable.interference_path_length)),' m']; + % dispname = [dispname, '/ PAM ', num2str(filterParams.Configurations.pam_level)]; + % dispname = [dispname, '/ ', num2str(filterParams.Configurations.symbolrate.*1e-9),' GBd']; + end + + if plotBoundaries + % Plot bounded line + [hl, hp] = boundedline(x_values, y_mean, y_bounds, ... + 'alpha', 'transparency', 0.1, ... + 'cmap', cols(i,:), ... + 'nan', 'fill', ... + 'orientation', 'vert'); + + % % Style the main line: thinnest, dotted, no marker + set(hl, 'LineWidth', 0.5, 'LineStyle', ':', 'Marker', 'none', ... + 'Color', cols(i,:), 'DisplayName', string(dispname)); + + plt = errorbar(x_values,y_mean,y_lower,y_upper,'LineWidth', 0.9, 'LineStyle', 'none', 'Marker', 'none','Color', cols(i,:), 'DisplayName', string(dispname),'HandleVisibility','off'); + + % Hide patch (shaded area) from legend + set(hp, 'HandleVisibility', 'off','LineStyle',':','LineWidth',0.5,'Marker','none'); + + + + % Fit a 4th-order polynomial to log10(BER) + p = polyfit(x_values, log10(y_mean), 3); % 4 is fitting order, adjust as needed + + % Evaluate the fitted polynomial + x_fit = linspace(min(x_values), max(x_values), 300); % Fine points + y_fit_log = polyval(p, x_fit); % Still in log10 domain + y_fit = 10.^y_fit_log; % Back to BER domain + + + + plot(x_fit,y_fit,'LineWidth', 1, 'LineStyle', '-', 'Marker', 'none', ... + 'Color', cols(i,:), 'DisplayName', string(dispname),'HandleVisibility','off'); + + % % Add invisible scatter for DataTips + % plt = scatter(x_values, y_mean, ... + % 'Marker', 'o', 'MarkerEdgeColor', 'none', 'MarkerFaceColor', 'none', ... + % 'HandleVisibility', 'off', 'PickableParts', 'all'); + else + + plt= plot(x_values,y_mean,'LineWidth', 1, 'LineStyle', '-', 'Marker', 'none', ... + 'Color', cols(i,:), 'DisplayName', string(dispname)); + % plt= errorbar(x_values,y_mean,y_lower,y_upper,'LineWidth', 1, 'LineStyle', '-', 'Marker', 'none','Color', cols(i,:), 'DisplayName', string(dispname)); + + end + % Add data tips to the invisible scatter + pair_one = {'Run ID', dataTableGrpd_mean.run_id(loopFiltGrpd, :)}; + pair_two = {'Rate', dataTableGrpd_mean.bitrate(loopFiltGrpd, :) * 1e-9}; + pair_three = {'PD in', round(dataTableGrpd_mean.power_pd_in(loopFiltGrpd, :), 2)}; + addDatatips(plt, pair_one, pair_two, pair_three); + + + + % Optionally: outline bounds for better visibility (optional) + % hnew = outlinebounds(hl, hp); + + % Tick marks (x-axis) + + + % Optional: scatter realizations + if plotRealizations + loopFiltSingle = dataTable.(loop_var) == loopValue; + x_single = double(dataTable.(x_var)(loopFiltSingle, :)); + y_single = double(dataTable.(y_var)(loopFiltSingle, :)); + + mkr = '.'; + sc = scatter(x_single+(mode*0.1)-0.2, y_single,15, 'Marker', mkr, 'MarkerEdgeColor', cols(i, :), ... + 'LineWidth', 0.5, 'HandleVisibility', 'off', 'DisplayName', string(dispname)); + + pair_one = {'Run ID', dataTable.run_id(loopFiltSingle, :)}; + pair_two = {'Baud', dataTable.symbolrate(loopFiltSingle, :) * 1e-9}; + pair_three = {'PD in', round(dataTable.power_pd_in(loopFiltSingle, :), 2)}; + pair_four = {'#bits', round(dataTable.numBits(loopFiltSingle, :), 2)}; + addDatatips(sc, pair_one, pair_two, pair_three,pair_four); + end + end + + % Label axes and title + legend('Interpreter', 'latex'); + xlabel(x_var); + if ~y_here + ylabel(y_var); + yticklabels = []; + + y_here = 1; + end + if int_len ~= 0 + set(gca, 'YTick', []); + end + % title([x_var, ' vs. ', y_var]); + + if string(y_var) == "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([1e-5, 0.1]); + end + + xlim([15,35]); + ylim([9e-5 0.1 ]); + xticks([13:2:35]); + + % Enable grid and beautify + grid on; + beautifyBERplot; - pair_one = {'Run ID', dataTable.run_id(loopFiltSingle, :)}; - pair_two = {'Rate', dataTable.bitrate(loopFiltSingle, :) * 1e-9}; - pair_three = {'PD in', round(dataTable.power_pd_in(loopFiltSingle, :), 2)}; - addDatatips(sc, pair_one, pair_two, pair_three); end end -% Label axes and title -legend('Interpreter', 'latex'); -xlabel(x_var); -ylabel(y_var); -title([x_var, ' vs. ', y_var]); - -if y_var == 'BER' - yline(4e-4, 'LineWidth', 1, 'LineStyle', '--', 'HandleVisibility', 'off'); - ylim([1e-5, 0.1]); -end - -xlim([15,50]); - -% Enable grid and beautify -grid on; -beautifyBERplot; - - - function resultTable = groupIt(fixedVars, dataTable, aggregationFunction) % groupIt Groups data in a table based on fixedVars and applies aggregationFunction to numeric data. @@ -243,7 +409,6 @@ resultTable.nRows = groupCount; end - function addDatatips(sc, varargin) % addDatatips Adds custom data tip rows to a scatter plot. % @@ -283,7 +448,6 @@ end end - function cleanedTable = cleanUpTable(inputTable) % cleanUpTable Cleans a MATLAB table where numbers and NaNs are stored as strings or structs. % @@ -335,7 +499,7 @@ for i = 1:numel(varNames) else % Try convert to datetime try - cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); + cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); catch % Leave as string end @@ -391,8 +555,9 @@ for groupIdx = 1:height(groupKeys) continue; end - % Detect outliers - outlierMask = isoutlier(y_values, 'quartiles'); + % Detect outliers in log space + y_log = log10(y_values); + outlierMask = isoutlier(y_log, 'quartiles',1); % or 'median', 'grubbs', etc. % If any outliers found, collect their data if any(outlierMask) diff --git a/projects/ECOC_2025/submit_dsp.m b/projects/ECOC_2025/submit_dsp.m index 6920aa0..4c78fd2 100644 --- a/projects/ECOC_2025/submit_dsp.m +++ b/projects/ECOC_2025/submit_dsp.m @@ -12,6 +12,7 @@ arguments savePath options.parallel (1,1) logical = true options.max_occurences = 1; + options.paramstruct = struct(); end if options.parallel @@ -29,7 +30,8 @@ if options.parallel "database_name", database_name, ... 'storage_path', savePath, ... 'append_to_db', 1, ... - 'max_occurences', options.max_occurences ... + 'max_occurences', options.max_occurences, ... + 'parameters', options.paramstruct ... ); output = []; @@ -46,7 +48,8 @@ else "database_name", database_name, ... 'storage_path', savePath, ... 'append_to_db', 1, ... - 'max_occurences', options.max_occurences ... + 'max_occurences', options.max_occurences,... + 'parameters', options.paramstruct ... ); future = []; % No future since it's synchronous diff --git a/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_mpi_database.m b/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_mpi_database.m index c34aa7d..f991d79 100644 --- a/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_mpi_database.m +++ b/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_mpi_database.m @@ -2,17 +2,18 @@ % 1) Find RUN ID's basePath = 'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\'; -database = DBHandler("pathToDB",[basePath,'silas_labor.db']); +% database = DBHandler("pathToDB",[basePath,'silas_labor.db']); +database = DBHandler("type",'mysql'); filterParams = database.tables; filterParams.Configurations = struct( ... - 'bitrate', [], ... %[224,336,360,390,420,448] + 'bitrate', 112e9, ... %[224,336,360,390,420,448] 'db_mode', [], ... 'fiber_length', [], ... 'interference_attenuation',[], ... - 'is_mpi', 0, ... - 'pam_level', [], ... - 'rop_attenuation', 0 ... + 'interference_path_length',300, ... + 'is_mpi', 1, ... + 'pam_level', 4 ... ); selectedFields = {'Runs.run_id',... diff --git a/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_run_id.m b/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_run_id.m index ebb7dfc..816505d 100644 --- a/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_run_id.m +++ b/projects/HighSpeedExperiment_2024/auswertung MPI/dsp_run_id.m @@ -124,7 +124,7 @@ for occ = 1:proc_occ % %%%%% VNLE + DFE %%%% if 0 - eq_vnle_dfe = EQ("Ne",vnle_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); + eq_vnle_dfe = EQ("Ne",vnle_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.001,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0); [result] = vnle(eq_vnle_dfe,M,Scpe_sig,Symbols,Tx_bits,"precode_mode",duob_mode,"showAnalysis",1,"postFFE",[]); diff --git a/projects/HighSpeedExperiment_2024/auswertung MPI/plot_from_database.m b/projects/HighSpeedExperiment_2024/auswertung MPI/plot_from_database.m index 3093427..0aa8010 100644 --- a/projects/HighSpeedExperiment_2024/auswertung MPI/plot_from_database.m +++ b/projects/HighSpeedExperiment_2024/auswertung MPI/plot_from_database.m @@ -1,26 +1,27 @@ basePath = 'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\'; -database = DBHandler("pathToDB",[basePath,'silas_labor.db']); +database = DBHandler("pathToDB",[basePath,'silas_labor_plain.db'],"type",'sqlite'); filterParams = database.tables; filterParams.Configurations = struct( ... 'bitrate', [], ... %[224,336,360,390,420,448] - 'db_mode', int32(db_mode.no_db), ... + 'db_mode', [], ... 'fiber_length', 1, ... 'interference_attenuation', [], ... 'interference_path_length', [], ... - 'is_mpi', 1, ... + 'is_mpi', 0, ... 'pam_level', 4, ... 'rop_attenuation', 0, ... 'wavelength', 1310 ... ); % filterParams.EqualizerParameters.diff_precode = int32(db_mode.no_db); - filterParams.EqualizerParameters.equalizer_structure = int32(equalizer_structure.vnle); + % filterParams.EqualizerParameters.equalizer_structure = int32(equalizer_structure.vnle); % filterParams.EqualizerParameters.DCmu = 0.005; selectedFields = {'Configurations.run_id' 'Runs.rx_raw_path' 'Configurations.bitrate' 'Configurations.symbolrate' 'Configurations.pam_level' 'Configurations.db_mode' 'Configurations.rop_attenuation' 'Configurations.is_mpi' 'Configurations.interference_attenuation' 'EqualizerParameters.equalizer_structure' 'EqualizerParameters.diff_precode' 'EqualizerParameters.eq_id' 'Measurements.power_pd_in' 'Measurements.power_mpi_interference' 'Measurements.power_mpi_signal' 'Results.BER' 'Results.SNR' 'Results.GMI' 'Results.Alpha'}; +% selectedFields = {'Configurations.run_id'}; [dataTable,sql_query] = database.queryDB(filterParams, selectedFields); diff --git a/projects/HighSpeedExperiment_2024/auswertung/only_show_precomp.m b/projects/HighSpeedExperiment_2024/auswertung/only_show_precomp.m index 9902b03..7fe1e7d 100644 --- a/projects/HighSpeedExperiment_2024/auswertung/only_show_precomp.m +++ b/projects/HighSpeedExperiment_2024/auswertung/only_show_precomp.m @@ -5,85 +5,118 @@ precomp_mode = 2; %0=do nothing ; 1= measure; 2=precomp active db_precode = 0; db_coding_approach = 0; -fsym = 224e9; +fsym = 160e9; fdac = 256e9; random_key = 0; -M = 4; +pams = [4]; -if (db_precode==1)&&(db_coding_approach==0) +cols = cbrewer2('Paired',6); +for i = 1:length(pams) + M = pams(i); + if (db_precode==1)&&(db_coding_approach==0) - if M == 4 - pulsef=1; - precomp_amp_max = -50; - elseif M == 6 - pulsef=0; - precomp_amp_max = -50; - elseif M == 8 - pulsef=0; - precomp_amp_max = -50; + if M == 4 + pulsef=1; + precomp_amp_max = -50; + fsym = 196e9; + elseif M == 6 + pulsef=0; + precomp_amp_max = -50; + fsym = 180e9; + elseif M == 8 + pulsef=0; + precomp_amp_max = -50; + fsym = 160e9; + end + + elseif (db_precode==1)&&(db_coding_approach==1) + + if M == 4 + pulsef=1; + precomp_amp_max = -38; + pulsef = 1; + elseif M == 6 + pulsef=0; + precomp_amp_max = -38; + pulsef = 1; + elseif M == 8 + pulsef=0; + precomp_amp_max = -38; + pulsef = 1; + end + + elseif (db_precode==0)&&(db_coding_approach==0) + + if M == 4 + pulsef=1; + precomp_amp_max = -37; + pulsef = 1; + fsym = 196e9; + elseif M == 6 + pulsef=0; + precomp_amp_max = -34; + pulsef = 1; + fsym = 180e9; + elseif M == 8 + pulsef=0; + precomp_amp_max = -34; + pulsef = 0; + fsym = 160e9; + end end -elseif (db_precode==1)&&(db_coding_approach==1) - if M == 4 - pulsef=1; - precomp_amp_max = -38; - pulsef = 1; - elseif M == 6 - pulsef=0; - precomp_amp_max = -38; - pulsef = 1; - elseif M == 8 - pulsef=0; - precomp_amp_max = -38; - pulsef = 1; + rcalpha = 0.05; + Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rcalpha); + + Pamsource = PAMsource(... + "fsym",fsym,"M",M,"order",19,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.2,... + "applypulseform",pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "db_precode",db_precode,"db_encode",db_coding_approach,... + "mrds_code",0,"mrds_blocklength",512); + + [Digi_sig,Symbols,Bits] = Pamsource.process(); + + Digi_sig = Digi_sig.normalize("mode","rms"); + + precomp_est = ChannelFreqResp("Nacq",2048,"Navg",100,"Ncp",63,'f_ref',Digi_sig.fs); + + % maxampdb = [-30:-3:-50,precomp_amp_max]; + maxampdb = precomp_amp_max;%sort(maxampdb); + cols_ = cbrewer2('spectral',15); + + for j = 1:length(maxampdb) + + if maxampdb(j) == precomp_amp_max + color=clr.Set1.green; + else + color=cols_(j,:); + end + + Digi_sig_pre = precomp_est.precomp(Digi_sig,'maxampdb',maxampdb(j),'loadPath',precomp_path,'fileName',precomp_fn); + + % Digi_sig_pre = Digi_sig_pre.normalize("mode","rms"); + + Digi_sig_pre = Digi_sig_pre.resample("fs_out",fdac); + + Digi_sig_pre= Digi_sig_pre.normalize("mode","rms"); + + Digi_sig_pre.spectrum("displayname","Strong Precomp","fignum",2223,"normalizeToNyquist",0,"normalizeTo0dB",0,"color",color,"linestyle",'-','addDCoffset',27); + end -elseif (db_precode==0)&&(db_coding_approach==0) + Digi_sig.spectrum("displayname","No Precomp","fignum",2223,"normalizeToNyquist",0,"normalizeTo0dB",0,"color",cols(2*i,:),"linestyle",'-','addDCoffset',27); - if M == 4 - pulsef=1; - precomp_amp_max = -37; - pulsef = 1; - elseif M == 6 - pulsef=0; - precomp_amp_max = -34; - pulsef = 1; - elseif M == 8 - pulsef=0; - precomp_amp_max = -34; - pulsef = 0; - end end +ylim([-25,10]); +xlim([0,105]); +xticks(0:20:110); +yticks(-30:10:10); -rcalpha = 0.05; -Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",rcalpha); - - -Pamsource = PAMsource(... - "fsym",fsym,"M",M,"order",19,"useprbs",1,... - "fs_out",fdac,... - "applyclipping",0,"clipfactor",1.2,... - "applypulseform",pulsef,"pulseformer",Pform,... - "randkey",random_key,... - "db_precode",db_precode,"db_encode",db_coding_approach,... - "mrds_code",0,"mrds_blocklength",512); - -[Digi_sig,Symbols,Bits] = Pamsource.process(); - -Digi_sig = Digi_sig.normalize("mode","rms"); - -Digi_sig.spectrum("displayname","No Precomp","fignum",2223,"normalizeToNyquist",0,"normalizeTo0dB",0); - -precomp_est = ChannelFreqResp("Nacq",2048,"Navg",100,"Ncp",63,'f_ref',Digi_sig.fs); - -Digi_sig = precomp_est.precomp(Digi_sig,'maxampdb',precomp_amp_max,'loadPath',precomp_path,'fileName',precomp_fn); - -Digi_sig = Digi_sig.normalize("mode","rms"); - -Digi_sig = Digi_sig.resample("fs_out",fdac); -Digi_sig= Digi_sig.normalize("mode","rms"); -Digi_sig.spectrum("displayname","Strong Precomp","fignum",2223,"normalizeToNyquist",0,"normalizeTo0dB",0); - - +fig = gcf; +pos = [536.3333 879 450 222]; +set(fig, 'Position', pos); \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/bias_evaluation.m b/projects/HighSpeedExperiment_2024/bias_evaluation.m index babd881..c042156 100644 --- a/projects/HighSpeedExperiment_2024/bias_evaluation.m +++ b/projects/HighSpeedExperiment_2024/bias_evaluation.m @@ -1,5 +1,5 @@ -filename = "C:\Users\sioe\Documents\High_Speed_Measurement_2024\bias_5km\PAMX_5km_20241025_204334_wh.mat"; +filename = "Z:\2024\sioe\High Speed Messungen Oktober\bias_5km\PAMX_5km_20241025_204334_wh.mat"; a = load(filename); wh = a.obj; @@ -26,11 +26,14 @@ clf hold on cols = cbrewer2('Set1',3); for l = 1:numel(lambda_vals) + figure() for m = 1:numel(M_vals) ber_ffe = wh.getStoValue('ber_ffe',v_bias_vals,awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(m),lambda_vals(l)); - ber = wh.getStoValue('ber_collect',v_bias_vals,awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(m),lambda_vals(l)); + ber = wh.getStoValue('ber_ffe',v_bias_vals,awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(m),lambda_vals(l)); exfo = wh.getStoValue('exfo',v_bias_vals,awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(m),lambda_vals(l)); + + lb = wh.getStoValue('exfo',v_bias_vals,awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(m),lambda_vals(l)); for e = 1:numel(exfo) laser_pow(e) = exfo{e}.cur_power; @@ -42,7 +45,7 @@ for l = 1:numel(lambda_vals) rx_logbook = wh.getStoValue('rx_logbook',v_bias_vals(1),awg_vpp_vals(1),precomp_amp_max_vals(1),rop_atten_vals(1),M_vals(1),lambda_vals(1)); - subplot(1,3,l) + hold on a = scatter(v_bias_vals,min(ber,[],2),40,'LineWidth',2,'Marker','.','DisplayName',['PAM ',num2str(M_vals(m))],'MarkerEdgeColor',cols(m,:)); title([num2str(lambda_vals(l)),'nm']) @@ -66,7 +69,7 @@ for l = 1:numel(lambda_vals) % Polynomial fit (e.g., second-order polynomial) [woutliers,n] = rmoutliers( min(ber,[],2) ); - p = polyfit( v_bias_vals(~n), log10(woutliers), 4); % Adjust order as needed + p = polyfit( v_bias_vals(~n), log10(woutliers), 3); % Adjust order as needed BER_fit = polyval(p, v_bias_vals); @@ -93,9 +96,10 @@ for l = 1:numel(lambda_vals) end end +%% -filename = "C:\Users\sioe\Documents\High_Speed_Measurement_2024\bias_testing_and_b2b\PAM4_b2b_bias_sweep_20241023_191202_wh_BB_BIAS_FINAL.mat"; +filename = "Z:\2024\sioe\High Speed Messungen Oktober\bias_testing_and_b2b\PAM4_b2b_bias_sweep_20241023_191202_wh_BB_BIAS_FINAL.mat"; a = load(filename); wh = a.obj; diff --git a/projects/HighSpeedExperiment_2024/db_auswertung/rate_vs_ber.m b/projects/HighSpeedExperiment_2024/db_auswertung/rate_vs_ber.m index e53d7c6..9e527cf 100644 --- a/projects/HighSpeedExperiment_2024/db_auswertung/rate_vs_ber.m +++ b/projects/HighSpeedExperiment_2024/db_auswertung/rate_vs_ber.m @@ -1,13 +1,13 @@ basePath = 'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\'; -database = DBHandler("pathToDB",[basePath,'silas_labor.db']); +database = DBHandler("dataBase",[basePath,'silas_labor.db'],"type",'sqlite'); filterParams = database.tables; filterParams.Configurations = struct( ... - 'bitrate', [], ... %[224,336,360,390,420,448] - 'db_mode', int32(db_mode.db_encoded), ... - 'fiber_length', 10, ... + 'bitrate', [390e9], ... %[224,336,360,390,420,448] + 'db_mode', 1, ... + 'fiber_length', 1, ... 'interference_attenuation', [], ... 'interference_path_length', [], ... 'is_mpi', 0, ... @@ -18,11 +18,11 @@ filterParams.Configurations = struct( ... % filterParams.EqualizerParameters.diff_precode = int32(db_mode.db_encoded); % filterParams.EqualizerParameters.equalizer_structure = int32(equalizer_structure.vnle); -filterParams.EqualizerParameters.DCmu = 0.00; +% filterParams.EqualizerParameters.DCmu = 0.00; selectedFields = {'Configurations.run_id' 'Runs.rx_raw_path' 'Configurations.bitrate' 'Configurations.symbolrate' 'Configurations.pam_level'... 'Configurations.db_mode' 'Configurations.rop_attenuation' 'Configurations.is_mpi' 'Configurations.interference_attenuation' ... - 'EqualizerParameters.equalizer_structure' 'EqualizerParameters.diff_precode' 'EqualizerParameters.eq_id' 'Measurements.power_pd_in' ... + 'EqualizerParameters.equalizer_structure' 'EqualizerParameters.diff_precode' 'EqualizerParameters.eq_id' 'EqualizerParameters.DCmu' 'Measurements.power_pd_in' ... 'Measurements.power_mpi_interference' 'Measurements.power_mpi_signal' 'Results.BER' 'Results.BER_precoded' 'Results.SNR' 'Results.GMI' 'Results.Alpha' 'Results.date_of_processing'}; [dataTable,sql_query] = database.queryDB(filterParams, selectedFields); diff --git a/projects/HighSpeedExperiment_2024/db_auswertung/realization_vs_ber.m b/projects/HighSpeedExperiment_2024/db_auswertung/realization_vs_ber.m index b941680..786605e 100644 --- a/projects/HighSpeedExperiment_2024/db_auswertung/realization_vs_ber.m +++ b/projects/HighSpeedExperiment_2024/db_auswertung/realization_vs_ber.m @@ -1,11 +1,11 @@ basePath = 'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\'; -database = DBHandler("pathToDB",[basePath,'silas_labor.db']); +database = DBHandler("pathToDB",[basePath,'silas_labor.db'],"type",'sqlite'); filterParams = database.tables; filterParams.Configurations = struct( ... - 'bitrate', 450e9, ... %[224,336,360,390,420,448] + 'bitrate', 420e9, ... %[224,336,360,390,420,448] 'db_mode', int32(db_mode.no_db), ... 'fiber_length', 10, ... 'interference_attenuation', [], ... diff --git a/projects/IMDD_base_system/imdd_it.m b/projects/IMDD_base_system/imdd_it.m index d2fdf3c..bd2461d 100644 --- a/projects/IMDD_base_system/imdd_it.m +++ b/projects/IMDD_base_system/imdd_it.m @@ -15,7 +15,7 @@ if 1 wh.addStorage("ber"); % wh = submit_simulations(wh,"parallel",0,"simulation_mode",0); - wh = submit_handle(@imdd_model,wh,"parallel",1); + wh = submit_handle(@imdd_model,wh,"parallel",0); end diff --git a/projects/IMDD_base_system/imdd_model.m b/projects/IMDD_base_system/imdd_model.m index c15a919..d400350 100644 --- a/projects/IMDD_base_system/imdd_model.m +++ b/projects/IMDD_base_system/imdd_model.m @@ -1,6 +1,6 @@ function [output] = imdd_model(varargin) -simulation_mode = 0; +simulation_mode = 1; %%% Change folder curFolder = pwd; @@ -134,6 +134,7 @@ if fsym_ ~= fsym fsym = fsym_; % fprintf('Adapted symbolrate to %d GBd, to match provided bitrate of %d GBit/s using PAM %d \n',fsym.*1e-9,bitrate.*1e-9, M); end + f_nyquist = fsym/2; %%% run the simulation or measurement or ... diff --git a/projects/IMDD_base_system/tx_simulation.m b/projects/IMDD_base_system/tx_simulation.m index 77a1679..1cd6216 100644 --- a/projects/IMDD_base_system/tx_simulation.m +++ b/projects/IMDD_base_system/tx_simulation.m @@ -1,6 +1,6 @@ - Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",rcalpha); + Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",rcalpha); [Digi_sig,Symbols,Tx_bits] = PAMsource(... "fsym",fsym,"M",M,"order",19,"useprbs",1,... diff --git a/projects/Lab_2024/offline_dsp_analysis/load_n_plot_transfer_characteristic.m b/projects/Lab_2024/offline_dsp_analysis/load_n_plot_transfer_characteristic.m index 17d04f5..533892a 100644 --- a/projects/Lab_2024/offline_dsp_analysis/load_n_plot_transfer_characteristic.m +++ b/projects/Lab_2024/offline_dsp_analysis/load_n_plot_transfer_characteristic.m @@ -6,3 +6,11 @@ precomp_filename = "lab_high_speed"; freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',92e9); freqresp.load('loadPath',precomp_path,'fileName',precomp_filename); freqresp.plot(); + + +freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',256e9); +precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\HighSpeedExperiment_2024\Auswertung_JLT"; +precomp_fn = "precomp_simulated.mat"; + +freqresp.load('loadPath',precomp_path,'fileName',precomp_fn); +freqresp.plot(); \ No newline at end of file diff --git a/projects/standard_system/freqresp_test.m b/projects/standard_system/freqresp_test.m index d305da3..f0cab66 100644 --- a/projects/standard_system/freqresp_test.m +++ b/projects/standard_system/freqresp_test.m @@ -1,28 +1,19 @@ -measure = 0; +measure = 1; + +freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",70,"f_ref",256e9); +% +Digi_sig = freqresp.buildOFDM(); + +Digi_sig.spectrum("fignum",1112,"displayname",['maxamp:',num2str(maxamp)]); + +Digi_sig = Filter('filtdegree',3,"f_cutoff",70e9,"fs",256e9,"filterType",filtertypes.butterworth,"active",true).process(Digi_sig); + +Digi_sig = Filter('filtdegree',3,"f_cutoff",70e9,"fs",256e9,"filterType",filtertypes.bessel_inp,"active",true).process(Digi_sig); -if measure - freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",70,"f_ref",256e9); - % - Digi_sig = freqresp.buildOFDM(); -else - [Digi_sig,Symbols,Bits] = PAMsource("fsym",fsym,"M",M,"order",18,"useprbs",0,... - "fs_out",M8199.fdac,"applyclipping",1,"clipfactor",1.7,"applypulseform",1,"pulseformer",Pform,"randkey",pn_key,"mrds_code",usemrds,"mrds_blocklength",512).process(); -end +freqresp.estimate(Digi_sig,"fileName",'','save',false); -Digi_sig.spectrum("fignum",1112,"displayname",['Signal']); +freqresp.plot() -maxamp = -1; -El_sig = freqresp.precomp(Digi_sig,"maxampdb",maxamp); - -El_sig.spectrum("fignum",1112,"displayname",['maxamp:',num2str(maxamp)]); - -El_sig = Filter('filtdegree',2,"f_cutoff",60e9,"fs",256e9,"filterType",filtertypes.butterworth,"active",true).process(El_sig); - -if measure - freqresp.estimate(El_sig,"fileName",'','save',false); -end - - - -El_sig.spectrum("fignum",1112,"displayname",['after filter; maxamp:',num2str(maxamp)]); \ No newline at end of file +a = gca; +a.YTick = [-30,-20,-10,0]; diff --git a/test/duobinary_emulation_minimal_example.m b/test/duobinary_emulation_minimal_example.m index 3f808b3..4bb75ca 100644 --- a/test/duobinary_emulation_minimal_example.m +++ b/test/duobinary_emulation_minimal_example.m @@ -1,65 +1,49 @@ -useprbs = 1; + + M = 6; -randkey = 1; -datarate = 224e9; -fsym = round(datarate / log2(M)) ; +apply_precode = 1; -db_pre = 1; +bitpattern = []; +s = RandStream('twister','Seed',1); +for i = 1:log2(M) + N = 2^(17-1); %length of prbs + bitpattern(:,i) = randi(s,[0 1], N, 1); +end -Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",0.05); +if M == 6 + bitpattern = reshape(bitpattern',[],1); + bitpattern = bitpattern(1:end-mod(length(bitpattern),5)); +end -[d,Symbols,Bits] = PAMsource(... - "fsym",fsym,"M",M,"order",17,"useprbs",1,... - "fs_out",fsym,... - "applyclipping",0,"clipfactor",1.5,... - "applypulseform",0,"pulseformer",Pform,... - "randkey",1,... - "db_precode",db_pre,"db_encode",0,... - "mrds_code",0,"mrds_blocklength",512).process(); +bits = Informationsignal(bitpattern); -%%%CHANNEL +symbols = PAMmapper(M,0).map(bits); -% s = RandStream('twister','Seed',2); -% start = 10000; -% burstwidth = 100; -% d_burst = d; -% for pos = start:start+burstwidth -% lvls = 1.5 .* PAMmapper(M,0).levels / rms(PAMmapper(M,0).levels); -% d_burst.signal(pos) = d.signal(pos)+randn(s,1,1); -% end +bits_rx = PAMmapper(M,0).demap(symbols); +[~,~,ber_direct,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); -d_resample = d.resample("fs_out",2.*fsym); +if apply_precode + symbols_tx = Duobinary().precode(symbols); +else + symbols_tx = symbols; +end -eq_ffe = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",1024,"mu_dd",0.0004,"mu_tr",0,"order",25,"sps",2,"decide",1); -d_eq = eq_ffe.process(d_resample,Symbols); +show2Dconstellation(symbols_tx,symbols_tx,"displayname",'VNLE Out','fignum',2241); + -% s = RandStream('twister','Seed',2); -% start = 10000; -% burstwidth = 100; -% d_burst = d_eq; -% for pos = start:start+burstwidth -% lvls = 1.5 .* PAMmapper(M,0).levels / rms(PAMmapper(M,0).levels); -% d_burst.signal(pos) = d_eq.signal(pos)+randn(s,1,1); -% end -% -% d_burst = PAMmapper(M,0).decide_pamlevel(d_burst); - -if db_pre +if apply_precode % Entschiedene Symbole codieren: d_DB(n) = d(n) + d(n-1) (im Fall von PAM4 7 level [0 1 2 3 4 5 6]) - d_db = Duobinary().encode(d); + symbols_db = Duobinary().encode(symbols_tx); % Entschiedene codierte Symbole decodieren: d_dec(n) = d_DB(n) mod4 - d_dec = Duobinary().decode(d_db); + symbols_rx = Duobinary().decode(symbols_db); else - d_dec = d_burst; + symbols_rx = symbols_tx; end % Vergleichen von b(n) und d_dec(n) -Rx_bits = PAMmapper(M,0).demap(d_dec); - -Tx_bits = Bits; - -[~,error_num,ber,error_pos] = calc_ber(Tx_bits.signal,Rx_bits.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +bits_rx = PAMmapper(M,0).demap(symbols_rx); +[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1); disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]); diff --git a/test/duobinary_minimal_example.m b/test/duobinary_minimal_example.m index 8866e69..c39183e 100644 --- a/test/duobinary_minimal_example.m +++ b/test/duobinary_minimal_example.m @@ -46,66 +46,55 @@ end Tx_bits = Informationsignal(bitpattern); %%%%% Duobinary %%%%%% - -precode = 1; -db_encode = 0; - close all -Symbols_tx = PAMmapper(M,0).map(Tx_bits); +Symbols_tx = PAMmapper(M,0,"eth_style",0).map(Tx_bits); Symbols_tx.fs = fsym; +precode = db_mode.db_precoded; + %%% precode -if precode - Symbols0 = Duobinary().precode(Symbols_tx); -else - Symbols0 = Symbols_tx; +switch precode + case db_mode.db_precoded + Symbols_tx = Duobinary().precode(Symbols_tx); + case db_mode.db_encoded + Symbols_tx = Duobinary().precode(Symbols_tx); + Symbols_tx = Duobinary().encode(Symbols_tx); + case db_mode.no_db + end -figure;histogram(Symbols0.signal); +for n = 10 -for n = 0:200 + Symbols_rx = Symbols_tx; - if db_encode - Symbols1 = Duobinary().encode(Symbols0); - else - Symbols1 = Symbols0; - end - - Symbols2 = Symbols1; pos = 1; if n~=0 for pos = 1:n po = randi(100); - a = Symbols2.signal(100+pos) == Symbols1.signal(100+po); + a = Symbols_rx.signal(100+pos) == Symbols_tx.signal(100+po); while a == 1 po = po+1; po = randi(100); - a = Symbols2.signal(100+pos) == Symbols1.signal(100+po); + a = Symbols_rx.signal(100+pos) == Symbols_tx.signal(100+po); end - Symbols2.signal(100+pos) = Symbols1.signal(100+po); + Symbols_rx.signal(100+pos) = Symbols_tx.signal(100+po); end end - % disp(Symbols2.signal(100:100+pos)==Symbols1.signal(100:100+pos)) + error_positions = ~(Symbols_rx.signal == Symbols_tx.signal); + error_positions = find(error_positions==1); - %%% encode + switch precode + + case db_mode.db_precoded + Symbols_rx = Duobinary().encode(Symbols_rx); + Symbols_rx = Duobinary().decode(Symbols_rx); + case db_mode.db_encoded + Symbols_rx = Duobinary().decode(Symbols_rx); - if db_encode - - % figure;histogram(Symbols2.signal); - - Symbols3 = Duobinary().decode(Symbols2); - % figure;histogram(Symbols3.signal); - % autoArrangeFigures; - elseif precode - Symbols3 = Duobinary().encode(Symbols2); - Symbols3 = Duobinary().decode(Symbols3); - % figure;histogram(Symbols3.signal); - else - Symbols3 = Symbols2; end - - Rx_bits = PAMmapper(M,0).demap(Symbols3); + + Rx_bits = PAMmapper(M,0).demap(Symbols_rx); %%%%% Check BER of Bit Sequence %%%%%% @@ -113,14 +102,10 @@ for n = 0:200 % disp(['BER: ',sprintf('%.1E',ber),sprintf(' - Num. Err: %.1d',error_num(n+1)-2),' - - PAM-',num2str(M)]); fprintf('n: %d - Num. Err: %.1d \n',n,error_num(n+1)); + end -figure() -hold on -scatter(1:length(Symbols3),Symbols3.signal,1,'.'); -scatter(error_pos,Symbols3.signal(error_pos),14,'o'); - figure(3); clf @@ -128,8 +113,8 @@ clf subplot(2,2,1) hold on title('First Bits') -stairs(Tx_bits.signal(1:100,1),'LineStyle','-','LineWidth',2,'DisplayName','Tx Bits'); -stairs(Rx_bits.signal(1:100,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':') +stairs(Tx_bits.signal(100:150,1),'LineStyle','-','LineWidth',2,'DisplayName','Tx Bits'); +stairs(Rx_bits.signal(100:150,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':') legend subplot(2,2,2) @@ -143,12 +128,12 @@ subplot(2,2,3) hold on title('First Symbols Compare') stairs(Symbols_tx.signal(1:100,1),'LineWidth',2,'DisplayName','Tx Symbols','LineStyle','-') -stairs(Symbols.signal(1:100,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols'); +stairs(Symbols_rx.signal(1:100,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols'); legend subplot(2,2,4) hold on title('Last Symbols Compare') stairs(Symbols_tx.signal(end-50:end,1),'LineWidth',2,'DisplayName','Tx Symbols','LineStyle','-') -stairs(Symbols.signal(end-50:end,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols'); +stairs(Symbols_rx.signal(end-50:end,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols'); legend \ No newline at end of file