Many changes here and there. I lost track... :-(

Current work is on MLSE and SD Decoding etc. MLSE is currently not 100% working, the scalings are maybe off?!
This commit is contained in:
Silas Oettinghaus
2025-08-11 07:42:04 +02:00
parent 09d9e5011c
commit 5dbc48abc0
37 changed files with 1506 additions and 570 deletions

View File

@@ -269,6 +269,7 @@ classdef Signal
SignalPower = [obj.power];
Nase = [0];
SignalCopy = obj.signal;
SignalCopy = [];
ModifierName = class(CallingModifier);
@@ -365,7 +366,7 @@ classdef Signal
if options.normalizeTo0dB
p_lin = p_lin./ max(p_lin);
p_dbm = 10*log10(p_lin); % normalized to 0 dB
ylab = "normalized to 0 dB";
ylab = "Normalized PSD";
else
p_dbm = 10*log10(p_lin);
ylab = "Power (dB/Hz)";
@@ -395,7 +396,7 @@ classdef Signal
if options.normalizeToNyquist == 0
xlabel("Frequency in GHz");
edgetick = 2^(nextpow2(obj.fs*1e-9));
xticks(-edgetick:16:edgetick);
xticks(-edgetick:32:edgetick);
xlim([100*round(min(w)/100,1)-10, 100*round(max(w)/100,1)+10])
xlim([-128 128]);%256GSa/s
else
@@ -406,13 +407,20 @@ classdef Signal
ylabel(ylab);
try
ylim([max(min(floor(min(p_dbm))-3, ax.YLim(1)),-40), min(max(ceil(max(p_dbm))+3, ax.YLim(2)),10)]);
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))-3, ceil(max(p_dbm))+3]);
end
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
if options.normalizeTo0dB
% ylim([-40, 3]);
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
else
ylim([floor(min(p_dbm))-3, ceil(max(p_dbm))+3]);
end
yticks(-200:10:10);
grid on; grid minor;
grid on;% grid minor;
legend
% legend('Interpreter','none');
@@ -912,8 +920,8 @@ classdef Signal
elseif mode == 1
% generate eye diagram using histogram
maxA = max(sig(100:end-100))*2;
minA = min(sig(100:end-100))*2;
maxA = max(sig(100:end-100))*1.3;
minA = min(sig(100:end-100))*1.3;
% maxA = 0.0015;
% minA = 0;
@@ -926,8 +934,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
ax = gca;
plot_data = 20*log10(hist_data);
plot_data(plot_data==-Inf) = 0;
@@ -944,27 +952,29 @@ classdef Signal
if isa(obj,'Opticalsignal')
title(['Optical Eye ',options.displayname])
ylabel("Power in mW");
y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,16));
y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,6));
min_ = min(abs(obj.signal(100:end-100)).^2);
max_ = abs(max(obj.signal(100:end-100)).^2);
elseif isa(obj,'Electricalsignal')
title(['Electrical Eye ',options.displayname])
ylabel("Voltage in V");
y_tickstring = string(linspace(maxA,minA,16));
y_tickstring = string(linspace(maxA,minA,6));
min_ = min(obj.signal(100:end-100));
max_ = abs(max(obj.signal(100:end-100)));
else
title(['Digital Eye ',options.displayname])
ylabel("Digital Signal Amplitude");
y_tickstring = string(linspace(maxA,minA,16));
y_tickstring = string(linspace(maxA,minA,6));
min_ = min(obj.signal(100:end-100));
max_ = abs(max(obj.signal(100:end-100)));
end
xlabel('Time in ps')
% add information
if 0
if 1
pwr_dbm = round(obj.power,3);
pwr_lin = obj.power("unit",power_notation.W);
@@ -1073,13 +1083,15 @@ classdef Signal
end
yticks(linspace(0,histpoints,16));
yticks(linspace(0,histpoints,6));
y_tickstring = sprintfc('%.2f', y_tickstring);
yticklabels(y_tickstring);
xticks(linspace(0,histpoints_horizontal,8))
xticks(linspace(0,histpoints_horizontal,6))
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
xticklabels(x_tickstring);
grid off
end

View File

