diff --git a/Classes/02_optical/DP_Fiber.m b/Classes/02_optical/DP_Fiber.m new file mode 100644 index 0000000..a5f3dce --- /dev/null +++ b/Classes/02_optical/DP_Fiber.m @@ -0,0 +1,240 @@ +classdef DP_Fiber + % Dual-Polarization fiber propagation (CNLSE / Manakov) — class version + % Runs full setup + propagation inside process_ (no external loop). + + properties (Access=public) + % ---- User options (public) ---- + L % [km] fiber length + dz % [m] step size target (kept for compatibility; adaptive dz uses SS_* below) + lambda % [nm] reference wavelength + rng % RNG seed + gamma % [1/W/m] nonlinear coefficient + + % Optional / advanced options (match legacy names where possible) + fa % [Hz] sampling frequency + X_alpha % [dB/100km] attenuation per 100 km (per-pol, used for X and Y) + X_beta % [1..4] dispersion coefficients vector for X (Y mirrors X) + D % [ps/(nm*km)] + Ds % [ps/(nm^2*km)] dispersion slope + Dpmd % [ps/sqrt(km)] PMD coefficient + beat_len % [m] beat length (for beta(1) if X_beta is zero) + corr_len % [m] correlation length (not directly used; kept for compatibility) + manakov % 1=Manakov (legacy behavior: manakov=eq-1) + SS_dphimax % [rad] max nonlinear phase per step (adaptive SSFM) + SS_dzmax % [m] max dz (adaptive SSFM) + SS_dzmin % [m] min dz (adaptive SSFM) + n_waveplates % number of PMD waveplates + + % ---- Internal state (persistent between calls) ---- + state % struct mirroring legacy 'state' + end + + methods (Access=public) + function obj = DP_Fiber(options) + % Constructor — copies fields from 'options' and sets defaults. + + arguments + options.L + options.dz + options.lambda + options.rng = 0 + options.gamma + + % optional but recommended + options.fa + + % legacy-compatible optional params + options.X_alpha = 4.605170185988092e-05 % dB/100km + options.X_beta = [0,0,-2.16826193914149e-26,3.56839456298263e-41] + options.D = 17 % ps/(nm*km) + options.Ds = 0.06 % ps/(nm^2*km) + options.Dpmd = 3 % ps/sqrt(km) + options.beat_len = 50 % m + options.corr_len = 50 % m (kept) + options.manakov = 0 % 1=Manakov + options.SS_dphimax = 5e-3 % rad + options.SS_dzmax = 2e4 % m + options.SS_dzmin = 100 % m + options.n_waveplates = 100 + end + + % Copy provided options into properties + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + % Initialize empty state; will be built on first process_ call + obj.state = struct(); + end + + function signalclass_out = process(obj, signalclass_in) + % Public entry point: takes a signal class with .signal (2xN) + % and writes back the propagated signal. + + signalclass_in.signal = obj.process_(signalclass_in.signal, signalclass_in.fs); + + % logbook (kept as in new framework skeleton) + lbdesc = 'DP_Fiber propagation (CNLSE_plain)'; + if ismethod(signalclass_in, 'logbookentry') + signalclass_in = signalclass_in.logbookentry(lbdesc); + end + + signalclass_out = signalclass_in; + end + + function signal_out = process_(obj, signal_in,fs) + % Core processing — builds legacy 'state' and calls CNLSE_plain + % data_in: [2 x N] complex, dual-pol envelope + arguments (Input) + obj + signal_in + fs + end + + % ---- Basic checks + if isempty(signal_in) || size(signal_in,2) ~= 2 + error('DP_Fiber:Input','Expected data_in of size [2 x N].'); + end + if isempty(fs) + error('DP_Fiber:Config','Sampling frequency options.fa is required.'); + end + + obj.fa = fs; + + % ---- RNG (legacy behavior) + R = RandStream("twister","Seed",obj.rng); + + % ---- (Re)build state if empty or size-dependent fields changed + need_rebuild = ~isfield(obj.state,'nt') || (obj.state.nt ~= size(signal_in,2)); + + if need_rebuild + % Constants + c0 = 299792458; % [m/s] + + % Legacy state mapping + st = struct(); + + % High-level + st.L = obj.L * 1000; % [m] legacy expects meters + st.polNames = {'X','Y'}; + st.dt = 1/obj.fa; + st.nt = max(size(signal_in)); + st.omega = 2*pi*[(0:st.nt/2-1),(-st.nt/2:-1)]/(st.dt*st.nt); + st.lambda = obj.lambda * 1e-9; % [m] + st.D = obj.D * 1e-6; % ps/(nm*km) -> s/(nm*m) + st.Dpmd = obj.Dpmd * 1e-6; % ps/sqrt(km) -> s/sqrt(km) + st.Ds = obj.Ds * 1e3; % ps/(nm^2*km) -> s/(nm^2*m) + st.beat_len = obj.beat_len; + st.SS_dzmax = obj.SS_dzmax; + st.SS_dzmin = obj.SS_dzmin; + st.SS_dphimax= obj.SS_dphimax; + + st.chi = 0; % legacy placeholders + st.psi = 0; + + st.manakov = obj.manakov; % 1 if eq==2 (Manakov), 0 if eq==1 (CNLSE) + st.wave_plates = obj.n_waveplates; + + % Alpha (same X/Y) + st.alpha.X = obj.X_alpha; + st.alpha_lin.X = st.alpha.X/10*log(10)/1000; + st.alpha.Y = st.alpha.X; + st.alpha_lin.Y = st.alpha_lin.X; + + % Beta (dispersion) for X (Y mirrors X) + if ~any(obj.X_beta) + % Populate from (beat_len, D, Ds, lambda) like legacy + if obj.beat_len ~= 0 + b1 = pi/obj.beat_len; + else + b1 = 0; + end + b2 = 0; % PMD freq term handled elsewhere in legacy + b3 = -(st.lambda.^2/(2*pi*c0))*st.D; + b4 = (st.lambda^2/(2*pi*c0))^2*st.Ds + (2/st.lambda)*(st.lambda.^2/(2*pi*c0)).^2*st.D; + + st.beta.X = [b1, b2, b3, b4]; % keep 4 terms, legacy had 0 for beta0; b2 unused + % Note: legacy stored [b0,b1,b2,b3]? Here we mirror their usage. + % We follow their X_beta layout length=4. + else + st.beta.X = obj.X_beta; + end + st.beta.Y = st.beta.X; + + % Gamma + st.gamma = obj.gamma; + + % PMD / birefringence (legacy waveplate model) + st.corr_length = st.L / st.wave_plates; + + % DGD formula (Agrawal 1.1.18) + st.dgd = st.Dpmd * sqrt(st.L/1000); % Dpmd in s/sqrt(km), L in m -> convert: sqrt(m/1000) + % For exact legacy match, they used: state.dgd = Dpmd * sqrt(L) wisth L in meters and Dpmd already scaled. + % Using their pattern: + st.dgd = obj.Dpmd*1e-6 * sqrt(st.L); % match old: state.Dpmd already 1e-6*ps/sqrt(km); they used sqrt(L) with L [m] + + brf_multiplier = 1; + if st.Dpmd == 0 + brf_multiplier = 0; + end + + % Waveplate random parameters + st.pauli_mats.s0 = eye(2); + st.pauli_mats.s2 = [0 1; 1 0]; + st.pauli_mats.s3i = [0 1; -1 0]; + + st.brf.theta = (R.rand(st.wave_plates,1)*pi - 0.5*pi) * brf_multiplier; + st.brf.epsilon = 0.5*asin(R.rand(st.wave_plates,1)*2-1) * brf_multiplier; + + st.brf.stokes = NaN(st.wave_plates,3); + st.Ttest = zeros(1, st.wave_plates); + st.brf.matR = cell(st.wave_plates,1); + + for n=1:st.wave_plates + matRth = cos(st.brf.theta(n)) * st.pauli_mats.s0 - sin(st.brf.theta(n)) * st.pauli_mats.s3i; + matRepsilon = complex(cos(st.brf.epsilon(n))*st.pauli_mats.s0, sin(st.brf.epsilon(n))*st.pauli_mats.s2); + matR = matRth * matRepsilon; + + st.brf.matR{n} = matR; + + u1 = matR(1,1); + u2 = matR(1,2); + + st.Ttest(n) = abs(u1).^2 + abs(u2).^2; + st.brf.stokes(n,:) = [abs(u1).^2 - abs(u2).^2 , (u1) * conj(u2) + conj(u1) .* u2 , 1i*(u1) * conj(u2) - conj(u1) .* u2]; + end + + % Frequency-dependent PMD phase term (legacy form) + st.brf.db0 = (R.rand(st.wave_plates,1)*2*pi - pi) * brf_multiplier; + st.brf.db1 = sqrt(3*pi/8)*(st.dgd/obj.fa)/st.wave_plates .* st.omega; + st.brf.simdgd = 0; + % cumsum used in legacy only for debug; keep compatibility variable: + ~cumsum(st.brf.db0); % no-op to mirror legacy path + + % Bookkeeping + st.missing_dz = 0; + st.n_plates_done = 0; + st.test_plates = []; + st.test_plate_numbers = []; + st.lin_z_test = 0; + st.propagated_length = 0; + + % Cache + obj.state = st; + end + + % ---- Call the exact same CNLSE_plain as in the old framework + x_in = signal_in(:,1).'; + y_in = signal_in(:,2).'; + + [x_out, y_out, obj.state] = CNLSE_plain(x_in, y_in, obj.state); + + obj.state.propagated_length = obj.state.propagated_length + obj.state.L; + + signal_out = [x_out; y_out].'; + + + end + end +end diff --git a/Classes/02_optical/Optical_Demultiplex.m b/Classes/02_optical/Optical_Demultiplex.m new file mode 100644 index 0000000..7d7c39e --- /dev/null +++ b/Classes/02_optical/Optical_Demultiplex.m @@ -0,0 +1,149 @@ +classdef Optical_Demultiplex < handle + % Dual-Polarization optical demultiplexer + % - Input: total-field signal + % - Output: single-channel dual-pol signal objects in cell array + % + % Notes: + % Opt_sig_wdm_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1,"fs_out",Opt_sig_wdm_rx.fs/4,"fs_in",Opt_sig_wdm_rx.fs,"lambda_center",1310).process(Opt_sig_wdm_rx); + % Opt_sig_wdm_demux{1}.spectrum("fignum",1100,"displayname",'bla','normalizeTo0dB',0,'max_num_lines',4); + % Opt_sig_wdm_demux{2}.spectrum("fignum",1100,"displayname",'bla','normalizeTo0dB',0,'max_num_lines',4); + + properties (Access=public) + fs_in % [Hz] (optional; inferred from data_in.fs if omitted) + fs_out % [Hz] + lambda_center % [nm] center wavelength of the WDM grid + wavelengthplan % [nm] + attenuation = 0 % [dB] insertion loss + filtype = 1 % 1=Gaussian, 2=Rectangle, 3=No filter + B = 200e9 % [Hz] 3 dB bandwidth (Gaussian) or width (Rect) + mgauss = 3 % Gaussian order (multiple of 1/2) + + % Derived/utility + c = physconst('lightspeed') % [m/s] + end + + methods (Access=public) + function obj = Optical_Demultiplex(options) + arguments + options.fs_in = [] + options.fs_out + options.lambda_center + options.wavelengthplan + options.attenuation = 0 + options.filtype = 1 + options.B = 2.5e10 + options.mgauss = 3 + end + fn = fieldnames(options); + for n = 1:numel(fn) + try obj.(fn{n}) = options.(fn{n}); end + end + end + + function signalclasses_out = process(obj, signalclass_in) + + % ---- Infer wavelength: either given or from input total signal + if isempty(obj.wavelengthplan) + obj.wavelengthplan = signalclass_in.lambda; %meter + else + if all(500e-9 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500e-9) %check if given in nm + obj.wavelengthplan = obj.wavelengthplan.*1e-9; + end + end + + % ---- Infer input sampling rates + if isempty(obj.fs_in) + assert(isprop(signalclass_in,'fs') && ~isempty(signalclass_in.fs), ... + 'Dual_Pol_Demultiplexer: data_in.fs missing and options.fs_in not provided.'); + obj.fs_in = signalclass_in.fs; + end + + % Runs demultiplexing in one go and appends a logbook entry. + [x_envelopes,y_envelopes] = obj.process_(signalclass_in.signal); + + for n = 1:min(size(x_envelopes)) + signalclasses_out{n} = signalclass_in; + signalclasses_out{n}.signal = [x_envelopes(:,n), y_envelopes(:,n)]; + signalclasses_out{n} = signalclasses_out{n}.resample("fs_in",obj.fs_in,"fs_out",obj.fs_out); + signalclasses_out{n}.lambda = obj.wavelengthplan(n); + lbdesc = ['Opt. Demux ', num2str( obj.wavelengthplan(n)),' nm']; + signalclasses_out{n} = signalclasses_out{n}.logbookentry(lbdesc); + end + + end + + function [x_envelopes,y_envelopes] = process_(obj, signal_in) + % Core demux: + % - frequency translate target channel to baseband + % - apply optical filter H + % - resample to fs_out + arguments (Input) + obj + signal_in + end + + w = obj.fs_out ./ obj.fs_in ; + blocklen_in = length(signal_in); + blocklen_out = w*blocklen_in; + + att = 1/10^(obj.attenuation/10); + faxis=linspace( -obj.fs_in/2 , obj.fs_in/2 , blocklen_in+1 ); + faxis=ifftshift(faxis(1:end-1)); + + switch obj.filtype + case 1 + H=exp(-(faxis/obj.B).^(2*obj.mgauss)*log(2)*2^(2*obj.mgauss-1)).'; + case 2 + %all zero filter + H=zeros(1,length(faxis)).'; + %set filter = 1 inside bandwidth -B/2 <-> B/2 + H(abs(faxis)<=obj.B/2)=1; + case 3 + H = 1; + end + + f_mid = obj.c/(obj.lambda_center*1e-9); % center frequency of WDM grid [Hz] + + f_channels = obj.c./(obj.wavelengthplan) ; + + N = numel(f_channels); + + df_T = f_mid - f_channels; + + pha = mod(-2*pi*(0:blocklen_in-1).'.*df_T/obj.fs_in, 2*pi); + lo = cos(pha)+1i*sin(pha); + + % x_envelopes = ifft(fft(att.*signal_in(:,1).*lo).*H); + % y_envelopes = ifft(fft(att.*signal_in(:,2).*lo).*H); + + N = size(lo,1); + C = size(lo,2); + + x_envelopes = zeros(N, C, 'like', signal_in); + y_envelopes = zeros(N, C, 'like', signal_in); + + s1 = signal_in(:,1); + s2 = signal_in(:,2); + + % Reusable work buffers (avoid reallocations) + wrk_time = zeros(N,1, 'like', signal_in); + wrk_freq = zeros(N,1, 'like', signal_in); + + for c = 1:C + % ---- X branch ---- + wrk_time(:) = att .* s1 .* lo(:,c); % N×1 + wrk_freq(:) = fft(wrk_time); % N×1 + wrk_freq(:) = wrk_freq .* H; % N×1 + x_envelopes(:,c) = ifft(wrk_freq); % N×1 + + % ---- Y branch ---- + wrk_time(:) = att .* s2 .* lo(:,c); + wrk_freq(:) = fft(wrk_time); + wrk_freq(:) = wrk_freq .* H; + y_envelopes(:,c) = ifft(wrk_freq); + end + + end + end +end + diff --git a/Classes/02_optical/Optical_Multiplex.m b/Classes/02_optical/Optical_Multiplex.m new file mode 100644 index 0000000..1d722b0 --- /dev/null +++ b/Classes/02_optical/Optical_Multiplex.m @@ -0,0 +1,171 @@ +classdef Optical_Multiplex < handle + % Takes a cell array of signals + % returns a total field signal + % WDM spacing is given in wavelength plan OR via delta_F + + % The grid is stored in the output signal -> the demux will ideally + % look this up and use this as the demux frequencies... + + % signal_cell = {Opt_sig_1, Opt_sig_2}; + % Opt_sig_wdm = Optical_Multiplex("fs_in",Opt_sig.fs,"fs_out",4*Opt_sig.fs,... + % "lambda_center",1310,"random_key",0,"filtype",1,"B",200e9,"delta_f",400e9).process(signal_cell); + + properties(Access=public) + fs_in + fs_out + lambda_center + delta_f + random_key + attenuation + B + mgauss + filtype + + c = physconst('lightspeed') + f_center + f_T + lambda_T + df_T + end + + methods (Access=public) + function obj = Optical_Multiplex(options) + %NAME Construct an instance of this class + % Detailed explanation goes here + + arguments + options.fs_in + options.fs_out + options.lambda_center + options.B = 200e9 + options.mgauss = 3 + options.filtype = 2 + + options.delta_f = 0 + options.random_key + options.attenuation = 0; + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + + + end + + function signalclass_out = process(obj,signalclasses_in) + + % actual processing of the signal (steps 1. - 3.) + signalclass_out = obj.process_(signalclasses_in); + + % append to logbook + lbdesc = ['Opt. Mux. ']; + signalclass_out = signalclass_out.logbookentry(lbdesc); + + end + + function data_out = process_(obj,data_in) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + arguments(Input) + obj + data_in cell + end + + % assert(data_in{1}.fs == obj.fs_in,'Sampling rate'); + att = 1/10^(obj.attenuation/10); + N = numel(data_in); + w = obj.fs_out/data_in{1}.fs; + blocklen_in = length(data_in{1}); + blocklen_out = w*blocklen_in; + freqaxis = linspace(-obj.fs_out/2, obj.fs_out/2, blocklen_out+1); + obj.f_center = obj.c/(obj.lambda_center.*1e-9); + + if obj.random_key ~= 0 + res = freqaxis(2)-freqaxis(1); + R = RandStream("twister","Seed",obj.random_key); + laser_frequency_imperfection = res .* round(R.randn(N,1)*10); %in mutliples of the fft resolution, i.e. the distance between two freq. bins + else + laser_frequency_imperfection = zeros(blocklen_in,1); + end + + obj.f_T = []; + obj.df_T = []; + polrots = []; + for o = 1:N + + if obj.delta_f ~= 0 + % user defined a channel spacing in GHz. Build plan + % left and right from zero + obj.df_T(o) = (-length(data_in)/2-0.5+o) .* obj.delta_f; + obj.df_T(o) = obj.df_T(o)+ laser_frequency_imperfection(o); + + obj.f_T = [obj.f_T obj.f_center+obj.df_T(o)]; + else + %center frequencies of channels + obj.f_T = [obj.f_T obj.c/(data_in{o}.lambda)]; + obj.lambda_T = [obj.lambda_T data_in{o}.lambda]; + + %difference between mid frequency of MUX and channels + obj.df_T = [obj.df_T obj.f_center - obj.f_T(o)]; + end + + % adapt frequency shifts to match the FFT grid! Find nearest grid point + [glitch(o),pos] = min(abs( freqaxis-obj.df_T(o) )); + obj.df_T(o) = freqaxis(pos); + + polrots = [polrots, data_in{o}.polrot]; + end + + obj.lambda_T = obj.c ./ (obj.f_center-obj.df_T); + + obj.B = 200e9; %200GHz + faxis = linspace(-obj.fs_out/2,obj.fs_out/2, blocklen_out+1);%generates arow vector faxis of blocklen+1 points linearly spaced between and including -para.fs/2 and para.fs/2 + faxis = ifftshift(faxis(1:end-1)); + + switch obj.filtype + case 1 + H =exp(-(faxis/obj.B).^(2*obj.mgauss)*log(2)*2^(2*obj.mgauss-1)).'; + case 2 + H=zeros(length(faxis),1); + H(find(abs(faxis)<=obj.B/2))=1; + case 3 + H = 1; + end + + x_envelopes = NaN([blocklen_out N]); + y_envelopes = x_envelopes; + + for o = 1:N + + pha = mod(2*pi*(0:blocklen_out-1)*obj.df_T(o)/obj.fs_out,2*pi).'; + lo = cos(pha)+1i*sin(pha); + data_in_resampled = data_in{o}.resample("fs_out",obj.fs_out); + + res_env = ifft(fft(data_in_resampled.signal(:,1)).*H); + x_envelopes(:,o) = att.*res_env.*lo; + + res_env = ifft(fft(data_in_resampled.signal(:,2)).*H); + y_envelopes(:,o) = att.*res_env.*lo; + + end + + data_out = data_in_resampled; + data_out.signal = [sum(x_envelopes,2), sum(y_envelopes,2)]; + data_out.lambda = obj.lambda_T; + data_out.polrot = polrots; + end + + end + + methods (Access=private) + % Cant be seen from outside! So put all your functions here that can/ + % shall not be called from outside + + + end +end diff --git a/Classes/02_optical/Polarization_Controller.m b/Classes/02_optical/Polarization_Controller.m new file mode 100644 index 0000000..62792b9 --- /dev/null +++ b/Classes/02_optical/Polarization_Controller.m @@ -0,0 +1,92 @@ +classdef Polarization_Controller + %Input can be "normal" - output will be DP! + + properties(Access=public) + + mode + desired_angle + desired_power + + rotation_angle + rotation_matrix + + end + + methods (Access=public) + function obj = Polarization_Controller(options) + %NAME Construct an instance of this class + % Detailed explanation goes here + + arguments + options.mode polarization_control_mode = polarization_control_mode.rot_power + options.desired_angle + options.desired_power + + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + + % do more stuff + + end + + function signalclass_out = process(obj,signalclass_in) + + % actual processing of the signal (steps 1. - 3.) + [signalclass_in.signal,signalclass_in.polrot] = obj.process_(signalclass_in.signal,signalclass_in.polrot); + + % append to logbook + lbdesc = ['Logbookentry']; + signalclass_in = signalclass_in.logbookentry(lbdesc); + + % write to output + signalclass_out = signalclass_in; + + end + + function [data_out, polrot_out] = process_(obj,data_in,polrot_in) + % Rotate polarization of am opt signal + + arguments(Input) + obj + data_in double + polrot_in double + end + + + if obj.mode ~= polarization_control_mode.deactivate + + switch obj.mode + case polarization_control_mode.random + obj.rotation_angle = 2*pi*rand ; + + case polarization_control_mode.rot_angle + obj.rotation_angle = obj.desired_angle*pi/180 ; + + case polarization_control_mode.rot_power + obj.rotation_angle = -polrot_in + acos(sqrt(obj.desired_power/100)) ; + end + + obj.rotation_matrix = [cos(obj.rotation_angle) -sin(obj.rotation_angle) ; sin(obj.rotation_angle) cos(obj.rotation_angle)].' ; + + if min(size(data_in)) == 1 + data_in = reshape(data_in,[],1); + data_in = [data_in, zeros(length(data_in),1)]; + end + + data_out = data_in * obj.rotation_matrix; + + polrot_out = polrot_in + obj.rotation_angle ; + + end + + + end + end +end diff --git a/Classes/02_optical/dp_fiber_lib/CNLSE.m b/Classes/02_optical/dp_fiber_lib/CNLSE.m new file mode 100644 index 0000000..d2773dc --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/CNLSE.m @@ -0,0 +1,135 @@ + +function [opt_out_struct,state] = CNLSE(opt_in_struct,state) + + % init transfer functions h.X and h.Y + h = struct('X',0,'Y',0); + state.common_beta=struct('X',0,'Y',0); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % pre calculations + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % calculate transfer function and rotate coordines for both + % polarizations + + for n=1:2 + % get current polarization name and contrary one + curPol = state.polNames{n}; + + % extend linear transfer function depending on beta values for the + % current polarization + for n_beta = 1:length(state.beta.(curPol)) +% h.(curPol) = h.(curPol) - 1j*state.beta.(curPol)(n_beta)*(state.omega).^(n_beta-1)/factorial(n_beta-1); +% if n_beta ~= 2 + state.common_beta.(curPol) = state.common_beta.(curPol) + state.beta.(curPol)(n_beta) * (state.omega).^(n_beta-1) / factorial(n_beta-1); +% end + end + + opt_out_struct.(curPol)=opt_in_struct.(curPol).envelope; + end + + state.h=h; + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Splitstep method + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +% state.SS_dzs = zeros(1,state.max_nonlin_its); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Split Step Method + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % get nonlinear step size + [state.dz] = getNLstepsize(state,opt_out_struct); + + state.n_step = 0; + state.z_prop = 0; + state.test_dz = []; + state.powers = []; + + while state.z_prop < state.L + + + + + if state.z_prop + state.dz > state.L + state.dz = state.L - state.z_prop; + end + + + % lin conv + % opt_out_struct = [ opt_out_struct 0 0 0 0 0 ]; + % opt_out_struct + %%%%%%%%%%%%% + % STEP + % update step number + + state.n_step=state.n_step+1; + state.dzs(state.n_step)=state.dz; + + % half linear step + [opt_out_struct,state] = lin_step(state,opt_out_struct,state.dz/2); + + + % complete nonlinear step + [opt_out_struct,state] = nl_step(state,opt_out_struct,state.dz); + + % half linear step + [opt_out_struct,state] = lin_step(state,opt_out_struct,state.dz/2); + + %%%%%%%%%%%%% + % prepare next STEP + + % overlap(n_step+1,:) = opt_out_struct(M+1:end); + % opt_out_struct = opt_out_struct(1:M); + + + % get nonlinear step size + [state.dz] = getNLstepsize(state,opt_out_struct); + + end + + +% figure(88);clf;subplot(2,1,1);stem(state.test_plates);subplot(2,1,1); hold all;stem(-1000*state.test_plate_numbers);subplot(2,1,2);stem(state.dzs) + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Post Calculations + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% opt_out_struct.X.envelope = ( cos(state.psi)*cos(state.chi) + 1j*sin(state.psi)*sin(state.chi))*opt_out_struct.X + ... +% (-sin(state.psi)*cos(state.chi) - 1j*cos(state.psi)*sin(state.chi))*opt_out_struct.Y; +% + buffer.X.envelope = opt_out_struct.X; + buffer.X.type = opt_in_struct.X.type; + buffer.X.wavelength = opt_in_struct.X.wavelength; + if isfield(buffer.X,'Nase') + buffer.X.Nase = opt_in_struct.X.Nase; + else + buffer.X.Nase = 0; + end + + opt_out_struct.X =[]; + opt_out_struct.X.envelope = buffer.X.envelope; + opt_out_struct.X.type = buffer.X.type; + opt_out_struct.X.wavelength = buffer.X.wavelength; + opt_out_struct.X.Nase = buffer.X.Nase; + +% +% opt_out_struct.Y.envelope = ( sin(state.psi)*cos(state.chi) - 1j*cos(state.psi)*sin(state.chi))*opt_out_struct.X.envelope + ... +% ( cos(state.psi)*cos(state.chi) - 1j*sin(state.psi)*sin(state.chi))*opt_out_struct.Y; +% + buffer.Y.envelope = opt_out_struct.Y; + buffer.Y.type = opt_in_struct.Y.type; + buffer.Y.wavelength = opt_in_struct.Y.wavelength; + if isfield(buffer.Y,'Nase') + buffer.Y.Nase = opt_in_struct.Y.Nase; + else + buffer.Y.Nase = 0; + end + + opt_out_struct.Y =[]; + opt_out_struct.Y.envelope = buffer.Y.envelope; + opt_out_struct.Y.type = buffer.Y.type; + opt_out_struct.Y.wavelength = buffer.Y.wavelength; + opt_out_struct.Y.Nase = buffer.Y.Nase; + +% figure(100+loop);plot([real(opt_out_struct.X.envelope);real(opt_out_struct.Y.envelope)].'); +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m b/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m new file mode 100644 index 0000000..cfa7294 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m @@ -0,0 +1,120 @@ + +function [opt_out_x,opt_out_y,state] = CNLSE_plain(opt_in_x,opt_in_y,state) + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % pre calculations + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + state.common_beta=struct('X',0,'Y',0); + + for n=1:2 + % get current polarization name and contrary one + curPol = state.polNames{n}; + + % extend linear transfer function depending on beta values for the current polarization + % Was ist der Sinn dieser komischen beta notation? zB. state.beta.X = [0.3142 0 -9.1105e-28 5.1068e-41] + for n_beta = 1:length(state.beta.(curPol)) + state.common_beta.(curPol) = state.common_beta.(curPol) + state.beta.(curPol)(n_beta) * (state.omega).^(n_beta-1) / factorial(n_beta-1); + end + + %opt_out_struct.(curPol)=opt_in_struct.(curPol).envelope; + end + + beta_const = state.beta.('X')(1); + beta_1 = state.beta.('X')(2); + beta_2 = state.beta.('X')(3); + beta_3 = state.beta.('X')(4); + deltaomega = state.omega; + beta_x = beta_const + beta_1 * deltaomega + 1/2 * beta_2 * deltaomega.^2 + 1/6 *beta_3 * deltaomega.^3; + +% opt_in_x = gpuArray(opt_in_x); +% opt_in_y = gpuArray(opt_in_y); + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Split Step Method + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% [opt_out_x,opt_out_y] = split_step_loop(state.L,opt_in_x,opt_in_y,state.gamma,state.SS_dzmin,state.SS_dzmax,state.SS_dphimax,state.alpha_lin,... +% state.lin_z_test,state.corr_length,state.n_plates_done,state.missing_dz,state.brf,state.common_beta,.... +% state.chi,state.manakov,state.beat_len); + +% [opt_out_x,opt_out_y] = split_step_loop_mex(state.L,opt_in_x,opt_in_y,state.gamma,state.SS_dzmin,state.SS_dzmax,state.SS_dphimax,state.alpha_lin,... +% state.lin_z_test,state.corr_length,state.n_plates_done,state.missing_dz,state.brf,state.common_beta,.... +% state.chi,state.manakov,state.beat_len); + + % get nonlinear step size + [state.dz] = getNLstepsize(opt_in_x,opt_in_y,state.gamma,state.SS_dzmin,state.SS_dzmax,state.SS_dphimax,state.alpha_lin); + %[state.dz] = getNLstepsize_original(state,opt_out_struct); + + state.n_step = 0; + state.z_prop = 0; + state.test_dz = []; + state.powers = []; + + tic + + while state.z_prop < state.L + + % reduce step length (dz) if we are to overshoot the fiber length + % (L) in the next step + if state.z_prop + state.dz > state.L + state.dz = state.L - state.z_prop; + end + + % update step number (n) + state.n_step=state.n_step+1; + + % append current step length to logbook (dzs) + state.dzs(state.n_step)=state.dz; + + + % half linear step + [opt_in_x,opt_in_y,state.z_prop,state.lin_z_test,... + state.corr_length,state.n_plates_done,state.missing_dz,state.n_step,... + state.test_plates,state.test_plate_numbers,state.brf,state.common_beta.X,... + state.common_beta.Y,state.alpha_lin.X,state.alpha_lin.X]... + = lin_step(... + opt_in_x,opt_in_y,state.z_prop,state.lin_z_test,... + state.dz/2,state.corr_length,state.n_plates_done,state.missing_dz,state.n_step,... + state.test_plates,state.test_plate_numbers,state.brf,state.common_beta.X,... + state.common_beta.Y,state.alpha_lin.X,state.alpha_lin.X); + + % complete nonlinear step + + [opt_in_x,opt_in_y] = nl_step(opt_in_x,opt_in_y, state.dz, state.gamma, state.chi, state.manakov, state.beat_len ,state.alpha_lin.X, state.alpha_lin.Y); + + % half linear step + [opt_in_x,opt_in_y,state.z_prop,state.lin_z_test,... + state.corr_length,state.n_plates_done,state.missing_dz,state.n_step,... + state.test_plates,state.test_plate_numbers,state.brf,state.common_beta.X,... + state.common_beta.Y,state.alpha_lin.X,state.alpha_lin.X]... + = lin_step... + (opt_in_x,opt_in_y,state.z_prop,state.lin_z_test,... + state.dz/2,state.corr_length,state.n_plates_done,state.missing_dz,state.n_step,... + state.test_plates,state.test_plate_numbers,state.brf,state.common_beta.X,... + state.common_beta.Y,state.alpha_lin.X,state.alpha_lin.X); + + % get nonlinear step size + [state.dz] = getNLstepsize(opt_in_x,opt_in_y,state.gamma,state.SS_dzmin,state.SS_dzmax,state.SS_dphimax,state.alpha_lin); + %[state.dz] = getNLstepsize_original(state,opt_out_struct); + + + + end + + toc + + + + + + opt_out_x = (opt_in_x); + opt_out_y = (opt_in_y); + +% opt_out_x = gather(opt_in_x); +% opt_out_y = gather(opt_in_y); + +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/getNLstepsize.m b/Classes/02_optical/dp_fiber_lib/getNLstepsize.m new file mode 100644 index 0000000..a70edb9 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/getNLstepsize.m @@ -0,0 +1,25 @@ + +function [rDZ] = getNLstepsize(ux,uy,gamma,dzmin,dzmax,dphimax,alpha_lin) + + + maxPow = max(gamma.*max(real(ux).^2+imag(ux).^2+real(uy).^2+imag(uy).^2)); + + Leff = dphimax/maxPow; + alpha_lin = max([alpha_lin.X alpha_lin.Y]); + nl_att_len_ratio = alpha_lin*Leff; + + if nl_att_len_ratio >= 1 + rDZ = dzmax; + else + if alpha_lin == 0 + step = Leff; + else + %effective length? + step = -1/alpha_lin*log(1-nl_att_len_ratio); + end + + rDZ = min([step dzmax]); + rDZ = max([rDZ dzmin]); + end + +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/getNLstepsize_original.m b/Classes/02_optical/dp_fiber_lib/getNLstepsize_original.m new file mode 100644 index 0000000..93a3d63 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/getNLstepsize_original.m @@ -0,0 +1,27 @@ + +function [rDZ] = getNLstepsize_original(state,aOpt) + + ux = aOpt.X; + uy = aOpt.Y; + + maxPow = max(state.gamma.*max(real(ux).^2+imag(ux).^2+real(uy).^2+imag(uy).^2)); + + Leff = state.SS_dphimax/maxPow; + alpha_lin = max([state.alpha_lin.X state.alpha_lin.Y]); + nl_att_len_ratio = alpha_lin*Leff; + + if nl_att_len_ratio >= 1 + rDZ = state.SS_dzmax; + else + if alpha_lin == 0 + step = Leff; + else + %effective length? + step = -1/alpha_lin*log(1-nl_att_len_ratio); + end + + rDZ = min([step state.SS_dzmax]); + rDZ = max([rDZ state.SS_dzmin]); + end + +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/lin_step.m b/Classes/02_optical/dp_fiber_lib/lin_step.m new file mode 100644 index 0000000..eea8a1e --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/lin_step.m @@ -0,0 +1,125 @@ + +%function [rOpt,state] = lin_step(state,aOpt,aStepSize) + +function [rOpt_x,rOpt_y,z_prop,lin_z_test,... + corr_length,n_plates_done,missing_dz,n_step,test_plates,... + test_plate_numbers,brf,common_beta_x,common_beta_y,alpha_lin_x,alpha_lin_y]... + = lin_step(... + opt_x,opt_y,z_prop,lin_z_test,aStepSize,corr_length,... + n_plates_done,missing_dz,n_step,test_plates,test_plate_numbers,... + brf,common_beta_x,common_beta_y,alpha_lin_x,alpha_lin_y) + +%%%%%% 1) Update and Check Distances etc. %%%%%% + +% update propgated distance z_prop +z_prop = z_prop + aStepSize; + +% calculate the number of plates needed for the so far propagated fiber length +n_plates = ceil(z_prop/corr_length); + +% subtract the number of plates which were already processed +n_plates_left = n_plates - n_plates_done; + +% compute last plate size ( if it fits, it should be 0) +if missing_dz > aStepSize + + last_plate = aStepSize; + missing_dz = missing_dz-aStepSize; + plate_sizes = last_plate; + plate_numbers = n_plates; + +else + + last_plate = aStepSize - missing_dz - (n_plates_left-1)*corr_length; + + if missing_dz == 0 + missing_dz = []; + end + + %build vector of plate lengths with missing plate part from prev. + %iterartion , then some normal plates and finally a fraction of a plate + %to fit into the step length + plate_sizes = [missing_dz corr_length*ones(1,n_plates_left-1) last_plate]; + + if n_plates_done == 0 + plate_numbers =[(n_plates_done+1):(n_plates-1) n_plates]; + else + plate_numbers = [n_plates_done (n_plates_done+1):(n_plates-1) n_plates]; % not wrking yet + end + + %remember for next step + missing_dz = corr_length - last_plate; + +end + +plate_steps = repmat(n_step,1,length(plate_sizes)); + +% +%figure;stem(plate_sizes); +test_plates = [test_plates,plate_sizes]; + +test_plate_numbers = [test_plate_numbers, plate_numbers]; + +%%%%%% 2) Apply Waveplate Model %%%%%% + +% transfer optical envelope to frequency domain for effective convolution with transfer function h +opt_x=fft(opt_x); +opt_y=fft(opt_y); + +% db1 = gpuArray(brf.db1); +% db0 = gpuArray(brf.db0); +% common_beta_x = gpuArray(common_beta_x); +% common_beta_y = gpuArray(common_beta_y); + +db1 = (brf.db1); +db0 = (brf.db0); +common_beta_x = (common_beta_x); +common_beta_y = (common_beta_y); + +% process every waveplate with given sizes in plate_sizes +for n=1:length(plate_sizes) + dz = plate_sizes(n); + + % figure(87);subplot(2,1,1);plot(real(x(900:1150)));subplot(2,1,2);plot(real(y(900:1150))); + % MOV1=[MOV1 getframe(87)]; + + % extract rotation matrix from pre calculated matrices + matR = brf.matR{plate_numbers(n)}; + + % transform to eigenvalue of of fiber segment + tOpt.X = conj(matR(1,1))*opt_x + conj(matR(2,1))*opt_y; + tOpt.Y = conj(matR(1,2))*opt_x + conj(matR(2,2))*opt_y; + + % calculate statistical delta beta for pmd + delta_beta = 0.5*(db1+db0(n))/corr_length; + % build transfer function with delta beta + %common.beta = beta1+beta2*omega^2 + + %accumulate delta beta for log... + brf.simdgd = brf.simdgd + (db1(length(db1)/2+1)+db0(n))/corr_length; + + h.X = exp(-1j*(common_beta_x-delta_beta)*dz); + h.Y = exp(-1j*(common_beta_y+delta_beta)*dz); + % delta_beta has to be added to the transfer function + + % process with transfer function + tOpt.X = h.X.*tOpt.X ; + tOpt.Y = h.Y.*tOpt.Y ; + + % rotate back + opt_x = matR(1,1)*tOpt.X + matR(1,2)*tOpt.Y; + opt_y = matR(2,1)*tOpt.X + matR(2,2)*tOpt.Y; + +end + +lin_z_test = lin_z_test + sum(plate_sizes,2); + +%update the number of processed plates so far +n_plates_done = n_plates_done + n_plates_left; + +% attanuate the signal each linear state with alpha +% ( 0.2dB = 4.6052e-05 ) +rOpt_x=ifft(exp(-alpha_lin_x*aStepSize/2).*opt_x); % /2 not sure why (have to find it in formulas) +rOpt_y=ifft(exp(-alpha_lin_y*aStepSize/2).*opt_y); % but not relevant for now + +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/lin_step_original.m b/Classes/02_optical/dp_fiber_lib/lin_step_original.m new file mode 100644 index 0000000..dac5b32 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/lin_step_original.m @@ -0,0 +1,106 @@ + +function [rOpt,state] = lin_step_original(state,aOpt,aStepSize) + + + % update propgated distance z_prop + state.z_prop = state.z_prop + aStepSize; + +% if state.synchronous_plates % not waveplate model (just rotation with dz) +% state.plate_sizes = aStepSize; +% +% else + % calculate the number of plates needed for the so far propagated fiber + % length + state.n_plates = ceil(state.z_prop/state.corr_length); + + % subtract the number of plates which were already be processed + state.n_plates_left = state.n_plates - state.n_plates_done; + + % compute last plate size ( if it fits, it should be 0) + if state.missing_dz > aStepSize + + state.last_plate = aStepSize; + state.missing_dz = state.missing_dz-aStepSize; + state.plate_sizes = state.last_plate; + state.plate_numbers = state.n_plates; + + else + + state.last_plate = aStepSize - state.missing_dz - (state.n_plates_left-1)*state.corr_length; + + if state.missing_dz == 0 + state.missing_dz = []; + end + + state.plate_sizes = [state.missing_dz state.corr_length*ones(1,state.n_plates_left-1) state.last_plate]; + + if state.n_plates_done == 0 + state.plate_numbers =[(state.n_plates_done+1):(state.n_plates-1) state.n_plates]; + else + state.plate_numbers = [state.n_plates_done (state.n_plates_done+1):(state.n_plates-1) state.n_plates]; % not wrking yet + end + + state.missing_dz = state.corr_length - state.last_plate; + end + + state.plate_steps = repmat(state.n_step,1,length(state.plate_sizes)); + + % figure;stem(state.plate_sizes); + state.test_plates = [state.test_plates,state.plate_sizes]; + state.test_plate_numbers = [state.test_plate_numbers, state.plate_numbers]; +% end + + % transfer optical envelope to frequency domain for effective + % convolution with transfer function h + aOpt.X=fft(aOpt.X); + aOpt.Y=fft(aOpt.Y); + + % process every waveplate with given sizes in state.plate_sizes + for n=1:length(state.plate_sizes) + dz = state.plate_sizes(n); + +% figure(87);subplot(2,1,1);plot(real(x(900:1150)));subplot(2,1,2);plot(real(y(900:1150))); +% state.MOV1=[state.MOV1 getframe(87)]; + + % extract rotation matrix from pre calculated matrices + matR = state.brf.matR{state.plate_numbers(n)}; + + % transform to eigenvalue of of fiber segment + tOpt.X = conj(matR(1,1))*aOpt.X + conj(matR(2,1))*aOpt.Y; + tOpt.Y = conj(matR(1,2))*aOpt.X + conj(matR(2,2))*aOpt.Y; + + % calculate statistical delta beta for pmd + delta_beta = 0.5*(state.brf.db1+state.brf.db0(n))/state.corr_length; + % db1 = sqrt(3*pi/8)*(para.dgd/para.fa)/state.wave_plates.*state.omega; +% delta_beta = 0.5*(state.brf.db0(n))/state.corr_length; + + % build transfer function with delta beta + % common.beta = beta1+beta2*omega^2 + % delta beta + h.X = exp(-1j*(state.common_beta.X-delta_beta)*dz); + h.Y = exp(-1j*(state.common_beta.Y+delta_beta)*dz); + % delta_beta has to be added to the transfer function + + % process with transfer function + tOpt.X = h.X.*tOpt.X ; + tOpt.Y = h.Y.*tOpt.Y ; + + % rotate back + aOpt.X = matR(1,1)*tOpt.X + matR(1,2)*tOpt.Y; + aOpt.Y = matR(2,1)*tOpt.X + matR(2,2)*tOpt.Y; + + end + + state.lin_z_test = state.lin_z_test + sum(state.plate_sizes,2); + + %update the number of processed plates so far + state.n_plates_done = state.n_plates_done + state.n_plates_left; + + + % attanuate the signal each linear state with alpha + % ( 0.2dB = 4.6052e-05 ) + rOpt.X=ifft(exp(-state.alpha_lin.X*aStepSize/2).*aOpt.X); % /2 not sure why (have to find it in formulas) + rOpt.Y=ifft(exp(-state.alpha_lin.Y*aStepSize/2).*aOpt.Y); % but not relevant for now + + +end \ No newline at end of file diff --git a/Classes/02_optical/dp_fiber_lib/nl_step.m b/Classes/02_optical/dp_fiber_lib/nl_step.m new file mode 100644 index 0000000..800c502 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/nl_step.m @@ -0,0 +1,39 @@ + +%function [rOpt,state] = nl_step(state,aOpt,aDz) + +function [rOpt_x,rOpt_y] = nl_step(opt_x,opt_y, dz, gamma, chi, use_manakov, beatlength, alpha_lin_x, alpha_lin_y) + + if ~use_manakov % CNLSE + + rOpt_x = opt_x .* exp( (-1j*(1/3)*gamma*dz).* ... + ( (2 + cos(2*chi)^2)*(abs(opt_x).^2) + ... + (2+2*sin(2*chi)^2)*(abs(opt_y).^2) ) ); + + rOpt_y = opt_y .* exp( (-1j*(1/3)*gamma*dz).* ... + ( (2 + cos(2*chi)^2)*(abs(opt_y).^2) + ... + (2+2*sin(2*chi)^2)*(abs(opt_x).^2) ) ); + +% A_x = opt_x; +% A_y = opt_y; +% +% rOpt_x = 1i* gamma * (abs(A_x).^2 + (2/3 .* abs(A_y).^2) ) .* A_x + ((1i * gamma / 3) * conj(A_x).*(A_y.^2) * exp(-2i * dz * 2*pi / beatlength )); +% rOpt_y = 1i* gamma * (abs(A_y).^2 + (2/3 .* abs(A_x).^2) ) .* A_y + ((1i * gamma / 3) * conj(A_y).*(A_x.^2) * exp(-2i * dz * 2*pi / beatlength )); + + + else + % estimate effective length of dz (ref?) + if (alpha_lin_x == 0) && (alpha_lin_y == 0) + Leff = dz; + else + Leff = (1-exp(-alpha_lin_x*dz))/alpha_lin_x; + end + + %compute power + power = real(opt_x).^2+imag(opt_x).^2+real(opt_y).^2+imag(opt_y).^2; +% power= abs(opt_x).^2+abs(opt_y).^2; +% powers = [powers;power]; + Hnl = exp( -1j*8/9*gamma*power*Leff); + rOpt_x = opt_x .* Hnl; + rOpt_y = opt_y .* Hnl; + end +end diff --git a/Classes/02_optical/dp_fiber_lib/nl_step_original.m b/Classes/02_optical/dp_fiber_lib/nl_step_original.m new file mode 100644 index 0000000..4fb9593 --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/nl_step_original.m @@ -0,0 +1,30 @@ + +function [rOpt,state] = nl_step(state,aOpt,aDz) + + if ~state.manakov % CNLSE + + rOpt.X = aOpt.X .* exp( (-1j*(1/3)*state.gamma*aDz).* ... + ( (2 + cos(2*state.chi)^2)*(abs(aOpt.X).^2) + ... + (2+2*sin(2*state.chi)^2)*(abs(aOpt.Y).^2) ) ); + + rOpt.Y = aOpt.Y .* exp( (-1j*(1/3)*state.gamma*aDz).* ... + ( (2 + cos(2*state.chi)^2)*(abs(aOpt.Y).^2) + ... + (2+2*sin(2*state.chi)^2)*(abs(aOpt.X).^2) ) ); + + else + % estimate effective length of dz (ref?) + if (state.alpha_lin.X == 0) && (state.alpha_lin.Y == 0) + Leff = aDz; + else + Leff = (1-exp(-state.alpha_lin.X*aDz))/state.alpha_lin.X; + end + + %compute power + power = real(aOpt.X).^2+imag(aOpt.X).^2+real(aOpt.Y).^2+imag(aOpt.Y).^2; +% power= abs(aOpt.X).^2+abs(aOpt.Y).^2; +% state.powers = [state.powers;power]; + Hnl = exp( -1j*8/9*state.gamma*power*Leff); + rOpt.X = aOpt.X .* Hnl; + rOpt.Y = aOpt.Y .* Hnl; + end +end diff --git a/Classes/02_optical/dp_fiber_lib/split_step_loop.m b/Classes/02_optical/dp_fiber_lib/split_step_loop.m new file mode 100644 index 0000000..e74f5bf --- /dev/null +++ b/Classes/02_optical/dp_fiber_lib/split_step_loop.m @@ -0,0 +1,93 @@ +function [opt_x,opt_y] = split_step_loop(L,opt_x,opt_y,gamma,SS_dzmin,SS_dzmax,SS_dphimax,alpha_lin,... + lin_z_test,corr_length,n_plates_done,missing_dz,brf,common_beta,... + chi,manakov,beat_len) + +%SPLIT_STEP_LOOP Summary of this function goes here +% Detailed explanation goes here + %Optical Input +% opt_x; +% opt_y; +% +% %required for loop condition +% z_prop = 0; +% L; +% +% %required for NLstepsize +% gamma; +% SS_dzmin; +% SS_dzmax; +% SS_dphimax; +% alpha_lin; +% +% %required for lin_step +% z_prop; +% lin_z_test; +% corr_length; +% n_plates_done; +% missing_dz; +% n_step = 0; +% brf; +% common_beta.X; +% common_beta.Y; +% alpha_lin.X; +% alpha_lin.X; +% +% %required fr nonlin step +% chi; +% manakov; +% beat_len ; +% alpha_lin.X; +% alpha_lin.Y; + + + % get nonlinear step size + [dz] = getNLstepsize(opt_x,opt_y,gamma,SS_dzmin,SS_dzmax,SS_dphimax,alpha_lin); + + n_step = 0; + z_prop = 0; + + while z_prop < L + + % reduce step length (dz) if we are to overshoot the fiber length + % (L) in the next step + if z_prop + dz > L + dz = L - z_prop; + end + + % update step number (n) + n_step=n_step+1; + + % half linear step + [opt_x,opt_y,z_prop,lin_z_test,... + corr_length,n_plates_done,missing_dz,n_step,... + brf,common_beta.X,... + common_beta.Y,alpha_lin.X,alpha_lin.X]... + = lin_step(... + opt_x,opt_y,z_prop,lin_z_test,... + dz/2,corr_length,n_plates_done,missing_dz,n_step,... + brf,common_beta.X,... + common_beta.Y,alpha_lin.X,alpha_lin.X); + + % complete nonlinear step + + [opt_x,opt_y] = nl_step(opt_x,opt_y, dz, gamma, chi, manakov, beat_len ,alpha_lin.X, alpha_lin.Y); + + % half linear step + [opt_x,opt_y,z_prop,lin_z_test,... + corr_length,n_plates_done,missing_dz,n_step,... + brf,common_beta.X,... + common_beta.Y,alpha_lin.X,alpha_lin.X]... + = lin_step... + (opt_x,opt_y,z_prop,lin_z_test,... + dz/2,corr_length,n_plates_done,missing_dz,n_step,... + brf,common_beta.X,... + common_beta.Y,alpha_lin.X,alpha_lin.X); + + % get nonlinear step size + [dz] = getNLstepsize(opt_x,opt_y,gamma,SS_dzmin,SS_dzmax,SS_dphimax,alpha_lin); + + end + + +end + diff --git a/Classes/04_DSP/Equalizer/FFE_DCremoval_adaptive_mu.m b/Classes/04_DSP/Equalizer/FFE_DCremoval_adaptive_mu.m new file mode 100644 index 0000000..9265652 --- /dev/null +++ b/Classes/04_DSP/Equalizer/FFE_DCremoval_adaptive_mu.m @@ -0,0 +1,351 @@ +classdef FFE_DCremoval_adaptive_mu < handle + % Implementation of plain and simple FFE. + % 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); + + properties + sps % usually 2 + order + e + e_tr + error + + len_tr + mu_tr + epochs_tr + + mu_dd + epochs_dd + + mu_dc + dc_buffer_len + + adaptive_mu_mode + + ffe_buffer_len + + smoothing_buffer_length + smoothing_buffer_update + + constellation + + decide + end + + methods + function obj = FFE_DCremoval_adaptive_mu(options) + arguments(Input) + + options.sps = 2; + options.order = 15; + + options.len_tr = 4096; + options.mu_tr = 0; + options.epochs_tr = 5; + + options.mu_dd = 1e-5; + options.epochs_dd = 5; + + options.mu_dc = 0.05; + options.dc_buffer_len = 1; + + options.ffe_buffer_len = 1; + + options.adaptive_mu_mode = 1; + + options.smoothing_buffer_length = 0; + options.smoothing_buffer_update = 0; + options.decide = false; + + end + + assert(options.dc_buffer_len>0); + + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + 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) + + % actual processing of the signal (steps 1. - 3.) + % 1 normalize RMS + X = X.normalize("mode","rms"); + + obj.constellation = unique(D.signal); + + % if obj.smoothing_buffer_length > 0 + % % Apply A1 filter smoothing + % % Calculate the moving sum with the window size N1 + % moving_sum = movsum(X.signal, [obj.smoothing_buffer_length,0]); + % + % % Initialize the output smoothed signal + % X.signal = X.signal - (1 / obj.smoothing_buffer_length) * moving_sum; + % end + + % Training Mode + training = 1; + obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training); + obj.e_tr = obj.e; + + % Decision Directed Mode + N = X.length; + training = 0; + [signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training); + + % Output Signal + if obj.decide + X.signal = decision; + else + X.signal = signal; + end + X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym + lbdesc = [num2str(obj.order),' tap FFE']; + X = X.logbookentry(lbdesc); % append to logbook + + Noi = X - D; + + end + + function [y,d_hat] = equalize(obj, x, d, mu_lms, epochs, N, training) + % Equalize with adaptive DC-removal, VSS, and parallel-buffered DC updates + % Added: FFE gradient buffering in DD mode (error buffer) with update every obj.dc_buffer_len symbols + + arguments + obj + x + d + mu_lms % LMS step-size (or 0 for NLMS) + epochs % number of training/DD epochs + N % number of samples to process + training % boolean flag: true->training mode, false->DD mode + end + + if isempty(obj.e) + obj.e = zeros(obj.order,1); + end + + % Zero-padding for filter memory + x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)]; + + % Initialize storage + numSymbols = ceil(N/obj.sps); + y = zeros(numSymbols,1); + d_hat = zeros(numSymbols,1); + err = NaN(numSymbols,numel(obj.constellation)); + e_dc_save= zeros(numSymbols,1); + + % DC-adaptation parameters + P_err = 0; % running error power + alpha = 0.98; % forgetting factor for error power + err_prev = 0; % previous error sample for VSS correlation + gamma_dc = 1e-6; % meta step-size for DC VSS + mu_min = 1e-6; % lower bound for mu_dc + mu_max = 3e-1; % upper bound for mu_dc + + % DC removal buffer + L = obj.dc_buffer_len; % buffer length + e_dc_buf = NaN(L,1); + e_dc_est = 0; + + % FFE gradient buffer (DD mode only) + L_grad = obj.ffe_buffer_len; % buffer length + if ~training + % each column holds one past gradient of length obj.order + grad_buf = NaN(obj.order, L_grad); + end + + smth_buffer = zeros(1, obj.smoothing_buffer_length); + smth_mean = 0; + % Main loop + for epoch = 1:epochs + s = 0; + for sample = 1:obj.sps:N + s = s + 1; + + if obj.smoothing_buffer_length > 0 + smth_buffer = circshift(smth_buffer,1,2); + smth_buffer(1) = x(sample); + if mod(s, obj.smoothing_buffer_update) == 0 + smth_mean = mean(smth_buffer); + end + x(sample:sample+obj.sps-1) = x(sample:sample+obj.sps-1)-smth_mean; + end + + U = x(obj.order+sample-1:-1:sample); + + %-- 1) filter output with DC correction + y(s) = e_dc_est + obj.e.'*U; + + %-- 2) decision + if training + [~, idx] = min(abs(d(s) - obj.constellation)); + else + [~, idx] = min(abs(y(s) - obj.constellation)); + end + d_hat(s) = obj.constellation(idx); + + %-- 3) error + e_val = y(s) - d_hat(s); + if epoch == epochs + + err(s,idx) = e_val; + true_err(s,idx) = y(s) - d(s); + end + + %-- 4) tap-weight update: training immediate, DD buffered + if training + % immediate update (LMS or NLMS) + if mu_lms ~= 0 + obj.e = obj.e - mu_lms * e_val * U; + else + normU = (U.'*U) + eps; + obj.e = obj.e - e_val * U / normU; + end + else + if 0 + % buffer gradient + if mu_lms ~= 0 + grad = e_val * U; + else + normU = (U.'*U) + eps; + grad = e_val * U / normU; + end + % shift and insert + grad_buf = circshift(grad_buf, 1, 2); + grad_buf(:,1) = grad; + % update once every L symbols + if mod(s, L_grad) == 0 + avg_grad = mean(grad_buf, 2, 'omitnan'); + if mu_lms ~= 0 + obj.e = obj.e - mu_lms * avg_grad; + else + obj.e = obj.e - avg_grad; + end + end + end + end + + + %-- 5) DC adaptation + if obj.mu_dc ~= 0 + + if obj.adaptive_mu_mode + + % VSS for mu_dc + delta_mu = gamma_dc * e_val * err_prev * (U.'*U); + obj.mu_dc = min(max(obj.mu_dc + delta_mu, mu_min), mu_max); + err_prev = e_val; + + % DC buffer update & periodic estimate + P_err = alpha*P_err + (1-alpha)*e_val^2; + mu_dc_norm = obj.mu_dc / (P_err + eps); + + else + + % DC buffer update & periodic estimate + % P_err = alpha*P_err + (1-alpha)*e_val^2; + % mu_dc_norm = obj.mu_dc / (P_err + eps); + + mu_dc_norm = obj.mu_dc; + + end + + e_dc_buf = circshift(e_dc_buf, 1); + e_dc_buf(1) = e_dc_est - mu_dc_norm * e_val; + + if mod(s, L) == 0 + e_dc_est = median(e_dc_buf, 'omitnan'); + end + + P_err_save(s) = P_err; + % Pcorr_save(s) = e_val * err_prev; + Ucorr_save(s) = (U.'*U); + mu_dc_save(s) = mu_dc_norm; + e_dc_save(s) = e_dc_est; + + end + + % store instantaneous squared error + obj.error(epoch, s) = e_val^2; + end + end + + % Optional plotting in DD mode (uncomment if needed) + if 0%~training + + constellation = unique(d); + lvlcol = cbrewer2('Paired', numel(constellation)*2); + lvlcol = lvlcol(2:2:end, :); + + true_err(true_err==0) = NaN; + true_errmoverr = movsum(true_err, 4096, 'omitnan'); + true_errmoverr = true_errmoverr./rms(true_errmoverr); + + moverr = movsum(err, [100,100], 'omitnan'); + moverr = moverr./rms(moverr); + + figure(500); clf + hold on + % 1st subplot: true_errmoverr + % subplot(2,2,1); hold on + % for k = 1:4 + % scatter(1:numSymbols, true_errmoverr(:,k), 1, lvlcol(k,:), '.'); + % end + + % scatter(1:numSymbols, Ucorr_save./rms(Ucorr_save), 1, lvlcol(1,:), '.','DisplayName','Ucorr_save'); + % scatter(1:numSymbols, Pcorr_save./rms(Pcorr_save), 1, lvlcol(1,:), '.','DisplayName','P_corr'); + % scatter(1:numSymbols, P_err_save, 1, lvlcol(1,:), '.','DisplayName','P_err'); + % scatter(1:numSymbols, mu_dc_save, 1, lvlcol(2,:), '.','DisplayName','adapted value of $\mu_{DC}$'); + % scatter(1:numSymbols, sum(moverr,2,'omitnan'), 1, lvlcol(1,:), '.','DisplayName','Mov Error $\hat{d}$ - x over all levels'); + scatter(1:numSymbols, sum(e_dc_save,2,'omitnan'), 1, lvlcol(2,:), '.','DisplayName','Est. Error that is subtracted'); + title('Moving Sum Error'); + hold off + legend + + % 2nd subplot: moverr + subplot(2,2,2); hold on + for k = 1:4 + scatter(1:numSymbols, moverr(:,k), 1, lvlcol(k,:), '.'); + end + title('Moving Sum Error'); + hold off + legend + + % 3rd subplot: err + subplot(2,2,3); hold on + for k = 1:4 + scatter(1:numSymbols, err(:,k), 1, lvlcol(k,:), '.'); + end + title('Error'); + hold off + legend + + % 4th subplot: err + obj.constellation' + subplot(2,2,4); hold on + for k = 1:4 + scatter(1:numSymbols, err(:,k) + obj.constellation(k), 1, lvlcol(k,:), '.'); + end + yline(obj.constellation, '--k'); + title('Error + Constellation'); + hold off + legend + + sgtitle('Error Analysis Subplots'); + + end + end + + + end +end + diff --git a/Classes/04_DSP/Equalizer/FFE_DCremoval_level.m b/Classes/04_DSP/Equalizer/FFE_DCremoval_level.m new file mode 100644 index 0000000..3539d2c --- /dev/null +++ b/Classes/04_DSP/Equalizer/FFE_DCremoval_level.m @@ -0,0 +1,240 @@ +classdef FFE_DCremoval_level < handle + % Implementation of plain and simple FFE. + % 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); + + properties + sps % usually 2 + order + e + error + + len_tr + mu_tr + epochs_tr + + mu_dd + epochs_dd + + mu_dc + dc_buffer_len + + constellation + + decide + + KF_meas_noise = 0; + KF_process_noise = 0; + KF_state_cov = 0; + end + + methods + function obj = FFE_DCremoval_level(options) + arguments(Input) + + options.sps = 2; + options.order = 15; + + options.len_tr = 4096; + options.mu_tr = 0; + options.epochs_tr = 5; + + options.mu_dd = 1e-5; + options.epochs_dd = 5; + + options.mu_dc = 0.05; + options.dc_buffer_len = 1; + + options.decide = false; + + end + + assert(options.dc_buffer_len>0); + + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + 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) + + % actual processing of the signal (steps 1. - 3.) + % 1 normalize RMS + X = X.normalize("mode","rms"); + + obj.constellation = unique(D.signal); + + % Training Mode + training = 1; + obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training); + + % Decision Directed Mode + N = X.length; + training = 0; + [signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training); + + % Output Signal + if obj.decide + X.signal = decision; + else + X.signal = signal; + end + X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym + lbdesc = [num2str(obj.order),' tap FFE']; + X = X.logbookentry(lbdesc); % append to logbook + + Noi = X - D; + + end + function [y, d_hat, logs] = equalize(obj, x, d, mu_lms, epochs, N, training) + % Equalize with Kalman-based DC removal; training epochs estimate KF noise parameters + + arguments + obj + x + d + mu_lms % LMS step-size (or 0 for NLMS) + epochs % number of training or DD epochs + N % number of samples to process + training % true => training mode (tap-training + noise estimation) + end + + % Zero-pad for filter memory + x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)]; + numSym = ceil(N/obj.sps); + + % Pre-allocate outputs + y = zeros(numSym,1); + d_hat = zeros(numSym,1); + + % --- Training: estimate noise stats and train taps --- + if training + % Pre-allocate error accumulator + totalTrain = epochs * numSym; + trainErrs = zeros(totalTrain,1); + te_idx = 0; + + for ep = 1:epochs + s = 0; + for n = 1:obj.sps:N + s = s + 1; + U = x(obj.order + n - 1 : -1 : n); + + % Equalizer output (no DC correction yet) + y(s) = obj.e.' * U; + + % Decision based on known symbol + [~, idx] = min(abs(d(s) - obj.constellation)); + d_hat(s) = obj.constellation(idx); + + % Instantaneous error + e_n = y(s) - d_hat(s); + + % Collect error for noise estimation + te_idx = te_idx + 1; + trainErrs(te_idx) = e_n; + + % Tap-weight update (LMS or NLMS) + if mu_lms ~= 0 + obj.e = obj.e - mu_lms * e_n * U; + else + normU = (U.'*U) + eps; + obj.e = obj.e - e_n * U / normU; + end + end + end + + % Estimate measurement noise R and process noise Q + R_est = var(trainErrs(1:te_idx)); + Q_est = 1e-3 * R_est; % Q/R ratio = 1e-3 (tune as needed) + + % Store into object for DD pass + obj.KF_meas_noise = R_est; + obj.KF_process_noise = Q_est; + obj.KF_state_cov = 5*R_est; % or 5*R_est for a more “eager” start + + % No Kalman in training; return + logs = struct(); + return; + end + + % --- Decision-Directed with Kalman DC tracking --- + % Initialize Kalman state + x_est = 0; + P = obj.KF_state_cov; % initial P (tune in obj; e.g. 1) + + % Logging containers + logs.y_raw = zeros(numSym,1); + logs.y_corr = zeros(numSym,1); + logs.err = zeros(numSym,1); + logs.K_gain = zeros(numSym,1); + logs.x_est = zeros(numSym,1); + logs.P = zeros(numSym,1); + logs.normU = zeros(numSym,1); + logs.tap_norm = zeros(numSym,1); + + for ep = 1:epochs + s = 0; + for n = 1:obj.sps:N + s = s + 1; + U = x(obj.order + n - 1 : -1 : n); + + % 1) Kalman prediction + P = P + obj.KF_process_noise; + x_prior = x_est; + + % 2) raw equalizer output + y_raw = obj.e.' * U; + logs.y_raw(s) = y_raw; + + % 3) DC-corrected output + y_corr = y_raw + x_prior; + y(s) = y_corr; + logs.y_corr(s) = y_corr; + + % 4) decision-directed symbol + [~, idx] = min(abs(y_corr - obj.constellation)); + d_hat(s) = obj.constellation(idx); + + % 5) error + e_n = y_corr - d_hat(s); + logs.err(s) = e_n; + + % 6) tap-weight update (LMS/NLMS) + if mu_lms ~= 0 + obj.e = obj.e - mu_lms * e_n * U; + else + normU = U.' * U + eps; + logs.normU(s) = normU; + obj.e = obj.e - e_n * U / normU; + end + + logs.tap_norm(s) = norm(obj.e); + + % 7) Kalman update + K_gain = P / (P + obj.KF_meas_noise); + x_est = x_prior + K_gain * (e_n - x_prior); + P = (1 - K_gain) * P; + + % 8) log Kalman state + logs.K_gain(s) = K_gain; + logs.x_est(s) = x_est; + logs.P(s) = P; + end + end + end + + + + end +end + diff --git a/Classes/04_DSP/Equalizer/FFE_MLSE.m b/Classes/04_DSP/Equalizer/FFE_MLSE.m new file mode 100644 index 0000000..d94da34 --- /dev/null +++ b/Classes/04_DSP/Equalizer/FFE_MLSE.m @@ -0,0 +1,233 @@ +classdef FFE_MLSE < handle + % Implementation of plain and simple FFE. + % 1) Training mode (stable performance when you use NLMS) + % 2) Decision directed mode + + %LMS: mu in order of 0.0001 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 + + len_tr + mu_tr + epochs_tr + + dd_mode % 1 or 0 to set DD-mode on or off + mu_dd %weight update in dd mode + epochs_dd + + constellation + + L %viterbi memory length + + alpha + DIR + DIR_flip + trellis_states + + traceback_depth + end + + methods + function obj = FFE_MLSE(options) + arguments(Input) + + options.sps = 2; + options.order = 15; + + options.len_tr = 4096; + options.mu_tr = 0; + options.epochs_tr = 5; + + options.dd_mode = 1; + options.mu_dd = 1e-5; + options.epochs_dd = 5; + + options.traceback_depth = 1024; + + options.L = 1 + + end + + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + obj.e = zeros(obj.order,1); + obj.error = 0; + + end + + function [X,X_viterbi] = process(obj, X, D) + + % actual processing of the signal (steps 1. - 3.) + % 1 normalize RMS + X = X.normalize("mode","rms"); + + obj.constellation = unique(D.signal); + + if length(X)/length(D) ~= obj.sps + warning('Signal length does not fit to reference!'); + end + + % Training Mode + n = obj.len_tr; + training = 1; + obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_dd,n,training); + obj.e_tr = obj.e; + + % Decision Directed Mode + n = X.length; + training = 0; + [y,y_vit]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training); + + X_viterbi = X; + + X.signal = y; + X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym + lbdesc = [num2str(obj.order),' tap FFE']; + X = X.logbookentry(lbdesc); % append to logbook + + X_viterbi.signal = y_vit; + X_viterbi.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym + lbdesc = [num2str(obj.order),'order FFE + PF + Viterbi']; + X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook + + + end + + function [y,y_vit] = equalize(obj,x,d,mu,epochs,N,training) + % ============================================================== + % FFE + Whitening + Viterbi Equalizer (reference implementation) + % ============================================================== + + % --- Input padding and preallocation + x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)]; + N_ = N / obj.sps; + y = zeros(N_,1); + y_white = zeros(N_,1); + + for epoch = 1:epochs + + % ============================================================== + % INITIALIZATION (only before final epoch and detection mode) + % ============================================================== + if epoch == epochs && ~training + + % --- Parameters + S = numel(unique(d)); + L = obj.L; + nStates = S^L; + nFeasible = S^(L-1)*S; + + % --- Trellis setup + obj.DIR = arburg(y-d, L); + obj.DIR_flip = flip(obj.DIR); + obj.trellis_states = reshape(unique(d),1,[]); + + pre_comb_mat = repmat(obj.trellis_states, L, 1); + pre_comb_cell = mat2cell(pre_comb_mat, ones(1,L), size(pre_comb_mat,2)); + combs = fliplr(combvec(pre_comb_cell{:}).'); + first_sym = combs(:,1); + last_sym = combs(:,end); + nStates = size(combs,1); + + noise_free_received = inf(nStates,nStates); + valid = false(nStates); + + for from = 1:nStates + for to = 1:nStates + if all(combs(to,2:end) == combs(from,1:end-1)) + noise_free_received(to,from) = ... + dot(combs(to,:), obj.DIR_flip(end:-1:2)) + last_sym(from)*obj.DIR_flip(1); + valid(to,from) = true; + end + end + end + + nf_vec = noise_free_received(valid); + [valid_to, valid_from] = find(valid); + from_per_to = arrayfun(@(to)find(valid(to,:)), 1:nStates, 'UniformOutput', false); + + % --- Noise stats + y_ideal = conv(d(:), obj.DIR(:), "same"); + sigma2 = mean(abs(y - y_ideal).^2); + inv2s2 = 1/(2*sigma2); + + % --- Vector initialization + bm_vec = zeros(1,nFeasible); + pm = zeros(nStates,1); + pm_next = zeros(nStates,1); + bm_fw = zeros(nStates,nStates,length(y)); + zi = zeros(max(numel(obj.DIR)-1,0),1); + end + + % ============================================================== + % RUNTIME LOOP (FFE update + Viterbi detection in last epoch) + % ============================================================== + symbol = 0; + for sample = 1:obj.sps:N + symbol = symbol + 1; + + % --- FFE output + U = x(obj.order+sample-1:-1:sample); + y(symbol,1) = obj.e.' * U; + + % --- Decision + if training + d_hat(symbol,1) = d(symbol); + else + [~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); + d_hat(symbol,1) = obj.constellation(symbol_idx); + end + + % --- LMS weight update + err(symbol) = d_hat(symbol) - y(symbol); + obj.e = obj.e + mu * (err(symbol) * U); + + % --- Whitening + Viterbi (final epoch only) + if epoch == epochs && ~training + + [y_white(symbol), zi] = filter(obj.DIR,1,y(symbol), zi); + + if symbol == 1 + pm = -inf(nStates,nStates); + pm(:,1:nStates) = 0; + else + bm_vec = -(y_white(symbol) - nf_vec).^2 * inv2s2; + bm_mat = -inf(nStates,nStates); + bm_mat(valid) = bm_vec; + + pm_new = pm + bm_mat; + [pm_survive(:,symbol), pm_survivor_fw_idx(:,symbol)] = max(pm_new,[],2); + pm = repmat(pm_survive(:,symbol).', nStates,1); + end + + % --- Traceback + if mod(symbol,obj.traceback_depth) == 0 + [~,viterbi_path(symbol)] = max(pm_survive(:,symbol)); + for n = symbol:-1:symbol-obj.traceback_depth+2 + viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n); + end + end + end + end + + % --- Final reconstruction + if epoch == epochs && ~training + y_vit = first_sym(viterbi_path); + end + end + end + + end +end \ No newline at end of file diff --git a/Classes/04_DSP/Equalizer/ML_MLSE.m b/Classes/04_DSP/Equalizer/ML_MLSE.m new file mode 100644 index 0000000..3f584c3 --- /dev/null +++ b/Classes/04_DSP/Equalizer/ML_MLSE.m @@ -0,0 +1,355 @@ +classdef ML_MLSE < handle + % --------------------------------------------------------------------- + % W. Lanneer and Y. Lefevre, + % “Machine Learning-Based Pre-Equalizers for Maximum Likelihood + % Sequence Estimation in High-Speed PONs,” EUSIPCO 2023 + % --------------------------------------------------------------------- + % This implementation reproduces the closed-loop ML-based + % pre-equalizer training for MLSE, supporting both training and + % detection (decision-directed) modes. + % --------------------------------------------------------------------- + + properties + sps + order + e + e_tr + error + + len_tr + mu_tr + epochs_tr + + dd_mode + mu_dd + epochs_dd + adaptive_mu + + constellation + L + alpha + DIR + DIR_flip + trellis_states + traceback_depth + delta + + % Internal variables + S + Nf + nStates + nFeasible + combs + first_sym + last_sym + valid + valid_to_idx + valid_from_idx + w + + % Fast lookup + nSym + key_table + trans_index + true_to_state_idx + + % Debug metrics + ber = [] + ce = ones(1,1) + end + + methods + function obj = ML_MLSE(options) + arguments(Input) + options.sps = 2; + options.order = 15; + options.len_tr = 4096; + options.mu_tr = 0.001; + options.epochs_tr = 5; + options.dd_mode = 1; + options.mu_dd = 1e-5; + options.epochs_dd = 5; + options.adaptive_mu = 1; + options.delta = 0; + options.traceback_depth = 1024; + options.L = 1; + end + + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + obj.e = zeros(obj.order,1); + obj.error = 0; + end + + % ============================================================== + % PROCESS + % ============================================================== + function [X,X_viterbi] = process(obj, X, D) + % Normalize input RMS + X = X.normalize("mode","rms"); + obj.constellation = sort(unique(D.signal),'ascend'); + obj.nSym = numel(obj.constellation); + + if length(X)/length(D) ~= obj.sps + warning('Signal length does not fit to reference!'); + end + + % --- Parameters + obj.S = obj.nSym; + obj.Nf = obj.order * obj.sps; + obj.nStates = obj.S^obj.L; + obj.nFeasible = obj.nStates * obj.S; + + % --- Trellis mapping + obj.trellis_states = reshape(obj.constellation,1,[]); + pre_comb_mat = repmat(obj.trellis_states, obj.L, 1); + pre_comb_cell = mat2cell(pre_comb_mat, ones(1,obj.L), size(pre_comb_mat,2)); + obj.combs = fliplr(combvec(pre_comb_cell{:}).'); + obj.first_sym = obj.combs(:,1); + obj.last_sym = obj.combs(:,end); + obj.nStates = size(obj.combs,1); + + % --- Valid transitions + obj.valid = false(obj.nStates); + for from = 1:obj.nStates + for to = 1:obj.nStates + if all(obj.combs(to,2:end) == obj.combs(from,1:end-1)) + obj.valid(to,from) = true; + end + end + end + [obj.valid_to_idx,obj.valid_from_idx] = find(obj.valid); + + % --- Initialize weights + if isempty(obj.w) || any(size(obj.w) ~= [obj.Nf+1,obj.nFeasible]) + obj.w = randn(obj.Nf+1,obj.nFeasible); + end + + % --- Fast lookup tables + [~, sym_idx_mat] = ismember(obj.combs, obj.constellation); + key_vals = 1 + sum((sym_idx_mat - 1) .* (obj.nSym .^ (0:obj.L-1)), 2); + max_key = obj.nSym^obj.L; + obj.key_table = zeros(max_key,1,'uint32'); + obj.key_table(key_vals) = 1:obj.nStates; + + obj.trans_index = sparse(obj.nStates,obj.nStates); + for i = 1:length(obj.valid_from_idx) + f = obj.valid_from_idx(i); + t = obj.valid_to_idx(i); + obj.trans_index(t,f) = i; + end + + % ============================================================== + % TRAINING + % ============================================================== + fprintf('\n--- Training mode ---\n'); + obj.equalize(X.signal, D.signal, obj.mu_tr, obj.epochs_tr, obj.len_tr, true); + obj.e_tr = obj.e; + + % ============================================================== + % DECISION-DIRECTED / TESTING + % ============================================================== + fprintf('--- Decision-directed / detection mode ---\n'); + [y, y_vit] = obj.equalize(X.signal, D.signal, obj.mu_dd, obj.epochs_dd, X.length, false); + + X_viterbi = X; + X.signal = y; + X_viterbi.signal = y_vit; + end + + % ============================================================== + % EQUALIZE + % ============================================================== + function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training) + debug = 0; + showPlots = 0; + y = zeros(N,1); + nSymbols = ceil(N/obj.sps); + + for epoch = 1:epochs + pm = zeros(obj.nStates,1); + pred = zeros(nSymbols,obj.nStates,'uint32'); + pm_sto = nan(obj.nStates,nSymbols,'like',pm); + CE_accum = 0; + + start_sample = 1; + end_sample = N; + start_symbol = 1 + floor((start_sample - 1)/obj.sps); + + % --- initialize true state + if numel(d) >= obj.L && start_symbol >= obj.L + init_seq = d(start_symbol-obj.L+1:start_symbol); + key_init = obj.seq2key(init_seq); + true_to_state_idx = obj.key_table(key_init); + if true_to_state_idx==0, true_to_state_idx=1; end + else + true_to_state_idx = uint32(1); + end + + for sample = start_sample:obj.sps:end_sample + symbol = (sample - start_sample)/obj.sps + 1; + sym_idx = start_symbol + (symbol - 1); + + % --- Observation window (with delta) + i1 = sample - obj.Nf + 1 + obj.delta; + i2 = sample + obj.delta; + buf = x(max(1,i1):min(length(x),i2)); + padL = max(0,1 - i1); + padR = max(0,i2 - length(x)); + yk = [zeros(padL,1); buf(:); zeros(padR,1)]; + yk = [yk;1]; + + % --- Branch metrics + c_hat = (yk.' * obj.w).'; + pm = pm - min(pm); + v_tilde = pm(obj.valid_from_idx) + c_hat; + + % --- allocate once + if epoch==1 && symbol==1 + obj.true_to_state_idx = ones(ceil(N/obj.sps),1,'uint32'); + end + + % --- previous "to" becomes "from" + if symbol>1 + true_from_state_idx = obj.true_to_state_idx(symbol-1); + else + true_from_state_idx = 1; + end + + % --- compute or reuse "to" state + if epoch==1 + if sym_idx>=obj.L + key_to = obj.seq2key(d(sym_idx-obj.L+1:sym_idx)); + state_idx = obj.key_table(key_to); + if state_idx==0 + state_idx = true_from_state_idx; + end + obj.true_to_state_idx(symbol) = state_idx; + else + obj.true_to_state_idx(symbol) = true_from_state_idx; + end + end + true_to_state_idx = obj.true_to_state_idx(symbol); + + % --- fast Dirac creation + dirac = zeros(obj.nFeasible,1); + trans_idx = obj.trans_index(true_to_state_idx,true_from_state_idx); + if trans_idx~=0 + dirac(trans_idx)=1; + end + + % =================================================================== + % TRAINING MODE (weight update) + % =================================================================== + if training + % --- Softmax and CE + v_shift = -(v_tilde - min(v_tilde)); + v_shift = min(v_shift,100); + expv = exp(v_shift); + p = expv./(sum(expv)+eps); + CE_symbol(symbol) = -log(p(dirac==1)+eps); + + % --- CE smoothing and adaptive μ + if sym_idx>obj.L + CE_smooth(symbol)=0.01*CE_symbol(symbol)+0.99*CE_symbol(symbol-1); + else + CE_smooth(symbol)=CE_symbol(symbol); + end + CE_accum=CE_accum+CE_symbol(symbol); + + % --- Gradient update + dmp=(dirac-p)'; + dL_Dw=(yk).*dmp; + if sym_idx>=obj.L + if obj.adaptive_mu + mu_eff=CE_smooth(symbol); + mu_eff=max(min(mu_eff,0.2),1e-4); + else + mu_eff=mu; + end + obj.w=obj.w - mu_eff.*dL_Dw; + end + end + + % =================================================================== + % DECODING MODE (Viterbi only) + % =================================================================== + % Compare-Select (always executed) + vmat=inf(obj.nStates,obj.nStates); + vmat(obj.valid)=v_tilde; + [pm_next,pred(symbol,:)]=min(vmat,[],2); + pm_next=pm_next-min(pm_next); + pm=pm_next; + pm_sto(:,symbol)=pm; + end + + % --- Traceback + [~,s_end]=min(pm); + vpath=zeros(symbol,1,'uint32'); + vpath(symbol)=s_end; + for n=symbol:-1:2 + vpath(n-1)=pred(n,vpath(n)); + end + + y_ref=d(start_symbol:end); + y=obj.first_sym(vpath); + + % --- BER/CE reporting and plots + if training + err=sum(y~=y_ref(1:length(y))); + ser=err/length(y); + try + ref_bits=PAMmapper(obj.S,0).demap(y_ref(1:length(y))); + eq_bits=PAMmapper(obj.S,0).demap(y); + [~,~,ber,~]=calc_ber(ref_bits,eq_bits,"skip_front",10,"skip_end",10,"returnErrorLocation",1); + fprintf('Epoch %d - BER: %.2e\n',epoch,ber); + obj.ber(epoch)=ber; + catch + fprintf('Epoch %d - SER: %.2e\n',epoch,ser); + obj.ber(epoch)=ser; + end + obj.ce(epoch)=CE_accum/symbol; + + if debug && mod(epoch,10)==1 && showPlots + figure(10);clf + subplot(3,2,1:2); + imagesc(obj.w);axis xy;colorbar;title('Filter W'); + subplot(3,2,3); + vtilde_mat=NaN(obj.nStates,obj.nStates); + vtilde_mat(obj.valid)=v_tilde; + imagesc(vtilde_mat);axis xy;colorbar;title('Path Metrics (v\_tilde)'); + subplot(3,2,4); + plot(1:symbol,pm_sto);title('Path Metric Evolution'); + subplot(3,2,5);hold on; + scatter(1:symbol,CE_symbol,1,'.'); + scatter(1:symbol,CE_smooth,1,'.'); + title('Cross Entropy'); + subplot(3,2,6);hold on; + yyaxis left + scatter(1:length(obj.ce),obj.ce,10,'s','filled'); + ylabel('Cross Entropy'); + yyaxis right + scatter(1:length(obj.ber),obj.ber,10,'d','filled'); + set(gca,'YScale','log'); + ylabel('BER (log)'); + xlabel('Epoch');grid on; + title('Convergence'); + drawnow; + end + end + end + end + + % ============================================================== + % Helper: Sequence → key (always scalar) + % ============================================================== + function key = seq2key(obj, seq) + [~, idx] = ismember(flip(seq), obj.constellation); + pow = (obj.nSym .^ (0:obj.L-1)).'; + key = 1 + sum((idx(:) - 1) .* pow); + end + end +end diff --git a/Classes/04_DSP/Sequence Detection/MLSE_new.m b/Classes/04_DSP/Sequence Detection/MLSE_new.m new file mode 100644 index 0000000..ef7a1e2 --- /dev/null +++ b/Classes/04_DSP/Sequence Detection/MLSE_new.m @@ -0,0 +1,152 @@ +classdef MLSE_new < handle + %MLSE: BCJR‐based soft‐output for PAM‐M sequence estimation & GMI + + properties + M % PAM order (e.g. 4) + DIR % channel impulse response + trellis_states % PAM constellation levels (e.g. [-3 -1 1 3]) + end + + methods + function obj = MLSE_new(opts) + arguments + opts.M double = 4; + opts.DIR double = 1; + opts.trellis_states double = [-3 -1 1 3]; + end + obj.M = opts.M; + obj.DIR = opts.DIR; + obj.trellis_states = opts.trellis_states; + end + + function [hd_out, LLR, NGMI] = process(obj, signalclass,ref_symbolclass) + % rx, tx: column vectors of equalized and reference symbols + data_in = signalclass.signal; + shape_in = size(data_in); + data_ref = ref_symbolclass.signal; + + [data_out_hd,LLR,GMI] = obj.bcjr_soft(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; + + + + + [hd_out, LLR, NGMI] = obj.bcjr_soft(rx, tx); + end + end + + methods (Access=private) + function [hd_sym, LLR, NGMI] = bcjr_soft(obj, y, x) + %--- build trellis states & branch outputs --- + DIR = flip(obj.DIR(:)); + S = combvec(obj.trellis_states, obj.trellis_states)'; + prev = S(:,1); cur = S(:,2); + branch_out = prev*DIR(1) + cur*DIR(2); % for 2‐tap + + N = numel(y); + M = obj.M; + m = log2(M); + + %--- forward/backward metrics (max‐log) --- + nStates = numel(obj.trellis_states); + alpha = -inf(nStates,N); + beta = -inf(nStates,N); + bm_fw = zeros(nStates,nStates,N); + + % branch metrics + sigma2 = var(y - x); + inv2sigma2 = 1/(2*sigma2); + for n=1:N + bm = -((y(n)-branch_out).^2)* inv2sigma2; + bm = reshape(bm, nStates, nStates); + bm_fw(:,:,n) = bm; + end + % forward + alpha(:,1) = max(bm_fw(:,:,1),[],2); + for n=2:N + mat = alpha(:,n-1) + bm_fw(:,:,n); + alpha(:,n) = max(mat,[],2); + end + % backward + beta(:,N) = 0; + for n=N-1:-1:1 + mat = beta(:,n+1).' + bm_fw(:,:,n+1); + beta(:,n) = max(mat,[],2); + end + + % Precompute the “zero‐th” forward metric + alpha0 = zeros(nStates,1); + + LLP = -inf(nStates,nStates,N); + for n = 1:N + % Select the correct previous alpha column + if n == 1 + a_prev = alpha0; + else + a_prev = alpha(:,n-1); + end + + % Combine α, branch metric, and β + mat = a_prev + bm_fw(:,:,n) + beta(:,n).'; + LLP(:,:,n) = mat; + end + + %--- compute LLRs symbol‐wise via log‐sum‐exp over branches --- + levels = obj.trellis_states; + bit_map = PAMmapper(M,0).demap(levels'); + LLR = zeros(N,m); + for n=1:N + % sum branch posteriors per symbol + llp_n = LLP(:,:,n); + logZ = logsumexp(llp_n(:)); + post = exp(llp_n - logZ); + % aggregate per symbol index + Psym = zeros(M,1); + for i=1:nStates + for j=1:nStates + % branch (i->j) emits symbol 'cur(j)' + idx = find(levels==cur(j)); + Psym(idx) = Psym(idx) + post(i,j); + end + end + % bit‐LLR from symbol posterior + for k=1:m + idx1 = bit_map(:,k)==1; + idx0 = ~idx1; + P1 = sum(Psym(idx1)); + P0 = sum(Psym(idx0)); + LLR(n,k) = log(P1/P0); + end + end + + %--- hard decisions --- + [~,symIdx] = max(LLR,[],2); + hd_sym = levels(symIdx); + + %--- GMI via Alvarado Eq.(30) --- + tx_bits = PAMmapper(M,0).demap(x); + MI = zeros(1,m); + for k=1:m + r0 = LLR(tx_bits(:,k)==0,k); + r1 = LLR(tx_bits(:,k)==1,k); + I0 = mean(log2(1+exp(-r0))); + I1 = mean(log2(1+exp(+r1))); + MI(k) = 1 - 0.5*(I0+I1); + end + GMI = sum(MI); + NGMI = GMI/m; + end + end +end + +function s = logsumexp(a) + m = max(a(:)); + s = m + log(sum(exp(a-m), 'all')); +end diff --git a/Classes/DataBaseHandler/Equalizerstruct.m b/Classes/DataBaseHandler/Equalizerstruct.m new file mode 100644 index 0000000..3a72307 --- /dev/null +++ b/Classes/DataBaseHandler/Equalizerstruct.m @@ -0,0 +1,68 @@ +classdef Equalizerstruct + % Equalizerstruct - Class to store and manage equalizer structure data + + properties + eq_id (1,1) double {mustBeNumeric} = NaN + equalizer_structure equalizer_structure = equalizer_structure.ffe + eq + mlse + comment char = string.empty() % Changed to string with proper empty initialization + hash char = string.empty() % Changed to string with proper empty initialization + end + + methods + function obj = Equalizerstruct(varargin) + % Constructor method for Equalizerstruct + % Can be called empty or with name-value pairs + + if nargin > 0 + for i = 1:2:nargin + if isprop(obj, varargin{i}) + obj.(varargin{i}) = varargin{i+1}; + else + error('Property %s does not exist in Equalizerstruct', varargin{i}); + end + end + end + end + + function s = toStruct(obj) + % Convert the object to a struct + s = struct(); + props = properties(obj); + for i = 1:length(props) + s.(props{i}) = obj.(props{i}); + end + end + + function str = toString(obj) + % Convert the object to a formatted string + s = obj.toStruct(); + str = sprintf('Equalizerstruct:\n'); + fields = fieldnames(s); + for i = 1:length(fields) + val = s.(fields{i}); + if isempty(val) + str = sprintf('%s%s: []\n', str, fields{i}); + elseif isnumeric(val) && length(val) > 1 + str = sprintf('%s%s: [%s]\n', str, fields{i}, num2str(val')); + else + str = sprintf('%s%s: %s\n', str, fields{i}, string(val)); + end + end + end + end + + methods (Static) + function obj = fromStruct(s) + % Create an Equalizerstruct object from a struct + obj = Equalizerstruct(); + fields = fieldnames(s); + for i = 1:length(fields) + if isprop(obj, fields{i}) + obj.(fields{i}) = s.(fields{i}); + end + end + end + end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/Metricstruct.m b/Classes/DataBaseHandler/Metricstruct.m new file mode 100644 index 0000000..ef00675 --- /dev/null +++ b/Classes/DataBaseHandler/Metricstruct.m @@ -0,0 +1,147 @@ +classdef Metricstruct + % ResultData - Class to store and manage metric data from signal processing results + + properties + result_id (1,1) double {mustBeNumeric} = NaN + run_id (1,1) double {mustBeNumeric} = NaN + eqParam_id (1,1) double {mustBeNumeric} = NaN + date_of_processing (1,1) datetime = datetime('now') + + numBits (1,1) double {mustBeInteger, mustBeNonnegative} = 0 + BER (1,1) double {mustBeNumeric, mustBeNonnegative, mustBeLessThanOrEqual(BER,1)} = 0 + numBitErr (1,1) double {mustBeInteger, mustBeNonnegative} = 0 + BER_precoded (1,1) double {mustBeNumeric, mustBeNonnegative, mustBeLessThanOrEqual(BER_precoded,1)} = 0 + numBitErr_precoded (1,1) double {mustBeInteger, mustBeNonnegative} = 0 + + SNR (1,1) double {mustBeNumeric} = NaN + SNR_level (:,1) double {mustBeNumeric} = [] + STD (1,1) double {mustBeNumeric} = NaN + STD_level (:,1) double = [] + STDrx (1,1) double {mustBeNumeric} = NaN + STDrx_level (:,1) double = [] + EVM (1,1) double {mustBeNumeric} = NaN + EVM_level (:,1) double {mustBeNumeric} = [] + + GMI (1,1) double {mustBeNumeric} = NaN + AIR (1,1) double {mustBeNumeric} = NaN + Alpha (:,1) double {mustBeNumeric} = NaN + MLSE_dir (:,1) double {mustBeNumeric} = [] + end + + methods + function obj = Metricstruct(varargin) + % Constructor method for ResultData + % Can be called empty or with name-value pairs + + % Process name-value pairs if provided + if nargin > 0 + for i = 1:2:nargin + if isprop(obj, varargin{i}) + obj.(varargin{i}) = varargin{i+1}; + else + error('Property %s does not exist in ResultData', varargin{i}); + end + end + end + end + + function s = toStruct(obj) + % Convert the object to a struct + s = struct(); + props = properties(obj); + for i = 1:length(props) + s.(props{i}) = obj.(props{i}); + end + end + + function str = toString(obj) + % Convert the object to a formatted string + s = obj.toStruct(); + str = sprintf('ResultData:\n'); + fields = fieldnames(s); + for i = 1:length(fields) + val = s.(fields{i}); + if isempty(val) + str = sprintf('%s%s: []\n', str, fields{i}); + elseif isnumeric(val) && length(val) > 1 + str = sprintf('%s%s: [%s]\n', str, fields{i}, num2str(val')); + else + str = sprintf('%s%s: %s\n', str, fields{i}, string(val)); + end + end + end + + function print(obj,options) + % Print method to display key metrics in a formatted way + arguments + obj + options.description = ''; + end + + + % Define the width for formatting + nameWidth = 15; % Width for parameter names + valueWidth = 12; % Width for values + + % Print header + + fprintf('\n%s\n', repmat('=', 1, nameWidth + valueWidth)); + fprintf([char(options.description),' Results \n']); + fprintf('%s\n', repmat('=', 1, nameWidth + valueWidth)); + + % Function to format numbers with appropriate precision + function str = formatNumber(value) + if isnan(value) + str = 'N/A'; + elseif abs(value) < 0.1 && value ~= 0 + str = sprintf('%.2e', value); + else + str = sprintf('%.4f', value); + end + end + + % Print each metric + % BER metrics + fprintf('%-*s: %*s\n', nameWidth, 'BER', valueWidth, formatNumber(obj.BER)); + fprintf('%-*s: %*s\n', nameWidth, 'BER (precoded)', valueWidth, formatNumber(obj.BER_precoded)); + + % SNR + fprintf('%-*s: %*s\n', nameWidth, 'SNR', valueWidth, formatNumber(obj.SNR)); + + % Information rates + fprintf('%-*s: %*s\n', nameWidth, 'GMI', valueWidth, formatNumber(obj.GMI)); + fprintf('%-*s: %*s GBd \n', nameWidth, 'AIR', valueWidth, formatNumber(obj.AIR.*1e-9)); + + % Alpha (if it's not empty, show first few values) + if ~isempty(obj.Alpha) + if length(obj.Alpha) <= 3 + alphaStr = sprintf('%.4f ', obj.Alpha); + else + alphaStr = sprintf('%.4f %.4f %.4f...', obj.Alpha(1:3)); + end + fprintf('%-*s: %s\n', nameWidth, 'Alpha', alphaStr); + else + fprintf('%-*s: %*s\n', nameWidth, 'Alpha', valueWidth, '[]'); + end + + % Print footer + fprintf('%s\n', repmat('=', 1, nameWidth + valueWidth)); + end + + end + + methods (Static) + + function obj = fromStruct(s) + % Create a ResultData object from a struct + obj = Metricstruct(); + fields = fieldnames(s); + for i = 1:length(fields) + if isprop(obj, fields{i}) + obj.(fields{i}) = s.(fields{i}); + end + end + end + + end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/QueryFilter.m b/Classes/DataBaseHandler/QueryFilter.m new file mode 100644 index 0000000..86dfe01 --- /dev/null +++ b/Classes/DataBaseHandler/QueryFilter.m @@ -0,0 +1,169 @@ +% QueryFilter - Class for building SQL WHERE conditions for database queries. +% +% Usage: +% qf = QueryFilter(); +% qf.where('Runs', 'fiber_length', SqlOperator.GREATER_THAN, 5); +% qf.where('Runs', 'bitrate', SqlOperator.IN, [224, 336, 448]); +% qf.where('Runs', 'is_mpi', SqlOperator.EQUALS, 0); +% +% % Convert to struct for use in DBHandler or other query functions: +% filterStruct = qf.toStruct(); +% +% % Display current filters: +% disp(qf); + +classdef QueryFilter < handle + + + properties (Access = private) + filters struct = struct() + end + + methods + function obj = QueryFilter(oldFormat) + % Constructor - optionally convert from old filter format + if nargin > 0 && isstruct(oldFormat) + tableNames = fieldnames(oldFormat); + for t = 1:length(tableNames) + tableName = tableNames{t}; + if isstruct(oldFormat.(tableName)) + fields = fieldnames(oldFormat.(tableName)); + for f = 1:length(fields) + fieldName = fields{f}; + value = oldFormat.(tableName).(fieldName); + if ~isempty(value) + obj.where(tableName, fieldName, value); + end + end + end + end + end + end + + function where(obj, tableName, fieldName, operator, value) + % Add a WHERE condition to the query + % + % Inputs: + % tableName - Name of the database table (char) + % fieldName - Name of the field/column (char) + % operator - SqlOperator enum or value if using EQUALS + % value - Value to filter by + % + % Example: + % filter.where('Runs', 'fiber_length', SqlOperator.GREATER_THAN, 5) + arguments + obj + tableName char + fieldName char + operator SqlOperator + value = [] + end + + % Handle case where operator is the value (using default EQUALS) + if nargin < 5 + value = operator; + operator = SqlOperator.EQUALS; + end + + % Validate operator type + if ~isa(operator, 'SqlOperator') + error('Operator must be a SqlOperator enumeration'); + end + + % Validate value based on operator + obj.validateValue(operator, value); + + % Create filter + filter = SqlFilter(value, operator.toSqlString()); + + % Add to filters + if ~isfield(obj.filters, tableName) + obj.filters.(tableName) = struct(); + end + obj.filters.(tableName).(fieldName) = filter; + end + + function s = toStruct(obj) + % Convert filters to struct format for database query + s = obj.filters; + end + + function display(obj) + % Custom display of filter conditions + fprintf('QueryFilter with conditions:\n'); + if isempty(fieldnames(obj.filters)) + fprintf(' No filters set\n'); + return; + end + + tables = fieldnames(obj.filters); + for t = 1:length(tables) + tableName = tables{t}; + if ~isempty(fieldnames(obj.filters.(tableName))) + fprintf('\nTable: %s\n', tableName); + fields = fieldnames(obj.filters.(tableName)); + for f = 1:length(fields) + fieldName = fields{f}; + filter = obj.filters.(tableName).(fieldName); + if isnumeric(filter.value) + if length(filter.value) > 1 + valueStr = ['[', num2str(filter.value), ']']; + else + valueStr = num2str(filter.value); + end + elseif isempty(filter.value) + valueStr = 'empty'; + else + valueStr = char(filter.value); + end + fprintf(' %s %s %s\n', fieldName, filter.operator, valueStr); + end + end + end + end + + function clear(obj, tableName) + % Clear all filters or filters for a specific table + if nargin < 2 + obj.filters = struct(); + else + if isfield(obj.filters, tableName) + obj.filters = rmfield(obj.filters, tableName); + end + end + end + + function remove(obj, tableName, fieldName) + % Remove a specific filter + if isfield(obj.filters, tableName) && ... + isfield(obj.filters.(tableName), fieldName) + obj.filters.(tableName) = rmfield(obj.filters.(tableName), fieldName); + end + end + end + + methods (Access = private) + function validateValue(~, operator, value) + % Validate value based on operator type + switch operator + case SqlOperator.BETWEEN + if ~isnumeric(value) || length(value) ~= 2 + error('BETWEEN operator requires array of 2 numbers'); + end + case SqlOperator.IN + if ~isnumeric(value) || isempty(value) + error('IN operator requires non-empty array'); + end + case SqlOperator.LIKE + if ~ischar(value) && ~isstring(value) + error('LIKE operator requires string value'); + end + otherwise + % For other operators, just ensure value is not empty + if isempty(value) + error('Value cannot be empty'); + end + end + end + end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/SqlFilter.m b/Classes/DataBaseHandler/SqlFilter.m new file mode 100644 index 0000000..3dbc07f --- /dev/null +++ b/Classes/DataBaseHandler/SqlFilter.m @@ -0,0 +1,22 @@ +% SqlFilter - Internal class for holding a value and SQL operator for a filter condition. +% +% Usage: +% f = SqlFilter(10, SqlOperator.GREATER_THAN.toSqlString()); +% % f.value == 10, f.operator == '>' + + +classdef SqlFilter + properties + value + operator char = '=' + end + + methods + function obj = SqlFilter(value, operator) + obj.value = value; + if nargin > 1 + obj.operator = operator; + end + end + end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/SqlOperator.m b/Classes/DataBaseHandler/SqlOperator.m new file mode 100644 index 0000000..4e4beea --- /dev/null +++ b/Classes/DataBaseHandler/SqlOperator.m @@ -0,0 +1,48 @@ +% SqlOperator - Enumeration of supported SQL operators for query building. +% +% Usage: +% op = SqlOperator.GREATER_THAN; +% opStr = op.toSqlString(); % returns '>' +% +% Supported operators: +% EQUALS, GREATER_THAN, LESS_THAN, GREATER_EQUAL, LESS_EQUAL, +% NOT_EQUAL, IN, BETWEEN, LIKE + +classdef SqlOperator < uint32 + enumeration + EQUALS (1) % == + GREATER_THAN (2) % > + LESS_THAN (3) % < + GREATER_EQUAL (4) % >= + LESS_EQUAL (5) % <= + NOT_EQUAL (6) % != + IN (7) % IN + BETWEEN (8) % BETWEEN + LIKE (9) % LIKE + end + + methods + function op = toSqlString(obj) + switch obj + case SqlOperator.EQUALS + op = '='; + case SqlOperator.GREATER_THAN + op = '>'; + case SqlOperator.LESS_THAN + op = '<'; + case SqlOperator.GREATER_EQUAL + op = '>='; + case SqlOperator.LESS_EQUAL + op = '<='; + case SqlOperator.NOT_EQUAL + op = '!='; + case SqlOperator.IN + op = 'IN'; + case SqlOperator.BETWEEN + op = 'BETWEEN'; + case SqlOperator.LIKE + op = 'LIKE'; + end + end + end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/related functions/cleanUpTable.m b/Classes/DataBaseHandler/related functions/cleanUpTable.m new file mode 100644 index 0000000..239ca30 --- /dev/null +++ b/Classes/DataBaseHandler/related functions/cleanUpTable.m @@ -0,0 +1,32 @@ +function cleanedTable = cleanUpTable(inputTable) +% Converts string numbers to numeric, 'NaN' to NaN, and tries to convert date strings to datetime. + +cleanedTable = inputTable; +varNames = cleanedTable.Properties.VariableNames; + +for i = 1:numel(varNames) + col = cleanedTable.(varNames{i}); + if iscell(col) + numericCol = str2double(col); + if all(isnan(numericCol) == strcmpi(col, 'NaN') | cellfun(@isempty, col)) + cleanedTable.(varNames{i}) = numericCol; + else + try + cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS'); + catch + cleanedTable.(varNames{i}) = string(col); + end + end + elseif isstring(col) + numericCol = str2double(col); + if all(isnan(numericCol) == strcmpi(col, "NaN")) + cleanedTable.(varNames{i}) = numericCol; + else + try + cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); + catch + end + end + end +end +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/related functions/groupIt.m b/Classes/DataBaseHandler/related functions/groupIt.m new file mode 100644 index 0000000..85a1851 --- /dev/null +++ b/Classes/DataBaseHandler/related functions/groupIt.m @@ -0,0 +1,53 @@ +function resultTable = groupIt(fixedVars, dataTable, aggregationFunction) +% groupIt Groups data in a table based on fixedVars and applies aggregationFunction to numeric data. +% +% resultTable = groupIt(fixedVars, dataTable, aggregationFunction) +% +% Inputs: +% fixedVars - Cell array of variable names to group by +% dataTable - Input MATLAB table +% aggregationFunction - Function handle (e.g., @mean, @min, @max) +% +% Output: +% resultTable - Grouped and aggregated table + +[G, groupKeys] = findgroups(dataTable(:, fixedVars)); +varNames = dataTable.Properties.VariableNames; +nVars = numel(varNames); +aggData = cell(height(groupKeys), nVars); +groupCount = zeros(height(groupKeys), 1); + +for i = 1:height(groupKeys) + idx = (G == i); + groupCount(i) = sum(idx); + for j = 1:nVars + colData = dataTable.(varNames{j}); + if isnumeric(colData) + if any(idx) + aggData{i, j} = aggregationFunction(colData(idx)); + else + aggData{i, j} = NaN; + end + else + if iscell(colData) + nonEmptyIdx = find(idx & ~cellfun(@isempty, colData), 1); + if ~isempty(nonEmptyIdx) + aggData{i, j} = colData{nonEmptyIdx}; + else + aggData{i, j} = []; + end + else + nonEmptyIdx = find(idx, 1); + if ~isempty(nonEmptyIdx) + aggData{i, j} = colData(nonEmptyIdx); + else + aggData{i, j} = []; + end + end + end + end +end + +resultTable = cell2table(aggData, 'VariableNames', varNames); +resultTable.nRows = groupCount; +end \ No newline at end of file diff --git a/Classes/DataBaseHandler/related functions/removeGroupOutliers.m b/Classes/DataBaseHandler/related functions/removeGroupOutliers.m new file mode 100644 index 0000000..cd287da --- /dev/null +++ b/Classes/DataBaseHandler/related functions/removeGroupOutliers.m @@ -0,0 +1,61 @@ +function [cleanedTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var) +% removeGroupOutliers removes outliers in y_var within each group defined by fixedVars. +% +% [cleanedTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var) +% +% Inputs: +% dataTable - Input MATLAB table +% fixedVars - Cell array of variable names to group by +% y_var - Name of the variable to check for outliers (string or char) +% +% Outputs: +% cleanedTable - Table with outliers removed +% outliersTable - Table of removed outlier rows + +[G, groupKeys] = findgroups(dataTable(:, fixedVars)); +keepIdx = true(height(dataTable), 1); +outlierRecords = []; + +for groupIdx = 1:height(groupKeys) + groupRows = (G == groupIdx); + y_values = dataTable.(y_var)(groupRows); + + % Skip groups with fewer than 3 points + if sum(groupRows) < 3 + continue; + end + + % Detect outliers in log10 space (robust for BER, etc.) + y_log = log10(y_values); + outlierMask = isoutlier(y_log, 'quartiles', 1); + + if any(outlierMask) + groupData = dataTable(groupRows, :); + outlierGroupTable = groupData(outlierMask, :); + + % Optionally, add group key values for traceability + for k = 1:numel(fixedVars) + outlierGroupTable.(['Group_', fixedVars{k}]) = repmat(groupKeys{groupIdx, k}, height(outlierGroupTable), 1); + end + + outlierRecords = [outlierRecords; outlierGroupTable]; %#ok + end + + % Mark outliers for removal + groupRowIdx = find(groupRows); + keepIdx(groupRowIdx(outlierMask)) = false; +end + +cleanedTable = dataTable(keepIdx, :); + +if isempty(outlierRecords) + outliersTable = table(); +else + outliersTable = outlierRecords; +end + +nRemoved = sum(~keepIdx); +nTotalOriginal = height(dataTable); +fprintf('Removed %d outliers from the data table (%.2f%% of total %d entries).\n', ... + nRemoved, 100*nRemoved/nTotalOriginal, nTotalOriginal); +end \ No newline at end of file diff --git a/Classes/GifWriter.m b/Classes/GifWriter.m new file mode 100644 index 0000000..c6483db --- /dev/null +++ b/Classes/GifWriter.m @@ -0,0 +1,146 @@ +classdef GifWriter < handle + %GIFWRITER Simple class to create GIFs from figures (parallel-safe) + % + % Example: + % g = GifWriter('Name','mySim','Parallel',true); + % parfor i = 1:10 + % plot(rand(10,1)); + % g.addFrame(1,i); + % end + % g.compile(1); + + properties + Name (1,:) char = 'default' % GIF base name + DelayTime (1,1) double = 0.1 % Frame delay in seconds + Parallel (1,1) logical = false % Enable parallel-safe mode + BaseDir (1,:) char % Base directory for temp frames + OutputDir (1,:) char % Final GIF output directory + end + + methods + %% Constructor + function obj = GifWriter(varargin) + % Parse name/value pairs + p = inputParser; + addParameter(p, 'Name', 'default', @ischar); + addParameter(p, 'DelayTime', 0.1, @isnumeric); + addParameter(p, 'Parallel', false, @islogical); + addParameter(p, 'OutputDir', fullfile(pwd, 'gif_output'), @ischar); + parse(p, varargin{:}); + + obj.Name = p.Results.Name; + obj.DelayTime = p.Results.DelayTime; + obj.Parallel = p.Results.Parallel; + obj.OutputDir = p.Results.OutputDir; + obj.BaseDir = fullfile(obj.OutputDir, 'tmp', obj.Name); + + if ~exist(obj.BaseDir, 'dir'), mkdir(obj.BaseDir); end + if ~exist(obj.OutputDir, 'dir'), mkdir(obj.OutputDir); end + end + + %% + function addFrame(obj, figInput, pos) + %ADDFRAME Add a figure frame to the GIF (supports parallel mode) + % + % Usage: + % obj.addFrame(figHandle) + % obj.addFrame(figNum) + % obj.addFrame(figHandle, pos) % parallel mode + % obj.addFrame(figNum, pos) + % + % In parallel mode, 'pos' must be a unique integer (loop index). + + if nargin < 3, pos = []; end + + % --- Resolve figure handle --- + if isnumeric(figInput) + % User passed a figure number + if ~ishandle(figInput) + warning('GifWriter:addFrame', 'Figure %d not found.', figInput); + return; + end + figHandle = figure(figInput); + elseif isa(figInput, 'matlab.ui.Figure') + figHandle = figInput; + else + error('GifWriter:addFrame:InvalidInput', ... + 'Input must be a figure handle or figure number.'); + end + + % --- Parallel-safe frame writing --- + if obj.Parallel + if isempty(pos) + error('GifWriter:ParallelMode', ... + 'In parallel mode, provide a unique ''pos'' identifier.'); + end + + % Directory for this figure number + frameDir = fullfile(obj.BaseDir, sprintf('fig_%d', figHandle.Number)); + if ~exist(frameDir, 'dir') + mkdir(frameDir); + end + + % File path for this frame + frameFile = fullfile(frameDir, sprintf('frame_%05d.png', pos)); + + % Export to PNG (headless-safe) + exportgraphics(figHandle, frameFile, 'Resolution', 150); + + else + % --- Serial mode: append directly to GIF --- + gifFile = fullfile(obj.OutputDir, ... + sprintf('%s_fig_%d.gif', obj.Name, figHandle.Number)); + + % Export frame temporarily + tmpFile = [tempname, '.png']; + exportgraphics(figHandle, tmpFile, 'Resolution', 150); + img = imread(tmpFile); + delete(tmpFile); + + % Append to GIF + [A, map] = rgb2ind(img, 256); + if ~isfile(gifFile) + imwrite(A, map, gifFile, 'gif', ... + 'LoopCount', Inf, 'DelayTime', obj.DelayTime); + else + imwrite(A, map, gifFile, 'gif', ... + 'WriteMode', 'append', 'DelayTime', obj.DelayTime); + end + end + end + + + + %% Compile all PNGs into a GIF (and clean up) + function compile(obj, fignum) + figDir = fullfile(obj.BaseDir, sprintf('fig_%d', fignum)); + gifFile = fullfile(obj.OutputDir, sprintf('%s_fig_%d.gif', obj.Name, fignum)); + + frames = dir(fullfile(figDir, 'frame_*.png')); + if isempty(frames) + warning('GifWriter:NoFrames', 'No frames found for figure %d.', fignum); + return; + end + + % Sort by frame name + [~, idx] = sort({frames.name}); + frames = frames(idx); + + % Combine into a GIF + for i = 1:numel(frames) + img = imread(fullfile(frames(i).folder, frames(i).name)); + [A, map] = rgb2ind(img, 256); + if i == 1 + imwrite(A, map, gifFile, 'gif', ... + 'LoopCount', Inf, 'DelayTime', obj.DelayTime); + else + imwrite(A, map, gifFile, 'gif', ... + 'WriteMode', 'append', 'DelayTime', obj.DelayTime); + end + end + + % Clean up temporary frames + rmdir(figDir, 's'); + end + end +end diff --git a/Classes/Warehouse_class/classes/Parameter.m b/Classes/Warehouse_class/classes/Parameter.m new file mode 100644 index 0000000..1673241 --- /dev/null +++ b/Classes/Warehouse_class/classes/Parameter.m @@ -0,0 +1,7 @@ +classdef Parameter < StorageParameter + methods + function obj = Parameter(varargin) + obj@StorageParameter(varargin{:}); + end + end +end diff --git a/Classes/Warehouse_class/classes/minimal_example.m b/Classes/Warehouse_class/classes/minimal_example.m new file mode 100644 index 0000000..b106929 --- /dev/null +++ b/Classes/Warehouse_class/classes/minimal_example.m @@ -0,0 +1,40 @@ + +params = struct; +params.sir = [20:2:36]; +params.laser_linewidth = [1e5 1e6 10e6]; +params.pn_key = [1:3]; +params.vp = [0.25,0.5,0.75,1]; +params.vb = [1:0.1:1.8]; +params.rop = -5:0; + +wh = DataStorage(params); +wh.addStorage("ber"); +wh.addStorage("rop_save"); +wh.addStorage("cspr"); +wh.addStorage("mod_out_pow"); + + +cnt = 1; +for sir = params.sir + for lw = params.laser_linewidth + for pnk = params.pn_key + for vp = params.vp + for vb = params.vb + for rop = params.rop + + current_ber = randn; + wh.addValueToStorage(current_ber,'ber',sir,lw,pnk,vp,vb,rop); + wh.addValueToStorage(-10,'rop_save',sir,lw,pnk,vp,vb,rop); + wh.addValueToStorage(10,'cspr',sir,lw,pnk,vp,vb,rop); + wh.addValueToStorage(-6,'mod_out_pow',sir,lw,pnk,vp,vb,rop); + + end + end + end + end + end +end + +wh.getStoValue('ber',20,1e5,2,0.25,1.1,-3) + +wh.showInfo \ No newline at end of file diff --git a/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/CompleteRoutine.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/CompleteRoutine.m new file mode 100644 index 0000000..f55c95f --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/CompleteRoutine.m @@ -0,0 +1,251 @@ +% 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/phase_predist_plots/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m new file mode 100644 index 0000000..170fee9 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/CompleteRoutine_DifferentChannels_Fig3.m @@ -0,0 +1,250 @@ +% 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/phase_predist_plots/fwm_plots/automate_JLT_plots.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/automate_JLT_plots.m new file mode 100644 index 0000000..c29a009 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/automate_JLT_plots.m @@ -0,0 +1,415 @@ + + +%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; + +% fields = fieldnames(wh.parameter); +% for k = 1:numel(fields) +% oldParam = wh.parameter.(fields{k}); +% % copy over the properties to your new class +% wh.parameter.(fields{k}) = StorageParameter(... +% oldParam.Name, oldParam.values); +% end +% + + +plotJob = struct(); +width = 350; +height = 200; +plotJob.Position = [100 100 width 100+height]; +cols = cbrewer2("paired",12); +plotJob.color = cols(1,:); +plotJob.l = 10; +plotJob.ch = 16; +plotJob.d = 0; +plotJob.sgm = 0; +plotJob.pol = "copolarized"; +plotJob.p_in = 3; +plotJob.gamma = 0.0023; +plotJob.pmd = 0.1; +plotJob.channelspacing = 400e9; +plotJob.randzdw = 1; + + +plotJob.plot_ber_curve = 0; +plotJob.plot_3dber_curve = 0; +plotJob.plot_violin = 1; +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) + + P = [3]; + for i = 1:2 + plotJob.p_in = P(i); + createviolinplots(wh,plotJob); + end + % 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 = [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:length(P_launch) + + 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.randzdw = 1; + +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 = [4]; +Len = plotJob.l; + +fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); +if isvalid(fig) + figure(fig) + % fig = get(fig); + AxesMain = fig.CurrentAxes; + hold on + % t = tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); +else + fig = figure('name',char(plotJob.figName)); + AxesMain = gca; + hold on; grid on; + % t = tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); +end + +for idx = 1:(numRows * numCols) + % Create subplot + subplot(numRows, numCols, idx); + + 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'); + +% 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/phase_predist_plots/fwm_plots/automate_PTL_plot_new.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/automate_PTL_plot_new.m new file mode 100644 index 0000000..f92f86f --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/automate_PTL_plot_new.m @@ -0,0 +1,137 @@ +% Select dataset +[file, path] = uigetfile("C:\Users\Silas\Documents\MATLAB\Datensätze\Raw_Cluster_Simulations\session_februar_24\wh_mi_nacht.mat"); +wh = load(fullfile(path, file)); +wh = wh.wh; + +%% --- Plot Settings --- +cols = cbrewer2("Paired", 12); + +plotJob = struct(); +plotJob.Position = [100 100 600 400]; +plotJob.channelspacing = 400e9; +plotJob.ch = 16; +plotJob.d = 0; +plotJob.sgm = 0; +plotJob.gamma = 0.0023; +plotJob.pmd = 0.1; +plotJob.randzdw = 1; +plotJob.plot_ber_curve = 1; +plotJob.xAxisLabel = 'ROP per $\lambda$ [dBm]'; +plotJob.yAxisLabel = 'BER'; +plotJob.figName = 'avg BER_vs_Plaunch_combined'; +plotJob.dataStatArg = 'Lineplot with quartiles';%'All Channels; mean(PMD Realizations)';Lineplot with quartiles +plotJob.plotTypeArg = 'Lines'; +plotJob.displayname = 'bla'; +% --- Parameter combinations --- +Len = [2, 10]; +Pol = ["copolarized", "copolarized", "alternated", "paired"]; +Title = ["CoPol","LS", "API", "PPI"]; +D = [0, 3, 0, 0]; +Sgm = [0, 1, 0, 0]; +% Pol = ["copolarized", "copolarized"]; +% Title = ["CoPol","LS"]; +% D = [0, 3]; +% Sgm = [0, 1]; +colidx = [6,4,2,2]; % color indices for different schemes + +P_launch = [0,3,6]; % input power sweep + +%% --- Create Figure --- + +fig = findall(groot, 'Type', 'figure', 'Name', plotJob.figName); + +if isvalid(fig) + figure(fig) + % fig = get(fig); + AxesMain = fig.CurrentAxes; + hold on + % t = tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); +else + fig = figure('name',char(plotJob.figName)); + AxesMain = gca; + hold on; grid on; + % t = tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); +end + +dsa = ["AVG"]; + +for d = 1 + + plotJob.dataStatArg = dsa(d); + cnt = 1; + + for l = 1:numel(Len) + + subplot(1,2,cnt); + cnt = cnt+1; + + for s = 1:length(P_launch) + + + for p = 1:numel(Title) + + plotJob.l = Len(l); + + plotJob.pol = Pol(p); + plotJob.d = D(p); + plotJob.sgm = Sgm(p); + + % color + style per length + baseColor = cols(colidx(p), :); + + if s == 1 + plotJob.linestyle = '-'; + elseif s == 2 + plotJob.linestyle = '--'; + else + plotJob.linestyle = ':'; + end + + + + if p == 1 + % plotJob.linestyle = '-'; + plotJob.markerstyle = 'o'; + plotJob.markersize = 2; + elseif p == 2 + % plotJob.linestyle = ':'; + plotJob.markerstyle = 'square'; + plotJob.markersize = 2; + elseif p == 3 + % plotJob.linestyle = '-'; + plotJob.markerstyle = 'x'; + plotJob.markersize = 6; + else + % plotJob.linestyle = '-'; + plotJob.markerstyle = 'diamond'; + plotJob.markersize = 2; + end + + % if d == 1 + % plotJob.linestyle = '-'; + % else + % plotJob.linestyle = ':'; + % plotJob.markerstyle = 'none'; + % end + + plotJob.p_in = P_launch(s); + + plotJob.displayname = sprintf('%s',Title(p)); + plotJob.color = baseColor;% * (1 - 0.15*(p-1)); % slight shade for powers + plotCurve(wh, plotJob); + % h = findobj(gca,'Type','Line','-not','Tag','FEC'); + % set(h(p),'DisplayName',sprintf('%s (%.0f km, %.0f dBm)',Title(p),Len(l),P_launch(s))); + % title(sprintf('%d km; %d Channels, \Delta f = %.0f GHz', ... + % plotJob.l, plotJob.ch, plotJob.channelspacing*1e-9)); + end + end + end +end +set(gca, 'YScale', 'log'); +xlabel(plotJob.xAxisLabel); +ylabel(plotJob.yAxisLabel); + +% legend('Interpreter','latex','NumColumns',2,'Location','southoutside'); +grid on; box on; + +copygraphics(fig, 'BackgroundColor','none'); diff --git a/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/dispersion_only.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/dispersion_only.m new file mode 100644 index 0000000..47e5566 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/dispersion_only.m @@ -0,0 +1,83 @@ +%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/phase_predist_plots/fwm_plots/dispersion_validation_miniskript.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/dispersion_validation_miniskript.m new file mode 100644 index 0000000..e0d6843 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/dispersion_validation_miniskript.m @@ -0,0 +1,52 @@ + +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/phase_predist_plots/fwm_plots/generatePlots.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/generatePlots.m new file mode 100644 index 0000000..911b79e --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/generatePlots.m @@ -0,0 +1,61 @@ +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/phase_predist_plots/fwm_plots/plot3dCurve.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plot3dCurve.m new file mode 100644 index 0000000..5929aa6 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plot3dCurve.m @@ -0,0 +1,248 @@ +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/phase_predist_plots/fwm_plots/plotBerVsZDW.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotBerVsZDW.m new file mode 100644 index 0000000..d0d5f06 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotBerVsZDW.m @@ -0,0 +1,300 @@ +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/phase_predist_plots/fwm_plots/plotBerVsZdwFailureRate.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotBerVsZdwFailureRate.m new file mode 100644 index 0000000..069dbc8 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotBerVsZdwFailureRate.m @@ -0,0 +1,157 @@ +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/phase_predist_plots/fwm_plots/plotChannelSpacingAna.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotChannelSpacingAna.m new file mode 100644 index 0000000..b64a827 --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotChannelSpacingAna.m @@ -0,0 +1,51 @@ +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/phase_predist_plots/fwm_plots/plotCurve.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotCurve.m new file mode 100644 index 0000000..be3813e --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotCurve.m @@ -0,0 +1,294 @@ +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; + +markerstyle = 'o'; +linestyle = '-'; + +% 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) = quantile(temp,0.9,"all"); + % ber(xl) = max(temp,[],'all'); + linew = 1.0; + markersz = plotJob.markersize; + markerstyle = plotJob.markerstyle; + linestyle = plotJob.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 = plotJob.markersize; + markerstyle = plotJob.markerstyle; + linestyle = plotJob.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 = plotJob.markersize; + markerstyle = plotJob.markerstyle; + linestyle = plotJob.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; + markerstyle = plotJob.markerstyle; + linestyle = plotJob.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"); + + + % upperq(xl) = 0.5*std(tmp,1,'all','omitnan'); + % lowerq(xl) = 0.5*std(tmp,1,'all','omitnan'); + + upperq(xl) = max(temp(:)); + lowerq(xl) = min(temp(:)); + + if lowerq(xl) == 0 + lowerq(xl) = 1e-8; + end + + % 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 + + cols = cbrewer2('RdBu',size(ber,1)); + 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) + + col = cols(rlz,:); + + 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.06,'linewidth',0.7); + hl.MarkerFaceColor = col; + hl.MarkerSize = 2; + set(hp,'HandleVisibility','off'); + %hp.LineWidth = 1.2; + + ho = outlinebounds(hl,hp); + set(ho, 'linestyle', ':', 'color', col,'Linewidth',0.6); + set(ho,'HandleVisibility','off'); + + % errorbar(xAxis,ber(rlz,:),ber(rlz,:)-lowerq(rlz,:),upperq(rlz,:)-ber(rlz,:),'-o','Color',col,'linewidth',0.7); + + else + + s = plot(xAxis,ber(rlz,:),linestyle,'Marker',markerstyle,'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([-10,-4]); + + +% 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/phase_predist_plots/fwm_plots/plotHistogram.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotHistogram.m new file mode 100644 index 0000000..408cded --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotHistogram.m @@ -0,0 +1,151 @@ +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/phase_predist_plots/fwm_plots/plotViolin.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotViolin.m new file mode 100644 index 0000000..ebe24ad --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plotViolin.m @@ -0,0 +1,206 @@ +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/phase_predist_plots/fwm_plots/plot_ber_distribution.m b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plot_ber_distribution.m new file mode 100644 index 0000000..8abf21b --- /dev/null +++ b/Classes/Warehouse_class/functions/phase_predist_plots/fwm_plots/plot_ber_distribution.m @@ -0,0 +1,58 @@ + +%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/adaption_method.m b/Datatypes/adaption_method.m new file mode 100644 index 0000000..5b4c12c --- /dev/null +++ b/Datatypes/adaption_method.m @@ -0,0 +1,9 @@ +classdef adaption_method < int32 + + enumeration + lms (1) + nlms (2) + rls (3) + end + +end \ No newline at end of file diff --git a/Datatypes/polarization_control_mode.m b/Datatypes/polarization_control_mode.m new file mode 100644 index 0000000..c968fcd --- /dev/null +++ b/Datatypes/polarization_control_mode.m @@ -0,0 +1,10 @@ +classdef polarization_control_mode < int32 + + enumeration + random (1) + rot_angle (2) + rot_power (3) + deactivate (4) + end + +end \ No newline at end of file diff --git a/Datatypes/processingMode.m b/Datatypes/processingMode.m new file mode 100644 index 0000000..c47dc7a --- /dev/null +++ b/Datatypes/processingMode.m @@ -0,0 +1,8 @@ + +classdef processingMode < int32 + enumeration + serial (1) + parallel (2) + end + +end \ No newline at end of file diff --git a/Functions/EQ_structures/dsp_runid.m b/Functions/EQ_structures/dsp_runid.m new file mode 100644 index 0000000..4d207bd --- /dev/null +++ b/Functions/EQ_structures/dsp_runid.m @@ -0,0 +1,316 @@ +function [output] = dsp_runid(run_id, options) + +arguments + run_id + options.append_to_db = 0; + options.max_occurences = 4; + options.parameters = struct(); + options.database_type + options.dataBase + options.load_file_path = struct(); + options.storage_path + options.mode +end + +try + % Initialize output structures + output.ffe_package = {}; + output.mlse_package = {}; + output.vnle_package = {}; + output.dbtgt_package = {}; + output.dbenc_package = {}; + output.mlmlse_package = {}; + + if options.mode == "load_run_id" || options.append_to_db + % Initialize database connection + database = DBHandler("dataBase", [options.dataBase], "type", options.database_type ); + + if 0 + % 2. Check if an equalizer configuration with the same hash exists + queryStr = sprintf('SELECT COUNT(DISTINCT eq_id) AS unique_eq_count, COUNT(*) AS entries_for_run FROM `Results` WHERE run_id = %d', run_id); + existing_results = database.fetch(queryStr); + + if existing_results.unique_eq_count >= 6 + if (existing_results.entries_for_run / existing_results.unique_eq_count) > 5 + return + end + end + end + + end + + if options.mode == "load_run_id" + + dataTable = queryRunid(run_id, database); + fsym = dataTable.symbolrate; + M = double(dataTable.pam_level); + duob_mode = db_mode(strrep(dataTable.db_mode,'"','')); + + % if database.checkIfRunExists('Results','run_id',run_id) + % disp(['Already got at least one reulst for run id: ',num2str(run_id),' ']) + % return + % end + + % Load and Sync signal data from DB + [Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, options); + + elseif options.mode == "load_files" + + Tx_bits = load(options.load_file_path.tx_bits_path); + Symbols = load(options.load_file_path.tx_symbols_path); + Scpe_sig_raw = load(options.load_file_path.rx_raw_path); + + Tx_bits = Tx_bits.Bits; + Symbols = Symbols.Symbols; + Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; + + fsym = Symbols.fs; + M = Symbols.logbook.ModifierCopy{1}.M; + duob_mode = Symbols.logbook.ModifierCopy{1}.duobinary_mode; + + 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", fsym, "debug_plots", 1); + + else + + % Run quick Simulation + tx_simulation; + + end + + % Handle Settings and argument replacement + + len_tr = 4096*2; + + ffe_order = [50, 5, 5]; + dfe_order = [0, 0, 0]; + pf_ncoeffs = 1; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + mu_dc = 0.005; + dc_buffer_len = 1; + + mu_tr = 0; + mu_dd = 0.05; + adaption= 1; + use_dd_mode = 1; + + use_ffe = 0; + use_dfe = 0; + use_vnle_mlse = 0; + use_dbtgt = 0; + use_dbenc = 0; + use_ml_mlse = 1; + + addProcessingResultToDatabase = 0; + + % Overwrite default parameters if given in options.parameters + paramStruct = options.parameters; + if ~isempty(paramStruct) + paramNames = fieldnames(paramStruct); + for i = 1:numel(paramNames) + thisName = paramNames{i}; + thisValue = paramStruct.(thisName); + eval([thisName ' = thisValue;']); + end + end + + % Configure equalizers + + options.max_occurences = min(options.max_occurences,length(Scpe_cell)); + for r = 1:options.max_occurences + + %FFE + % eq_dfe = 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); + + + % + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + % mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + + 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,"adaption_technique","lms"); + eq_post = 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); + % Duobinary signaling (db encoded) + mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); + eq_db_enc = EQ("Ne", ffe_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); + + % Preprocess signal + Scpe_sig = preprocessSignal(Scpe_cell{r}, Symbols, fsym); + + Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-6); + + Scpe_sig.spectrum("fignum",201,"normalizeTo0dB",0,"displayname",'Rx'); + + ylim([-30,3]); + xlim([-5,100]); + % Scpe_sig.spectrum("fignum",22233,"normalizeTo0dB",0,"displayname",'Rx'); + % Scpe_sig.eye(fsym,M,"fignum",1024); + + if duob_mode ~= db_mode.db_encoded + + if use_ffe + + ffe_order = [50, 0, 0]; + eq_dfe = EQ("Ne",ffe_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); + + ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + output.ffe_package{r} = ffe_results; + + ffe_results.metrics.print; + ffe_results.config.equalizer_structure = "ffe"; + + if options.append_to_db + database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config); + end + + end + + if use_dfe + + ffe_order = [50, 5, 5]; + eq_dfe = EQ("Ne",ffe_order,"Nb",[2,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); + + dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + output.ffe_package{r} = dfe_results; + dfe_results.config.equalizer_structure = "dfe"; + + dfe_results.metrics.print; + + if options.append_to_db + database.addProcessingResult(run_id, dfe_results.metrics, dfe_results.config); + end + + end + + if use_vnle_mlse + + pf_ncoeffs = 1; + ffe_order = [50, 5, 5]; + eq_ = EQ("Ne",ffe_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",0); + % eq_ = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0.0004,"order",[50,5,5],"sps",2,"decide",0); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + useviterbi = 0; + if useviterbi + mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + else + + if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation + trellexlusion = 1; + else + trellexlusion = 0; + end + + %state_mode 3 -> stat lvl; state_mode 2 -> use target lvls + %scale_mode 2 -> mmse adaption + + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2); + + end + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + ffe_results.metrics.print; + ffe_results.config.equalizer_structure = "vnle"; + mlse_results.metrics.print; + + output.mlse_package{r} = mlse_results; + output.vnle_package{r} = ffe_results; + + if options.append_to_db + database.addProcessingResult(run_id, mlse_results.metrics, mlse_results.config); + database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config); + end + + end + + if use_ml_mlse + + %ML-based MLSE (L=2) + mu_ml = 0.01; training_epochs = 100; + ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",length(Scpe_sig),"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0); + + [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits,"precode_mode",duob_mode); + output.mlmlse_package{r} = ml_mlse_results; + + if options.append_to_db + database.addProcessingResult(run_id, ml_mlse_results.metrics, ml_mlse_results.config); + end + + end + + + if use_dbtgt + + useviterbi = 0; + if useviterbi + mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + else + if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation + trellexlusion = 1; + else + trellexlusion = 0; + end + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3); + end + ffe_order = [50, 5, 5]; + eq_ = EQ("Ne",ffe_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); + + dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode, ... + 'showAnalysis', 0,... + "postFFE", []); + + dbt_results.metrics.print; + + output.dbtgt_package{r} = dbt_results; + + if options.append_to_db + database.addProcessingResult(run_id, dbt_results.metrics, dbt_results.config); + end + + end + end + + if duob_mode == db_mode.db_encoded + + mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); + + db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",0,"postFFE",[]); + output.dbenc_package{r} = db_results; + if options.append_to_db + database.addProcessingResult(run_id, db_results.metrics, db_results.config); + end + + end + + + end + +catch ME + save('workerError.mat','ME'); + rethrow(ME); +end + + +end \ No newline at end of file diff --git a/Functions/EQ_structures/ffe.m b/Functions/EQ_structures/ffe.m new file mode 100644 index 0000000..1f927b1 --- /dev/null +++ b/Functions/EQ_structures/ffe.m @@ -0,0 +1,192 @@ +function [ffe_results] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options) +% FFE Processes signals through FFE equalizer +% +% Inputs: +% eq_ - Equalizer 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 processing + +arguments + eq_ + 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 +% FFE or VNLE +[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols); + +% Apply post-FFE if provided +if ~isempty(options.postFFE) + tic + [eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols); + toc +end + +try + ch_coefficients = arburg(eq_noise.signal,1); + channel_alpha = ch_coefficients(2); +end + +% Hard decision on FFE output +eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd); + +%% Calculate BER based on precoding mode +[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping); + +%% Calculate performance metrics +[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal); +% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000); +[gmi] = calc_ngmi(eq_signal_sd,tx_symbols); +gmi = max(gmi,0); + +air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M)); +[evm_total, evm_lvl] = calc_evm(eq_signal_sd, tx_symbols); +[std_total, std_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); + +%% Display analysis if requested +if options.showAnalysis + displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, options.postFFE); +end + + +%% Prepare output structure +% Determine postFFE order +if ~isempty(options.postFFE) + npostFFE = options.postFFE.order; +else + npostFFE = 0; +end + +% Create FFE results structure +ffe_results = struct(); +try + eq_.e = []; + eq_.e2 = []; + eq_.e3 = []; + eq_.b = []; + eq_.b2 = []; + eq_.b3 = []; +end + +ffe_results.config = Equalizerstruct(); +ffe_results.config.eq = jsonencode(eq_); +ffe_results.config.equalizer_structure = int32(equalizer_structure.ffe); +ffe_results.config.comment = 'function: ffe'; + +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 = ber; +ffe_results.metrics.numBits = bits; +ffe_results.metrics.numBitErr = errors; +ffe_results.metrics.BER_precoded = ber_precoded; +ffe_results.metrics.numBitErr_precoded = errors_precoded; +ffe_results.metrics.SNR = snr; +ffe_results.metrics.SNR_level = snr_lvl; +ffe_results.metrics.STD = std_total; +ffe_results.metrics.STD_level = std_lvl; +ffe_results.metrics.STDrx = std_rxraw_total; +ffe_results.metrics.STDrx_level = std_rxraw_lvl; +ffe_results.metrics.GMI = gmi; +ffe_results.metrics.AIR = air; +ffe_results.metrics.EVM = evm_total; +ffe_results.metrics.EVM_level = evm_lvl; +ffe_results.metrics.Alpha = channel_alpha; + + +end + +%% Helper Functions +function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style) +% Calculate BER based on precoding mode +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); + + 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 = mapper.demap(eq_signal_hd_precoded); + [~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1); + + % B) Just determine BER + rx_bits = mapper.demap(eq_signal_hd); + [bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 30000, "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_decoded = mapper.demap(eq_signal_hd_decoded); + [~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "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 = mapper.demap(eq_signal_hd); + [bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1); +end +end + +function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, postFFE) + % Display analysis plots and metrics + + % Initialize figure handles + % Corrected line - added tx_symbols as second positional argument + showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 100); + + warning off + showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 101); + figure(gcf);hold on; plot(((1:length(eq_noise.signal)) / eq_noise.fs) * 1e6,movmean(eq_noise.signal,2000,1), 'LineWidth',3,'Color','black') + warning on + + showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 102); + + showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE'); + + % Figure 2: Post-FFE coefficients (if available) + if ~isempty(postFFE) + showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104); + end + + try + figure(339); + showEQfilter(eq_.e_tr, eq_signal_sd.fs.*2,"displayname",'training','fignum',339); + showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'dec. directed','fignum',339); + legend on + end + + try + figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training'); + + figure(241); hold on; plot(pow2db(movmean(eq_.debug_struct.update_tr',100)));title('update step training'); + + figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd'); + end + % eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105); + +end \ No newline at end of file diff --git a/Functions/EQ_structures/ml_mlse.m b/Functions/EQ_structures/ml_mlse.m new file mode 100644 index 0000000..f2d468d --- /dev/null +++ b/Functions/EQ_structures/ml_mlse.m @@ -0,0 +1,140 @@ +function [ml_mlse_results] = ml_mlse(eq_, M, rx_signal, tx_symbols, tx_bits, options) +% +% +% Inputs: +% eq_ - Equalizer 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 processing + +arguments + eq_ + M + rx_signal + tx_symbols + tx_bits + options.precode_mode db_mode + options.eth_style_symbol_mapping = 0; + options.postFFE = []; + +end + +%% Process signals through equalizer + +[eq_signal_hd,y_ref] = eq_.process(rx_signal,tx_symbols); + +%% Calculate BER based on precoding mode +[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping); + + +% Create FFE results structure +ml_mlse_results = struct(); +try + eq_.e = []; + eq_.e2 = []; + eq_.e3 = []; + eq_.b = []; + eq_.b2 = []; + eq_.b3 = []; +end + +ml_mlse_results.config = Equalizerstruct(); + +eq_small = strip_eq(eq_, 10); +json_str = jsonencode(eq_small); + +ml_mlse_results.config.eq = jsonencode(eq_); +ml_mlse_results.config.equalizer_structure = int32(equalizer_structure.ml_mlse); +ml_mlse_results.config.comment = 'function: ML-based MLSE'; + +ml_mlse_results.metrics = Metricstruct; +% ml_mlse_results.metrics.result_id = NaN; +% ml_mlse_results.metrics.run_id = NaN; +% ml_mlse_results.metrics.eqParam_id = NaN; +ml_mlse_results.metrics.date_of_processing = datetime('now'); +ml_mlse_results.metrics.BER = ber; +ml_mlse_results.metrics.numBits = bits; +ml_mlse_results.metrics.numBitErr = errors; +ml_mlse_results.metrics.BER_precoded = ber_precoded; +ml_mlse_results.metrics.numBitErr_precoded = errors_precoded; +% ml_mlse_results.metrics.SNR = NaN; +% ml_mlse_results.metrics.SNR_level = NaN; +% ml_mlse_results.metrics.STD = NaN; +% ml_mlse_results.metrics.STD_level = NaN; +% ml_mlse_results.metrics.STDrx = NaN; +% ml_mlse_results.metrics.STDrx_level = NaN; +% ml_mlse_results.metrics.GMI = NaN; +% ml_mlse_results.metrics.AIR = NaN; +% ml_mlse_results.metrics.EVM = NaN; +% ml_mlse_results.metrics.EVM_level = NaN; +% ml_mlse_results.metrics.Alpha = NaN; + + +end + +%% Helper Functions +function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style) +% Calculate BER based on precoding mode +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); + + 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 = mapper.demap(eq_signal_hd_precoded); + [~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1); + + % B) Just determine BER + rx_bits = mapper.demap(eq_signal_hd); + [bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 30000, "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_decoded = mapper.demap(eq_signal_hd_decoded); + [~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "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 = mapper.demap(eq_signal_hd); + [bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1); +end +end + + +function eq_out = strip_eq(eq_, max_elems) + % strip_eq removes all large fields from the ML_MLSE object + % eq_out = strip_eq(eq_, max_elems) + % max_elems ... maximum number of elements to keep (default = 10) + + if nargin < 2 + max_elems = 10; % default threshold + end + + props = properties(eq_); + for i = 1:numel(props) + val = eq_.(props{i}); + if ~isempty(val) + % Count total number of elements + if numel(val) > max_elems + eq_.(props{i}) = []; + end + end + end + eq_out = eq_; +end diff --git a/Functions/EQ_visuals/show2Dconstellation.m b/Functions/EQ_visuals/show2Dconstellation.m new file mode 100644 index 0000000..8596676 --- /dev/null +++ b/Functions/EQ_visuals/show2Dconstellation.m @@ -0,0 +1,85 @@ +function [symbols_for_lvl,avg_for_lvl] = show2Dconstellation(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 +end + +plot_shit = 1; + +if isa(eq_signal,'Signal') + eq_signal = eq_signal.signal; +end +if isa(ref_symbols,'Signal') + ref_symbols = ref_symbols.signal; +end + + +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 + + +rx_symbols = eq_signal; %./ rms(eq_signal); +correct_symbols = ref_symbols; + +col = cbrewer2('Paired',numel(unique(correct_symbols))*2); +ccnt = -1; + +levels = unique(correct_symbols); +M = numel(levels); +symbols_for_lvl = NaN(numel(levels),length(correct_symbols)); +start = 1; +ende = length(correct_symbols); + + + + +for l = 1:numel(levels) + ccnt = ccnt+2; + + level_amplitude = levels(l); + + symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); + + statistical_mean(l) = mean(symbols_for_lvl(l,:),'omitnan'); + +end + +tx_even = correct_symbols(1:2:end); +tx_odd = correct_symbols(2:2:end); + +rx_even = rx_symbols(1:2:end); +rx_odd = rx_symbols(2:2:end); + +D_even = tx_even - rx_even; +D_odd = tx_odd - rx_odd; + +D = sqrt(D_even.^2 + D_odd.^2); + +[X,Y] = meshgrid(levels, levels); +[X_,Y_] = meshgrid(statistical_mean, statistical_mean); + +hold on; +scatter(rx_even,rx_odd,5*ones(1,length(D)),D,'.','DisplayName',['Even/Odd PAM-',num2str(M)],'MarkerEdgeColor',col(2,:)); +% colormap(gca,flip(cbrewer2('Spectral',100))) +colormap(gca,'hsv'); +scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(6,:),'DisplayName','Statistical Rx Levels'); +scatter(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels'); +xlim([floor(min(X_(:)))-1, ceil(max(X_(:)))+1]); +ylim([floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1]); + +yticks((levels(1:end-1) + levels(2:end)) / 2); +xticks((levels(1:end-1) + levels(2:end)) / 2); +legend +xlabel('even symbols'); +ylabel('odd symbols'); +axis equal; grid on; +end diff --git a/Functions/Job_Processing/loadAndSyncSignalDataFromDb.m b/Functions/Job_Processing/loadAndSyncSignalDataFromDb.m new file mode 100644 index 0000000..c10f1d7 --- /dev/null +++ b/Functions/Job_Processing/loadAndSyncSignalDataFromDb.m @@ -0,0 +1,114 @@ +function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncSignalDataFromDb(dataTable, options) +% LOADSIGNALDATA Loads and synchronizes signal data from storage +% +% Inputs:d +% dataTable - Table with file paths and configuration +% options - Struct with storage_path and max_occurences +% +% Outputs: +% Symbols_mapped - Mapped symbols from bits +% Symbols - Original symbols +% Scpe_cell - Cell array of synchronized signals +% found_sync - Boolean indicating if synchronization was successful + +found_sync = 0; +tempLocalStorage = 1; + +% Define the fixed storage directory relative to the user's MATLAB preferences directory +storage_dir = fullfile(prefdir, 'temp_sync_data'); + +% Part A: Check and load from local storage if available- +if tempLocalStorage == 1 + local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id))); + if exist(local_filename, 'file') + % Load from local storage and return + try + load(local_filename, 'Bits', 'Symbols', 'Scpe_cell'); + found_sync = 1; + return + catch + delete(local_filename); + end + end +end + +% If not locally saved, load from storage +if ~found_sync + % Load transmitted bits + Bits = load(fullfile([options.storage_path, char(dataTable.tx_bits_path)])); + Bits = Bits.Bits; + + % Map bits to symbols + M = double(dataTable.pam_level); + fsym = dataTable.symbolrate; + Symbols_mapped = PAMmapper(M,0).map(Bits); + Symbols_mapped.fs = fsym; + + % Load original symbols + Symbols = load(fullfile([options.storage_path, char(dataTable.tx_symbols_path)])); + Symbols = Symbols.Symbols; + + found_sync = 0; + Scpe_cell = {}; + + % Try to load pre-synchronized data + try + Scpe_load = load(fullfile([options.storage_path, char(dataTable.rx_sync_path)])); + Scpe_cell = Scpe_load.S; + [~,~,~,found_sync] = Scpe_cell{2}.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1); + catch + % Continue to next method if this fails + end +end + +% If not found, try with raw data +if ~found_sync + try + 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", fsym, "debug_plots", 1); + catch + % Continue to next method if this fails + end +end + +% Last attempt with mapped symbols +if ~found_sync && exist('Scpe_sig_raw', 'var') + if length(Symbols_mapped.signal) ~= sum(Symbols_mapped.signal == Symbols.signal) + [~, Scpe_cell, ~, found_sync] = Scpe_sig_raw.tsynch("reference", Symbols_mapped, "fs_ref", fsym, "debug_plots", 0); + end +end + +% Part B: Save to local storage if data was loaded and synced +if tempLocalStorage == 1 && found_sync + % Create directory if it doesn't exist + if ~exist(storage_dir, 'dir') + mkdir(storage_dir); + end + +% local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id))); + + % List existing files and remove oldest if more than N + max_local_files = 10; % Store up to N files + files = dir(fullfile(storage_dir, 'sync_data_run_*.mat')); + if length(files) >= max_local_files + % Sort by date + [~, idx] = sort([files.datenum]); + % Delete oldest file + delete(fullfile(storage_dir, files(idx(1)).name)); + end + + % Save current data + save(local_filename, 'Bits', 'Symbols', 'Scpe_cell'); +end + +% Limit number of occurrences +if found_sync + record_realizations = min(options.max_occurences, length(Scpe_cell)); + Scpe_cell = Scpe_cell(1:record_realizations); +else + warning('Could not synchronize the received signal with the stored symbols!'); +end + +end \ No newline at end of file diff --git a/Functions/Job_Processing/preprocessSignal.m b/Functions/Job_Processing/preprocessSignal.m new file mode 100644 index 0000000..6932697 --- /dev/null +++ b/Functions/Job_Processing/preprocessSignal.m @@ -0,0 +1,32 @@ +function Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym) +% PREPROCESSSIGNAL Performs standard preprocessing on a signal +% +% Inputs: +% Scpe_sig - Input signal +% Symbols - Reference symbols for synchronization +% fsym - Symbol frequency +% +% Outputs: +% Scpe_sig - Preprocessed signal + +% Resample to 2x symbol rate +Scpe_sig = Scpe_sig.resample("fs_out", 2*fsym); + +% Synchronize with reference +[Scpe_sig, ~] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0); + +% Apply Gaussian filter +if 1 + Scpe_sig = Filter('filtdegree', 8, "f_cutoff", Symbols.fs.*0.52, ... + "fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ... + "active", true).process(Scpe_sig); +else + Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ... + "fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ... + "active", true).process(Scpe_sig); +end + +%Remove DC offset +Scpe_sig = Scpe_sig - mean(Scpe_sig.signal); + +end \ No newline at end of file diff --git a/Functions/Job_Processing/printResults.m b/Functions/Job_Processing/printResults.m new file mode 100644 index 0000000..4cd1ddc --- /dev/null +++ b/Functions/Job_Processing/printResults.m @@ -0,0 +1,47 @@ +function printResults(run_id, M, Symbols, vnle_pf_package, dbtgt_package, occ) + % PRINTRESULTS Prints formatted results from equalization + % + % Inputs: + % run_id - Run identifier + % M - PAM level + % Symbols - Symbol data with fs property + % vnle_pf_package - VNLE+PF results package + % dbtgt_package - DB target results package (optional) + % occ - Occurrence index + + % Print header + fprintf("==== EQUALIZATION RUN-ID %d | PAM-%d | %.2f GBd ====\n\n", run_id, M, Symbols.fs.*1e-9); + + % VNLE Results + if ~isempty(vnle_pf_package) + vnle_result = vnle_pf_package{occ}.resultsVNLE; + mlse_result = vnle_pf_package{occ}.resultsMLSE; + + fprintf(">> VNLE Results:\n"); + 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_result.AIR.*1e-9); + fprintf("\n"); + + % MLSE Results + fprintf(">> MLSE Results:\n"); + 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 + + % DB Target Results + if ~isempty(dbtgt_package) + dbtgt = dbtgt_package{occ}.resultsDBtgt; + fprintf(">> DB Target Results:\n"); + fprintf(" BER: %.2e\n", dbtgt.BER); + fprintf(" BER (pre-code): %.2e\n", dbtgt.BER_precoded); + fprintf("\n"); + end + + fprintf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n\n"); + end \ No newline at end of file diff --git a/Functions/Job_Processing/queryRunid.m b/Functions/Job_Processing/queryRunid.m new file mode 100644 index 0000000..7a0e048 --- /dev/null +++ b/Functions/Job_Processing/queryRunid.m @@ -0,0 +1,11 @@ +function dataTable = queryRunid(run_id, database) + + % Query database for run configuration + filterParams = database.tables; + filterParams.Runs = struct('run_id', run_id); + + [dataTable, ~] = database.queryDB(filterParams, database.getTableFieldNames('Runs')); + [~, uniqueIdx] = unique(dataTable.run_id); % Get unique run_id indices + dataTable = dataTable(uniqueIdx, :); % Extract unique configurations for each run_id + +end \ No newline at end of file diff --git a/Functions/Job_Processing/submitJobs.m b/Functions/Job_Processing/submitJobs.m new file mode 100644 index 0000000..5826814 --- /dev/null +++ b/Functions/Job_Processing/submitJobs.m @@ -0,0 +1,235 @@ +function [results, wh] = submitJobs(run_ids, dsp_options, submit_mode, submit_options) +% SUBMITJOBS Submits dsp_runid jobs for processing (parallel or serial) +% +% [results, wh] = submitJobs(run_ids, dsp_options, submit_mode, submit_options) +% +% run_ids : scalar or vector of run IDs +% dsp_options : struct of dsp_runid name/value options +% submit_mode : processingMode.parallel or .serial +% submit_options : struct with fields +% .waitbar (logical) +% .wh (DataStorage object) +% +% results : cell(nJobsPerRunId, nRunIds) +% wh : updated DataStorage + +arguments + run_ids int32 = 0 + dsp_options struct = struct() + submit_mode processingMode = processingMode.serial + submit_options.waitbar (1,1) logical = true + submit_options.wh = DataStorage(struct()) +end + +% Normalize +run_ids = run_ids(:)'; +nRunIds = numel(run_ids); +nJobsPerRunId = submit_options.wh.getLastLinIndice(); +totalJobs = nRunIds * nJobsPerRunId; + +% Preallocate +results = cell(nJobsPerRunId, nRunIds); +futures = parallel.FevalFuture.empty(totalJobs,0); +jobIndices = zeros(totalJobs,2); + +% Optional waitbar +if submit_options.waitbar + h = waitbar(0, 'Processing Jobs...'); + cleanupObj = onCleanup(@() delete(h)); +end + +switch submit_mode + case processingMode.parallel + %—– SET UP POOL & QUEUE —– + p = setupParallelPool(11, 300); % 10 workers, 300s idle timeout + + % === submit all futures === + jobCounter = 0; + for r = 1:nRunIds + for k = 1:nJobsPerRunId + jobCounter = jobCounter + 1; + jobIndices(jobCounter,:) = [r,k]; + + opt = buildOptionalVars(k, submit_options.wh); + futures(jobCounter) = parfeval( ... + p, @dsp_runid, 1, ... + run_ids(r), ... + "database_type", dsp_options.database_type, ... + "dataBase", dsp_options.dataBase, ... + "append_to_db", dsp_options.append_to_db, ... + "load_file_path", dsp_options.load_file_path, ... + "max_occurences", dsp_options.max_occurences, ... + "storage_path", dsp_options.storage_path, ... + "mode", dsp_options.mode, ... + "parameters", opt ... + ); + + fprintf('[RunID %d, Job %d] Submitted to pool.\n', run_ids(r), k); + end + end + + %—– W A I T B A R U P D A T E —– + if submit_options.waitbar + futureArray = futures; + updateWB = @() waitbar( ... + sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray))/totalJobs, ... + h, sprintf('Completed %d/%d jobs', ... + sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray)), totalJobs) ... + ); + afterEach(futureArray, updateWB, 0); + end + + % —– before the loop —– + % Keep track of which futures we've already handled: + consumedIdx = false(totalJobs,1); + + % —– fetch in completion order, handling successes and errors —– + for n = 1:totalJobs + try + % This returns the value AND the linear index in 'futures' + [idx, val] = fetchNext(futures); + + duration = futures(idx).RunningDuration; + startDT = futures(idx).StartDateTime; + finishDT = futures(idx).FinishDateTime; + duration = finishDT - startDT; + + % Mark it consumed + consumedIdx(idx) = true; + + % Map back to (r,k) and store + r = jobIndices(idx,1); + k = jobIndices(idx,2); + + fprintf('[%s] JobID %d/%d (%.1f%%) — %s — RunID %d — Subjob %d — fetched.\n', ... + datestr(now,'yyyy-mm-dd HH:MM:SS'), ... + r, totalJobs, 100*n/totalJobs,char(duration), ... + run_ids(r), k); + + % Update waitbar + if submit_options.waitbar + waitbar(n/totalJobs, h, ... + sprintf('Fetched %d/%d (%.1f% Percent)', n, totalJobs, 100*n/totalJobs)); + drawnow; % force the GUI to refresh + end + + storeResult(val, k, submit_options.wh); + results{k,r} = val; + + catch fetchErr + % fetchNext has already set Read=true on the errored future. + % Find the one Read==true that we have _not_ yet consumed. + readMask = arrayfun(@(f) f.Read, futures); + idxErr = find(readMask & ~consumedIdx', 1); + consumedIdx(idxErr) = true; + + % Pull the _real_ exception out of the future object + errInfo = futures(idxErr).Error; + if iscell(errInfo) + origME = errInfo{1}; + else + origME = errInfo; + end + + % Map back to (r,k) and log + r = jobIndices(idxErr,1); + k = jobIndices(idxErr,2); + handleError(origME, k, run_ids(r)); + results{k,r} = origME; + end + end + + + case processingMode.serial + %—– SERIAL EXECUTION —– + jobCounter = 0; + for r = 1:nRunIds + for k = 1:nJobsPerRunId + jobCounter = jobCounter + 1; + optionalVars = buildOptionalVars(k, submit_options.wh); + try + fprintf('[RunID %d, Job %d] Running in linear mode...\n', run_ids(r), k); + val = dsp_runid( run_ids(r), ... + "database_type", dsp_options.database_type, ... + "dataBase", dsp_options.dataBase, ... + "append_to_db", dsp_options.append_to_db, ... + "load_file_path", dsp_options.load_file_path, ... + "max_occurences", dsp_options.max_occurences, ... + "storage_path", dsp_options.storage_path, ... + "mode", dsp_options.mode, ... + "parameters", optionalVars ); + + fprintf('[RunID %d, Job %d] Completed successfully.\n', run_ids(r), k); + storeResult(val, k, submit_options.wh); + results{k,r} = val; + + catch ME + handleError(ME, k, run_ids(r)); + results{k,r} = ME; + end + + if submit_options.waitbar + waitbar(jobCounter/totalJobs, h, ... + sprintf('Completed %d/%d jobs', jobCounter, totalJobs)); + end + end + end + + otherwise + error('Unknown submit_mode "%s".', string(submit_mode)) +end + +wh = submit_options.wh; + +%% Local helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + function handleError(ME, jobIndex, run_id) + fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', run_id, jobIndex, ... + ME.identifier, ME.message); + for st = ME.stack' + fprintf(' %s:%d (%s)\n', st.file, st.line, st.name); + end + fprintf('Full report:\n%s\n', getReport(ME,'extended')); + end + + function optionalVars = buildOptionalVars(jobIndex, wh) + optionalVars = struct(); + if ~isempty(wh.getDimension()) + [vals, names] = wh.getPhysIndicesByLinIndex(jobIndex); + for pi = 1:numel(names) + optionalVars.(names{pi}) = vals{pi}; + end + end + end + + function storeResult(val, jobIndex, wh) + if ~isempty(wh) + wh.addValueToStorageByLinIdx(val.ffe_package, 'ffe_package', jobIndex); + wh.addValueToStorageByLinIdx(val.mlse_package, 'mlse_package', jobIndex); + wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex); + wh.addValueToStorageByLinIdx(val.dbtgt_package,'dbtgt_package',jobIndex); + wh.addValueToStorageByLinIdx(val.dbenc_package,'dbenc_package',jobIndex); + wh.addValueToStorageByLinIdx(val.mlmlse_package,'mlmlse_package',jobIndex); + end + end + function p = setupParallelPool(numWorkers, idleTimeout) + % Ensure a pool exists at the right size & timeout + p = gcp('nocreate'); + if isempty(p) || p.NumWorkers~=numWorkers + if ~isempty(p) + delete(p); + end + p = parpool('local', numWorkers, 'IdleTimeout', idleTimeout); + end + + % Cancel anything left in the pool's default queue + q = p.FevalQueue; + if ~isempty(q.QueuedFutures) || ~isempty(q.RunningFutures) + cancelAll(q); + fprintf('Canceled %d unfetched jobs from old queue.\n', ... + numel(q.QueuedFutures)+numel(q.RunningFutures)); + end + end + + +end diff --git a/Functions/Metrics/calc_gmi_bitwise.m b/Functions/Metrics/calc_gmi_bitwise.m new file mode 100644 index 0000000..0b9f0b4 --- /dev/null +++ b/Functions/Metrics/calc_gmi_bitwise.m @@ -0,0 +1,158 @@ +function [GMI,NGMI] = calc_gmi_bitwise(test_signal,reference_signal,options) +% https://cioffi-group.stanford.edu/doc/book/AppendixG.pdf +% Supports PAM2/4/8 (per-symbol) and PAM6 (pairwise: 5 bits per 2 symbols) + +arguments(Input) + 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_front = abs(options.skip_front); +assert((options.skip_end+options.skip_front) < numel(test_signal), ... + "You can not skip more samples than overall length."); + +if isa(reference_signal,'Signal'); reference_signal = reference_signal.signal; end +if isa(test_signal,'Signal'); test_signal = test_signal.signal; end + +% TRIM +[test_signal,reference_signal] = trimseq(test_signal,reference_signal,options.skip_front,options.skip_end); + +% Common precompute +N = length(test_signal); +M = numel(unique(reference_signal)); +noise = test_signal - reference_signal; +sigma2 = var(noise); % real AWGN variance + +% --- Constellation (levels observed) +constellation = unique(reference_signal); +M = numel(constellation); + +% --- Empirical P_X (uniform is fine too; keep your original behavior) +N = numel(reference_signal); +counts = arrayfun(@(c) sum(reference_signal==c), constellation); +priors = counts / N; + +% ----- PAM6: 5 bits mapped to 2 consecutive symbols (36 transitions) ----- +if M == 6 + % Pair the stream (drop last if odd) + numPairs = floor(N/2); + if numPairs == 0, GMI = 0; NGMI = 0; return; end + + y1 = test_signal(1:2:2*numPairs); + y2 = test_signal(2:2:2*numPairs); + + % Use the same level order as PAMmapper to be consistent with labeling + levels = constellation;%PAMmapper(6,0,"eth_style",0).levels; % 1x6 (actual amplitudes used) + % Build all 36 transitions (i,j) + [S1,S2] = ndgrid(levels, levels); % 6x6 + trans_pairs = [S1(:), S2(:)]; % 36x2 + + % 5-bit labels for each transition (ETH style). + % NOTE: keep the normalization you use with your PAMmapper (often /sqrt(10)). + bits72 = PAMmapper(6,0,"eth_style",0).demap( reshape(trans_pairs.',[],1) ); + pairBits = reshape(bits72.', 5, []).'; % 36 x 5 + + % Transmitted 5-bit labels for each observed pair (from the reference) + tx_bits_pair = PAMmapper(6,0,"eth_style",0).demap( reshape(reference_signal(1:2* numPairs).',[],1) ); + tx_bits_pair = reshape(tx_bits_pair.', 5, []).'; % numPairs x 5 + + % Precompute symbol log-priors (uniform => constant; included for shaped inputs) + logP = log(priors); + + % Precompute masks for bit=0 / bit=1 in the 6x6 grid + mask1 = false(6,6,5); + mask0 = false(6,6,5); + for b = 1:5 + tmp = false(6,6); + tmp(:) = pairBits(:,b)==1; + mask1(:,:,b) = tmp; + tmp = false(6,6); + tmp(:) = pairBits(:,b)==0; + mask0(:,:,b) = tmp; + end + + % Exact LLRs via log-sum-exp over the 6x6 grid + LLR = zeros(numPairs,5); + cst = -0.5*log(2*pi*sigma2); % cancels in LLR but harmless + for k = 1:numPairs + li = cst - ((y1(k) - levels).^2)/(2*sigma2) + logP; % 1x6 + lj = cst - ((y2(k) - levels).^2)/(2*sigma2) + logP; % 1x6 + logW = li(:) + lj(:).'; % 6x6 (adds logP twice) + + for b = 1:5 + L1 = logsumexp(logW(mask1(:,:,b))); + L0 = logsumexp(logW(mask0(:,:,b))); + LLR(k,b) = L1 - L0; % natural-log LLR + end + end + + % Bit-wise MI using consistency relation + MI_bits = zeros(1,5); + for b = 1:5 + idx0 = (tx_bits_pair(:,b)==0); + idx1 = ~idx0; + r0 = LLR(idx0,b); + r1 = LLR(idx1,b); + I0 = mean( log2(1 + exp( r0)) ); % natural LLR inside exp() + I1 = mean( log2(1 + exp(-r1)) ); + MI_bits(b) = 1 - 0.5*(I0 + I1); + end + + % Per-symbol outputs (5 bits per 2 symbols) + GMI = sum(MI_bits)/2; + NGMI = GMI / 2.5; + +% ----- Generic PAM2/4/8…: per-symbol, Gray bits directly on symbols ----- +else + nBits = log2(M); + levels = PAMmapper(M,0).levels' / PAMmapper(M,0).scaling; % match PAMmapper ordering + grayBits= PAMmapper(M,0).showBitMapping; % M x nBits + + % Allocate + LLR = zeros(N, nBits); + + for n = 1:N + y = test_signal(n); + ll_table = -((y - levels).^2)/(2*sigma2) + log(priors); % 1xM (log-domain) + for b = 1:nBits + idx0 = grayBits(:,b)==0; + idx1 = ~idx0; + L0 = logsumexp(ll_table(idx0)); + L1 = logsumexp(ll_table(idx1)); + LLR(n,b) = L1 - L0; % natural-log LLR + end + end + + tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal); + + % Bit-wise MI + MI_bits = zeros(1,nBits); + for b = 1:nBits + r0 = LLR(tx_bits(:,b)==0,b); + r1 = LLR(tx_bits(:,b)==1,b); + I0 = mean( log2(1 + exp( r0)) ); + I1 = mean( log2(1 + exp(-r1)) ); + MI_bits(b) = 1 - 0.5*(I0 + I1); + end + + GMI = sum(MI_bits); % bits / symbol + NGMI = GMI / nBits; +end + +% ===== Helpers ===== +function s = logsumexp(a) + if isempty(a), s = -inf; return; end + m = max(a(:)); + s = m + log(sum(exp(a(:) - m))); +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/count_error_bursts.m b/Functions/Metrics/count_error_bursts.m new file mode 100644 index 0000000..47d5439 --- /dev/null +++ b/Functions/Metrics/count_error_bursts.m @@ -0,0 +1,31 @@ +function burst_count = count_error_bursts(err_pos, max_burst_length) + % err_pos: Vector of error positions + % max_burst_length: Maximum length for which bursts are counted (e.g., 10) + + % Sort the error positions to ensure they're in increasing order + err_pos = sort(err_pos); + + % Initialize burst_count array to hold the counts for each burst length + burst_count = zeros(1, max_burst_length); + + % Find the differences between consecutive error positions + diffs = diff(err_pos); + + % Find the start of the bursts (where the difference is greater than 1) + burst_starts = [1, find(diffs > 1) + 1]; % Starts at index 1 and after gaps + burst_ends = [find(diffs > 1), length(err_pos)]; % Ends where gaps are + + % Loop over the bursts + for b = 1:length(burst_starts) + burst_length = burst_ends(b) - burst_starts(b) + 1; + + % Check if the burst length exceeds any of the thresholds + for i = 1:max_burst_length + if burst_length == i + burst_count(i) = burst_count(i) + 1; + else + + end + end + end +end diff --git a/Functions/Theory/CCDM/ccdm_gpt_example.m b/Functions/Theory/CCDM/ccdm_gpt_example.m new file mode 100644 index 0000000..bea3bf3 --- /dev/null +++ b/Functions/Theory/CCDM/ccdm_gpt_example.m @@ -0,0 +1,27 @@ +%% Target source entropy for PS-PAM8 +clear; clc; + +M = 8; +a = -(M-1):2:(M-1); % PAM-8 amplitude levels: [-7 -5 -3 -1 1 3 5 7] +H_target = 2.79; % desired entropy [bits/symbol] + +% Objective: find nu such that H(PA) = H_target +f = @(nu) entropy_MB(a,nu) - H_target; +nu_opt = fzero(f, [0, 2]); % search ν in reasonable range + +% Compute final distribution +P = exp(-nu_opt*a.^2); +P = P/sum(P); +H = -sum(P .* log2(P)); + +fprintf('Shaping parameter ν = %.4f\n', nu_opt); +fprintf('Entropy H(A) = %.3f bits/symbol\n', H); +disp('Probability vector (P_A):'); +disp(P.'); + +%% Helper: entropy function +function H = entropy_MB(a,nu) + P = exp(-nu*a.^2); + P = P/sum(P); + H = -sum(P .* log2(P)); +end diff --git a/Functions/Theory/analyze_moving_average_filter.m b/Functions/Theory/analyze_moving_average_filter.m new file mode 100644 index 0000000..43bcc5f --- /dev/null +++ b/Functions/Theory/analyze_moving_average_filter.m @@ -0,0 +1,64 @@ +% Parameters +N_values = [10 100 1000 4096]; % Different filter lengths to analyze +fs = 112e9; + +% Create figure +figure; + +% Plot frequency responses +subplot(211) +hold on +grid on +ylabel('Magnitude (dB)') +title('Frequency Response') +yline(-3,'--r') +ylim([-40 5]) + +subplot(212) +hold on +grid on +xlabel('Frequency (GHz)') +ylabel('Phase (rad)') +title('Phase Response') + +% Color map for different lines +colors = cbrewer2('Set1',length(N_values)); + +% Loop through different filter lengths +for i = 1:length(N_values) + N = N_values(i); + + % Filter coefficients + b = ones(1,N)/N; + a = 1; + + % Frequency response + [h,w] = freqz(b,a,4096*8); + freq = (w/(2*pi))*fs; + h_db = 20*log10(abs(h)); + + % Plot magnitude response + subplot(211) + plot(freq/1e9, h_db, 'Color', colors(i,:), 'DisplayName', sprintf('N=%d', N),'LineWidth',0.1) + + % Plot phase response + subplot(212) + plot(freq/1e9, unwrap(angle(h)), 'Color', colors(i,:), 'DisplayName', sprintf('N=%d', N),'LineWidth',0.1) + + % Find -3dB frequency + cutoff_idx = find(h_db <= -3, 1); + f_cutoff = freq(cutoff_idx)/1e9; + fprintf('N=%d: Cutoff frequency (-3dB point): %.2f GHz\n', N, f_cutoff) +end + +% Add legend and adjust axes +subplot(211) +legend('show') +xlim([0 16]) % Adjust x-axis limit to better see the differences + +subplot(212) +legend('show') +xlim([0 16]) % Adjust x-axis limit to better see the differences + +% Analytical approximation +f_3db_approx = 0.443 * fs./N_values ./ 1e9; \ No newline at end of file diff --git a/Functions/Theory/dispersion_10km.m b/Functions/Theory/dispersion_10km.m new file mode 100644 index 0000000..83fcf29 --- /dev/null +++ b/Functions/Theory/dispersion_10km.m @@ -0,0 +1,89 @@ +%% ============================================================ +% IM/DD Fading Notch – λ_null vs. Bandwidth (Fixed 10 km) +% ============================================================ + +clear; clc; + +%% Fiber and dispersion parameters +lambda0 = 1310e-9; % Zero-dispersion wavelength [m] +S0 = 0.09; % Dispersion slope at ZDW [ps/(nm²·km)] +L = 10e3; % Fiber length [m] +c = physconst('lightspeed'); + +%% Frequency sweep (defines the desired first-fading notch) +f_targets = linspace(40e9, 150e9, 200); % [Hz] +f_GHz = f_targets / 1e9; + +%% Compute wavelength λ_null for each target f_null +[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0); +lambda_nm = lambda_vec * 1e9; % Convert to nm +Dacc = Dacc_vec; % [ps/nm] + +%% ------------------------------------------------------------ +% Plot λ_null vs. f_null for 10 km fiber +% ------------------------------------------------------------ +cols = cbrewer2('Paired',10); +figure('Color','w'); hold on; + +hLine = plot(lambda_nm, f_GHz, ... + 'LineWidth', 2, ... + 'DisplayName', sprintf('L = %.1f km', L/1000), ... + 'Color', cols(2,:)); + +xlabel('Wavelength λ [nm]'); +ylabel('First fading notch f_{null} [GHz]'); +title('IM/DD Fading Notch Position vs. Wavelength'); +grid on; box on; + +lim = (lambda0.*1e9) - [8, 40]; +xlim([lim(2) lim(1)]); +yticks([56,75,90,112]); + +%% ------------------------------------------------------------ +% Custom DataTip Template +% ------------------------------------------------------------ +% Add accumulated dispersion value to the DataTip +hLine.DataTipTemplate.DataTipRows(1).Label = 'λ [nm]'; +hLine.DataTipTemplate.DataTipRows(2).Label = 'f_{null} [GHz]'; + +% Create a new row for Dacc +dRow = dataTipTextRow('D_{acc} [ps/nm]', Dacc); +hLine.DataTipTemplate.DataTipRows(end+1) = dRow; + +%% ------------------------------------------------------------ +% Helper function: lambda_for_first_null_full +% ------------------------------------------------------------ +function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0) + c = physconst('lightspeed'); + S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³) + + lambda_min = 1260e-9; + lambda_max = 1360e-9; + + f_target = f_target(:); + N = numel(f_target); + + lambda_vec = zeros(N,1); + Dacc_vec = zeros(N,1); + + for k = 1:N + RHS = c * 0.5 / (f_target(k)^2 * L); + + fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS; + + try + lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]); + catch + lambda_sol = lambda_min; + end + + lambda_sol = min(max(lambda_sol, lambda_min), lambda_max); + lambda_vec(k) = lambda_sol; + + D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km) + Dacc_val = D_lambda * (L/1000); % ps/nm + Dacc_val = min(max(Dacc_val, -100), 100); + + Dacc_vec(k) = Dacc_val; + end +end diff --git a/Functions/Theory/dispersion_contour.m b/Functions/Theory/dispersion_contour.m new file mode 100644 index 0000000..324dc2d --- /dev/null +++ b/Functions/Theory/dispersion_contour.m @@ -0,0 +1,55 @@ +% Gitter für lambda0 und S0 +lambda0_vec = linspace(1260,1360,200); +S0_vec = linspace(0.06,0.1,200); +[Lambda0, S0] = meshgrid(lambda0_vec, S0_vec); + +% Festen Betriebsparameter +lambda = 1293; % nm +L = 1; % km + +% Dispersion berechnen (lineare Näherung) +D = S0 .* ( lambda - Lambda0 ) * L; +% D = (S0./4) .* ( lambda - (Lambda0.^4)./(lambda^3) ) * L; + +%% 2D-Konturplot nur mit Linien und Text +figure('Color','w'); +hold on + +% Konturlinien +numLevels = 10; +levels = linspace(min(D(:)), max(D(:)), numLevels); +[C,h] = contour(S0, Lambda0, D, levels, ... + 'LineWidth',1.5, ... + 'ShowText','on', ... + 'LabelFormat','%0.1f'); + +% cbrewer2-Colormap für die Linien +cmap = cbrewer2('div','RdYlGn', numLevels); +colormap(cmap); + +% Achsenlinien +% yline(1310, '--k','ZDW_{mean}','LabelVerticalAlignment','top','LabelHorizontalAlignment','center'); +x0 = 0.09; +% xline(x0, '--k','S_{0}','LabelHorizontalAlignment','left'); + +% Gaussian auf der x-Linie (S0 = 0.09) +mu_zwd = 1310; % nm +sigma_zwd = 2; % nm +zwd_vals = linspace(min(lambda0_vec), max(lambda0_vec), 500); +% PDF berechnen +gauss_pdf = (1/(sigma_zwd*sqrt(2*pi))) * exp(-0.5*((zwd_vals-mu_zwd)/sigma_zwd).^2); +% Normieren und auf eine sichtbare Breite skalieren +scale = 0.005; % passt die Maximal-Auslenkung in x-Richtung an +x_gauss = x0 + (gauss_pdf/max(gauss_pdf)) * scale; + +% Plot +% plot(x_gauss, zwd_vals, 'LineWidth',2); + +% Achsenbeschriftung & Titel +% Achsenbeschriftung & Titel +xlabel('S0 [ps / nm2 km]', 'FontSize', 12); +ylabel('ZDW [nm]', 'FontSize', 12); +title (sprintf('Dispersion: %d km; %d nm', L, lambda), 'FontSize', 14); + +grid on +hold off \ No newline at end of file diff --git a/Functions/Theory/dispersion_contour_bandwidth_lambda.m b/Functions/Theory/dispersion_contour_bandwidth_lambda.m new file mode 100644 index 0000000..1f46f0e --- /dev/null +++ b/Functions/Theory/dispersion_contour_bandwidth_lambda.m @@ -0,0 +1,146 @@ +%% ------------------------------------------------------------ +% Contour plot: λ_null as function of bandwidth (f_target) and reach (L) +% ------------------------------------------------------------ + +% Parameters +lambda0 = 1310e-9; % [m] +S0 = 0.08; % [ps/(nm²·km)] +c = physconst('lightspeed'); + +% Sweep dimensions +f_targets = linspace(50e9, 120e9, 100); % [Hz] (x-axis) +L_values = linspace(0.5e3, 10e3, 100); % [m] (y-axis) + +lambda_surface = zeros(numel(L_values), numel(f_targets)); +Dacc_surface = zeros(numel(L_values), numel(f_targets)); + +% Outer loop over fiber length (since L must be scalar) +for iL = 1:numel(L_values) + L = L_values(iL); + [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0); + lambda_vec = 2*abs(lambda0 - lambda_vec); + + if 0 + fprintf('\n- %d km ------------------------------------\n',L); + fprintf(' f_null [GHz] lambda [nm] Dacc [ps/nm]\n'); + fprintf('----------------------------------------------\n'); + fprintf('%10.1f %8.2f %+8.3f\n',[f_targets(:)/1e9, lambda_vec(:)*1e9, Dacc_vec(:)].'); + fprintf('----------------------------------------------\n\n'); + end + + lambda_surface(iL, :) = lambda_vec; % λ for each f_target + Dacc_surface(iL, :) = Dacc_vec; % corresponding accumulated dispersion +end + +% Convert for plotting +lambda_surface_nm = lambda_surface * 1e9; % [nm] +L_km = L_values / 1000; % [km] +f_GHz = f_targets / 1e9; % [GHz] + +%% Contour plot +figure('Color','w'); + +% Define wavelength contour levels [nm] +lambda_levels = [1260:10:1290, 1290:5:1300, 1300:2.5:1310]; +lambda_levels = [100:-20:50, 50:-10:30,30:-5:0]; + +% Contour plot +contour(f_GHz, L_km, lambda_surface_nm, lambda_levels, ... + 'LineWidth', 1.5, ... + 'ShowText', 'on', ... + 'LabelFormat', '%.0f nm'); + +% Colormap and colorbar +colormap((cbrewer2('RdYlGn',100))); +colorbar; +clim([0 100]); + +% Axis formatting +xlabel('Signal Bandwidth [GHz]'); +ylabel('Fiber length [km]'); +legend('$\Delta \lambda$') + +% X-axis ticks at 56 : 16 : 150 GHz +xticks(56:8:150); + +grid on; box on; + + +%% Optional: overlay accumulated-dispersion contours +if 0 + hold on; + [CS, h] = contour(f_GHz, L_km, Dacc_surface, 10, 'k--', 'LineWidth', 0.8); + clabel(CS, h, 'Color','k', 'FontSize',8); +end + +function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0) +% lambda_for_first_null_full (stable, single-branch + validity checks) +% -------------------------------------------------------------------- +% Computes the wavelength(s) at which the first IM/DD fading null +% occurs at frequency/ies f_target using the full dispersion model: +% +% D(lambda) = (S0/4)*(lambda - lambda0^4 / lambda^3) +% +% Restricted to the NORMAL-dispersion branch (λ < λ0), +% and valid only in the O-band (1260–1360 nm). +% +% Inputs: +% f_target - scalar or vector of target null frequencies [Hz] +% L - fiber length [m] +% lambda0 - zero-dispersion wavelength (ZDW) [m] +% S0 - dispersion slope at ZDW [ps/(nm²·km)] +% +% Outputs: +% lambda_vec - wavelength(s) [m] where first null occurs (clamped to O-band) +% Dacc_vec - accumulated dispersion(s) [ps/nm] (NaN if out of valid range) +% -------------------------------------------------------------------- + + c = physconst('lightspeed'); + S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³) + + % Define O-band boundaries (in meters) + lambda_min = 1255e-9; + lambda_max = 1361e-9; + + % Force column vector + f_target = f_target(:); + N = numel(f_target); + + lambda_vec = NaN(N,1); + Dacc_vec = NaN(N,1); + + for k = 1:N + RHS = c * 0.5 / (f_target(k)^2 * L); + + % Normal-dispersion branch (λ < λ0) + fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS; + + % Limit the search to [λ_min, λ0) + try + lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]); + catch + % If the zero is not within bounds, skip this point + lambda_sol = NaN; + end + + % Validate solution + if isnan(lambda_sol) || lambda_sol < lambda_min || lambda_sol > lambda_max + lambda_vec(k) = NaN; + Dacc_vec(k) = NaN; + continue + end + + % Compute D(lambda) and accumulated dispersion + D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km) + Dacc_val = D_lambda * (L/1000); % ps/nm + + % Sanity bound on dispersion (avoid unphysical > ±100 ps/nm) + if abs(Dacc_val) > 100 + lambda_vec(k) = NaN; + Dacc_vec(k) = NaN; + else + lambda_vec(k) = lambda_sol; + Dacc_vec(k) = Dacc_val; + end + end +end diff --git a/Functions/Theory/dispersion_first_notch_10km.m b/Functions/Theory/dispersion_first_notch_10km.m new file mode 100644 index 0000000..96a2149 --- /dev/null +++ b/Functions/Theory/dispersion_first_notch_10km.m @@ -0,0 +1,38 @@ +%% ------------------------------------------------------------ +% Plot: Maximum usable IM/DD bandwidth vs wavelength +% ------------------------------------------------------------ + +% Fiber and dispersion parameters +lambda0 = 1310e-9; % [m] +S0 = 0.08; % [ps/(nm²·km)] +L = 10000; % [m] +c = physconst('lightspeed'); + +% Wavelength range around ZDW +lambda_vec = linspace(1250e-9, 1350e-9, 200); % [m] + +% Compute D(lambda) using full model +lambda_nm = lambda_vec * 1e9; +lambda0_nm = lambda0 * 1e9; +D_lambda = (S0/4) .* (lambda_nm - (lambda0_nm.^4) ./ (lambda_nm.^3)); % [ps/(nm·km)] + +% Convert D to [s/m²] +D_si = D_lambda * 1e-6; + +% Compute first null frequency (f₀) for each wavelength +f_null = sqrt(c*(0.5) ./ (abs(D_si).*lambda_vec.^2*L)); % [Hz] + +% Plot +figure('Color','w'); +plot(lambda_vec*1e9, f_null/1e9, 'LineWidth', 1.6); +grid on; box on; +xlabel('Wavelength [nm]'); +ylabel('First Fading Null Frequency [GHz]'); +title(sprintf('IM/DD Bandwidth Limit vs. Wavelength (L = %.1f km)', L/1000)); + +% Highlight useful bandwidth thresholds +yline(25, '--', '25 GHz','Color',[0.4 0.4 0.4],'LabelHorizontalAlignment','left'); +yline(50, '--', '50 GHz','Color',[0.2 0.6 0.2],'LabelHorizontalAlignment','left'); +yline(100,'--', '100 GHz','Color',[0.6 0.2 0.2],'LabelHorizontalAlignment','left'); + +legend('First fading notch (f_{null})','Location','best'); diff --git a/Functions/Theory/dispersion_power_fading.m b/Functions/Theory/dispersion_power_fading.m new file mode 100644 index 0000000..6ed8fab --- /dev/null +++ b/Functions/Theory/dispersion_power_fading.m @@ -0,0 +1,165 @@ +%% Chromatic Dispersion Power Fading Demonstration +% ------------------------------------------------------------ +% This script computes and visualizes power fading after +% photodiode detection caused by chromatic dispersion in IM/DD links. +% +% It also determines the wavelength λ that produces the first +% fading null at a specified RF frequency f_target using the +% full physical dispersion model: +% +% D(λ) = (S0/4) * (λ - λ0^4 / λ^3) +% +% and compares the analytic null frequency with simulation. +% ------------------------------------------------------------ + +% clear; close all; clc; + +%% Fiber and wavelength parameters +lambda0 = 1310e-9; % Zero-dispersion wavelength (ZDW) [m] +S0 = 0.08; % Dispersion slope at ZDW [ps/(nm^2·km)] +L = 10000; % Fiber length [m] +alpha_dB = 0; % Attenuation [dB/m] (ignored here) + +%% Target null frequency +f_targets = linspace(55e9,58e9,10); +f_targets = 56e9; +% f_targets = 80e9; +% Compute wavelength that gives the first null at f_target +[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0); +% lambda_vec = 1293e-9; + + +fprintf('\n----------------------------------------------\n'); +fprintf(' f_null [GHz] lambda [nm] Dacc [ps/nm]\n'); +fprintf('----------------------------------------------\n'); +fprintf('%10.1f %8.2f %+8.3f\n',[f_targets(:)/1e9, lambda_vec(:)*1e9, Dacc_vec(:)].'); +fprintf('----------------------------------------------\n\n'); + +%% Frequency grid +f_simu = 500e9; % Simulation bandwidth [Hz] +N_freq = 500000; +faxis = linspace(-f_simu/2, f_simu/2, N_freq); + +%% Derived fiber parameters +c = physconst('lightspeed'); +S0_si = S0 * 1e3; % ps/(nm²·km) -> s/m³ + +% Convert wavelengths to nm for the D(lambda) model +lambda_nm = lambda_vec(end) * 1e9; +lambda0_nm = lambda0 * 1e9; + +% Dispersion parameter [ps/(nm·km)] +D_lambda = (S0/4) * (lambda_nm - (lambda0_nm^4)/(lambda_nm^3)); + +% Convert to [s/m²] +D_si = D_lambda * 1e-6; + +% β2 in [s²/m] +b2 = -D_si * lambda_vec(end)^2 / (2*pi*c); + +%% IM/DD intensity response (simulation) +phi = 2*pi^2*b2*faxis.^2*L; +H_field_pos = exp(-1j*phi); % +f sideband +H_field_neg = exp(+1j*phi); % -f sideband +H_intensity = 0.5 * (H_field_pos + H_field_neg); % PD beating term +H_sim = abs(H_intensity); + +%% Theoretical analytical IM/DD response +phi = 2*pi^2 * abs(b2) * faxis.^2 * L; +H_theoretical = abs(cos(phi)); + +%% Analytic first null (for verification) +f_null_analytic = sqrt(c*(0.5)/(abs(D_si)*lambda_vec(end)^2*L)); +fprintf('Analytic first null from D,λ,L: %.2f GHz\n\n', f_null_analytic/1e9); + +%% Plot +cols = linspecer(5); +figure('Color','w'); hold on; grid on; box on; +plot(faxis*1e-9, 10*log10(H_sim), 'DisplayName','$|H_{sim}|$ (IM/DD simulation)','Color',cols(1,:)); +plot(faxis*1e-9, 10*log10(H_theoretical), 'DisplayName','|cos($\phi$)| (theory)','Color',cols(2,:),'LineStyle','--'); +xline(f_targets(end)/1e9,'k:','LineWidth',1.2,'DisplayName','Target null (56 GHz)'); +xline(f_null_analytic/1e9,'Color',[0.2 0.6 0.2],'LineStyle','-.','LineWidth',1.2,'DisplayName','Analytic null'); +xlabel('Frequency [GHz]'); +ylabel('Magnitude [dB]'); +title(sprintf('Power Fading for %.2f nm, L = %.1f km',lambda_nm,L/1000)); +legend('Location','best'); ylim([-30 0]); + +%% Plot Bandwidth vs Lambda max + +figure(); +hold on; +plot(lambda_vec.*1e6,f_targets.*1e-9) +xlabel('wavelength'); +ylabel('max. Bandwidth') + +function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0) +% lambda_for_first_null_full (stable, single-branch + validity checks) +% -------------------------------------------------------------------- +% Computes the wavelength(s) at which the first IM/DD fading null +% occurs at frequency/ies f_target using the full dispersion model: +% +% D(lambda) = (S0/4)*(lambda - lambda0^4 / lambda^3) +% +% Restricted to the NORMAL-dispersion branch (λ < λ0), +% and valid only in the O-band (1260–1360 nm). +% +% Inputs: +% f_target - scalar or vector of target null frequencies [Hz] +% L - fiber length [m] +% lambda0 - zero-dispersion wavelength (ZDW) [m] +% S0 - dispersion slope at ZDW [ps/(nm²·km)] +% +% Outputs: +% lambda_vec - wavelength(s) [m] where first null occurs (clamped to O-band) +% Dacc_vec - accumulated dispersion(s) [ps/nm] (NaN if out of valid range) +% -------------------------------------------------------------------- + + c = physconst('lightspeed'); + S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³) + + % Define O-band boundaries (in meters) + lambda_min = 1255e-9; + lambda_max = 1361e-9; + + % Force column vector + f_target = f_target(:); + N = numel(f_target); + + lambda_vec = NaN(N,1); + Dacc_vec = NaN(N,1); + + for k = 1:N + RHS = c * 0.5 / (f_target(k)^2 * L); + + % Normal-dispersion branch (λ < λ0) + fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS; + + % Limit the search to [λ_min, λ0) + try + lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]); + catch + % If the zero is not within bounds, skip this point + lambda_sol = NaN; + end + + % Validate solution + if isnan(lambda_sol) || lambda_sol < lambda_min || lambda_sol > lambda_max + lambda_vec(k) = NaN; + Dacc_vec(k) = NaN; + continue + end + + % Compute D(lambda) and accumulated dispersion + D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km) + Dacc_val = D_lambda * (L/1000); % ps/nm + + % Sanity bound on dispersion (avoid unphysical > ±100 ps/nm) + if abs(Dacc_val) > 100 + lambda_vec(k) = NaN; + Dacc_vec(k) = NaN; + else + lambda_vec(k) = lambda_sol; + Dacc_vec(k) = Dacc_val; + end + end +end \ No newline at end of file diff --git a/Functions/Theory/dispersion_wavelength_notch.m b/Functions/Theory/dispersion_wavelength_notch.m new file mode 100644 index 0000000..0f4da7e --- /dev/null +++ b/Functions/Theory/dispersion_wavelength_notch.m @@ -0,0 +1,32 @@ +%% Dependency f_null vs Delta_lambda +lambda0 = 1310e-9; +S0 = 0.09; % ps/(nm²·km) +L = 10e3; % m +c = physconst('lightspeed'); + +% Convert slope to SI +S0_si = S0 * 1e3; % s/m³ + +Delta_lambda = linspace(5e-9, 80e-9, 300); % [m] detuning +f_null_2 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L) ); +L = 2e3; % m +f_null_10 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L) ); + +cols = cbrewer2('Paired',10); +figure('Color','w');hold on +cnt = 2; +for L = 10%[2,5,10] + f_null_10 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L*1e3) ); + plot(1310-Delta_lambda*1e9, f_null_10/1e9, 'LineWidth',2,'DisplayName',sprintf('%d km',L),'Color',cols(cnt,:)); + cnt = cnt+2; +end +yticks([56,75,90,112]) +tickse = 1310-[7.5, 12, 17, 31.5]; +xticks(flip(tickse)); + +xlabel('$\Delta \lambda$ from ZDW [nm]'); +ylabel('$F_{null}$ [GHz]'); +grid on; box on; +lim=1310-[5,35]; +xlim([lim(2) lim(1)]); +ylim([40,130]) \ No newline at end of file diff --git a/Functions/Theory/dispersion_wdm.m b/Functions/Theory/dispersion_wdm.m new file mode 100644 index 0000000..f9ead0c --- /dev/null +++ b/Functions/Theory/dispersion_wdm.m @@ -0,0 +1,111 @@ +%% ============================================================ +% IM/DD Fading Notch Design Map +% Shows λ_null vs. bandwidth (f_target) and fiber length (L) +% ============================================================ + +clear; close all; clc; + +%% Parameters +lambda0 = 1310e-9; % Zero-dispersion wavelength [m] +S0 = 0.08; % Dispersion slope at ZDW [ps/(nm²·km)] +c = physconst('lightspeed'); + +% Frequency and length sweep +f_targets = linspace(20e9, 140e9, 80); % [Hz] → x-axis +L_values = linspace(0.5e3, 12e3, 80); % [m] → y-axis + +% Preallocate result matrices +lambda_surface = zeros(numel(L_values), numel(f_targets)); +Dacc_surface = zeros(numel(L_values), numel(f_targets)); + +%% Compute λ_null and Dacc for each (f_target, L) +for iL = 1:numel(L_values) + L = L_values(iL); + [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0); + lambda_surface(iL, :) = lambda_vec; % [m] + Dacc_surface(iL, :) = Dacc_vec; % [ps/nm] +end + +%% Convert to display units +lambda_surface_nm = lambda_surface * 1e9; % [nm] +L_km = L_values / 1000; % [km] +f_GHz = f_targets / 1e9; % [GHz] + +%% ------------------------------------------------------------ +% Contour plot (λ_null as function of f_null and L) +% ------------------------------------------------------------ +figure('Color','w'); + +% Define wavelength contour levels [nm] +lambda_levels = [1260:10:1290, 1290:5:1300, 1300:2:1310]; + +contourf(f_GHz, L_km, lambda_surface_nm, lambda_levels, ... + 'LineWidth', 1.5, ... + 'ShowText', 'on', ... + 'LabelFormat', '%1.1d nm'); + +% Colormap and colorbar +colormap(flip(cbrewer2('RdYlGn',100))); +clim([1260 1310]); +% c = colorbar; +% ylabel(c, 'λ_{null} [nm]', 'Rotation', 90); + +% Axis formatting +xlabel('Signal Bandwidth [GHz]'); +ylabel('Fiber length L [km]'); +% X-axis ticks (every 16 GHz starting at 56 GHz) +xticks(56:8:120); +xlim([56,120]) +grid on; box on; + +%% Optional overlay: accumulated dispersion contours +hold on; +[CS, h] = contour(f_GHz, L_km, Dacc_surface, 10, 'k--', 'LineWidth', 0.8); +clabel(CS, h, 'Color','k', 'FontSize',8); +legend('λ_{null} contours','|D_{acc}| [ps/nm]','Location','best'); + +%% ============================================================ +% Helper function: lambda_for_first_null_full +% Stable, single-branch, clamped to O-band +% ============================================================ +function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0) + c = physconst('lightspeed'); + S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³) + + % Define O-band boundaries (in meters) + lambda_min = 1260e-9; + lambda_max = 1360e-9; + + % Force column vector + f_target = f_target(:); + N = numel(f_target); + + lambda_vec = zeros(N,1); + Dacc_vec = zeros(N,1); + + for k = 1:N + RHS = c * 0.5 / (f_target(k)^2 * L); + + % Normal-dispersion branch (λ < λ0) + fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS; + + % Solve within the normal-dispersion range + try + lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]); + catch + lambda_sol = lambda_min; + end + + % Clamp to O-band range + lambda_sol = min(max(lambda_sol, lambda_min), lambda_max); + lambda_vec(k) = lambda_sol; + + % Compute D(lambda) and accumulated dispersion + D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km) + Dacc_val = D_lambda * (L/1000); % ps/nm + + % Clamp to physical range + Dacc_val = min(max(Dacc_val, -100), 100); + Dacc_vec(k) = Dacc_val; + end +end diff --git a/Functions/Theory/matched_filter_rrc.m b/Functions/Theory/matched_filter_rrc.m new file mode 100644 index 0000000..577ec10 --- /dev/null +++ b/Functions/Theory/matched_filter_rrc.m @@ -0,0 +1,120 @@ +%% Matched Filter SNR Demonstration (Correct Timing) +% clear; close all; clc; + +%% Parameters +M = 4; % QPSK +numSymbols = 1e6; +sps = 25; % samples per symbol +rolloff = 0.5; +EbNo_dB = 10; + +%% Generate random data +data = randi([0 M-1], numSymbols, 1); +txSym = qammod(data, M, 'UnitAveragePower', true); + +%% Root Raised Cosine filters +span = 64; % filter span in symbols +rrcTx = rcosdesign(rolloff, span, sps, 'sqrt'); +rrcRx = rrcTx; % matched filter + +txSignal2 = ifft(fft(rrcTx).*fft(txSym)); + +%% Transmit filtering (includes upsampling) +txSignal = upfirdn(txSym, rrcTx, sps, 1); + +%% AWGN channel +rxSignal = awgn(txSignal, EbNo_dB + 10*log10(sps), 'measured'); + +%% Receiver matched filter +rxFilt = conv(rxSignal, rrcRx, 'same'); + +%% Symbol timing (group delay compensation) +delay = span * sps / 2; % total delay per filter is span*sps/2 +rxAligned = rxFilt(delay+1 : end-delay); + +%% Downsample to symbol rate +rxSampled = rxAligned(1:sps:end); + +%% Align lengths +L = min(length(rxSampled), length(txSym)); +rxSampled = rxSampled(1:L); +txSym = txSym(1:L); + +%% Decision and BER +rxSym = qamdemod(rxSampled, M, 'UnitAveragePower', true); +[~, ber] = biterr(data(1:L), rxSym); + +%% Compute effective SNR +snr_meas = 10*log10(mean(abs(txSym).^2) / mean(abs(txSym - rxSampled).^2)); + +fprintf('Measured BER: %.3e | Effective SNR: %.2f dB\n', ber, snr_meas); + + +%% Eye diagrams +eyediagram(rxSignal(1:4000), 2*sps); +title('Received Signal (Before Matched Filter)'); +eyediagram(rxFilt(1:4000), 2*sps); +title('After Matched Filter (RRC)'); + +%% -------------------------------------------------------------- +%% Spectrum analysis of shaped and filtered signals +%% -------------------------------------------------------------- + +Fs = sps; % normalized sample rate (symbol rate = 1) +Nfft = 2^16; % FFT size for high resolution +f = (-Nfft/2:Nfft/2-1)/Nfft * Fs; % normalized frequency axis (symbol-rate units) + +% Spectra +S_tx = 20*log10(abs(fftshift(fft(txSignal, Nfft)))/max(abs(fft(txSignal, Nfft)))); +S_rx = 20*log10(abs(fftshift(fft(rxFilt, Nfft)))/max(abs(fft(rxFilt, Nfft)))); + +% Unshaped (rectangular pulse) for comparison +txRect_unf = upfirdn(txSym, ones(1, sps), sps, 1); +S_rect = 20*log10(abs(fftshift(fft(txRect_unf, Nfft)))/max(abs(fft(txRect_unf, Nfft)))); + +% Plot +figure('Name','Spectrum after Pulse Shaping'); +plot(f, S_rect, '--', 'DisplayName','Rectangular pulse'); +hold on; +plot(f, S_tx, 'LineWidth',1.4, 'DisplayName','RRC (TX)'); +plot(f, S_rx, 'LineWidth',1.4, 'DisplayName','After Matched Filter'); +grid on; +xlabel('Normalized frequency (× symbol rate)'); +ylabel('Magnitude [dB]'); +title('Spectra Before and After RRC Pulse Shaping'); +legend('Location','best'); +xlim([-1.5 1.5]); +ylim([-60 0]); + + +%% -------------------------------------------------------------- +%% Visualization: RRC and Raised-Cosine Frequency Responses +%% -------------------------------------------------------------- + +% Frequency axis for plotting (normalized to symbol rate) +Nfft = 4096; +H_rrc = fftshift(fft(rrcTx, Nfft)); +H_rc = H_rrc .* H_rrc; % cascade of TX and RX RRC = full RC + +f = linspace(-0.5, 0.5, Nfft); % normalized frequency (symbol-rate units) + +figure('Name','Raised Cosine Filter Characteristics'); + +subplot(2,1,1); +plot(f, 20*log10(abs(H_rrc)/max(abs(H_rrc))), 'LineWidth', 1.5); +hold on; +plot(f, 20*log10(abs(H_rc)/max(abs(H_rc))), '--', 'LineWidth', 1.5); +grid on; +xlabel('Normalized frequency (× symbol rate)'); +ylabel('Magnitude [dB]'); +title(sprintf('RRC (rolloff = %.2f) and Full RC Spectrum', rolloff)); +legend('Root Raised Cosine','Raised Cosine (TX×RX)','Location','best'); +ylim([-60 5]); + +subplot(2,1,2); +t = (-span*sps/2 : span*sps/2) / sps; % time axis in symbol durations +plot(t, rrcTx, 'LineWidth', 1.5); +grid on; +xlabel('Time [symbols]'); +ylabel('Amplitude'); +title('RRC Impulse Response'); diff --git a/Functions/Theory/power_fading.m b/Functions/Theory/power_fading.m new file mode 100644 index 0000000..0e14663 --- /dev/null +++ b/Functions/Theory/power_fading.m @@ -0,0 +1,40 @@ +%% ============================================================ +% Minimal IM/DD Power Fading Plot +% ============================================================ + + +%% Fiber and system parameters +lambda0 = 1310e-9; % zero-dispersion wavelength [m] +lambda = 1275e-9; % operating wavelength [m] +S0 = 0.08; % dispersion slope [ps/(nm²·km)] +L = 10e3; % fiber length [m] +c = physconst('lightspeed'); + +%% Derived quantities +S0_si = S0 * 1e3; % → s/m³ +D_lambda = (S0/4) * (lambda*1e9 - (lambda0*1e9)^4/(lambda*1e9)^3); % ps/(nm·km) +D_si = D_lambda * 1e-6; % → s/m² +b2 = -D_si * lambda^2 / (2*pi*c); % s²/m + +%% Frequency grid +f_max = 150e9; +f = linspace(0, f_max, 4000); % [Hz] + +%% IM/DD transfer function (power fading) +phi = 2*pi^2 * b2 * f.^2 * L; +H = abs(cos(phi)); + +%% Plot +figure('Color','w'); +plot(f/1e9, 10*log10(H), 'LineWidth', 1.8); +grid on; box on; +xlabel('Frequency [GHz]'); +ylabel('Magnitude [dB]'); +title(sprintf('IM/DD Power Fading |H| for λ = %.1f nm, L = %.1f km', lambda*1e9, L/1000)); +ylim([-30 0]); + +%% Mark analytic first-null frequency +f_null = sqrt(c*(0.5)/(abs(D_si)*lambda^2*L)); +xline(f_null/1e9, 'r--', 'LineWidth', 1.2, ... + 'Label', sprintf('f_{null}=%.1f GHz', f_null/1e9), ... + 'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'bottom'); diff --git a/Functions/Theory/propagation_time.m b/Functions/Theory/propagation_time.m new file mode 100644 index 0000000..9c64897 --- /dev/null +++ b/Functions/Theory/propagation_time.m @@ -0,0 +1,11 @@ +function prop_time = propagation_time(fiber_len_m) + + c = physconst('lightspeed'); % Speed of light in vacuum (m/s) + n = 1.46; % Refractive index of the fiber + + % Calculate the propagation speed in the fiber + propagation_speed = c / n; + + % Calculate the propagation time + prop_time = fiber_len_m ./ propagation_speed; +end \ No newline at end of file diff --git a/Functions/convert_freq_lambda.m b/Functions/convert_freq_lambda.m new file mode 100644 index 0000000..e65f569 --- /dev/null +++ b/Functions/convert_freq_lambda.m @@ -0,0 +1,62 @@ +%% ============================================================ +% Wavelength–Frequency Conversion Utilities +% ============================================================ + +% Example usage: +% f = lambda2freq(1310e-9); % 1310 nm -> Hz +% lambda = freq2lambda(224e12); % 224 THz -> m +% delta_lambda_nm = df2dlambda(224e12, 400e9); % 400 GHz @ 224 THz -> nm +% delta_freq_GHz = dlambda2df(1310e-9, 3.45); % 3.45 nm @ 1310 nm -> GHz + +%% ---- Core conversion functions ---- +function f = lambda2freq(lambda) +% lambda2freq Convert wavelength [m] → frequency [Hz] + c = physconst('lightspeed'); + f = c ./ lambda; +end + +function lambda = freq2lambda(f) +% freq2lambda Convert frequency [Hz] → wavelength [m] + c = physconst('lightspeed'); + lambda = c ./ f; +end + +%% ---- Differential conversions ---- +function d_lambda = df2dlambda(f_center, d_f) +% df2dlambda Convert frequency spacing Δf [Hz] → wavelength spacing Δλ [m] +% around a given center frequency f_center [Hz]. +% Uses first-order differential: Δλ ≈ (c / f^2) * Δf + + c = physconst('lightspeed'); + d_lambda = (c ./ (f_center.^2)) .* d_f; +end + +function d_f = dlambda2df(lambda_center, d_lambda) +% dlambda2df Convert wavelength spacing Δλ [m] → frequency spacing Δf [Hz] +% around a given center wavelength λ_center [m]. +% Uses first-order differential: Δf ≈ (c / λ^2) * Δλ + + c = physconst('lightspeed'); + d_f = (c ./ (lambda_center.^2)) .* d_lambda; +end + +%% ============================================================ +% Example section (can be commented out) +% ============================================================ + +if ~isdeployed + fprintf('--- Example conversions ---\n'); + + lambda_nm = 1310; % nm + lambda = lambda_nm * 1e-9; % m + f = lambda2freq(lambda); % Hz + fprintf('λ = %.1f nm → f = %.3f THz\n', lambda_nm, f/1e12); + + d_f = 2000e9; % 400 GHz spacing + d_lambda = df2dlambda(f, d_f); % [m] + fprintf('Δf = %.0f GHz @ %.1f nm → Δλ = %.3f nm\n', d_f/1e9, lambda_nm, d_lambda*1e9); + + % Verify reverse direction + d_f_back = dlambda2df(lambda, d_lambda); + fprintf('Δλ = %.3f nm @ %.1f nm → Δf = %.0f GHz\n', d_lambda*1e9, lambda_nm, d_f_back/1e9); +end diff --git a/Functions/getFigureSize.m b/Functions/getFigureSize.m new file mode 100644 index 0000000..e3034fb --- /dev/null +++ b/Functions/getFigureSize.m @@ -0,0 +1,33 @@ +% GETFIGURESIZE Retrieve the size of the current MATLAB figure window. +% [WIDTH, HEIGHT] = GETFIGURESIZE() returns the width and height of the +% current figure in pixels. +% +% Example: +% % Get size of current figure +% [w, h] = getFigureSize(); +% fprintf('Current figure is %d pixels wide and %d pixels tall.\n', w, h); +% +% Adapt snippet for other figures: +% % Suppose H is a handle to any MATLAB figure (existing or new): +% H = figure; % or H = ; +% % Retrieve current size of the active (or any) figure: +% [wCur, hCur] = getFigureSize(); +% % Set the other figure H to match that size, preserving its position: +% posH = get(H, 'Position'); % [left, bottom, width, height] +% newPos = [posH(1), posH(2), wCur, hCur]; +% set(H, 'Position', newPos); +% +% Note: +% - Position vector is given as [left, bottom, width, height] in pixels. +% - If you want to specify a custom size directly, you can replace wCur/hCur +% with desired values. + +function [width, height] = getFigureSize() + % Ensure a figure is available + fig = gcf; + % Get the position vector: [left, bottom, width, height] + pos = get(fig, 'Position'); + % Extract width and height + width = pos(3); + height = pos(4); +end diff --git a/Libs/wesanderson_colors/WesPalette.m b/Libs/wesanderson_colors/WesPalette.m new file mode 100644 index 0000000..7cd1dcb --- /dev/null +++ b/Libs/wesanderson_colors/WesPalette.m @@ -0,0 +1,133 @@ +classdef WesPalette + % WESPALETTE Wes Anderson color palettes with auto-completion + % Usage: + % cmap = WesPalette.Zissou1.rgb() + % cmap = WesPalette.Zissou1.rgb(3) + + % https://github.com/karthik/wesanderson?tab=readme-ov-file + + enumeration + BottleRocket1 + BottleRocket2 + Rushmore1 + Rushmore + Royal1 + Royal2 + Zissou1 + Zissou1Continuous + Darjeeling1 + Darjeeling2 + Chevalier1 + FantasticFox1 + Moonrise1 + Moonrise2 + Moonrise3 + Cavalcanti1 + GrandBudapest1 + GrandBudapest2 + IsleofDogs1 + IsleofDogs2 + FrenchDispatch + AsteroidCity1 + AsteroidCity2 + AsteroidCity3 + end + + methods + function cmap = rgb(obj, n) + % Return palette as Nx3 RGB colormap [0–1] + + hex = obj.hex(); + + rgb = hex2rgb(hex); + + if nargin == 2 + if n > size(rgb,1) + error('Requested %d colors, but only %d available.', ... + n, size(rgb,1)) + end + cmap = rgb(1:n,:); + else + cmap = rgb; + end + end + end + + methods (Access = private) + function hex = hex(obj) + % Internal HEX storage + + switch obj + case WesPalette.BottleRocket1 + hex = {'#A42820','#5F5647','#9B110E','#3F5151','#4E2A1E','#550307','#0C1707'}; + + case WesPalette.BottleRocket2 + hex = {'#FAD510','#CB2314','#273046','#354823','#1E1E1E'}; + + case {WesPalette.Rushmore1, WesPalette.Rushmore} + hex = {'#E1BD6D','#EABE94','#0B775E','#35274A','#F2300F'}; + + case WesPalette.Royal1 + hex = {'#899DA4','#C93312','#FAEFD1','#DC863B'}; + + case WesPalette.Royal2 + hex = {'#9A8822','#F5CDB4','#F8AFA8','#FDDDA0','#74A089'}; + + case WesPalette.Zissou1 + hex = {'#3B9AB2','#78B7C5','#EBCC2A','#E1AF00','#F21A00'}; + + case WesPalette.Zissou1Continuous + hex = {'#3A9AB2','#6FB2C1','#91BAB6','#A5C2A3','#BDC881', ... + '#DCCB4E','#E3B710','#E79805','#EC7A05','#EF5703','#F11B00'}; + + case WesPalette.Darjeeling1 + hex = {'#FF0000','#00A08A','#F2AD00','#F98400','#5BBCD6'}; + + case WesPalette.Darjeeling2 + hex = {'#ECCBAE','#046C9A','#D69C4E','#ABDDDE','#000000'}; + + case WesPalette.Chevalier1 + hex = {'#446455','#FDD262','#D3DDDC','#C7B19C'}; + + case WesPalette.FantasticFox1 + hex = {'#DD8D29','#E2D200','#46ACC8','#E58601','#B40F20'}; + + case WesPalette.Moonrise1 + hex = {'#F3DF6C','#CEAB07','#D5D5D3','#24281A'}; + + case WesPalette.Moonrise2 + hex = {'#798E87','#C27D38','#CCC591','#29211F'}; + + case WesPalette.Moonrise3 + hex = {'#85D4E3','#F4B5BD','#9C964A','#CDC08C','#FAD77B'}; + + case WesPalette.Cavalcanti1 + hex = {'#D8B70A','#02401B','#A2A475','#81A88D','#972D15'}; + + case WesPalette.GrandBudapest1 + hex = {'#F1BB7B','#FD6467','#5B1A18','#D67236'}; + + case WesPalette.GrandBudapest2 + hex = {'#E6A0C4','#C6CDF7','#D8A499','#7294D4'}; + + case WesPalette.IsleofDogs1 + hex = {'#9986A5','#79402E','#CCBA72','#0F0D0E','#D9D0D3','#8D8680'}; + + case WesPalette.IsleofDogs2 + hex = {'#EAD3BF','#AA9486','#B6854D','#39312F','#1C1718'}; + + case WesPalette.FrenchDispatch + hex = {'#90D4CC','#BD3027','#B0AFA2','#7FC0C6','#9D9C85'}; + + case WesPalette.AsteroidCity1 + hex = {'#0A9F9D','#CEB175','#E54E21','#6C8645','#C18748'}; + + case WesPalette.AsteroidCity2 + hex = {'#C52E19','#AC9765','#54D8B1','#B67C3B','#175149','#AF4E24'}; + + case WesPalette.AsteroidCity3 + hex = {'#FBA72A','#D3D4D8','#CB7A5C','#5785C1'}; + end + end + end +end diff --git a/Libs/wesanderson_colors/hex2rgb.m b/Libs/wesanderson_colors/hex2rgb.m new file mode 100644 index 0000000..b193e8d --- /dev/null +++ b/Libs/wesanderson_colors/hex2rgb.m @@ -0,0 +1,20 @@ +function rgb = hex2rgb(hex) +% HEX2RGB Convert HEX color codes to RGB [0–1] + +if ischar(hex) + hex = {hex}; +end + +n = numel(hex); +rgb = zeros(n,3); + +for k = 1:n + h = hex{k}; + h = strrep(h,'#',''); + rgb(k,1) = hex2dec(h(1:2)); + rgb(k,2) = hex2dec(h(3:4)); + rgb(k,3) = hex2dec(h(5:6)); +end + +rgb = rgb / 255; +end diff --git a/Libs/wesanderson_colors/minimal_example_wespalette.m b/Libs/wesanderson_colors/minimal_example_wespalette.m new file mode 100644 index 0000000..f55d2ad --- /dev/null +++ b/Libs/wesanderson_colors/minimal_example_wespalette.m @@ -0,0 +1,64 @@ +x = -10:2:25; % Input power [dBm] + +y1 = 1e-5 * 10.^(0.12*x); % Dispersion-only +y2 = 1e0 ./ (1 + exp(-0.4*(x-12))); % NLPN +y3 = 1e-6 * 10.^(0.45*x); % RP on gamma +y4 = 1e-2 * 10.^(0.18*(x-8)); % RP on beta2 + +cmap = WesPalette.AsteroidCity1.rgb(4); +cmap = linspecer(4); +figure1=figure(202998);clf;hold on +lw = 0.8; ms = 4; +plot(x,y1,'LineWidth',lw,'Color',cmap(1,:),'Marker','o','MarkerEdgeColor',cmap(1,:),'MarkerFaceColor',[1,1,1],'MarkerSize',ms); +plot(x,y2,'LineWidth',lw,'Color',cmap(2,:),'Marker','square','MarkerEdgeColor',cmap(2,:),'MarkerFaceColor',[1,1,1],'MarkerSize',ms); +plot(x,y3,'LineWidth',lw,'Color',cmap(3,:),'Marker','o','MarkerEdgeColor',cmap(3,:),'MarkerFaceColor',[1,1,1],'MarkerSize',ms); +plot(x,y4,'LineWidth',lw,'Color',cmap(4,:),'Marker','o','MarkerEdgeColor',cmap(4,:),'MarkerFaceColor',[1,1,1],'MarkerSize',ms); +yline(3.8e-3) + +grid on +xlabel('Input power [dBm]') +ylabel('NSD ($\%$)') +legend({'Dispersion','NLPN','RP','RP on $\beta_2$'}, ... + 'Location','best') + +grid off +set(gca,'MinorGridLineWidth',0.5); +set(gca,'GridLineWidth',0.5,'GridLineStyle','--','GridColor',[0.9,0.9,0.9]); + +set(gca,'FontSize',12,'YScale','log'); +ylim([1e-6 1e3]) +xlim([-10 23]) + +% % Create textarrow +% annotation(figure1,'textarrow',[0.564444444444444 0.548148148148148],... +% [0.768523809523809 0.63047619047619],'String',{'(A)'}); +% +% % Create doublearrow +% annotation(figure1,'doublearrow',[0.724444444444444 0.699259259259259],... +% [0.854238095238095 0.723809523809524]); +% +% % Create line +% annotation(figure1,'line',[0.700740740740741 0.699259259259259],... +% [0.554285714285714 0.405714285714286]); +% +% % Create textbox +% annotation(figure1,'textbox',... +% [0.578777777777778 0.194285714285714 0.104185185185185 0.0685714285714286],... +% 'String',{'BOX'},... +% 'FitBoxToText','off'); + +% +fig_path = 'C:\Users\Silas\Documents\Dissertation\00_Examples\tikz\textfig.tikz'; +matlab2tikz(fig_path, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'xlabel style={font=\color{white!15!black},font=\small},',... + 'ylabel style={font=\color{white!15!black},font=\small},',... + 'legend columns=1', ... + 'every axis/.append style={font=\scriptsize}',... + 'legend columns=2',... + 'legend style={at={(0.02,0.98)},font=\footnotesize,draw=black!60,rounded corners=2pt,inner sep=1pt,fill=white,column sep=6pt,anchor= north west}',... + }); \ No newline at end of file diff --git a/Libs/wesanderson_colors/wes_palettes.m b/Libs/wesanderson_colors/wes_palettes.m new file mode 100644 index 0000000..df744b5 --- /dev/null +++ b/Libs/wesanderson_colors/wes_palettes.m @@ -0,0 +1,79 @@ +function palettes = wes_palettes() +% WES_PALETTES Full Wes Anderson color palette collection for MATLAB +% Colors are stored as HEX and converted to RGB on demand. + +palettes = struct(); + +palettes.BottleRocket1 = { ... + '#A42820', '#5F5647', '#9B110E', '#3F5151', '#4E2A1E', '#550307', '#0C1707'}; + +palettes.BottleRocket2 = { ... + '#FAD510', '#CB2314', '#273046', '#354823', '#1E1E1E'}; + +palettes.Rushmore1 = { ... + '#E1BD6D', '#EABE94', '#0B775E', '#35274A', '#F2300F'}; + +palettes.Rushmore = palettes.Rushmore1; + +palettes.Royal1 = { ... + '#899DA4', '#C93312', '#FAEFD1', '#DC863B'}; + +palettes.Royal2 = { ... + '#9A8822', '#F5CDB4', '#F8AFA8', '#FDDDA0', '#74A089'}; + +palettes.Zissou1 = { ... + '#3B9AB2', '#78B7C5', '#EBCC2A', '#E1AF00', '#F21A00'}; + +palettes.Zissou1Continuous = { ... + '#3A9AB2', '#6FB2C1', '#91BAB6', '#A5C2A3', '#BDC881', ... + '#DCCB4E', '#E3B710', '#E79805', '#EC7A05', '#EF5703', '#F11B00'}; + +palettes.Darjeeling1 = { ... + '#FF0000', '#00A08A', '#F2AD00', '#F98400', '#5BBCD6'}; + +palettes.Darjeeling2 = { ... + '#ECCBAE', '#046C9A', '#D69C4E', '#ABDDDE', '#000000'}; + +palettes.Chevalier1 = { ... + '#446455', '#FDD262', '#D3DDDC', '#C7B19C'}; + +palettes.FantasticFox1 = { ... + '#DD8D29', '#E2D200', '#46ACC8', '#E58601', '#B40F20'}; + +palettes.Moonrise1 = { ... + '#F3DF6C', '#CEAB07', '#D5D5D3', '#24281A'}; + +palettes.Moonrise2 = { ... + '#798E87', '#C27D38', '#CCC591', '#29211F'}; + +palettes.Moonrise3 = { ... + '#85D4E3', '#F4B5BD', '#9C964A', '#CDC08C', '#FAD77B'}; + +palettes.Cavalcanti1 = { ... + '#D8B70A', '#02401B', '#A2A475', '#81A88D', '#972D15'}; + +palettes.GrandBudapest1 = { ... + '#F1BB7B', '#FD6467', '#5B1A18', '#D67236'}; + +palettes.GrandBudapest2 = { ... + '#E6A0C4', '#C6CDF7', '#D8A499', '#7294D4'}; + +palettes.IsleofDogs1 = { ... + '#9986A5', '#79402E', '#CCBA72', '#0F0D0E', '#D9D0D3', '#8D8680'}; + +palettes.IsleofDogs2 = { ... + '#EAD3BF', '#AA9486', '#B6854D', '#39312F', '#1C1718'}; + +palettes.FrenchDispatch = { ... + '#90D4CC', '#BD3027', '#B0AFA2', '#7FC0C6', '#9D9C85'}; + +palettes.AsteroidCity1 = { ... + '#0A9F9D', '#CEB175', '#E54E21', '#6C8645', '#C18748'}; + +palettes.AsteroidCity2 = { ... + '#C52E19', '#AC9765', '#54D8B1', '#B67C3B', '#175149', '#AF4E24'}; + +palettes.AsteroidCity3 = { ... + '#FBA72A', '#D3D4D8', '#CB7A5C', '#5785C1'}; + +end diff --git a/projects/ECOC_2025/auswertung_algorithms/mpi_dsp_debug.m b/projects/ECOC_2025/auswertung_algorithms/mpi_dsp_debug.m new file mode 100644 index 0000000..d765e30 --- /dev/null +++ b/projects/ECOC_2025/auswertung_algorithms/mpi_dsp_debug.m @@ -0,0 +1,60 @@ + +load("ffe_debug_snapshot.mat"); + + +dc_buffer_len = logspace(0,3,12); +dc_buffer_len = 1024; + +mu_dc = logspace(-3,0,24); + +parfor d = 1:length(mu_dc) + + eq_lin = 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(d),... + "dc_buffer_len",1024, ... + "ffe_buffer_len",1,... + "smoothing_buffer_length",0,... + "smoothing_buffer_update",1,... + "adaptive_mu_mode",0); + % + % eq_lin = FFE("epochs_tr",5,"epochs_dd",3,"len_tr",4096*2,"mu_dd",... + % 0.0002,"mu_tr",0,"order",25,"sps",2,"decide",0); + + ffe_results = ffe(eq_lin,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + ffe_results.metrics.print + + % % eq_lin = FFE_DFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"ffe_mu_dd",1e-4,"dfe_mu_dd",5e-4,"ffe_mu_tr",0,"dfe_mu_tr",0,"ffe_order",21,"dfe_order",2,"sps",2,"decide",0); + % + % eq_lin = FFE("epochs_tr",5,"epochs_dd",3,"len_tr",4096*2,"mu_dd",... + % 0.0002,"mu_tr",0,"order",25,"sps",2,"decide",0); + % pf_ = Postfilter("ncoeff",2,"useBurg",1); + % mlse_ = MLSE_viterbi("duobinary_output",0,'M',4,'trellis_states',PAMmapper(4,0).levels); + % + % [ffe_results2, mlse_results] = vnle_postfilter_mlse(eq_lin, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... + % "precode_mode", duob_mode,... + % 'showAnalysis', 1, ... + % "postFFE", [],... + % "eth_style_symbol_mapping", 0); + + ber(d) = ffe_results.metrics.BER; + + +end + +figure(10) +hold on +plot(mu_dc,ber,'LineWidth',1,'DisplayName',sprintf('DC buffer len = 1024'),'Marker','.','MarkerSize',10); +xlabel('BER'); +xlabel('MU DC'); +title('BER Optimization over dc\_buffer\_len'); +yline([4.85e-3,2e-2],'HandleVisibility', 'off','LineWidth',1,'LineStyle','--'); +ylim([9e-4, 0.5]); +set(gca, 'YScale', 'log'); % BER is usually plotted log-scale +legend('show', 'Location', 'best'); +grid on; \ No newline at end of file diff --git a/projects/ECOC_2025/auswertung_algorithms/run_offline_dsp.m b/projects/ECOC_2025/auswertung_algorithms/run_offline_dsp.m new file mode 100644 index 0000000..af3e9d7 --- /dev/null +++ b/projects/ECOC_2025/auswertung_algorithms/run_offline_dsp.m @@ -0,0 +1,118 @@ +% === SETTINGS === + +dsp_options.append_to_db = 0; +dsp_options.max_occurences = 15; +dsp_options.database_path = 'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\'; +dsp_options.database_name = 'silas_labor_newdsp_newstructure.db'; +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; + +dsp_options.parameters = struct(); +dsp_options.parameters.mu_dc = [0.005]; + +% === Get Run ID's === +db = DBHandler("pathToDB", [dsp_options.database_path, dsp_options.database_name], "type", "sqlite"); +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 5108); +fp.where('Runs', 'is_mpi','EQUALS', 0); +fp.where('Runs', 'fiber_length','EQUALS', 1); +fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', 1); +fp.where('Runs', 'rop_attenuation','EQUALS', 0); +fp.where('Runs', 'pam_level','EQUALS', 4); +fp.where('Runs', 'bitrate','EQUALS', 360e9); +% fp.where('Runs', 'power_pd_in','GREATER_THAN', 7); +[dataTable,~] = db.queryDB(fp, db.getTableFieldNames('Runs')); + +% === Initialize DataStorage === +wh = DataStorage(dsp_options.parameters); +wh.addStorage("ffe_package"); +wh.addStorage("mlse_package"); +wh.addStorage("vnle_package"); +wh.addStorage("dbtgt_package"); +wh.addStorage("dbenc_package"); + +% === RUN IT === + +% [results,wh] = submitJobs(dataTable.run_id(:), dsp_options, "serial", 'wh', wh, 'waitbar', true); +% wh.getStoValue('ffe_package',0.005); +% wh.getStoValue('mlse_package',0.005); + +[dataTable,~] = db.queryDB(fp, [db.getTableFieldNames('Runs');db.getTableFieldNames('Results');db.getTableFieldNames('Equalizer')]); + +dataTable = cleanUpTable(dataTable); + +% === Look at it === +y_var = 'BER_precoded'; +x_var = 'bitrate'; +fixedVars = {'equalizer_structure', x_var}; + +[dataTableClean, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var); + +% --- Group and aggregate --- +dataTableGrpd_mean = groupIt(fixedVars, dataTableClean, @mean); +dataTableGrpd_min = groupIt(fixedVars, dataTableClean, @min); +dataTableGrpd_max = groupIt(fixedVars, dataTableClean, @max); + +% Choose a color map +cols = linspecer(numel(unique(dataTableGrpd_mean.equalizer_structure))); + +figure; +hold on; + +% Get unique equalizer structures for grouping +unique_eq = unique(dataTableGrpd_mean.equalizer_structure); + +for i = 1:numel(unique_eq) + eq_val = unique_eq(i); + + % Filter grouped data for this equalizer structure + filt = dataTableGrpd_mean.equalizer_structure == eq_val; + + x = dataTableGrpd_mean.(x_var)(filt); + y_mean = dataTableGrpd_mean.(y_var)(filt); + y_min = dataTableGrpd_min.(y_var)(filt); + y_max = dataTableGrpd_max.(y_var)(filt); + + % Bounds for boundedline (distance from mean) + y_lower = y_mean - y_min; + y_upper = y_max - y_mean; + y_bounds = [y_lower, y_upper]; + + % --- Bounded line (mean ± min/max) --- + if exist('boundedline', 'file') + [hl, hp] = boundedline(x, y_mean, y_bounds, ... + 'alpha', 'transparency', 0.1, ... + 'cmap', cols(i,:), ... + 'nan', 'fill', ... + 'orientation', 'vert'); + set(hl, 'LineWidth', 1.2, 'DisplayName', sprintf('Eq %s', eq_val)); + set(hp, 'HandleVisibility', 'off'); + else + % If boundedline is not available, use errorbar + errorbar(x, y_mean, y_lower, y_upper, ... + 'o-', 'Color', cols(i,:), 'LineWidth', 1.2, ... + 'DisplayName', sprintf('Eq %d', eq_val),'HandleVisibility', 'off'); + end + + % --- Normal line (mean only) --- + plot(x, y_mean, '-', 'Color', cols(i,:), 'LineWidth', 1.5, ... + 'DisplayName', sprintf('Mean Eq %s', eq_val),'HandleVisibility', 'off'); + + % --- Scatter plot for individual points (from original data) --- + % Filter original data for this group + orig_filt = dataTableClean.equalizer_structure == eq_val; + x_scatter = dataTableClean.(x_var)(orig_filt); + y_scatter = dataTableClean.(y_var)(orig_filt); + + scatter(x_scatter, y_scatter, 10,cols(i,:), 'filled', ... + 'MarkerFaceAlpha', 0.5, 'DisplayName', sprintf('Scatter Eq %s', eq_val),'HandleVisibility', 'off'); +end + +yline([2.2e-4,4.85e-3,2e-2],'HandleVisibility', 'off','LineWidth',1,'LineStyle','--'); +set(gca, 'YScale', 'log'); % BER is usually plotted log-scale +xlabel(x_var, 'Interpreter', 'none'); +ylabel(y_var, 'Interpreter', 'none'); +legend('show', 'Location', 'best'); +grid on; +title(sprintf('%s vs. %s', y_var, x_var), 'Interpreter', 'none'); +hold off; \ No newline at end of file diff --git a/projects/ECOC_2025/dsp_standalone_2.m b/projects/ECOC_2025/dsp_standalone_2.m new file mode 100644 index 0000000..97007ae --- /dev/null +++ b/projects/ECOC_2025/dsp_standalone_2.m @@ -0,0 +1,63 @@ + + + +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); +filterParams.Configurations = struct( ... + 'symbolrate', 112e9, ... %[224,336,360,390,420,448] + 'fiber_length', 0, ... + 'db_mode', '"no_db"', ... + 'interference_attenuation', [], ... + 'interference_path_length', 0, ... + '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~=217,:) = []; + +num_occ = 10; +run_par = true; +run_id = dataTable.run_id; + +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 + +% Extract all ber_mlse values from the vnle_pf_package using cellfun +ber_mlse = cellfun(@(pkg) pkg.ber_mlse, future.OutputArguments{1,1}.vnle_pf_package); +ber_vnle = cellfun(@(pkg) pkg.ber_vnle, future.OutputArguments{1,1}.vnle_pf_package); + +figure(101) +hold on; +scatter(1:num_occ,ber_mlse,15,'Marker','*'); +scatter(1:num_occ,ber_vnle,15,'Marker','*'); +legend('Interpreter', 'latex'); +xlabel('Occurences'); +ylabel('BER'); +grid on; +beautifyBERplot; diff --git a/projects/ECOC_2025/dsp_test/hyperparam_tuning.m b/projects/ECOC_2025/dsp_test/hyperparam_tuning.m new file mode 100644 index 0000000..aec4165 --- /dev/null +++ b/projects/ECOC_2025/dsp_test/hyperparam_tuning.m @@ -0,0 +1,58 @@ + + +Scpe_sig = load("imdd_simulation\projects\ECOC_2025\dsp_test\pam4_scopesignal.mat");Scpe_sig = Scpe_sig.Scpe_sig; +Tx_bits = load("imdd_simulation\projects\ECOC_2025\dsp_test\pam4_bits.mat");Tx_bits = Tx_bits.Tx_bits; +Symbols = load("imdd_simulation\projects\ECOC_2025\dsp_test\pam4_symbols.mat");Symbols = Symbols.Symbols; + +eq_ = FFE_DCremoval("epochs_tr",5,"epochs_dd",3,"len_tr",4096*2,"mu_dd",1e-5,"mu_tr",0,"order",50,"sps",2,"decide",0,"mu_dc",0.005,"dc_buffer_len",1); + +% 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"); + +params = (logspace(-6,-2,20)); +params = floor((logspace(2,3,20))); +params = 224; + +for i = 1:length(params) + + 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",0.005,"dc_buffer_len",1, ... + "ffe_buffer_len",1,... + "smoothing_buffer_length",0,... + "smoothing_buffer_update",1); + + result = ffe(eq_,4,Scpe_sig,Symbols,Tx_bits,"precode_mode",db_mode.no_db,'showAnalysis',1,"postFFE",[],"eth_style_symbol_mapping",0); + ber_ffe(i) = result.metrics; + fprintf(" FFE Results: %.2e\n", ber_ffe(i)); + + % db.addProcessingResult(run_id, result.resultsVNLE, result.equalizerConfigVNLE); + + % eq_ = FFE_adaptive_decision("epochs_tr",5,"epochs_dd",3,"len_tr",4096*2,"mu_dd",... + % 0.0003,"mu_tr",0,"order",50,"sps",2,"decide",1,"buffer_length",params(i)); + % + % result = vnle(eq_,4,Scpe_sig,Symbols,Tx_bits,"precode_mode",db_mode.no_db,'showAnalysis',1,"postFFE",[],"eth_style_symbol_mapping",0); + % ber_dc(i) = result.ber_vnle; + % + % fprintf(" FFE+dc tr. Results: %.2e\n", ber_dc(i)); + +end + +figure(103435) +hold on; +% scatter(params,ber_dc,15,'Marker','o','LineWidth',1,'DisplayName','DC tracking'); +% [a,b]=min(ber_dc); +% scatter(params(b),a,45,'Marker','x','MarkerEdgeColor','r','LineWidth',1); + +scatter(params,ber_ffe,15,'Marker','square','LineWidth',1); +[a,b]=min(ber_ffe); +scatter(params(b),a,45,'Marker','x','MarkerEdgeColor','r','LineWidth',1,'DisplayName','FFE'); + +legend('Interpreter', 'latex'); +xlabel('Occurences'); +ylabel('BER'); +grid on; +beautifyBERplot; +ylim([1e-4,0.1 ]) \ No newline at end of file diff --git a/projects/ECOC_2025/ecoc2025.db b/projects/ECOC_2025/ecoc2025.db new file mode 100644 index 0000000..e28ebc8 Binary files /dev/null and b/projects/ECOC_2025/ecoc2025.db differ diff --git a/projects/ECOC_2025/ecoc2025.sqbpro b/projects/ECOC_2025/ecoc2025.sqbpro new file mode 100644 index 0000000..87c9a0c --- /dev/null +++ b/projects/ECOC_2025/ecoc2025.sqbpro @@ -0,0 +1 @@ +
diff --git a/projects/ECOC_2025/ecoc2025_einmessung.db b/projects/ECOC_2025/ecoc2025_einmessung.db new file mode 100644 index 0000000..f8e12f5 Binary files /dev/null and b/projects/ECOC_2025/ecoc2025_einmessung.db differ diff --git a/projects/ECOC_2025/ecoc2025_fail.db b/projects/ECOC_2025/ecoc2025_fail.db new file mode 100644 index 0000000..af35623 Binary files /dev/null and b/projects/ECOC_2025/ecoc2025_fail.db differ diff --git a/projects/ECOC_2025/ecoc2025_loops - Kopie.db b/projects/ECOC_2025/ecoc2025_loops - Kopie.db new file mode 100644 index 0000000..bb4d7cf Binary files /dev/null and b/projects/ECOC_2025/ecoc2025_loops - Kopie.db differ diff --git a/projects/ECOC_2025/ecoc2025_loops.db b/projects/ECOC_2025/ecoc2025_loops.db new file mode 100644 index 0000000..ae8f55d Binary files /dev/null and b/projects/ECOC_2025/ecoc2025_loops.db differ diff --git a/projects/ECOC_2025/sqlite_sequence.sql b/projects/ECOC_2025/sqlite_sequence.sql new file mode 100644 index 0000000..b9e5076 --- /dev/null +++ b/projects/ECOC_2025/sqlite_sequence.sql @@ -0,0 +1,163 @@ +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "Configurations" ( + "configuration_id" INTEGER, + "run_id" INTEGER, + "unique_elab_id" TEXT, + "bitrate" REAL, + "symbolrate" REAL, + "pam_level" INTEGER, + "db_mode" TEXT, + "pulsef_alpha" INTEGER, + "v_bias" REAL, + "v_awg" REAL, + "precomp_amp" REAL, + "rop_attenuation" REAL, + "wavelength" REAL, + "laser_power" REAL, + "fiber_length" REAL, + "pd_in_desired" REAL, + "is_mpi" BIT, + "signal_attenuation" REAL, + "interference_path_length" REAL, + "interference_attenuation" REAL, + "pam_source" TEXT, + PRIMARY KEY("configuration_id" AUTOINCREMENT), + FOREIGN KEY("run_id") REFERENCES "Runs"("run_id") +); +CREATE TABLE IF NOT EXISTS "EqualizerParameters" ( + "eq_id" INTEGER, + "equalizer_structure" REAL, + "M" INTEGER, + "target_constellation" TEXT, + "db_target" INTEGER, + "diff_precode" INTEGER, + "postFFE" INTEGER, + "NpostFFE" INTEGER, + "Ne1" INTEGER, + "Ne2" INTEGER, + "Ne3" INTEGER, + "Nb1" INTEGER, + "Nb2" INTEGER, + "Nb3" INTEGER, + "K" INTEGER, + "DCmu" REAL, + "ideal_dfe" INTEGER, + "training_length" INTEGER, + "training_loops" INTEGER, + "TRmu1" REAL, + "TRmu2" REAL, + "TRmu3" REAL, + "TRmuDFE" REAL, + "dd_loops" INTEGER, + "DDmu1" REAL, + "DDmu2" REAL, + "DDmu3" REAL, + "DDmuDFE" REAL, + "MLSE_mode" TEXT, + "MLSE_trellis_states" TEXT, + "comment" TEXT, + "config_hash" TEXT, + UNIQUE("config_hash"), + PRIMARY KEY("eq_id" AUTOINCREMENT) +); +CREATE TABLE IF NOT EXISTS "Measurements" ( + "measurement_id" INTEGER, + "run_id" INTEGER, + "power_laser" REAL, + "power_rop" REAL, + "power_pd_in" REAL, + "power_mpi_interference" REAL, + "power_mpi_signal" REAL, + "voa_class" TEXT, + "pdfa_class" TEXT, + "laser_class" TEXT, + PRIMARY KEY("measurement_id" AUTOINCREMENT), + FOREIGN KEY("run_id") REFERENCES "Runs"("run_id") +); +CREATE TABLE IF NOT EXISTS "Results" ( + "result_id" INTEGER, + "run_id" INTEGER, + "eqParam_id" INTEGER, + "date_of_processing" DATETIME DEFAULT (datetime('now', 'localtime')), + "numBits" INTEGER, + "numBitErr" INTEGER, + "BER" REAL, + "numBitErr_precoded" REAL, + "BER_precoded" REAL, + "SNR" REAL, + "SNR_level" TEXT, + "GMI" REAL, + "AIR" REAL, + "EVM" REAL, + "EVM_level" TEXT, + "Alpha" REAL, + "result_hash" TEXT UNIQUE, + "MLSE_dir" INTEGER, + PRIMARY KEY("result_id" AUTOINCREMENT), + FOREIGN KEY("eqParam_id") REFERENCES "EqualizerParameters"("eq_id"), + FOREIGN KEY("run_id") REFERENCES "Runs"("run_id") +); +CREATE TABLE IF NOT EXISTS "Runs" ( + "run_id" INTEGER, + "date_of_run" DATETIME DEFAULT (datetime('now', 'localtime')), + "tx_bits_path" TEXT, + "tx_symbols_path" TEXT, + "rx_sync_path" TEXT, + "rx_raw_path" TEXT, + "filename" TEXT, + "tx_signal_path" TEXT, + PRIMARY KEY("run_id" AUTOINCREMENT) +); +CREATE VIEW "View_ResultOverview" AS +SELECT + -- Run info + Runs.run_id, + Runs.date_of_run, + + Results.BER, + Results.SNR, + Results.GMI, + Results.AIR, + Results.EVM, + Results.Alpha, + + -- Configurations + Configurations.symbolrate, + Configurations.pam_level, + Configurations.db_mode, + Configurations.pulsef_alpha, + Configurations.v_bias, + Configurations.v_awg, + Configurations.precomp_amp, + Configurations.is_mpi, + Configurations.signal_attenuation, + Configurations.interference_path_length, + Configurations.interference_attenuation, + + -- Measurement data + Measurements.power_laser, + Measurements.power_rop, + Measurements.power_pd_in, + Measurements.power_mpi_interference, + Measurements.power_mpi_signal, + + -- Equalizer parameters + EqualizerParameters.equalizer_structure, + EqualizerParameters.db_target, + EqualizerParameters.diff_precode + + +FROM Results + +-- Join related tables +LEFT JOIN Runs ON Results.run_id = Runs.run_id +LEFT JOIN Configurations ON Configurations.run_id = Runs.run_id +LEFT JOIN Measurements ON Measurements.run_id = Runs.run_id +LEFT JOIN EqualizerParameters ON Results.eqParam_id = EqualizerParameters.eq_id; +CREATE INDEX IF NOT EXISTS "idx_run_id_on_Configurations" ON "Configurations" ( + "run_id" +); +CREATE INDEX IF NOT EXISTS "idx_run_id_on_Measurements" ON "Measurements" ( + "run_id" +); +COMMIT; diff --git a/projects/ECOC_2025/theory/analytic_mpi_evaluation.m b/projects/ECOC_2025/theory/analytic_mpi_evaluation.m new file mode 100644 index 0000000..460f24f --- /dev/null +++ b/projects/ECOC_2025/theory/analytic_mpi_evaluation.m @@ -0,0 +1,85 @@ +% This script is used to evaluate Fig. 1b) in the paper "Adaptive Removal of Multipath Interference in Short Reach 112 GBd PAM-4 IM/DD Systems" + +%% Parameters +df = 1e6; % Laser linewidth [Hz] +SIR_dB = 20; % Interference attenuation [dB] +alpha = 10^(-SIR_dB/20); % Interference attenuation [linear] +n_fiber = 1.467; % Refractive index +c = physconst('lightspeed'); % [m/s] + +L = linspace(0,250,50); % Interference delay [m] +tau = n_fiber./c.*L; % Interference time (= tau) [s] + +tau_c = 1/(pi*df); % laser coherence time [s] +L_c = (c/n_fiber)*tau_c; % laser coherence length [m] + +var_sat = 2*alpha^2; % Analytical saturation of variance + +%% Monte–Carlo Simulation +fs = 100e9; % sampling rate [Hz] +Tsim = 50e-6; % sim duration [s] +N = round(Tsim*fs); % number of samples for each realization +max_delay_samples = round(max(tau)*fs); % largest delay that is evaluated (based on max. Interference delay) +phase_noise_std = sqrt(2*pi*df/fs); % standard dev. phase noise + +num_realizations = 50; % number of parallel runs +monte_carlo_variance = zeros(num_realizations, length(L)); +parfor r = 1:num_realizations + + % generate a realization of phase noise random walk + dphi = phase_noise_std * randn(1, N + max_delay_samples); % matlab randn process has std = 1 + phi = cumsum(dphi); + phi_direct = phi(max_delay_samples+1 : max_delay_samples+N); + var_k = zeros(1, length(L)); + for t = 1:length(tau) + + nd = round( tau(t)*fs ); % delay in samples for current interference time + phi_delayed = phi(max_delay_samples+1-nd : max_delay_samples+N-nd); %cut out interfering signal part (was earlier) + + E = exp(1j*phi_direct) + alpha*exp(1j*phi_delayed); % E-fields combined + I = abs(E).^2; % photo current as magnitude square of E-field + var_k(t) = var(I); + + end + monte_carlo_variance(r, :) = var_k; +end + +avg_of_mc_variances = mean(monte_carlo_variance, 1); +std_of_mc_variances = std(monte_carlo_variance, 0, 1); + +%% Analytic variance +L_ = linspace(0,250,500); % Interference delay [m] +tau_ = n_fiber./c.*L_; +analytic_variance = 2*alpha^2 * (1 - exp(-2*pi*df.*tau_)).^2; + +%% Plot +cols = [0.3467 0.5360 0.6907 + 0.9153 0.2816 0.2878 + 0.4416 0.7490 0.4322]; + +coherence_length_multiples = 0.5:0.5:ceil(L(end)/L_c); + +figure(); +hold on; +plot(L, avg_of_mc_variances, 'LineWidth',2, 'DisplayName','Simulation','Color',cols(1,:),'LineStyle','-'); +errorbar(L, avg_of_mc_variances,std_of_mc_variances, 'LineWidth',0.7,'LineStyle','none', 'DisplayName','Simulation','Color',cols(1,:),'HandleVisibility','off'); + +plot(L_, analytic_variance, 'LineWidth',2, 'DisplayName','Analytic','Color',cols(2,:),'LineStyle','-'); +xticks(coherence_length_multiples.*L_c); +xticklabels(round(coherence_length_multiples.*L_c,1)); + +norm_to_coherence_len = 1; +if norm_to_coherence_len + xticklabels(coherence_length_multiples); + xlabel('$n \cdot L_c$', 'FontSize',12); +else + xlabel('Interference Delay [m]', 'FontSize',12); +end + +xline(L_c.*coherence_length_multiples, 'LineWidth',1.5, 'DisplayName','Coh. Length','HandleVisibility','off','Color',[0.7,0.7,0.7],'LineStyle','-'); +xlim([0,L(end)]); +yline(var_sat, '-.k','LineWidth',1.5, 'DisplayName','Saturation: 2$\alpha ^2$'); +grid on; +ylabel('Intensity Variance', 'FontSize',12); +title(sprintf('MPI Variance; %d MHz; SIR: %d dB',df.*1e-6,SIR_dB), 'FontSize',14); +legend('Location','southeast'); diff --git a/projects/ECOC_2025/theory/coherence_length_plot.m b/projects/ECOC_2025/theory/coherence_length_plot.m new file mode 100644 index 0000000..b793687 --- /dev/null +++ b/projects/ECOC_2025/theory/coherence_length_plot.m @@ -0,0 +1,32 @@ +%% Parameters +df = linspace(1,50e6,10000); % Laser FWHM linewidth [Hz] +n_fiber = 1.467; % Fiber group index +c = 3e8; % Speed of light [m/s] + +% Compute coherence length (1/e of mean-fringe decay) +tau_c = 1./(pi*df); +L_c = (c.* tau_c/n_fiber) ; % Coherence length [m] + +%% Plot +figure('Color','w'); +loglog(df/1e6, L_c, 'LineWidth',2,'LineStyle','-'); % linewidth in MHz +% xticks([0.1, 1, 10, 50]); +% yticks([1, 10, 100, 1000]); +% yticklabels({'1','10','100','1000'}) +grid on; box on; +xlabel('Laser linewidth [MHz]','FontSize',12,'Interpreter','latex'); +ylabel('Coherence length [m]','FontSize',12,'Interpreter','latex'); +title('Coherence Length vs. Laser Linewidth','FontSize',14,'Interpreter','latex'); + +%% Annotate some key points +% hold on; +% freqs = [150e3, 1e6, 10e6, 50e6]; % [Hz] +% for f = freqs +% x = f/1e6; +% y = (c/n_fiber) * (1/(pi*f)); +% scatter(x,y,'Marker','x','LineWidth',1,'MarkerEdgeColor','black'); +% +% text(x*1.1,y, sprintf('%.2f MHz', f/1e6), ... +% 'FontSize',10,'HorizontalAlignment','left'); +% +% end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_dispersion.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_dispersion.m new file mode 100644 index 0000000..9c0d120 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_dispersion.m @@ -0,0 +1,111 @@ + +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +M = 6; +fp.where('Runs', 'pam_level','EQUALS', M); +baudrate = 162e9; +fp.where('Runs', 'symbolrate','EQUALS', baudrate); +% fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +% fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')]; +[dataTable,~] = db.queryDB(fp, fields); + +eqstructures = unique(dataTable.equalizer_structure); +fiber_len = unique(dataTable.fiber_length); +cnt = 1; +f=figure(); +clf +hold on + +markers = {'o', 's', 'd', '^', 'v', '>', '<', 'p', 'h'}; % Define marker styles + +for fl = 1:numel(fiber_len) + + fl_filtered = dataTable(dataTable.fiber_length == fiber_len(fl),:); + + for eqs = [equalizer_structure.vnle_pf_mlse] + + eq_choice = equalizer_structure(eqs); + if sum(eqstructures == eq_choice)~=1 + disp(eq_choice) + continue + end + + eq_filtered = fl_filtered(fl_filtered.equalizer_structure == eq_choice,:); + + dispersion_sorted = sortrows(eq_filtered, {'accumulated_dispersion'}, 'ascend'); + % dispersion_sorted = dispersion_sorted(dispersion_sorted.wavelength <= 1320,:); + % dispersion_sorted = dispersion_sorted(dispersion_sorted.BER < 0.02,:); + % pull out your vectors + accumulated_dispersion = dispersion_sorted.accumulated_dispersion; + ber = dispersion_sorted.BER; + % ber = dispersion_sorted.BER_precoded; + run_ids = dispersion_sorted.run_id; % <-- this is what we want in the datatip + len = dispersion_sorted.fiber_length; + lambda = dispersion_sorted.wavelength; + cols = cbrewer2('Set1',8); + % cols = flip(cbrewer2('RdYlGn',14)); + cols = linspecer(8); + + ber_wavelen_grouped = groupsummary( ... + dispersion_sorted, ... % input table + "wavelength", ... % grouping variable + "min", ... % which summary statistic + "BER_precoded"); + + dname = sprintf('%s; %d km',eq_choice, fiber_len(fl)); + h1 = plot(ber_wavelen_grouped.wavelength, ber_wavelen_grouped.min_BER_precoded,'LineWidth', 2, 'MarkerSize', 5,'Marker',markers(cnt),'LineStyle','-','Color',cols(cnt,:),'MarkerEdgeColor','auto','MarkerFaceColor','white','DisplayName',dname); + + + plotallscatters=0; + if plotallscatters + % plot the two curves and capture their Line handles + dname = sprintf('%s; %d km',eq_choice, fiber_len(fl)); + h1 = plot(lambda, ber,'LineWidth', 1.5, 'MarkerSize', 5,'Marker','o','LineStyle','none','Color',cols(cnt,:),'MarkerFaceColor',cols(cnt,:),'DisplayName',dname); + % —————— Add run_id as a datatip row —————— + % For each line, tell the datatip template where to find the run_id: + h1.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('run\_id', run_ids); + h1.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('len', len); + h1.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('lambda', lambda); + end + + xticks(sort(unique(lambda))); + xticklabels(sort(unique(lambda))); + + grid on; + + % Labels, scales, legend, etc. + xlabel('Wavelength in nm','FontSize',12); + ylabel('BER','FontSize',12); + tit = sprintf('%d GBd PAM-%d',baudrate.*1e-9, M); + title(tit,'FontSize',14,'FontWeight','bold'); + set(gca, 'XScale','linear','YScale','log','FontSize',11); + legend + + xlim([min(lambda)-2, max(lambda)+2]); + ylim([1e-4, 0.2]); + + cnt = cnt+1; + end +end + +yline([4.85e-3, 2e-2],'--','LineWidth',1,'HandleVisibility','off'); +posH = get(f, 'Position'); % [left, bottom, width, height] +newPos = [posH(1), posH(2), 750, 300]; +set(f, 'Position', newPos); \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate.m new file mode 100644 index 0000000..38ff1cf --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate.m @@ -0,0 +1,284 @@ + +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +M = 6; +fp.where('Runs', 'pam_level','EQUALS', M); +% fp.where('Runs', 'bitrate','LESS_THAN', 310e9); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +fp.where('Runs', 'wavelength','EQUALS', 1310); +% fp.where('Runs', 'db_mode','EQUALS', 0); +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_aug_nov_2025')]; +[dataTable,~] = db.queryDB(fp, fields); + +eqstructures = unique(dataTable.equalizer_structure); +% Create the figure +showFiltered = true; +showPrecoded = false; +show_bitrate = true; +figure(5); +hold on + +for eqs = [equalizer_structure.vnle] + + % figure('Name',string([char(eqs),''])); + % hold on + + for pre_emph = [0,1] + + dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); + + eq_choice = equalizer_structure(eqs); + + if sum(eqstructures == eq_choice)~=1 + disp(eq_choice) + continue + end + + eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); + + % ===== NEW: compute averages + per-row keep masks (robust filtering) ===== + [Tav, keepMask, keepMaskP] = avgBerBySymbolrate(eq_filtered); % <= NEW + + % x-values (bitrate) for raw points (same mapping as your lines) + M = unique(eq_filtered.pam_level); % (assumes single PAM per curve) + if show_bitrate + x_raw = eq_filtered.symbolrate.*1e-9 .* floor(log2(M)*10)/10; + else + x_raw = eq_filtered.symbolrate.*1e-9; + end + + % ===== NEW: scatter kept raw BER points (hidden from legend) ===== + cols = cbrewer2('Paired',12); + thisColor = cols((2*eqs)+1+pre_emph,:); + scatter(x_raw(keepMask), ... % kept points + eq_filtered.BER(keepMask), ... + 14, thisColor, 'filled', ... + 'MarkerFaceAlpha', 0.35, ... + 'MarkerEdgeAlpha', 0.35, ... + 'HandleVisibility','off'); + + if showPrecoded + scatter(x_raw(keepMaskP), ... % kept precoded points + eq_filtered.BER_precoded(keepMaskP), ... + 14, thisColor, 'filled', ... + 'Marker', 'square', ... + 'MarkerFaceAlpha', 0.35, ... + 'MarkerEdgeAlpha', 0.35, ... + 'HandleVisibility','off'); + end + + % ===== NEW: optionally show filtered-out points in red ===== + if showFiltered + bad = ~keepMask; + if any(bad) + scatter(x_raw(bad), eq_filtered.BER(bad), ... + 18, 'r', 'x', 'LineWidth', 1.2, ... + 'HandleVisibility','off'); + end + badp = ~keepMaskP; + if any(badp) + scatter(x_raw(badp), eq_filtered.BER_precoded(badp), ... + 18, 'r', '+', 'LineWidth', 1.2, ... + 'HandleVisibility','off'); + end + end + + % Keep your sorting and one-per-symbolrate behavior (using Tav) + symbolrate_sorted = sortrows(Tav,{'symbolrate','avg_BER_calc'}, 'ascend'); + [~, ia] = unique(symbolrate_sorted.symbolrate, 'first'); + symbolrate_sorted = symbolrate_sorted(ia, :); + + if show_bitrate + % Bitrate for the averaged curves (unchanged) + xraw = symbolrate_sorted.symbolrate.*1e-9 .* floor(log2(M)*10)/10; + else + xraw = symbolrate_sorted.symbolrate.*1e-9; + end + % Use the MATLAB-averaged BERs + ber = symbolrate_sorted.avg_BER_calc; + ber_precoded = symbolrate_sorted.avg_BER_precoded_calc; + + + dname = strrep([char(eq_choice)],'_',' '); + if pre_emph + dname = [dname,' with pre-emph.']; + else + dname = [dname,' w/o pre-emph.']; + end + + plot(xraw, ber, ... + 'LineWidth', 1.5, 'MarkerSize', 5, ... + 'Marker','o','LineStyle','-', ... + 'Color',thisColor,'MarkerEdgeColor',thisColor,'MarkerFaceColor',[1,1,1], ... + 'DisplayName', dname); + + if showPrecoded + plot(xraw, ber_precoded, ... + 'LineWidth', 1.5, 'MarkerSize', 5, ... + 'Marker','square','LineStyle',':', ... + 'Color',thisColor,'MarkerEdgeColor',thisColor,'MarkerFaceColor',[1,1,1], ... + 'DisplayName', [dname,'; pre-coded']); + end + + grid on; + + if show_bitrate + xlabel('Net bitrate [GBps]', 'FontSize', 12); + else + xlabel('Symbol rate [GBd]', 'FontSize', 12); + end + ylabel('BER', 'FontSize', 12); + title('BER vs. Baud Rate','FontSize', 14, 'FontWeight', 'bold'); + + set(gca, 'XScale', 'linear', ... + 'YScale', 'log', ... + 'TickLabelInterpreter', 'latex', ... + 'FontSize', 11); + + xticks(xraw); + if show_bitrate + % xticks(200:25:500); + % xlim([350 500]); + xlim([min(xraw), max(xraw)]); + else + xlim([min(xraw), max(xraw)]); + end + + ylim([1e-4, 0.5]); + + end + yline([2.2e-4, 4.85e-3, 2e-2],'LineWidth',1,'LineStyle','--','HandleVisibility','off'); +end + +function [Tav, keepAll, keepAllP] = avgBerBySymbolrate(T, ZT, MIN_G) +% Minimal robust averaging of BER per symbolrate (+ masks for kept points). +% Usage: [Tav, keepAll, keepAllP] = avgBerBySymbolrate(T, ZT, MIN_G) +% Defaults: ZT=3 (MAD z-thresh in log10), MIN_G=2 (min points to filter) + + if nargin < 2, ZT = 5; end + if nargin < 5, MIN_G = 0; end + + hasP = ismember('BER_precoded', T.Properties.VariableNames); + hasNB = ismember('numBits', T.Properties.VariableNames); + + [G,~,idx] = unique(T.symbolrate); + nG = numel(G); + + avgBER = nan(nG,1); + avgBERp = nan(nG,1); + keepAll = false(height(T),1); + keepAllP = false(height(T),1); + + for gi = 1:nG + r = idx==gi; + + x = T.BER(r); + nb = hasNB * T.numBits(r) + ~hasNB; % if missing, nb==1 (scalar expansion ok) + + [avgBER(gi), keepAll(r)] = rmeanBer(x, nb, ZT, MIN_G); + + if hasP + xp = T.BER_precoded(r); + [avgBERp(gi), keepAllP(r)] = rmeanBer(xp, nb, ZT, MIN_G); + end + end + + Tav = table(G, avgBER, avgBERp, ... + 'VariableNames', {'symbolrate','avg_BER_calc','avg_BER_precoded_calc'}); +end + +function [mu, keep] = rmeanBer(x, nb, ZT, MIN_G, onlyHighOutliers, minKeepThreshold) +% Robust arithmetic mean of BER with log-domain MAD filtering (returns keep mask) +% +% Params: +% x : BER values +% nb : numBits (for floor) +% ZT : MAD z-threshold +% MIN_G : min group size before filtering +% onlyHighOutliers : (bool) if true, only discard values above mean +% minKeepThreshold : values below this BER are always kept +% +% Returns: +% mu : robust mean +% keep : logical mask of kept samples + + if nargin < 5, onlyHighOutliers = false; end + if nargin < 6, minKeepThreshold = 0; end + + x(~isfinite(x)) = NaN; + + if ~isscalar(nb), nb(~isfinite(nb)) = NaN; end + if isscalar(nb) && ~isfinite(nb), nb = 1; end + + floorVal = realmin; + if ~isscalar(nb) || (isscalar(nb) && isfinite(nb) && nb~=1) + fv = 0.5 ./ max(nb, eps); % rule-of-three style floor + if isscalar(fv), floorVal = fv; else, floorVal = fv; end + end + + xAdj = x; + bad = ~isfinite(xAdj) | xAdj <= 0; + if isscalar(floorVal) + xAdj(bad) = floorVal; + else + xAdj(bad) = floorVal(bad); + end + + valid = isfinite(xAdj) & xAdj > 0; + keep = false(size(xAdj)); + + if nnz(valid)==0 + mu = NaN; return + end + if nnz(valid) < MIN_G + mu = mean(xAdj(valid),'omitnan'); keep(valid)=true; return + end + + lx = log10(xAdj(valid)); + med = median(lx,'omitnan'); + mad = median(abs(lx-med),'omitnan'); + + if mad<=0 || ~isfinite(mad) + keep(valid) = true; + mu = mean(xAdj(valid),'omitnan'); + return + end + + sigma = 1.4826*mad; + ksel = abs(lx-med) <= ZT*sigma; + + % convert to linear indices + vIdx = find(valid); + + % === Extension A: only drop high outliers === + if onlyHighOutliers + logMean = mean(lx,'omitnan'); + highIdx = lx > logMean; + ksel = ksel | ~highIdx; % always keep values below/equal to mean + end + + % === Extension B: always keep values below minKeepThreshold === + belowThr = xAdj(valid) < minKeepThreshold; + ksel = ksel | belowThr; + + keep(vIdx(ksel)) = true; + + if any(keep) + mu = mean(xAdj(keep),'omitnan'); + else + mu = mean(xAdj(valid),'omitnan'); + keep(valid) = true; + end +end + diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate_only_best_configs.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate_only_best_configs.m new file mode 100644 index 0000000..e9a886e --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/ber_vs_rate_only_best_configs.m @@ -0,0 +1,117 @@ + +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +M = 8; +fp.where('Runs', 'pam_level','EQUALS', M); +% fp.where('Runs', 'bitrate','LESS_THAN', 310e9); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +fp.where('Runs', 'wavelength','EQUALS', 1310); +% fp.where('Runs', 'db_mode','EQUALS', 1); +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +[dataTable,~] = db.queryDB(fp, db.getTableFieldNames('dashboard_ungrouped_after_nov_2025')); + +eqstructures = unique(dataTable.equalizer_structure); + +% Create the figure +f=figure(4); +hold on + +eqs = [equalizer_structure.vnle, equalizer_structure.vnle_pf_mlse , equalizer_structure.vnle_db_mlse]; +cols = [0.4660 0.6740 0.1880 ; 0.9290 0.6940 0.1250 ; 0 0.4470 0.7410; 0.4940 0.1840 0.5560]; %VNLE; PF ; DFE ; DB tgt + +eq_choice = equalizer_structure.vnle; +pre_emph = 1; +dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); +eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); +symbolrate_sorted = sortrows(eq_filtered,{'symbolrate','min_BER_precoded'}, 'ascend'); +[~, ia] = unique(symbolrate_sorted.symbolrate, 'first'); +symbolrate_sorted = symbolrate_sorted(ia, :); +symbolrate = symbolrate_sorted.symbolrate.*1e-9; % in baud +bitrate = symbolrate * floor(log2(M)*10)/10; +ber = symbolrate_sorted.min_BER; % BER +ber_precoded = symbolrate_sorted.min_BER_precoded; % BER +plot(bitrate, ber, 'LineWidth', 1.5, 'MarkerSize', 5,'Marker','v','LineStyle',':','Color',cols(1,:),'MarkerEdgeColor',cols(1,:),'MarkerFaceColor',cols(1,:),'DisplayName',['Tx pre-emphasis + VNLE']); + +eq_choice = equalizer_structure.vnle_pf_mlse; +pre_emph = 0; +dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); +eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); +symbolrate_sorted = sortrows(eq_filtered,{'symbolrate','min_BER_precoded'}, 'ascend'); +[~, ia] = unique(symbolrate_sorted.symbolrate, 'first'); +symbolrate_sorted = symbolrate_sorted(ia, :); +symbolrate = symbolrate_sorted.symbolrate.*1e-9; % in baud +bitrate = symbolrate * floor(log2(M)*10)/10; +ber = symbolrate_sorted.min_BER; % BER +ber_precoded = symbolrate_sorted.min_BER_precoded; % BER +plot(bitrate, ber, 'LineWidth', 1.5, 'MarkerSize', 5,'Marker','diamond','LineStyle',':','Color',cols(2,:),'MarkerEdgeColor',cols(2,:),'MarkerFaceColor',cols(2,:),'DisplayName',['VNLE+2-tap post-filter+MLSE']); + +% eq_choice = equalizer_structure.dfe; +% pre_emph = 1; +% dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); +% eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); +% symbolrate_sorted = sortrows(eq_filtered,{'symbolrate','min_BER_precoded'}, 'ascend'); +% [~, ia] = unique(symbolrate_sorted.symbolrate, 'first'); +% symbolrate_sorted = symbolrate_sorted(ia, :); +% symbolrate = symbolrate_sorted.symbolrate.*1e-9; % in baud +% bitrate = symbolrate * floor(log2(M)*10)/10; +% ber = symbolrate_sorted.min_BER; % BER +% ber_precoded = symbolrate_sorted.min_BER_precoded; % BER +% plot(bitrate, ber, 'LineWidth', 1.5, 'MarkerSize', 5,'Marker','o','LineStyle','-','Color',cols(3,:),'MarkerEdgeColor',cols(3,:),'MarkerFaceColor',cols(3,:),'DisplayName',[char(eq_choice)]); + +eq_choice = equalizer_structure.vnle_db_mlse; +pre_emph = 0; +dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); +eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); +symbolrate_sorted = sortrows(eq_filtered,{'symbolrate','min_BER_precoded'}, 'ascend'); +[~, ia] = unique(symbolrate_sorted.symbolrate, 'first'); +symbolrate_sorted = symbolrate_sorted(ia, :); +symbolrate = symbolrate_sorted.symbolrate.*1e-9; % in baud +bitrate = symbolrate * floor(log2(M)*10)/10; +ber = symbolrate_sorted.min_BER; % BER +ber_precoded = symbolrate_sorted.min_BER_precoded; % BER +plot(bitrate, ber_precoded, 'LineWidth', 1.5, 'MarkerSize', 5,'Marker','square','LineStyle',':','Color',cols(4,:),'MarkerEdgeColor',cols(4,:),'MarkerFaceColor',cols(4,:),'DisplayName',['DB precoding + DB tgt. + MLSE']); + + +% Axis labels and title with Arial font +xlabel('Gross bitrate [Gb/s]', 'FontSize', 12, 'FontName', 'Arial', 'Interpreter', 'none'); +ylabel('BER', 'FontSize', 12, 'FontName', 'Arial', 'Interpreter', 'none'); +title('', 'FontSize', 14, 'FontWeight', 'bold', 'FontName', 'Arial', 'Interpreter', 'none'); + +% Improve tick formatting +set(gca, 'XScale', 'linear', ... + 'YScale', 'log', ... + 'TickLabelInterpreter', 'none', ... + 'FontSize', 11, ... + 'FontName', 'Arial'); + +% Legend with Arial font +% legend('FontName', 'Arial', 'Interpreter', 'none','Location','best'); + +xticks(bitrate); + +% Optional: tighten axis limits +xlim([min(bitrate), max(bitrate)]); +ylim([5e-4, 0.05]); + +yline([3.8e-3], 'LineWidth', 2, 'LineStyle', '--', ... + 'HandleVisibility', 'off', 'LabelHorizontalAlignment', 'left'); + +posH = get(f, 'Position'); % [left, bottom, width, height] +newPos = [posH(1), posH(2), 350, 200]; +set(f, 'Position', newPos); + +annotation(f,'textbox',... + [0.398095238095238 0.273381294964029 0.491428571428572 0.140287769784173],... + 'String',{'PAM-8; 2 km; 1293 nm'},... + 'LineWidth',0.5,... + 'FitBoxToText','off',... + 'BackgroundColor',[1 1 1]); diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_ROP.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_ROP.m new file mode 100644 index 0000000..d42b76d --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_ROP.m @@ -0,0 +1,146 @@ +%% ============================================================ +% SETTINGS +% ============================================================ +database_type = 'mysql'; +db = DBHandler("dataBase", "labor_highspeed", "type", database_type); + +fiberL = 1; % km +wlen = 1310; % nm +bit = 300e9; % example (adjust if needed) +max_pd = 7; % ROP limit (same as before) + +PAM_list = [4 6 8]; % formats to compare + +% Colors for PAM formats +colors = {clr.Paired.red, clr.Paired.green, clr.Paired.blue}; + +% Best DSP selection: +bestDSP = struct; +bestDSP = struct; +bestDSP.P4 = equalizer_structure.vnle_db_mlse; % PAM-4 +bestDSP.P6 = equalizer_structure.vnle; % PAM-6 +bestDSP.P8 = equalizer_structure.vnle; % PAM-8 + + + +%% ============================================================ +% LOOP over PAM formats — extract data +% ============================================================ +results = struct; + +for pi = 1:numel(PAM_list) + M = PAM_list(pi); + eq = bestDSP.(sprintf('P%d', M)); + + % ---- DB FILTER ---- + fp = QueryFilter(); + fp.where('Runs','pam_level','EQUALS',M); + fp.where('Runs','fiber_length','EQUALS',fiberL); + fp.where('Runs','wavelength','EQUALS',wlen); + fp.where('Runs','bitrate','EQUALS',bit); + fp.where('Runs','power_pd_in','LESS_THAN',max_pd); + + fields = [ + db.getTableFieldNames('power_state_info'); + db.getTableFieldNames('dashboard_ungrouped_alltime') + ]; + [T,~] = db.queryDB(fp, fields); + + % ---- DSP OPTIONS ---- + pre_emph = decide_preemph(M, eq); + precoded = decide_precoded(M, eq); + + cfg = struct; + cfg.x_axis = 'power_mzm'; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.outlier = 'none'; + cfg.show_raw = false; + + cfg.filters = struct( ... + 'pam_level', M, ... + 'fiber_length', fiberL, ... + 'wavelength', wlen, ... + 'bitrate', bit, ... + 'is_mpi', 0, ... + 'equalizer_structure', eq, ... + 'pre_emph', pre_emph); + + A = analyze_measurements_gpt(T, cfg); + + results(pi).M = M; + results(pi).x = A.group{1}.x; + results(pi).color = colors{pi}; + + if precoded + results(pi).ber = A.group{1}.y_precoded; + else + results(pi).ber = A.group{1}.y; + end +end + + +%% ============================================================ +% PLOT — all PAM formats in one ROP plot +% ============================================================ +fig = figure(91); hold on; + +lw = 2.2; ms = 7; + +for pi = 1:numel(results) + plot(results(pi).x, results(pi).ber, ... + '-o', ... + 'LineWidth', lw, ... + 'MarkerSize', ms, ... + 'MarkerFaceColor', results(pi).color, ... + 'Color', results(pi).color, ... + 'DisplayName', sprintf('PAM-%d', results(pi).M)); +end + +set(gca,'YScale','log'); +grid minor; + +xlabel('ROP / Power (MZM) [dBm]'); +ylabel('BER'); + +ylim([1e-4 2e-1]); + +legend('Location','best'); +title(sprintf('BER vs ROP — Best DSP (4,6,8) at %.0f GBd, λ=%d nm, %.0f km', ... + bit*1e-9, wlen, fiberL)); + +beautifyBERplot(); + +set(fig,'Position',1e3*[0.35 0.45 1.0 0.45]); + +%% ============================================================ +% DECISION LOGIC (INLINE FUNCTIONS) +% ============================================================ + +function pe = decide_preemph(M, eq) + % PRE-EMPH RULES: + switch M + case 4 + if eq == equalizer_structure.vnle + pe = 1; % PAM4: VNLE → pre-emph on + else + pe = 0; % PAM4: all others → off + end + case {6,8} + pe = 1; % PAM6/8: all → pre-emph on + otherwise + pe = 0; + end +end + + +function flag = decide_precoded(M, eq) + % PRE-CODE RULES: + if eq == equalizer_structure.vnle_db_mlse + flag = 1; % Always for DB-target + elseif eq == equalizer_structure.ml_mlse && M == 4 + flag = 1; % PAM4: ML-based → precoded + else + flag = 0; + end +end \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_WAVELENGTH.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_WAVELENGTH.m new file mode 100644 index 0000000..21f881f --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_WAVELENGTH.m @@ -0,0 +1,272 @@ +%% ============================================================ +% LOAD DATA FOR PAM = 4,6,8 +% ============================================================ +database_type = 'mysql'; +db = DBHandler("dataBase", "labor_highspeed", "type", database_type); + +pam_levels = [4, 6, 8]; % three tiles +bitrate_set = 360e9; +fiberL = 10; + +fields = [ + db.getTableFieldNames('power_state_info'); + db.getTableFieldNames('dashboard_ungrouped_alltime') +]; + +%% ============================================================ +% DEFINE DSP SCHEMES +% ============================================================ +curves = struct; + +curves(1).name = 'VNLE'; +curves(1).eq = equalizer_structure.vnle; +curves(1).color = clr.Paired.red; + +curves(2).name = 'PF + MLSE'; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).color = clr.Paired.green; + +curves(3).name = 'DB-target + MLSE'; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).color = clr.Paired.blue; + +curves(4).name = 'ML-based MLSE'; +curves(4).eq = equalizer_structure.ml_mlse; +curves(4).color = clr.Paired.purple; + +%% ============================================================ +% ANALYSIS — NO PLOTTING +% results(p, k) → p: PAM index, k: DSP index +% ============================================================ +results = struct; + +for p = 1:length(pam_levels) + M = pam_levels(p); + + % --- Load DB rows for this PAM --- + fp = QueryFilter(); + fp.where('Runs','pam_level','EQUALS', M); + fp.where('Runs','fiber_length','EQUALS', fiberL); + fp.where('Runs','bitrate','EQUALS', bitrate_set); + fp.where('Runs','is_mpi','EQUALS', 0); + + [dataTable, ~] = db.queryDB(fp, fields); + + for k = 1:numel(curves) + + %% ===================================================== + % DECIDE PRE-EMPHASIS AND PRECoded BER + % ====================================================== + pre_emph = decide_preemph(M, curves(k).eq); + use_precoded = decide_precoded(M, curves(k).eq); + + %% ---- base config ---- + cfg = struct; + cfg.x_axis = 'wavelength'; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.outlier = 'none'; + % cfg.group_by = {'wavelength'}; + cfg.show_raw = false; + + cfg.filters = struct( ... + 'pam_level', M, ... + 'is_mpi', 0, ... + 'bitrate', bitrate_set, ... + 'fiber_length', fiberL, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', pre_emph); + + %% ---- Run analysis ---- + A = analyze_measurements_gpt(dataTable, cfg); + + results(p,k).wavelength = A.group{1}.x; + + %% ---- store BER variant ---- + if use_precoded + results(p,k).ber = A.group{1}.y_precoded; + else + results(p,k).ber = A.group{1}.y; + end + + end +end + + +%% ============================================================ +% PLOT — 1×3 (PAM-4, PAM-6, PAM-8) +% ============================================================ +fig = figure(9110); clf; +tiledlayout(1,3,'TileSpacing','compact','Padding','compact'); + +lw = 1.8; +ms = 6; + +for p = 1:length(pam_levels) + nexttile; hold on; + + for k = 1:numel(curves) + plot(results(p,k).wavelength, results(p,k).ber, ... + '-o', ... + 'Color', curves(k).color, ... + 'MarkerFaceColor', curves(k).color, ... + 'MarkerSize', ms, ... + 'LineWidth', lw, ... + 'DisplayName', curves(k).name); + end + + set(gca,'YScale','log'); + grid on; + if p == 1 + ylabel('BER'); + else +ylabel(''); + end + xlabel('wavelength'); + + ylim([4e-4, 0.1]); + + beautifyBERplot(); + + yline([2.2e-4 4.85e-3 2e-2], ... + 'LineWidth',1.1, 'Color',[0.2 0.2 0.2], ... + 'LineStyle',':','HandleVisibility','off'); + + + if p == 1 + + x1 = 1290; + x2 = 1297; + x3 = 1300; + x4 = 1323; + x5 = 1325; + x6 = 1330; + + elseif p == 2 + + x1 = 1290; + x2 = 1295; + x3 = 1300; + x4 = 1323.5; + x5 = 1325; + x6 = 1330; + + elseif p == 3 + + x1 = 1290; + x2 = 1292; + x3 = 1298; + x4 = 1323; + x5 = 1327.5; + x6 = 1330; + end + + % --- Get current y-limits --- + yl = ylim; + + % --- LEFT AREA BELOW KP4 FEC --- + patch([x1 x2 x2 x1], [yl(1) yl(1) yl(2) yl(2)], ... + clr.Set1.red, ... % RGB = red + 'FaceAlpha', 0.1, ... % transparency 0.1 + 'EdgeColor', 'none'); % no border + + % --- RIGHT AREA BELOW KP4 FEC --- + patch([x2 x3 x3 x2], [yl(1) yl(1) yl(2) yl(2)], ... + clr.Set1.blue, ... % RGB = red + 'FaceAlpha', 0.10, ... % transparency 0.1 + 'EdgeColor', 'none'); % no border + + % --- LEFT AREA BELOW O-FEC --- + patch([x4 x5 x5 x4], [yl(1) yl(1) yl(2) yl(2)], ... + clr.Set1.blue, ... % RGB = red + 'FaceAlpha', 0.10, ... % transparency 0.1 + 'EdgeColor', 'none'); % no border + + % --- RIGHT AREA BELOW O-FEC --- + patch([x5 x6 x6 x5], [yl(1) yl(1) yl(2) yl(2)], ... + clr.Set1.red, ... % RGB = red + 'FaceAlpha', 0.10, ... % transparency 0.1 + 'EdgeColor', 'none'); % no border + + uistack(findobj(gca,'Type','patch'),'bottom'); % send the patch behind curves + + +% ax = gca; +% axpos = ax.Position; % [x y w h] normalized +% xl = xlim; +% yl = ylim; +% +% % Convert axis coords → normalized figure coords +% toNorm = @(x,y) [ ... +% axpos(1) + (x - xl(1)) / (xl(2)-xl(1)) * axpos(3), ... +% axpos(2) + (y - yl(1)) / (yl(2)-yl(1)) * axpos(4) ... +% ]; +% +% % Choose vertical placement (10% above bottom of axis) +% y_arrow = yl(1) * (yl(2)/yl(1))^0.10; % works with log-scale axes +% +% % === Arrow 1: x3 <-> x4 ====================================== +% p1 = toNorm(x3, y_arrow); +% p2 = toNorm(x4, y_arrow); +% +% annotation('doublearrow', ... +% [p1(1) p2(1)], [p1(2) p2(2)], ... +% 'Color', [0 0 0], 'LineWidth', 1.4); +% +% % === Arrow 2: x2 <-> x5 ====================================== +% p3 = toNorm(x2, y_arrow); +% p4 = toNorm(x5, y_arrow); +% +% annotation('doublearrow', ... +% [p3(1) p4(1)], [p3(2) p4(2)], ... +% 'Color', [0 0 0], 'LineWidth', 1.4); + +end + +pos = 1e3.*[2.7770 1.2017 1.4000 0.3200]; +set(fig, 'Position', pos); + +%% === EXPORT === +outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\wavelength_analysis.tikz'; +matlab2tikz(outfile, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'legend columns=1' ... + }); + + + +%% ============================================================ +% DECISION LOGIC (INLINE FUNCTIONS) +% ============================================================ + +function pe = decide_preemph(M, eq) + % PRE-EMPH RULES: + switch M + case 4 + if eq == equalizer_structure.vnle + pe = 1; % PAM4: VNLE → pre-emph on + else + pe = 0; % PAM4: all others → off + end + case {6,8} + pe = 1; % PAM6/8: all → pre-emph on + otherwise + pe = 0; + end +end + + +function flag = decide_precoded(M, eq) + % PRE-CODE RULES: + if eq == equalizer_structure.vnle_db_mlse + flag = 1; % Always for DB-target + elseif eq == equalizer_structure.ml_mlse && M == 4 + flag = 1; % PAM4: ML-based → precoded + else + flag = 0; + end +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_BER_3x4.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_BER_3x4.m new file mode 100644 index 0000000..5bd84b7 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_BER_3x4.m @@ -0,0 +1,132 @@ +database_type = 'mysql'; +dataBase = 'labor_highspeed'; +db = DBHandler("dataBase", dataBase, "type", database_type); + +%% FILTER QUERY +fp = QueryFilter(); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_alltime')]; + +[dataTable,~] = db.queryDB(fp, fields); + +%% ---- CONFIG ---- +cfg = struct; +cfg.x_axis = 'grossrate'; +cfg.y_axis = 'BER'; +cfg.y_scale = 'log'; +cfg.outlier = 'mad'; +cfg.show_raw = false; +cfg.show_spread = 'none'; +cfg.agg = 'min'; +cfg.show_precoded = 1; +cfg.fec_lines = []; + +cfg.plot = struct; +cfg.plot.use_cbrewer2 = false; +cfg.plot.lineWidth = 2.0; +cfg.plot.errWidth = 1.2; +cfg.plot.scatterAlpha = 0.35; +cfg.plot.legendLocation = 'best'; +cfg.plot.fecLineWidth = 2.4; +cfg.plot.custom_colors_scatter = []; % disabled + +%% ---- DSP DEFINITIONS ---- +DSP(1).name = 'VNLE'; +DSP(1).eq = equalizer_structure.vnle; +DSP(1).color = clr.Paired.red; +DSP(1).lightcolor = clr.Paired.lightred; + +DSP(2).name = 'VNLE PF MLSE'; +DSP(2).eq = equalizer_structure.vnle_pf_mlse; +DSP(2).color = clr.Paired.green; +DSP(2).lightcolor = clr.Paired.lightgreen; + +DSP(3).name = 'VNLE DB MLSE'; +DSP(3).eq = equalizer_structure.vnle_db_mlse; +DSP(3).color = clr.Paired.blue; +DSP(3).lightcolor = clr.Paired.lightblue; + +DSP(4).name = 'ML MLSE'; +DSP(4).eq = equalizer_structure.ml_mlse; +DSP(4).color = clr.Paired.purple; +DSP(4).lightcolor = clr.Paired.lightpurple; + +%% ---- GRID CONFIG ---- +rows = 3; % PAM 4,6,8 +cols = 4; % DSP schemes +pam = [4 6 8]; + +cfg.figure_number = 46; +fig = figure(cfg.figure_number); clf; + +t = tiledlayout(rows, cols, ... + 'TileSpacing','compact', ... + 'Padding','compact'); + +cfg.group_by = {'equalizer_structure','pre_emph'}; +cfg.plot.use_cbrewer2 = false; + +%% ==== MAIN PLOT LOOP ===== +for r = 1:rows + Mlev = pam(r); + + for c = 1:cols + ax = nexttile(t, (r-1)*cols + c); + cfg.ax = ax; + + % ---- PRE-EMPH = 1 ---- + cfg.filters = struct('is_mpi',0,'pam_level',Mlev, ... + 'equalizer_structure',DSP(c).eq, ... + 'pre_emph',1); + cfg.plot.custom_colors = DSP(c).lightcolor; + cfg.plot.custom_linetypes = {'-'}; + [~, M1] = plot_measurements_gpt(dataTable, cfg); + + % ---- PRE-EMPH = 0 ---- + cfg.filters.pre_emph = 0; + cfg.plot.custom_colors = DSP(c).color; + cfg.plot.custom_linetypes = {'-'}; + [~, M0] = plot_measurements_gpt(dataTable, cfg); + + % Axis limits + if Mlev == 4 + ylim([1e-5 0.3]); + elseif Mlev == 6 + ylim([6e-4 0.1]); + elseif Mlev == 8 + ylim([9e-4 0.1]); + end + + % ---- FEC lines ---- + yline([2.2e-4 4.85e-3 2e-2], ... + 'LineWidth',1.1, 'Color',[0.2 0.2 0.2], ... + 'LineStyle',':','HandleVisibility','off'); + + beautifyBERplot; + + % ---- Remove redundant labels ---- + if c > 1, ax.YLabel = []; end + if r < rows, ax.XLabel = []; end + + grid(ax,'on'); box(ax,'on'); + end +end + +%% ---- FIXED FIGURE SIZE ---- +pos = 1e3.*[0.1070 0.5497 1.4113 0.6847]; +set(fig, 'Position', pos); + +% %% === EXPORT === +% outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\compare_pre_emphasis.tikz'; +% matlab2tikz(outfile, ... +% 'width','\fwidth', ... +% 'height','\fheight', ... +% 'showInfo',false, ... +% 'extraAxisOptions',{ ... +% 'legend style={font=\footnotesize}', ... +% 'legend columns=1' ... +% }); diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_EYES.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_EYES.m new file mode 100644 index 0000000..c22e90f --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_EYES.m @@ -0,0 +1,257 @@ +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; +dsp_options.max_occurences = 1; +database = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' ); + +rate = [300e9]; +cols = cbrewer2('BuPu',25); +cols = [cols(end-10:2:end,:)]; +cols = cbrewer2('Set1',6); + +fignum = 200; +fig=figure(fignum);clf; + +dbmode = 0; + + +% 1 - PAM 4 with preemphasis +fp = QueryFilter(); +M = 6; +fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'bitrate','EQUALS', rate);%360,390 +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', dbmode); +fp.where('Runs', 'rop_attenuation','EQUAL', 0); + +[dataTable,~] = db.queryDB(fp, database.getTableFieldNames('Runs')); + +dataTable = queryRunid(dataTable.run_id, database); +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); +duob_mode = db_mode(strrep(dataTable.db_mode,'"','')); + +% Load and Sync signal data from DB +[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + +Scpe_sig.eye(fsym,M,"fignum",M*10); + +%% === EXPORT TO TIKZ === +% outfile = ['C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\eye_pam_',num2str(M),'.tikz']; +% outfile = ['C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\vnle_optimization.tikz']; +% matlab2tikz(outfile, ... +% 'width','\fwidth', ... +% 'height','\fheight', ... +% 'showInfo',false, ... +% 'extraAxisOptions',{ ... +% 'legend style={font=\footnotesize}', ... +% 'legend columns=1' ... +% } ); + +%% + +if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation + trellexlusion = 1; +else + trellexlusion = 0; +end +mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3); +len_tr = 4096*2; + +ffe_order = [50, 5, 5]; +dfe_order = [0, 0, 0]; +pf_ncoeffs = 1; +mu_ffe = [0.0001, 0.0008, 0.001]; +mu_dfe = 0.0004; +mu_dc = 0.005; +dc_buffer_len = 1; + +mu_tr = 0; +mu_dd = 0.05; +adaption= 1; +use_dd_mode = 1; +ffe_order = [50, 5, 5]; +eq_ = EQ("Ne",ffe_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); + +dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode, ... + 'showAnalysis', 1,... + "postFFE", []); + +%% === FINAL FIGURE SIZE === + +% Existing figure numbers +figEye = 249; +figConst = 341; + +% Find axes in the source figures +srcAxEye = findobj(figEye, 'Type', 'axes'); +srcAxConst = findobj(figConst, 'Type', 'axes'); + +% Create new combined figure +figCombined = figure; +t = tiledlayout(figCombined, 1, 2); +t.TileSpacing = 'compact'; +t.Padding = 'compact'; + +% ------------------------------------------------------------ +% LEFT TILE: EYE DIAGRAM +% ------------------------------------------------------------ +ax1 = nexttile(t, 1); +hold(ax1, 'on') + +% Copy children (images, lines, patches, hist objects, etc.) +copyobj(srcAxEye.Children, ax1); + +% Copy labels and title +ax1.XLabel.String = srcAxEye.XLabel.String; +ax1.YLabel.String = srcAxEye.YLabel.String; +ax1.Title.String = srcAxEye.Title.String; + +% Copy axis limits +ax1.XLim = srcAxEye.XLim; +ax1.YLim = srcAxEye.YLim; +ax1.YDir = srcAxEye.YDir; + +% Copy ticks + labels EXACTLY (including remapped/scaled ones) +ax1.XTick = srcAxEye.XTick; +ax1.XTickLabel = srcAxEye.XTickLabel; +ax1.YTick = srcAxEye.YTick; +ax1.YTickLabel = srcAxEye.YTickLabel; + +% Copy colormap + clim (important for density eye) +colormap(ax1, colormap(srcAxEye.Parent)); +ax1.CLim = srcAxEye.CLim; + +% Copy any style props that matter +ax1.TickDir = srcAxEye.TickDir; +ax1.TickLength = srcAxEye.TickLength; +ax1.FontSize = srcAxEye.FontSize; +ax1.Box = srcAxEye.Box; + +grid(ax1,'on'); + + +% ------------------------------------------------------------ +% RIGHT TILE: CONSTELLATION HISTOGRAM +% ------------------------------------------------------------ +ax2 = nexttile(t, 2); +hold(ax2, 'on') + +copyobj(srcAxConst.Children, ax2); + +% Copy labels and title +ax2.XLabel.String = srcAxConst.XLabel.String; +ax2.YLabel.String = srcAxConst.YLabel.String; +ax2.Title.String = srcAxConst.Title.String; + +% The histogram uses the same y-axis as the eye +% Extract mapping from eye +rawTicks = ax1.YTick; +rawLabelsCell = ax1.YTickLabel; +trueVoltages = str2double(rawLabelsCell); + +% Apply true voltages to the histogram axis +ax2.XTick = flip(trueVoltages); +ax2.XTickLabel = flip(rawLabelsCell); + +% Set histogram y-limits to match the actual voltages +ax2.XLim = [min(trueVoltages) max(trueVoltages)]; + +% Ensure eye diagram prints the same (we *do not* touch ax1.YLim) +ax1.XTickLabel = rawLabelsCell; + + +% Copy colormap (your histogram uses same palette) +colormap(ax2, colormap(srcAxConst.Parent)); + +% Style properties +ax2.TickDir = srcAxConst.TickDir; +ax2.TickLength = srcAxConst.TickLength; +ax2.FontSize = srcAxConst.FontSize; +ax2.Box = srcAxConst.Box; + +grid(ax2,'on'); + +% ============================================================ +% remove right y-axis completely +% ============================================================ +ax2.XAxis.Visible = 'off'; % hides ticks + labels + axis line + +% BUT we still keep the YTick positions internally for alignment: +% ax2.YTick = ; + + +% ============================================================ +% minimize distance between the two plots +% ============================================================ +t.TileSpacing = 'none'; % no space between tiles +t.Padding = 'none'; % no outer padding + +% Also reduce internal padding for each axis +ax1.Position(3) = ax1.Position(3) + 0.02; % widen eye a bit +ax2.Position(1) = ax2.Position(1) - 0.02; % pull histogram closer + + +% Keep left axis grid visible +ax2.YGrid = 'off'; + +% +% ===================================================================== +% FINAL POLISHING: unified visual style +% ======================================================================= + +% --- unified font size --- +FS = 12; +set([ax1 ax2], 'FontSize', FS); + +% --- unified axis line width (outline stroke thickness) --- +LW = 1.0; +set([ax1 ax2], 'LineWidth', LW); + +% --- unified tick length --- +TL = [.015 .015]; +set([ax1 ax2], 'TickLength', TL); + +% --- unified grid style --- +set([ax1 ax2], 'XGrid', 'on', 'YGrid', 'on'); +set([ax1 ax2], 'GridLineStyle', '--'); +set([ax1 ax2], 'GridAlpha', 0.2); + +% --- remove right y-axis ticks and labels --- +ax2.YAxis.Visible = 'off'; + +% --- copy colormap + CLim from the eye to histogram (synchronize look) --- +colormap(ax1, colormap(srcAxEye.Parent)); +colormap(ax2, colormap(srcAxEye.Parent)); +ax2.CLim = ax1.CLim; + +% --- minimal spacing between tiles --- +t.TileSpacing = 'none'; +t.Padding = 'none'; + + +% --- pull the panels together (touching boundary effect) --- +pos1 = ax1.Position; +pos2 = ax2.Position; + +% Shift histogram left until the outlines touch +pos2(1) = pos1(1) + pos1(3) - 0.002; % 0.002 = fine overlap control +ax2.Position = pos2; + +% Expand histogram slightly, remove white band +pos2 = ax2.Position; +pos2(3) = pos2(3) + 0.01; +ax2.Position = pos2; + +% Ensure the left plot stays correct after the move +ax1.Position = pos1; + +% --- enforce same visible outline --- +% For ax2, create a fake left spine (since YAxis is hidden) +ax2.Box = 'on'; % keep outline but no ticks on the right +ax1.Box = 'on'; + +ax2.View = [90 -90]; \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI.m new file mode 100644 index 0000000..cf3e317 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI.m @@ -0,0 +1,221 @@ +%% ============================================================ +% GRID: NGMI, AIR, HD-NetRate, SD-NetRate (1 × 4) +% ============================================================ + +db = DBHandler("dataBase","labor_highspeed","type","mysql"); + +%% --- Base DB Filters (shared across all curves) +fp = QueryFilter(); +fp.where('Runs','fiber_length','EQUALS', 2); +fp.where('Runs','wavelength','EQUALS', 1310); +fp.where('Runs','rop_attenuation','EQUALS', 0); +fp.where('Runs','is_mpi','EQUALS', 0); + +fields = db.getTableFieldNames('dashboard_ungrouped_alltime'); +[dataTable,~] = db.queryDB(fp, fields); + + +%% === Curve Definitions ======================================= +curves = struct; + +% PAM-8 — VNLE PF MLSE — no_emph = 1 — RED +curves(1).pam = 8; +curves(1).eq = equalizer_structure.vnle_pf_mlse; +curves(1).pre = 0; +curves(1).color = clr.Paired.red; +curves(1).mkr = 'o'; + +% PAM-6 — VNLE PF MLSE — no_emph = 1 — BLUE +curves(2).pam = 6; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).pre = 1; +curves(2).color = clr.Paired.blue; +curves(2).mkr = 'square'; + +% PAM-4 — VNLE DB MLSE — pre_emph = 0 — GREEN +curves(3).pam = 4; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).pre = 0; +curves(3).color = clr.Paired.green; +curves(3).mkr = 'diamond'; + +%% === Prepare Analysis Config ================================== +base = struct; +base.group_by = {'equalizer_structure','pre_emph'}; +base.x_axis = 'symbolrate'; +base.outlier = 'none'; +base.show_raw = false; +base.filters = struct; % will be filled per curve + + +%% === Precompute All Curves ==================================== +results = struct; + +for k = 1:numel(curves) + + % --- BER --- + cfg = base; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.filters = struct('pam_level', curves(k).pam, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', curves(k).pre); + + A = analyze_measurements_gpt(dataTable, cfg); + cfg.x_axis = 'grossrate'; + B = analyze_measurements_gpt(dataTable, cfg); + + results(k).baudr = A.group{1}.x; + results(k).gross = B.group{1}.x; + + if curves(k).pam == 4 + results(k).ber = A.group{1}.y_precoded; + else + results(k).ber = A.group{1}.y; + end + + % --- NGMI --- + cfg.y_axis = 'NGMI'; + cfg.agg = 'max'; + A = analyze_measurements_gpt(dataTable, cfg); + results(k).ngmi = A.group{1}.y; + + + + % --- AIR --- + cfg.y_axis = 'AIR'; + cfg.agg = 'max'; + A = analyze_measurements_gpt(dataTable, cfg); + results(k).air = A.group{1}.y; + results(k).air = results(k).ngmi .* results(k).gross; + + % --- Net Rates --- + tp = TransmissionPerformance; + results(k).ndr = tp.calculateNetRate(results(k).gross, ... + 'NGMI', results(k).ngmi, ... + 'BER', results(k).ber); +end + + +%% ============================================================ +% FIGURE: 1 × 4 GRID +% ============================================================ +fig = figure(71); clf; +t = tiledlayout(1,4, 'TileSpacing','compact', 'Padding','compact'); + +lw = 1.0; + +% === NGMI vs Grossrate === +ax = nexttile(t,1); +hold on; +for k = 1:3 + plot(results(k).baudr, results(k).ngmi, ... + 'LineWidth', lw, ... + 'Color', curves(k).color, ... + 'MarkerSize', 1, ... + 'MarkerFaceColor', curves(k).color,... + 'Marker',curves(k).mkr); +end +ylabel('NGMI'); +xlabel('Baud rate [GBd]'); +xlim([100 210]); +xticks(100:15:225); +ylim([0.9, 1]); +grid minor; box on; +beautifyBERplot("logscale",0,"setmarkers",0); + + +% === AIR vs Grossrate === +ax = nexttile(t,2); +hold on; +for k = 1:3 + plot(results(k).baudr, results(k).air, ... + '-', 'LineWidth', lw, ... + 'Color', curves(k).color, ... + 'MarkerSize', 2, ... + 'MarkerFaceColor', curves(k).color,'Marker',curves(k).mkr); +end +ylabel('AIR [Gb/s]'); +xlabel('Baud rate [GBd]'); +ylim([280 430]); +yticks(280:30:440) +xlim([100 210]); +xticks(100:15:225); +grid minor; box on; +beautifyBERplot("logscale",0,"setmarkers",0); +yline(400,'LineStyle','--'); + +% === SD-FEC Net Rate === +ax = nexttile(t,3); +hold on; +for k = 1:3 + plot(results(k).baudr, results(k).ndr.SDHD.NetRate, ... + 'LineWidth', lw, ... + 'Color', curves(k).color, ... + 'MarkerSize', 2, ... + 'MarkerFaceColor', curves(k).color,... + 'Marker',curves(k).mkr); +end +ylabel('NDR [Gb/s]'); +xlabel('Baud rate [GBd]'); +ylim([280 430]); +yticks(280:30:440) +xlim([100 210]); +xticks(100:15:225); +grid minor; box on; +beautifyBERplot("logscale",0,"setmarkers",0); +yline(400,'LineStyle','--'); + +% === HD-FEC Net Rate === +ax = nexttile(t,4); +hold on; +for k = 1:3 + % plot(results(k).baudr, results(k).ndr.STAIR.NetRate, ... + % '-', 'LineWidth', lw, ... + % 'Color', curves(k).color, ... + % 'MarkerSize', 4,'Marker','+', ... + % 'MarkerFaceColor', curves(k).color); + + plot(results(k).baudr, results(k).ndr.O_FEC.NetRate, ... + ':', 'LineWidth', lw, ... + 'Color', curves(k).color, ... + 'MarkerSize', 2,... + 'MarkerFaceColor', curves(k).color,... + 'Marker',curves(k).mkr); + + plot(results(k).baudr, results(k).ndr.KP4_hamming.NetRate, ... + '--', 'LineWidth', lw, ... + 'Color', curves(k).color, ... + 'MarkerSize', 2,'Marker','diamond', ... + 'MarkerFaceColor', curves(k).color,... + 'Marker',curves(k).mkr); +end + +yline(400,'LineStyle','--'); +ylabel(''); +xlabel('Baud rate [GBd]'); +ylim([280 430]); +yticks(280:30:440) +xlim([100 210]); +xticks(100:15:225); +grid minor; box on; +beautifyBERplot("logscale",0,"setmarkers",0); + +% === FINAL FIGURE SIZE === +pos = 1e3.*[0.7950 1.1150 1.4113 0.1900]; +set(fig, 'Position', pos); + +% % % %% === EXPORT === +outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\compare_ndr_v3.tikz'; +matlab2tikz(outfile, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'legend columns=1' ... + 'every axis/.append style={font=\scriptsize}',... + 'minor grid style={line width=0.2pt, solid, color=black!10}',... + 'grid style={line width=0.4pt, solid, color=black!20}',... + 'grid style={dashed}',... + }); diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI_v2.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI_v2.m new file mode 100644 index 0000000..a690207 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_NGMI_v2.m @@ -0,0 +1,180 @@ +%% ============================================================ +% GRID (1 × 4): +% 1) NGMI overview (PAM4+PAM6+PAM8 superimposed) +% 2) PAM-4 tile (AIR + SD-NDR + HD-NDR) +% 3) PAM-6 tile +% 4) PAM-8 tile +% ============================================================ + +db = DBHandler("dataBase","labor_highspeed","type","mysql"); + +%% --- Base DB Filters (shared across all curves) +fp = QueryFilter(); +fp.where('Runs','fiber_length','EQUALS', 2); +fp.where('Runs','wavelength','EQUALS', 1310); +fp.where('Runs','rop_attenuation','EQUALS', 0); +fp.where('Runs','is_mpi','EQUALS', 0); + +fields = db.getTableFieldNames('dashboard_ungrouped_alltime'); +[dataTable,~] = db.queryDB(fp, fields); + +%% === CURVE DEFINITIONS ================================================= +curves = struct; + +curves(1).pam = 8; +curves(1).eq = equalizer_structure.vnle_pf_mlse; +curves(1).pre = 0; +curves(1).color = clr.Paired.red; + +curves(2).pam = 6; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).pre = 1; +curves(2).color = clr.Paired.blue; + +curves(3).pam = 4; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).pre = 0; +curves(3).color = clr.Paired.green; + +% === ANALYSIS ENGINE (extract BER/NGMI/AIR/netrates) =================== +base = struct; +base.group_by = {'equalizer_structure','pre_emph'}; +base.x_axis = 'grossrate'; +base.outlier = 'none'; +base.show_raw = false; + +results = struct; + +for k = 1:numel(curves) + + % ========== BER ========== + cfg = base; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + + cfg.filters = struct('pam_level', curves(k).pam, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', curves(k).pre); + A = analyze_measurements_gpt(dataTable, cfg); + + results(k).gross = A.group{1}.x; + if curves(k).pam == 4 + results(k).ber = A.group{1}.y_precoded; + else + results(k).ber = A.group{1}.y; + end + + % ========== NGMI ========== + cfg.y_axis = 'NGMI'; cfg.agg = 'max'; + A = analyze_measurements_gpt(dataTable, cfg); + results(k).ngmi = A.group{1}.y; + + % ========== AIR ========== + cfg.y_axis = 'AIR'; cfg.agg = 'max'; + A = analyze_measurements_gpt(dataTable, cfg); + results(k).air = A.group{1}.y; + + % ========== NET RATES ========== + tp = TransmissionPerformance; + results(k).ndr = tp.calculateNetRate(results(k).gross, ... + 'NGMI', results(k).ngmi, ... + 'BER', results(k).ber); +end + + +% ============================================================ +% FIGURE +% ============================================================ +fig = figure(3); +t = tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); + +lw = 1.7; + +% ======================================================================= +% (1) NGMI OVERVIEW TILE (all 3 curves) +% ======================================================================= +ax = nexttile(t,1); hold on; + +for k = 1:3 + plot(results(k).gross, results(k).ngmi, ... + '-o', 'Color', curves(k).color, ... + 'LineWidth',lw,'MarkerSize',5, ... + 'MarkerFaceColor',curves(k).color); +end + +ylabel('NGMI'); +xlabel('Grossrate [Gb/s]'); +ylim([0.9 1]); % your chosen limits +xlim([300 480]); +xticks(300:30:480) +grid minor; box on; +beautifyBERplot; + +% ======================================================================= +% (2–4) PAM-SPECIFIC TILES: AIR, SD-NDR, HD-NDR +% ======================================================================= + +pam_order = [4 6 8]; % left → right + +for ti = 1:3 + pam_target = pam_order(ti); + ax = nexttile(t, 1+ti); hold on; + + % find matching curve + for k = 1:3 + if curves(k).pam ~= pam_target, continue; end + + col = curves(k).color; + + % AIR + plot(results(k).gross, results(k).air, ... + '-','Color',col,'LineWidth',lw,'Marker','*', ... + 'MarkerSize',5,'MarkerFaceColor',col,'DisplayName','AIR'); + + % SD-based net rate + plot(results(k).gross, results(k).ndr.SDHD.NetRate, ... + '--','Color',col,'LineWidth',lw,'Marker','v', ... + 'MarkerSize',5,'MarkerFaceColor',col,'DisplayName','SD+HD'); + + % HD-based net rate + plot(results(k).gross, results(k).ndr.STAIR.NetRate, ... + ':','Color',col,'LineWidth',lw,'Marker','x', ... + 'MarkerSize',5,'MarkerFaceColor',col,'DisplayName','HD-FEC (Staircase)'); + + % HD-based net rate + plot(results(k).gross, results(k).ndr.O_FEC.NetRate, ... + 'LineStyle','-.','Color',col,'LineWidth',lw,'Marker','+', ... + 'MarkerSize',5,'MarkerFaceColor',col,'DisplayName','O-FEC'); + + % HD-based net rate + plot(results(k).gross, results(k).ndr.KP4_hamming.NetRate, ... + 'LineStyle','-','Color',col,'LineWidth',lw,'Marker','x', ... + 'MarkerSize',5,'MarkerFaceColor',col,'DisplayName','KP4+Hamming'); + end + + ylabel('NDR [Gb/s]'); + xlabel('Grossrate [Gb/s]'); + ylim([300 440]); % your chosen limits + yticks(300:20:480) + xlim([300 480]); + xticks(300:30:480) + grid minor; box on; + beautifyBERplot; + yline(400,'HandleVisibility','off'); +end + +% === FIX FIGURE SIZE FOR TIKZ ========================================== +if 0 +pos = 1e3.*[0.3643 0.9943 1.4113 0.2120]; +set(fig,'Position',pos); + +% outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\compare_ndr.tikz'; +% matlab2tikz(outfile, ... +% 'width','\fwidth', ... +% 'height','\fheight', ... +% 'showInfo',false, ... +% 'extraAxisOptions',{ ... +% 'legend style={font=\footnotesize}', ... +% 'legend columns=1' ... +% }); +end \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_ROP.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_ROP.m new file mode 100644 index 0000000..5a3c530 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_ROP.m @@ -0,0 +1,153 @@ +%% ============================================================ +% LOAD DATA (PAM-4, sweep over ROP) +% ============================================================ +database_type = 'mysql'; +db = DBHandler("dataBase", "labor_highspeed", "type", database_type); + +pam_level = 4; +fiberL = 1; % 1 km +wlen = 1310; +baudrate = 360e9; + +fp = QueryFilter(); +fp.where('Runs','pam_level','EQUALS', pam_level); +fp.where('Runs','fiber_length','EQUALS', fiberL); +fp.where('Runs','wavelength','EQUALS', wlen); +fp.where('Runs','bitrate','EQUALS', baudrate); +fp.where('Runs','power_pd_in','LESS_THAN', 7); + +fields = [ + db.getTableFieldNames('power_state_info'); + db.getTableFieldNames('dashboard_ungrouped_alltime') +]; + +[dataTable,~] = db.queryDB(fp, fields); + + +%% ============================================================ +% DSP SCHEMES (Best combinations only) +% ============================================================ +curves = struct; + +curves(1).name = 'VNLE'; +curves(1).eq = equalizer_structure.vnle; +curves(1).color = clr.Paired.red; + +curves(2).name = 'PF + MLSE'; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).color = clr.Paired.green; + +curves(3).name = 'DB-target + MLSE'; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).color = clr.Paired.blue; + +curves(4).name = 'ML-based MLSE'; +curves(4).eq = equalizer_structure.ml_mlse; +curves(4).color = clr.Paired.purple; + + + + +%% ============================================================ +% ANALYSIS ENGINE (No plotting) +% ============================================================ +results = struct; + +for k = 1:numel(curves) + + pre_emph = decide_preemph(pam_level,curves(k).eq); + precoded = decide_precoded(pam_level,curves(k).eq); + + cfg = struct; + cfg.x_axis = 'power_mzm'; % ROP axis + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.outlier = 'none'; + cfg.show_raw = false; + + cfg.filters = struct( ... + 'pam_level', pam_level, ... + 'fiber_length', fiberL, ... + 'wavelength', wlen, ... + 'bitrate', baudrate, ... + 'is_mpi', 0, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', pre_emph); + + A = analyze_measurements_gpt(dataTable, cfg); + + results(k).x = A.group{1}.x; + if precoded + results(k).ber = A.group{1}.y_precoded; + else + results(k).ber = A.group{1}.y; + end +end + + +%% ============================================================ +% PLOT — BER vs ROP (Single Axis) +% ============================================================ +fig = figure(); clf; hold on; + +lw = 2.0; +ms = 7; + +for k = 1:numel(curves) + plot(results(k).x, results(k).ber, ... + '-o', ... + 'Color', curves(k).color, ... + 'MarkerFaceColor', curves(k).color, ... + 'MarkerSize', ms, ... + 'LineWidth', lw, ... + 'DisplayName', curves(k).name); +end + +set(gca,'YScale','log'); +grid on; + +xlabel('ROP / Power (MZM) [dBm]'); +ylabel('BER'); + +ylim([1e-4 2e-1]); + +title(sprintf('BER vs ROP — PAM-%d, %.0f km, %.0f GBd, %.0f nm', ... + pam_level, fiberL, baudrate*1e-9, wlen)); + +legend('Location','best'); +beautifyBERplot(); + +pos = 1e3.*[0.2 0.6 1.3 0.4]; +set(fig, 'Position', pos); + +%% ============================================================ +% DECISION LOGIC (INLINE FUNCTIONS) +% ============================================================ + +function pe = decide_preemph(M, eq) + % PRE-EMPH RULES: + switch M + case 4 + if eq == equalizer_structure.vnle + pe = 1; % PAM4: VNLE → pre-emph on + else + pe = 0; % PAM4: all others → off + end + case {6,8} + pe = 1; % PAM6/8: all → pre-emph on + otherwise + pe = 0; + end +end + + +function flag = decide_precoded(M, eq) + % PRE-CODE RULES: + if eq == equalizer_structure.vnle_db_mlse + flag = 1; % Always for DB-target + elseif eq == equalizer_structure.ml_mlse && M == 4 + flag = 1; % PAM4: ML-based → precoded + else + flag = 0; + end +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_Spectra.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_Spectra.m new file mode 100644 index 0000000..97dc7a9 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_Spectra.m @@ -0,0 +1,182 @@ +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; +dsp_options.max_occurences = 1; +database = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' ); + +rates = [300e9]; +cols = cbrewer2('BuPu',25); +cols = [cols(end-10:2:end,:)]; +cols = cbrewer2('Set1',6); + +fignum = 200; +fig=figure(fignum);clf; + +for dbmode = 0:1%length(rates) + + + if 0 + rcalpha = 0.05; + fsym = rates/2; + pulsef = 1; + Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rcalpha); + + Pamsource = PAMsource(... + "fsym",fsym,"M",4,"order",18,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.2,... + "applypulseform",pulsef,"pulseformer",Pform,... + "randkey",20,... + "db_precode",dbmode,"db_encode",0,... + "mrds_code",0,"mrds_blocklength",512); + + [Digi_sig,Symbols,Bits] = Pamsource.process(); + + Digi_sig = Digi_sig.normalize("mode","rms"); + + %%% 1) PLOT FULL RESPONSE SIGNAL + Digi_sig.spectrum("displayname","Full Response","fignum",fignum+dbmode,"normalizeToNyquist",0,"normalizeTo0dB",0,"color",[0.2,0.2,0.2],"linestyle",'-','addDCoffset',0,'normalizeToDC',1); + + + %%% 2) PLOT PREEMPH. TX SIGNAL + if dbmode == 0 + maxamp = -37; + precomp_est = ChannelFreqResp("Nacq",2048,"Navg",100,"Ncp",63,'f_ref',Digi_sig.fs); + + precomp_path = "C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\precomp"; + precomp_fn = "lab_high_speed"; + Digi_sig_pre = precomp_est.precomp(Digi_sig,'maxampdb',maxamp,'loadPath',precomp_path,'fileName',precomp_fn); + + 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",fignum+dbmode,"normalizeToNyquist",0,"normalizeTo0dB",0,"color",[0,0,0],"linestyle",'-.','addDCoffset',0,'normalizeToDC',1); + end + + end + + % 1 - PAM 4 with preemphasis + fp = QueryFilter(); + M = 4; + fp.where('Runs', 'pam_level','EQUALS', M); + fp.where('Runs', 'bitrate','EQUALS', rates);%360,390 + fp.where('Runs', 'fiber_length','EQUALS', 10); + fp.where('Runs', 'wavelength','EQUALS', 1322.7); %1327.4 + fp.where('Runs', 'db_mode','EQUALS', dbmode); + fp.where('Runs', 'rop_attenuation','EQUAL', 0); + + [dataTable,~] = database.queryDB(fp, database.getTableFieldNames('Runs')); + + dataTable = queryRunid(dataTable.run_id, database); + fsym = dataTable.symbolrate; + M = double(dataTable.pam_level); + + % Load and Sync signal data from DB + [Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + + % Preprocess signal + Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + Scpe_sig = Scpe_cell{1}; + + %%% 3) PLOT DB Tgt. SIGNAL + if 1 + DB_Symbols = Duobinary().encode(Symbols); + DB_Symbols.spectrum("fignum",fignum+dbmode,"normalizeTo0dB",1,"displayname",'DB-Response','addDCoffset',0,'color',clr.Set1.blue,'normalizeToNyquist',0,'linestyle','--'); + end + + %%% 4) Plot RX Signal + Scpe_sig.spectrum("fignum",fignum+dbmode,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',1,'color',[0,0,0],'normalizeToNyquist',0,'linestyle',':'); +Scpe_sig.eye(fsym,M,"fignum",47,"displayname",' Eye of AVG Signal'); + % xline(Symbols.fs/2.*1e-9,'Color',cols(r,:),'HandleVisibility','off'); + + average_signals = 1; + if average_signals + Scpe_sig_avg = Scpe_sig; + scope_mean = zeros(size(Scpe_cell{1}.signal)); + for n=1:numel(Scpe_cell) + scope_mean = scope_mean + Scpe_cell{n}.signal; + end + scope_mean = scope_mean ./ n; + Scpe_sig_avg.signal = scope_mean; + + Scpe_sig_avg.spectrum("displayname","Scope PSD","fignum",20,"normalizeTo0dB",1); + Scpe_sig_avg.plot("displayname","Scope raw signal","fignum",27,"clear",1); + Scpe_sig_avg.eye(fsym,M,"fignum",48,"displayname",' Eye of AVG Signal'); + end + + + fig = figure(fignum+dbmode); + if dbmode == 0 + ylim([-22,12]); + else + ylim([-22,2]); + end + xlim([0,105]); + xticks(-100:20:100); + yticks(-20:10:10); + + beautifyBERplot("logscale",0,"setmarkers",0) + pos = [100.3333 991.6667 358.0000 192.6667]; + set(fig, 'Position', pos); + + %%%%%%%%%%%% + drawnow; + + % Do EQ and find alpha's + len_tr = 4096*2; + + ffe_order = [50, 5, 5]; + dfe_order = [0, 0, 0]; + pf_ncoeffs = 1; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + mu_dc = 0.005; + + %%% FULL RESP TARGET + eq_ = EQ("Ne",ffe_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",0); + pf_1 = Postfilter("ncoeff",1,"useBurg",1); + + [eq_signal_sd, eq_noise] = eq_.process(Scpe_sig, Symbols); + + % eq_noise.signal = eq_noise.signal - mean(eq_noise.signal); + % eq_noise = eq_noise.normalize("mode","rms"); + + [mlse_sig_sd,whitened_noise] = pf_1.process(eq_signal_sd, eq_noise); + + fig = figure(fignum+dbmode+10); hold on + + [h, w] = freqz(1, pf_1.coefficients, length(eq_noise), "whole", eq_noise.fs); + h = h / max(abs(h)); % Normalize the filter response + w_ = (w - eq_noise.fs / 2); + + %%% DB TARGET + db_ref_sequence = Duobinary().encode(Symbols); + eq_ = EQ("Ne",ffe_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",0); + [eq_signal, db_noise] = eq_.process(Scpe_sig,db_ref_sequence); + + % db_noise.signal = db_noise.signal - mean(db_noise.signal); + % db_noise = db_noise.normalize("mode","rms"); + + %%% 1-3) Plot EQ Noise EEN + figure(fignum+dbmode+10) + eq_noise.spectrum("displayname", 'Noise', "fignum", fignum+dbmode+10, "normalizeTo0dB", 0,"color",clr.Set1.green,"normalizeToDC",0,"addDCoffset",0); + if dbmode == 1 + offset = 27.7; + else + offset = 29.8; + end + plot(w_ * 1e-9, 20 * log10(fftshift(abs(h)))-offset, 'DisplayName', ['Burg Coeffs: ', num2str(round(pf_1.coefficients, 2)), ' '], 'LineWidth', 1,'Color',clr.Set1.green,'LineStyle','--'); + db_noise.spectrum("displayname", 'DBt. Noise', "fignum", fignum+dbmode+10, "normalizeTo0dB", 0,"color",clr.Set1.blue,"normalizeToDC",0,"addDCoffset",0); + + ylim([-54,-25]); + xlim([0,105]); + xticks(0:20:110); + yticks(-50:10:10); + + beautifyBERplot("logscale",0,"setmarkers",0) + pos = [100.3333 991.6667 358.0000 192.6667]; + set(fig, 'Position', pos); + +end + + +% === FINAL FIGURE SIZE === diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH.m new file mode 100644 index 0000000..ffac5d2 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH.m @@ -0,0 +1,124 @@ +%% ============================================================ +% LOAD DATA +% ============================================================ +database_type = 'mysql'; +db = DBHandler("dataBase", "labor_highspeed", "type", database_type); + +M = 4; % PAM level for this analysis + +fp = QueryFilter(); +fp.where('Runs', 'pam_level', 'EQUALS', M); +fp.where('Runs', 'fiber_length', 'EQUALS', 10); +fp.where('Runs', 'bitrate', 'EQUALS', 360e9); +fp.where('Runs', 'is_mpi', 'EQUALS', 0); + +fields = [ + db.getTableFieldNames('power_state_info'); + db.getTableFieldNames('dashboard_ungrouped_alltime') +]; + +[dataTable, ~] = db.queryDB(fp, fields); + + +%% ============================================================ +% COMMON CONFIGURATION FOR ALL SUBPLOTS +% ============================================================ +%% ============================================================ +% DEFINE DSP ALGORITHMS FOR THE 4 SUBPLOTS +% ============================================================ +curves = struct; + +curves(1).name = 'VNLE'; +curves(1).eq = equalizer_structure.vnle; +curves(1).pre = 0; +curves(1).color = clr.Paired.red; + +curves(2).name = 'PF + MLSE'; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).pre = 0; +curves(2).color = clr.Paired.green; + +curves(3).name = 'DB-target + MLSE'; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).pre = 0; +curves(3).color = clr.Paired.blue; + +curves(4).name = 'ML-based MLSE'; +curves(4).eq = equalizer_structure.ml_mlse; +if M == 4 +curves(4).pre = 0; +else +curves(4).pre = 1; +end +curves(4).color = clr.Paired.purple; + + +%% ============================================================ +% ANALYSIS ENGINE — NO PLOTTING +% ============================================================ +results = struct; + +for k = 1:numel(curves) + + %% ---- BASE CONFIG ---- + cfg = struct; + cfg.x_axis = 'wavelength'; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.outlier = 'none'; + % cfg.group_by = {'wavelength'}; + cfg.show_raw = false; + + cfg.filters = struct( ... + 'pam_level', M, ... + 'is_mpi', 0, ... + 'bitrate', 360e9, ... + 'fiber_length', 10, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', curves(k).pre); + + %% ---- GET BER ---- + cfg.y_axis = 'BER'; + A = analyze_measurements_gpt(dataTable, cfg); + + results(k).wavelength = A.group{1}.x; + + if curves(k).eq == equalizer_structure.vnle_db_mlse || ... + curves(k).eq == equalizer_structure.ml_mlse + % DB and ML-based need precoded BER + results(k).ber = A.group{1}.y_precoded; + else + results(k).ber = A.group{1}.y; + end + +end + +%% ============================================================ +% 1×4 TILED BER-vs-WAVELENGTH FIGURE +% ============================================================ +fig=figure(901); +tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); + +lw = 1.8; % line width +ms = 6; % marker size + +for k = 1:numel(curves) + nexttile; hold on; + + plot(results(k).wavelength, results(k).ber, ... + '-o', ... + 'Color', curves(k).color, ... + 'MarkerFaceColor', curves(k).color, ... + 'MarkerSize', ms, ... + 'LineWidth', lw); + + set(gca,'YScale','log'); + grid on; + xlabel('wavelength'); + ylabel('BER'); + title(curves(k).name); + ylim([1e-4, 0.1]) + beautifyBERplot(); +end +pos = 1e3.*[0.1070 0.5497 1.4113 0.3253]; +set(fig, 'Position', pos); \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH_VS_BAUDRATE.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH_VS_BAUDRATE.m new file mode 100644 index 0000000..d987359 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_WAVELENGTH_VS_BAUDRATE.m @@ -0,0 +1,160 @@ +%% ============================================================ +% PARAMETERS +% ============================================================ +database_type = 'mysql'; +db = DBHandler("dataBase", "labor_highspeed", "type", database_type); + +pam_level = 4; % FIXED for this figure +baudrates = [300e9 330e9 360e9 390e9]; +fiberL = 10; + +fields = [ + db.getTableFieldNames('power_state_info'); + db.getTableFieldNames('dashboard_ungrouped_alltime') +]; + +%% ============================================================ +% DEFINE DSP SCHEMES +% ============================================================ +curves = struct; + +curves(1).name = 'VNLE'; +curves(1).eq = equalizer_structure.vnle; +curves(1).color = clr.Paired.red; + +curves(2).name = 'PF + MLSE'; +curves(2).eq = equalizer_structure.vnle_pf_mlse; +curves(2).color = clr.Paired.green; + +curves(3).name = 'DB-target + MLSE'; +curves(3).eq = equalizer_structure.vnle_db_mlse; +curves(3).color = clr.Paired.blue; + +curves(4).name = 'ML-based MLSE'; +curves(4).eq = equalizer_structure.ml_mlse; +curves(4).color = clr.Paired.purple; + + +%% ============================================================ +% ANALYSIS — results(b, k): b = baudrate index, k = DSP scheme index +% ============================================================ +results = struct; + +for b = 1:length(baudrates) + + Rb = baudrates(b); + + % --- query matching runs --- + fp = QueryFilter(); + fp.where('Runs','pam_level','EQUALS', pam_level); + fp.where('Runs','fiber_length','EQUALS', fiberL); + fp.where('Runs','bitrate','EQUALS', Rb); + fp.where('Runs','is_mpi','EQUALS', 0); + + [dataTable, ~] = db.queryDB(fp, fields); + + for k = 1:numel(curves) + + %% ---- DECIDE PRE-EMPH & PRECoded RULES for PAM-4 ---- + pre_emph = decide_preemph(pam_level, curves(k).eq); + use_precoded = decide_precoded(pam_level, curves(k).eq); + + %% ---- SETUP ANALYSIS CONFIG ---- + cfg = struct; + cfg.x_axis = 'wavelength'; + cfg.y_axis = 'BER'; + cfg.agg = 'min'; + cfg.outlier = 'none'; + % cfg.group_by = {'wavelength'}; + cfg.show_raw = false; + + cfg.filters = struct( ... + 'pam_level', pam_level, ... + 'is_mpi', 0, ... + 'bitrate', Rb, ... + 'fiber_length', fiberL, ... + 'equalizer_structure', curves(k).eq, ... + 'pre_emph', pre_emph); + + %% ---- RUN ANALYSIS ---- + A = analyze_measurements_gpt(dataTable, cfg); + + results(b,k).wavelength = A.group{1}.x; + + if use_precoded + results(b,k).ber = A.group{1}.y_precoded; + else + results(b,k).ber = A.group{1}.y; + end + end +end + + +%% ============================================================ +% PLOT — 1×4 (one tile per baudrate) +% ============================================================ +fig = figure(); clf; +tiledlayout(1,4,'TileSpacing','compact','Padding','compact'); + +lw = 1.8; +ms = 6; + +for b = 1:length(baudrates) + nexttile; hold on; + + for k = 1:numel(curves) + plot(results(b,k).wavelength, results(b,k).ber, ... + '-o', ... + 'Color', curves(k).color, ... + 'MarkerFaceColor', curves(k).color, ... + 'MarkerSize', ms, ... + 'LineWidth', lw, ... + 'DisplayName', curves(k).name); + end + + set(gca,'YScale','log'); + grid on; + xlabel('Wavelength [nm]'); + ylabel('BER'); + ylim([1e-4 0.1]); + title(sprintf('PAM-%d @ %.0f GBd',pam_level, baudrates(b)/1e9)); + legend('Location','best'); + beautifyBERplot(); +end + +% Optional figure size +pos = 1e3.*[0.1 0.55 1.4 0.32]; +set(fig, 'Position', pos); + + +%% ============================================================ +% DECISION LOGIC (INLINE FUNCTIONS) +% ============================================================ + +function pe = decide_preemph(M, eq) + % PRE-EMPH RULES: + switch M + case 4 + if eq == equalizer_structure.vnle + pe = 1; % PAM4: VNLE → pre-emph on + else + pe = 0; % PAM4: all others → off + end + case {6,8} + pe = 1; % PAM6/8: all → pre-emph on + otherwise + pe = 0; + end +end + + +function flag = decide_precoded(M, eq) + % PRE-CODE RULES: + if eq == equalizer_structure.vnle_db_mlse + flag = 1; % Always for DB-target + elseif eq == equalizer_structure.ml_mlse && M == 4 + flag = 1; % PAM4: ML-based → precoded + else + flag = 0; + end +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m new file mode 100644 index 0000000..2213c8a --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m @@ -0,0 +1,88 @@ +%% ============================================================ +% PLOT +% ============================================================ +figure; hold on; +ms = 32; % scatter size +lw = 0.8; % line width + +for k = 1:4 % PAM-2/4/6/8 + + M = pam_list(k); + idxPam = (Mvals == M); + + % Extract for this PAM + x = baud(idxPam); + y = netrate(idxPam); + n = names(idxPam); + + % Get color for this PAM format + col = colors(k,:); + + % ----- LEGEND FLAG (only add one entry per PAM) ----- + firstLegend = true; + + % ---- PLOT ALL POINTS (marker based on publication) ---- + for i = 1:sum(idxPam) + + % marker selection by publication + pubIdx = find(pub_list == n(i), 1); + marker = markerlist{mod(pubIdx-1, nMarkers) + 1}; + + if firstLegend + h = scatter(x(i), y(i), ms, ... + 'Marker', marker, ... + 'MarkerEdgeColor', col, ... + 'MarkerFaceColor', col, ... + 'DisplayName', sprintf('PAM-%d', M)); + firstLegend = false; + else + h = scatter(x(i), y(i), ms, ... + 'Marker', marker, ... + 'MarkerEdgeColor', col, ... + 'MarkerFaceColor', col, ... + 'HandleVisibility','off'); + end + + % ====== CUSTOM DATATIP CONTENT ====== + dt = h.DataTipTemplate; + dt.DataTipRows(1).Label = 'Baud rate'; + dt.DataTipRows(2).Label = 'Net rate'; + + % Add publication name + dt.DataTipRows(end+1) = dataTipTextRow('Publication', n(i)); + + + end + + % ---- Fit (PAM-specific) ---- + valid = ~isnan(x) & ~isnan(y); + if sum(valid) >= 3 + p = polyfit(x(valid), y(valid), 2); + xfit = linspace(min(x(valid)), max(x(valid)), 200); + yfit = polyval(p, xfit); + + plot(xfit, yfit, ':', ... + 'LineWidth', lw, ... + 'Color', col, ... + 'HandleVisibility', 'off'); % do NOT add to legend + end +end + +grid on; +xlabel('Baud rate [GBd]'); +ylabel('Net rate [Gb/s]'); + +legend('Location','northwest'); +set(gca,'FontSize',11); + + +%% === EXPORT === +outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\highspeedresults.tikz'; +matlab2tikz(outfile, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'legend columns=1' ... + }); diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/HighSpeedExperiments.xlsx b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/HighSpeedExperiments.xlsx new file mode 100644 index 0000000..59d1ab5 Binary files /dev/null and b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/HighSpeedExperiments.xlsx differ diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/analyze_measurements_gpt.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/analyze_measurements_gpt.m new file mode 100644 index 0000000..9ab6e85 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/analyze_measurements_gpt.m @@ -0,0 +1,334 @@ +function [M, cfg] = analyze_measurements_gpt(T, cfg) +% ANALYZE_MEASUREMENTS_GPT +% Filter, compute X/Y, group and aggregate measurements from table T. +% No plotting here. +% +% Usage: +% [M, cfg] = analyze_measurements_gpt(dataTable, cfg); +% +% Typical result (single group): +% M.x -> aggregated x-values (e.g., grossrate) +% M.y -> aggregated y-values (e.g., BER or NGMI) +% M.y_precoded -> aggregated precoded BER (if available) +% +% For multiple groups: +% M.group(g).x, M.group(g).y, M.group(g).label, ... + +%% ---- Defaults (non-plot) ---- +if nargin < 2, cfg = struct; end +defaults = struct( ... + 'x_axis' , 'symbolrate', ... + 'y_axis' , 'BER', ... + 'y_scale' , 'auto', ... + 'group_by' , {{'equalizer_structure','pre_emph'}}, ... + 'filters' , struct, ... + 'agg' , 'mean', ... + 'outlier' , 'auto', ... + 'mad_z' , 4, ... + 'pct_limits' , [2.5 97.5], ... + 'min_pts_x' , 3, ... + 'show_raw' , true, ... + 'show_precoded', [], ... + 'show_spread' , 'none', ... + 'fec_lines' , [], ... + 'plot' , struct() ... % plot settings handled in plot function +); +cfg = filldefaults(cfg, defaults); + +%% ---- Derived/prep columns ---- +if ~ismember('pre_emph', T.Properties.VariableNames) + if ~ismember('db_mode', T.Properties.VariableNames) + error('Missing column "db_mode" for pre_emph derivation.'); + end + T.pre_emph = T.db_mode == 0; +end + +if ~ismember(cfg.y_axis, T.Properties.VariableNames) + error('y_axis "%s" not found in table.', cfg.y_axis); +end + +isBER = startsWith(cfg.y_axis, "BER", 'IgnoreCase', true); +M.isBER = isBER; + +if strcmpi(cfg.y_scale,'auto') + cfg.y_scale = tern(isBER, 'log', 'linear'); +end +if strcmpi(cfg.outlier,'auto') + cfg.outlier = tern(isBER, 'mad', 'none'); +end +if isempty(cfg.show_precoded) + cfg.show_precoded = isBER && ismember('BER_precoded', T.Properties.VariableNames); +end + +%% ---- Filters & core X/Y extraction ---- +T = applyFilters(T, cfg.filters); + +[x_raw, x_label] = computeX(T, cfg.x_axis); +y_raw = T.(cfg.y_axis); + +validXY = isfinite(x_raw) & isfinite(y_raw); +T = T(validXY, :); +x_raw = x_raw(validXY); +y_raw = y_raw(validXY); + +% Degiga if needed +if mean(abs(y_raw)) > 1e8 + y_raw = y_raw .* 1e-9; +end + +if cfg.show_precoded && ismember('BER_precoded', T.Properties.VariableNames) + y_raw_p = T.BER_precoded(validXY); +else + y_raw_p = []; +end + +%% ---- Grouping ---- +group_by = cfg.group_by; +if ~all(ismember(group_by, T.Properties.VariableNames)) + error('Some group_by columns are missing in table.'); +end +[G, grpTbl] = findgroups(T(:, group_by)); +nG = max(G); + +%% ---- Aggregation per group ---- +M = struct; +M.cfg = cfg; +M.x_label = x_label; +M.y_axis = cfg.y_axis; +M.x_axis = cfg.x_axis; +M.nGroups = nG; + +% raw (filtered) data +M.raw = struct; +M.raw.x = x_raw; +M.raw.y = y_raw; +M.raw.y_precoded = y_raw_p; +M.raw.T = T; + +M.group = cell(nG,1); + +useLog = strcmpi(cfg.y_scale,'log'); +for gi = 1:nG + idx = (G == gi); + Ti = T(idx,:); + xi = x_raw(idx); + yi = y_raw(idx); + + [xu, ~, iu] = unique(xi); + yu = nan(size(xu)); + ylo = nan(size(xu)); + yhi = nan(size(xu)); + + for k = 1:numel(xu) + bin = (iu==k); + yy = yi(bin); + yy = yy(isfinite(yy)); + if isempty(yy), continue; end + + km = outlierMask(yy, cfg, useLog); + if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end + yy = yy(km); + + switch lower(cfg.agg) + case 'median' + yu(k) = median(yy,'omitnan'); + case 'mean' + yu(k) = mean(yy,'omitnan'); + case 'min' + yu(k) = min(yy); + case 'max' + yu(k) = max(yy); + otherwise + error('Unknown agg mode "%s".', cfg.agg); + end + + if strcmpi(cfg.show_spread,'iqr') + q = prctile(yy,[25 75]); + ylo(k) = max(yu(k)-q(1), eps); + yhi(k) = max(q(2)-yu(k), eps); + elseif strcmpi(cfg.show_spread,'minmax') + ylo(k) = min(yy); + yhi(k) = max(yy); + end + end + + % sort by x + [xu, ord] = sort(xu); + yu = yu(ord); + ylo = ylo(ord); + yhi = yhi(ord); + + g = struct; + g.label = buildLabel(grpTbl(gi,:), group_by); + g.idx = find(G==gi); + g.T = Ti; + g.x_raw = xi; + g.y_raw = yi; + g.x = xu; + g.y = yu; + g.y_lo = ylo; + g.y_hi = yhi; + g.y_precoded = []; + g.y_precoded_lo = []; + g.y_precoded_hi = []; + + % Precoded aggregation (if requested & available) + if cfg.show_precoded && ~isempty(y_raw_p) && strcmpi(cfg.y_axis,'BER') + ypi = y_raw_p(idx); + ypu = nan(size(xu)); + + for k = 1:numel(xu) + bin = (iu==k); + yy = ypi(bin); + yy = yy(isfinite(yy)); + if isempty(yy), continue; end + + km = outlierMask(yy, cfg, true); + if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end + yy = yy(km); + + switch lower(cfg.agg) + case 'median' + ypu(k) = median(yy,'omitnan'); + case 'mean' + ypu(k) = mean(yy,'omitnan'); + case 'min' + ypu(k) = min(yy); + case 'max' + ypu(k) = max(yy); + end + end + + g.y_precoded = ypu(ord); + end + + M.group{gi} = g; +end + +% Convenience flatten for single-group case +if nG == 1 + g = M.group{1}; + M.x = g.x; + M.y = g.y; + M.y_precoded = g.y_precoded; +end + +end % ===== main ===== + + +%% ===================== Helpers ===================== + +function cfg = filldefaults(cfg, defs) +fn = fieldnames(defs); +for i = 1:numel(fn) + f = fn{i}; + if ~isfield(cfg, f) || isempty(cfg.(f)) + cfg.(f) = defs.(f); + elseif isstruct(defs.(f)) && isstruct(cfg.(f)) + cfg.(f) = filldefaults(cfg.(f), defs.(f)); % recursive + end +end +end + +function out = tern(cond, a, b) +if cond + out = a; +else + out = b; +end +end + +function T2 = applyFilters(T, filters) +if isempty(filters), T2 = T; return; end +keep = true(height(T),1); +fns = fieldnames(filters); +for i = 1:numel(fns) + name = fns{i}; + if ~ismember(name, T.Properties.VariableNames) + warning('Filter column "%s" not found. Ignored.', name); + continue + end + val = filters.(name); + col = T.(name); + if isa(val,'function_handle') + m = val(col); + if ~islogical(m) || ~isequal(size(m), size(col)) + error('Filter for %s must return logical mask of same size.', name); + end + keep = keep & m; + else + keep = keep & ismember(col, val); + end +end +T2 = T(keep,:); +end + +function [x, label] = computeX(T, whichX) +switch lower(whichX) + case {'symbolrate','baudrate'} + x = T.symbolrate * 1e-9; + label = 'Symbol rate [GBd]'; + case 'bitrate' + if ~ismember('pam_level', T.Properties.VariableNames) + error('bitrate requires "pam_level" column.'); + end + bits = floor(log2(double(T.pam_level))*10)/10; + x = (T.symbolrate .* bits) * 1e-9; + label = 'Grossrate [Gb/s]'; + case 'grossrate' + x = T.grossrate * 1e-9; + label = 'Grossrate [Gb/s]'; + otherwise + if ~ismember(whichX, T.Properties.VariableNames) + error('x_axis "%s" not found in table.', whichX); + end + x = T.(whichX); + label = whichX; +end +x = double(x(:)); +end + +function keep = outlierMask(y, cfg, useLog) +if isempty(y), keep = false(size(y)); return; end +y = y(:); +switch lower(cfg.outlier) + case 'none' + keep = true(size(y)); return + case 'mad' + z = tern(useLog, log10(y), y); + med = median(z,'omitnan'); + madv = median(abs(z-med),'omitnan'); + if ~(isfinite(madv) && madv>0) + keep = true(size(y)); return + end + sigma = 1.4826*madv; + zz = tern(useLog, log10(y), y); + keep = abs(zz - med) <= cfg.mad_z*sigma; + case 'pctl' + pr = prctile(y, cfg.pct_limits); + keep = (y >= pr(1)) & (y <= pr(2)); + otherwise + error('Unknown outlier mode "%s".', cfg.outlier); +end +end + +function s = buildLabel(grpRow, group_by) +parts = strings(1, numel(group_by)); +for i = 1:numel(group_by) + key = group_by{i}; + val = grpRow.(key); + if iscell(val), val = val{1}; end + if islogical(val), val = tern(val,'w/','w/o'); end + if key == "equalizer_structure" + key = ''; + val = upper(val); + val = strrep(val,'_',' '); + end + if key == "pre_emph" + val = [val, ' pre-emph.']; + key = ''; + end + parts(i) = sprintf('%s %s', key, string(val)); +end +s = strjoin(parts, ', '); +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_ber_best_results.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_ber_best_results.m new file mode 100644 index 0000000..5b2e245 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_ber_best_results.m @@ -0,0 +1,240 @@ + + +database_type = 'mysql'; +dataBase = 'labor_highspeed'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + + +% M = 4; +fp = QueryFilter(); +% fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','LESS_THAN', 1312); +% fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_alltime')]; %dashboard_ungrouped_after_nov_2025 dashboard_ungrouped_aug_nov_2025 +[dataTable,~] = db.queryDB(fp, fields); + +%% +cfg = struct; +cfg.x_axis = 'grossrate'; % 'symbol rate' | 'bitrate' | 'wavelength' grossrate +cfg.y_axis = 'BER'; % 'BER' | 'GMI' | 'AIR' | ... + +cfg.y_scale = 'auto'; % auto -> log for BER*, linear otherwise +cfg.outlier = 'mad'; % simple, robust; 'none' or 'pctl' also available +cfg.show_raw = false; +cfg.show_spread = 'none'; % 'none' or 'iqr' or minmax +cfg.agg = 'min'; % or 'median' +cfg.show_precoded = 0; + +% cfg.fec_lines = [2.2e-4 4.85e-3 2e-2]; % optional +cfg.fec_lines = []; +cfg.plot.custom_colors = [ + clr.Paired.red; + clr.Paired.blue; + clr.Paired.green; + clr.Paired.orange; + clr.Paired.purple + ]; + +cfg.plot.custom_colors_scatter = [ + clr.Paired.lightred; + clr.Paired.lightblue; + clr.Paired.lightgreen; + clr.Paired.lightorange; + clr.Paired.lightpurple + ]; + + +% New styling knobs +cfg.plot.use_cbrewer2 = true; +cfg.plot.colormap = 'Paired'; +cfg.plot.paired_dark_first = false; % dark for lines, light for scatter +cfg.plot.lineWidth = 2.0; +cfg.plot.errWidth = 1.2; +cfg.plot.scatterAlpha = 0.35; +cfg.plot.legendLocation = 'best'; +cfg.plot.fecLineWidth = 2.4; % thicker FEC limits +cfg.plot.lineStyle_pre_emph_on = '-'; +cfg.plot.lineStyle_pre_emph_off = '-'; + +%% PLOT NGMI + +% Common cfg +cfg.show_precoded = 1; +cfg.group_by = {'equalizer_structure','pre_emph'}; +cfg.x_axis = 'grossrate'; +cfg.y_axis = 'NGMI'; % 'BER' | 'GMI' | 'AIR' | ... +cfg.y_scale = 'lin'; +cfg.plot.custom_colors_scatter = []; +cfg.plot.use_cbrewer2 = false; +cfg.fec_lines = []; +cfg.agg = 'max'; + +cfg.figure_number = 45; +% fig = figure(cfg.figure_number); + +lambda = 1310; +% PAM 4 +cfg.plot.custom_colors = clr.Paired.red; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',4, ... + 'equalizer_structure',equalizer_structure.vnle_db_mlse, ... + 'pre_emph',0,'wavelength',lambda); +cfg.show_precoded = 1; +a = plot_measurements_gpt(dataTable, cfg); +ngmi_pam4 = a.lines(1).YData; +grossrates = a.lines(1).XData; +tp = TransmissionPerformance; +netrates_vnle = tp.calculateNetRate(grossrates, ... + 'NGMI', ngmi_pam4, ... + 'BER', BER_VNLE); + +cfg.plot.custom_colors = clr.Paired.red; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',4, ... + 'equalizer_structure',equalizer_structure.vnle_db_mlse, ... + 'pre_emph',0,'wavelength',1293); +cfg.show_precoded = 1; +plot_measurements_gpt(dataTable, cfg); + +% PAM 6 +cfg.plot.custom_colors = clr.Paired.blue; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',6, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',lambda); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +cfg.plot.custom_colors = clr.Paired.blue; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',6, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',1293); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +% PAM 8 +cfg.plot.custom_colors = clr.Paired.green; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',8, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',lambda); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +cfg.plot.custom_colors = clr.Paired.green; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',8, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',1293); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +beautifyBERplot +ylim([0.87,1.01]); +% xlim([290,480]); + +%% PLOT AIR + +% Common cfg +cfg.show_precoded = 1; +cfg.group_by = {'equalizer_structure','pre_emph'}; +cfg.x_axis = 'grossrate'; +cfg.y_axis = 'AIR'; % 'BER' | 'GMI' | 'AIR' | ... +cfg.y_scale = 'lin'; +cfg.plot.custom_colors_scatter = []; +cfg.plot.use_cbrewer2 = false; +cfg.fec_lines = []; +cfg.agg = 'max'; + +cfg.figure_number = 47; + +lambda = 1310; + +% cfg = struct; +cfg.filters = struct('is_mpi',0,'pam_level',4, ... + 'equalizer_structure',equalizer_structure.vnle_db_mlse, ... + 'pre_emph',0,'wavelength',lambda); +cfg.x_axis = 'grossrate'; +cfg.y_axis = 'NGMI'; % or 'NGMI', etc. +cfg.show_precoded = 1; + +[M, cfg] = analyze_measurements_gpt(dataTable, cfg); + +grossrates = M.x; % aggregated X +ber = M.y; % aggregated Y (BER or NGMI) +ber_prec = M.y_precoded; % precoded BER (if available) + +[h, M] = plot_measurements_gpt(dataTable, cfg); + + +% PAM 4 +cfg.plot.custom_colors = clr.Paired.red; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',4, ... + 'equalizer_structure',equalizer_structure.vnle_db_mlse, ... + 'pre_emph',0,'wavelength',lambda); +cfg.show_precoded = 1; +plot_measurements_gpt(dataTable, cfg); + + + +cfg.plot.custom_colors = clr.Paired.red; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',4, ... + 'equalizer_structure',equalizer_structure.vnle_db_mlse, ... + 'pre_emph',0,'wavelength',1293); +cfg.show_precoded = 1; +plot_measurements_gpt(dataTable, cfg); + +% PAM 6 +cfg.plot.custom_colors = clr.Paired.blue; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',6, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',lambda); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +cfg.plot.custom_colors = clr.Paired.blue; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',6, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',1293); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +% PAM 8 +cfg.plot.custom_colors = clr.Paired.green; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',8, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',lambda); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +cfg.plot.custom_colors = clr.Paired.green; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters = struct('is_mpi',0,'pam_level',8, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',1,'wavelength',1293); +cfg.show_precoded = 0; +plot_measurements_gpt(dataTable, cfg); + +ax = gca; + +beautifyBERplot + +ylim([275,435]); +xlim([290,480]); + + +%% + + + + diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_pre_emphasis.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_pre_emphasis.m new file mode 100644 index 0000000..525956b --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/compare_pre_emphasis.m @@ -0,0 +1,112 @@ +database_type = 'mysql'; +dataBase = 'labor_highspeed'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + + +M = 4; +fp = QueryFilter(); +fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','EQUALS', 1310); +% fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_aug_nov_2025')]; %dashboard_ungrouped_after_nov_2025 dashboard_ungrouped_aug_nov_2025 +[dataTable,~] = db.queryDB(fp, fields); + +%% +cfg = struct; +cfg.x_axis = 'grossrate'; % 'symbol rate' | 'bitrate' | 'wavelength' grossrate +cfg.y_axis = 'BER'; % 'BER' | 'GMI' | 'AIR' | ... + +cfg.y_scale = 'auto'; % auto -> log for BER*, linear otherwise +cfg.outlier = 'mad'; % simple, robust; 'none' or 'pctl' also available +cfg.show_raw = false; +cfg.show_spread = 'none'; % 'none' or 'iqr' or minmax +cfg.agg = 'min'; % or 'median' +cfg.show_precoded = 0; +% cfg.fec_lines = [2.2e-4 4.85e-3 2e-2]; % optional +cfg.fec_lines = []; +cfg.plot.custom_colors = [ + clr.Paired.red; + clr.Paired.blue; + clr.Paired.green; + clr.Paired.orange; + clr.Paired.purple +]; + +cfg.plot.custom_colors_scatter = [ + clr.Paired.lightred; + clr.Paired.lightblue; + clr.Paired.lightgreen; + clr.Paired.lightorange; + clr.Paired.lightpurple +]; + + +% New styling knobs +cfg.plot.use_cbrewer2 = true; +cfg.plot.colormap = 'Paired'; +cfg.plot.paired_dark_first = false; % dark for lines, light for scatter +cfg.plot.lineWidth = 2.0; +cfg.plot.errWidth = 1.2; +cfg.plot.scatterAlpha = 0.35; +cfg.plot.legendLocation = 'best'; +cfg.plot.fecLineWidth = 2.4; % thicker FEC limits +cfg.plot.lineStyle_pre_emph_on = '-'; +cfg.plot.lineStyle_pre_emph_off = '-'; + +%% +cfg.figure_number = 42; + + +% ---- VNLE, no pre-emph (solid red) ---- +cfg.plot.custom_colors = [clr.Paired.red]; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters = struct('is_mpi',0,'pam_level',M, ... + 'equalizer_structure',equalizer_structure.vnle, ... + 'pre_emph',0); +plot_measurements_gpt(dataTable, cfg); + +% ---- VNLE, with pre-emph (dashed red) ---- +cfg.plot.custom_colors = [clr.Paired.red]; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters.pre_emph = 1; +plot_measurements_gpt(dataTable, cfg); + +% ---- VNLE PF MLSE, no pre-emph (solid green) ---- +cfg.plot.custom_colors = [clr.Paired.green]; +cfg.plot.custom_linetypes = {'-'}; +cfg.filters.equalizer_structure = equalizer_structure.vnle_pf_mlse; +cfg.filters.pre_emph = 0; +plot_measurements_gpt(dataTable, cfg); + +% ---- VNLE PF MLSE, with pre-emph (dashed green) ---- +cfg.plot.custom_colors = [clr.Paired.green]; +cfg.plot.custom_linetypes = {'--'}; +cfg.filters.pre_emph = 1; +plot_measurements_gpt(dataTable, cfg); + + +% === FEC LINES (no legend) === +yline([2.2e-4 4.85e-3 2e-2], ... + 'LineWidth',1.5,'Color',[0.4 0.4 0.4], ... + 'LineStyle',':','HandleVisibility','off'); + + +% === BEAUTIFY === +% beautifyBERplot; % your function + + +%% === EXPORT TO TIKZ === +outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\compare_pre_emphasis.tikz'; + +matlab2tikz(outfile, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'legend columns=1' ... + } ); \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/example_usage.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/example_usage.m new file mode 100644 index 0000000..bf3d4bf --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/example_usage.m @@ -0,0 +1,92 @@ +% ============================================================ +% MINIMAL EXAMPLE: Query → Analyze → Plot → Extract X/Y data +% ============================================================ + +%% === Load from database === +db = DBHandler("dataBase","labor_highspeed","type","mysql"); + +fp = QueryFilter(); +fp.where('Runs','fiber_length','EQUALS',2); +% fp.where('Runs','pam_level','EQUALS',6); % PAM-4 +% fp.where('Runs','db_mode','EQUALS',0); % w/o pre-emph + +fields = db.getTableFieldNames('dashboard_ungrouped_alltime'); +[dataTable,~] = db.queryDB(fp, fields); + + + + + +%% === Define config === + +for m = [4,6,8] + + cfg = struct; + cfg.x_axis = 'symbolrate'; + cfg.y_axis = 'Alpha'; + cfg.group_by = {'equalizer_structure','pre_emph'}; + cfg.filters = struct('is_mpi',0,'pam_level',m, ... + 'equalizer_structure',equalizer_structure.vnle_pf_mlse, ... + 'pre_emph',0); + cfg.agg = 'max'; + cfg.outlier = 'mad'; + cfg.show_raw = false; + cfg.show_precoded = 0; + + % Plot cosmetics (minimal) + cfg.plot = struct; + cfg.plot.custom_colors = linspecer(8); + cfg.plot.custom_linetypes = {'-'}; + cfg.plot.lineWidth = 2; + + + % ============================================================ + % === ANALYSIS ONLY (no plotting) ============================= + % ============================================================ + A = analyze_measurements_gpt(dataTable, cfg); + + % Now you have: + % A.raw.x = raw x-values + % A.raw.y = raw BER values + % A.group{1}.x = unique sorted x-values + % A.group{1}.y = aggregated BER for each x + + x_values = A.group{1}.x; + y_values = A.group{1}.y; + + + % ============================================================ + % === PLOT ==================================================== + % ============================================================ + + figure(10);hold on + cfg.ax = gca; % optional: plot into existing axes + plot(x_values,y_values,... + 'LineWidth', 2, ... + 'Color', clr.Set1.red, ... + 'MarkerSize', 5, ... + 'MarkerFaceColor', clr.Set1.red,... + 'Marker','o'); + % [h, ~] = plot_measurements_gpt(dataTable, cfg); + + title('Minimal VNLE BER Example') + xlabel('Grossrate [Gb/s]') + ylabel('BER') + xticks(100:30:220) + xlim([100,220]); + ylim([0,1]); + +end +% outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\alphas.tikz'; +% matlab2tikz(outfile, ... +% 'width','\fwidth', ... +% 'height','\fheight', ... +% 'showInfo',false, ... +% 'extraAxisOptions',{ ... +% 'legend style={font=\footnotesize}', ... +% 'legend columns=1' ... +% 'every axis/.append style={font=\scriptsize}',... +% 'minor grid style={line width=0.2pt, solid, color=black!10}',... +% 'grid style={line width=0.4pt, solid, color=black!20}',... +% 'grid style={dashed}',... +% }); diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/generate_spectrum_plots.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/generate_spectrum_plots.m new file mode 100644 index 0000000..f90cf1a --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/generate_spectrum_plots.m @@ -0,0 +1,111 @@ +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; +dsp_options.max_occurences = 1; +database = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' ); + +rate = 390e9; + +%% 1 - PAM 4 with preemphasis +fp = QueryFilter(); +M = 4; +fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'bitrate','EQUALS', rate);%360,390 +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', 0); +fp.where('Runs', 'rop_attenuation','EQUAL', 0); + +[dataTable,~] = db.queryDB(fp, database.getTableFieldNames('Runs')); + +dataTable = queryRunid(dataTable.run_id, database); +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); + +% Load and Sync signal data from DB +[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + +if rate == 390e9 +Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-5.8); +elseif rate == 300e9 +Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-4.7); +end +ylim([-30,3]); +xlim([-5,100]); +Scpe_sig.spectrum("fignum",201,"normalizeTo0dB",0,"displayname",'Rx'); + + +%% 1 - PAM 4 without preemphasis +fp = QueryFilter(); +M = 4; +fp.where('Runs', 'pam_level','EQUALS', M); +fp.where('Runs', 'bitrate','EQUALS', rate);%360,390 +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', 1); +fp.where('Runs', 'rop_attenuation','EQUAL', 0); + +[dataTable,~] = db.queryDB(fp, database.getTableFieldNames('Runs')); + +dataTable = queryRunid(dataTable.run_id, database); +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); + +% Load and Sync signal data from DB +[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + +Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',0); +ylim([-30,3]); +xlim([-5,100]); +Scpe_sig.spectrum("fignum",201,"normalizeTo0dB",0,"displayname",'Rx'); + + + +if 1 + %% show freuqncy response of filter + + 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); + + freqresp.estimate(Digi_sig,"fileName",'','save',false); + + freqresp.plot() + + a = gca; + a.YTick = [-30,-20,-10,0]; + + %% system frex + + + precomp_filename ='lab_high_speed'; + precomp_path = "C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\precomp"; + freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',92e9); + freqresp.load('loadPath', precomp_path, 'fileName', precomp_filename); + + fprintf('Plotting: %s\n', precomp_filename); + freqresp.plot(); + + outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\spectrum_2.tikz'; +matlab2tikz(outfile, ... + 'width','\fwidth', ... + 'height','\fheight', ... + 'showInfo',false, ... + 'extraAxisOptions',{ ... + 'legend style={font=\footnotesize}', ... + 'legend columns=1' ... + }); + +end \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt.m new file mode 100644 index 0000000..a31fab0 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt.m @@ -0,0 +1,222 @@ +function [h, M, cfg] = plot_measurements_gpt(T, cfg) +% PLOT_MEASUREMENTS_GPT +% Plot measurements, using analyze_measurements_gpt for data handling. +% +% Usage: +% h = plot_measurements_gpt(dataTable, cfg); +% [h, M] = plot_measurements_gpt(dataTable, cfg); +% +% For values only, without plotting, call: +% [M, cfg] = analyze_measurements_gpt(dataTable, cfg); + +if nargin < 2, cfg = struct; end + +% --- First: run analysis (filtering, grouping, aggregation) --- +[M, cfg] = analyze_measurements_gpt(T, cfg); + +nG = M.nGroups; + +%% ---- Plot defaults ---- +plotdefs = struct( ... + 'use_cbrewer2' , true, ... + 'colormap' , 'Paired', ... + 'paired_dark_first' , true, ... + 'lineWidth' , 1.8, ... + 'errWidth' , 1.0, ... + 'scatterSize' , 14, ... + 'scatterAlpha' , 0.35, ... + 'marker' , 'o', ... + 'marker_precoded' , 's', ... + 'legendLocation' , 'best', ... + 'fecLineWidth' , 2.2, ... + 'fecColor' , [0.25 0.25 0.25], ... + 'capSize' , 6, ... + 'lineStyle_default' , '-', ... + 'custom_colors' , [], ... + 'custom_colors_scatter' , [] ... +); +if ~isfield(cfg,'plot') || isempty(cfg.plot) + cfg.plot = struct; +end +cfg.plot = filldefaults(cfg.plot, plotdefs); + +%% ---- Colors ---- +[cols_line, cols_scatter] = buildGroupColors(nG, cfg.plot); + +%% ---- Axes / Figure handling ---- +if isfield(cfg,'ax') && ~isempty(cfg.ax) && isgraphics(cfg.ax,'axes') + ax = cfg.ax; + set(gcf,'CurrentAxes',ax); +else + if isfield(cfg,'figure_number') && ~isempty(cfg.figure_number) + figure(cfg.figure_number); + else + figure; + end + ax = gca; +end +hold(ax,'on'); +grid(ax,'on'); + +h.lines = gobjects(nG,1); +h.err = gobjects(nG,1); +h.scat = gobjects(nG,1); +h.lines_p = gobjects(nG,1); + +%% ---- Plot each group ---- +for gi = 1:nG + g = M.group{gi}; + xu = g.x; + yu = g.y; + ylo = g.y_lo; + yhi = g.y_hi; + + % --- Linestyle selection --- + ls = cfg.plot.lineStyle_default; + if isfield(cfg.plot,'custom_linetypes') && ~isempty(cfg.plot.custom_linetypes) + L = cfg.plot.custom_linetypes; + ls = L{ mod(gi-1, numel(L)) + 1 }; + end + + colL = cols_line(gi,:); + lbl = g.label; + + % Main line + h.lines(gi) = plot(ax, xu, yu, ... + 'LineWidth', cfg.plot.lineWidth, ... + 'Marker', cfg.plot.marker, 'MarkerSize', 3, ... + 'Color', colL, 'LineStyle', ls, ... + 'DisplayName', char(lbl)); + + % Spread + if any(isfinite(ylo)) && any(isfinite(yhi)) + h.err(gi) = errorbar(ax, xu, yu, ylo, yhi, 'LineStyle','none', ... + 'Color', colL, 'CapSize', cfg.plot.capSize, 'HandleVisibility','off'); + h.err(gi).LineWidth = cfg.plot.errWidth; + end + + % Raw scatter + if cfg.show_raw + % reuse stored raw data (no extra filtering) + xi = g.x_raw; + yi = g.y_raw; + colS = cols_scatter(gi,:); + scatter(ax, xi, yi, cfg.plot.scatterSize, colS, 'filled', ... + 'MarkerFaceAlpha', cfg.plot.scatterAlpha, ... + 'MarkerEdgeAlpha', cfg.plot.scatterAlpha, ... + 'HandleVisibility','off'); + end + + % Precoded overlay + if cfg.show_precoded && ~isempty(g.y_precoded) && strcmpi(M.y_axis,'BER') + ypu = g.y_precoded; + h.lines_p(gi) = plot(ax, xu, ypu, ... + 'LineWidth', max(1.2, cfg.plot.lineWidth-0.2), ... + 'Marker', cfg.plot.marker_precoded, 'MarkerSize', 3, ... + 'Color', colL, 'LineStyle', ':', ... + 'DisplayName', [char(lbl) ' (precoded)']); + end +end + +%% ---- Axes / Labels / FEC ---- +ylabel(ax, M.y_axis, 'Interpreter','none'); +xlabel(ax, M.x_label, 'Interpreter','none'); +set(ax, 'YScale', cfg.y_scale, 'FontSize', 11); + +% X ticks/limits using all group x-values +allX = cellfun(@(g) g.x(:), M.group, 'UniformOutput', false); +allX = unique(vertcat(allX{:})); +if ~isempty(allX) + xticks(ax, allX); + xticklabels(cellstr(num2str(round(allX,1), '%.4f'))) + xlim(ax, [min(allX), max(allX)]); +end + +if startsWith(M.y_axis,"BER",'IgnoreCase',true) + for v = cfg.fec_lines + yline(ax, v, '--', 'Color', cfg.plot.fecColor, ... + 'LineWidth', cfg.plot.fecLineWidth, 'HandleVisibility','off'); + end + ylim(ax, [1e-5, 0.5]); + yticks(ax, [1e-5, 1e-4, 1e-3, 1e-2, 1e-1]); +end + +% legend(ax, 'Location', cfg.plot.legendLocation); % if you want legends + +box(ax,'on'); + +end % ===== main ===== + + +%% ===================== Helpers ===================== + +function cfg = filldefaults(cfg, defs) +fn = fieldnames(defs); +for i = 1:numel(fn) + f = fn{i}; + if ~isfield(cfg, f) || isempty(cfg.(f)) + cfg.(f) = defs.(f); + elseif isstruct(defs.(f)) && isstruct(cfg.(f)) + cfg.(f) = filldefaults(cfg.(f), defs.(f)); + end +end +end + +function [cols_line, cols_scatter] = buildGroupColors(nG, plotcfg) + +% 1) User-provided custom colors +if isfield(plotcfg,'custom_colors') && ~isempty(plotcfg.custom_colors) + C = plotcfg.custom_colors; + if size(C,1) < nG + error('custom_colors must have at least nG=%d rows.', nG); + end + cols_line = C(1:nG, :); + + if isfield(plotcfg,'custom_colors_scatter') && ~isempty(plotcfg.custom_colors_scatter) + Cs = plotcfg.custom_colors_scatter; + if size(Cs,1) < nG + error('custom_colors_scatter must have at least nG=%d rows.', nG); + end + cols_scatter = Cs(1:nG, :); + else + cols_scatter = zeros(nG,3); + for i = 1:nG + cols_scatter(i,:) = lightenColor(cols_line(i,:), 0.40); + end + end + return; +end + +% 2) Standard behavior +useBrewer = plotcfg.use_cbrewer2 && exist('cbrewer2','file')==2; +if useBrewer + N = max(2*nG, 12); + C = cbrewer2(plotcfg.colormap, N); + cols_line = zeros(nG,3); + cols_scatter = zeros(nG,3); + for i = 1:nG + if plotcfg.paired_dark_first + dark = C(2*i-1, :); + light = C(2*i, :); + else + light = C(2*i-1, :); + dark = C(2*i, :); + end + cols_line(i,:) = dark; + cols_scatter(i,:) = light; + end +else + C = lines(max(nG,7)); + cols_line = C(1:nG,:); + cols_scatter = zeros(nG,3); + for i = 1:nG + cols_scatter(i,:) = lightenColor(cols_line(i,:), 0.50); + end +end + +end + +function c2 = lightenColor(c, fracTowardWhite) +c = c(:).'; +c2 = (1-fracTowardWhite)*c + fracTowardWhite*1; +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt_old.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt_old.m new file mode 100644 index 0000000..207b4d1 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/plot_measurements_gpt_old.m @@ -0,0 +1,436 @@ +function h = plot_measurements_gpt_old(T, cfg) +% Versatile plotting from your DB table (with cbrewer2 'Paired' palette). +% +% Usage: +% h = plot_measurements_flex(dataTable, cfg) + +%% ---- Defaults +if nargin < 2, cfg = struct; end +defaults = struct( ... + 'x_axis' , 'symbolrate', ... + 'y_axis' , 'BER', ... + 'y_scale' , 'auto', ... + 'group_by' , {{'equalizer_structure','pre_emph'}}, ... + 'filters' , struct, ... + 'agg' , 'mean', ... + 'outlier' , 'auto', ... + 'mad_z' , 4, ... + 'pct_limits' , [2.5 97.5], ... + 'min_pts_x' , 3, ... + 'show_raw' , true, ... + 'show_precoded', [], ... + 'show_spread' , 'none', ... + 'fec_lines' , [], ... + 'plot', struct() ... +); +cfg = filldefaults(cfg, defaults); + +% ---- Plot defaults (new) +plotdefs = struct( ... + 'use_cbrewer2' , true, ... + 'colormap' , 'Paired', ... % ColorBrewer 'Paired' + 'paired_dark_first' , true, ... % dark for lines, light for scatter + 'lineWidth' , 1.8, ... + 'errWidth' , 1.0, ... + 'scatterSize' , 14, ... + 'scatterAlpha' , 0.35, ... + 'marker' , 'o', ... + 'marker_precoded' , 's', ... + 'lineStyle_pre_emph_on' , '--', ... + 'lineStyle_pre_emph_off', '-', ... + 'legendLocation' , 'best', ... + 'fecLineWidth' , 2.2, ... % thicker FEC limits + 'fecColor' , [0.25 0.25 0.25], ... + 'capSize' , 6, ... + 'lineStyle_default' , '-', ... + 'use_pre_emph_styling' , true ... +); +cfg.plot = filldefaults(cfg.plot, plotdefs); + +%% ---- Derived/prep columns +if ~ismember('pre_emph', T.Properties.VariableNames) + if ~ismember('db_mode', T.Properties.VariableNames) + error('Missing column "db_mode" for pre_emph derivation.'); + end + T.pre_emph = T.db_mode == 0; +end +if ~ismember(cfg.y_axis, T.Properties.VariableNames) + error('y_axis "%s" not found in table.', cfg.y_axis); +end + +isBER = startsWith(cfg.y_axis, "BER", 'IgnoreCase', true); +if strcmpi(cfg.y_scale,'auto'), cfg.y_scale = tern(isBER, 'log', 'linear'); end +if strcmpi(cfg.outlier,'auto'), cfg.outlier = tern(isBER, 'mad', 'none'); end +if isempty(cfg.show_precoded) + cfg.show_precoded = isBER && ismember('BER_precoded', T.Properties.VariableNames); +end + +%% ---- Filters +T = applyFilters(T, cfg.filters); +[x_raw, x_label] = computeX(T, cfg.x_axis); +y_raw = T.(cfg.y_axis); + +validXY = isfinite(x_raw) & isfinite(y_raw); +T = T(validXY, :); +x_raw = x_raw(validXY); +y_raw = y_raw(validXY); + +if mean(abs(y_raw)) > 1e8 + %giga values + y_raw = y_raw.*1e-9; +end + +if cfg.show_precoded && ismember('BER_precoded', T.Properties.VariableNames) + y_raw_p = T.BER_precoded(validXY); +else + y_raw_p = []; +end + +%% ---- Grouping +group_by = cfg.group_by; +if ~all(ismember(group_by, T.Properties.VariableNames)) + error('Some group_by columns are missing in table.'); +end +[G, grpTbl] = findgroups(T(:, group_by)); +nG = max(G); + +% ==== Colors (cbrewer2 'Paired' with dark/ light pairs) ==== +[cols_line, cols_scatter] = buildGroupColors(nG, cfg.plot); + +%% ---- Axes / Figure handling (new unified logic) + +% Priority: +% 1) cfg.ax → use existing axes (subplots/tiles) +% 2) cfg.figure_number → select/create figure +% 3) fallback: create new figure + +if isfield(cfg,'ax') && ~isempty(cfg.ax) && isgraphics(cfg.ax,'axes') + ax = cfg.ax; % use caller-provided axes + set(gcf,'CurrentAxes',ax); +else + if isfield(cfg,'figure_number') && ~isempty(cfg.figure_number) + figure(cfg.figure_number); + else + figure; + end + ax = gca; % active axes +end + +hold(ax,'on'); +grid(ax,'on'); + + +h.lines = gobjects(nG,1); +h.err = gobjects(nG,1); +h.scat = gobjects(nG,1); +h.lines_p = gobjects(nG,1); + +for gi = 1:nG + idx = (G==gi); + Ti = T(idx,:); + xi = x_raw(idx); + yi = y_raw(idx); + + % Aggregate per unique x + [xu, ia, iu] = unique(xi); + yu = nan(size(xu)); + ylo = nan(size(xu)); + yhi = nan(size(xu)); + + for k = 1:numel(xu) + bin = (iu==k); + yy = yi(bin); + yy = yy(isfinite(yy)); + if isempty(yy), continue; end + km = outlierMask(yy, cfg, strcmpi(cfg.y_scale,'log')); + if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end + yy = yy(km); + + if strcmpi(cfg.agg,'median'), yu(k)=median(yy,'omitnan'); elseif strcmpi(cfg.agg,'mean'), yu(k)=mean(yy,'omitnan'); elseif strcmpi(cfg.agg,'min'), yu(k)=min(yy); elseif strcmpi(cfg.agg,'max'), yu(k)=max(yy); end + if strcmpi(cfg.show_spread,'iqr') + q = prctile(yy,[25 75]); + ylo(k) = max(yu(k)-q(1), eps); + yhi(k) = max(q(2)-yu(k), eps); + elseif strcmpi(cfg.show_spread,'minmax') + ylo(k) = min(yy); + yhi(k) = max(yy); + end + end + + % sort + [xu, ord] = sort(xu); + yu = yu(ord); + ylo = ylo(ord); + yhi = yhi(ord); + + % Styles + % Decide if we style by pre_emph + % --- LINE TYPE SELECTION (no pre-emphasis logic) --- + ls = cfg.plot.lineStyle_default; + + % User-defined override (cycled) + if isfield(cfg.plot,'custom_linetypes') && ~isempty(cfg.plot.custom_linetypes) + L = cfg.plot.custom_linetypes; + ls = L{ mod(gi-1, numel(L)) + 1 }; + end + + lbl = buildLabel(grpTbl(gi,:), group_by); + + % Main line (dark) + colL = cols_line(gi,:); + h.lines(gi) = plot(xu, yu, ... + 'LineWidth', cfg.plot.lineWidth, ... + 'Marker', cfg.plot.marker, 'MarkerSize', 3, ... + 'Color', colL, 'LineStyle', ls, ... + 'DisplayName', char(lbl)); + + % Spread (IQR) in line color + if any(isfinite(ylo)) && any(isfinite(yhi)) + h.err(gi) = errorbar(xu, yu, ylo, yhi, 'LineStyle','none', ... + 'Color', colL, 'CapSize', cfg.plot.capSize, 'HandleVisibility','off'); + h.err(gi).LineWidth = cfg.plot.errWidth; + end + + % Raw kept scatter (light) + if cfg.show_raw + keep_all = false(size(yi)); + for k = 1:numel(xu) + bin = (iu==k); + yy = yi(bin); + km = outlierMask(yy, cfg, strcmpi(cfg.y_scale,'log')); + if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end + keep_all(bin) = km; + end + colS = cols_scatter(gi,:); + scatter(xi(keep_all), yi(keep_all), cfg.plot.scatterSize, colS, 'filled', ... + 'MarkerFaceAlpha', cfg.plot.scatterAlpha, 'MarkerEdgeAlpha', cfg.plot.scatterAlpha, ... + 'HandleVisibility','off'); + end + + % Precoded overlay (dotted, squares), in line color + if cfg.show_precoded && ~isempty(y_raw_p) && strcmpi(cfg.y_axis,'BER') + ypi = y_raw_p(idx); + ypu = nan(size(xu)); + for k = 1:numel(xu) + bin = (iu==k); + yy = ypi(bin); + yy = yy(isfinite(yy)); + if isempty(yy), continue; end + km = outlierMask(yy, cfg, true); + if nnz(km) < cfg.min_pts_x, km = true(size(yy)); end + yy = yy(km); + if strcmpi(cfg.agg,'median'), ypu(k)=median(yy,'omitnan'); elseif strcmpi(cfg.agg,'mean'), ypu(k)=mean(yy,'omitnan'); elseif strcmpi(cfg.agg,'min'), ypu(k)=min(yy); elseif strcmpi(cfg.agg,'max'), ypu(k)=max(yy); end + end + h.lines_p(gi) = plot(xu, ypu, ... + 'LineWidth', max(1.2, cfg.plot.lineWidth-0.2), ... + 'Marker', cfg.plot.marker_precoded, 'MarkerSize', 3, ... + 'Color', colL, 'LineStyle', ':', ... + 'DisplayName', [char(lbl) ' (precoded)']); + end + + +end + +%% ---- Axes / Labels / FEC +ylabel(cfg.y_axis, 'Interpreter','none'); +xlabel(x_label, 'Interpreter','none'); +set(gca, 'YScale', cfg.y_scale, 'FontSize', 11); +% legend('Location', cfg.plot.legendLocation); box on; + +xticks(floor(xu)); +xlim([min(xu), max(xu)]) + +if startsWith(cfg.y_axis,"BER",'IgnoreCase',true) + for v = cfg.fec_lines + yline(v, '--', 'Color', cfg.plot.fecColor, ... + 'LineWidth', cfg.plot.fecLineWidth, 'HandleVisibility','off'); + end + ylim([1e-5, 0.5]); + yticks([1e-5, 1e-4, 1e-3, 1e-2, 1e-1]); +end + + + +end % ===== main ===== + + +%% ===================== Helpers ===================== + +function cfg = filldefaults(cfg, defs) +fn = fieldnames(defs); +for i = 1:numel(fn) + f = fn{i}; + if ~isfield(cfg, f) || isempty(cfg.(f)) + cfg.(f) = defs.(f); + elseif isstruct(defs.(f)) && isstruct(cfg.(f)) + cfg.(f) = filldefaults(cfg.(f), defs.(f)); % recursive for structs + end +end +end + +function out = tern(cond, a, b) + if cond + out = a; + else + out = b; + end +end + +function T2 = applyFilters(T, filters) +if isempty(filters), T2 = T; return; end +keep = true(height(T),1); +fns = fieldnames(filters); +for i = 1:numel(fns) + name = fns{i}; + if ~ismember(name, T.Properties.VariableNames) + warning('Filter column "%s" not found. Ignored.', name); %#ok<*WNTAG> + continue + end + val = filters.(name); + col = T.(name); + if isa(val,'function_handle') + m = val(col); + if ~islogical(m) || ~isequal(size(m), size(col)) + error('Filter for %s must return logical mask of same size.', name); + end + keep = keep & m; + else + keep = keep & ismember(col, val); + end +end +T2 = T(keep,:); +end + +function [x, label] = computeX(T, whichX) +switch lower(whichX) + case {'symbolrate','baudrate'} + x = T.symbolrate * 1e-9; + label = 'Symbol rate [GBd]'; + case 'bitrate' + if ~ismember('pam_level', T.Properties.VariableNames) + error('bitrate requires "pam_level" column.'); + end + bits = floor(log2(double(T.pam_level))*10)/10; + x = (T.symbolrate .* bits) * 1e-9; + label = 'Grossrate [Gb/s]'; + case 'grossrate' + x = (T.grossrate) * 1e-9; + label = 'Grossrate [Gb/s]'; + otherwise + if ~ismember(whichX, T.Properties.VariableNames) + error('x_axis "%s" not found in table.', whichX); + end + x = T.(whichX); + label = whichX; +end +x = double(x(:)); +end + +function keep = outlierMask(y, cfg, useLog) +if isempty(y), keep = false(size(y)); return; end +y = y(:); +switch lower(cfg.outlier) + case 'none' + keep = true(size(y)); return + case 'mad' + z = tern(useLog, log10(y), y); + med = median(z,'omitnan'); + madv = median(abs(z-med),'omitnan'); + if ~(isfinite(madv) && madv>0) + keep = true(size(y)); return + end + sigma = 1.4826*madv; + zz = tern(useLog, log10(y), y); + keep = abs(zz - med) <= cfg.mad_z*sigma; + case 'pctl' + pr = prctile(y, cfg.pct_limits); + keep = (y >= pr(1)) & (y <= pr(2)); + otherwise + error('Unknown outlier mode "%s".', cfg.outlier); +end +end + +function s = buildLabel(grpRow, group_by) +parts = strings(1, numel(group_by)); +for i = 1:numel(group_by) + key = group_by{i}; + val = grpRow.(key); + if iscell(val), val = val{1}; end + if islogical(val), val = tern(val,'w/','w/o'); end + if key == "equalizer_structure" + key = ''; + val = upper(val); + val = strrep(val,'_',' '); + end + + if key == "pre_emph" + % key = strrep(key,'_','-'); + val = [val, ' pre-emph.']; + key = ''; + end + + parts(i) = sprintf('%s %s', key, string(val)); +end +s = strjoin(parts, ', '); +end + +function [cols_line, cols_scatter] = buildGroupColors(nG, plotcfg) + +% --- 1) User-provided custom colors ------------------------------- +if isfield(plotcfg,'custom_colors') && ~isempty(plotcfg.custom_colors) + C = plotcfg.custom_colors; + if size(C,1) < nG + error('custom_colors must have at least nG=%d rows.', nG); + end + cols_line = C(1:nG, :); + + % Scatter colors: either user-provided or lightened + if isfield(plotcfg,'custom_colors_scatter') && ~isempty(plotcfg.custom_colors_scatter) + Cs = plotcfg.custom_colors_scatter; + if size(Cs,1) < nG + error('custom_colors_scatter must have at least nG=%d rows.', nG); + end + cols_scatter = Cs(1:nG, :); + else + % auto-lighten scatter colors + cols_scatter = zeros(nG,3); + for i = 1:nG + cols_scatter(i,:) = lightenColor(cols_line(i,:), 0.40); + end + end + return; +end + +% --- 2) Standard behavior (using cbrewer2 or fallback) ------------ +useBrewer = plotcfg.use_cbrewer2 && exist('cbrewer2','file')==2; +if useBrewer + N = max(2*nG, 12); + C = cbrewer2(plotcfg.colormap, N); + cols_line = zeros(nG,3); + cols_scatter = zeros(nG,3); + for i = 1:nG + if plotcfg.paired_dark_first + dark = C(2*i-1, :); + light = C(2*i, :); + else + light = C(2*i-1, :); + dark = C(2*i, :); + end + cols_line(i,:) = dark; + cols_scatter(i,:) = light; + end +else + C = lines(max(nG,7)); + cols_line = C(1:nG,:); + cols_scatter = zeros(nG,3); + for i = 1:nG + cols_scatter(i,:) = lightenColor(cols_line(i,:), 0.50); + end +end + +end + +function c2 = lightenColor(c, fracTowardWhite) +c = c(:).'; +c2 = (1-fracTowardWhite)*c + fracTowardWhite*1; +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/rates.csv b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/rates.csv new file mode 100644 index 0000000..c00999e --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/rates.csv @@ -0,0 +1,38 @@ +X-Werte;PAM-2;PAM-4;PAM-6;PAM-8;PAM-12 +224;204;;;; +205,5;193,4;;;; +205,1;189,7;;;; +192;168;;;; +240;;427,4;;; +225;;420,5;;; +210;;336;;; +184;;332;;; +192;;320;;; +176;;306,0869565;;; +190;;304;;; +168;;294;;; +156,2;;287,1;;; +160,8;;286,9;;; +170;;272;;; +132;;250,9505703;;; +112;;209,3457944;;; +172;;337;;; +216;;;474,6;; +147,2;;;329,9;; +132;;;319,7891753;; +143,1;;;318;; +160;;;377;; +225;;;;562,5; +200;;;;510; +160;;;;438; +180;;;;432; +180;;;;432; +144;;;;384; +143,7;;;;363,4; +144;;;;360; +136;;;;353,859497; +136;;;;342,7995295; +128;;;;329,0488432; +129,7;;;;311,2; +160;;;;413; +160;;;;;481,2 diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/run_plot_measurements.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/run_plot_measurements.m new file mode 100644 index 0000000..6ebf0fc --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/run_plot_measurements.m @@ -0,0 +1,76 @@ +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +M = 8; +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +fp.where('Runs', 'pam_level','EQUALS', M); +% fp.where('Runs', 'symbolrate','EQUALS', 165e9); %150, 165, 180, 195, 210, 225, 240 +fp.where('Runs', 'fiber_length','EQUALS', 2); +% fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'power_pd_in','GREATER_THAN', 7); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +fp.where('Runs', 'wavelength','EQUALS', 1310); +% fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis +% fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_alltime')]; %dashboard_ungrouped_after_nov_2025 dashboard_ungrouped_aug_nov_2025 +[dataTable,~] = db.queryDB(fp, fields); + + +%% +cfg = struct; +cfg.x_axis = 'grossrate'; % 'symbol rate' | 'bitrate' | 'wavelength' grossrate +cfg.y_axis = 'BER'; % 'BER' | 'GMI' | 'AIR' | ... +cfg.group_by = {'equalizer_structure','pre_emph'}; +cfg.filters = struct('is_mpi',0,'pam_level',M,'equalizer_structure',[equalizer_structure.ml_mlse]);%,equalizer_structure.vnle_pf_mlse,equalizer_structure.vnle]); + +cfg.y_scale = 'auto'; % auto -> log for BER*, linear otherwise +cfg.outlier = 'mad'; % simple, robust; 'none' or 'pctl' also available +cfg.show_raw = false; +cfg.show_spread = 'none'; % 'none' or 'iqr' or minmax +cfg.agg = 'min'; % or 'median' +cfg.show_precoded = 0; +cfg.fec_lines = [2.2e-4 4.85e-3 2e-2]; % optional + +cfg.figure_number = 42; +cfg.plot.custom_colors = [ + clr.Paired.red; + clr.Paired.blue; + clr.Paired.green; + clr.Paired.orange; + clr.Paired.purple +]; + +cfg.plot.custom_colors_scatter = [ + clr.Paired.lightred; + clr.Paired.lightblue; + clr.Paired.lightgreen; + clr.Paired.lightorange; + clr.Paired.lightpurple +]; + + +% New styling knobs +cfg.plot.use_cbrewer2 = true; +cfg.plot.colormap = 'Paired'; +cfg.plot.paired_dark_first = false; % dark for lines, light for scatter +cfg.plot.lineWidth = 2.0; +cfg.plot.errWidth = 1.2; +cfg.plot.scatterAlpha = 0.35; +cfg.plot.legendLocation = 'best'; +cfg.plot.fecLineWidth = 2.4; % thicker FEC limits + +plot_measurements_gpt(dataTable, cfg); + +% beautifyBERplot() + +%% FIG PRE EMPHASIS + + + + diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/gmi_vs_rate.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/gmi_vs_rate.m new file mode 100644 index 0000000..26aaddb --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/gmi_vs_rate.m @@ -0,0 +1,87 @@ + +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +M = 8; +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 987); +fp.where('Runs', 'pam_level','EQUALS', M); +% fp.where('Runs', 'symbolrate','EQUALS', 165e9); +fp.where('Runs', 'fiber_length','EQUALS', 2); +fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +fp.where('Runs', 'wavelength','EQUALS', 1310); +% fp.where('Runs', 'db_mode','EQUALS', 1); % 0 == high preemphasis // 1 == low preemphasis +fp.where('Runs', 'rop_attenuation','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +fields = [fields; db.getTableFieldNames('dashboard_ungrouped_new')]; +[dataTable,~] = db.queryDB(fp, fields); + +eqstructures = unique(dataTable.equalizer_structure); + +% Create the figure +figure(18); +hold on + +for pre_emph = [0,1] + + dbmode_filtered = dataTable(dataTable.db_mode == ~pre_emph,:); + + for eqs = [equalizer_structure.vnle] + + eq_choice = equalizer_structure(eqs); + if sum(eqstructures == eq_choice)~=1 + disp(eq_choice) + continue + end + + eq_filtered = dbmode_filtered(dbmode_filtered.equalizer_structure == eq_choice,:); + if eqs ==equalizer_structure.vnle_pf_mlse + eq_filtered = eq_filtered(eq_filtered.DIR == "1",:); + end + symbolrate_sorted = sortrows(eq_filtered,{'symbolrate'}, 'ascend'); + + + % Example data (replace these with your real vectors) + symbolrate = symbolrate_sorted.symbolrate.*1e-9; % in baud + bitrate = symbolrate * 2; + gmi = symbolrate_sorted.GMI; % BER + snr = symbolrate_sorted.SNR; % BER + cols = cbrewer2('Paired',12); + + dname = [char(eq_choice)]; + dname = strrep(dname,'_','+'); + if pre_emph + dname = [dname,'; w/ pre-emph.']; + else + dname = [dname,'; w/o pre-emph.']; + end + + plot(symbolrate, snr, 'LineWidth', 1.5, 'MarkerSize', 5,'Marker','o','LineStyle','-','Color',cols((2*eqs)+1+pre_emph,:),'MarkerEdgeColor',cols((2*eqs)+1+pre_emph,:),'MarkerFaceColor',[1,1,1],'DisplayName',[dname]); + grid on; + + % Axis labels and title + xlabel('Bit Rate Gbps', 'FontSize', 12); + ylabel('GMI', 'FontSize', 12); + title('GMI vs. Bit Rate', 'FontSize', 14, 'FontWeight', 'bold'); + + % Improve tick formatting + set(gca, 'XScale', 'linear', ... + 'YScale', 'linear', ... + 'TickLabelInterpreter', 'none', ... + 'FontSize', 11); + legend + + xticks(symbolrate); + + % Optional: tighten axis limits + xlim([min(symbolrate), max(symbolrate)]); + % ylim([log2(M)-1, log2(M)]); + + end + +end diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/power_vs_wavelength.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/power_vs_wavelength.m new file mode 100644 index 0000000..d018b47 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/power_vs_wavelength.m @@ -0,0 +1,96 @@ + +database_type = 'mysql'; +dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; +db = DBHandler("dataBase", [dataBase], "type", database_type); + +fp = QueryFilter(); +fp.where('power_state_info', 'pam_level','EQUALS', 4); +fp.where('power_state_info', 'db_mode','EQUALS', 1); +% fp.where('power_state_info', 'fiber_length','EQUALS', 1); +fp.where('power_state_info', 'is_mpi','EQUALS', 0); + +fields = db.getTableFieldNames('power_state_info'); +% [dataTable,~] = db.queryDB(fp, fields); + +fiber_len = unique(dataTable.fiber_length); +cnt = 0; + +y_variable = 'power_mzm'; +x_variable = "wavelength"; +f = figure(3); +clf +hold on +for fl = 1:numel(fiber_len) + + + fl_filtered = dataTable(dataTable.fiber_length == fiber_len(fl),:); + [~, ia] = unique(fl_filtered.run_id, 'first'); + fl_filtered = fl_filtered(ia, :); + + fl_filtered_ = groupsummary( ... + fl_filtered, ... % input table + x_variable, ... % grouping variable + "mean", ... % which summary statistic + y_variable); % which column to average + + wavelength_sorted = sortrows(fl_filtered, {'wavelength'}, 'ascend'); + + % pull out your vectors + lambda = wavelength_sorted.wavelength; + power_laser = wavelength_sorted.power_laser; + power_mzm = wavelength_sorted.power_mzm; + power_rop = wavelength_sorted.power_rop; + power_pd = wavelength_sorted.power_pd_in; + voa = wavelength_sorted.voa_atten; + len = wavelength_sorted.fiber_length; + run_ids = wavelength_sorted.run_id; % <-- this is what we want in the datatip + cols = linspecer(8); + + + % plot the two curves and capture their Line handles + % h1 = plot(lambda, power_laser,'LineWidth', 0.5, 'MarkerSize', 4,'Marker','o','LineStyle','none','Color',cols(fl,:),'MarkerFaceColor',cols(fl,:),'DisplayName','Laser Output'); + % % —————— Add run_id as a datatip row —————— + % % For each line, tell the datatip template where to find the run_id: + % h1.DataTipTemplate.DataTipRows(end+1) = ... + % dataTipTextRow('run\_id', run_ids); + % h1.DataTipTemplate.DataTipRows(end+1) = ... + % dataTipTextRow('len', run_ids); + % h1.DataTipTemplate.DataTipRows(end+1) = ... + % dataTipTextRow('voaatten', voa); + + % + dname = sprintf('%s; %d km',y_variable, fiber_len(fl)); + h2 = plot(fl_filtered_.(x_variable), fl_filtered_.(['mean_',y_variable]), 'LineWidth', 1, 'MarkerSize', 4,'Marker','o','LineStyle','-','Color',cols(fl,:),'MarkerFaceColor',cols(fl,:),'DisplayName',dname); + + h2.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('run\_id', run_ids); + h2.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('len', len); + h2.DataTipTemplate.DataTipRows(end+1) = ... + dataTipTextRow('voaatten', voa); + + grid on; + xticks(sort(unique(lambda))); + xticklabels(sort(unique(lambda))); + + % Labels, scales, legend, etc. + xlabel('Wavelength in nm','FontSize',12); + ylabel('Power in dB','FontSize',12); + title('Power ','FontSize',14,'FontWeight','bold'); + set(gca, 'XScale','linear','YScale','linear','FontSize',11); + legend + + xlim([min(lambda)-2, max(lambda)+2]); + ylim([floor(min(fl_filtered_.(['mean_',y_variable])))-1 12]); + ylim([-12 12]); + + cnt = cnt+1; + + yline(8,'HandleVisibility','off'); + +end + +yline([4.85e-3, 2e-2],'--','LineWidth',1,'HandleVisibility','off'); +posH = get(f, 'Position'); % [left, bottom, width, height] +newPos = [posH(1), posH(2), 750, 300]; +set(f, 'Position', newPos); \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m new file mode 100644 index 0000000..8f96ba2 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m @@ -0,0 +1,379 @@ +% === SETTINGS === +dsp_options.append_to_db = 1; +dsp_options.max_occurences = 1; + +experiment = "highspeed_2024"; +dsp_options.mode = "load_run_id"; % 'simulate' & 'load_files' +dsp_options.load_file_path = struct(); + +if dsp_options.mode == "load_run_id" + + if experiment == "highspeed_2024" + + dsp_options.database_type = 'mysql'; + dsp_options.dataBase = 'labor_highspeed';%'C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\silas_labor_newdsp_newstructure.db'; + dsp_options.storage_path = 'Z:\2024\sioe_labor\'; + db = DBHandler("dataBase", [dsp_options.dataBase], "type", dsp_options.database_type); + + elseif experiment == "mpi_ecoc_2025" + + dsp_options.database_type = 'mysql'; + dsp_options.dataBase = 'labor'; + dsp_options.storage_path = 'Z:\2025\ECOC Silas\ecoc_2025\'; + db = DBHandler("dataBase", [dsp_options.dataBase], "type", dsp_options.database_type); + + end + +elseif dsp_options.mode == "load_files" + + dsp_options.load_file_path.tx_bits_path = "Z:\2025\ECOC Silas\ecoc_2025\mpi_opti_1000m_pam2 4 6 8\20250417_091513_PAM_4_R_112_bits.mat"'; + dsp_options.load_file_path.tx_symbols_path = "Z:\2025\ECOC Silas\ecoc_2025\mpi_opti_1000m_pam2 4 6 8\20250417_091513_PAM_4_R_112_symbols.mat"'; + dsp_options.load_file_path.rx_raw_path = "Z:\2025\ECOC Silas\ecoc_2025\mpi_opti_1000m_pam2 4 6 8\20250417_091525_PAM_4_R_112_rec01_rx_signal_raw.mat"'; + +elseif dsp_options.mode == "simulate" + + error('Not yet implemented') + +end + +% === Get Run ID's === + +fp = QueryFilter(); +% fp.where('Runs', 'run_id','EQUALS', 2776); +M = 6; +fp.where('Runs', 'pam_level','EQUALS', M); +% fp.where('Runs', 'bitrate','EQUALS', 390e9);%360,390 +% fp.where('Runs', 'symbolrate','EQUALS', 195e9); +fp.where('Runs', 'fiber_length','EQUALS', 10); +fp.where('Runs', 'is_mpi','EQUALS', 0); +% fp.where('Runs', 'interference_path_length','EQUALS', 1000); +% fp.where('Runs', 'loop_id','GREATER_THAN', 11); +% fp.where('Runs', 'sir','EQUALS',18); +% fp.where('Runs', 'wavelength','EQUALS', 1310); +fp.where('Runs', 'db_mode','EQUALS', 0); +fp.where('Runs', 'rop_attenuation','EQUAL', 0); +% fp.where('Runs', 'power_pd_in','LESS_THAN', 7); + +[dataTable,~] = db.queryDB(fp, db.getTableFieldNames('Runs')); + +% === Set LOOPS & Initialize DataStorage === +dsp_options.parameters = struct(); +% dsp_options.parameters.pf_ncoeffs = [1,2];%s[0,logspace(-4,0,10)]; + +wh = DataStorage(dsp_options.parameters); +wh.addStorage("ffe_package"); +wh.addStorage("mlse_package"); +wh.addStorage("vnle_package"); +wh.addStorage("dbtgt_package"); +wh.addStorage("dbenc_package"); +wh.addStorage("mlmlse_package"); + +%% === RUN IT === + +[results,wh] = submitJobs(dataTable.run_id(:), dsp_options, "parallel", 'wh', wh, 'waitbar', true); + + + +%% ========================================================================= +% LOAD METADATA +% ========================================================================= +[dataTable, ~] = db.queryDB(fp, db.getTableFieldNames('Runs')); +results = results(:).'; % ensure row vector +N = numel(results); + +% ========================================================================= +% PREALLOCATE METRIC ARRAYS +% ========================================================================= +BER_VNLE = nan(1,N); +BER_MLSE = nan(1,N); +BER_DB = nan(1,N); +BER_DB_PREC = nan(1,N); +BER_MLMLSE = nan(1,N); +BER_MLMLSE_PREC = nan(1,N); + +% ========================================================================= +% EXTRACT METRICS (ONE LOOP, ROBUST) +% ========================================================================= +for i = 1:N + r = results{i}; + + % ---- VNLE (no-DB mode) ---- + if isfield(r, 'vnle_package') && ~isempty(r.vnle_package) + pkg = r.vnle_package; + BER_VNLE(i) = min(cellfun(@(c) c.metrics.BER, pkg)); + end + + % ---- Classical MLSE (DB mode) ---- + if isfield(r, 'mlse_package') && ~isempty(r.mlse_package) + pkg = r.mlse_package; + BER_MLSE(i) = min(cellfun(@(c) c.metrics.BER, pkg)); + BER_MLSE_PREC(i) = min(cellfun(@(c) c.metrics.BER_precoded, pkg)); + end + + % ---- DB Target (DB mode) ---- + if isfield(r, 'dbtgt_package') && ~isempty(r.dbtgt_package) + pkg = r.dbtgt_package; + BER_DB(i) = min(cellfun(@(c) c.metrics.BER, pkg)); + BER_DB_PREC(i) = min(cellfun(@(c) c.metrics.BER_precoded, pkg)); + end + + % ---- ML-based MLSE (both modes) ---- + if isfield(r, 'mlmlse_package') && ~isempty(r.mlmlse_package) + pkg = r.mlmlse_package; + + % raw BER + BER_MLMLSE(i) = min(cellfun(@(c) c.metrics.BER, pkg)); + + % precoded BER + if isfield(pkg{1}.metrics, 'BER_precoded') + BER_MLMLSE_PREC(i) = min(cellfun(@(c) c.metrics.BER_precoded, pkg)); + end + end +end + +%% ========================================================================= +% METADATA (ALWAYS INDEX-ALIGNED WITH RESULTS) +% ========================================================================= +bitrate = dataTable.bitrate(:).'; +baudrate = dataTable.symbolrate(:).'; + +rop_atten = dataTable.rop_attenuation(2:2:end).'; +rop_pre = dataTable.power_rop(1:2:end).'; +rop = dataTable.power_rop(2:2:end).'; + +% ========================================================================= +% PLOT STYLE +% ========================================================================= +STYLE_BASE = 2; +MARKER_SIZE = STYLE_BASE; +LINE_WIDTH = max(2, STYLE_BASE/3); + +cols = cbrewer2('Paired', 8); + +cm.VNLE = cols(1,:); +cm.MLSE = cols(2,:); +cm.DB_PREC = cols(3,:); +cm.DB = cols(4,:); +cm.ML_MLSE = cols(6,:); + +mk = @(col,shape) {'Marker',shape,'MarkerFaceColor',col,'MarkerEdgeColor',col,'MarkerSize',MARKER_SIZE}; + +% ========================================================================= +% FIGURE 1: BER vs BAUDRATE +% ========================================================================= +figure(112+M); clf; hold on; +xGHz = baudrate * 1e-9; + +plot(xGHz, BER_VNLE, 'DisplayName','VNLE', 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, BER_MLSE, 'DisplayName','MLSE', 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +% plot(xGHz, BER_MLSE_PREC, 'DisplayName','MLSE', 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, BER_DB_PREC, 'DisplayName','Diff. Precode + DB', 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB_PREC); +plot(xGHz, BER_MLMLSE_PREC, 'DisplayName','ML-based MLSE', 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.ML_MLSE); + +yline(2e-2,'LineWidth',1,'HandleVisibility','off'); +yline(4.85e-3,'LineWidth',1,'HandleVisibility','off'); +yline(2.2e-4,'LineWidth',1,'HandleVisibility','off'); + +xlabel('Baudrate in GBd'); +ylabel('BER'); +set(gca, 'YScale', 'log'); grid on; legend('Location','best'); +% beautifyBERplot; + + + + + + + + + + + + + + + + + +%% ---------------- FIGURE 15 : GMI ---------------- +figure(113+M); clf; hold on; +plot(xGHz, GMI_VNLE, ... + 'DisplayName','VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, GMI_MLSE, ... + 'DisplayName','MLSE', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, GMI_DB, ... + 'DisplayName','DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); + +ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('GMI'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); + + + +% ---------------- FIGURE 15 : AIR ---------------- +m = floor(log2(M)*10)/10; +figure(114+M); clf; hold on; +plot(xGHz, GMI_VNLE.*xGHz, ... + 'DisplayName','AIR VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +% duobinary has only one GMI curve (DB output) +plot(xGHz, GMI_MLSE.*xGHz, ... + 'DisplayName','AIR MLSE', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +% MLSE symbol-wise (if present) +plot(xGHz, GMI_DB.*xGHz, ... + 'DisplayName','AIR DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); + +% ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('AIR in Gbps'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); + + + + + + +% ---------------- FIGURE 15 : Information Rates ---------------- +tp = TransmissionPerformance; + + +m = floor(log2(M)*10)/10; +figure(213+M); clf; hold on; + +netrates_vnle = tp.calculateNetRate(baudrate.* m, ... + 'NGMI', GMI_VNLE./m, ... + 'BER', BER_VNLE); +% +% plot(xGHz, GMI_VNLE.*xGHz, ... +% 'DisplayName','GMI*R VNLE', ... +% mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +% +% plot(xGHz, netrates_vnle.SDHD.NetRate.*1e-9, ... +% 'DisplayName','SD+HD VNLE', ... +% mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +% plot(xGHz, netrates_vnle.HD.NetRate.*1e-9, ... +% 'DisplayName','Staircase VNLE', ... +% mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +% +% +% % +% % MLSE symbol-wise (if present) +% plot(xGHz, GMI_MLSE.*xGHz, ... +% 'DisplayName','GMI*R MLSE', ... +% mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +% +% netrates_mlse = tp.calculateNetRate(baudrate.* m, ... +% 'NGMI', GMI_MLSE./m, ... +% 'BER', BER_MLSE); +% plot(xGHz, netrates_mlse.SDHD.NetRate.*1e-9, ... +% 'DisplayName','SD+HD MLSE', ... +% mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +% plot(xGHz, netrates_mlse.HD.NetRate.*1e-9, ... +% 'DisplayName','Staircase MLSE', ... +% mk.MLSE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + + +% duobinary has only one GMI curve (DB output) +figure(1111); clf; hold on; +plot(xGHz, GMI_DB.*xGHz, ... + 'DisplayName','GMI*R DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); + +netrates_db = tp.calculateNetRate(baudrate.* m, ... + 'NGMI', GMI_DB./m, ... + 'BER', BER_DB_PREC); + +plot(xGHz, netrates_db.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD DB', ... + mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB); +plot(xGHz, netrates_db.STAIR.NetRate.*1e-9, ... + 'DisplayName','Staircase DB', ... + mk.DB_precode{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.DB_precode); +plot(xGHz, netrates_db.O_FEC.NetRate.*1e-9, ... + 'DisplayName','O-FEC DB', ... + mk.DB_precode{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.DB_precode); +plot(xGHz, netrates_db.KP4_hamming.NetRate.*1e-9, ... + 'DisplayName','KP4 Hamming DB', ... + mk.DB_precode{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB_precode); + +% ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('AIR in Gbps'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +% xlim([1, 256]) + + + +figure(2222); clf; hold on; +plot(xGHz, GMI_MLSE.*xGHz, ... + 'DisplayName','GMI*R DB tgt.', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + +netrates_mlse = tp.calculateNetRate(baudrate.* m, ... + 'NGMI', GMI_MLSE./m, ... + 'BER', BER_MLSE); + +plot(xGHz, netrates_mlse.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD DB', ... + mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, netrates_mlse.STAIR.NetRate.*1e-9, ... + 'DisplayName','Staircase DB', ... + mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, netrates_mlse.O_FEC.NetRate.*1e-9, ... + 'DisplayName','O-FEC DB', ... + mk.VNLE{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, netrates_mlse.KP4_hamming.NetRate.*1e-9, ... + 'DisplayName','KP4 Hamming DB', ... + mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE); + +% ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('AIR in Gbps'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +% xlim([1, 256]) + + + +figure(3333); clf; hold on; +plot(xGHz, GMI_VNLE.*xGHz, ... + 'DisplayName','GMI*R DB tgt.', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + +netrates_vnle = tp.calculateNetRate(baudrate.* m, ... + 'NGMI', GMI_VNLE./m, ... + 'BER', BER_VNLE); + +plot(xGHz, netrates_vnle.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD DB', ... + mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, netrates_vnle.STAIR.NetRate.*1e-9, ... + 'DisplayName','Staircase DB', ... + mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, netrates_vnle.O_FEC.NetRate.*1e-9, ... + 'DisplayName','O-FEC DB', ... + mk.VNLE{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, netrates_vnle.KP4_hamming.NetRate.*1e-9, ... + 'DisplayName','KP4 Hamming DB', ... + mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE); + +% ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('AIR in Gbps'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +% xlim([1, 256]) \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/simulate_and_dsp.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/simulate_and_dsp.m new file mode 100644 index 0000000..772f110 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/simulate_and_dsp.m @@ -0,0 +1,236 @@ + +precomp_mode = 0; +precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\HighSpeedExperiment_2024\Auswertung_JLT"; +precomp_fn = "precomp_simulated.mat"; + +% TX +M = 4; +fsym = 72e9; +f_nyquist = fsym/2; +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +% fdac = 2*fsym; +% fadc = 2*fsym; +random_key = 1; + +duob_mode = db_mode.no_db; + +tx_bwl = 0.8.*f_nyquist; +rx_bwl = 0.8.*f_nyquist; + +rcalpha = 0.05; +kover = 16; +vbias_rel = 0.5; +u_pi = 2.9; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1293; +laser_linewidth = 0; + + +% Channel +link_length = 60000; + +% RX +rop = -8; + +% EQ +eq_mode = equalizer_structure.vnle_pf_mlse; +ffe_order=[50,0,0]; +vnle_order=[50,5,5]; +dfe_order = [0 0 0]; + +len_tr = 4096*2; +mu_ffe = [0.0004 0.0004 0.0004]; +mu_dfe = 0.0004; +mu_dc = 0.00; + +dfe_ = sum(dfe_order)>0; + +Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha); + +[Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym,"M",M,"order",18,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "duobinary_mode",duob_mode).process(); + +if precomp_mode == 1 % measure channel + precomp_est = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',fdac); + Digi_sig = precomp_est.buildOFDM(); +elseif precomp_mode == 2 % apply precomp + precomp_est = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',Digi_sig.fs); + Digi_sig = precomp_est.precomp(Digi_sig,'maxampdb',-50,'loadPath',precomp_path,'fileName',precomp_fn); +end + +% Symbols.spectrum("displayname",'Tx Symbols','fignum',10,'normalizeTo0dB',1); + +Digi_sig.eye(fsym,M,"fignum",1234567); +%%%%% AWG +%El_sig = M8199B("kover",kover).process(Digi_sig); +El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",1).process(Digi_sig); +% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',0); +% El_sig = El_sig.setPower(0,"dBm"); +El_sig.spectrum("displayname",'Tx Signal','fignum',10,'normalizeTo0dB',0); +%%%%% Low-pass el. components %%%%%% + +El_sig = Filter('filtdegree',4,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.gaussian,"active",true).process(El_sig); +% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',1); + +%%%%% Electrical Driver Amplifier %%%%%% +El_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",3).process(El_sig); +El_sig = El_sig.normalize("mode","oneone"); + +%%%%% MODULATE E/O CONVERSION %%%%%% +[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig); + +Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + +%%%%%% ROP %%%%%% +Rx_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig); + +%%%%%% PD Square Law %%%%%% +Rx_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11).process(Rx_sig); + +%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% +Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.bessel_inp,"active",true).process(Rx_sig); + +% %%%%%% Low-pass Scope %%%%%% +Lp_scpe = Filter('filtdegree',4,"f_cutoff",35e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + +% Rx_sig.spectrum("displayname",'Analog Rx Spectrum','fignum',100,'normalizeTo0dB',1); + +%%%%%% Scope %%%%%% +Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(Rx_sig); + +Scpe_sig.spectrum("displayname",'Rx Signal','fignum',10,'normalizeTo0dB',1); +Scpe_sig.eye(fsym,M,"fignum",1973763) + +%%%%% Precompensation Routine %%%%%% +if precomp_mode == 1 + Scpe_sig_resampled = Scpe_sig.resample("fs_in",fadc,"fs_out",2*fsym); + precomp_est.estimate(Scpe_sig_resampled,"save",false,"savePath",precomp_path,"fileName",precomp_fn); + precomp_est.plot(); + precomp_est.save(); +end + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym); +Scpe_sig.signal = Scpe_sig.signal(1:2*Symbols.length); +use_ffe = 0; +use_dfe = 0; +use_vnle_mlse = 1; +use_dbtgt = 1; +use_dbenc = 1; + + +if duob_mode ~= db_mode.db_encoded + + if use_ffe + + ffe_order = [50, 0, 0]; + eq_dfe = EQ("Ne",ffe_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); + + ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',1,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + disp('FFE:') + ffe_results.metrics.print; + + + end + + if use_dfe + + ffe_order = [50, 5, 5]; + eq_dfe = EQ("Ne",ffe_order,"Nb",[2,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); + + dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + disp('DFE:') + dfe_results.metrics.print; + + + end + + if use_vnle_mlse + + if 0 + pf_ncoeffs = 1; + eq_ = EQ("Ne",ffe_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); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + % mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 1, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + disp('VNLE:') + ffe_results.metrics.print; + disp('MLSE:') + mlse_results.metrics.print; + end + + pf_ncoeffs = 2; + eq_ = EQ("Ne",ffe_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); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + % mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 1, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + disp('VNLE:') + ffe_results.metrics.print; + disp('MLSE:') + mlse_results.metrics.print; + + + end + + if use_dbtgt + eq_ = EQ("Ne",ffe_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); + + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels); + + dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode, ... + 'showAnalysis',1 ,... + "postFFE", []); + + disp('DB:') + dbt_results.metrics.print; + + end +end + +if duob_mode == db_mode.db_encoded + + eq_db_enc = EQ("Ne", ffe_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); + + mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); + + db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",1,"postFFE",[]); + + db_results.metrics.print; +end \ No newline at end of file diff --git a/projects/HighSpeedExperiment_2024/a_minimal_example.m b/projects/HighSpeedExperiment_2024/a_minimal_example.m new file mode 100644 index 0000000..de85571 --- /dev/null +++ b/projects/HighSpeedExperiment_2024/a_minimal_example.m @@ -0,0 +1,118 @@ + + +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; +dsp_options.max_occurences = 1; +db = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' ); + +fp = QueryFilter(); + +fp.where('Runs','fiber_length','EQUALS', 2); +fp.where('Runs','wavelength','EQUALS', 1310); +fp.where('Runs','bitrate','EQUALS', 300e9); +fp.where('Runs','pam_level','EQUALS', 4); +fp.where('Runs','rop_attenuation','EQUALS', 0); +fp.where('Runs','is_mpi','EQUALS', 0); +fp.where('Runs', 'db_mode','EQUALS', 0); +fields = db.getTableFieldNames('Runs'); +[dataTable,~] = db.queryDB(fp, fields); + + +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); +duob_mode = db_mode(strrep(dataTable.db_mode,'"','')); + + +% Load and Sync signal data from DB +[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + +% Show spectrum +Scpe_sig.spectrum("fignum",1,"displayname",'Rx') + + + + + + +%% simple FFE + +mu_ffe = [0.0001, 0.0008, 0.001]; +mu_dfe = 0.0004; +ffe_order = [50, 0, 0]; +eq_dfe = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",4096,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); + +ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + +ffe_results.metrics.print("description",'FFE'); +ffe_results.config.equalizer_structure = "ffe"; + +%% a) VNLE // b) concatenated VNLE + MLSE + +pf_ncoeffs = 1; +ffe_order = [50, 5, 5]; +dfe_order = [0,0,0]; +eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",4096,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); +pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + + +if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation + trellexlusion = 1; +else + trellexlusion = 0; +end + +%state_mode 3 -> stat lvl; state_mode 2 -> use target lvls +%scale_mode 2 -> mmse adaption + +mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2); + +[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + +vnle_results.metrics.print("description",'VNLE'); +mlse_results.metrics.print("description",'VNLE + PF + MLSE'); + + +%% Duobinary Equalization + + +if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation + trellexlusion = 1; +else + trellexlusion = 0; +end +mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3); + +ffe_order = [50, 5, 5]; +dfe_order = [0,0,0]; +eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",4096,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + +dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode, ... + 'showAnalysis', 0,... + "postFFE", []); + +dbt_results.metrics.print("description",'Duobinary EQ'); + +%% Ml based Viterbi + +%ML-based MLSE (L=2) +mu_ml = 0.01; training_epochs = 100; +ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",length(Scpe_sig),"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0); + +[ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits,"precode_mode",duob_mode); + +ml_mlse_results.metrics.print("description",'ML pre Eq. + Viterbi') \ No newline at end of file diff --git a/projects/IMDD_base_system/model_linewidth_evaluation.m b/projects/IMDD_base_system/model_linewidth_evaluation.m new file mode 100644 index 0000000..e648184 --- /dev/null +++ b/projects/IMDD_base_system/model_linewidth_evaluation.m @@ -0,0 +1,181 @@ +%%% Run parameters +% TX +M = 4; + +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +random_key = 2; + +rcalpha = 0.05; +kover = 8; +vbias_rel = 0.5; +u_pi = 3.2; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1300; +laser_linewidth = logspace(0,6.2,24); + +% Channel +link_length = 10; + +alpha = 0; + +doub_mode = db_mode.no_db; +cols = linspecer(6); +rop = [-6]; +bwl = [0.5:0.1:1.5]; +fsym = [200:16:256].*1e9; +% nonlin_mod = [0.5:0.01:0.75]; +fsym = ones(size(laser_linewidth)).*fsym(1); +nonlin_mod = ones(size(laser_linewidth)).*0.5; + +ffe_results = {}; +mlse_results_lin= {}; + +parfor r = 1:length(laser_linewidth) + + Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha); + + db_precode = 0; + db_encode = 0; + duob_mode = db_mode.no_db; + apply_pulsef = 1; + + [Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym(r),"M",M,"order",18,"useprbs",1,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "db_precode",db_precode,"db_encode",db_encode,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + + El_sig = M8199B("kover",kover).process(Digi_sig); + + %%%%% Electrical Driver Amplifier %%%%%% + El_sig = El_sig.normalize("mode","oneone"); + + %%%%% MODULATE E/O CONVERSION %%%%% + u_pi = 3.2; + vbias = -u_pi*nonlin_mod(r); + [Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth(r),"randomkey",random_key+1).process(El_sig); + + %%%%%% Fiber %%%%%% + mpi = 1; + if mpi + Combined_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + else + + % 2) ping pong fiber propagation + mpi_path = 00; + Interference_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",mpi_path*2,"alpha",0,"D",0,"lambda0",1310,"gamma",0).process(Opt_sig); + + Interference_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",-30).process(Interference_sig); + + [Main_sig,dly] = Opt_sig.delay("delay_meter",mpi_path*2); + + % Add + Combined_sig = Main_sig + Interference_sig; + + % Cut (due to the delays there is a jump in the signals) + if dly == 0;dly = 1;end + Combined_sig.signal = Combined_sig.signal(ceil(dly):end); + + % Fiber + Combined_sig = Fiber("fsimu",Combined_sig.fs,"fiber_length",2,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.08).process(Combined_sig); + end + + + %%%%%% ROP %%%%%% + Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Combined_sig); + + %%%%%% PD Square Law %%%%%% + PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig); + + %%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% + rx_bwl = 70e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig); + + % %%%%%% Low-pass Scope %%%%%% + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + + %%%%%% Scope %%%%%% + Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r)); + % Symbols.signal = Symbols.signal(1:Scpe_sig_2sps.length/2); + + % 2sps + [~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0); + Rx_sig_2sps = Scpe_cell{1}; + Rx_sig_2sps = Rx_sig_2sps.normalize("mode","rms"); + + % 1sps + Scpe_sig_1sps = Scpe_sig.resample("fs_out",1*fsym(r)); + + [~, Scpe_cell_1sps, ~, found_sync] = Scpe_sig_1sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0); + Rx_sig_1sps = Scpe_cell_1sps{1}; + Rx_sig_1sps = Rx_sig_1sps.normalize("mode","rms"); + + + %% RUN DSP + len_tr = 4096*2; + + mu_ffe1 = 0.0001; + mu_ffe2 = 0.0008; + mu_ffe3 = 0.001; + mu_dc = 0.005; + % mu_dc = 0; + + mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3]; + mu_dfe = 0.0004; + pf_ncoeffs = 1; + ffe_order = [50, 3, 3]; + mu_lms = 0.0005; + eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms"); + % eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",1,"DCmu",0.00,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0); + + [ffe_results{r}, mlse_results_lin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + + ffe_results{r}.metrics.print; + mlse_results_lin{r}.metrics.print; + +end + + +figure(1);hold on; +plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.BER, ffe_results),'DisplayName','VNLE') +plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.BER, mlse_results_lin),'DisplayName','VNLE+MLSE') +xlabel('Linewidth [GHz]'); +ylabel('BER') +set(gca,'YScale','log'); +legend; +ylim([1e-5 1e-1]); +beautifyBERplot; + +figure();hold on; +plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.AIR.*1e-9, ffe_results),'DisplayName','FFE') +plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.AIR.*1e-9, mlse_results_lin),'DisplayName','FFE+MLSE') +xlabel('Linewidth [GHz]'); +ylabel('AIR [GBd]') +% set(gca,'YScale','log'); +legend; +beautifyBERplot("logscale",0); + +figure(); hold on +stem(calcWavelengthPlan(16,400e9,1310),ones(16,1),'DisplayName','16x400','Marker','.','LineWidth',1); +stem(calcWavelengthPlan(8,800e9,1310),ones(8,1),'DisplayName','8x800','Marker','.','LineWidth',1); +stem(calcWavelengthPlan(16,800e9,1310),ones(16,1),'DisplayName','16x800','Marker','.','LineWidth',1); +ylim([0,1.2]); +ylabel('wavelength [nm]'); +xlim([1270, 1350]) \ No newline at end of file diff --git a/projects/IMDD_base_system/simulation_bwl.m b/projects/IMDD_base_system/simulation_bwl.m new file mode 100644 index 0000000..e70c40d --- /dev/null +++ b/projects/IMDD_base_system/simulation_bwl.m @@ -0,0 +1,569 @@ +%%% Run parameters +% TX +M = 4; +m = floor(log2(M)*10)/10; +fsym = 224e9; + +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +random_key = 2; + +rcalpha = 0.05; +kover = 8; +vbias_rel = 0.5; +u_pi = 3.2; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1310; +laser_linewidth = 1e6; + + +% Channel +link_length = 0; + +vnle_order1 = 50; +vnle_order2 = 0; +vnle_order3 = 0; + +vnle_order=[vnle_order1,vnle_order2,vnle_order3]; +dfe_order = [0 0 0]; + +alpha = 0; + +len_tr = 4096*2; + +mu_ffe1 = 0.0001; +mu_ffe2 = 0.0008; +mu_ffe3 = 0.001; +mu_dc = 0.005; +% mu_dc = 0; + +mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3]; +mu_dfe = 0.0004; + +dfe_ = sum(dfe_order)>0; + +doub_mode = db_mode.no_db; +cols = linspecer(6); +rop = [-6]; +bwl = [0.5:0.1:1.5]; +fsym = [192:16:256].*1e9; +fsym = 208e9; + +ber_vnle = []; +ber_mlse = []; +ber_mlse_burg = []; +ber_viterbi = []; +ber_db = []; +ber_db_diff_precoded = []; +gmi_vnle_bitwise = []; +gmi_mlse = []; +gmi_mlse_db = []; + +for r = 1:length(fsym) + + Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha); + + db_precode = 0; + db_encode = 0; + duob_mode = db_mode.no_db; + apply_pulsef = 1; + + [Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym(r),"M",M,"order",19,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "db_precode",db_precode,"db_encode",db_encode,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + + % El_sig = AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig); + El_sig = M8199B("kover",kover).process(Digi_sig); + % AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig); + + %%%%% Low-pass el. components %%%%%% + % tx_bwl = 100e9; + % El_sig = Filter('filtdegree',3,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig); + + %%%%% Electrical Driver Amplifier %%%%%% + El_sig = El_sig.normalize("mode","oneone"); + % El_sig = El_sig.setPower(1,"dBm"); + % figure;histogram(El_sig.signal); + + %%%%% MODULATE E/O CONVERSION %%%%% + u_pi = 3.2; + vbias = -u_pi*0.5; + [Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig); + + if 0 + figure(15); + hold on + scatter(El_sig.signal(1:100000)+vbias,(abs(Opt_sig.signal(1:100000)).^2)*1e3,0.1,'.','DisplayName','Modulator TF') + xlabel('Input in V') + ylabel('abs(Eopt)2 in mW','Interpreter','latex') + ylim([0 2]); + xlim([-3.2 0]); + + Opt_sig.eye(fsym(r),M,"fignum",103837); + end + + %%%%%% Fiber %%%%%% + Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + + %%%%%% ROP %%%%%% + Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig); + + % Opt_sig.eye(fsym(r),M,"fignum",103838); + + % % Opt_sig.signal = Opt_sig.signal + 5*abs(mean(Opt_sig.signal)); + % Opt_sig.move_it_spectrum("displayname",'Opt Sig after Amp','fignum',1223323); + % Pc = abs(mean(Opt_sig.signal)).^2; % carrier power + % Ptot = mean(abs(Opt_sig.signal).^2); % total power + % Ps = max(Ptot - Pc, eps); + % Pcdb = 10*log10(Pc); + % Psdb = 10*log10(Ps); + % + % cspr_dB = 10*log10(Pc / Ps); + % + % % Minimal in-place CSPR set (real, nonnegative field constraint) + % E = Opt_sig.signal; % real field samples + % target_cspr_dB = 20; % <-- set your target CSPR (dB) + % + % % Decompose into DC + zero-mean waveform + % m = mean(E); + % x0 = E - m; % zero-mean modulation + % Ps0 = mean(x0.^2); % sideband power (fixed if shape kept) + % + % % Current CSPR (for reference) + % Pc_cur = m^2; + % Ptot_cur = mean(E.^2); + % Ps_cur = max(Ptot_cur - Pc_cur, eps); + % cspr_in = 10*log10(Pc_cur / Ps_cur); + % + % % Bias needed for target CSPR, and minimal bias to keep E>=0 + % R_tgt = 10^(target_cspr_dB/10); % Pc/Ps + % a_req = sqrt(R_tgt * Ps0); % required DC bias + % a_min = -min(x0); % to avoid negatives everywhere + % a = max(a_req, a_min); % if infeasible, lands at CSPR_min + % + % % Apply bias (preserves waveform shape) + % E_new = a + x0; + % + % % Achieved CSPR + % Pc_new = mean(E_new)^2; + % Ptot_new = mean(E_new.^2); + % Ps_new = max(Ptot_new - Pc_new, eps); + % cspr_out = 10*log10(Pc_new / Ps_new); + % + % % (Optional) show feasibility info + % cspr_min = 10*log10((a_min^2)/max(Ps0,eps)); + % disp(table(cspr_in, target_cspr_dB, cspr_min, cspr_out)); + % + % % Use E_new as your adjusted field + % Opt_sig.signal = E_new; + + %%%%%% PD Square Law %%%%%% + PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig); + + %%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% + rx_bwl = 70e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig); + + % %%%%%% Low-pass Scope %%%%%% + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + + %%%%%% Scope %%%%%% + Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r)); + % Scpe_sig_resampled.signal = Scpe_sig_resampled.signal(1:2*length(Symbols)); + + [~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0); + Rx_sig = Scpe_cell{1}; + Rx_sig = Rx_sig.normalize("mode","rms"); + + if 1 + %Duobinary Targeting + + eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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); + + db_ref_sequence = Duobinary().encode(Symbols); + db_ref_constellation = unique(db_ref_sequence.signal); + [eq_signal, eq_noise] = eq_.process(Rx_sig,db_ref_sequence); + + viterbi = 0; + if viterbi + mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + mlse_.DIR = [1,1]; + [eq_signal_whitened] = mlse_.process(eq_signal); + else + mlse_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling); + mlse_.DIR = [1,1]; + [eq_signal_whitened,LLR,gmi_mlse_db(r)] = mlse_.process(eq_signal,Symbols); + end + + mlse_sig_hd = PAMmapper(M,0,"eth_style",0).quantize(eq_signal_whitened); + 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(Symbols); + tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded); + + tx_bits_precoded = PAMmapper(M,0,"eth_style",0).demap(tx_symbols_precoded); + + rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd_precoded); + [~,errors_db_diff_precoded,ber_db_diff_precoded(r),a] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + burst_db_pre(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal); + + %B) Just determine BER + rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd); + [bits_mlse,errors_db,ber_db(r),a] = calc_ber(rx_bits_mlse.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + burst_db(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal); + + fprintf('BER ber_db_diff_precoded: %.2e \n',ber_db_diff_precoded(r)); + fprintf('BER Vber_dbNLE: %.2e \n',ber_db(r)); + % figure();hold on;stem(1:15,burst_db(r,:),'LineWidth',1,'Color',cols(1,:));stem(1:15,burst_db_pre(r,:),'LineWidth',1,'Color',cols(2,:));set(gca, 'yscale', 'log'); + + end + + + % FFE or VNLE + eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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.00,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); + % eq = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0,"order",[50,2,2],"sps",2,"decide",0); + + [eq_signal_fullresp, eq_noise] = eq_.process(Rx_sig, Symbols); + showEQNoisePSD(eq_noise, "fignum",1273876,"displayname",'noise after EQ'); + [mi_gomez(r)] = calc_air(eq_signal_fullresp, Symbols, "skip_front", 100, "skip_end", 100); + [gmi_vnle_bitwise(r)] = calc_ngmi(eq_signal_fullresp,Symbols); + [gmi_bitwise_2(r)] = calc_gmi_bitwise(eq_signal_fullresp,Symbols); + snr_vnle(r) = calc_snr(Symbols, eq_signal_fullresp-Symbols); + + % eq_signal_fullresp.plot("displayname",'bla','fignum',199); + % eq_signal_fullresp.eye(fsym(r),M,"fignum",103837); + + % Hard decision on VNLE output + eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_fullresp); + rx_bits = PAMmapper(M,0,"eth_style",0).demap(eq_signal_hd); + [~,tot_err,ber_vnle(r),a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + burst_vnle(r,:) = count_error_bursts(a, 10)./tot_err; + + % showLevelConfusionMatrix(eq_signal_hd,Symbols,"M",M,"fignum",200,"displayname",'bla'); + % showLevelScatter(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','f_sym',fsym(r),'fignum',201); + % show2Dconstellation(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','fignum',2241); + + fprintf('BER VNLE: %.2e \n',ber_vnle(r)); + fprintf('NGMI VNLE: %.2f \n',gmi_vnle_bitwise(r)./m); + + if 1 + + % Process through postfilter and MLSE + pf_ncoeffs = 1; + if fsym(r) < 200e9 + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.1]); + else + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.85]); + end + + % showEQNoisePSD(eq_noise,"postfilter_taps",pf_.coefficients,"displayname",'Postfilter Burg based'); + alpha(r) = pf_.coefficients(2); + alpha_vec = max(0,round(alpha(r),2)-0.2):0.025:round(alpha(r),2)+0.4; + alpha_vec = unique(sort([alpha_vec, 1, alpha(r)])); + + gmi_mlse_ = zeros(size(alpha_vec)); + ber_mlse_ = zeros(size(alpha_vec)); + parfor a=1:numel(alpha_vec) + + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)]); + + pf_ = Postfilter("ncoeff",1,"useBurg",0,"coefficients",[1,alpha_vec(a)]); + [eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise); + + [signalclass_hd,LLR,gmi_mlse_(a)] = mlse_.process(eq_signal_whitened,Symbols); + + mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd); + + rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd); + + [~,tot_err,ber_mlse_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + % burst_mlse(r,:) = count_error_bursts(errpos, 10); + + % if 0 + % fprintf('BER MLSE: %.2e \n',ber_mlse(r)); + % fprintf('NGMI MLSE: %.5f \n',gmi_mlse(r)./m); + % + % showLevelConfusionMatrix(mlse_sig_hd,Symbols,"M",M,"fignum",300,"displayname",'bla'); + % + % levels = sort(unique(Symbols.signal(:)).'); % 1×6 + % pairs = reshape(mlse_sig_hd.signal,2,[]).'; + % isedge = ismember(pairs, [levels(1) levels(end)]); + % isforbidden = sum(isedge,2)==2; + % fprintf('Found %d forbidden transitions (even→odd edges).\n', nnz(isforbidden)); + % + % + % % Process through postfilter and MLSE + % pf_ncoeffs = 1; + % pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + % mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + % [eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise); + % mlse_.DIR = pf_.coefficients; + % mlse_output = mlse_.process(eq_signal_whitened); + % mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(mlse_output); + % rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd); + % [~,~,ber_viterbi(r),~] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + % fprintf('Viterbi BER: %.2e \n',ber_viterbi(r)); + % end + end + + [ber_mlse(r),idx] = min(ber_mlse_); + gmi_mlse(r) = gmi_mlse_(idx); + ber_mlse_burg(r) = ber_mlse_(alpha_vec==alpha(r)); + best_alpha(r) = alpha_vec(idx); + + end + + % IR target in EQ + if 1 + + % alpha_vec = max(0,round(alpha(r),2)-0.1):0.01:min(1,round(alpha(r),2)+0.1); + plot_stuff = 0; + gmi_mlse_pr_tgt_ = zeros(size(alpha_vec)); + ber_mlse_pr_tgt_ = zeros(size(alpha_vec)); + for a = 1:numel(alpha_vec) + + alpha_vec(a) = 0.9; + eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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.00,"FFEmu",0,"plotfinal",0,"ideal_dfe",0); + Symbols_filt = Symbols.filter([1,alpha_vec(a)],1); + [eq_signal_prtgt, eq_noise] = eq_.process(Rx_sig, Symbols_filt); + + showLevelHistogram(eq_signal_prtgt,Symbols_filt,"displayname",'VNLE Out','fignum',201); + + if plot_stuff + % Plot the response for respective EQ targets + Symbols_filt.spectrum("displayname",'IDEAL Filtered Reference','fignum',240587); + eq_signal_whitened.spectrum("displayname",'Full tgt. EQ + PF','fignum',240587); + eq_signal_prtgt.spectrum("displayname",'Partial Resp. Target EQ','fignum',240587); + + noise_pf_out = Symbols_filt-eq_signal_whitened; + noise_pr_tgt = Symbols_filt-eq_signal_prtgt; + + noise_pf_out.spectrum("displayname",'Ideal PR - Whitening Out','fignum',240588); + noise_pr_tgt.spectrum("displayname",'Ideal PR - PR Target Out','fignum',240588); + end + + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)],'debug',0); + + [signalclass_hd,LLR,gmi_mlse_pr_tgt_(a)] = mlse_.process(eq_signal_prtgt,Symbols); + + mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd); + + rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd); + + [~,tot_err,ber_mlse_pr_tgt_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + + % burst_mlse_(a,:) = count_error_bursts(errpos, 10); + + % fprintf('BER MLSE: %.2e \n',ber_mlse_pr_tgt_(a)); + % fprintf('NGMI MLSE: %.5f \n',gmi_mlse_pr_tgt_(a)./m); + + end + + [ber_mlse_pr_tgt(r),idx] = min(ber_mlse_pr_tgt_); + gmi_mlse_pr_tgt(r) = gmi_mlse_pr_tgt_(idx); + best_alpha_pr_tgt(r) = alpha_vec(idx); + + end + + + cols = cbrewer2('paired',8); + figure(); hold on + title(sprintf('%d GBd',fsym(r).*1e-9)); + scatter(alpha_vec,ber_mlse_,15,'Marker','o','LineWidth',1,'DisplayName','MLSE','MarkerEdgeColor',cols(1,:)); + scatter(best_alpha(r),ber_mlse(r),15,'Marker','o','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:)); + scatter(alpha(r),ber_mlse_burg(r),25,'Marker','+','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:)); + + scatter(1,ber_db_diff_precoded(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:)); + scatter(1,ber_db(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:)); + + scatter(alpha_vec,ber_mlse_pr_tgt_,15,'Marker','x','LineWidth',1,'DisplayName','MLSE Partial Resp tgt','MarkerEdgeColor',cols(5,:)); + scatter(best_alpha_pr_tgt(r),ber_mlse_pr_tgt(r),25,'Marker','x','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(6,:)); + + set(gca,"YScale","log"); + % ylim([1e-6 0.5]); + % xlim([0.1 1]); + drawnow; + + + +end + + + + +% --- style control (one variable controls both marker size and linewidth) --- +STYLE_BASE = 2; % adjust this single number to scale markers & lines +MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize) +LINE_WIDTH = max(1.5, STYLE_BASE/3); % line width (keeps lines reasonable when STYLE_BASE large) + +% --- color map / method -> color assignment (keeps colors consistent) --- +cols = cbrewer2('Paired',8); +cols = linspecer(6); +d = 0; +cm.VNLE = cols(1 + d, :); +cm.MLSE = cols(2 + d, :); +cm.DB_precode = cols(3 + d, :); +cm.DB = cols(4 + d, :); % duobinary + +% prepare x values in GBd +xGHz = fsym .* 1e-9; +xticks_vals = xGHz; +xtick_labels = arrayfun(@(v) sprintf('%d', round(v)), xticks_vals, 'UniformOutput', false); + +% common marker settings (filled, same face+edge color) +mk.VNLE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.VNLE,'MarkerSize',MARKER_SIZE}; +mk.MLSE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE}; +mk.DB_precode = {'Marker','none','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE}; +mk.DB = {'Marker','none','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE}; + +% ---------------- FIGURE 11 : alpha (VNLE) ---------------- +figure(110+M); clf; hold on; +plot(xGHz, alpha, ... + 'DisplayName','VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +xlabel('Baudrate in GBd'); +ylabel('alpha'); +set(gca, 'XTick', xticks_vals, 'XTickLabel', xtick_labels); +grid on; +legend('Location','best'); + +% ---------------- FIGURE 15 : GMI ---------------- +figure(111+M); clf; hold on; +plot(xGHz, mi_gomez, ... + 'DisplayName','MI VNLE', ... + mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, gmi_vnle_bitwise, ... + 'DisplayName','GMI VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +% duobinary has only one GMI curve (DB output) +plot(xGHz, gmi_mlse_db, ... + 'DisplayName','GMI DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); +% MLSE symbol-wise (if present) +plot(xGHz, gmi_mlse, ... + 'DisplayName','GMI MLSE', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + +ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('GMI'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +% xlim([184, 256]) + +% ---------------- FIGURE 13 : BER ---------------- +figure(312+M); hold on; +plot(xGHz, ber_vnle, ... + 'DisplayName','VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, ber_mlse, ... + 'DisplayName','MLSE', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, ber_viterbi, ... + 'DisplayName','Viterbi', ... + mk.MLSE{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + +yline(4.85e-3,'LineWidth',1,'HandleVisibility','off'); +yline(2.2e-4,'LineWidth',1,'HandleVisibility','off'); + +plot(xGHz, ber_db, ... + 'DisplayName','DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); + +plot(xGHz, ber_db_diff_precoded, ... + 'DisplayName','Prec. + DB tgt.', ... + mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB); + +xlabel('Baudrate in GBd'); +ylabel('BER'); +set(gca, 'yscale', 'log'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +% xlim([184, 256]) + +% ---------------- FIGURE 15 : Information Rates ---------------- +tp = TransmissionPerformance; + + +m = floor(log2(M)*10)/10; +figure(113+M); clf; hold on; + +netrates_vnle = tp.calculateNetRate(fsym.* m, ... + 'NGMI', gmi_vnle_bitwise./m, ... + 'BER', ber_vnle); +% +plot(xGHz, gmi_vnle_bitwise.*xGHz, ... + 'DisplayName','GMI*R VNLE', ... + mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE); + +plot(xGHz, netrates_vnle.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD VNLE', ... + mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE); +plot(xGHz, netrates_vnle.HD.NetRate.*1e-9, ... + 'DisplayName','Staircase VNLE', ... + mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE); + + +% +% MLSE symbol-wise (if present) +plot(xGHz, gmi_mlse.*xGHz, ... + 'DisplayName','GMI*R MLSE', ... + mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + +netrates_mlse = tp.calculateNetRate(fsym.* m, ... + 'NGMI', gmi_mlse./m, ... + 'BER', ber_mlse); +plot(xGHz, netrates_mlse.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD MLSE', ... + mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE); +plot(xGHz, netrates_mlse.HD.NetRate.*1e-9, ... + 'DisplayName','Staircase MLSE', ... + mk.MLSE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.MLSE); + + +% duobinary has only one GMI curve (DB output) +plot(xGHz, gmi_mlse_db.*xGHz, ... + 'DisplayName','GMI*R DB tgt.', ... + mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB); + +netrates_db = tp.calculateNetRate(fsym.* m, ... + 'NGMI', gmi_mlse_db./m, ... + 'BER', ber_db); + +plot(xGHz, netrates_db.SDHD.NetRate.*1e-9, ... + 'DisplayName','SD+HD DB', ... + mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB); +plot(xGHz, netrates_db.HD.NetRate.*1e-9, ... + 'DisplayName','Staircase DB', ... + mk.DB{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.DB); + + + +% ylim([log2(M)-1, log2(M)]); +xlabel('Baudrate in GBd'); +ylabel('AIR in Gbps'); +set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end)); +grid on; +legend('Location','best'); +xlim([184, 256]) + +% Auxiliary nested helper for numerically stable log-sum-exp +function s = logsumexp(a) +% LOGSUMEXP Compute log(sum(exp(a))) in a numerically stable way +m = max(a); +s = m + log(sum(exp(a - m))); +end diff --git a/projects/IMDD_base_system/simulation_bwl_2.m b/projects/IMDD_base_system/simulation_bwl_2.m new file mode 100644 index 0000000..dec8036 --- /dev/null +++ b/projects/IMDD_base_system/simulation_bwl_2.m @@ -0,0 +1,192 @@ +%%% Run parameters +% TX +M = 4; +fsym = 180e9; + +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +random_key = 1; + +db_precode = 0; +db_encode = 0; + +rcalpha = 0.05; +kover = 16; +vbias_rel = 0.5; +u_pi = 2.9; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1310; +laser_linewidth = 0; +tx_bw_nyquist = 1; + +% Channel +link_length = 1; + +% RX +rop = 0; +rx_bw_nyquist = 0.99; + +vnle_order1 = 50; +vnle_order2 = 3; +vnle_order3 = 3; + +vnle_order=[vnle_order1,vnle_order2,vnle_order3]; +dfe_order = [0 0 0]; + +pf_ncoeffs = 1; + +alpha = 0; + +len_tr = 4096*2; + +mu_ffe1 = 0.0001; +mu_ffe2 = 0.0008; +mu_ffe3 = 0.001; +mu_dc = 0.005; +% mu_dc = 0; + +mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3]; +mu_dfe = 0.0004; + + +dfe_ = sum(dfe_order)>0; + +doub_mode = db_mode.no_db; + + +Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha); + +db_precode = 0; +db_encode = 0; +duob_mode = db_mode.db_precoded; +apply_pulsef = 1; +[Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym,"M",M,"order",18,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + +Digi_sig.spectrum("displayname",'Digi Spectrum','fignum',10,'normalizeTo0dB',1); + + +%% proof of concept +Symbols_db = Duobinary().encode(Symbols); +mim_decoded = Duobinary().decode(Symbols_db,"M",M); +rx_bits_mim_decoded = PAMmapper(M,0,"eth_style",0).demap(mim_decoded); +rx_bits_mim_decoded_.signal = circshift(rx_bits_mim_decoded.signal,0); +[~,~,ber_mim_decode,~] = calc_ber(rx_bits_mim_decoded_.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); +fprintf('BER mim: %.2e \n',ber_mim_decode); + +%% + + +%%%%% AWG +% El_sig = M8199A("kover",kover).process(Digi_sig); +El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",1).process(Digi_sig); +% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',0); +% El_sig = El_sig.setPower(0,"dBm"); + +%%%%% Low-pass el. components %%%%%% +f_nyquist = fsym/2; +tx_bwl = tx_bw_nyquist.*f_nyquist; +% tx_bwl = 80e9; +El_sig = Filter('filtdegree',4,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig); +% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',1); + +%%%%% Electrical Driver Amplifier %%%%%% +El_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",3).process(El_sig); +El_sig = El_sig.normalize("mode","oneone"); + +%%%%% MODULATE E/O CONVERSION %%%%%% +[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig); + +Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + +%%%%%% ROP %%%%%% +Rx_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig); + +%%%%%% PD Square Law %%%%%% +Rx_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11).process(Rx_sig); + +%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% +rx_bwl = rx_bw_nyquist.*f_nyquist; +% rx_bwl = 80e9; +Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(Rx_sig); + +% %%%%%% Low-pass Scope %%%%%% +Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + +% Rx_sig.spectrum("displayname",'Analog Rx Spectrum','fignum',100,'normalizeTo0dB',1); + +%%%%%% Scope %%%%%% +Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(Rx_sig); + +Scpe_sig_resampled = Scpe_sig.resample("fs_out",2*fsym); + +[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1); + +eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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); +pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); +% mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); +mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); + +if duob_mode == db_mode.no_db + + % FFE or VNLE + [eq_signal_sd, eq_noise] = eq_.process(Scpe_cell{1}, Symbols); + + % Hard decision on VNLE output + eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd); + + % Process through postfilter and MLSE + [mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise); + mlse_.DIR = pf_.coefficients; + mlse_sig_sd = mlse_.process(mlse_sig_sd,Symbols); + + + % BER + rx_bits = PAMmapper(M,0,"eth_style",0).demap(eq_signal_hd); + [~,tot_err,ber_vnle,a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + + rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_sd); + [~,tot_err,ber_mlse,a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + +elseif duob_mode == db_mode.db_precoded + + %% + [EQ_sig, Noi] = eq_.process(Scpe_cell{1},Duobinary().encode(Symbols)); + + showLevelHistogram(EQ_sig,Duobinary().encode(Symbols),"displayname",101); + + mim_decoded = Duobinary().decode(EQ_sig,"M",M); + + showLevelHistogram(mim_decoded,Symbols,"displayname",101); + + rx_bits_mim_decoded = PAMmapper(M,0,"eth_style",0).demap(mim_decoded); + + rx_bits_mim_decoded_.signal = circshift(rx_bits_mim_decoded.signal,0); + + [~,~,ber_mim_decode,~] = calc_ber(rx_bits_mim_decoded_.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + + fprintf('BER mim: %.2e \n',ber_mim_decode); + + %% + mlse_sig_hd = MLSE("DIR",[1,1],"duobinary_output",1,"M",M,"trellis_states",PAMmapper(M,0).levels).process(EQ_sig,Symbols); + + 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",0).demap(mlse_sig_hd_decoded); + + [~,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); + + %% + +end \ No newline at end of file diff --git a/projects/ML_based_MLSE/analyze_filter_length.m b/projects/ML_based_MLSE/analyze_filter_length.m new file mode 100644 index 0000000..616b715 --- /dev/null +++ b/projects/ML_based_MLSE/analyze_filter_length.m @@ -0,0 +1,164 @@ +%% analyze_filter_length.m +clear; clc; + +M = 4; +randkey = 1; + +% --- Parameter sweep +order_range = 2:3:11; % FFE order +delta_range = 0:2:4; % delta +SNR_dB = 20; + +% --- Prepare bit sequence +order_bits = 19; +s = RandStream('twister','Seed',randkey); +for i = 1:log2(M) + N = 2^(order_bits-1); + bitpattern(:,i) = randi(s,[0 1], N, 1); +end +Bits = Informationsignal(bitpattern); +Symbols = PAMmapper(M,0).map(Bits); +Symbols.fs = 200e9; + +% --- Channel (minimal ISI + AWGN) +h = [0.3 0.9 0.3]; h = h/norm(h); +symbols_filt = Symbols.filter(h,1); +symbols_noi = symbols_filt; +symbols_noi.signal = awgn(symbols_filt.signal,SNR_dB,'measured'); + +% --- Generate all parameter pairs +[O,D] = ndgrid(order_range, delta_range); +pairs = [O(:), D(:)]; + +training_len = 100; + +ber_vec = nan(size(pairs,1),1); % initialize with NaN +ber_training = nan(size(pairs,1),training_len); +ce_vec = nan(size(pairs,1),1); +ce_training = nan(size(pairs,1),training_len); + +% --- Parallel loop over parameter pairs +parfor k = 1:size(pairs,1) + order_k = pairs(k,1); + delta_k = pairs(k,2); + + % Skip invalid combinations (delay cannot exceed filter length) + if abs(delta_k) >= order_k + fprintf('Skip: order=%d, delta=%d (invalid)\n', order_k, delta_k); + continue; + end + + try + ml = ML_MLSE("epochs_tr",training_len,"epochs_dd",1,"len_tr",2^15, ... + "mu_dd",0.1,"mu_tr",0.1,"order",order_k,"sps",1, ... + "traceback_depth",128,"L",3,"delta",delta_k,"adaptive_mu",0); + + [y_ml,y_ref] = ml.process(symbols_noi,Symbols); + ref_bits = PAMmapper(M,0).demap(y_ref); + eq_bits = PAMmapper(M,0).demap(y_ml); + + ber_training(k,:) = ml.ber; + ce_training(k,:) = ml.ce; + + [~,~,ber_vec(k)] = calc_ber(eq_bits.signal, ref_bits.signal, ... + "skip_front",10,"skip_end",10); + L = min(length(ml.ce),30); + ce_vec(k) = mean(ml.ce(end-L+1:end)); + + fprintf('order=%d, delta=%d → BER=%.2e, CE=%.3f\n', ... + order_k, delta_k, ber_vec(k), ce_vec(k)); + catch ME + fprintf('Error at order=%d, delta=%d: %s\n', ... + order_k, delta_k, ME.message); + ber_vec(k) = NaN; + ce_vec(k) = NaN; + end +end + +% --- reshape to 2D matrices +ber_mat = reshape(ber_vec, numel(order_range), numel(delta_range)); +ce_mat = reshape(ce_vec, numel(order_range), numel(delta_range)); + + +%% --- Plot BER +figure; hold on +cols = cbrewer2('Set1',10); +for i = 1:numel(delta_range) + plot(order_range,ber_mat(:,i),'DisplayName',sprintf('delta: %d',delta_range(i)),'Color',cols(i,:)) +end +beautifyBERplot +ylabel('BER'); xlabel('Filter Order [N]'); +title('BER vs. Filter order'); +ylim([1e-4, 0.1]); +yline(3.8e-3,'HandleVisibility','off'); +yline(2.2e-4,'HandleVisibility','off'); + +%% --- Plot Cross-Entropy +figure; hold on +for i = 1:numel(delta_range) + plot(order_range,ce_mat(:,i),'DisplayName',sprintf('delta: %d',delta_range(i))) +end +% beautifyBERplot +ylabel('BER'); xlabel('Filter Order [N]'); +title('BER vs. Filter order'); + +%% --- Training Curves: BER and CE per combination +figure('Name','Training Convergence'); hold on +cols = cbrewer2('Set1', 10); % one color per delta + +[O, D] = ndgrid(order_range, delta_range); + +for i = 1:size(ber_training,1) + ord = O(i); + del = D(i); + + if ord <= del + continue; + end + % --- show only order 2 and 10 + if ord == 2 + lnst = '-'; + elseif ord == 5 + lnst = ':'; + elseif ord == 8 + lnst = '--'; + elseif ord == 11 + lnst = '-.'; + end + + + b = ber_training(i,:); + + + plot_label = sprintf('order=%d, delta=%d', ord, del); + plot(1:length(b), b, 'Color', cols(del+1, :), ... + 'DisplayName', plot_label,'LineStyle',lnst); +end + +set(gca,'YScale','log'); +xlabel('Epoch'); +ylabel('BER'); +title('Training Convergence (BER)'); +legend('show'); +grid on; + + +%% --- Cross-Entropy curves +figure('Name','Cross-Entropy'); hold on +cols = cbrewer2('Set1',size(ce_training,1)); +for i = 1:size(ce_training,1) + + [O, D] = ndgrid(order_range, delta_range); + plot_label = sprintf('order=%d, delta=%d', O(i), D(i)); + c = ce_training(i,:); + c(~isfinite(c) | c==0) = NaN; + if all(isnan(c)), continue; end + plot(1:length(c), c, 'Color', cols(D(i)+1,:), ... + 'DisplayName', plot_label); +end +set(gca,'YScale','log'); +xlabel('Epoch'); +ylabel('Cross-Entropy'); +title('Training Convergence (CE)'); +legend('show'); +grid on; diff --git a/projects/ML_based_MLSE/analyze_mu.m b/projects/ML_based_MLSE/analyze_mu.m new file mode 100644 index 0000000..044b71a --- /dev/null +++ b/projects/ML_based_MLSE/analyze_mu.m @@ -0,0 +1,113 @@ + +M = 4; +order = 19; +randkey = 1; + +bitpattern = []; +s = RandStream('twister','Seed',randkey); +for i = 1:log2(M) + N = 2^(order-1); %length of prbs + bitpattern(:,i) = randi(s,[0 1], N, 1); +end + +if M == 6 + bitpattern = reshape(bitpattern',[],1); + bitpattern = bitpattern(1:end-mod(length(bitpattern),5)); +end + +Bits = Informationsignal(bitpattern); + +Symbols = PAMmapper(M,0).map(Bits); +Symbols.fs = 200e9; + +Bits_ = PAMmapper(M, 0, "eth_style", 0).demap(Symbols); + +% --- Channel: minimal ISI response + AWGN --- +h = [0.3 0.9 0.3]; % impulse response (normalized later if desired) +h = h / norm(h); % optional normalization for unit energy + +symbols_filt = Symbols.filter(h,1); + + +%% SHOW Loss during training + +mu = logspace(-3,-0.8,12); +ber_ml_mlse = zeros(size(mu)); +ber_training = []; +ce_training = []; + +parfor i = 1:numel(mu) + + symbols_noi = symbols_filt; + SNR_dB = 20; + symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB, 'measured'); % AWGN with given SNR + + ml_mlse_equalizer = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",2^15,... + "mu_dd",mu(i),"mu_tr",mu(i),"order",5,"sps",1,... + "traceback_depth",128,"L",3,"delta",0,'adaptive_mu',0); + + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(symbols_noi,Symbols); + ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber_ml_mlse(i), errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse(i)); + ber_training(i,:) = ml_mlse_equalizer.ber; + ce_training(i,:) = ml_mlse_equalizer.ce; +end + +%% +symbols_noi = symbols_filt; +SNR_dB = 20; +symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB, 'measured'); % AWGN with given SNR +ml_mlse_equalizer_adap = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",2^16,... + "mu_dd",1,"mu_tr",1,"order",5,"sps",1,... + "traceback_depth",128,"L",3,"delta",0,"adaptive_mu",1); + +[y_ml_mlse,y_ref] = ml_mlse_equalizer_adap.process(symbols_noi,Symbols); +ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref); +ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); +[~, errors, ber_ml_mlse_, errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); +fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_); + +%% +figure();hold on + +plot(mu,ber_ml_mlse,'DisplayName','ML-based MLSE'); + +beautifyBERplot; +xlim([mu(1), mu(end)]); +xlabel('mu'); +ylabel('BER'); +title('PAM-4; M=3; AWGN Channel'); +ylim([1e-5 0.1]); + +%% +figure() +hold on; +cols = cbrewer2('Spectral',12); +for i = 1:12 + plot(1:200,ber_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:)); +end +set(gca,'YScale','log'); +xlabel('Epoch'); +ylabel('BER'); +title('PAM-4; L=3; SNR=20; AWGN Channel'); +plot(1:200,ml_mlse_equalizer_adap.ber,'DisplayName','Adaptive mu'); + +%% +figure() +hold on; +cols = cbrewer2('Spectral',12); +for i = 1:12 + plot(1:200,ce_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:)); +end +set(gca,'YScale','log'); +xlabel('Epoch'); +ylabel('Cross-Entropy'); +title('PAM-4; L=3; SNR=20; AWGN Channel'); +plot(1:200,ml_mlse_equalizer_adap.ce,'DisplayName','Adaptive mu'); + + +%% SUPER LONG EPOCHS + + diff --git a/projects/ML_based_MLSE/experimental_data.m b/projects/ML_based_MLSE/experimental_data.m new file mode 100644 index 0000000..a594a92 --- /dev/null +++ b/projects/ML_based_MLSE/experimental_data.m @@ -0,0 +1,163 @@ + + +dsp_options.storage_path = 'Z:\2024\sioe_labor\'; +dsp_options.max_occurences = 1; +database = DBHandler("dataBase", 'labor_highspeed', "type", 'mysql' ); +run_id = 2776; +dataTable = queryRunid(run_id, database); +fsym = dataTable.symbolrate; +M = double(dataTable.pam_level); +duob_mode = db_mode(strrep(dataTable.db_mode,'"','')); + +% if database.checkIfRunExists('Results','run_id',run_id) +% disp(['Already got at least one reulst for run id: ',num2str(run_id),' ']) +% return +% end + +% Load and Sync signal data from DB +[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, dsp_options); + +% Preprocess signal +Scpe_sig = preprocessSignal(Scpe_cell{1}, Symbols, fsym); + +Scpe_sig.spectrum("fignum",1,"displayname",'Rx') + +%% + +ffe_order = [50, 5, 5]; +mu_ffe = [0.0001, 0.0008, 0.001]; +mu_dfe = 0.0004; +eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^14,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); +mlse_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',3); + + +%Duobinary Targeting +db_ref_sequence = Duobinary().encode(Symbols); +db_ref_constellation = unique(db_ref_sequence.signal); +[eq_signal, eq_noise] = eq_.process(Scpe_sig,db_ref_sequence); + +%% +if 1 + [mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,Symbols); +else + % Ml MLSE + ml_mlse_equalizer = ML_MLSE("epochs_tr",20,"epochs_dd",1,"len_tr",length(eq_signal),... + "mu_dd",0.01,"mu_tr",0.01,"order",11,"sps",2,... + "traceback_depth",128,"L",1,"delta",4,'adaptive_mu',0); + [mlse_sig_sd,ref_sig] = ml_mlse_equalizer.process(Scpe_sig,db_ref_sequence); +end + +%% +mlse_sig_sd_decoded = Duobinary().decode(mlse_sig_sd,"M",M); +ref_sig_decoded = Duobinary().decode(db_ref_sequence,"M",M); + +mlse_sig_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_sd_decoded); +ref_sig_bits = PAMmapper(M,0,"eth_style",0).demap(ref_sig_decoded); + +err = sum(ref_sig_decoded.signal ~= mlse_sig_sd_decoded.signal); + +[bits_db,errors_db,ber_db,a] = calc_ber(mlse_sig_bits.signal,ref_sig_bits.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); + +%% +switch duob_mode + + case db_mode.no_db + % TX Data is not precoded: + + % A) Emulate diff precoding + 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(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_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 + rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd); + [bits_mlse,errors_mlse,ber_db,~] = 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! + 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",0).demap(mlse_sig_hd_decoded); + [~,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",0).demap(Symbols); + rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd); + [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 + + + + + + +%% SHOW Loss during training + +mu = logspace(-3,-0.8,12); +ber_ml_mlse = zeros(size(mu)); +ber_training = []; +ce_training = []; + +parfor i = 1:numel(mu) + + + + ml_mlse_equalizer = ML_MLSE("epochs_tr",200,"epochs_dd",1,"len_tr",length(Scpe_sig),... + "mu_dd",mu(i),"mu_tr",mu(i),"order",11,"sps",2,... + "traceback_depth",128,"L",2,"delta",4,'adaptive_mu',0); + + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Scpe_sig,Symbols); + ref_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ref); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber_ml_mlse(i), errpos] = calc_ber(ml_mlse_bits.signal, ref_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse(i)); + + ber_training(i,:) = ml_mlse_equalizer.ber; + ce_training(i,:) = ml_mlse_equalizer.ce; +end + +%% +figure();hold on + +plot(mu,ber_ml_mlse,'DisplayName','ML-based MLSE'); + +beautifyBERplot; +xlim([mu(1), mu(end)]); +xlabel('mu'); +ylabel('BER'); +title('PAM-4; M=3; AWGN Channel'); +ylim([1e-5 0.1]); + + +%% +figure() +hold on; +cols = cbrewer2('Spectral',12); +for i = 1:12 + plot(1:200,ber_training(i,:),'DisplayName', sprintf('mu=%.3f ',mu(i)),'Color',cols(i,:)); +end +set(gca,'YScale','log'); +xlabel('Epoch'); +ylabel('BER'); +title('PAM-4; L=3; SNR=20; AWGN Channel'); +plot(1:200,ml_mlse_equalizer_adap.ber,'DisplayName','Adaptive mu'); \ No newline at end of file diff --git a/projects/ML_based_MLSE/interp_fec_cross.m b/projects/ML_based_MLSE/interp_fec_cross.m new file mode 100644 index 0000000..18eac3f --- /dev/null +++ b/projects/ML_based_MLSE/interp_fec_cross.m @@ -0,0 +1,13 @@ +function rop_fec = interp_fec_cross(rops, ber, fec_thr) + if all(~isfinite(ber)) + rop_fec = NaN; return; + end + idx = find(ber < fec_thr, 1, 'first'); + if isempty(idx) || idx == 1 + rop_fec = NaN; return; % no crossing + end + % linear interpolation between the two nearest points + x1 = rops(idx-1); x2 = rops(idx); + y1 = ber(idx-1); y2 = ber(idx); + rop_fec = interp1([y1 y2], [x1 x2], fec_thr, 'linear', NaN); +end diff --git a/projects/ML_based_MLSE/minimal_example_huawei.zip b/projects/ML_based_MLSE/minimal_example_huawei.zip new file mode 100644 index 0000000..cab08ae Binary files /dev/null and b/projects/ML_based_MLSE/minimal_example_huawei.zip differ diff --git a/projects/ML_based_MLSE/minimal_example_huawei/bcjr_pam.m b/projects/ML_based_MLSE/minimal_example_huawei/bcjr_pam.m new file mode 100644 index 0000000..674b55f --- /dev/null +++ b/projects/ML_based_MLSE/minimal_example_huawei/bcjr_pam.m @@ -0,0 +1,555 @@ +classdef bcjr_pam < handle + %MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length + + properties(Access=public) + M %PAM-M + DIR + trellis_states + duobinary_output + end + + methods (Access=public) + + function obj = bcjr_pam(options) + %NAME Construct an instance of this class + % Detailed explanation goes here + + arguments + options.M double = 4; + options.DIR double = [1]; + options.trellis_states double = [-3 -1 1 3]; + options.duobinary_output logical = false; + + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + end + + function [VITERBI_ESTIMATION_SYMBOLS,LLR_exact,GMI] = process(obj,data_in,data_ref,tx_bits,bit_mapping) + + + debug = 0; + + % States should match the target states of the prev. EQ (EQ's job was to reduce the error between signal and the target) + trellis_state_mode = 2; + % 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 = 1; % PAM-6 only (only if data is NOT precoded!) + + % Additional scaling between states, expected output (noiseless_received) and the noisy, filtered input signal + scale_mode = 2; % scale_mode: + % 0 = no scaling, + % 1 = use RMS to scale MODEL, + % 2 = use MMSE/time-corr to scale MODEL, -> This best to get the GMI right -> sometimes the LLP's are not centered around zero... + % 3 = use RMS to scale DATA, + % 4 = use MMSE/time-corr to scale DATA + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%% PREPARATIONS %%%%%%%% + + % remove unnecessary zeros at start of impulse response to keep + % number of trellis states minimal + DIR_nonzero = find(obj.DIR ~= 0); + if DIR_nonzero(1) > 1 + obj.DIR(1:DIR_nonzero(1)-1) = []; + end + + if isscalar(obj.DIR) + 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 + + % Trellis States + obj.trellis_states = reshape(obj.trellis_states,1,[]); + if trellis_state_mode == 1 % Normalize the Trellis states to =1 RMS + + obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states); + + 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),size(obj.trellis_states)); + + 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 + + + % 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 + 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 + % 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 + end + 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 states/ expected values + noise_free_received = g*noise_free_received + b; + last_sym = g*last_sym + b; + + % calculate noise power + sigma2 = mean(abs(data_in - (g*y_ideal + b)).^2); %noise = mean(abs((RX Signal - IDEAL Signal)))^2 + inv2s2 = 1/(2*sigma2); + + if debug + figure(100); clf; hold on + obj.showLevelScatter_(data_in, data_ref); + 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 (VITERBI -Alpha's) %%%%% + + % Initialize the output vector + 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 = 2:length(data_in) + + bm = -(data_in(n) - noise_free_received).^2 * inv2s2; + + % exclude edge to 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 + + pm = pm + bm; + [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 + viterbi_path = NaN(1,length(data_in)); + + % find ideal trellis path by going through the trellis backwards + [~,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]); + end + + VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path); + VITERBI_ESTIMATION_SYMBOLS = reshape(VITERBI_ESTIMATION_SYMBOLS,size(data_in)); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% BACKWARD (Beta's) %%%%% + + % Initialize the output vector + 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:1 + + bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2; + + % exclude edge to edge transitions for even->odd steps && PAM-6 + if mod(h+1, 2) == 0 && obj.M == 6 && trellis_exclusion + bm(edge_edge_mask) = -Inf; + end + + pm = pm + bm.'; + [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 + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% FORWARD (Combine Alpha and Beta to yield LLP's) %%%%% + + %calc the log probabilities (llp's) + + 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 + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% Calc LLR's %%%%% + + % These are interchangeable... + 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 = [-5 -3 -1 1 3 5]; + pam6transitions = combvec(states,states)'; % pam6transitions = + % [-5 -5; + % -3 -5; + % -1 -5; ... + + [~, idx_sym_1] = ismember(pam6transitions(:,1), states); + [~, idx_sym_2] = ismember(pam6transitions(:,2), states); + pam6ind = [idx_sym_1, idx_sym_2]; + + 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); + + % All 36 Combinations: M = LLP Symbol 1 + LLP Symbol 2 + Mij = LLP1(pam6ind(:,1)) + LLP2(pam6ind(:,2)); + pij = prob1(pam6ind(:,1)) .* prob2(pam6ind(:,2)); + + % for each of the 5 bits sum exact-probs or max-log + for b = 1:num_bits + idx_sym_1 = bit_mapping(:,b)==1; + idx_bit_1 = bit_mapping(:,b)==0; + + % exact LLR from probabilities + P1 = sum(pij(idx_sym_1)); %prob that bit == 1 + P0 = sum(pij(idx_bit_1)); + LLR_exact(k,b) = log(P1./P0); %ratio by multiplication + + % max-log: + LLR_maxlogmap(k,b) = max( Mij(idx_sym_1) ) - max( Mij(idx_bit_1) ); % ratio by subtraction + end + end + + % GMI calc includes the Tx-bitstream + tx_bits_pam6_reshaped = reshape(tx_bits',5,[])'; % N x 5 + 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; % GMI per single symbol not per two symbols + + 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).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 uses the single max LLP value + % instead of sum over all LLP's + 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); + 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); + + % 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); % assumes equally distributed ones and zeros + end + + GMI = sum(MI); % Total bitwise mutual information + + end + + + 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 + + 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 + + if obj.M == 6 + 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 ; edge -> edge).\n', nnz(isforbidden)); + end + + end + + + end + + function [symbols_for_lvl,avg_for_lvl] = showLevelScatter_(~,eq_signal,ref_symbols) + + figure() + + rx_symbols = eq_signal; %./ rms(eq_signal); + correct_symbols = ref_symbols; + + % col = cbrewer2('Paired',numel(unique(correct_symbols))*2); + col = ... + [0.6510 0.8078 0.8902; ... + 0.1216 0.4706 0.7059; ... + 0.6980 0.8745 0.5412; ... + 0.2000 0.6275 0.1725; ... + 0.9843 0.6039 0.6000; ... + 0.8902 0.1020 0.1098; ... + 0.9922 0.7490 0.4353; ... + 1.0000 0.4980 0; ... + 0.7922 0.6980 0.8392; ... + 0.4157 0.2392 0.6039; ... + 1.0000 1.0000 0.6000; ... + 0.6941 0.3490 0.1569; ... + 0.6510 0.8078 0.8902; ... + 0.1216 0.4706 0.7059; ... + 0.6980 0.8745 0.5412; ... + 0.2000 0.6275 0.1725]; + ccnt = -1; + + levels = unique(correct_symbols); + symbols_for_lvl = NaN(numel(levels),length(correct_symbols)); + start = 1; + ende = length(correct_symbols); + + for l = 1:numel(levels) + ccnt = ccnt+2; + + level_amplitude = levels(l); + + symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); + std_lvl(l) = std(symbols_for_lvl(l,:),'omitnan'); + xax = 1:length(correct_symbols); + + scatter(xax(start:ende),symbols_for_lvl(l,start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:)); + hold on; + + + 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); + + L = 500; + movmean = 1/L .* movsum(rx_symbols(correct_symbols==level_amplitude),[L/2,L/2], 'Endpoints', 'fill'); + + avg_for_lvl(l,correct_symbols==level_amplitude) = movmean; + + 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)); + + plot(xax(start:ende),avg_for_lvl(l,start:ende),'Color',col(ccnt,:)); + + hold on + end + + % yline(levels); + xlabel('Samples'); + ylabel('Amplitude'); + ylim([-3 3]); + + end + + + end +end diff --git a/projects/ML_based_MLSE/minimal_example_huawei/minimal_example.m b/projects/ML_based_MLSE/minimal_example_huawei/minimal_example.m new file mode 100644 index 0000000..504a1b2 --- /dev/null +++ b/projects/ML_based_MLSE/minimal_example_huawei/minimal_example.m @@ -0,0 +1,193 @@ + +if 0 + % A) RUN FULL LOOP + M_format = [2,4,6,8]; + snr = 10:25; +else + % B) RUN FOR DEBUG AND TEST + M_format = 4; + snr = 20; +end + +for m = 1:length(M_format) + % --- Parameters --- + M = M_format(m); % PAM order (e.g., 2,4,8) + Nsym = 1e5; % number of symbols + h = [1, 0.5, 0.2]; % Impulse response to remove + + b = log2(M); + if M == 6 b = 5; end + rng(1); + bits_tx = logical(randi([0 1], Nsym, b, 'uint8')); + + tx_symbols = pammap(bits_tx,M); + + if M == 6 + states = unique(tx_symbols); + pam6transitions = combvec(states',states')'; % pam6transitions = + bitmapping = pamdemap(reshape(pam6transitions',1,[])',M); + else + bitmapping = pamdemap(unique(tx_symbols),M); + end + + scaling = sqrt(sum(unique(tx_symbols).^2)/numel(unique(tx_symbols))); + tx_symbols = tx_symbols ./ scaling; + + % apply impulse response to signal + y_filt = filter(h, 1, tx_symbols); + + for s = 1:length(snr) + + % apply noise + y = awgn(y_filt,snr(s),"measured",1); + + % apply ml-MLSE + adaptive_mu = 0; + mu_lms = 0.15; + ml_mlse_equalizer = ml_mlse_pam("epochs_tr",50,"epochs_dd",1,"len_tr",length(y)/2,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,... + "L",2,"delta",4,"adaptive_mu",adaptive_mu); + + [ml_mlse_estimate,~] = ml_mlse_equalizer.process(y,tx_symbols); + rx_symbols = ml_mlse_estimate .* scaling; + bits_rx = pamdemap(rx_symbols,M); + + BER_ml(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx); + fprintf('BER = %.2e \n', BER_ml(m,s)); + + + % apply bcjr + BCJR = bcjr_pam("DIR",h,"duobinary_output",0,"M",M,"trellis_states",unique(tx_symbols)); + [viterbi_estimate,LLR,GMI(m,s)] = BCJR.process(y,tx_symbols,bits_tx,bitmapping); + + % decode LLR's + bits_LLR = LLR > 0; + + % demap viterbi symbols sequence + rx_symbols = viterbi_estimate .* scaling; + bits_rx = pamdemap(rx_symbols,M); + + % BER calc + BER_vit(m,s) = nnz(bits_tx ~= bits_LLR) / numel(bits_tx); + fprintf('BER LLR = %.2e \n', BER_vit(m,s)); + + BER_llr(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx); + fprintf('BER = %.2e \n', BER_llr(m,s)); + end +end +%% +figure();hold on +for m = 1:length(M_format) + p=plot(snr,BER_llr(m,:),'DisplayName',sprintf('Viterbi: PAM %d',M_format(m))); + plot(snr,BER_ml(m,:),'DisplayName',sprintf('ML-Based: PAM %d',M_format(m)),'LineStyle',':','Color',p.Color); +end +ylabel('BER'); +xlabel('SNR') +title('BER vs. SNR'); +set(gca, 'XScale', 'linear', ... + 'YScale', 'log', ... + 'TickLabelInterpreter', 'latex', ... + 'FontSize', 11); + +%% +figure();hold on +for m = 1:length(M_format) + plot(snr,GMI(m,:),'DisplayName',sprintf('GMI PAM %d',M_format(m))) +end +ylabel('GMI'); +xlabel('SNR') +title('GMI vs. SNR'); +set(gca, 'XScale', 'linear', ... + 'YScale', 'linear', ... + 'TickLabelInterpreter', 'latex', ... + 'FontSize', 11); + +function symbols = pammap(bits,M) +bits = logical(bits); +if M == 2 + symbols = bits; +elseif M == 4 + symbols= 2*bits(:,1) + (bits(:,1)==bits(:,2)); + symbols=2*symbols-3; + +elseif M == 6 + + m = 1; + + if size(bits,2)>size(bits,1) + bits = bits'; %vector aufrecht stellen + end + bits = reshape(bits',1,[])'; + thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1]; + % LUT based mapping + for k = 1:5:fix(length(bits)/5)*5 + symbols(m:m+1,1) = thres(bin2dec(int2str(bits(k:k+4)'))+1,:); + m = m+2; + end + +elseif M == 8 + x1 = bits(:,1); + x2 = (bits(:,1)==bits(:,3)); + x3 = x2~=bits(:,2); + + symbols = 4*x1 + 2*x2 + x3; + symbols=2*symbols-7; +end +end + +function bits = pamdemap(symbols,M) + +if M == 2 + thres=0; +elseif M == 4 + thres=[-2,0,2]; +elseif M == 6 + thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1]; +elseif M == 8 + thres=-6:2:6; +end + +if M ~= 6 + symbols = symbols'; + a = squeeze(repmat(real(symbols),[1 1 length(thres)])); %Eingangssignal in 3 spalten + b = squeeze(repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1])); %Threshold in 3 Spalten + comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3 + comp_real=repmat(real(symbols),[1 1 length(thres)]) > repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1]); + s1=size(comp_real,1); + s2=size(comp_real,2); +end + +if M == 2 + data_out=abs(comp_real(:,:,1)); +elseif M == 4 + data_out=[comp_real(:,:,2); ones(s1,s2) - comp_real(:,:,1) + comp_real(:,:,3)]; +elseif M == 6 + + if size(symbols,2) > 1 + symbols = symbols.'; + end + + if length(symbols)/2 ~= round(length(symbols)/2) + symbols = [symbols;0]; + end + + m = 1; + for n = 1:2:length(symbols) + dist = sqrt((symbols(n)-thres(:,1)).^2+(symbols(n+1)-thres(:,2)).^2); + [~,dd_idx] = min(dist); + % dec_out(n:n+1) = LUT(dd_idx,:); + data_out(m:m+4) = bitget(dd_idx-1,5:-1:1); + m = m+5; + end + + data_out = reshape(data_out',5,[]); + +elseif M == 8 + data_out=[comp_real(:,:,4); + comp_real(:,:,1)-comp_real(:,:,3)+comp_real(:,:,5)-comp_real(:,:,7); + 1-comp_real(:,:,2)+comp_real(:,:,6)]; +end + +bits = data_out'; + +end \ No newline at end of file diff --git a/projects/ML_based_MLSE/minimal_example_huawei/ml_mlse_pam.m b/projects/ML_based_MLSE/minimal_example_huawei/ml_mlse_pam.m new file mode 100644 index 0000000..84ebfb4 --- /dev/null +++ b/projects/ML_based_MLSE/minimal_example_huawei/ml_mlse_pam.m @@ -0,0 +1,473 @@ +classdef ml_mlse_pam < handle + + % ALGORITHM DESCRIBED IN: + % W. Lanneer and Y. Lefevre, “Machine Learning-Based Pre-Equalizers for + % Maximum Likelihood Sequence Estimation in High-Speed PONs,” + % in 2023 31st European Signal Processing Conference + + % Further ML Refs: + % https://machinelearningmastery.com/cross-entropy-for-machine-learning/ + % https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html + + % The central idea is to overcome the (white-) noise assumption within the previously described + % Viterbi algorithm, more precisely a closed-loop optimization is proposed that finds a suitable + % filter-set to directly compute the branch metrics c_k (s,s^' ). These can directly be used to + % carry out the conventional Viterbi algorithm. The system consists of S^L S=F linear FIR filters, + % combined with one bias coefficient respectively. These filters take the received input samples to + % compute the branch metrics estimates (c_k ) ̂(s,s^' ) according toThe central idea is to overcome + % the (white-) noise assumption within the previously described Viterbi algorithm, more precisely + % a closed-loop optimization is proposed that finds a suitable filter-set to directly compute the + % branch metrics c_k (s,s^' ). These can directly be used to carry out the conventional Viterbi + % algorithm. The system consists of S^L S=F linear FIR filters, combined with one bias coefficient + % respectively. These filters take the received input samples to compute the branch metrics + % estimates. Finally, the usual Viterbi is carried out... + + % Recommended Settings and some findings: + + % Requires many training epochs. According to ML people, 100,200 or + % even up to 1000 epochs are normal for ML-convergence + + % The mu parameter _can_ be adaptive - using the cross entropy and when + % analyzing the isolated training it looks very promisig. However, is + % later use I found this is not as stable as a fixed learning rate. + % mu = 0.1 worked good for me + + % Longer orders/ filter length are not always better. For me order=11 + % was good. + + % Delay factor (delta) is good when the order is also increased. With + % order = 11, a delta of =4 shows good results + + properties + sps % usually 2 + order + e + e_tr + error + + len_tr + mu_tr + epochs_tr + + % dd_mode -> not implemented here! + mu_dd %weight update in dd mode + epochs_dd + + adaptive_mu + + constellation + + L %viterbi memory length + + alpha + DIR + DIR_flip + trellis_states + + traceback_depth + + S + Nf + delta + nStates + nFeasible + combs + first_sym + last_sym + valid + valid_to_idx + valid_from_idx + w + nbiasTerms + + true_to_state_idx + state_dict % containers.Map: key(sequence)->state index + key_fmt = '%.8g_'; % key format for sequence strings + nSym % |constellation| + + ber = [] + ce = ones(1,1); + end + + methods + function obj = ml_mlse_pam(options) + arguments(Input) + + options.sps = 2; + options.order = 15; + + options.len_tr = 4096; + options.mu_tr = 0; + options.epochs_tr = 5; + + % options.dd_mode = 1; + options.mu_dd = 1e-5; + options.epochs_dd = 5; + + options.adaptive_mu = 1; + + options.delta = 0; + options.traceback_depth = 1024; + + options.L = 1 + + end + + fn = fieldnames(options); + for n = 1:numel(fn) + obj.(fn{n}) = options.(fn{n}); + end + + obj.e = zeros(obj.order,1); + obj.error = 0; + end + + function [x_viterbi,x_ref] = process(obj, X, D) + + % actual processing of the signal (steps 1. - 3.) + % 1 normalize RMS + X = X./rms(X); + + % Use sorted constellation for deterministic mapping + obj.constellation = sort(unique(D),'ascend'); + obj.nSym = numel(obj.constellation); + + if length(X)/length(D) ~= obj.sps + warning('Signal length does not fit to reference!'); + end + + % ============================================================== + % INITIALIZATION + % ============================================================== + + % --- Parameters + obj.S = numel(obj.constellation); % Num of Symbols + obj.Nf = obj.order*obj.sps; % filter length (auto adapt for n-SPS...) + obj.nStates = obj.S^obj.L; % S^L states + obj.nFeasible = obj.nStates*obj.S; % S^(L+1) feasible states + + % --- Trellis mapping + obj.trellis_states = reshape(obj.constellation,1,[]); % make row vector + pre_comb_mat = repmat(obj.trellis_states, obj.L, 1); + pre_comb_cell = mat2cell(pre_comb_mat, ones(1,obj.L), size(pre_comb_mat,2)); + obj.combs = fliplr(combvec(pre_comb_cell{:}).'); % rows: states, columns: [x_k, x_{k-1}, ...] + obj.first_sym = obj.combs(:,1); + obj.last_sym = obj.combs(:,end); + obj.nStates = size(obj.combs,1); + + % --- Valid transitions; adapted from the old Viterbi in + % Move-It where the "noise free received" states are calculated + % using the same loop and clause + obj.valid = false(obj.nStates); + for from = 1:obj.nStates + for to = 1:obj.nStates + if all(obj.combs(to,2:end) == obj.combs(from,1:end-1)) + obj.valid(to,from) = true; + end + end + end + [obj.valid_to_idx, obj.valid_from_idx] = find(obj.valid); + + % Allocate vectors and weights + % !! IF SHAPE FIT, then we already have smth there an we want + % to start with the existing filter-set (saves comp. time/ or to test fixed filter on new data) + obj.nbiasTerms = 1; + if isempty(obj.w) || any(size(obj.w) ~= [obj.Nf+obj.nbiasTerms,obj.nFeasible]) + obj.w = zeros(obj.Nf+obj.nbiasTerms,obj.nFeasible); % filter weights per transition + bias tap + % obj.w = randn(obj.Nf+obj.nbiasTerms,obj.nFeasible); + end + + % This is a weird workaround - but it works and is much faster + % than findig the state indices every time: + % Precompute dictionary for fast state lookup (sequence -> state) + keys = cell(obj.nStates,1); + for i = 1:obj.nStates + keys{i} = obj.seq_key(obj.combs(i,:)); % combs row is already [x_k, x_{k-1}, ...] + end + obj.state_dict = containers.Map(keys, 1:obj.nStates); + + % ============================================================== + % TRAINING + % ============================================================== + + n = obj.len_tr; + training = 1; + obj.equalize(X, D,obj.mu_tr,obj.epochs_tr,n,training); + obj.e_tr = obj.e; + + % ============================================================== + % Testing; Fixed Mode + % ============================================================== + + n = length(X); + training = 0; + obj.mu_dd = obj.mu_tr; %For now no DD mode is implemented... + [x_viterbi,x_ref]=obj.equalize(X, D,obj.mu_dd,obj.epochs_dd,n,training); + + end + + function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training) + % ============================================================== + % ML-Based Branch Metric Estimation + Viterbi + % ============================================================== + debug = 1; + showPlots = 1; + + nSymbols = ceil(N/obj.sps); + + for epoch = 1:epochs + + % state metrics (log-domain costs): keep as column [nStatesx1] + pm = zeros(obj.nStates,1); + v_tilde = zeros(1,obj.nFeasible); + pred = zeros(nSymbols, obj.nStates); + pm_sto = nan(obj.nStates, nSymbols); + CE_accum = 0; + + % START IDX can be randomized during training, but this + % requires some testing - it is not better, maybe a + % solutiuon is to use the same window for 10-20 epochs + % and then switch to another window + % for now: simply use the first parts of the signal for + % training and also for testing... not "the + randomize_training_window = 0; + if randomize_training_window && training + max_start = length(x) - ( (ceil(N/obj.sps)-1)*obj.sps + 1 ); + max_start = max(1, max_start); % safety + start_sample = randi([1, max_start], 1); %rnd training; not really good + else + start_sample = 1; + end + + end_sample = start_sample + (ceil(N/obj.sps)-1)*obj.sps; + start_symbol = 1 + floor((start_sample - 1)/obj.sps); % ABSOLUTE symbol index + + symbol = 0; + for sample = start_sample:obj.sps:end_sample + symbol = symbol + 1; + k = symbol; + sym_idx = start_symbol + (symbol - 1); + + % input signal window y_k; delayed by delta + i1 = sample - obj.Nf + 1 + obj.delta; + i2 = sample + obj.delta; + buf = x(max(1,i1):min(length(x),i2)); + padL = max(0,1 - i1); + padR = max(0,i2 - length(x)); + yk = [zeros(padL,1); buf(:); zeros(padR,1)]; % Nfx1 + yk = [yk;ones( obj.nbiasTerms,1)]; + + % Apply Filter; Predict branch metrics for all feasible transitions: c_hat + % Formula (8) + c_hat = (yk.' * obj.w); % [1xnFeasible] + c_hat = c_hat.'; % [nFeasiblex1] + + % Extended path metrics: v_tilde = pm(from) + c_hat + v_tilde = pm(obj.valid_from_idx) + c_hat; % [nFeasiblex1] + + % ===== Cross Entropy Loss Update ===== + + if 1 %training + % --- allocate storage once + if epoch == 1 && symbol == 1 + obj.true_to_state_idx = ones(ceil(N/obj.sps),1,'uint32'); + end + + % --- previous "to" becomes current "from" + if symbol > 1 + true_from_state_idx = obj.true_to_state_idx(symbol-1); + else + true_from_state_idx = 1; + end + + % --- compute or reuse "to" state + if epoch == 1 + % only compute in first epoch + if sym_idx >= obj.L + key_to = obj.seq_key(flip(d(sym_idx-obj.L+1 : sym_idx))); + if isKey(obj.state_dict, key_to) + obj.true_to_state_idx(symbol) = obj.state_dict(key_to); + else + obj.true_to_state_idx(symbol) = true_from_state_idx; + end + else + obj.true_to_state_idx(symbol) = true_from_state_idx; + end + end + + % --- ensure valid (from,to) + dirac = zeros(obj.nFeasible,1); + mask = obj.valid_from_idx==true_from_state_idx & ... + obj.valid_to_idx == obj.true_to_state_idx(symbol); + if any(mask) + dirac(mask) = 1; + else + idx = find(obj.valid_from_idx==true_from_state_idx,1,'first'); + dirac(idx) = 1; + obj.true_to_state_idx(symbol) = obj.valid_to_idx(idx); + end + + % softmax over -v_tilde (numerically safe shift) + v_shift = -(v_tilde - min(v_tilde)); % shift to small positive numbers + v_shift = min(v_shift, 100); % clamp exponent argument to avoid extreme numbers/ overflow (exp(50)=5e21) + expv = exp(v_shift); + p = expv ./ (sum(expv) + eps); + + % Cross entropy + CE_symbol(symbol) = -log(p(dirac==1) + eps); + + if sym_idx > obj.L + CE_smooth(symbol) = 0.01*CE_symbol(symbol) + 0.99*CE_smooth(symbol-1); + else + if epoch > 1 + CE_smooth(symbol) = obj.ce(end); %stitch together ce from last epoch? or =1 for very first round?! + else + CE_smooth(symbol) = CE_symbol(symbol); + end + end + + CE_accum = CE_symbol(symbol) + CE_accum; + + % Formula (10) + % gradient term (t - p) + dmp = (dirac - p)'; % 1xnFeasible + + % Formula (10) + dL_Dw = (yk) .* dmp; + + % Start updates only when the symbol index has ≥ L history + if sym_idx >= obj.L + if obj.adaptive_mu + mu_eff = CE_smooth(sym_idx); + mu_eff = max(min(mu_eff, 0.2), 1e-4); + else + mu_eff = mu; + end + + % see Algorithm 1 in paper + obj.w = obj.w - mu_eff .* dL_Dw; % (Nf+1)xnFeasible + end + + % if debug && epoch > 2 + % figure(100); + % subplot(4,1,1); + % heatmap(p'); + % title('Probs') + % subplot(4,1,2); + % heatmap(dmp); + % title('Update') + % subplot(4,1,3); + % heatmap(dL_Dw); + % title('Update') + % subplot(4,1,4); + % heatmap(bj.w); + % title('Update') + % + % end + + end + + % Compare-Select + v_tilde_mat = inf(obj.nStates, obj.nStates); + v_tilde_mat(obj.valid) = v_tilde; %reshapes to usual (from x to) matrix + [pm_next, pred(k,:)] = min(v_tilde_mat, [], 2); %here, calc min for each column + + % re-center, otherwise it will overflow + pm_next = pm_next - min(pm_next); + + pm = pm_next; + pm_sto(:,symbol) = pm; + end + + % Traceback + [~, s_end] = min(pm); + viterbi_path = zeros(symbol,1); + viterbi_path(symbol) = s_end; + for n = symbol:-1:2 + viterbi_path(n-1) = pred(n, viterbi_path(n)); + end + + % cut here to have the same indices when shuffling/ + % starting the start_symbol indx != 1 + y_ref = d(start_symbol:end); + y = obj.first_sym(viterbi_path); + + % Debug and Plots + if debug && training + sym_start = start_symbol; + sym_end = start_symbol + symbol - 1; + ref_slice = d(sym_start : sym_end); + err = sum(y ~= ref_slice(1:numel(y))); + + try %works with demapper, not provided in Deliverable + ref_bits = PAMmapper(obj.S,0).demap(ref_slice); + eq_bits = PAMmapper(obj.S,0).demap(y); + [~, ~, ber, ~] = calc_ber(ref_bits, eq_bits, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('Epoch: %d - BER: %.1e \n',epoch, ber); + obj.ber(epoch) = ber; + berlabel = 'BER'; + catch %fallback ser + ser = err./length(y); + fprintf('Epoch: %d - SER: %.1e \n',epoch, ser); + obj.ber(epoch) = ser; + berlabel = 'BER'; + end + + obj.ce(epoch) = CE_accum./symbol; + + if showPlots + figure(10);clf + subplot(3,2,1:2); + heatmap(obj.w); + title('Filter') + + subplot(3,2,3); + v_tildemat = NaN(obj.nStates, obj.nStates); + v_tildemat(obj.valid) = v_tilde; % log-domain scores + heatmap(v_tildemat); + title('Extended Path Metrics v-tilde') + + subplot(3,2,4); + scatter(1:symbol,pm_sto,1,'.') + title('Path Metric Winners v') + + subplot(3,2,5);hold on + scatter(1:symbol,CE_symbol,1,'.'); + scatter(1:symbol,CE_smooth,1,'.') + title('Cross Entropy') + ylabel('Cross Entropy') + xlabel('Symbols') + + subplot(3,2,6); hold on + % Left y-axis: Cross Entropy + yyaxis left + scatter(1:length(obj.ce), obj.ce, 10, 's', 'filled') + ylabel('Cross Entropy') + + % Right y-axis: BER + yyaxis right + scatter(1:length(obj.ber), obj.ber, 10, 'd', 'filled') + set(gca, 'YScale', 'log') + ylabel(berlabel) + + xlim([1, epochs]) + xlabel('Epoch') + title('Cross Entropy // BER') + grid on + + drawnow + end + end + end + end + end + + methods (Access=private) + function k = seq_key(obj, seq) + % Build a stable key string for a sequence row vector in the *same order as combs rows* ([x_k, x_{k-1}, ...]) + % Use rounding via sprintf to avoid floating-point issues. + % seq must be a row vector. + k = sprintf(obj.key_fmt, seq); + end + end +end diff --git a/projects/ML_based_MLSE/model.m b/projects/ML_based_MLSE/model.m new file mode 100644 index 0000000..4610b72 --- /dev/null +++ b/projects/ML_based_MLSE/model.m @@ -0,0 +1,222 @@ +%%% Run parameters +% TX +M = 4; + +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +random_key = 2; + +rcalpha = 0.05; +kover = 8; +vbias_rel = 0.5; +u_pi = 3.2; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1310; +laser_linewidth = 1e6; + +% Channel +link_length = 0; + + +doub_mode = db_mode.no_db; +cols = linspecer(6); +rop = [-8]; +bwl = [0.5:0.1:1.5]; +fsym = [160:16:256].*1e9; +% nonlin_mod = [0.5:0.01:0.75]; +nonlin_mod = ones(size(fsym)).*0.5; + +ffe_results = {}; +mlse_results_lin= {}; + +for r = 1:length(fsym) + + Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha); + + db_precode = 0; + db_encode = 0; + duob_mode = db_mode.no_db; + apply_pulsef = 1; + + [Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym(r),"M",M,"order",18,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "db_precode",db_precode,"db_encode",db_encode,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + + El_sig = M8199B("kover",kover).process(Digi_sig); + + %%%%% Electrical Driver Amplifier %%%%%% + El_sig = El_sig.normalize("mode","oneone"); + + %%%%% MODULATE E/O CONVERSION %%%%% + u_pi = 3.2; + vbias = -u_pi*nonlin_mod(r); + [Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig); + + %%%%%% Fiber %%%%%% + Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + + %%%%%% ROP %%%%%% + Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig); + + %%%%%% PD Square Law %%%%%% + PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig); + + %%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% + rx_bwl = 70e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig); + + % %%%%%% Low-pass Scope %%%%%% + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + + %%%%%% Scope %%%%%% + Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r)); + + % 2sps + [~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 1); + Rx_sig_2sps = Scpe_cell{1}; + Rx_sig_2sps = Rx_sig_2sps.normalize("mode","rms"); + + % 1sps + Scpe_sig_1sps = Scpe_sig.resample("fs_out",1*fsym(r)); + + [~, Scpe_cell_1sps, ~, found_sync] = Scpe_sig_1sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 1); + Rx_sig_1sps = Scpe_cell_1sps{1}; + Rx_sig_1sps = Rx_sig_1sps.normalize("mode","rms"); + showLevelHistogram(Rx_sig_1sps,Symbols,"displayname",'ffe','fignum',111); + + + %% + + mu_lms = 0.0005; + pf_ncoeffs = 2; + eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms"); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + % FFE + [y_ffe, ffe_noise] = eq_.process(Rx_sig_2sps, Symbols); + + Eq_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ffe); + [~, errors, ber_ffe, ~] = calc_ber(Eq_bits.signal, Tx_bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1); + fprintf('FFE: %.2e \n',ber_ffe); + + % Postfilter + [y_white,whitened_noise] = pf_.process(y_ffe, ffe_noise); + + % Sequence Est + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients); + [y_mlse] = mlse_.process(y_white,Symbols); + mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse); + [~, errors, ber_mlse_normal, errpos] = calc_ber(mlse_bits.signal, Tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('MLSE: %.2e \n',ber_mlse_normal); + + showLevelHistogram(y_ffe,Symbols,"displayname",'ffe','fignum',111); + + bursts = count_error_bursts(errpos, 10); + e = zeros(size(mlse_bits.signal)); + e(errpos) = 1; + figure(8) + stem(e) + + %% RUN ML-Based MLSE + + mu_lms = 0.15; + ml_mlse_equalizer = ML_MLSE("epochs_tr",50,"epochs_dd",10,"len_tr",Rx_sig_2sps.length-100,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",15,"sps",2,... + "traceback_depth",128,"L",3,"delta",5); + + %% + ml_mlse_equalizer.epochs_tr = 50; + ml_mlse_equalizer.epochs_dd = 1; + [y_ml_mlse,Vit_signal] = ml_mlse_equalizer.process(Rx_sig_2sps,Symbols); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber, errpos] = calc_ber(ml_mlse_bits.signal, Tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber); + + bursts = count_error_bursts(errpos, 10); + e = zeros(size(ml_mlse_bits.signal)); + e(errpos) = 1; + figure(8) + stem(e) + + figure() + plot(ml_mlse_equalizer.ber) + beautifyBERplot + + + + + + + %% optimize delta + deltas = [-1:4]; + ber = zeros(1,length(deltas)); + parfor m = 1:numel(deltas) + + mu_lms = 0.2; + ml_mlse_equalizer = ML_MLSE("epochs_tr",2,"epochs_dd",5,"len_tr",2^13,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",1,... + "traceback_depth",128,"L",3,"delta",deltas(m)); + + [y_ml_mlse,Vit_signal] = ml_mlse_equalizer.process(y_ffe,Symbols); + y_ml_mlse_ = y_ml_mlse; + y_ml_mlse_.signal = circshift(y_ml_mlse.signal,0); + mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse_); + [~, errors, ber(m), errpos] = calc_ber(mlse_bits.signal, Tx_bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1); + fprintf('ML MLSE: %.2e \n',ber(m)); + end + + + figure(7); hold on + title('ML MLSE') + plot(deltas,ber,'DisplayName','BER'); + yline(ber_ffe,'DisplayName','BER FFE'); + yline(ber_mlse_normal,'DisplayName','BER MLSE'); + xlabel('deltas') + beautifyBERplot + legend + ylim([1e-5, 1e-1]); + set(gca,'YScale','log'); + + + + + %% RUN Comparison + len_tr = 4096*2; + + mu_ffe1 = 0.0001; + mu_ffe2 = 0.0008; + mu_ffe3 = 0.001; + mu_dc = 0.005; + % mu_dc = 0; + + mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3]; + mu_dfe = 0.0004; + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + mu_lms = 0.0005; + eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms"); + % eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",1,"DCmu",0.00,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0); + + [ffe_results{r}, mlse_results_lin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + mlse_results_lin{r}.metrics.print; + ffe_results{r}.metrics.print; + +end \ No newline at end of file diff --git a/projects/ML_based_MLSE/rate_evaluation.m b/projects/ML_based_MLSE/rate_evaluation.m new file mode 100644 index 0000000..00494b7 --- /dev/null +++ b/projects/ML_based_MLSE/rate_evaluation.m @@ -0,0 +1,116 @@ + +ber_ffe = []; +ber_mlse = []; +ber_dbtgt = []; +ber_ml = []; + +mlse = 1; +dbtgt = 0; +duob_mode = db_mode.no_db; +baudrates = [136:8:224].*1e9; +for i = 1:length(baudrates) + + rop = -8; + M = 4; + [Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model("M",M,"fsym",baudrates(i),"rop",rop,"laser_linewidth",1310,"link_length_m",0,"random_key",1,"apply_pulsef",1); + % [Rx_sig_2sps_v2, Symbols_v2, Tx_bits_v2] = standard_link_model("M",M,"fsym",200e9,"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",2); + % [Rx_sig_2sps_v3, Symbols_v3, Tx_bits_v3] = standard_link_model("M",M,"fsym",200e9,"rop",rop,"laser_linewidth",1300,"link_length_m",0,"random_key",3); + + %% FFE + MLSE + if mlse + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients); + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + ber_ffe(i) = ffe_results.metrics.BER; + ber_mlse(i) = mlse_results.metrics.BER; + + fprintf('BER FFE: %.2e \n',ber_ffe(i)); + fprintf('BER MLSE: %.2e \n',ber_mlse(i)); + end + + + %% FFE DB tgt. + MLSE + if dbtgt + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',3); + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + + dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... + "precode_mode", duob_mode, ... + 'showAnalysis', 0,... + "postFFE", []); + + ber_dbtgt(i) = dbt_results.metrics.BER; + end + + + %% + mu_lms = 0.0005; + pf_ncoeffs = 2; + eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms"); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + % FFE + [y_ffe, ffe_noise] = eq_.process(Rx_sig_2sps_v1, Symbols_v1); + + + % Postfilter + [y_white,whitened_noise] = pf_.process(y_ffe, ffe_noise); + + %% RUN ML-Based MLSE + + mu_lms = 0.15; + ml_mlse_equalizer = ML_MLSE("epochs_tr",50,"epochs_dd",1,"len_tr",2^16,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",4,"sps",2,... + "traceback_depth",128,"L",2,"delta",0); + + ml_mlse_equalizer.mu_tr = 0.005; + ml_mlse_equalizer.epochs_tr = 2; + ml_mlse_equalizer.epochs_dd = 1; + [y_ml_mlse,~] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber_ml(i), errpos] = calc_ber(ml_mlse_bits.signal, Tx_bits_v1.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber_ml(i)); + + % figure(11);hold on + % plot(1:numel(ml_mlse_equalizer.ber),ml_mlse_equalizer.ber); + % beautifyBERplot; + % xlim([1,numel(ml_mlse_equalizer.ber)]) + +end + +%% + +figure(6); hold on; +if mlse +plot(baudrates,ber_ffe,'DisplayName','FFE'); +plot(baudrates,ber_mlse,'DisplayName','MLSE'); +end +if dbtgt +plot(baudrates,ber_dbtgt,'DisplayName','DB tgt'); +end +plot(baudrates,ber_ml,'DisplayName','ML-MLSE'); +beautifyBERplot; +legend + + + + + + + + + diff --git a/projects/ML_based_MLSE/read_csv.m b/projects/ML_based_MLSE/read_csv.m new file mode 100644 index 0000000..fbed164 --- /dev/null +++ b/projects/ML_based_MLSE/read_csv.m @@ -0,0 +1,30 @@ +%% read_wpd_csv.m +% Minimal importer for WebPlotDigitizer multi-curve CSV + +filename = 'wpd_datasets.csv'; % <-- set your file path here +T = readtable(filename); + +% Read header row manually +fid = fopen(filename); +hdr1 = strsplit(strrep(fgetl(fid), '"', ''), ','); % curve names +hdr2 = strsplit(strrep(fgetl(fid), '"', ''), ','); % X/Y header row +fclose(fid); + +% Extract unique curve names +names = hdr1(~cellfun('isempty',hdr1)); + +% Create struct for each curve +mii = struct(); +for i = 1:numel(names) + base = matlab.lang.makeValidName(strrep(names{i},' ','_')); + xi = 2*(i-1)+1; % X column + yi = xi+1; % Y column + mii.(base).X = T{:,xi}; + mii.(base).Y = T{:,yi}; + + % also create workspace variable "name_wpd" + assignin('base',[base '_wpd'], mii.(base)); +end + +disp('Imported datasets:'); +disp(fieldnames(mii)); diff --git a/projects/ML_based_MLSE/rop_evaluation.m b/projects/ML_based_MLSE/rop_evaluation.m new file mode 100644 index 0000000..eb77a3f --- /dev/null +++ b/projects/ML_based_MLSE/rop_evaluation.m @@ -0,0 +1,139 @@ +clear; clc; + +M = 4; +randkey = 1; +duob_mode = db_mode.no_db; + +mlse = 1; +dbtgt = 1; + +baudrates = 180e9:2e9:220e9; % outer loop +rops = linspace(-10,0,12); % inner sweep +FEC_thr = 3.8e-3; % BER target + +% --- allocate results +reqROP_FFE = nan(size(baudrates)); +reqROP_MLSE = nan(size(baudrates)); +reqROP_DBTGT = nan(size(baudrates)); +reqROP_ML_MLSE2 = nan(size(baudrates)); +reqROP_ML_MLSE3 = nan(size(baudrates)); + +%% ====================== OUTER LOOP ====================== +for b = 1:numel(baudrates) + baudrate = baudrates(b); + fprintf('\n=== %.0f GBd ===\n', baudrate/1e9); + + ber_ffe = nan(size(rops)); + ber_mlse = nan(size(rops)); + ber_dbtgt = nan(size(rops)); + ber_ml2 = nan(size(rops)); + ber_ml3 = nan(size(rops)); + + %% -------- inner ROP loop -------- + for i = 1:length(rops) + rop = rops(i); + + [Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model( ... + "M",M,"fsym",baudrate,"rop",rop,"laser_linewidth",1310, ... + "link_length_m",0,"random_key",1); + + + + %% FFE + MLSE + if mlse + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ... + "training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ... + "DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ... + "plotfinal",0,"ideal_dfe",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M, ... + 'trellis_states',PAMmapper(M,0).levels,'scale_mode',2, ... + 'trellis_exclusion',0,'trellis_state_mode',2,'debug',0, ... + 'DIR',pf_.coefficients); + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, ... + Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... + "precode_mode", duob_mode,'showAnalysis', 0, "postFFE", [], ... + "eth_style_symbol_mapping", 0); + + ber_ffe(i) = ffe_results.metrics.BER; + ber_mlse(i) = mlse_results.metrics.BER; + end + + %% FFE + duobinary target MLSE + if dbtgt + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M, ... + "trellis_states",PAMmapper(M,0).levels,'scale_mode',2, ... + 'trellis_exclusion',0,'trellis_state_mode',3); + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ... + "training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ... + "DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ... + "plotfinal",0,"ideal_dfe",1); + + dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, ... + Symbols_v1, Tx_bits_v1, "precode_mode", duob_mode, ... + 'showAnalysis', 0, "postFFE", []); + ber_dbtgt(i) = dbt_results.metrics.BER; + end + + %% ML-based MLSE (L=2) + mu_ml = 0.1; training_epochs = 100; + ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",2,"delta",4,"adaptive_mu",0); + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); + ref_bits = PAMmapper(M,0).demap(y_ref); + ml_bits = PAMmapper(M,0).demap(y_ml_mlse); + [~,~,ber_ml2(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ... + "skip_front",10,"skip_end",10); + + %% ML-based MLSE (L=3) + ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0); + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); + ref_bits = PAMmapper(M,0).demap(y_ref); + ml_bits = PAMmapper(M,0).demap(y_ml_mlse); + [~,~,ber_ml3(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ... + "skip_front",10,"skip_end",10); + end % ROP loop + + %% --- find required ROP (FEC crossing) + reqROP_FFE(b) = interp_fec_cross(rops, ber_ffe, FEC_thr); + reqROP_MLSE(b) = interp_fec_cross(rops, ber_mlse, FEC_thr); + reqROP_DBTGT(b) = interp_fec_cross(rops, ber_dbtgt, FEC_thr); + reqROP_ML_MLSE2(b) = interp_fec_cross(rops, ber_ml2, FEC_thr); + reqROP_ML_MLSE3(b) = interp_fec_cross(rops, ber_ml3, FEC_thr); + + % --- diagnostic + fprintf('Baud %.0f GBd: FFE %.1f, MLSE %.1f, DB %.1f, ML2 %.1f, ML3 %.1f\n', ... + baudrate/1e9, reqROP_FFE(b), reqROP_MLSE(b), reqROP_DBTGT(b), ... + reqROP_ML_MLSE2(b), reqROP_ML_MLSE3(b)); +end + +%% ====================== PLOT REQUIRED ROP ====================== +cols = cbrewer2('Set1',8); +colFFE = cols(1,:); +colMLSE = cols(2,:); +colDBTGT = cols(4,:); +colML_MLSE = cols(3,:); + +figure(); hold on +plot(baudrates/1e9, reqROP_FFE, '-o','Color',colFFE, 'DisplayName','FFE'); +plot(baudrates/1e9, reqROP_MLSE, '-s','Color',colMLSE, 'DisplayName','FFE+PF+MLSE'); +plot(baudrates/1e9, reqROP_DBTGT, '--^','Color',colDBTGT, 'DisplayName','DB tgt. MLSE'); +plot(baudrates/1e9, reqROP_ML_MLSE2, '-v','Color',colML_MLSE, 'DisplayName','ML-based MLSE (L=2)'); +plot(baudrates/1e9, reqROP_ML_MLSE3, '-d','Color',colML_MLSE*0.8,'DisplayName','ML-based MLSE (L=3)'); + +xlabel('Baud rate [GBd]'); +ylabel('Required ROP [dBm]'); +title('ROP required for FEC threshold'); +grid on; legend('Location','northwest'); +beautifyBERplot("logscale",0,"polyfit",1,"polyorder",4,"fitmethod",'polyfit'); diff --git a/projects/ML_based_MLSE/rrop_vs_length_evaluation.m b/projects/ML_based_MLSE/rrop_vs_length_evaluation.m new file mode 100644 index 0000000..9d2ea2b --- /dev/null +++ b/projects/ML_based_MLSE/rrop_vs_length_evaluation.m @@ -0,0 +1,140 @@ +clear; clc; + +M = 4; +randkey = 1; +duob_mode = db_mode.no_db; + +mlse = 1; +dbtgt = 1; + +link_lengths = 0:1:8; % [m] --- outer loop +rops = linspace(-10, 0, 12); % [dBm] --- inner sweep +FEC_thr = 3.8e-3; % BER target +baudrate = 200e9; + +% --- allocate results +reqROP_FFE = nan(size(link_lengths)); +reqROP_MLSE = nan(size(link_lengths)); +reqROP_DBTGT = nan(size(link_lengths)); +reqROP_ML_MLSE2 = nan(size(link_lengths)); +reqROP_ML_MLSE3 = nan(size(link_lengths)); + +%% ====================== OUTER LOOP ====================== +for L = 1:numel(link_lengths) + link_length_m = link_lengths(L); + fprintf('\n=== %.0f m fiber length ===\n', link_length_m); + + ber_ffe = nan(size(rops)); + ber_mlse = nan(size(rops)); + ber_dbtgt = nan(size(rops)); + ber_ml2 = nan(size(rops)); + ber_ml3 = nan(size(rops)); + + %% -------- inner ROP loop -------- + parfor i = 1:length(rops) + rop = rops(i); + + [Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1] = standard_link_model( ... + "M",M,"fsym",baudrate,"rop",rop,"laser_wavelength",1290, ... + "link_length_km",link_length_m,"random_key",1); + + Rx_sig_2sps_v1.spectrum("displayname",'Rx Sig','normalizeTo0dB',1); + + %% FFE + MLSE + if mlse + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ... + "training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ... + "DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ... + "plotfinal",0,"ideal_dfe",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M, ... + 'trellis_states',PAMmapper(M,0).levels,'scale_mode',2, ... + 'trellis_exclusion',0,'trellis_state_mode',2,'debug',0, ... + 'DIR',pf_.coefficients); + + [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, ... + Rx_sig_2sps_v1, Symbols_v1, Tx_bits_v1, ... + "precode_mode", duob_mode,'showAnalysis', 0, "postFFE", [], ... + "eth_style_symbol_mapping", 0); + + ber_ffe(i) = ffe_results.metrics.BER; + ber_mlse(i) = mlse_results.metrics.BER; + end + + %% FFE + duobinary target MLSE + if dbtgt + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M, ... + "trellis_states",PAMmapper(M,0).levels,'scale_mode',2, ... + 'trellis_exclusion',0,'trellis_state_mode',3); + ffe_order = [50, 0, 0]; + mu_ffe = [0.0001, 0.0008, 0.001]; + mu_dfe = 0.0004; + eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",2^13, ... + "training_loops",5,"dd_loops",5,"K",2,"DCmu",0.005, ... + "DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0, ... + "plotfinal",0,"ideal_dfe",1); + + dbt_results = duobinary_target(eq_, mlse_db_, M, Rx_sig_2sps_v1, ... + Symbols_v1, Tx_bits_v1, "precode_mode", duob_mode, ... + 'showAnalysis', 0, "postFFE", []); + ber_dbtgt(i) = dbt_results.metrics.BER; + end + + %% ML-based MLSE (L=2) + mu_ml = 0.1; training_epochs = 100; + ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",2,"delta",4,"adaptive_mu",0); + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); + ref_bits = PAMmapper(M,0).demap(y_ref); + ml_bits = PAMmapper(M,0).demap(y_ml_mlse); + [~,~,ber_ml2(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ... + "skip_front",10,"skip_end",10); + + %% ML-based MLSE (L=3) + ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ... + "len_tr",2^16,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ... + "traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0); + [y_ml_mlse,y_ref] = ml_mlse_equalizer.process(Rx_sig_2sps_v1,Symbols_v1); + ref_bits = PAMmapper(M,0).demap(y_ref); + ml_bits = PAMmapper(M,0).demap(y_ml_mlse); + [~,~,ber_ml3(i)] = calc_ber(ml_bits.signal, ref_bits.signal, ... + "skip_front",10,"skip_end",10); + end % ROP loop + + %% --- find required ROP (FEC crossing) + reqROP_FFE(L) = interp_fec_cross(rops, ber_ffe, FEC_thr); + reqROP_MLSE(L) = interp_fec_cross(rops, ber_mlse, FEC_thr); + reqROP_DBTGT(L) = interp_fec_cross(rops, ber_dbtgt, FEC_thr); + reqROP_ML_MLSE2(L) = interp_fec_cross(rops, ber_ml2, FEC_thr); + reqROP_ML_MLSE3(L) = interp_fec_cross(rops, ber_ml3, FEC_thr); + + fprintf('Length %.0f m: FFE %.1f, MLSE %.1f, DB %.1f, ML2 %.1f, ML3 %.1f\n', ... + link_length_m, reqROP_FFE(L), reqROP_MLSE(L), reqROP_DBTGT(L), ... + reqROP_ML_MLSE2(L), reqROP_ML_MLSE3(L)); +end + +%% ====================== PLOT REQUIRED ROP ====================== +cols = cbrewer2('Set1',8); +colFFE = cols(1,:); +colMLSE = cols(2,:); +colDBTGT = cols(4,:); +colML_MLSE = cols(3,:); + +figure(); hold on +plot(link_lengths, reqROP_FFE, '-o','Color',colFFE, 'DisplayName','FFE'); +plot(link_lengths, reqROP_MLSE, '-s','Color',colMLSE, 'DisplayName','FFE+PF+MLSE'); +plot(link_lengths, reqROP_DBTGT, '--^','Color',colDBTGT, 'DisplayName','DB tgt. MLSE'); +plot(link_lengths, reqROP_ML_MLSE2, '-v','Color',colML_MLSE, 'DisplayName','ML-based MLSE (L=2)'); +plot(link_lengths, reqROP_ML_MLSE3, '-d','Color',colML_MLSE*0.8,'DisplayName','ML-based MLSE (L=3)'); + +xlabel('Link length [km]'); +ylabel('Required ROP [dBm]'); +title(sprintf('Required ROP the reach FEC threshold (3.8e-3); %.0f GBd PAM-%d', baudrate.*1e-9, M)); +legend('Location','northwest'); +grid on; +beautifyBERplot("logscale",0,"polyfit",1,"polyorder",3,"fitmethod",'smoothingspline'); diff --git a/projects/ML_based_MLSE/standard_link_model.m b/projects/ML_based_MLSE/standard_link_model.m new file mode 100644 index 0000000..004c249 --- /dev/null +++ b/projects/ML_based_MLSE/standard_link_model.m @@ -0,0 +1,99 @@ +function [Rx_sig_2sps,Symbols,Tx_bits] = standard_link_model(options) + + % STANDARD_LINK_MODEL Basic IM/DD link simulation + % Rx_sig_2sps = standard_link_model(...optional args...) + % + % All arguments are optional and default to standard parameters + % if not provided. + + arguments + + % --- Transmitter settings --- + options.M (1,1) double = 4 + options.apply_pulsef (1,1) logical = true + options.fdac (1,1) double = 256e9 + options.fadc (1,1) double = 256e9 + options.random_key (1,1) double = 2 + options.rcalpha (1,1) double = 0.05 + options.kover (1,1) double = 8 + options.vbias_rel (1,1) double = 0.5 + options.u_pi (1,1) double = 3.2 + options.laser_wavelength (1,1) double = 1310 + options.laser_linewidth (1,1) double = 1e6 + + % --- Channel parameters --- + options.link_length_km (1,1) double = 0 + options.rop (1,:) double = -5 + options.fsym (1,:) double = (212:16:256)*1e9 + options.doub_mode (1,1) db_mode = db_mode.no_db + + % --- Debug --- + options.debug (1,1) logical = false + + end + + % --- Pulse former --- + Pform = Pulseformer("fsym",options.fsym,"fdac",4*options.fsym, ... + "pulse","rc","pulselength",16,"alpha",options.rcalpha); + + % --- Transmitter source --- + [Digi_sig,Symbols,Tx_bits] = PAMsource( ... + "fsym",options.fsym,"M",options.M,"order",18,"useprbs",0, ... + "fs_out",options.fdac,"applyclipping",0,"clipfactor",1.5, ... + "applypulseform",options.apply_pulsef,"pulseformer",Pform, ... + "randkey",options.random_key,"db_precode",0,"db_encode",0, ... + "mrds_code",0,"mrds_blocklength",512, ... + "duobinary_mode",options.doub_mode).process(); + + % --- AWG driver --- + El_sig = M8199B("kover",options.kover).process(Digi_sig); + El_sig = El_sig.normalize("mode","oneone"); + + % --- E/O Modulation --- + vbias = -options.vbias_rel*options.u_pi; + Opt_sig = EML("mode",eml_mode.im_cosinus,"power",3, ... + "fsimu",El_sig.fs,"lambda",options.laser_wavelength, ... + "bias",vbias,"u_pi",options.u_pi,"linewidth",options.laser_linewidth, ... + "randomkey",options.random_key+1).process(El_sig); + + % --- Fiber --- + Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",options.link_length_km, ... + "alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + + % --- Amplifier (ROP set) --- + Opt_sig = Amplifier("amp_mode","ideal_no_noise", ... + "gain_mode","output_power","amplification_db",options.rop).process(Opt_sig); + + % --- Photodiode --- + PD_sig = Photodiode("fsimu",options.fdac*options.kover,"dark_current",2e-8, ... + "responsivity",1,"temperature",20,"nep",1.8e-11, ... + "randomkey",options.random_key).process(Opt_sig); + + % --- Electrical LPF (receiver frontend) --- + rx_bwl = 70e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl, ... + "fs",options.fdac*options.kover,"filterType",filtertypes.butterworth, ... + "active",true).process(PD_sig); + + % --- Scope low-pass and sampling --- + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",options.fadc, ... + "filterType",filtertypes.butterworth,"active",true); + + Scpe_sig = Scope("fsimu",options.fdac*options.kover,"fadc",options.fadc, ... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth, ... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0, ... + "samp_jitter",0,"adcresolution",8,"quantbuffer",0.1, ... + 'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + % --- Downsample to 2 sps --- + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*options.fsym); + [~,Scpe_cell,~,found_sync] = Scpe_sig_2sps.tsynch( ... + "reference",Symbols,"fs_ref",options.fsym,"debug_plots",0); + + try + Rx_sig_2sps = Scpe_cell{1}.normalize("mode","rms"); + catch + Rx_sig_2sps = Scpe_sig_2sps.normalize("mode","rms"); + end + +end diff --git a/projects/ML_based_MLSE/theoretic_channel_evaluation.m b/projects/ML_based_MLSE/theoretic_channel_evaluation.m new file mode 100644 index 0000000..3eb6f09 --- /dev/null +++ b/projects/ML_based_MLSE/theoretic_channel_evaluation.m @@ -0,0 +1,157 @@ + +M = 4; +order = 18; +randkey = 1; + +bitpattern = []; +s = RandStream('twister','Seed',randkey); +for i = 1:log2(M) + N = 2^(order-1); %length of prbs + bitpattern(:,i) = randi(s,[0 1], N, 1); +end + +if M == 6 + bitpattern = reshape(bitpattern',[],1); + bitpattern = bitpattern(1:end-mod(length(bitpattern),5)); +end + +Bits = Informationsignal(bitpattern); + +Symbols = PAMmapper(M,0).map(Bits); +Symbols.fs = 200e9; + +Bits_ = PAMmapper(M, 0, "eth_style", 0).demap(Symbols); + +% --- Channel: minimal ISI response + AWGN --- +h = [0.3 0.9 0.3,0.1]; % impulse response (normalized later if desired) +h = h / norm(h); % optional normalization for unit energy + +symbols_filt = Symbols.filter(h,1); + + +%% SHOW FIG 3 in Paper: "ML Base Pre-Eq" + +SNR_dB = [20:1:25]; +SNR_db = linspace(12,25,12); + +ber_ffe = zeros(size(SNR_dB)); +ber_mlse_l5 = zeros(size(SNR_dB)); +ber_nwf_mlse_l2 = zeros(size(SNR_dB)); +ber_ml_mlse_l2 = zeros(size(SNR_dB)); +ber_ml_mlse_l3 = zeros(size(SNR_dB)); +ber_ml_mlse_l4 = zeros(size(SNR_dB)); + +epochs_training = 100; + +for i = 1:numel(SNR_dB) + + symbols_noi = symbols_filt; + symbols_noi.signal = awgn(symbols_filt.signal, SNR_dB(i), 'measured'); % AWGN with given SNR + + % Sequence Est L=5 + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',h); + mlse_.DIR = h; + [y_mlse] = mlse_.process(symbols_noi,Symbols); + mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse); + [~, ~, ber_mlse_l5(i), ~] = calc_ber(mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('MLSE L5: %.2e \n',ber_mlse_l5(i)); + + % 2nd Approach + mu_lms = 0.0005; + pf_ncoeffs = 1; + eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",16,"sps",1,"dd_mode",1,"adaption_technique","lms"); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + % FFE + [y_ffe, ffe_noise] = eq_.process(symbols_noi, Symbols); + + Eq_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ffe); + [~, ~, ber_ffe(i), ~] = calc_ber(Eq_bits.signal, Bits.signal, "skip_front", 0, "skip_end", 0, "returnErrorLocation", 1); + fprintf('FFE: %.2e \n',ber_ffe(i)); + + % Postfilter + [y_white,~] = pf_.process(y_ffe, ffe_noise); + + % Sequence Est + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0,'DIR',pf_.coefficients); + [y_mlse] = mlse_.process(y_white,Symbols); + mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_mlse); + [~, errors, ber_nwf_mlse_l2(i), errpos] = calc_ber(mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('MLSE: %.2e \n',ber_nwf_mlse_l2(i)); + + % ML-base MLSE L=2 + adaptive_mu = 0; + mu_lms = 0.15; + ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^15,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,... + "traceback_depth",128,"L",2,"delta",4,"adaptive_mu",adaptive_mu); + [y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber_ml_mlse_l2(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l2(i)); + + % ML-base MLSE L=3 + mu_lms = 0.15; + ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^16,... + "mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,... + "traceback_depth",128,"L",3,"delta",4,"adaptive_mu",adaptive_mu); + [y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols); + ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + [~, errors, ber_ml_mlse_l3(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l3(i)); + + % % ML-base MLSE L=5 + % mu_lms = 0.15; + % ml_mlse_equalizer = ML_MLSE("epochs_tr",epochs_training,"epochs_dd",1,"len_tr",2^15,... + % "mu_dd",mu_lms,"mu_tr",mu_lms,"order",11,"sps",1,... + % "traceback_depth",128,"L",5,"delta",4); + % [y_ml_mlse,~] = ml_mlse_equalizer.process(symbols_noi,Symbols); + % ml_mlse_bits = PAMmapper(M, 0, "eth_style", 0).demap(y_ml_mlse); + % [~, errors, ber_ml_mlse_l5(i), errpos] = calc_ber(ml_mlse_bits.signal, Bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1); + % fprintf('ML MLSE BER: %.2e \n',ber_ml_mlse_l5(i)); + + +end + +%% +figure(); hold on; + +% --- define scheme colors (consistent palette) +cols = cbrewer2('SET1',8); +colFFE = cols(1,:); % blue +colMLSE = cols(2,:); % orange +colML_MLSE = cols(3,:); % green +colNWF_MLSE = cols(4,:); % purple + +% --- local simulation results +plot(SNR_dB, ber_ffe, '-o', 'Color', colFFE, 'DisplayName','FFE (N=16)'); +if M==2, plot(FFE_wpd.X, FFE_wpd.Y, ':', 'LineWidth',1.5, 'Color', colFFE, 'DisplayName','Paper FFE'); end + + +plot(SNR_dB, ber_mlse_l5, '-s', 'Color', colMLSE, 'DisplayName','MLSE (L=5)'); +if M==2, plot(MLSE_wpd.X, MLSE_wpd.Y, ':', 'LineWidth',1.5, 'Color', colMLSE, 'DisplayName','Paper MLSE L=5'); end + +plot(SNR_dB, ber_nwf_mlse_l2,'--^','Color', colNWF_MLSE, 'DisplayName','FFE+PF+MLSE (L=2)'); +plot(SNR_dB, ber_ml_mlse_l2, '-v', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=2)'); +if M==2, plot(ML_MLSE_L_2_wpd.X,ML_MLSE_L_2_wpd.Y,':', 'LineWidth',1.5, 'Color', colML_MLSE, 'DisplayName','Paper ML-based MLSE L=2'); end + + +plot(SNR_dB, ber_ml_mlse_l3, '-d', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=3)'); + +if M==2, plot(SNR_dB, ber_ml_mlse_l5, '-p', 'Color', colML_MLSE, 'DisplayName','ML-based MLSE (L=5)'); end +if M==2, plot(ML_MLSE_L_5_wpd.X,ML_MLSE_L_5_wpd.Y,':', 'LineWidth',1.5, 'Color', colML_MLSE, 'DisplayName','Paper ML-based MLSE L=5'); end +% --- imported WebPlotDigitizer data (dotted) + +yline(3.8e-3,'HandleVisibility','off'); +yline(2.2e-4,'HandleVisibility','off'); + +% --- formatting +beautifyBERplot; +xlim([SNR_dB(1), SNR_dB(end)]); +ylim([1e-5 0.1]); +xlabel('Input SNR [dB]'); +ylabel('Bit Error Rate (BER)'); +title('PAM-4; M=4; AWGN Channel'); +legend('Location','southwest'); +grid on; + diff --git a/projects/ML_based_MLSE/wpd_datasets.csv b/projects/ML_based_MLSE/wpd_datasets.csv new file mode 100644 index 0000000..c2059d7 --- /dev/null +++ b/projects/ML_based_MLSE/wpd_datasets.csv @@ -0,0 +1,9 @@ +FFE,,MLSE,,ML MLSE L=2,,ML MLSE L=5, +X,Y,X,Y,X,Y,X,Y +7.988476019722099,0.038632829886662855,7.9828552218736,0.02056096426419196,7.988825638727029,0.026145160025945406,7.982882115643211,0.01995262314968881 +8.984755714926044,0.02462092401494627,8.991855670103094,0.008868008219069292,8.991519497982967,0.012908315306800594,8.998027790228598,0.009002182769536628 +9.993487225459436,0.014339094903163154,9.988632900044824,0.0032424222072799827,9.994320932317347,0.005651632488900345,9.994751232631108,0.0034952501326754757 +10.989914836396235,0.007746944324122318,10.991730165844913,0.001020224273461357,10.997256835499776,0.002129417748571358,10.991689825190498,0.0010672369906432938 +12.005060510981624,0.0034952501326754757,12.001483639623487,0.00018978455660928717,11.994316450022412,0.0005679993334792185,12.007359928283282,0.0002680778116002006 +12.983254146122816,0.0013169376605490738,13.011492604213357,0.000026540740973404924,12.997722994173017,0.00012652429120320801,12.992384580905423,0.000049125201846734525 +13.99255042581802,0.0004081967712510506,14.00969520394442,0.000001975386920666194,13.995172568354999,0.00002183385565256239,14.015275661138503,0.0000038826695948713645 diff --git a/projects/Nonlinear_MLSE/simulation_nonlin_dsp.m b/projects/Nonlinear_MLSE/simulation_nonlin_dsp.m new file mode 100644 index 0000000..1896dd4 --- /dev/null +++ b/projects/Nonlinear_MLSE/simulation_nonlin_dsp.m @@ -0,0 +1,229 @@ +%%% Run parameters +% TX +M = 4; +m = floor(log2(M)*10)/10; +fsym = 224e9; + +apply_pulsef = 1; +fdac = 256e9; +fadc = 256e9; +random_key = 2; + +rcalpha = 0.05; +kover = 8; +vbias_rel = 0.5; +u_pi = 3.2; +vbias = -vbias_rel*u_pi; +laser_wavelength = 1310; +laser_linewidth = 1e6; + + +% Channel +link_length = 0; + +vnle_order1 = 50; +vnle_order2 = 0; +vnle_order3 = 0; + +vnle_order=[vnle_order1,vnle_order2,vnle_order3]; +dfe_order = [0 0 0]; + +alpha = 0; + +len_tr = 4096*2; + +mu_ffe1 = 0.0001; +mu_ffe2 = 0.0008; +mu_ffe3 = 0.001; +mu_dc = 0.005; +% mu_dc = 0; + +mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3]; +mu_dfe = 0.0004; + +dfe_ = sum(dfe_order)>0; + +doub_mode = db_mode.no_db; +cols = linspecer(6); +rop = [-8]; +bwl = [0.5:0.1:1.5]; +fsym = [208:16:256].*1e9; +nonlin_mod = [0.5:0.01:0.75]; +fsym = ones(size(nonlin_mod)).*fsym(1); + +ffe_results = {}; +mlse_results_lin= {}; +vnle_results= {}; +mlse_results_nonlin= {}; +mlse_results_nonlin_states= {}; + +g_eye = GifWriter('Name','eye','Parallel',true); +g_mod = GifWriter('Name','modulator','Parallel',true); + +for r = 1:length(nonlin_mod) + + Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha); + + db_precode = 0; + db_encode = 0; + duob_mode = db_mode.no_db; + apply_pulsef = 1; + + [Digi_sig,Symbols,Tx_bits] = PAMsource(... + "fsym",fsym(r),"M",M,"order",19,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",random_key,... + "db_precode",db_precode,"db_encode",db_encode,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + + % El_sig = AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig); + El_sig = M8199B("kover",kover).process(Digi_sig); + % AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig); + + %%%%% Low-pass el. components %%%%%% + % tx_bwl = 100e9; + % El_sig = Filter('filtdegree',3,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig); + + %%%%% Electrical Driver Amplifier %%%%%% + El_sig = El_sig.normalize("mode","oneone"); + % El_sig = El_sig.setPower(1,"dBm"); + % figure;histogram(El_sig.signal); + + %%%%% MODULATE E/O CONVERSION %%%%% + u_pi = 3.2; + vbias = -u_pi*nonlin_mod(r); + [Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig); + + if 1 + figure(15); + hold on + scatter(El_sig.signal(1:100000)+vbias,(abs(Opt_sig.signal(1:100000)).^2)*1e3,0.1,'.','DisplayName','Modulator TF') + xlabel('Input in V') + ylabel('abs(Eopt)2 in mW','Interpreter','latex') + ylim([0 2]); + xlim([-3.2 0]); + g_mod.addFrame(15, r); + + Opt_sig.eye(fsym(r), M, "fignum", 103837); + g_eye.addFrame(103837, r); + end + + %%%%%% Fiber %%%%%% + Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig); + + %%%%%% ROP %%%%%% + Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig); + + %%%%%% PD Square Law %%%%%% + PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig); + + %%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% + rx_bwl = 70e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig); + + % %%%%%% Low-pass Scope %%%%%% + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + + %%%%%% Scope %%%%%% + Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r)); + % Scpe_sig_resampled.signal = Scpe_sig_resampled.signal(1:2*length(Symbols)); + + [~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0); + Rx_sig = Scpe_cell{1}; + Rx_sig = Rx_sig.normalize("mode","rms"); + + if 1 + + %% FFE + % ffe_order = [50, 0, 0]; + % eq_ffe = EQ("Ne",ffe_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); + % + % ffe_results = ffe(eq_ffe,M,Rx_sig,Symbols,Tx_bits,... + % "precode_mode",duob_mode,... + % 'showAnalysis',0,... + % "postFFE",[],... + % "eth_style_symbol_mapping",0); + % + % ffe_results.metrics.print; + % ffe_results.config.equalizer_structure = "ffe"; + % + + %% MLSE linear + + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + eq_ = EQ("Ne",ffe_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",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',2,'debug',1); + + [ffe_results{r}, mlse_results_lin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + mlse_results_lin{r}.metrics.print; + ffe_results{r}.metrics.print; + + %% MLSE nonlinear pre + + pf_ncoeffs = 1; + ffe_order = [50, 1, 0]; + eq_ = EQ("Ne",ffe_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",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',2); + + [vnle_results{r}, mlse_results_nonlin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + mlse_results_nonlin{r}.metrics.print; + vnle_results{r}.metrics.print; + + %% nonlinear states MLSE linear pre + + pf_ncoeffs = 1; + ffe_order = [50, 0, 0]; + eq_ = EQ("Ne",ffe_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",1); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',0,'trellis_state_mode',3); + + [~, mlse_results_nonlin_states{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig, Symbols, Tx_bits, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + mlse_results_nonlin_states{r}.metrics.print; + + end +end + +g_mod.compile(15); +g_eye.compile(103837); + +%% + +figure();hold on; +plot(nonlin_mod,cellfun(@(x) x.metrics.BER, ffe_results),'DisplayName','FFE') +plot(nonlin_mod,cellfun(@(x) x.metrics.BER, vnle_results),'DisplayName','VNLE') +plot(nonlin_mod,cellfun(@(x) x.metrics.BER, mlse_results_lin),'DisplayName','FFE+MLSE') +plot(nonlin_mod,cellfun(@(x) x.metrics.BER, mlse_results_nonlin_states),'DisplayName','FFE+nonlin. states MLSE') +plot(nonlin_mod,cellfun(@(x) x.metrics.BER, mlse_results_nonlin),'DisplayName','VNLE+MLSE') +xlabel('Nonlinear Driving'); +ylabel('BER') +set(gca,'YScale','log'); +legend; +ylim([1e-4 1e-1]); +beautifyBERplot; + + diff --git a/projects/WDM/WDM_auswertung.m b/projects/WDM/WDM_auswertung.m new file mode 100644 index 0000000..d13c97f --- /dev/null +++ b/projects/WDM/WDM_auswertung.m @@ -0,0 +1,252 @@ + + +try + rop = res.settings.rop; % 12 points + wavelengthplan = res.settings.wavelengthplan; +catch + wavelengthplan = [1295,1305,1315,1325]; + wavelengthplan = calcWavelengthPlan(16,400e9,1310); + rop = -8.25:0.75:0; +end + +N = length(wavelengthplan); +figure(); hold on; +cols = cbrewer2('set2',N); % one color per wavelength (Ch) + +fec = 2.2e-4; +fec = 3.8e-3; +Sffe = cell(1,N); +Svnle = cell(1,N); +Smlse = cell(1,N); +Sdbt = cell(1,N); + +% Choose your quantile band. For your old style, use 0.04/0.99: +qLow = 0.0; % lower quantile (e.g., 0.04 for old script) +qHigh = 1; % upper quantile (e.g., 0.99 for old script) +cols = linspecer(N); % one color per wavelength (Ch) +cols = cbrewer2('set1',N); + +for l = 1:N + % Slice 12x50 cell arrays + ffe_cells = reshape(squeeze(res.ffe(l,:,:)),length(rop),[]); + vnle_cells = reshape(squeeze(res.vnle(l,:,:)),length(rop),[]); + mlse_cells = reshape(squeeze(res.mlse(l,:,:)),length(rop),[]); + dbt_cells = reshape(squeeze(res.dbt(l,:,:)),length(rop),[]); + + [Sffe{l}, noX_ffe] = fecCrossings(rop, ffe_cells, fec); + + [Svnle{l}, noX_ffe] = fecCrossings(rop, vnle_cells, fec); + + [Smlse{l}, noX_ffe] = fecCrossings(rop, mlse_cells, fec); + + [Sdbt{l}, noX_ffe] = fecCrossings(rop, dbt_cells, fec); + + % Extract BER matrices using only complete realizations (12/12 ROP filled) + ffe_mat = extractCompleteBER(ffe_cells); % 12 x K_ffe + vnle_mat = extractCompleteBER(vnle_cells); % 12 x K_vnle + mlse_mat = extractCompleteBER(mlse_cells); % 12 x K_mlse + mlse_alpha_mat = extractCompleteAlphas(mlse_cells); % 12 x K_mlse + dbt_mat = extractCompleteBER(dbt_cells); % 12 x K_dbt + + showLegend = 1; % one legend entry per technique + + % Plot shaded band + mean line with boundedline + % plotBandMeanBL(rop, ffe_mat, cols(l,:), sprintf('FFE @ %.1d nm',round(wavelengthplan(l))), qLow, qHigh, '--s', showLegend); + % scatter(Sffe,fec.*ones(size(Sffe)),20,'v','MarkerFaceColor','black'); + + plotBandMeanBL(rop, vnle_mat, cols(l,:), sprintf('VNLE @ %.1d nm',round(wavelengthplan(l))), qLow, qHigh, '--x', showLegend); + + % plotBandMeanBL(rop, mlse_mat, cols(l,:), sprintf('VNLE+PF+MLSE @ %.1d nm',round(wavelengthplan(l))), qLow, qHigh, '-o', showLegend); + + % plotBandMeanBL(rop, dbt_mat, cols(l,:), sprintf('DBt.+MLSE @ %.1d nm',round(wavelengthplan(l))), qLow, qHigh, '--v', showLegend); + + set(gca,'XScale','linear','YScale','log','TickLabelInterpreter','latex','FontSize',11); + yline([3.8e-3, 2.2e-4], 'HandleVisibility','off','LineWidth',1.5); + +end + +ylabel('BER'); +xlabel('ROP'); +title('BER vs. ROP'); +xlim([min(rop) max(rop)]); +ylim([1e-5 0.3]); +grid on; +legend show; + + +S_cell = Sdbt; +S_cell =Smlse; +S_cell = {Svnle,Smlse,Sdbt}; +S_cell = {Svnle}; +figure(5); hold on; +for i = 1:length(S_cell) + % Pad to rectangular matrix: rows = realizations, cols = wavelengths + Kmax = max(cellfun(@numel, S_cell{i})); + S_mat = NaN(Kmax, N); + for l = 1:N + k = numel(S_cell{i}{l}); + if k > 0 + S_mat(1:k, l) = S_cell{i}{l}; + end + end + + % --- Violin plot over wavelengths (columns) --- + + cols=linspecer(3); + catLabels = arrayfun(@(nm) sprintf('%d nm', nm), wavelengthplan, 'UniformOutput', false); + vs = violinplot(S_mat, catLabels, ... + 'ViolinColor', cols(i,:), ... + 'ViolinAlpha', 0.10, ... + 'MarkerSize', 20, ... + 'ShowMedian', true, ... + 'EdgeColor', cols(i,:), ... + 'ShowWhiskers', false, ... + 'ShowData', true, ... + 'ShowBox', false, ... + 'Bandwidth', 0.05); + + ylim([floor(min(S_mat,[],'all')), ceil(max(S_mat,[],'all'))]) + ylim([-8 0]); + ylabel('ROP at FEC crossing'); + title(sprintf('RROP to cross BER %.2e', fec)); + grid on; box on; + +end + + + + + + + + + + +%% ================= helper ================= +function plotBandMeanBL(x, Y, color, techLabel, qLow, qHigh, lineSpec, showLegend) + % Y: (nPoints x nRealizations) + % Remove realizations that are entirely zero (like removeZeros behavior) + badCols = all(Y == 0, 1); + Y(:, badCols) = []; + + Y(Y==0) = 1e-8; + % Stats across realizations + mu = mean(Y, 2, 'omitnan'); % mean line + lo = quantile(Y, qLow, 2); % lower bound + hi = quantile(Y, qHigh, 2); % upper bound + + % Convert to asymmetric distances required by boundedline: + % b(:,1) = distance to lower side; b(:,2) = distance to upper side + b = [mu - lo, hi - mu]; + + % Call boundedline with alpha shading + [hl, hp] = boundedline(x(:), mu(:), b, lineSpec, 'alpha', ... + 'transparency', 0.18); + % Color styling + set(hl, 'Color', color, 'LineWidth', 1.4, 'MarkerSize', 4); + set(hp, 'FaceColor', color, 'HandleVisibility','off'); % patch hidden in legend + + % Single legend entry per technique (use first wavelength only) + if showLegend + set(hl, 'DisplayName', techLabel); + else + set(hl, 'HandleVisibility','off'); + end + + % Optional: outline the bounds if outlinebounds is available + if exist('outlinebounds','file') == 2 + ho = outlinebounds(hl, hp); + set(ho, 'linestyle', ':', 'color', color, 'linewidth', 1, ... + 'HandleVisibility','off'); + end +end + +function [S, noCrossingMask, Y_keep] = fecCrossings(rop, cells12xR, fec) +% cells12xR: 12xR cell array (one wavelength + scheme slice) +% each cell must be a struct with .metrics.BER +% rop: 12x1 numeric vector of ROP points +% fec: scalar FEC threshold (e.g., 3.8e-3) +% +% Outputs: +% S 1xK vector of crossing ROP per kept realization (NaN if none) +% noCrossingMask 1xK logical mask: true if no crossing for that realization +% Y_keep 12xK numeric BER matrix used for the crossing detection + + % 1) keep only complete realization columns + Y = extractCompleteBER(cells12xR); % -> 12 x K + if isempty(Y) + S = []; + noCrossingMask = []; + Y_keep = Y; + return; + end + + % 2) optionally drop realizations with mean BER > 0.1 + ok = mean(Y,1,'omitnan') <= 0.1; + Y = Y(:, ok); + if isempty(Y) + S = []; + noCrossingMask = []; + Y_keep = Y; + return; + end + + % 3) find crossings per realization + nR = size(Y,2); + S = nan(1,nR); + noCrossingMask = true(1,nR); + + rop = rop(:); % ensure column + for j = 1:nR + y = Y(:,j); + + % sign change from >fec to <=fec (first time it drops below FEC) + above = (y > fec); + idx = find(above(1:end-1) & ~above(2:end), 1, 'first'); + + if ~isempty(idx) + % linear interpolation between (x1,y1) and (x2,y2) + x1 = rop(idx); y1 = y(idx); + x2 = rop(idx+1); y2 = y(idx+1); + + if isfinite(y1) && isfinite(y2) && y2 ~= y1 + t = (fec - y1) / (y2 - y1); + S(j) = x1 + t*(x2 - x1); + noCrossingMask(j) = false; + end + end + end + + Y_keep = Y; +end + + + +function Y = extractCompleteBER(cellSlice) +% cellSlice: 12xR cell array; each cell should be a struct with .metrics.BER +% Keep only those realization columns where ALL 12 ROP entries are valid. + if isempty(cellSlice), Y = []; return; end + nR = size(cellSlice,2); + keep = false(1,nR); + for r = 1:nR + col = cellSlice(:,r); + keep(r) = all(cellfun(@(c) ~isempty(c) , col)); + end + if ~any(keep), Y = []; return; end + Y = cellfun(@(c) c.metrics.BER, cellSlice(:,keep), 'UniformOutput', true); +end + +function Y = extractCompleteAlphas(cellSlice) +% cellSlice: 12xR cell array; each cell should be a struct with .metrics.BER +% Keep only those realization columns where ALL 12 ROP entries are valid. + if isempty(cellSlice), Y = []; return; end + nR = size(cellSlice,2); + keep = false(1,nR); + for r = 1:nR + col = cellSlice(:,r); + keep(r) = all(cellfun(@(c) ~isempty(c) , col)); + end + if ~any(keep), Y = []; return; end + Y = cellfun(@(c) c.metrics.Alpha, cellSlice(:,keep), 'UniformOutput', true); +end + diff --git a/projects/WDM/WDM_model.m b/projects/WDM/WDM_model.m new file mode 100644 index 0000000..0d7cbe2 --- /dev/null +++ b/projects/WDM/WDM_model.m @@ -0,0 +1,278 @@ +%%% Run parameters +% TX +% --- FIRST LINE: evaluate settings located beside this script --- +run(fullfile(fileparts(mfilename('fullpath')),'WDM_settings.m')); +s = struct; +s.num_realiz = 1; +s.wavelengthplan = calcWavelengthPlan(16,400e9,1310); +s.wavelengthplan = [1295,1305,1315,1325]; +s.link_length = 2; +s.pmd = 0.0; +s.gamma = 0.00; + +s.M = 4; +m = floor(log2(s.M)*10)/10; +fsym = 224e9; +fdac = 2*fsym; +fadc = 2*fsym; +s.random_key = 100; + +% Laser / s.Modulator +vbias_rel = 0.5; +u_pi = 3.2; +vbias = -vbias_rel*u_pi; +laser_linewidth = 0e6; + +% EQ SETTINGS +vnle_order1 = 50; +vnle_order2 = 3; +vnle_order3 = 3; +vnle_order=[vnle_order1,vnle_order2,vnle_order3]; +dfe_order = [0 0 0]; +len_tr = 4096*2; +mu_ffe1 = 0.0001; +mu_ffe2 = 0.0008; +mu_ffe3 = 0.001; +mu_dc = 0.005; +% mu_dc = 0; +mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3]; +mu_dfe = 0.0004; + +%DB Stuff +db_precode = 0; +db_encode = 0; +duob_mode = db_mode.no_db; +apply_pulsef = 0; + +rcalpha = 0.05; +Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha); + +N = numel(s.wavelengthplan); +f_plan = physconst('lightspeed')./(s.wavelengthplan.*1e-9); +margin = 25e12; % some THz left and right +f_span = (max(f_plan)+margin)-(min(f_plan)-margin); +f_nyq = f_span/2; +kover = 8; +upsample_required = f_nyq./(fdac*kover/2); +upsample_pow = 2^nextpow2(upsample_required); +upsample_ceil = ceil(upsample_required); + +s.f_opt = fdac*kover*upsample_pow; +s.f_opt_nyq = s.f_opt/2; + +signal_cell = {}; +Symbols = {}; +Tx_bits = {}; + +s.rop = -6:0.75:-0.75; + +output_ffe = cell(length(s.wavelengthplan),length(s.rop),s.num_realiz); +output_vnle = cell(length(s.wavelengthplan),length(s.rop),s.num_realiz); +output_mlse = cell(length(s.wavelengthplan),length(s.rop),s.num_realiz); +output_dbt = cell(length(s.wavelengthplan),length(s.rop),s.num_realiz); + +for realiz = 1:s.num_realiz + + + parfor l = 1:N + + [Digi_sig,Symbols{l},Tx_bits{l}] = PAMsource(... + "fsym",fsym,"M",s.M,"order",18,"useprbs",0,... + "fs_out",fdac,... + "applyclipping",0,"clipfactor",1.5,... + "applypulseform",apply_pulsef,"pulseformer",Pform,... + "randkey",s.random_key+l+realiz,... + "db_precode",db_precode,"db_encode",db_encode,... + "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process(); + + % Digi_sig.spectrum("fignum",101,"displayname",'bla','normalizeTo0dB',0,'lambda0_nm',1310,'useWavelengthAxis',0); + Lp_awg = Filter('filtdegree',3,"f_cutoff",100e9,"fs",fdac*kover,"filterType",filtertypes.gaussian,"active",true); + El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",1,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0,"H_lpf",Lp_awg,"dac_max",0.6,"dac_min",-0.6).process(Digi_sig); + % El_sig = s.M8199B("kover",kover).process(Digi_sig); + % El_sig.spectrum("fignum",101,"displayname",'bla','normalizeTo0dB',0,'lambda0_nm',1310,'useWavelengthAxis',0); + + %%%%% Electrical Driver Amplifier %%%%%% + El_sig = El_sig.normalize("mode","oneone"); + % El_sig = El_sig.setPower(1,"dBm"); + % figure;histogram(El_sig.signal); + + %%%%% s.MODULATE E/O CONVERSION %%%%% + Eml_out = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",s.wavelengthplan(l),"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",s.random_key+l+realiz).process(El_sig); + + signal_cell{l} = Polarization_Controller("mode","rot_power","desired_power",100).process(Eml_out); + end + + Opt_sig_wdm = Optical_Multiplex("fs_in",fdac*kover,"fs_out",upsample_pow*fdac*kover,... + "lambda_center",1310,"random_key",0,"filtype",1,"B",200e9).process(signal_cell); + + Opt_sig_wdm = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",3+10*log10(N)).process(Opt_sig_wdm); + + % Opt_sig_wdm.spectrum("fignum",101,"displayname",'bla','normalizeTo0dB',0,'lambda0_nm',1310,'useWavelengthAxis',0); + + % Opt_sig_wdm.spectrum("fignum",101,"displayname",'bla','normalizeTo0dB',1,'max_num_lines',2); + + %%%%%% Fiber %%%%%% + Opt_sig_wdm_fib=Opt_sig_wdm; + + nSegments = 2; + zdw = 1310; + D_local = 0; %if ~=0, simulation uses "segmented fiber with d+,d-) + randomize_D = true; + Dvec = getDispersionVector(nSegments, D_local, zdw, randomize_D, s.random_key+realiz); + for seg = 1:nSegments + + Opt_sig_wdm_fib = DP_Fiber("L",s.link_length/nSegments,"D",Dvec(seg),"Dpmd",s.pmd,"Ds",0.07,... + "beat_len",10,"corr_len",100,"dz",1,"manakov",0,... + "gamma",s.gamma,"lambda",zdw,"n_waveplates",10,"SS_dphimax",0.01,... + "SS_dzmax",50,"SS_dzmin",10,"X_alpha",0.3,"X_beta",0,"rng",1).process(Opt_sig_wdm_fib); + + end + + Opt_sig_wdm_fib.spectrum("fignum",realiz,"displayname",'bla','lambda0_nm',1310,'useWavelengthAxis',0); + + % Opt_sig_wdm_fib.move_it_spectrum("fignum",100212,"displayname",'bla'); + + % Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",s.link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"s.gamma",0,"Dslope",0.07).process(Opt_sig) + + for ri = 1:length(s.rop) + + %%%%%% ROP %%%%%% + Opt_sig_wdm_rx = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",s.rop(ri)+10*log10(N)).process(Opt_sig_wdm_fib); + + Opt_sig_wdm_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1,"fs_out",Opt_sig_wdm_rx.fs/upsample_pow,"fs_in",Opt_sig_wdm_rx.fs,"lambda_center",1310).process(Opt_sig_wdm_rx); + + PD_cell = {}; + for l = 1:N + + %%%%%% PD Square Law %%%%%% + assert(fdac*kover==Opt_sig_wdm_demux{l}.fs,'Sampling Frequencies do not match! Check previous steps'); + PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",s.random_key+l+realiz).process(Opt_sig_wdm_demux{l}); + + % PD_sig.spectrum("fignum",222,"displayname",'bla','normalizeTo0dB',1); + + %%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%% + rx_bwl = 100e9; + PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig); + + % %%%%%% Low-pass Scope %%%%%% + Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true); + + %%%%%% Scope %%%%%% + Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,... + "delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,... + "samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,... + "adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig); + + Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym); + % Scpe_sig.spectrum("fignum",222,"displayname",'bla','normalizeTo0dB',1); + + [~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols{l}, "fs_ref", fsym, "debug_plots", 1); + Rx_sig = Scpe_cell{1}; + Rx_sig = Rx_sig.normalize("mode","rms"); + + + + % FFE + ffe_order = [50, 0, 0]; + eq_ffe = EQ("Ne",ffe_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); + ffe_results = ffe(eq_ffe,s.M,Rx_sig,Symbols{l},Tx_bits{l},... + "precode_mode",duob_mode,... + 'showAnalysis',0,... + "postFFE",[],... + "eth_style_symbol_mapping",0); + + output_ffe{l,ri,realiz} = ffe_results; + + + + %VNLE + pf_ncoeffs = 1; + ffe_order = [50, 5, 5]; + eq_ = EQ("Ne",ffe_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); + pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1); + + useviterbi = 0; + if useviterbi + mlse_ = MLSE_viterbi("duobinary_output",0,'M',s.M,'trellis_states',PAMmapper(s.M,0).levels); + else + mlse_ = MLSE("duobinary_output",0,'M',s.M,'trellis_states',PAMmapper(s.M,0).levels); + end + + [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, s.M, Rx_sig, Symbols{l},Tx_bits{l}, ... + "precode_mode", duob_mode,... + 'showAnalysis', 0, ... + "postFFE", [],... + "eth_style_symbol_mapping", 0); + + output_vnle{l,ri,realiz} = vnle_results; + output_mlse{l,ri,realiz} = mlse_results; + + + % DB tgt. + useviterbi = 0; + if useviterbi + mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',s.M,'trellis_states',PAMmapper(s.M,0).levels); + else + mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",s.M,"trellis_states",PAMmapper(s.M,0).levels); + end + ffe_order = [50, 5, 5]; + eq_ = EQ("Ne",ffe_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); + + dbt_results = duobinary_target(eq_, mlse_db_, s.M, Rx_sig, Symbols{l},Tx_bits{l}, ... + "precode_mode", duob_mode, ... + 'showAnalysis', 0,... + "postFFE", []); + + output_dbt{l,ri,realiz} = dbt_results; + + end + + end + + res = struct(); + res.settings = s; + res.ffe = output_ffe; + res.vnle = output_vnle; + res.mlse = output_mlse; + res.dbt = output_dbt; + + % Save results + save(fullfile(output_root, fname), 'res', '-v7.3'); + fprintf('Saved results to: %s\n', fullfile(output_root, fname)); + disp(datetime('now','TimeZone','local','Format','yyyyMs.Mdd_HHmmss')); + + +end + +function dispersion_vector = getDispersionVector(N, D, ref_zdw, randomize_ZDW, randomkey) +% s.MATLAB version of the Python generator shown above. +% Returns an N×1 vector (ps/(nm·km)). +% +% D is the nominal dispersion magnitude. For D>0 the link is segmented with +% alternating sign (+D, -D, +D, …). For D==0 it is flat (0) except for +% ZDW randomization. The ZDW detuning is ~N(0, 2 nm) around 1310 nm and is +% converted to dispersion via 0.09 ps/(nm·km) per nm. + + % constants (matching the Python code) + meanLambda_nm = 1310; % center wavelength + sigma_nm = 2; % ZDW sigma + Dslope = 0.07; % ps/(nm·km) per nm detuning + + % random ZDW-induced dispersion offset + if randomize_ZDW + rng(randomkey, 'twister'); + rand_zdws_nm = meanLambda_nm + sigma_nm .* randn(N,1); + rand_D = (rand_zdws_nm - ref_zdw) .* Dslope; % ps/(nm·km) + else + rand_D = zeros(N,1); + end + + % nominal segmented pattern (match Python intent; keep length N) + if D > 0 + base = (-1) .^ ((0:N-1).'); % +1,-1,+1,-1,... + else % D == 0 (or anything else) + base = ones(N,1); + end + + dispersion_vector = base .* D + rand_D; % ps/(nm·km) +end diff --git a/projects/WDM/WDM_settings.m b/projects/WDM/WDM_settings.m new file mode 100644 index 0000000..68a04a3 --- /dev/null +++ b/projects/WDM/WDM_settings.m @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + +% Add the imdd_simulation framework to the path +if ispc + addpath(genpath('C:\Users\Silas\Documents\MATLAB\imdd_simulation')); +else + % Linux path on the cluster + addpath(genpath('/work_beegfs/sutef391/imdd_simulation')); +end + +% Quiet the ambiguous CET warning (best is to set TZ in sbatch; see below) +warning('off','MATLAB:datetime:AmbiguousTimeZone'); + +% How many workers? +cpus = str2double(getenv('SLURM_CPUS_PER_TASK')); +if ~isfinite(cpus) || cpus < 1, cpus = max(1, feature('numcores')); end + +% Use a per-job, node-local JobStorageLocation to avoid stale locks on $HOME +% Prefer $TMPDIR if your cluster provides it, else tempdir(). +tmpbase = getenv('TMPDIR'); +if isempty(tmpbase), tmpbase = tempdir; end +jsl = fullfile(tmpbase, sprintf('matlab_jobstorage_%s_%s', ... + getenv('USER'), getenv('SLURM_JOB_ID'))); +if ~exist(jsl,'dir'); mkdir(jsl); end + +% Configure the local cluster explicitly and start the pool +c = parcluster('local'); +c.NumWorkers = cpus; +c.JobStorageLocation = jsl; + +p = gcp('nocreate'); +if isempty(p) || p.NumWorkers ~= cpus + if ~isempty(p), delete(p); end + p = parpool(c, cpus); % avoids the “queued” state +end +fprintf('parpool up with %d workers; JobStorage=%s\n', p.NumWorkers, c.JobStorageLocation); + + + + + + +% result filename (timestamp + optional job id) +t = datetime('now','TimeZone','local','Format','yyyyMMdd_HHmmss'); +jobid = getenv('SLURM_JOB_ID'); if isempty(jobid), jobid = 'nojid'; end +host = getenv('HOSTNAME'); if isempty(host), host = 'localhost'; end + +% Output directory depends on platform +if ispc + output_root = fullfile('C:\Users\Silas\Documents\MATLAB\Datensätze\FWM_2025\'); +else + output_root = '/work_beegfs/sutef391/results_WDM'; +end +if ~exist(output_root,'dir'), mkdir(output_root); end + +% Build filename +t = datetime('now','TimeZone','local','Format','yyyyMMdd_HHmmss'); +jobid = getenv('SLURM_JOB_ID'); if isempty(jobid), jobid = 'nojid'; end +host = getenv('HOSTNAME'); if isempty(host), host = 'localhost'; end + +fname = sprintf('WDM_%s_%s_%s.mat', char(t), host, jobid); \ No newline at end of file diff --git a/test/bcjr_pam.m b/test/bcjr_pam.m new file mode 100644 index 0000000..674b55f --- /dev/null +++ b/test/bcjr_pam.m @@ -0,0 +1,555 @@ +classdef bcjr_pam < handle + %MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length + + properties(Access=public) + M %PAM-M + DIR + trellis_states + duobinary_output + end + + methods (Access=public) + + function obj = bcjr_pam(options) + %NAME Construct an instance of this class + % Detailed explanation goes here + + arguments + options.M double = 4; + options.DIR double = [1]; + options.trellis_states double = [-3 -1 1 3]; + options.duobinary_output logical = false; + + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + end + + function [VITERBI_ESTIMATION_SYMBOLS,LLR_exact,GMI] = process(obj,data_in,data_ref,tx_bits,bit_mapping) + + + debug = 0; + + % States should match the target states of the prev. EQ (EQ's job was to reduce the error between signal and the target) + trellis_state_mode = 2; + % 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 = 1; % PAM-6 only (only if data is NOT precoded!) + + % Additional scaling between states, expected output (noiseless_received) and the noisy, filtered input signal + scale_mode = 2; % scale_mode: + % 0 = no scaling, + % 1 = use RMS to scale MODEL, + % 2 = use MMSE/time-corr to scale MODEL, -> This best to get the GMI right -> sometimes the LLP's are not centered around zero... + % 3 = use RMS to scale DATA, + % 4 = use MMSE/time-corr to scale DATA + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%% PREPARATIONS %%%%%%%% + + % remove unnecessary zeros at start of impulse response to keep + % number of trellis states minimal + DIR_nonzero = find(obj.DIR ~= 0); + if DIR_nonzero(1) > 1 + obj.DIR(1:DIR_nonzero(1)-1) = []; + end + + if isscalar(obj.DIR) + 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 + + % Trellis States + obj.trellis_states = reshape(obj.trellis_states,1,[]); + if trellis_state_mode == 1 % Normalize the Trellis states to =1 RMS + + obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states); + + 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),size(obj.trellis_states)); + + 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 + + + % 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 + 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 + % 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 + end + 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 states/ expected values + noise_free_received = g*noise_free_received + b; + last_sym = g*last_sym + b; + + % calculate noise power + sigma2 = mean(abs(data_in - (g*y_ideal + b)).^2); %noise = mean(abs((RX Signal - IDEAL Signal)))^2 + inv2s2 = 1/(2*sigma2); + + if debug + figure(100); clf; hold on + obj.showLevelScatter_(data_in, data_ref); + 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 (VITERBI -Alpha's) %%%%% + + % Initialize the output vector + 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 = 2:length(data_in) + + bm = -(data_in(n) - noise_free_received).^2 * inv2s2; + + % exclude edge to 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 + + pm = pm + bm; + [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 + viterbi_path = NaN(1,length(data_in)); + + % find ideal trellis path by going through the trellis backwards + [~,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]); + end + + VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path); + VITERBI_ESTIMATION_SYMBOLS = reshape(VITERBI_ESTIMATION_SYMBOLS,size(data_in)); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% BACKWARD (Beta's) %%%%% + + % Initialize the output vector + 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:1 + + bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2; + + % exclude edge to edge transitions for even->odd steps && PAM-6 + if mod(h+1, 2) == 0 && obj.M == 6 && trellis_exclusion + bm(edge_edge_mask) = -Inf; + end + + pm = pm + bm.'; + [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 + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% FORWARD (Combine Alpha and Beta to yield LLP's) %%%%% + + %calc the log probabilities (llp's) + + 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 + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% Calc LLR's %%%%% + + % These are interchangeable... + 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 = [-5 -3 -1 1 3 5]; + pam6transitions = combvec(states,states)'; % pam6transitions = + % [-5 -5; + % -3 -5; + % -1 -5; ... + + [~, idx_sym_1] = ismember(pam6transitions(:,1), states); + [~, idx_sym_2] = ismember(pam6transitions(:,2), states); + pam6ind = [idx_sym_1, idx_sym_2]; + + 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); + + % All 36 Combinations: M = LLP Symbol 1 + LLP Symbol 2 + Mij = LLP1(pam6ind(:,1)) + LLP2(pam6ind(:,2)); + pij = prob1(pam6ind(:,1)) .* prob2(pam6ind(:,2)); + + % for each of the 5 bits sum exact-probs or max-log + for b = 1:num_bits + idx_sym_1 = bit_mapping(:,b)==1; + idx_bit_1 = bit_mapping(:,b)==0; + + % exact LLR from probabilities + P1 = sum(pij(idx_sym_1)); %prob that bit == 1 + P0 = sum(pij(idx_bit_1)); + LLR_exact(k,b) = log(P1./P0); %ratio by multiplication + + % max-log: + LLR_maxlogmap(k,b) = max( Mij(idx_sym_1) ) - max( Mij(idx_bit_1) ); % ratio by subtraction + end + end + + % GMI calc includes the Tx-bitstream + tx_bits_pam6_reshaped = reshape(tx_bits',5,[])'; % N x 5 + 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; % GMI per single symbol not per two symbols + + 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).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 uses the single max LLP value + % instead of sum over all LLP's + 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); + 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); + + % 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); % assumes equally distributed ones and zeros + end + + GMI = sum(MI); % Total bitwise mutual information + + end + + + 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 + + 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 + + if obj.M == 6 + 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 ; edge -> edge).\n', nnz(isforbidden)); + end + + end + + + end + + function [symbols_for_lvl,avg_for_lvl] = showLevelScatter_(~,eq_signal,ref_symbols) + + figure() + + rx_symbols = eq_signal; %./ rms(eq_signal); + correct_symbols = ref_symbols; + + % col = cbrewer2('Paired',numel(unique(correct_symbols))*2); + col = ... + [0.6510 0.8078 0.8902; ... + 0.1216 0.4706 0.7059; ... + 0.6980 0.8745 0.5412; ... + 0.2000 0.6275 0.1725; ... + 0.9843 0.6039 0.6000; ... + 0.8902 0.1020 0.1098; ... + 0.9922 0.7490 0.4353; ... + 1.0000 0.4980 0; ... + 0.7922 0.6980 0.8392; ... + 0.4157 0.2392 0.6039; ... + 1.0000 1.0000 0.6000; ... + 0.6941 0.3490 0.1569; ... + 0.6510 0.8078 0.8902; ... + 0.1216 0.4706 0.7059; ... + 0.6980 0.8745 0.5412; ... + 0.2000 0.6275 0.1725]; + ccnt = -1; + + levels = unique(correct_symbols); + symbols_for_lvl = NaN(numel(levels),length(correct_symbols)); + start = 1; + ende = length(correct_symbols); + + for l = 1:numel(levels) + ccnt = ccnt+2; + + level_amplitude = levels(l); + + symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); + std_lvl(l) = std(symbols_for_lvl(l,:),'omitnan'); + xax = 1:length(correct_symbols); + + scatter(xax(start:ende),symbols_for_lvl(l,start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:)); + hold on; + + + 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); + + L = 500; + movmean = 1/L .* movsum(rx_symbols(correct_symbols==level_amplitude),[L/2,L/2], 'Endpoints', 'fill'); + + avg_for_lvl(l,correct_symbols==level_amplitude) = movmean; + + 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)); + + plot(xax(start:ende),avg_for_lvl(l,start:ende),'Color',col(ccnt,:)); + + hold on + end + + % yline(levels); + xlabel('Samples'); + ylabel('Amplitude'); + ylim([-3 3]); + + end + + + end +end diff --git a/test/minimal_example_bcjr.m b/test/minimal_example_bcjr.m new file mode 100644 index 0000000..a692d31 --- /dev/null +++ b/test/minimal_example_bcjr.m @@ -0,0 +1,171 @@ + +M_format = [2,4,6,8]; + +for m = 1:length(M_format) + % --- Parameters --- + M = M_format(m); % PAM order (e.g., 2,4,8) + Nsym = 1e5; % number of symbols + h = [1, 0.5]; % Impulse response to remove + + b = log2(M); + if M == 6 b = 5; end + rng(1); + bits_tx = logical(randi([0 1], Nsym, b, 'uint8')); + + tx_symbols = pammap(bits_tx,M); + + if M == 6 + states = unique(tx_symbols); + pam6transitions = combvec(states',states')'; % pam6transitions = + bitmapping = pamdemap(reshape(pam6transitions',1,[])',M); + else + bitmapping = pamdemap(unique(tx_symbols),M); + end + + scaling = sqrt(sum(unique(tx_symbols).^2)/numel(unique(tx_symbols))); + tx_symbols = tx_symbols ./ scaling; + + % apply impulse response to signal + y_filt = filter(h, 1, tx_symbols); + + sir = 10:25; + for s = 1:length(sir) + + % apply noise + y = awgn(y_filt,sir(s),"measured",1); + + % apply bcjr + BCJR = bcjr_pam("DIR",h,"duobinary_output",0,"M",M,"trellis_states",unique(tx_symbols)); + [viterbi_estimate,LLR,GMI(m,s)] = BCJR.process(y,tx_symbols,bits_tx,bitmapping); + + % decode LLR's + bits_LLR = LLR > 0; + + % demap viterbi symbols sequence + rx_symbols = viterbi_estimate .* scaling; + bits_rx = pamdemap(rx_symbols,M); + + % BER calc + BER_vit(m,s) = nnz(bits_tx ~= bits_LLR) / numel(bits_tx); + fprintf('BER LLR = %.2e \n', BER_vit); + + BER_llr(m,s) = nnz(bits_tx ~= bits_rx) / numel(bits_tx); + fprintf('BER = %.2e \n', BER_llr); + end +end + +figure();hold on +for m = 1:length(M_format) + plot(sir,BER_llr(m,:),'DisplayName',sprintf('PAM %d',M_format(m))) + % plot(sir,BER_vit(m,:),'DisplayName',sprintf('PAM %d',M_format(m)),'LineStyle',':','LineWidth',0.1,'HandleVisibility','off'); +end +ylabel('BER'); +xlabel('SNR') +title('BER vs. SNR'); +set(gca, 'XScale', 'linear', ... + 'YScale', 'log', ... + 'TickLabelInterpreter', 'latex', ... + 'FontSize', 11); + + +figure();hold on +for m = 1:length(M_format) + plot(sir,GMI(m,:),'DisplayName',sprintf('GMI PAM %d',M_format(m))) +end +ylabel('GMI'); +xlabel('SNR') +title('GMI vs. SNR'); +set(gca, 'XScale', 'linear', ... + 'YScale', 'linear', ... + 'TickLabelInterpreter', 'latex', ... + 'FontSize', 11); + +function symbols = pammap(bits,M) +bits = logical(bits); +if M == 2 + symbols = bits; +elseif M == 4 + symbols= 2*bits(:,1) + (bits(:,1)==bits(:,2)); + symbols=2*symbols-3; + +elseif M == 6 + + m = 1; + + if size(bits,2)>size(bits,1) + bits = bits'; %vector aufrecht stellen + end + bits = reshape(bits',1,[])'; + thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1]; + % LUT based mapping + for k = 1:5:fix(length(bits)/5)*5 + symbols(m:m+1,1) = thres(bin2dec(int2str(bits(k:k+4)'))+1,:); + m = m+2; + end + +elseif M == 8 + x1 = bits(:,1); + x2 = (bits(:,1)==bits(:,3)); + x3 = x2~=bits(:,2); + + symbols = 4*x1 + 2*x2 + x3; + symbols=2*symbols-7; +end +end + +function bits = pamdemap(symbols,M) + +if M == 2 + thres=0; +elseif M == 4 + thres=[-2,0,2]; +elseif M == 6 + thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1]; +elseif M == 8 + thres=-6:2:6; +end + +if M ~= 6 + symbols = symbols'; + a = squeeze(repmat(real(symbols),[1 1 length(thres)])); %Eingangssignal in 3 spalten + b = squeeze(repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1])); %Threshold in 3 Spalten + comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3 + comp_real=repmat(real(symbols),[1 1 length(thres)]) > repmat(reshape(thres(:).',[1 1 length(thres)]),[1 length(symbols) 1]); + s1=size(comp_real,1); + s2=size(comp_real,2); +end + +if M == 2 + data_out=abs(comp_real(:,:,1)); +elseif M == 4 + data_out=[comp_real(:,:,2); ones(s1,s2) - comp_real(:,:,1) + comp_real(:,:,3)]; +elseif M == 6 + + if size(symbols,2) > 1 + symbols = symbols.'; + end + + if length(symbols)/2 ~= round(length(symbols)/2) + symbols = [symbols;0]; + end + + m = 1; + for n = 1:2:length(symbols) + dist = sqrt((symbols(n)-thres(:,1)).^2+(symbols(n+1)-thres(:,2)).^2); + [~,dd_idx] = min(dist); + % dec_out(n:n+1) = LUT(dd_idx,:); + data_out(m:m+4) = bitget(dd_idx-1,5:-1:1); + m = m+5; + end + + data_out = reshape(data_out',5,[]); + +elseif M == 8 + data_out=[comp_real(:,:,4); + comp_real(:,:,1)-comp_real(:,:,3)+comp_real(:,:,5)-comp_real(:,:,7); + 1-comp_real(:,:,2)+comp_real(:,:,6)]; +end + +bits = data_out'; + +end \ No newline at end of file diff --git a/test/pam_6_differential_code_understand.m b/test/pam_6_differential_code_understand.m new file mode 100644 index 0000000..3f13c5a --- /dev/null +++ b/test/pam_6_differential_code_understand.m @@ -0,0 +1,79 @@ +M = 6; +data = [1,2,3,4,5,6]; + +M = 6; + +bitpattern = []; +s = RandStream('twister','Seed',1); +for i = 1:log2(M) + N = 2^(12-1); %length of prbs + bitpattern(:,i) = randi(s,[0 1], N, 1); +end + +if M == 6 + bitpattern = reshape(bitpattern',[],1); + bitpattern = bitpattern(1:end-mod(length(bitpattern),5)); +end + +bits = Informationsignal(bitpattern); + +symbols = PAMmapper(M,0).map(bits); +symbols_tx_prec = Duobinary().precode(symbols); + +% 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; ... +pam6transitions_serial = reshape(pam6transitions',[],1); + +data = pam6transitions_serial; +data = round(data); +b = min(data); +data = data - b; +data = data ./ 2; +% THIS WAS USED! +bk = zeros(size(data)); +for k = 2:numel(data) + bk(k) = mod(data(k)-bk(k-1),M); +end + + +%% State Analysis +x = bk;%symbols_tx_prec.signal; +levels = sort(unique(x)).'; % or provide known 1x6 level values + +[~,ix] = min(abs(x - levels),[],2); +x = levels(ix); % snapped/quantized + +%% TRANSITION COUNTS & PROBABILITIES +K = numel(levels); +% map to state indices 1..K +[tf, idx] = ismember(x, levels); +idx = idx(:); +from = idx(1:end-1); +to = idx(2:end); +from = idx(1:2:end); +to = idx(2:2:end); + +% counts C(from,to) +C = accumarray([from,to], 1, [K K], @sum, 0); +% row-stochastic transition matrix P(to|from) +rowSums = sum(C,2); +P = C ./ max(rowSums,1); + +%% 1) HEATMAP (which transitions are more probable?) +figure('Name','Transition Probabilities (to | from)'); +h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on'); +colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]); +h.XLabel = 'From state (level)'; +h.YLabel = 'To state (level)'; +h.Title = 'P(to | from)'; + +%% 2) WEIGHTED TRANSITION GRAPH +% Use dtmc if you have Econometrics Toolbox: +mc = dtmc(P, 'StateNames', string(levels)); +figure('Name','Markov Graph (dtmc)'); +gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true); \ No newline at end of file diff --git a/test/pam_6_states_analysis.m b/test/pam_6_states_analysis.m new file mode 100644 index 0000000..a92d3ca --- /dev/null +++ b/test/pam_6_states_analysis.m @@ -0,0 +1,212 @@ + + +M = 6; + +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 + +if M == 6 + bitpattern = reshape(bitpattern',[],1); + bitpattern = bitpattern(1:end-mod(length(bitpattern),5)); +end + +bits = Informationsignal(bitpattern); + +symbols = PAMmapper(M,0).map(bits); + +bits_rx = PAMmapper(M,0).demap(symbols); +[~,~,ber_direct,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +assert(ber_direct==0,'Mapping is wrong'); + +nBursts = 0; +% No Precoding %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%SEND DIRECTLY +symbols_tx = symbols; + +symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, nBursts, 42); + +%RECEIVE BRANCH (do nothing special) +bits_rx = PAMmapper(M,0).demap(symbols_rx); + +[~,~,ber,errpos] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +disp(['BER normal: - ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]); +bursts_normal = count_error_bursts(errpos, 20); + + +% Precode Emulation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%SEND DIRECTLY +symbols_tx = symbols; + +symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, nBursts, 42); + +%REFERENCE BRACH +symbols_db = Duobinary().encode(symbols_tx); +symbols_tx_emu = Duobinary().decode(symbols_db); +bits_tx_emu = PAMmapper(M,0).demap(symbols_tx_emu); + +% symbols_rx = introduce_symbol_errors(symbols_tx, 1, 10, 200, 42); + +%RECEIVE BRANCH +symbols_db = Duobinary().encode(symbols_rx); +symbols_rx_emu = Duobinary().decode(symbols_db); +bits_rx = PAMmapper(M,0).demap(symbols_rx_emu); + +[~,~,ber_precode_emulation,errpos_precode_emulation] = calc_ber(bits_tx_emu.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +disp(['BER precode emulation: ',sprintf('%.1E',ber_precode_emulation),' - - PAM-',num2str(M)]); +bursts_precode_emulation = count_error_bursts(errpos_precode_emulation, 20); + + + + +% Precode at Tx %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%SEND PRECODED DATA +symbols_tx_prec = Duobinary().precode(symbols); + +symbols_rx_prec = introduce_symbol_errors(symbols_tx_prec, 1, 10, nBursts, 42); + +%RECEIVE BRANCH +symbols_db = Duobinary().encode(symbols_rx_prec); +symbols_rx_prec = Duobinary().decode(symbols_db); +bits_rx = PAMmapper(M,0).demap(symbols_rx_prec); + +[~,~,ber_precoded,errpos_precoded] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +disp(['BER precoded: ',sprintf('%.1E',ber_precoded),' - - PAM-',num2str(M)]); +burst_precoded = count_error_bursts(errpos_precoded, 20); + + +% Precode at Tx but omit at Rx %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%SEND PRECODED DATA +symbols_tx_prec = Duobinary().precode(symbols); +bits_tx_prec = PAMmapper(M,0).demap(symbols_tx_prec); + +symbols_rx_omit = introduce_symbol_errors(symbols_tx_prec, 1, 10, nBursts, 42); + +%RECEIVE BRANCH +bits_rx = PAMmapper(M,0).demap(symbols_rx_omit); + +[~,~,ber_omit,errpos_omit] = calc_ber(bits_tx_prec.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); +disp(['BER (omit precode): ',sprintf('%.1E',ber_omit),' - - PAM-',num2str(M)]); +burst_omit = count_error_bursts(errpos_omit, 20); + +if 0 + cols = linspecer(8); + figure();hold on; + stem(1:20,bursts_normal,'LineWidth',2,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder'); + stem(1:20,bursts_precode_emulation,'LineWidth',2,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','emulated precoder'); + stem(1:20,burst_precoded,'LineWidth',1,'Color',cols(6,:),'Marker','_','DisplayName','w/ diff. precoder'); + stem(1:20,burst_omit,'LineWidth',1,'Color',cols(5,:),'Marker','.','LineStyle',':','DisplayName','omit precoder'); + xlabel('Bit Error Burst Length') + ylabel('Occurence') + set(gca, 'yscale', 'log'); +end + + +%% State Analysis +signal_to_analyze = symbols_tx_emu; +x = signal_to_analyze.signal(:); +levels = sort(unique(x)).'; % or provide known 1x6 level values + +[~,ix] = min(abs(x - levels),[],2); +x = levels(ix); % snapped/quantized + +%% TRANSITION COUNTS & PROBABILITIES +K = numel(levels); +% map to state indices 1..K +[tf, idx] = ismember(x, levels); +idx = idx(:); +from = idx(1:end-1); +to = idx(2:end); +from = idx(1:2:end); +to = idx(2:2:end); + +% counts C(from,to) +C = accumarray([from,to], 1, [K K], @sum, 0); +% row-stochastic transition matrix P(to|from) +rowSums = sum(C,2); +P = C ./ max(rowSums,1); + +%% 1) HEATMAP (which transitions are more probable?) +figure('Name','Transition Probabilities (to | from)'); +h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on'); +colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]); +h.XLabel = 'From state (level)'; +h.YLabel = 'To state (level)'; +h.Title = 'P(to | from)'; + +%% 2) WEIGHTED TRANSITION GRAPH +% Use dtmc if you have Econometrics Toolbox: +mc = dtmc(P, 'StateNames', string(levels.*PAMmapper(M,0).get_scaling)); +figure('Name','Markov Graph (dtmc)'); +gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true); + + +function symbols = introduce_symbol_errors(symbols, j, maxBurstLen, nBursts, seed) +%INTRODUCE_SYMBOL_ERRORS injects bursty level errors into symbols.signal. +% symbols.signal : column/row vector of quantized levels (exactly one of 6 values) +% j : max level step per sample (default 1) +% maxBurstLen : maximum burst length (default 8) +% nBursts : number of bursts to insert (default ~1% of length) +% seed : RNG seed (optional) + + if nargin < 2 || isempty(j), j = 1; end + if nargin < 3 || isempty(maxBurstLen), maxBurstLen = 8; end + x = symbols.signal(:); + N = numel(x); + if nargin < 4 || isempty(nBursts), nBursts = max(1, round(0.01*N)); end + if nargin >= 5 && ~isempty(seed), rng(seed); end + + % known levels and index mapping + lvls = sort(unique(x)).'; + K = numel(lvls); + + [~, idx] = ismember(x, lvls); % idx in 1..6 + + used = false(N,1); % avoid overlapping bursts + burst_ranges = zeros(nBursts,2); + + for b = 1:nBursts + % pick start not inside an existing burst + s = randi(N); + while used(s), s = randi(N); end + L = randi(maxBurstLen); + e = min(N, s+L-1); + + % mark used range + used(s:e) = true; + burst_ranges(b,:) = [s e]; + + % choose one direction for the whole burst: -1 (down) or +1 (up) + dir = randi([0 1])*2 - 1; + + % apply level errors within the burst + for t = s:e + k = idx(t); % current level index (1..6) + + % force inward movement at edges; prevents "flipping" to opposite edge + if k == 1 && dir == -1, dir = +1; end + if k == K && dir == +1, dir = -1; end + + step = randi([1 j]); % 1..j steps + kNew = k + dir*step; + + % clamp to [1,K], no wrap-around + if kNew < 1, kNew = 1; elseif kNew > K, kNew = K; end + + % if clamped to the same edge repeatedly, flip direction to keep changing + if kNew == k + dir = -dir; + kNew = max(1, min(K, k + dir*step)); + end + + idx(t) = kNew; + end + end + + x_err = lvls(idx); + symbols.signal = reshape(x_err, size(symbols.signal)); % preserve original shape + +end