Strengthen WDM and IM/DD optical tests
This commit is contained in:
@@ -1,10 +1,64 @@
|
||||
classdef Opticalsignal_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Opticalsignal.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/00_signals/Opticalsignal.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'signals', 'optical'})
|
||||
function constructorWiresOpticalMetadata(testCase)
|
||||
opt = makeOpticalsignal([1; 2; 3], 12.5e9, 1310e-9, 0.25, 2);
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Opticalsignal are not implemented yet.");
|
||||
testCase.verifyClass(opt, 'Opticalsignal');
|
||||
testCase.verifyEqual(opt.signal, [1; 2; 3]);
|
||||
testCase.verifyEqual(opt.fs, 12.5e9);
|
||||
testCase.verifyEqual(opt.lambda, 1310e-9);
|
||||
testCase.verifyEqual(opt.nase, 0.25);
|
||||
testCase.verifyEqual(opt.polrot, 2);
|
||||
testCase.verifyEqual(height(opt.logbook), 0);
|
||||
end
|
||||
|
||||
function delayBySamplesAndMetersGiveTheSameResult(testCase)
|
||||
fs = physconst('LightSpeed') / 1.4677;
|
||||
opt = makeOpticalsignal([1; 0; -1; 2], fs, 1550e-9, 0, 0);
|
||||
|
||||
[bySamples, delayNs] = opt.delay("delay_samples", 2);
|
||||
[byMeters, delayNm] = opt.delay("delay_meter", 2);
|
||||
|
||||
testCase.verifyEqual(delayNs, 2);
|
||||
testCase.verifyEqual(delayNm, 2);
|
||||
testCase.verifyEqual(bySamples.signal, byMeters.signal);
|
||||
testCase.verifyEqual(bySamples.fs, opt.fs);
|
||||
testCase.verifyClass(bySamples, 'Opticalsignal');
|
||||
testCase.verifyClass(byMeters, 'Opticalsignal');
|
||||
end
|
||||
|
||||
function csprMatchesKnownCarrierToSidebandRatio(testCase)
|
||||
opt = makeOpticalsignal([3; 1; 3; 1], 10, 1550e-9, 0, 0);
|
||||
|
||||
actual = opt.cspr();
|
||||
expected = 10 * log10(abs(mean(opt.signal)).^2 / mean(abs(opt.signal - mean(opt.signal)).^2));
|
||||
|
||||
testCase.verifyEqual(actual, expected, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function inheritedPlusAndLengthPreserveOpticalSemantics(testCase)
|
||||
left = makeOpticalsignal([1; 2; 3], 5, 1310e-9, 0.1, 1);
|
||||
right = makeOpticalsignal([4; 5; 6], 5, 1310e-9, 0.2, 1);
|
||||
|
||||
sumSig = left + right;
|
||||
|
||||
testCase.verifyClass(sumSig, 'Opticalsignal');
|
||||
testCase.verifyEqual(sumSig.signal, [5; 7; 9]);
|
||||
testCase.verifyEqual(sumSig.fs, left.fs);
|
||||
testCase.verifyEqual(sumSig.lambda, left.lambda);
|
||||
testCase.verifyEqual(sumSig.nase, left.nase);
|
||||
testCase.verifyEqual(sumSig.polrot, left.polrot);
|
||||
testCase.verifyEqual(sumSig.length(), 3);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function opt = makeOpticalsignal(signal, fs, lambda, nase, polrot)
|
||||
base = Signal(signal);
|
||||
opt = Opticalsignal(signal, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook, ...
|
||||
"lambda", lambda, ...
|
||||
"nase", nase, ...
|
||||
"polrot", polrot);
|
||||
end
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
classdef Signalgenerator_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Signalgenerator.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/01_transmit/Signalgenerator.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'transmit', 'signalgenerator'})
|
||||
function constructorStoresDefaultConfiguration(testCase)
|
||||
gen = Signalgenerator();
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Signalgenerator are not implemented yet.");
|
||||
testCase.verifyEqual(gen.form, signalform.sine);
|
||||
testCase.verifyEqual(gen.length, 1024);
|
||||
testCase.verifyEqual(gen.fs, 1000);
|
||||
testCase.verifyEqual(gen.fsig, 50);
|
||||
end
|
||||
|
||||
function buildSignalProducesExpectedSineWave(testCase)
|
||||
gen = Signalgenerator( ...
|
||||
"form", signalform.sine, ...
|
||||
"length", 4, ...
|
||||
"fs", 4, ...
|
||||
"fsig", 1);
|
||||
|
||||
signal = gen.build_signal();
|
||||
expected = [0 1 0 -1];
|
||||
|
||||
testCase.verifySize(signal, [1 4]);
|
||||
testCase.verifyEqual(signal, expected, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function processWrapsGeneratedSignalInInformationsignal(testCase)
|
||||
gen = Signalgenerator( ...
|
||||
"form", signalform.sine, ...
|
||||
"length", 4, ...
|
||||
"fs", 4, ...
|
||||
"fsig", 1);
|
||||
|
||||
out = gen.process();
|
||||
|
||||
testCase.verifyClass(out, 'Informationsignal');
|
||||
testCase.verifySize(out.signal, [1 4]);
|
||||
testCase.verifyEqual(out.fs, 4);
|
||||
testCase.verifyEqual(out.signal, [0 1 0 -1], "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(height(out.logbook), 1);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,69 @@
|
||||
classdef Amplifier_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Amplifier.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_etc/Amplifier.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'etc', 'amplifier'})
|
||||
function constructorStoresConfiguredModesAndSettings(testCase)
|
||||
amp = Amplifier( ...
|
||||
"amp_mode", amp_mode.ideal_no_noise, ...
|
||||
"gain_mode", gain_mode.gain, ...
|
||||
"nase_mode", nase_mode.pass_ase, ...
|
||||
"amplification_db", 7.5, ...
|
||||
"noifig", 12);
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Amplifier are not implemented yet.");
|
||||
testCase.verifyEqual(amp.amp_mode, amp_mode.ideal_no_noise);
|
||||
testCase.verifyEqual(amp.gain_mode, gain_mode.gain);
|
||||
testCase.verifyEqual(amp.nase_mode, nase_mode.pass_ase);
|
||||
testCase.verifyEqual(amp.amplification_db, 7.5);
|
||||
testCase.verifyEqual(amp.noifig, 12);
|
||||
end
|
||||
|
||||
function gainModeAmplifiesOpticalSignalAndPreservesMetadata(testCase)
|
||||
inputSignal = makeOpticalSignal([1; -1; 1; -1], 64e9, 1310e-9, 0, 0);
|
||||
amp = Amplifier( ...
|
||||
"amp_mode", amp_mode.ideal_no_noise, ...
|
||||
"gain_mode", gain_mode.gain, ...
|
||||
"nase_mode", nase_mode.pass_ase, ...
|
||||
"amplification_db", 6);
|
||||
|
||||
outputSignal = amp.process(inputSignal);
|
||||
|
||||
expectedScale = 10^(6/20);
|
||||
|
||||
testCase.verifyClass(outputSignal, 'Opticalsignal');
|
||||
testCase.verifyEqual(outputSignal.signal, expectedScale * inputSignal.signal, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(outputSignal.nase, inputSignal.nase * expectedScale^2, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(outputSignal.fs, inputSignal.fs);
|
||||
testCase.verifyEqual(outputSignal.lambda, inputSignal.lambda);
|
||||
testCase.verifyEqual(outputSignal.polrot, inputSignal.polrot);
|
||||
testCase.verifyEqual(height(outputSignal.logbook), height(inputSignal.logbook) + 1);
|
||||
testCase.verifyEqual(string(outputSignal.logbook.Description(end)), "Optical Amplifier ");
|
||||
end
|
||||
|
||||
function outputPowerModeTargetsRequestedAveragePower(testCase)
|
||||
inputSignal = makeOpticalSignal([1; -1; 1; -1], 64e9, 1310e-9, 0, 0);
|
||||
amp = Amplifier( ...
|
||||
"amp_mode", amp_mode.ideal_no_noise, ...
|
||||
"gain_mode", gain_mode.output_power, ...
|
||||
"nase_mode", nase_mode.pass_ase, ...
|
||||
"amplification_db", 33);
|
||||
|
||||
outputSignal = amp.process(inputSignal);
|
||||
|
||||
expectedPowerW = 10^(33/10 - 3);
|
||||
expectedScale = sqrt(expectedPowerW / mean(abs(inputSignal.signal).^2));
|
||||
|
||||
testCase.verifyClass(outputSignal, 'Opticalsignal');
|
||||
testCase.verifyEqual(outputSignal.signal, expectedScale * inputSignal.signal, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(outputSignal.power("unit", power_notation.W), expectedPowerW, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(outputSignal.fs, inputSignal.fs);
|
||||
testCase.verifyEqual(outputSignal.lambda, inputSignal.lambda);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sig = makeOpticalSignal(values, fs, lambda, nase, polrot)
|
||||
sig = Opticalsignal(values, ...
|
||||
"fs", fs, ...
|
||||
"logbook", Signal(values, "fs", fs).logbook, ...
|
||||
"lambda", lambda, ...
|
||||
"nase", nase, ...
|
||||
"polrot", polrot);
|
||||
end
|
||||
|
||||
@@ -1,10 +1,71 @@
|
||||
classdef Filter_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Filter.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_etc/Filter.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'filter', 'etc'})
|
||||
function constructorBuildsFilterWhenSamplingRateAndLengthAreKnown(testCase)
|
||||
flt = Filter( ...
|
||||
"active", true, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"f_cutoff", 3.5, ...
|
||||
"fs", 8, ...
|
||||
"signal_length", 8, ...
|
||||
"filtdegree", 3, ...
|
||||
"lowpass", 1);
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Filter are not implemented yet.");
|
||||
testCase.verifyEqual(flt.active, true);
|
||||
testCase.verifyEqual(flt.filterType, filtertypes.butterworth);
|
||||
testCase.verifyEqual(flt.f_cutoff, 3.5);
|
||||
testCase.verifyEqual(flt.fs, 8);
|
||||
testCase.verifyEqual(flt.signal_length, 8);
|
||||
testCase.verifyNotEmpty(flt.H);
|
||||
testCase.verifyNotEmpty(flt.w);
|
||||
testCase.verifyEqual(numel(flt.H), 8);
|
||||
testCase.verifyEqual(numel(flt.w), 8);
|
||||
end
|
||||
|
||||
function inactiveFilterIsExactPassThrough(testCase)
|
||||
flt = Filter( ...
|
||||
"active", false, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"f_cutoff", 3.5, ...
|
||||
"fs", 8, ...
|
||||
"signal_length", 8, ...
|
||||
"filtdegree", 3);
|
||||
|
||||
sig = Signal([1; 2; 3], "fs", 8);
|
||||
numIn = [1; -2; 3];
|
||||
|
||||
sigOut = flt.process(sig);
|
||||
numOut = flt.process(numIn);
|
||||
|
||||
testCase.verifyClass(sigOut, "Signal");
|
||||
testCase.verifyEqual(sigOut.signal, sig.signal);
|
||||
testCase.verifyEqual(sigOut.fs, sig.fs);
|
||||
testCase.verifyEqual(height(sigOut.logbook), height(sig.logbook));
|
||||
testCase.verifyEqual(numOut, numIn);
|
||||
end
|
||||
|
||||
function activeButterworthFilterPreservesConstantSignals(testCase)
|
||||
flt = Filter( ...
|
||||
"active", true, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"f_cutoff", 3.5, ...
|
||||
"fs", 8, ...
|
||||
"signal_length", 8, ...
|
||||
"filtdegree", 3, ...
|
||||
"lowpass", 1);
|
||||
|
||||
sig = Signal(ones(8, 1), "fs", 8);
|
||||
numIn = ones(8, 1);
|
||||
|
||||
sigOut = flt.process(sig);
|
||||
numOut = flt.process_(numIn);
|
||||
|
||||
testCase.verifyEqual(size(sigOut.signal), size(sig.signal));
|
||||
testCase.verifyEqual(sigOut.signal, ones(8, 1), "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(sigOut.fs, sig.fs);
|
||||
testCase.verifyEqual(height(sigOut.logbook), 1);
|
||||
|
||||
testCase.verifyEqual(size(numOut), size(numIn));
|
||||
testCase.verifyEqual(numOut, ones(8, 1), "AbsTol", 1e-12);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,78 @@
|
||||
classdef DP_Fiber_test < IMDDTestCase
|
||||
% Auto-generated placeholder for DP_Fiber.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/DP_Fiber.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'optical', 'dp_fiber'})
|
||||
function constructorStoresConfigurationAndStartsWithEmptyState(testCase)
|
||||
fiber = makeIdentityFiber();
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for DP_Fiber are not implemented yet.");
|
||||
testCase.verifyEqual(fiber.L, 0);
|
||||
testCase.verifyEqual(fiber.dz, 1);
|
||||
testCase.verifyEqual(fiber.lambda, 1550);
|
||||
testCase.verifyEqual(fiber.rng, 7);
|
||||
testCase.verifyEqual(fiber.gamma, 0);
|
||||
testCase.verifyEqual(fiber.fa, 64e9);
|
||||
testCase.verifyEqual(fiber.X_alpha, 0);
|
||||
testCase.verifyEqual(fiber.D, 0);
|
||||
testCase.verifyEqual(fiber.Ds, 0);
|
||||
testCase.verifyEqual(fiber.Dpmd, 0);
|
||||
testCase.verifyEqual(fiber.n_waveplates, 1);
|
||||
testCase.verifyEqual(fiber.useGPU, false);
|
||||
testCase.verifyEqual(fiber.useSingle, false);
|
||||
testCase.verifyTrue(isstruct(fiber.state));
|
||||
testCase.verifyEmpty(fieldnames(fiber.state));
|
||||
end
|
||||
|
||||
function processPreservesAZeroImpairmentDualPolarizationSignal(testCase)
|
||||
fiber = makeIdentityFiber();
|
||||
inputMatrix = [ ...
|
||||
1 + 1i, 2 - 1i; ...
|
||||
0.5, -0.25i; ...
|
||||
-1 + 0.2i, 0.75 - 0.5i; ...
|
||||
0, 0.25 + 0.75i];
|
||||
|
||||
sigIn = Signal(inputMatrix, "fs", fiber.fa);
|
||||
sigOut = fiber.process(sigIn);
|
||||
|
||||
testCase.verifyClass(sigOut, "Signal");
|
||||
testCase.verifyEqual(sigOut.fs, sigIn.fs);
|
||||
testCase.verifyEqual(size(sigOut.signal), size(sigIn.signal));
|
||||
testCase.verifyEqual(sigOut.signal, sigIn.signal, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(height(sigOut.logbook), 1);
|
||||
end
|
||||
|
||||
function process_ProducesIdentityOutputForRawMatrixInput(testCase)
|
||||
fiber = makeIdentityFiber();
|
||||
inputMatrix = [ ...
|
||||
1 + 1i, 2 - 1i; ...
|
||||
0.5, -0.25i; ...
|
||||
-1 + 0.2i, 0.75 - 0.5i; ...
|
||||
0, 0.25 + 0.75i];
|
||||
|
||||
sigOut = fiber.process_(inputMatrix, fiber.fa);
|
||||
|
||||
testCase.verifyEqual(size(sigOut), size(inputMatrix));
|
||||
testCase.verifyEqual(sigOut, inputMatrix, "AbsTol", 1e-12);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function fiber = makeIdentityFiber()
|
||||
fiber = DP_Fiber( ...
|
||||
"L", 0, ...
|
||||
"dz", 1, ...
|
||||
"lambda", 1550, ...
|
||||
"rng", 7, ...
|
||||
"gamma", 0, ...
|
||||
"fa", 64e9, ...
|
||||
"X_alpha", 0, ...
|
||||
"D", 0, ...
|
||||
"Ds", 0, ...
|
||||
"Dpmd", 0, ...
|
||||
"beat_len", 1e9, ...
|
||||
"corr_len", 1e9, ...
|
||||
"manakov", 0, ...
|
||||
"SS_dphimax", 5e-3, ...
|
||||
"SS_dzmax", 1, ...
|
||||
"SS_dzmin", 1, ...
|
||||
"n_waveplates", 1, ...
|
||||
"useGPU", false, ...
|
||||
"useSingle", false);
|
||||
end
|
||||
|
||||
@@ -1,10 +1,100 @@
|
||||
classdef EML_test < IMDDTestCase
|
||||
% Auto-generated placeholder for EML.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/EML.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'optical', 'eml'})
|
||||
function constructorStoresDerivedScalarsForDeterministicIqMode(testCase)
|
||||
eml = EML( ...
|
||||
"mode", eml_mode.iq_linear, ...
|
||||
"fsimu", 64e9, ...
|
||||
"lambda", 1550, ...
|
||||
"power", 0, ...
|
||||
"linewidth", 0, ...
|
||||
"alpha", 0, ...
|
||||
"ampl_imbal", 0, ...
|
||||
"pha_imbal", 0, ...
|
||||
"bias", 0, ...
|
||||
"u_pi", 1, ...
|
||||
"randomkey", 7);
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for EML are not implemented yet.");
|
||||
testCase.verifyEqual(eml.mode, eml_mode.iq_linear);
|
||||
testCase.verifyEqual(eml.fsimu, 64e9);
|
||||
testCase.verifyEqual(eml.lambda, 1550);
|
||||
testCase.verifyEqual(eml.power, 0);
|
||||
testCase.verifyEqual(eml.linewidth, 0);
|
||||
testCase.verifyEqual(eml.alpha, 0);
|
||||
testCase.verifyEqual(eml.field, sqrt(1e-3), "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(eml.noisefactor, 0, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(eml.phase, 0, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function process_ProducesDeterministicIqLinearOutput(testCase)
|
||||
eml = EML( ...
|
||||
"mode", eml_mode.iq_linear, ...
|
||||
"fsimu", 32e9, ...
|
||||
"lambda", 1310, ...
|
||||
"power", 3, ...
|
||||
"linewidth", 0, ...
|
||||
"alpha", 0, ...
|
||||
"ampl_imbal", 0, ...
|
||||
"pha_imbal", 0, ...
|
||||
"bias", 0, ...
|
||||
"u_pi", 2, ...
|
||||
"randomkey", 11);
|
||||
inputSig = makeElectricalsignal([1 + 1i; -2 + 0.5i; 0.25 - 0.75i], 32e9);
|
||||
|
||||
[optField, emlOut] = eml.process_(inputSig.signal);
|
||||
|
||||
expectedField = eml.field .* inputSig.signal ./ eml.u_pi;
|
||||
|
||||
testCase.verifyEqual(optField, expectedField, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(emlOut.signal_len, length(inputSig));
|
||||
testCase.verifyEqual(emlOut.phase, 0, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function processReturnsOpticalsignalWithUpdatedMetadata(testCase)
|
||||
eml = EML( ...
|
||||
"mode", eml_mode.iq_linear, ...
|
||||
"fsimu", 64e9, ...
|
||||
"lambda", 1550, ...
|
||||
"power", 0, ...
|
||||
"linewidth", 0, ...
|
||||
"alpha", 0, ...
|
||||
"ampl_imbal", 0, ...
|
||||
"pha_imbal", 0, ...
|
||||
"bias", 0, ...
|
||||
"u_pi", 1, ...
|
||||
"randomkey", 19);
|
||||
inputSig = makeElectricalsignal([1 + 1i; -1 - 1i], 64e9);
|
||||
|
||||
[outputSig, emlOut] = eml.process(inputSig);
|
||||
|
||||
expectedField = eml.field .* inputSig.signal ./ eml.u_pi;
|
||||
|
||||
testCase.verifyClass(outputSig, 'Opticalsignal');
|
||||
testCase.verifyEqual(outputSig.signal, expectedField, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(outputSig.fs, eml.fsimu);
|
||||
testCase.verifyEqual(outputSig.lambda, eml.lambda * 1e-9, "AbsTol", 1e-15);
|
||||
testCase.verifyEqual(height(outputSig.logbook), height(inputSig.logbook) + 1);
|
||||
testCase.verifyTrue(contains(string(outputSig.logbook.Description(end)), "nm Laser"));
|
||||
testCase.verifyEqual(emlOut.signal_len, length(inputSig));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sig = makeElectricalsignal(values, fs)
|
||||
sig = Electricalsignal(values, ...
|
||||
"fs", fs, ...
|
||||
"logbook", emptyLogbook());
|
||||
end
|
||||
|
||||
function lb = emptyLogbook()
|
||||
SignalType = [];
|
||||
TimeStamp = [];
|
||||
Length = [];
|
||||
SignalPower = [];
|
||||
Nase = [];
|
||||
SignalCopy = [];
|
||||
ModifierName = [];
|
||||
ModifierCopy = {};
|
||||
Description = [];
|
||||
|
||||
lb = table(SignalType, TimeStamp, Length, SignalPower, Nase, SignalCopy, ModifierName, ModifierCopy, Description);
|
||||
end
|
||||
|
||||
@@ -1,10 +1,96 @@
|
||||
classdef Optical_Demultiplex_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Optical_Demultiplex.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/Optical_Demultiplex.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'optical', 'demux'})
|
||||
function processReturnsOneCellPerChannelAndPreservesMetadata(testCase)
|
||||
fs = 32e9;
|
||||
lambdaCenterNm = 1310;
|
||||
lambdaA = 1309e-9;
|
||||
lambdaB = 1311e-9;
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Optical_Demultiplex are not implemented yet.");
|
||||
sigA = makeOpticalSignal( ...
|
||||
[1 2; 1 2; 1 2; 1 2], ...
|
||||
fs, ...
|
||||
lambdaA, ...
|
||||
0.10);
|
||||
sigB = makeOpticalSignal( ...
|
||||
[3 4; 3 4; 3 4; 3 4], ...
|
||||
fs, ...
|
||||
lambdaB, ...
|
||||
0.90);
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", fs, ...
|
||||
"fs_out", fs, ...
|
||||
"lambda_center", lambdaCenterNm, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3, ...
|
||||
"B", 200e9);
|
||||
muxOut = mux.process({sigA, sigB});
|
||||
|
||||
demux = Optical_Demultiplex( ...
|
||||
"fs_in", muxOut.fs, ...
|
||||
"fs_out", muxOut.fs, ...
|
||||
"lambda_center", lambdaCenterNm, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3, ...
|
||||
"B", 200e9);
|
||||
out = demux.process(muxOut);
|
||||
|
||||
testCase.verifyClass(out, 'cell');
|
||||
testCase.verifyNumElements(out, 2);
|
||||
testCase.verifyClass(out{1}, 'Opticalsignal');
|
||||
testCase.verifyClass(out{2}, 'Opticalsignal');
|
||||
testCase.verifyEqual(size(out{1}.signal), size(muxOut.signal));
|
||||
testCase.verifyEqual(size(out{2}.signal), size(muxOut.signal));
|
||||
testCase.verifyEqual(out{1}.fs, fs);
|
||||
testCase.verifyEqual(out{2}.fs, fs);
|
||||
testCase.verifyEqual(out{1}.lambda, muxOut.lambda(1), 'AbsTol', 1e-12);
|
||||
testCase.verifyEqual(out{2}.lambda, muxOut.lambda(2), 'AbsTol', 1e-12);
|
||||
end
|
||||
|
||||
function roundTripThroughMuxAndDemuxPreservesChannelMagnitudes(testCase)
|
||||
fs = 16e9;
|
||||
lambdaCenterNm = 1310;
|
||||
|
||||
sigA = makeOpticalSignal(ones(8, 2), fs, 1309.5e-9, 0.0);
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", fs, ...
|
||||
"fs_out", fs, ...
|
||||
"lambda_center", lambdaCenterNm, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3, ...
|
||||
"B", 200e9);
|
||||
muxOut = mux.process({sigA});
|
||||
|
||||
demux = Optical_Demultiplex( ...
|
||||
"fs_in", muxOut.fs, ...
|
||||
"fs_out", muxOut.fs, ...
|
||||
"lambda_center", lambdaCenterNm, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3, ...
|
||||
"B", 200e9);
|
||||
out = demux.process(muxOut);
|
||||
|
||||
testCase.verifyNumElements(out, 1);
|
||||
testCase.verifyEqual(size(out{1}.signal), size(sigA.signal));
|
||||
testCase.verifyEqual(out{1}.fs, fs);
|
||||
testCase.verifyEqual(out{1}.lambda, muxOut.lambda(1), 'AbsTol', 1e-12);
|
||||
testCase.verifyEqual(mean(abs(out{1}.signal), 'all'), mean(abs(sigA.signal), 'all'), 'AbsTol', 1e-6);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sig = makeOpticalSignal(signal, fs, lambda, polrot)
|
||||
base = Signal(signal, "fs", fs);
|
||||
sig = Opticalsignal( ...
|
||||
signal, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook, ...
|
||||
"lambda", lambda, ...
|
||||
"nase", 0, ...
|
||||
"polrot", polrot);
|
||||
end
|
||||
|
||||
@@ -1,10 +1,101 @@
|
||||
classdef Optical_Multiplex_test < IMDDTestCase
|
||||
% Auto-generated placeholder for Optical_Multiplex.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/Optical_Multiplex.m
|
||||
methods (Test, TestTags = {'unit', 'fast', 'optical', 'mux'})
|
||||
function processCombinesChannelsAndPreservesMetadata(testCase)
|
||||
fs = 32e9;
|
||||
lambdaCenter = 1550;
|
||||
lambdaA = 1550e-9;
|
||||
lambdaB = 1550e-9;
|
||||
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for Optical_Multiplex are not implemented yet.");
|
||||
sigA = makeOpticalSignal( ...
|
||||
[1 2; 1 2; 1 2], ...
|
||||
fs, ...
|
||||
lambdaA, ...
|
||||
0.10);
|
||||
sigB = makeOpticalSignal( ...
|
||||
[3 4; 3 4; 3 4], ...
|
||||
fs, ...
|
||||
lambdaB, ...
|
||||
0.90);
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", fs, ...
|
||||
"fs_out", fs, ...
|
||||
"lambda_center", lambdaCenter, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3);
|
||||
|
||||
out = mux.process({sigA, sigB});
|
||||
expected = combineSingleChannelMuxOutputs({sigA, sigB}, mux);
|
||||
|
||||
testCase.verifyClass(out, 'Opticalsignal');
|
||||
testCase.verifyEqual(size(out.signal), [3 2]);
|
||||
testCase.verifyEqual(out.signal, expected, 'AbsTol', 1e-12);
|
||||
testCase.verifyEqual(out.fs, fs);
|
||||
testCase.verifyEqual(out.lambda, [lambdaA, lambdaB], 'AbsTol', 1e-9);
|
||||
testCase.verifyEqual(out.polrot, [0.10, 0.90], 'AbsTol', 1e-12);
|
||||
testCase.verifyEqual(height(out.logbook), 2);
|
||||
testCase.verifyEqual(string(out.logbook.Description(end)), "Opt. Mux. ");
|
||||
end
|
||||
|
||||
function processSupportsMoreThanOneInputChannel(testCase)
|
||||
fs = 16e9;
|
||||
lambdaCenter = 1310;
|
||||
|
||||
sig1 = makeOpticalSignal(ones(4, 2), fs, lambdaCenter * 1e-9, 0.0);
|
||||
sig2 = makeOpticalSignal(2 * ones(4, 2), fs, lambdaCenter * 1e-9, 0.5);
|
||||
sig3 = makeOpticalSignal(3 * ones(4, 2), fs, lambdaCenter * 1e-9, 1.0);
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", fs, ...
|
||||
"fs_out", fs, ...
|
||||
"lambda_center", lambdaCenter, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 3);
|
||||
|
||||
out = mux.process({sig1, sig2, sig3});
|
||||
expected = combineSingleChannelMuxOutputs({sig1, sig2, sig3}, mux);
|
||||
|
||||
testCase.verifySize(out.signal, [4, 2]);
|
||||
testCase.verifyEqual(out.signal, expected, 'AbsTol', 1e-12);
|
||||
testCase.verifyEqual(numel(out.lambda), 3);
|
||||
testCase.verifyEqual(numel(out.polrot), 3);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sig = makeOpticalSignal(signal, fs, lambda, polrot)
|
||||
base = Signal(signal, "fs", fs);
|
||||
sig = Opticalsignal( ...
|
||||
signal, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook, ...
|
||||
"lambda", lambda, ...
|
||||
"nase", 0, ...
|
||||
"polrot", polrot);
|
||||
end
|
||||
|
||||
function expected = combineSingleChannelMuxOutputs(signals, muxPrototype)
|
||||
expected = [];
|
||||
|
||||
for idx = 1:numel(signals)
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", muxPrototype.fs_in, ...
|
||||
"fs_out", muxPrototype.fs_out, ...
|
||||
"lambda_center", muxPrototype.lambda_center, ...
|
||||
"delta_f", muxPrototype.delta_f, ...
|
||||
"random_key", muxPrototype.random_key, ...
|
||||
"attenuation", muxPrototype.attenuation, ...
|
||||
"filtype", muxPrototype.filtype);
|
||||
singleOut = mux.process({signals{idx}});
|
||||
|
||||
if isempty(expected)
|
||||
expected = zeros(size(singleOut.signal));
|
||||
end
|
||||
|
||||
expected = expected + singleOut.signal;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
classdef IMDD_base_system_impairment_monotonicity_integration_test < IMDDTestCase
|
||||
% Integration test that compares a clean baseline IM/DD workflow
|
||||
% against a deliberately impaired variant.
|
||||
%
|
||||
% The aim here is monotonic behavior, not exact waveform matching.
|
||||
% If the channel is made worse, the recovered performance should not
|
||||
% improve, and the intermediate physical metrics should reflect that.
|
||||
|
||||
properties
|
||||
baseline
|
||||
impaired
|
||||
end
|
||||
|
||||
methods (TestClassSetup)
|
||||
function runScenarioPairOnce(testCase)
|
||||
% Build both scenarios once and share them across the test
|
||||
% methods. The chain itself is deterministic, so the two
|
||||
% results are directly comparable.
|
||||
testCase.baseline = buildReducedImddWorkflow("baseline");
|
||||
testCase.impaired = buildReducedImddWorkflow("impaired");
|
||||
end
|
||||
end
|
||||
|
||||
methods (Test, TestTags = {'integration', 'slow', 'imdd'})
|
||||
function impairedScenarioDoesNotOutperformBaseline(testCase)
|
||||
base = testCase.baseline;
|
||||
bad = testCase.impaired;
|
||||
|
||||
% The impaired configuration should not beat the baseline.
|
||||
testCase.verifyLessThanOrEqual( ...
|
||||
base.ffe_results.metrics.BER, ...
|
||||
bad.ffe_results.metrics.BER + 1e-12);
|
||||
testCase.verifyLessThanOrEqual( ...
|
||||
base.mlse_results.metrics.BER, ...
|
||||
bad.mlse_results.metrics.BER + 1e-12);
|
||||
end
|
||||
|
||||
function bothScenariosStayFiniteAndExposeTheImpairment(testCase)
|
||||
base = testCase.baseline;
|
||||
bad = testCase.impaired;
|
||||
|
||||
finiteSignals = {
|
||||
base.Digi_sig.signal
|
||||
base.El_sig.signal
|
||||
base.Opt_sig_tx.signal
|
||||
base.Opt_sig.signal
|
||||
base.Rx_sig_after_pd.signal
|
||||
base.Rx_sig_filtered.signal
|
||||
base.Scpe_sig_pre_mf.signal
|
||||
base.Scpe_sig.signal
|
||||
base.Synced_sig_centered.signal
|
||||
base.Synced_sig.signal
|
||||
bad.Digi_sig.signal
|
||||
bad.El_sig.signal
|
||||
bad.Opt_sig_tx.signal
|
||||
bad.Opt_sig.signal
|
||||
bad.Rx_sig_after_pd.signal
|
||||
bad.Rx_sig_filtered.signal
|
||||
bad.Scpe_sig_pre_mf.signal
|
||||
bad.Scpe_sig.signal
|
||||
bad.Synced_sig_centered.signal
|
||||
bad.Synced_sig.signal
|
||||
};
|
||||
|
||||
for idx = 1:numel(finiteSignals)
|
||||
sig = finiteSignals{idx};
|
||||
testCase.verifyFalse(any(isnan(sig), 'all'));
|
||||
testCase.verifyFalse(any(isinf(sig), 'all'));
|
||||
end
|
||||
|
||||
% The impaired scenario is defined by a lower received power and
|
||||
% a tighter electrical receiver bandwidth.
|
||||
testCase.verifyLessThan(bad.params.ropDbm, base.params.ropDbm);
|
||||
testCase.verifyLessThan(bad.params.rxElectricalBandwidthHz, base.params.rxElectricalBandwidthHz);
|
||||
testCase.verifyLessThan(bad.params.scopeBandwidthHz, base.params.scopeBandwidthHz);
|
||||
|
||||
basePdPower = signalPower(base.Rx_sig_after_pd.signal);
|
||||
badPdPower = signalPower(bad.Rx_sig_after_pd.signal);
|
||||
baseRxPower = signalPower(base.Rx_sig_filtered.signal);
|
||||
badRxPower = signalPower(bad.Rx_sig_filtered.signal);
|
||||
baseScopePower = signalPower(base.Scpe_sig_pre_mf.signal);
|
||||
badScopePower = signalPower(bad.Scpe_sig_pre_mf.signal);
|
||||
|
||||
testCase.verifyGreaterThan(basePdPower, badPdPower);
|
||||
testCase.verifyGreaterThan(baseRxPower, badRxPower);
|
||||
testCase.verifyGreaterThan(baseScopePower, badScopePower);
|
||||
|
||||
% Both scenarios must still produce valid metrics.
|
||||
testCase.verifyGreaterThanOrEqual(base.ffe_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(base.ffe_results.metrics.BER, 1);
|
||||
testCase.verifyGreaterThanOrEqual(bad.ffe_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(bad.ffe_results.metrics.BER, 1);
|
||||
testCase.verifyGreaterThanOrEqual(base.mlse_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(base.mlse_results.metrics.BER, 1);
|
||||
testCase.verifyGreaterThanOrEqual(bad.mlse_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(bad.mlse_results.metrics.BER, 1);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function p = signalPower(signal)
|
||||
p = mean(abs(signal(:)).^2);
|
||||
end
|
||||
@@ -6,6 +6,7 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
% - the reduced end-to-end chain runs without errors
|
||||
% - key signal objects have the expected lengths and sampling rates
|
||||
% - the DSP outputs return finite, bounded metrics
|
||||
% - the intermediate optical and electrical stages stay physically sane
|
||||
% - MLSE does not regress relative to the preceding FFE stage
|
||||
%
|
||||
% Thresholds are intentionally provisional in this first iteration.
|
||||
@@ -19,7 +20,7 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
function runReducedImddWorkflowOnce(testCase)
|
||||
% Run the deterministic reduced workflow once and share the
|
||||
% resulting signals/metrics across all test methods.
|
||||
testCase.workflow = runReducedWorkflow();
|
||||
testCase.workflow = buildReducedImddWorkflow("minimal");
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,18 +32,30 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
testCase.verifyClass(wf.Symbols, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
|
||||
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
|
||||
testCase.verifyClass(wf.Opt_sig_tx, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.Rx_sig_after_pd, 'Electricalsignal');
|
||||
testCase.verifyClass(wf.Scpe_sig_pre_mf, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Scpe_sig, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Synced_sig_centered, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
|
||||
|
||||
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
|
||||
testCase.verifyGreaterThan(length(wf.Symbols.signal), 0);
|
||||
testCase.verifyEqual(wf.Digi_sig.fs, wf.params.fdac);
|
||||
% Tx_bits is a bit-level container and does not carry a
|
||||
% sampling-rate contract on this path.
|
||||
testCase.verifyEqual(wf.Symbols.fs, wf.params.fsym);
|
||||
testCase.verifyEqual(wf.El_sig.fs, wf.params.fdac * wf.params.kover);
|
||||
testCase.verifyEqual(wf.Opt_sig_tx.fs, wf.El_sig.fs);
|
||||
testCase.verifyEqual(wf.Opt_sig.fs, wf.Opt_sig_tx.fs);
|
||||
testCase.verifyEqual(wf.Rx_sig_after_pd.fs, wf.Opt_sig.fs);
|
||||
testCase.verifyEqual(wf.Scpe_sig_pre_mf.fs, wf.params.fadc);
|
||||
|
||||
% After the matched filter, the signal is intentionally reduced
|
||||
% to 2 samples per symbol for the downstream DSP chain.
|
||||
testCase.verifyEqual(wf.Scpe_sig.fs, 2 * wf.params.fsym);
|
||||
testCase.verifyEqual(wf.Synced_sig_centered.fs, 2 * wf.params.fsym);
|
||||
testCase.verifyEqual(wf.Synced_sig.fs, 2 * wf.params.fsym);
|
||||
|
||||
% The synchronized signal is explicitly cropped to 2 samples per
|
||||
@@ -56,9 +69,12 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
finiteSignals = {
|
||||
wf.Digi_sig.signal
|
||||
wf.El_sig.signal
|
||||
wf.Opt_sig_tx.signal
|
||||
wf.Opt_sig.signal
|
||||
wf.Rx_sig_after_pd.signal
|
||||
wf.Scpe_sig_pre_mf.signal
|
||||
wf.Scpe_sig.signal
|
||||
wf.Synced_sig_centered.signal
|
||||
wf.Synced_sig.signal
|
||||
};
|
||||
|
||||
@@ -68,6 +84,20 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
testCase.verifyFalse(any(isinf(sig), 'all'));
|
||||
end
|
||||
|
||||
txOptPower = signalPower(wf.Opt_sig_tx.signal);
|
||||
fiberOptPower = signalPower(wf.Opt_sig.signal);
|
||||
pdPower = signalPower(wf.Rx_sig_after_pd.signal);
|
||||
preMfVariance = signalVariance(wf.Scpe_sig_pre_mf.signal);
|
||||
mfVariance = signalVariance(wf.Scpe_sig.signal);
|
||||
centeredMean = mean(wf.Synced_sig_centered.signal(:));
|
||||
|
||||
testCase.verifyGreaterThan(txOptPower, 0);
|
||||
testCase.verifyGreaterThan(fiberOptPower, 0);
|
||||
testCase.verifyGreaterThan(pdPower, 0);
|
||||
testCase.verifyGreaterThan(preMfVariance, 0);
|
||||
testCase.verifyGreaterThan(mfVariance, 0);
|
||||
testCase.verifyLessThanOrEqual(abs(centeredMean), 1e-12);
|
||||
|
||||
testCase.verifyGreaterThanOrEqual(wf.ffe_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
|
||||
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
|
||||
@@ -105,217 +135,11 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function workflow = runReducedWorkflow()
|
||||
params = reducedWorkflowParameters();
|
||||
|
||||
% -------------------- TX --------------------
|
||||
txPulse = Pulseformer( ...
|
||||
"fsym", params.fsym, ...
|
||||
"fdac", params.fdac, ...
|
||||
"pulse", "rrc", ...
|
||||
"pulselength", 12, ...
|
||||
"alpha", params.rcalpha);
|
||||
|
||||
[digiSig, symbols, txBits] = PAMsource( ...
|
||||
"fsym", params.fsym, ...
|
||||
"M", params.M, ...
|
||||
"order", params.sourceOrder, ...
|
||||
"useprbs", false, ...
|
||||
"fs_out", params.fdac, ...
|
||||
"applyclipping", false, ...
|
||||
"applypulseform", true, ...
|
||||
"pulseformer", txPulse, ...
|
||||
"randkey", params.randomKey, ...
|
||||
"duobinary_mode", db_mode.no_db, ...
|
||||
"mrds_code", 0).process();
|
||||
|
||||
elSig = AWG( ...
|
||||
"fdac", params.fdac, ...
|
||||
"f_cutoff", params.fsym, ...
|
||||
"lpf_active", false, ...
|
||||
"kover", params.kover, ...
|
||||
"bit_resolution", 8, ...
|
||||
"upsampling_method", "samplehold", ...
|
||||
"precomp_sinc_rolloff", 1).process(digiSig);
|
||||
|
||||
elSig = elSig.normalize("mode", "oneone");
|
||||
elSig = elSig .* params.driverScaling;
|
||||
|
||||
% -------------------- Optical Channel --------------------
|
||||
optSig = EML( ...
|
||||
"mode", eml_mode.im_cosinus, ...
|
||||
"power", params.opticalPowerDbm, ...
|
||||
"fsimu", elSig.fs, ...
|
||||
"lambda", params.laserWavelengthNm, ...
|
||||
"bias", params.vbias, ...
|
||||
"u_pi", params.uPi, ...
|
||||
"linewidth", 0, ...
|
||||
"randomkey", params.randomKey + 1, ...
|
||||
"alpha", 0).process(elSig);
|
||||
|
||||
optSig = Fiber( ...
|
||||
"fsimu", optSig.fs, ...
|
||||
"fiber_length", params.linkLengthKm, ...
|
||||
"alpha", params.fiberAlphaDbPerKm, ...
|
||||
"D", 0, ...
|
||||
"lambda0", 1310, ...
|
||||
"gamma", 0, ...
|
||||
"Dslope", 0.07).process(optSig);
|
||||
|
||||
rxOptSig = Amplifier( ...
|
||||
"amp_mode", "ideal_no_noise", ...
|
||||
"gain_mode", "output_power", ...
|
||||
"amplification_db", params.ropDbm).process(optSig);
|
||||
|
||||
rxSigAfterPd = Photodiode( ...
|
||||
"fsimu", params.fdac * params.kover, ...
|
||||
"dark_current", 0, ...
|
||||
"responsivity", 1, ...
|
||||
"temperature", 20, ...
|
||||
"nep", 0, ...
|
||||
"randomkey", params.randomKey + 2).process(rxOptSig);
|
||||
|
||||
rxSigFiltered = Filter( ...
|
||||
"filtdegree", 4, ...
|
||||
"f_cutoff", params.rxElectricalBandwidthHz, ...
|
||||
"fs", params.fdac * params.kover, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"active", true).process(rxSigAfterPd);
|
||||
|
||||
scopeLpf = Filter( ...
|
||||
"filtdegree", 4, ...
|
||||
"f_cutoff", params.scopeBandwidthHz, ...
|
||||
"fs", params.fadc, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"active", true);
|
||||
|
||||
scpeSig = Scope( ...
|
||||
"fsimu", params.fdac * params.kover, ...
|
||||
"fadc", params.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", scopeLpf).process(rxSigFiltered);
|
||||
|
||||
rxMatchedFilter = Pulseformer( ...
|
||||
"fsym", params.fsym, ...
|
||||
"fdac", 2 * params.fsym, ...
|
||||
"pulse", "rrc", ...
|
||||
"pulselength", 12, ...
|
||||
"alpha", params.rcalpha, ...
|
||||
"matched", 1);
|
||||
scpeSig = rxMatchedFilter.process(scpeSig);
|
||||
|
||||
[syncedSig, ~] = scpeSig.tsynch("reference", symbols, "fs_ref", params.fsym, "debug_plots", 0);
|
||||
syncedSig = syncedSig - mean(syncedSig.signal);
|
||||
syncedSig.signal = syncedSig.signal(1 : 2 * length(symbols));
|
||||
|
||||
% -------------------- DSP --------------------
|
||||
ffeEq = FFE( ...
|
||||
"epochs_tr", 2, ...
|
||||
"epochs_dd", 1, ...
|
||||
"len_tr", params.lenTr, ...
|
||||
"mu_dd", 1e-4, ...
|
||||
"mu_tr", 1e-2, ...
|
||||
"order", 21, ...
|
||||
"sps", 2, ...
|
||||
"decide", 0, ...
|
||||
"adaption_technique", adaption_method.nlms, ...
|
||||
"dd_mode", 1);
|
||||
|
||||
ffeResults = ffe( ...
|
||||
ffeEq, ...
|
||||
params.M, ...
|
||||
syncedSig, ...
|
||||
symbols, ...
|
||||
txBits, ...
|
||||
"precode_mode", db_mode.no_db, ...
|
||||
"showAnalysis", 0, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
mlseEq = FFE( ...
|
||||
"epochs_tr", 2, ...
|
||||
"epochs_dd", 1, ...
|
||||
"len_tr", params.lenTr, ...
|
||||
"mu_dd", 1e-4, ...
|
||||
"mu_tr", 1e-2, ...
|
||||
"order", 21, ...
|
||||
"sps", 2, ...
|
||||
"decide", 0, ...
|
||||
"adaption_technique", adaption_method.nlms, ...
|
||||
"dd_mode", 1);
|
||||
postfilter = Postfilter("ncoeff", 1, "useBurg", 1);
|
||||
mlse = MLSE( ...
|
||||
"duobinary_output", 0, ...
|
||||
"M", params.M, ...
|
||||
"trellis_states", PAMmapper(params.M, 0).levels);
|
||||
|
||||
[vnleResults, mlseResults] = vnle_postfilter_mlse( ...
|
||||
mlseEq, ...
|
||||
postfilter, ...
|
||||
mlse, ...
|
||||
params.M, ...
|
||||
syncedSig, ...
|
||||
symbols, ...
|
||||
txBits, ...
|
||||
"precode_mode", db_mode.no_db, ...
|
||||
"showAnalysis", 0, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
workflow = struct();
|
||||
workflow.params = params;
|
||||
workflow.Digi_sig = digiSig;
|
||||
workflow.Symbols = symbols;
|
||||
workflow.Tx_bits = txBits;
|
||||
workflow.El_sig = elSig;
|
||||
workflow.Opt_sig = optSig;
|
||||
workflow.Rx_sig_after_pd = rxSigAfterPd;
|
||||
workflow.Scpe_sig = scpeSig;
|
||||
workflow.Synced_sig = syncedSig;
|
||||
workflow.ffe_results = ffeResults;
|
||||
workflow.vnle_results = vnleResults;
|
||||
workflow.mlse_results = mlseResults;
|
||||
function p = signalPower(signal)
|
||||
p = mean(abs(signal(:)).^2);
|
||||
end
|
||||
|
||||
function params = reducedWorkflowParameters()
|
||||
params = struct();
|
||||
|
||||
% Smaller, deterministic version of the IM/DD base workflow.
|
||||
params.M = 4;
|
||||
params.fsym = 16e9;
|
||||
params.fdac = 64e9;
|
||||
params.fadc = 64e9;
|
||||
params.kover = 2;
|
||||
params.randomKey = 1;
|
||||
params.sourceOrder = 12;
|
||||
params.rcalpha = 0.05;
|
||||
params.lenTr = 256;
|
||||
|
||||
% Driver / modulator operating point.
|
||||
params.uPi = 3;
|
||||
params.vbiasRel = 0.5;
|
||||
params.vbias = -params.vbiasRel * params.uPi;
|
||||
params.driverScaling = 0.6 * (params.uPi / 2 - abs(params.vbias - params.uPi / 2));
|
||||
|
||||
% Optical path.
|
||||
params.laserWavelengthNm = 1293;
|
||||
params.opticalPowerDbm = 3;
|
||||
params.linkLengthKm = 1;
|
||||
params.fiberAlphaDbPerKm = 0.3;
|
||||
params.ropDbm = 0;
|
||||
|
||||
% Receiver filtering.
|
||||
params.rxElectricalBandwidthHz = 40e9;
|
||||
params.scopeBandwidthHz = 25e9;
|
||||
function v = signalVariance(signal)
|
||||
centered = signal(:) - mean(signal(:));
|
||||
v = mean(abs(centered).^2);
|
||||
end
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
classdef IMDD_base_system_no_impairment_integration_test < IMDDTestCase
|
||||
% Integration test for a reduced IM/DD workflow with no added channel
|
||||
% impairments.
|
||||
%
|
||||
% The purpose of this test is to guard the near-ideal system behavior:
|
||||
% the full chain still runs, the signal objects stay well-formed, and
|
||||
% the DSP stages achieve very low error rates when fiber and receiver
|
||||
% impairments are neutralized as far as practical.
|
||||
|
||||
properties
|
||||
workflow
|
||||
end
|
||||
|
||||
methods (TestClassSetup)
|
||||
function runNoImpairmentWorkflowOnce(testCase)
|
||||
% Run the deterministic workflow once and reuse the result for
|
||||
% all test methods.
|
||||
testCase.workflow = buildReducedImddWorkflow("no-impairment");
|
||||
end
|
||||
end
|
||||
|
||||
methods (Test, TestTags = {'integration', 'slow', 'imdd'})
|
||||
function noImpairmentWorkflowBuildsExpectedSignalStages(testCase)
|
||||
wf = testCase.workflow;
|
||||
|
||||
testCase.verifyClass(wf.Tx_bits, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Symbols, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
|
||||
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
|
||||
testCase.verifyClass(wf.Opt_sig_tx, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.Rx_sig_after_pd, 'Electricalsignal');
|
||||
testCase.verifyClass(wf.Scpe_sig_pre_mf, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Scpe_sig, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Synced_sig_centered, 'Informationsignal');
|
||||
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
|
||||
|
||||
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
|
||||
testCase.verifyGreaterThan(length(wf.Symbols.signal), 0);
|
||||
testCase.verifyEqual(wf.Symbols.fs, wf.params.fsym);
|
||||
testCase.verifyEqual(wf.Digi_sig.fs, wf.params.fdac);
|
||||
testCase.verifyEqual(wf.El_sig.fs, wf.params.fdac * wf.params.kover);
|
||||
testCase.verifyEqual(wf.Opt_sig_tx.fs, wf.El_sig.fs);
|
||||
testCase.verifyEqual(wf.Opt_sig.fs, wf.Opt_sig_tx.fs);
|
||||
testCase.verifyEqual(wf.Rx_sig_after_pd.fs, wf.Opt_sig.fs);
|
||||
testCase.verifyEqual(wf.Scpe_sig_pre_mf.fs, wf.params.fadc);
|
||||
testCase.verifyEqual(wf.Scpe_sig.fs, 2 * wf.params.fsym);
|
||||
testCase.verifyEqual(wf.Synced_sig_centered.fs, 2 * wf.params.fsym);
|
||||
testCase.verifyEqual(wf.Synced_sig.fs, 2 * wf.params.fsym);
|
||||
testCase.verifyEqual(length(wf.Synced_sig.signal), 2 * length(wf.Symbols.signal));
|
||||
end
|
||||
|
||||
function noImpairmentWorkflowProducesFiniteSignalsAndMetrics(testCase)
|
||||
wf = testCase.workflow;
|
||||
|
||||
finiteSignals = {
|
||||
wf.Digi_sig.signal
|
||||
wf.El_sig.signal
|
||||
wf.Opt_sig_tx.signal
|
||||
wf.Opt_sig.signal
|
||||
wf.Rx_sig_after_pd.signal
|
||||
wf.Scpe_sig_pre_mf.signal
|
||||
wf.Scpe_sig.signal
|
||||
wf.Synced_sig_centered.signal
|
||||
wf.Synced_sig.signal
|
||||
};
|
||||
|
||||
for idx = 1:numel(finiteSignals)
|
||||
sig = finiteSignals{idx};
|
||||
testCase.verifyFalse(any(isnan(sig), 'all'));
|
||||
testCase.verifyFalse(any(isinf(sig), 'all'));
|
||||
end
|
||||
|
||||
testCase.verifyEqual(wf.params.linkLengthKm, 0);
|
||||
testCase.verifyEqual(wf.params.fiberAlphaDbPerKm, 0);
|
||||
testCase.verifyEqual(wf.params.rxFilterActive, false);
|
||||
testCase.verifyEqual(wf.params.scopeLpfActive, false);
|
||||
|
||||
testCase.verifyGreaterThanOrEqual(wf.ffe_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
|
||||
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
|
||||
testCase.verifyLessThanOrEqual(wf.mlse_results.metrics.BER, 1);
|
||||
|
||||
testCase.verifyTrue(isfinite(wf.ffe_results.metrics.GMI));
|
||||
testCase.verifyTrue(isfinite(wf.ffe_results.metrics.AIR));
|
||||
testCase.verifyTrue(isfinite(wf.mlse_results.metrics.GMI));
|
||||
testCase.verifyTrue(isfinite(wf.mlse_results.metrics.AIR));
|
||||
end
|
||||
|
||||
function noImpairmentWorkflowMeetsPerformanceChecks(testCase)
|
||||
wf = testCase.workflow;
|
||||
|
||||
% This is a no-added-impairment baseline, not a mathematical
|
||||
% idealization of the full chain. The BER is therefore bounded
|
||||
% rather than expected to be near zero.
|
||||
maxFfeBer = 3e-1;
|
||||
maxMlseBer = 5e-2;
|
||||
|
||||
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, maxFfeBer);
|
||||
testCase.verifyLessThanOrEqual(wf.mlse_results.metrics.BER, maxMlseBer);
|
||||
|
||||
% MLSE should never perform worse than the direct FFE path in
|
||||
% this no-impairment regime.
|
||||
testCase.verifyLessThanOrEqual( ...
|
||||
wf.mlse_results.metrics.BER, ...
|
||||
wf.ffe_results.metrics.BER + 1e-12);
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,222 @@
|
||||
classdef WDM_end_to_end_two_channel_receiver_integration_test < IMDDTestCase
|
||||
% End-to-end WDM receiver integration test.
|
||||
%
|
||||
% The fixture is intentionally small, deterministic, and aligned with
|
||||
% the repo's WDM workflow:
|
||||
% electrical drive -> EML -> polarization control -> optical mux
|
||||
% -> launch amplifier -> optical demux -> photodiode
|
||||
%
|
||||
% The receiver-side check is based on a matched projection score and a
|
||||
% normalized correlation check. That makes the test useful for
|
||||
% regression while staying far away from a brittle golden waveform.
|
||||
|
||||
properties
|
||||
workflow
|
||||
end
|
||||
|
||||
methods (TestClassSetup)
|
||||
function buildWorkflowOnce(testCase)
|
||||
testCase.workflow = buildReceiverWorkflow();
|
||||
end
|
||||
end
|
||||
|
||||
methods (Test, TestTags = {'integration', 'slow', 'optical', 'wdm', 'receiver'})
|
||||
function receiverChainHasExpectedStagesAndMetadata(testCase)
|
||||
wf = testCase.workflow;
|
||||
|
||||
testCase.verifyClass(wf.muxOut, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.launchOut, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.demuxOut, 'cell');
|
||||
testCase.verifyClass(wf.rxOut, 'cell');
|
||||
testCase.verifyNumElements(wf.demuxOut, wf.params.numChannels);
|
||||
testCase.verifyNumElements(wf.rxOut, wf.params.numChannels);
|
||||
|
||||
testCase.verifyEqual(wf.muxOut.fs, wf.params.fsMux);
|
||||
testCase.verifyEqual(wf.launchOut.fs, wf.params.fsMux);
|
||||
testCase.verifyEqual(wf.demuxOut{1}.fs, wf.params.fsBase);
|
||||
testCase.verifyEqual(wf.demuxOut{2}.fs, wf.params.fsBase);
|
||||
testCase.verifyEqual(wf.rxOut{1}.fs, wf.params.fsBase);
|
||||
testCase.verifyEqual(wf.rxOut{2}.fs, wf.params.fsBase);
|
||||
|
||||
testCase.verifyEqual(numel(wf.muxOut.lambda), wf.params.numChannels);
|
||||
testCase.verifyEqual(wf.muxOut.lambda, wf.channelPlanM, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(wf.demuxOut{1}.lambda, wf.channelPlanM(1), "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(wf.demuxOut{2}.lambda, wf.channelPlanM(2), "AbsTol", 1e-12);
|
||||
|
||||
testCase.verifyTrue(all(isfinite(wf.muxOut.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.launchOut.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.demuxOut{1}.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.demuxOut{2}.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.rxOut{1}.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.rxOut{2}.signal), "all"));
|
||||
|
||||
testCase.verifyGreaterThan(height(wf.muxOut.logbook), 0);
|
||||
testCase.verifyGreaterThan(height(wf.demuxOut{1}.logbook), 0);
|
||||
testCase.verifyGreaterThan(height(wf.rxOut{1}.logbook), 0);
|
||||
end
|
||||
|
||||
function intendedBranchBeatsWrongBranchAfterDetection(testCase)
|
||||
wf = testCase.workflow;
|
||||
|
||||
rx1 = centeredSignal(wf.rxOut{1}.signal);
|
||||
rx2 = centeredSignal(wf.rxOut{2}.signal);
|
||||
drive1 = centeredSignal(wf.driveRefs{1}.signal);
|
||||
drive2 = centeredSignal(wf.driveRefs{2}.signal);
|
||||
|
||||
selfCorr1 = normalizedCorrelation(rx1, drive1);
|
||||
selfCorr2 = normalizedCorrelation(rx2, drive2);
|
||||
crossCorr1 = normalizedCorrelation(rx2, drive1);
|
||||
crossCorr2 = normalizedCorrelation(rx1, drive2);
|
||||
|
||||
selfScore1 = matchedProjectionScore(rx1, drive1);
|
||||
selfScore2 = matchedProjectionScore(rx2, drive2);
|
||||
crossScore1 = matchedProjectionScore(rx2, drive1);
|
||||
crossScore2 = matchedProjectionScore(rx1, drive2);
|
||||
|
||||
testCase.verifyGreaterThan(selfCorr1, crossCorr1);
|
||||
testCase.verifyGreaterThan(selfCorr2, crossCorr2);
|
||||
testCase.verifyGreaterThan(selfScore1, crossScore1);
|
||||
testCase.verifyGreaterThan(selfScore2, crossScore2);
|
||||
|
||||
% Loose first-pass guardrails. The exact values can be tightened
|
||||
% later once the baseline is reviewed across repeated runs.
|
||||
testCase.verifyGreaterThan(selfCorr1, 0.05);
|
||||
testCase.verifyGreaterThan(selfCorr2, 0.05);
|
||||
testCase.verifyGreaterThan(selfScore1, 0);
|
||||
testCase.verifyGreaterThan(selfScore2, 0);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function workflow = buildReceiverWorkflow()
|
||||
params = receiverIntegrationParameters();
|
||||
channelPlanNm = calcWavelengthPlan(params.numChannels, params.channelSpacingHz, params.lambdaCenterNm);
|
||||
channelPlanM = channelPlanNm * 1e-9;
|
||||
|
||||
driveRefs = cell(1, params.numChannels);
|
||||
opticalRefs = cell(1, params.numChannels);
|
||||
|
||||
for ch = 1:params.numChannels
|
||||
driveRefs{ch} = makeElectricalSignal(params.driveSignals{ch}, params.fsBase);
|
||||
|
||||
optical = EML( ...
|
||||
"mode", eml_mode.im_cosinus, ...
|
||||
"fsimu", params.fsBase, ...
|
||||
"lambda", channelPlanNm(ch), ...
|
||||
"power", params.laserPowerDbm, ...
|
||||
"linewidth", 0, ...
|
||||
"alpha", 0, ...
|
||||
"ampl_imbal", 0, ...
|
||||
"pha_imbal", 0, ...
|
||||
"bias", params.vbias, ...
|
||||
"u_pi", params.uPi, ...
|
||||
"randomkey", params.randomKey + ch).process(driveRefs{ch});
|
||||
|
||||
% The WDM mux expects dual-polarization optical signals.
|
||||
opticalRefs{ch} = Polarization_Controller( ...
|
||||
"mode", "rot_power", ...
|
||||
"desired_power", 50).process(optical);
|
||||
end
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", params.fsBase, ...
|
||||
"fs_out", params.fsMux, ...
|
||||
"lambda_center", params.lambdaCenterNm, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 1, ...
|
||||
"B", 200e9);
|
||||
muxOut = mux.process(opticalRefs);
|
||||
|
||||
launchOut = Amplifier( ...
|
||||
"amp_mode", amp_mode.ideal_no_noise, ...
|
||||
"gain_mode", gain_mode.output_power, ...
|
||||
"nase_mode", nase_mode.pass_ase, ...
|
||||
"amplification_db", params.launchPowerDbm).process(muxOut);
|
||||
|
||||
demux = Optical_Demultiplex( ...
|
||||
"fs_in", launchOut.fs, ...
|
||||
"fs_out", params.fsBase, ...
|
||||
"lambda_center", params.lambdaCenterNm, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 1, ...
|
||||
"B", 200e9);
|
||||
demuxOut = demux.process(launchOut);
|
||||
|
||||
rxOut = cell(1, params.numChannels);
|
||||
for ch = 1:params.numChannels
|
||||
rxOut{ch} = Photodiode( ...
|
||||
"fsimu", params.fsBase, ...
|
||||
"responsivity", 1, ...
|
||||
"dark_current", 0, ...
|
||||
"temperature", 20, ...
|
||||
"nep", 0, ...
|
||||
"randomkey", params.randomKey + 100 + ch).process(demuxOut{ch});
|
||||
end
|
||||
|
||||
workflow = struct();
|
||||
workflow.params = params;
|
||||
workflow.channelPlanNm = channelPlanNm;
|
||||
workflow.channelPlanM = channelPlanM;
|
||||
workflow.driveRefs = driveRefs;
|
||||
workflow.opticalRefs = opticalRefs;
|
||||
workflow.muxOut = muxOut;
|
||||
workflow.launchOut = launchOut;
|
||||
workflow.demuxOut = demuxOut;
|
||||
workflow.rxOut = rxOut;
|
||||
end
|
||||
|
||||
function params = receiverIntegrationParameters()
|
||||
params = struct();
|
||||
|
||||
% Small fixture, but still close to the WDM project conventions.
|
||||
params.numChannels = 2;
|
||||
params.channelSpacingHz = 200e9;
|
||||
params.lambdaCenterNm = 1310;
|
||||
params.fsBase = 64e9;
|
||||
params.fsMux = 4 * params.fsBase;
|
||||
params.laserPowerDbm = 3;
|
||||
params.launchPowerDbm = params.laserPowerDbm + 10 * log10(params.numChannels);
|
||||
params.uPi = 4.6;
|
||||
params.vbias = -0.5 * params.uPi;
|
||||
params.randomKey = 21;
|
||||
|
||||
n = (0:63).';
|
||||
params.driveSignals = {
|
||||
0.18 * sin(2*pi*n/16) + 0.03 * cos(2*pi*n/8)
|
||||
0.17 * cos(2*pi*n/11 + pi/5) - 0.04 * sin(2*pi*n/5)
|
||||
};
|
||||
end
|
||||
|
||||
function sig = makeElectricalSignal(values, fs)
|
||||
base = Signal(values, "fs", fs);
|
||||
sig = Electricalsignal( ...
|
||||
values, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook);
|
||||
end
|
||||
|
||||
function centered = centeredSignal(signal)
|
||||
centered = signal(:) - mean(signal(:));
|
||||
end
|
||||
|
||||
function corrVal = normalizedCorrelation(candidate, reference)
|
||||
denom = sqrt(sum(abs(candidate).^2) * sum(abs(reference).^2));
|
||||
if denom == 0
|
||||
corrVal = 0;
|
||||
return
|
||||
end
|
||||
|
||||
corrVal = abs(sum(conj(candidate) .* reference)) / denom;
|
||||
end
|
||||
|
||||
function score = matchedProjectionScore(candidate, reference)
|
||||
denom = sum(abs(reference).^2);
|
||||
if denom == 0
|
||||
score = 0;
|
||||
return
|
||||
end
|
||||
|
||||
score = abs(sum(conj(candidate) .* reference)).^2 / denom;
|
||||
end
|
||||
264
Tests/integration/WDM_mux_demux_optical_chain_integration_test.m
Normal file
264
Tests/integration/WDM_mux_demux_optical_chain_integration_test.m
Normal file
@@ -0,0 +1,264 @@
|
||||
classdef WDM_mux_demux_optical_chain_integration_test < IMDDTestCase
|
||||
% Integration test for the WDM optical subchain.
|
||||
%
|
||||
% The goal is to verify the mux/demux behavior in a way that matches
|
||||
% the repo's WDM workflows:
|
||||
% electrical drive -> EML -> polarization control -> optical mux
|
||||
% -> launch amplifier -> optical demux
|
||||
%
|
||||
% The electrical stimuli are intentionally small and deterministic so
|
||||
% the test isolates the optical chain rather than the TX waveform
|
||||
% generation stack.
|
||||
|
||||
properties
|
||||
workflow
|
||||
end
|
||||
|
||||
methods (TestClassSetup)
|
||||
function buildWorkflowOnce(testCase)
|
||||
testCase.workflow = buildWdmWorkflow();
|
||||
end
|
||||
end
|
||||
|
||||
methods (Test, TestTags = {'integration', 'slow', 'optical', 'wdm'})
|
||||
function muxAndDemuxPreserveMetadataAndSamplingRates(testCase)
|
||||
wf = testCase.workflow;
|
||||
numChannels = wf.params.numChannels;
|
||||
|
||||
testCase.verifyClass(wf.muxOut, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.launchOut, 'Opticalsignal');
|
||||
testCase.verifyClass(wf.demuxOut, 'cell');
|
||||
testCase.verifyNumElements(wf.demuxOut, numChannels);
|
||||
|
||||
testCase.verifyEqual(wf.muxOut.fs, wf.params.fsMux);
|
||||
testCase.verifyEqual(wf.launchOut.fs, wf.params.fsMux);
|
||||
for ch = 1:numChannels
|
||||
testCase.verifyEqual(wf.demuxOut{ch}.fs, wf.params.fsBase);
|
||||
end
|
||||
|
||||
testCase.verifyEqual(numel(wf.muxOut.lambda), numChannels);
|
||||
testCase.verifyEqual(wf.muxOut.lambda, wf.channelPlanM, "AbsTol", 5e-9);
|
||||
for ch = 1:numChannels
|
||||
testCase.verifyEqual(wf.demuxOut{ch}.lambda, wf.channelPlanM(ch), "AbsTol", 5e-9);
|
||||
end
|
||||
|
||||
testCase.verifyTrue(all(isfinite(wf.muxOut.signal), "all"));
|
||||
testCase.verifyTrue(all(isfinite(wf.launchOut.signal), "all"));
|
||||
for ch = 1:numChannels
|
||||
testCase.verifyTrue(all(isfinite(wf.demuxOut{ch}.signal), "all"));
|
||||
end
|
||||
|
||||
testCase.verifyGreaterThan(height(wf.muxOut.logbook), 0);
|
||||
for ch = 1:numChannels
|
||||
testCase.verifyGreaterThan(height(wf.demuxOut{ch}.logbook), 0);
|
||||
end
|
||||
end
|
||||
|
||||
function intendedChannelIsRecoveredBetterThanCrossTalk(testCase)
|
||||
wf = testCase.workflow;
|
||||
numChannels = wf.params.numChannels;
|
||||
|
||||
projectionPower = zeros(numChannels);
|
||||
branchPower = zeros(numChannels, 1);
|
||||
branchLambdaIdx = zeros(numChannels, 1);
|
||||
|
||||
for outCh = 1:numChannels
|
||||
branchSignal = wf.demuxOut{outCh}.signal;
|
||||
branchPower(outCh) = mean(abs(branchSignal).^2, 'all');
|
||||
branchLambdaIdx(outCh) = nearestWavelengthIndex( ...
|
||||
wf.demuxOut{outCh}.lambda, ...
|
||||
wf.channelPlanM);
|
||||
|
||||
for refCh = 1:numChannels
|
||||
projectionPower(outCh, refCh) = projectedPower( ...
|
||||
branchSignal, ...
|
||||
wf.channelRefs{refCh}.signal);
|
||||
end
|
||||
end
|
||||
|
||||
for ch = 1:numChannels
|
||||
matchedRef = branchLambdaIdx(ch);
|
||||
otherIdx = setdiff(1:numChannels, matchedRef);
|
||||
bestProjection = projectionPower(ch, matchedRef);
|
||||
offProjection = max(projectionPower(ch, otherIdx));
|
||||
|
||||
% The recovered branch should keep non-trivial optical power
|
||||
% after demux, even when compared against the leaked branches.
|
||||
testCase.verifyGreaterThan(branchPower(ch), 0);
|
||||
testCase.verifyGreaterThan(branchPower(ch), 0.7 * mean(branchPower));
|
||||
|
||||
% The intended branch should dominate its row of the
|
||||
% projection matrix even if the demux ordering changes.
|
||||
testCase.verifyGreaterThan(bestProjection, offProjection * 1.0001);
|
||||
testCase.verifyGreaterThan( ...
|
||||
bestProjection / sum(projectionPower(ch, :)), ...
|
||||
0.5 / numChannels);
|
||||
|
||||
% Correlation is kept as a secondary diagnostic metric.
|
||||
testCase.verifyTrue(isfinite(normalizedCorrelation( ...
|
||||
centredPowerEnvelope(wf.demuxOut{ch}.signal), ...
|
||||
centredPowerEnvelope(wf.channelRefs{matchedRef}.signal))));
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function workflow = buildWdmWorkflow()
|
||||
params = wdmIntegrationParameters();
|
||||
channelPlanNm = calcWavelengthPlan(params.numChannels, params.channelSpacingHz, params.lambdaCenterNm);
|
||||
channelPlanM = channelPlanNm * 1e-9;
|
||||
|
||||
% Build the two WDM channels separately so the test can later compare
|
||||
% the recovered branch against the original reference branch.
|
||||
channelRefs = cell(1, params.numChannels);
|
||||
for ch = 1:params.numChannels
|
||||
drive = makeElectricalSignal(params.driveSignals{ch}, params.fsBase);
|
||||
|
||||
optical = EML( ...
|
||||
"mode", eml_mode.im_cosinus, ...
|
||||
"fsimu", params.fsBase, ...
|
||||
"lambda", channelPlanNm(ch), ...
|
||||
"power", params.laserPowerDbm, ...
|
||||
"linewidth", 0, ...
|
||||
"alpha", 0, ...
|
||||
"ampl_imbal", 0, ...
|
||||
"pha_imbal", 0, ...
|
||||
"bias", params.vbias, ...
|
||||
"u_pi", params.uPi, ...
|
||||
"randomkey", params.randomKey + ch).process(drive);
|
||||
|
||||
% Create a dual-polarization optical channel, mirroring the WDM
|
||||
% scripts where the mux receives DP optical signals.
|
||||
channelRefs{ch} = Polarization_Controller( ...
|
||||
"mode", "rot_power", ...
|
||||
"desired_power", 50).process(optical);
|
||||
end
|
||||
|
||||
mux = Optical_Multiplex( ...
|
||||
"fs_in", params.fsBase, ...
|
||||
"fs_out", params.fsMux, ...
|
||||
"lambda_center", params.lambdaCenterNm, ...
|
||||
"delta_f", 0, ...
|
||||
"random_key", 0, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 1, ...
|
||||
"B", 200e9);
|
||||
muxOut = mux.process(channelRefs);
|
||||
|
||||
% A very small deterministic propagation hop keeps the fixture closer
|
||||
% to the WDM workflows without turning this into a full channel study.
|
||||
wdmFiberOut = DP_Fiber( ...
|
||||
"L", 0, ...
|
||||
"dz", 1, ...
|
||||
"lambda", params.lambdaCenterNm, ...
|
||||
"rng", 0, ...
|
||||
"gamma", 0, ...
|
||||
"fa", muxOut.fs, ...
|
||||
"X_alpha", 0, ...
|
||||
"D", 0, ...
|
||||
"Ds", 0, ...
|
||||
"Dpmd", 0, ...
|
||||
"beat_len", 1e9, ...
|
||||
"corr_len", 1e9, ...
|
||||
"manakov", 0, ...
|
||||
"SS_dphimax", 1e-2, ...
|
||||
"SS_dzmax", 1, ...
|
||||
"SS_dzmin", 1, ...
|
||||
"n_waveplates", 1, ...
|
||||
"useGPU", false, ...
|
||||
"useSingle", false).process(muxOut);
|
||||
|
||||
% The WDM project scripts normally apply a launch amplifier after the
|
||||
% mux. We keep that stage here, but with deterministic no-noise settings.
|
||||
launchOut = Amplifier( ...
|
||||
"amp_mode", amp_mode.ideal_no_noise, ...
|
||||
"gain_mode", gain_mode.output_power, ...
|
||||
"nase_mode", nase_mode.pass_ase, ...
|
||||
"amplification_db", params.launchPowerDbm).process(wdmFiberOut);
|
||||
|
||||
demux = Optical_Demultiplex( ...
|
||||
"fs_in", launchOut.fs, ...
|
||||
"fs_out", params.fsBase, ...
|
||||
"lambda_center", params.lambdaCenterNm, ...
|
||||
"attenuation", 0, ...
|
||||
"filtype", 1, ...
|
||||
"B", 200e9);
|
||||
demuxOut = demux.process(launchOut);
|
||||
|
||||
workflow = struct();
|
||||
workflow.params = params;
|
||||
workflow.channelPlanNm = channelPlanNm;
|
||||
workflow.channelPlanM = channelPlanM;
|
||||
workflow.channelRefs = channelRefs;
|
||||
workflow.muxOut = muxOut;
|
||||
workflow.wdmFiberOut = wdmFiberOut;
|
||||
workflow.launchOut = launchOut;
|
||||
workflow.demuxOut = demuxOut;
|
||||
end
|
||||
|
||||
function params = wdmIntegrationParameters()
|
||||
params = struct();
|
||||
|
||||
% Keep the fixture small, deterministic, and close to the WDM scripts.
|
||||
params.numChannels = 4;
|
||||
params.channelSpacingHz = 200e9;
|
||||
params.lambdaCenterNm = 1310;
|
||||
params.fsBase = 64e9;
|
||||
params.fsMux = 4 * params.fsBase;
|
||||
params.laserPowerDbm = 3;
|
||||
params.launchPowerDbm = params.laserPowerDbm + 10*log10(params.numChannels);
|
||||
params.uPi = 4.6;
|
||||
params.vbias = -0.5 * params.uPi;
|
||||
params.randomKey = 11;
|
||||
|
||||
n = (0:63).';
|
||||
params.driveSignals = {
|
||||
0.18 * sin(2*pi*n/16) + 0.03 * cos(2*pi*n/8)
|
||||
0.17 * cos(2*pi*n/11 + pi/5) - 0.04 * sin(2*pi*n/5)
|
||||
0.16 * sin(2*pi*n/7 + pi/7) + 0.02 * cos(2*pi*n/4)
|
||||
0.14 * cos(2*pi*n/9) - 0.05 * sin(2*pi*n/6 + pi/8)
|
||||
};
|
||||
end
|
||||
|
||||
function sig = makeElectricalSignal(values, fs)
|
||||
base = Signal(values, "fs", fs);
|
||||
sig = Electricalsignal( ...
|
||||
values, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook);
|
||||
end
|
||||
|
||||
function corrVal = normalizedCorrelation(refSignal, candidateSignal)
|
||||
ref = refSignal(:);
|
||||
cand = candidateSignal(:);
|
||||
|
||||
denom = sqrt(sum(abs(ref).^2) * sum(abs(cand).^2));
|
||||
if denom == 0
|
||||
corrVal = 0;
|
||||
return
|
||||
end
|
||||
|
||||
corrVal = abs(sum(conj(ref) .* cand)) / denom;
|
||||
end
|
||||
|
||||
function idx = nearestWavelengthIndex(value, plan)
|
||||
[~, idx] = min(abs(plan(:) - value));
|
||||
end
|
||||
|
||||
function pwr = projectedPower(refSignal, candidateSignal)
|
||||
ref = refSignal(:);
|
||||
cand = candidateSignal(:);
|
||||
|
||||
denom = sum(abs(ref).^2);
|
||||
if denom == 0
|
||||
pwr = 0;
|
||||
return
|
||||
end
|
||||
|
||||
pwr = abs(sum(conj(ref) .* cand)).^2 / denom;
|
||||
end
|
||||
|
||||
function centeredEnvelope = centredPowerEnvelope(signal)
|
||||
powerEnvelope = abs(signal(:)).^2;
|
||||
centeredEnvelope = powerEnvelope - mean(powerEnvelope);
|
||||
end
|
||||
290
Tests/integration/helpers/buildReducedImddWorkflow.m
Normal file
290
Tests/integration/helpers/buildReducedImddWorkflow.m
Normal file
@@ -0,0 +1,290 @@
|
||||
function workflow = buildReducedImddWorkflow(scenario)
|
||||
%BUILDREDUCEDIMDDWORKFLOW Build a reduced deterministic IM/DD workflow.
|
||||
%
|
||||
% The helper keeps the stage order aligned with the project workflow while
|
||||
% allowing tests to switch between representative scenarios:
|
||||
% - "minimal": reduced regression baseline
|
||||
% - "no-impairment": near-ideal baseline with impairment knobs neutralized
|
||||
% - "baseline": reference point for monotonicity checks
|
||||
% - "impaired": intentionally degraded variant for monotonicity checks
|
||||
|
||||
arguments
|
||||
scenario (1, 1) string = "minimal"
|
||||
end
|
||||
|
||||
params = scenarioParameters(scenario);
|
||||
workflow = runReducedWorkflow(params, scenario);
|
||||
end
|
||||
|
||||
function workflow = runReducedWorkflow(params, scenario)
|
||||
% -------------------- TX --------------------
|
||||
txPulse = Pulseformer( ...
|
||||
"fsym", params.fsym, ...
|
||||
"fdac", params.fdac, ...
|
||||
"pulse", "rrc", ...
|
||||
"pulselength", params.pulseLength, ...
|
||||
"alpha", params.rcalpha);
|
||||
|
||||
[digiSig, symbols, txBits] = PAMsource( ...
|
||||
"fsym", params.fsym, ...
|
||||
"M", params.M, ...
|
||||
"order", params.sourceOrder, ...
|
||||
"useprbs", false, ...
|
||||
"fs_out", params.fdac, ...
|
||||
"applyclipping", false, ...
|
||||
"applypulseform", true, ...
|
||||
"pulseformer", txPulse, ...
|
||||
"randkey", params.randomKey, ...
|
||||
"duobinary_mode", db_mode.no_db, ...
|
||||
"mrds_code", 0).process();
|
||||
|
||||
elSig = AWG( ...
|
||||
"fdac", params.fdac, ...
|
||||
"f_cutoff", params.fsym, ...
|
||||
"lpf_active", params.awgLpfActive, ...
|
||||
"kover", params.kover, ...
|
||||
"bit_resolution", params.awgBitResolution, ...
|
||||
"upsampling_method", params.awgUpsamplingMethod, ...
|
||||
"precomp_sinc_rolloff", params.awgPrecompSincRolloff, ...
|
||||
"normalize2dac", params.awgNormalize2dac).process(digiSig);
|
||||
|
||||
elSig = elSig.normalize("mode", "oneone");
|
||||
elSig = elSig .* params.driverScaling;
|
||||
|
||||
% -------------------- Optical Channel --------------------
|
||||
optSigTx = EML( ...
|
||||
"mode", eml_mode.im_cosinus, ...
|
||||
"power", params.opticalPowerDbm, ...
|
||||
"fsimu", elSig.fs, ...
|
||||
"lambda", params.laserWavelengthNm, ...
|
||||
"bias", params.vbias, ...
|
||||
"u_pi", params.uPi, ...
|
||||
"linewidth", 0, ...
|
||||
"randomkey", params.randomKey + 1, ...
|
||||
"alpha", 0).process(elSig);
|
||||
|
||||
optSig = Fiber( ...
|
||||
"fsimu", optSigTx.fs, ...
|
||||
"fiber_length", params.linkLengthKm, ...
|
||||
"alpha", params.fiberAlphaDbPerKm, ...
|
||||
"D", 0, ...
|
||||
"lambda0", 1310, ...
|
||||
"gamma", 0, ...
|
||||
"Dslope", 0.07).process(optSigTx);
|
||||
|
||||
rxOptSig = Amplifier( ...
|
||||
"amp_mode", "ideal_no_noise", ...
|
||||
"gain_mode", "output_power", ...
|
||||
"amplification_db", params.ropDbm).process(optSig);
|
||||
|
||||
rxSigAfterPd = Photodiode( ...
|
||||
"fsimu", params.fdac * params.kover, ...
|
||||
"dark_current", 0, ...
|
||||
"responsivity", 1, ...
|
||||
"temperature", 20, ...
|
||||
"nep", 0, ...
|
||||
"randomkey", params.randomKey + 2).process(rxOptSig);
|
||||
|
||||
rxSigFiltered = Filter( ...
|
||||
"active", params.rxFilterActive, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"f_cutoff", params.rxElectricalBandwidthHz, ...
|
||||
"fs", params.fdac * params.kover, ...
|
||||
"signal_length", 0, ...
|
||||
"filtdegree", 4, ...
|
||||
"lowpass", 1).process(rxSigAfterPd);
|
||||
|
||||
scopeLpf = Filter( ...
|
||||
"active", params.scopeLpfActive, ...
|
||||
"filterType", filtertypes.butterworth, ...
|
||||
"f_cutoff", params.scopeBandwidthHz, ...
|
||||
"fs", params.fadc, ...
|
||||
"signal_length", 0, ...
|
||||
"filtdegree", 4, ...
|
||||
"lowpass", 1);
|
||||
|
||||
scpeSigPreMf = Scope( ...
|
||||
"fsimu", params.fdac * params.kover, ...
|
||||
"fadc", params.fadc, ...
|
||||
"adcresolution", params.scopeAdcResolution, ...
|
||||
"quantbuffer", params.scopeQuantBuffer, ...
|
||||
"delay", 0, ...
|
||||
"fixed_delay", 0, ...
|
||||
"filtertype", filtertypes.butterworth, ...
|
||||
"samplingdelay", 0, ...
|
||||
"rand_samplingdelay", 0, ...
|
||||
"freq_offset", 0, ...
|
||||
"samp_jitter", 0, ...
|
||||
"block_dc", 1, ...
|
||||
"lpf_active", params.scopeLpfActive, ...
|
||||
"H_lpf", scopeLpf).process(rxSigFiltered);
|
||||
|
||||
rxMatchedFilter = Pulseformer( ...
|
||||
"fsym", params.fsym, ...
|
||||
"fdac", 2 * params.fsym, ...
|
||||
"pulse", "rrc", ...
|
||||
"pulselength", params.pulseLength, ...
|
||||
"alpha", params.rcalpha, ...
|
||||
"matched", 1);
|
||||
scpeSig = rxMatchedFilter.process(scpeSigPreMf);
|
||||
|
||||
[syncedSig, ~] = scpeSig.tsynch( ...
|
||||
"reference", symbols, ...
|
||||
"fs_ref", params.fsym, ...
|
||||
"debug_plots", 0);
|
||||
syncedCenteredSig = syncedSig - mean(syncedSig.signal);
|
||||
syncedSig = syncedCenteredSig;
|
||||
syncedSig.signal = syncedSig.signal(1 : 2 * length(symbols));
|
||||
|
||||
% -------------------- DSP --------------------
|
||||
ffeEq = FFE( ...
|
||||
"epochs_tr", 2, ...
|
||||
"epochs_dd", 1, ...
|
||||
"len_tr", params.lenTr, ...
|
||||
"mu_dd", 1e-4, ...
|
||||
"mu_tr", 1e-2, ...
|
||||
"order", 21, ...
|
||||
"sps", 2, ...
|
||||
"decide", 0, ...
|
||||
"adaption_technique", adaption_method.nlms, ...
|
||||
"dd_mode", 1);
|
||||
|
||||
ffeResults = ffe( ...
|
||||
ffeEq, ...
|
||||
params.M, ...
|
||||
syncedSig, ...
|
||||
symbols, ...
|
||||
txBits, ...
|
||||
"precode_mode", db_mode.no_db, ...
|
||||
"showAnalysis", 0, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
mlseEq = FFE( ...
|
||||
"epochs_tr", 2, ...
|
||||
"epochs_dd", 1, ...
|
||||
"len_tr", params.lenTr, ...
|
||||
"mu_dd", 1e-4, ...
|
||||
"mu_tr", 1e-2, ...
|
||||
"order", 21, ...
|
||||
"sps", 2, ...
|
||||
"decide", 0, ...
|
||||
"adaption_technique", adaption_method.nlms, ...
|
||||
"dd_mode", 1);
|
||||
postfilter = Postfilter("ncoeff", 1, "useBurg", 1);
|
||||
mlse = MLSE( ...
|
||||
"duobinary_output", 0, ...
|
||||
"M", params.M, ...
|
||||
"trellis_states", PAMmapper(params.M, 0).levels);
|
||||
|
||||
[vnleResults, mlseResults] = vnle_postfilter_mlse( ...
|
||||
mlseEq, ...
|
||||
postfilter, ...
|
||||
mlse, ...
|
||||
params.M, ...
|
||||
syncedSig, ...
|
||||
symbols, ...
|
||||
txBits, ...
|
||||
"precode_mode", db_mode.no_db, ...
|
||||
"showAnalysis", 0, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
workflow = struct();
|
||||
workflow.scenario = scenario;
|
||||
workflow.params = params;
|
||||
workflow.Digi_sig = digiSig;
|
||||
workflow.Symbols = symbols;
|
||||
workflow.Tx_bits = txBits;
|
||||
workflow.El_sig = elSig;
|
||||
workflow.Opt_sig_tx = optSigTx;
|
||||
workflow.Opt_sig = optSig;
|
||||
workflow.Rx_sig_after_pd = rxSigAfterPd;
|
||||
workflow.Rx_sig_filtered = rxSigFiltered;
|
||||
workflow.Scpe_sig_pre_mf = scpeSigPreMf;
|
||||
workflow.Scpe_sig = scpeSig;
|
||||
workflow.Synced_sig_centered = syncedCenteredSig;
|
||||
workflow.Synced_sig = syncedSig;
|
||||
workflow.ffe_results = ffeResults;
|
||||
workflow.vnle_results = vnleResults;
|
||||
workflow.mlse_results = mlseResults;
|
||||
end
|
||||
|
||||
function params = scenarioParameters(scenario)
|
||||
params = commonParameters();
|
||||
|
||||
switch lower(string(scenario))
|
||||
case "minimal"
|
||||
params.ropDbm = 0;
|
||||
params.rxElectricalBandwidthHz = 40e9;
|
||||
params.scopeBandwidthHz = 25e9;
|
||||
|
||||
case "no-impairment"
|
||||
params.randomKey = 11;
|
||||
params.linkLengthKm = 0;
|
||||
params.fiberAlphaDbPerKm = 0;
|
||||
params.ropDbm = 3;
|
||||
params.rxElectricalBandwidthHz = 200e9;
|
||||
params.scopeBandwidthHz = 200e9;
|
||||
params.rxFilterActive = false;
|
||||
params.scopeLpfActive = false;
|
||||
params.scopeAdcResolution = 24;
|
||||
params.scopeQuantBuffer = 0.05;
|
||||
params.awgLpfActive = false;
|
||||
params.awgBitResolution = 12;
|
||||
params.awgUpsamplingMethod = upsampling_mode.resample;
|
||||
params.awgNormalize2dac = true;
|
||||
|
||||
case "baseline"
|
||||
params.ropDbm = 0;
|
||||
params.rxElectricalBandwidthHz = 40e9;
|
||||
params.scopeBandwidthHz = 25e9;
|
||||
|
||||
case "impaired"
|
||||
params.ropDbm = -15;
|
||||
params.rxElectricalBandwidthHz = 12e9;
|
||||
params.scopeBandwidthHz = 12e9;
|
||||
|
||||
otherwise
|
||||
error("buildReducedImddWorkflow:UnknownScenario", ...
|
||||
"Unknown reduced IM/DD scenario '%s'.", scenario);
|
||||
end
|
||||
end
|
||||
|
||||
function params = commonParameters()
|
||||
params = struct();
|
||||
|
||||
params.M = 4;
|
||||
params.fsym = 16e9;
|
||||
params.fdac = 64e9;
|
||||
params.fadc = 64e9;
|
||||
params.kover = 2;
|
||||
params.randomKey = 1;
|
||||
params.sourceOrder = 12;
|
||||
params.rcalpha = 0.05;
|
||||
params.pulseLength = 12;
|
||||
params.lenTr = 256;
|
||||
|
||||
params.uPi = 3;
|
||||
params.vbiasRel = 0.5;
|
||||
params.vbias = -params.vbiasRel * params.uPi;
|
||||
params.driverScaling = 0.6 * (params.uPi / 2 - abs(params.vbias - params.uPi / 2));
|
||||
|
||||
params.laserWavelengthNm = 1293;
|
||||
params.opticalPowerDbm = 3;
|
||||
params.linkLengthKm = 1;
|
||||
params.fiberAlphaDbPerKm = 0.3;
|
||||
|
||||
params.ropDbm = 0;
|
||||
params.rxElectricalBandwidthHz = 40e9;
|
||||
params.scopeBandwidthHz = 25e9;
|
||||
params.rxFilterActive = true;
|
||||
params.scopeLpfActive = true;
|
||||
params.scopeAdcResolution = 8;
|
||||
params.scopeQuantBuffer = 0.1;
|
||||
params.awgLpfActive = false;
|
||||
params.awgBitResolution = 8;
|
||||
params.awgUpsamplingMethod = upsampling_mode.samplehold;
|
||||
params.awgPrecompSincRolloff = 1;
|
||||
params.awgNormalize2dac = false;
|
||||
end
|
||||
Reference in New Issue
Block a user