@@ -17,7 +17,7 @@ classdef M8199B < AWG
fdac = 256e9;
Lp_awg = Filter('filtdegree',4,"f_cutoff",75e9,"fs",fdac*options.kover,"filterType",filtertypes.butterworth,"active",true);
Lp_awg = Filter('filtdegree',3,"f_cutoff",75e9,"fs",fdac*options.kover,"filterType",filtertypes.gaussian,"active",true);
obj = obj@AWG("fdac",fdac,"dac_min",dac_min,"dac_max",dac_max,"lpf_active",1,"H_lpf",Lp_awg,"kover",options.kover,...
"bit_resolution",5.5,"normalize2dac",1,"upsampling_method","samplehold");

View File

@@ -136,6 +136,13 @@ classdef PAMsource
%%%%%% Duobinary %%%%%%%%%%%
if obj.db_precode
obj.duobinary_mode = db_mode.db_precoded;
end
if obj.db_encode
obj.duobinary_mode = db_mode.db_encoded;
end
switch obj.duobinary_mode
case db_mode.no_db

View File

@@ -1,189 +1,171 @@
classdef Fiber
%FIBER Summary of this class goes here
% Detailed explanation goes here
% Fiber: Simulate optical fiber signal propagation using
% the split-step Fourier method (SSFM).
properties
% Simulation sampling frequency [Hz]
fsimu
% Fiber length [m]
fiber_length
% Attenuation coefficient [dB/km]
alpha
% Dispersion parameter D [s/m^2]
D
% Dispersion slope [s/m^3]
Dslope
% Reference wavelength [m]
lambda0
% Nonlinear coefficient [1/(W·m)]
gamma
% Maximum allowed nonlinear phase change per step [rad]
dphimax
% Derived parameters:
% Second-order dispersion coefficient β2 [s^2/m]
b2
% Third-order dispersion coefficient β3 [s^3/m]
b3
% Linear attenuation constant [1/m]
alpha_lin
% Frequency-domain linear operator per unit length
linstep
end
methods
function obj = Fiber(options)
%FIBER Construct an instance of this class
% Detailed explanation goes here
% Constructor: initialize fiber physical and simulation parameters.
arguments
options.fsimu
options.fiber_length = 0
options.alpha = 0.2
options.D = 17
options.Dslope = 0.06
options.lambda0 = 1550
options.gamma = 0
options.dphimax = 5e-3
options.fsimu % Sampling frequency [Hz]
options.fiber_length = 0 % Fiber length [km]
options.alpha = 0.2 % Attenuation [dB/km]
options.D = 17 % Dispersion D parameter [ps/(nm·km)]
options.Dslope = 0.06 % Dispersion slope [ps/(nm^2·km)]
options.lambda0 = 1550 % Reference wavelength [nm]
options.gamma = 0 % Nonlinear coefficient [1/(W·km)]
options.dphimax = 5e-3 % Max phase step [rad]
end
obj.fsimu = options.fsimu;
obj.fiber_length = options.fiber_length*1000; %km
obj.alpha = options.alpha;
obj.D = options.D*1e-6;
obj.Dslope = options.Dslope*1e3;
obj.lambda0 = options.lambda0*1e-9;
obj.gamma = options.gamma;
obj.dphimax = options.dphimax;
% Assign inputs to object properties, converting units to SI.
obj.fsimu = options.fsimu;
obj.fiber_length = options.fiber_length * 1e3; % km m
obj.alpha = options.alpha;
obj.D = options.D * 1e-6; % ps/(nm·km) s/(m·m)
obj.Dslope = options.Dslope * 1e3; % ps/(nm^2·km) s/m^2
obj.lambda0 = options.lambda0 * 1e-9; % nm m
obj.gamma = options.gamma; % 1/(W·km) (assumed 1/(W·m) internally)
obj.dphimax = options.dphimax;
end
function signalclass_out = process(obj,signalclass_in)
function signalclass_out = process(obj, signalclass_in)
% process: Apply fiber propagation to input signal class.
% Calls the internal SSFM routine and logs the operation.
% actual processing of the signal (steps 1. - 3.)
signalclass_in = obj.process_(signalclass_in);
% Run internal split-step propagation
signalclass = obj.process_(signalclass_in);
% append to logbook
lbdesc = 'Fiber ';
signalclass_in = signalclass_in.logbookentry(lbdesc);
% write to output
signalclass_out = signalclass_in;
% Add entry to logbook for tracking
signalclass = signalclass.logbookentry('Fiber ');
% Return processed signal class
signalclass_out = signalclass;
end
function opt_sig = process_(obj,opt_sig)
%METHOD1 Summary of this method goes here
% Detailed explanation goes here
function opt_sig = process_(obj, opt_sig)
% process_: Internal routine for one-step fiber propagation.
% Computes linear and (optionally) nonlinear effects.
signal = opt_sig.signal;
% Extract time-domain field and wavelength
signal = opt_sig.signal;
lambda_signal = opt_sig.lambda;
obj.D = obj.D + (lambda_signal-obj.lambda0)*obj.Dslope;
obj.b2 = -obj.D*lambda_signal^2/(2*pi*Constant.LightSpeed);
obj.b3 = ((lambda_signal.^2/(2*pi*Constant.LightSpeed)).^2*obj.Dslope);
obj.alpha_lin = obj.alpha/10*log(10)/1000;
% Update dispersion parameter D for current wavelength
obj.D = obj.D + (lambda_signal - obj.lambda0) * obj.Dslope;
% Compute dispersion coefficients (β2, β3)
obj.b2 = -obj.D * lambda_signal^2 / (2 * pi * Constant.LightSpeed);
obj.b3 = (lambda_signal^2 / (2 * pi * Constant.LightSpeed))^2 * obj.Dslope;
% Convert attenuation from dB/km to linear 1/m
obj.alpha_lin = obj.alpha / 10 * log(10) / 1e3;
% Build frequency axis for FFT operations
N = length(signal);
faxis = linspace(-obj.fsimu/2,obj.fsimu/2,N+1);
faxis = ifftshift(faxis(:,1:end-1));
faxis = faxis';
obj.linstep = -obj.alpha_lin/2 - 2*1j*pi^2*obj.b2*faxis.^2 - 4/3*1j*pi^3*obj.b3*faxis.^3;
faxis = linspace(-obj.fsimu/2, obj.fsimu/2, N+1);
faxis = faxis(1:end-1); % drop redundant endpoint
faxis = ifftshift(faxis)'; % center zero frequency
if 0
H = exp((obj.linstep)*obj.fiber_length);
figure(222)
hold on
plot(faxis.*1e-9,abs(real(Y)),'LineStyle','-','DisplayName','Abs(real) part of complex TF');
xlabel("Frequency in GHz")
ylabel("$ R|(H(\omega, L))|$")
end
% Define linear operator per meter in frequency domain
obj.linstep = -obj.alpha_lin/2 ... % half-step loss
- 1j*2*pi^2*obj.b2 .* faxis.^2 ... % second-order dispersion
- 1j*(4/3)*pi^3*obj.b3 .* faxis.^3; % third-order dispersion
% Choose linear-only or nonlinear SSFM based on gamma
if obj.gamma ~= 0
% Full adaptive SSFM with nonlinear Schrödinger solver
opt_out = obj.NLSE(signal);
else
opt_out = ifft( fft(signal) .* exp(obj.linstep*obj.fiber_length) ); % only one linear step
% Single-step linear propagation in frequency domain
opt_out = ifft( fft(signal) .* exp(obj.linstep * obj.fiber_length) );
end
%TODO: attenuate nase ...
% Update output field in signal class
opt_sig.signal = opt_out;
end
function [yout] = NLSE(obj, xin)
maxPow = obj.gamma.*max(abs(xin).^2);
Leff = obj.dphimax / maxPow ;
dz = Leff;
function yout = NLSE(obj, xin)
% NLSE: Solve nonlinear Schrödinger equation by adaptive split-step
% xin: input time-domain field
% Returns yout: output time-domain field after propagation
% Initial estimate of effective length per step
maxPow = obj.gamma * max(abs(xin).^2);
Leff = obj.dphimax / maxPow;
dz = Leff;
z_prop = 0;
yout = fft(xin);
yout = ((yout).*exp(obj.linstep*dz/2));
% Apply initial half-step linear operator
yout = fft(xin) .* exp(obj.linstep * dz/2);
% Loop until full fiber length is reached
while true
% Inverse FFT to time domain for nonlinear phase shift
yout = ifft(yout);
Leff = dz;
% Nonlinear phase rotation per segment
power = abs(yout).^2;
Hnl = exp( -1j*obj.gamma*power*Leff);
yout = yout .* Hnl;
Hnl = exp(-1j * obj.gamma * power * dz);
yout = yout .* Hnl;
% Accumulate propagation distance
z_prop = z_prop + dz;
maxPow = obj.gamma*max(abs(yout).^2);
Leff = obj.dphimax/maxPow;
dz_new = Leff;
% Compute adaptive step for next interval
maxPow = obj.gamma * max(abs(yout).^2);
dz_new = obj.dphimax / maxPow;
% If remaining length shorter than new step, finish loop
if z_prop + dz_new > obj.fiber_length
dz_new = obj.fiber_length - z_prop;
break
break;
end
yout = fft(yout);
yout = ((yout).*exp(obj.linstep*(dz/2+dz_new/2)));
dz = dz_new;
% Half-step linear operator bridging segments
yout = fft(yout) .* exp(obj.linstep * ((dz/2) + (dz_new/2)));
dz = dz_new;
end
yout = fft(yout);
yout = ((yout).*exp(obj.linstep*(dz/2+dz_new/2)));
% Final propagation segment: combine half-steps and nonlinear
yout = fft(yout) .* exp(obj.linstep * ((dz/2) + (dz_new/2)));
yout = ifft(yout);
Leff = dz;
% Last nonlinear phase shift
power = abs(yout).^2;
Hnl = exp(-1j * obj.gamma * power * dz_new);
yout = yout .* Hnl;
Hnl = exp( -1j*obj.gamma*power*Leff);
yout = yout .* Hnl;
yout = fft(yout);
yout = ((yout).*exp(obj.linstep*(dz/2)));
% Final half-step linear operator to complete SSFM
yout = fft(yout) .* exp(obj.linstep * (dz_new/2));
yout = ifft(yout);
end
end
end

