Unify batch job processing with a shared runBatch core

This commit is contained in:
Silas Oettinghaus
2026-03-25 11:51:26 +01:00
parent 0ae846d3c3
commit 2691b90d65
12 changed files with 290 additions and 1354 deletions

View File

@@ -0,0 +1,209 @@
function results = runBatch(workerFcn, jobs, options)
%RUNBATCH Execute a list of jobs in serial or parallel.
% results = runBatch(workerFcn, jobs) executes each jobs(i).args cell
% with workerFcn and returns a cell array aligned with the input jobs.
%
% Supported job fields:
% .args cell array of positional arguments for workerFcn
% .label string/char used for progress messages
% .meta arbitrary metadata for wrapper-specific bookkeeping
arguments
workerFcn (1,1) function_handle
jobs (1,:) struct = struct('args', {})
options.mode = processingMode.serial
options.waitbar (1,1) logical = true
options.waitbarMessage (1,1) string = "Processing Jobs..."
options.numWorkers (1,1) double {mustBeNonnegative, mustBeInteger} = 0
options.idleTimeout (1,1) double {mustBePositive} = 300
options.cancelExistingQueue (1,1) logical = true
options.resultHandler = []
options.errorHandler = []
end
mode = normalizeProcessingMode(options.mode);
jobs = normalizeJobs(jobs);
nJobs = numel(jobs);
results = cell(1, nJobs);
h = [];
if nJobs == 0
return
end
if options.waitbar
h = waitbar(0, char(options.waitbarMessage));
cleanupWaitbar = onCleanup(@() closeWaitbar(h)); %#ok<NASGU>
end
switch mode
case processingMode.parallel
pool = setupParallelPool(options.numWorkers, options.idleTimeout, options.cancelExistingQueue);
futures = parallel.FevalFuture.empty(nJobs, 0);
for idx = 1:nJobs
fprintf('[%s] Submitted.\n', jobs(idx).label);
futures(idx) = parfeval(pool, workerFcn, 1, jobs(idx).args{:});
end
consumedIdx = false(nJobs, 1);
for completedCount = 1:nJobs
try
[idx, value] = fetchNext(futures);
consumedIdx(idx) = true;
results{idx} = value;
fprintf('[%s] Completed (%d/%d).\n', jobs(idx).label, completedCount, nJobs);
if ~isempty(options.resultHandler)
options.resultHandler(value, jobs(idx), idx);
end
catch fetchErr
idxErr = findErroredFuture(futures, consumedIdx);
if isempty(idxErr)
rethrow(fetchErr);
end
consumedIdx(idxErr) = true;
errInfo = extractFutureError(futures(idxErr));
results{idxErr} = errInfo;
if ~isempty(options.errorHandler)
options.errorHandler(errInfo, jobs(idxErr), idxErr);
else
defaultErrorHandler(errInfo, jobs(idxErr), idxErr);
end
end
updateWaitbar(options.waitbar, h, completedCount, nJobs);
end
case processingMode.serial
for idx = 1:nJobs
try
fprintf('[%s] Running.\n', jobs(idx).label);
value = feval(workerFcn, jobs(idx).args{:});
results{idx} = value;
fprintf('[%s] Completed (%d/%d).\n', jobs(idx).label, idx, nJobs);
if ~isempty(options.resultHandler)
options.resultHandler(value, jobs(idx), idx);
end
catch ME
results{idx} = ME;
if ~isempty(options.errorHandler)
options.errorHandler(ME, jobs(idx), idx);
else
defaultErrorHandler(ME, jobs(idx), idx);
end
end
updateWaitbar(options.waitbar, h, idx, nJobs);
end
otherwise
error('runBatch:UnknownMode', 'Unknown processing mode "%s".', string(mode));
end
end
function mode = normalizeProcessingMode(mode)
if isa(mode, 'processingMode')
return
end
modeString = string(mode);
switch modeString
case "serial"
mode = processingMode.serial;
case "parallel"
mode = processingMode.parallel;
otherwise
error('runBatch:InvalidMode', 'Unsupported processing mode "%s".', modeString);
end
end
function jobs = normalizeJobs(jobs)
if isempty(jobs)
jobs = struct('args', {}, 'label', {}, 'meta', {});
return
end
for idx = 1:numel(jobs)
if ~isfield(jobs, 'args') || isempty(jobs(idx).args)
jobs(idx).args = {};
end
if ~iscell(jobs(idx).args)
error('runBatch:InvalidArgs', 'jobs(%d).args must be a cell array.', idx);
end
if ~isfield(jobs, 'label') || isempty(jobs(idx).label)
jobs(idx).label = sprintf('Job %d', idx);
else
jobs(idx).label = string(jobs(idx).label);
end
if ~isfield(jobs, 'meta')
jobs(idx).meta = struct();
end
end
end
function updateWaitbar(useWaitbar, h, completedCount, totalCount)
if ~useWaitbar || ~isgraphics(h)
return
end
waitbar(completedCount / totalCount, h, ...
sprintf('Completed %d/%d jobs', completedCount, totalCount));
drawnow;
end
function closeWaitbar(h)
if isgraphics(h)
delete(h);
end
end
function pool = setupParallelPool(numWorkers, idleTimeout, cancelExistingQueue)
pool = gcp('nocreate');
if isempty(pool)
if numWorkers > 0
pool = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
else
pool = parpool('local', 'IdleTimeout', idleTimeout);
end
elseif numWorkers > 0 && pool.NumWorkers ~= numWorkers
delete(pool);
pool = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
end
if cancelExistingQueue
queue = pool.FevalQueue;
if ~isempty(queue.QueuedFutures) || ~isempty(queue.RunningFutures)
queuedCount = numel(queue.QueuedFutures) + numel(queue.RunningFutures);
cancelAll(queue);
fprintf('Canceled %d unfinished jobs from the current pool queue.\n', queuedCount);
end
end
end
function idxErr = findErroredFuture(futures, consumedIdx)
readMask = arrayfun(@(future) future.Read, futures).';
idxErr = find(readMask & ~consumedIdx, 1);
end
function errInfo = extractFutureError(future)
errInfo = future.Error;
if iscell(errInfo)
errInfo = errInfo{1};
end
end
function defaultErrorHandler(ME, job, jobIndex)
fprintf('[%s | #%d] ERROR [%s]: %s\n', job.label, jobIndex, ME.identifier, ME.message);
for st = ME.stack'
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
end
end

