halfway merged and pulled?!
This commit is contained in:
316
Functions/EQ_structures/dsp_runid.m
Normal file
316
Functions/EQ_structures/dsp_runid.m
Normal file
@@ -0,0 +1,316 @@
|
||||
function [output] = dsp_runid(run_id, options)
|
||||
|
||||
arguments
|
||||
run_id
|
||||
options.append_to_db = 0;
|
||||
options.max_occurences = 4;
|
||||
options.parameters = struct();
|
||||
options.database_type
|
||||
options.dataBase
|
||||
options.load_file_path = struct();
|
||||
options.storage_path
|
||||
options.mode
|
||||
end
|
||||
|
||||
try
|
||||
% Initialize output structures
|
||||
output.ffe_package = {};
|
||||
output.mlse_package = {};
|
||||
output.vnle_package = {};
|
||||
output.dbtgt_package = {};
|
||||
output.dbenc_package = {};
|
||||
output.mlmlse_package = {};
|
||||
|
||||
if options.mode == "load_run_id" || options.append_to_db
|
||||
% Initialize database connection
|
||||
database = DBHandler("dataBase", [options.dataBase], "type", options.database_type );
|
||||
|
||||
if 0
|
||||
% 2. Check if an equalizer configuration with the same hash exists
|
||||
queryStr = sprintf('SELECT COUNT(DISTINCT eq_id) AS unique_eq_count, COUNT(*) AS entries_for_run FROM `Results` WHERE run_id = %d', run_id);
|
||||
existing_results = database.fetch(queryStr);
|
||||
|
||||
if existing_results.unique_eq_count >= 6
|
||||
if (existing_results.entries_for_run / existing_results.unique_eq_count) > 5
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if options.mode == "load_run_id"
|
||||
|
||||
dataTable = queryRunid(run_id, database);
|
||||
fsym = dataTable.symbolrate;
|
||||
M = double(dataTable.pam_level);
|
||||
duob_mode = db_mode(strrep(dataTable.db_mode,'"',''));
|
||||
|
||||
% if database.checkIfRunExists('Results','run_id',run_id)
|
||||
% disp(['Already got at least one reulst for run id: ',num2str(run_id),' '])
|
||||
% return
|
||||
% end
|
||||
|
||||
% Load and Sync signal data from DB
|
||||
[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, options);
|
||||
|
||||
elseif options.mode == "load_files"
|
||||
|
||||
Tx_bits = load(options.load_file_path.tx_bits_path);
|
||||
Symbols = load(options.load_file_path.tx_symbols_path);
|
||||
Scpe_sig_raw = load(options.load_file_path.rx_raw_path);
|
||||
|
||||
Tx_bits = Tx_bits.Bits;
|
||||
Symbols = Symbols.Symbols;
|
||||
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
|
||||
|
||||
fsym = Symbols.fs;
|
||||
M = Symbols.logbook.ModifierCopy{1}.M;
|
||||
duob_mode = Symbols.logbook.ModifierCopy{1}.duobinary_mode;
|
||||
|
||||
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
|
||||
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
|
||||
|
||||
else
|
||||
|
||||
% Run quick Simulation
|
||||
tx_simulation;
|
||||
|
||||
end
|
||||
|
||||
% Handle Settings and argument replacement
|
||||
|
||||
len_tr = 4096*2;
|
||||
|
||||
ffe_order = [50, 5, 5];
|
||||
dfe_order = [0, 0, 0];
|
||||
pf_ncoeffs = 1;
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
mu_dc = 0.005;
|
||||
dc_buffer_len = 1;
|
||||
|
||||
mu_tr = 0;
|
||||
mu_dd = 0.05;
|
||||
adaption= 1;
|
||||
use_dd_mode = 1;
|
||||
|
||||
use_ffe = 0;
|
||||
use_dfe = 0;
|
||||
use_vnle_mlse = 0;
|
||||
use_dbtgt = 0;
|
||||
use_dbenc = 0;
|
||||
use_ml_mlse = 1;
|
||||
|
||||
addProcessingResultToDatabase = 0;
|
||||
|
||||
% Overwrite default parameters if given in options.parameters
|
||||
paramStruct = options.parameters;
|
||||
if ~isempty(paramStruct)
|
||||
paramNames = fieldnames(paramStruct);
|
||||
for i = 1:numel(paramNames)
|
||||
thisName = paramNames{i};
|
||||
thisValue = paramStruct.(thisName);
|
||||
eval([thisName ' = thisValue;']);
|
||||
end
|
||||
end
|
||||
|
||||
% Configure equalizers
|
||||
|
||||
options.max_occurences = min(options.max_occurences,length(Scpe_cell));
|
||||
for r = 1:options.max_occurences
|
||||
|
||||
%FFE
|
||||
% eq_dfe = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
|
||||
|
||||
|
||||
%
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
% mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
|
||||
eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms");
|
||||
eq_post = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
|
||||
% Duobinary signaling (db encoded)
|
||||
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
|
||||
eq_db_enc = EQ("Ne", ffe_order, "Nb", dfe_order, "training_length", len_tr, ...
|
||||
"training_loops", 5, "dd_loops", 5, "K", 2, "DCmu", mu_dc, ...
|
||||
"DDmu", [mu_ffe mu_dfe], "DFEmu", 0.005, "FFEmu", 0, "plotfinal", 0, "ideal_dfe", 1);
|
||||
|
||||
% Preprocess signal
|
||||
Scpe_sig = preprocessSignal(Scpe_cell{r}, Symbols, fsym);
|
||||
|
||||
Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-6);
|
||||
|
||||
Scpe_sig.spectrum("fignum",201,"normalizeTo0dB",0,"displayname",'Rx');
|
||||
|
||||
ylim([-30,3]);
|
||||
xlim([-5,100]);
|
||||
% Scpe_sig.spectrum("fignum",22233,"normalizeTo0dB",0,"displayname",'Rx');
|
||||
% Scpe_sig.eye(fsym,M,"fignum",1024);
|
||||
|
||||
if duob_mode ~= db_mode.db_encoded
|
||||
|
||||
if use_ffe
|
||||
|
||||
ffe_order = [50, 0, 0];
|
||||
eq_dfe = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
|
||||
ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
|
||||
"precode_mode",duob_mode,...
|
||||
'showAnalysis',0,...
|
||||
"postFFE",[],...
|
||||
"eth_style_symbol_mapping",0);
|
||||
|
||||
output.ffe_package{r} = ffe_results;
|
||||
|
||||
ffe_results.metrics.print;
|
||||
ffe_results.config.equalizer_structure = "ffe";
|
||||
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if use_dfe
|
||||
|
||||
ffe_order = [50, 5, 5];
|
||||
eq_dfe = EQ("Ne",ffe_order,"Nb",[2,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
|
||||
dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
|
||||
"precode_mode",duob_mode,...
|
||||
'showAnalysis',0,...
|
||||
"postFFE",[],...
|
||||
"eth_style_symbol_mapping",0);
|
||||
|
||||
output.ffe_package{r} = dfe_results;
|
||||
dfe_results.config.equalizer_structure = "dfe";
|
||||
|
||||
dfe_results.metrics.print;
|
||||
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, dfe_results.metrics, dfe_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if use_vnle_mlse
|
||||
|
||||
pf_ncoeffs = 1;
|
||||
ffe_order = [50, 5, 5];
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
% eq_ = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0.0004,"order",[50,5,5],"sps",2,"decide",0);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
|
||||
useviterbi = 0;
|
||||
if useviterbi
|
||||
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
else
|
||||
|
||||
if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation
|
||||
trellexlusion = 1;
|
||||
else
|
||||
trellexlusion = 0;
|
||||
end
|
||||
|
||||
%state_mode 3 -> stat lvl; state_mode 2 -> use target lvls
|
||||
%scale_mode 2 -> mmse adaption
|
||||
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2);
|
||||
|
||||
end
|
||||
|
||||
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode,...
|
||||
'showAnalysis', 0, ...
|
||||
"postFFE", [],...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ffe_results.metrics.print;
|
||||
ffe_results.config.equalizer_structure = "vnle";
|
||||
mlse_results.metrics.print;
|
||||
|
||||
output.mlse_package{r} = mlse_results;
|
||||
output.vnle_package{r} = ffe_results;
|
||||
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, mlse_results.metrics, mlse_results.config);
|
||||
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if use_ml_mlse
|
||||
|
||||
%ML-based MLSE (L=2)
|
||||
mu_ml = 0.01; training_epochs = 100;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",length(Scpe_sig),"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0);
|
||||
|
||||
[ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits,"precode_mode",duob_mode);
|
||||
output.mlmlse_package{r} = ml_mlse_results;
|
||||
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, ml_mlse_results.metrics, ml_mlse_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
if use_dbtgt
|
||||
|
||||
useviterbi = 0;
|
||||
if useviterbi
|
||||
mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
else
|
||||
if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation
|
||||
trellexlusion = 1;
|
||||
else
|
||||
trellexlusion = 0;
|
||||
end
|
||||
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3);
|
||||
end
|
||||
ffe_order = [50, 5, 5];
|
||||
eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', 0,...
|
||||
"postFFE", []);
|
||||
|
||||
dbt_results.metrics.print;
|
||||
|
||||
output.dbtgt_package{r} = dbt_results;
|
||||
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, dbt_results.metrics, dbt_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if duob_mode == db_mode.db_encoded
|
||||
|
||||
mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
|
||||
|
||||
db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",0,"postFFE",[]);
|
||||
output.dbenc_package{r} = db_results;
|
||||
if options.append_to_db
|
||||
database.addProcessingResult(run_id, db_results.metrics, db_results.config);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
catch ME
|
||||
save('workerError.mat','ME');
|
||||
rethrow(ME);
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
192
Functions/EQ_structures/ffe.m
Normal file
192
Functions/EQ_structures/ffe.m
Normal file
@@ -0,0 +1,192 @@
|
||||
function [ffe_results] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% FFE Processes signals through FFE equalizer
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.database = [];
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
% FFE or VNLE
|
||||
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols);
|
||||
|
||||
% Apply post-FFE if provided
|
||||
if ~isempty(options.postFFE)
|
||||
tic
|
||||
[eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols);
|
||||
toc
|
||||
end
|
||||
|
||||
try
|
||||
ch_coefficients = arburg(eq_noise.signal,1);
|
||||
channel_alpha = ch_coefficients(2);
|
||||
end
|
||||
|
||||
% Hard decision on FFE output
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
%% Calculate performance metrics
|
||||
[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal);
|
||||
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
[gmi] = calc_ngmi(eq_signal_sd,tx_symbols);
|
||||
gmi = max(gmi,0);
|
||||
|
||||
air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
|
||||
[evm_total, evm_lvl] = calc_evm(eq_signal_sd, tx_symbols);
|
||||
[std_total, std_lvl] = calc_std(eq_signal_sd, tx_symbols);
|
||||
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
|
||||
|
||||
%% Display analysis if requested
|
||||
if options.showAnalysis
|
||||
displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, options.postFFE);
|
||||
end
|
||||
|
||||
|
||||
%% Prepare output structure
|
||||
% Determine postFFE order
|
||||
if ~isempty(options.postFFE)
|
||||
npostFFE = options.postFFE.order;
|
||||
else
|
||||
npostFFE = 0;
|
||||
end
|
||||
|
||||
% Create FFE results structure
|
||||
ffe_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
end
|
||||
|
||||
ffe_results.config = Equalizerstruct();
|
||||
ffe_results.config.eq = jsonencode(eq_);
|
||||
ffe_results.config.equalizer_structure = int32(equalizer_structure.ffe);
|
||||
ffe_results.config.comment = 'function: ffe';
|
||||
|
||||
ffe_results.metrics = Metricstruct;
|
||||
ffe_results.metrics.result_id = NaN;
|
||||
ffe_results.metrics.run_id = NaN;
|
||||
ffe_results.metrics.eqParam_id = NaN;
|
||||
ffe_results.metrics.date_of_processing = datetime('now');
|
||||
ffe_results.metrics.BER = ber;
|
||||
ffe_results.metrics.numBits = bits;
|
||||
ffe_results.metrics.numBitErr = errors;
|
||||
ffe_results.metrics.BER_precoded = ber_precoded;
|
||||
ffe_results.metrics.numBitErr_precoded = errors_precoded;
|
||||
ffe_results.metrics.SNR = snr;
|
||||
ffe_results.metrics.SNR_level = snr_lvl;
|
||||
ffe_results.metrics.STD = std_total;
|
||||
ffe_results.metrics.STD_level = std_lvl;
|
||||
ffe_results.metrics.STDrx = std_rxraw_total;
|
||||
ffe_results.metrics.STDrx_level = std_rxraw_lvl;
|
||||
ffe_results.metrics.GMI = gmi;
|
||||
ffe_results.metrics.AIR = air;
|
||||
ffe_results.metrics.EVM = evm_total;
|
||||
ffe_results.metrics.EVM_level = evm_lvl;
|
||||
ffe_results.metrics.Alpha = channel_alpha;
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Calculate BER based on precoding mode
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
end
|
||||
end
|
||||
|
||||
function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, postFFE)
|
||||
% Display analysis plots and metrics
|
||||
|
||||
% Initialize figure handles
|
||||
% Corrected line - added tx_symbols as second positional argument
|
||||
showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 100);
|
||||
|
||||
warning off
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 101);
|
||||
figure(gcf);hold on; plot(((1:length(eq_noise.signal)) / eq_noise.fs) * 1e6,movmean(eq_noise.signal,2000,1), 'LineWidth',3,'Color','black')
|
||||
warning on
|
||||
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 102);
|
||||
|
||||
showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE');
|
||||
|
||||
% Figure 2: Post-FFE coefficients (if available)
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
|
||||
end
|
||||
|
||||
try
|
||||
figure(339);
|
||||
showEQfilter(eq_.e_tr, eq_signal_sd.fs.*2,"displayname",'training','fignum',339);
|
||||
showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'dec. directed','fignum',339);
|
||||
legend on
|
||||
end
|
||||
|
||||
try
|
||||
figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training');
|
||||
|
||||
figure(241); hold on; plot(pow2db(movmean(eq_.debug_struct.update_tr',100)));title('update step training');
|
||||
|
||||
figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd');
|
||||
end
|
||||
% eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
|
||||
|
||||
end
|
||||
140
Functions/EQ_structures/ml_mlse.m
Normal file
140
Functions/EQ_structures/ml_mlse.m
Normal file
@@ -0,0 +1,140 @@
|
||||
function [ml_mlse_results] = ml_mlse(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
%
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
|
||||
[eq_signal_hd,y_ref] = eq_.process(rx_signal,tx_symbols);
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
|
||||
% Create FFE results structure
|
||||
ml_mlse_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
end
|
||||
|
||||
ml_mlse_results.config = Equalizerstruct();
|
||||
|
||||
eq_small = strip_eq(eq_, 10);
|
||||
json_str = jsonencode(eq_small);
|
||||
|
||||
ml_mlse_results.config.eq = jsonencode(eq_);
|
||||
ml_mlse_results.config.equalizer_structure = int32(equalizer_structure.ml_mlse);
|
||||
ml_mlse_results.config.comment = 'function: ML-based MLSE';
|
||||
|
||||
ml_mlse_results.metrics = Metricstruct;
|
||||
% ml_mlse_results.metrics.result_id = NaN;
|
||||
% ml_mlse_results.metrics.run_id = NaN;
|
||||
% ml_mlse_results.metrics.eqParam_id = NaN;
|
||||
ml_mlse_results.metrics.date_of_processing = datetime('now');
|
||||
ml_mlse_results.metrics.BER = ber;
|
||||
ml_mlse_results.metrics.numBits = bits;
|
||||
ml_mlse_results.metrics.numBitErr = errors;
|
||||
ml_mlse_results.metrics.BER_precoded = ber_precoded;
|
||||
ml_mlse_results.metrics.numBitErr_precoded = errors_precoded;
|
||||
% ml_mlse_results.metrics.SNR = NaN;
|
||||
% ml_mlse_results.metrics.SNR_level = NaN;
|
||||
% ml_mlse_results.metrics.STD = NaN;
|
||||
% ml_mlse_results.metrics.STD_level = NaN;
|
||||
% ml_mlse_results.metrics.STDrx = NaN;
|
||||
% ml_mlse_results.metrics.STDrx_level = NaN;
|
||||
% ml_mlse_results.metrics.GMI = NaN;
|
||||
% ml_mlse_results.metrics.AIR = NaN;
|
||||
% ml_mlse_results.metrics.EVM = NaN;
|
||||
% ml_mlse_results.metrics.EVM_level = NaN;
|
||||
% ml_mlse_results.metrics.Alpha = NaN;
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Calculate BER based on precoding mode
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function eq_out = strip_eq(eq_, max_elems)
|
||||
% strip_eq removes all large fields from the ML_MLSE object
|
||||
% eq_out = strip_eq(eq_, max_elems)
|
||||
% max_elems ... maximum number of elements to keep (default = 10)
|
||||
|
||||
if nargin < 2
|
||||
max_elems = 10; % default threshold
|
||||
end
|
||||
|
||||
props = properties(eq_);
|
||||
for i = 1:numel(props)
|
||||
val = eq_.(props{i});
|
||||
if ~isempty(val)
|
||||
% Count total number of elements
|
||||
if numel(val) > max_elems
|
||||
eq_.(props{i}) = [];
|
||||
end
|
||||
end
|
||||
end
|
||||
eq_out = eq_;
|
||||
end
|
||||
85
Functions/EQ_visuals/show2Dconstellation.m
Normal file
85
Functions/EQ_visuals/show2Dconstellation.m
Normal file
@@ -0,0 +1,85 @@
|
||||
function [symbols_for_lvl,avg_for_lvl] = show2Dconstellation(eq_signal,ref_symbols,options)
|
||||
arguments
|
||||
eq_signal
|
||||
ref_symbols
|
||||
options.fignum (1,1) double = NaN % Default to NaN if not provided
|
||||
options.displayname (1,:) char = '' % Default to an empty string if not provided
|
||||
end
|
||||
|
||||
plot_shit = 1;
|
||||
|
||||
if isa(eq_signal,'Signal')
|
||||
eq_signal = eq_signal.signal;
|
||||
end
|
||||
if isa(ref_symbols,'Signal')
|
||||
ref_symbols = ref_symbols.signal;
|
||||
end
|
||||
|
||||
|
||||
if plot_shit
|
||||
% Determine the figure number to use or create a new figure
|
||||
if isnan(options.fignum)
|
||||
fig = figure; % Create a new figure and get its handle
|
||||
else
|
||||
fig = figure(options.fignum); % Use the specified figure number
|
||||
clf;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
rx_symbols = eq_signal; %./ rms(eq_signal);
|
||||
correct_symbols = ref_symbols;
|
||||
|
||||
col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
|
||||
ccnt = -1;
|
||||
|
||||
levels = unique(correct_symbols);
|
||||
M = numel(levels);
|
||||
symbols_for_lvl = NaN(numel(levels),length(correct_symbols));
|
||||
start = 1;
|
||||
ende = length(correct_symbols);
|
||||
|
||||
|
||||
|
||||
|
||||
for l = 1:numel(levels)
|
||||
ccnt = ccnt+2;
|
||||
|
||||
level_amplitude = levels(l);
|
||||
|
||||
symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude);
|
||||
|
||||
statistical_mean(l) = mean(symbols_for_lvl(l,:),'omitnan');
|
||||
|
||||
end
|
||||
|
||||
tx_even = correct_symbols(1:2:end);
|
||||
tx_odd = correct_symbols(2:2:end);
|
||||
|
||||
rx_even = rx_symbols(1:2:end);
|
||||
rx_odd = rx_symbols(2:2:end);
|
||||
|
||||
D_even = tx_even - rx_even;
|
||||
D_odd = tx_odd - rx_odd;
|
||||
|
||||
D = sqrt(D_even.^2 + D_odd.^2);
|
||||
|
||||
[X,Y] = meshgrid(levels, levels);
|
||||
[X_,Y_] = meshgrid(statistical_mean, statistical_mean);
|
||||
|
||||
hold on;
|
||||
scatter(rx_even,rx_odd,5*ones(1,length(D)),D,'.','DisplayName',['Even/Odd PAM-',num2str(M)],'MarkerEdgeColor',col(2,:));
|
||||
% colormap(gca,flip(cbrewer2('Spectral',100)))
|
||||
colormap(gca,'hsv');
|
||||
scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(6,:),'DisplayName','Statistical Rx Levels');
|
||||
scatter(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels');
|
||||
xlim([floor(min(X_(:)))-1, ceil(max(X_(:)))+1]);
|
||||
ylim([floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1]);
|
||||
|
||||
yticks((levels(1:end-1) + levels(2:end)) / 2);
|
||||
xticks((levels(1:end-1) + levels(2:end)) / 2);
|
||||
legend
|
||||
xlabel('even symbols');
|
||||
ylabel('odd symbols');
|
||||
axis equal; grid on;
|
||||
end
|
||||
114
Functions/Job_Processing/loadAndSyncSignalDataFromDb.m
Normal file
114
Functions/Job_Processing/loadAndSyncSignalDataFromDb.m
Normal file
@@ -0,0 +1,114 @@
|
||||
function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncSignalDataFromDb(dataTable, options)
|
||||
% LOADSIGNALDATA Loads and synchronizes signal data from storage
|
||||
%
|
||||
% Inputs:d
|
||||
% dataTable - Table with file paths and configuration
|
||||
% options - Struct with storage_path and max_occurences
|
||||
%
|
||||
% Outputs:
|
||||
% Symbols_mapped - Mapped symbols from bits
|
||||
% Symbols - Original symbols
|
||||
% Scpe_cell - Cell array of synchronized signals
|
||||
% found_sync - Boolean indicating if synchronization was successful
|
||||
|
||||
found_sync = 0;
|
||||
tempLocalStorage = 1;
|
||||
|
||||
% Define the fixed storage directory relative to the user's MATLAB preferences directory
|
||||
storage_dir = fullfile(prefdir, 'temp_sync_data');
|
||||
|
||||
% Part A: Check and load from local storage if available-
|
||||
if tempLocalStorage == 1
|
||||
local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id)));
|
||||
if exist(local_filename, 'file')
|
||||
% Load from local storage and return
|
||||
try
|
||||
load(local_filename, 'Bits', 'Symbols', 'Scpe_cell');
|
||||
found_sync = 1;
|
||||
return
|
||||
catch
|
||||
delete(local_filename);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% If not locally saved, load from storage
|
||||
if ~found_sync
|
||||
% Load transmitted bits
|
||||
Bits = load(fullfile([options.storage_path, char(dataTable.tx_bits_path)]));
|
||||
Bits = Bits.Bits;
|
||||
|
||||
% Map bits to symbols
|
||||
M = double(dataTable.pam_level);
|
||||
fsym = dataTable.symbolrate;
|
||||
Symbols_mapped = PAMmapper(M,0).map(Bits);
|
||||
Symbols_mapped.fs = fsym;
|
||||
|
||||
% Load original symbols
|
||||
Symbols = load(fullfile([options.storage_path, char(dataTable.tx_symbols_path)]));
|
||||
Symbols = Symbols.Symbols;
|
||||
|
||||
found_sync = 0;
|
||||
Scpe_cell = {};
|
||||
|
||||
% Try to load pre-synchronized data
|
||||
try
|
||||
Scpe_load = load(fullfile([options.storage_path, char(dataTable.rx_sync_path)]));
|
||||
Scpe_cell = Scpe_load.S;
|
||||
[~,~,~,found_sync] = Scpe_cell{2}.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
|
||||
catch
|
||||
% Continue to next method if this fails
|
||||
end
|
||||
end
|
||||
|
||||
% If not found, try with raw data
|
||||
if ~found_sync
|
||||
try
|
||||
Scpe_sig_raw = load([options.storage_path, char(dataTable.rx_raw_path(1))]);
|
||||
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
|
||||
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
|
||||
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
|
||||
catch
|
||||
% Continue to next method if this fails
|
||||
end
|
||||
end
|
||||
|
||||
% Last attempt with mapped symbols
|
||||
if ~found_sync && exist('Scpe_sig_raw', 'var')
|
||||
if length(Symbols_mapped.signal) ~= sum(Symbols_mapped.signal == Symbols.signal)
|
||||
[~, Scpe_cell, ~, found_sync] = Scpe_sig_raw.tsynch("reference", Symbols_mapped, "fs_ref", fsym, "debug_plots", 0);
|
||||
end
|
||||
end
|
||||
|
||||
% Part B: Save to local storage if data was loaded and synced
|
||||
if tempLocalStorage == 1 && found_sync
|
||||
% Create directory if it doesn't exist
|
||||
if ~exist(storage_dir, 'dir')
|
||||
mkdir(storage_dir);
|
||||
end
|
||||
|
||||
% local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id)));
|
||||
|
||||
% List existing files and remove oldest if more than N
|
||||
max_local_files = 10; % Store up to N files
|
||||
files = dir(fullfile(storage_dir, 'sync_data_run_*.mat'));
|
||||
if length(files) >= max_local_files
|
||||
% Sort by date
|
||||
[~, idx] = sort([files.datenum]);
|
||||
% Delete oldest file
|
||||
delete(fullfile(storage_dir, files(idx(1)).name));
|
||||
end
|
||||
|
||||
% Save current data
|
||||
save(local_filename, 'Bits', 'Symbols', 'Scpe_cell');
|
||||
end
|
||||
|
||||
% Limit number of occurrences
|
||||
if found_sync
|
||||
record_realizations = min(options.max_occurences, length(Scpe_cell));
|
||||
Scpe_cell = Scpe_cell(1:record_realizations);
|
||||
else
|
||||
warning('Could not synchronize the received signal with the stored symbols!');
|
||||
end
|
||||
|
||||
end
|
||||
32
Functions/Job_Processing/preprocessSignal.m
Normal file
32
Functions/Job_Processing/preprocessSignal.m
Normal file
@@ -0,0 +1,32 @@
|
||||
function Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym)
|
||||
% PREPROCESSSIGNAL Performs standard preprocessing on a signal
|
||||
%
|
||||
% Inputs:
|
||||
% Scpe_sig - Input signal
|
||||
% Symbols - Reference symbols for synchronization
|
||||
% fsym - Symbol frequency
|
||||
%
|
||||
% Outputs:
|
||||
% Scpe_sig - Preprocessed signal
|
||||
|
||||
% Resample to 2x symbol rate
|
||||
Scpe_sig = Scpe_sig.resample("fs_out", 2*fsym);
|
||||
|
||||
% Synchronize with reference
|
||||
[Scpe_sig, ~] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
|
||||
|
||||
% Apply Gaussian filter
|
||||
if 1
|
||||
Scpe_sig = Filter('filtdegree', 8, "f_cutoff", Symbols.fs.*0.52, ...
|
||||
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
|
||||
"active", true).process(Scpe_sig);
|
||||
else
|
||||
Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ...
|
||||
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
|
||||
"active", true).process(Scpe_sig);
|
||||
end
|
||||
|
||||
%Remove DC offset
|
||||
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
|
||||
|
||||
end
|
||||
47
Functions/Job_Processing/printResults.m
Normal file
47
Functions/Job_Processing/printResults.m
Normal file
@@ -0,0 +1,47 @@
|
||||
function printResults(run_id, M, Symbols, vnle_pf_package, dbtgt_package, occ)
|
||||
% PRINTRESULTS Prints formatted results from equalization
|
||||
%
|
||||
% Inputs:
|
||||
% run_id - Run identifier
|
||||
% M - PAM level
|
||||
% Symbols - Symbol data with fs property
|
||||
% vnle_pf_package - VNLE+PF results package
|
||||
% dbtgt_package - DB target results package (optional)
|
||||
% occ - Occurrence index
|
||||
|
||||
% Print header
|
||||
fprintf("==== EQUALIZATION RUN-ID %d | PAM-%d | %.2f GBd ====\n\n", run_id, M, Symbols.fs.*1e-9);
|
||||
|
||||
% VNLE Results
|
||||
if ~isempty(vnle_pf_package)
|
||||
vnle_result = vnle_pf_package{occ}.resultsVNLE;
|
||||
mlse_result = vnle_pf_package{occ}.resultsMLSE;
|
||||
|
||||
fprintf(">> VNLE Results:\n");
|
||||
fprintf(" BER: %.2e\n", vnle_result.BER);
|
||||
fprintf(" BER (pre-code): %.2e\n", vnle_result.BER_precoded);
|
||||
fprintf(" SNR: %.2f dB\n", vnle_result.SNR);
|
||||
fprintf(" GMI: %.4f\n", vnle_result.GMI);
|
||||
fprintf(" Linerate: %.2f Gbps\n", Symbols.fs .* floor(log2(M)*10)/10 .*1e-9);
|
||||
fprintf(" AIR: %.2f Gbps\n", vnle_result.AIR.*1e-9);
|
||||
fprintf("\n");
|
||||
|
||||
% MLSE Results
|
||||
fprintf(">> MLSE Results:\n");
|
||||
fprintf(" BER: %.2e\n", mlse_result.BER);
|
||||
fprintf(" BER (pre-code): %.2e\n", mlse_result.BER_precoded);
|
||||
fprintf(" Channel Alpha: %.2f\n", mlse_result.Alpha);
|
||||
fprintf("\n");
|
||||
end
|
||||
|
||||
% DB Target Results
|
||||
if ~isempty(dbtgt_package)
|
||||
dbtgt = dbtgt_package{occ}.resultsDBtgt;
|
||||
fprintf(">> DB Target Results:\n");
|
||||
fprintf(" BER: %.2e\n", dbtgt.BER);
|
||||
fprintf(" BER (pre-code): %.2e\n", dbtgt.BER_precoded);
|
||||
fprintf("\n");
|
||||
end
|
||||
|
||||
fprintf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n\n");
|
||||
end
|
||||
11
Functions/Job_Processing/queryRunid.m
Normal file
11
Functions/Job_Processing/queryRunid.m
Normal file
@@ -0,0 +1,11 @@
|
||||
function dataTable = queryRunid(run_id, database)
|
||||
|
||||
% Query database for run configuration
|
||||
filterParams = database.tables;
|
||||
filterParams.Runs = struct('run_id', run_id);
|
||||
|
||||
[dataTable, ~] = database.queryDB(filterParams, database.getTableFieldNames('Runs'));
|
||||
[~, uniqueIdx] = unique(dataTable.run_id); % Get unique run_id indices
|
||||
dataTable = dataTable(uniqueIdx, :); % Extract unique configurations for each run_id
|
||||
|
||||
end
|
||||
235
Functions/Job_Processing/submitJobs.m
Normal file
235
Functions/Job_Processing/submitJobs.m
Normal file
@@ -0,0 +1,235 @@
|
||||
function [results, wh] = submitJobs(run_ids, dsp_options, submit_mode, submit_options)
|
||||
% SUBMITJOBS Submits dsp_runid jobs for processing (parallel or serial)
|
||||
%
|
||||
% [results, wh] = submitJobs(run_ids, dsp_options, submit_mode, submit_options)
|
||||
%
|
||||
% run_ids : scalar or vector of run IDs
|
||||
% dsp_options : struct of dsp_runid name/value options
|
||||
% submit_mode : processingMode.parallel or .serial
|
||||
% submit_options : struct with fields
|
||||
% .waitbar (logical)
|
||||
% .wh (DataStorage object)
|
||||
%
|
||||
% results : cell(nJobsPerRunId, nRunIds)
|
||||
% wh : updated DataStorage
|
||||
|
||||
arguments
|
||||
run_ids int32 = 0
|
||||
dsp_options struct = struct()
|
||||
submit_mode processingMode = processingMode.serial
|
||||
submit_options.waitbar (1,1) logical = true
|
||||
submit_options.wh = DataStorage(struct())
|
||||
end
|
||||
|
||||
% Normalize
|
||||
run_ids = run_ids(:)';
|
||||
nRunIds = numel(run_ids);
|
||||
nJobsPerRunId = submit_options.wh.getLastLinIndice();
|
||||
totalJobs = nRunIds * nJobsPerRunId;
|
||||
|
||||
% Preallocate
|
||||
results = cell(nJobsPerRunId, nRunIds);
|
||||
futures = parallel.FevalFuture.empty(totalJobs,0);
|
||||
jobIndices = zeros(totalJobs,2);
|
||||
|
||||
% Optional waitbar
|
||||
if submit_options.waitbar
|
||||
h = waitbar(0, 'Processing Jobs...');
|
||||
cleanupObj = onCleanup(@() delete(h));
|
||||
end
|
||||
|
||||
switch submit_mode
|
||||
case processingMode.parallel
|
||||
%—– SET UP POOL & QUEUE —–
|
||||
p = setupParallelPool(11, 300); % 10 workers, 300s idle timeout
|
||||
|
||||
% === submit all futures ===
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
jobIndices(jobCounter,:) = [r,k];
|
||||
|
||||
opt = buildOptionalVars(k, submit_options.wh);
|
||||
futures(jobCounter) = parfeval( ...
|
||||
p, @dsp_runid, 1, ...
|
||||
run_ids(r), ...
|
||||
"database_type", dsp_options.database_type, ...
|
||||
"dataBase", dsp_options.dataBase, ...
|
||||
"append_to_db", dsp_options.append_to_db, ...
|
||||
"load_file_path", dsp_options.load_file_path, ...
|
||||
"max_occurences", dsp_options.max_occurences, ...
|
||||
"storage_path", dsp_options.storage_path, ...
|
||||
"mode", dsp_options.mode, ...
|
||||
"parameters", opt ...
|
||||
);
|
||||
|
||||
fprintf('[RunID %d, Job %d] Submitted to pool.\n', run_ids(r), k);
|
||||
end
|
||||
end
|
||||
|
||||
%—– W A I T B A R U P D A T E —–
|
||||
if submit_options.waitbar
|
||||
futureArray = futures;
|
||||
updateWB = @() waitbar( ...
|
||||
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray))/totalJobs, ...
|
||||
h, sprintf('Completed %d/%d jobs', ...
|
||||
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray)), totalJobs) ...
|
||||
);
|
||||
afterEach(futureArray, updateWB, 0);
|
||||
end
|
||||
|
||||
% —– before the loop —–
|
||||
% Keep track of which futures we've already handled:
|
||||
consumedIdx = false(totalJobs,1);
|
||||
|
||||
% —– fetch in completion order, handling successes and errors —–
|
||||
for n = 1:totalJobs
|
||||
try
|
||||
% This returns the value AND the linear index in 'futures'
|
||||
[idx, val] = fetchNext(futures);
|
||||
|
||||
duration = futures(idx).RunningDuration;
|
||||
startDT = futures(idx).StartDateTime;
|
||||
finishDT = futures(idx).FinishDateTime;
|
||||
duration = finishDT - startDT;
|
||||
|
||||
% Mark it consumed
|
||||
consumedIdx(idx) = true;
|
||||
|
||||
% Map back to (r,k) and store
|
||||
r = jobIndices(idx,1);
|
||||
k = jobIndices(idx,2);
|
||||
|
||||
fprintf('[%s] JobID %d/%d (%.1f%%) — %s — RunID %d — Subjob %d — fetched.\n', ...
|
||||
datestr(now,'yyyy-mm-dd HH:MM:SS'), ...
|
||||
r, totalJobs, 100*n/totalJobs,char(duration), ...
|
||||
run_ids(r), k);
|
||||
|
||||
% Update waitbar
|
||||
if submit_options.waitbar
|
||||
waitbar(n/totalJobs, h, ...
|
||||
sprintf('Fetched %d/%d (%.1f% Percent)', n, totalJobs, 100*n/totalJobs));
|
||||
drawnow; % force the GUI to refresh
|
||||
end
|
||||
|
||||
storeResult(val, k, submit_options.wh);
|
||||
results{k,r} = val;
|
||||
|
||||
catch fetchErr
|
||||
% fetchNext has already set Read=true on the errored future.
|
||||
% Find the one Read==true that we have _not_ yet consumed.
|
||||
readMask = arrayfun(@(f) f.Read, futures);
|
||||
idxErr = find(readMask & ~consumedIdx', 1);
|
||||
consumedIdx(idxErr) = true;
|
||||
|
||||
% Pull the _real_ exception out of the future object
|
||||
errInfo = futures(idxErr).Error;
|
||||
if iscell(errInfo)
|
||||
origME = errInfo{1};
|
||||
else
|
||||
origME = errInfo;
|
||||
end
|
||||
|
||||
% Map back to (r,k) and log
|
||||
r = jobIndices(idxErr,1);
|
||||
k = jobIndices(idxErr,2);
|
||||
handleError(origME, k, run_ids(r));
|
||||
results{k,r} = origME;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
case processingMode.serial
|
||||
%—– SERIAL EXECUTION —–
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
optionalVars = buildOptionalVars(k, submit_options.wh);
|
||||
try
|
||||
fprintf('[RunID %d, Job %d] Running in linear mode...\n', run_ids(r), k);
|
||||
val = dsp_runid( run_ids(r), ...
|
||||
"database_type", dsp_options.database_type, ...
|
||||
"dataBase", dsp_options.dataBase, ...
|
||||
"append_to_db", dsp_options.append_to_db, ...
|
||||
"load_file_path", dsp_options.load_file_path, ...
|
||||
"max_occurences", dsp_options.max_occurences, ...
|
||||
"storage_path", dsp_options.storage_path, ...
|
||||
"mode", dsp_options.mode, ...
|
||||
"parameters", optionalVars );
|
||||
|
||||
fprintf('[RunID %d, Job %d] Completed successfully.\n', run_ids(r), k);
|
||||
storeResult(val, k, submit_options.wh);
|
||||
results{k,r} = val;
|
||||
|
||||
catch ME
|
||||
handleError(ME, k, run_ids(r));
|
||||
results{k,r} = ME;
|
||||
end
|
||||
|
||||
if submit_options.waitbar
|
||||
waitbar(jobCounter/totalJobs, h, ...
|
||||
sprintf('Completed %d/%d jobs', jobCounter, totalJobs));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
otherwise
|
||||
error('Unknown submit_mode "%s".', string(submit_mode))
|
||||
end
|
||||
|
||||
wh = submit_options.wh;
|
||||
|
||||
%% Local helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
function handleError(ME, jobIndex, run_id)
|
||||
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', run_id, jobIndex, ...
|
||||
ME.identifier, ME.message);
|
||||
for st = ME.stack'
|
||||
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
|
||||
end
|
||||
fprintf('Full report:\n%s\n', getReport(ME,'extended'));
|
||||
end
|
||||
|
||||
function optionalVars = buildOptionalVars(jobIndex, wh)
|
||||
optionalVars = struct();
|
||||
if ~isempty(wh.getDimension())
|
||||
[vals, names] = wh.getPhysIndicesByLinIndex(jobIndex);
|
||||
for pi = 1:numel(names)
|
||||
optionalVars.(names{pi}) = vals{pi};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function storeResult(val, jobIndex, wh)
|
||||
if ~isempty(wh)
|
||||
wh.addValueToStorageByLinIdx(val.ffe_package, 'ffe_package', jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.mlse_package, 'mlse_package', jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.dbtgt_package,'dbtgt_package',jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.dbenc_package,'dbenc_package',jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.mlmlse_package,'mlmlse_package',jobIndex);
|
||||
end
|
||||
end
|
||||
function p = setupParallelPool(numWorkers, idleTimeout)
|
||||
% Ensure a pool exists at the right size & timeout
|
||||
p = gcp('nocreate');
|
||||
if isempty(p) || p.NumWorkers~=numWorkers
|
||||
if ~isempty(p)
|
||||
delete(p);
|
||||
end
|
||||
p = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
|
||||
end
|
||||
|
||||
% Cancel anything left in the pool's default queue
|
||||
q = p.FevalQueue;
|
||||
if ~isempty(q.QueuedFutures) || ~isempty(q.RunningFutures)
|
||||
cancelAll(q);
|
||||
fprintf('Canceled %d unfetched jobs from old queue.\n', ...
|
||||
numel(q.QueuedFutures)+numel(q.RunningFutures));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
158
Functions/Metrics/calc_gmi_bitwise.m
Normal file
158
Functions/Metrics/calc_gmi_bitwise.m
Normal file
@@ -0,0 +1,158 @@
|
||||
function [GMI,NGMI] = calc_gmi_bitwise(test_signal,reference_signal,options)
|
||||
% https://cioffi-group.stanford.edu/doc/book/AppendixG.pdf
|
||||
% Supports PAM2/4/8 (per-symbol) and PAM6 (pairwise: 5 bits per 2 symbols)
|
||||
|
||||
arguments(Input)
|
||||
test_signal
|
||||
reference_signal
|
||||
options.skip_front (1,1) double = 0
|
||||
options.skip_end (1,1) double = 0
|
||||
options.returnErrorLocation (1,1) double = 0 %#ok<NASGU>
|
||||
end
|
||||
|
||||
options.skip_end = abs(options.skip_end);
|
||||
options.skip_front = abs(options.skip_front);
|
||||
assert((options.skip_end+options.skip_front) < numel(test_signal), ...
|
||||
"You can not skip more samples than overall length.");
|
||||
|
||||
if isa(reference_signal,'Signal'); reference_signal = reference_signal.signal; end
|
||||
if isa(test_signal,'Signal'); test_signal = test_signal.signal; end
|
||||
|
||||
% TRIM
|
||||
[test_signal,reference_signal] = trimseq(test_signal,reference_signal,options.skip_front,options.skip_end);
|
||||
|
||||
% Common precompute
|
||||
N = length(test_signal);
|
||||
M = numel(unique(reference_signal));
|
||||
noise = test_signal - reference_signal;
|
||||
sigma2 = var(noise); % real AWGN variance
|
||||
|
||||
% --- Constellation (levels observed)
|
||||
constellation = unique(reference_signal);
|
||||
M = numel(constellation);
|
||||
|
||||
% --- Empirical P_X (uniform is fine too; keep your original behavior)
|
||||
N = numel(reference_signal);
|
||||
counts = arrayfun(@(c) sum(reference_signal==c), constellation);
|
||||
priors = counts / N;
|
||||
|
||||
% ----- PAM6: 5 bits mapped to 2 consecutive symbols (36 transitions) -----
|
||||
if M == 6
|
||||
% Pair the stream (drop last if odd)
|
||||
numPairs = floor(N/2);
|
||||
if numPairs == 0, GMI = 0; NGMI = 0; return; end
|
||||
|
||||
y1 = test_signal(1:2:2*numPairs);
|
||||
y2 = test_signal(2:2:2*numPairs);
|
||||
|
||||
% Use the same level order as PAMmapper to be consistent with labeling
|
||||
levels = constellation;%PAMmapper(6,0,"eth_style",0).levels; % 1x6 (actual amplitudes used)
|
||||
% Build all 36 transitions (i,j)
|
||||
[S1,S2] = ndgrid(levels, levels); % 6x6
|
||||
trans_pairs = [S1(:), S2(:)]; % 36x2
|
||||
|
||||
% 5-bit labels for each transition (ETH style).
|
||||
% NOTE: keep the normalization you use with your PAMmapper (often /sqrt(10)).
|
||||
bits72 = PAMmapper(6,0,"eth_style",0).demap( reshape(trans_pairs.',[],1) );
|
||||
pairBits = reshape(bits72.', 5, []).'; % 36 x 5
|
||||
|
||||
% Transmitted 5-bit labels for each observed pair (from the reference)
|
||||
tx_bits_pair = PAMmapper(6,0,"eth_style",0).demap( reshape(reference_signal(1:2* numPairs).',[],1) );
|
||||
tx_bits_pair = reshape(tx_bits_pair.', 5, []).'; % numPairs x 5
|
||||
|
||||
% Precompute symbol log-priors (uniform => constant; included for shaped inputs)
|
||||
logP = log(priors);
|
||||
|
||||
% Precompute masks for bit=0 / bit=1 in the 6x6 grid
|
||||
mask1 = false(6,6,5);
|
||||
mask0 = false(6,6,5);
|
||||
for b = 1:5
|
||||
tmp = false(6,6);
|
||||
tmp(:) = pairBits(:,b)==1;
|
||||
mask1(:,:,b) = tmp;
|
||||
tmp = false(6,6);
|
||||
tmp(:) = pairBits(:,b)==0;
|
||||
mask0(:,:,b) = tmp;
|
||||
end
|
||||
|
||||
% Exact LLRs via log-sum-exp over the 6x6 grid
|
||||
LLR = zeros(numPairs,5);
|
||||
cst = -0.5*log(2*pi*sigma2); % cancels in LLR but harmless
|
||||
for k = 1:numPairs
|
||||
li = cst - ((y1(k) - levels).^2)/(2*sigma2) + logP; % 1x6
|
||||
lj = cst - ((y2(k) - levels).^2)/(2*sigma2) + logP; % 1x6
|
||||
logW = li(:) + lj(:).'; % 6x6 (adds logP twice)
|
||||
|
||||
for b = 1:5
|
||||
L1 = logsumexp(logW(mask1(:,:,b)));
|
||||
L0 = logsumexp(logW(mask0(:,:,b)));
|
||||
LLR(k,b) = L1 - L0; % natural-log LLR
|
||||
end
|
||||
end
|
||||
|
||||
% Bit-wise MI using consistency relation
|
||||
MI_bits = zeros(1,5);
|
||||
for b = 1:5
|
||||
idx0 = (tx_bits_pair(:,b)==0);
|
||||
idx1 = ~idx0;
|
||||
r0 = LLR(idx0,b);
|
||||
r1 = LLR(idx1,b);
|
||||
I0 = mean( log2(1 + exp( r0)) ); % natural LLR inside exp()
|
||||
I1 = mean( log2(1 + exp(-r1)) );
|
||||
MI_bits(b) = 1 - 0.5*(I0 + I1);
|
||||
end
|
||||
|
||||
% Per-symbol outputs (5 bits per 2 symbols)
|
||||
GMI = sum(MI_bits)/2;
|
||||
NGMI = GMI / 2.5;
|
||||
|
||||
% ----- Generic PAM2/4/8…: per-symbol, Gray bits directly on symbols -----
|
||||
else
|
||||
nBits = log2(M);
|
||||
levels = PAMmapper(M,0).levels' / PAMmapper(M,0).scaling; % match PAMmapper ordering
|
||||
grayBits= PAMmapper(M,0).showBitMapping; % M x nBits
|
||||
|
||||
% Allocate
|
||||
LLR = zeros(N, nBits);
|
||||
|
||||
for n = 1:N
|
||||
y = test_signal(n);
|
||||
ll_table = -((y - levels).^2)/(2*sigma2) + log(priors); % 1xM (log-domain)
|
||||
for b = 1:nBits
|
||||
idx0 = grayBits(:,b)==0;
|
||||
idx1 = ~idx0;
|
||||
L0 = logsumexp(ll_table(idx0));
|
||||
L1 = logsumexp(ll_table(idx1));
|
||||
LLR(n,b) = L1 - L0; % natural-log LLR
|
||||
end
|
||||
end
|
||||
|
||||
tx_bits = PAMmapper(M,0,"eth_style",0).demap(reference_signal);
|
||||
|
||||
% Bit-wise MI
|
||||
MI_bits = zeros(1,nBits);
|
||||
for b = 1:nBits
|
||||
r0 = LLR(tx_bits(:,b)==0,b);
|
||||
r1 = LLR(tx_bits(:,b)==1,b);
|
||||
I0 = mean( log2(1 + exp( r0)) );
|
||||
I1 = mean( log2(1 + exp(-r1)) );
|
||||
MI_bits(b) = 1 - 0.5*(I0 + I1);
|
||||
end
|
||||
|
||||
GMI = sum(MI_bits); % bits / symbol
|
||||
NGMI = GMI / nBits;
|
||||
end
|
||||
|
||||
% ===== Helpers =====
|
||||
function s = logsumexp(a)
|
||||
if isempty(a), s = -inf; return; end
|
||||
m = max(a(:));
|
||||
s = m + log(sum(exp(a(:) - m)));
|
||||
end
|
||||
|
||||
function [data_,reference_] = trimseq(data,reference,skipstart,skip_end)
|
||||
data_ = data(skipstart+1:end-skip_end,:);
|
||||
delta = numel(reference) - numel(data_);
|
||||
reference_ = reference(skipstart+1:end-(skip_end+delta),:);
|
||||
end
|
||||
end
|
||||
31
Functions/Metrics/count_error_bursts.m
Normal file
31
Functions/Metrics/count_error_bursts.m
Normal file
@@ -0,0 +1,31 @@
|
||||
function burst_count = count_error_bursts(err_pos, max_burst_length)
|
||||
% err_pos: Vector of error positions
|
||||
% max_burst_length: Maximum length for which bursts are counted (e.g., 10)
|
||||
|
||||
% Sort the error positions to ensure they're in increasing order
|
||||
err_pos = sort(err_pos);
|
||||
|
||||
% Initialize burst_count array to hold the counts for each burst length
|
||||
burst_count = zeros(1, max_burst_length);
|
||||
|
||||
% Find the differences between consecutive error positions
|
||||
diffs = diff(err_pos);
|
||||
|
||||
% Find the start of the bursts (where the difference is greater than 1)
|
||||
burst_starts = [1, find(diffs > 1) + 1]; % Starts at index 1 and after gaps
|
||||
burst_ends = [find(diffs > 1), length(err_pos)]; % Ends where gaps are
|
||||
|
||||
% Loop over the bursts
|
||||
for b = 1:length(burst_starts)
|
||||
burst_length = burst_ends(b) - burst_starts(b) + 1;
|
||||
|
||||
% Check if the burst length exceeds any of the thresholds
|
||||
for i = 1:max_burst_length
|
||||
if burst_length == i
|
||||
burst_count(i) = burst_count(i) + 1;
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
Functions/Theory/CCDM/ccdm_gpt_example.m
Normal file
27
Functions/Theory/CCDM/ccdm_gpt_example.m
Normal file
@@ -0,0 +1,27 @@
|
||||
%% Target source entropy for PS-PAM8
|
||||
clear; clc;
|
||||
|
||||
M = 8;
|
||||
a = -(M-1):2:(M-1); % PAM-8 amplitude levels: [-7 -5 -3 -1 1 3 5 7]
|
||||
H_target = 2.79; % desired entropy [bits/symbol]
|
||||
|
||||
% Objective: find nu such that H(PA) = H_target
|
||||
f = @(nu) entropy_MB(a,nu) - H_target;
|
||||
nu_opt = fzero(f, [0, 2]); % search ν in reasonable range
|
||||
|
||||
% Compute final distribution
|
||||
P = exp(-nu_opt*a.^2);
|
||||
P = P/sum(P);
|
||||
H = -sum(P .* log2(P));
|
||||
|
||||
fprintf('Shaping parameter ν = %.4f\n', nu_opt);
|
||||
fprintf('Entropy H(A) = %.3f bits/symbol\n', H);
|
||||
disp('Probability vector (P_A):');
|
||||
disp(P.');
|
||||
|
||||
%% Helper: entropy function
|
||||
function H = entropy_MB(a,nu)
|
||||
P = exp(-nu*a.^2);
|
||||
P = P/sum(P);
|
||||
H = -sum(P .* log2(P));
|
||||
end
|
||||
64
Functions/Theory/analyze_moving_average_filter.m
Normal file
64
Functions/Theory/analyze_moving_average_filter.m
Normal file
@@ -0,0 +1,64 @@
|
||||
% Parameters
|
||||
N_values = [10 100 1000 4096]; % Different filter lengths to analyze
|
||||
fs = 112e9;
|
||||
|
||||
% Create figure
|
||||
figure;
|
||||
|
||||
% Plot frequency responses
|
||||
subplot(211)
|
||||
hold on
|
||||
grid on
|
||||
ylabel('Magnitude (dB)')
|
||||
title('Frequency Response')
|
||||
yline(-3,'--r')
|
||||
ylim([-40 5])
|
||||
|
||||
subplot(212)
|
||||
hold on
|
||||
grid on
|
||||
xlabel('Frequency (GHz)')
|
||||
ylabel('Phase (rad)')
|
||||
title('Phase Response')
|
||||
|
||||
% Color map for different lines
|
||||
colors = cbrewer2('Set1',length(N_values));
|
||||
|
||||
% Loop through different filter lengths
|
||||
for i = 1:length(N_values)
|
||||
N = N_values(i);
|
||||
|
||||
% Filter coefficients
|
||||
b = ones(1,N)/N;
|
||||
a = 1;
|
||||
|
||||
% Frequency response
|
||||
[h,w] = freqz(b,a,4096*8);
|
||||
freq = (w/(2*pi))*fs;
|
||||
h_db = 20*log10(abs(h));
|
||||
|
||||
% Plot magnitude response
|
||||
subplot(211)
|
||||
plot(freq/1e9, h_db, 'Color', colors(i,:), 'DisplayName', sprintf('N=%d', N),'LineWidth',0.1)
|
||||
|
||||
% Plot phase response
|
||||
subplot(212)
|
||||
plot(freq/1e9, unwrap(angle(h)), 'Color', colors(i,:), 'DisplayName', sprintf('N=%d', N),'LineWidth',0.1)
|
||||
|
||||
% Find -3dB frequency
|
||||
cutoff_idx = find(h_db <= -3, 1);
|
||||
f_cutoff = freq(cutoff_idx)/1e9;
|
||||
fprintf('N=%d: Cutoff frequency (-3dB point): %.2f GHz\n', N, f_cutoff)
|
||||
end
|
||||
|
||||
% Add legend and adjust axes
|
||||
subplot(211)
|
||||
legend('show')
|
||||
xlim([0 16]) % Adjust x-axis limit to better see the differences
|
||||
|
||||
subplot(212)
|
||||
legend('show')
|
||||
xlim([0 16]) % Adjust x-axis limit to better see the differences
|
||||
|
||||
% Analytical approximation
|
||||
f_3db_approx = 0.443 * fs./N_values ./ 1e9;
|
||||
89
Functions/Theory/dispersion_10km.m
Normal file
89
Functions/Theory/dispersion_10km.m
Normal file
@@ -0,0 +1,89 @@
|
||||
%% ============================================================
|
||||
% IM/DD Fading Notch – λ_null vs. Bandwidth (Fixed 10 km)
|
||||
% ============================================================
|
||||
|
||||
clear; clc;
|
||||
|
||||
%% Fiber and dispersion parameters
|
||||
lambda0 = 1310e-9; % Zero-dispersion wavelength [m]
|
||||
S0 = 0.09; % Dispersion slope at ZDW [ps/(nm²·km)]
|
||||
L = 10e3; % Fiber length [m]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
%% Frequency sweep (defines the desired first-fading notch)
|
||||
f_targets = linspace(40e9, 150e9, 200); % [Hz]
|
||||
f_GHz = f_targets / 1e9;
|
||||
|
||||
%% Compute wavelength λ_null for each target f_null
|
||||
[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0);
|
||||
lambda_nm = lambda_vec * 1e9; % Convert to nm
|
||||
Dacc = Dacc_vec; % [ps/nm]
|
||||
|
||||
%% ------------------------------------------------------------
|
||||
% Plot λ_null vs. f_null for 10 km fiber
|
||||
% ------------------------------------------------------------
|
||||
cols = cbrewer2('Paired',10);
|
||||
figure('Color','w'); hold on;
|
||||
|
||||
hLine = plot(lambda_nm, f_GHz, ...
|
||||
'LineWidth', 2, ...
|
||||
'DisplayName', sprintf('L = %.1f km', L/1000), ...
|
||||
'Color', cols(2,:));
|
||||
|
||||
xlabel('Wavelength λ [nm]');
|
||||
ylabel('First fading notch f_{null} [GHz]');
|
||||
title('IM/DD Fading Notch Position vs. Wavelength');
|
||||
grid on; box on;
|
||||
|
||||
lim = (lambda0.*1e9) - [8, 40];
|
||||
xlim([lim(2) lim(1)]);
|
||||
yticks([56,75,90,112]);
|
||||
|
||||
%% ------------------------------------------------------------
|
||||
% Custom DataTip Template
|
||||
% ------------------------------------------------------------
|
||||
% Add accumulated dispersion value to the DataTip
|
||||
hLine.DataTipTemplate.DataTipRows(1).Label = 'λ [nm]';
|
||||
hLine.DataTipTemplate.DataTipRows(2).Label = 'f_{null} [GHz]';
|
||||
|
||||
% Create a new row for Dacc
|
||||
dRow = dataTipTextRow('D_{acc} [ps/nm]', Dacc);
|
||||
hLine.DataTipTemplate.DataTipRows(end+1) = dRow;
|
||||
|
||||
%% ------------------------------------------------------------
|
||||
% Helper function: lambda_for_first_null_full
|
||||
% ------------------------------------------------------------
|
||||
function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0)
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³)
|
||||
|
||||
lambda_min = 1260e-9;
|
||||
lambda_max = 1360e-9;
|
||||
|
||||
f_target = f_target(:);
|
||||
N = numel(f_target);
|
||||
|
||||
lambda_vec = zeros(N,1);
|
||||
Dacc_vec = zeros(N,1);
|
||||
|
||||
for k = 1:N
|
||||
RHS = c * 0.5 / (f_target(k)^2 * L);
|
||||
|
||||
fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS;
|
||||
|
||||
try
|
||||
lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]);
|
||||
catch
|
||||
lambda_sol = lambda_min;
|
||||
end
|
||||
|
||||
lambda_sol = min(max(lambda_sol, lambda_min), lambda_max);
|
||||
lambda_vec(k) = lambda_sol;
|
||||
|
||||
D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km)
|
||||
Dacc_val = D_lambda * (L/1000); % ps/nm
|
||||
Dacc_val = min(max(Dacc_val, -100), 100);
|
||||
|
||||
Dacc_vec(k) = Dacc_val;
|
||||
end
|
||||
end
|
||||
55
Functions/Theory/dispersion_contour.m
Normal file
55
Functions/Theory/dispersion_contour.m
Normal file
@@ -0,0 +1,55 @@
|
||||
% Gitter für lambda0 und S0
|
||||
lambda0_vec = linspace(1260,1360,200);
|
||||
S0_vec = linspace(0.06,0.1,200);
|
||||
[Lambda0, S0] = meshgrid(lambda0_vec, S0_vec);
|
||||
|
||||
% Festen Betriebsparameter
|
||||
lambda = 1293; % nm
|
||||
L = 1; % km
|
||||
|
||||
% Dispersion berechnen (lineare Näherung)
|
||||
D = S0 .* ( lambda - Lambda0 ) * L;
|
||||
% D = (S0./4) .* ( lambda - (Lambda0.^4)./(lambda^3) ) * L;
|
||||
|
||||
%% 2D-Konturplot nur mit Linien und Text
|
||||
figure('Color','w');
|
||||
hold on
|
||||
|
||||
% Konturlinien
|
||||
numLevels = 10;
|
||||
levels = linspace(min(D(:)), max(D(:)), numLevels);
|
||||
[C,h] = contour(S0, Lambda0, D, levels, ...
|
||||
'LineWidth',1.5, ...
|
||||
'ShowText','on', ...
|
||||
'LabelFormat','%0.1f');
|
||||
|
||||
% cbrewer2-Colormap für die Linien
|
||||
cmap = cbrewer2('div','RdYlGn', numLevels);
|
||||
colormap(cmap);
|
||||
|
||||
% Achsenlinien
|
||||
% yline(1310, '--k','ZDW_{mean}','LabelVerticalAlignment','top','LabelHorizontalAlignment','center');
|
||||
x0 = 0.09;
|
||||
% xline(x0, '--k','S_{0}','LabelHorizontalAlignment','left');
|
||||
|
||||
% Gaussian auf der x-Linie (S0 = 0.09)
|
||||
mu_zwd = 1310; % nm
|
||||
sigma_zwd = 2; % nm
|
||||
zwd_vals = linspace(min(lambda0_vec), max(lambda0_vec), 500);
|
||||
% PDF berechnen
|
||||
gauss_pdf = (1/(sigma_zwd*sqrt(2*pi))) * exp(-0.5*((zwd_vals-mu_zwd)/sigma_zwd).^2);
|
||||
% Normieren und auf eine sichtbare Breite skalieren
|
||||
scale = 0.005; % passt die Maximal-Auslenkung in x-Richtung an
|
||||
x_gauss = x0 + (gauss_pdf/max(gauss_pdf)) * scale;
|
||||
|
||||
% Plot
|
||||
% plot(x_gauss, zwd_vals, 'LineWidth',2);
|
||||
|
||||
% Achsenbeschriftung & Titel
|
||||
% Achsenbeschriftung & Titel
|
||||
xlabel('S0 [ps / nm2 km]', 'FontSize', 12);
|
||||
ylabel('ZDW [nm]', 'FontSize', 12);
|
||||
title (sprintf('Dispersion: %d km; %d nm', L, lambda), 'FontSize', 14);
|
||||
|
||||
grid on
|
||||
hold off
|
||||
146
Functions/Theory/dispersion_contour_bandwidth_lambda.m
Normal file
146
Functions/Theory/dispersion_contour_bandwidth_lambda.m
Normal file
@@ -0,0 +1,146 @@
|
||||
%% ------------------------------------------------------------
|
||||
% Contour plot: λ_null as function of bandwidth (f_target) and reach (L)
|
||||
% ------------------------------------------------------------
|
||||
|
||||
% Parameters
|
||||
lambda0 = 1310e-9; % [m]
|
||||
S0 = 0.08; % [ps/(nm²·km)]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
% Sweep dimensions
|
||||
f_targets = linspace(50e9, 120e9, 100); % [Hz] (x-axis)
|
||||
L_values = linspace(0.5e3, 10e3, 100); % [m] (y-axis)
|
||||
|
||||
lambda_surface = zeros(numel(L_values), numel(f_targets));
|
||||
Dacc_surface = zeros(numel(L_values), numel(f_targets));
|
||||
|
||||
% Outer loop over fiber length (since L must be scalar)
|
||||
for iL = 1:numel(L_values)
|
||||
L = L_values(iL);
|
||||
[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0);
|
||||
lambda_vec = 2*abs(lambda0 - lambda_vec);
|
||||
|
||||
if 0
|
||||
fprintf('\n- %d km ------------------------------------\n',L);
|
||||
fprintf(' f_null [GHz] lambda [nm] Dacc [ps/nm]\n');
|
||||
fprintf('----------------------------------------------\n');
|
||||
fprintf('%10.1f %8.2f %+8.3f\n',[f_targets(:)/1e9, lambda_vec(:)*1e9, Dacc_vec(:)].');
|
||||
fprintf('----------------------------------------------\n\n');
|
||||
end
|
||||
|
||||
lambda_surface(iL, :) = lambda_vec; % λ for each f_target
|
||||
Dacc_surface(iL, :) = Dacc_vec; % corresponding accumulated dispersion
|
||||
end
|
||||
|
||||
% Convert for plotting
|
||||
lambda_surface_nm = lambda_surface * 1e9; % [nm]
|
||||
L_km = L_values / 1000; % [km]
|
||||
f_GHz = f_targets / 1e9; % [GHz]
|
||||
|
||||
%% Contour plot
|
||||
figure('Color','w');
|
||||
|
||||
% Define wavelength contour levels [nm]
|
||||
lambda_levels = [1260:10:1290, 1290:5:1300, 1300:2.5:1310];
|
||||
lambda_levels = [100:-20:50, 50:-10:30,30:-5:0];
|
||||
|
||||
% Contour plot
|
||||
contour(f_GHz, L_km, lambda_surface_nm, lambda_levels, ...
|
||||
'LineWidth', 1.5, ...
|
||||
'ShowText', 'on', ...
|
||||
'LabelFormat', '%.0f nm');
|
||||
|
||||
% Colormap and colorbar
|
||||
colormap((cbrewer2('RdYlGn',100)));
|
||||
colorbar;
|
||||
clim([0 100]);
|
||||
|
||||
% Axis formatting
|
||||
xlabel('Signal Bandwidth [GHz]');
|
||||
ylabel('Fiber length [km]');
|
||||
legend('$\Delta \lambda$')
|
||||
|
||||
% X-axis ticks at 56 : 16 : 150 GHz
|
||||
xticks(56:8:150);
|
||||
|
||||
grid on; box on;
|
||||
|
||||
|
||||
%% Optional: overlay accumulated-dispersion contours
|
||||
if 0
|
||||
hold on;
|
||||
[CS, h] = contour(f_GHz, L_km, Dacc_surface, 10, 'k--', 'LineWidth', 0.8);
|
||||
clabel(CS, h, 'Color','k', 'FontSize',8);
|
||||
end
|
||||
|
||||
function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0)
|
||||
% lambda_for_first_null_full (stable, single-branch + validity checks)
|
||||
% --------------------------------------------------------------------
|
||||
% Computes the wavelength(s) at which the first IM/DD fading null
|
||||
% occurs at frequency/ies f_target using the full dispersion model:
|
||||
%
|
||||
% D(lambda) = (S0/4)*(lambda - lambda0^4 / lambda^3)
|
||||
%
|
||||
% Restricted to the NORMAL-dispersion branch (λ < λ0),
|
||||
% and valid only in the O-band (1260–1360 nm).
|
||||
%
|
||||
% Inputs:
|
||||
% f_target - scalar or vector of target null frequencies [Hz]
|
||||
% L - fiber length [m]
|
||||
% lambda0 - zero-dispersion wavelength (ZDW) [m]
|
||||
% S0 - dispersion slope at ZDW [ps/(nm²·km)]
|
||||
%
|
||||
% Outputs:
|
||||
% lambda_vec - wavelength(s) [m] where first null occurs (clamped to O-band)
|
||||
% Dacc_vec - accumulated dispersion(s) [ps/nm] (NaN if out of valid range)
|
||||
% --------------------------------------------------------------------
|
||||
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³)
|
||||
|
||||
% Define O-band boundaries (in meters)
|
||||
lambda_min = 1255e-9;
|
||||
lambda_max = 1361e-9;
|
||||
|
||||
% Force column vector
|
||||
f_target = f_target(:);
|
||||
N = numel(f_target);
|
||||
|
||||
lambda_vec = NaN(N,1);
|
||||
Dacc_vec = NaN(N,1);
|
||||
|
||||
for k = 1:N
|
||||
RHS = c * 0.5 / (f_target(k)^2 * L);
|
||||
|
||||
% Normal-dispersion branch (λ < λ0)
|
||||
fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS;
|
||||
|
||||
% Limit the search to [λ_min, λ0)
|
||||
try
|
||||
lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]);
|
||||
catch
|
||||
% If the zero is not within bounds, skip this point
|
||||
lambda_sol = NaN;
|
||||
end
|
||||
|
||||
% Validate solution
|
||||
if isnan(lambda_sol) || lambda_sol < lambda_min || lambda_sol > lambda_max
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
continue
|
||||
end
|
||||
|
||||
% Compute D(lambda) and accumulated dispersion
|
||||
D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km)
|
||||
Dacc_val = D_lambda * (L/1000); % ps/nm
|
||||
|
||||
% Sanity bound on dispersion (avoid unphysical > ±100 ps/nm)
|
||||
if abs(Dacc_val) > 100
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
else
|
||||
lambda_vec(k) = lambda_sol;
|
||||
Dacc_vec(k) = Dacc_val;
|
||||
end
|
||||
end
|
||||
end
|
||||
38
Functions/Theory/dispersion_first_notch_10km.m
Normal file
38
Functions/Theory/dispersion_first_notch_10km.m
Normal file
@@ -0,0 +1,38 @@
|
||||
%% ------------------------------------------------------------
|
||||
% Plot: Maximum usable IM/DD bandwidth vs wavelength
|
||||
% ------------------------------------------------------------
|
||||
|
||||
% Fiber and dispersion parameters
|
||||
lambda0 = 1310e-9; % [m]
|
||||
S0 = 0.08; % [ps/(nm²·km)]
|
||||
L = 10000; % [m]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
% Wavelength range around ZDW
|
||||
lambda_vec = linspace(1250e-9, 1350e-9, 200); % [m]
|
||||
|
||||
% Compute D(lambda) using full model
|
||||
lambda_nm = lambda_vec * 1e9;
|
||||
lambda0_nm = lambda0 * 1e9;
|
||||
D_lambda = (S0/4) .* (lambda_nm - (lambda0_nm.^4) ./ (lambda_nm.^3)); % [ps/(nm·km)]
|
||||
|
||||
% Convert D to [s/m²]
|
||||
D_si = D_lambda * 1e-6;
|
||||
|
||||
% Compute first null frequency (f₀) for each wavelength
|
||||
f_null = sqrt(c*(0.5) ./ (abs(D_si).*lambda_vec.^2*L)); % [Hz]
|
||||
|
||||
% Plot
|
||||
figure('Color','w');
|
||||
plot(lambda_vec*1e9, f_null/1e9, 'LineWidth', 1.6);
|
||||
grid on; box on;
|
||||
xlabel('Wavelength [nm]');
|
||||
ylabel('First Fading Null Frequency [GHz]');
|
||||
title(sprintf('IM/DD Bandwidth Limit vs. Wavelength (L = %.1f km)', L/1000));
|
||||
|
||||
% Highlight useful bandwidth thresholds
|
||||
yline(25, '--', '25 GHz','Color',[0.4 0.4 0.4],'LabelHorizontalAlignment','left');
|
||||
yline(50, '--', '50 GHz','Color',[0.2 0.6 0.2],'LabelHorizontalAlignment','left');
|
||||
yline(100,'--', '100 GHz','Color',[0.6 0.2 0.2],'LabelHorizontalAlignment','left');
|
||||
|
||||
legend('First fading notch (f_{null})','Location','best');
|
||||
165
Functions/Theory/dispersion_power_fading.m
Normal file
165
Functions/Theory/dispersion_power_fading.m
Normal file
@@ -0,0 +1,165 @@
|
||||
%% Chromatic Dispersion Power Fading Demonstration
|
||||
% ------------------------------------------------------------
|
||||
% This script computes and visualizes power fading after
|
||||
% photodiode detection caused by chromatic dispersion in IM/DD links.
|
||||
%
|
||||
% It also determines the wavelength λ that produces the first
|
||||
% fading null at a specified RF frequency f_target using the
|
||||
% full physical dispersion model:
|
||||
%
|
||||
% D(λ) = (S0/4) * (λ - λ0^4 / λ^3)
|
||||
%
|
||||
% and compares the analytic null frequency with simulation.
|
||||
% ------------------------------------------------------------
|
||||
|
||||
% clear; close all; clc;
|
||||
|
||||
%% Fiber and wavelength parameters
|
||||
lambda0 = 1310e-9; % Zero-dispersion wavelength (ZDW) [m]
|
||||
S0 = 0.08; % Dispersion slope at ZDW [ps/(nm^2·km)]
|
||||
L = 10000; % Fiber length [m]
|
||||
alpha_dB = 0; % Attenuation [dB/m] (ignored here)
|
||||
|
||||
%% Target null frequency
|
||||
f_targets = linspace(55e9,58e9,10);
|
||||
f_targets = 56e9;
|
||||
% f_targets = 80e9;
|
||||
% Compute wavelength that gives the first null at f_target
|
||||
[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0);
|
||||
% lambda_vec = 1293e-9;
|
||||
|
||||
|
||||
fprintf('\n----------------------------------------------\n');
|
||||
fprintf(' f_null [GHz] lambda [nm] Dacc [ps/nm]\n');
|
||||
fprintf('----------------------------------------------\n');
|
||||
fprintf('%10.1f %8.2f %+8.3f\n',[f_targets(:)/1e9, lambda_vec(:)*1e9, Dacc_vec(:)].');
|
||||
fprintf('----------------------------------------------\n\n');
|
||||
|
||||
%% Frequency grid
|
||||
f_simu = 500e9; % Simulation bandwidth [Hz]
|
||||
N_freq = 500000;
|
||||
faxis = linspace(-f_simu/2, f_simu/2, N_freq);
|
||||
|
||||
%% Derived fiber parameters
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/m³
|
||||
|
||||
% Convert wavelengths to nm for the D(lambda) model
|
||||
lambda_nm = lambda_vec(end) * 1e9;
|
||||
lambda0_nm = lambda0 * 1e9;
|
||||
|
||||
% Dispersion parameter [ps/(nm·km)]
|
||||
D_lambda = (S0/4) * (lambda_nm - (lambda0_nm^4)/(lambda_nm^3));
|
||||
|
||||
% Convert to [s/m²]
|
||||
D_si = D_lambda * 1e-6;
|
||||
|
||||
% β2 in [s²/m]
|
||||
b2 = -D_si * lambda_vec(end)^2 / (2*pi*c);
|
||||
|
||||
%% IM/DD intensity response (simulation)
|
||||
phi = 2*pi^2*b2*faxis.^2*L;
|
||||
H_field_pos = exp(-1j*phi); % +f sideband
|
||||
H_field_neg = exp(+1j*phi); % -f sideband
|
||||
H_intensity = 0.5 * (H_field_pos + H_field_neg); % PD beating term
|
||||
H_sim = abs(H_intensity);
|
||||
|
||||
%% Theoretical analytical IM/DD response
|
||||
phi = 2*pi^2 * abs(b2) * faxis.^2 * L;
|
||||
H_theoretical = abs(cos(phi));
|
||||
|
||||
%% Analytic first null (for verification)
|
||||
f_null_analytic = sqrt(c*(0.5)/(abs(D_si)*lambda_vec(end)^2*L));
|
||||
fprintf('Analytic first null from D,λ,L: %.2f GHz\n\n', f_null_analytic/1e9);
|
||||
|
||||
%% Plot
|
||||
cols = linspecer(5);
|
||||
figure('Color','w'); hold on; grid on; box on;
|
||||
plot(faxis*1e-9, 10*log10(H_sim), 'DisplayName','$|H_{sim}|$ (IM/DD simulation)','Color',cols(1,:));
|
||||
plot(faxis*1e-9, 10*log10(H_theoretical), 'DisplayName','|cos($\phi$)| (theory)','Color',cols(2,:),'LineStyle','--');
|
||||
xline(f_targets(end)/1e9,'k:','LineWidth',1.2,'DisplayName','Target null (56 GHz)');
|
||||
xline(f_null_analytic/1e9,'Color',[0.2 0.6 0.2],'LineStyle','-.','LineWidth',1.2,'DisplayName','Analytic null');
|
||||
xlabel('Frequency [GHz]');
|
||||
ylabel('Magnitude [dB]');
|
||||
title(sprintf('Power Fading for %.2f nm, L = %.1f km',lambda_nm,L/1000));
|
||||
legend('Location','best'); ylim([-30 0]);
|
||||
|
||||
%% Plot Bandwidth vs Lambda max
|
||||
|
||||
figure();
|
||||
hold on;
|
||||
plot(lambda_vec.*1e6,f_targets.*1e-9)
|
||||
xlabel('wavelength');
|
||||
ylabel('max. Bandwidth')
|
||||
|
||||
function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0)
|
||||
% lambda_for_first_null_full (stable, single-branch + validity checks)
|
||||
% --------------------------------------------------------------------
|
||||
% Computes the wavelength(s) at which the first IM/DD fading null
|
||||
% occurs at frequency/ies f_target using the full dispersion model:
|
||||
%
|
||||
% D(lambda) = (S0/4)*(lambda - lambda0^4 / lambda^3)
|
||||
%
|
||||
% Restricted to the NORMAL-dispersion branch (λ < λ0),
|
||||
% and valid only in the O-band (1260–1360 nm).
|
||||
%
|
||||
% Inputs:
|
||||
% f_target - scalar or vector of target null frequencies [Hz]
|
||||
% L - fiber length [m]
|
||||
% lambda0 - zero-dispersion wavelength (ZDW) [m]
|
||||
% S0 - dispersion slope at ZDW [ps/(nm²·km)]
|
||||
%
|
||||
% Outputs:
|
||||
% lambda_vec - wavelength(s) [m] where first null occurs (clamped to O-band)
|
||||
% Dacc_vec - accumulated dispersion(s) [ps/nm] (NaN if out of valid range)
|
||||
% --------------------------------------------------------------------
|
||||
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³)
|
||||
|
||||
% Define O-band boundaries (in meters)
|
||||
lambda_min = 1255e-9;
|
||||
lambda_max = 1361e-9;
|
||||
|
||||
% Force column vector
|
||||
f_target = f_target(:);
|
||||
N = numel(f_target);
|
||||
|
||||
lambda_vec = NaN(N,1);
|
||||
Dacc_vec = NaN(N,1);
|
||||
|
||||
for k = 1:N
|
||||
RHS = c * 0.5 / (f_target(k)^2 * L);
|
||||
|
||||
% Normal-dispersion branch (λ < λ0)
|
||||
fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS;
|
||||
|
||||
% Limit the search to [λ_min, λ0)
|
||||
try
|
||||
lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]);
|
||||
catch
|
||||
% If the zero is not within bounds, skip this point
|
||||
lambda_sol = NaN;
|
||||
end
|
||||
|
||||
% Validate solution
|
||||
if isnan(lambda_sol) || lambda_sol < lambda_min || lambda_sol > lambda_max
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
continue
|
||||
end
|
||||
|
||||
% Compute D(lambda) and accumulated dispersion
|
||||
D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km)
|
||||
Dacc_val = D_lambda * (L/1000); % ps/nm
|
||||
|
||||
% Sanity bound on dispersion (avoid unphysical > ±100 ps/nm)
|
||||
if abs(Dacc_val) > 100
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
else
|
||||
lambda_vec(k) = lambda_sol;
|
||||
Dacc_vec(k) = Dacc_val;
|
||||
end
|
||||
end
|
||||
end
|
||||
32
Functions/Theory/dispersion_wavelength_notch.m
Normal file
32
Functions/Theory/dispersion_wavelength_notch.m
Normal file
@@ -0,0 +1,32 @@
|
||||
%% Dependency f_null vs Delta_lambda
|
||||
lambda0 = 1310e-9;
|
||||
S0 = 0.09; % ps/(nm²·km)
|
||||
L = 10e3; % m
|
||||
c = physconst('lightspeed');
|
||||
|
||||
% Convert slope to SI
|
||||
S0_si = S0 * 1e3; % s/m³
|
||||
|
||||
Delta_lambda = linspace(5e-9, 80e-9, 300); % [m] detuning
|
||||
f_null_2 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L) );
|
||||
L = 2e3; % m
|
||||
f_null_10 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L) );
|
||||
|
||||
cols = cbrewer2('Paired',10);
|
||||
figure('Color','w');hold on
|
||||
cnt = 2;
|
||||
for L = 10%[2,5,10]
|
||||
f_null_10 = sqrt( c * 0.5 ./ (S0_si .* abs(Delta_lambda) .* lambda0.^2 .* L*1e3) );
|
||||
plot(1310-Delta_lambda*1e9, f_null_10/1e9, 'LineWidth',2,'DisplayName',sprintf('%d km',L),'Color',cols(cnt,:));
|
||||
cnt = cnt+2;
|
||||
end
|
||||
yticks([56,75,90,112])
|
||||
tickse = 1310-[7.5, 12, 17, 31.5];
|
||||
xticks(flip(tickse));
|
||||
|
||||
xlabel('$\Delta \lambda$ from ZDW [nm]');
|
||||
ylabel('$F_{null}$ [GHz]');
|
||||
grid on; box on;
|
||||
lim=1310-[5,35];
|
||||
xlim([lim(2) lim(1)]);
|
||||
ylim([40,130])
|
||||
111
Functions/Theory/dispersion_wdm.m
Normal file
111
Functions/Theory/dispersion_wdm.m
Normal file
@@ -0,0 +1,111 @@
|
||||
%% ============================================================
|
||||
% IM/DD Fading Notch Design Map
|
||||
% Shows λ_null vs. bandwidth (f_target) and fiber length (L)
|
||||
% ============================================================
|
||||
|
||||
clear; close all; clc;
|
||||
|
||||
%% Parameters
|
||||
lambda0 = 1310e-9; % Zero-dispersion wavelength [m]
|
||||
S0 = 0.08; % Dispersion slope at ZDW [ps/(nm²·km)]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
% Frequency and length sweep
|
||||
f_targets = linspace(20e9, 140e9, 80); % [Hz] → x-axis
|
||||
L_values = linspace(0.5e3, 12e3, 80); % [m] → y-axis
|
||||
|
||||
% Preallocate result matrices
|
||||
lambda_surface = zeros(numel(L_values), numel(f_targets));
|
||||
Dacc_surface = zeros(numel(L_values), numel(f_targets));
|
||||
|
||||
%% Compute λ_null and Dacc for each (f_target, L)
|
||||
for iL = 1:numel(L_values)
|
||||
L = L_values(iL);
|
||||
[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0);
|
||||
lambda_surface(iL, :) = lambda_vec; % [m]
|
||||
Dacc_surface(iL, :) = Dacc_vec; % [ps/nm]
|
||||
end
|
||||
|
||||
%% Convert to display units
|
||||
lambda_surface_nm = lambda_surface * 1e9; % [nm]
|
||||
L_km = L_values / 1000; % [km]
|
||||
f_GHz = f_targets / 1e9; % [GHz]
|
||||
|
||||
%% ------------------------------------------------------------
|
||||
% Contour plot (λ_null as function of f_null and L)
|
||||
% ------------------------------------------------------------
|
||||
figure('Color','w');
|
||||
|
||||
% Define wavelength contour levels [nm]
|
||||
lambda_levels = [1260:10:1290, 1290:5:1300, 1300:2:1310];
|
||||
|
||||
contourf(f_GHz, L_km, lambda_surface_nm, lambda_levels, ...
|
||||
'LineWidth', 1.5, ...
|
||||
'ShowText', 'on', ...
|
||||
'LabelFormat', '%1.1d nm');
|
||||
|
||||
% Colormap and colorbar
|
||||
colormap(flip(cbrewer2('RdYlGn',100)));
|
||||
clim([1260 1310]);
|
||||
% c = colorbar;
|
||||
% ylabel(c, 'λ_{null} [nm]', 'Rotation', 90);
|
||||
|
||||
% Axis formatting
|
||||
xlabel('Signal Bandwidth [GHz]');
|
||||
ylabel('Fiber length L [km]');
|
||||
% X-axis ticks (every 16 GHz starting at 56 GHz)
|
||||
xticks(56:8:120);
|
||||
xlim([56,120])
|
||||
grid on; box on;
|
||||
|
||||
%% Optional overlay: accumulated dispersion contours
|
||||
hold on;
|
||||
[CS, h] = contour(f_GHz, L_km, Dacc_surface, 10, 'k--', 'LineWidth', 0.8);
|
||||
clabel(CS, h, 'Color','k', 'FontSize',8);
|
||||
legend('λ_{null} contours','|D_{acc}| [ps/nm]','Location','best');
|
||||
|
||||
%% ============================================================
|
||||
% Helper function: lambda_for_first_null_full
|
||||
% Stable, single-branch, clamped to O-band
|
||||
% ============================================================
|
||||
function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0)
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³)
|
||||
|
||||
% Define O-band boundaries (in meters)
|
||||
lambda_min = 1260e-9;
|
||||
lambda_max = 1360e-9;
|
||||
|
||||
% Force column vector
|
||||
f_target = f_target(:);
|
||||
N = numel(f_target);
|
||||
|
||||
lambda_vec = zeros(N,1);
|
||||
Dacc_vec = zeros(N,1);
|
||||
|
||||
for k = 1:N
|
||||
RHS = c * 0.5 / (f_target(k)^2 * L);
|
||||
|
||||
% Normal-dispersion branch (λ < λ0)
|
||||
fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS;
|
||||
|
||||
% Solve within the normal-dispersion range
|
||||
try
|
||||
lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]);
|
||||
catch
|
||||
lambda_sol = lambda_min;
|
||||
end
|
||||
|
||||
% Clamp to O-band range
|
||||
lambda_sol = min(max(lambda_sol, lambda_min), lambda_max);
|
||||
lambda_vec(k) = lambda_sol;
|
||||
|
||||
% Compute D(lambda) and accumulated dispersion
|
||||
D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km)
|
||||
Dacc_val = D_lambda * (L/1000); % ps/nm
|
||||
|
||||
% Clamp to physical range
|
||||
Dacc_val = min(max(Dacc_val, -100), 100);
|
||||
Dacc_vec(k) = Dacc_val;
|
||||
end
|
||||
end
|
||||
120
Functions/Theory/matched_filter_rrc.m
Normal file
120
Functions/Theory/matched_filter_rrc.m
Normal file
@@ -0,0 +1,120 @@
|
||||
%% Matched Filter SNR Demonstration (Correct Timing)
|
||||
% clear; close all; clc;
|
||||
|
||||
%% Parameters
|
||||
M = 4; % QPSK
|
||||
numSymbols = 1e6;
|
||||
sps = 25; % samples per symbol
|
||||
rolloff = 0.5;
|
||||
EbNo_dB = 10;
|
||||
|
||||
%% Generate random data
|
||||
data = randi([0 M-1], numSymbols, 1);
|
||||
txSym = qammod(data, M, 'UnitAveragePower', true);
|
||||
|
||||
%% Root Raised Cosine filters
|
||||
span = 64; % filter span in symbols
|
||||
rrcTx = rcosdesign(rolloff, span, sps, 'sqrt');
|
||||
rrcRx = rrcTx; % matched filter
|
||||
|
||||
txSignal2 = ifft(fft(rrcTx).*fft(txSym));
|
||||
|
||||
%% Transmit filtering (includes upsampling)
|
||||
txSignal = upfirdn(txSym, rrcTx, sps, 1);
|
||||
|
||||
%% AWGN channel
|
||||
rxSignal = awgn(txSignal, EbNo_dB + 10*log10(sps), 'measured');
|
||||
|
||||
%% Receiver matched filter
|
||||
rxFilt = conv(rxSignal, rrcRx, 'same');
|
||||
|
||||
%% Symbol timing (group delay compensation)
|
||||
delay = span * sps / 2; % total delay per filter is span*sps/2
|
||||
rxAligned = rxFilt(delay+1 : end-delay);
|
||||
|
||||
%% Downsample to symbol rate
|
||||
rxSampled = rxAligned(1:sps:end);
|
||||
|
||||
%% Align lengths
|
||||
L = min(length(rxSampled), length(txSym));
|
||||
rxSampled = rxSampled(1:L);
|
||||
txSym = txSym(1:L);
|
||||
|
||||
%% Decision and BER
|
||||
rxSym = qamdemod(rxSampled, M, 'UnitAveragePower', true);
|
||||
[~, ber] = biterr(data(1:L), rxSym);
|
||||
|
||||
%% Compute effective SNR
|
||||
snr_meas = 10*log10(mean(abs(txSym).^2) / mean(abs(txSym - rxSampled).^2));
|
||||
|
||||
fprintf('Measured BER: %.3e | Effective SNR: %.2f dB\n', ber, snr_meas);
|
||||
|
||||
|
||||
%% Eye diagrams
|
||||
eyediagram(rxSignal(1:4000), 2*sps);
|
||||
title('Received Signal (Before Matched Filter)');
|
||||
eyediagram(rxFilt(1:4000), 2*sps);
|
||||
title('After Matched Filter (RRC)');
|
||||
|
||||
%% --------------------------------------------------------------
|
||||
%% Spectrum analysis of shaped and filtered signals
|
||||
%% --------------------------------------------------------------
|
||||
|
||||
Fs = sps; % normalized sample rate (symbol rate = 1)
|
||||
Nfft = 2^16; % FFT size for high resolution
|
||||
f = (-Nfft/2:Nfft/2-1)/Nfft * Fs; % normalized frequency axis (symbol-rate units)
|
||||
|
||||
% Spectra
|
||||
S_tx = 20*log10(abs(fftshift(fft(txSignal, Nfft)))/max(abs(fft(txSignal, Nfft))));
|
||||
S_rx = 20*log10(abs(fftshift(fft(rxFilt, Nfft)))/max(abs(fft(rxFilt, Nfft))));
|
||||
|
||||
% Unshaped (rectangular pulse) for comparison
|
||||
txRect_unf = upfirdn(txSym, ones(1, sps), sps, 1);
|
||||
S_rect = 20*log10(abs(fftshift(fft(txRect_unf, Nfft)))/max(abs(fft(txRect_unf, Nfft))));
|
||||
|
||||
% Plot
|
||||
figure('Name','Spectrum after Pulse Shaping');
|
||||
plot(f, S_rect, '--', 'DisplayName','Rectangular pulse');
|
||||
hold on;
|
||||
plot(f, S_tx, 'LineWidth',1.4, 'DisplayName','RRC (TX)');
|
||||
plot(f, S_rx, 'LineWidth',1.4, 'DisplayName','After Matched Filter');
|
||||
grid on;
|
||||
xlabel('Normalized frequency (× symbol rate)');
|
||||
ylabel('Magnitude [dB]');
|
||||
title('Spectra Before and After RRC Pulse Shaping');
|
||||
legend('Location','best');
|
||||
xlim([-1.5 1.5]);
|
||||
ylim([-60 0]);
|
||||
|
||||
|
||||
%% --------------------------------------------------------------
|
||||
%% Visualization: RRC and Raised-Cosine Frequency Responses
|
||||
%% --------------------------------------------------------------
|
||||
|
||||
% Frequency axis for plotting (normalized to symbol rate)
|
||||
Nfft = 4096;
|
||||
H_rrc = fftshift(fft(rrcTx, Nfft));
|
||||
H_rc = H_rrc .* H_rrc; % cascade of TX and RX RRC = full RC
|
||||
|
||||
f = linspace(-0.5, 0.5, Nfft); % normalized frequency (symbol-rate units)
|
||||
|
||||
figure('Name','Raised Cosine Filter Characteristics');
|
||||
|
||||
subplot(2,1,1);
|
||||
plot(f, 20*log10(abs(H_rrc)/max(abs(H_rrc))), 'LineWidth', 1.5);
|
||||
hold on;
|
||||
plot(f, 20*log10(abs(H_rc)/max(abs(H_rc))), '--', 'LineWidth', 1.5);
|
||||
grid on;
|
||||
xlabel('Normalized frequency (× symbol rate)');
|
||||
ylabel('Magnitude [dB]');
|
||||
title(sprintf('RRC (rolloff = %.2f) and Full RC Spectrum', rolloff));
|
||||
legend('Root Raised Cosine','Raised Cosine (TX×RX)','Location','best');
|
||||
ylim([-60 5]);
|
||||
|
||||
subplot(2,1,2);
|
||||
t = (-span*sps/2 : span*sps/2) / sps; % time axis in symbol durations
|
||||
plot(t, rrcTx, 'LineWidth', 1.5);
|
||||
grid on;
|
||||
xlabel('Time [symbols]');
|
||||
ylabel('Amplitude');
|
||||
title('RRC Impulse Response');
|
||||
40
Functions/Theory/power_fading.m
Normal file
40
Functions/Theory/power_fading.m
Normal file
@@ -0,0 +1,40 @@
|
||||
%% ============================================================
|
||||
% Minimal IM/DD Power Fading Plot
|
||||
% ============================================================
|
||||
|
||||
|
||||
%% Fiber and system parameters
|
||||
lambda0 = 1310e-9; % zero-dispersion wavelength [m]
|
||||
lambda = 1275e-9; % operating wavelength [m]
|
||||
S0 = 0.08; % dispersion slope [ps/(nm²·km)]
|
||||
L = 10e3; % fiber length [m]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
%% Derived quantities
|
||||
S0_si = S0 * 1e3; % → s/m³
|
||||
D_lambda = (S0/4) * (lambda*1e9 - (lambda0*1e9)^4/(lambda*1e9)^3); % ps/(nm·km)
|
||||
D_si = D_lambda * 1e-6; % → s/m²
|
||||
b2 = -D_si * lambda^2 / (2*pi*c); % s²/m
|
||||
|
||||
%% Frequency grid
|
||||
f_max = 150e9;
|
||||
f = linspace(0, f_max, 4000); % [Hz]
|
||||
|
||||
%% IM/DD transfer function (power fading)
|
||||
phi = 2*pi^2 * b2 * f.^2 * L;
|
||||
H = abs(cos(phi));
|
||||
|
||||
%% Plot
|
||||
figure('Color','w');
|
||||
plot(f/1e9, 10*log10(H), 'LineWidth', 1.8);
|
||||
grid on; box on;
|
||||
xlabel('Frequency [GHz]');
|
||||
ylabel('Magnitude [dB]');
|
||||
title(sprintf('IM/DD Power Fading |H| for λ = %.1f nm, L = %.1f km', lambda*1e9, L/1000));
|
||||
ylim([-30 0]);
|
||||
|
||||
%% Mark analytic first-null frequency
|
||||
f_null = sqrt(c*(0.5)/(abs(D_si)*lambda^2*L));
|
||||
xline(f_null/1e9, 'r--', 'LineWidth', 1.2, ...
|
||||
'Label', sprintf('f_{null}=%.1f GHz', f_null/1e9), ...
|
||||
'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'bottom');
|
||||
11
Functions/Theory/propagation_time.m
Normal file
11
Functions/Theory/propagation_time.m
Normal file
@@ -0,0 +1,11 @@
|
||||
function prop_time = propagation_time(fiber_len_m)
|
||||
|
||||
c = physconst('lightspeed'); % Speed of light in vacuum (m/s)
|
||||
n = 1.46; % Refractive index of the fiber
|
||||
|
||||
% Calculate the propagation speed in the fiber
|
||||
propagation_speed = c / n;
|
||||
|
||||
% Calculate the propagation time
|
||||
prop_time = fiber_len_m ./ propagation_speed;
|
||||
end
|
||||
62
Functions/convert_freq_lambda.m
Normal file
62
Functions/convert_freq_lambda.m
Normal file
@@ -0,0 +1,62 @@
|
||||
%% ============================================================
|
||||
% Wavelength–Frequency Conversion Utilities
|
||||
% ============================================================
|
||||
|
||||
% Example usage:
|
||||
% f = lambda2freq(1310e-9); % 1310 nm -> Hz
|
||||
% lambda = freq2lambda(224e12); % 224 THz -> m
|
||||
% delta_lambda_nm = df2dlambda(224e12, 400e9); % 400 GHz @ 224 THz -> nm
|
||||
% delta_freq_GHz = dlambda2df(1310e-9, 3.45); % 3.45 nm @ 1310 nm -> GHz
|
||||
|
||||
%% ---- Core conversion functions ----
|
||||
function f = lambda2freq(lambda)
|
||||
% lambda2freq Convert wavelength [m] → frequency [Hz]
|
||||
c = physconst('lightspeed');
|
||||
f = c ./ lambda;
|
||||
end
|
||||
|
||||
function lambda = freq2lambda(f)
|
||||
% freq2lambda Convert frequency [Hz] → wavelength [m]
|
||||
c = physconst('lightspeed');
|
||||
lambda = c ./ f;
|
||||
end
|
||||
|
||||
%% ---- Differential conversions ----
|
||||
function d_lambda = df2dlambda(f_center, d_f)
|
||||
% df2dlambda Convert frequency spacing Δf [Hz] → wavelength spacing Δλ [m]
|
||||
% around a given center frequency f_center [Hz].
|
||||
% Uses first-order differential: Δλ ≈ (c / f^2) * Δf
|
||||
|
||||
c = physconst('lightspeed');
|
||||
d_lambda = (c ./ (f_center.^2)) .* d_f;
|
||||
end
|
||||
|
||||
function d_f = dlambda2df(lambda_center, d_lambda)
|
||||
% dlambda2df Convert wavelength spacing Δλ [m] → frequency spacing Δf [Hz]
|
||||
% around a given center wavelength λ_center [m].
|
||||
% Uses first-order differential: Δf ≈ (c / λ^2) * Δλ
|
||||
|
||||
c = physconst('lightspeed');
|
||||
d_f = (c ./ (lambda_center.^2)) .* d_lambda;
|
||||
end
|
||||
|
||||
%% ============================================================
|
||||
% Example section (can be commented out)
|
||||
% ============================================================
|
||||
|
||||
if ~isdeployed
|
||||
fprintf('--- Example conversions ---\n');
|
||||
|
||||
lambda_nm = 1310; % nm
|
||||
lambda = lambda_nm * 1e-9; % m
|
||||
f = lambda2freq(lambda); % Hz
|
||||
fprintf('λ = %.1f nm → f = %.3f THz\n', lambda_nm, f/1e12);
|
||||
|
||||
d_f = 2000e9; % 400 GHz spacing
|
||||
d_lambda = df2dlambda(f, d_f); % [m]
|
||||
fprintf('Δf = %.0f GHz @ %.1f nm → Δλ = %.3f nm\n', d_f/1e9, lambda_nm, d_lambda*1e9);
|
||||
|
||||
% Verify reverse direction
|
||||
d_f_back = dlambda2df(lambda, d_lambda);
|
||||
fprintf('Δλ = %.3f nm @ %.1f nm → Δf = %.0f GHz\n', d_lambda*1e9, lambda_nm, d_f_back/1e9);
|
||||
end
|
||||
33
Functions/getFigureSize.m
Normal file
33
Functions/getFigureSize.m
Normal file
@@ -0,0 +1,33 @@
|
||||
% GETFIGURESIZE Retrieve the size of the current MATLAB figure window.
|
||||
% [WIDTH, HEIGHT] = GETFIGURESIZE() returns the width and height of the
|
||||
% current figure in pixels.
|
||||
%
|
||||
% Example:
|
||||
% % Get size of current figure
|
||||
% [w, h] = getFigureSize();
|
||||
% fprintf('Current figure is %d pixels wide and %d pixels tall.\n', w, h);
|
||||
%
|
||||
% Adapt snippet for other figures:
|
||||
% % Suppose H is a handle to any MATLAB figure (existing or new):
|
||||
% H = figure; % or H = <some existing figure handle>;
|
||||
% % Retrieve current size of the active (or any) figure:
|
||||
% [wCur, hCur] = getFigureSize();
|
||||
% % Set the other figure H to match that size, preserving its position:
|
||||
% posH = get(H, 'Position'); % [left, bottom, width, height]
|
||||
% newPos = [posH(1), posH(2), wCur, hCur];
|
||||
% set(H, 'Position', newPos);
|
||||
%
|
||||
% Note:
|
||||
% - Position vector is given as [left, bottom, width, height] in pixels.
|
||||
% - If you want to specify a custom size directly, you can replace wCur/hCur
|
||||
% with desired values.
|
||||
|
||||
function [width, height] = getFigureSize()
|
||||
% Ensure a figure is available
|
||||
fig = gcf;
|
||||
% Get the position vector: [left, bottom, width, height]
|
||||
pos = get(fig, 'Position');
|
||||
% Extract width and height
|
||||
width = pos(3);
|
||||
height = pos(4);
|
||||
end
|
||||
Reference in New Issue
Block a user