View File

@@ -83,6 +83,14 @@ classdef MLSE < handle
% impulse respnse to remove from signal
obj.DIR = flip(obj.DIR); %i.e. -0.2676 -0.0478 1.0000
% % make the combined impulse-response have net gain = 1
% h = obj.DIR(:);
% h = h / sum(h);
% obj.DIR = h.';
% Normalize the Trellis states to =1 RMS
obj.trellis_states = obj.trellis_states ./ rms(obj.trellis_states);
% seems to be the only way to use combvec for a flexible amount
% of vectors. 'combs' contains all trellis states
pre_comb_mat = repmat(obj.trellis_states,length(obj.DIR)-1,1);
@@ -116,6 +124,7 @@ classdef MLSE < handle
% first: RMS normalization of input data (rms==1)
data_in = data_in ./ rms(data_in);
data_in = data_in - mean(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)
@@ -125,6 +134,10 @@ classdef MLSE < handle
end
end
y_clean = conv( data_ref, flip(obj.DIR), "same" );
sigma2 = var( data_in - y_clean );
inv2s2 = 1/(2*sigma2);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% FORWARD PASS (VITERBI -Alpha's) %%%%%
@@ -134,7 +147,7 @@ classdef MLSE < handle
% first start is evaluated without ISI/ wihout the full Impulse response
% so simply use the constellation here
bm = -(data_in(1) - last_sym).^2 ;
bm = -(data_in(1) - last_sym).^2 * inv2s2;
pm = pm + bm;
[alpha(:,1),pm_survivor_fw_idx(:,1)] = max(pm,[],2);
pm = repmat(alpha(:,1).',nStates,1);
@@ -143,10 +156,10 @@ classdef MLSE < handle
% Forward Recursion (FSM Computation)
for n = 2:length(data_in)
bm = -(data_in(n) - noise_free_received).^2 ;
bm = -(data_in(n) - noise_free_received).^2 * inv2s2;
pm = pm + bm;
[alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state
pm = repmat(alpha(:,n).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
[alpha(:,n),pm_survivor_fw_idx(:,n)] = max(pm,[],2); % choose lowest path metric as new state
pm = repmat(alpha(:,n).',nStates,1); % update pm (chosen state to 2nd dimension -> FROM state)
bm_fw(:,:,n) = bm;
@@ -177,7 +190,7 @@ classdef MLSE < handle
% predecessor
for h = length(data_in)-1:-1:1
bm = -(data_in(h+1) - noise_free_received).^2 ;
bm = -(data_in(h+1) - noise_free_received).^2 * inv2s2;
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)
@@ -214,10 +227,22 @@ classdef MLSE < handle
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% FORWARD PASS PAM2,4,8 %%%%%
nml_LLP = LLP - max(LLP);
expLLP = exp(nml_LLP); %subtract highst value for better numerical stability, LLP's are not always close to zero
state_prob = expLLP ./ sum(expLLP);
nml_LLP = LLP - max(LLP); %subtract highest value for better numerical stability, LLP's are not always close to zero
expLLP = exp(nml_LLP);
state_prob = expLLP ./ sum(expLLP); % sums to one (or numerically close to one)
% compute symbolposteriors from LLP in the logdomain:
amax = max(LLP,[],1);
logZ = amax + log(sum(exp(LLP - amax), 1));
logPstate = LLP - logZ; % still in logdomain
state_prob = exp(logPstate); % exact, sums to 1
% figure
% hold on;
% for i = 1:obj.M
% scatter(1:length(expLLP),expLLP(i,:),1,'.');
% end
% scatter(1:length(expLLP),max(expLLP(:,:)),3,'.');
if obj.M == 6
@@ -305,8 +330,8 @@ classdef MLSE < handle
% Number of symbols and bits per symbol
num_bits = log2(length(obj.trellis_states)); % 2 bits per symbol
bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).demap(first_sym./rms(first_sym));
% bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).demap(first_sym./rms(first_sym));
bit_mapping = PAMmapper(length(obj.trellis_states),0,"eth_style",0).showBitMapping;
% Initialize LLR storage
LLR_maxlogmap = zeros(length(data_in),num_bits);
LLR_exact = zeros(length(data_in),num_bits);
@@ -343,8 +368,8 @@ classdef MLSE < handle
llr1 = LLR_exact(idx_bit_0,k);
% Calculate mutual information for bit position k
I0 = mean(log2(1 + exp(llr0(100:end-100)))); % exp(--LLR) = exp(positive) > 1
I1 = mean(log2(1 + exp(-llr1(100:end-100)))); % exp(-+LLR) = exp(negative) < 1
I0 = mean(log2(1 + exp(llr0))); % exp(--LLR) = exp(positive) > 1
I1 = mean(log2(1 + exp(-llr1))); % exp(-+LLR) = exp(negative) < 1
MI(k) = 1 - 0.5 * (I0 + I1);
end
@@ -354,8 +379,7 @@ classdef MLSE < handle
VITERBI_ESTIMATION_SYMBOLS = VITERBI_ESTIMATION_SYMBOLS./rms(VITERBI_ESTIMATION_SYMBOLS);
debug = 0;
debug = 1;
if debug
%%% DEBUG PLOT LIKELIHOOD RATIOS %%%
figure(115);clf
@@ -380,6 +404,7 @@ classdef MLSE < handle
if debug
tx_bits = reshape(tx_bits',[],1);
disp('Start DEBUG MLSE:')
% DECIDE based on Viterbi traceback
VITERBI_ESTIMATION_SYMBOLS = VITERBI_ESTIMATION_SYMBOLS./rms(VITERBI_ESTIMATION_SYMBOLS);
rx_bits = PAMmapper(obj.M,0,"eth_style",0).demap(VITERBI_ESTIMATION_SYMBOLS');
@@ -417,13 +442,19 @@ classdef MLSE < handle
rx_bits = reshape(rx_bits',[],1);
[~,~,ber_fw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('FW BER: %.2e \n',ber_fw);
disp('Stop DEBUG MLSE:')
disp('')
end
end
end
methods (Access=private)
function s = logsumexp(a,dim)
% returns log(sum(exp(a),dim)) safely
amax = max(a,[],dim);
s = amax + log(sum(exp(a - amax), dim));
end
end
end

View File

@@ -0,0 +1,152 @@
classdef MLSE_new < handle
%MLSE: BCJRbased softoutput for PAMM sequence estimation & GMI
properties
M % PAM order (e.g. 4)
DIR % channel impulse response
trellis_states % PAM constellation levels (e.g. [-3 -1 1 3])
end
methods
function obj = MLSE_new(opts)
arguments
opts.M double = 4;
opts.DIR double = 1;
opts.trellis_states double = [-3 -1 1 3];
end
obj.M = opts.M;
obj.DIR = opts.DIR;
obj.trellis_states = opts.trellis_states;
end
function [hd_out, LLR, NGMI] = process(obj, signalclass,ref_symbolclass)
% rx, tx: column vectors of equalized and reference symbols
data_in = signalclass.signal;
shape_in = size(data_in);
data_ref = ref_symbolclass.signal;
[data_out_hd,LLR,GMI] = obj.bcjr_soft(data_in,data_ref);
try
data_out_hd = reshape(data_out_hd,shape_in(1),shape_in(2));
catch
warning('output reshaping failed after MLSE');
end
signalclass_hd = signalclass;
signalclass_hd.signal = data_out_hd;
[hd_out, LLR, NGMI] = obj.bcjr_soft(rx, tx);
end
end
methods (Access=private)
function [hd_sym, LLR, NGMI] = bcjr_soft(obj, y, x)
%--- build trellis states & branch outputs ---
DIR = flip(obj.DIR(:));
S = combvec(obj.trellis_states, obj.trellis_states)';
prev = S(:,1); cur = S(:,2);
branch_out = prev*DIR(1) + cur*DIR(2); % for 2tap
N = numel(y);
M = obj.M;
m = log2(M);
%--- forward/backward metrics (maxlog) ---
nStates = numel(obj.trellis_states);
alpha = -inf(nStates,N);
beta = -inf(nStates,N);
bm_fw = zeros(nStates,nStates,N);
% branch metrics
sigma2 = var(y - x);
inv2sigma2 = 1/(2*sigma2);
for n=1:N
bm = -((y(n)-branch_out).^2)* inv2sigma2;
bm = reshape(bm, nStates, nStates);
bm_fw(:,:,n) = bm;
end
% forward
alpha(:,1) = max(bm_fw(:,:,1),[],2);
for n=2:N
mat = alpha(:,n-1) + bm_fw(:,:,n);
alpha(:,n) = max(mat,[],2);
end
% backward
beta(:,N) = 0;
for n=N-1:-1:1
mat = beta(:,n+1).' + bm_fw(:,:,n+1);
beta(:,n) = max(mat,[],2);
end
% Precompute the zeroth forward metric
alpha0 = zeros(nStates,1);
LLP = -inf(nStates,nStates,N);
for n = 1:N
% Select the correct previous alpha column
if n == 1
a_prev = alpha0;
else
a_prev = alpha(:,n-1);
end
% Combine α, branch metric, and β
mat = a_prev + bm_fw(:,:,n) + beta(:,n).';
LLP(:,:,n) = mat;
end
%--- compute LLRs symbolwise via logsumexp over branches ---
levels = obj.trellis_states;
bit_map = PAMmapper(M,0).demap(levels');
LLR = zeros(N,m);
for n=1:N
% sum branch posteriors per symbol
llp_n = LLP(:,:,n);
logZ = logsumexp(llp_n(:));
post = exp(llp_n - logZ);
% aggregate per symbol index
Psym = zeros(M,1);
for i=1:nStates
for j=1:nStates
% branch (i->j) emits symbol 'cur(j)'
idx = find(levels==cur(j));
Psym(idx) = Psym(idx) + post(i,j);
end
end
% bitLLR from symbol posterior
for k=1:m
idx1 = bit_map(:,k)==1;
idx0 = ~idx1;
P1 = sum(Psym(idx1));
P0 = sum(Psym(idx0));
LLR(n,k) = log(P1/P0);
end
end
%--- hard decisions ---
[~,symIdx] = max(LLR,[],2);
hd_sym = levels(symIdx);
%--- GMI via Alvarado Eq.(30) ---
tx_bits = PAMmapper(M,0).demap(x);
MI = zeros(1,m);
for k=1:m
r0 = LLR(tx_bits(:,k)==0,k);
r1 = LLR(tx_bits(:,k)==1,k);
I0 = mean(log2(1+exp(-r0)));
I1 = mean(log2(1+exp(+r1)));
MI(k) = 1 - 0.5*(I0+I1);
end
GMI = sum(MI);
NGMI = GMI/m;
end
end
end
function s = logsumexp(a)
m = max(a(:));
s = m + log(sum(exp(a-m), 'all'));
end

View File

@@ -44,8 +44,6 @@ classdef DBHandler < handle
% datasource = "jdbc:mysql://134.245.243.254:3306/labor";
obj.conn = database( ...
string(obj.dataBase), ... % Database name
"silas", ... % Username
@@ -645,7 +643,7 @@ classdef DBHandler < handle
cleanedTable.(varNames{i}) = numCol;
else
% Clean double-quoted SQL literals (e.g., ""no_db"")
cleanedTable.(varNames{i}) = strrep(string(col), '""', '"');
cleanedTable.(varNames{i}) = strrep(string(col), '"', '');
end
end
end