BCJR implementation
WDM code added (Pol Cont., Opt MUX/DEMUX, Opt Atten, DP_Fiber) -> the codebase is not optimized to always work with dp signals!
This commit is contained in:
@@ -5,6 +5,7 @@ classdef Opticalsignal < Signal
|
||||
properties
|
||||
nase
|
||||
lambda
|
||||
polrot
|
||||
|
||||
end
|
||||
|
||||
@@ -18,6 +19,7 @@ classdef Opticalsignal < Signal
|
||||
options.logbook
|
||||
options.lambda
|
||||
options.nase
|
||||
options.polrot
|
||||
end
|
||||
|
||||
obj = obj@Signal(signal);
|
||||
|
||||
@@ -108,6 +108,7 @@ classdef Signal
|
||||
options.logbook
|
||||
options.nase
|
||||
options.lambda
|
||||
options.polrot
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
@@ -122,7 +123,7 @@ classdef Signal
|
||||
|
||||
%convert to optical
|
||||
|
||||
o_sig = Opticalsignal(obj.signal,"fs",obj.fs,"lambda",options.lambda,"logbook",obj.logbook,"nase",options.nase);
|
||||
o_sig = Opticalsignal(obj.signal,"fs",obj.fs,"lambda",options.lambda,"logbook",obj.logbook,"nase",options.nase,"polrot",options.polrot);
|
||||
|
||||
|
||||
elseif isa(obj,'Informationsignal')
|
||||
@@ -141,7 +142,7 @@ classdef Signal
|
||||
|
||||
arguments
|
||||
obj
|
||||
options.fignum = []
|
||||
options.fignum = randi(1000)
|
||||
options.displayname = [];
|
||||
options.timeframe = 0;
|
||||
options.clear = 0;
|
||||
@@ -187,7 +188,7 @@ classdef Signal
|
||||
ylabel('Amplitude');
|
||||
end
|
||||
|
||||
|
||||
|
||||
% Add legend if not already present
|
||||
if isempty(get(gca, 'Legend'))
|
||||
legend;
|
||||
@@ -350,21 +351,30 @@ classdef Signal
|
||||
options.normalizeTo0dB = 0;
|
||||
options.max_num_lines = []; % Leave empty or omit to disable line rotation
|
||||
options.fft_length = [];
|
||||
% --- NEW options ---
|
||||
options.useWavelengthAxis (1,1) logical = false % plot x-axis in wavelength
|
||||
options.lambda0_nm (1,1) double = 1310 % center wavelength [nm]
|
||||
end
|
||||
|
||||
if isempty(options.fft_length)
|
||||
options.fft_length = 2^(nextpow2(length(obj.signal))-9);
|
||||
end
|
||||
|
||||
|
||||
if options.normalizeToNyquist == 0
|
||||
[p_lin,w] = pwelch(obj.signal,hanning(options.fft_length),options.fft_length/2,options.fft_length,obj.fs,"centered","power","mean");
|
||||
w = w.*1e-9;
|
||||
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
|
||||
options.fft_length/2, options.fft_length, ...
|
||||
obj.fs, "centered", "power", "mean");
|
||||
f_GHz = f_Hz*1e-9; % keep frequency vector for frequency axis
|
||||
else
|
||||
[p_lin,w] = pwelch(obj.signal,hanning(options.fft_length),options.fft_length/2,options.fft_length,"centered","power","mean");
|
||||
[p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ...
|
||||
options.fft_length/2, options.fft_length, ...
|
||||
"centered", "power", "mean");
|
||||
% In normalized mode, pwelch returns rad/sample centered on 0.
|
||||
% We'll keep f_rad for the x-axis in that mode.
|
||||
end
|
||||
|
||||
if options.normalizeTo0dB
|
||||
p_lin = p_lin./ max(p_lin);
|
||||
p_lin = p_lin ./ max(p_lin);
|
||||
p_dbm = 10*log10(p_lin); % normalized to 0 dB
|
||||
ylab = "Normalized PSD";
|
||||
else
|
||||
@@ -372,36 +382,69 @@ classdef Signal
|
||||
ylab = "Power (dB/Hz)";
|
||||
end
|
||||
|
||||
% --- If requested, build wavelength axis from frequency offset ---
|
||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
||||
c = physconst('LightSpeed'); % [m/s]
|
||||
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
|
||||
f_c = c / lambda0_m; % carrier frequency [Hz]
|
||||
|
||||
% exact mapping
|
||||
f_abs = f_c + f_Hz; % absolute frequency [Hz]
|
||||
lambda_m = c ./ f_abs; % wavelength [m]
|
||||
lambda_nm = lambda_m * 1e9; % wavelength [nm]
|
||||
|
||||
% assign axis
|
||||
x_vec = lambda_nm(:);
|
||||
x_label = "Wavelength [nm]";
|
||||
|
||||
% Sort to ensure axis is ascending
|
||||
[x_vec, sortIdx] = sort(x_vec, 'ascend');
|
||||
p_dbm = p_dbm(sortIdx, :);
|
||||
else
|
||||
% Frequency or normalized axes
|
||||
if options.normalizeToNyquist == 0
|
||||
x_vec = f_GHz;
|
||||
x_label = "Frequency in GHz";
|
||||
else
|
||||
x_vec = f_rad; % normalized frequency in rad/sample
|
||||
x_label = "Normalized Frequency";
|
||||
end
|
||||
end
|
||||
|
||||
figure(options.fignum);
|
||||
ax = gca;
|
||||
hold on
|
||||
|
||||
if isempty(options.color)
|
||||
hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1);
|
||||
else
|
||||
hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color);
|
||||
for s = 1:min(size(p_dbm))
|
||||
if isempty(options.color)
|
||||
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1);
|
||||
else
|
||||
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color);
|
||||
end
|
||||
end
|
||||
|
||||
% If user wants to limit the number of lines, check and remove old lines
|
||||
% Limit number of lines if requested
|
||||
if ~isempty(options.max_num_lines) && options.max_num_lines > 0
|
||||
allLines = findall(ax, 'Type', 'Line');
|
||||
if length(allLines) > options.max_num_lines
|
||||
% Sort lines by creation order. Usually, the oldest lines appear first in allLines.
|
||||
% If needed, you can sort by UserData or other criteria.
|
||||
numToRemove = length(allLines) - options.max_num_lines;
|
||||
delete(allLines(1:numToRemove));
|
||||
end
|
||||
end
|
||||
|
||||
if options.normalizeToNyquist == 0
|
||||
xlabel("Frequency in GHz");
|
||||
edgetick = 2^(nextpow2(obj.fs*1e-9));
|
||||
xticks(-edgetick:32:edgetick);
|
||||
xlim([100*round(min(w)/100,1)-10, 100*round(max(w)/100,1)+10])
|
||||
xlim([-128 128]);%256GSa/s
|
||||
% Axis labels and limits
|
||||
xlabel(x_label);
|
||||
|
||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
||||
xlim([min(x_vec) max(x_vec)]);
|
||||
else
|
||||
xlabel("Normalized Frequency");
|
||||
xlim([-pi, pi]);
|
||||
if options.normalizeToNyquist == 0
|
||||
% Keep your existing freq handling (you can fine-tune as needed)
|
||||
% xlim([-128 128]); % example for 256 GSa/s if desired
|
||||
xlim([min(x_vec) max(x_vec)]);
|
||||
else
|
||||
xlim([-pi, pi]);
|
||||
end
|
||||
end
|
||||
|
||||
ylabel(ylab);
|
||||
@@ -409,24 +452,23 @@ classdef Signal
|
||||
try
|
||||
ylim([max(min(floor(min(p_dbm))-3, ax.YLim(1)),-40), min(max(ceil(max(p_dbm))+3, ax.YLim(2)),10)]);
|
||||
catch
|
||||
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
end
|
||||
|
||||
if options.normalizeTo0dB
|
||||
% ylim([-40, 3]);
|
||||
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
else
|
||||
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
end
|
||||
|
||||
yticks(-200:10:10);
|
||||
grid on;% grid minor;
|
||||
grid on;
|
||||
legend
|
||||
% legend('Interpreter','none');
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
function move_it_spectrum(obj,options)
|
||||
arguments
|
||||
obj
|
||||
@@ -554,7 +596,7 @@ classdef Signal
|
||||
options.unit power_notation = power_notation.dBm
|
||||
end
|
||||
|
||||
pow = mean(abs(obj.signal).^2);
|
||||
pow = sum(mean(abs(obj.signal).^2));
|
||||
|
||||
switch options.unit
|
||||
case power_notation.dBm
|
||||
@@ -669,9 +711,9 @@ classdef Signal
|
||||
options.fs_ref = 0;
|
||||
options.debug_plots = 0;
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
S = {};
|
||||
inverted = -1;
|
||||
sequenceFound = 0;
|
||||
@@ -715,25 +757,25 @@ classdef Signal
|
||||
findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum,'SortStr','descend')
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
shifts = lags(pkpos);
|
||||
sequenceStarts = shifts;
|
||||
shifts = shifts(shifts>=0);
|
||||
|
||||
if numel(shifts) > 0
|
||||
|
||||
|
||||
%Cut occurences of ref signal from signal (only positive shifts)
|
||||
if all(sign(co(pkpos))==-1)
|
||||
inverted = 1;
|
||||
end
|
||||
|
||||
|
||||
for c = shifts
|
||||
sig = obj.delay(-c,'mode','samples');
|
||||
sig.signal = sig.signal(1:length(b));% .* -inverted;
|
||||
S{end+1,1} = sig;
|
||||
end
|
||||
|
||||
|
||||
% %return/keep the sinal with the highest correlation (only within positive shifts)
|
||||
% [~,idx]=max(pks);
|
||||
% obj.signal = S{idx}.signal;
|
||||
@@ -741,7 +783,7 @@ classdef Signal
|
||||
% swap = S{1};
|
||||
% S{1} = S{idx};
|
||||
% S{idx} = swap;
|
||||
|
||||
|
||||
for c = 1:numel(shifts)
|
||||
S{c}.logbook = [];
|
||||
end
|
||||
@@ -770,10 +812,6 @@ classdef Signal
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%%
|
||||
function obj = filter(obj,a,b)
|
||||
|
||||
@@ -934,8 +972,8 @@ classdef Signal
|
||||
nn=histcounts(data_ind_y(n,:),1:histpoints+1);
|
||||
hist_data(:,n)=flip(nn.'); %without flip, the eye is upside down :-(
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
plot_data = 20*log10(hist_data);
|
||||
plot_data(plot_data==-Inf) = 0;
|
||||
@@ -970,7 +1008,7 @@ classdef Signal
|
||||
end
|
||||
xlabel('Time in ps')
|
||||
|
||||
|
||||
|
||||
|
||||
% add information
|
||||
|
||||
@@ -1091,7 +1129,7 @@ classdef Signal
|
||||
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
|
||||
xticklabels(x_tickstring);
|
||||
|
||||
grid off
|
||||
grid off
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ classdef Amplifier
|
||||
if obj.gain_mode == gain_mode.output_power
|
||||
|
||||
%get linear gain for output power mode
|
||||
pow_in = mean(abs(xin.^2)) ; % lin input power
|
||||
pow_in = sum(mean(abs(xin.^2)),2) ; % lin input power
|
||||
pow_out = 10^(obj.amplification_db/10 - 3) ; % dBm to lin
|
||||
a_lin = sqrt(pow_out/pow_in) ;
|
||||
|
||||
|
||||
240
Classes/02_optical/DP_Fiber.m
Normal file
240
Classes/02_optical/DP_Fiber.m
Normal file
@@ -0,0 +1,240 @@
|
||||
classdef DP_Fiber
|
||||
% Dual-Polarization fiber propagation (CNLSE / Manakov) — class version
|
||||
% Runs full setup + propagation inside process_ (no external loop).
|
||||
|
||||
properties (Access=public)
|
||||
% ---- User options (public) ----
|
||||
L % [km] fiber length
|
||||
dz % [m] step size target (kept for compatibility; adaptive dz uses SS_* below)
|
||||
lambda % [nm] reference wavelength
|
||||
rng % RNG seed
|
||||
gamma % [1/W/m] nonlinear coefficient
|
||||
|
||||
% Optional / advanced options (match legacy names where possible)
|
||||
fa % [Hz] sampling frequency
|
||||
X_alpha % [dB/100km] attenuation per 100 km (per-pol, used for X and Y)
|
||||
X_beta % [1..4] dispersion coefficients vector for X (Y mirrors X)
|
||||
D % [ps/(nm*km)]
|
||||
Ds % [ps/(nm^2*km)] dispersion slope
|
||||
Dpmd % [ps/sqrt(km)] PMD coefficient
|
||||
beat_len % [m] beat length (for beta(1) if X_beta is zero)
|
||||
corr_len % [m] correlation length (not directly used; kept for compatibility)
|
||||
manakov % 1=Manakov (legacy behavior: manakov=eq-1)
|
||||
SS_dphimax % [rad] max nonlinear phase per step (adaptive SSFM)
|
||||
SS_dzmax % [m] max dz (adaptive SSFM)
|
||||
SS_dzmin % [m] min dz (adaptive SSFM)
|
||||
n_waveplates % number of PMD waveplates
|
||||
|
||||
% ---- Internal state (persistent between calls) ----
|
||||
state % struct mirroring legacy 'state'
|
||||
end
|
||||
|
||||
methods (Access=public)
|
||||
function obj = DP_Fiber(options)
|
||||
% Constructor — copies fields from 'options' and sets defaults.
|
||||
|
||||
arguments
|
||||
options.L
|
||||
options.dz
|
||||
options.lambda
|
||||
options.rng = 0
|
||||
options.gamma
|
||||
|
||||
% optional but recommended
|
||||
options.fa
|
||||
|
||||
% legacy-compatible optional params
|
||||
options.X_alpha = 4.605170185988092e-05 % dB/100km
|
||||
options.X_beta = [0,0,-2.16826193914149e-26,3.56839456298263e-41]
|
||||
options.D = 17 % ps/(nm*km)
|
||||
options.Ds = 0.06 % ps/(nm^2*km)
|
||||
options.Dpmd = 3 % ps/sqrt(km)
|
||||
options.beat_len = 50 % m
|
||||
options.corr_len = 50 % m (kept)
|
||||
options.manakov = 0 % 1=Manakov
|
||||
options.SS_dphimax = 5e-3 % rad
|
||||
options.SS_dzmax = 2e4 % m
|
||||
options.SS_dzmin = 100 % m
|
||||
options.n_waveplates = 100
|
||||
end
|
||||
|
||||
% Copy provided options into properties
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
% Initialize empty state; will be built on first process_ call
|
||||
obj.state = struct();
|
||||
end
|
||||
|
||||
function signalclass_out = process(obj, signalclass_in)
|
||||
% Public entry point: takes a signal class with .signal (2xN)
|
||||
% and writes back the propagated signal.
|
||||
|
||||
signalclass_in.signal = obj.process_(signalclass_in.signal, signalclass_in.fs);
|
||||
|
||||
% logbook (kept as in new framework skeleton)
|
||||
lbdesc = 'DP_Fiber propagation (CNLSE_plain)';
|
||||
if ismethod(signalclass_in, 'logbookentry')
|
||||
signalclass_in = signalclass_in.logbookentry(lbdesc);
|
||||
end
|
||||
|
||||
signalclass_out = signalclass_in;
|
||||
end
|
||||
|
||||
function signal_out = process_(obj, signal_in,fs)
|
||||
% Core processing — builds legacy 'state' and calls CNLSE_plain
|
||||
% data_in: [2 x N] complex, dual-pol envelope
|
||||
arguments (Input)
|
||||
obj
|
||||
signal_in
|
||||
fs
|
||||
end
|
||||
|
||||
% ---- Basic checks
|
||||
if isempty(signal_in) || size(signal_in,2) ~= 2
|
||||
error('DP_Fiber:Input','Expected data_in of size [2 x N].');
|
||||
end
|
||||
if isempty(fs)
|
||||
error('DP_Fiber:Config','Sampling frequency options.fa is required.');
|
||||
end
|
||||
|
||||
obj.fa = fs;
|
||||
|
||||
% ---- RNG (legacy behavior)
|
||||
R = RandStream("twister","Seed",obj.rng);
|
||||
|
||||
% ---- (Re)build state if empty or size-dependent fields changed
|
||||
need_rebuild = ~isfield(obj.state,'nt') || (obj.state.nt ~= size(signal_in,2));
|
||||
|
||||
if need_rebuild
|
||||
% Constants
|
||||
c0 = 299792458; % [m/s]
|
||||
|
||||
% Legacy state mapping
|
||||
st = struct();
|
||||
|
||||
% High-level
|
||||
st.L = obj.L * 1000; % [m] legacy expects meters
|
||||
st.polNames = {'X','Y'};
|
||||
st.dt = 1/obj.fa;
|
||||
st.nt = max(size(signal_in));
|
||||
st.omega = 2*pi*[(0:st.nt/2-1),(-st.nt/2:-1)]/(st.dt*st.nt);
|
||||
st.lambda = obj.lambda * 1e-9; % [m]
|
||||
st.D = obj.D * 1e-6; % ps/(nm*km) -> s/(nm*m)
|
||||
st.Dpmd = obj.Dpmd * 1e-6; % ps/sqrt(km) -> s/sqrt(km)
|
||||
st.Ds = obj.Ds * 1e3; % ps/(nm^2*km) -> s/(nm^2*m)
|
||||
st.beat_len = obj.beat_len;
|
||||
st.SS_dzmax = obj.SS_dzmax;
|
||||
st.SS_dzmin = obj.SS_dzmin;
|
||||
st.SS_dphimax= obj.SS_dphimax;
|
||||
|
||||
st.chi = 0; % legacy placeholders
|
||||
st.psi = 0;
|
||||
|
||||
st.manakov = obj.manakov; % 1 if eq==2 (Manakov), 0 if eq==1 (CNLSE)
|
||||
st.wave_plates = obj.n_waveplates;
|
||||
|
||||
% Alpha (same X/Y)
|
||||
st.alpha.X = obj.X_alpha;
|
||||
st.alpha_lin.X = st.alpha.X/10*log(10)/1000;
|
||||
st.alpha.Y = st.alpha.X;
|
||||
st.alpha_lin.Y = st.alpha_lin.X;
|
||||
|
||||
% Beta (dispersion) for X (Y mirrors X)
|
||||
if ~any(obj.X_beta)
|
||||
% Populate from (beat_len, D, Ds, lambda) like legacy
|
||||
if obj.beat_len ~= 0
|
||||
b1 = pi/obj.beat_len;
|
||||
else
|
||||
b1 = 0;
|
||||
end
|
||||
b2 = 0; % PMD freq term handled elsewhere in legacy
|
||||
b3 = -(st.lambda.^2/(2*pi*c0))*st.D;
|
||||
b4 = (st.lambda^2/(2*pi*c0))^2*st.Ds + (2/st.lambda)*(st.lambda.^2/(2*pi*c0)).^2*st.D;
|
||||
|
||||
st.beta.X = [b1, b2, b3, b4]; % keep 4 terms, legacy had 0 for beta0; b2 unused
|
||||
% Note: legacy stored [b0,b1,b2,b3]? Here we mirror their usage.
|
||||
% We follow their X_beta layout length=4.
|
||||
else
|
||||
st.beta.X = obj.X_beta;
|
||||
end
|
||||
st.beta.Y = st.beta.X;
|
||||
|
||||
% Gamma
|
||||
st.gamma = obj.gamma;
|
||||
|
||||
% PMD / birefringence (legacy waveplate model)
|
||||
st.corr_length = st.L / st.wave_plates;
|
||||
|
||||
% DGD formula (Agrawal 1.1.18)
|
||||
st.dgd = st.Dpmd * sqrt(st.L/1000); % Dpmd in s/sqrt(km), L in m -> convert: sqrt(m/1000)
|
||||
% For exact legacy match, they used: state.dgd = Dpmd * sqrt(L) wisth L in meters and Dpmd already scaled.
|
||||
% Using their pattern:
|
||||
st.dgd = obj.Dpmd*1e-6 * sqrt(st.L); % match old: state.Dpmd already 1e-6*ps/sqrt(km); they used sqrt(L) with L [m]
|
||||
|
||||
brf_multiplier = 1;
|
||||
if st.Dpmd == 0
|
||||
brf_multiplier = 0;
|
||||
end
|
||||
|
||||
% Waveplate random parameters
|
||||
st.pauli_mats.s0 = eye(2);
|
||||
st.pauli_mats.s2 = [0 1; 1 0];
|
||||
st.pauli_mats.s3i = [0 1; -1 0];
|
||||
|
||||
st.brf.theta = (R.rand(st.wave_plates,1)*pi - 0.5*pi) * brf_multiplier;
|
||||
st.brf.epsilon = 0.5*asin(R.rand(st.wave_plates,1)*2-1) * brf_multiplier;
|
||||
|
||||
st.brf.stokes = NaN(st.wave_plates,3);
|
||||
st.Ttest = zeros(1, st.wave_plates);
|
||||
st.brf.matR = cell(st.wave_plates,1);
|
||||
|
||||
for n=1:st.wave_plates
|
||||
matRth = cos(st.brf.theta(n)) * st.pauli_mats.s0 - sin(st.brf.theta(n)) * st.pauli_mats.s3i;
|
||||
matRepsilon = complex(cos(st.brf.epsilon(n))*st.pauli_mats.s0, sin(st.brf.epsilon(n))*st.pauli_mats.s2);
|
||||
matR = matRth * matRepsilon;
|
||||
|
||||
st.brf.matR{n} = matR;
|
||||
|
||||
u1 = matR(1,1);
|
||||
u2 = matR(1,2);
|
||||
|
||||
st.Ttest(n) = abs(u1).^2 + abs(u2).^2;
|
||||
st.brf.stokes(n,:) = [abs(u1).^2 - abs(u2).^2 , (u1) * conj(u2) + conj(u1) .* u2 , 1i*(u1) * conj(u2) - conj(u1) .* u2];
|
||||
end
|
||||
|
||||
% Frequency-dependent PMD phase term (legacy form)
|
||||
st.brf.db0 = (R.rand(st.wave_plates,1)*2*pi - pi) * brf_multiplier;
|
||||
st.brf.db1 = sqrt(3*pi/8)*(st.dgd/obj.fa)/st.wave_plates .* st.omega;
|
||||
st.brf.simdgd = 0;
|
||||
% cumsum used in legacy only for debug; keep compatibility variable:
|
||||
~cumsum(st.brf.db0); % no-op to mirror legacy path
|
||||
|
||||
% Bookkeeping
|
||||
st.missing_dz = 0;
|
||||
st.n_plates_done = 0;
|
||||
st.test_plates = [];
|
||||
st.test_plate_numbers = [];
|
||||
st.lin_z_test = 0;
|
||||
st.propagated_length = 0;
|
||||
|
||||
% Cache
|
||||
obj.state = st;
|
||||
end
|
||||
|
||||
% ---- Call the exact same CNLSE_plain as in the old framework
|
||||
x_in = signal_in(:,1).';
|
||||
y_in = signal_in(:,2).';
|
||||
|
||||
[x_out, y_out, obj.state] = CNLSE_plain(x_in, y_in, obj.state);
|
||||
|
||||
obj.state.propagated_length = obj.state.propagated_length + obj.state.L;
|
||||
|
||||
signal_out = [x_out; y_out].';
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -70,7 +70,7 @@ classdef EML
|
||||
[signalclass_in.signal,obj] = obj.process_(signalclass_in.signal);
|
||||
|
||||
% cast the inform. signal to electrical signal
|
||||
signalclass_in = Opticalsignal(signalclass_in,"fs",obj.fsimu,"logbook",signalclass_in.logbook,"lambda",obj.lambda*1e-9,"nase",0);
|
||||
signalclass_in = Opticalsignal(signalclass_in,"fs",obj.fsimu,"logbook",signalclass_in.logbook,"lambda",obj.lambda*1e-9,"nase",0,"polrot",0);
|
||||
|
||||
% append to logbook
|
||||
lbdesc = [num2str(obj.lambda),' nm Laser with ',num2str(obj.power),' dBm P_out. Linew.=',num2str(obj.linewidth*1e-6),' MHz. Modulation mode: ',char(obj.mode) ];
|
||||
|
||||
122
Classes/02_optical/Optical_Demultiplex.m
Normal file
122
Classes/02_optical/Optical_Demultiplex.m
Normal file
@@ -0,0 +1,122 @@
|
||||
classdef Optical_Demultiplex < handle
|
||||
% Dual-Polarization optical demultiplexer
|
||||
% - Input: total-field signal
|
||||
% - Output: single-channel dual-pol signal objects in cell array
|
||||
%
|
||||
% Notes:
|
||||
% Opt_sig_wdm_demux = Optical_Demultiplex("attenuation",0,"B",200e9,"filtype",1,"fs_out",Opt_sig_wdm_rx.fs/4,"fs_in",Opt_sig_wdm_rx.fs,"lambda_center",1310).process(Opt_sig_wdm_rx);
|
||||
% Opt_sig_wdm_demux{1}.spectrum("fignum",1100,"displayname",'bla','normalizeTo0dB',0,'max_num_lines',4);
|
||||
% Opt_sig_wdm_demux{2}.spectrum("fignum",1100,"displayname",'bla','normalizeTo0dB',0,'max_num_lines',4);
|
||||
|
||||
properties (Access=public)
|
||||
fs_in % [Hz] (optional; inferred from data_in.fs if omitted)
|
||||
fs_out % [Hz]
|
||||
lambda_center % [nm] center wavelength of the WDM grid
|
||||
wavelengthplan % [nm]
|
||||
attenuation = 0 % [dB] insertion loss
|
||||
filtype = 1 % 1=Gaussian, 2=Rectangle, 3=No filter
|
||||
B = 200e9 % [Hz] 3 dB bandwidth (Gaussian) or width (Rect)
|
||||
mgauss = 3 % Gaussian order (multiple of 1/2)
|
||||
|
||||
% Derived/utility
|
||||
c = physconst('lightspeed') % [m/s]
|
||||
end
|
||||
|
||||
methods (Access=public)
|
||||
function obj = Optical_Demultiplex(options)
|
||||
arguments
|
||||
options.fs_in = []
|
||||
options.fs_out
|
||||
options.lambda_center
|
||||
options.wavelengthplan
|
||||
options.attenuation = 0
|
||||
options.filtype = 1
|
||||
options.B = 2.5e10
|
||||
options.mgauss = 3
|
||||
end
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
try obj.(fn{n}) = options.(fn{n}); end
|
||||
end
|
||||
end
|
||||
|
||||
function signalclasses_out = process(obj, signalclass_in)
|
||||
|
||||
% ---- Infer wavelength: either given or from input total signal
|
||||
if isempty(obj.wavelengthplan)
|
||||
obj.wavelengthplan = signalclass_in.lambda; %meter
|
||||
else
|
||||
if all(500e-9 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500e-9) %check if given in nm
|
||||
obj.wavelengthplan = obj.wavelengthplan.*1e-9;
|
||||
end
|
||||
end
|
||||
|
||||
% ---- Infer input sampling rates
|
||||
if isempty(obj.fs_in)
|
||||
assert(isprop(signalclass_in,'fs') && ~isempty(signalclass_in.fs), ...
|
||||
'Dual_Pol_Demultiplexer: data_in.fs missing and options.fs_in not provided.');
|
||||
obj.fs_in = signalclass_in.fs;
|
||||
end
|
||||
|
||||
% Runs demultiplexing in one go and appends a logbook entry.
|
||||
[x_envelopes,y_envelopes] = obj.process_(signalclass_in.signal);
|
||||
|
||||
for n = 1:min(size(x_envelopes))
|
||||
signalclasses_out{n} = signalclass_in;
|
||||
signalclasses_out{n}.signal = [x_envelopes(:,n), y_envelopes(:,n)];
|
||||
signalclasses_out{n} = signalclasses_out{n}.resample("fs_in",obj.fs_in,"fs_out",obj.fs_out);
|
||||
signalclasses_out{n}.lambda = obj.wavelengthplan(n);
|
||||
lbdesc = ['Opt. Demux ', num2str( obj.wavelengthplan(n)),' nm'];
|
||||
signalclasses_out{n} = signalclasses_out{n}.logbookentry(lbdesc);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function [x_envelopes,y_envelopes] = process_(obj, signal_in)
|
||||
% Core demux:
|
||||
% - frequency translate target channel to baseband
|
||||
% - apply optical filter H
|
||||
% - resample to fs_out
|
||||
arguments (Input)
|
||||
obj
|
||||
signal_in
|
||||
end
|
||||
|
||||
w = obj.fs_out ./ obj.fs_in ;
|
||||
blocklen_in = length(signal_in);
|
||||
blocklen_out = w*blocklen_in;
|
||||
|
||||
att = 1/10^(obj.attenuation/10);
|
||||
faxis=linspace( -obj.fs_in/2 , obj.fs_in/2 , blocklen_in+1 );
|
||||
faxis=ifftshift(faxis(1:end-1));
|
||||
|
||||
switch obj.filtype
|
||||
case 1
|
||||
H=exp(-(faxis/obj.B).^(2*obj.mgauss)*log(2)*2^(2*obj.mgauss-1)).';
|
||||
case 2
|
||||
%all zero filter
|
||||
H=zeros(1,length(faxis)).';
|
||||
%set filter = 1 inside bandwidth -B/2 <-> B/2
|
||||
H(abs(faxis)<=obj.B/2)=1;
|
||||
case 3
|
||||
H = 1;
|
||||
end
|
||||
|
||||
f_mid = obj.c/(obj.lambda_center*1e-9); % center frequency of WDM grid [Hz]
|
||||
|
||||
f_channels = obj.c./(obj.wavelengthplan) ;
|
||||
|
||||
N = numel(f_channels);
|
||||
|
||||
df_T = f_mid - f_channels;
|
||||
|
||||
pha = mod(-2*pi*(0:blocklen_in-1).'.*df_T/obj.fs_in, 2*pi);
|
||||
lo = cos(pha)+1i*sin(pha);
|
||||
|
||||
x_envelopes = ifft(fft(att.*signal_in(:,1).*lo).*H);
|
||||
y_envelopes = ifft(fft(att.*signal_in(:,2).*lo).*H);
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
171
Classes/02_optical/Optical_Multiplex.m
Normal file
171
Classes/02_optical/Optical_Multiplex.m
Normal file
@@ -0,0 +1,171 @@
|
||||
classdef Optical_Multiplex < handle
|
||||
% Takes a cell array of signals
|
||||
% returns a total field signal
|
||||
% WDM spacing is given in wavelength plan OR via delta_F
|
||||
|
||||
% The grid is stored in the output signal -> the demux will ideally
|
||||
% look this up and use this as the demux frequencies...
|
||||
|
||||
% signal_cell = {Opt_sig_1, Opt_sig_2};
|
||||
% Opt_sig_wdm = Optical_Multiplex("fs_in",Opt_sig.fs,"fs_out",4*Opt_sig.fs,...
|
||||
% "lambda_center",1310,"random_key",0,"filtype",1,"B",200e9,"delta_f",400e9).process(signal_cell);
|
||||
|
||||
properties(Access=public)
|
||||
fs_in
|
||||
fs_out
|
||||
lambda_center
|
||||
delta_f
|
||||
random_key
|
||||
attenuation
|
||||
B
|
||||
mgauss
|
||||
filtype
|
||||
|
||||
c = physconst('lightspeed')
|
||||
f_center
|
||||
f_T
|
||||
lambda_T
|
||||
df_T
|
||||
end
|
||||
|
||||
methods (Access=public)
|
||||
function obj = Optical_Multiplex(options)
|
||||
%NAME Construct an instance of this class
|
||||
% Detailed explanation goes here
|
||||
|
||||
arguments
|
||||
options.fs_in
|
||||
options.fs_out
|
||||
options.lambda_center
|
||||
options.B = 200e9
|
||||
options.mgauss = 3
|
||||
options.filtype = 2
|
||||
|
||||
options.delta_f = 0
|
||||
options.random_key
|
||||
options.attenuation = 0;
|
||||
end
|
||||
|
||||
%
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
try
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function signalclass_out = process(obj,signalclasses_in)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
signalclass_out = obj.process_(signalclasses_in);
|
||||
|
||||
% append to logbook
|
||||
lbdesc = ['Opt. Mux. '];
|
||||
signalclass_out = signalclass_out.logbookentry(lbdesc);
|
||||
|
||||
end
|
||||
|
||||
function data_out = process_(obj,data_in)
|
||||
%METHOD1 Summary of this method goes here
|
||||
% Detailed explanation goes here
|
||||
arguments(Input)
|
||||
obj
|
||||
data_in cell
|
||||
end
|
||||
|
||||
% assert(data_in{1}.fs == obj.fs_in,'Sampling rate');
|
||||
att = 1/10^(obj.attenuation/10);
|
||||
N = numel(data_in);
|
||||
w = obj.fs_out/data_in{1}.fs;
|
||||
blocklen_in = length(data_in{1});
|
||||
blocklen_out = w*blocklen_in;
|
||||
freqaxis = linspace(-obj.fs_out/2, obj.fs_out/2, blocklen_out+1);
|
||||
obj.f_center = obj.c/(obj.lambda_center.*1e-9);
|
||||
|
||||
if obj.random_key ~= 0
|
||||
res = freqaxis(2)-freqaxis(1);
|
||||
R = RandStream("twister","Seed",obj.random_key);
|
||||
laser_frequency_imperfection = res .* round(R.randn(N,1)*10); %in mutliples of the fft resolution, i.e. the distance between two freq. bins
|
||||
else
|
||||
laser_frequency_imperfection = zeros(blocklen_in,1);
|
||||
end
|
||||
|
||||
obj.f_T = [];
|
||||
obj.df_T = [];
|
||||
polrots = [];
|
||||
for o = 1:N
|
||||
|
||||
if obj.delta_f ~= 0
|
||||
% user defined a channel spacing in GHz. Build plan
|
||||
% left and right from zero
|
||||
obj.df_T(o) = (-length(data_in)/2-0.5+o) .* obj.delta_f;
|
||||
obj.df_T(o) = obj.df_T(o)+ laser_frequency_imperfection(o);
|
||||
|
||||
obj.f_T = [obj.f_T obj.f_center+obj.df_T(o)];
|
||||
else
|
||||
%center frequencies of channels
|
||||
obj.f_T = [obj.f_T obj.c/(data_in{o}.lambda)];
|
||||
obj.lambda_T = [obj.lambda_T data_in{o}.lambda];
|
||||
|
||||
%difference between mid frequency of MUX and channels
|
||||
obj.df_T = [obj.df_T obj.f_center - obj.f_T(o)];
|
||||
end
|
||||
|
||||
% adapt frequency shifts to match the FFT grid! Find nearest grid point
|
||||
[glitch(o),pos] = min(abs( freqaxis-obj.df_T(o) ));
|
||||
obj.df_T(o) = freqaxis(pos);
|
||||
|
||||
polrots = [polrots, data_in{o}.polrot];
|
||||
end
|
||||
|
||||
obj.lambda_T = obj.c ./ (obj.f_center-obj.df_T);
|
||||
|
||||
obj.B = 200e9; %200GHz
|
||||
faxis = linspace(-obj.fs_out/2,obj.fs_out/2, blocklen_out+1);%generates arow vector faxis of blocklen+1 points linearly spaced between and including -para.fs/2 and para.fs/2
|
||||
faxis = ifftshift(faxis(1:end-1));
|
||||
|
||||
switch obj.filtype
|
||||
case 1
|
||||
H =exp(-(faxis/obj.B).^(2*obj.mgauss)*log(2)*2^(2*obj.mgauss-1)).';
|
||||
case 2
|
||||
H=zeros(length(faxis),1);
|
||||
H(find(abs(faxis)<=obj.B/2))=1;
|
||||
case 3
|
||||
H = 1;
|
||||
end
|
||||
|
||||
x_envelopes = NaN([blocklen_out N]);
|
||||
y_envelopes = x_envelopes;
|
||||
|
||||
for o = 1:N
|
||||
|
||||
pha = mod(2*pi*(0:blocklen_out-1)*obj.df_T(o)/obj.fs_out,2*pi).';
|
||||
lo = cos(pha)+1i*sin(pha);
|
||||
data_in_resampled = data_in{o}.resample("fs_out",obj.fs_out);
|
||||
|
||||
res_env = ifft(fft(data_in_resampled.signal(:,1)).*H);
|
||||
x_envelopes(:,o) = att.*res_env.*lo;
|
||||
|
||||
res_env = ifft(fft(data_in_resampled.signal(:,2)).*H);
|
||||
y_envelopes(:,o) = att.*res_env.*lo;
|
||||
|
||||
end
|
||||
|
||||
data_out = data_in_resampled;
|
||||
data_out.signal = [sum(x_envelopes,2), sum(y_envelopes,2)];
|
||||
data_out.lambda = obj.lambda_T;
|
||||
data_out.polrot = polrots;
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
methods (Access=private)
|
||||
% Cant be seen from outside! So put all your functions here that can/
|
||||
% shall not be called from outside
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
92
Classes/02_optical/Polarization_Controller.m
Normal file
92
Classes/02_optical/Polarization_Controller.m
Normal file
@@ -0,0 +1,92 @@
|
||||
classdef Polarization_Controller
|
||||
%Input can be "normal" - output will be DP!
|
||||
|
||||
properties(Access=public)
|
||||
|
||||
mode
|
||||
desired_angle
|
||||
desired_power
|
||||
|
||||
rotation_angle
|
||||
rotation_matrix
|
||||
|
||||
end
|
||||
|
||||
methods (Access=public)
|
||||
function obj = Polarization_Controller(options)
|
||||
%NAME Construct an instance of this class
|
||||
% Detailed explanation goes here
|
||||
|
||||
arguments
|
||||
options.mode polarization_control_mode = polarization_control_mode.rot_power
|
||||
options.desired_angle
|
||||
options.desired_power
|
||||
|
||||
end
|
||||
|
||||
%
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
try
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
end
|
||||
|
||||
% do more stuff
|
||||
|
||||
end
|
||||
|
||||
function signalclass_out = process(obj,signalclass_in)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
[signalclass_in.signal,signalclass_in.polrot] = obj.process_(signalclass_in.signal,signalclass_in.polrot);
|
||||
|
||||
% append to logbook
|
||||
lbdesc = ['Logbookentry'];
|
||||
signalclass_in = signalclass_in.logbookentry(lbdesc);
|
||||
|
||||
% write to output
|
||||
signalclass_out = signalclass_in;
|
||||
|
||||
end
|
||||
|
||||
function [data_out, polrot_out] = process_(obj,data_in,polrot_in)
|
||||
% Rotate polarization of am opt signal
|
||||
|
||||
arguments(Input)
|
||||
obj
|
||||
data_in double
|
||||
polrot_in double
|
||||
end
|
||||
|
||||
|
||||
if obj.mode ~= polarization_control_mode.deactivate
|
||||
|
||||
switch obj.mode
|
||||
case polarization_control_mode.random
|
||||
obj.rotation_angle = 2*pi*rand ;
|
||||
|
||||
case polarization_control_mode.rot_angle
|
||||
obj.rotation_angle = obj.desired_angle*pi/180 ;
|
||||
|
||||
case polarization_control_mode.rot_power
|
||||
obj.rotation_angle = -polrot_in + acos(sqrt(obj.desired_power/100)) ;
|
||||
end
|
||||
|
||||
obj.rotation_matrix = [cos(obj.rotation_angle) -sin(obj.rotation_angle) ; sin(obj.rotation_angle) cos(obj.rotation_angle)].' ;
|
||||
|
||||
if min(size(data_in)) == 1
|
||||
data_in = reshape(data_in,[],1);
|
||||
data_in = [data_in, zeros(length(data_in),1)];
|
||||
end
|
||||
|
||||
data_out = data_in * obj.rotation_matrix;
|
||||
|
||||
polrot_out = polrot_in + obj.rotation_angle ;
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -53,6 +53,7 @@ classdef Duobinary
|
||||
% bk(k+1) =mod(data(k)-bk(k),M);
|
||||
% end
|
||||
|
||||
% THIS WAS USED!
|
||||
for k = 2:numel(data)
|
||||
bk(k) = mod(data(k)-bk(k-1),M);
|
||||
end
|
||||
|
||||
@@ -29,9 +29,6 @@ classdef MLSE < handle
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
end
|
||||
|
||||
% do more stuff
|
||||
|
||||
end
|
||||
|
||||
function [signalclass_hd,LLR,GMI] = process(obj,signalclass,ref_symbolclass)
|
||||
@@ -57,6 +54,26 @@ classdef MLSE < handle
|
||||
|
||||
function [VITERBI_ESTIMATION_SYMBOLS,LLR_maxlogmap,GMI] = process_(obj,data_in,data_ref)
|
||||
|
||||
debug = 0;
|
||||
|
||||
trellis_state_mode = 2; % General: States should match the target states of the prev. EQ (EQ's job was to reduce the error between signal and the target)
|
||||
% 0 = use provided states (MUST provide the correct states);
|
||||
% 1 = normalize to = 1 rms;
|
||||
% 2 = use target symbols;
|
||||
% 3 = use statistical levels
|
||||
% 3 analyzes avg of rx signal levels - can help with nonlinear impairments
|
||||
|
||||
trellis_exclusion = 0; % PAM-6 only (only if data is NOT precoded!)
|
||||
|
||||
scale_mode = 2; % scale_mode:
|
||||
% 0 = no scaling,
|
||||
% 1 = RMS→scale MODEL,
|
||||
% 2 = MMSE/time-corr→scale MODEL,
|
||||
% 3 = RMS→scale DATA,
|
||||
% 4 = MMSE/time-corr→scale DATA
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%%% PREPARATIONS %%%%%%%%
|
||||
|
||||
% remove unnecessary zeros at start of impulse response to keep
|
||||
% number of trellis states minimal
|
||||
@@ -69,22 +86,42 @@ classdef MLSE < handle
|
||||
obj.DIR = [0 obj.DIR];
|
||||
end
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%%% PREPARATIONS %%%%%%%%
|
||||
|
||||
%%%% Separate the equalized signal into the respective levels based on the actually transmitted level
|
||||
constellation = unique(data_ref);
|
||||
decisionLevels = (constellation(1:end-1) + constellation(2:end)) / 2;
|
||||
N = length(data_in);
|
||||
|
||||
tx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(data_ref);
|
||||
|
||||
% impulse respnse to remove from signal
|
||||
obj.DIR = flip(obj.DIR); %i.e. -0.2676 -0.0478 1.0000
|
||||
|
||||
% Normalize the Trellis states to =1 RMS
|
||||
obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states);
|
||||
tx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(data_ref);
|
||||
|
||||
% Trellis States
|
||||
if trellis_state_mode == 1 % Normalize the Trellis states to =1 RMS
|
||||
|
||||
obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states);
|
||||
|
||||
elseif trellis_state_mode == 2 %simply use the states from the ref signal (should be a robust option)
|
||||
|
||||
obj.trellis_states = reshape(unique(data_ref),size(obj.trellis_states));
|
||||
|
||||
elseif trellis_state_mode == 3 %use_statistical_levels
|
||||
|
||||
%%%% Separate the equalized signal into the respective levels based on the actually transmitted level
|
||||
constellation = unique(data_ref);
|
||||
|
||||
% find actual levels from rx signal
|
||||
symbols_for_lvl = NaN(numel(constellation),length(data_ref));
|
||||
for l = 1:numel(constellation)
|
||||
level_amplitude = constellation(l);
|
||||
symbols_for_lvl(l,data_ref==level_amplitude) = data_in(data_ref==level_amplitude);
|
||||
end
|
||||
|
||||
%replace the trellis states
|
||||
avg_levels = mean(symbols_for_lvl,2,'omitnan');
|
||||
obj.trellis_states = sort(avg_levels)';
|
||||
|
||||
%also replace the whole ref signal (PAM-M) levels
|
||||
[~, idx] = ismember(data_ref, unique(data_ref));
|
||||
data_ref = avg_levels(idx);
|
||||
|
||||
end
|
||||
|
||||
|
||||
% seems to be the only way to use combvec for a flexible amount
|
||||
% of vectors. 'combs' contains all trellis states
|
||||
@@ -97,74 +134,84 @@ classdef MLSE < handle
|
||||
states = sum(combs,2);
|
||||
nStates = length(last_sym);
|
||||
|
||||
% Calculate all possible input symbols for the desired impulse
|
||||
% response. Row number is the index of the previous state,
|
||||
% column number is the index of the next state
|
||||
% noise free received == branch metrics
|
||||
noise_free_received = zeros(nStates,nStates);
|
||||
count_row = 1;
|
||||
count_col = 1;
|
||||
for l1 = 1:nStates
|
||||
for l2 = 1:nStates
|
||||
if sum(combs(l2,2:end) == combs(l1,1:end-1)) == size(combs,2)-1
|
||||
noise_free_received(count_row,count_col) = sum(combs(l2,:).*obj.DIR(end:-1:2)) + last_sym(l1)*obj.DIR(1);
|
||||
else
|
||||
noise_free_received(count_row,count_col) = inf;
|
||||
% % Calculate all possible input symbols for the desired impulse
|
||||
% % response. Row number is the index of the previous state,
|
||||
% % column number is the index of the next state
|
||||
% % noise free received == branch metrics
|
||||
% assumes: last_sym = combs(:,end); % already defined earlier
|
||||
levels = sort(unique(obj.trellis_states(:)).');
|
||||
edges = [levels(1) levels(end)]; % edge levels (0 and 5 in PAM6)
|
||||
|
||||
noise_free_received = inf(nStates,nStates); % rows: to, cols: from
|
||||
edge_edge_mask = false(nStates,nStates); % rows: to, cols: from
|
||||
|
||||
for from = 1:nStates
|
||||
for to = 1:nStates
|
||||
% valid transition if shift-register overlap holds
|
||||
if all(combs(to,2:end) == combs(from,1:end-1))
|
||||
% noiseless sample for the 'to' state reached from 'from'
|
||||
noise_free_received(to,from) = ...
|
||||
dot(combs(to,:), obj.DIR(end:-1:2)) + last_sym(from)*obj.DIR(1);
|
||||
|
||||
% mark edge→edge candidate (to be excluded only on even→odd steps)
|
||||
edge_edge_mask(to,from) = ...
|
||||
(last_sym(from)==edges(1) || last_sym(from)==edges(2)) && ...
|
||||
(last_sym(to) ==edges(1) || last_sym(to) ==edges(2));
|
||||
end
|
||||
count_row = count_row + 1;
|
||||
end
|
||||
count_col = count_col + 1;
|
||||
count_row = 1;
|
||||
end
|
||||
|
||||
% Was used earlier from Tom Wettlin etc. Sufficient for Viterbi
|
||||
% but the BCJR is sensitive to the scaling, so I use another
|
||||
% apporach
|
||||
% % first: RMS normalization of input data (rms==1)
|
||||
% data_in = data_in ./ rms(data_in);
|
||||
%
|
||||
% % then, match amplitude levels of input signal to those of the calculated ideal symbols
|
||||
% % i.e. match the rms values of data_in to noise_free_received (rms=1.xx)
|
||||
% if isreal(data_in)
|
||||
% if obj.M == round(obj.M)
|
||||
% data_in = data_in * rms(noise_free_received(noise_free_received ~= inf),'all','omitnan');
|
||||
% end
|
||||
% end
|
||||
|
||||
% quick and dirty alignment with xcorr
|
||||
y = data_in;
|
||||
x = data_ref;
|
||||
h = flip(obj.DIR(:)).';
|
||||
y_ideal = conv(x, h, "same");
|
||||
[c,lags] = xcorr(y, y_ideal, 64); % small max lag is enough
|
||||
[~,ix] = max(abs(c));
|
||||
lag = lags(ix);
|
||||
y_ideal = circshift(y_ideal, lag);
|
||||
data_in = data_in(:);
|
||||
y_ideal = conv(data_ref(:), h, "same");
|
||||
|
||||
% center, skip transients, rm mean
|
||||
skip = max(100, numel(obj.DIR)+50);
|
||||
y_prep = y(1+skip:end-skip) - mean(y(1+skip:end-skip));
|
||||
y_ideal_prep = y_ideal(1+skip:end-skip) - mean(y_ideal(1+skip:end-skip));
|
||||
|
||||
% one-tap LS/MMSE gain and optional bias
|
||||
g = (y_ideal_prep' * y_prep) / (y_ideal_prep' * y_ideal_prep); % complex allowed
|
||||
b = mean(data_in) - g*mean(y_ideal); % intercept (if you keep means)
|
||||
|
||||
dp = dot(y_ideal_prep, y_prep - g*y_ideal_prep); %shall be close to zero
|
||||
|
||||
sigma2 = mean(abs(y_prep - g*y_ideal_prep).^2);
|
||||
inv2s2 = 1/(2*sigma2);
|
||||
|
||||
debug = 0;
|
||||
if debug
|
||||
figure(100); hold on
|
||||
plot(1:length(y),y,'DisplayName','Y: Received Signal','LineStyle','none','Marker','.','MarkerSize',1);
|
||||
plot(1:length(y_ideal),y_ideal,'DisplayName','Y ideal: x * DIR','LineStyle','none','Marker','.','MarkerSize',1);
|
||||
yline(noise_free_received(:),'DisplayName','All Transition States','HandleVisibility','off','Color','red');
|
||||
yline(g*noise_free_received(:)+ b,'DisplayName','Scaled Transition States','HandleVisibility','off');
|
||||
switch scale_mode
|
||||
case 0
|
||||
g = 1; b = 0;
|
||||
case 1 % RMS: scale model to data
|
||||
g = rms(data_in)/rms(y_ideal); b = mean(data_in) - g*mean(y_ideal);
|
||||
case 2 % MMSE/time-corr: scale states to data
|
||||
[c,lags] = xcorr(data_in(:), y_ideal, 64);
|
||||
[~,ix] = max(abs(c));
|
||||
lag = lags(ix);
|
||||
y_ideal = circshift(y_ideal, lag);
|
||||
mu_y = mean(data_in(:));
|
||||
mu_i = mean(y_ideal);
|
||||
y_c = data_in(:)-mu_y;
|
||||
yi_c = y_ideal-mu_i;
|
||||
g = (yi_c'*y_c)/(yi_c'*yi_c);
|
||||
b = mu_y - g*mu_i;
|
||||
case 3 % RMS flipped: scale data to model
|
||||
gd = rms(y_ideal)/rms(data_in); bd = mean(y_ideal) - gd*mean(data_in);
|
||||
data_in = gd*data_in + bd;
|
||||
g = 1; b = 0;
|
||||
case 4 % MMSE/time-corr flipped: scale data to states
|
||||
[c,lags] = xcorr(data_in(:), y_ideal(:), 64);
|
||||
[~,ix] = max(abs(c));
|
||||
lag = lags(ix);
|
||||
y_ideal = circshift(y_ideal(:), lag);
|
||||
mu_y = mean(data_in(:));
|
||||
mu_i = mean(y_ideal);
|
||||
y_c = data_in(:) - mu_y; % data_in centered
|
||||
yi_c = y_ideal - mu_i; % ideal centered
|
||||
g = (y_c' * yi_c) / (y_c' * y_c);
|
||||
b = mu_i - g * mu_y;
|
||||
data_in = g * data_in(:) + b;
|
||||
g = 1; b = 0;
|
||||
end
|
||||
|
||||
noise_free_received = (g*noise_free_received + b);
|
||||
% apply (g,b) to model and compute common sigma
|
||||
noise_free_received = g*noise_free_received + b;
|
||||
last_sym = g*last_sym + b;
|
||||
sigma2 = mean(abs(data_in - (g*y_ideal + b)).^2);
|
||||
inv2s2 = 1/(2*sigma2);
|
||||
|
||||
if debug
|
||||
figure(100); clf; hold on
|
||||
showLevelScatter(data_in, data_ref, "fignum", 100);
|
||||
yline(noise_free_received(:), 'DisplayName','Transition States','Color','red','HandleVisibility','off');
|
||||
yline(obj.trellis_states(:), 'DisplayName','Transition States','Color','green','LineWidth',2,'HandleVisibility','off')
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% FORWARD PASS (VITERBI -Alpha's) %%%%%
|
||||
@@ -185,8 +232,14 @@ classdef MLSE < handle
|
||||
for n = 2:length(data_in)
|
||||
|
||||
bm = -(data_in(n) - noise_free_received).^2 * inv2s2;
|
||||
|
||||
% exclude edge→edge transitions only for even->odd steps && PAM-6
|
||||
if mod(n,2) == 0 && obj.M == 6 && trellis_exclusion
|
||||
bm(edge_edge_mask) = -Inf;
|
||||
end
|
||||
|
||||
pm = pm + bm;
|
||||
[alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state
|
||||
[alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state (get min distance for all state transitions towards a new state)
|
||||
pm = repmat(alpha(:,n).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
|
||||
|
||||
bm_fw(:,:,n) = bm;
|
||||
@@ -202,6 +255,18 @@ classdef MLSE < handle
|
||||
viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n);
|
||||
end
|
||||
|
||||
|
||||
if debug
|
||||
alpha_ = alpha - min(alpha) + eps;
|
||||
figure();hold on;
|
||||
n = 10;
|
||||
scatter(1:n,obj.trellis_states(repmat([1:numel(obj.trellis_states)]',1,n)),abs(alpha_(:,end-n+1:end)),'Marker','o','LineWidth',1);
|
||||
scatter(1:n,obj.trellis_states(viterbi_path(end-n+1:end)),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','green');
|
||||
% scatter(1:n,data_ref(end-n+1:end),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','red');
|
||||
yticks(obj.trellis_states);
|
||||
ylim([min(obj.trellis_states)-1 max(obj.trellis_states)+1]);
|
||||
end
|
||||
|
||||
VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path);
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@@ -219,6 +284,12 @@ classdef MLSE < handle
|
||||
for h = length(data_in)-1:-1:1
|
||||
|
||||
bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2;
|
||||
|
||||
% exclude edge→edge transitions only for even->odd steps && PAM-6
|
||||
if mod(h+1, 2) == 0 && obj.M == 6 && trellis_exclusion
|
||||
bm(edge_edge_mask) = -Inf;
|
||||
end
|
||||
|
||||
pm = pm + bm.';
|
||||
[beta(:,h),pm_survivor_bw_idx(:,h)] = max(pm,[],2); % choose lowest path metric as new state
|
||||
pm = repmat(beta(:,h).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
|
||||
@@ -255,6 +326,7 @@ classdef MLSE < handle
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% FORWARD PASS PAM2,4,8 %%%%%
|
||||
|
||||
% These are interchangeable... second is chatgpt:
|
||||
nml_LLP = LLP - max(LLP); %subtract highest value for better numerical stability, LLP's are not always close to zero
|
||||
expLLP = exp(nml_LLP);
|
||||
state_prob = expLLP ./ sum(expLLP); % sums to one (or numerically close to one)
|
||||
@@ -266,9 +338,6 @@ classdef MLSE < handle
|
||||
state_prob = exp(logPstate); % exact, sums to 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if obj.M == 6
|
||||
|
||||
num_bits = 5;
|
||||
@@ -357,17 +426,19 @@ classdef MLSE < handle
|
||||
for bit_idx = 1:num_bits
|
||||
|
||||
% Find indices where bit is 0 and where it is 1
|
||||
idx_sym_1 = bit_mapping(:,bit_idx) == 0;
|
||||
idx_bit_0 = bit_mapping(:,bit_idx) == 0;
|
||||
idx_bit_1 = bit_mapping(:,bit_idx) == 1;
|
||||
|
||||
% Sum over log-probabilities (Max-Log approximation: using max instead of sum)
|
||||
LLR_maxlogmap(:,bit_idx) = max(LLP(idx_bit_1,:), [], 1) - max(LLP(idx_sym_1,:), [], 1);
|
||||
% Sum over log-probabilities
|
||||
% Max-Log approximation: using max instead of sum)
|
||||
LLR_maxlogmap(:,bit_idx) = max(LLP(idx_bit_1,:), [], 1) - max(LLP(idx_bit_0,:), [], 1);
|
||||
|
||||
% Sum probabilities over states for which the bit is 1 and 0, respectively.
|
||||
P0 = sum(state_prob(idx_sym_1, :),1);
|
||||
P0 = sum(state_prob(idx_bit_0, :),1);
|
||||
P1 = sum(state_prob(idx_bit_1, :),1);
|
||||
LLR_exact(:,bit_idx) = log(P1./P0); % N x num_bits
|
||||
|
||||
|
||||
end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@@ -377,12 +448,12 @@ classdef MLSE < handle
|
||||
LLR_exact = LLR_exact;
|
||||
for k = 1:num_bits
|
||||
|
||||
idx_bit_1 = (tx_bits(:,k) == 0); %wo sind die 1en
|
||||
idx_sym_1 = (tx_bits(:,k) == 1); %wo sind die 0en
|
||||
idx_bit_0 = (tx_bits(:,k) == 0); %wo sind die 1en
|
||||
idx_bit_1 = (tx_bits(:,k) == 1); %wo sind die 0en
|
||||
|
||||
%LLR's for all actually transmitted ones or zeros
|
||||
llr0 = LLR_exact(idx_bit_1,k);
|
||||
llr1 = LLR_exact(idx_sym_1,k);
|
||||
llr0 = LLR_exact(idx_bit_0,k);
|
||||
llr1 = LLR_exact(idx_bit_1,k);
|
||||
|
||||
% Calculate mutual information for bit position k
|
||||
I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1
|
||||
@@ -396,7 +467,6 @@ classdef MLSE < handle
|
||||
|
||||
VITERBI_ESTIMATION_SYMBOLS = VITERBI_ESTIMATION_SYMBOLS./rms(VITERBI_ESTIMATION_SYMBOLS);
|
||||
|
||||
|
||||
if debug
|
||||
%%% DEBUG PLOT LIKELIHOOD RATIOS %%%
|
||||
figure(115);clf
|
||||
@@ -415,6 +485,8 @@ classdef MLSE < handle
|
||||
legend
|
||||
end
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%% CHECK BER's %%%%%
|
||||
|
||||
@@ -430,6 +502,13 @@ classdef MLSE < handle
|
||||
fprintf('Viterbi BER: %.2e \n',ber_viterbi);
|
||||
fprintf('Viterbi Errors = %d\n', numErr);
|
||||
|
||||
pairs = reshape(VITERBI_ESTIMATION_SYMBOLS,2,[]).';
|
||||
levels = sort(unique(VITERBI_ESTIMATION_SYMBOLS));
|
||||
isedge = ismember(pairs, [levels(1) levels(end)]);
|
||||
isforbidden = sum(isedge,2)==2;
|
||||
fprintf('Found %d forbidden transitions (even→odd edges).\n', nnz(isforbidden));
|
||||
|
||||
|
||||
% Convert LLR values to a hard-decision bit stream
|
||||
bit_stream = LLR_maxlogmap > 0; %ratio separates lower or higher than =0 -> simply decode for the negative values
|
||||
bit_stream = reshape(bit_stream',[],1);
|
||||
@@ -458,7 +537,7 @@ classdef MLSE < handle
|
||||
rx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(FW_EST');
|
||||
rx_bits = reshape(rx_bits',[],1);
|
||||
[~,~,ber_fw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
fprintf('FW BER: %.2e \n',ber_fw);
|
||||
fprintf('FW BER : %.2e \n',ber_fw);
|
||||
disp('Stop DEBUG MLSE:')
|
||||
fprintf('\n')
|
||||
end
|
||||
@@ -471,7 +550,5 @@ classdef MLSE < handle
|
||||
s = amax + log(sum(exp(a - amax), dim));
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -42,6 +42,7 @@ classdef TransmissionPerformance
|
||||
0.8733, 0.8790, 0.8848, 0.8905, 0.8962, ...
|
||||
0.9019, 0.9077, 0.9134, 0.9191, 0.9248, ...
|
||||
0.9306, 0.9363, 0.9420, 0.9477, 0.9535];
|
||||
|
||||
NGMITHRESHOLDS_SDHD = [0.8116, 0.8167, 0.8241, 0.8317, 0.8401, ...
|
||||
0.8459, 0.8512, 0.8574, 0.8685, 0.8746, ...
|
||||
0.8829, 0.8892, 0.8958, 0.9022, 0.9090,...
|
||||
@@ -60,7 +61,7 @@ classdef TransmissionPerformance
|
||||
|
||||
%% LUT for KP4-FEC and Inner Code https://grouper.ieee.org/groups/802/3/dj/public/23_03/patra_3dj_01b_2303.pdf
|
||||
|
||||
CODE_RATE_KP4_AND_INNER = [0.885799];
|
||||
CODE_RATE_KP4_AND_INNER = [0.885799]; %https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9979198 -> 199.8 / 224 = 0.891 als code rate
|
||||
BERTHRESHOLDS_KP4_AND_INNER = 4.85e-3;
|
||||
|
||||
% https://www.ieee802.org/3/bs/public/14_11/parthasarathy_3bs_01a_1114.pdf
|
||||
@@ -153,11 +154,21 @@ classdef TransmissionPerformance
|
||||
netrates.SDHD.Threshold = NaN(1, numMeasurements);
|
||||
end
|
||||
if ~isempty(ber)
|
||||
netrates.STAIR.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.STAIR.NetRate = NaN(1, numMeasurements);
|
||||
netrates.STAIR.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.STAIR.Threshold = NaN(1, numMeasurements);
|
||||
|
||||
netrates.HD.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.HD.NetRate = NaN(1, numMeasurements);
|
||||
netrates.HD.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.HD.Threshold = NaN(1, numMeasurements);
|
||||
|
||||
netrates.KP4.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.KP4.NetRate = NaN(1, numMeasurements);
|
||||
netrates.KP4.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.KP4.Threshold = NaN(1, numMeasurements);
|
||||
|
||||
netrates.KP4_hamming.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.KP4_hamming.NetRate = NaN(1, numMeasurements);
|
||||
netrates.KP4_hamming.CodeRate = NaN(1, numMeasurements);
|
||||
@@ -200,11 +211,47 @@ classdef TransmissionPerformance
|
||||
end
|
||||
if ~isempty(idxBER)
|
||||
codeRate = obj.CODE_RATES_HD(idxBER);
|
||||
netrates.STAIR.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.STAIR.GrossRate(i) = grossRate(i) ;
|
||||
netrates.STAIR.CodeRate(i) = codeRate;
|
||||
netrates.STAIR.Threshold(i) = obj.BERTHRESHOLDS_HD(idxBER);
|
||||
end
|
||||
|
||||
% HD FEC 3,8e-3
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_HDFEC):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_HDFEC(j)
|
||||
idxBER = j;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if ~isempty(idxBER)
|
||||
codeRate = obj.CODE_RATE_HDFEC(idxBER);
|
||||
netrates.HD.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.HD.GrossRate(i) = grossRate(i) ;
|
||||
netrates.HD.CodeRate(i) = codeRate;
|
||||
netrates.HD.Threshold(i) = obj.BERTHRESHOLDS_HD(idxBER);
|
||||
netrates.HD.Threshold(i) = obj.CODE_RATE_HDFEC(idxBER);
|
||||
end
|
||||
|
||||
|
||||
% KP4
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_KP4):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_KP4(j)
|
||||
idxBER = j;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if ~isempty(idxBER)
|
||||
codeRate = obj.CODE_RATE_KP4(idxBER);
|
||||
netrates.KP4.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.KP4.GrossRate(i) = grossRate(i) ;
|
||||
netrates.KP4.CodeRate(i) = codeRate;
|
||||
netrates.KP4.Threshold(i) = obj.CODE_RATE_KP4(idxBER);
|
||||
end
|
||||
|
||||
|
||||
% KP4 + inner Hamming
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_KP4_AND_INNER):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_KP4_AND_INNER(j)
|
||||
@@ -220,6 +267,7 @@ classdef TransmissionPerformance
|
||||
netrates.KP4_hamming.Threshold(i) = obj.BERTHRESHOLDS_KP4_AND_INNER(idxBER);
|
||||
end
|
||||
|
||||
% O FEC
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_O_FEC):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_O_FEC(j)
|
||||
|
||||
@@ -63,7 +63,9 @@ classdef DBHandler < handle
|
||||
obj.refresh();
|
||||
|
||||
else
|
||||
|
||||
error('DB seems to be corrupt')
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -183,6 +185,7 @@ classdef DBHandler < handle
|
||||
fprintf('Raw Rx Paths: Found %d duplictaes of %s \n',duplictae_raw.occurrences(i),duplictae_raw.rx_raw_path(i));
|
||||
end
|
||||
end
|
||||
|
||||
healthyDB = true;
|
||||
end
|
||||
|
||||
@@ -713,68 +716,167 @@ classdef DBHandler < handle
|
||||
selectedFields
|
||||
end
|
||||
|
||||
% Step 1: Handle selectedFields conversion
|
||||
% -------- Step 1: Normalize selectedFields to {'Table.field', ...} --------
|
||||
if isempty(selectedFields) || (ischar(selectedFields) && strcmpi(selectedFields, 'all'))
|
||||
selectedFields = obj.getTableFieldNames('Runs'); % Default to Runs table
|
||||
selectedFields = obj.getTableFieldNames('Runs'); % default
|
||||
elseif isstruct(selectedFields)
|
||||
% Convert struct to cell array of 'Table.field' format
|
||||
newFields = {};
|
||||
tableNames = fieldnames(selectedFields);
|
||||
for t = 1:numel(tableNames)
|
||||
tableStruct = selectedFields.(tableNames{t});
|
||||
fieldNames = fieldnames(tableStruct);
|
||||
for f = 1:numel(fieldNames)
|
||||
if isequal(tableStruct.(fieldNames{f}), 1)
|
||||
newFields{end+1} = sprintf('%s.%s', tableNames{t}, fieldNames{f});
|
||||
fns = fieldnames(tableStruct);
|
||||
for f = 1:numel(fns)
|
||||
if isequal(tableStruct.(fns{f}), 1)
|
||||
newFields{end+1} = sprintf('%s.%s', tableNames{t}, fns{f}); %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
end
|
||||
selectedFields = newFields;
|
||||
end
|
||||
|
||||
% Step 2: Generate COALESCE string for SELECT clause
|
||||
% Parse the table names actually referenced by the SELECT
|
||||
reqTables = unique(cellfun(@(s) extractBefore(s, '.'), selectedFields, ...
|
||||
'UniformOutput', false));
|
||||
|
||||
% -------- Step 2: Build SELECT with COALESCE wrapper as you already do ----
|
||||
selectClause = obj.generateCoalesceString(selectedFields);
|
||||
|
||||
% Step 3: Generate FROM clause with joins
|
||||
mainTable = 'Runs';
|
||||
fromClause = ['FROM ', mainTable, ' '];
|
||||
% -------- Step 3: FROM and minimal JOIN plan ------------------------------
|
||||
% Decide main table: prefer the first explicitly referenced table, else 'Runs'
|
||||
if ~isempty(reqTables)
|
||||
mainTable = reqTables{1};
|
||||
else
|
||||
mainTable = 'Runs';
|
||||
end
|
||||
|
||||
% Prepare join buffers
|
||||
normalJoins = '';
|
||||
equalizerJoin = '';
|
||||
|
||||
tableNamesAll = fieldnames(obj.tables);
|
||||
for t = 1:numel(tableNamesAll)
|
||||
tableName = tableNamesAll{t};
|
||||
|
||||
if strcmpi(tableName, mainTable) || strcmpi(tableName, 'sqlite_sequence')
|
||||
continue;
|
||||
end
|
||||
|
||||
if isfield(obj.tables.(tableName), 'run_id')
|
||||
% Direct join to Runs
|
||||
normalJoins = [normalJoins, 'LEFT JOIN ', tableName, ...
|
||||
' ON ', mainTable, '.run_id = ', tableName, '.run_id '];
|
||||
elseif isfield(obj.tables.(tableName), 'eq_id')
|
||||
% Equalizer depends on Results, collect this join separately
|
||||
equalizerJoin = ['LEFT JOIN ', tableName, ...
|
||||
' ON Results.eq_id = ', tableName, '.eq_id '];
|
||||
% If WHERE references a table not in reqTables (e.g., Runs.*), make sure it’s present.
|
||||
whereClause = '';
|
||||
if ~isempty(filterParams)
|
||||
whereClause = obj.generateWhereClause(filterParams);
|
||||
% Heuristic: add 'Runs' if WHERE clause mentions 'Runs.'
|
||||
if contains(whereClause, 'Runs.')
|
||||
reqTables = unique([reqTables; {'Runs'}]); %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
|
||||
% Combine joins: normal joins first, Equalizer last
|
||||
fromClause = [fromClause, normalJoins, equalizerJoin];
|
||||
% Ensure main table is included
|
||||
if ~ismember(mainTable, reqTables)
|
||||
reqTables = unique([mainTable; reqTables]); %#ok<AGROW>
|
||||
end
|
||||
|
||||
% Step 4: Generate WHERE clause if filter parameters exist
|
||||
if ~isempty(filterParams)
|
||||
whereClause = obj.generateWhereClause(filterParams);
|
||||
if ~isempty(whereClause)
|
||||
query = [selectClause, ' ', fromClause, 'WHERE ', whereClause];
|
||||
else
|
||||
query = [selectClause, ' ', fromClause];
|
||||
% We’ll build joins only for the required tables (minus the main)
|
||||
otherTables = setdiff(reqTables, {mainTable});
|
||||
|
||||
% Keep track of what’s already in the FROM graph (start with main)
|
||||
present = string(mainTable);
|
||||
joins = strings(0,1);
|
||||
|
||||
% Helper lambdas
|
||||
hasField = @(tbl, fld) isfield(obj.tables.(char(tbl)), char(fld));
|
||||
canJoinBy = @(left, right, key) hasField(left, key) && hasField(right, key);
|
||||
|
||||
|
||||
% A small helper that adds a LEFT JOIN if the right table isn't present yet
|
||||
function addJoinByKey(rightTbl, key)
|
||||
if any(present == string(rightTbl))
|
||||
return; % already joined
|
||||
end
|
||||
% Prefer to join against an already-present table that has the key
|
||||
anchor = '';
|
||||
for k = 1:numel(present)
|
||||
if canJoinBy(char(present(k)), rightTbl, key)
|
||||
anchor = char(present(k));
|
||||
break;
|
||||
end
|
||||
end
|
||||
if isempty(anchor)
|
||||
% No anchor in current graph; if the right table is 'Equalizer' and key is eq_id,
|
||||
% try to ensure a bridge table with eq_id exists (Results or a dashboard view).
|
||||
if strcmpi(rightTbl,'Equalizer') && strcmpi(key,'eq_id')
|
||||
% Bring in one eq_id-capable table if it is requested
|
||||
bridgeOrder = {'Results','dashboard_old','dashboard_new','dashboard_ungrouped'};
|
||||
for b = 1:numel(bridgeOrder)
|
||||
br = bridgeOrder{b};
|
||||
if ismember(br, reqTables) && ~any(present == string(br)) && hasField(obj.tables.(br),'eq_id')
|
||||
% Attach bridge by run_id if possible, otherwise leave for eq_id
|
||||
if any(present == "Runs") && hasField(obj.tables.(br),'run_id') && hasField(obj.tables.('Runs'),'run_id')
|
||||
joins(end+1,1) = "LEFT JOIN " + br + " ON Runs.run_id = " + br + ".run_id";
|
||||
present(end+1,1) = string(br);
|
||||
anchor = br; % we can now anchor Equalizer on eq_id to this
|
||||
break;
|
||||
else
|
||||
% Fallback: anchor to main if it shares eq_id
|
||||
for k = 1:numel(present)
|
||||
pk = char(present(k));
|
||||
if canJoinBy(pk, br, 'eq_id')
|
||||
joins(end+1,1) = "LEFT JOIN " + br + " ON " + pk + ".eq_id = " + br + ".eq_id";
|
||||
present(end+1,1) = string(br);
|
||||
anchor = br;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if ~isempty(anchor), break; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
% Re-scan for an anchor (maybe the bridge helped)
|
||||
if isempty(anchor)
|
||||
for k = 1:numel(present)
|
||||
if canJoinBy(char(present(k)), rightTbl, key)
|
||||
anchor = char(present(k));
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
if isempty(anchor)
|
||||
% As a final fallback, if the main table is Runs and right has run_id, join by run_id
|
||||
if strcmpi(mainTable,'Runs') && hasField(obj.tables.(rightTbl),'run_id') && hasField(obj.tables.('Runs'),'run_id')
|
||||
anchor = 'Runs';
|
||||
key = 'run_id';
|
||||
end
|
||||
end
|
||||
if isempty(anchor)
|
||||
% Could not find a path; skip join silently (or throw if you prefer strict)
|
||||
return;
|
||||
end
|
||||
joins(end+1,1) = "LEFT JOIN " + rightTbl + " ON " + anchor + "." + key + " = " + rightTbl + "." + key;
|
||||
present(end+1,1) = string(rightTbl);
|
||||
end
|
||||
|
||||
% First pass: if WHERE uses Runs.* and mainTable isn’t Runs, ensure Runs is in the graph
|
||||
if contains(string(whereClause), "Runs.") && ~any(present == "Runs")
|
||||
% Try to join Runs to whatever has run_id (mainTable ideally)
|
||||
if hasField(mainTable, 'run_id') && hasField('Runs', 'run_id')
|
||||
joins(end+1,1) = "LEFT JOIN Runs ON " + string(mainTable) + ".run_id = Runs.run_id";
|
||||
present(end+1,1) = "Runs";
|
||||
end
|
||||
end
|
||||
|
||||
% Join the required tables with minimal edges
|
||||
for i = 1:numel(otherTables)
|
||||
tbl = otherTables{i};
|
||||
% Prefer run_id join if possible, else eq_id, else skip
|
||||
if any(present == "Runs") && hasField(tbl,'run_id')
|
||||
addJoinByKey(tbl, 'run_id');
|
||||
elseif hasField(tbl,'run_id') && hasField(mainTable,'run_id')
|
||||
addJoinByKey(tbl, 'run_id');
|
||||
elseif hasField(tbl,'eq_id')
|
||||
addJoinByKey(tbl, 'eq_id');
|
||||
else
|
||||
% no obvious key; skip
|
||||
end
|
||||
end
|
||||
|
||||
% Build the FROM clause
|
||||
fromClause = "FROM " + string(mainTable) + " " + strjoin(joins, " ");
|
||||
|
||||
% -------- Step 4: WHERE (unchanged logic) ------------------------------
|
||||
if ~isempty(whereClause)
|
||||
query = char(strjoin([selectClause, fromClause, "WHERE " + string(whereClause)], " "));
|
||||
else
|
||||
query = [selectClause, ' ', fromClause];
|
||||
query = char(strjoin([selectClause, fromClause], " "));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -51,8 +51,9 @@ classdef DataStorage < handle
|
||||
function save(obj,path)
|
||||
try
|
||||
save(path,"obj");
|
||||
catch
|
||||
|
||||
catch e
|
||||
disp(e.message)
|
||||
disp('Provide save path')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
40
Classes/Warehouse_class/classes/minimal_example.m
Normal file
40
Classes/Warehouse_class/classes/minimal_example.m
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
params = struct;
|
||||
params.sir = [20:2:36];
|
||||
params.laser_linewidth = [1e5 1e6 10e6];
|
||||
params.pn_key = [1:3];
|
||||
params.vp = [0.25,0.5,0.75,1];
|
||||
params.vb = [1:0.1:1.8];
|
||||
params.rop = -5:0;
|
||||
|
||||
wh = DataStorage(params);
|
||||
wh.addStorage("ber");
|
||||
wh.addStorage("rop_save");
|
||||
wh.addStorage("cspr");
|
||||
wh.addStorage("mod_out_pow");
|
||||
|
||||
|
||||
cnt = 1;
|
||||
for sir = params.sir
|
||||
for lw = params.laser_linewidth
|
||||
for pnk = params.pn_key
|
||||
for vp = params.vp
|
||||
for vb = params.vb
|
||||
for rop = params.rop
|
||||
|
||||
current_ber = randn;
|
||||
wh.addValueToStorage(current_ber,'ber',sir,lw,pnk,vp,vb,rop);
|
||||
wh.addValueToStorage(-10,'rop_save',sir,lw,pnk,vp,vb,rop);
|
||||
wh.addValueToStorage(10,'cspr',sir,lw,pnk,vp,vb,rop);
|
||||
wh.addValueToStorage(-6,'mod_out_pow',sir,lw,pnk,vp,vb,rop);
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
wh.getStoValue('ber',20,1e5,2,0.25,1.1,-3)
|
||||
|
||||
wh.showInfo
|
||||
Reference in New Issue
Block a user