This commit is contained in:
Silas Labor Zizou
2025-03-03 09:13:51 +01:00
295 changed files with 6849 additions and 2695 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -24,11 +24,11 @@ classdef Signal
obj.signal = signal;
obj.signal = obj.signal;
obj.fs = options.fs;
[~,obj.gitSHA] = system('git rev-parse HEAD');
[~,obj.gitStatus] = system('git status --porcelain');
% [~,obj.gitPatch] = system('git diff');
%%% Stuff for Logbook %%%
SignalType = [];
TimeStamp = [];
@@ -133,6 +133,7 @@ classdef Signal
end
%%
function plot(obj, options)
% signal to plot: obj.signal
% fsamp : obj.fs (e.g. 92e9 => 92 GHz)
@@ -144,6 +145,7 @@ classdef Signal
options.displayname = [];
options.timeframe = 0;
options.clear = 0;
options.color = [];
end
figure(options.fignum); % If figure does not exist, create new figure
@@ -169,8 +171,11 @@ classdef Signal
end
hold on;
plot(t, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
if isempty(options.color)
plot(t, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
else
plot(t, 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)
% - ylabel amplitude
@@ -254,7 +259,12 @@ classdef Signal
CallingModifier = evalin('caller','obj');
end
CallingModifierStruct = obj.objToStructFilteredRecursive(CallingModifier);
if isa(CallingModifier,"Signal")
CallingModifierStruct = obj.objToStructFilteredRecursive(CallingModifier);
ModifierCopy = {CallingModifierStruct};
end
ModifierCopy = {CallingModifier};
SignalType = [string(class(obj))];
TimeStamp = [(datetime('now','TimeZone','local','Format','HH:mm:ss'))];
@@ -263,7 +273,7 @@ classdef Signal
Nase = [0];
SignalCopy = obj.signal;
ModifierName = class(CallingModifier);
ModifierCopy = {CallingModifierStruct};
cell = {SignalType , TimeStamp , Length , SignalPower(1) , Nase, SignalCopy, ModifierName, ModifierCopy, Description};
@@ -313,17 +323,17 @@ classdef Signal
if options.fs_in == options.fs_out
desc = ['No need to resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
obj = obj.logbookentry(desc,obj);
else
obj.signal = resample(obj.signal,options.fs_out,options.fs_in,options.n,options.beta);
desc = ['resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
obj = obj.logbookentry(desc,obj);
obj.fs = options.fs_out;
end
@@ -340,59 +350,193 @@ classdef Signal
options.color = [];
options.normalizeToNyquist = 0;
options.normalizeTo0dB = 0;
options.max_num_lines = []; % Leave empty or omit to disable line rotation
options.fft_length = [];
end
% spectrum_plot(obj.signal,options.fsamp,options.figurename,options.displayname);
N = 2^(nextpow2(length(obj.signal))-10);
if options.normalizeToNyquist==0
[p_lin,w] = pwelch(obj.signal,hanning(N),N/2,N,obj.fs,"centered","power","mean");
w=w.*1e-9;
else
[p_lin,w] = pwelch(obj.signal,hanning(N),N/2,N,"centered","power","mean");
p_lin = smooth(p_lin,0.05,'rloess');
if isempty(options.fft_length)
options.fft_length = 2^(nextpow2(length(obj.signal))-7);
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","psd","mean");
w = w.*1e-9;
else
[p_lin,w] = pwelch(obj.signal,hanning(options.fft_length),options.fft_length/2,options.fft_length,"centered","psd","mean");
end
if options.normalizeTo0dB
p_lin = p_lin./ max(p_lin);
p_dbm = 10*log10(p_lin); %dB to dBm in case of "power"
p_dbm = 10*log10(p_lin); % normalized to 0 dB
ylab = "normalized to 0 dB";
else
p_dbm = 10*log10(p_lin)+30; %dB to dBm in case of "power"
ylab = "Power (dBm)";
p_dbm = 10*log10(p_lin);
ylab = "Power (dB/Hz)";
end
figure(options.fignum); % If figure does not exist, create new figure
figure(options.fignum);
ax = gca;
hold on
if isempty(options.color)
plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1);
hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1);
else
plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color);
hLine = plot(w,p_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color);
end
if options.normalizeToNyquist==0
% If user wants to limit the number of lines, check and remove old lines
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");
%xlim([-obj.fs/2 obj.fs/2].*1e-9)
edgetick = 2^(nextpow2(obj.fs*1e-9));
xticks([-edgetick:16:edgetick]);
xlim([100*round( min(w)/100,1)-10,100*round( max(w)/100,1)+10])
xticks(-edgetick:16:edgetick);
xlim([100*round(min(w)/100,1)-10, 100*round(max(w)/100,1)+10])
xlim([-128 128]);%256GSa/s
else
xlabel("Normalized Frequency");
xlim([-pi, pi]);
end
ylabel("Power/frequency (dB/Hz)");
ylabel(ylab);
ylim([min(floor( min(p_dbm))-3 , ax.YLim(1)), max(ceil( max(p_dbm) )+(3), ax.YLim(2))]);
yticks([-200:10:10]);
grid on
grid minor
legend('Interpreter','none');
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([min(floor(min(p_dbm))-3, ax.YLim(1)), max(ceil(max(p_dbm))+3, ax.YLim(2))]);
end
yticks(-200:10:10);
grid on; grid minor;
legend
% legend('Interpreter','none');
end
function move_it_spectrum(obj,options)
arguments
obj
options.fignum
options.displayname = "";
options.color = [];
options.normalizeToNyquist = 0;
options.normalizeTo0dB = 0;
end
data_in = obj.signal;
if size(data_in,1) > size(data_in,2)
data_in = data_in';
end
for pol = 1:size(data_in,1)
%compute FFT of input
Data_in = fft( data_in(pol,:) );
%psd = Data_in.*conj(Data_in);
psd = Data_in;
%Use only magnitude of FFT (which was complex)
psd = abs(psd);
%Shift the spectrum to yield
psd = fftshift(psd);
%divide by N
psd = psd/length(data_in(pol,:));
psd_plot = 20*log10(psd);
% psd_plot = psd_plot - max(psd_plot);
%smoothing
% psd_smoothed = smooth(psd,1000);
%
% psd_smoothed = 10*log10(psd_smoothed);
% psd_smoothed = psd_smoothed - max(psd_smoothed);
carrier_power_time_dbm = 20*log10( mean(abs(data_in)) .^2 )+30; % dB -> +30 -> dBm
carrier_power_freq_dbm = max(psd_plot);
% psd_plot = psd_plot - max(psd_plot);
%% cspr
c = mean(data_in).^2;
s = mean(data_in.^2);
cspr = 10*log10(c / s);
testParseval = 1;
if testParseval == 1
E_FreqDomain =1/length(psd) * sum((psd.*length(psd)).^2);
%test parseval
E_TimeDomain = sum( (data_in(pol,:).^2) );
if isequal(round(E_FreqDomain,1),round(E_TimeDomain,1))
disp('Parseval is right!');
else
% disp('Something is wrong here?!');
end
end
figure(options.fignum); % If figure does not exist, create new figure
if 1
%Frequency Axis
freq_vec = linspace(-obj.fs/2,obj.fs/2,length(psd));
freq_vec = reshape(freq_vec,size(psd_plot));
if nargin == 4
p = plot(freq_vec*1e-9,psd_plot,'Linewidth',0.5,'DisplayName',options.displayname);
% plot(freq_vec*1e-9,psd_smoothed','Linewidth',1,'Color',[0 0 0],'DisplayName',[char(varargin{2}),' smoothed']);
else
p = plot(freq_vec*1e-9,psd_plot,'Linewidth',0.5);
% plot(freq_vec*1e-9,psd_smoothed','Linewidth',1,'Color',[1 1 1],'LineStyle',':');
end
%xlim([freq_vec(1)/1e9-2 freq_vec(end)/1e9+2])
xlabel('frequency [GHz]')
else
%Wavelength Axis
freq_vec = physconst('LightSpeed')*linspace(-obj.fs/2,obj.fs/2,length(psd))./((physconst('LightSpeed')/1310e-9)^2)*1e9;
freq_vec = freq_vec+1310;
if nargin == 4
plot(freq_vec',psd_plot,'Linewidth',0.5,'DisplayName',options.displayname)
else
plot(freq_vec,psd_plot','Linewidth',0.5);
end
%xlim([freq_vec(1)/1e9-2 freq_vec(end)/1e9+2])
xlabel('wavelength [nm]')
end
hold on
end
% xlim([-150 150])
% ylim([-100,0]);
ylabel('magnitude [dBm]')
legend
grid minor;
end
@@ -500,6 +644,7 @@ classdef Signal
if options.mode == delay_mode.samples
obj.signal=delayseq(obj.signal,delay);
% obj.signal=circshift(obj.signal,delay);
elseif options.mode == delay_mode.time
@@ -534,50 +679,63 @@ classdef Signal
%estimate start pos of signal
maxpeaknum = floor(length(a)/length(b));
[pks,pkpos] = findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum);
[pks,pkpos] = findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum,'SortStr','descend');
shifts = lags(pkpos);
%Cut occurences of ref signal from signal (only positive shifts)
shifts = shifts(shifts>=0);
S = {};
for c = shifts(shifts>0)
sig = obj.delay(-c,'mode','samples');
sig.signal = sig.signal(1:length(b));
S{end+1,1} = sig;
end
%
isFlipped=0;
if all(sign(co(pkpos)))
isFlipped = 1;
if numel(shifts) > 0
%Cut occurences of ref signal from signal (only positive shifts)
for c = shifts(shifts>=0)
sig = obj.delay(-c,'mode','samples');
sig.signal = sig.signal(1:length(b));
S{end+1,1} = sig;
end
%
if all(sign(co(pkpos)))
isFlipped = 1;
end
%return/keep the sinal with the highest correlation (only within positive shifts)
[~,idx]=max(pks(shifts>=0));
obj.signal = S{idx}.signal;
%put signal with highest corr. to first index in S array
swap = S{1};
S{1} = S{idx};
S{idx} = swap;
for c = 1:numel(shifts(shifts>0))
S{c}.logbook = [];
end
else
%do nothing when shifts are negative or there are none...
end
%return/keep the sinal with the highest correlation (only within positive shifts)
[~,idx]=max(pks(shifts>0));
obj.signal = S{idx}.signal;
%put signal with highest corr. to first index in S array
swap = S{1};
S{1} = S{idx};
S{idx} = swap;
for c = 1:numel(shifts(shifts>0))
S{c}.logbook = [];
end
%plot all synced signals and the ref signal
debug = 0;
if debug
if debug
figure;hold on;
for i = 1:size(S,1)
plot(S{i}.normalize('mode','oneone').signal(1000:1100),'LineWidth',0.1,'Color',[0.2157 0.4941 0.7216]);
plot(b(1000:1100),'LineWidth',1);
end
end
end
%%
function obj = filter(obj,a,b)
lbdesc = ['Filtering signal with H = a: ',num2str(a),' / b: ',num2str(b)];
@@ -586,6 +744,7 @@ classdef Signal
obj.signal = filter(a,b,obj.signal);
end
%%
function er = extinctionratio(obj,fsym,M)
histpoints = 1024; %% verticale resolution
histpoints = floor(histpoints/2)*2+1; %% to have the eye digram centered around one point make the vertical resolution uneven
@@ -665,7 +824,7 @@ classdef Signal
end
%%
function eye(obj,fsym,M,options)
arguments
@@ -796,85 +955,85 @@ classdef Signal
try
hist_interest = plot_data(:,posxall);
hist_interest_smoth = smooth(hist_interest,20);
a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red');
hist_interest = plot_data(:,posxall);
hist_interest_smoth = smooth(hist_interest,20);
a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red');
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10);
scatter(posxall,loc,'red','Marker','x','LineWidth',2);
yline(loc,'Color','red','LineWidth',1,'LineStyle',':');
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10);
for i = 1:numel(loc)
ppeak(i) = maxA - (difference/histpoints*loc(i));
end
scatter(posxall,loc,'red','Marker','x','LineWidth',2);
oma = false;
if isa(obj,'Opticalsignal')
er=10*log10(ppeak(1)/ppeak(end));
elseif isa(obj,'Electricalsignal')
if mean([ppeak(1),ppeak(end)]) < 1e-2
oma = true;
er=max(ppeak)-min(ppeak);
yline(loc,'Color','red','LineWidth',1,'LineStyle',':');
for i = 1:numel(loc)
ppeak(i) = maxA - (difference/histpoints*loc(i));
end
oma = false;
if isa(obj,'Opticalsignal')
er=10*log10(ppeak(1)/ppeak(end));
elseif isa(obj,'Electricalsignal')
if mean([ppeak(1),ppeak(end)]) < 1e-2
oma = true;
er=max(ppeak)-min(ppeak);
else
er=10*log10(ppeak(1)/ppeak(end));
end
else
er=10*log10(ppeak(1)/ppeak(end));
end
else
er=10*log10(ppeak(1)/ppeak(end));
end
% Adjust position for the third box (slightly to the right)
boxPosition = [0.59 0.86 0.2 0.05]; % Adjusted position
% Adjust position for the third box (slightly to the right)
boxPosition = [0.59 0.86 0.2 0.05]; % Adjusted position
% Create third annotation box for Vmax
if ~oma
thirdboxstring = ['ER (db):',num2str(er),' dB'];
else
thirdboxstring = ['OMA outer:',num2str(er),' V'];
end
% Create third annotation box for Vmax
if ~oma
thirdboxstring = ['ER (db):',num2str(er),' dB'];
else
thirdboxstring = ['OMA outer:',num2str(er),' V'];
end
plot_infos = 0;
if plot_infos
plot_infos = 0;
if plot_infos
% Define properties
boxPosition = [0.15 0.86 0.2 0.05]; % Position for the first box [x y width height]
boxColor = [0.9 0.9 0.9]; % Light grey background color
boxEdgeColor = 'k'; % Black edge color
boxLineStyle = '--'; % Dashed line style
boxFontWeight = 'bold'; % Bold font
% Define properties
boxPosition = [0.15 0.86 0.2 0.05]; % Position for the first box [x y width height]
boxColor = [0.9 0.9 0.9]; % Light grey background color
boxEdgeColor = 'k'; % Black edge color
boxLineStyle = '--'; % Dashed line style
boxFontWeight = 'bold'; % Bold font
% Create first annotation box for Power
annotation('textbox', boxPosition, ...
'String', ['Power: ',num2str(pwr_dbm),' dBm'], ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
% Create first annotation box for Power
annotation('textbox', boxPosition, ...
'String', ['Power: ',num2str(pwr_dbm),' dBm'], ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
% Adjust position for the second box (slightly to the right)
boxPosition = [0.37 0.86 0.2 0.05]; % Adjusted position
% Adjust position for the second box (slightly to the right)
boxPosition = [0.37 0.86 0.2 0.05]; % Adjusted position
% Create second annotation box for PAPR
annotation('textbox', boxPosition, ...
'String', ['PAPR(lin):',num2str(papr_),''], ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
% Create second annotation box for PAPR
annotation('textbox', boxPosition, ...
'String', ['PAPR(lin):',num2str(papr_),''], ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
annotation('textbox', boxPosition, ...
'String',thirdboxstring , ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
end
annotation('textbox', boxPosition, ...
'String',thirdboxstring , ...
'BackgroundColor', boxColor, ...
'EdgeColor', boxEdgeColor, ...
'LineStyle', boxLineStyle, ...
'FontWeight', boxFontWeight, ...
'HorizontalAlignment', 'center');
end
end

View File

@@ -8,6 +8,7 @@ classdef AWG < handle
upsampling_method
repetitions %repeat the signal to generate a longer sequence?
fdac %needed
precomp_sinc_rolloff = 1;
normalize2dac %want to normalize at first? either 0 or 1
bit_resolution %bit res. of quantizer (e.g. 5 bit)
dac_min
@@ -33,7 +34,8 @@ classdef AWG < handle
arguments
options.kover = 16;
options.upsampling_method upsampling_mode
options.upsampling_method upsampling_mode = upsampling_mode.samplehold;
options.precomp_sinc_rolloff = 1;
options.repetitions = 1;
options.normalize2dac = 1;
options.fdac = 92e9;
@@ -43,7 +45,7 @@ classdef AWG < handle
options.skew_active = 0;
options.awg_skew = 0;
options.lpf_active = 0;
options.lpf_type = 0;
options.lpf_type = filtertypes.butterworth;
options.f_cutoff = 32e9;
options.H_lpf Filter
@@ -95,7 +97,7 @@ classdef AWG < handle
signalclass_in = obj.H_lpf.process(signalclass_in);
else
%4.B) just use a standard filter
lpf = Filter('filtdegree',5,"f_cutoff",obj.f_cutoff,"fsamp",obj.kover*obj.fdac,"filterType",obj.lpf_type);
lpf = Filter('filtdegree',5,"f_cutoff",obj.f_cutoff,"fs",obj.kover*obj.fdac,"filterType",obj.lpf_type);
signalclass_in = lpf.process(signalclass_in);
end
end
@@ -133,7 +135,7 @@ classdef AWG < handle
obj.signal_length = length(data_in);
%%%%%%%%% PRECOMP SINC ROLLOFF %%%%%%%%%
if 1
if obj.precomp_sinc_rolloff
% X: design FIR filter for sinc precomp
% https://www.dsprelated.com/showarticle/1191.php
ntaps = 13;

View File

@@ -202,19 +202,19 @@ classdef ChannelFreqResp < handle
function plot(obj)
figure(55);
%clf;
clf;
Havg = obj.H;
%1)
subplot(2,1,1);hold on;box on;title('Magnitude Freq. Response');
subplot(2,1,1);hold all;box on;title('Magnitude Freq. Response');
plot(obj.faxis/1e9, 20*log10(abs(obj.H_all)),'linewidth',0.1,'LineStyle','-','Color','#808080') ;
xlim([0.2 .5*max(obj.faxis)*1e-9]);
plot(obj.faxis/1e9, 20*log10(abs(Havg)),'LineWidth',2);
grid on;
%2)
subplot(2,1,2); hold on; box on; title('Phase Freq. Response');
subplot(2,1,2); hold all; box on; title('Phase Freq. Response');
plot(obj.faxis/1e9, angle(obj.H_all),'linewidth',0.1,'LineStyle','-','Color','#808080') ;
plot(obj.faxis/1e9, unwrap(angle(Havg)),'LineWidth',2) ;
xlim([0.2 .5*max(obj.faxis)*1e-9]);
@@ -228,7 +228,7 @@ classdef ChannelFreqResp < handle
Havg = Havg./mean(Havg(2:10));
%3)
subplot(2,1,1); hold on; box on; title('Inverse Magnitude Freq. Response');
subplot(2,1,1); hold all; box on; title('Inverse Magnitude Freq. Response');
plot(obj.faxis/1e9, 20*log10(abs(1./Havg)),"LineWidth",2,"Color",[0.3467 0.5360 0.6907]) ;
xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on;
ylim([-1 15]);
@@ -236,17 +236,19 @@ classdef ChannelFreqResp < handle
yline(3,'LineWidth',2,'LineStyle','--');
%4)
subplot(2,1,2); hold on; box on; title('Inverse Phase Freq. Response');
subplot(2,1,2); hold all; box on; title('Inverse Phase Freq. Response');
plot(obj.faxis/1e9, unwrap(angle(1./Havg)),"LineWidth",2,"Color",[0.3467 0.5360 0.6907]) ;
xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on;
%%% plot for publication
figure(30);hold on;box on;title('Magnitude Freq. Response');
% xlim([0 max(obj.faxis)*1e-9]);
% ylim([-20, 10]);
fax = obj.faxis - obj.f_ref/2;
Havg = Havg ./ max(abs(Havg));
plot(fax/1e9, 20*log10(abs(fftshift(Havg)))+7,'LineWidth',2);
figure(1234);hold all;box on;title('Magnitude Freq. Response');
%xlim([0.2 .5*max(obj.faxis)*1e-9]);
%ylim([-40, 2]);
Havg_smooth = smooth(Havg,50);
symaxis = (obj.faxis-(obj.f_ref/2))/1e9;
Havg = fftshift(Havg);
%Havg = smooth(Havg);
plot(symaxis, 20*log10(abs(Havg)),'LineWidth',0.5);
grid on;
@@ -362,7 +364,7 @@ classdef ChannelFreqResp < handle
end
% fprintf('Frequency response information successfully loaded from %s\n', fullFileName);
fprintf('Frequency response information successfully loaded from %s\n', fullFileName);
end
end

View File

@@ -23,7 +23,6 @@ classdef PAMmapper
obj.scaling = rms(obj.get_levels());
end
function out = map(obj,signal_in)
@@ -41,11 +40,25 @@ classdef PAMmapper
end
function signalclass_out = demap(obj,signalclass_in)
signalclass_in.signal = obj.demap_(signalclass_in.signal);
lbdesc = ['Demap PAM ',num2str(obj.M),' symbols to bit stream'];
signalclass_in = signalclass_in.logbookentry(lbdesc,obj);
signalclass_out = signalclass_in;
function signal_out = demap(obj,signal_in)
issignalclass = 0;
if isa(signal_in,'Signal')
signalclass = signal_in;
signal_in = signal_in.signal;
issignalclass = 1;
end
signal_out = obj.demap_(signal_in);
if issignalclass
lbdesc = ['Demap PAM ',num2str(obj.M),' symbols to bit stream'];
signal_in = signalclass;
signal_in = signal_in.logbookentry(lbdesc,obj);
signal_in.signal = signal_out;
signal_out = signal_in;
end
end
function pam_sig = map_(obj,bitpattern)
@@ -156,6 +169,8 @@ classdef PAMmapper
case 6 %PAM 6
thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1];
% thres = [-4 -2 0 2 4];
% thres = thres ./ sqrt(10);
case 8
% 8-ASK
@@ -194,7 +209,6 @@ classdef PAMmapper
end
end
function [data_out] = demap_(obj,data_in)
data_in= data_in';
if obj.M ~= 6
@@ -309,14 +323,35 @@ classdef PAMmapper
end
function [Signal_out] = quantize(obj,Signal_in)
constellation = obj.get_levels();
constellation = constellation ./ rms(constellation);
Signal_out = Signal_in;
dist = abs(Signal_in.signal - constellation);
[~,symbol_idx] = min(dist,[],2); % decision for closest constellation point
Signal_out.signal = constellation(symbol_idx);
Signal_out.signal = reshape(Signal_out.signal,size(Signal_in.signal));
issignalclass = 0;
if isa(Signal_in,'Signal')
issignalclass = 1;
Sig_class = Signal_in;
Signal_in = Signal_in.signal;
end
[~,high_dim_sig] = max(size(Signal_in));
[~,high_dim_const] = max(size(constellation));
if high_dim_sig == high_dim_const
Signal_in = Signal_in';
end
dist = abs(Signal_in - constellation);
[~,symbol_idx] = min(dist,[],2); % decision for closest constellation point
Signal_out = constellation(symbol_idx);
Signal_out = reshape(Signal_out,size(Signal_in));
if issignalclass
Sig_class.signal = Signal_out;
Signal_out = Sig_class;
end
end

