Files
imdd_silas/tore --source rescue-premerge -- Classes
2026-02-02 13:38:56 +01:00

6962 lines
368 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
diff --git a/Classes/00_signals/Signal.m b/Classes/00_signals/Signal.m
index e06c41f..f09a03e 100644
--- a/Classes/00_signals/Signal.m
+++ b/Classes/00_signals/Signal.m
@@ -172,11 +172,9 @@ classdef Signal

hold on;
if isempty(options.color)
- % plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
- plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1);
+ plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);

else
- % plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color);
- plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Color',options.color);
+ plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color);

end
% 2 c)
% - xlabel if not already here: time in readable format (1 ms and not 1e-3 s)
diff --git a/Classes/02_optical/DP_Fiber.m b/Classes/02_optical/DP_Fiber.m
index a5f3dce..c1f08ea 100644
--- a/Classes/02_optical/DP_Fiber.m
+++ b/Classes/02_optical/DP_Fiber.m
@@ -24,6 +24,8 @@ classdef DP_Fiber
SS_dzmax % [m] max dz (adaptive SSFM)
SS_dzmin % [m] min dz (adaptive SSFM)
n_waveplates % number of PMD waveplates
+ useGPU % GPU acceleration: true, false, or 'auto' (default)
+ useSingle % Use single precision on GPU (default: false)

% ---- Internal state (persistent between calls) ----
state % struct mirroring legacy 'state'
@@ -56,6 +58,8 @@ classdef DP_Fiber
options.SS_dzmax = 2e4 % m
options.SS_dzmin = 100 % m
options.n_waveplates = 100
+ options.useGPU = 'auto' % 'auto', true, or false
+ options.useSingle = false % single precision GPU
end

% Copy provided options into properties
@@ -208,7 +212,7 @@ classdef DP_Fiber
% 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; 
+ st.brf.simdgd = 0;
% cumsum used in legacy only for debug; keep compatibility variable:
~cumsum(st.brf.db0); % no-op to mirror legacy path

@@ -228,7 +232,18 @@ classdef DP_Fiber
x_in = signal_in(:,1).';
y_in = signal_in(:,2).';

- [x_out, y_out, obj.state] = CNLSE_plain(x_in, y_in, obj.state);
+ % Determine GPU usage
+ if ischar(obj.useGPU) || isstring(obj.useGPU)
+ if strcmpi(obj.useGPU, 'auto')
+ gpuFlag = []; % Let CNLSE_plain auto-detect
+ else
+ error('DP_Fiber:InvalidGPU', 'useGPU must be true, false, or ''auto''');
+ end
+ else
+ gpuFlag = logical(obj.useGPU);
+ end
+
+ [x_out, y_out, obj.state] = CNLSE_plain(x_in, y_in, obj.state, gpuFlag, obj.useSingle);

obj.state.propagated_length = obj.state.propagated_length + obj.state.L;

diff --git a/Classes/02_optical/Optical_Demultiplex.m b/Classes/02_optical/Optical_Demultiplex.m
index 7d7c39e..8e272d2 100644
--- a/Classes/02_optical/Optical_Demultiplex.m
+++ b/Classes/02_optical/Optical_Demultiplex.m
@@ -42,7 +42,7 @@ classdef Optical_Demultiplex < handle

function signalclasses_out = process(obj, signalclass_in)

- % ---- Infer wavelength: either given or from input total signal 
+ % ---- Infer wavelength: either given or from input total signal
if isempty(obj.wavelengthplan)
obj.wavelengthplan = signalclass_in.lambda; %meter
else
@@ -81,7 +81,7 @@ classdef Optical_Demultiplex < handle
obj
signal_in
end
- 
+
w = obj.fs_out ./ obj.fs_in ;
blocklen_in = length(signal_in);
blocklen_out = w*blocklen_in;
@@ -119,30 +119,31 @@ classdef Optical_Demultiplex < handle
N = size(lo,1);
C = size(lo,2);

- x_envelopes = zeros(N, C, 'like', signal_in);
- y_envelopes = zeros(N, C, 'like', signal_in);
+ % ---- VECTORIZED: Process all channels in parallel ----
+ % Batched FFT operates on each column simultaneously on GPU

- s1 = signal_in(:,1);
- s2 = signal_in(:,2);
+ % Extract polarization signals
+ s1 = signal_in(:,1); % X polarization [N×1]
+ s2 = signal_in(:,2); % Y polarization [N×1]

- % Reusable work buffers (avoid reallocations)
- wrk_time = zeros(N,1, 'like', signal_in);
- wrk_freq = zeros(N,1, 'like', signal_in);
+ % Broadcast signal to all channels and multiply with LO
+ % s1, s2 are [N×1], lo is [N×C] → result is [N×C]
+ x_mixed = att .* s1 .* lo; % [N×C]
+ y_mixed = att .* s2 .* lo; % [N×C]
+
+ % Batched FFT: each column computed in parallel
+ x_freq = fft(x_mixed); % [N×C]
+ y_freq = fft(y_mixed); % [N×C]
+
+ % Apply filter (H is [N×1], broadcasts across columns)
+ x_filtered = x_freq .* H; % [N×C]
+ y_filtered = y_freq .* H; % [N×C]
+
+ % Batched IFFT
+ x_envelopes = ifft(x_filtered); % [N×C]
+ y_envelopes = ifft(y_filtered); % [N×C]

- 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
index 1d722b0..7fc8a33 100644
--- a/Classes/02_optical/Optical_Multiplex.m
+++ b/Classes/02_optical/Optical_Multiplex.m
@@ -1,10 +1,10 @@
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 
- 
+ % 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... 
+ % 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,...
@@ -117,7 +117,7 @@ classdef Optical_Multiplex < handle
% 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

@@ -139,25 +139,36 @@ classdef Optical_Multiplex < handle

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;
+ % ---- OPTIMIZED: Pre-compute all LO phases as [blocklen_out × N] matrix ----
+ time_idx = (0:blocklen_out-1).'; % [blocklen_out × 1]
+ lo_phases = mod(2*pi * time_idx * obj.df_T / obj.fs_out, 2*pi); % [blocklen_out × N]
+ lo_all = cos(lo_phases) + 1i*sin(lo_phases); % [blocklen_out × N]

- res_env = ifft(fft(data_in_resampled.signal(:,2)).*H);
- y_envelopes(:,o) = att.*res_env.*lo;
+ % Collect all resampled signals first (still requires loop due to cell array)
+ x_signals = zeros(blocklen_out, N);
+ y_signals = zeros(blocklen_out, N);

+ for o = 1:N
+ data_in_resampled = data_in{o}.resample("fs_out", obj.fs_out);
+ x_signals(:, o) = data_in_resampled.signal(:, 1);
+ y_signals(:, o) = data_in_resampled.signal(:, 2);
end

+ % ---- VECTORIZED: Batched FFT/IFFT for all channels ----
+ % Apply filter to all channels at once
+ x_filtered = ifft(fft(x_signals) .* H); % [blocklen_out × N]
+ y_filtered = ifft(fft(y_signals) .* H); % [blocklen_out × N]
+
+ % Apply attenuation and LO shift to all channels
+ x_envelopes = att .* x_filtered .* lo_all; % [blocklen_out × N]
+ y_envelopes = att .* y_filtered .* lo_all; % [blocklen_out × N]
+
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
diff --git a/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m b/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m
index cfa7294..463adca 100644
--- a/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m
+++ b/Classes/02_optical/dp_fiber_lib/CNLSE_plain.m
@@ -1,41 +1,72 @@

-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};
+function [opt_out_x,opt_out_y,state] = CNLSE_plain(opt_in_x,opt_in_y,state,useGPU,useSingle)
+
+% GPU auto-detection if not specified
+if nargin < 4 || isempty(useGPU)
+ useGPU = canUseGPU();
+end
+if nargin < 5 || isempty(useSingle)
+ useSingle = false;
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% 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

- % 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);
+ %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;
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% GPU Transfer (if enabled)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+if useGPU
+ % Convert to single precision if requested (faster on most GPUs)
+ if useSingle
+ opt_in_x = gpuArray(single(opt_in_x));
+ opt_in_y = gpuArray(single(opt_in_y));
+ state.common_beta.X = gpuArray(single(state.common_beta.X));
+ state.common_beta.Y = gpuArray(single(state.common_beta.Y));
+ state.brf.db1 = gpuArray(single(state.brf.db1));
+ state.brf.db0 = gpuArray(single(state.brf.db0));
+ for k = 1:numel(state.brf.matR)
+ state.brf.matR{k} = gpuArray(single(state.brf.matR{k}));
+ end
+ else
+ opt_in_x = gpuArray(opt_in_x);
+ opt_in_y = gpuArray(opt_in_y);
+ state.common_beta.X = gpuArray(state.common_beta.X);
+ state.common_beta.Y = gpuArray(state.common_beta.Y);
+ state.brf.db1 = gpuArray(state.brf.db1);
+ state.brf.db0 = gpuArray(state.brf.db0);
+ for k = 1:numel(state.brf.matR)
+ state.brf.matR{k} = gpuArray(state.brf.matR{k});
end
-
- %opt_out_struct.(curPol)=opt_in_struct.(curPol).envelope; 
end
+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
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% 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,....
@@ -44,35 +75,33 @@ function [opt_out_x,opt_out_y,state] = CNLSE_plain(opt_in_x,opt_in_y,state)
% [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
+% 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);

- while state.z_prop < state.L 
+state.n_step = 0;
+state.z_prop = 0;
+state.test_dz = [];
+state.powers = [];

- % 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;
+while state.z_prop < state.L

- % append current step length to logbook (dzs)
- state.dzs(state.n_step)=state.dz;
+ % 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,...
+
+ % 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]...
@@ -82,12 +111,12 @@ function [opt_out_x,opt_out_y,state] = CNLSE_plain(opt_in_x,opt_in_y,state)
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,...
+ % 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]...
@@ -97,24 +126,34 @@ function [opt_out_x,opt_out_y,state] = CNLSE_plain(opt_in_x,opt_in_y,state)
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);
+ % 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

- 
+end
+


+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% GPU Gather (if enabled)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+if useGPU
+ opt_out_x = gather(opt_in_x);
+ opt_out_y = gather(opt_in_y);

- opt_out_x = (opt_in_x);
- opt_out_y = (opt_in_y);
+ % Gather state arrays back to CPU
+ state.common_beta.X = gather(state.common_beta.X);
+ state.common_beta.Y = gather(state.common_beta.Y);
+ state.brf.db1 = gather(state.brf.db1);
+ state.brf.db0 = gather(state.brf.db0);
+ for k = 1:numel(state.brf.matR)
+ state.brf.matR{k} = gather(state.brf.matR{k});
+ end
+else
+ opt_out_x = opt_in_x;
+ opt_out_y = opt_in_y;
+end

-% 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/canUseGPU.m b/Classes/02_optical/dp_fiber_lib/canUseGPU.m
new file mode 100644
index 0000000..84666bb
--- /dev/null
+++ b/Classes/02_optical/dp_fiber_lib/canUseGPU.m
@@ -0,0 +1,20 @@
+function canUse = canUseGPU()
+%CANUSEGPU Check if a compatible GPU is available for parallel computing
+% Returns true if MATLAB Parallel Computing Toolbox is available and
+% a CUDA-capable GPU is detected.
+
+canUse = false;
+
+% Check if Parallel Computing Toolbox is installed
+if ~license('test', 'Distrib_Computing_Toolbox')
+ return;
+end
+
+% Check for GPU device
+try
+ gpu = gpuDevice();
+ canUse = gpu.DeviceSupported;
+catch
+ canUse = false;
+end
+end
diff --git a/Classes/02_optical/dp_fiber_lib/getNLstepsize.m b/Classes/02_optical/dp_fiber_lib/getNLstepsize.m
index a70edb9..fd42eba 100644
--- a/Classes/02_optical/dp_fiber_lib/getNLstepsize.m
+++ b/Classes/02_optical/dp_fiber_lib/getNLstepsize.m
@@ -1,25 +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;
+% gather() handles gpuArray inputs - ensures scalar is on CPU for comparisons
+maxPow = gather(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
- 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 
- 
+ %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/lin_step.m b/Classes/02_optical/dp_fiber_lib/lin_step.m
index eea8a1e..cdb3562 100644
--- a/Classes/02_optical/dp_fiber_lib/lin_step.m
+++ b/Classes/02_optical/dp_fiber_lib/lin_step.m
@@ -22,37 +22,37 @@ 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 
+
+ %remember for next step
missing_dz = corr_length - last_plate;
- 
+
end

-plate_steps = repmat(n_step,1,length(plate_sizes));
+plate_steps = repmat(n_step,1,length(plate_sizes)); %#ok<NASGU>

%
%figure;stem(plate_sizes);
@@ -66,50 +66,39 @@ test_plate_numbers = [test_plate_numbers, plate_numbers];
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);
+% Note: db1, db0, common_beta_x, common_beta_y are already on GPU when
+% useGPU=true (transferred in CNLSE_plain)
+db1 = brf.db1;
+db0 = brf.db0;

% 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);
@@ -117,9 +106,8 @@ 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
+% attenuate the signal each linear step with alpha
+rOpt_x=ifft(exp(-alpha_lin_x*aStepSize/2).*opt_x);
+rOpt_y=ifft(exp(-alpha_lin_y*aStepSize/2).*opt_y);

-end
\ No newline at end of file
+end
diff --git a/Classes/04_DSP/Equalizer/EQ.m b/Classes/04_DSP/Equalizer/EQ.m
index f9d58d2..a03d66b 100644
--- a/Classes/04_DSP/Equalizer/EQ.m
+++ b/Classes/04_DSP/Equalizer/EQ.m
@@ -10,10 +10,6 @@ classdef EQ < handle
training_length %Number of training symbols
training_loops %Number of loops through sequence for training mode
ideal_dfe %Error free DFE decisions
- weighted_DFE %Weighted DFE on/off
- weighted_DFE_mode %Weighted DFE mode
- weighted_DFE_d_min %d_min threshold parameter for weighted DFE mode 1
- weighted_DFE_I_mode %[a_s, b_s, I_max]-parameters for the weighted DFE

DB_aim %Aim at duobinary output sequence

@@ -72,10 +68,6 @@ classdef EQ < handle
options.training_length = 1024 %Number of training symbols
options.training_loops = 1 %Number of loops through sequence for training mode
options.ideal_dfe = 0 %Error free DFE decisions
- options.weighted_DFE = 0;
- options.weighted_DFE_mode = 'R1';
- options.weighted_DFE_d_min = 0.5;
- options.weighted_DFE_I_mode = [5,0.5,0.6];

options.DB_aim %Aim at duobinary output sequence

@@ -446,43 +438,6 @@ classdef EQ < handle
dd_out(k) = constellation_in_(dd_idx);
end

- % Implementation of a weighted DFE in
- % order to prevent error propagation.
- % For further details, study [1], chapter 3.2.2 -
- % Modifications of DFE
-
- if obj.weighted_DFE
- % define new constellations
- const = unique(ref_in);
-
- % determine reliability factor gamma_k
- if output_vec(m) > min(const) && output_vec(m) < max(const)
- gamma_k = 1 - abs(output_vec(m) - dd_out(k));
- else
- gamma_k = 1;
- end
-
- % select mode
- if strcmp(obj.weighted_DFE_mode,'R1')
- if gamma_k >= obj.weighted_DFE_d_min
- f_gamma_k = 1;
- else
- f_gamma_k = 0;
- end
- elseif strcmp(obj.weighted_DFE_mode,'R2')
- f_gamma_k = gamma_k;
- elseif strcmp(obj.weighted_DFE_mode,'I1')
- nom = 1-exp(-obj.weighted_DFE_I_mode(1)*((gamma_k/obj.weighted_DFE_I_mode(2))-1));
- denom = 1+exp(-obj.weighted_DFE_I_mode(1)*((gamma_k/obj.weighted_DFE_I_mode(2))-1));
- f_gamma_k = (1/2)*((nom/denom) - 1);
- elseif strcmp(obj.weighted_DFE_mode,'I2')
- nom = 1-exp(-obj.weighted_DFE_I_mode(1)*((gamma_k/obj.weighted_DFE_I_mode(2))-1));
- denom = 1+exp(-obj.weighted_DFE_I_mode(1)*((gamma_k/obj.weighted_DFE_I_mode(2))-1));
- f_gamma_k = (obj.weighted_DFE_I_mode(3)/2)*((nom/denom) - 1);
- end
- % calculate weighted output
- output_vec(m) = f_gamma_k.*dd_out(k)+(1-f_gamma_k).*output_vec(m);
- end

if obj.Nb(1) > 0
dd_DFE(2:end) = dd_DFE(1:end-1);
@@ -784,6 +739,3 @@ classdef EQ < handle
end
end

-% References
-% [1] T. J. Wettlin, “Experimental Evaluation of Advanced Digital Signal Processing for Intra-Datacenter Systems using Direct-Detection,” 2023. [Online]. Available: https://nbn-resolving.org/urn:nbn:de:gbv:8:3-2023-00703-8
-
diff --git a/Classes/04_DSP/Equalizer/ML_MLSE.m b/Classes/04_DSP/Equalizer/ML_MLSE.m
index 9965525..168b837 100644
--- a/Classes/04_DSP/Equalizer/ML_MLSE.m
+++ b/Classes/04_DSP/Equalizer/ML_MLSE.m
@@ -597,7 +597,7 @@ classdef ML_MLSE < handle
obj.S = obj.nSym;
obj.Nf = obj.order * obj.sps;
obj.nStates = obj.S^obj.L;
- obj.nFeasible = obj.nStates * obj.S;
+ obj.nFeasible = obj.nStates * obj.S; %feasible state transitions