View File

@@ -27,32 +27,17 @@ nRunIds = numel(run_ids);
nJobsPerRunId = submit_options.wh.getLastLinIndice();
totalJobs = nRunIds * nJobsPerRunId;
% Preallocate
wh = submit_options.wh;
results = cell(nJobsPerRunId, nRunIds);
futures = parallel.FevalFuture.empty(totalJobs,0);
jobIndices = zeros(totalJobs,2);
jobs = repmat(struct('args', {{}}, 'label', "", 'meta', struct()), 1, totalJobs);
% 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];
optionalVars = buildOptionalVars(k, submit_options.wh);
opt = buildOptionalVars(k, submit_options.wh);
futures(jobCounter) = parfeval( ...
p, @dsp_runid, 1, ...
jobs(jobCounter).args = { ...
run_ids(r), ...
"database_type", dsp_options.database_type, ...
"dataBase", dsp_options.dataBase, ...
@@ -61,130 +46,34 @@ switch submit_mode
"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);
"parameters", optionalVars};
jobs(jobCounter).label = sprintf('RunID %d, Job %d', run_ids(r), k);
jobs(jobCounter).meta.runIndex = r;
jobs(jobCounter).meta.jobIndex = k;
jobs(jobCounter).meta.run_id = run_ids(r);
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);
linearResults = runBatch(@dsp_runid, jobs, ...
"mode", submit_mode, ...
"waitbar", submit_options.waitbar, ...
"waitbarMessage", "Processing Jobs...", ...
"numWorkers", 11, ...
"idleTimeout", 300, ...
"cancelExistingQueue", true, ...
"resultHandler", @storeResult, ...
"errorHandler", @handleError);
for idx = 1:totalJobs
r = jobs(idx).meta.runIndex;
k = jobs(idx).meta.jobIndex;
results{k, r} = linearResults{idx};
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, ...
function handleError(ME, job, ~)
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', job.meta.run_id, job.meta.jobIndex, ...
ME.identifier, ME.message);
for st = ME.stack'
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
@@ -202,8 +91,9 @@ wh = submit_options.wh;
end
end
function storeResult(val, jobIndex, wh)
function storeResult(val, job, ~)
if ~isempty(wh)
jobIndex = job.meta.jobIndex;
wh.addValueToStorageByLinIdx(val.ffe_package, 'ffe_package', jobIndex);
wh.addValueToStorageByLinIdx(val.mlse_package, 'mlse_package', jobIndex);
wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex);
@@ -212,24 +102,5 @@ wh = submit_options.wh;
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

View File

@@ -25,7 +25,7 @@ end
figure
hold on
for alpha = uloops.alpha
a=wh.getStoValue('ber',1, [300].*1e9 , 1293, 4, uloops.link_length,alpha,uloops.duob_mode,uloops.decoding_mode);
a=wh.getStoValue('ber',0, [300].*1e9 , 1293, 4, uloops.link_length,alpha,uloops.duob_mode,uloops.decoding_mode);
% ffe = cellfun(@(x) x.ffe_results.metrics.BER, a);
ffe = cellfun(@(x) x.dbt_results.metrics.BER, a);
plot(uloops.link_length,ffe,'DisplayName',sprintf('Alpha: %d',alpha),'LineStyle','-','HandleVisibility','on');

View File

@@ -173,7 +173,7 @@ Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
%%% EQUALIZING
if 0
if 1
% -------------------- FFE --------------------
ffe_order = [50, 0, 0];
eq_ffe = EQ("Ne",ffe_order,"Nb",[0,0,0], ...

View File

@@ -1,181 +0,0 @@
%%% Run parameters
% TX
M = 4;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
random_key = 2;
rcalpha = 0.05;
kover = 8;
vbias_rel = 0.5;
u_pi = 3.2;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1300;
laser_linewidth = logspace(0,6.2,24);
% Channel
link_length = 10;
alpha = 0;
doub_mode = db_mode.no_db;
cols = linspecer(6);
rop = [-6];
bwl = [0.5:0.1:1.5];
fsym = [200:16:256].*1e9;
% nonlin_mod = [0.5:0.01:0.75];
fsym = ones(size(laser_linewidth)).*fsym(1);
nonlin_mod = ones(size(laser_linewidth)).*0.5;
ffe_results = {};
mlse_results_lin= {};
parfor r = 1:length(laser_linewidth)
Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha);
db_precode = 0;
db_encode = 0;
duob_mode = db_mode.no_db;
apply_pulsef = 1;
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym(r),"M",M,"order",18,"useprbs",1,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
"db_precode",db_precode,"db_encode",db_encode,...
"mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process();
El_sig = M8199B("kover",kover).process(Digi_sig);
%%%%% Electrical Driver Amplifier %%%%%%
El_sig = El_sig.normalize("mode","oneone");
%%%%% MODULATE E/O CONVERSION %%%%%
u_pi = 3.2;
vbias = -u_pi*nonlin_mod(r);
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth(r),"randomkey",random_key+1).process(El_sig);
%%%%%% Fiber %%%%%%
mpi = 1;
if mpi
Combined_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
else
% 2) ping pong fiber propagation
mpi_path = 00;
Interference_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",mpi_path*2,"alpha",0,"D",0,"lambda0",1310,"gamma",0).process(Opt_sig);
Interference_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",-30).process(Interference_sig);
[Main_sig,dly] = Opt_sig.delay("delay_meter",mpi_path*2);
% Add
Combined_sig = Main_sig + Interference_sig;
% Cut (due to the delays there is a jump in the signals)
if dly == 0;dly = 1;end
Combined_sig.signal = Combined_sig.signal(ceil(dly):end);
% Fiber
Combined_sig = Fiber("fsimu",Combined_sig.fs,"fiber_length",2,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.08).process(Combined_sig);
end
%%%%%% ROP %%%%%%
Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Combined_sig);
%%%%%% PD Square Law %%%%%%
PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
rx_bwl = 70e9;
PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig);
Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r));
% Symbols.signal = Symbols.signal(1:Scpe_sig_2sps.length/2);
% 2sps
[~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0);
Rx_sig_2sps = Scpe_cell{1};
Rx_sig_2sps = Rx_sig_2sps.normalize("mode","rms");
% 1sps
Scpe_sig_1sps = Scpe_sig.resample("fs_out",1*fsym(r));
[~, Scpe_cell_1sps, ~, found_sync] = Scpe_sig_1sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0);
Rx_sig_1sps = Scpe_cell_1sps{1};
Rx_sig_1sps = Rx_sig_1sps.normalize("mode","rms");
%% RUN DSP
len_tr = 4096*2;
mu_ffe1 = 0.0001;
mu_ffe2 = 0.0008;
mu_ffe3 = 0.001;
mu_dc = 0.005;
% mu_dc = 0;
mu_ffe = [mu_ffe1 mu_ffe2 mu_ffe3];
mu_dfe = 0.0004;
pf_ncoeffs = 1;
ffe_order = [50, 3, 3];
mu_lms = 0.0005;
eq_ = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",2^13,"mu_dd",mu_lms,"mu_tr",mu_lms,"order",50,"sps",2,"dd_mode",1,"adaption_technique","lms");
% eq_ = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",1,"DCmu",0.00,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',0,'trellis_exclusion',0,'trellis_state_mode',2,'debug',0);
[ffe_results{r}, mlse_results_lin{r}] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Rx_sig_2sps, Symbols, Tx_bits, ...
"precode_mode", duob_mode,...
'showAnalysis', 0, ...
"postFFE", [],...
"eth_style_symbol_mapping", 0);
ffe_results{r}.metrics.print;
mlse_results_lin{r}.metrics.print;
end
figure(1);hold on;
plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.BER, ffe_results),'DisplayName','VNLE')
plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.BER, mlse_results_lin),'DisplayName','VNLE+MLSE')
xlabel('Linewidth [GHz]');
ylabel('BER')
set(gca,'YScale','log');
legend;
ylim([1e-5 1e-1]);
beautifyBERplot;
figure();hold on;
plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.AIR.*1e-9, ffe_results),'DisplayName','FFE')
plot(laser_linewidth.*1e-6,cellfun(@(x) x.metrics.AIR.*1e-9, mlse_results_lin),'DisplayName','FFE+MLSE')
xlabel('Linewidth [GHz]');
ylabel('AIR [GBd]')
% set(gca,'YScale','log');
legend;
beautifyBERplot("logscale",0);
figure(); hold on
stem(calcWavelengthPlan(16,400e9,1310),ones(16,1),'DisplayName','16x400','Marker','.','LineWidth',1);
stem(calcWavelengthPlan(8,800e9,1310),ones(8,1),'DisplayName','8x800','Marker','.','LineWidth',1);
stem(calcWavelengthPlan(16,800e9,1310),ones(16,1),'DisplayName','16x800','Marker','.','LineWidth',1);
ylim([0,1.2]);
ylabel('wavelength [nm]');
xlim([1270, 1350])

