classdef IMDD_base_system_minimal_integration_test < IMDDTestCase % First integration test for the reduced IM/DD base-system workflow. % % The goal of this test is not to freeze the full waveform exactly. % Instead, it checks stable system-level contracts: % - 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. % Once the team has reviewed repeated runs, these can be tightened. properties workflow end methods (TestClassSetup) function runReducedImddWorkflowOnce(testCase) % Run the deterministic reduced workflow once and share the % resulting signals/metrics across all test methods. testCase.workflow = buildReducedImddWorkflow("minimal"); end end methods (Test, TestTags = {'integration', 'slow', 'imdd'}) function reducedWorkflowBuildsExpectedSignalStages(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.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 % transmitted symbol before equalization. testCase.verifyEqual(length(wf.Synced_sig.signal), 2 * length(wf.Symbols.signal)); end function reducedWorkflowProducesFiniteSignalsAndMetrics(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 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); 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 reducedWorkflowMeetsProvisionalPerformanceChecks(testCase) wf = testCase.workflow; % These are deliberately loose first-pass regression guards. % Tighten them only after repeated baseline runs are reviewed. % First observed baseline on 2026-03-24 for this reduced setup: % FFE BER ~= 2.58e-1 % % This threshold is intentionally loose for the first % integration-test iteration. Tighten it only after the reduced % workflow has been reviewed across repeated runs and code % changes. provisionalMaxFfeBer = 3e-1; provisionalMaxMlseBer = 3e-1; testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, provisionalMaxFfeBer); testCase.verifyLessThanOrEqual(wf.mlse_results.metrics.BER, provisionalMaxMlseBer); % MLSE should not regress relative to the direct FFE path on % this deterministic reduced setup. testCase.verifyLessThanOrEqual( ... wf.mlse_results.metrics.BER, ... wf.ffe_results.metrics.BER + 1e-12); end end end function p = signalPower(signal) p = mean(abs(signal(:)).^2); end function v = signalVariance(signal) centered = signal(:) - mean(signal(:)); v = mean(abs(centered).^2); end