% --- Trellis mapping
obj.trellis_states = reshape(obj.constellation,1,[]);
diff --git a/Classes/04_DSP/Timing Recovery/Godard_Timing_Recovery.m b/Classes/04_DSP/Timing Recovery/Godard_Timing_Recovery.m
new file mode 100644
index 0000000..12eeeaf
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/Godard_Timing_Recovery.m
@@ -0,0 +1,112 @@
+classdef Godard_Timing_Recovery < handle
+
+ properties(Access=public)
+ mode
+ num_blocks
+ fft_length
+ sps
+ rolloff
+ mu
+ Ki
+ end
+
+ methods(Access=public)
+ function obj = Godard_Timing_Recovery(options)
+ arguments(Input)
+
+ options.mode = 0;
+ options.num_blocks = 1;
+ options.fft_length = 1024;
+ options.sps = 2;
+ options.rolloff = 0;
+ options.mu = 0;
+ options.Ki = 1e-3;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function [data_out, tau_hat] = process(obj, data_in)
+
+ data_out = data_in;
+ output_vector = zeros(length(data_in),1);
+ block_length = length(data_in)/obj.num_blocks;
+ for i = 1:obj.num_blocks
+ i_start = ((i-1)*block_length)+1;
+ i_end = i*block_length;
+ x = data_in.signal(i_start:i_end);
+ beta = obj.rolloff;
+ eta = obj.sps;
+ R = fft(x,obj.fft_length);
+ R_full = fft(x);
+ 
+ if obj.mode == 0 % Classic Godard
+ % Calculate time shift
+ k0 = (1:obj.fft_length/2).';
+ idx1 = k0;
+ idx2 = k0+(obj.fft_length/2);
+ tau_hat = sum(imag(R(idx1) .* conj(R(idx2))));
+ elseif obj.mode == 1 || obj.mode == 2 % Modified Godard 1
+ % Calculate Shift for the received signal
+ shiftBins = (1 - 1/eta) * obj.fft_length;
+ if abs(shiftBins - round(shiftBins)) > 1e-12
+ disp('Warning: shiftBins=(1-1/eta)*fft_length is non-integer. Choose compatible values for fft_length and eta.');
+ end
+ shiftBins = round(shiftBins);
+ 
+ % Calculate upper and lower bounds
+ kStart = ((1-beta)/(2*eta)) * obj.fft_length;
+ kEnd = ((1+beta)/(2*eta)) * obj.fft_length - 1;
+ if abs(kStart - round(kStart)) > 1e-12 || abs(kEnd - round(kEnd)) > 1e-12
+ disp('Warning: kStart/kEnd are non-integer. Choose compatible values for fft_length, eta, and beta.');
+ end
+ kStart = round(kStart);
+ kEnd = round(kEnd);
+ 
+ k0 = (kStart:kEnd).';
+ idx1 = k0 + 1;
+ idx2 = mod(k0 + shiftBins, obj.fft_length) + 1;
+ 
+ % Calculate time shift 
+ if obj.mode == 1
+ tau_hat = sum(imag(R(idx1) .* conj(R(idx2))));
+ elseif obj.mode == 2
+ tau_hat = sum(angle(R(idx1))-angle(R(idx2)));
+ end
+ end
+
+ 
+ % % Normalization
+ % denom = floor(log10(abs(tau_hat)));
+ % tau_hat = tau_hat / 10^denom;
+
+ % Calculate mu using a first-order loop filter
+ mu_block = obj.mu + obj.Ki * tau_hat;
+
+ % % Shifting the signal in time domain using interpolation
+ % x_original = linspace(0,length(x)-1,length(x)).';
+ % x_new = x_original + mu_block;
+ % output_vector(i_start:i_end) = interp1(x_original,x,x_new,'linear','extrap');
+ 
+ % Shifting the signal in frequency domain
+ k = (0:block_length-1).';
+ phaseRamp = exp(-1j * 2*pi * (k/block_length) * mu_block);
+ R_shifted = R_full .* phaseRamp;
+
+ output_vector(i_start:i_end) = real(ifft(R_shifted));
+
+ end
+ data_out.signal = output_vector;
+ end
+
+
+ end
+end
+
diff --git a/Classes/04_DSP/Timing Recovery/MaxVar_Timing_Recovery.m b/Classes/04_DSP/Timing Recovery/MaxVar_Timing_Recovery.m
new file mode 100644
index 0000000..d6517d5
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/MaxVar_Timing_Recovery.m
@@ -0,0 +1,111 @@
+classdef MaxVar_Timing_Recovery < handle
+
+ properties(Access=public)
+ mode
+ sps
+ fsym
+ fadc
+ num_tau
+ comp_signal
+ comp_mode
+ end
+
+ methods(Access=public)
+ function obj = MaxVar_Timing_Recovery(options)
+ arguments(Input)
+
+ options.mode = 0;
+ options.sps = 2;
+ options.fsym = 32e9;
+ options.fadc = 80e9;
+ options.num_tau = 64;
+ options.comp_signal = 0;
+ options.comp_mode = 0;
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function [data_out] = process(obj, data_in)
+
+ data_out = data_in;
+ x = data_in.signal;
+
+ if obj.comp_mode
+ our_signal = data_in;
+ their_signal = obj.comp_signal;
+ end
+
+ if obj.mode == 0
+ vars = zeros(obj.sps,1);
+ for phi = 1:obj.sps
+
+ % Test variance for different start samples
+ y_phi = x(phi:obj.sps:end);
+ vars(phi) = var(y_phi);
+
+ % Comparison to reference signal
+ if obj.comp_mode
+ our_signal.signal = x(phi:obj.sps:end);
+ our_signal.fs = 6e9;
+ our_signal.normalize("mode","rms").plot("displayname",['Our signal, ' num2str(vars(phi))],'fignum',1231+phi);
+ their_signal.normalize("mode","rms").plot("displayname",'Their signal','fignum',1231+phi);
+ end
+
+ end
+ 
+ % Choose signal configuration with the maximum variance
+ [~,phi_opt] = max(vars);
+ 
+ y = x(phi_opt:obj.sps:end).'; 
+
+ elseif obj.mode == 1
+ % Create a grid with different (sub)sample starting points
+ T = 1/obj.fsym;
+ t = (0:length(x)-1)/obj.fadc;
+ tauGrid = linspace(0, T, obj.num_tau);
+ 
+ % Interpolate the signal starting from every defined point
+ % and calculate the MMSE/variance
+ vars = zeros(size(tauGrid));
+ for i = 1:numel(tauGrid)
+ tau = tauGrid(i);
+ % tk = linspace(tau, t(end), length(x)/obj.sps);
+ tk = tau : T : t(end)+tau;
+ xk = interp1(t, x, tk, 'linear', 'extrap');
+ if obj.comp_mode % MMSE
+ e = xk.' - obj.comp_signal.signal;
+ vars(i) = mean(abs(e).^2);
+ else % Variance
+ vars(i) = var(xk, 1);
+ end
+ end
+ 
+ if obj.comp_mode % MMSE
+ [~,idx] = min(vars);
+ else % Variance
+ [~,idx] = max(vars);
+ end
+ tau_opt = tauGrid(idx);
+ 
+ % Choosing the signal at optimum
+ % tk = linspace(tau_opt, t(end), length(x)/obj.sps);
+ tk = tau_opt : T : t(end)+tau_opt;
+ y = interp1(t, x, tk, 'linear', 'extrap');
+ end
+ % if length(y) ~= length(x)/obj.sps
+ % y = [y 0];
+ % end
+ data_out.signal = y.';
+ end
+
+
+ end
+end
+
diff --git a/Classes/04_DSP/Timing Recovery/Time_Shifter.m b/Classes/04_DSP/Timing Recovery/Time_Shifter.m
new file mode 100644
index 0000000..f08e3fe
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/Time_Shifter.m
@@ -0,0 +1,39 @@
+classdef Time_Shifter < handle
+
+ properties(Access=public)
+ value
+ end
+
+ methods(Access=public)
+ function obj = Time_Shifter(options)
+ arguments(Input)
+
+ options.value = 0;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function [data_out, tau_hat] = process(obj, data_in)
+
+ data_out = data_in;
+ x = data_in.signal;
+ R = fft(x);
+ k = (0:length(R)-1).';
+ phaseRamp = exp(-1j * 2*pi * (k/length(R)) * obj.value);
+ R_shifted = R .* phaseRamp;
+ data_out.signal = real(ifft(R_shifted));
+ 
+ end
+
+
+ end
+end
+
diff --git a/Classes/04_DSP/Timing Recovery/Timing_Recovery.m b/Classes/04_DSP/Timing Recovery/Timing_Recovery.m
new file mode 100644
index 0000000..3999ad7
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/Timing_Recovery.m
@@ -0,0 +1,49 @@
+classdef Timing_Recovery < handle
+
+ properties(Access=public)
+ modulation
+ timing_error_detector
+ sps
+ damping_factor
+ normalized_loop_bandwidth
+ detector_gain
+ end
+
+ methods(Access=public)
+ function obj = Timing_Recovery(options)
+ arguments(Input)
+
+ options.modulation = 'PAM/PSK/QAM';
+ options.timing_error_detector = 'Gardner (non-data-aided)';
+ options.sps = 2;
+ options.damping_factor = 1.0;
+ options.normalized_loop_bandwidth = 0.01;
+ options.detector_gain = 2.7;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function [data_out, timing_error] = process(obj, data_in)
+
+ timing_synchronization = comm.SymbolSynchronizer( ...
+ "Modulation", obj.modulation,...
+ "TimingErrorDetector", obj.timing_error_detector, ...
+ "SamplesPerSymbol", obj.sps, ...
+ "DampingFactor", obj.damping_factor, ...
+ "NormalizedLoopBandwidth", obj.normalized_loop_bandwidth, ...
+ "DetectorGain", obj.detector_gain);
+ data_out = data_in;
+ [data_out.signal, timing_error] = timing_synchronization(data_in.signal);
+
+ end
+ end
+end
+
diff --git a/Classes/04_DSP/Timing Recovery/Timing_Recovery_GPT.m b/Classes/04_DSP/Timing Recovery/Timing_Recovery_GPT.m
new file mode 100644
index 0000000..33d0509
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/Timing_Recovery_GPT.m
@@ -0,0 +1,77 @@
+classdef Timing_Recovery_GPT < handle
+
+ properties(Access=public)
+ sps
+ muGrid
+ end
+
+ methods(Access=public)
+ function obj = Timing_Recovery_GPT(options)
+ arguments(Input)
+
+ options.sps = 2;
+ options.muGrid = 0;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function [data_out, mu_best, score] = process(obj, data_in)
+ %MAXVARTIMINGSYNC Choose sampling phase mu that maximizes variance of downsampled symbols.
+ %
+ % data_in : matched-filtered samples (complex or real), length N
+ % sps : samples per symbol (here typically 2)
+ % muGrid : candidate fractional offsets in [0,1)
+ %
+ % data_out : symbol-rate samples (length floor(N/sps))
+ % mu_best: chosen fractional offset
+ % score : variance score for each mu in muGrid
+
+ data_out = data_in;
+ 
+ x = data_in.signal(:);
+ N = length(x);
+ Ns = floor(N/obj.sps);
+ 
+ if nargin < 3 || isempty(obj.muGrid)
+ obj.muGrid = linspace(0, 0.99, 101); % 0..0.99 in ~0.01 steps
+ end
+ 
+ % Symbol indices (1-based sample positions)
+ n0 = 1; % start sample index
+ k = (0:Ns-1).';
+ tBase = n0 + k*obj.sps; % integer times (1, 1+sps, ...)
+ 
+ score = zeros(numel(obj.muGrid),1);
+ 
+ for m = 1:numel(obj.muGrid)
+ mu = obj.muGrid(m);
+ t = tBase + mu;
+ 
+ % Linear fractional sampling
+ y = interp1(1:N, x, t, 'linear', 'extrap');
+ 
+ % For PAM, maximize variance of real part (or abs if you prefer)
+ yr = real(y);
+ score(m) = var(yr, 1); % use population variance (normalization doesn't matter for argmax)
+ end
+ 
+ % Pick best mu
+ [~, idx] = max(score);
+ mu_best = obj.muGrid(idx);
+ 
+ % Resample with best mu
+ t = tBase + mu_best;
+ data_out.signal = interp1(1:N, x, t, 'linear', 'extrap');
+
+ end
+ end
+end
+
diff --git a/Classes/04_DSP/Timing Recovery/Timing_Recovery_Move_It.m b/Classes/04_DSP/Timing Recovery/Timing_Recovery_Move_It.m
new file mode 100644
index 0000000..bbd0578
--- /dev/null
+++ b/Classes/04_DSP/Timing Recovery/Timing_Recovery_Move_It.m
@@ -0,0 +1,77 @@
+classdef Timing_Recovery_Move_It < handle
+
+ properties(Access=public)
+ f_sim
+ gamma
+ end
+
+ methods(Access=public)
+ function obj = Timing_Recovery_Move_It(options)
+ arguments(Input)
+
+ options.f_sim = 14e9;
+ options.gamma = 0.1;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ %obj-Initialization here%
+
+ end
+
+ function data_out = process(obj, data_in)
+
+ e = NaN(size(data_in.signal));
+ T = 1/obj.f_sim;
+ t = zeros(size(data_in.signal));
+ t(2) = T/2;
+ mu = zeros(size(data_in.signal));
+
+% n = 2;
+ for k = 3:2:length(data_in.signal)
+ data_in.signal(k-1) = data_in.signal(k-1)*(1-mu(k-2)) + data_in.signal(k)*mu(k-2);
+ data_in.signal(k) = data_in.signal(k)*(1-mu(k-1)) + data_in.signal(k+1)*mu(k-1);
+
+ % TED
+ e(k) = (data_in.signal(k-2)-data_in.signal(k))*data_in.signal(k-1); 
+ e(k+1) = e(k);
+ 
+ % TED Mueller Mueller
+% e(k) = ref_in(n-1)*data_in.signal(k) - ref_in(n)*data_in.signal(k-2);
+% e(k+1) = e(k);
+% n = n+1;
+% 
+ % interpolator control
+% t(k) = t(k-1) + T/2 + obj.gamma/2*e(k);
+% t(k+1) = t(k) + T/2 + obj.gamma/2*e(k+1);
+ t(k) = t(k-1) + T/2 + obj.gamma*e(k)*T/2;
+ t(k+1) = t(k) + T/2 + obj.gamma*e(k+1)*T/2;
+% t(k) = k*T/2 + obj.gamma*e(k)*T/2;
+% t(k+1) = k*T/2 + obj.gamma*e(k+1)*T/2;
+
+ % interpolator
+ mu(k) = t(k)/(T/2) - round(t(k)/(T/2));
+ mu(k+1) = t(k+1)/(T/2) - round(t(k+1)/(T/2)); 
+ 
+ thres = 0.7;
+ if mu(k)-mu(k-1) > thres
+ mu(k) = mu(k) - 1;
+ elseif mu(k) - mu(k-1) < -thres
+ mu(k) = mu(k) + 1;
+ end
+ 
+ if mu(k+1)-mu(k) > thres
+ mu(k+1) = mu(k+1) - 1;
+ elseif mu(k+1) - mu(k) < -thres
+ mu(k+1) = mu(k+1) + 1;
+ end
+ end
+ data_out = data_in;
+ end
+ end
+end
+
diff --git a/Classes/04_DSP/Timing_Recovery.m b/Classes/04_DSP/Timing_Recovery.m
new file mode 100644
index 0000000..23cc331
--- /dev/null
+++ b/Classes/04_DSP/Timing_Recovery.m
@@ -0,0 +1,47 @@
+classdef Timing_Recovery < handle
+
+ properties(Access=public)
+ timing_error_detector
+ sps
+ damping_factor
+ normalized_loop_bandwidth
+ detector_gain
+ end
+
+ methods(Access=public)
+ function obj = Timing_Recovery(options)
+ arguments(Input)
+
+ options.timing_error_detector = 'Gardner';
+ options.sps = 2;
+ options.damping_factor = 1.0;
+ options.normalized_loop_bandwidth = 0.005;
+ options.detector_gain = 1;
+
+ end
+
+ fn = fieldnames(options);
+ for n = 1:numel(fn)
+ obj.(fn{n}) = options.(fn{n});
+ end
+
+ 
+
+ end
+
+ function [data_out,timing_error] = process(obj, data_in)
+
+ timing_synchronization = comm.SymbolSynchronizer( ...
+ "TimingErrorDetector", obj.timing_error_detector, ...
+ "SamplesPerSymbol", obj.sps, ...
+ "DampingFactor", obj.damping_factor, ...
+ "NormalizedLoopBandwidth", obj.normalized_loop_bandwidth, ...
+ "DetectorGain", obj.detector_gain);
+
+ data_out = data_in;
+ [data_out.signal,timing_error] = timing_synchronization(data_in.signal);
+
+ end
+ end
+end
+
diff --git a/Classes/DataBaseHandler/DBHandler.m b/Classes/DataBaseHandler/DBHandler.m
index 5569746..b7ba026 100644
--- a/Classes/DataBaseHandler/DBHandler.m
+++ b/Classes/DataBaseHandler/DBHandler.m
@@ -23,6 +23,12 @@ classdef DBHandler < handle
arguments
options.dataBase = ""; % Default value for pathToDB if not provided
options.type = "mysql";
+ options.server = "";
+ options.port = 3306;
+ options.user = "";
+ options.password = "";
+ 
+
end

% Assign values to class properties based on input arguments
@@ -46,12 +52,13 @@ classdef DBHandler < handle

obj.conn = database( ...
string(obj.dataBase), ... % Database name
- "silas", ... % Username
- "silas", ... % Password (or getSecret)
+ options.user, ... % Username
+ options.password, ... % Password (or getSecret)
"Vendor", "MySQL", ...
- "Server", "134.245.243.254", ...
+ "Server", options.server, ...
"PortNumber", 3306, ...
"JDBCDriverLocation", "C:\Users\Silas\Documents\mysql-connector-j-9.3.0\mysql-connector-j-9.3.0.jar");
+ 
end

catch e
diff --git a/Functions/EQ_structures/ml_mlse.m b/Functions/EQ_structures/ml_mlse.m
index 84ff5cb..04fe317 100644
--- a/Functions/EQ_structures/ml_mlse.m
+++ b/Functions/EQ_structures/ml_mlse.m
@@ -54,12 +54,6 @@ for k = 1:numel(fn)
end
json_str = jsonencode(eq_small);

-fn = fieldnames(eq_);
-for k = 1:numel(fn)
- if issparse(eq_.(fn{k}))
- eq_.(fn{k}) = full(eq_.(fn{k}));
- end
-end
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';
@@ -93,7 +87,6 @@ end
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);
-skip_front = 150;