View File

@@ -1,46 +0,0 @@
% TX
M = 4;
fsym = 180e9;
f_nyquist = fsym/2;
apply_pulsef = 0;
fdac = 2*fsym;%256e9;
fadc = 2*fsym;%256e9;
fdac = 256e9;
fadc = 256e9;
random_key = 1;
db_precode = 0;
emulate_precode = 0;
discard_precode = 0;
db_encode = 0;
% duob_mode = db_mode.db_emulate;
emulate_db = 1;
rcalpha = 0.05;
kover = 16;
vbias_rel = 0.5;
u_pi = 2.9;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1310;
laser_linewidth = 0;
tx_bw_nyquist = 1.5;
% Channel
link_length = 1;
% RX
rop = -8;
rx_bw_nyquist = 0.7;
% EQ
eq_mode = equalizer_structure.vnle_pf_mlse;
ffe_order=[50,0,0];
vnle_order=[50,7,7];
dfe_order = [2 0 0];
len_tr = 4096*2;
mu_ffe = [0.0004 0.0004 0.0004];
mu_dfe = 0.0004;
mu_dc = 0.00;
dfe_ = sum(dfe_order)>0;

View File

@@ -1,24 +0,0 @@
%%%%%% ROP %%%%%%
Rx_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
%%%%%% PD Square Law %%%%%%
Rx_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11).process(Rx_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bw_nyquist.*f_nyquist,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(Rx_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
% Rx_sig.spectrum("displayname",'Analog Rx Spectrum','fignum',100,'normalizeTo0dB',1);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(Rx_sig);

View File

@@ -1,569 +0,0 @@
%%% Run parameters
% TX
M = 4;
m = floor(log2(M)*10)/10;
fsym = 224e9;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
random_key = 2;
rcalpha = 0.05;
kover = 8;
vbias_rel = 0.5;
u_pi = 3.2;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1310;
laser_linewidth = 1e6;
% Channel
link_length = 0;
vnle_order1 = 50;
vnle_order2 = 0;
vnle_order3 = 0;
vnle_order=[vnle_order1,vnle_order2,vnle_order3];
dfe_order = [0 0 0];
alpha = 0;
len_tr = 4096*2;
mu_ffe1 = 0.0001;
mu_ffe2 = 0.0008;
mu_ffe3 = 0.001;
mu_dc = 0.005;
% mu_dc = 0;
mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
mu_dfe = 0.0004;
dfe_ = sum(dfe_order)>0;
doub_mode = db_mode.no_db;
cols = linspecer(6);
rop = [-6];
bwl = [0.5:0.1:1.5];
fsym = [192:16:256].*1e9;
fsym = 208e9;
ber_vnle = [];
ber_mlse = [];
ber_mlse_burg = [];
ber_viterbi = [];
ber_db = [];
ber_db_diff_precoded = [];
gmi_vnle_bitwise = [];
gmi_mlse = [];
gmi_mlse_db = [];
for r = 1:length(fsym)
Pform = Pulseformer("fsym",fsym(r),"fdac",4*fsym(r),"pulse","rc","pulselength",16,"alpha",rcalpha);
db_precode = 0;
db_encode = 0;
duob_mode = db_mode.no_db;
apply_pulsef = 1;
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym(r),"M",M,"order",19,"useprbs",0,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
"db_precode",db_precode,"db_encode",db_encode,...
"mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process();
% El_sig = AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig);
El_sig = M8199B("kover",kover).process(Digi_sig);
% AWG("fdac",fdac,"f_cutoff",fsym(r),"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",0).process(Digi_sig);
%%%%% Low-pass el. components %%%%%%
% tx_bwl = 100e9;
% El_sig = Filter('filtdegree',3,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig);
%%%%% Electrical Driver Amplifier %%%%%%
El_sig = El_sig.normalize("mode","oneone");
% El_sig = El_sig.setPower(1,"dBm");
% figure;histogram(El_sig.signal);
%%%%% MODULATE E/O CONVERSION %%%%%
u_pi = 3.2;
vbias = -u_pi*0.5;
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig);
if 0
figure(15);
hold on
scatter(El_sig.signal(1:100000)+vbias,(abs(Opt_sig.signal(1:100000)).^2)*1e3,0.1,'.','DisplayName','Modulator TF')
xlabel('Input in V')
ylabel('abs(Eopt)2 in mW','Interpreter','latex')
ylim([0 2]);
xlim([-3.2 0]);
Opt_sig.eye(fsym(r),M,"fignum",103837);
end
%%%%%% Fiber %%%%%%
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
%%%%%% ROP %%%%%%
Opt_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
% Opt_sig.eye(fsym(r),M,"fignum",103838);
% % Opt_sig.signal = Opt_sig.signal + 5*abs(mean(Opt_sig.signal));
% Opt_sig.move_it_spectrum("displayname",'Opt Sig after Amp','fignum',1223323);
% Pc = abs(mean(Opt_sig.signal)).^2; % carrier power
% Ptot = mean(abs(Opt_sig.signal).^2); % total power
% Ps = max(Ptot - Pc, eps);
% Pcdb = 10*log10(Pc);
% Psdb = 10*log10(Ps);
%
% cspr_dB = 10*log10(Pc / Ps);
%
% % Minimal in-place CSPR set (real, nonnegative field constraint)
% E = Opt_sig.signal; % real field samples
% target_cspr_dB = 20; % <-- set your target CSPR (dB)
%
% % Decompose into DC + zero-mean waveform
% m = mean(E);
% x0 = E - m; % zero-mean modulation
% Ps0 = mean(x0.^2); % sideband power (fixed if shape kept)
%
% % Current CSPR (for reference)
% Pc_cur = m^2;
% Ptot_cur = mean(E.^2);
% Ps_cur = max(Ptot_cur - Pc_cur, eps);
% cspr_in = 10*log10(Pc_cur / Ps_cur);
%
% % Bias needed for target CSPR, and minimal bias to keep E>=0
% R_tgt = 10^(target_cspr_dB/10); % Pc/Ps
% a_req = sqrt(R_tgt * Ps0); % required DC bias
% a_min = -min(x0); % to avoid negatives everywhere
% a = max(a_req, a_min); % if infeasible, lands at CSPR_min
%
% % Apply bias (preserves waveform shape)
% E_new = a + x0;
%
% % Achieved CSPR
% Pc_new = mean(E_new)^2;
% Ptot_new = mean(E_new.^2);
% Ps_new = max(Ptot_new - Pc_new, eps);
% cspr_out = 10*log10(Pc_new / Ps_new);
%
% % (Optional) show feasibility info
% cspr_min = 10*log10((a_min^2)/max(Ps0,eps));
% disp(table(cspr_in, target_cspr_dB, cspr_min, cspr_out));
%
% % Use E_new as your adjusted field
% Opt_sig.signal = E_new;
%%%%%% PD Square Law %%%%%%
PD_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11,"randomkey",random_key).process(Opt_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
rx_bwl = 70e9;
PD_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(PD_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(PD_sig);
Scpe_sig_2sps = Scpe_sig.resample("fs_out",2*fsym(r));
% Scpe_sig_resampled.signal = Scpe_sig_resampled.signal(1:2*length(Symbols));
[~, Scpe_cell, ~, found_sync] = Scpe_sig_2sps.tsynch("reference", Symbols, "fs_ref", fsym(r), "debug_plots", 0);
Rx_sig = Scpe_cell{1};
Rx_sig = Rx_sig.normalize("mode","rms");
if 1
%Duobinary Targeting
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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);
db_ref_sequence = Duobinary().encode(Symbols);
db_ref_constellation = unique(db_ref_sequence.signal);
[eq_signal, eq_noise] = eq_.process(Rx_sig,db_ref_sequence);
viterbi = 0;
if viterbi
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
mlse_.DIR = [1,1];
[eq_signal_whitened] = mlse_.process(eq_signal);
else
mlse_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling);
mlse_.DIR = [1,1];
[eq_signal_whitened,LLR,gmi_mlse_db(r)] = mlse_.process(eq_signal,Symbols);
end
mlse_sig_hd = PAMmapper(M,0,"eth_style",0).quantize(eq_signal_whitened);
mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd,"M",M);
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded,"M",M);
tx_symbols_precoded = Duobinary().encode(Symbols);
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
tx_bits_precoded = PAMmapper(M,0,"eth_style",0).demap(tx_symbols_precoded);
rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd_precoded);
[~,errors_db_diff_precoded,ber_db_diff_precoded(r),a] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db_pre(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal);
%B) Just determine BER
rx_bits_mlse = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[bits_mlse,errors_db,ber_db(r),a] = calc_ber(rx_bits_mlse.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db(r,:) = count_error_bursts(a, 15)./numel(Tx_bits.signal);
fprintf('BER ber_db_diff_precoded: %.2e \n',ber_db_diff_precoded(r));
fprintf('BER Vber_dbNLE: %.2e \n',ber_db(r));
% figure();hold on;stem(1:15,burst_db(r,:),'LineWidth',1,'Color',cols(1,:));stem(1:15,burst_db_pre(r,:),'LineWidth',1,'Color',cols(2,:));set(gca, 'yscale', 'log');
end
% FFE or VNLE
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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.00,"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,"order",[50,2,2],"sps",2,"decide",0);
[eq_signal_fullresp, eq_noise] = eq_.process(Rx_sig, Symbols);
showEQNoisePSD(eq_noise, "fignum",1273876,"displayname",'noise after EQ');
[mi_gomez(r)] = calc_air(eq_signal_fullresp, Symbols, "skip_front", 100, "skip_end", 100);
[gmi_vnle_bitwise(r)] = calc_ngmi(eq_signal_fullresp,Symbols);
[gmi_bitwise_2(r)] = calc_gmi_bitwise(eq_signal_fullresp,Symbols);
snr_vnle(r) = calc_snr(Symbols, eq_signal_fullresp-Symbols);
% eq_signal_fullresp.plot("displayname",'bla','fignum',199);
% eq_signal_fullresp.eye(fsym(r),M,"fignum",103837);
% Hard decision on VNLE output
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_fullresp);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(eq_signal_hd);
[~,tot_err,ber_vnle(r),a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_vnle(r,:) = count_error_bursts(a, 10)./tot_err;
% showLevelConfusionMatrix(eq_signal_hd,Symbols,"M",M,"fignum",200,"displayname",'bla');
% showLevelScatter(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','f_sym',fsym(r),'fignum',201);
% show2Dconstellation(eq_signal_fullresp,Symbols,"displayname",'VNLE Out','fignum',2241);
fprintf('BER VNLE: %.2e \n',ber_vnle(r));
fprintf('NGMI VNLE: %.2f \n',gmi_vnle_bitwise(r)./m);
if 1
% Process through postfilter and MLSE
pf_ncoeffs = 1;
if fsym(r) < 200e9
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.1]);
else
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1,"coefficients",[1,0.85]);
end
% showEQNoisePSD(eq_noise,"postfilter_taps",pf_.coefficients,"displayname",'Postfilter Burg based');
alpha(r) = pf_.coefficients(2);
alpha_vec = max(0,round(alpha(r),2)-0.2):0.025:round(alpha(r),2)+0.4;
alpha_vec = unique(sort([alpha_vec, 1, alpha(r)]));
gmi_mlse_ = zeros(size(alpha_vec));
ber_mlse_ = zeros(size(alpha_vec));
parfor a=1:numel(alpha_vec)
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)]);
pf_ = Postfilter("ncoeff",1,"useBurg",0,"coefficients",[1,alpha_vec(a)]);
[eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise);
[signalclass_hd,LLR,gmi_mlse_(a)] = mlse_.process(eq_signal_whitened,Symbols);
mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[~,tot_err,ber_mlse_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% burst_mlse(r,:) = count_error_bursts(errpos, 10);
% if 0
% fprintf('BER MLSE: %.2e \n',ber_mlse(r));
% fprintf('NGMI MLSE: %.5f \n',gmi_mlse(r)./m);
%
% showLevelConfusionMatrix(mlse_sig_hd,Symbols,"M",M,"fignum",300,"displayname",'bla');
%
% levels = sort(unique(Symbols.signal(:)).'); % 1×6
% pairs = reshape(mlse_sig_hd.signal,2,[]).';
% isedge = ismember(pairs, [levels(1) levels(end)]);
% isforbidden = sum(isedge,2)==2;
% fprintf('Found %d forbidden transitions (evenodd edges).\n', nnz(isforbidden));
%
%
% % Process through postfilter and MLSE
% pf_ncoeffs = 1;
% pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
% mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
% [eq_signal_whitened,whitened_noise] = pf_.process(eq_signal_fullresp, eq_noise);
% mlse_.DIR = pf_.coefficients;
% mlse_output = mlse_.process(eq_signal_whitened);
% mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(mlse_output);
% rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
% [~,~,ber_viterbi(r),~] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('Viterbi BER: %.2e \n',ber_viterbi(r));
% end
end
[ber_mlse(r),idx] = min(ber_mlse_);
gmi_mlse(r) = gmi_mlse_(idx);
ber_mlse_burg(r) = ber_mlse_(alpha_vec==alpha(r));
best_alpha(r) = alpha_vec(idx);
end
% IR target in EQ
if 1
% alpha_vec = max(0,round(alpha(r),2)-0.1):0.01:min(1,round(alpha(r),2)+0.1);
plot_stuff = 0;
gmi_mlse_pr_tgt_ = zeros(size(alpha_vec));
ber_mlse_pr_tgt_ = zeros(size(alpha_vec));
for a = 1:numel(alpha_vec)
alpha_vec(a) = 0.9;
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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.00,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
Symbols_filt = Symbols.filter([1,alpha_vec(a)],1);
[eq_signal_prtgt, eq_noise] = eq_.process(Rx_sig, Symbols_filt);
showLevelHistogram(eq_signal_prtgt,Symbols_filt,"displayname",'VNLE Out','fignum',201);
if plot_stuff
% Plot the response for respective EQ targets
Symbols_filt.spectrum("displayname",'IDEAL Filtered Reference','fignum',240587);
eq_signal_whitened.spectrum("displayname",'Full tgt. EQ + PF','fignum',240587);
eq_signal_prtgt.spectrum("displayname",'Partial Resp. Target EQ','fignum',240587);
noise_pf_out = Symbols_filt-eq_signal_whitened;
noise_pr_tgt = Symbols_filt-eq_signal_prtgt;
noise_pf_out.spectrum("displayname",'Ideal PR - Whitening Out','fignum',240588);
noise_pr_tgt.spectrum("displayname",'Ideal PR - PR Target Out','fignum',240588);
end
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).get_levels ./ PAMmapper(M,0).get_scaling,'DIR',[1,alpha_vec(a)],'debug',0);
[signalclass_hd,LLR,gmi_mlse_pr_tgt_(a)] = mlse_.process(eq_signal_prtgt,Symbols);
mlse_sig_hd = PAMmapper(M, 0, "eth_style", 0).quantize(signalclass_hd);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd);
[~,tot_err,ber_mlse_pr_tgt_(a),errpos] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% burst_mlse_(a,:) = count_error_bursts(errpos, 10);
% fprintf('BER MLSE: %.2e \n',ber_mlse_pr_tgt_(a));
% fprintf('NGMI MLSE: %.5f \n',gmi_mlse_pr_tgt_(a)./m);
end
[ber_mlse_pr_tgt(r),idx] = min(ber_mlse_pr_tgt_);
gmi_mlse_pr_tgt(r) = gmi_mlse_pr_tgt_(idx);
best_alpha_pr_tgt(r) = alpha_vec(idx);
end
cols = cbrewer2('paired',8);
figure(); hold on
title(sprintf('%d GBd',fsym(r).*1e-9));
scatter(alpha_vec,ber_mlse_,15,'Marker','o','LineWidth',1,'DisplayName','MLSE','MarkerEdgeColor',cols(1,:));
scatter(best_alpha(r),ber_mlse(r),15,'Marker','o','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:));
scatter(alpha(r),ber_mlse_burg(r),25,'Marker','+','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(2,:));
scatter(1,ber_db_diff_precoded(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:));
scatter(1,ber_db(r),15,'Marker','diamond','LineWidth',2,'DisplayName','Duobinary','MarkerEdgeColor',cols(4,:));
scatter(alpha_vec,ber_mlse_pr_tgt_,15,'Marker','x','LineWidth',1,'DisplayName','MLSE Partial Resp tgt','MarkerEdgeColor',cols(5,:));
scatter(best_alpha_pr_tgt(r),ber_mlse_pr_tgt(r),25,'Marker','x','LineWidth',2,'DisplayName','MLSE','MarkerEdgeColor',cols(6,:));
set(gca,"YScale","log");
% ylim([1e-6 0.5]);
% xlim([0.1 1]);
drawnow;
end
% --- style control (one variable controls both marker size and linewidth) ---
STYLE_BASE = 2; % adjust this single number to scale markers & lines
MARKER_SIZE = STYLE_BASE; % marker size (MATLAB MarkerSize)
LINE_WIDTH = max(1.5, STYLE_BASE/3); % line width (keeps lines reasonable when STYLE_BASE large)
% --- color map / method -> color assignment (keeps colors consistent) ---
cols = cbrewer2('Paired',8);
cols = linspecer(6);
d = 0;
cm.VNLE = cols(1 + d, :);
cm.MLSE = cols(2 + d, :);
cm.DB_precode = cols(3 + d, :);
cm.DB = cols(4 + d, :); % duobinary
% prepare x values in GBd
xGHz = fsym .* 1e-9;
xticks_vals = xGHz;
xtick_labels = arrayfun(@(v) sprintf('%d', round(v)), xticks_vals, 'UniformOutput', false);
% common marker settings (filled, same face+edge color)
mk.VNLE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.VNLE,'MarkerSize',MARKER_SIZE};
mk.MLSE = {'Marker','none','MarkerFaceColor',cm.MLSE,'MarkerEdgeColor',cm.MLSE,'MarkerSize',MARKER_SIZE};
mk.DB_precode = {'Marker','none','MarkerFaceColor',cm.DB_precode,'MarkerEdgeColor',cm.DB_precode,'MarkerSize',MARKER_SIZE};
mk.DB = {'Marker','none','MarkerFaceColor',cm.DB,'MarkerEdgeColor',cm.DB,'MarkerSize',MARKER_SIZE};
% ---------------- FIGURE 11 : alpha (VNLE) ----------------
figure(110+M); clf; hold on;
plot(xGHz, alpha, ...
'DisplayName','VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
xlabel('Baudrate in GBd');
ylabel('alpha');
set(gca, 'XTick', xticks_vals, 'XTickLabel', xtick_labels);
grid on;
legend('Location','best');
% ---------------- FIGURE 15 : GMI ----------------
figure(111+M); clf; hold on;
plot(xGHz, mi_gomez, ...
'DisplayName','MI VNLE', ...
mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, gmi_vnle_bitwise, ...
'DisplayName','GMI VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
% duobinary has only one GMI curve (DB output)
plot(xGHz, gmi_mlse_db, ...
'DisplayName','GMI DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
% MLSE symbol-wise (if present)
plot(xGHz, gmi_mlse, ...
'DisplayName','GMI MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
ylim([log2(M)-1, log2(M)]);
xlabel('Baudrate in GBd');
ylabel('GMI');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
% xlim([184, 256])
% ---------------- FIGURE 13 : BER ----------------
figure(312+M); hold on;
plot(xGHz, ber_vnle, ...
'DisplayName','VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, ber_mlse, ...
'DisplayName','MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(xGHz, ber_viterbi, ...
'DisplayName','Viterbi', ...
mk.MLSE{:}, 'LineStyle','--','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
yline(4.85e-3,'LineWidth',1,'HandleVisibility','off');
yline(2.2e-4,'LineWidth',1,'HandleVisibility','off');
plot(xGHz, ber_db, ...
'DisplayName','DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, ber_db_diff_precoded, ...
'DisplayName','Prec. + DB tgt.', ...
mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB);
xlabel('Baudrate in GBd');
ylabel('BER');
set(gca, 'yscale', 'log');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
% xlim([184, 256])
% ---------------- FIGURE 15 : Information Rates ----------------
tp = TransmissionPerformance;
m = floor(log2(M)*10)/10;
figure(113+M); clf; hold on;
netrates_vnle = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_vnle_bitwise./m, ...
'BER', ber_vnle);
%
plot(xGHz, gmi_vnle_bitwise.*xGHz, ...
'DisplayName','GMI*R VNLE', ...
mk.VNLE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, netrates_vnle.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD VNLE', ...
mk.VNLE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
plot(xGHz, netrates_vnle.HD.NetRate.*1e-9, ...
'DisplayName','Staircase VNLE', ...
mk.VNLE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.VNLE);
%
% MLSE symbol-wise (if present)
plot(xGHz, gmi_mlse.*xGHz, ...
'DisplayName','GMI*R MLSE', ...
mk.MLSE{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
netrates_mlse = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_mlse./m, ...
'BER', ber_mlse);
plot(xGHz, netrates_mlse.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD MLSE', ...
mk.MLSE{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
plot(xGHz, netrates_mlse.HD.NetRate.*1e-9, ...
'DisplayName','Staircase MLSE', ...
mk.MLSE{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.MLSE);
% duobinary has only one GMI curve (DB output)
plot(xGHz, gmi_mlse_db.*xGHz, ...
'DisplayName','GMI*R DB tgt.', ...
mk.DB{:}, 'LineStyle','-','LineWidth',LINE_WIDTH,'Color',cm.DB);
netrates_db = tp.calculateNetRate(fsym.* m, ...
'NGMI', gmi_mlse_db./m, ...
'BER', ber_db);
plot(xGHz, netrates_db.SDHD.NetRate.*1e-9, ...
'DisplayName','SD+HD DB', ...
mk.DB{:}, 'LineStyle',':','LineWidth',LINE_WIDTH,'Color',cm.DB);
plot(xGHz, netrates_db.HD.NetRate.*1e-9, ...
'DisplayName','Staircase DB', ...
mk.DB{:}, 'LineStyle','-.','LineWidth',LINE_WIDTH,'Color',cm.DB);
% ylim([log2(M)-1, log2(M)]);
xlabel('Baudrate in GBd');
ylabel('AIR in Gbps');
set(gca, 'XTick', xticks_vals(1:2:end), 'XTickLabel', xtick_labels(1:2:end));
grid on;
legend('Location','best');
xlim([184, 256])
% Auxiliary nested helper for numerically stable log-sum-exp
function s = logsumexp(a)
% LOGSUMEXP Compute log(sum(exp(a))) in a numerically stable way
m = max(a);
s = m + log(sum(exp(a - m)));
end

View File

@@ -1,191 +0,0 @@
%%% Run parameters
% TX
M = 4;
fsym = 180e9;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
random_key = 1;
db_precode = 0;
db_encode = 0;
rcalpha = 0.05;
kover = 16;
vbias_rel = 0.5;
u_pi = 2.9;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1310;
laser_linewidth = 0;
tx_bw_nyquist = 1;
% Channel
link_length = 1;
% RX
rop = 0;
rx_bw_nyquist = 0.99;
vnle_order1 = 50;
vnle_order2 = 3;
vnle_order3 = 3;
vnle_order=[vnle_order1,vnle_order2,vnle_order3];
dfe_order = [0 0 0];
pf_ncoeffs = 1;
alpha = 0;
len_tr = 4096*2;
mu_ffe1 = 0.0001;
mu_ffe2 = 0.0008;
mu_ffe3 = 0.001;
mu_dc = 0.005;
% mu_dc = 0;
mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
mu_dfe = 0.0004;
dfe_ = sum(dfe_order)>0;
doub_mode = db_mode.no_db;
Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",16,"alpha",rcalpha);
db_precode = 0;
db_encode = 0;
duob_mode = db_mode.no_db;
apply_pulsef = 1;
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym,"M",M,"order",18,"useprbs",0,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
"mrds_code",0,"mrds_blocklength",512,"duobinary_mode",duob_mode).process();
Digi_sig.spectrum("displayname",'Digi Spectrum','fignum',10,'normalizeTo0dB',1);
% %% proof of concept memoryless inverse mapping (direct db targeting and decoding)
% Symbols_db = Duobinary().encode(Symbols);
% mim_decoded = Duobinary().decode(Symbols_db,"M",M);
% rx_bits_mim_decoded = PAMmapper(M,0,"eth_style",0).demap(mim_decoded);
% [~,~,ber_mim_decode,~] = calc_ber(rx_bits_mim_decoded_.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% fprintf('BER mim: %.2e \n',ber_mim_decode);
%%
%%%%% AWG
% El_sig = M8199A("kover",kover).process(Digi_sig);
El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",1).process(Digi_sig);
% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',0);
% El_sig = El_sig.setPower(0,"dBm");
%%%%% Low-pass el. components %%%%%%
f_nyquist = fsym/2;
tx_bwl = tx_bw_nyquist.*f_nyquist;
% tx_bwl = 80e9;
El_sig = Filter('filtdegree',4,"f_cutoff",tx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig);
% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',1);
%%%%% Electrical Driver Amplifier %%%%%%
El_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",3).process(El_sig);
El_sig = El_sig.normalize("mode","oneone");
%%%%% MODULATE E/O CONVERSION %%%%%%
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig);
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length/1000,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
%%%%%% ROP %%%%%%
Rx_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
%%%%%% PD Square Law %%%%%%
Rx_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11).process(Rx_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
rx_bwl = rx_bw_nyquist.*f_nyquist;
% rx_bwl = 80e9;
Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(Rx_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
% Rx_sig.spectrum("displayname",'Analog Rx Spectrum','fignum',100,'normalizeTo0dB',1);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(Rx_sig);
Scpe_sig_resampled = Scpe_sig.resample("fs_out",2*fsym);
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
eq_ = EQ("Ne",[vnle_order1,vnle_order2,vnle_order3],"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);
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);
if duob_mode == db_mode.no_db
% FFE or VNLE
[eq_signal_sd, eq_noise] = eq_.process(Scpe_cell{1}, Symbols);
% Hard decision on VNLE output
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
% Process through postfilter and MLSE
[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise);
mlse_.DIR = pf_.coefficients;
mlse_sig_sd = mlse_.process(mlse_sig_sd,Symbols);
% BER
rx_bits = PAMmapper(M,0,"eth_style",0).demap(eq_signal_hd);
[~,tot_err,ber_vnle,a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
rx_bits = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_sd);
[~,tot_err,ber_mlse,a] = calc_ber(rx_bits.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
elseif duob_mode == db_mode.db_precoded
%%
[EQ_sig, Noi] = eq_.process(Scpe_cell{1},Duobinary().encode(Symbols));
showLevelHistogram(EQ_sig,Duobinary().encode(Symbols),"displayname",101);
mim_decoded = Duobinary().decode(EQ_sig,"M",M);
showLevelHistogram(mim_decoded,Symbols,"displayname",101);
rx_bits_mim_decoded = PAMmapper(M,0,"eth_style",0).demap(mim_decoded);
rx_bits_mim_decoded_.signal = circshift(rx_bits_mim_decoded.signal,0);
[~,~,ber_mim_decode,~] = calc_ber(rx_bits_mim_decoded_.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
fprintf('BER mim: %.2e \n',ber_mim_decode);
%%
mlse_sig_hd = MLSE("DIR",[1,1],"duobinary_output",1,"M",M,"trellis_states",PAMmapper(M,0).levels).process(EQ_sig,Symbols);
mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd,"M",M);
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M);
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",0).demap(mlse_sig_hd_decoded);
[~,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,Tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
%%
end

View File

@@ -4,69 +4,50 @@ arguments
funcHandle
wh
options.parallel = 1;
end
%%% 2) SUBMIT SIMULATION
% Initialize job results
if options.parallel
curpool = gcp('nocreate');
if isempty(curpool)
parpool;
else
% stop all forgotten or unfetched jobs from queue
if ~isempty(curpool.FevalQueue.QueuedFutures) || ~isempty(curpool.FevalQueue.RunningFutures)
oldq = length(curpool.FevalQueue.QueuedFutures) + length(curpool.FevalQueue.RunningFutures);
curpool.FevalQueue.cancelAll
fprintf('Canceled %d unfetched jobs from old queue.', oldq);
end
end
results = parallel.FevalFuture.empty();
else
results = [];
options.waitbar = true;
end
fprintf('Requested %d loops\n', wh.getLastLinIndice);
for lin_idx = 1:wh.getLastLinIndice
optionalVars = struct();
jobs = repmat(struct('args', {{}}, 'label', "", 'meta', struct()), 1, wh.getLastLinIndice);
if ~isempty(wh.getDimension)
% Build the optionalVars struct
[parametervalues, parameternames] = wh.getPhysIndicesByLinIndex(lin_idx);
for lin_idx = 1:wh.getLastLinIndice
optionalVars = buildOptionalVars(lin_idx, wh);
jobs(lin_idx).args = {optionalVars};
jobs(lin_idx).label = sprintf('Job %d', lin_idx);
jobs(lin_idx).meta.lin_idx = lin_idx;
end
mode = processingMode.serial;
if options.parallel
mode = processingMode.parallel;
end
runBatch(funcHandle, jobs, ...
"mode", mode, ...
"waitbar", options.waitbar, ...
"waitbarMessage", "Processing Simulations...", ...
"resultHandler", @storeResult, ...
"errorHandler", @handleError);
function optionalVars = buildOptionalVars(lin_idx, dataStorage)
optionalVars = struct();
if ~isempty(dataStorage.getDimension)
[parametervalues, parameternames] = dataStorage.getPhysIndicesByLinIndex(lin_idx);
for pidx = 1:numel(parameternames)
optionalVars.(parameternames{pidx}) = parametervalues{pidx};
end
end
%%% SIMULATION HERE
if options.parallel
numOutputs = 1;
results(lin_idx) = parfeval(funcHandle, numOutputs, optionalVars);
else
finalresults{lin_idx} = feval(funcHandle, optionalVars);
wh.addValueToStorageByLinIdx(finalresults{lin_idx}, 'ber', lin_idx);
end
end
if options.parallel
%%% 4) Setup waitbar
h = waitbar(0, 'Processing Simulations...');
updateWaitbar = @(~) waitbar(mean(arrayfun(@(f) strcmp(f.State, 'finished'), results)), h);
function storeResult(result, job, ~)
wh.addValueToStorageByLinIdx(result, 'ber', job.meta.lin_idx);
end
fprintf('Fetching results... \n');
updateWaitbarFutures = afterEach(results, updateWaitbar, 0);
afterAll(updateWaitbarFutures, @(~) delete(h), 0);
%%% 7) Fetch final results iteratively as they finish
for i = 1:length(results)
try
[ridx, result] = fetchNext(results);
wh.addValueToStorageByLinIdx(result, 'ber', ridx);
catch ME
fprintf('A job failed or could not be fetched. Error: %s\n', ME.message);
function handleError(ME, job, ~)
fprintf('[%s] ERROR [%s]: %s\n', job.label, ME.identifier, ME.message);
for st = ME.stack'
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
end
end
end
end

View File

@@ -1,83 +0,0 @@
function wh = submit_simulations(wh,options)
arguments
wh
options.parallel = 1;
options.simulation_mode = 1;
end
%%% 2) SUBMIT SIMULATION
% Initialize job results
if options.parallel
curpool = gcp('nocreate');
if isempty(curpool)
parpool;
else
% stop all forgotten or unfetched jobs from queue
if ~isempty(curpool.FevalQueue.QueuedFutures) || ~isempty(curpool.FevalQueue.RunningFutures)
oldq = length(curpool.FevalQueue.QueuedFutures) +length(curpool.FevalQueue.RunningFutures);
curpool.FevalQueue.cancelAll
fprintf('Canceled %d unfetched jobs from old queue.',oldq);
end
end
results = parallel.FevalFuture.empty();
else
results = [];
end
fprintf('Requested %d loops',wh.getLastLinIndice);
lin_idx = 1;
for lin_idx = 1:wh.getLastLinIndice
optionalVars = struct();
if ~isempty(wh.getDimension)
% Build the optionalVars struct
[parametervalues,parameternames]=wh.getPhysIndicesByLinIndex(lin_idx);
for pidx = 1:numel(parameternames)
optionalVars.(parameternames{pidx}) = parametervalues{pidx};
end
end
%%% SIMULATION HERE
if options.parallel
numOutputs = 1;
results(lin_idx) = parfeval(@imdd_model, numOutputs, options.simulation_mode, optionalVars);
else
finalresults{lin_idx} = imdd_model(options.simulation_mode,optionalVars);
wh.addValueToStorageByLinIdx(finalresults{lin_idx}, 'ber', lin_idx);
end
end
if options.parallel
%%% 4) Setup waitbar
h = waitbar(0, 'Processing Simulations...');
% Helper function to compute progress
updateWaitbar = @(~) waitbar(mean(arrayfun(@(f) strcmp(f.State, 'finished'), results)), h);
fprintf('Fetching results... \n');
% Update the waitbar after each simulation
updateWaitbarFutures = afterEach(results, updateWaitbar, 0);
% Close the waitbar after all simulations complete
afterAll(updateWaitbarFutures, @(~) delete(h), 0);
%%% 7) Fetch final results after all computations
fetchOutputs(results);
for ridx = 1:length(results)
wh.addValueToStorageByLinIdx(results(ridx).OutputArguments{1}, 'ber', ridx);
end
end
end

View File

@@ -1,31 +0,0 @@
Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",rcalpha);
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym,"M",M,"order",19,"useprbs",1,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
"db_precode",db_precode,"db_encode",db_encode,...
"mrds_code",0,"mrds_blocklength",512).process();
% Digi_sig.spectrum("displayname",'Digi Spectrum','fignum',10,'normalizeTo0dB',1);
%%%%% AWG
% El_sig = M8199A("kover",kover).process(Digi_sig);
El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",1).process(Digi_sig);
% El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',0);
% El_sig = El_sig.setPower(0,"dBm");
%%%%% Low-pass el. components %%%%%%
El_sig = Filter('filtdegree',4,"f_cutoff",tx_bw_nyquist.*f_nyquist,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(El_sig);
%%%%% Electrical Driver Amplifier %%%%%%
El_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",3).process(El_sig);
El_sig = El_sig.normalize("mode","oneone");
%%%%% MODULATE E/O CONVERSION %%%%%%
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1).process(El_sig);