View File

@@ -151,6 +151,12 @@ classdef PAMsource
sym_max = max(symbols.signal);
end
% symbols.move_it_spectrum("fignum",222,"displayname","Symbols only");
% symbols.spectrum("fignum",222,"displayname","Symbols only","normalizeTo0dB",1);
%%%%% Pulse-forming %%%%%%
if obj.applypulseform
digi_sig = obj.pulseformer.process(symbols);

View File

@@ -4,6 +4,8 @@ classdef Pulseformer
properties(Access=public)
fdac
end
properties(Access=private)
fsym
pulse
pulselength

View File

@@ -1,10 +1,9 @@
classdef Duobinary
%Duobinary Coding
% should work
% should work
properties(Access=public)
end
methods (Access=public)
@@ -12,6 +11,7 @@ classdef Duobinary
function obj = Duobinary()
%NAME Construct an instance of this class
end
function signal = precode(~,signal)
@@ -52,7 +52,7 @@ classdef Duobinary
% end
for k = 2:numel(data)
bk(k) =mod(data(k)-bk(k-1),M);
bk(k) = mod(data(k)-bk(k-1),M);
end
%make bipolar
@@ -78,7 +78,13 @@ classdef Duobinary
end
function signal = encode(~,signal)
function signal = encode(~,signal,options)
arguments
~
signal
options.M = [];
end
if isa(signal,'Signal')
data = signal.signal;
@@ -88,17 +94,19 @@ classdef Duobinary
data = data./rms(data);
u = unique(data);
M = numel(u);
if isempty(options.M)
u = unique(data);
options.M = numel(u);
end
%make unipolar
if M == 4
if options.M == 4
data = data .* sqrt(5);
elseif M == 6
elseif options.M == 6
data = data .* sqrt(10);
elseif M == 8
elseif options.M == 8
data = data .* sqrt(21);
elseif M == 16
elseif options.M == 16
data = data .* sqrt(85);
warning('Check if PAM16 implementation, mapping and scaling is correct!')
end
@@ -108,7 +116,7 @@ classdef Duobinary
data = data - b;
data = data ./ 2;
% assert(isequal((0:M-1)',unique(data)),'Check Duobinary Precoding'); %seems the signal is not unipolar
% assert(isequal((0:options.M-1)',unique(data)),'Check Duobinary Precoding'); %seems the signal is not unipolar
% duobinary coding (1+D)
% coeff = [1,1];
@@ -132,12 +140,14 @@ classdef Duobinary
mean_power = sum((unique_points .^ 2) .* probabilities);
scaling_factor = sqrt(mean_power);
if M == 4
if options.M == 4
data = data ./ sqrt(2.5); % 7-level constellation weighted with probability after DB code i.e. mean([-3 3 -2 -2 2 2 -1 -1 -1 1 1 1 0 0 0 0].^2) = 2.5 --> sqrt(2.5) == rms(constellation)
elseif M == 6
elseif options.M == 6
data = data ./ sqrt(5.8);
elseif M == 8
elseif options.M == 8
data = data ./ sqrt(10.5); % 15-level constellation weighted with probability after DB code i.e.
else
error("Error in: Duobinary encode > scale unipolar to bipolar > The data is not PAM4, PAM6 or PAM 8? ")
end
if isa(signal,'Signal')
@@ -158,24 +168,30 @@ classdef Duobinary
end
function signalclass = decode(~,signalclass)
function signalclass = decode(~,signalclass,options)
arguments
~
signalclass
options.M = [];
end
data = signalclass.signal;
u = unique(data);
I = numel(u); %number of duobinary coded const. points
M = (I+1)/2; %PAM-M order
if isempty(options.M)
M = (I+1)/2; %PAM-M order
else
M = options.M; %PAM-M order
end
%make unipolar
if I == 7
if I == 7 || I == 6
data = data .* sqrt(2.5);
elseif I == 11
%todo
data = data .* sqrt(5.8);
warning('Check db decode implementation, mapping and scaling is correct!')
elseif I == 15
data = data .* sqrt(10.5);
elseif I == 16
warning('Check db decode implementation, mapping and scaling is correct!')
end
data = round(data);
@@ -196,6 +212,8 @@ classdef Duobinary
data = data ./ sqrt(10);
elseif M == 8
data = data ./ sqrt(21);
else
errormsg("Error in: Duobinary decode > scale unipolar to bipolar > The data is not PAM4, PAM6 or PAM 8? ")
end
signalclass.signal = data;

View File

@@ -1,4 +1,4 @@
classdef EQ %< handle
classdef EQ < handle
%EQ Summary of this class goes here
% Detailed explanation goes here
@@ -289,9 +289,9 @@ classdef EQ %< handle
e_dc = e_dc - obj.DCmu*error;
obj.error_log.e_ffe(cnt,trainloops) = e_ffe;
obj.error_log.e_dfe(cnt,trainloops) = e_dfe;
obj.error_log.e_(cnt,trainloops) = error;
% obj.error_log.e_ffe(cnt,trainloops) = e_ffe;
% obj.error_log.e_dfe(cnt,trainloops) = e_dfe;
% obj.error_log.e_(cnt,trainloops) = error;
cnt = cnt+1;
if obj.Nb(1) > 0
@@ -460,7 +460,7 @@ classdef EQ %< handle
if 1%mu_mat ~= 0
e_dc = e_dc - obj.DCmu*error;
error_log(cnt,dd_loop) = e_dc;
% error_log(cnt,dd_loop) = e_dc;
cnt = cnt+1;
end

View File

@@ -0,0 +1,98 @@
classdef Postfilter < handle
%NAME Summary of this class goes here
% Detailed explanation goes here
properties(Access=public)
ncoeff = 2;
coefficients = [];
useBurg = NaN
end
methods (Access=public)
function obj = Postfilter(options)
%NAME Construct an instance of this class
% Detailed explanation goes here
arguments
options.useBurg = NaN
options.coefficients = []
options.ncoeff double = 2
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,noiseclass_in,options)
arguments
obj
signalclass_in
noiseclass_in
options.useBurg = obj.useBurg;
options.coefficients = obj.coefficients;
end
if ~isnan(options.useBurg) && options.useBurg
disp('using burg alg')
obj.coefficients = arburg(noiseclass_in.signal,obj.ncoeff);
elseif ~isempty(options.coefficients)
disp('using given taps')
obj.coefficients = options.coefficients;
obj.useBurg = 0;
else
end
signalclass_in = signalclass_in.filter(obj.coefficients,1);
% append to logbook
lbdesc = ['Postfilter'];
signalclass_in = signalclass_in.logbookentry(lbdesc);
% write to output
signalclass_out = signalclass_in;
end
function showFilter(obj,noiseclass_in,options)
arguments
obj
noiseclass_in
options.fignum = 121
options.color = []
end
% noiseclass_in.spectrum('displayname','Noise PSD shifted to 0dBm','fignum',options.fignum,'normalizeTo0dB',1);
figure(options.fignum)
[h,w] = freqz(1,obj.coefficients,length(noiseclass_in),"whole",noiseclass_in.fs);
h = h/max(abs(h));
hold on
w_ = (w - noiseclass_in.fs/2);
if isempty(options.color)
plot(w_.*1e-9,20*log10(fftshift(abs(h))),'DisplayName',['Burg Coeffs: ', num2str(round(obj.coefficients,2)), ' '],'LineWidth',2);
else
plot(w_.*1e-9,20*log10(fftshift(abs(h))),'DisplayName',['Burg Coeffs: ', num2str(round(obj.coefficients,2)), ' '],'Color',options.color,'LineWidth',2);
end
ylim([-30,2]);
end
end
end

View File

@@ -57,7 +57,7 @@ classdef VNLE < handle
end
function [X] = process(obj, X, D)
function [X,N] = process(obj, X, D)
% actual processing of the signal (steps 1. - 3.)
% 1 normalize RMS
@@ -91,6 +91,9 @@ classdef VNLE < handle
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc); % append to logbook
N = X;
N = X - D;
end
@@ -156,7 +159,6 @@ classdef VNLE < handle
end
if ~all(mu==0,'all') %mu has not only zeros
% obj.e = obj.e - (mu * err * x_in) ; % Weight update rule of LMS
obj.e = obj.e - ( (mu * x_in) * err ) ; % Weight update rule of LMS
else
normalizationfactor = (x_in.' * x_in);

View File

@@ -1,4 +1,4 @@
classdef MLSE
classdef MLSE < handle
%MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length
properties(Access=public)
@@ -34,17 +34,22 @@ classdef MLSE
end
function signalclass = process(obj,signalclass)
function [signalclass_hd,signalclass_sd] = process(obj,signalclass,ref_symbolclass)
data_in = signalclass.signal;
data_ref = ref_symbolclass.signal;
data_out = obj.process_(data_in);
[data_out_hd,data_out_sd] = obj.process_(data_in,data_ref);
signalclass.signal = data_out;
signalclass_hd = signalclass;
signalclass_hd.signal = data_out_hd;
signalclass_sd = signalclass;
signalclass_sd.signal = data_out_sd;
end
function data_out = process_(obj,data_in)
function [VITERBI_ESTIMATION_SYMBOLS,soft_decisions] = process_(obj,data_in,data_ref)
% remove unnecessary zeros at start of impulse response to keep
@@ -58,6 +63,17 @@ classdef MLSE
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;
tx_bits = PAMmapper(numel(constellation),0).demap(data_ref);
% impulse respnse i.e. [0.5, 1.0000]
obj.DIR = flip(obj.DIR);
% RMS normalization of input data
@@ -77,6 +93,7 @@ classdef MLSE
% Calculate all possible input symbols for the desired impulse
% response. Row number is the index of the previous state,
% column number is the index of the next state
% noise free received == branch metrics
noise_free_received = zeros(length(states),length(states));
count_row = 1;
count_col = 1;
@@ -101,127 +118,302 @@ classdef MLSE
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% FORWARD PASS %%%%%
%
% %% Optimized
% % Preallocate and initialize variables
% data_out = NaN(size(data_in)); % output vector
% sum_path_metrics_res = zeros(length(states), length(data_in));
% path_idx = zeros(length(states), length(data_in));
%
% % Precompute repmat size
% num_states = length(states);
% num_signals = numel(noise_free_received);
%
% % First trellis path (initialize)
% sum_path_metrics = zeros(num_states, num_states);
% path_metrics = (abs(data_in(1) - noise_free_received)).^2; % Use broadcasting instead of repmat
% sum_path_metrics = sum_path_metrics + path_metrics; % Compute initial path metrics
%
% [sum_path_metrics_res(:,1), path_idx(:,1)] = min(sum_path_metrics, [], 2); % Best path for first step
%
% % Preallocate path_metrics and sum_path_metrics to avoid reallocating in each loop
% path_metrics = zeros(num_states, num_signals);
%
% % Loop over remaining trellis paths
% for n = 2:length(data_in)
% % Avoid reallocation of sum_path_metrics, reuse the same matrix and update
% previous_sum_path_metrics = sum_path_metrics_res(:,n-1).'; % Transpose once for broadcasting
% sum_path_metrics = repmat(previous_sum_path_metrics, num_states, 1); % Avoid dynamic resizing
%
% % Calculate path metrics using broadcasting
% path_metrics = (abs(data_in(n) - noise_free_received)).^2; % Avoid repmat
%
% % Update sum path metrics
% sum_path_metrics = sum_path_metrics + path_metrics;
%
% % Find the best path for each state and store results
% [sum_path_metrics_res(:,n), path_idx(:,n)] = min(sum_path_metrics, [], 2);
% end
%
% %% Traceback
% ideal_path = NaN(1, length(data_in)+1); % Preallocate ideal path
% [~, ideal_path(length(data_in)+1)] = min(sum_path_metrics_res(:,length(data_in))); % Start from final state
%
% % Trace back through trellis
% for h = length(data_in):-1:1
% ideal_path(h) = path_idx(ideal_path(h+1),h); % Follow the best path back
% end
%
% % Extract the output indices
% idx_out = ideal_path(2:end);
% Initialize the output vector
pm = zeros(length(states),length(states));
bm_fw = zeros(length(states),length(states),length(data_in));
% Forward Recursion (FSM Computation)
for n = 1:length(data_in)
bm = abs(data_in(n) - noise_free_received).^2;
pm = pm + bm;
[pm_survivor_fw(:,n),pm_survivor_fw_idx(:,n)] = min(pm,[],2); % choose lowest path metric as new state
pm = repmat(pm_survivor_fw(:,n).',length(states),1); % update pm (chosen state to 2nd dimension -> FROM state)
bm_fw(:,:,n) = bm;
% OLD
% initilaize the output vector
data_out = NaN(size(data_in));
sum_path_metrics = zeros(length(states),length(states));
% first trellis path
% euclidian distance as path metric
path_metrics = (abs(repmat(data_in(1),size(noise_free_received)) - noise_free_received)).^2;
sum_path_metrics = sum_path_metrics + path_metrics; % calculation of all possible sum path metrics
[sum_path_metrics_res(:,1),path_idx(:,1)] = min(sum_path_metrics,[],2); % find the best path to each state, store sum path metric and predecessor for each state
% remaining trellis paths
for n = 2:length(data_in)
sum_path_metrics = repmat(sum_path_metrics_res(:,n-1).',length(states),1);
% path_metrics = (repmat(data_in(n),size(noise_free_received)) - noise_free_received).^2;%.*prob_mat;
path_metrics = (abs(repmat(data_in(n),size(noise_free_received)) - noise_free_received)).^2;
sum_path_metrics = sum_path_metrics + path_metrics;
[sum_path_metrics_res(:,n),path_idx(:,n)] = min(sum_path_metrics,[],2);
end
%% trace back
ideal_path = NaN(1,length(data_in)+1);
% find ideal trellis path by going through the trellis
% backwards
[~,ideal_path(length(data_in)+1)] = min(sum_path_metrics_res(:,length(data_in)));
% we can now get the best path as min
best_fw_path = NaN(1,length(data_in)+1);
% find ideal trellis path by going through the trellis backwards
[~,best_fw_path(length(data_in)+1)] = min(pm_survivor_fw(:,length(data_in)));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% BACKWARD PASS %%%%%
% Initialize the output vector
pm = zeros(length(states),length(states));
pm_survivor_bw = zeros(length(states),length(data_in)+1);
pm_survivor_bw_idx = zeros(length(states),length(data_in)+1);
bm_bw = zeros(length(states),length(states),length(data_in)+1);
% starting with the state that has the lowest sum path
% metric, follow the stored information about the
% predecessor
for h = length(data_in):-1:1
ideal_path(h) = path_idx(ideal_path(h+1),h);
best_fw_path(h) = pm_survivor_fw_idx(best_fw_path(h+1),h);
bm = abs(data_in(h) - noise_free_received).^2;
pm = pm + bm.';
[pm_survivor_bw(:,h),pm_survivor_bw_idx(:,h)] = min(pm,[],2); % choose lowest path metric as new state
pm = repmat(pm_survivor_bw(:,h).',length(states),1); % update pm (chosen state to 2nd dimension -> FROM state)
bm_bw(:,:,h) = bm;
end
idx_out = ideal_path(2:length(data_in)+1);
VITERBI_ESTIMATION_IDX(1:length(data_in)) = first_sym(best_fw_path(2:end));
VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = constellation(best_fw_path(2:end));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% FORWARD PASS %%%%%
%calc the log probabilities (llp's)
for k = 2:length(data_in)
fsm = repmat(pm_survivor_fw(:,k-1)',[length(states),1]); %pm_survivor_fw size: length(states)xlength(sequence)
bm = bm_fw(:,:,k); %bm_fw size: length(states)xlength(states)xlength(sequence)
bsm = repmat(pm_survivor_bw(:,k)',[length(states),1]); %pm_survivor_bw size: length(states)xlength(sequence)+1
llp(:,k) = min(fsm+bm+bsm,[],2);
end
% directly decide based on lowest LLP index
[~,llp_based_state_seq]=min(llp);
LLP_EST(1:length(data_in)) = constellation(llp_based_state_seq);
rx_bits = PAMmapper(numel(constellation),0).demap(LLP_EST');
[~,~,ber_llp,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('LLP BER : %.2e \n',ber_llp);
% [~,~,ber_llp,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('LLP BER +1: %.2e \n',ber_llp);
% [~,~,ber_llp,~] = calc_ber(circshift(rx_bits,-1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('LLP BER -1: %.2e \n',ber_llp);
%%%% DECIDE based on Viterbi traceback
rx_bits = PAMmapper(numel(constellation),0).demap(VITERBI_ESTIMATION_SYMBOLS');
[~,~,ber_viterbi,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('Viterbi BER: %.2e \n',ber_viterbi);
% [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('Viterbi BER: %.2e \n',ber_viterbi);
% directly decide based on the FW path metrics
[~,fw_direct_state_seq]=min(pm_survivor_fw);
FW_EST(1:length(data_in)) = constellation(fw_direct_state_seq);
rx_bits = PAMmapper(numel(constellation),0).demap(FW_EST');
[~,~,ber_fw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('FW BER: %.2e \n',ber_fw);
% directly decide based on the BW path metrics
[~,bw_direct_state_seq]=min(pm_survivor_bw);
BW_EST(1:length(data_in)) = constellation(bw_direct_state_seq(2:end));
rx_bits = PAMmapper(numel(constellation),0).demap(BW_EST');
[~,~,ber_bw,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('BW BER: %.2e \n',ber_bw);
% [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('BW BER: %.2e \n',ber_viterbi);
% [~,~,ber_viterbi,~] = calc_ber(circshift(rx_bits,-1),tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('BW BER: %.2e \n',ber_viterbi);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
tx_symbolpos = zeros(numel(constellation),length(data_ref));
est_symbolpos = zeros(numel(constellation),length(data_ref));
for lvl = 1:numel(constellation)
tx_symbolpos(lvl,data_ref==constellation(lvl)) = 1;
est_symbolpos(lvl,VITERBI_ESTIMATION_IDX==first_sym(lvl)) = 1;
est_symbolpos_llm(lvl,llp_based_state_seq==lvl) = 1;
end
est_symbolpos= logical(est_symbolpos);
tx_symbolpos = logical(tx_symbolpos);
est_symbolpos_llm = logical(est_symbolpos_llm);
figure(299);clf;hold on;
llp_filt = NaN(length(states),length(data_in));
llp_ = llp - min(llp,[],1);
for c = 2%:numel(constellation)
for c2 = 1:3%numel(constellation)
if obj.duobinary_output
%use duobinary encoder, output is already scaled inside this
%one
data_out = Duobinary().encode(first_sym(idx_out));
idx = est_symbolpos_llm(c,:);
llp_filt(c2,idx) = (llp(c2,idx));
plotidx = 2:200000;
scatter(plotidx,llp_filt(c2,plotidx),1,'o','LineWidth',1,'DisplayName',sprintf('LLP of Symbol %d | %d was transmitted',c2,c));
else
%
data_out(1:length(data_in)) = first_sym(idx_out);
% scale to rms = 1 using the standard sqrt() expressions
if obj.M == 4
data_out = data_out./sqrt(5);
elseif obj.M == 6
data_out = data_out./sqrt(10);
elseif obj.M == 8
data_out = data_out./sqrt(21);
end
end
% Assume llp is a 4 x N matrix (4 PAM-4 symbols, N time steps)
[numSymbols, numTimeSteps] = size(llp);
P_symbols = zeros(numSymbols, numTimeSteps); % To hold the soft output probabilities
for k = 1:numTimeSteps
% Compute the exponentials. (Use -llp_shifted if llp's are costs.)
exponents = -llp_(:, k);
% Normalize to form a probability vector.
P_symbols(:, k) = exponents / sum(exponents);
end
% Optionally, if you prefer a single soft output value per symbol (an expectation),
% define your PAM-4 constellation levels, e.g.:
soft_output = sum(P_symbols .* constellation, 1); % 1 x N vector of soft outputs
%%%% BITWISE LLR's ??? %%%%%
figure(100);
clf
hold on
title('LLR between inner and outer bits')
bitmapping = PAMmapper(numel(constellation),0).demap(constellation);
for bp = 1:size(bitmapping,2)
pos_bitone = find(bitmapping(:,bp)==1);
pos_bitzero = find(bitmapping(:,bp)~=1);
llr_bits(bp,:) = min(llp(pos_bitone,:),[],1) - min(llp(pos_bitzero,:),[],1);
subplot(size(bitmapping,2),1,bp)
scatter(1:length(data_ref),llr_bits(bp,:),1,'.');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% From: Log-Likelihood Probabilities LLP --> To: Log-Likelihood Ratios LLR
for s = 1:length(states)-1
llr(s,:) = llp_(s,:) - llp_(s+1,:); % subtract llp's of successive const. points to get llr's
end
% Build Sets of LLR's that contain only those values at
% timepoint k, where the symbols:
% a) were actually transmitted: set "S"
% b) were decoded by viterbi: set "SC"
S = NaN(length(states)-1,length(data_in));
SC = NaN(length(states)-1,length(data_in));
llp_inf = llp_;
llp_inf(llp_==0) = Inf;
[~,scnd_idx] = min(llp_inf,[],1) ;
for s = 1:length(states)-1
%%% SET "S"
% find all time indices k, where const point s was actually transmitted
indice = tx_symbolpos(s,:)==1;
S(s,indice) = llr(s,indice);
%calc mean of all llr's where symbol s was transmitted
K(s,1) = mean(S(s,indice),'omitnan');
% find all time indices k, where const. point s+1 was actually transmitted
indice_plusone = tx_symbolpos(s+1,:)==1;
S(s,indice_plusone) = llr(s,indice_plusone);
K(s,2) = mean(S(s,indice_plusone),'omitnan');
%%% SET "SC"
% find all time indices k, where symbol s was decoded
% idx: find positions in time where a symbol s was decoded
idx = est_symbolpos_llm(s,:);
% idx 2: find positions where the strongest competitor is
% s+1, i.e. the second best llp is at s+1
idx2 = scnd_idx == s+1;
SC(s,idx&idx2) = llr(s,idx&idx2);
KC(s,1) = mean(SC(s,idx&idx2),'omitnan');
% find all time indices k, where symbol s+1 was decoded
% idx: find positions in time where a symbol s+1 was decoded
idx = est_symbolpos_llm(s+1,:);
idx2 = scnd_idx == s;
% idx 2: find positions where the strongest competitor is
% s, i.e. the second best llp is at s
SC(s,idx&idx2) = llr(s,idx&idx2);
KC(s,2) = mean(SC(s,idx&idx2),'omitnan');
% scale the sets, using the average (?) of the
llrcn(s,:) = SC(s,:) * ( constellation(s+1)-constellation(s) ) ./ ( K(s,2)-K(s,1)) + decisionLevels(s);
end
figure(111)
title("Bla")
clf
for s = 1:length(states)-1
figure(1111)
hold on
title(sprintf('llrcn = llp %d - llp %d',s, s+1,s, s+1));
scatter(1:length(llrcn),llrcn(s,:),1,'.');
% STUFENARTIGE LLR'S UND LLP'S %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
figure(111)
subplot(1,length(states),s)
hold on
title(sprintf('llr = llp %d - llp %d \n SC=llr(tx sym = %d or %d)',s, s+1,s, s+1));
scatter(1:length(llr),llr(s,:),1,'.');
scatter(1:length(SC),SC(s,:),1,'.');
yline([K(s,1),K(s,2)]);
low = min(min(llr));
hi = max(max(llr));
ylim([low,hi]);
subplot(1,length(states),length(states))
hold on
scatter(1:length(llr),llr(s,:),1,'.');
ylim([low,hi]);
% HISTOGRAMME %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
figure(112)
subplot(length(states),1,s)
hold on
title(sprintf('histogram of llr; between const point %d - %d',s, s+1));
histogram(llrcn(s,:),10000,'EdgeAlpha',0)
% histogram(SC(s,:),10000,'EdgeAlpha',0)
xlim([-20, 20]);
subplot(length(states),1,length(states))
hold on
histogram(llrcn(s,:),10000,'EdgeAlpha',0)
% histogram(SC(s,:),10000,'EdgeAlpha',0)
% xlim([-20, 20]);
end
softdecisions = mean(llrcn,1,'omitnan');
distance = abs(VITERBI_ESTIMATION_SYMBOLS-llrcn);
[win_cost,win_idx] = min(distance,[],1);
for i = 1:length(data_in)
soft_decisions(i) = llrcn(win_idx(i),i);
end
soft_decisions = max(llrcn,[],1);
showLevelHistogram(soft_decisions,data_ref)
figure()
% scatter(1:length(data_in),VITERBI_ESTIMATION_SYMBOLS,1,'.');
scatter(1:length(data_in),soft_decisions,1,'.');
MLM_ESTIMATION(1:length(data_in)) = PAMmapper(numel(constellation),0).quantize(soft_decisions)';
rx_bits = PAMmapper(numel(constellation),0).demap(MLM_ESTIMATION');
[~,~,ber_mlm,~] = calc_ber(rx_bits,tx_bits,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('MLM BER: %.2e \n',ber_mlm);
end
end
methods (Access=private)
% Cant be seen from outside! So put all your functions here that can/
% shall not be called from outside
end
end

View File

@@ -0,0 +1,177 @@
classdef MLSE_viterbi < handle
%MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length
properties(Access=public)
M %PAM-M
DIR
trellis_states
duobinary_output
end
methods (Access=public)
function obj = MLSE_viterbi(options)
%NAME Construct an instance of this class
% Detailed explanation goes here
arguments
options.M double = 4;
options.DIR double = [1];
options.trellis_states double = [-3 -1 1 3];
options.duobinary_output logical = false;
end
%
fn = fieldnames(options);
for n = 1:numel(fn)
try
obj.(fn{n}) = options.(fn{n});
end
end
% do more stuff
end
function signalclass = process(obj,signalclass)
data_in = signalclass.signal;
data_out = obj.process_(data_in);
signalclass.signal = data_out;
end
function data_out = process_(obj,data_in)
% remove unnecessary zeros at start of impulse response to keep
% number of trellis states minimal
DIR_nonzero = find(obj.DIR ~= 0);
if DIR_nonzero(1) > 1
obj.DIR(1:DIR_nonzero(1)-1) = [];
end
if isscalar(obj.DIR)
obj.DIR = [0 obj.DIR];
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%% WORKING
% impulse respnse i.e. [0.5, 1.0000]
obj.DIR = flip(obj.DIR);
% RMS normalization of input data
data_in = data_in ./ rms(data_in);
% seems to be the only way to use combvec for a flexible amount
% of vectors. 'combs' contains all trellis states
pre_comb_mat = repmat(obj.trellis_states,length(obj.DIR)-1,1);
pre_comb_cell = mat2cell(pre_comb_mat,ones(1,size(pre_comb_mat,1)),size(pre_comb_mat,2));
combs = fliplr(combvec(pre_comb_cell{:}).');
% Save first and last symbol of each state
first_sym = combs(:,1);
last_sym = combs(:,end);
states = sum(combs,2);
% Calculate all possible input symbols for the desired impulse
% response. Row number is the index of the previous state,
% column number is the index of the next state
% noise free received == branch metrics
noise_free_received = zeros(length(states),length(states));
count_row = 1;
count_col = 1;
for l1 = 1:length(states)
for l2 = 1:length(states)
if sum(combs(l2,2:end) == combs(l1,1:end-1)) == size(combs,2)-1
noise_free_received(count_row,count_col) = sum(combs(l2,:).*obj.DIR(end:-1:2)) + last_sym(l1)*obj.DIR(1);
else
noise_free_received(count_row,count_col) = inf;
end
count_row = count_row + 1;
end
count_col = count_col + 1;
count_row = 1;
end
% match amplitude levels of input signal to those of the calculated ideal symbols
% i.e. match the rms values of data_in to noise_free_received
if isreal(data_in)
if obj.M == round(obj.M)
data_in = data_in * rms(noise_free_received(noise_free_received ~= inf),'all','omitnan');
end
end
% OLD
% initilaize the output vector
data_out = NaN(size(data_in));
sum_path_metrics = zeros(length(states),length(states));
% first trellis path
% euclidian distance as path metric
path_metrics = (abs(repmat(data_in(1),size(noise_free_received)) - noise_free_received)).^2;
sum_path_metrics = sum_path_metrics + path_metrics; % calculation of all possible sum path metrics
[sum_path_metrics_res(:,1),path_idx(:,1)] = min(sum_path_metrics,[],2); % find the best path to each state, store sum path metric and predecessor for each state
% remaining trellis paths
for n = 2:length(data_in)
sum_path_metrics = repmat(sum_path_metrics_res(:,n-1).',length(states),1);
% path_metrics = (repmat(data_in(n),size(noise_free_received)) - noise_free_received).^2;%.*prob_mat;
path_metrics = (abs(repmat(data_in(n),size(noise_free_received)) - noise_free_received)).^2;
sum_path_metrics = sum_path_metrics + path_metrics;
[sum_path_metrics_res(:,n),path_idx(:,n)] = min(sum_path_metrics,[],2);
end
%% trace back
ideal_path = NaN(1,length(data_in)+1);
% find ideal trellis path by going through the trellis
% backwards
[~,ideal_path(length(data_in)+1)] = min(sum_path_metrics_res(:,length(data_in)));
% starting with the state that has the lowest sum path
% metric, follow the stored information about the
% predecessor
for h = length(data_in):-1:1
ideal_path(h) = path_idx(ideal_path(h+1),h);
end
idx_out = ideal_path(2:length(data_in)+1);
%%%%%%%%%%%%%%%%%%%%%%%%%%%% WORKING
if obj.duobinary_output
%use duobinary encoder, output is already scaled inside this
%one
data_out = Duobinary().encode(first_sym(idx_out));
else
%
data_out(1:length(data_in)) = first_sym(idx_out);
% scale to rms = 1 using the standard sqrt() expressions
if obj.M == 4
data_out = data_out./sqrt(5);
elseif obj.M == 6
data_out = data_out./sqrt(10);
elseif obj.M == 8
data_out = data_out./sqrt(21);
end
end
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

View File

@@ -0,0 +1,214 @@
classdef TransmissionPerformance
% TransmissionPerformance
%
% This class calculates the best possible net data rate (in bits/s)
% from a given gross rate (bits/s) and a measured channel quality
% parameter (either bit error ratio (BER) or NGMI). The class uses
% lookup tables for three different FEC modes:
%
% 1. SD+HD concatenated (uses overall code rate and an NGMI threshold)
% 2. SD+HD concatenated with punctured LDPC (uses overall code rate and an NGMI threshold)
% 3. HD-only (uses a code rate and a BER threshold)
%
% The basic algorithm is as follows:
% 1. Given a measured quality (NGMI or BER) for each measurement, find in
% the table the first (i.e. best) entry that is satisfied by the
% measured value. For NGMI the condition is measured NGMI >= threshold,
% while for BER the condition is measured BER <= threshold.
% 2. Use the corresponding overall code rate to compute:
%
% net rate = gross rate * code rate.
%
% USAGE EXAMPLE (array inputs):
%
% tp = TransmissionPerformance;
% % Suppose we have 3 measurements:
% % Gross rates: 10, 15, and 20 Gbps
% % Measured NGMI: 0.92, 0.88, and 0.85
% % Measured BER: 1e-4, 7e-3, and 2e-2
% netrates = tp.calculatenetrate([10e9 15e9 20e9], ...
% 'NGMI', [0.92 0.88 0.85], ...
% 'BER', [1e-4 7e-3 2e-2]);
%
% The returned structure netrates will contain the net rates (and the
% used code rates and thresholds) for each mode in vector form.
properties (Constant)
%% Lookup Table for SD+HD concatenated (using NGMI)
% Overall code rate and NGMI threshold for each row.
OVERALLCODERATES_SDHD = [0.7519, 0.7602, 0.7684, 0.7766, 0.7850, ...
0.7932, 0.8014, 0.8098, 0.8180, 0.8262, ...
0.8345, 0.8428, 0.8510, 0.8593, 0.8676, ...
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,...
0.9150, 0.9210, 0.9270, 0.9330, 0.9390, ...
0.9450, 0.9510, 0.9570, 0.9630, 0.9690, ...
0.9750, 0.9810, 0.9870, 0.9930, 0.9990];
ISPUNCT_LDPI = [zeros(1,15),ones(1,15)];
%% Lookup Table for HD-only mode (using BER)
% For HD-only, the table gives the code rate and the maximum acceptable BER.
CODE_RATES_HD = [0.7500, 0.8000, 0.8123, 0.8333, 0.8571, ...
0.8750, 0.8889, 0.9000, 0.9091, 0.9167, 0.9412];
BERTHRESHOLDS_HD = [2.12e-2, 1.76e-2, 1.62e-2, 1.44e-2, 1.25e-2, ...
1.03e-2, 9.29e-3, 8.33e-3, 7.54e-3, 7.04e-3, 4.70e-3];
%% 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];
BERTHRESHOLDS_KP4_AND_INNER = 4.85e-3;
end
methods
function netrates = calculateNetRate(obj, grossRate, varargin)
% calculatenetrate Calculate the net data rate(s) from measured data.
%
% netrates = tp.calculatenetrate(grossRate, 'BER', berValue, 'NGMI', ngmiValue)
%
% INPUTS:
% grossRate - (scalar or vector) gross data rate(s) [bits/s]
%
% Optional name/value pairs:
% 'BER' - (scalar or vector) measured bit error ratio(s)
% 'NGMI' - (scalar or vector) measured NGMI value(s)
%
% At least one of 'BER' or 'NGMI' must be provided.
%
% OUTPUT:
% netrates - a structure with the following fields (if available):
% .SDHD - for SD+HD concatenated (using NGMI)
% .SDHD_LDPC - for SD+HD with punctured LDPC (using NGMI)
% .HD - for HD-only (using BER)
%
% Each sub-structure contains fields:
% .CodeRate - the chosen overall code rate from the table (vector)
% .Threshold - the threshold value used from the table (vector)
% .<BER/NGMI> - net rate computed as grossRate * CodeRate (vector)
% Parse inputs.
p = inputParser;
addRequired(p, 'grossRate', @(x) isnumeric(x));
addParameter(p, 'BER', [], @(x) isnumeric(x));
addParameter(p, 'NGMI', [], @(x) isnumeric(x));
parse(p, grossRate, varargin{:});
ber = p.Results.BER;
ngmi = p.Results.NGMI;
if isempty(ber) && isempty(ngmi)
error('At least one of ''BER'' or ''NGMI'' must be provided.');
end
% Determine the number of measurements.
numMeasurements = max([numel(grossRate), numel(ngmi), numel(ber)]);
% Broadcast scalars if needed.
if isscalar(grossRate) && numMeasurements > 1
grossRate = repmat(grossRate, 1, numMeasurements);
elseif numel(grossRate) ~= numMeasurements
error('grossRate must be scalar or have %d elements.', numMeasurements);
end
if ~isempty(ngmi)
if isscalar(ngmi) && numMeasurements > 1
ngmi = repmat(ngmi, 1, numMeasurements);
elseif numel(ngmi) ~= numMeasurements
error('NGMI must be scalar or have %d elements.', numMeasurements);
end
end
if ~isempty(ber)
if isscalar(ber) && numMeasurements > 1
ber = repmat(ber, 1, numMeasurements);
elseif numel(ber) ~= numMeasurements
error('BER must be scalar or have %d elements.', numMeasurements);
end
end
% Initialize the output structure.
netrates = struct;
if ~isempty(ngmi)
netrates.SDHD.GrossRate = NaN(1, numMeasurements);
netrates.SDHD.NetRate = NaN(1, numMeasurements);
netrates.SDHD.punctLDPI = NaN(1, numMeasurements);
netrates.SDHD.CodeRate = NaN(1, numMeasurements);
netrates.SDHD.Threshold = NaN(1, numMeasurements);
end
if ~isempty(ber)
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_hamming.GrossRate = NaN(1, numMeasurements);
netrates.KP4_hamming.NetRate = NaN(1, numMeasurements);
netrates.KP4_hamming.CodeRate = NaN(1, numMeasurements);
netrates.KP4_hamming.Threshold = NaN(1, numMeasurements);
end
% Process each measurement individually.
for i = 1:numMeasurements
% --- NGMI-based modes ---
if ~isempty(ngmi)
% SD+HD concatenated mode.
idx = find(obj.NGMITHRESHOLDS_SDHD <= ngmi(i), 1, 'last');
if ~isempty(idx)
codeRate = obj.OVERALLCODERATES_SDHD(idx);
netrates.SDHD.NetRate(i) = grossRate(i) * codeRate;
netrates.SDHD.GrossRate(i) = grossRate(i) ;
netrates.SDHD.CodeRate(i) = codeRate;
netrates.SDHD.Threshold(i) = obj.NGMITHRESHOLDS_SDHD(idx);
netrates.SDHD.punctLDPI(i) = obj.ISPUNCT_LDPI(idx);
end
end
% --- BER-based mode (HD-only) ---
if ~isempty(ber)
% Loop through the BER thresholds from the best (highest code rate)
% to the worst until the measured BER is acceptable.
idxBER = [];
for j = length(obj.BERTHRESHOLDS_HD):-1:1
if ber(i) <= obj.BERTHRESHOLDS_HD(j)
idxBER = j;
break;
end
end
if ~isempty(idxBER)
codeRate = obj.CODE_RATES_HD(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);
end
idxBER = [];
for j = length(obj.BERTHRESHOLDS_KP4_AND_INNER):-1:1
if ber(i) <= obj.BERTHRESHOLDS_KP4_AND_INNER(j)
idxBER = j;
break;
end
end
if ~isempty(idxBER)
codeRate = obj.CODE_RATE_KP4_AND_INNER(idxBER);
netrates.KP4_hamming.NetRate(i) = grossRate(i) * codeRate;
netrates.KP4_hamming.GrossRate(i) = grossRate(i) ;
netrates.KP4_hamming.CodeRate(i) = codeRate;
netrates.KP4_hamming.Threshold(i) = obj.BERTHRESHOLDS_KP4_AND_INNER(idxBER);
end
end
end
end
end
end

View File

@@ -0,0 +1,660 @@
classdef DBHandler < handle
% DBHANDLER Class to handle database queries
% This class provides methods to interact with an SQLite database, including
% inserting data, retrieving table names, and appending new rows.
properties
conn % Database connection object
pathToDB % Path to the SQLite database
tableNames % Cell array containing names of all tables in the database
tables = struct(); % Structure containing MATLAB tables for each database table
distinctValues
end
methods
function obj = DBHandler(options)
% DBHANDLER Constructor for the DBHandler class
% Initializes the database connection and retrieves table and field names.
%
% Usage:
% obj = DBHandler('pathToDB', 'path/to/database.db');
arguments
options.pathToDB = ""; % Default value for pathToDB if not provided
end
% Assign values to class properties based on input arguments
fn = fieldnames(options);
for n = 1:numel(fn)
try
obj.(fn{n}) = options.(fn{n});
end
end
% Establish a connection to the SQLite database
try
obj.conn = sqlite(obj.pathToDB);
catch e
error('Failed to connect to the database: %s', e.message);
end
if obj.dbIsHealthy
obj.refresh();
else
error('DB seems to be corrupt')
end
end
function obj = refresh(obj)
% Get table names and the first rows of each table to understand the structure
obj.getTableNames();
obj.getTables();
obj.getDistinctValues();
end
function obj = getTableNames(obj)
% Get all table names from the database
try
result = fetch(obj.conn, 'SELECT name FROM sqlite_master WHERE type="table"');
obj.tableNames = result.name;
catch e
error('Failed to retrieve table names: %s', e.message);
end
end
function obj = getTables(obj)
% Get a preview (first row) of each table to understand its structure
for i = 1:numel(obj.tableNames)
try
tableName = obj.tableNames{i};
results = fetch(obj.conn, sprintf('SELECT * FROM %s WHERE 1 = 2', tableName));
% Matlab cant handle if there is a NULL in a returned
% datarow... therefore do not return a row using the
% above condition which is never true
% results = sqlread(obj.conn, tableName, MaxRows=1);
for l = 1:numel(results.Properties.VariableNames)
varName = results.Properties.VariableNames{l};
obj.tables.(tableName).(varName) = []; % Store the preview as a reference
end
catch e
warning('Failed to read the table %s: %s', tableName, e.message);
end
end
end
function obj = getDistinctValues(obj)
% getDistinctValues Retrieves distinct values for each relevant field in all tables
% excluding fields ending with "_id". Stores distinct values in the 'distinctValues'
% property.
% Initialize a structure to store distinct values for each table
obj.distinctValues = struct();
% Iterate over each table in obj.tables
tableNames_ = fieldnames(obj.tables);
for i = 1:numel(tableNames_)
tableName = tableNames_{i};
% Initialize a sub-struct to store distinct values for each field in the table
obj.distinctValues.(tableName) = struct();
% Get all fields of the current table
fieldNames = fieldnames(obj.tables.(tableName));
% Iterate over each field
for j = 1:numel(fieldNames)
fieldName = fieldNames{j};
% % Skip fields ending with '_id' as they don't contain useful distinct values
% if endsWith(fieldName, '_id')
% continue;
% end
% Construct SQL to get distinct values for the current field
query = sprintf('SELECT DISTINCT %s FROM %s', fieldName, tableName);
% Execute query and fetch distinct values
try
result = fetch(obj.conn, query);
% Store the distinct values in the structure
if ~isempty(result)
distinctValues = table2array(result);
else
distinctValues = [];
end
obj.distinctValues.(tableName).(fieldName) = distinctValues;
catch e
% warning('Failed to retrieve distinct values for %s.%s: %s', tableName, fieldName, e.message);
obj.distinctValues.(tableName).(fieldName) = [];
end
end
end
end
function healthyDB = dbIsHealthy(obj)
healthyDB = false;
num_runs = obj.fetch('SELECT COUNT(*) AS total_runs FROM Runs');
num_configs = obj.fetch('SELECT COUNT(*) AS total_configurations FROM Configurations');
num_meas = obj.fetch('SELECT COUNT(*) AS total_measurements FROM Measurements');
assert((num_runs{1,1}==num_configs{1,1})&&(num_configs{1,1}==num_meas{1,1}),'Different num of entries per table');
%Check for any duplicate paths
duplictae_raw = obj.fetch("SELECT COALESCE(Runs.rx_raw_path,'NaN') AS rx_raw_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_raw_path HAVING COUNT(*) > 1");
duplictae_sync = obj.fetch("SELECT COALESCE(Runs.rx_sync_path,'NaN') AS rx_sync_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_sync_path HAVING COUNT(*) > 1");
if size(duplictae_raw,2) > 1
for i = 1:size(duplictae_raw,1)
fprintf('Raw Rx Paths: Found %d duplictaes of %s \n',duplictae_raw.occurrences(i),duplictae_raw.rx_raw_path(i));
end
end
healthyDB = true;
end
function lastID = appendToTable(obj, tableName, newRow)
% appendToTable Appends a new row to the specified table
%
% Usage:
% appendToTable(tableName, newRow)
%
% Inputs:
% tableName: The name of the table to append data to.
% newRow: A MATLAB table or struct containing the new row to be appended.
% Check if the table exists in the fetched tables
if ~isfield(obj.tables, tableName)
error('Table %s does not exist in the database or has not been fetched.', tableName);
end
% Convert newRow to a table if it is a struct
if isstruct(newRow)
fields = fieldnames(newRow);
emptyFields = structfun(@isempty,newRow);
if sum(emptyFields)>0
newRow.(fields{emptyFields==1}) = NaN;
disp(['In Table: ',tableName,': ',fields{emptyFields==1},' was empty, is now NaN ',newRow.(fields{emptyFields==1})])
end
newRow = struct2table(newRow);
end
% Ensure the new row matches the structure of the existing table
existingTableStructure = obj.tables.(tableName);
% Perform data type checks and conversions
for colName = newRow.Properties.VariableNames
% Extract the value and its intended column type
value = newRow.(colName{1});
existingValue = existingTableStructure.(colName{1});
% If the value is a class object, convert it to JSON format
if isobject(value) && ~isdatetime(value) && ~isa(value,"string")
newRow.(colName{1}) = string(jsonencode(value));
% If the value is a character array, convert it to a string
elseif ischar(value)
newRow.(colName{1}) = string(value);
end
end
% Append the new row to the database table
try
sqlwrite(obj.conn, tableName, newRow);
% disp(['Successfully appended new row to the table ', tableName]);
catch e
error('Failed to append to the table %s: %s', tableName, e.message);
end
% Retrieve the measurement_id of the newly inserted row for linking other tables
result = fetch(obj.conn, 'SELECT last_insert_rowid()');
lastID = result{1, 1}; % Access the value directly from the table
end
function exists = checkIfRunExists(obj, table2check, column2check, value2check)
% checkIfRunExists Checks if a specific value exists in a specified column of a table
%
% Usage:
% exists = checkIfRunExists(table2check, column2check, value2check)
%
% Inputs:
% table2check: The name of the table to check for duplicates.
% column2check: The name of the column to check within the specified table.
% value2check: The value to check for in the specified column.
%
% Outputs:
% exists: Boolean indicating whether the value already exists in the table.
% Ensure the specified table and column exist in the database
if ~isfield(obj.tables, table2check)
error('Table %s does not exist in the database.', table2check);
end
% Ensure the specified column exists in the table structure
if ~isfield(obj.tables.(table2check), column2check)
error('Column %s does not exist in the table %s.', column2check, table2check);
end
% Construct the query to check for the value in the specified column
query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = "%s"', table2check, column2check, value2check);
% Execute the query and pass the value2check to avoid SQL injection issues
try
result = fetch(obj.conn, query);
count = result{1, 1}; % Extract the count from the result
catch e
error('Failed to execute the duplicate check query: %s', e.message);
end
% If count is greater than 0, then the value exists in the table
exists = count > 0;
if exists
disp(['The value "', value2check, '" already exists in the column "', column2check, '" of the table "', table2check, '".']);
else
% disp(['The value "', value2check, '" does not exist in the column "', column2check, '" of the table "', table2check, '".']);
end
end
function addBEREntry(obj, berValue, occurrence, runID, ffe, dfe, mlse, pf, eqType, ffe_order, dfe_order, len_tr, mu_ffe, mu_dfe, mu_dc, comment)
% addBEREntry Adds a BER entry linked to an existing or new Equalizer entry.
% Usage:
% addBEREntry(runID, eq, ffe, dfe, mlse, pf, eqType, ffe_order, dfe_order, len_tr, mu_ffe, mu_dfe, mu_dc, berValue, comment)
if isempty(pf)
postfilter_taps = [];
else
postfilter_taps = pf.burg_coeff;
end
% Create equalizer data struct for searching and adding if necessary
equalizerData = struct( ...
'ffe', jsonencode(ffe), ...
'dfe', jsonencode(dfe), ...
'mlse', jsonencode(mlse), ...
'pf', jsonencode(pf), ...
'eq_type', string(eqType), ...
'ffe_order', jsonencode(ffe_order), ...
'dfe_order', jsonencode(dfe_order), ...
'postfilter_taps',jsonencode(postfilter_taps),...
'len_tr', len_tr, ...
'mu_ffe', jsonencode(mu_ffe), ...
'mu_dfe', mu_dfe, ...
'mu_dc', mu_dc, ...
'comment', comment ...
);
% Check if exact Equalizer and BER entries already exist in the DB ...
selectedFields = {'Runs.run_id','BERs.ber_id','Equalizer.eq_id','BERs.ber',['BERs.occurrence' ...
'']};
filterParams = obj.tables;
filterParams.Equalizer = equalizerData;
[dataTable,sql_query] = obj.queryDB(filterParams, selectedFields);
% get or insert Equalizer
if ~isempty(dataTable)
% Equalizer entry already exists, use the existing eq_id
cur_eq_id = dataTable.eq_id;
else
% Insert the new Equalizer entry
cur_eq_id = obj.appendToTable('Equalizer', equalizerData);
end
% skip if already here or insert BER entry
if ~isempty(dataTable)
% A BER entry with the same eq_id, run_id, and occurrence already exists
existingBERValue = dataTable.ber;
% Compare the existing BER value with the new BER value
if existingBERValue == berValue
fprintf('The BER entry %.2e || -- eq_id: %d -- run_id: %d -- occurrence: %d already exists. \n',berValue, cur_eq_id, runID, occurrence);
else
fprintf('Already found BER for EQ: %.2e ~= %.2e || -- eq_id: %d -- run_id: %d -- occurrence: %d already exists.\n', berValue, existingBERValue, cur_eq_id, runID, occurrence);
end
else
% No such BER entry exists, insert the new BER entry
berData = struct( ...
'run_id', runID, ...
'eq_id', cur_eq_id, ...
'ber', berValue, ...
'occurrence', occurrence ...
);
obj.appendToTable('BERs', berData);
end
end
function executeSQL(obj, query)
% This method executes an SQL statement using MATLAB's execute function.
execute(obj.conn, query);
end
function answer = fetch(obj,query)
answer = fetch(obj.conn,query);
end
function [result,query] = queryDB(obj, filterParams, selectedFields)
% getPathsWithFlexibleFilter Retrieves values from Runs table with flexible filtering
% and lets the user select which fields to include in the SELECT statement.
%
% Usage:
% [rxRawPaths, filteredValues] = getPathsWithFlexibleFilter(filterParams)
%
% Inputs:
% filterParams: A structure containing the parameters with their values.
% If left empty, two popup windows will prompt the user for input.
%
% Outputs:
% result: table with sql return
% query: this was send to SQL DB
arguments
obj
filterParams = [];
selectedFields = [];
end
% Step 1: Prompt the user to input filter parameters if not provided
if isempty(filterParams)
filterParams = obj.promptFilterParameters();
end
% Step 2: Prompt the user to select fields to include in the SELECT statement
if isempty(selectedFields)
selectedFields = obj.promptSelectFields();
else
if iscell(selectedFields)
elseif isstruct(selectedFields)
end
end
% Step 3: Construct the SQL query based on the inputs
query = obj.constructSQLQuery(filterParams, selectedFields);
% Step 4: Execute the query and handle results
result = obj.fetch(query);
end
function query = constructSQLQuery(obj, filterParams, selectedFields)
% constructSQLQuery Constructs the SQL query based on filter parameters and selected fields.
% Construct the SELECT clause dynamically based on user selection
selectClause = 'SELECT DISTINCT ';
for i = 1:numel(selectedFields)
fieldParts = strsplit(selectedFields{i}, '.');
tableName = fieldParts{1};
fieldName = fieldParts{2};
if isnumeric(obj.tables.(tableName).(fieldName))
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', ''NaN'') AS ', fieldName];
else
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', '''') AS ', fieldName];
end
if i < numel(selectedFields)
selectClause = [selectClause, ', '];
else
selectClause = [selectClause, ' '];
end
end
% Construct the FROM and WHERE clause
baseQuery = [selectClause, 'FROM Runs ' ...
'LEFT JOIN Configurations ON Runs.run_id = Configurations.run_id ' ...
'LEFT JOIN Measurements ON Runs.run_id = Measurements.run_id ' ...
'WHERE '];
% 'LEFT JOIN BERs ON Runs.run_id = BERs.run_id ' ...
% 'LEFT JOIN Equalizer ON BERs.eq_id = Equalizer.eq_id ' ...
% Loop through each table in filterParams
filterClauses = [];
tableNames_ = fieldnames(filterParams);
for t = 1:numel(tableNames_)
tableName = tableNames_{t};
tableParams = filterParams.(tableName);
% Loop through each parameter in the table
fieldNames = fieldnames(tableParams);
for i = 1:numel(fieldNames)
fieldName = fieldNames{i};
value = tableParams.(fieldName);
% Construct the full column name in the format "tableName.fieldName"
fullName = sprintf('%s.%s', tableName, fieldName);
% Handle different types of values for SQL query construction
if isempty(value)
% Skip this parameter if it is empty (include all values)
continue;
elseif isnumeric(value) && isnan(value)
% If value is NaN, use IS NULL in SQL
filterClause = sprintf('%s IS NULL', fullName);
elseif isnumeric(value) && ~isEnumeration(value)
filterClause = sprintf('%s = %f', fullName, value);
elseif islogical(value) || (isnumeric(value) && ismember(value, [0, 1])) && ~isEnumeration(value)
filterClause = sprintf('%s = %d', fullName, value);
elseif ischar(value) || isstring(value)
filterClause = sprintf('%s = ''%s''', fullName, char(value)); %nicht nach string suchen sondern nach chararray -> 'bla' statt "bla"
elseif isEnumeration(value)
filterClause = sprintf('%s = ''%s''', fullName, value);
else
error('Unsupported data type for field "%s".', fullName);
end
% Add the constructed filter clause to the list
filterClauses = [filterClauses, filterClause, ' AND '];
end
end
% Remove trailing ' AND ' from the filter clauses if any filters were added
if ~isempty(filterClauses)
filterClauses = filterClauses(1:end-5);
end
% Construct the final SQL query
if isempty(filterClauses)
query = [selectClause, 'FROM Runs ' ...
'LEFT JOIN Configurations ON Runs.run_id = Configurations.run_id ' ...
'LEFT JOIN Measurements ON Runs.run_id = Measurements.run_id ' ...
'LEFT JOIN BERs ON Runs.run_id = BERs.run_id'];
else
query = [baseQuery, filterClauses];
end
end
function selectedFields = promptSelectFields(obj)
% promptSelectFields Prompts the user to select fields from multiple tables to include in the SELECT statement using settingsdlg.
% Get all possible fields from all tables (excluding sqlite_sequence)
tableNames = fieldnames(obj.tables);
tableNames = setdiff(tableNames, {'sqlite_sequence'}); % Remove sqlite_sequence
% Prepare the inputs for settingsdlg
promptSettings = {};
allFieldsFullName = {};
convertedFieldNames = {};
for i = 1:numel(tableNames)
tableFields = fieldnames(obj.tables.(tableNames{i}));
for j = 1:numel(tableFields)
fieldName = tableFields{j};
fullName = sprintf('%s.%s', tableNames{i}, fieldName);
convertedName = strrep(fullName, '.', '_'); % Replace '.' with '_'
allFieldsFullName{end + 1} = fullName; % Add full name to the list
convertedFieldNames{end + 1} = convertedName; % Store the converted name
% Add the field name and checkbox setting to the prompt
promptSettings{end + 1} = {sprintf('Include %s', fullName), convertedName};
promptSettings{end + 1} = false; % Default: not selected
end
end
% Create the settings dialog
[settings, button] = settingsdlg(...
'title', 'Select Fields for the SQL Query', ...
'description', 'Check the boxes for the fields you want to include in the SELECT statement.', ...
promptSettings{:} ...
);
% If the user cancels, default to selecting all fields
if strcmp(button, 'cancel')
selectedFields = allFieldsFullName;
return;
end
% Parse user input into selectedFields
selectedFields = {};
for i = 1:numel(allFieldsFullName)
convertedName = convertedFieldNames{i};
if isfield(settings, convertedName) && settings.(convertedName) % Add to selectedFields if the checkbox was selected
selectedFields{end + 1} = allFieldsFullName{i}; %#ok<AGROW>
end
end
% If no fields are selected, default to selecting all fields
if isempty(selectedFields)
selectedFields = allFieldsFullName;
end
end
function filterParams = promptFilterParameters(obj)
% promptFilterParameters Prompts the user to enter filter parameters using the settingsdlg framework.
% Get all possible parameters from all tables (excluding sqlite_sequence)
tableNames_ = fieldnames(obj.tables);
tableNames_ = setdiff(tableNames_, {'sqlite_sequence'}); % Remove sqlite_sequence
% Prepare the inputs for settingsdlg with sections and separators
promptSettings = {};
allFieldsFullName = {};
convertedFieldNames = {};
for i = 1:numel(tableNames_)
% Add a separator for each table section
promptSettings{end + 1} = 'separator';
promptSettings{end + 1} = tableNames_{i};
% Get all fields from the current table
tableFields = fieldnames(obj.tables.(tableNames_{i}));
% Prepare each field to be added to the dialog
for j = 1:numel(tableFields)
fieldName = tableFields{j};
fullName = sprintf('%s.%s', tableNames_{i}, fieldName);
convertedName = strrep(fullName, '.', '_'); % Replace '.' with '_'
% Skip fields that do not have distinct values stored
if ~isfield(obj.distinctValues.(tableNames_{i}), fieldName)
continue;
end
% Get the distinct values for the field
distinctValues_ = obj.distinctValues.(tableNames_{i}).(fieldName);
% Prepare distinct values for dropdown
if isempty(distinctValues_)
% If there are no distinct values, use only an "All" entry
distinctValues_ = {'All'};
else
% Ensure distinctValues is a cell array of strings
if isnumeric(distinctValues_)
distinctValues_ = arrayfun(@(x) num2str(x), distinctValues_, 'UniformOutput', false);
elseif isstring(distinctValues_)
distinctValues_ = cellstr(distinctValues_);
elseif iscell(distinctValues_) && ~iscellstr(distinctValues_)
distinctValues_ = cellfun(@num2str, distinctValues_, 'UniformOutput', false);
end
% Add an "All" option at the beginning of the distinct values list
distinctValues_ = [{'All'}; distinctValues_];
end
allFieldsFullName{end + 1} = fullName; % Add full name to the list
convertedFieldNames{end + 1} = convertedName; % Store the converted name
% Add the field name and value setting to the prompt
promptSettings{end + 1} = {sprintf('%s', fullName), convertedName};
promptSettings{end + 1} = distinctValues_; % Add distinct values as dropdown options
end
end
% Create the settings dialog
[settings, button] = settingsdlg(...
'title', 'Input Parameters for Filtering', ...
'description', 'Enter the values for each field to filter. Select "All" to include all values.', ...
promptSettings{:} ...
);
% If the user cancels, return an empty struct
if strcmp(button, 'cancel')
filterParams = struct();
return;
end
% Parse user input into filterParams structure
filterParams = struct();
for i = 1:numel(allFieldsFullName)
value = settings.(convertedFieldNames{i});
% Split full name to get table and field names
fieldParts = strsplit(allFieldsFullName{i}, '.');
tableName = fieldParts{1};
fieldName = fieldParts{2};
% If the table does not exist in the filterParams struct, create it
if ~isfield(filterParams, tableName)
filterParams.(tableName) = struct();
end
% Assign values to the respective fields under each table
if strcmp(value, 'All')
filterParams.(tableName).(fieldName) = []; % Set to empty to include all values
elseif isnumeric(value) && isnan(value)
filterParams.(tableName).(fieldName) = NaN; % Use NaN to handle as NULL
else
filterParams.(tableName).(fieldName) = value; % Use the entered value
end
end
end
end
end

View File

@@ -0,0 +1,76 @@
function copyStylingFrom(figNumSource, figNumTgt)
% Get handles to the source and target figures
sourceFig = figure(figNumSource);
targetFig = figure(figNumTgt);
% Get axes of source and target figures
sourceAxes = findall(sourceFig, 'type', 'axes');
targetAxes = findall(targetFig, 'type', 'axes');
% Ensure the number of axes match
if length(sourceAxes) ~= length(targetAxes)
error('Number of axes in source and target figures must be the same.');
end
% Loop through each pair of axes and copy styling properties
for i = 1:length(sourceAxes)
copyAxesProperties(sourceAxes(i), targetAxes(i));
end
% Apply general figure properties if desired
targetFig.Color = sourceFig.Color; % Background color
end
function copyAxesProperties(sourceAx, targetAx)
% List of properties to copy from source to target axes
propsToCopy = {'XColor', 'YColor', 'ZColor', 'FontSize', 'FontName', ...
'GridColor', 'GridLineStyle', 'MinorGridColor', 'Box', ...
'XGrid', 'YGrid', 'ZGrid', 'XMinorGrid', 'YMinorGrid', 'ZMinorGrid', ...
'LineWidth', 'TitleFontSizeMultiplier', 'LabelFontSizeMultiplier'};
% Copy properties from source to target
for i = 1:length(propsToCopy)
try
targetAx.(propsToCopy{i}) = sourceAx.(propsToCopy{i});
catch
% Skip property if it doesn't exist or can't be copied
end
end
% Copy axis labels and titles
targetAx.Title.String = sourceAx.Title.String;
targetAx.XLabel.String = sourceAx.XLabel.String;
targetAx.YLabel.String = sourceAx.YLabel.String;
targetAx.ZLabel.String = sourceAx.ZLabel.String;
% Copy children elements like lines, patches, etc.
sourceChildren = allchild(sourceAx);
targetChildren = allchild(targetAx);
% Ensure the number of children elements match
if length(sourceChildren) ~= length(targetChildren)
warning('Number of elements in source and target axes differ. Styling may not be applied completely.');
end
% Copy properties of children (like lines, patches, etc.), except colors and legends
for i = 1:min(length(sourceChildren), length(targetChildren))
copyObjectProperties(sourceChildren(i), targetChildren(i));
end
end
function copyObjectProperties(sourceObj, targetObj)
% List of common properties to copy for plot elements (lines, patches, etc.)
propsToCopy = {'LineStyle', 'LineWidth', 'Marker', 'MarkerSize', ...
'MarkerEdgeColor', 'MarkerFaceColor', 'DisplayName'};
% Copy properties from source to target, excluding colors
for i = 1:length(propsToCopy)
try
if ~contains(propsToCopy{i}, 'Color') % Skip color properties
targetObj.(propsToCopy{i}) = sourceObj.(propsToCopy{i});
end
catch
% Skip property if it doesn't exist or can't be copied
end
end
end

View File

@@ -114,6 +114,12 @@ classdef DataStorage < handle
end
function addValueToStorageByLinIdx(obj, valueToStore ,storageVarName, lin_idx)
obj.sto.(storageVarName){lin_idx} = valueToStore;
end
% Access Value(s)
function value = getStoValue(obj,storageVarName, varargin)
@@ -125,74 +131,79 @@ classdef DataStorage < handle
lin_idx = obj.getIndicesByPhys(varargin);
errcnt = 0;
for i=1:numel(lin_idx)
tmp = obj.sto.(storageVarName){lin_idx(i)};
if ~isempty(tmp)
if isa(tmp,'Signal') || isa(tmp,'struct') || isa(tmp,'Exfo_laser') || isa(tmp,'DC_supply')
tmp = obj.sto.(storageVarName){lin_idx(i)};
if ~isempty(tmp)
if isa(tmp,'double')
value(i) = tmp ;
elseif isa(tmp,'Signal') || isa(tmp,'struct') || isa(tmp,'Exfo_laser') || isa(tmp,'DC_supply')
if i == 1
value = {};
end
value{i} = tmp ;
elseif isa(tmp,'cell')
if isa(tmp{1},'Signal')
if i == 1
value = {};
end
value{i} = tmp{1} ;
else
value{i} = tmp{1} ;
end
else
try
if i == 1
value = {};
end
value{i} = tmp ;
catch
% value(i,:) = tmp(1:size(value,2)) ;
elseif isa(tmp,'cell')
if isa(tmp{1},'Signal')
if i == 1
value = {};
end
value{i} = tmp{1} ;
else
value{i} = tmp{1} ;
end
if size(value,2) < size(tmp,2)
else
try
diff = size(tmp,2) - size(value,2);
value(:,end+1:end+diff) = NaN(size(value,1),diff);
value(i,:) = tmp ;
catch
% value(i,:) = tmp(1:size(value,2)) ;
if size(value,2) < size(tmp,2)
diff = size(tmp,2) - size(value,2);
value(:,end+1:end+diff) = NaN(size(value,1),diff);
value(i,:) = tmp ;
elseif size(value,2) > size(tmp,2)
elseif size(value,2) > size(tmp,2)
diff = size(value,2) - size(tmp,2);
tmp(:,end+1:end+diff) = NaN(1,diff);
value(i,:) = tmp ;
end
diff = size(value,2) - size(tmp,2);
tmp(:,end+1:end+diff) = NaN(1,diff);
value(i,:) = tmp ;
end
end
else
errcnt = errcnt+1;
if errcnt < 3
end
end
else
errcnt = errcnt+1;
if errcnt < 3
%get back the n-dimensional subiondices...
[sub{1:length(size(obj.sto.(storageVarName)))}] = ind2sub(size(obj.sto.(storageVarName)),lin_idx(i));
%get back the physical representaion
%get back the physical representaion
word = [];
for phys_idx = 1:numel(obj.fn)
parametername = obj.fn(phys_idx);
word = [word,char(parametername),': ', num2str(obj.parameter.(parametername).getPhysForIndex(sub{phys_idx})),' ;'];
end
% warning(['Requested Data is not in Warehouse ', word]);
elseif errcnt == 3
% warning(['... ', word]);
end
end
if errcnt > 2
% warning([num2str(errcnt),' requested datapoint(s) not in warehouse.']);
% warning(['Requested Data is not in Warehouse ', word]);
elseif errcnt == 3
% warning(['... ', word]);
end
end
if errcnt > 2
% warning([num2str(errcnt),' requested datapoint(s) not in warehouse.']);
end
end
else
error('Wrong Request using ExampleWarehouse.getStoValue(*parameter set*). Give me all the Parameters! Please!')
@@ -256,7 +267,7 @@ classdef DataStorage < handle
end
% Mapping for single Index
function idx = getIndexByPhys(obj,fieldname,phys)
%map single phys to index
@@ -264,7 +275,70 @@ classdef DataStorage < handle
end
function [phys_indices,param_name] = getPhysIndicesByLinIndex(obj, lin_idx)
% Converts a linear index into the corresponding physical parameter values
% Inputs:
% - lin_idx: The linear index within the storage array
% Output:
% - phys_indices: A cell array containing the physical parameter values for each dimension
% Initialize output cell array
phys_indices = cell(1, numel(obj.fn));
% Convert linear index to subscript indices
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
% Map subscripts to physical values for each parameter
for i = 1:numel(obj.fn)
param_name{i} = obj.fn(i);
phys_indices{i} = obj.parameter.(param_name{i}).getPhysForIndex(subscripts{i});
end
end
function [physStruct, stored_value] = getPhysAndValueByLinIndex(obj, storageVarName, lin_idx)
% Retrieves a structure with physical parameter values as fieldnames,
% their corresponding parameter names as values, and the stored value
% for a given linear index.
% Inputs:
% - storageVarName: Name of the storage variable in obj.sto
% - lin_idx: The linear index within the storage array
% Outputs:
% - physStruct: A structure with physical parameter values as fieldnames
% and parameter names as values
% - stored_value: The value stored at the given linear index in the
% specified storage variable
% Initialize an empty structure
physStruct = struct();
% Convert linear index to subscript indices
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
% Map subscripts to physical values and parameter names for each dimension
for i = 1:numel(obj.fn)
param_name = obj.fn(i);
phys_value = obj.parameter.(param_name).getPhysForIndex(subscripts{i});
% Add to the structure with phys_value as the fieldname and param_name as the value
physStruct.(param_name) = phys_value;
end
% Retrieve the stored value at the given linear index
stored_value = obj.sto.(storageVarName){lin_idx};
end
function num_elements = getLastLinIndice(obj)
% Returns all possible linear indices for the data structure
% Output:
% - lin_indices: A column vector containing all linear indices for the storage array
% Calculate the total number of elements in the storage array
num_elements = prod(obj.dim);
end
end
end

View File

@@ -1,114 +0,0 @@
classdef DataStorage2 < handle
% DATASTORAGE: Stores data with physical parameter mappings
properties
inputParams = struct;
parameter = struct;
fn = [];
dim = [];
sto = struct;
end
methods
function obj = DataStorage2(inputParams)
% Constructor to initialize the DataStorage object
if nargin > 0
obj.inputParams = inputParams;
obj.fn = string(fieldnames(inputParams));
obj = obj.buildParameter();
obj.dim = obj.getDimension();
obj.sto = struct;
else
error('Input parameters are required.');
end
end
function showInfo(obj)
% Displays information about the storage and its dimensions
disp("Data Structure with fields:");
fprintf('%-12s | %-8s | %-12s\n', 'Name', 'Dimension', 'Physical Values');
disp('-------------------------------------------------------');
for i = 1:numel(obj.fn)
fprintf('%-12s | %-8d | %-12s\n', ...
char(obj.fn(i)), obj.dim(i), ...
strjoin(string(obj.parameter.(obj.fn(i)).values), ', '));
end
disp('-------------------------------------------------------');
end
function dim = getDimension(obj)
% Get the dimensions based on the length of parameters
dim = zeros(1, numel(obj.fn));
for p = 1:numel(obj.fn)
dim(p) = obj.parameter.(obj.fn(p)).length;
end
end
function obj = buildParameter(obj)
% Build the Parameter objects for each input parameter
for p = 1:numel(obj.fn)
name = obj.fn(p);
values = obj.inputParams.(name);
obj.parameter.(name) = Parameter2(name, values);
end
end
function addStorage(obj, varName)
% Create an empty storage for a specific variable name
obj.sto.(string(varName)) = cell(obj.dim);
end
function addValueToStorage(obj, valueToStore, storageVarName, varargin)
% Add a value to the storage at the specified indices
if nargin - 3 == numel(obj.fn)
lin_idx = obj.getIndicesByPhys(varargin);
obj.sto.(storageVarName){lin_idx} = valueToStore;
else
error('Please provide all indices for the storage.');
end
end
function value = getStoValue(obj, storageVarName, varargin)
% Retrieve a value from storage based on physical parameters
if nargin - 2 == numel(obj.fn)
lin_idx = obj.getIndicesByPhys(varargin);
value = cell(1, numel(lin_idx));
for i = 1:numel(lin_idx)
value{i} = obj.sto.(storageVarName){lin_idx(i)};
end
value = value(~cellfun('isempty', value)); % Remove empty entries
else
error('Please provide all physical parameters.');
end
end
function lin_idx = getIndicesByPhys(obj, varargin)
% Unpack nested cell array if needed
if numel(varargin) == 1 && iscell(varargin{1})
varargin = varargin{1}; % Unpack if single cell array is passed
end
indices = cell(1, numel(obj.fn));
% Loop through each parameter (e.g., L, D)
for p = 1:numel(obj.fn)
% Unwrap if it's a cell
if iscell(varargin{p})
physVal = varargin{p}{1}; % Extract scalar from cell
else
physVal = varargin{p}; % It's already a scalar
end
paramName = obj.fn(p); % Get the parameter name (e.g., 'L' or 'D')
% Call getIndexByPhys on the corresponding Parameter2 object
indices{p} = obj.parameter.(paramName).getIndexByPhys(physVal);
end
% Convert subscript indices to a linear index
lin_idx = sub2ind(obj.dim, indices{:});
end
end
end

View File

@@ -1,51 +0,0 @@
classdef Parameter2 < handle
% PARAMETER2: Represents a physical parameter with mappings between values and indices
properties
name
values
length
physToIndexMap % Rename this from 'getPhysForIndex'
indexToPhysMap % Rename this from 'getIndexForPhys'
end
methods
function obj = Parameter2(name, values)
% Constructor to initialize the Parameter2 object
obj.name = name;
obj.values = values;
obj.length = numel(values);
% Initialize the mappings
obj.physToIndexMap = containers.Map('KeyType', 'double', 'ValueType', 'any');
obj.indexToPhysMap = containers.Map('KeyType', 'double', 'ValueType', 'any');
obj = obj.buildMappings();
end
function obj = buildMappings(obj)
% Build mappings between physical values and indices
for idx = 1:obj.length
obj.indexToPhysMap(idx) = obj.values(idx);
obj.physToIndexMap(obj.values(idx)) = idx;
end
end
function physVal = getPhysForIndex(obj, idx)
% Return the physical value corresponding to the index
if isKey(obj.indexToPhysMap, idx)
physVal = obj.indexToPhysMap(idx);
else
error('Index out of range for parameter %s', obj.name);
end
end
function idx = getIndexByPhys(obj, physVal)
% Return the index corresponding to the physical value
if isKey(obj.physToIndexMap, physVal)
idx = obj.physToIndexMap(physVal);
else
error('Physical value %g not found in parameter %s', physVal, obj.name);
end
end
end
end

View File

@@ -1,45 +0,0 @@
% Define input parameters for the DataStorage2
inputParams.L = [1, 2, 10, 80]; % Length in kilometers
inputParams.D = [16, 17, 18]; % Diameter in millimeters
% Create a DataStorage2 instance with the input parameters
dataStorage = DataStorage2(inputParams); % Using DataStorage2 class
% Display the current information about the data storage structure
dataStorage.showInfo();
% Add a storage variable named 'testStorage'
dataStorage.addStorage('testStorage');
% Add a value (e.g., 100) to the storage at specific physical parameter values
% For example, we store the value 100 at L = 10 km and D = 17 mm
dataStorage.addValueToStorage(100, 'testStorage', 10, 16);
dataStorage.addValueToStorage(100, 'testStorage', 10, 17);
dataStorage.addValueToStorage(100, 'testStorage', 10, 18);
% Retrieve the value from the storage at the same physical parameter values
storedValue = dataStorage.getStoValue('testStorage', 10, 16:18);
disp('Retrieved value from storage:');
disp(storedValue);
% Retrieve another value at a non-existent location (L = 2 km, D = 8 mm)
% This will show how the function handles empty storage entries
nonExistentValue = dataStorage.getStoValue('testStorage', 2, 8);
disp('Retrieved value from empty location:');
disp(nonExistentValue);
% Use the internal mappings to check how physical values map to indices
% Get the linear index for physical values L = 10 km and D = 17 mm
lin_idx = dataStorage.getIndicesByPhys(10, 17);
disp('Linear index for L=10 km and D=17 mm:');
disp(lin_idx);
% Check the reverse mapping: physical value for index 2 of parameter L
physValForIndex = dataStorage.parameter.L.getPhysForIndex(2);
disp('Physical value for index 2 of parameter L:');
disp(physValForIndex);
% Check the mapping: index for physical value D = 21 mm
indexForPhys = dataStorage.parameter.D.getIndexByPhys(21);
disp('Index for physical value D=21 mm:');
disp(indexForPhys);

Binary file not shown.