switch precode_mode
case db_mode.no_db
@@ -108,11 +101,11 @@ switch precode_mode
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", skip_front, "skip_end", 150, "returnErrorLocation", 1);
+ [~, 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", skip_front, "skip_end", 150, "returnErrorLocation", 1);
+ [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
@@ -120,12 +113,12 @@ switch precode_mode
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", skip_front, "skip_end", 150, "returnErrorLocation", 1);
+ [~, 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", skip_front, "skip_end", 150, "returnErrorLocation", 1);
+ [bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
end
end

diff --git a/Functions/Theory/Dissertation/mach_zehnder_modulator.m b/Functions/Theory/Dissertation/mach_zehnder_modulator.m
new file mode 100644
index 0000000..f9eca58
--- /dev/null
+++ b/Functions/Theory/Dissertation/mach_zehnder_modulator.m
@@ -0,0 +1,219 @@
+
+
+% Parameters
+c0 = physconst('lightspeed'); % [m/s]
+lambda0 = 1310e-9; % [m]
+omega0 = 2*pi*c0/lambda0;
+
+L = 5e-3; % [m] effective phase section length (set as needed)
+n_eff = 2.2; % [-] effective index (set as needed)
+
+E0 = 1; % field amplitude (arbitrary)
+Vpi = 3.2; % [V] half-wave voltage (your V_pi)
+
+% Drive
+f0 = 1e9; % [Hz]
+fs = 200e9; % [Hz]
+Nper = 2; % number of periods
+Vpp = 0.6*Vpi; % [V] peak-to-peak of v_drive(t)
+
+biasV = 1.1; % [V] differential bias added to v_drive
+
+% Time axis + differential drive voltage v_drive(t)
+T = Nper/f0;
+t = (0:1/fs:T-1/fs).';
+
+
+if 1
+ % SINE
+ v_drive = biasV + (Vpp/2)*sin(2*pi*f0*t); % v_drive(t) (peak = Vpp/2)
+
+else
+
+ % --- Generate PAM-4 Sequence ---
+ symbols = linspace(-0.5, 0.5, 4);
+ num_symbols = 12; % Increased slightly for better visual
+ rng(44);
+ random_data = symbols(randi(4, 1, num_symbols));
+
+ % Create time axis (Note: T is your period from the sine code)
+ sps = round(T * fs);
+ t = (0:1/fs:(num_symbols*T)-1/fs).';
+
+ % Upsample to rectangular waveform
+ v_pam = repelem(random_data, sps).';
+
+ % Apply swing and bias: Resulting range is [biasV-Vpp/2, biasV+Vpp/2]
+ v_drive_rect = biasV + (v_pam * Vpp);
+
+ % --- Round the edges ---
+ filter_span = round(sps/1.5); % Increased span for smoother "rounding"
+ window = gausswin(filter_span);
+ window = window / sum(window);
+
+ % Apply filter (using 'same' to keep vector length, but be aware of edge transients)
+ v_drive = conv(v_drive_rect, window, 'same');
+
+end
+
+
+% Analytic
+v_ = linspace(-1,2, 2001);
+% Field transfer function (amplitude)
+Field_mzm_analytic = cos((pi/2)*v_);
+
+% Power transfer function (intensity)
+P_mzm_analytic = Field_mzm_analytic.^2;
+
+% Imbalance factor in YOUR notation:
+rho = 1; 
+
+% Push-pull branch voltages (consistent with v_drive = v1 - v2)
+v1 = +0.5*v_drive; % arm 1
+v2 = -0.5*v_drive; % arm 2
+
+% Phases phi1, phi2
+phi1 = pi * v1 / Vpi;
+phi2 = pi * v2 / Vpi;
+
+% Fields: E_in and E_out (exactly your Eq. (mzm_e_field))
+E_in = E0 .* exp(1i*omega0*t);
+
+common_phase = exp(-1i * (omega0*L*n_eff/c0)); % exp(-j*omega0*L*n_eff/c0)
+
+E_out = E0 .* exp(1i*omega0*t) .* common_phase .* 0.5 .* ...
+ ( exp(-1i*phi1) + rho .* exp(-1i*phi2) );
+
+% Transfer function (numerical): E_out/E_in
+H_num = E_out ./ E_in;
+
+% Power (normalized)
+Pnorm_num = abs(H_num).^2; % since |E_out/E_in|^2
+
+% Ideal TF (analytic) for comparison (rho=1, push-pull)
+H_ideal = common_phase .* cos( (pi/2) * (v_drive./Vpi) );
+
+Pnorm_ideal = abs(H_ideal).^2;
+Pnorm_math = cos( (pi/2) * (v_drive./Vpi) ).^2;
+
+
+set(groot, 'defaultLegendInterpreter', 'tex');
+set(groot, 'defaultAxesTickLabelInterpreter', 'tex');
+set(groot, 'defaultTextInterpreter', 'tex');
+
+% Normalized voltage axis (multiples of Vpi)
+v_norm = v_drive./Vpi;
+
+colfield = [0,0,0]; %is black
+colpow = linspecer(2);
+colpow = colpow(1,:);
+colvdrive = linspecer(2);
+colvdrive = colvdrive(2,:);
+
+%% SIGNAL IN
+figure(1); clf
+plot(v_norm,t*1e9, 'LineWidth', 1.0,'Color',colvdrive); grid on;
+ylabel('t [ns]'); xlabel('v_{drive}(t)/V_\pi'); 
+title('Drive voltage (normalized)');
+xlim([min(v_) max(v_)]);
+% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\linear_casee\mzm_input_signal.tex');
+
+
+%% IN/OUT (static transfer) — normalized x-axis + analytic curve
+if 0
+figure(2); clf
+plot(v_, Field_mzm_analytic, 'LineWidth', 1.2,'LineStyle','--','Color',colfield); hold on;% analytic power TF
+plot(v_, P_mzm_analytic, 'LineWidth', 1.2, 'Color',colpow); hold on;% analytic power TF
+% show input time signal
+plot(v_norm,-1+t*1e9, 'LineWidth', 1.0,'Color',colvdrive); grid on;
+% show output time signal
+plot(2+t*1e9, Pnorm_num, 'LineWidth', 1.0,'DisplayName','Intensity', 'Color',colvdrive); hold on;
+plot(2+t*1e9, real(H_ideal), '--', 'LineWidth', 1.0,'DisplayName','Field','Color',colfield); hold on;
+scatter(v_norm, Pnorm_num, 12, '.', 'LineWidth', 1,'MarkerEdgeColor',colvdrive); 
+scatter(biasV./Vpi,(cos((pi/2)*biasV./Vpi)^2),10,'Marker','o');
+line([min(v_drive), min(v_drive)]./Vpi,[(cos((pi/2)*min(v_drive)./Vpi)^2), -2],'linewidth',0.5,'color','black','linestyle','--');
+line([max(v_drive) max(v_drive)]./Vpi,[(cos((pi/2)*max(v_drive)./Vpi)^2), -2],'linewidth',0.5,'color','black','linestyle','--');
+xline([min(v_norm) max(v_norm)])
+
+grid on;
+xlabel('v_{drive}(t)/V_\pi'); ylabel('|E_{out}/E_{in}|^2');
+% legend
+xlim([min(v_) max(v_)+1]);
+ylim([-1 1]);
+
+% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\mzm.tex');
+end
+%%
+
+figure(3); clf
+plot(v_, Field_mzm_analytic, 'LineWidth', 1.2,'LineStyle','--','Color',colfield); hold on;% analytic power TF
+plot(v_, P_mzm_analytic, 'LineWidth', 1.2, 'Color',colpow); hold on;% analytic power TF
+
+scatter(v_norm, Pnorm_num, 12, '.', 'LineWidth', 1,'MarkerEdgeColor',colvdrive); 
+scatter(biasV./Vpi,(cos((pi/2)*biasV./Vpi)^2),10,'Marker','o');
+line([min(v_drive), min(v_drive)]./Vpi,[(cos((pi/2)*min(v_drive)./Vpi)^2), -2],'linewidth',0.5,'color','black','linestyle','--');
+line([max(v_drive) max(v_drive)]./Vpi,[(cos((pi/2)*max(v_drive)./Vpi)^2), -2],'linewidth',0.5,'color','black','linestyle','--');
+xline([min(v_norm) max(v_norm)])
+
+grid on;
+xlabel('v_{drive}(t)/V_\pi'); ylabel('|E_{out}/E_{in}|^2');
+% legend
+xlim([min(v_) max(v_)]);
+ylim([-1 1]);
+
+% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\mzm_tramsfer_function_matlab.tex');
+
+
+%% 
+
+figure(4); clf
+% plot(v_, Field_mzm_analytic, 'LineWidth', 1.2,'LineStyle','--','Color',colfield); hold on;% analytic power TF
+plot(v_, P_mzm_analytic, 'LineWidth', 1.2, 'Color','black'); hold on;% analytic power TF
+input_dots = linspace(min(v_drive),max(v_drive),4)./Vpi;
+% input_dots = unique(v_drive_rect)./Vpi;
+output_dots = (cos((pi/2)*input_dots).^2);
+scatter(input_dots,output_dots,'Marker','x','LineWidth',1,'MarkerEdgeColor','black');
+scatter(input_dots,zeros(size(input_dots)),'Marker','^','LineWidth',2,'MarkerEdgeColor','black');
+% scatter(ones(size(input_dots)),output_dots,'Marker','<','LineWidth',2,'MarkerEdgeColor','black');
+
+for i = 1:numel(input_dots)
+ % Draw the dashed projection lines
+ line([input_dots(i), input_dots(i)], [output_dots(i), 0], 'linewidth', 0.5, 'color', 'black', 'linestyle', '--', 'handlevisibility', 'off');
+ line([input_dots(i), 1], [output_dots(i), output_dots(i)], 'linewidth', 0.5, 'color', 'black', 'linestyle', '--', 'handlevisibility', 'off');
+ 
+ % Add the level annotation boxes near the output (y-axis)
+ % Adjust the '1.05' to move the box further right or 'output_dots(i)' for height
+ j = 3-(i-1)*2;
+ text(1, output_dots(i), sprintf('Level %d', j), ...
+ 'FontSize', 8, ...
+ 'EdgeColor', 'black', ...
+ 'BackgroundColor', 'white', ...
+ 'Margin', 2);
+end
+
+xlim([0,1.5]);
+ylim([0,1])
+
+% line([min(v_drive), min(v_drive)]./Vpi,[(cos((pi/2)*min(v_drive)./Vpi)^2), 0],'linewidth',0.5,'color','black','linestyle','--');
+% line([max(v_drive), max(v_drive)]./Vpi,[(cos((pi/2)*max(v_drive)./Vpi)^2), 0],'linewidth',0.5,'color','black','linestyle','--');
+
+% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\linear_casee\mzm_tf.tex');
+% xticks(sort(input_dots));
+% yticks(sort(output_dots));
+grid off
+
+
+%% 
+% % FIELD TF (only field here; do not mix power into this figure)
+figure(5); clf
+% plot(t*1e9, real(H_num), 'LineWidth', 1.0); hold on;
+% plot(t*1e9, real(H_ideal), '--', 'LineWidth', 1.0,'DisplayName','Field','Color',colfield); hold on;
+plot(t*1e9, Pnorm_num, 'LineWidth', 1.0,'DisplayName','Intensity', 'Color',colpow); hold on;
+grid on;
+xlabel('t [ns]'); ylabel('Re\{E_{out}/E_{in}\}');
+legend
+yticks(sort(output_dots));
+% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\linear_casee\mzm_output_signal.tex');
+
+
+
diff --git a/Functions/mat2tikz_improved.m b/Functions/mat2tikz_improved.m
new file mode 100644
index 0000000..a36b1d1
--- /dev/null
+++ b/Functions/mat2tikz_improved.m
@@ -0,0 +1,23 @@
+function mat2tikz_improved(filename)
+arguments
+ % Default to the path in your example if no argument is provided
+ filename (1,1) string = 'C:\Users\Silas\Documents\Dissertation\00_Examples\tikz\textfig.tikz';
+end
+cleanfigure;
+matlab2tikz(char(filename), ...
+ 'width','\fwidth', ...
+ 'height','\fheight', ...
+ 'showInfo',false, ...
+ 'extraAxisOptions',{ ...
+ 'legend style={font=\footnotesize}', ...
+ 'xlabel style={font=\color{white!15!black},font=\small},',...
+ 'ylabel style={font=\color{white!15!black},font=\small},',...
+ 'legend columns=1', ...
+ 'every axis/.append style={font=\scriptsize}',...
+ 'legend columns=1',...
+ 'legend style={at={(0.02,0.98)},font=\footnotesize,draw=black!60,rounded corners=2pt,inner sep=1pt,fill=white,column sep=6pt,anchor= north west}',...
+ 'legend style={at={(0.02,0.98)},draw=white!0!white,font=\scriptsize,inner sep=0.1pt,fill=white,column sep=1pt,anchor= north west}',...
+ 'every axis/.append style={font=\scriptsize}',...
+ });
+
+end
\ No newline at end of file
diff --git a/Libs/wesanderson_colors/WesPalette.m b/Libs/wesanderson_colors/WesPalette.m
index 7cd1dcb..13e976f 100644
--- a/Libs/wesanderson_colors/WesPalette.m
+++ b/Libs/wesanderson_colors/WesPalette.m
@@ -1,10 +1,13 @@
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
+ % cmap = WesPalette.Zissou1.rgb() % full palette
+ % cmap = WesPalette.Zissou1.rgb(3) % 3 colors (discrete default)
+ % cmap = WesPalette.Zissou1.rgb(12,"discrete") % any n, no interpolation
+ % cmap = WesPalette.Zissou1.rgb(256,"continuous") % smooth colormap (Lab interpolation)
+ %
+ % Requires:
+ % - colorspace.m (Pascal Getreuer) on MATLAB path for "continuous" mode

enumeration
BottleRocket1
@@ -34,21 +37,33 @@ classdef WesPalette
end

methods
- function cmap = rgb(obj, n)
+ function cmap = rgb(obj, n, mode)
% Return palette as Nx3 RGB colormap [01]
+ %
+ % n : number of requested colors (optional)
+ % mode : "discrete" (default) or "continuous"

- hex = obj.hex();
+ base_hex = obj.hex();
+ base_rgb = WesPalette.hex2rgb(base_hex);

- rgb = hex2rgb(hex);
+ if nargin < 2 || isempty(n)
+ cmap = base_rgb;
+ return;
+ end
+ if nargin < 3 || isempty(mode)
+ mode = "discrete";
+ end
+ mode = lower(string(mode));

- 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,:);
+ validateattributes(n, {'numeric'}, {'scalar','integer','positive'}, mfilename, 'n');
+ if mode ~= "discrete" && mode ~= "continuous"
+ error('mode must be "discrete" or "continuous".');
+ end
+
+ if mode == "discrete"
+ cmap = WesPalette.sample_discrete(base_rgb, n);
else
- cmap = rgb;
+ cmap = WesPalette.interpolate_continuous_lab(base_rgb, n);
end
end
end
@@ -56,78 +71,116 @@ classdef WesPalette
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
+
+ methods (Static, Access = private)
+ function rgb = hex2rgb(hex)
+ % hex: cellstr like {'#RRGGBB', ...}
+ if isstring(hex), hex = cellstr(hex); end
+ n = numel(hex);
+ rgb = zeros(n,3);
+ for i = 1:n
+ h = char(hex{i});
+ if startsWith(h,'#'), h = h(2:end); end
+ if numel(h) ~= 6
+ error('Invalid HEX color: %s', hex{i});
+ end
+ rgb(i,1) = hex2dec(h(1:2))/255;
+ rgb(i,2) = hex2dec(h(3:4))/255;
+ rgb(i,3) = hex2dec(h(5:6))/255;
+ end
+ end
+
+ function cmap = sample_discrete(base_rgb, n)
+ % No interpolation; allow any n by sampling/repeating.
+ k = size(base_rgb,1);
+
+ if n <= k
+ idx = round(linspace(1, k, n)); % spread across palette
+ idx = max(1, min(k, idx));
+ cmap = base_rgb(idx,:);
+ else
+ reps = floor(n / k);
+ rmd = mod(n, k);
+ cmap = [repmat(base_rgb, reps, 1); base_rgb(1:rmd,:)];
+ end
+ end
+
+ function cmap = interpolate_continuous_lab(base_rgb, n)
+ % Smooth interpolation in Lab using colorspace().
+ % Requires colorspace.m by Pascal Getreuer on MATLAB path.
+
+ k = size(base_rgb,1);
+ if k == 1
+ cmap = repmat(base_rgb, n, 1);
+ return;
+ end
+
+ % Convert to Lab, interpolate each channel, convert back
+ lab = colorspace('Lab<-RGB', base_rgb);
+
+ t_base = linspace(0, 1, k);
+ t_new = linspace(0, 1, n);
+
+ lab_new = zeros(n,3);
+ for c = 1:3
+ lab_new(:,c) = interp1(t_base, lab(:,c), t_new, 'linear');
+ end
+
+ rgb_new = colorspace('RGB<-Lab', lab_new);
+
+ % Clamp to displayable gamut
+ cmap = min(max(rgb_new, 0), 1);
+ end
+ end
end
diff --git a/Libs/wesanderson_colors/minimal_example_wespalette.m b/Libs/wesanderson_colors/minimal_example_wespalette.m
index 9fcd99b..f308f27 100644
--- a/Libs/wesanderson_colors/minimal_example_wespalette.m
+++ b/Libs/wesanderson_colors/minimal_example_wespalette.m
@@ -5,8 +5,8 @@ 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);
+cmap = WesPalette.AsteroidCity1;
+% 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); 
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_FFE_DFE.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_FFE_DFE.m
new file mode 100644
index 0000000..29069ce
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_FFE_DFE.m
@@ -0,0 +1,36 @@
+taps_ffe = [200, 3, 2];
+taps_dfe = [5, 2, 1];
+trlen = 4096*2;
+trloops = 5;
+ddloops = 5;
+K_FFE = 1;
+DCmu = 0.005;
+mu_ffe_values = [0.001, 0.0005, 0.001];
+mu_dfe_values = 0.0007;
+DDmu = [mu_ffe_values, mu_dfe_values];
+DFEmu = 0.004;
+FFEmu = 0;
+plot_final = 0;
+idealdfe = 1;
+num_pf_coeffs = 1;
+damp_factor = 1;
+norm_loop_bw = 0.01;
+det_gain = 2.7;
+
+step_size = 0.0001;
+
+BER = [];
+taps_ffe_and_dfe = zeros(1,6);
+for i = 1:6
+ for j = 1:200
+ taps_ffe_and_dfe(i) = j;
+ BER_run = first_analysis_2(taps_ffe_and_dfe(1:3), taps_ffe_and_dfe(4:6), trlen, trloops, ddloops, ...
+ K_FFE, DCmu, DDmu, DFEmu, FFEmu, plot_final, idealdfe, num_pf_coeffs, ...
+ damp_factor, norm_loop_bw, det_gain);
+ BER = [BER, BER_run];
+ close all
+ end
+ [~,index_minimum_ber] = min(BER);
+ taps_ffe_and_dfe(i) = index_minimum_ber;
+end
+disp(taps_ffe_and_dfe)
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_ffe.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_ffe.m
new file mode 100644
index 0000000..8b55700
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_ffe.m
@@ -0,0 +1,57 @@
+%%
+ffe_start = 0;
+ffe_step = 5;
+ffe_end = 30;
+ffe_first_order = ffe_start:ffe_step:ffe_end;
+
+current = 225:10:285;
+current = string(current);
+power = [28.02, 33.2, 37.5, 42.3, 46.3, 49.3, 53.4];
+power = string(power);
+num_pf_coeff = 4;
+% taps_ffe = [300, 0, 0];
+taps_dfe = [0, 0, 0];
+M = 4;
+trlength = 4096*4;
+x = 225:10:285;
+BER_PAM_4 = [];
+
+for i = 1:length(ffe_first_order)
+ for eq_method = 2
+ for j = 4
+ BER_run = first_analysis_ber(current(j), power(j), num_pf_coeff, [300, 15, ffe_first_order(i)], taps_dfe, M, trlength, eq_method);
+ BER_PAM_4 = [BER_PAM_4, BER_run];
+ end
+ end
+end
+save('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_4_FFE_Second_Order_Tap_Sweep.mat', 'ffe_first_order', 'BER_PAM_4')
+BER_PAM_4 = [];
+
+%%
+BER = load('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_4_FFE_Second_Order_Tap_Sweep.mat');
+BER = BER.BER_PAM_4;
+ 
+figure(202120)
+plot(ffe_first_order, BER, '-o','LineWidth',1.75);
+hold on
+
+h1 = yline(2e-2, ':k', 'LineWidth',1.5);
+h2 = yline(3.8e-3,':b', 'LineWidth',1.5);
+h3 = yline(4.85e-3,':g', 'LineWidth',1.5);
+h4 = yline(2.2e-4,':r', 'LineWidth',1.5);
+
+% FEC Labels direkt im Plot
+text(286,2.2e-2,'o-FEC','Color','k','FontSize', 14, 'Interpreter','latex')
+text(285,3.3e-3,'HD-FEC','Color','b','FontSize', 14, 'Interpreter','latex')
+text(282.5,5.4e-3,'KP4+Hamming','Color','g','FontSize', 14,'Interpreter','latex')
+text(287,2.4e-4,'KP4','Color','r','FontSize', 14,'Interpreter','latex')
+
+xlabel('Number of DFE First Order Taps', 'Interpreter','latex')
+ylabel('BER', 'Interpreter','latex')
+% title('BER for PAM-4', 'Interpreter','latex')
+
+grid minor
+% ylim([1e-4 5e-1])
+set(gca,'YScale','log')
+% beautifyBERplot
+hold off
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_optimize_timing_recovery_parameters.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_optimize_timing_recovery_parameters.m
new file mode 100644
index 0000000..f867c5a
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_optimize_timing_recovery_parameters.m
@@ -0,0 +1,68 @@
+%% Fixe Simulationsparameter
+baud_rate = 6;
+power = 42.3;
+num_pf_coeff = 4;
+M = 4;
+trlength = 4096*2;
+
+%% === Suchräume für Tap-Werte (selbst bestimmbar) =================
+% WICHTIG: wähle Grenzen passend zur Skalierung deiner Taps.
+% Beispiel: FFE Taps vielleicht grob im Bereich [-500 .. 500],
+% DFE Taps eher kleiner, z.B. [-50 .. 50] anpassen!
+ffeRange = [0, 500];
+dfeRange = [0, 200];
+
+vars = [
+ optimizableVariable('ffe1', ffeRange)
+ optimizableVariable('ffe2', ffeRange)
+ optimizableVariable('ffe3', ffeRange)
+
+ optimizableVariable('dfe1', dfeRange)
+ optimizableVariable('dfe2', dfeRange)
+ optimizableVariable('dfe3', dfeRange)
+];
+
+%% === Objective ===================================================
+objectiveFcn = @(x) objectiveWrapperTaps(x, ...
+ baud_rate, power, num_pf_coeff, M, trlength);
+
+%% === bayesopt ====================================================
+results = bayesopt(objectiveFcn, vars, ...
+ 'AcquisitionFunctionName','expected-improvement-plus', ...
+ 'MaxObjectiveEvaluations', 60, ...
+ 'IsObjectiveDeterministic', false, ...
+ 'ExplorationRatio', 0.5, ...
+ 'Verbose', 1, ...
+ 'PlotFcn', {@plotObjectiveModel,@plotMinObjective});
+
+%% === Beste Lösung ausgeben ======================================
+bestX = results.XAtMinObjective;
+bestBER = results.MinObjective;
+
+bestFFE = [bestX.ffe1, bestX.ffe2, bestX.ffe3];
+bestDFE = [bestX.dfe1, bestX.dfe2, bestX.dfe3];
+
+fprintf('\n=== Bestes Ergebnis ===\n');
+fprintf('BER : %.3e\n', bestBER);
+fprintf('taps_ffe : [%g %g %g]\n', bestFFE);
+fprintf('taps_dfe : [%g %g %g]\n\n', bestDFE);
+
+%% =================================================================
+function ber = objectiveWrapperTaps(x, baud_rate, power, num_pf_coeff, M, trlength)
+ taps_ffe = double([x.ffe1, x.ffe2, x.ffe3]);
+ taps_dfe = double([x.dfe1, x.dfe2, x.dfe3]);
+
+ % Optional: falls du z.B. Monotonicität / Struktur erzwingen willst, hier.
+ % taps_dfe(1) = 0; % Beispiel: ersten DFE-Tap fixieren
+
+ try
+ ber = first_analysis_optimize(baud_rate, power, num_pf_coeff, taps_ffe, taps_dfe, M, trlength);
+
+ if isempty(ber) || ~isscalar(ber) || ~isfinite(ber) || ber < 0
+ ber = 1; % Penalty
+ end
+ catch ME
+ warning("Objective failed: %s", ME.message);
+ ber = 1; % Penalty
+ end
+end
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_2.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_2.m
new file mode 100644
index 0000000..b0310bd
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_2.m
@@ -0,0 +1,66 @@
+%%
+current = 225:10:285;
+current = string(current);
+power = [28.02, 33.2, 37.5, 42.3, 46.3, 49.3, 53.4];
+power = string(power);
+num_pf_coeff = 4;
+taps_ffe = [200, 0, 0];
+taps_dfe = [0, 0, 0];
+M = 2;
+trlength = 4096*2;
+x = 225:10:285;
+BER_PAM_2 = [];
+post_only = 0;
+
+for eq_method = 2:4
+
+ if ~post_only
+ for j = 1:length(current)
+ BER_run = first_analysis_ber(current(j), power(j), num_pf_coeff, taps_ffe, taps_dfe, M, trlength, eq_method);
+ BER_PAM_2 = [BER_PAM_2, BER_run];
+ end
+
+ save('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_2_' + string(eq_method) + '.mat', 'x', 'BER_PAM_2')
+ end
+ 
+end
+
+%%
+x = 225:10:285;
+for k = 1:4
+ BER = load('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_2_' + string(k) + '.mat');
+ BER = BER.BER_PAM_2;
+ 
+ figure(202120)
+ plot(x, BER, '-o','LineWidth',1.75);
+ hold on
+end
+old_BER = [-1.5, -1.75, -2, -2.3, -2.6, -2.25, -1.95];
+old_BER = 10.^(old_BER);
+plot(x, old_BER, '-o','LineWidth',1.75)
+ 
+h1 = yline(2e-2, ':k', 'LineWidth',1.5);
+h2 = yline(3.8e-3,':b', 'LineWidth',1.5);
+h3 = yline(4.85e-3,':g', 'LineWidth',1.5);
+h4 = yline(2.2e-4,':r', 'LineWidth',1.5);
+
+% Legende NUR für Kurven
+legend('FFE', 'FFE+PF+MLSE', 'DB', 'ML-MLSE', 'BER Paper', ...
+ 'Interpreter','latex', ...
+ 'Location','southwest', 'FontSize', 14)
+
+% FEC Labels direkt im Plot
+text(280,2.2e-2,'o-FEC','Color','k','FontSize', 14, 'Interpreter','latex')
+text(280,3.3e-3,'HD-FEC','Color','b','FontSize', 14, 'Interpreter','latex')
+text(280,5.4e-3,'KP4+Hamming','Color','g','FontSize', 14,'Interpreter','latex')
+text(280,2.4e-4,'KP4','Color','r','FontSize', 14,'Interpreter','latex')
+
+xlabel('Laser Bias Current [mA]', 'Interpreter','latex')
+ylabel('BER', 'Interpreter','latex')
+% title('BER for PAM-2', 'Interpreter','latex')
+
+grid minor
+ylim([1e-4 5e-1])
+set(gca,'YScale','log')
+% beautifyBERplot
+hold off
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4.m
new file mode 100644
index 0000000..cf3b301
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4.m
@@ -0,0 +1,61 @@
+%%
+current = 225:10:285;
+current = string(current);
+power = [28.02, 33.2, 37.5, 42.3, 46.3, 49.3, 53.4];
+power = string(power);
+num_pf_coeff = 4;
+taps_ffe = [300, 0, 0];
+taps_dfe = [5, 0, 0];
+M = 4;
+trlength = 4096*4;
+x = 225:10:285;
+BER_PAM_4 = [];
+
+for eq_method = 2
+ for j = 4
+ BER_run = first_analysis_ber(current(j), power(j), num_pf_coeff, taps_ffe, taps_dfe, M, trlength, eq_method);
+ BER_PAM_4 = [BER_PAM_4, BER_run];
+ end
+ save('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_4_DFE_' + string(eq_method) + '.mat', 'x', 'BER_PAM_4')
+ BER_PAM_4 = [];
+end
+
+%%
+x = 225:10:285;
+for k = 1:4
+ BER = load('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\BER_PAM_4_' + string(k) + '.mat');
+ BER = BER.BER_PAM_4;
+ 
+ figure(202120)
+ plot(x, BER, '-o','LineWidth',1.75);
+ hold on
+end
+old_BER = [-1.6, -1.85, -2.2, -2.45, -2.3, -2, -1.4];
+old_BER = 10.^(old_BER);
+plot(x, old_BER, '-o','LineWidth',1.75)
+
+h1 = yline(2e-2, ':k', 'LineWidth',1.5);
+h2 = yline(3.8e-3,':b', 'LineWidth',1.5);
+h3 = yline(4.85e-3,':g', 'LineWidth',1.5);
+h4 = yline(2.2e-4,':r', 'LineWidth',1.5);
+
+% Legende NUR für Kurven
+legend('FFE', 'FFE+PF+MLSE', 'DB', 'ML-MLSE', 'BER Paper', ...
+ 'Interpreter','latex', ...
+ 'Location','southwest', 'FontSize', 14)
+
+% FEC Labels direkt im Plot
+text(286,2.2e-2,'o-FEC','Color','k','FontSize', 14, 'Interpreter','latex')
+text(285,3.3e-3,'HD-FEC','Color','b','FontSize', 14, 'Interpreter','latex')
+text(282.5,5.4e-3,'KP4+Hamming','Color','g','FontSize', 14,'Interpreter','latex')
+text(287,2.4e-4,'KP4','Color','r','FontSize', 14,'Interpreter','latex')
+
+xlabel('Laser Bias Current [mA]', 'Interpreter','latex')
+ylabel('BER', 'Interpreter','latex')
+% title('BER for PAM-4', 'Interpreter','latex')
+
+grid minor
+ylim([1e-4 5e-1])
+set(gca,'YScale','log')
+% beautifyBERplot
+hold off
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4_baud_rate_sweep.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4_baud_rate_sweep.m
new file mode 100644
index 0000000..0575787
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_pam_4_baud_rate_sweep.m
@@ -0,0 +1,77 @@
+baud_rate = 4:1:8;
+baud_rate = string(baud_rate);
+power = [8.4, 8.4, 42.3, 8.4, 8.4];
+power = string(power);
+num_pf_coeff = 4;
+taps_ffe = [300, 0, 0];
+taps_dfe = [0, 0, 0];
+M = 4;
+trlength = 4096*4;
+x = 4:1:8;
+BER_PAM_4 = [];
+Alpha_PAM_4 = [];
+
+for method = 1:4
+ for i = 1:5
+ [BER_run, ~] = first_analysis_baud_rate_sweep(baud_rate(i), power(i), num_pf_coeff, taps_ffe, taps_dfe, M, trlength, method);
+ BER_PAM_4 = [BER_PAM_4, BER_run];
+ % Alpha_PAM_4 = [Alpha_PAM_4, Alpha_run];
+ end
+ save('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\New Baud Rate Sweep Data\BER_PAM_4_' + string(method) + '.mat', 'x', 'BER_PAM_4')
+ BER_PAM_4 = [];
+end
+
+%% BER
+for k = 1:4
+ BER = load('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\New Baud Rate Sweep Data\BER_PAM_4_' + string(k) + '.mat');
+ BER = BER.BER_PAM_4;
+ % x = BER.x;
+ % BER_Alpha = load(filename);
+ % BER = BER_Alpha.BER_PAM_4;
+ 
+ % close all
+ figure(239)
+ plot(x, BER, '-o','LineWidth',1.75)
+ hold on
+end
+
+% old_BER = [-4.4, -4, -3.3, -2.7, -2.5, -2.475];
+% old_BER = 10.^(old_BER);
+% plot(x, old_BER, '--o','LineWidth',1)
+
+h1 = yline(2e-2, ':k', 'LineWidth',1.5);
+h2 = yline(3.8e-3,':b', 'LineWidth',1.5);
+h3 = yline(4.85e-3,':g', 'LineWidth',1.5);
+h4 = yline(2.2e-4,':r', 'LineWidth',1.5);
+
+% Legende NUR für Kurven
+legend('FFE','FFE+PF+MLSE','DB','ML-MLSE',...
+ 'Interpreter','latex', ...
+ 'Location','southwest', 'FontSize', 14)
+
+% FEC Labels direkt im Plot
+text(8.2,2.3e-2,'o-FEC','Color','k','FontSize', 14, 'Interpreter','latex')
+text(8.2,3e-3,'HD-FEC','Color','b','FontSize', 14, 'Interpreter','latex')
+text(8.2,5.6e-3,'KP4+Hamming','Color','g','FontSize', 14,'Interpreter','latex')
+text(8.2,2.5e-4,'KP4','Color','r','FontSize', 14,'Interpreter','latex')
+
+xlabel('Symbol Rate [GBd]', 'Interpreter','latex')
+ylabel('BER', 'Interpreter','latex')
+% title('BER for PAM-4', 'Interpreter','latex')
+
+grid minor
+% ylim([1e-4 5e-2])
+xlim([3 9])
+set(gca,'YScale','log')
+hold off
+
+%% Channel Alpha
+% Alpha = BER_Alpha.Alpha_PAM_4;
+% figure
+% plot(x, Alpha, '--o','LineWidth',1)
+% hold on
+% xlabel('Symbol Rate [GBd]', 'Interpreter','latex')
+% ylabel('Channel Alpha', 'Interpreter','latex')
+% title('Channel Alpha for PAM-4 - 300-Tap-FFE - 1 Postfilter Coefficients - 8192 Training Symbols', 'Interpreter','latex')
+% grid minor
+% hold off
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/evalscript_timing_shift.m b/projects/FSO_transmission/Evaluation Scripts/evalscript_timing_shift.m
new file mode 100644
index 0000000..435b540
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/evalscript_timing_shift.m
@@ -0,0 +1,9 @@
+time_shift = -2:0.1:2;
+BER = zeros(1,length(time_shift));
+for i = 1:length(time_shift)
+ BER_value = first_analysis_time_shift(time_shift(i));
+ BER(i) = BER_value;
+ close all
+end
+figure;
+plot(time_shift,BER)
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_2.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_2.m
new file mode 100644
index 0000000..c3a44f0
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_2.m
@@ -0,0 +1,190 @@
+function BER_value = first_analysis_2(taps_ffe, taps_dfe, trlen, trloops, ddloops, K_FFE, DCmu, DDmu, DFEmu, FFEmu, plot_final, idealdfe, num_pf_coeffs, damp_factor, norm_loop_bw, det_gain)
+ %%
+ base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+ mode = 0; %0 oder 1
+ M = 2;
+ 
+ all_files = dir(fullfile(base, "**/*.mat"));
+ 
+ if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ end
+ 
+ if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+ end
+ 
+ tx_data = load(tx_data_path);
+ datas = load(filename);
+ 
+ %%
+ str = filename;
+ M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+ assert(M==M_);
+ fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+ fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+ I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+ rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+ L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+ pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+ rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+ mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+ 
+ %%
+ % Tx data
+ 
+ Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+ Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+ 
+ mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+ PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+ 
+ Symbols_ = PM.map(Bits) .* PM.scaling;
+ assert(isequal(Symbols.signal,Symbols_.signal));
+ 
+ Bits_ = PM.demap(Symbols);
+ [bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+ assert(ber == 0);
+ 
+ %% For comparison, apply pulsef on Tx Symbols
+ Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rc","pulselength",16,"alpha",rolloff);
+ Digi_sig_compare = Pform.process(Symbols);
+ MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Rx_sig_compare = MF.process(Digi_sig_compare);
+ 
+ %%
+ 
+ % Rx Data
+ traceData = datas.tr.lastData(2).trace.ch3;
+ 
+ %FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+ scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+ demystified = isequal(traceData.YData,scoperead_volts);
+ assert(demystified);
+ 
+ Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+ 
+ Scope_sig.plot("displayname",'raw','fignum',100);
+ Scope_sig.spectrum("displayname",'raw','fignum',101)
+ 
+ % 1) matched filter
+ % pulse is symmetric, hence we can use pulsef firectly as matched filter.
+ % It feels off (bit I think correct) that the fsym is now the output freq.!!
+ % -> output 2 sps to omit timing recovery!?
+ Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Matched_Filter.process(Scope_sig);
+ Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+ 
+ % timing sync -> at this point we still have no symbol timing recovery, we
+ % try to do this with 2sps EQ!
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_matched_1 = Rx_synced_cell{1};
+ 
+ % Rx_Time_Rec = Rx_matched;
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2, ...
+ 'damping_factor',damp_factor,'normalized_loop_bandwidth',norm_loop_bw,'detector_gain',det_gain).process(Rx_matched_1);
+ figure;plot(Timing_Error);
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 28e9, 14e9);
+ 
+ %% not working.. 
+ Rx_synced = Rx_Time_Rec;
+ Rx_synced.fs = 14e9;
+ % Rx_synced = Rx_synced_cell{1};
+ % len_tr = trlen;
+ % mu_ffe1 = mu_ffe_values(1);
+ % mu_ffe2 = mu_ffe_values(2);
+ % mu_ffe3 = mu_ffe_values(3);
+ % mu_dc = 0.005;
+ % mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ % mu_dfe = mu_dfe_values;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',2);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',0);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',0);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',0);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ % "training_length",trlen,"training_loops",trloops,"dd_loops",ddloops, ...
+ % "K",K_FFE,"DCmu",DCmu,"DDmu",DDmu,"DFEmu",DFEmu, ...
+ % "FFEmu",FFEmu,"plotfinal",plot_final,"ideal_dfe",idealdfe);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % % ffe_results.metrics.print
+ % fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % BER_value = ffe_results.metrics.BER;
+
+ %% -------------------- VNLE + MLSE --------------------
+
+ pf_ncoeffs = num_pf_coeffs;
+ eq_v = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ "training_length",trlen,"training_loops",trloops,"dd_loops",ddloops, ...
+ "K",K_FFE,"DCmu",DCmu,"DDmu",DDmu,"DFEmu",DFEmu, ...
+ "FFEmu",FFEmu,"plotfinal",plot_final,"ideal_dfe",idealdfe);
+ pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+ mlse_results.metrics.print
+ fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER_value = mlse_results.metrics.BER;
+
+ %% -------------------- DB target --------------------
+ % mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+ % 
+ % eq_ = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ % "training_length",trlen,"training_loops",trloops,"dd_loops",ddloops, ...
+ % "K",K_FFE,"DCmu",DCmu,"DDmu",DDmu,"DFEmu",DFEmu, ...
+ % "FFEmu",FFEmu,"plotfinal",plot_final,"ideal_dfe",idealdfe);
+ % 
+ % dbt_results = duobinary_target(eq_,mlse_db_, M, Rx_synced, Symbols, Bits, ...
+ % "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+ % 
+ % dbt_results.metrics.print("description",'Duobinary');
+ % mlse_results.metrics.print
+ % fprintf('My EQ: %.1e \n',dbt_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",100,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced)/2,"mu_dd",0.03,"mu_tr",0.03,"order",30,"sps",1, ...
+ % "traceback_depth",256,"L",2,"delta",10,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % fprintf('ML-based MLSE:');
+ % fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % 
+ % % -------------------- Post-FFE --------------------
+ % eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",len_tr,"mu_dd",1e-4,"mu_tr",0,"order",1001,"sps",1,"decide",0);
+ % post_ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % fprintf('Post-FFE:');
+ % fprintf('My EQ: %.1e \n',post_ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_baud_rate_sweep.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_baud_rate_sweep.m
new file mode 100644
index 0000000..0fe859c
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_baud_rate_sweep.m
@@ -0,0 +1,202 @@
+function [BER, Channel_Alpha] = first_analysis_baud_rate_sweep(baud_rate, power, num_pf_coeff, taps_ffe, taps_dfe, M, trlength, method)
+ %%
+ close all
+ base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\Sweep Data\";
+ mode = 0; %0 oder 1
+ % M = 2;
+ 
+ all_files = dir(fullfile(base, "**/*.mat"));
+ 
+ if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=" + current + "mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ elseif M == 4
+ tx_data_path = fullfile(base, baud_rate + "G_PAM4\tx_info\tx_info_PAM4_" + baud_rate + "Gbd0.6RRC.mat");
+ filename = fullfile(base, baud_rate + "G_PAM4\M=4_Rs="+ baud_rate + "e9_Fs=8e10_I=255mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ end
+ 
+ if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+ end
+ 
+ tx_data = load(tx_data_path);
+ datas = load(filename);
+ 
+ %%
+ str = filename;
+ M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+ assert(M==M_);
+ fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+ fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+ I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+ rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+ L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+ pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+ rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+ mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+ 
+ %%
+ % Tx data
+ 
+ Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+ Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+ 
+ mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+ PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+ 
+ Symbols_ = PM.map(Bits) .* PM.scaling;
+ assert(isequal(Symbols.signal,Symbols_.signal));
+ 
+ Bits_ = PM.demap(Symbols);
+ [bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+ assert(ber == 0);
+ 
+ %% For comparison, apply pulsef on Tx Symbols
+ Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Digi_sig_compare = Pform.process(Symbols);
+ MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Rx_sig_compare = MF.process(Digi_sig_compare);
+ 
+ %%
+ 
+ % Rx Data
+ traceData = datas.tr.lastData(2).trace.ch3;
+ 
+ %FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+ scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+ demystified = isequal(traceData.YData,scoperead_volts);
+ assert(demystified);
+ 
+ Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+ 
+ Scope_sig.plot("displayname",'raw','fignum',100);
+ Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+ Kov = 14;
+
+ Scope_sig = Scope_sig.resample('fs_in',fs,'fs_out',Kov*fsym);
+ 
+ % 1) matched filter
+ % pulse is symmetric, hence we can use pulsef firectly as matched filter.
+ % It feels off (bit I think correct) that the fsym is now the output freq.!!
+ % -> output 2 sps to omit timing recovery!?
+ Matched_Filter = Pulseformer("fsym",fsym,"fdac",Kov*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Matched_Filter.process(Scope_sig);
+ Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+ 
+ % timing sync -> at this point we still have no symbol timing recovery, we
+ % try to do this with 2sps EQ!
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_matched_1 = Rx_synced_cell{1};
+ 
+ % Rx_Time_Rec = Rx_matched;
+ % Rx_Time_Rec = Timing_Recovery_Move_It('f_sim', 28e9, 'gamma', 0.1).process(Rx_matched_1);
+ 
+ Time_Rec = 1;
+ if Time_Rec
+ Rx_Time_Rec = MaxVar_Timing_Recovery('mode',0,'fsym',fsym,'fadc',Kov*fsym,'num_tau',Kov*128,'sps',Kov,'comp_signal',0,'comp_mode',0).process(Rx_matched_1);
+ sps = 1;
+ else
+ sps = 2;
+ end
+
+ Rx_Time_Rec.fs = fsym;
+ 
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 12e9, 6e9);
+ 
+ % Rx_matched_1.plot("fignum",231231)
+ % Rx_Time_Rec.plot("fignum",231231)
+ 
+ %% not working.. 
+ Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = trlength;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ if method == 1
+ %% -------------------- FFE --------------------
+ % requires some more digging what is going on :-) 
+ eq_ffe = EQ("Ne",[500, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ 
+ ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ "eth_style_symbol_mapping",mapping_style);
+ 
+ % ffe_results.metrics.print
+ fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER = ffe_results.metrics.BER;
+ 
+ elseif method == 2
+ %% -------------------- VNLE + MLSE --------------------
+ pf_ncoeffs = num_pf_coeff;
+ eq_v = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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,"eth_style",mapping_style).levels);
+ 
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ 
+ mlse_results.metrics.print
+ fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER = mlse_results.metrics.BER;
+ % Channel_Alpha = mlse_results.metrics.Alpha;
+ 
+ elseif method == 3
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ ml_mlse_equalizer = ML_MLSE("epochs_tr",150,"epochs_dd",1, ...
+ "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",80,"sps",sps, ...
+ "traceback_depth",256,"L",1,"delta",4,"adaptive_mu",0);
+ 
+ [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ fprintf('ML-based MLSE:\n');
+ fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER = ml_mlse_results.metrics.BER;
+
+ elseif method == 4
+ %% -------------------- DB target --------------------
+ mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+ 
+ eq_ = EQ("Ne",taps_ffe,"Nb",taps_dfe,"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+ 
+ dbt_results.metrics.print("description",'Duobinary');
+ fprintf('My EQ: %.1e \n',dbt_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER = dbt_results.metrics.BER;
+ end
+ Channel_Alpha = 0;
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_ber.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_ber.m
new file mode 100644
index 0000000..bc02b58
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_ber.m
@@ -0,0 +1,207 @@
+function BER_value = first_analysis_ber(current, power, num_pf_coeff, taps_ffe, taps_dfe, M, trlength, eq_method)
+ %%
+ close all
+
+ %%
+ base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+ mode = 0; %0 oder 1
+ % M = 2;
+ 
+ all_files = dir(fullfile(base, "**/*.mat"));
+ 
+ if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=" + current + "mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=" + current + "mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ end
+ 
+ if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+ end
+ 
+ tx_data = load(tx_data_path);
+ datas = load(filename);
+ 
+ %%
+ str = filename;
+ M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+ assert(M==M_);
+ fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+ fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+ I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+ rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+ L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+ pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+ rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+ mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+ 
+ %%
+ % Tx data
+ 
+ Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+ Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+ 
+ mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+ PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+ 
+ Symbols_ = PM.map(Bits) .* PM.scaling;
+ assert(isequal(Symbols.signal,Symbols_.signal));
+ 
+ Bits_ = PM.demap(Symbols);
+ [bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+ assert(ber == 0);
+ 
+ %% For comparison, apply pulsef on Tx Symbols
+ Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Digi_sig_compare = Pform.process(Symbols);
+ MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Rx_sig_compare = MF.process(Digi_sig_compare);
+ 
+ %%
+ 
+ % Rx Data
+ traceData = datas.tr.lastData(2).trace.ch3;
+ 
+ %FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+ scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+ demystified = isequal(traceData.YData,scoperead_volts);
+ assert(demystified);
+ 
+ Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+ 
+ Scope_sig.plot("displayname",'raw','fignum',100);
+ Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+ Kov = 14;
+
+ Scope_sig = Scope_sig.resample('fs_in', fs, 'fs_out', Kov*fsym);
+ 
+ % 1) matched filter
+ % pulse is symmetric, hence we can use pulsef firectly as matched filter.
+ % It feels off (bit I think correct) that the fsym is now the output freq.!!
+ % -> output 2 sps to omit timing recovery!?
+ Matched_Filter = Pulseformer("fsym",fsym,"fdac",Kov*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Matched_Filter.process(Scope_sig);
+ Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+ 
+ % timing sync -> at this point we still have no symbol timing recovery, we
+ % try to do this with 2sps EQ!
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_matched_1 = Rx_synced_cell{1};
+ 
+ % Rx_Time_Rec = Rx_matched;
+ % Rx_Time_Rec = Timing_Recovery_Move_It('f_sim', 28e9, 'gamma', 0.1).process(Rx_matched_1);
+ 
+ Time_Rec = 1;
+ if Time_Rec
+ Rx_Time_Rec = MaxVar_Timing_Recovery('mode',0,'fsym',fsym,'fadc',Kov*fsym,'num_tau',Kov*128,'sps',Kov,'comp_signal',0,'comp_mode',0).process(Rx_matched_1);
+ sps = 1;
+ else
+ sps = Kov;
+ end
+ Rx_Time_Rec.fs = fsym;
+ 
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 12e9, 6e9);
+ 
+ % Rx_matched_1.plot("fignum",231231)
+ % Rx_Time_Rec.plot("fignum",231231)
+ 
+ %% not working.. 
+ Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = trlength;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+
+ if eq_method == 1
+ 
+ %% -------------------- FFE --------------------
+ % requires some more digging what is going on :-) 
+ eq_ffe = EQ("Ne",[500, 0, 0],"Nb",[0,0,0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ 
+ ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ "eth_style_symbol_mapping",mapping_style);
+ 
+ % ffe_results.metrics.print
+ fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER_value = ffe_results.metrics.BER;
+ 
+ elseif eq_method == 2
+
+ %% -------------------- VNLE + MLSE --------------------
+ 
+ pf_ncoeffs = num_pf_coeff;
+ eq_v = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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,"eth_style",mapping_style).levels);
+ 
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ 
+ mlse_results.metrics.print
+ fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER_value = mlse_results.metrics.BER;
+
+ elseif eq_method == 3
+
+ %% -------------------- DB target --------------------
+ mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+
+ eq_ = EQ("Ne",taps_ffe,"Nb",taps_dfe,"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+
+ dbt_results.metrics.print("description",'Duobinary');
+ fprintf('My EQ: %.1e \n',dbt_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER_value = dbt_results.metrics.BER;
+
+ elseif eq_method == 4
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ ml_mlse_equalizer = ML_MLSE("epochs_tr",150,"epochs_dd",1, ...
+ "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",80,"sps",1, ...
+ "traceback_depth",256,"L",1,"delta",4,"adaptive_mu",0);
+ 
+ [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ fprintf('ML-based MLSE:\n');
+ fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER_value = ml_mlse_results.metrics.BER;
+
+ end
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_mf.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_mf.m
new file mode 100644
index 0000000..b2a6027
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_mf.m
@@ -0,0 +1,293 @@
+
+<<<<<<< HEAD
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+=======
+base = "C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC";
+>>>>>>> 719e5508e776c18e3dcf72a954c71bdedda179e0
+mode = 0; %0 oder 1
+M = 2;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+<<<<<<< HEAD
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+=======
+ tx_data = load("C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC\14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data = load("C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC\6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+>>>>>>> 719e5508e776c18e3dcf72a954c71bdedda179e0
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_tx_compare = Pform.process(Symbols);
+
+Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sync = Pform.process(Symbols);
+
+MF = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_tx_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+timesig_compare = [0:1:datas.tr.lastData(1).trace.ch3.Points-1] ./ fs;
+timesig = datas.tr.lastData(1).trace.ch3.XData;
+assert(isequal(traceData.YData,scoperead_volts));
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+%%
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef directly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+apply_matched_filter = 0;
+k = 4;
+if apply_matched_filter
+ 
+ Pform = Pulseformer("fsym",fsym,"fdac",k*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Pform.process(Scope_sig);
+else
+
+ Rx_matched = Filter('filtdegree',4,"f_cutoff",fsym*0.5,"fs",Scope_sig.fs,"filterType",filtertypes.gaussian,"active",true).process(Scope_sig);
+ Rx_matched = Rx_matched.resample("fs_out",k*fsym);
+end
+Rx_matched.spectrum();
+
+<<<<<<< HEAD
+%%
+
+sys = comm.SymbolSynchronizer('TimingErrorDetector', 'Gardner (non-data-aided)', ...
+ 'SamplesPerSymbol', 2, ...
+ 'DampingFactor', 0.7, ...
+ 'NormalizedLoopBandwidth', 0.01);
+Rx_symbolsync = Rx_matched;
+[Rx_symbolsync.signal, timing_error] = sys(Rx_matched.signal);
+
+plot(timing_error); % If this is a ramp, you have drift!
+
+%% timing sync -> at this point we still have no symbol timing recovery, we
+% % try to do this with 2sps EQ!
+
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_symbolsync.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+% Rx_matched_1 = Rx_synced_cell{1};
+% 
+% % Rx_Time_Rec = Rx_matched;
+% [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',0.01,'detector_gain',2.7).process(Rx_matched_1);
+% figure;plot(Timing_Error);
+% % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 28e9, 14e9);
+
+%% not working.. 
+% Rx_synced = Rx_Time_Rec;
+Rx_synced = Rx_synced_cell{1};
+len_tr = 4096*2;
+mu_ffe1 = 0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.005;
+mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+
+Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',2);
+
+Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',0);
+Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',0);
+Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',0);
+=======
+%% Timing Rec
+apply_timing_rec = 1;
+if apply_timing_rec
+ [Rx_symbolsync, timing_error] = Timing_Recovery("timing_error_detector",'Gardner (non-data-aided)','sps',k,'damping_factor',0.1,'normalized_loop_bandwidth',0.1,'detector_gain',2.7).process(Rx_matched);
+ figure();plot(timing_error);
+ Rx_symbolsync.fs = fsym;
+ % Tsynch
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_symbolsync.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
+ Rx_synced = Rx_synced_cell{1};
+ sps = 1;
+else
+ % Tsynch
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
+ Rx_synced = Rx_synced_cell{1};
+ Rx_synced = Rx_synced.resample("fs_out",2*fsym);
+ sps = 2;
+end
+>>>>>>> 719e5508e776c18e3dcf72a954c71bdedda179e0
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+
+%%
+
+Rx_synced = Rx_synced_cell{1};
+
+
+% -------------------- FFE --------------------
+% requires some more digging what is going on :-)
+eq_ffe = EQ("Ne",[50, 1, 1],"Nb",[2,0,0], ...
+ "training_length",512,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+
+vars = logspace(-4,-3,36);
+
+parfor i = 1:numel(vars)
+
+ len_tr = 4096;
+ mu_ffe1 = 0.01;% mus(i);%0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.005;
+ mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
+ mu_dfe = vars(i);
+ duob_mode = db_mode.no_db;
+
+ % requires some more digging what is going on :-)
+ eq_ffe_1 = EQ("Ne",[150, 1, 0],"Nb",[50,0,0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",0);
+
+ eq_ffe_2 = FFE("epochs_tr",1,"epochs_dd",vars(i),"len_tr",4096,"mu_dd",vars(i),"mu_tr",vars(i),"order",999,"sps",1,"decide",0, "adaption",adaption_method.nlms,"dd_mode",0);
+ % eq_ffe_2 = FFE_DFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"ffe_mu_dd",1e-5,"dfe_mu_dd",mus(i),"ffe_mu_tr",0,"dfe_mu_tr",0,"ffe_order",50,"dfe_order",10,"sps",1,"decide",1);
+
+
+ ffe_results = ffe(eq_ffe_1,M,Rx_synced,Symbols,Bits, ...
+ "precode_mode",duob_mode,'showAnalysis',1,"postFFE",[], ...
+ "eth_style_symbol_mapping",mapping_style);
+
+ ffe_results.metrics.BER
+ bers(i) = ffe_results.metrics.BER;
+end
+
+figure();
+plot(vars,bers);
+yline(ber_in_paper)
+beautifyBERplot();
+% 
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+ffe_results.metrics.print("description",'FFE');
+fprintf('FFE: %.1e \n',ffe_results.metrics.BER);
+
+
+%% -------------------- VNLE + MLSE --------------------
+len_tr = 4096;
+mu_ffe1 = 0.0001;% mus(i);%0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.005;
+mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+
+pf_ncoeffs = 4;
+
+vars = 1:7;
+bers = zeros(size(vars));
+parfor i = 1:numel(vars)
+ 
+ eqv = EQ("Ne",[200, 1, 0],"Nb",[2, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ pf_ncoeffs = vars(i);
+ pf = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eqv, pf, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ vnle_results.metrics.print("description",'VNLE');
+ mlse_results.metrics.print("description",'MLSE');
+ bers(i) = mlse_results.metrics.BER;
+end
+%%
+figure();hold on
+plot(vars,bers_ffe,'DisplayName','FFE [200,0,0] + PF + MLSE');
+plot(vars,bers_vnle,'DisplayName','VNLE [200,1,0] + PF + MLSE');
+plot(vars,bers_vnledfe,'DisplayName','VNLE [200,1,0] + DFE [2] + PF + MLSE');
+plot(vars,bers_vnledfe_ideal,'DisplayName','VNLE [200,1,0] + ideal DFE [2] + PF + MLSE');
+yline(ber_in_paper);
+yline([2e-2, 4.85e-3, 3.8e-3, 2,2e-4],'LineWidth',2,'Color',[0.8,0.8,0.8],'LineStyle',':','HandleVisibility','off');
+ylim([1e-5,0.1]);
+beautifyBERplot();
+
+%% -------------------- DB target --------------------
+mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+
+eq_ = EQ("Ne",[50, 5, 5],"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+dbt_results.metrics.print("description",'Duobinary');
+
+
+%%
+
+%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",len_tr,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",1, ...
+ "traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0);
+
+[ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode);
+ml_mlse_results.metrics.print("description",'ML ');
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_optimize.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_optimize.m
new file mode 100644
index 0000000..5d5b4f9
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_optimize.m
@@ -0,0 +1,177 @@
+function BER = first_analysis_optimize(baud_rate, power, num_pf_coeff, taps_ffe, taps_dfe, M, trlength)
+ %%
+ base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\Sweep Data\";
+ mode = 0; %0 oder 1
+ % M = 2;
+ 
+ all_files = dir(fullfile(base, "**/*.mat"));
+ 
+ if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=" + current + "mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ elseif M == 4
+ tx_data_path = fullfile(base, baud_rate + "G_PAM4\tx_info\tx_info_PAM4_" + baud_rate + "Gbd0.6RRC.mat");
+ filename = fullfile(base, baud_rate + "G_PAM4\M=4_Rs="+ baud_rate + "e9_Fs=8e10_I=255mA_RoP=" + power + "mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ end
+ 
+ if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+ end
+ 
+ tx_data = load(tx_data_path);
+ datas = load(filename);
+ 
+ %%
+ str = filename;
+ M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+ assert(M==M_);
+ fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+ fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+ I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+ rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+ L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+ pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+ rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+ mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+ 
+ %%
+ % Tx data
+ 
+ Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+ Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+ 
+ mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+ PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+ 
+ Symbols_ = PM.map(Bits) .* PM.scaling;
+ assert(isequal(Symbols.signal,Symbols_.signal));
+ 
+ Bits_ = PM.demap(Symbols);
+ [bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+ assert(ber == 0);
+ 
+ %% For comparison, apply pulsef on Tx Symbols
+ Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Digi_sig_compare = Pform.process(Symbols);
+ MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Rx_sig_compare = MF.process(Digi_sig_compare);
+ 
+ %%
+ 
+ % Rx Data
+ traceData = datas.tr.lastData(2).trace.ch3;
+ 
+ %FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+ scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+ demystified = isequal(traceData.YData,scoperead_volts);
+ assert(demystified);
+ 
+ Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+ 
+ Scope_sig.plot("displayname",'raw','fignum',100);
+ Scope_sig.spectrum("displayname",'raw','fignum',101)
+ 
+ % 1) matched filter
+ % pulse is symmetric, hence we can use pulsef firectly as matched filter.
+ % It feels off (bit I think correct) that the fsym is now the output freq.!!
+ % -> output 2 sps to omit timing recovery!?
+ Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Matched_Filter.process(Scope_sig);
+ Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+ 
+ % timing sync -> at this point we still have no symbol timing recovery, we
+ % try to do this with 2sps EQ!
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_matched_1 = Rx_synced_cell{1};
+ 
+ % Rx_Time_Rec = Rx_matched;
+ % Rx_Time_Rec = Timing_Recovery_Move_It('f_sim', 28e9, 'gamma', 0.1).process(Rx_matched_1);
+ 
+ Time_Rec = 1;
+ if Time_Rec
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',0.0123793,'normalized_loop_bandwidth',4.50865e-06,'detector_gain',3.33311).process(Rx_matched_1);
+ sps = 1;
+ else
+ sps = 2;
+ end
+
+ baud_rate_num = str2double(baud_rate);
+ Rx_Time_Rec.fs = baud_rate_num*10^9;
+ 
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 12e9, 6e9);
+ 
+ % Rx_matched_1.plot("fignum",231231)
+ % Rx_Time_Rec.plot("fignum",231231)
+ 
+ %% not working.. 
+ Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = trlength;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",[400, 0, 0],"Nb",[0,0,0], ...
+ % "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ % "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ % "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % % ffe_results.metrics.print
+ % fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % BER_value = ffe_results.metrics.BER;
+ 
+ %% -------------------- VNLE + MLSE --------------------
+ 
+ pf_ncoeffs = num_pf_coeff;
+ eq_v = EQ("Ne",taps_ffe,"Nb",taps_dfe, ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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,"eth_style",mapping_style).levels);
+ 
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ 
+ mlse_results.metrics.print
+ fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ BER = mlse_results.metrics.BER;
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",150,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",80,"sps",1, ...
+ % "traceback_depth",256,"L",1,"delta",4,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % fprintf('ML-based MLSE:\n');
+ % fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec.m
new file mode 100644
index 0000000..22622ec
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec.m
@@ -0,0 +1,160 @@
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 4;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+% Rx_Time_Rec = Rx_matched;
+% Rx_Time_Rec = Timing_Recovery_Move_It('f_sim', 28e9, 'gamma', 0.1).process(Rx_matched_1);
+[Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',0.01,'detector_gain',2.7).process(Rx_matched_1);
+
+%% not working.. 
+Rx_synced = Rx_Time_Rec;
+% Rx_synced = Rx_synced_cell{1};
+len_tr = 4096*2;
+mu_ffe1 = 0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.004;
+mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+sps = 1;
+
+Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+
+Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+%% -------------------- FFE --------------------
+% % requires some more digging what is going on :-) 
+% eq_ffe = EQ("Ne",[50, 5, 5],"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",1);
+% 
+% ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+% "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+% "eth_style_symbol_mapping",mapping_style);
+% 
+% % ffe_results.metrics.print
+% fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
+% BER_value = ffe_results.metrics.BER;
+
+%% -------------------- VNLE + MLSE --------------------
+
+pf_ncoeffs = 1;
+eq_v = EQ("Ne",[200, 3, 2],"Nb",[5, 2, 1], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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,"eth_style",mapping_style).levels);
+
+[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+mlse_results.metrics.print
+fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+%% -------------------- ML-based MLSE (L=2) --------------------
+% ml_mlse_equalizer = ML_MLSE("epochs_tr",150,"epochs_dd",1, ...
+% "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",80,"sps",1, ...
+% "traceback_depth",256,"L",1,"delta",4,"adaptive_mu",0);
+% 
+% [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+% fprintf('ML-based MLSE:\n');
+% fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam2.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam2.m
new file mode 100644
index 0000000..e681f2e
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam2.m
@@ -0,0 +1,171 @@
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 2;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+Time_Rec = 1;
+if Time_Rec
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',0.01,'detector_gain',2.7).process(Rx_matched_1);
+ sps = 1;
+else
+ sps = 2;
+end
+
+if Time_Rec == 1 && M == 2 
+ Rx_Time_Rec.fs = 14e9;
+elseif Time_Rec == 1 && M == 4
+ Rx_Time_Rec.fs = 6e9;
+end
+
+%% not working.. 
+Rx_synced = Rx_Time_Rec;
+% Rx_synced = Rx_synced_cell{1};
+len_tr = 4096*4;
+mu_ffe1 = 0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.004;
+mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+sps = 1;
+
+Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+
+Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+%% -------------------- FFE --------------------
+% % requires some more digging what is going on :-) 
+% eq_ffe = EQ("Ne",[200, 0, 0],"Nb",[0,0,0], ...
+% "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+% "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+% "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+% 
+% ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+% "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+% "eth_style_symbol_mapping",mapping_style);
+% 
+% % ffe_results.metrics.print
+% fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
+% BER_value = ffe_results.metrics.BER;
+
+%% -------------------- VNLE + MLSE --------------------
+
+pf_ncoeffs = 4;
+eq_v = EQ("Ne",[250, 0, 0],"Nb",[7, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",0, ...
+ 'weighted_DFE',1,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','I2','weighted_DFE_I_mode',[5,0.5,0.6]);
+pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+mlse_results.metrics.print
+fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+%% -------------------- ML-based MLSE (L=2) --------------------
+% ml_mlse_equalizer = ML_MLSE("epochs_tr",400,"epochs_dd",1, ...
+% "len_tr",len_tr,"mu_dd",0.03,"mu_tr",0.03,"order",100,"sps",sps, ...
+% "traceback_depth",256,"L",2,"delta",4,"adaptive_mu",0);
+% 
+% [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+% fprintf('ML-based MLSE:\n');
+% fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam4.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam4.m
new file mode 100644
index 0000000..2881ccf
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_rec_pam4.m
@@ -0,0 +1,189 @@
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\Sweep Data\";
+mode = 0; %0 oder 1
+M = 4;
+
+baud_rate = '8';
+baud_rate_num = str2double(baud_rate);
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+ tx_data_path = fullfile(base, baud_rate + "G_PAM2\tx_info\tx_info_PAM2_" + baud_rate + "Gbd0.75RRC.mat");
+ filename = fullfile(base, baud_rate + "G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, baud_rate + "G_PAM4\tx_info\tx_info_PAM4_" + baud_rate + "Gbd0.6RRC.mat");
+ filename = fullfile(base, baud_rate + "G_PAM4\M=4_Rs=" + baud_rate + "e9_Fs=8e10_I=255mA_RoP=8.4mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+% Rx_Time_Rec = Rx_matched;
+% Rx_Time_Rec = Timing_Recovery_Move_It('f_sim', 28e9, 'gamma', 0.1).process(Rx_matched_1);
+
+Time_Rec = 1;
+if Time_Rec
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',0.01,'detector_gain',2.7).process(Rx_matched_1);
+ sps = 1;
+else
+ sps = 2;
+end
+
+Rx_Time_Rec.fs = baud_rate_num * 10^9;
+
+% Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal, 12e9, 6e9);
+
+% Rx_matched_1.plot("fignum",231231)
+% Rx_Time_Rec.plot("fignum",231231)
+
+%% not working.. 
+Rx_synced = Rx_Time_Rec;
+% Rx_synced = Rx_synced_cell{1};
+len_tr = 4096*2;
+mu_ffe1 = 0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.004;
+mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+
+Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+
+Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+%% -------------------- FFE --------------------
+% % requires some more digging what is going on :-) 
+% eq_ffe = EQ("Ne",[400, 0, 0],"Nb",[0,0,0], ...
+% "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+% "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+% "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+% 
+% ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+% "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+% "eth_style_symbol_mapping",mapping_style);
+% 
+% % ffe_results.metrics.print
+% fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
+% BER_value = ffe_results.metrics.BER;
+
+%% -------------------- VNLE + MLSE --------------------
+
+pf_ncoeffs = 4;
+eq_v = EQ("Ne",[300, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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,"eth_style",mapping_style).levels);
+
+[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+mlse_results.metrics.print
+fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+%% -------------------- ML-based MLSE (L=2) --------------------
+% ml_mlse_equalizer = ML_MLSE("epochs_tr",200,"epochs_dd",1, ...
+% "len_tr",length(Rx_synced)/2,"mu_dd",0,"mu_tr",0.03,"order",100,"sps",sps, ...
+% "traceback_depth",256,"L",3,"delta",4,"adaptive_mu",0);
+% 
+% [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+% fprintf('ML-based MLSE:\n');
+% fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+%% -------------------- DB target --------------------
+% mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+% 
+% eq_ = EQ("Ne",[50, 5, 5],"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+% "K",sps,"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, Rx_synced, Symbols, Bits, ...
+% "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+% 
+% dbt_results.metrics.print("description",'Duobinary');
+% fprintf('My EQ: %.1e \n',dbt_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_shift.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_shift.m
new file mode 100644
index 0000000..162d6a6
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_time_shift.m
@@ -0,0 +1,221 @@
+function BER = first_analysis_time_shift(time_shift) 
+ %%
+ base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+ mode = 0; %0 oder 1
+ M = 4;
+ 
+ all_files = dir(fullfile(base, "**/*.mat"));
+ 
+ % data_tr_mf is the already recovered and filtered data
+ if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ end
+ 
+ if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+ end
+ 
+ tx_data = load(tx_data_path);
+ datas = load(filename);
+ 
+ %%
+ str = filename;
+ M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+ assert(M==M_);
+ fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+ fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+ I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+ rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+ L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+ pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+ rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+ mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+ 
+ %%
+ % Tx data
+ 
+ Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+ Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+ 
+ mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+ PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+ 
+ Symbols_ = PM.map(Bits) .* PM.scaling;
+ assert(isequal(Symbols.signal,Symbols_.signal));
+ 
+ Bits_ = PM.demap(Symbols);
+ [bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+ assert(ber == 0);
+ 
+ %% For comparison, apply pulsef on Tx Symbols
+ Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Digi_sig_compare = Pform.process(Symbols);
+ MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+ Rx_sig_compare = MF.process(Digi_sig_compare);
+ 
+ %%
+ 
+ % Rx Data
+ traceData = datas.tr.lastData(2).trace.ch3;
+ 
+ %FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+ scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+ demystified = isequal(traceData.YData,scoperead_volts);
+ assert(demystified);
+ 
+ Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+ 
+ Scope_sig.plot("displayname",'raw','fignum',100);
+ Scope_sig.spectrum("displayname",'raw','fignum',101)
+ 
+ % 1) matched filter
+ % pulse is symmetric, hence we can use pulsef firectly as matched filter.
+ % It feels off (bit I think correct) that the fsym is now the output freq.!!
+ % -> output 2 sps to omit timing recovery!?
+ Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Matched_Filter.process(Scope_sig);
+ Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+ 
+ data_tr_mf = Electricalsignal(data_tr_mf.Results, "fs", fsym);
+ 
+ % timing sync -> at this point we still have no symbol timing recovery, we
+ % try to do this with 2sps EQ!
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_matched_1 = Rx_synced_cell{1};
+ Rx_matched_original = Rx_synced_cell{1};
+ Rx_matched_original.signal = resample(Rx_matched_original.signal,1,2);
+ 
+ [~,Rx_synced_cell_tr_mf,inverted_tr_mf,sequenceFound_tr_mf,sequenceStarts_tr_mf] = data_tr_mf.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+ Rx_tr_mf = Rx_synced_cell_tr_mf{1};
+ 
+ Time_Rec = 1;
+ if Time_Rec
+ % [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',1e-4,'detector_gain',2.7).process(Rx_matched_1);
+ Rx_Time_Rec = Time_Shifter('value',time_shift).process(Rx_matched_1);
+ Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal,1,2);
+ % figure(2222222)
+ % plot(Timing_Error)
+ sps = 1;
+ else
+ sps = 2;
+ end
+ 
+ if Time_Rec == 1 && M == 2 
+ Rx_Time_Rec.fs = 14e9;
+ elseif Time_Rec == 1 && M == 4
+ Rx_Time_Rec.fs = 6e9;
+ end
+ 
+ Rx_Time_Rec.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+ % Rx_matched_original.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+ Rx_tr_mf.spectrum('normalizeTo0dB',1,"displayname",'Their signal','fignum',101111);
+ 
+ Rx_Time_Rec_plot = Rx_Time_Rec;
+ % Rx_matched_original_plot = Rx_matched_original;
+ Rx_tr_mf_plot = Rx_tr_mf;
+ Rx_Time_Rec_plot.normalize("mode","rms").plot("displayname",'Our signal','fignum',101311);
+ % Rx_matched_original_plot.normalize("mode","rms").plot("displayname",'Original signal','fignum',101311);
+ Rx_tr_mf_plot.normalize("mode","rms").plot("displayname",'Their signal','fignum',101311);
+ 
+ %% not working.. 
+ % Use our or their signal
+ for our_signal = 1
+ if our_signal
+ Rx_synced = Rx_Time_Rec;
+ else
+ Rx_synced = Rx_tr_mf;
+ end
+ % Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = 4096*2;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",[500, 0, 0],"Nb",[0,0,0], ...
+ % "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ % "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ % "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ % 
+ % % eq_ffe = FFE_DFE('ffe_order',99,'dfe_order',99,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5,'sps',sps,'decide',0);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % if our_signal
+ % fprintf('Our signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+ 
+ %% -------------------- VNLE + MLSE --------------------
+ pf_ncoeffs = 4;
+ eq_v = EQ("Ne",[100, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1, ...
+ 'weighted_DFE',0,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','R2','weighted_DFE_I_mode',[5,0.5,0.6]);
+ % eq_v = FFE_DFE('ffe_order',200,'dfe_order',0,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5, ...
+ % 'ffe_mu_dd',mu_ffe,'ffe_mu_tr',0,'dfe_mu_dd',mu_dfe,'dfe_mu_tr',0.005,'sps',sps,'decide',0);
+ pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+ 
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ 
+ mlse_results.metrics.print
+ if our_signal
+ fprintf('Our Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ else
+ fprintf('Their Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ end
+ BER = mlse_results.metrics.BER;
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",100,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",11,"sps",sps, ...
+ % "traceback_depth",256,"L",4,"delta",4,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % if our_signal
+ % fprintf('Our Signal: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+ end
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf.m
new file mode 100644
index 0000000..fe8f371
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf.m
@@ -0,0 +1,271 @@
+%%
+clear all;
+close all;
+
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 4;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+% data_tr_mf is the already recovered and filtered data
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+%Calculate Transfer Function
+Tx_spectrum = Digi_sig_compare;
+Rx_spectrum = Scope_sig;
+
+[~,Rx_synced_spectrum,inverted_spectrum,sequenceFound_spectrum,sequenceStarts_spectrum] = Rx_spectrum.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_spectrum = Rx_synced_spectrum{1};
+
+Tx_spectrum = Tx_spectrum.resample("fs_out",fsym);
+Tx_spectrum.signal = fft(Tx_spectrum.signal);
+
+Rx_spectrum = Rx_spectrum.resample("fs_out",fsym);
+Rx_spectrum.signal = fft(Rx_spectrum.signal);
+
+H_transfer = Rx_spectrum.signal./Tx_spectrum.signal;
+H_inv = 1./H_transfer;
+
+%Number of Samples/Symbol after Matched Filter
+Kov = 14;
+
+Scope_sig = Scope_sig.resample('fs_in',fs,'fs_out',Kov*fsym);
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",Kov*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+% Rx_matched = Scope_sig;
+% Rx_matched = Rx_matched.resample('fs_out',Kov*fsym);
+
+data_tr_mf = Electricalsignal(data_tr_mf.Results, "fs", fsym);
+[~,Rx_synced_cell_tr_mf,inverted_tr_mf,sequenceFound_tr_mf,sequenceStarts_tr_mf] = data_tr_mf.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_tr_mf = Rx_synced_cell_tr_mf{1};
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+
+% Rx_matched = Scope_sig;
+% Rx_matched = Rx_matched.resample("fs_out",2*fsym);
+
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+Rx_matched_original = Rx_synced_cell{1};
+Rx_matched_original.signal = resample(Rx_matched_original.signal,1,Kov);
+Rx_matched_original.fs = fsym;
+
+% Rx_matched_1 = Rx_matched_1.resample("fs_in",Kov*fsym,'fs_out',2*fsym);
+
+Time_Rec = 1;
+if Time_Rec
+ % [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',Kov,'damping_factor',1,'normalized_loop_bandwidth',1e-4,'detector_gain',2.7).process(Rx_matched_1);
+
+ [Rx_Time_Rec, Timing_Error_MG] = Godard_Timing_Recovery('mode',3,'num_blocks',1,'fft_length',length(Rx_matched_1),'sps',Kov,'rolloff',0.6,'mu',-0.2,'Ki',1e-4).process(Rx_matched_1);
+ Rx_Time_Rec = Rx_Time_Rec.resample('fs_in',Kov*fsym,'fs_out',fsym);
+
+ % Rx_Time_Rec = MaxVar_Timing_Recovery('mode',0,'fsym',fsym,'fadc',Kov*fsym,'num_tau',Kov*128,'sps',Kov,'comp_signal',Rx_tr_mf,'comp_mode',0).process(Rx_matched_1);
+ 
+ sps = 1;
+else
+ Rx_Time_Rec = Rx_matched_1;
+ sps = 1;
+end
+
+Rx_Time_Rec.fs = fsym;
+
+% h = gaussdesign(0.4,64,1);
+% Rx_Time_Rec.signal = filtfilt(h,1,Rx_Time_Rec.signal);
+
+% Rx_com = Rx_Time_Rec;
+% Rx_com = Rx_com.resample('fs_in',fsym,'fs_out',2*fsym);
+% Rx_com.spectrum("displayname",'After TR','normalizeTo0dB',1,'fignum',101114);
+% Rx_matched_1.spectrum("displayname",'Before TR','normalizeTo0dB',1,'fignum',101114);
+% Rx_tr_mf_2sps = Rx_tr_mf;
+% Rx_tr_mf_2sps = Rx_tr_mf_2sps.resample('fs_in',fsym,'fs_out',2*fsym);
+% Rx_tr_mf_2sps.spectrum("displayname",'Their signal after TR','normalizeTo0dB',1,'fignum',101114);
+
+% Rx_Time_Rec = Rx_Time_Rec.resample('fs_out',6e9);
+% Rx_tr_mf = Rx_tr_mf.resample('fs_out',fsym);
+
+% Amax = 10;
+% H_inv = min(abs(H_inv), Amax) .* exp(1j*angle(H_inv));
+% Rx_Time_Rec.signal = fft(Rx_Time_Rec.signal);
+% Rx_Time_Rec.signal = ifft(Rx_Time_Rec.signal .* H_inv);
+
+Rx_Time_Rec = Rx_Time_Rec.normalize('mode','rms');
+Rx_tr_mf = Rx_tr_mf.normalize('mode','rms');
+
+Rx_Time_Rec.spectrum("displayname",'Our signal','normalizeTo0dB',1,'fignum',101111);
+% Rx_matched_original.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+Rx_tr_mf.spectrum("displayname",'Their signal','normalizeTo0dB',1,'fignum',101111);
+
+Rx_Time_Rec.normalize("mode","rms").plot("displayname",'Our signal','fignum',101311);
+% Rx_matched_original.normalize("mode","rms").plot("displayname",'Original signal','fignum',101311);
+Rx_tr_mf.normalize("mode","rms").plot("displayname",'Their signal','fignum',101311);
+
+%% not working.. 
+% Use our or their signal
+for our_signal = 1
+ if our_signal
+ Rx_synced = Rx_Time_Rec;
+ else
+ Rx_synced = Rx_tr_mf;
+ end
+ % Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = 4096*2;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",[150, 0, 0],"Nb",[0,0,0], ...
+ % "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ % "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ % "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ % 
+ % % eq_ffe = FFE_DFE('ffe_order',99,'dfe_order',99,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5,'sps',sps,'decide',0);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % if our_signal
+ % fprintf('Our signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+ 
+ %% -------------------- VNLE + MLSE --------------------
+ pf_ncoeffs = 4;
+ eq_v = EQ("Ne",[300, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1, ...
+ 'weighted_DFE',0,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','R2','weighted_DFE_I_mode',[5,0.5,0.6]);
+ % eq_v = FFE_DFE('ffe_order',300,'dfe_order',5,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5, ...
+ % 'ffe_mu_dd',mu_ffe,'ffe_mu_tr',0,'dfe_mu_dd',mu_dfe,'dfe_mu_tr',0.005,'sps',sps,'decide',0);
+ pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+ mlse_results.metrics.print
+ if our_signal
+ fprintf('Our Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ else
+ fprintf('Their Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ end
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",100,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",11,"sps",sps, ...
+ % "traceback_depth",256,"L",4,"delta",4,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % if our_signal
+ % fprintf('Our Signal: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam2.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam2.m
new file mode 100644
index 0000000..e6f4c45
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam2.m
@@ -0,0 +1,195 @@
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 2;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ % Already recovered and filtered data
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+
+data_tr_mf = Electricalsignal(data_tr_mf.Results, "fs", fsym);
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+[~,Rx_synced_cell_tr_mf,inverted_tr_mf,sequenceFound_tr_mf,sequenceStarts_tr_mf] = data_tr_mf.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_tr_mf = Rx_synced_cell_tr_mf{1};
+
+
+Time_Rec = 1;
+if Time_Rec
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',2,'damping_factor',1,'normalized_loop_bandwidth',0.01,'detector_gain',2.7).process(Rx_matched_1);
+ sps = 1;
+else
+ sps = 2;
+end
+
+if Time_Rec == 1 && M == 2 
+ Rx_Time_Rec.fs = 14e9;
+elseif Time_Rec == 1 && M == 4
+ Rx_Time_Rec.fs = 6e9;
+end
+
+Rx_Time_Rec.spectrum("displayname",'Signal after matched filter, synchronization, and timing recovery','fignum',101111);
+Rx_tr_mf.spectrum("displayname",'Signal after matched filter, synchronization, and timing recovery','fignum',101111);
+
+Rx_Time_Rec_plot = Rx_Time_Rec;
+Rx_tr_mf_plot = Rx_tr_mf;
+Rx_Time_Rec_plot.normalize("mode","rms").plot("displayname",'Signal after matched filter, synchronization, and timing recovery','fignum',101311);
+Rx_tr_mf_plot.normalize("mode","rms").plot("displayname",'Signal after matched filter, synchronization, and timing recovery','fignum',101311);
+
+%% not working.. 
+% Use our or their signal
+our_signal = 0;
+if our_signal
+ Rx_synced = Rx_Time_Rec;
+else
+ Rx_synced = Rx_tr_mf;
+end
+% Rx_synced = Rx_Time_Rec;
+% Rx_synced = Rx_synced_cell{1};
+len_tr = 4096*2;
+mu_ffe1 = 0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.004;
+mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+sps = 1;
+
+Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+
+Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+%% -------------------- FFE --------------------
+% % requires some more digging what is going on :-) 
+% eq_ffe = EQ("Ne",[200, 0, 0],"Nb",[0,0,0], ...
+% "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+% "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+% "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+% 
+% ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+% "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+% "eth_style_symbol_mapping",mapping_style);
+% 
+% % ffe_results.metrics.print
+% fprintf('My EQ: %.1e \n',ffe_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
+% BER_value = ffe_results.metrics.BER;
+
+%% -------------------- VNLE + MLSE --------------------
+
+pf_ncoeffs = 4;
+eq_v = EQ("Ne",[200, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1, ...
+ 'weighted_DFE',0,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','I2','weighted_DFE_I_mode',[5,0.5,0.6]);
+pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+mlse_results.metrics.print
+fprintf('My EQ: %.1e \n',mlse_results.metrics.BER);
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+
+%% -------------------- ML-based MLSE (L=2) --------------------
+% ml_mlse_equalizer = ML_MLSE("epochs_tr",400,"epochs_dd",1, ...
+% "len_tr",len_tr,"mu_dd",0.03,"mu_tr",0.03,"order",100,"sps",sps, ...
+% "traceback_depth",256,"L",2,"delta",4,"adaptive_mu",0);
+% 
+% [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+% fprintf('ML-based MLSE:\n');
+% fprintf('My EQ: %.1e \n',ml_mlse_results.metrics.BER);
+% fprintf('Paper: %.1e \n \n',ber_in_paper);
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam_2.m b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam_2.m
new file mode 100644
index 0000000..d27d6e1
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/first_analysis_tr_mf_pam_2.m
@@ -0,0 +1,272 @@
+%%
+clear all;
+close all;
+
+%%
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 2;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+% data_tr_mf is the already recovered and filtered data
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+%Calculate Transfer Function
+Tx_spectrum = Digi_sig_compare;
+Rx_spectrum = Scope_sig;
+
+[~,Rx_synced_spectrum,inverted_spectrum,sequenceFound_spectrum,sequenceStarts_spectrum] = Rx_spectrum.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_spectrum = Rx_synced_spectrum{1};
+
+Tx_spectrum = Tx_spectrum.resample("fs_out",fsym);
+Tx_spectrum.signal = fft(Tx_spectrum.signal);
+
+Rx_spectrum = Rx_spectrum.resample("fs_out",fsym);
+Rx_spectrum.signal = fft(Rx_spectrum.signal);
+
+H_transfer = Rx_spectrum.signal./Tx_spectrum.signal;
+H_inv = 1./H_transfer;
+
+%Number of Samples/Symbol after Matched Filter
+Kov = 14;
+
+Scope_sig = Scope_sig.resample('fs_in',fs,'fs_out',Kov*fsym);
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",Kov*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+% Rx_matched = Scope_sig;
+% Rx_matched = Rx_matched.resample('fs_out',Kov*fsym);
+
+data_tr_mf = Electricalsignal(data_tr_mf.Results, "fs", fsym);
+[~,Rx_synced_cell_tr_mf,inverted_tr_mf,sequenceFound_tr_mf,sequenceStarts_tr_mf] = data_tr_mf.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_tr_mf = Rx_synced_cell_tr_mf{1};
+
+% timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+
+% Rx_matched = Scope_sig;
+% Rx_matched = Rx_matched.resample("fs_out",2*fsym);
+
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+Rx_matched_original = Rx_synced_cell{1};
+Rx_matched_original.signal = resample(Rx_matched_original.signal,1,Kov);
+Rx_matched_original.fs = fsym;
+
+% Rx_matched_1 = Rx_matched_1.resample("fs_in",Kov*fsym,'fs_out',2*fsym);
+
+Time_Rec = 1;
+if Time_Rec
+ % [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',Kov,'damping_factor',1,'normalized_loop_bandwidth',1e-4,'detector_gain',2.7).process(Rx_matched_1);
+ % Rx_matched_1.spectrum('normalizeTo0dB',1,"displayname",'Signal before Timing Recovery','fignum',1023);
+ % [Rx_Time_Rec, Timing_Error_MG] = Godard_Timing_Recovery('mode',3,'num_blocks',1,'fft_length',length(Rx_matched_1),'sps',Kov,'rolloff',0.6,'mu',-0.2,'Ki',1e-4).process(Rx_matched_1);
+ Rx_Time_Rec = MaxVar_Timing_Recovery('mode',0,'fsym',fsym,'fadc',Kov*fsym,'num_tau',Kov*16,'sps',Kov,'comp_signal',Rx_tr_mf,'comp_mode',0).process(Rx_matched_1);
+ % Rx_Time_Rec.spectrum('normalizeTo0dB',1,"displayname",'Signal after Timing Recovery','fignum',1023);
+ % idx = 1:2:length(Rx_Time_Rec);
+ % Rx_Time_Rec.signal = Rx_Time_Rec.signal(idx);
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal,1,Kov);
+ % Rx_Time_Rec = Rx_matched_1;
+ % Rx_Time_Rec.signal = resample(Rx_Time_Rec.signal,1,Kov);
+ % figure(2222222)
+ % plot(Timing_Error)
+ sps = 1;
+else
+ Rx_Time_Rec = Rx_matched_1;
+ sps = 1;
+end
+Rx_Time_Rec.fs = fsym;
+
+Rx_com = Rx_Time_Rec;
+Rx_com = Rx_com.resample('fs_in',fsym,'fs_out',2*fsym);
+Rx_com.spectrum("displayname",'After TR','normalizeTo0dB',1,'fignum',101114);
+Rx_matched_1.spectrum("displayname",'Before TR','normalizeTo0dB',1,'fignum',101114);
+Rx_tr_mf_2sps = Rx_tr_mf;
+Rx_tr_mf_2sps = Rx_tr_mf_2sps.resample('fs_in',fsym,'fs_out',2*fsym);
+Rx_tr_mf_2sps.spectrum("displayname",'Their signal after TR','normalizeTo0dB',1,'fignum',101114);
+
+% Rx_Time_Rec = Rx_Time_Rec.resample('fs_out',6e9);
+% Rx_tr_mf = Rx_tr_mf.resample('fs_out',fsym);
+
+% Amax = 10;
+% H_inv = min(abs(H_inv), Amax) .* exp(1j*angle(H_inv));
+% Rx_Time_Rec.signal = fft(Rx_Time_Rec.signal);
+% Rx_Time_Rec.signal = ifft(Rx_Time_Rec.signal .* H_inv);
+
+Rx_Time_Rec = Rx_Time_Rec.normalize('mode','rms');
+Rx_tr_mf = Rx_tr_mf.normalize('mode','rms');
+
+Rx_Time_Rec.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+% Rx_matched_original.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+Rx_tr_mf.spectrum('normalizeTo0dB',1,"displayname",'Their signal','fignum',101111);
+
+Rx_Time_Rec.normalize("mode","rms").plot("displayname",'Our signal','fignum',101311);
+% Rx_matched_original.normalize("mode","rms").plot("displayname",'Original signal','fignum',101311);
+Rx_tr_mf.normalize("mode","rms").plot("displayname",'Their signal','fignum',101311);
+
+%% not working.. 
+% Use our or their signal
+for our_signal = 1
+ if our_signal
+ Rx_synced = Rx_Time_Rec;
+ else
+ Rx_synced = Rx_tr_mf;
+ end
+ % Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = 4096*2;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",[500, 0, 0],"Nb",[0,0,0], ...
+ % "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ % "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ % "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ % 
+ % % eq_ffe = FFE_DFE('ffe_order',99,'dfe_order',99,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5,'sps',sps,'decide',0);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % if our_signal
+ % fprintf('Our signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+ 
+ %% -------------------- VNLE + MLSE --------------------
+ pf_ncoeffs = 4;
+ eq_v = EQ("Ne",[250, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1, ...
+ 'weighted_DFE',0,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','R2','weighted_DFE_I_mode',[5,0.5,0.6]);
+ % eq_v = FFE_DFE('ffe_order',200,'dfe_order',0,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5, ...
+ % 'ffe_mu_dd',mu_ffe,'ffe_mu_tr',0,'dfe_mu_dd',mu_dfe,'dfe_mu_tr',0.005,'sps',sps,'decide',0);
+ pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+ mlse_results.metrics.print
+ if our_signal
+ fprintf('Our Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ else
+ fprintf('Their Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ end
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",100,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",11,"sps",sps, ...
+ % "traceback_depth",256,"L",4,"delta",4,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % if our_signal
+ % fprintf('Our Signal: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/Evaluation Scripts/ml_mlse_complexity_plot.m b/projects/FSO_transmission/Evaluation Scripts/ml_mlse_complexity_plot.m
new file mode 100644
index 0000000..d30035b
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/ml_mlse_complexity_plot.m
@@ -0,0 +1,11 @@
+N = 1:4;
+S = 4;
+L = 1:4;
+Complexity_N = (N+1).*S.^(1+1);
+Complexity_L = (1+1).*S.^(L+1);
+
+figure;
+plot(N,Complexity_N)
+hold on
+plot(L,Complexity_L)
+hold off
diff --git a/projects/FSO_transmission/Evaluation Scripts/plotscript_baud_rate_sweep_method.m b/projects/FSO_transmission/Evaluation Scripts/plotscript_baud_rate_sweep_method.m
new file mode 100644
index 0000000..ae3a1b5
--- /dev/null
+++ b/projects/FSO_transmission/Evaluation Scripts/plotscript_baud_rate_sweep_method.m
@@ -0,0 +1,36 @@
+x = 4:8;
+for method = 1:4
+ filename = fullfile('C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\Silas DSP\imdd_simulation\projects\FSO_transmission\Data', ['BER_PAM_4_baud_rate_method' num2str(method) '.mat']);
+ data = load(filename);
+ BER = data.BER_PAM_4;
+ figure(22113344)
+ plot(x,BER,'-o','LineWidth',1.75);
+ hold on
+end
+
+h1 = yline(2e-2, ':k', 'LineWidth',1.5);
+h2 = yline(3.8e-3,':b', 'LineWidth',1.5);
+h3 = yline(4.85e-3,':g', 'LineWidth',1.5);
+h4 = yline(2.2e-4,':r', 'LineWidth',1.5);
+
+% Legende NUR für Kurven
+legend('FFE - 300 Taps','FFE+PF+MLSE - 300 FFE Taps - 4 PF Coefficients','ML-MLSE - 1000 epochs - 100th order - Memory Length = 2','DB - 300 FFE Taps',...
+ 'Interpreter','latex', ...
+ 'Location','southeast', 'FontSize', 14)
+
+% FEC Labels direkt im Plot
+text(6.85,2.3e-2,'o-FEC','Color','k','FontSize', 14, 'Interpreter','latex')
+text(7,3e-3,'HD-FEC','Color','b','FontSize', 14, 'Interpreter','latex')
+text(7,5.6e-3,'KP4+Hamming','Color','g','FontSize', 14,'Interpreter','latex')
+text(7,2.5e-4,'KP4','Color','r','FontSize', 14,'Interpreter','latex')
+
+xlabel('Symbol Rate [GBd]', 'Interpreter','latex')
+ylabel('BER', 'Interpreter','latex')
+% title('BER for PAM-4', 'Interpreter','latex')
+
+grid minor
+% ylim([1e-4 5e-2])
+xlim([3 9])
+set(gca,'YScale','log')
+hold off
+% beautifyBERplot
\ No newline at end of file
diff --git a/projects/FSO_transmission/FSO_timing_recovery_minimal_example.m b/projects/FSO_transmission/FSO_timing_recovery_minimal_example.m
new file mode 100644
index 0000000..36884ff
--- /dev/null
+++ b/projects/FSO_transmission/FSO_timing_recovery_minimal_example.m
@@ -0,0 +1,251 @@
+%%
+clear all;
+close all;
+
+%% Choose a fitting base leading to the FSO Data
+base = "C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\";
+mode = 0; %0 oder 1
+M = 4;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+% data_tr_mf is the already recovered and filtered data
+if M == 2
+ tx_data_path = fullfile(base, "14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data_path = fullfile(base, "6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+ data_tr_mf = load("C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\FSO Equalizer\FSO_FP_QCL_60umUTC\Already Recovered and Filtered\AfterSync_M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+tx_data = load(tx_data_path);
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_compare = Pform.process(Symbols);
+
+MF = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+Scope_sig.plot("displayname",'raw','fignum',100);
+Scope_sig.spectrum("displayname",'raw','fignum',101)
+
+%Calculate Transfer Function
+Tx_spectrum = Digi_sig_compare;
+Rx_spectrum = Scope_sig;
+
+[~,Rx_synced_spectrum,inverted_spectrum,sequenceFound_spectrum,sequenceStarts_spectrum] = Rx_spectrum.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_spectrum = Rx_synced_spectrum{1};
+
+Tx_spectrum = Tx_spectrum.resample("fs_out",fsym);
+Tx_spectrum.signal = fft(Tx_spectrum.signal);
+
+Rx_spectrum = Rx_spectrum.resample("fs_out",fsym);
+Rx_spectrum.signal = fft(Rx_spectrum.signal);
+
+H_transfer = Rx_spectrum.signal./Tx_spectrum.signal;
+H_inv = 1./H_transfer;
+
+%Number of Samples/Symbol after Matched Filter
+Kov = 14;
+
+Scope_sig = Scope_sig.resample('fs_in',fs,'fs_out',Kov*fsym);
+
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef firectly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+Matched_Filter = Pulseformer("fsym",fsym,"fdac",Kov*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+Rx_matched = Matched_Filter.process(Scope_sig);
+Rx_matched.spectrum("displayname",'Signal after matched filter','fignum',1);
+
+% Loading and synchronizing their matched filtered and timing recovered data
+data_tr_mf = Electricalsignal(data_tr_mf.Results, "fs", fsym);
+[~,Rx_synced_cell_tr_mf,inverted_tr_mf,sequenceFound_tr_mf,sequenceStarts_tr_mf] = data_tr_mf.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_tr_mf = Rx_synced_cell_tr_mf{1};
+
+% Timing sync -> at this point we still have no symbol timing recovery, we
+% try to do this with 2sps EQ!
+[~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
+Rx_matched_1 = Rx_synced_cell{1};
+
+% Timing recovery
+Time_Rec = 1;
+Timing_Mode = 3;
+if Time_Rec
+ if Timing_Mode == 1 % Zero-Crossing/Gardner/Early-Late/Müller-Mueller Timing Recovery
+ [Rx_Time_Rec, Timing_Error] = Timing_Recovery("modulation", 'PAM/PSK/QAM', "timing_error_detector",'Gardner (non-data-aided)','sps',Kov,'damping_factor',1,'normalized_loop_bandwidth',1e-4,'detector_gain',2.7).process(Rx_matched_1);
+ elseif Timing_Mode == 2 % Godard Timing Recovery (resampling is required as the Godard timing recovery does not change the number of samples per symbol)
+ [Rx_Time_Rec, Timing_Error_MG] = Godard_Timing_Recovery('mode',3,'num_blocks',1,'fft_length',length(Rx_matched_1),'sps',Kov,'rolloff',0.6,'mu',-0.2,'Ki',1e-4).process(Rx_matched_1);
+ Rx_Time_Rec = Rx_Time_Rec.resample('fs_in',Kov*fsym,'fs_out',fsym);
+ elseif Timing_Mode == 3 % Maximum Variance Timing Recovery
+ Rx_Time_Rec = MaxVar_Timing_Recovery('mode',0,'fsym',fsym,'fadc',Kov*fsym,'num_tau',Kov*128,'sps',Kov,'comp_signal',Rx_tr_mf,'comp_mode',0).process(Rx_matched_1);
+ end
+else
+ Rx_Time_Rec = Rx_matched_1.resample('fs_in',Kov*fsym,'fs_out',fsym);
+end
+sps = 1;
+Rx_Time_Rec.fs = fsym;
+
+% % Compensate using the inverse transfer function
+% Amax = 10;
+% H_inv = min(abs(H_inv), Amax) .* exp(1j*angle(H_inv));
+% Rx_Time_Rec.signal = fft(Rx_Time_Rec.signal);
+% Rx_Time_Rec.signal = ifft(Rx_Time_Rec.signal .* H_inv);
+
+% Normalization
+Rx_Time_Rec = Rx_Time_Rec.normalize('mode','rms');
+Rx_tr_mf = Rx_tr_mf.normalize('mode','rms');
+
+% Compare spectra of our and their time signal
+Rx_Time_Rec.spectrum("displayname",'Our signal','normalizeTo0dB',1,'fignum',101111);
+% Rx_matched_original.spectrum('normalizeTo0dB',1,"displayname",'Our signal','fignum',101111);
+Rx_tr_mf.spectrum("displayname",'Their signal','normalizeTo0dB',1,'fignum',101111);
+
+% Compare our and their time signal
+Rx_Time_Rec.normalize("mode","rms").plot("displayname",'Our signal','fignum',101311);
+% Rx_matched_original.normalize("mode","rms").plot("displayname",'Original signal','fignum',101311);
+Rx_tr_mf.normalize("mode","rms").plot("displayname",'Their signal','fignum',101311);
+
+%%
+% Use our or their signal
+for our_signal = 1
+ if our_signal
+ Rx_synced = Rx_Time_Rec;
+ else
+ Rx_synced = Rx_tr_mf;
+ end
+ % Rx_synced = Rx_Time_Rec;
+ % Rx_synced = Rx_synced_cell{1};
+ len_tr = 4096*2;
+ mu_ffe1 = 0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.004;
+ mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
+ mu_dfe = 0.0004;
+ duob_mode = db_mode.no_db;
+ 
+ Rx_synced.plot("displayname",'RX: Matched+Sync+2sps','fignum',103);
+ Rx_synced.spectrum("displayname",'RX: Matched+Sync+2sps','fignum',104);
+ 
+ Digi_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped','fignum',1,'normalizeTo0dB',1);
+ Rx_sig_compare.normalize("mode","rms").spectrum("displayname",'Tx: RC-shaped + matched filtered ','fignum',1,'normalizeTo0dB',1);
+ Rx_synced.normalize("mode","rms").spectrum("displayname",'RX: matched filtered + synced','fignum',1,'normalizeTo0dB',1);
+ 
+ if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+ elseif M == 4
+ ber_in_paper = 10^(-2.5);
+ end
+ 
+ %% -------------------- FFE --------------------
+ % requires some more digging what is going on :-) 
+ % eq_ffe = EQ("Ne",[150, 0, 0],"Nb",[0,0,0], ...
+ % "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ % "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ % "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ % 
+ % % eq_ffe = FFE_DFE('ffe_order',99,'dfe_order',99,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5,'sps',sps,'decide',0);
+ % 
+ % ffe_results = ffe(eq_ffe,M,Rx_synced,Symbols,Bits, ...
+ % "precode_mode",duob_mode,'showAnalysis',0,"postFFE",[], ...
+ % "eth_style_symbol_mapping",mapping_style);
+ % 
+ % if our_signal
+ % fprintf('Our signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their signal: %.1e \n',ffe_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+ 
+ %% -------------------- VNLE + MLSE --------------------
+ pf_ncoeffs = 4;
+ eq_v = EQ("Ne",[300, 0, 0],"Nb",[0, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1, ...
+ 'weighted_DFE',0,'weighted_DFE_d_min',0.5,'weighted_DFE_mode','R2','weighted_DFE_I_mode',[5,0.5,0.6]);
+ % eq_v = FFE_DFE('ffe_order',300,'dfe_order',5,'len_tr',len_tr,'epochs_tr',5,'epochs_dd',5, ...
+ % 'ffe_mu_dd',mu_ffe,'ffe_mu_tr',0,'dfe_mu_dd',mu_dfe,'dfe_mu_tr',0.005,'sps',sps,'decide',0);
+ pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+
+ mlse_results.metrics.print
+ if our_signal
+ fprintf('Our Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ else
+ fprintf('Their Signal: %.1e \n',mlse_results.metrics.BER);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ end
+ 
+ %% -------------------- ML-based MLSE (L=2) --------------------
+ % ml_mlse_equalizer = ML_MLSE("epochs_tr",100,"epochs_dd",1, ...
+ % "len_tr",length(Rx_synced),"mu_dd",0.03,"mu_tr",0.03,"order",11,"sps",sps, ...
+ % "traceback_depth",256,"L",4,"delta",4,"adaptive_mu",0);
+ % 
+ % [ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode,"eth_style_symbol_mapping",mapping_style);
+ % if our_signal
+ % fprintf('Our Signal: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % else
+ % fprintf('Their EQ: %.1e \n',ml_mlse_results.metrics.BER);
+ % fprintf('Paper: %.1e \n \n',ber_in_paper);
+ % end
+end
\ No newline at end of file
diff --git a/projects/FSO_transmission/first_analysis.m b/projects/FSO_transmission/first_analysis.m
new file mode 100644
index 0000000..9a327ab
--- /dev/null
+++ b/projects/FSO_transmission/first_analysis.m
@@ -0,0 +1,252 @@
+
+base = "C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC";
+mode = 0; %0 oder 1
+M = 2;
+
+all_files = dir(fullfile(base, "**/*.mat"));
+
+if M == 2
+ tx_data = load("C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC\14G_PAM2\tx_info\tx_info_PAM2_14Gbd0.75RRC.mat");
+ filename = fullfile(base, "14G_PAM2\M=2_Rs=1.4e10_Fs=8e10_I=265mA_RoP=46.3mW_L=31m_PS=RRC_rolloff=0.75_Mode=Rise.mat");
+elseif M == 4
+ tx_data = load("C:\Users\Silas\Nextcloud4\Dokumente\02_Ablage_Office\FSO_FP_QCL_60umUTC\6G_PAM4\tx_info\tx_info_PAM4_6Gbd0.6RRC.mat");
+ filename = fullfile(base, "6G_PAM4\M=4_Rs=6e9_Fs=8e10_I=255mA_RoP=42.3mW_L=31m_PS=RRC_rolloff=0.6_Mode=Rise.mat");
+end
+
+if mode == 1
+ [f, p] = uigetfile(fullfile(base, "**/*.mat")); 
+ if f~=0
+ filename = fullfile(p,f);
+ end
+end
+
+
+datas = load(filename);
+
+%%
+str = filename;
+M_ = str2double(regexp(str, 'M=([^_]+)', 'tokens', 'once'));
+assert(M==M_);
+fsym = str2double(regexp(str, 'Rs=([^_]+)', 'tokens', 'once'));
+fs = str2double(regexp(str, 'Fs=([^_]+)', 'tokens', 'once'));
+I = sscanf(char(regexp(str, 'I=([^_]+)', 'tokens', 'once')), '%f');
+rop = sscanf(char(regexp(str, 'RoP=([^_]+)', 'tokens', 'once')), '%f');
+L = sscanf(char(regexp(str, 'L=([^_]+)', 'tokens', 'once')), '%f');
+pulseshape = string( regexp(str, 'PS=([^_]+)', 'tokens', 'once'));
+rolloff = str2double(regexp(str, 'rolloff=([^_]+)', 'tokens', 'once'));
+mode = string( regexp(str, 'Mode=([^\.]+)', 'tokens', 'once'));
+
+%%
+% Tx data
+
+Bits = Informationsignal(tx_data.tx_data,"fs",fsym);
+Symbols = Informationsignal(real(tx_data.tx_PAM_sym),"fs",fsym);
+
+mapping_style = M==4; % Pam2 is like move-it; PAM-4 is different, same mapping like ETH peopled used in Zurich... hence the "eth_style" argument here and there
+PM = PAMmapper(M,0,"eth_style",mapping_style); % one should rename "eth style" as this is simply a different mapping scheme
+
+Symbols_ = PM.map(Bits) .* PM.scaling;
+assert(isequal(Symbols.signal,Symbols_.signal));
+
+Bits_ = PM.demap(Symbols);
+[bits,errors,ber,errorIndice] = calc_ber(Bits_.signal,Bits.signal);
+assert(ber == 0);
+
+%% For comparison, apply pulsef on Tx Symbols
+Pform = Pulseformer("fsym",fsym,"fdac",fs,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sig_tx_compare = Pform.process(Symbols);
+
+Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Digi_sync = Pform.process(Symbols);
+
+MF = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff);
+Rx_sig_compare = MF.process(Digi_sig_tx_compare);
+
+%%
+
+% Rx Data
+traceData = datas.tr.lastData(2).trace.ch3;
+
+%FYI: Voltage=(RawDataYReference)×YIncrement+YOrigin
+scoperead_volts = (traceData.RawData - traceData.YReference) * traceData.YIncrement + traceData.YOrigin;
+demystified = isequal(traceData.YData,scoperead_volts);
+assert(demystified);
+
+timesig_compare = [0:1:datas.tr.lastData(1).trace.ch3.Points-1] ./ fs;
+timesig = datas.tr.lastData(1).trace.ch3.XData;
+assert(isequal(traceData.YData,scoperead_volts));
+
+Scope_sig = Electricalsignal(traceData.YData,"fs",fs);
+
+%%
+% 1) matched filter
+% pulse is symmetric, hence we can use pulsef directly as matched filter.
+% It feels off (bit I think correct) that the fsym is now the output freq.!!
+% -> output 2 sps to omit timing recovery!?
+apply_matched_filter = 1;
+k = 1;
+if apply_matched_filter
+ 
+ Pform = Pulseformer("fsym",fsym,"fdac",k*fsym,"pulse","rrc","pulselength",16,"alpha",rolloff,"matched",1);
+ Rx_matched = Pform.process(Scope_sig);
+else
+
+ Rx_matched = Filter('filtdegree',4,"f_cutoff",fsym*0.5,"fs",Scope_sig.fs,"filterType",filtertypes.gaussian,"active",true).process(Scope_sig);
+ Rx_matched = Rx_matched.resample("fs_out",k*fsym);
+end
+Rx_matched.spectrum();
+
+
+%%
+
+coefficients = arburg(Rx_matched.signal,25);
+
+figure()
+[h,w] = freqz(1,coefficients,Rx_matched.length,"whole",Rx_matched.fs);
+h = h/max(abs(h));
+hold on
+w_ = (w - Rx_matched.fs/2);
+plot(w_.*1e-9,20*log10(fftshift(abs(h))),'DisplayName',['Burg Coeffs: ', num2str(round(coefficients,2)), ' '],'LineWidth',2);
+
+%% Timing Rec
+apply_timing_rec = 1;
+if apply_timing_rec
+ [Rx_symbolsync, timing_error] = Timing_Recovery("timing_error_detector",'Gardner (non-data-aided)','sps',k,'damping_factor',0.1,'normalized_loop_bandwidth',0.1,'detector_gain',2.7).process(Rx_matched);
+ figure();plot(timing_error);
+ Rx_symbolsync.fs = fsym;
+ % Tsynch
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_symbolsync.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
+ Rx_synced = Rx_synced_cell{1};
+ sps = 1;
+else
+ % Tsynch
+ [~,Rx_synced_cell,inverted,sequenceFound,sequenceStarts] = Rx_matched.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
+ Rx_synced = Rx_synced_cell{1};
+ Rx_synced = Rx_synced.resample("fs_out",2*fsym);
+ sps = 2;
+end
+
+if M == 2
+ ber_in_paper = 10^(-2.6); %fig 3a) 4 Gb/s MWIR FSO Transmission using Directly Modulated QCL and an Uncooled UTC-PD at Room-Temperature
+elseif M == 4
+ ber_in_paper = 10^(-2.5);
+end
+
+
+%%
+
+Rx_synced = Rx_synced_cell{1};
+
+
+% -------------------- FFE --------------------
+% requires some more digging what is going on :-)
+eq_ffe = EQ("Ne",[50, 1, 1],"Nb",[2,0,0], ...
+ "training_length",512,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+
+vars = logspace(-4,-3,36);
+
+parfor i = 1:numel(vars)
+
+ len_tr = 4096;
+ mu_ffe1 = 0.01;% mus(i);%0.0001;
+ mu_ffe2 = 0.0008;
+ mu_ffe3 = 0.001;
+ mu_dc = 0.005;
+ mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
+ mu_dfe = vars(i);
+ duob_mode = db_mode.no_db;
+
+ % requires some more digging what is going on :-)
+ eq_ffe_1 = EQ("Ne",[150, 1, 0],"Nb",[50,0,0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",0);
+
+ eq_ffe_2 = FFE("epochs_tr",1,"epochs_dd",vars(i),"len_tr",4096,"mu_dd",vars(i),"mu_tr",vars(i),"order",999,"sps",1,"decide",0, "adaption",adaption_method.nlms,"dd_mode",0);
+ % eq_ffe_2 = FFE_DFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"ffe_mu_dd",1e-5,"dfe_mu_dd",mus(i),"ffe_mu_tr",0,"dfe_mu_tr",0,"ffe_order",50,"dfe_order",10,"sps",1,"decide",1);
+
+
+ ffe_results = ffe(eq_ffe_1,M,Rx_synced,Symbols,Bits, ...
+ "precode_mode",duob_mode,'showAnalysis',1,"postFFE",[], ...
+ "eth_style_symbol_mapping",mapping_style);
+
+ ffe_results.metrics.BER
+ bers(i) = ffe_results.metrics.BER;
+end
+
+figure();
+plot(vars,bers);
+yline(ber_in_paper)
+beautifyBERplot();
+% 
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+ffe_results.metrics.print("description",'FFE');
+fprintf('FFE: %.1e \n',ffe_results.metrics.BER);
+
+
+%% -------------------- VNLE + MLSE --------------------
+len_tr = 4096;
+mu_ffe1 = 0.0001;% mus(i);%0.0001;
+mu_ffe2 = 0.0008;
+mu_ffe3 = 0.001;
+mu_dc = 0.005;
+mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
+mu_dfe = 0.0004;
+duob_mode = db_mode.no_db;
+
+pf_ncoeffs = 4;
+
+vars = 1:7;
+bers = zeros(size(vars));
+parfor i = 1:numel(vars)
+ 
+ eqv = EQ("Ne",[200, 1, 0],"Nb",[2, 0, 0], ...
+ "training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005, ...
+ "FFEmu",0,"plotfinal",0,"ideal_dfe",1);
+ pf_ncoeffs = vars(i);
+ pf = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
+ mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0,"eth_style",mapping_style).levels);
+ [vnle_results, mlse_results] = vnle_postfilter_mlse(eqv, pf, mlse_, M, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", mapping_style);
+ fprintf('Paper: %.1e \n \n',ber_in_paper);
+ vnle_results.metrics.print("description",'VNLE');
+ mlse_results.metrics.print("description",'MLSE');
+ bers(i) = mlse_results.metrics.BER;
+end
+%%
+figure();hold on
+plot(vars,bers_ffe,'DisplayName','FFE [200,0,0] + PF + MLSE');
+plot(vars,bers_vnle,'DisplayName','VNLE [200,1,0] + PF + MLSE');
+plot(vars,bers_vnledfe,'DisplayName','VNLE [200,1,0] + DFE [2] + PF + MLSE');
+plot(vars,bers_vnledfe_ideal,'DisplayName','VNLE [200,1,0] + ideal DFE [2] + PF + MLSE');
+yline(ber_in_paper);
+yline([2e-2, 4.85e-3, 3.8e-3, 2,2e-4],'LineWidth',2,'Color',[0.8,0.8,0.8],'LineStyle',':','HandleVisibility','off');
+ylim([1e-5,0.1]);
+beautifyBERplot();
+
+%% -------------------- DB target --------------------
+mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
+
+eq_ = EQ("Ne",[50, 5, 5],"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
+ "K",sps,"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, Rx_synced, Symbols, Bits, ...
+ "precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", [],"eth_style_symbol_mapping",mapping_style);
+
+fprintf('Paper: %.1e \n \n',ber_in_paper);
+dbt_results.metrics.print("description",'Duobinary');
+
+
+%%
+
+%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",len_tr,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",1, ...
+ "traceback_depth",128,"L",3,"delta",4,"adaptive_mu",0);
+
+[ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Rx_synced, Symbols, Bits,"precode_mode",duob_mode);
+ml_mlse_results.metrics.print("description",'ML ');
\ 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
index 21f881f..986fcdb 100644
--- a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_WAVELENGTH.m
+++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/Copy_of_FIGURE_WAVELENGTH.m
@@ -6,7 +6,7 @@ db = DBHandler("dataBase", "labor_highspeed", "type", database_type);

pam_levels = [4, 6, 8]; % three tiles
bitrate_set = 360e9;
-fiberL = 10;
+fiberL = 2;

fields = [
db.getTableFieldNames('power_state_info');
@@ -96,7 +96,7 @@ end
%% ============================================================
% PLOT — 1×3 (PAM-4, PAM-6, PAM-8)
% ============================================================
-fig = figure(9110); clf;
+fig = figure(9112); clf;
tiledlayout(1,3,'TileSpacing','compact','Padding','compact');

lw = 1.8;
@@ -223,19 +223,19 @@ ylabel('');

end

-pos = 1e3.*[2.7770 1.2017 1.4000 0.3200];
-set(fig, 'Position', pos);
+% 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' ...
- });
+% 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' ...
+% });



diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m
index 2213c8a..147c7d9 100644
--- a/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m
+++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/final/FIGURE_introduction.m
@@ -1,83 +1,139 @@
+tablename = 'C:\Users\Silas\Documents\latex\JLT_400G_submission\HighSpeedExperiments_oneandonly_csv.csv';
+% Returns a Table
+data = readtable(tablename,"Delimiter",';','DecimalSeparator',',');
+
+
%% ============================================================
% PLOT
% ============================================================
+%% 1. DATA EXTRACTION & SETUP
+%% 1. DATA EXTRACTION & SETUP
+raw_M = data.M;
+raw_baud = data.BaudRate;
+raw_net = data.NetRate;
+raw_codes = string(data.ZoteroCode); 
+raw_names = string(data.Name);
+raw_band = string(data.Band);
+
+% Filter Valid Data
+target_M = [2, 4, 6, 8];
+validIdx = ismember(raw_M, target_M) & ~isnan(raw_baud) & ~isnan(raw_net);
+
+Mvals = raw_M(validIdx);
+baud = raw_baud(validIdx);
+netrate = raw_net(validIdx);
+codes = raw_codes(validIdx);
+names = raw_names(validIdx);
+bands = raw_band(validIdx);
+
+pam_list = target_M;
+colors = flip(cbrewer2('SET1',4));
+
+%% 2. PLOT (For Visual Check only)
figure; hold on;
-ms = 32; % scatter size
-lw = 0.8; % line width
-
-for k = 1:4 % PAM-2/4/6/8
+ms = 20; 
+lw = 0.5; 

+for k = 1:length(pam_list) 
M = pam_list(k);
idxPam = (Mvals == M);
-
- % Extract for this PAM
+ 
x = baud(idxPam);
y = netrate(idxPam);
+ b = bands(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};
-
+ % Marker Logic
+ ms = 20; 
+ if strcmpi(b(i), 'O')
+ marker = 'o'; 
+ elseif strcmpi(b(i), 'C')
+ marker = 'd'; 
+ else
+ marker = 's'; 
+ end
+ if strcmpi(n(i), 'THIS WORK')
+ marker = 'pentagram'; 
+ ms = 100;
+ end
+ 
+ % Plot Scatter
if firstLegend
- h = scatter(x(i), y(i), ms, ...
- 'Marker', marker, ...
- 'MarkerEdgeColor', col, ...
- 'MarkerFaceColor', col, ...
+ 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, ...
+ 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
+ 
+ % Fit lines
+ if length(x) >= 3
+ [p, S, mu] = polyfit(x, y, 2); 
+ xfit = linspace(min(x), max(x), 200);
+ yfit = polyval(p, xfit, S, mu);
+ plot(xfit, yfit, '-', 'LineWidth', lw, 'Color', col, 'HandleVisibility', 'off'); 
end
end

-grid on;
+grid on; box on;
xlabel('Baud rate [GBd]');
ylabel('Net rate [Gb/s]');
-
-legend('Location','northwest');
-set(gca,'FontSize',11);
-
+% title('Check Command Window for TikZ Code');
+% legend('Location','northwest');
+
+%% 3. GENERATE TIKZ ANNOTATION CODE
+% This prints the manual \draw commands to the console
+
+%% GENERATE TIKZ ANNOTATION CODE
+% This prints the manual \draw commands to the console
+
+%% GENERATE TIKZ ANNOTATION CODE (Colored Borders + Tiny Font)
+%% GENERATE TIKZ ANNOTATION CODE (No Arrow, Close Text)
+fprintf('\n\n%% ===========================================================\n');
+fprintf('%% COPY THE FOLLOWING LINES INTO YOUR .TEX FILE \n');
+fprintf('%% (Paste them just before \\end{axis})\n');
+fprintf('%% ===========================================================\n\n');
+
+for i = 1:length(baud)
+ bx = baud(i);
+ by = netrate(i);
+ key = codes(i);
+ M_val = Mvals(i);
+ 
+ % --- PLACEMENT LOGIC ---
+ if M_val == 8
+ % PAM-8: Place Top-Left
+ % 'south east' anchor means the text's bottom-right corner touches the coordinate
+ % shift moves it slightly up and left to clear the marker
+ anchorStr = 'south east';
+ shiftStr = 'shift={(-3pt, 3pt)}'; 
+ else
+ % Others: Place Bottom-Right
+ % 'north west' anchor means the text's top-left corner touches the coordinate
+ % shift moves it slightly down and right
+ anchorStr = 'north west';
+ shiftStr = 'shift={(3pt, -3pt)}';
+ end
+ 
+ % --- PRINT COMMAND ---
+ % Uses \node directly at the coordinate (axis cs:...)
+ fprintf('\\node[anchor=%s, %s, font=\\tiny, fill=white, inner sep=1pt] at (axis cs:%.2f, %.2f) {\\cite{%s}};\n', ...
+ anchorStr, shiftStr, bx, by, key);
+end
+fprintf('\n')

%% === EXPORT ===
-outfile = 'C:\Users\Silas\Documents\latex\JLT_400G copy\media\matlab2tikz\highspeedresults.tikz';
+outfile = 'C:\Users\Silas\Documents\latex\JLT_400G_submission\media\matlab2tikz\highspeedresults_test.tikz';
+
matlab2tikz(outfile, ...
'width','\fwidth', ...
'height','\fheight', ...
diff --git a/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m b/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m
index ab31451..67a2ff8 100644
--- a/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m
+++ b/projects/HighSpeedExperiment_2024/Auswertung_JLT/run_dsp_from_db.m
@@ -10,10 +10,11 @@ if dsp_options.mode == "load_run_id"

if experiment == "highspeed_2024"

- dsp_options.database_type = 'mysql';
+ 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);
+ db = DBHandler("dataBase", [dsp_options.dataBase],...
+ "type", dsp_options.database_type,"server","192.168.178.192","user","silas","password","silas");

elseif experiment == "mpi_ecoc_2025"

diff --git a/projects/ML_based_MLSE/analyze_filter_length.m b/projects/ML_based_MLSE/analyze_filter_length.m
index 616b715..1b31d6b 100644
--- a/projects/ML_based_MLSE/analyze_filter_length.m
+++ b/projects/ML_based_MLSE/analyze_filter_length.m
@@ -5,9 +5,9 @@ M = 4;
randkey = 1;

% --- Parameter sweep
-order_range = 2:3:11; % FFE order
-delta_range = 0:2:4; % delta
-SNR_dB = 20;
+order_range = 5:5:50; % FFE order
+delta_range = 0:5:20; % delta
+SNR_dB = 30;

% --- Prepare bit sequence
order_bits = 19;
@@ -21,10 +21,14 @@ Symbols = PAMmapper(M,0).map(Bits);
Symbols.fs = 200e9;

% --- Channel (minimal ISI + AWGN)
-h = [0.3 0.9 0.3]; h = h/norm(h);
+h = abs([0.3 0.9 0.3]); h = h/norm(h);
+
+% h = [1 -1.67085330039878 1.17918163282514 -0.805210559745616 0.571564213123367 -0.296337147529674 0.00649773445209780 0.0854177610195952 -0.0576009020965258 0.0520994427061551 -0.0624586034913656 0.0553280962699552 -0.00705582559925755 -0.0336399056707792 0.0706903719452810 -0.0334124287931977 0.0131699455037966 0.0587431373842994 -0.0515902976066452 0.00647904355473619 0.0137506750904990 -0.0547974515885928 0.00994735499340592 -0.0135513582534086 -0.00463322575007739 0.0277311946101940];
+% h = h/norm(h);
symbols_filt = Symbols.filter(h,1);
symbols_noi = symbols_filt;
symbols_noi.signal = awgn(symbols_filt.signal,SNR_dB,'measured');
+symbols_noi.spectrum();

% --- Generate all parameter pairs
[O,D] = ndgrid(order_range, delta_range);
@@ -38,7 +42,7 @@ 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)
+for k = 1:size(pairs,1)
order_k = pairs(k,1);
delta_k = pairs(k,2);

@@ -89,7 +93,7 @@ end
beautifyBERplot
ylabel('BER'); xlabel('Filter Order [N]');
title('BER vs. Filter order');
-ylim([1e-4, 0.1]);
+% ylim([1e-4, 0.1]);
yline(3.8e-3,'HandleVisibility','off');
yline(2.2e-4,'HandleVisibility','off');

diff --git a/projects/ML_based_MLSE/theoretic_channel_evaluation.m b/projects/ML_based_MLSE/theoretic_channel_evaluation.m
index 3e1efcc..3eb6f09 100644
--- a/projects/ML_based_MLSE/theoretic_channel_evaluation.m
+++ b/projects/ML_based_MLSE/theoretic_channel_evaluation.m
@@ -48,36 +48,36 @@ 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));
+ % 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;
diff --git "a/projects/Messung_Z\303\274rich/minimal_matched.m" "b/projects/Messung_Z\303\274rich/minimal_matched.m"
new file mode 100644
index 0000000..a5dfa33
--- /dev/null
+++ "b/projects/Messung_Z\303\274rich/minimal_matched.m"
@@ -0,0 +1,60 @@
+L = 4; % Oversampling factor
+fsym = 1e9/4;
+rollOff = 0.5; % Pulse shaping roll-off factor
+htx = rcosdesign(rollOff,16, L, 'sqrt');
+hrx = conj(fliplr(htx));
+
+signal = zeros(1000,1);
+signal(500) = 1;
+Digi_sig = Informationsignal(signal,"fs",fsym);
+
+% Digi_sig = Digi_sig.resample("fs_out",L*fsym);
+% 
+% Digi_sig_tx = Digi_sig.filter(htx,1);
+% Digi_sig_rx = Digi_sig_tx.filter(hrx,1);
+% 
+% figure;plot(Digi_sig_tx.signal);hold on;plot(Digi_sig_rx.signal),plot(Digi_sig.signal);
+% 
+% 
+% 
+% 
+pulsef = Pulseformer("alpha",1,"matched",0,"fdac",Digi_sig.fs*L,"fsym",fsym,"pulse","rrc","pulselength",16);
+Digi_sig = pulsef.process(Digi_sig);
+
+pulsef = Pulseformer("alpha",1,"matched",1,"fdac",Digi_sig.fs,"fsym",fsym,"pulse","rrc","pulselength",16);
+Digi_sig_matched = pulsef.process(Digi_sig);
+
+% Filter:
+htx = rcosdesign(rollOff,16, L, 'sqrt');
+% Note half of the target delay is used, because when combined
+% to the matched filter, the total delay will be achieved.
+hrx = conj(fliplr(htx));
+
+figure
+plot(htx)
+title('Transmit Filter')
+xlabel('Index')
+ylabel('Amplitude')
+
+figure
+plot(hrx)
+title('Rx Filter (Matched Filter)')
+xlabel('Index')
+ylabel('Amplitude')
+
+p = conv(htx,hrx);
+
+figure
+plot(p)
+title('Combined Tx-Rx = Raised Cosine')
+xlabel('Index')
+ylabel('Amplitude')
+
+% And let's highlight the zero-crossings
+zeroCrossings = NaN*ones(size(p));
+zeroCrossings(1:L:end) = 0;
+zeroCrossings((rcDelay)*L + 1) = NaN; % Except for the central index
+hold on
+plot(zeroCrossings, 'o')
+legend('RC Pulse', 'Zero Crossings')
+hold off
\ No newline at end of file
diff --git a/projects/WDM/WDM_model_10km_queue.m b/projects/WDM/WDM_model_10km_queue.m
index a41cb02..8aae2ea 100644
--- a/projects/WDM/WDM_model_10km_queue.m
+++ b/projects/WDM/WDM_model_10km_queue.m
@@ -312,7 +312,7 @@ for realiz = 1:s.num_realiz
Opt_sig_wdm_fib = DP_Fiber("L",segment_length,"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);
+ "SS_dzmax",50,"SS_dzmin",10,"X_alpha",0.3,"X_beta",0,"rng",1,"useGPU",1,"useSingle",1).process(Opt_sig_wdm_fib);

% -------- Evaluate at intermediate distance (enqueue jobs) --------
if eval_ptr <= nEval && seg == eval_seg(eval_ptr)
diff --git a/test/bayesopt_ffe_tuning.m b/test/bayesopt_ffe_tuning.m
new file mode 100644
index 0000000..adedb3d
--- /dev/null
+++ b/test/bayesopt_ffe_tuning.m
@@ -0,0 +1,219 @@
+%% Bayesian Optimization for FFE Parameter Tuning
+% This script uses bayesopt to find optimal mu_dd and mu_tr values
+% that minimize BER for the FFE equalizer.
+
+clear; clc;
+
+%% Setup - Same as gpu_processing_dpfiber.m
+s.wavelengthplan = calcWavelengthPlan(4, 400e9, 1310);
+link_length = 10;
+s.pmd = 0.1;
+s.gamma = 0.0023;
+
+s.M = 4;
+fsym = 112e9;
+fdac = 2*fsym;
+fadc = 120000000000;
+s.random_key = 1;
+
+% Laser / Modulator
+vbias_rel = 0.5;
+u_pi = 4.6;
+vbias = -vbias_rel*u_pi;
+laser_linewidth = 0e6;
+
+duob_mode = db_mode.no_db;
+rcalpha = 0.05;
+Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha);
+
+s.chirpalpha = 0;
+s.p_launch = 3;
+s.p = "co";
+
+N = numel(s.wavelengthplan);
+
+switch s.p
+ case "co"
+ pol_rot = 100.*ones(1,N);
+ d_local = 0;
+end
+
+f_plan = physconst('lightspeed')./(s.wavelengthplan.*1e-9);
+margin = 25e12;
+f_span = (max(f_plan)+margin)-(min(f_plan)-margin);
+f_nyq = f_span/2;
+
+kover = 4;
+upsample_required = f_nyq./(fdac*kover/2);
+upsample_pow = 2^nextpow2(upsample_required);
+
+s.f_opt = fdac*kover*upsample_pow;
+s.f_opt_nyq = s.f_opt/2;
+
+s.rop = -8; % Fixed ROP for optimization
+
+%% Generate TX signals (run once)
+fprintf('Generating TX signals...\n');
+for l = 1:N
+ [Digi_sig,Symbols{l},Tx_bits{l}] = PAMsource( ...
+ "fsym",fsym,"M",s.M,"order",15,"useprbs",0, ...
+ "fs_out",fdac, ...
+ "applyclipping",0,"clipfactor",1.5, ...
+ "applypulseform",1,"pulseformer",Pform, ...
+ "randkey",s.random_key+l, ...
+ "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode ...
+ ).process();
+
+ Lp_awg = Filter('filtdegree',3,"f_cutoff",56e9,"fs",fdac*kover, ...
+ "filterType",filtertypes.gaussian,"active",true);
+
+ El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",1,"kover",kover, ...
+ "bit_resolution",6,"upsampling_method","samplehold","precomp_sinc_rolloff",0, ...
+ "H_lpf",Lp_awg,"dac_max",0.6,"dac_min",-0.6).process(Digi_sig);
+
+ El_sig = El_sig.normalize("mode","oneone");
+ scaling = 0.6*(u_pi/2-abs(vbias-u_pi/2));
+ El_sig = El_sig .* scaling;
+
+ 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,"alpha",s.chirpalpha).process(El_sig);
+
+ signal_cell{l} = Polarization_Controller("mode","rot_power","desired_power",pol_rot(l)).process(Eml_out);
+end
+
+%% WDM mux + launch
+Opt_sig_wdm = Optical_Multiplex("fs_in",fdac*kover,"fs_out",upsample_pow*fdac*kover, ...
+ "lambda_center",1310,"random_key",0,"filtype",1,"B",120e9).process(signal_cell);
+
+Opt_sig_wdm = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",s.p_launch+10*log10(N)).process(Opt_sig_wdm);
+
+%% Fiber propagation
+segment_length = 1;
+nSegments = link_length/segment_length;
+nSegments = round(nSegments);
+
+zdw = 1310;
+randomize_D = true;
+Dvec = getDispersionVector(nSegments, d_local, zdw, randomize_D, s.random_key);
+
+Opt_sig_wdm_fib = Opt_sig_wdm;
+fprintf('Running fiber propagation...\n');
+for seg = 1:nSegments
+ fprintf('Segment %d/%d\n', seg, nSegments);
+ Opt_sig_wdm_fib = DP_Fiber("L",segment_length,"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,"useGPU",true,"useSingle",true).process(Opt_sig_wdm_fib);
+end
+
+%% Pre-process to get Rx_sig (do demux once)
+fprintf('Pre-processing receiver chain...\n');
+l = 1; % Use channel 1 for optimization
+
+Opt_sig_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1, ...
+ "fs_out",fdac*kover,"fs_in",fdac*kover*upsample_pow,"lambda_center",1310).process(Opt_sig_wdm_fib);
+
+Opt_sig_rx = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",s.rop).process(Opt_sig_demux{l});
+
+PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20, ...
+ "nep",1.8e-11,"randomkey",s.random_key+l).process(Opt_sig_rx);
+
+rx_bwl = 100e9;
+PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover, ...
+ "filterType",filtertypes.butterworth,"active",true).process(PD_sig);
+
+Lp_scpe = Filter('filtdegree',4,"f_cutoff",80e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
+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',0,'H_lpf',Lp_scpe).process(PD_sig);
+
+Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym);
+
+[~, Scpe_cell, ~, ~] = Scpe_sig_2sps.tsynch("reference", Symbols{l}, "fs_ref", fsym, "debug_plots", 0);
+Rx_sig = Scpe_cell{1};
+Rx_sig = Rx_sig.normalize("mode","rms");
+
+fprintf('Receiver pre-processing complete. Ready for optimization.\n\n');
+
+%% Define the objective function for bayesopt
+function ber = ffe_objective(params, Rx_sig, Symbols_l, Tx_bits_l, M, duob_mode)
+mu_dd = params.mu_dd;
+mu_tr = params.mu_tr;
+
+try
+ eq_ffe = FFE("epochs_tr", 5, "epochs_dd", 2, "len_tr", 2^13, ...
+ "mu_dd", mu_dd, "mu_tr", mu_tr, ...
+ "order", 50, "sps", 2, "decide", 0, ...
+ "adaption", adaption_method.nlms, "dd_mode", 1);
+
+ ffe_results = ffe(eq_ffe, M, Rx_sig, Symbols_l, Tx_bits_l, ...
+ "precode_mode", duob_mode, ...
+ 'showAnalysis', 0, ...
+ "postFFE", [], ...
+ "eth_style_symbol_mapping", 0);
+
+ ber = ffe_results.metrics.BER;
+
+ if ber == 0
+ ber = 1e-10;
+ end
+
+ if ~isfinite(ber)
+ ber = 0.5;
+ end
+
+ fprintf(' mu_dd=%.4e, mu_tr=%.4e -> BER=%.4e\n', mu_dd, mu_tr, ber);
+
+catch ME
+ fprintf(' mu_dd=%.4e, mu_tr=%.4e -> FAILED (%s)\n', mu_dd, mu_tr, ME.message);
+ ber = 0.5;
+end
+end
+
+%% Define optimizable variables
+mu_dd_var = optimizableVariable('mu_dd', [1e-5, 0.1], 'Transform', 'log');
+mu_tr_var = optimizableVariable('mu_tr', [1e-5, 0.1], 'Transform', 'log');
+
+%% Run Bayesian Optimization
+fprintf('========== Starting Bayesian Optimization ==========\n');
+fprintf('Optimizing mu_dd and mu_tr to minimize BER\n');
+fprintf('Search range: mu_dd=[1e-5, 0.1], mu_tr=[1e-5, 0.1]\n\n');
+
+objective_fn = @(params) ffe_objective(params, Rx_sig, Symbols{l}, Tx_bits{l}, s.M, duob_mode);
+
+results = bayesopt(objective_fn, [mu_dd_var, mu_tr_var], ...
+ 'MaxObjectiveEvaluations', 30, ...
+ 'AcquisitionFunctionName', 'expected-improvement-plus', ...
+ 'IsObjectiveDeterministic', false, ...
+ 'ExplorationRatio', 0.5, ...
+ 'Verbose', 1, ...
+ 'PlotFcn', []);
+
+%% Display Results
+fprintf('\n========== FFE Optimization Complete ==========\n');
+fprintf('Best FFE parameters found:\n');
+fprintf(' mu_dd = %.6e\n', results.XAtMinObjective.mu_dd);
+fprintf(' mu_tr = %.6e\n', results.XAtMinObjective.mu_tr);
+fprintf(' BER = %.6e\n', results.MinObjective);
+
+%% Verify with optimal parameters
+fprintf('\nVerifying optimal FFE parameters...\n');
+best_mu_dd = results.XAtMinObjective.mu_dd;
+best_mu_tr = results.XAtMinObjective.mu_tr;
+
+eq_ffe_best = FFE("epochs_tr", 5, "epochs_dd", 2, "len_tr", 2^13, ...
+ "mu_dd", best_mu_dd, "mu_tr", best_mu_tr, ...
+ "order", 50, "sps", 2, "decide", 0, ...
+ "adaption", adaption_method.nlms, "dd_mode", 1);
+
+ffe_results_best = ffe(eq_ffe_best, s.M, Rx_sig, Symbols{l}, Tx_bits{l}, ...
+ "precode_mode", duob_mode, ...
+ 'showAnalysis', 1, ...
+ "postFFE", [], ...
+ "eth_style_symbol_mapping", 0);
+
+fprintf('\nFinal FFE BER with optimal parameters: %.6e\n', ffe_results_best.metrics.BER);
diff --git a/test/bitwise_demapping_pam6.m b/test/bitwise_demapping_pam6.m
new file mode 100644
index 0000000..6ea0b3d
--- /dev/null
+++ b/test/bitwise_demapping_pam6.m
@@ -0,0 +1,43 @@
+
+
+%%%%% SETTINGS %%%%%%
+useprbs = 1;
+M = 8;
+randkey = 1;
+fsym = 112e9;
+viewresults = 0;
+
+%%%%% Mapping %%%%%
+M = 6;
+data = 0:M-1;
+bitpersymbol = log2(M);
+
+s = RandStream('twister','Seed',1);
+bitpattern = randi(s,[0 1], 2^18, 1);
+bitpattern = bitpattern(1:end-mod(length(bitpattern),5));
+
+bits_tx = Informationsignal(bitpattern);
+
+symbols = PAMmapper(M,0).map(bits);
+
+pam6transitions = combvec(PAMmapper(M,0).levels,PAMmapper(M,0).levels)';
+
+pam6bits = PAMmapper(6,0,"eth_style",0).demap(reshape(pam6transitions',[],1)./sqrt(10));
+symbols_rx = PAMmapper(M,0).map(pam6bits).*sqrt(10);
+
+pam6bits = reshape(pam6bits',5,[])';
+
+figure; hold on
+scatter(pam6transitions(:,1), pam6transitions(:,2), 'x', 'LineWidth', 1);
+n = size(pam6transitions,1);
+labels = cellstr(char(pam6bits + '0')); % -> N x 1 cell array of char rows
+text(pam6transitions(:,1), pam6transitions(:,2), labels, ...
+ 'HorizontalAlignment','left', 'VerticalAlignment','bottom');
+
+
+
+bits_rx = PAMmapper(M,0).demap(symbols);
+
+[~,error_num,ber,error_pos] = calc_ber(bits_tx.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
+
+PAMmapper(8,0).showBitMapping
\ No newline at end of file
diff --git a/test/duobinary_minimal_example.m b/test/duobinary_minimal_example.m
index d61c044..c39183e 100644
--- a/test/duobinary_minimal_example.m
+++ b/test/duobinary_minimal_example.m
@@ -29,9 +29,6 @@ para.skip =0;
para.bruijn = 0;
para.reset_prms = 0;
para.method = 1;
-para.pcs = 0;
-para.shape_para = 0;
-para.rng_num = 0;

data_in = [];
global loop;
diff --git a/test/gpu_cpu_comparison.m b/test/gpu_cpu_comparison.m
new file mode 100644
index 0000000..02d40ba
--- /dev/null
+++ b/test/gpu_cpu_comparison.m
@@ -0,0 +1,349 @@
+%% GPU vs CPU Comparison Test for DP_Fiber
+% This script runs the fiber simulation with and without GPU acceleration
+% and compares the numerical results.
+
+% clear; clc;
+
+%% Setup (same as gpu_processing_dpfiber.m but simplified)
+s.wavelengthplan = calcWavelengthPlan(4, 400e9, 1310);
+link_length = 10;
+s.pmd = 0.1;
+s.gamma = 0.0023;
+
+s.M = 4;
+fsym = 112e9;
+fdac = 2*fsym;
+fadc = 120000000000;
+s.random_key = 1;
+
+% Laser / Modulator
+vbias_rel = 0.5;
+u_pi = 4.6;
+vbias = -vbias_rel*u_pi;
+laser_linewidth = 0e6;
+
+% DB Stuff
+duob_mode = db_mode.no_db;
+
+rcalpha = 0.05;
+Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha);
+
+s.chirpalpha = 0;
+s.p_launch = 3;
+s.p = "co";
+
+N = numel(s.wavelengthplan);
+
+switch s.p
+ case "co"
+ pol_rot = 100.*ones(1,N);
+ d_local = 0;
+ case "pair"
+ pol_rot = repmat([100,100,0,0],1,N/4);
+ d_local = 0;
+ case "alt"
+ pol_rot = repmat([100,0,100,0],1,N/4);
+ d_local = 0;
+ case "seg"
+ pol_rot = 100.*ones(1,N);
+ d_local = 3;
+ otherwise
+ error('Unknown fwm_mitigation_technique: %s', string(s.p));
+end
+
+f_plan = physconst('lightspeed')./(s.wavelengthplan.*1e-9);
+margin = 5e12;
+f_span = (max(f_plan)+margin)-(min(f_plan)-margin);
+f_nyq = f_span/2;
+
+kover = 4;
+upsample_required = f_nyq./(fdac*kover/2);
+upsample_pow = 2^nextpow2(upsample_required);
+
+s.f_opt = fdac*kover*upsample_pow;
+s.f_opt_nyq = s.f_opt/2;
+
+%% TX per channel
+for l = 1:N
+ [Digi_sig,Symbols{l},Tx_bits{l}] = PAMsource( ...
+ "fsym",fsym,"M",s.M,"order",15,"useprbs",0, ...
+ "fs_out",fdac, ...
+ "applyclipping",0,"clipfactor",1.5, ...
+ "applypulseform",1,"pulseformer",Pform, ...
+ "randkey",s.random_key+l, ...
+ "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode ...
+ ).process();
+
+ Lp_awg = Filter('filtdegree',3,"f_cutoff",56e9,"fs",fdac*kover, ...
+ "filterType",filtertypes.gaussian,"active",true);
+
+ El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",1,"kover",kover, ...
+ "bit_resolution",6,"upsampling_method","samplehold","precomp_sinc_rolloff",0, ...
+ "H_lpf",Lp_awg,"dac_max",0.6,"dac_min",-0.6).process(Digi_sig);
+
+ clear Digi_sig
+
+ El_sig = El_sig.normalize("mode","oneone");
+ scaling = 0.6*(u_pi/2-abs(vbias-u_pi/2));
+ El_sig = El_sig .* scaling;
+
+ 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,"alpha",s.chirpalpha).process(El_sig);
+
+ clear El_sig
+
+ signal_cell{l} = Polarization_Controller("mode","rot_power","desired_power",pol_rot(l)).process(Eml_out);
+
+ clear Eml_out Lp_awg
+end
+
+disp('Signal generated for all channels.');
+
+%% WDM mux + launch
+Opt_sig_wdm = Optical_Multiplex("fs_in",fdac*kover,"fs_out",upsample_pow*fdac*kover, ...
+ "lambda_center",1310,"random_key",0,"filtype",1,"B",120e9).process(signal_cell);
+
+Opt_sig_wdm = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",s.p_launch+10*log10(N)).process(Opt_sig_wdm);
+
+% Save input for both runs
+Opt_sig_input = Opt_sig_wdm;
+
+segment_length = 1;
+nSegments = link_length/segment_length;
+if abs(nSegments - round(nSegments)) > 1e-12
+ error('fiber_length_km=%g must be an integer multiple of segment_length=%g km.', link_length, segment_length);
+end
+nSegments = round(nSegments);
+
+zdw = 1310;
+randomize_D = true;
+
+if nSegments > 0
+ Dvec = getDispersionVector(nSegments, d_local, zdw, randomize_D, s.random_key);
+else
+ Dvec = [];
+end
+
+%% Run WITHOUT GPU
+fprintf('\n========== Running WITHOUT GPU (CPU) ==========\n');
+Opt_sig_cpu = Opt_sig_input;
+
+tic;
+for seg = 1:nSegments
+ fprintf('CPU Segment %d/%d \n',seg, nSegments);
+
+ Opt_sig_cpu = DP_Fiber("L",segment_length,"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,"useGPU",false).process(Opt_sig_cpu);
+end
+time_cpu = toc;
+fprintf('CPU Time: %.3f seconds\n', time_cpu);
+
+%% Run WITH GPU (Double Precision)
+fprintf('\n========== Running WITH GPU (Double Precision) ==========\n');
+Opt_sig_gpu_double = Opt_sig_input;
+
+tic;
+for seg = 1:nSegments
+ fprintf('GPU-Double Segment %d/%d \n',seg, nSegments);
+
+ Opt_sig_gpu_double = DP_Fiber("L",segment_length,"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,"useGPU",true,"useSingle",false).process(Opt_sig_gpu_double);
+end
+time_gpu_double = toc;
+fprintf('GPU Double Time: %.3f seconds\n', time_gpu_double);
+
+%% Run WITH GPU (Single Precision)
+fprintf('\n========== Running WITH GPU (Single Precision) ==========\n');
+Opt_sig_gpu_single = Opt_sig_input;
+
+tic;
+for seg = 1:nSegments
+ fprintf('GPU-Single Segment %d/%d \n',seg, nSegments);
+
+ Opt_sig_gpu_single = DP_Fiber("L",segment_length,"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,"useGPU",true,"useSingle",true).process(Opt_sig_gpu_single);
+end
+time_gpu_single = toc;
+fprintf('GPU Single Time: %.3f seconds\n', time_gpu_single);
+
+%% Compare Results
+fprintf('\n========== Numerical Comparison ==========\n');
+
+sig_cpu = Opt_sig_cpu.signal;
+sig_gpu_double = Opt_sig_gpu_double.signal;
+sig_gpu_single = Opt_sig_gpu_single.signal;
+
+% Check dimensions
+fprintf('CPU signal size: [%d x %d]\n', size(sig_cpu,1), size(sig_cpu,2));
+fprintf('GPU Double signal size: [%d x %d]\n', size(sig_gpu_double,1), size(sig_gpu_double,2));
+fprintf('GPU Single signal size: [%d x %d]\n', size(sig_gpu_single,1), size(sig_gpu_single,2));
+
+% CPU vs GPU Double
+fprintf('\n--- CPU vs GPU Double ---\n');
+diff_cpu_double = abs(sig_cpu - sig_gpu_double);
+max_diff_cpu_double = max(diff_cpu_double(:));
+mean_diff_cpu_double = mean(diff_cpu_double(:));
+rel_diff_cpu_double = max_diff_cpu_double / max(abs(sig_cpu(:)));
+fprintf('Max absolute difference: %.6e\n', max_diff_cpu_double);
+fprintf('Mean absolute difference: %.6e\n', mean_diff_cpu_double);
+fprintf('Max relative difference: %.6e\n', rel_diff_cpu_double);
+
+% CPU vs GPU Single
+fprintf('\n--- CPU vs GPU Single ---\n');
+diff_cpu_single = abs(sig_cpu - sig_gpu_single);
+max_diff_cpu_single = max(diff_cpu_single(:));
+mean_diff_cpu_single = mean(diff_cpu_single(:));
+rel_diff_cpu_single = max_diff_cpu_single / max(abs(sig_cpu(:)));
+fprintf('Max absolute difference: %.6e\n', max_diff_cpu_single);
+fprintf('Mean absolute difference: %.6e\n', mean_diff_cpu_single);
+fprintf('Max relative difference: %.6e\n', rel_diff_cpu_single);
+
+% GPU Double vs GPU Single
+fprintf('\n--- GPU Double vs GPU Single ---\n');
+diff_double_single = abs(sig_gpu_double - sig_gpu_single);
+max_diff_double_single = max(diff_double_single(:));
+mean_diff_double_single = mean(diff_double_single(:));
+rel_diff_double_single = max_diff_double_single / max(abs(sig_gpu_double(:)));
+fprintf('Max absolute difference: %.6e\n', max_diff_double_single);
+fprintf('Mean absolute difference: %.6e\n', mean_diff_double_single);
+fprintf('Max relative difference: %.6e\n', rel_diff_double_single);
+
+% Check tolerances
+fprintf('\n--- Tolerance Check ---\n');
+tol_double = 1e-10;
+tol_single = 1e-5; % Single precision has ~7 significant digits
+
+if max_diff_cpu_double < tol_double
+ fprintf('✓ CPU vs GPU Double: EQUIVALENT (diff < %.0e)\n', tol_double);
+else
+ fprintf('✗ CPU vs GPU Double: DIFFER beyond tolerance (%.0e)\n', tol_double);
+end
+
+if max_diff_cpu_single < tol_single
+ fprintf('✓ CPU vs GPU Single: ACCEPTABLE (diff < %.0e)\n', tol_single);
+else
+ fprintf('⚠ CPU vs GPU Single: Precision loss detected (diff = %.2e, tol = %.0e)\n', max_diff_cpu_single, tol_single);
+end
+
+% Performance comparison
+fprintf('\n========== Performance Summary ==========\n');
+fprintf('CPU Time: %.3f s\n', time_cpu);
+fprintf('GPU Double Time: %.3f s\n', time_gpu_double);
+fprintf('GPU Single Time: %.3f s\n', time_gpu_single);
+fprintf('\n');
+fprintf('Speedup (GPU Double vs CPU): %.2fx\n', time_cpu/time_gpu_double);
+fprintf('Speedup (GPU Single vs CPU): %.2fx\n', time_cpu/time_gpu_single);
+fprintf('Speedup (GPU Single vs GPU Double): %.2fx\n', time_gpu_double/time_gpu_single);
+
+%% ========== BER Comparison ==========
+% Process each fiber output through simplified receiver to check if
+% single-precision affects actual BER performance
+
+fprintf('\n========== BER Comparison ==========\n');
+fprintf('Processing signals through receiver chain...\n');
+
+% Receiver parameters
+rop = -7; % Received optical power [dBm]
+len_tr = 4096; % Training length
+mu_dc = 0.005;
+mu_ffe = [0.0001 0.0008 0.001];
+mu_dfe = 0.0004;
+
+% Helper function to process through receiver and get BER
+function ber = process_receiver(Opt_sig_fib, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop, s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode)
+
+% Demux single channel
+Opt_sig_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1, ...
+ "fs_out",fdac*kover,"fs_in",fdac*kover*upsample_pow,"lambda_center",1310).process(Opt_sig_fib);
+
+% ROP amplifier
+Opt_sig_rx = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",rop).process(Opt_sig_demux{l});
+
+% Photodiode
+PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20, ...
+ "nep",1.8e-11,"randomkey",s.random_key+l).process(Opt_sig_rx);
+
+% Low-pass filter
+rx_bwl = 100e9;
+PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover, ...
+ "filterType",filtertypes.butterworth,"active",true).process(PD_sig);
+
+% Scope
+Lp_scpe = Filter('filtdegree',4,"f_cutoff",80e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
+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',0,'H_lpf',Lp_scpe).process(PD_sig);
+
+% Resample to 2 sps
+Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym);
+
+% Time sync
+[~, Scpe_cell, ~, ~] = Scpe_sig_2sps.tsynch("reference", Symbols{l}, "fs_ref", fsym, "debug_plots", 0);
+Rx_sig = Scpe_cell{1};
+Rx_sig = Rx_sig.normalize("mode","rms");
+
+% FFE Equalizer
+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);
+
+ber = ffe_results.metrics.BER;
+end
+
+% Process each mode for channel 1
+l = 4; % Use first channel for comparison
+
+fprintf('Processing CPU result...\n');
+ber_cpu = process_receiver(Opt_sig_cpu, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop, s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode);
+
+fprintf('Processing GPU Double result...\n');
+ber_gpu_double = process_receiver(Opt_sig_gpu_double, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop, s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode);
+
+fprintf('Processing GPU Single result...\n');
+ber_gpu_single = process_receiver(Opt_sig_gpu_single, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop, s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode);
+
+% Display BER results
+fprintf('\n========== BER Results (Channel %d, ROP = %d dBm) ==========\n', l, rop);
+fprintf('CPU: BER = %.4e\n', ber_cpu);
+fprintf('GPU Double: BER = %.4e\n', ber_gpu_double);
+fprintf('GPU Single: BER = %.4e\n', ber_gpu_single);
+
+fprintf('\n--- BER Comparison ---\n');
+if ber_cpu == 0 && ber_gpu_double == 0 && ber_gpu_single == 0
+ fprintf('✓ All BERs are zero (no errors detected)\n');
+else
+ ber_diff_double = abs(ber_cpu - ber_gpu_double);
+ ber_diff_single = abs(ber_cpu - ber_gpu_single);
+ fprintf('|BER_cpu - BER_gpu_double| = %.4e\n', ber_diff_double);
+ fprintf('|BER_cpu - BER_gpu_single| = %.4e\n', ber_diff_single);
+
+ if ber_diff_double < 1e-6 && ber_diff_single < 1e-6
+ fprintf('✓ BER differences are negligible\n');
+ elseif ber_diff_single > ber_diff_double * 10
+ fprintf('⚠ Single precision shows measurable BER impact\n');
+ else
+ fprintf('✓ BER differences within acceptable range\n');
+ end
+end
+
+fprintf('\n========== Test Complete ==========\n');
diff --git a/test/gpu_processing_dpfiber.m b/test/gpu_processing_dpfiber.m
new file mode 100644
index 0000000..317833e
--- /dev/null
+++ b/test/gpu_processing_dpfiber.m
@@ -0,0 +1,265 @@
+
+
+s.wavelengthplan = calcWavelengthPlan(16, 400e9, 1310);
+N = numel(s.wavelengthplan);
+link_length = 10;
+s.pmd = 0.1;%0.1;
+s.gamma = 0.0023;
+
+s.M = 4;
+fsym = 112e9;
+fdac = 2*fsym;
+fadc = 120000000000;
+s.random_key = 1;
+
+% Laser / s.Modulator
+vbias_rel = 0.5;
+u_pi = 4.6;
+vbias = -vbias_rel*u_pi;
+laser_linewidth = 0e6;
+
+% DB Stuff
+duob_mode = db_mode.no_db;
+
+rcalpha = 0.05;
+Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha);
+
+s.chirpalpha = 0;
+
+s.p_launch = 3;
+s.p = "co";
+
+switch s.p
+ case "co"
+ pol_rot = 100.*ones(1,N);
+ d_local = 0;
+ case "pair"
+ pol_rot = repmat([100,100,0,0],1,N/4);
+ d_local = 0;
+ case "alt"
+ pol_rot = repmat([100,0,100,0],1,N/4);
+ d_local = 0;
+ case "seg"
+ pol_rot = 100.*ones(1,N);
+ d_local = 3;
+ otherwise
+ error('Unknown fwm_mitigation_technique: %s', string(s.p));
+end
+
+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 = 4;
+upsample_required = f_nyq./(fdac*kover/2);
+upsample_pow = 2^nextpow2(upsample_required);
+
+s.f_opt = fdac*kover*upsample_pow;
+s.f_opt_nyq = s.f_opt/2;
+
+s.rop = -10:1:0;
+
+profile on
+
+%% ---------- TX per channel ----------
+for l = 1:N
+
+ [Digi_sig,Symbols{l},Tx_bits{l}] = PAMsource( ...
+ "fsym",fsym,"M",s.M,"order",17,"useprbs",0, ...
+ "fs_out",fdac, ...
+ "applyclipping",0,"clipfactor",1.5, ...
+ "applypulseform",1,"pulseformer",Pform, ...
+ "randkey",s.random_key+l, ...
+ "mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode ...
+ ).process();
+
+ Lp_awg = Filter('filtdegree',3,"f_cutoff",56e9,"fs",fdac*kover, ...
+ "filterType",filtertypes.gaussian,"active",true);
+
+ El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",1,"kover",kover, ...
+ "bit_resolution",6,"upsampling_method","samplehold","precomp_sinc_rolloff",0, ...
+ "H_lpf",Lp_awg,"dac_max",0.6,"dac_min",-0.6).process(Digi_sig);
+
+ % Digi_sig not needed after AWG
+ clear Digi_sig
+
+ % Electrical Driver Amplifier
+ El_sig = El_sig.normalize("mode","oneone");
+ scaling = 0.6*(u_pi/2-abs(vbias-u_pi/2));
+ El_sig = El_sig .* scaling;
+
+ % 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,"alpha",s.chirpalpha).process(El_sig);
+
+ % El_sig not needed after EML
+ clear El_sig
+
+ signal_cell{l} = Polarization_Controller("mode","rot_power","desired_power",pol_rot(l)).process(Eml_out);
+
+ % Eml_out not needed after pol controller
+ clear Eml_out Lp_awg
+end
+
+disp('Signal generated for all channels.');
+
+%% ---------- WDM mux + launch ----------
+Opt_sig_wdm = Optical_Multiplex("fs_in",fdac*kover,"fs_out",upsample_pow*fdac*kover, ...
+ "lambda_center",1310,"random_key",0,"filtype",1,"B",120e9).process(signal_cell);
+
+Opt_sig_wdm.spectrum();
+
+Opt_sig_wdm = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",s.p_launch+10*log10(N)).process(Opt_sig_wdm);
+
+Opt_sig_wdm_fib = Opt_sig_wdm;
+
+segment_length = 1;
+nSegments = link_length/segment_length;
+if abs(nSegments - round(nSegments)) > 1e-12
+ error('fiber_length_km=%g must be an integer multiple of segment_length=%g km.', link_length, segment_length);
+end
+nSegments = round(nSegments);
+
+zdw = 1310;
+randomize_D = true;
+
+% Guard for 0 km: avoid calling getDispersionVector(0,...) if it doesn't support it
+if nSegments > 0
+ Dvec = getDispersionVector(nSegments, d_local, zdw, randomize_D, s.random_key);
+else
+ Dvec = [];
+end
+
+for seg = 1:nSegments
+
+ fprintf('Segment %d/%d \n',seg, nSegments);
+
+ Opt_sig_wdm_fib = DP_Fiber("L",segment_length,"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,"useGPU",true,"useSingle",1).process(Opt_sig_wdm_fib);
+
+end
+
+profile off
+profile viewer
+
+Opt_sig_wdm_fib.spectrum();
+
+%% ========== BER Evaluation ==========
+fprintf('\n========== BER Evaluation ==========\n');
+fprintf('Processing signals through receiver chain...\n');
+
+% Receiver parameters
+len_tr = 4096; % Training length
+mu_dc = 0.005;
+mu_ffe = [0.0001 0.0008 0.001];
+mu_dfe = 0.0004;
+
+% Helper function to process through receiver and get BER
+function ber = process_receiver(Opt_sig_fib, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop, s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode)
+
+% Demux single channel
+Opt_sig_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1, ...
+ "fs_out",fdac*kover,"fs_in",fdac*kover*upsample_pow,"lambda_center",1310).process(Opt_sig_fib);
+
+% ROP amplifier
+Opt_sig_rx = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power", ...
+ "amplification_db",rop).process(Opt_sig_demux{l});
+
+% Photodiode
+PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20, ...
+ "nep",1.8e-11,"randomkey",s.random_key+l).process(Opt_sig_rx);
+
+% Low-pass filter
+rx_bwl = 100e9;
+PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover, ...
+ "filterType",filtertypes.butterworth,"active",true).process(PD_sig);
+
+% Scope
+Lp_scpe = Filter('filtdegree',4,"f_cutoff",80e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
+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',0,'H_lpf',Lp_scpe).process(PD_sig);
+
+% Resample to 2 sps
+Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym);
+
+% Time sync
+[~, Scpe_cell, ~, ~] = Scpe_sig_2sps.tsynch("reference", Symbols{l}, "fs_ref", fsym, "debug_plots", 0);
+Rx_sig = Scpe_cell{1};
+Rx_sig = Rx_sig.normalize("mode","rms");
+
+% FFE Equalizer
+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);
+eq_ffe = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",6.624e-05,"mu_tr",0.058136,"order",50,"sps",2,"decide",0, "adaption",adaption_method.nlms,"dd_mode",1);
+
+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);
+
+ber = ffe_results.metrics.BER;
+end
+
+% Process each ROP value for selected channels using parfor
+ber_results = zeros(length(s.rop), N);
+
+% Flatten loop for parfor: iterate over all (ROP, channel) combinations
+num_rop = length(s.rop);
+rop_vals = s.rop;
+ber_flat = zeros(num_rop * N, 1);
+
+parfor idx = 1:(num_rop * N)
+ % Convert linear index to (ri, l) subscripts
+ ri = ceil(idx / N);
+ l = mod(idx - 1, N) + 1;
+
+ fprintf('ROP %d dBm, Channel %d/%d\n', rop_vals(ri), l, N);
+ ber_flat(idx) = process_receiver(Opt_sig_wdm_fib, l, Symbols, Tx_bits, ...
+ fdac, kover, upsample_pow, fsym, fadc, rop_vals(ri), s, len_tr, mu_dc, mu_ffe, mu_dfe, duob_mode);
+end
+
+% Reshape back to [num_rop × N] matrix
+ber_results = reshape(ber_flat, [N, num_rop]).';
+
+
+%% Display BER Results
+fprintf('\n========== BER Results ==========\n');
+fprintf('ROP [dBm] | ');
+for l = 1:N
+ fprintf('Ch%d | ', l);
+end
+fprintf('\n');
+
+for ri = 1:length(s.rop)
+ fprintf('%8d | ', s.rop(ri));
+ for l = 1:N
+ fprintf('%.2e | ', ber_results(ri, l));
+ end
+ fprintf('\n');
+end
+
+% Plot BER vs ROP
+figure;
+semilogy(s.rop, mean(ber_results, 2), '-o', 'LineWidth', 2);
+hold on;
+for l = 1:N
+ semilogy(s.rop, ber_results(:, l), '--', 'LineWidth', 1);
+end
+hold off;
+xlabel('ROP [dBm]');
+ylabel('BER');
+title('BER vs Received Optical Power (GPU Single Precision)');
+legend(['Mean', arrayfun(@(x) sprintf('Ch%d', x), 1:N, 'UniformOutput', false)]);
+grid on;
+
+fprintf('\n========== Test Complete ==========\n');
diff --git a/test/gpu_processing_test.m b/test/gpu_processing_test.m
new file mode 100644
index 0000000..b08c03f
--- /dev/null
+++ b/test/gpu_processing_test.m
@@ -0,0 +1,70 @@
+% 1. Setup Data OUTSIDE the timer
+d = gpuDevice;
+N = 10000;
+
+fprintf('Preparing data...\n');
+A_cpu_double = rand(N, N); % Create double on CPU
+A_cpu_single = single(A_cpu_double); % Create single on CPU
+
+% Warmup run (wakes up the GPU from idle state)
+A_warm = gpuArray.rand(1000, 1000, 'single');
+B_warm = A_warm * A_warm;
+wait(d); 
+
+fprintf('------------------------------------------------\n');
+
+% TEST 1: Double Precision (The "Slow" way)
+% We move data to GPU first so we only measure calculation time
+A_gpu_double = gpuArray(A_cpu_double); 
+wait(d); % Ensure transfer is done before starting timer
+
+fprintf('Running DOUBLE precision test... ');
+tic;
+B_gpu = A_gpu_double * A_gpu_double;
+wait(d); % FORCE MATLAB TO WAIT FOR GPU
+time_double = toc;
+fprintf('Done.\n');
+fprintf('Double Precision Time: %.4f seconds\n', time_double);
+
+% TEST 2: Single Precision (The "Fast" way)
+A_gpu_single = gpuArray(A_cpu_single);
+wait(d); % Ensure transfer is done
+
+fprintf('Running SINGLE precision test... ');
+tic;
+B_gpu = A_gpu_single * A_gpu_single;
+wait(d); % FORCE MATLAB TO WAIT FOR GPU
+time_single = toc;
+fprintf('Done.\n');
+fprintf('Single Precision Time: %.4f seconds\n', time_single);
+
+% Calculate Speedup
+fprintf('------------------------------------------------\n');
+fprintf('Speedup Factor using single precision: %.2fx\n', time_double / time_single);
+
+%
+% Create 10,000 small matrices (10x10) stacked in a 3D array
+A_stack = gpuArray.rand(10, 10, 10000, 'single');
+B_stack = gpuArray.rand(10, 10, 10000, 'single');
+
+% BAD: Looping (GPU overhead kills you)
+tic;
+for i=1:10000
+ C(:,:,i) = A_stack(:,:,i) * B_stack(:,:,i);
+end
+wait(d);
+loop_time = toc;
+
+% GOOD: Pagefun (Executes all 10,000 mults simultaneously)
+tic;
+C_stack = pagefun(@mtimes, A_stack, B_stack);
+wait(d);
+pagefun_time = toc;
+
+fprintf('------------------------------------------------\n');
+fprintf('Speedup Factor using pagefun: %.2fx\n', loop_time / pagefun_time);
+
+
+fprintf('Total VRAM: %.2f GB\n', d.TotalMemory / 1e9);
+fprintf('Available VRAM: %.2f GB\n', d.AvailableMemory / 1e9);
+fprintf('Usage: %.1f%%\n', 100 * (1 - d.AvailableMemory / d.TotalMemory));
\ No newline at end of file