Merge branch 'main' of cau-git.rz.uni-kiel.de:nt/mitarbeiter/silas/imdd_simulation
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,3 +23,7 @@ codegen/
|
|||||||
|
|
||||||
|
|
||||||
.mat
|
.mat
|
||||||
|
|
||||||
|
# Local test dashboard status tracking
|
||||||
|
Tests/.last_test_run.json
|
||||||
|
Tests/reports/
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ classdef Optical_Demultiplex < handle
|
|||||||
if isempty(obj.wavelengthplan)
|
if isempty(obj.wavelengthplan)
|
||||||
obj.wavelengthplan = signalclass_in.lambda; %meter
|
obj.wavelengthplan = signalclass_in.lambda; %meter
|
||||||
else
|
else
|
||||||
if all(500e-9 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500e-9) %check if given in nm
|
if all(500 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500) %check if given in nm
|
||||||
obj.wavelengthplan = obj.wavelengthplan.*1e-9;
|
obj.wavelengthplan = obj.wavelengthplan.*1e-9;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
function loss_curve_digitized(filename)
|
|
||||||
% PLOT_WPD_DATASETS Reads and plots scattering data from a CSV file.
|
|
||||||
% filename: String containing the path to the CSV file (e.g., 'wpd_datasets.csv')
|
|
||||||
%
|
|
||||||
% This function expects a CSV with the following structure:
|
|
||||||
% Row 1: Dataset Names (every 2nd column)
|
|
||||||
% Row 2: Variable Names (X, Y, X, Y...)
|
|
||||||
% Row 3+: Numeric Data (potentially with NaNs for unequal lengths)
|
|
||||||
|
|
||||||
% --- 1. Import Data ---
|
|
||||||
if nargin < 1
|
|
||||||
filename = 'wpd_datasets.csv'; % Default filename
|
|
||||||
end
|
|
||||||
|
|
||||||
% Read the numeric data, skipping the first 2 header lines
|
|
||||||
% 'TreatAsMissing' ensures empty cells become NaNs
|
|
||||||
raw_data = readmatrix(filename, 'NumHeaderLines', 2);
|
|
||||||
|
|
||||||
% Read the first line separately to parse Dataset Names
|
|
||||||
fid = fopen(filename, 'r');
|
|
||||||
if fid == -1
|
|
||||||
error('Could not open file: %s', filename);
|
|
||||||
end
|
|
||||||
header_line = fgetl(fid);
|
|
||||||
fclose(fid);
|
|
||||||
|
|
||||||
% Split header by comma to get names
|
|
||||||
raw_names = split(header_line, ',');
|
|
||||||
|
|
||||||
% Extract non-empty names (assuming names are in col 1, 3, 5...)
|
|
||||||
dataset_names = raw_names(~cellfun('isempty', raw_names));
|
|
||||||
|
|
||||||
% --- 2. Setup Plot ---
|
|
||||||
figure('Color', 'w', 'Position', [100, 100, 800, 600]);
|
|
||||||
ax = gca;
|
|
||||||
hold(ax, 'on');
|
|
||||||
|
|
||||||
|
|
||||||
line_styles = {'-', '-', '-', '-'};
|
|
||||||
|
|
||||||
% --- 3. Iterate and Plot Each Dataset ---
|
|
||||||
num_datasets = length(dataset_names);
|
|
||||||
|
|
||||||
for i = 1:num_datasets
|
|
||||||
% Calculate column indices for X and Y
|
|
||||||
% Dataset 1: Cols 1,2 | Dataset 2: Cols 3,4 | etc.
|
|
||||||
col_x = (i-1)*2 + 1;
|
|
||||||
col_y = (i-1)*2 + 2;
|
|
||||||
|
|
||||||
% Extract data
|
|
||||||
if col_y > size(raw_data, 2)
|
|
||||||
warning('Data columns missing for dataset %d', i);
|
|
||||||
break;
|
|
||||||
end
|
|
||||||
|
|
||||||
X = raw_data(:, col_x);
|
|
||||||
Y = raw_data(:, col_y);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% Remove NaNs (missing data due to unequal lengths)
|
|
||||||
valid_mask = ~isnan(X) & ~isnan(Y);
|
|
||||||
X = X(valid_mask);
|
|
||||||
Y = Y(valid_mask);
|
|
||||||
|
|
||||||
[X, sortIdx] = sort(X);
|
|
||||||
Y = Y(sortIdx);
|
|
||||||
|
|
||||||
% 3. Smooth the Data
|
|
||||||
|
|
||||||
if numel(X) > 20
|
|
||||||
if 1
|
|
||||||
Y = smoothdata(Y, 'sgolay', 15);
|
|
||||||
else
|
|
||||||
Y = movmean(Y, 15);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
% Plotting
|
|
||||||
% Using semilogy because scattering data often spans orders of magnitude
|
|
||||||
% (Adjust to 'plot' if linear scale is preferred)
|
|
||||||
p = plot(ax, X, Y, ...
|
|
||||||
'LineStyle', line_styles{mod(i-1, length(line_styles)) + 1}, ...
|
|
||||||
'Marker', 'none', ...
|
|
||||||
'Color', 'black', ...
|
|
||||||
'LineWidth', 1.5, ...
|
|
||||||
'MarkerSize', 6, ...
|
|
||||||
'DisplayName', dataset_names{i});
|
|
||||||
|
|
||||||
% Optional: Fill marker faces for better visibility
|
|
||||||
% p.MarkerFaceColor = p.Color;
|
|
||||||
% p.MarkerFaceAlpha = 0.3; % Semi-transparent fill
|
|
||||||
end
|
|
||||||
|
|
||||||
% --- 4. Styling and Formatting ---
|
|
||||||
|
|
||||||
% Axis Labels (Inferred from typical scattering plots)
|
|
||||||
xlabel(ax, 'Wavelength (\mu m)', 'FontSize', 12, 'FontWeight', 'bold');
|
|
||||||
ylabel(ax, 'Intensity / Cross-Section (a.u.)', 'FontSize', 12, 'FontWeight', 'bold');
|
|
||||||
|
|
||||||
% Title
|
|
||||||
title(ax, 'Dataset Comparison', 'FontSize', 14);
|
|
||||||
|
|
||||||
% Legend
|
|
||||||
legend(ax, 'Location', 'best', 'Interpreter', 'none', 'Box', 'on');
|
|
||||||
|
|
||||||
% Grid
|
|
||||||
grid(ax, 'on');
|
|
||||||
ax.GridAlpha = 0.3;
|
|
||||||
ax.MinorGridAlpha = 0.1;
|
|
||||||
|
|
||||||
% Set Log Scale for Y (likely required for this data type)
|
|
||||||
set(ax, 'YScale', 'log');
|
|
||||||
|
|
||||||
% Enhance axis appearance
|
|
||||||
set(ax, 'Box', 'on', 'LineWidth', 1.2, 'FontSize', 10);
|
|
||||||
|
|
||||||
hold(ax, 'off');
|
|
||||||
end
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
Rayleigh,,Experimental,,Infrared Absorption,
|
|
||||||
X,Y,X,Y,X,Y
|
|
||||||
0.7066005680911753,3.651009696525016,0.7072188355785799,4.848577786727532,1.4943918372804705,0.009324755400827079
|
|
||||||
0.7362740977034739,3.1008424465551374,0.7104385926007812,5.059236053617898,1.5870823136443164,0.04795688074913024
|
|
||||||
0.7704634954089999,2.559836201011587,0.7143024334187478,5.3165954733738054,1.6443739975494367,0.12666181795273013
|
|
||||||
0.8246453070916075,1.9963763545469397,0.7194723146571098,4.679249634272495,1.6900695253042852,0.3029389206853443
|
|
||||||
0.8846384359818439,1.4197559292837703,0.7194729766137344,4.646181068667172,1.7344762235109785,0.7194296259968607
|
|
||||||
0.9375334039861705,1.0762856922437043,0.7227053108118038,4.236869693191961,1.7975573800551148,2.1896107578891244
|
|
||||||
0.9929980876073115,0.9010620061822204,0.7272205169484065,4.147543538340177,,
|
|
||||||
1.0420225952274251,0.6977835411732736,0.7291487965959542,4.420849736815974,,
|
|
||||||
1.0845923638007697,0.5842349714248559,0.7310784001567514,4.645798669234298,,
|
|
||||||
1.129741777340298,0.4856980392071641,0.7355902965102309,4.71201487210429,,
|
|
||||||
1.1981066716622326,0.3841608204561696,0.738821306795051,4.358286713153517,,
|
|
||||||
1.24841405122088,0.31935666463679646,0.7414069093708566,4.059822180897163,,
|
|
||||||
1.289688370679823,0.2850148847474179,0.742705668268381,3.676053825899206,,
|
|
||||||
1.3541801567910463,0.23691184007300667,0.7440031032526563,3.376112267508452,,
|
|
||||||
1.4083573347772815,0.19416797258075097,0.7452965664971838,3.235432943532804,,
|
|
||||||
1.4618851333147547,0.16723647350379714,0.7459426361628229,3.1898499103097238,,
|
|
||||||
1.50315548103395,0.15574103754742674,0.7549743723492774,3.0137121806723157,,
|
|
||||||
1.5386251028515106,0.1419883611511702,0.7620678995388148,2.971117042589562,,
|
|
||||||
1.596665459699088,0.12316075278891304,0.765291628300764,2.971049114206588,,
|
|
||||||
1.6437444767994136,0.10759838849626362,0.7756075603390012,2.9708317538173317,,
|
|
||||||
1.683725994970454,0.09949510555249974,0.7762523060913911,2.9708181693210105,,
|
|
||||||
1.717258069747722,0.09398483395032568,0.7839919029465676,2.8875658983600925,,
|
|
||||||
1.7636903552257834,0.08387517688001396,0.7885090949530442,2.767180560538318,,
|
|
||||||
1.7940013490676008,0.07701567091052414,0.7917348095848672,2.7088647150205,,
|
|
||||||
,,0.7936723566251598,2.6144535948583694,,
|
|
||||||
,,0.7962546494178423,2.5233214143921474,,
|
|
||||||
,,0.8085087904529968,2.417989089048743,,
|
|
||||||
,,0.8104423657535417,2.435165393892523,,
|
|
||||||
,,0.8201175237791369,2.3335556930105406,,
|
|
||||||
,,0.8285065000830756,2.158298098247211,,
|
|
||||||
,,0.8317335386281479,2.083056634772393,,
|
|
||||||
,,0.8356046609689853,2.024737963659226,,
|
|
||||||
,,0.8407639509013534,1.996148150112612,,
|
|
||||||
,,0.8459232408337212,1.967962031984024,,
|
|
||||||
,,0.854951005280428,1.9401206794865944,,
|
|
||||||
,,0.8601116191260452,1.8857864945455538,,
|
|
||||||
,,0.8691446792257491,1.7565635929668848,,
|
|
||||||
,,0.8743052930713663,1.7073700288086895,,
|
|
||||||
,,0.8788211611645935,1.6595617469926904,,
|
|
||||||
,,0.8826916215488064,1.624580544751916,,
|
|
||||||
,,0.8891397410293294,1.6130257674984603,,
|
|
||||||
,,0.896234592132116,1.5678305544227285,,
|
|
||||||
,,0.903974850943917,1.5131252291764425,,
|
|
||||||
,,0.90784531132813,1.4812307005435945,,
|
|
||||||
,,0.9155829223134326,1.470682044636039,,
|
|
||||||
,,0.9220303798373308,1.4706147972609174,,
|
|
||||||
,,0.9310574823274131,1.460128389693264,,
|
|
||||||
,,0.9349259568417521,1.4600883304432553,,
|
|
||||||
,,0.9433056657529459,1.4913980336424177,,
|
|
||||||
,,0.9516853746641398,1.5233791328756299,,
|
|
||||||
,,0.956844002639883,1.512557980118849,,
|
|
||||||
,,0.9671632444612435,1.4597545460975296,,
|
|
||||||
,,0.9710370146285795,1.379199997932108,,
|
|
||||||
,,0.9736206313345113,1.3123773159521164,,
|
|
||||||
,,0.9774937395452226,1.2487807938448074,,
|
|
||||||
,,0.9826543533908398,1.213807976266501,,
|
|
||||||
,,0.9903926263327669,1.1966468122906808,,
|
|
||||||
,,0.9942630867169798,1.171423198750918,,
|
|
||||||
,,1.0045796807118417,1.1630595787371243,,
|
|
||||||
,,1.0097376467309604,1.163017033545686,,
|
|
||||||
,,1.0219851681998686,1.1963787227370668,,
|
|
||||||
,,1.0290753856062829,1.2220446908209617,,
|
|
||||||
,,1.0348774354211667,1.2306917801168409,,
|
|
||||||
,,1.0471421677623152,1.052809309517664,,
|
|
||||||
,,1.0510159379296513,0.9947114748787712,,
|
|
||||||
,,1.0445565651865096,1.1302083245776995,,
|
|
||||||
,,1.0458487045177878,1.0985863367418764,,
|
|
||||||
,,1.0542449623445975,0.9398239868494617,,
|
|
||||||
,,1.0613444471437565,0.8692481092753317,,
|
|
||||||
,,1.0690886776953055,0.8039684355922487,,
|
|
||||||
,,1.0768322462902296,0.7488836107091129,,
|
|
||||||
,,1.0800586228786773,0.7279206840193796,,
|
|
||||||
,,1.0858633205200596,0.7125673758820761,,
|
|
||||||
,,1.1013418522737881,0.677981252075927,,
|
|
||||||
,,1.105210326788127,0.6779626513688453,,
|
|
||||||
,,1.1193973811672016,0.6589337368401503,,
|
|
||||||
,,1.1206888585418553,0.6450561487842132,,
|
|
||||||
,,1.123272475247787,0.613802971613336,,
|
|
||||||
,,1.129077834845794,0.5966103416145675,,
|
|
||||||
,,1.1335910551125228,0.5965912453535225,,
|
|
||||||
,,1.1413266802279516,0.6050805792103379,,
|
|
||||||
,,1.147134687652457,0.5716821968068484,,
|
|
||||||
,,1.1581006610960811,0.5401075330241505,,
|
|
||||||
,,1.167774495208427,0.524964710284852,,
|
|
||||||
,,1.17357786893656,0.521233298948351,,
|
|
||||||
,,1.1890511050372914,0.5248855006959243,,
|
|
||||||
,,1.1974327998183592,0.5248543002000403,,
|
|
||||||
,,1.2051671010205385,0.5399272739697006,,
|
|
||||||
,,1.2161264548979165,0.5475977747039503,,
|
|
||||||
,,1.2225739124218147,0.5475727356323816,,
|
|
||||||
,,1.2232186581742046,0.5475702317881956,,
|
|
||||||
,,1.2257923455307669,0.5795255128326332,,
|
|
||||||
,,1.2264284858470365,0.635494312878344,,
|
|
||||||
,,1.2264218662807902,0.6822012488404363,,
|
|
||||||
,,1.2322133247896798,0.7695840678419278,,
|
|
||||||
,,1.2354304339853828,0.8261273126225834,,
|
|
||||||
,,1.2386382757883407,0.9793976812064772,,
|
|
||||||
,,1.2399224716401234,1.0365631680970249,,
|
|
||||||
,,1.2405632456527655,1.0816189618908738,,
|
|
||||||
,,1.2457119442791393,1.1944819289049535,,
|
|
||||||
,,1.2508619668187624,1.3005429730851226,,
|
|
||||||
,,1.2521488104970433,1.3379536598639075,,
|
|
||||||
,,1.2586068593269357,1.1943726953174758,,
|
|
||||||
,,1.2605516878900995,1.0662340854711274,,
|
|
||||||
,,1.263782036218295,0.9932116176633944,,
|
|
||||||
,,1.267659116168754,0.9057092045091463,,
|
|
||||||
,,1.2721796179583538,0.8377104911187728,,
|
|
||||||
,,1.277345527456968,0.7693377754021644,,
|
|
||||||
,,1.2818653672899432,0.7166420946785479,,
|
|
||||||
,,1.2954156193961235,0.639704291832398,,
|
|
||||||
,,1.298640672071322,0.6306801565443763,,
|
|
||||||
,,1.3018657247465204,0.6217833222901857,,
|
|
||||||
,,1.3147579919678183,0.6396165438362177,,
|
|
||||||
,,1.3179764250767705,0.6769403979817149,,
|
|
||||||
,,1.3244132912946747,0.7582491296621654,,
|
|
||||||
,,1.3295613279644238,0.8433295073010272,,
|
|
||||||
,,1.336000180052202,0.9247376324361347,,
|
|
||||||
,,1.3430738485430005,1.1278180580973283,,
|
|
||||||
,,1.3449988184074253,1.2455302076050567,,
|
|
||||||
,,1.3482112939067554,1.4050952527481475,,
|
|
||||||
,,1.351423769406086,1.585102197634859,,
|
|
||||||
,,1.3533487392705106,1.7505418140103943,,
|
|
||||||
,,1.3565618767264658,1.9608478952847546,,
|
|
||||||
,,1.3604177740649368,2.243642151072621,,
|
|
||||||
,,1.3681421459177467,2.5671506730013776,,
|
|
||||||
,,1.3720059867357133,2.6977396395227498,,
|
|
||||||
,,1.3700631440424236,2.9583331848749403,,
|
|
||||||
,,1.3745670969164077,3.267039328784058,,
|
|
||||||
,,1.3758446732019438,3.711862721055264,,
|
|
||||||
,,1.377124897313979,4.099294432098773,,
|
|
||||||
,,1.381620906708467,4.929210899363566,,
|
|
||||||
,,1.381610315402473,5.521521237053952,,
|
|
||||||
,,1.382885243861511,6.453816707451638,,
|
|
||||||
,,1.3828733286422676,7.332602493027707,,
|
|
||||||
,,1.3835048352621646,8.450022001983974,,
|
|
||||||
,,1.3847837354609505,9.465318121186383,,
|
|
||||||
,,1.3892850405084358,10.753821633797266,,
|
|
||||||
,,1.3950817946703227,11.46214012981349,,
|
|
||||||
,,1.406041810504325,11.542823048802346,,
|
|
||||||
,,1.4086254272102567,10.983569572223233,,
|
|
||||||
,,1.4125144223799593,8.815541223987582,,
|
|
||||||
,,1.4138145051907332,7.8697993951128655,,
|
|
||||||
,,1.4164106990725327,6.544455667464655,,
|
|
||||||
,,1.4170686839574151,5.678973743658009,,
|
|
||||||
,,1.419664877839215,4.722584406045411,,
|
|
||||||
,,1.422267691287261,3.6583795017467264,,
|
|
||||||
,,1.4274475018749921,2.894876829955794,,
|
|
||||||
,,1.4293982880477776,2.4244991059437035,,
|
|
||||||
,,1.4319891862765801,2.133892660567146,,
|
|
||||||
,,1.4365249130685465,1.6766244945281037,,
|
|
||||||
,,1.442346821582169,1.3648832144532748,,
|
|
||||||
,,1.44946086942707,1.0800176935856405,,
|
|
||||||
,,1.4539886527395407,0.9239641429706197,,
|
|
||||||
,,1.4617467843802068,0.7363233849854021,,
|
|
||||||
,,1.4701536335130105,0.5623407795094862,,
|
|
||||||
,,1.477900511891058,0.5055620724926202,,
|
|
||||||
,,1.4856487141823544,0.44811473438788657,,
|
|
||||||
,,1.4940482817922873,0.3699994982234866,,
|
|
||||||
,,1.5005089784486785,0.32105511462339426,,
|
|
||||||
,,1.5089019264923649,0.2845721176091456,,
|
|
||||||
,,1.5166481429137875,0.2576601983353233,,
|
|
||||||
,,1.5179396202884412,0.2522337011156551,,
|
|
||||||
,,1.5359997828782272,0.23327413205434572,,
|
|
||||||
,,1.5430966198508878,0.22196482072259285,,
|
|
||||||
,,1.5469697280615993,0.21120862244291763,,
|
|
||||||
,,1.5501980905199209,0.20097457800069773,,
|
|
||||||
,,1.565674636403775,0.1953318790229525,,
|
|
||||||
,,1.5688977032090996,0.19671762962317987,,
|
|
||||||
,,1.5785662416684487,0.20236426842871638,,
|
|
||||||
,,1.5837215598610688,0.2081796533603784,,
|
|
||||||
,,1.5914545371499988,0.21721757235346942,,
|
|
||||||
,,1.5991901622654274,0.22030852031094905,,
|
|
||||||
,,1.6088620105078997,0.21873658192521878,,
|
|
||||||
,,1.61208309144335,0.22502554836218797,,
|
|
||||||
,,1.6262602164730557,0.2432590831170723,,
|
|
||||||
,,1.626903638312196,0.2467330049135315,,
|
|
||||||
,,1.6281904819904773,0.25383038758817866,,
|
|
||||||
,,1.6410748057322797,0.28430548752659124,,
|
|
||||||
,,1.660414530477476,0.29244621369149987,,
|
|
||||||
,,1.6642816810785659,0.29661578287603696,,
|
|
||||||
,,1.6707152375133467,0.34423590359793155,,
|
|
||||||
,,1.6739290369259265,0.3828666146961718,,
|
|
||||||
,,1.6758540067903511,0.4228270071256609,,
|
|
||||||
,,1.690028483993558,0.4702407837690221,,
|
|
||||||
,,1.6964732936909575,0.48374976893430716,,
|
|
||||||
,,1.6996923887565347,0.5083600695634991,,
|
|
||||||
,,1.7003318388559272,0.5380344919079171,,
|
|
||||||
|
69
Functions/Theory/calcFWM/FWM_products.m
Normal file
69
Functions/Theory/calcFWM/FWM_products.m
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
w0 = [1290:2:1290+15*2]';
|
||||||
|
w0 = [1290:2:1290+15*2]';
|
||||||
|
w0 = [ 1302 1304 1306 1308]';
|
||||||
|
%w0 = [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]';
|
||||||
|
m = 0.5*(numel(w0)^3 - numel(w0)^2);
|
||||||
|
a = [1,1,1]';
|
||||||
|
|
||||||
|
w = w0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for o = 2:3
|
||||||
|
|
||||||
|
p = nchoosek(w,3);
|
||||||
|
|
||||||
|
q = [];
|
||||||
|
parfor i = 1:size(p,1)
|
||||||
|
|
||||||
|
q_ = perms(p(i,:));
|
||||||
|
q = [q;q_];
|
||||||
|
|
||||||
|
end
|
||||||
|
p = q;
|
||||||
|
%p = unique(q,"rows");
|
||||||
|
w_ = p(:,1) + p(:,2) - p(:,3);
|
||||||
|
a_ = ones(size(w_)).* 1/o;
|
||||||
|
|
||||||
|
w = [w ; w_];
|
||||||
|
a = [a ; a_];
|
||||||
|
% w = unique(w);
|
||||||
|
% a = unique(a);
|
||||||
|
|
||||||
|
m(end+1) = 0.5*(numel(w)^3 - numel(w)^2);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
figure(11)
|
||||||
|
hold on
|
||||||
|
|
||||||
|
lambda = min(w):max(w);
|
||||||
|
|
||||||
|
gen = sum(lambda == w,1);
|
||||||
|
gen(gen==0) = NaN;
|
||||||
|
stem(lambda,gen,"filled",'LineWidth',1,'Marker','o','MarkerSize',2,'LineStyle',':')
|
||||||
|
|
||||||
|
initial = sum(lambda == w0,1);
|
||||||
|
initial(initial==0) = NaN;
|
||||||
|
stem(lambda,initial,"filled",'LineWidth',1.5,'MarkerSize',5,'Marker','^');
|
||||||
|
|
||||||
|
xlabel('Wavelength');
|
||||||
|
ylabel('number of FWM products');
|
||||||
|
|
||||||
|
grid minor
|
||||||
|
legend('Generated Products', 'Initial Channel Position')
|
||||||
|
AxesMain = gca;
|
||||||
|
fig = gcf;
|
||||||
|
fontsize(AxesMain,8,"points")
|
||||||
|
|
||||||
|
fig.Units = "centimeters";
|
||||||
|
fig.Position = [2 2 8.5 7];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
43
Functions/Theory/calcFWM/JLT_statistics_laser_and_zdw.m
Normal file
43
Functions/Theory/calcFWM/JLT_statistics_laser_and_zdw.m
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
%% Laser Offset Statistics
|
||||||
|
|
||||||
|
figure
|
||||||
|
for i = 1
|
||||||
|
res = 1.7e6;
|
||||||
|
n_chann = 16;
|
||||||
|
df_T_exact = (-n_chann/2+0.5:n_chann/2).* 200e9;
|
||||||
|
|
||||||
|
for key = 1:100
|
||||||
|
laser_frequency_imperfection(key,:) = res .* round(randn(1,n_chann)*i*100);
|
||||||
|
df_T(key,:) = df_T_exact + laser_frequency_imperfection(key,:);
|
||||||
|
end
|
||||||
|
|
||||||
|
hold on
|
||||||
|
histogram(laser_frequency_imperfection.*1e-6,100,"Normalization","probability","EdgeColor","none","FaceAlpha",0.3,'DisplayName',['Std. Dev.: ',num2str(mean(std(laser_frequency_imperfection))*1e-6),' MHz']);
|
||||||
|
xlabel('Laser Frequency Offset in MHz');
|
||||||
|
ylabel('Probability');
|
||||||
|
title(['Laser deviations from exact grid.'])
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
%% ZDW Statistics
|
||||||
|
|
||||||
|
for k = 1:1000
|
||||||
|
|
||||||
|
% Set parameters
|
||||||
|
meanUniformMin = 1309;
|
||||||
|
meanUniformMax = 1315;
|
||||||
|
meanValue = 1310;
|
||||||
|
sigma = 2;
|
||||||
|
|
||||||
|
% Seed the random number generator (assuming Mersenne Twister)
|
||||||
|
rng(k);
|
||||||
|
|
||||||
|
% Generate normally distributed random numbers
|
||||||
|
randomNumbers(k,:) = normrnd(meanValue, sigma, [n_chann, 1]).';
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
figure;
|
||||||
|
histogram(randomNumbers,100,"Normalization","probability","EdgeColor","none");
|
||||||
|
xlabel('ZDW in nm');
|
||||||
|
ylabel('Probability');
|
||||||
65
Functions/Theory/calcFWM/analytical_calculation_paper.m
Normal file
65
Functions/Theory/calcFWM/analytical_calculation_paper.m
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
%%FWM analysis from "Analytical Calculation of the Number of
|
||||||
|
%%Four-Wave-Mixing Products in Optical Multichannel Communication Systems"
|
||||||
|
|
||||||
|
N_ = [4,8,16];
|
||||||
|
|
||||||
|
df_hz = 400e9;
|
||||||
|
center_nm = 1310;
|
||||||
|
|
||||||
|
figure()
|
||||||
|
for i = 1:length(N_)
|
||||||
|
|
||||||
|
vec = (2*N_(i)-1:-1:2-N_(i)) -(N_(i)/2+0.5);
|
||||||
|
channelplan_hz = nm2hz(center_nm) + (vec * df_hz) ;
|
||||||
|
channelplan_nm = hz2nm(channelplan_hz);
|
||||||
|
|
||||||
|
[Mndg,Mdg] = getProducts(N_(i));
|
||||||
|
total(i) = sum(Mndg) + sum(Mdg);
|
||||||
|
subplot(1,length(N_),i)
|
||||||
|
xline(calcWavelengthPlan(N_(i), df_hz, center_nm));
|
||||||
|
hold on
|
||||||
|
stem(channelplan_nm,(Mndg+Mdg),'filled','LineWidth',1,'Marker','o','MarkerSize',2)
|
||||||
|
stem(channelplan_nm,(Mdg),'filled','LineWidth',1,'Marker','none');
|
||||||
|
ylim([0,100])
|
||||||
|
xlim([1260, 1365]);
|
||||||
|
grid off
|
||||||
|
xlabel('O-band wavelength region in nm');
|
||||||
|
ylabel('Number of FWM products');
|
||||||
|
title([num2str(N_(i)),' ch.'])
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function [Mndg,Mdg] = getProducts(N)
|
||||||
|
|
||||||
|
s = abs(2-N-1) ;
|
||||||
|
|
||||||
|
for n = 2-N:2*N-1
|
||||||
|
|
||||||
|
if n<-N
|
||||||
|
Mdg(n+s) = 0;
|
||||||
|
elseif (-N <= n)&&(n <= 0)
|
||||||
|
Mdg(n+s) = N - ceil((N-n)/2);
|
||||||
|
elseif (1 <= n)&&(n <= N)
|
||||||
|
Mdg(n+s) = N - 1 - floor(n/2) - ceil((N-n)/2);
|
||||||
|
elseif (N < n)&&(n <= 2*N)
|
||||||
|
Mdg(n+s) = N - floor(n/2);
|
||||||
|
elseif n > 2*N
|
||||||
|
Mdg(n+s) = 0;
|
||||||
|
end
|
||||||
|
|
||||||
|
if n<-N
|
||||||
|
Mndg(n+s) = 0;
|
||||||
|
elseif (-N <= n)&&(n < 1)
|
||||||
|
Mndg(n+s) = ceil((N^2 + n^2 - 2*N - 2*n + 2*N*n)/4);
|
||||||
|
elseif (1 <= n)&&(n <= N)
|
||||||
|
Mndg(n+s) = ceil(((N^2 - 6*N - 2*n^2 + 2*n + 4)/4) + floor((N*n)/2));
|
||||||
|
elseif (N < n)&&(n <= 2*N)
|
||||||
|
Mndg(n+s) = floor(N^2 + n^2 /4 - N*n);
|
||||||
|
elseif n > 2*N
|
||||||
|
Mndg(n+s) = 0;
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
23
Functions/Theory/calcFWM/calcFwmEfficiency.m
Normal file
23
Functions/Theory/calcFWM/calcFwmEfficiency.m
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
function [eta, deltaBeta] = calcFwmEfficiency(f_i, f_j, f_k, f_0, Ds, alphaDbPerKm, Lkm)
|
||||||
|
% FWM efficiency including phase mismatch and attenuation.
|
||||||
|
% alphaDbPerKm is the power attenuation in dB/km, Lkm is the fiber length in km.
|
||||||
|
|
||||||
|
deltaBeta = calcPhaseMatching(f_i, f_j, f_k, f_0, Ds);
|
||||||
|
|
||||||
|
alphaNpPerM = alphaDbPerKm .* log(10) ./ 10 ./ 1e3;
|
||||||
|
Lm = Lkm .* 1e3;
|
||||||
|
|
||||||
|
denominator = alphaNpPerM.^2 + deltaBeta.^2;
|
||||||
|
term1 = alphaNpPerM.^2 ./ denominator;
|
||||||
|
|
||||||
|
loss_term = 1 - exp(-alphaNpPerM .* Lm);
|
||||||
|
|
||||||
|
if abs(alphaNpPerM) < eps
|
||||||
|
term2 = 4 .* sin(deltaBeta .* Lm ./ 2).^2 ./ max((alphaNpPerM .* Lm).^2, eps);
|
||||||
|
else
|
||||||
|
term2 = 1 + 4 .* exp(-alphaNpPerM .* Lm) .* sin(deltaBeta .* Lm ./ 2).^2 ./ (loss_term.^2);
|
||||||
|
end
|
||||||
|
|
||||||
|
eta = term1 .* term2;
|
||||||
|
|
||||||
|
end
|
||||||
48
Functions/Theory/calcFWM/calcFwmPower.m
Normal file
48
Functions/Theory/calcFWM/calcFwmPower.m
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
function [P_fwm, eta, deltaBeta, Leff] = calcFwmPower( ...
|
||||||
|
f_i, f_j, f_k, f_0, Ds, alphaDbPerKm, Lkm, ...
|
||||||
|
P_i, P_j, P_k, gammaWInvKmInv, degeneracyFactor)
|
||||||
|
% Calculate FWM power for a fiber with attenuation and phase mismatch.
|
||||||
|
%
|
||||||
|
% Inputs:
|
||||||
|
% f_i, f_j, f_k, f_0 : frequencies in Hz
|
||||||
|
% Ds : dispersion slope in ps / (nm^2 km)
|
||||||
|
% alphaDbPerKm : attenuation in dB/km
|
||||||
|
% Lkm : fiber length in km
|
||||||
|
% P_i, P_j, P_k : launch powers in W
|
||||||
|
% gammaWInvKmInv : nonlinear coefficient in 1/(W km)
|
||||||
|
% degeneracyFactor : typically 3 for degenerate FWM, 6 for non-degenerate
|
||||||
|
|
||||||
|
if nargin < 8 || isempty(P_i)
|
||||||
|
P_i = 1;
|
||||||
|
end
|
||||||
|
if nargin < 9 || isempty(P_j)
|
||||||
|
P_j = P_i;
|
||||||
|
end
|
||||||
|
if nargin < 10 || isempty(P_k)
|
||||||
|
P_k = 1;
|
||||||
|
end
|
||||||
|
if nargin < 11 || isempty(gammaWInvKmInv)
|
||||||
|
gammaWInvKmInv = 1;
|
||||||
|
end
|
||||||
|
if nargin < 12 || isempty(degeneracyFactor)
|
||||||
|
degeneracyFactor = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
[eta, deltaBeta] = calcFwmEfficiency(f_i, f_j, f_k, f_0, Ds, alphaDbPerKm, Lkm);
|
||||||
|
|
||||||
|
alphaNpPerM = alphaDbPerKm .* log(10) ./ 10 ./ 1e3;
|
||||||
|
Lm = Lkm .* 1e3;
|
||||||
|
gammaWInvMInv = gammaWInvKmInv ./ 1e3;
|
||||||
|
|
||||||
|
if abs(alphaNpPerM) < eps
|
||||||
|
Leff = Lm;
|
||||||
|
else
|
||||||
|
Leff = (1 - exp(-alphaNpPerM .* Lm)) ./ alphaNpPerM;
|
||||||
|
end
|
||||||
|
|
||||||
|
P_fwm = degeneracyFactor .* eta .* ...
|
||||||
|
(gammaWInvMInv .* Leff).^2 .* ...
|
||||||
|
P_i .* P_j .* P_k .* ...
|
||||||
|
exp(-alphaNpPerM .* Lm);
|
||||||
|
|
||||||
|
end
|
||||||
20
Functions/Theory/calcFWM/calcPhaseMatching.m
Normal file
20
Functions/Theory/calcFWM/calcPhaseMatching.m
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
function deltaBeta = calcPhaseMatching(f_i, f_j, f_k, f_0, Ds)
|
||||||
|
% Approximate phase mismatch for degenerate FWM close to the ZDW.
|
||||||
|
% Inputs are frequencies in Hz.
|
||||||
|
% f_i, f_j : pump frequencies (equal in the degenerate case)
|
||||||
|
% f_k : signal frequency
|
||||||
|
% f_0 : zero-dispersion frequency
|
||||||
|
% Ds : dispersion slope in ps / (nm^2 km)
|
||||||
|
|
||||||
|
c = physconst('LightSpeed');
|
||||||
|
|
||||||
|
pump_frequency = 0.5 .* (f_i + f_j);
|
||||||
|
lambda_zdw_m = c ./ f_0;
|
||||||
|
dispersion_slope_si = Ds .* 1e3;
|
||||||
|
|
||||||
|
deltaBeta = -(2 .* pi .* lambda_zdw_m.^4 ./ c.^2) .* ...
|
||||||
|
dispersion_slope_si .* ...
|
||||||
|
(pump_frequency - f_0) .* ...
|
||||||
|
(pump_frequency - f_k).^2;
|
||||||
|
|
||||||
|
end
|
||||||
104
Functions/Theory/calcFWM/scriptFWM.m
Normal file
104
Functions/Theory/calcFWM/scriptFWM.m
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
clear;
|
||||||
|
clc;
|
||||||
|
|
||||||
|
% Sweep the degenerate pump frequency around its nominal wavelength.
|
||||||
|
pump_detuning_hz = (-800:0.01:800) .* 1e9;
|
||||||
|
|
||||||
|
% Degenerate FWM setup: two pump photons at f_p and one signal at f_s
|
||||||
|
% generate an idler at f_i = 2*f_p - f_s.
|
||||||
|
pump_wavelength_nm = 1310;
|
||||||
|
signal_wavelength_nm = 1308;
|
||||||
|
zdw_wavelength_nm = 1310;
|
||||||
|
|
||||||
|
f_pump_nominal = wavelength2frequency(pump_wavelength_nm, 'nm');
|
||||||
|
f_signal_scalar = wavelength2frequency(signal_wavelength_nm, 'nm');
|
||||||
|
f_zdw_scalar = wavelength2frequency(zdw_wavelength_nm, 'nm');
|
||||||
|
|
||||||
|
f_pump = f_pump_nominal + pump_detuning_hz;
|
||||||
|
f_signal = f_signal_scalar .* ones(size(f_pump));
|
||||||
|
f_zdw = f_zdw_scalar .* ones(size(f_pump));
|
||||||
|
f_idler = 2 .* f_pump - f_signal;
|
||||||
|
|
||||||
|
% Fiber parameters
|
||||||
|
dispersion_slope_ps_nm2_km = 0.07;
|
||||||
|
attenuation_db_per_km = 0.21;
|
||||||
|
fiber_length_km = 10;
|
||||||
|
|
||||||
|
% Launch powers and nonlinear coefficient
|
||||||
|
pump_power_dbm = 10;
|
||||||
|
signal_power_dbm = 10;
|
||||||
|
pump_power_w = dbm2watt(pump_power_dbm);
|
||||||
|
signal_power_w = dbm2watt(signal_power_dbm);
|
||||||
|
gamma_w_inv_km_inv = 1.3;
|
||||||
|
degeneracy_factor = 3;
|
||||||
|
|
||||||
|
[P_fwm, eta, delta_beta, L_eff_m] = calcFwmPower( ...
|
||||||
|
f_pump, f_pump, f_signal, f_zdw, ...
|
||||||
|
dispersion_slope_ps_nm2_km, attenuation_db_per_km, fiber_length_km, ...
|
||||||
|
pump_power_w, pump_power_w, signal_power_w, ...
|
||||||
|
gamma_w_inv_km_inv, degeneracy_factor);
|
||||||
|
|
||||||
|
f_pump_thz = f_pump .* 1e-12;
|
||||||
|
f_zdw_thz = f_zdw_scalar .* 1e-12;
|
||||||
|
f_signal_thz = f_signal_scalar .* 1e-12;
|
||||||
|
idler_power_dbm = 10 .* log10(max(P_fwm, realmin) ./ 1e-3);
|
||||||
|
|
||||||
|
figure;
|
||||||
|
tiledlayout(2,1);
|
||||||
|
|
||||||
|
ax1 = nexttile;
|
||||||
|
plot(ax1, f_pump_thz, eta, 'LineWidth', 2);
|
||||||
|
hold(ax1, 'on');
|
||||||
|
xline(ax1, f_zdw_thz, '--r', 'ZDW', 'LineWidth', 1.2, ...
|
||||||
|
'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'bottom');
|
||||||
|
xline(ax1, f_signal_thz, '--k', 'Signal', 'LineWidth', 1.2, ...
|
||||||
|
'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'middle');
|
||||||
|
ylabel(ax1, 'FWM efficiency');
|
||||||
|
grid(ax1, 'on');
|
||||||
|
title(ax1, 'FWM Efficiency and Idler Power versus Pump Frequency');
|
||||||
|
|
||||||
|
ax2 = nexttile;
|
||||||
|
plot(ax2, f_pump_thz, idler_power_dbm, 'LineWidth', 2);
|
||||||
|
hold(ax2, 'on');
|
||||||
|
xline(ax2, f_zdw_thz, '--r', 'ZDW', 'LineWidth', 1.2, ...
|
||||||
|
'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'bottom');
|
||||||
|
xline(ax2, f_signal_thz, '--k', 'Signal', 'LineWidth', 1.2, ...
|
||||||
|
'LabelOrientation', 'horizontal', 'LabelVerticalAlignment', 'middle');
|
||||||
|
xlabel(ax2, 'Pump frequency (THz)');
|
||||||
|
ylabel(ax2, 'FWM idler power (dBm)');
|
||||||
|
grid(ax2, 'on');
|
||||||
|
|
||||||
|
fprintf('Pump wavelength : %.3f nm -> %.6f THz\n', ...
|
||||||
|
pump_wavelength_nm, f_pump_nominal .* 1e-12);
|
||||||
|
fprintf('Signal wavelength : %.3f nm -> %.6f THz\n', ...
|
||||||
|
signal_wavelength_nm, f_signal_scalar .* 1e-12);
|
||||||
|
fprintf('ZDW wavelength : %.3f nm -> %.6f THz\n', ...
|
||||||
|
zdw_wavelength_nm, f_zdw_scalar .* 1e-12);
|
||||||
|
fprintf('Pump launch power : %.2f dBm -> %.4g W\n', ...
|
||||||
|
pump_power_dbm, pump_power_w);
|
||||||
|
fprintf('Signal launch power : %.2f dBm -> %.4g W\n', ...
|
||||||
|
signal_power_dbm, signal_power_w);
|
||||||
|
fprintf('Peak FWM efficiency : %.4g\n', max(eta));
|
||||||
|
fprintf('Peak FWM idler power : %.4g W (%.2f dBm)\n', ...
|
||||||
|
max(P_fwm), 10 .* log10(max(P_fwm) ./ 1e-3));
|
||||||
|
fprintf('Effective fiber length : %.4f km\n', L_eff_m ./ 1e3);
|
||||||
|
fprintf('Idler wavelength range : %.3f nm to %.3f nm\n', ...
|
||||||
|
min(frequency2wavelength(f_idler, 'nm')), max(frequency2wavelength(f_idler, 'nm')));
|
||||||
|
fprintf('Max |delta beta| : %.4g 1/m\n', max(abs(delta_beta)));
|
||||||
|
|
||||||
|
function wavelength = frequency2wavelength(frequency, outputUnit)
|
||||||
|
c = physconst('LightSpeed');
|
||||||
|
wavelength = c ./ frequency;
|
||||||
|
|
||||||
|
switch lower(outputUnit)
|
||||||
|
case 'm'
|
||||||
|
case 'nm'
|
||||||
|
wavelength = wavelength .* 1e9;
|
||||||
|
otherwise
|
||||||
|
error('Unsupported output unit "%s". Use "m" or "nm".', outputUnit);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function power_w = dbm2watt(power_dbm)
|
||||||
|
power_w = 1e-3 .* 10.^(power_dbm ./ 10);
|
||||||
|
end
|
||||||
124
Functions/Theory/calcFWM/validate_fwm.m
Normal file
124
Functions/Theory/calcFWM/validate_fwm.m
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
%% Validate the analytical FWM product count against a brute-force reference
|
||||||
|
% The paper counts channel combinations, not only unique output frequencies:
|
||||||
|
% non-degenerate: i < j, k ~= i, k ~= j, n = i + j - k
|
||||||
|
% degenerate: i == j, k ~= i, n = 2*i - k
|
||||||
|
|
||||||
|
clear;
|
||||||
|
clc;
|
||||||
|
|
||||||
|
N_values = [4, 8, 16];
|
||||||
|
plot_N = 8;
|
||||||
|
|
||||||
|
fprintf('Validating analytical FWM product count from Goebel and Hanik (2008)\n');
|
||||||
|
|
||||||
|
for idxN = 1:numel(N_values)
|
||||||
|
N = N_values(idxN);
|
||||||
|
fprintf('\nN = %d\n', N);
|
||||||
|
|
||||||
|
[Mndg_ana, Mdg_ana] = getProducts(N);
|
||||||
|
[Mndg_brute, Mdg_brute, n_values] = getProductsBruteForce(N);
|
||||||
|
|
||||||
|
diff_ndg = Mndg_ana - Mndg_brute;
|
||||||
|
diff_dg = Mdg_ana - Mdg_brute;
|
||||||
|
|
||||||
|
fprintf(' Analytical total : %d\n', sum(Mndg_ana) + sum(Mdg_ana));
|
||||||
|
fprintf(' Brute-force total : %d\n', sum(Mndg_brute) + sum(Mdg_brute));
|
||||||
|
|
||||||
|
if all(diff_ndg == 0) && all(diff_dg == 0)
|
||||||
|
fprintf(' Match : yes\n');
|
||||||
|
else
|
||||||
|
fprintf(' Match : no\n');
|
||||||
|
fprintf(' Non-degenerate diff: %s\n', mat2str(diff_ndg));
|
||||||
|
fprintf(' Degenerate diff : %s\n', mat2str(diff_dg));
|
||||||
|
end
|
||||||
|
|
||||||
|
if N == plot_N
|
||||||
|
plotComparison(n_values, Mndg_ana, Mdg_ana, Mndg_brute, Mdg_brute, N);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function [Mndg, Mdg, n_values] = getProductsBruteForce(N)
|
||||||
|
n_values = (2 - N):(2*N - 1);
|
||||||
|
Mndg = zeros(size(n_values));
|
||||||
|
Mdg = zeros(size(n_values));
|
||||||
|
|
||||||
|
for idx = 1:numel(n_values)
|
||||||
|
n = n_values(idx);
|
||||||
|
|
||||||
|
% Degenerate products: two identical pumps and one different channel.
|
||||||
|
for i = 1:N
|
||||||
|
k = 2*i - n;
|
||||||
|
if isValidChannel(k, N) && (k ~= i)
|
||||||
|
Mdg(idx) = Mdg(idx) + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% Non-degenerate products: unordered pump pair plus one third channel.
|
||||||
|
for i = 1:N
|
||||||
|
for j = (i + 1):N
|
||||||
|
k = i + j - n;
|
||||||
|
if isValidChannel(k, N) && (k ~= i) && (k ~= j)
|
||||||
|
Mndg(idx) = Mndg(idx) + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tf = isValidChannel(channel_idx, N)
|
||||||
|
tf = (channel_idx >= 1) && (channel_idx <= N) && (channel_idx == round(channel_idx));
|
||||||
|
end
|
||||||
|
|
||||||
|
function plotComparison(n_values, Mndg_ana, Mdg_ana, Mndg_brute, Mdg_brute, N)
|
||||||
|
figure;
|
||||||
|
|
||||||
|
subplot(1,2,1);
|
||||||
|
stem(n_values, Mndg_ana + Mdg_ana, 'filled', 'LineWidth', 1, 'Marker', 'o', 'MarkerSize', 2);
|
||||||
|
hold on;
|
||||||
|
stem(n_values, Mdg_ana, 'filled', 'LineWidth', 1, 'Marker', 'none');
|
||||||
|
title(['Analytical (N=', num2str(N), ')']);
|
||||||
|
xlabel('Product index n');
|
||||||
|
ylabel('Number of FWM products');
|
||||||
|
legend('Total', 'Degenerate');
|
||||||
|
grid on;
|
||||||
|
|
||||||
|
subplot(1,2,2);
|
||||||
|
stem(n_values, Mndg_brute + Mdg_brute, 'filled', 'LineWidth', 1, 'Marker', 'o', 'MarkerSize', 2);
|
||||||
|
hold on;
|
||||||
|
stem(n_values, Mdg_brute, 'filled', 'LineWidth', 1, 'Marker', 'none');
|
||||||
|
title(['Brute force (N=', num2str(N), ')']);
|
||||||
|
xlabel('Product index n');
|
||||||
|
ylabel('Number of FWM products');
|
||||||
|
legend('Total', 'Degenerate');
|
||||||
|
grid on;
|
||||||
|
end
|
||||||
|
|
||||||
|
function [Mndg, Mdg] = getProducts(N)
|
||||||
|
s = abs(2 - N - 1);
|
||||||
|
|
||||||
|
for n = 2 - N:2*N - 1
|
||||||
|
if n < -N
|
||||||
|
Mdg(n + s) = 0;
|
||||||
|
elseif (-N <= n) && (n <= 0)
|
||||||
|
Mdg(n + s) = N - ceil((N - n)/2);
|
||||||
|
elseif (1 <= n) && (n <= N)
|
||||||
|
Mdg(n + s) = N - 1 - floor(n/2) - ceil((N - n)/2);
|
||||||
|
elseif (N < n) && (n <= 2*N)
|
||||||
|
Mdg(n + s) = N - floor(n/2);
|
||||||
|
elseif n > 2*N
|
||||||
|
Mdg(n + s) = 0;
|
||||||
|
end
|
||||||
|
|
||||||
|
if n < -N
|
||||||
|
Mndg(n + s) = 0;
|
||||||
|
elseif (-N <= n) && (n < 1)
|
||||||
|
Mndg(n + s) = ceil((N^2 + n^2 - 2*N - 2*n + 2*N*n)/4);
|
||||||
|
elseif (1 <= n) && (n <= N)
|
||||||
|
Mndg(n + s) = ceil(((N^2 - 6*N - 2*n^2 + 2*n + 4)/4) + floor((N*n)/2));
|
||||||
|
elseif (N < n) && (n <= 2*N)
|
||||||
|
Mndg(n + s) = floor(N^2 + n^2/4 - N*n);
|
||||||
|
elseif n > 2*N
|
||||||
|
Mndg(n + s) = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
18
Functions/Theory/calcFWM/wavelength2frequency.m
Normal file
18
Functions/Theory/calcFWM/wavelength2frequency.m
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
function frequency = wavelength2frequency(wavelength, inputUnit)
|
||||||
|
% Convert wavelength to optical frequency.
|
||||||
|
% Supported units: m, nm.
|
||||||
|
|
||||||
|
c = physconst('LightSpeed');
|
||||||
|
|
||||||
|
switch lower(inputUnit)
|
||||||
|
case 'm'
|
||||||
|
wavelength_m = wavelength;
|
||||||
|
case 'nm'
|
||||||
|
wavelength_m = wavelength .* 1e-9;
|
||||||
|
otherwise
|
||||||
|
error('Unsupported input unit "%s". Use "m" or "nm".', inputUnit);
|
||||||
|
end
|
||||||
|
|
||||||
|
frequency = c ./ wavelength_m;
|
||||||
|
|
||||||
|
end
|
||||||
123
TEST_TODO.md
123
TEST_TODO.md
@@ -8,8 +8,8 @@ It should reflect the repository state now, not the original plan.
|
|||||||
Fast profile status:
|
Fast profile status:
|
||||||
|
|
||||||
- `run_all_tests('profile','fast')`
|
- `run_all_tests('profile','fast')`
|
||||||
- latest verified result: `Passed: 59 | Failed: 0 | Incomplete: 0`
|
- latest verified result: `Passed: 82 | Failed: 0 | Incomplete: 0`
|
||||||
- explicit placeholder tests remaining: `70`
|
- explicit placeholder tests remaining: `62`
|
||||||
|
|
||||||
Mandatory workflow for every future test-writing session:
|
Mandatory workflow for every future test-writing session:
|
||||||
|
|
||||||
@@ -28,10 +28,18 @@ These are already implemented and verified in the fast profile:
|
|||||||
- `Classes/00_signals/Signal.m` -> `Tests/00_signals/Signal_test.m`
|
- `Classes/00_signals/Signal.m` -> `Tests/00_signals/Signal_test.m`
|
||||||
- `Classes/00_signals/Informationsignal.m` -> `Tests/00_signals/Informationsignal_test.m`
|
- `Classes/00_signals/Informationsignal.m` -> `Tests/00_signals/Informationsignal_test.m`
|
||||||
- `Classes/00_signals/Electricalsignal.m` -> `Tests/00_signals/Electricalsignal_test.m`
|
- `Classes/00_signals/Electricalsignal.m` -> `Tests/00_signals/Electricalsignal_test.m`
|
||||||
|
- `Classes/00_signals/Opticalsignal.m` -> `Tests/00_signals/Opticalsignal_test.m`
|
||||||
- `Classes/01_transmit/PAMmapper.m` -> `Tests/01_transmit/PAMmapper_test.m`
|
- `Classes/01_transmit/PAMmapper.m` -> `Tests/01_transmit/PAMmapper_test.m`
|
||||||
- `Classes/01_transmit/PAMsource.m` -> `Tests/01_transmit/PAMsource_test.m`
|
- `Classes/01_transmit/PAMsource.m` -> `Tests/01_transmit/PAMsource_test.m`
|
||||||
|
- `Classes/01_transmit/Signalgenerator.m` -> `Tests/01_transmit/Signalgenerator_test.m`
|
||||||
- `Classes/01_transmit/Pulseformer.m` -> `Tests/01_transmit/Pulseformer_test.m`
|
- `Classes/01_transmit/Pulseformer.m` -> `Tests/01_transmit/Pulseformer_test.m`
|
||||||
|
- `Classes/02_etc/Amplifier.m` -> `Tests/02_etc/Amplifier_test.m`
|
||||||
|
- `Classes/02_etc/Filter.m` -> `Tests/02_etc/Filter_test.m`
|
||||||
- `Classes/02_optical/Fiber.m` -> `Tests/02_optical/Fiber_test.m`
|
- `Classes/02_optical/Fiber.m` -> `Tests/02_optical/Fiber_test.m`
|
||||||
|
- `Classes/02_optical/EML.m` -> `Tests/02_optical/EML_test.m`
|
||||||
|
- `Classes/02_optical/Optical_Multiplex.m` -> `Tests/02_optical/Optical_Multiplex_test.m`
|
||||||
|
- `Classes/02_optical/Optical_Demultiplex.m` -> `Tests/02_optical/Optical_Demultiplex_test.m`
|
||||||
|
- `Classes/02_optical/DP_Fiber.m` -> `Tests/02_optical/DP_Fiber_test.m`
|
||||||
- `Classes/03_receive/Photodiode.m` -> `Tests/03_receive/Photodiode_test.m`
|
- `Classes/03_receive/Photodiode.m` -> `Tests/03_receive/Photodiode_test.m`
|
||||||
- `Classes/03_receive/Scope.m` -> `Tests/03_receive/Scope_test.m`
|
- `Classes/03_receive/Scope.m` -> `Tests/03_receive/Scope_test.m`
|
||||||
- `Classes/04_DSP/TransmissionPerformance.m` -> `Tests/04_DSP/TransmissionPerformance_test.m`
|
- `Classes/04_DSP/TransmissionPerformance.m` -> `Tests/04_DSP/TransmissionPerformance_test.m`
|
||||||
@@ -41,75 +49,80 @@ These are already implemented and verified in the fast profile:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `Opticalsignal_test.m` still appears to be a placeholder and should be treated as unfinished.
|
|
||||||
- There are multiple `Timing_Recovery` class names in the repo, so the implemented base-class test currently uses the disambiguated file name:
|
- There are multiple `Timing_Recovery` class names in the repo, so the implemented base-class test currently uses the disambiguated file name:
|
||||||
`Tests/04_DSP/Timing Recovery/T_04_DSP_Timing_Recovery_Timing_Recovery_test.m`
|
`Tests/04_DSP/Timing Recovery/T_04_DSP_Timing_Recovery_Timing_Recovery_test.m`
|
||||||
|
|
||||||
## Next Priority Tests
|
## Integration Tests
|
||||||
|
|
||||||
Highest-value unfinished tests to implement next:
|
These are the current end-to-end regression guards:
|
||||||
|
|
||||||
1. `Classes/00_signals/Opticalsignal.m`
|
- `Tests/integration/IMDD_base_system_minimal_integration_test.m`
|
||||||
`Tests/00_signals/Opticalsignal_test.m`
|
- `Tests/integration/IMDD_base_system_no_impairment_integration_test.m`
|
||||||
Focus:
|
- `Tests/integration/IMDD_base_system_impairment_monotonicity_integration_test.m`
|
||||||
- constructor metadata wiring
|
- `Tests/integration/WDM_mux_demux_optical_chain_integration_test.m`
|
||||||
- `delay`
|
- `Tests/integration/WDM_end_to_end_two_channel_receiver_integration_test.m`
|
||||||
- `cspr`
|
|
||||||
- interaction with inherited signal behavior
|
|
||||||
|
|
||||||
2. `Classes/01_transmit/Signalgenerator.m`
|
Current integration focus:
|
||||||
`Tests/01_transmit/Signalgenerator_test.m`
|
|
||||||
Focus:
|
|
||||||
- constructor/config defaults
|
|
||||||
- deterministic generation path
|
|
||||||
- output type and shape
|
|
||||||
|
|
||||||
3. `Classes/02_etc/Filter.m`
|
- refactor the duplicated reduced IM/DD workflow setup into shared integration helpers
|
||||||
`Tests/02_etc/Filter_test.m`
|
- tighten the IM/DD baseline naming and thresholds so the physical intent is honest
|
||||||
Focus:
|
- strengthen the WDM optical-chain and end-to-end receiver fixtures into harder regression guards
|
||||||
- deterministic filter construction
|
- keep all integration fixtures deterministic and small enough to remain reviewable
|
||||||
- `process` behavior on small signals
|
|
||||||
- output rate/length invariants
|
|
||||||
|
|
||||||
4. `Classes/02_etc/Amplifier.m`
|
## Remaining Core Tests
|
||||||
`Tests/02_etc/Amplifier_test.m`
|
|
||||||
Focus:
|
|
||||||
- gain behavior
|
|
||||||
- noise-free baseline
|
|
||||||
- output class and metadata behavior
|
|
||||||
|
|
||||||
5. `Classes/02_optical/EML.m`
|
Highest-value unfinished unit tests to implement next:
|
||||||
`Tests/02_optical/EML_test.m`
|
|
||||||
Focus:
|
|
||||||
- deterministic modulation path
|
|
||||||
- output class and metadata
|
|
||||||
- small synthetic fixture only
|
|
||||||
|
|
||||||
6. `Classes/02_optical/Optical_Multiplex.m`
|
1. `Classes/02_optical/Polarization_Controller.m`
|
||||||
`Tests/02_optical/Optical_Multiplex_test.m`
|
|
||||||
Focus:
|
|
||||||
- combining channels
|
|
||||||
- output dimensions
|
|
||||||
- metadata handling
|
|
||||||
|
|
||||||
7. `Classes/02_optical/Optical_Demultiplex.m`
|
|
||||||
`Tests/02_optical/Optical_Demultiplex_test.m`
|
|
||||||
Focus:
|
|
||||||
- deterministic channel extraction
|
|
||||||
- output dimensions
|
|
||||||
- basic round-trip expectations with multiplexing if practical
|
|
||||||
|
|
||||||
8. `Classes/02_optical/Polarization_Controller.m`
|
|
||||||
`Tests/02_optical/Polarization_Controller_test.m`
|
`Tests/02_optical/Polarization_Controller_test.m`
|
||||||
Focus:
|
Focus:
|
||||||
- deterministic transform behavior
|
- deterministic transform behavior
|
||||||
- shape preservation
|
- shape preservation
|
||||||
|
|
||||||
9. `Classes/02_optical/DP_Fiber.m`
|
2. `Classes/01_transmit/AWG.m`
|
||||||
`Tests/02_optical/DP_Fiber_test.m`
|
`Tests/01_transmit/AWG_test.m`
|
||||||
Focus:
|
Focus:
|
||||||
- very small deterministic sanity checks only
|
- DAC-like resampling and quantization behavior
|
||||||
- avoid long or GPU-heavy runs
|
- length and sampling-rate invariants
|
||||||
|
- deterministic small fixtures
|
||||||
|
|
||||||
|
Follow-up around the WDM integration fixtures:
|
||||||
|
|
||||||
|
- `Classes/01_transmit/AWG.m`
|
||||||
|
- `Classes/02_optical/EML.m`
|
||||||
|
- `Classes/02_optical/Polarization_Controller.m`
|
||||||
|
- `Classes/02_optical/DP_Fiber.m`
|
||||||
|
- `Classes/02_optical/Optical_Multiplex.m`
|
||||||
|
- `Classes/02_optical/Optical_Demultiplex.m`
|
||||||
|
- `Classes/03_receive/Photodiode.m`
|
||||||
|
|
||||||
|
These should be revisited together when the WDM integration tests are tightened.
|
||||||
|
|
||||||
|
Immediate integration refactor backlog:
|
||||||
|
|
||||||
|
1. Extract shared reduced IM/DD integration helpers
|
||||||
|
Targets:
|
||||||
|
- `Tests/integration/IMDD_base_system_minimal_integration_test.m`
|
||||||
|
- `Tests/integration/IMDD_base_system_no_impairment_integration_test.m`
|
||||||
|
- `Tests/integration/IMDD_base_system_impairment_monotonicity_integration_test.m`
|
||||||
|
Goal:
|
||||||
|
- remove duplicated reduced-workflow builders
|
||||||
|
- keep assertions local to each test file
|
||||||
|
|
||||||
|
2. Revisit the "no impairment" IM/DD fixture
|
||||||
|
Target:
|
||||||
|
- `Tests/integration/IMDD_base_system_no_impairment_integration_test.m`
|
||||||
|
Goal:
|
||||||
|
- ensure the name, comments, and thresholds reflect the actual physical regime
|
||||||
|
|
||||||
|
3. Harden the WDM optical-chain regression guards
|
||||||
|
Targets:
|
||||||
|
- `Tests/integration/WDM_mux_demux_optical_chain_integration_test.m`
|
||||||
|
- `Tests/integration/WDM_end_to_end_two_channel_receiver_integration_test.m`
|
||||||
|
Goal:
|
||||||
|
- improve channel-separation assertions
|
||||||
|
- add propagated-chain checks where stable
|
||||||
|
- avoid brittle waveform snapshots
|
||||||
|
|
||||||
## Next DSP Layer
|
## Next DSP Layer
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,64 @@
|
|||||||
classdef Opticalsignal_test < IMDDTestCase
|
classdef Opticalsignal_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Opticalsignal.
|
methods (Test, TestTags = {'unit', 'fast', 'signals', 'optical'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/00_signals/Opticalsignal.m
|
function constructorWiresOpticalMetadata(testCase)
|
||||||
|
opt = makeOpticalsignal([1; 2; 3], 12.5e9, 1310e-9, 0.25, 2);
|
||||||
|
|
||||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
testCase.verifyClass(opt, 'Opticalsignal');
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(opt.signal, [1; 2; 3]);
|
||||||
testCase.assumeFail("Tests for Opticalsignal are not implemented yet.");
|
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
|
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
|
classdef Signalgenerator_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Signalgenerator.
|
methods (Test, TestTags = {'unit', 'fast', 'transmit', 'signalgenerator'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/01_transmit/Signalgenerator.m
|
function constructorStoresDefaultConfiguration(testCase)
|
||||||
|
gen = Signalgenerator();
|
||||||
|
|
||||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
testCase.verifyEqual(gen.form, signalform.sine);
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(gen.length, 1024);
|
||||||
testCase.assumeFail("Tests for Signalgenerator are not implemented yet.");
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,69 @@
|
|||||||
classdef Amplifier_test < IMDDTestCase
|
classdef Amplifier_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Amplifier.
|
methods (Test, TestTags = {'unit', 'fast', 'etc', 'amplifier'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_etc/Amplifier.m
|
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'})
|
testCase.verifyEqual(amp.amp_mode, amp_mode.ideal_no_noise);
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(amp.gain_mode, gain_mode.gain);
|
||||||
testCase.assumeFail("Tests for Amplifier are not implemented yet.");
|
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
|
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
|
classdef Filter_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Filter.
|
methods (Test, TestTags = {'unit', 'fast', 'filter', 'etc'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_etc/Filter.m
|
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'})
|
testCase.verifyEqual(flt.active, true);
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(flt.filterType, filtertypes.butterworth);
|
||||||
testCase.assumeFail("Tests for Filter are not implemented yet.");
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,78 @@
|
|||||||
classdef DP_Fiber_test < IMDDTestCase
|
classdef DP_Fiber_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for DP_Fiber.
|
methods (Test, TestTags = {'unit', 'fast', 'optical', 'dp_fiber'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/DP_Fiber.m
|
function constructorStoresConfigurationAndStartsWithEmptyState(testCase)
|
||||||
|
fiber = makeIdentityFiber();
|
||||||
|
|
||||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
testCase.verifyEqual(fiber.L, 0);
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(fiber.dz, 1);
|
||||||
testCase.assumeFail("Tests for DP_Fiber are not implemented yet.");
|
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
|
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
|
classdef EML_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for EML.
|
methods (Test, TestTags = {'unit', 'fast', 'optical', 'eml'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/EML.m
|
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'})
|
testCase.verifyEqual(eml.mode, eml_mode.iq_linear);
|
||||||
function testNotImplemented(testCase)
|
testCase.verifyEqual(eml.fsimu, 64e9);
|
||||||
testCase.assumeFail("Tests for EML are not implemented yet.");
|
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
|
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
|
classdef Optical_Demultiplex_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Optical_Demultiplex.
|
methods (Test, TestTags = {'unit', 'fast', 'optical', 'demux'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/Optical_Demultiplex.m
|
function processReturnsOneCellPerChannelAndPreservesMetadata(testCase)
|
||||||
|
fs = 32e9;
|
||||||
|
lambdaCenterNm = 1310;
|
||||||
|
lambdaA = 1309e-9;
|
||||||
|
lambdaB = 1311e-9;
|
||||||
|
|
||||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
sigA = makeOpticalSignal( ...
|
||||||
function testNotImplemented(testCase)
|
[1 2; 1 2; 1 2; 1 2], ...
|
||||||
testCase.assumeFail("Tests for Optical_Demultiplex are not implemented yet.");
|
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
|
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
|
classdef Optical_Multiplex_test < IMDDTestCase
|
||||||
% Auto-generated placeholder for Optical_Multiplex.
|
methods (Test, TestTags = {'unit', 'fast', 'optical', 'mux'})
|
||||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/02_optical/Optical_Multiplex.m
|
function processCombinesChannelsAndPreservesMetadata(testCase)
|
||||||
|
fs = 32e9;
|
||||||
|
lambdaCenter = 1550;
|
||||||
|
lambdaA = 1550e-9;
|
||||||
|
lambdaB = 1550e-9;
|
||||||
|
|
||||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
sigA = makeOpticalSignal( ...
|
||||||
function testNotImplemented(testCase)
|
[1 2; 1 2; 1 2], ...
|
||||||
testCase.assumeFail("Tests for Optical_Multiplex are not implemented yet.");
|
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
|
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
|
% - the reduced end-to-end chain runs without errors
|
||||||
% - key signal objects have the expected lengths and sampling rates
|
% - key signal objects have the expected lengths and sampling rates
|
||||||
% - the DSP outputs return finite, bounded metrics
|
% - 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
|
% - MLSE does not regress relative to the preceding FFE stage
|
||||||
%
|
%
|
||||||
% Thresholds are intentionally provisional in this first iteration.
|
% Thresholds are intentionally provisional in this first iteration.
|
||||||
@@ -19,7 +20,7 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
|||||||
function runReducedImddWorkflowOnce(testCase)
|
function runReducedImddWorkflowOnce(testCase)
|
||||||
% Run the deterministic reduced workflow once and share the
|
% Run the deterministic reduced workflow once and share the
|
||||||
% resulting signals/metrics across all test methods.
|
% resulting signals/metrics across all test methods.
|
||||||
testCase.workflow = runReducedWorkflow();
|
testCase.workflow = buildReducedImddWorkflow("minimal");
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -31,18 +32,30 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
|||||||
testCase.verifyClass(wf.Symbols, 'Informationsignal');
|
testCase.verifyClass(wf.Symbols, 'Informationsignal');
|
||||||
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
|
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
|
||||||
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
|
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
|
||||||
|
testCase.verifyClass(wf.Opt_sig_tx, 'Opticalsignal');
|
||||||
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
|
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
|
||||||
testCase.verifyClass(wf.Rx_sig_after_pd, 'Electricalsignal');
|
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.Scpe_sig, 'Informationsignal');
|
||||||
|
testCase.verifyClass(wf.Synced_sig_centered, 'Informationsignal');
|
||||||
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
|
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
|
||||||
|
|
||||||
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
|
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
|
||||||
testCase.verifyGreaterThan(length(wf.Symbols.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.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
|
% After the matched filter, the signal is intentionally reduced
|
||||||
% to 2 samples per symbol for the downstream DSP chain.
|
% to 2 samples per symbol for the downstream DSP chain.
|
||||||
testCase.verifyEqual(wf.Scpe_sig.fs, 2 * wf.params.fsym);
|
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(wf.Synced_sig.fs, 2 * wf.params.fsym);
|
||||||
|
|
||||||
% The synchronized signal is explicitly cropped to 2 samples per
|
% The synchronized signal is explicitly cropped to 2 samples per
|
||||||
@@ -56,9 +69,12 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
|||||||
finiteSignals = {
|
finiteSignals = {
|
||||||
wf.Digi_sig.signal
|
wf.Digi_sig.signal
|
||||||
wf.El_sig.signal
|
wf.El_sig.signal
|
||||||
|
wf.Opt_sig_tx.signal
|
||||||
wf.Opt_sig.signal
|
wf.Opt_sig.signal
|
||||||
wf.Rx_sig_after_pd.signal
|
wf.Rx_sig_after_pd.signal
|
||||||
|
wf.Scpe_sig_pre_mf.signal
|
||||||
wf.Scpe_sig.signal
|
wf.Scpe_sig.signal
|
||||||
|
wf.Synced_sig_centered.signal
|
||||||
wf.Synced_sig.signal
|
wf.Synced_sig.signal
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,6 +84,20 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
|||||||
testCase.verifyFalse(any(isinf(sig), 'all'));
|
testCase.verifyFalse(any(isinf(sig), 'all'));
|
||||||
end
|
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.verifyGreaterThanOrEqual(wf.ffe_results.metrics.BER, 0);
|
||||||
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
|
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
|
||||||
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
|
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
|
||||||
@@ -105,217 +135,11 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function p = signalPower(signal)
|
||||||
function workflow = runReducedWorkflow()
|
p = mean(abs(signal(:)).^2);
|
||||||
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;
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function params = reducedWorkflowParameters()
|
function v = signalVariance(signal)
|
||||||
params = struct();
|
centered = signal(:) - mean(signal(:));
|
||||||
|
v = mean(abs(centered).^2);
|
||||||
% 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;
|
|
||||||
end
|
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
|
||||||
245
Tests/integration/WDM_mux_demux_optical_chain_integration_test.m
Normal file
245
Tests/integration/WDM_mux_demux_optical_chain_integration_test.m
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
branchLambdaIdx = zeros(numChannels, 1);
|
||||||
|
branchPower = 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);
|
||||||
|
end
|
||||||
|
|
||||||
|
referencePower = zeros(numChannels, 1);
|
||||||
|
for refCh = 1:numChannels
|
||||||
|
referencePower(refCh) = mean(abs(wf.channelRefs{refCh}.signal).^2, 'all');
|
||||||
|
end
|
||||||
|
referenceFraction = referencePower / sum(referencePower);
|
||||||
|
recoveredFraction = branchPower / sum(branchPower);
|
||||||
|
|
||||||
|
for ch = 1:numChannels
|
||||||
|
matchedRef = branchLambdaIdx(ch);
|
||||||
|
otherIdx = setdiff(1:numChannels, matchedRef);
|
||||||
|
matchedError = abs(recoveredFraction(ch) - referenceFraction(matchedRef));
|
||||||
|
offErrors = abs(recoveredFraction(ch) - referenceFraction(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.verifyLessThanOrEqual(matchedError, min(offErrors) + 1e-3);
|
||||||
|
testCase.verifyLessThan(matchedError, 0.2);
|
||||||
|
|
||||||
|
% 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 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(ch), ...
|
||||||
|
"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", 100).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", 120e9);
|
||||||
|
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 = 400e9;
|
||||||
|
params.lambdaCenterNm = 1310;
|
||||||
|
params.fsBase = 64e9;
|
||||||
|
params.fsMux = 4 * params.fsBase;
|
||||||
|
params.laserPowerDbm = [-3, 0, 3, 6];
|
||||||
|
params.launchPowerDbm = 3 + 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 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
|
||||||
938
Tests/test_dashboard.m
Normal file
938
Tests/test_dashboard.m
Normal file
@@ -0,0 +1,938 @@
|
|||||||
|
function app = test_dashboard(options)
|
||||||
|
%TEST_DASHBOARD Minimal GUI for test discovery and execution.
|
||||||
|
% test_dashboard() opens a small dashboard that:
|
||||||
|
% - discovers implemented, placeholder, and missing class-backed tests
|
||||||
|
% - lists standalone tests such as integration or helper tests
|
||||||
|
% - runs the selected test file
|
||||||
|
% - runs the existing fast/full test profiles
|
||||||
|
%
|
||||||
|
% app = test_dashboard("Visible", false) builds the dashboard without
|
||||||
|
% showing it. This is useful for non-interactive validation.
|
||||||
|
|
||||||
|
arguments
|
||||||
|
options.Visible (1, 1) logical = true
|
||||||
|
end
|
||||||
|
|
||||||
|
app = struct();
|
||||||
|
app.Inventory = table();
|
||||||
|
app.SelectedRow = [];
|
||||||
|
app.ActiveProfileRowIndices = [];
|
||||||
|
app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
|
||||||
|
testsRoot = fileparts(mfilename("fullpath"));
|
||||||
|
repoRoot = fileparts(testsRoot);
|
||||||
|
reportsRoot = fullfile(testsRoot, 'reports');
|
||||||
|
latestRunFile = fullfile(testsRoot, '.last_test_run.json');
|
||||||
|
|
||||||
|
createUi();
|
||||||
|
refreshInventory();
|
||||||
|
|
||||||
|
if options.Visible
|
||||||
|
app.Figure.Visible = "on";
|
||||||
|
end
|
||||||
|
|
||||||
|
function createUi()
|
||||||
|
app.Figure = uifigure( ...
|
||||||
|
"Name", "IMDD Test Dashboard", ...
|
||||||
|
"Position", [80, 80, 1360, 820], ...
|
||||||
|
"Color", [0.965, 0.973, 0.988], ...
|
||||||
|
"Visible", "off");
|
||||||
|
|
||||||
|
app.Layout = uigridlayout(app.Figure, [3, 1]);
|
||||||
|
app.Layout.RowHeight = {92, "1x", 150};
|
||||||
|
app.Layout.Padding = [14, 14, 14, 14];
|
||||||
|
app.Layout.RowSpacing = 12;
|
||||||
|
|
||||||
|
app.HeaderPanel = uipanel(app.Layout, ...
|
||||||
|
"Title", "Suite Overview", ...
|
||||||
|
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||||
|
app.HeaderPanel.Layout.Row = 1;
|
||||||
|
|
||||||
|
headerGrid = uigridlayout(app.HeaderPanel, [2, 10]);
|
||||||
|
headerGrid.RowHeight = {28, 32};
|
||||||
|
headerGrid.ColumnWidth = {110, 110, 110, 90, 90, 90, "1x", 120, 120, 120};
|
||||||
|
headerGrid.Padding = [10, 8, 10, 8];
|
||||||
|
|
||||||
|
app.ImplementedLabel = makeMetricLabel(headerGrid, "Implemented", 1);
|
||||||
|
app.PlaceholderLabel = makeMetricLabel(headerGrid, "Placeholder", 2);
|
||||||
|
app.MissingLabel = makeMetricLabel(headerGrid, "Missing", 3);
|
||||||
|
app.PassedLabel = makeMetricLabel(headerGrid, "Passed", 4);
|
||||||
|
app.FailedLabel = makeMetricLabel(headerGrid, "Failed", 5);
|
||||||
|
app.IncompleteLabel = makeMetricLabel(headerGrid, "Incomplete", 6);
|
||||||
|
app.TotalLabel = makeMetricLabel(headerGrid, "Total", 7);
|
||||||
|
|
||||||
|
app.FilterDropDown = uidropdown(headerGrid, ...
|
||||||
|
"Items", {'All', 'Implemented', 'Missing', 'Standalone'}, ...
|
||||||
|
"Value", 'All', ...
|
||||||
|
"ValueChangedFcn", @filterChanged);
|
||||||
|
app.FilterDropDown.Layout.Row = 2;
|
||||||
|
app.FilterDropDown.Layout.Column = 8;
|
||||||
|
|
||||||
|
app.RefreshButton = uibutton(headerGrid, ...
|
||||||
|
"Text", "Refresh", ...
|
||||||
|
"ButtonPushedFcn", @refreshInventoryButtonPushed);
|
||||||
|
app.RefreshButton.Layout.Row = 2;
|
||||||
|
app.RefreshButton.Layout.Column = 9;
|
||||||
|
|
||||||
|
app.OpenButton = uibutton(headerGrid, ...
|
||||||
|
"Text", "Open Selected", ...
|
||||||
|
"ButtonPushedFcn", @openSelectedTest);
|
||||||
|
app.OpenButton.Layout.Row = 2;
|
||||||
|
app.OpenButton.Layout.Column = 10;
|
||||||
|
|
||||||
|
app.TablePanel = uipanel(app.Layout, ...
|
||||||
|
"Title", "Discovered Tests", ...
|
||||||
|
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||||
|
app.TablePanel.Layout.Row = 2;
|
||||||
|
|
||||||
|
tableGrid = uigridlayout(app.TablePanel, [2, 1]);
|
||||||
|
tableGrid.RowHeight = {36, "1x"};
|
||||||
|
tableGrid.Padding = [10, 8, 10, 10];
|
||||||
|
|
||||||
|
buttonGrid = uigridlayout(tableGrid, [1, 6]);
|
||||||
|
buttonGrid.ColumnWidth = {150, 120, 120, 140, 170, "1x"};
|
||||||
|
buttonGrid.Padding = [0, 0, 0, 0];
|
||||||
|
buttonGrid.ColumnSpacing = 10;
|
||||||
|
|
||||||
|
app.RunSelectedButton = uibutton(buttonGrid, ...
|
||||||
|
"Text", "Run Selected", ...
|
||||||
|
"ButtonPushedFcn", @runSelectedTest);
|
||||||
|
app.RunFastButton = uibutton(buttonGrid, ...
|
||||||
|
"Text", "Run Fast", ...
|
||||||
|
"ButtonPushedFcn", @(~, ~) runProfile("fast"));
|
||||||
|
app.RunFullButton = uibutton(buttonGrid, ...
|
||||||
|
"Text", "Run Full", ...
|
||||||
|
"ButtonPushedFcn", @(~, ~) runProfile("full"));
|
||||||
|
app.RunIntegrationButton = uibutton(buttonGrid, ...
|
||||||
|
"Text", "Run Integration", ...
|
||||||
|
"ButtonPushedFcn", @(~, ~) runProfile("integration"));
|
||||||
|
app.GenerateButton = uibutton(buttonGrid, ...
|
||||||
|
"Text", "Generate Missing Tests", ...
|
||||||
|
"ButtonPushedFcn", @generateMissingTests);
|
||||||
|
|
||||||
|
app.TableHintLabel = uilabel(buttonGrid, ...
|
||||||
|
"Text", "Select a row to run or open the corresponding test file.", ...
|
||||||
|
"HorizontalAlignment", "right", ...
|
||||||
|
"FontColor", [0.28, 0.32, 0.38]);
|
||||||
|
app.TableHintLabel.Layout.Column = 6;
|
||||||
|
|
||||||
|
app.Table = uitable(tableGrid, ...
|
||||||
|
"ColumnName", {"Status", "Last Run", "Result", "Date", "Commit", "Scope", "Target", "Test File", "Tags"}, ...
|
||||||
|
"ColumnEditable", false(1, 9), ...
|
||||||
|
"ColumnSortable", true(1, 9), ...
|
||||||
|
"ColumnWidth", {95, 95, 90, 150, 90, 100, 240, 300, "auto"}, ...
|
||||||
|
"SelectionType", "row", ...
|
||||||
|
"Multiselect", "off", ...
|
||||||
|
"CellSelectionCallback", @tableSelectionChanged, ...
|
||||||
|
"FontName", "Consolas");
|
||||||
|
app.Table.Layout.Row = 2;
|
||||||
|
|
||||||
|
app.LogPanel = uipanel(app.Layout, ...
|
||||||
|
"Title", "Run Log", ...
|
||||||
|
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||||
|
app.LogPanel.Layout.Row = 3;
|
||||||
|
|
||||||
|
logGrid = uigridlayout(app.LogPanel, [1, 1]);
|
||||||
|
logGrid.Padding = [10, 8, 10, 10];
|
||||||
|
|
||||||
|
app.LogArea = uitextarea(logGrid, ...
|
||||||
|
"Editable", "off", ...
|
||||||
|
"FontName", "Consolas", ...
|
||||||
|
"Value", {'IMDD Test Dashboard ready.'});
|
||||||
|
end
|
||||||
|
|
||||||
|
function label = makeMetricLabel(parent, titleText, columnIdx)
|
||||||
|
label = uilabel(parent, ...
|
||||||
|
"Text", sprintf("%s: --", titleText), ...
|
||||||
|
"FontWeight", "bold", ...
|
||||||
|
"FontSize", 13, ...
|
||||||
|
"FontColor", [0.12, 0.18, 0.26]);
|
||||||
|
label.Layout.Row = 1;
|
||||||
|
label.Layout.Column = columnIdx;
|
||||||
|
end
|
||||||
|
|
||||||
|
function refreshInventoryButtonPushed(~, ~)
|
||||||
|
refreshInventory();
|
||||||
|
end
|
||||||
|
|
||||||
|
function refreshInventory()
|
||||||
|
app.Inventory = buildInventory(repoRoot, testsRoot);
|
||||||
|
app.SelectedRow = [];
|
||||||
|
loadPersistedRunState();
|
||||||
|
updateSummary(app.Inventory);
|
||||||
|
applyFilter();
|
||||||
|
appendLog(sprintf("[%s] Refreshed test inventory.", stamp()));
|
||||||
|
end
|
||||||
|
|
||||||
|
function filterChanged(~, ~)
|
||||||
|
applyFilter();
|
||||||
|
end
|
||||||
|
|
||||||
|
function applyFilter()
|
||||||
|
filtered = app.Inventory;
|
||||||
|
switch string(app.FilterDropDown.Value)
|
||||||
|
case "Implemented"
|
||||||
|
filtered = filtered(filtered.Status == "Implemented", :);
|
||||||
|
case "Missing"
|
||||||
|
filtered = filtered(filtered.Status == "Missing", :);
|
||||||
|
case "Standalone"
|
||||||
|
filtered = filtered(filtered.Scope == "Standalone", :);
|
||||||
|
otherwise
|
||||||
|
% keep all rows
|
||||||
|
end
|
||||||
|
|
||||||
|
app.Table.Data = filtered(:, ["Status", "LastRun", "ResultSummary", "LastRunDate", "LastRunCommit", "Scope", "Target", "TestFile", "Tags"]);
|
||||||
|
applyRowStyles(filtered);
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateSummary(inventory)
|
||||||
|
implementedCount = nnz(inventory.Status == "Implemented");
|
||||||
|
missingCount = nnz(inventory.Status == "Missing");
|
||||||
|
passedCount = nnz(inventory.LastRun == "Passed");
|
||||||
|
failedCount = nnz(inventory.LastRun == "Failed");
|
||||||
|
incompleteCount = nnz(inventory.LastRun == "Incomplete");
|
||||||
|
totalCount = height(inventory);
|
||||||
|
|
||||||
|
app.ImplementedLabel.Text = sprintf("Implemented: %d", implementedCount);
|
||||||
|
app.PlaceholderLabel.Text = "Placeholder: hidden";
|
||||||
|
app.MissingLabel.Text = sprintf("Missing: %d", missingCount);
|
||||||
|
app.PassedLabel.Text = sprintf("Passed: %d", passedCount);
|
||||||
|
app.FailedLabel.Text = sprintf("Failed: %d", failedCount);
|
||||||
|
app.IncompleteLabel.Text = sprintf("Incomplete: %d", incompleteCount);
|
||||||
|
app.TotalLabel.Text = sprintf("Total: %d", totalCount);
|
||||||
|
|
||||||
|
app.PassedLabel.FontColor = metricColor(passedCount > 0, [0.18, 0.56, 0.24]);
|
||||||
|
app.FailedLabel.FontColor = metricColor(failedCount > 0, [0.78, 0.22, 0.22]);
|
||||||
|
app.IncompleteLabel.FontColor = metricColor(incompleteCount > 0, [0.72, 0.48, 0.08]);
|
||||||
|
end
|
||||||
|
|
||||||
|
function applyRowStyles(filtered)
|
||||||
|
removeStyle(app.Table);
|
||||||
|
|
||||||
|
greenStyle = uistyle("BackgroundColor", [0.90, 0.97, 0.92]);
|
||||||
|
amberStyle = uistyle("BackgroundColor", [0.995, 0.95, 0.85]);
|
||||||
|
redStyle = uistyle("BackgroundColor", [0.985, 0.90, 0.90]);
|
||||||
|
blueStyle = uistyle("BackgroundColor", [0.90, 0.94, 0.995]);
|
||||||
|
runningStyle = uistyle("BackgroundColor", [0.89, 0.95, 1.00]);
|
||||||
|
|
||||||
|
for rowIdx = 1:height(filtered)
|
||||||
|
if filtered.LastRun(rowIdx) == "Passed"
|
||||||
|
addStyle(app.Table, greenStyle, "row", rowIdx);
|
||||||
|
elseif filtered.LastRun(rowIdx) == "Failed"
|
||||||
|
addStyle(app.Table, redStyle, "row", rowIdx);
|
||||||
|
elseif filtered.LastRun(rowIdx) == "Incomplete"
|
||||||
|
addStyle(app.Table, amberStyle, "row", rowIdx);
|
||||||
|
elseif filtered.LastRun(rowIdx) == "Running"
|
||||||
|
addStyle(app.Table, runningStyle, "row", rowIdx);
|
||||||
|
elseif filtered.Scope(rowIdx) == "Standalone"
|
||||||
|
addStyle(app.Table, blueStyle, "row", rowIdx);
|
||||||
|
elseif filtered.Status(rowIdx) == "Implemented"
|
||||||
|
addStyle(app.Table, greenStyle, "row", rowIdx);
|
||||||
|
elseif filtered.Status(rowIdx) == "Placeholder"
|
||||||
|
addStyle(app.Table, amberStyle, "row", rowIdx);
|
||||||
|
else
|
||||||
|
addStyle(app.Table, redStyle, "row", rowIdx);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tableSelectionChanged(src, event)
|
||||||
|
if isempty(event.Indices)
|
||||||
|
app.SelectedRow = [];
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
selectedRow = event.Indices(1);
|
||||||
|
visibleRows = src.Data;
|
||||||
|
selectedTestFile = string(visibleRows.TestFile(selectedRow));
|
||||||
|
|
||||||
|
matchIdx = find(app.Inventory.TestFile == selectedTestFile, 1);
|
||||||
|
if isempty(matchIdx)
|
||||||
|
selectedTarget = string(visibleRows.Target(selectedRow));
|
||||||
|
matchIdx = find(app.Inventory.Target == selectedTarget, 1);
|
||||||
|
end
|
||||||
|
|
||||||
|
app.SelectedRow = matchIdx;
|
||||||
|
end
|
||||||
|
|
||||||
|
function openSelectedTest(~, ~)
|
||||||
|
record = selectedRecord();
|
||||||
|
if isempty(record)
|
||||||
|
uialert(app.Figure, "Select a test row first.", "No Selection");
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||||
|
uialert(app.Figure, "The selected class does not have a test file yet.", "No Test File");
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
edit(record.TestFile);
|
||||||
|
appendLog(sprintf("[%s] Opened %s", stamp(), record.TestFile));
|
||||||
|
end
|
||||||
|
|
||||||
|
function runSelectedTest(~, ~)
|
||||||
|
record = selectedRecord();
|
||||||
|
if isempty(record)
|
||||||
|
uialert(app.Figure, "Select a test row first.", "No Selection");
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||||
|
uialert(app.Figure, "The selected class does not have a runnable test file yet.", "Missing Test");
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
titleText = sprintf("Running %s", record.TestFileName);
|
||||||
|
markRowAsRunning(record);
|
||||||
|
runWithProgress(titleText, @() runSingleTestFile(record.TestFile), record);
|
||||||
|
end
|
||||||
|
|
||||||
|
function runProfile(profile)
|
||||||
|
titleText = sprintf("Running profile '%s'", profile);
|
||||||
|
rowIndices = profileRowIndices(profile);
|
||||||
|
app.ActiveProfileRowIndices = rowIndices;
|
||||||
|
markRowsAsRunning(rowIndices);
|
||||||
|
runWithProgress(titleText, @() runProfileInternal(profile, rowIndices), table());
|
||||||
|
app.ActiveProfileRowIndices = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
function generateMissingTests(~, ~)
|
||||||
|
setupRepositoryPath();
|
||||||
|
try
|
||||||
|
summary = generateTests("verbose", false);
|
||||||
|
refreshInventory();
|
||||||
|
appendLog(sprintf("[%s] Generated missing tests: %d new, %d existing skipped, %d classes tracked.", ...
|
||||||
|
stamp(), summary.generated, summary.skippedExisting, summary.classCount));
|
||||||
|
catch err
|
||||||
|
appendLog(sprintf("[%s] generateTests failed.", stamp()));
|
||||||
|
appendLog(getReport(err, "extended", "hyperlinks", "off"));
|
||||||
|
uialert(app.Figure, err.message, "Generate Tests Failed");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function runWithProgress(titleText, runner, record)
|
||||||
|
try
|
||||||
|
[output, results] = runner();
|
||||||
|
appendLog(sprintf("[%s] %s", stamp(), titleText));
|
||||||
|
appendLog(output);
|
||||||
|
updateRunStateFromResults(results, record);
|
||||||
|
writeRunArtifacts(titleText, results, record);
|
||||||
|
catch err
|
||||||
|
appendLog(sprintf("[%s] %s failed.", stamp(), titleText));
|
||||||
|
appendLog(getReport(err, "extended", "hyperlinks", "off"));
|
||||||
|
updateFailureState(record);
|
||||||
|
uialert(app.Figure, err.message, "Run Failed");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function [output, results] = runProfileInternal(profile, rowIndices)
|
||||||
|
setupRepositoryPath();
|
||||||
|
profile = string(profile);
|
||||||
|
|
||||||
|
results = matlab.unittest.TestResult.empty(0, 1);
|
||||||
|
logLines = strings(0, 1);
|
||||||
|
logLines(end + 1, 1) = sprintf("Running profile '%s' with %d test files.", profile, numel(rowIndices));
|
||||||
|
|
||||||
|
for idx = 1:numel(rowIndices)
|
||||||
|
record = app.Inventory(rowIndices(idx), :);
|
||||||
|
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
logLines(end + 1, 1) = sprintf("Running %s", record.TestFileName);
|
||||||
|
drawnow;
|
||||||
|
|
||||||
|
currentResults = runtests(char(record.TestFile));
|
||||||
|
currentResults = currentResults(:);
|
||||||
|
if isempty(results)
|
||||||
|
results = currentResults;
|
||||||
|
else
|
||||||
|
results = [results; currentResults]; %#ok<AGROW>
|
||||||
|
end
|
||||||
|
updateRunStateFromResults(currentResults, record);
|
||||||
|
|
||||||
|
[statusText, summaryText] = summarizeResults(currentResults);
|
||||||
|
logLines(end + 1, 1) = sprintf("Finished %s: %s (%s)", ...
|
||||||
|
record.TestFileName, statusText, summaryText);
|
||||||
|
drawnow;
|
||||||
|
end
|
||||||
|
|
||||||
|
logLines(end + 1, 1) = sprintf("Passed: %d | Failed: %d | Incomplete: %d", ...
|
||||||
|
nnz([results.Passed]), nnz([results.Failed]), nnz([results.Incomplete]));
|
||||||
|
output = strjoin(logLines, newline);
|
||||||
|
end
|
||||||
|
|
||||||
|
function [output, results] = runSingleTestFile(testFile)
|
||||||
|
setupRepositoryPath();
|
||||||
|
results = runtests(char(testFile));
|
||||||
|
output = evalc('disp(results);');
|
||||||
|
end
|
||||||
|
|
||||||
|
function record = selectedRecord()
|
||||||
|
record = table();
|
||||||
|
if isempty(app.SelectedRow)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if app.SelectedRow < 1 || app.SelectedRow > height(app.Inventory)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
record = app.Inventory(app.SelectedRow, :);
|
||||||
|
end
|
||||||
|
|
||||||
|
function setupRepositoryPath()
|
||||||
|
addpath(repoRoot);
|
||||||
|
addpath(testsRoot);
|
||||||
|
|
||||||
|
folders = ["Classes", "Functions", "Datatypes", "Libs"];
|
||||||
|
for folder = folders
|
||||||
|
fullFolder = fullfile(repoRoot, folder);
|
||||||
|
if isfolder(fullFolder)
|
||||||
|
addpath(genpath(fullFolder));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function appendLog(textIn)
|
||||||
|
newLines = string(splitlines(string(textIn)));
|
||||||
|
current = string(app.LogArea.Value);
|
||||||
|
current = current(current ~= "");
|
||||||
|
newLines = newLines(newLines ~= "");
|
||||||
|
combined = [current; newLines];
|
||||||
|
if isempty(combined)
|
||||||
|
combined = " ";
|
||||||
|
end
|
||||||
|
app.LogArea.Value = cellstr(combined);
|
||||||
|
drawnow;
|
||||||
|
end
|
||||||
|
|
||||||
|
function markRowAsRunning(record)
|
||||||
|
if isempty(record) || strlength(record.TestFile) == 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
updateMappedState(char(record.TestFile), 'Running', '...');
|
||||||
|
applyStoredRunState();
|
||||||
|
pause(0.05);
|
||||||
|
end
|
||||||
|
|
||||||
|
function markRowsAsRunning(rowIndices)
|
||||||
|
for idx = 1:numel(rowIndices)
|
||||||
|
key = char(app.Inventory.TestFile(rowIndices(idx)));
|
||||||
|
if ~isempty(key)
|
||||||
|
updateMappedState(key, 'Running', '...');
|
||||||
|
end
|
||||||
|
end
|
||||||
|
applyStoredRunState();
|
||||||
|
pause(0.05);
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateRunStateFromResults(results, record)
|
||||||
|
if ~isempty(record)
|
||||||
|
[statusText, summaryText] = summarizeResults(results);
|
||||||
|
updateMappedState(char(record.TestFile), statusText, summaryText);
|
||||||
|
else
|
||||||
|
resultNames = string({results.Name})';
|
||||||
|
uniqueNames = unique(resultNames);
|
||||||
|
for idx = 1:numel(uniqueNames)
|
||||||
|
currentResults = results(resultNames == uniqueNames(idx));
|
||||||
|
[statusText, summaryText] = summarizeResults(currentResults);
|
||||||
|
targetRows = find(app.Inventory.TestFileName == uniqueNames(idx));
|
||||||
|
for rowIdx = targetRows'
|
||||||
|
updateMappedState(char(app.Inventory.TestFile(rowIdx)), statusText, summaryText);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
applyStoredRunState();
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateFailureState(record)
|
||||||
|
if ~isempty(record) && strlength(record.TestFile) > 0
|
||||||
|
updateMappedState(char(record.TestFile), 'Failed', 'error');
|
||||||
|
applyStoredRunState();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateMappedState(key, statusText, summaryText)
|
||||||
|
if isempty(key)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
app.LastRunStatusMap(key) = char(statusText);
|
||||||
|
app.LastRunSummaryMap(key) = char(summaryText);
|
||||||
|
end
|
||||||
|
|
||||||
|
function applyStoredRunState()
|
||||||
|
for idx = 1:height(app.Inventory)
|
||||||
|
key = char(app.Inventory.TestFile(idx));
|
||||||
|
if isempty(key)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
if isKey(app.LastRunStatusMap, key)
|
||||||
|
app.Inventory.LastRun(idx) = string(app.LastRunStatusMap(key));
|
||||||
|
app.Inventory.ResultSummary(idx) = string(app.LastRunSummaryMap(key));
|
||||||
|
if isKey(app.LastRunDateMap, key)
|
||||||
|
app.Inventory.LastRunDate(idx) = string(app.LastRunDateMap(key));
|
||||||
|
end
|
||||||
|
if isKey(app.LastRunCommitMap, key)
|
||||||
|
app.Inventory.LastRunCommit(idx) = string(app.LastRunCommitMap(key));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
updateSummary(app.Inventory);
|
||||||
|
applyFilter();
|
||||||
|
end
|
||||||
|
|
||||||
|
function loadPersistedRunState()
|
||||||
|
app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||||
|
|
||||||
|
if ~isfile(latestRunFile)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
try
|
||||||
|
payload = jsondecode(fileread(latestRunFile));
|
||||||
|
fallbackDate = stringOrEmpty(payload, 'timestamp');
|
||||||
|
fallbackCommit = "";
|
||||||
|
if isfield(payload, 'git')
|
||||||
|
fallbackCommit = stringOrEmpty(payload.git, 'short_commit');
|
||||||
|
end
|
||||||
|
if isfield(payload, 'row_states')
|
||||||
|
rowStates = payload.row_states;
|
||||||
|
for idx = 1:numel(rowStates)
|
||||||
|
key = stringOrEmpty(rowStates(idx), 'test_file');
|
||||||
|
if strlength(key) == 0
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
app.LastRunStatusMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'last_run'));
|
||||||
|
app.LastRunSummaryMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'result_summary'));
|
||||||
|
rowDate = stringOrEmpty(rowStates(idx), 'last_run_date');
|
||||||
|
rowCommit = stringOrEmpty(rowStates(idx), 'last_run_commit');
|
||||||
|
if strlength(rowDate) == 0
|
||||||
|
rowDate = fallbackDate;
|
||||||
|
end
|
||||||
|
if strlength(rowCommit) == 0
|
||||||
|
rowCommit = fallbackCommit;
|
||||||
|
end
|
||||||
|
app.LastRunDateMap(char(key)) = char(rowDate);
|
||||||
|
app.LastRunCommitMap(char(key)) = char(rowCommit);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
applyStoredRunState();
|
||||||
|
catch
|
||||||
|
% Ignore malformed local cache files and rebuild from scratch.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function writeRunArtifacts(runLabel, results, record)
|
||||||
|
if ~isfolder(reportsRoot)
|
||||||
|
mkdir(reportsRoot);
|
||||||
|
end
|
||||||
|
|
||||||
|
gitInfo = collectGitMetadata(repoRoot);
|
||||||
|
runTimestamp = char(datetime("now", "TimeZone", "local", "Format", "yyyy-MM-dd'T'HH:mm:ssXXX"));
|
||||||
|
updateMetadataMaps(results, record, runTimestamp, gitInfo.short_commit);
|
||||||
|
|
||||||
|
report = struct();
|
||||||
|
report.timestamp = runTimestamp;
|
||||||
|
rowStates = inventoryRowStates(app.Inventory);
|
||||||
|
report.run_label = char(runLabel);
|
||||||
|
report.repo_root = repoRoot;
|
||||||
|
report.git = gitInfo;
|
||||||
|
report.totals = struct( ...
|
||||||
|
'passed', nnz([results.Passed]), ...
|
||||||
|
'failed', nnz([results.Failed]), ...
|
||||||
|
'incomplete', nnz([results.Incomplete]));
|
||||||
|
report.method_results = resultEntries(results);
|
||||||
|
report.row_states = rowStates;
|
||||||
|
|
||||||
|
jsonText = jsonencode(report, PrettyPrint=true);
|
||||||
|
writeTextFile(latestRunFile, jsonText);
|
||||||
|
|
||||||
|
reportFile = fullfile(reportsRoot, sprintf('%s_%s.json', ...
|
||||||
|
datestr(now, 'yyyy-mm-dd_HHMMSS'), sanitizeFileLabel(runLabel)));
|
||||||
|
writeTextFile(reportFile, jsonText);
|
||||||
|
appendLog(sprintf("[%s] Saved run report: %s", stamp(), reportFile));
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateMetadataMaps(results, record, runDate, shortCommit)
|
||||||
|
if ~isempty(record) && strlength(record.TestFile) > 0
|
||||||
|
key = char(record.TestFile);
|
||||||
|
app.LastRunDateMap(key) = char(string(runDate));
|
||||||
|
app.LastRunCommitMap(key) = char(string(shortCommit));
|
||||||
|
applyStoredRunState();
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if ~isempty(app.ActiveProfileRowIndices)
|
||||||
|
for rowIdx = app.ActiveProfileRowIndices(:)'
|
||||||
|
key = char(app.Inventory.TestFile(rowIdx));
|
||||||
|
if isempty(key)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
app.LastRunDateMap(key) = char(string(runDate));
|
||||||
|
app.LastRunCommitMap(key) = char(string(shortCommit));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
applyStoredRunState();
|
||||||
|
end
|
||||||
|
|
||||||
|
function rowIndices = profileRowIndices(profile)
|
||||||
|
rowIndices = [];
|
||||||
|
normalizedTags = "," + erase(app.Inventory.Tags, " ") + ",";
|
||||||
|
switch string(profile)
|
||||||
|
case "fast"
|
||||||
|
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||||
|
contains(normalizedTags, ",fast,") & ...
|
||||||
|
~contains(normalizedTags, ",placeholder,") & ...
|
||||||
|
~contains(normalizedTags, ",hardware,") & ...
|
||||||
|
~contains(normalizedTags, ",slow,"));
|
||||||
|
case "full"
|
||||||
|
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||||
|
~contains(normalizedTags, ",placeholder,") & ...
|
||||||
|
~contains(normalizedTags, ",hardware,"));
|
||||||
|
case "integration"
|
||||||
|
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||||
|
contains(normalizedTags, ",integration,") & ...
|
||||||
|
~contains(normalizedTags, ",placeholder,") & ...
|
||||||
|
~contains(normalizedTags, ",hardware,"));
|
||||||
|
case "placeholder"
|
||||||
|
rowIndices = find(contains(normalizedTags, ",placeholder,"));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function textOut = stamp()
|
||||||
|
textOut = char(datetime("now", "Format", "yyyy-MM-dd HH:mm:ss"));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function color = metricColor(hasRuns, activeColor)
|
||||||
|
if hasRuns
|
||||||
|
color = activeColor;
|
||||||
|
else
|
||||||
|
color = [0.48, 0.52, 0.58];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory = buildInventory(repoRoot, testsRoot)
|
||||||
|
repoRoot = char(string(repoRoot));
|
||||||
|
testsRoot = char(string(testsRoot));
|
||||||
|
classesRoot = fullfile(repoRoot, 'Classes');
|
||||||
|
classFiles = collectMFiles(classesRoot);
|
||||||
|
|
||||||
|
records = struct( ...
|
||||||
|
"Status", {}, ...
|
||||||
|
"LastRun", {}, ...
|
||||||
|
"ResultSummary", {}, ...
|
||||||
|
"LastRunDate", {}, ...
|
||||||
|
"LastRunCommit", {}, ...
|
||||||
|
"Scope", {}, ...
|
||||||
|
"Target", {}, ...
|
||||||
|
"TargetFile", {}, ...
|
||||||
|
"TestFile", {}, ...
|
||||||
|
"TestFileName", {}, ...
|
||||||
|
"Tags", {});
|
||||||
|
|
||||||
|
matchedTestFiles = strings(0, 1);
|
||||||
|
|
||||||
|
for idx = 1:numel(classFiles)
|
||||||
|
classFile = fullfile(classFiles(idx).folder, classFiles(idx).name);
|
||||||
|
if ~isClassFile(classFile)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
relativePath = strrep(classFile, [classesRoot, filesep], "");
|
||||||
|
[relativeDir, className] = fileparts(relativePath);
|
||||||
|
relativeDirText = char(join(string(relativeDir), ""));
|
||||||
|
classNameText = char(string(className));
|
||||||
|
testFile = findMatchingTestFile(testsRoot, relativeDirText, classNameText);
|
||||||
|
|
||||||
|
if strlength(testFile) == 0
|
||||||
|
status = "Missing";
|
||||||
|
tags = "";
|
||||||
|
testFileName = "";
|
||||||
|
else
|
||||||
|
source = fileread(testFile);
|
||||||
|
if contains(source, "assumeFail(")
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
matchedTestFiles(end + 1, 1) = testFile; %#ok<AGROW>
|
||||||
|
status = "Implemented";
|
||||||
|
tags = strjoin(extractTestTags(source), ", ");
|
||||||
|
[~, testFileName] = fileparts(testFile);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
targetLabel = fullfile('Classes', relativeDirText, [classNameText, '.m']);
|
||||||
|
targetLabel = strrep(targetLabel, "\", "/");
|
||||||
|
|
||||||
|
records(end + 1) = struct( ... %#ok<AGROW>
|
||||||
|
"Status", status, ...
|
||||||
|
"LastRun", "", ...
|
||||||
|
"ResultSummary", "", ...
|
||||||
|
"LastRunDate", "", ...
|
||||||
|
"LastRunCommit", "", ...
|
||||||
|
"Scope", "Class", ...
|
||||||
|
"Target", string(targetLabel), ...
|
||||||
|
"TargetFile", string(classFile), ...
|
||||||
|
"TestFile", string(testFile), ...
|
||||||
|
"TestFileName", string(testFileName), ...
|
||||||
|
"Tags", string(tags));
|
||||||
|
end
|
||||||
|
|
||||||
|
allTestFiles = dir(fullfile(testsRoot, "**", "*_test.m"));
|
||||||
|
for idx = 1:numel(allTestFiles)
|
||||||
|
testFile = fullfile(allTestFiles(idx).folder, allTestFiles(idx).name);
|
||||||
|
if any(strcmpi(testFile, matchedTestFiles))
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
source = fileread(testFile);
|
||||||
|
if contains(source, "assumeFail(")
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
status = "Implemented";
|
||||||
|
end
|
||||||
|
|
||||||
|
[~, testFileName] = fileparts(testFile);
|
||||||
|
relativeTest = strrep(testFile, [testsRoot, filesep], "");
|
||||||
|
relativeTest = strrep(relativeTest, "\", "/");
|
||||||
|
tags = strjoin(extractTestTags(source), ", ");
|
||||||
|
|
||||||
|
records(end + 1) = struct( ... %#ok<AGROW>
|
||||||
|
"Status", status, ...
|
||||||
|
"LastRun", "", ...
|
||||||
|
"ResultSummary", "", ...
|
||||||
|
"LastRunDate", "", ...
|
||||||
|
"LastRunCommit", "", ...
|
||||||
|
"Scope", "Standalone", ...
|
||||||
|
"Target", string(relativeTest), ...
|
||||||
|
"TargetFile", "", ...
|
||||||
|
"TestFile", string(testFile), ...
|
||||||
|
"TestFileName", string(testFileName), ...
|
||||||
|
"Tags", string(tags));
|
||||||
|
end
|
||||||
|
|
||||||
|
inventory = struct2table(records);
|
||||||
|
if isempty(inventory)
|
||||||
|
inventory = table(strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||||
|
strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||||
|
strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||||
|
"VariableNames", ["Status", "LastRun", "ResultSummary", "LastRunDate", ...
|
||||||
|
"LastRunCommit", "Scope", "Target", "TargetFile", "TestFile", ...
|
||||||
|
"TestFileName", "Tags"]);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
inventory = inventory(inventory.Status ~= "Placeholder", :);
|
||||||
|
|
||||||
|
statusOrder = containers.Map( ...
|
||||||
|
{'Implemented', 'Missing', 'Placeholder'}, ...
|
||||||
|
{1, 2, 3});
|
||||||
|
scopeOrder = containers.Map({'Class', 'Standalone'}, {1, 2});
|
||||||
|
|
||||||
|
statusRank = zeros(height(inventory), 1);
|
||||||
|
scopeRank = zeros(height(inventory), 1);
|
||||||
|
for idx = 1:height(inventory)
|
||||||
|
statusRank(idx) = statusOrder(char(inventory.Status(idx)));
|
||||||
|
scopeRank(idx) = scopeOrder(char(inventory.Scope(idx)));
|
||||||
|
end
|
||||||
|
|
||||||
|
inventory.statusRank = statusRank;
|
||||||
|
inventory.scopeRank = scopeRank;
|
||||||
|
inventory = sortrows(inventory, {'scopeRank', 'statusRank', 'Target'});
|
||||||
|
inventory = removevars(inventory, {'statusRank', 'scopeRank'});
|
||||||
|
end
|
||||||
|
|
||||||
|
function testFile = findMatchingTestFile(testsRoot, relativeDir, className)
|
||||||
|
testsRoot = char(string(testsRoot));
|
||||||
|
relativeDir = char(join(string(relativeDir), ""));
|
||||||
|
className = char(string(className));
|
||||||
|
testDir = fullfile(testsRoot, relativeDir);
|
||||||
|
testFile = "";
|
||||||
|
if ~isfolder(testDir)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
exactFile = fullfile(testDir, [className, '_test.m']);
|
||||||
|
if isfile(exactFile)
|
||||||
|
testFile = string(exactFile);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
candidates = dir(fullfile(testDir, '*_test.m'));
|
||||||
|
if isempty(candidates)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
candidateNames = string({candidates.name});
|
||||||
|
suffix = ['_', className, '_test.m'];
|
||||||
|
matchMask = endsWith(candidateNames, suffix, "IgnoreCase", true);
|
||||||
|
|
||||||
|
if any(matchMask)
|
||||||
|
firstMatch = candidates(find(matchMask, 1));
|
||||||
|
testFile = string(fullfile(firstMatch.folder, firstMatch.name));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tags = extractTestTags(source)
|
||||||
|
tokens = regexp(source, "TestTags\s*=\s*\{([^}]*)\}", "tokens", "once");
|
||||||
|
if isempty(tokens)
|
||||||
|
tags = strings(0, 1);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tags = regexp(tokens{1}, "'([^']*)'", "tokens");
|
||||||
|
tags = string(cellfun(@(entry) entry{1}, tags, "UniformOutput", false));
|
||||||
|
end
|
||||||
|
|
||||||
|
function tf = isClassFile(filePath)
|
||||||
|
source = fileread(filePath);
|
||||||
|
tf = contains(source, "classdef");
|
||||||
|
end
|
||||||
|
|
||||||
|
function files = collectMFiles(rootFolder)
|
||||||
|
pathString = genpath(rootFolder);
|
||||||
|
folders = regexp(pathString, pathsep, "split");
|
||||||
|
folders = folders(~cellfun("isempty", folders));
|
||||||
|
|
||||||
|
files = dir(fullfile(rootFolder, "*.m"));
|
||||||
|
files = files([]); %#ok<NASGU>
|
||||||
|
files = dir(fullfile(rootFolder, "*.m"));
|
||||||
|
files = files([]);
|
||||||
|
|
||||||
|
for idx = 1:numel(folders)
|
||||||
|
currentFiles = dir(fullfile(folders{idx}, "*.m"));
|
||||||
|
if isempty(currentFiles)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
currentFiles = currentFiles(~[currentFiles.isdir]);
|
||||||
|
files = [files; currentFiles]; %#ok<AGROW>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function [statusText, summaryText] = summarizeResults(results)
|
||||||
|
passedCount = nnz([results.Passed]);
|
||||||
|
failedCount = nnz([results.Failed]);
|
||||||
|
incompleteCount = nnz([results.Incomplete]);
|
||||||
|
|
||||||
|
if failedCount > 0
|
||||||
|
statusText = "Failed";
|
||||||
|
elseif incompleteCount > 0
|
||||||
|
statusText = "Incomplete";
|
||||||
|
else
|
||||||
|
statusText = "Passed";
|
||||||
|
end
|
||||||
|
|
||||||
|
summaryText = sprintf('%d/%d/%d', passedCount, failedCount, incompleteCount);
|
||||||
|
end
|
||||||
|
|
||||||
|
function out = stringOrEmpty(structValue, fieldName)
|
||||||
|
if isfield(structValue, fieldName)
|
||||||
|
out = string(structValue.(fieldName));
|
||||||
|
else
|
||||||
|
out = "";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function rows = inventoryRowStates(inventory)
|
||||||
|
rows = repmat(struct( ...
|
||||||
|
'test_file', '', ...
|
||||||
|
'target', '', ...
|
||||||
|
'scope', '', ...
|
||||||
|
'last_run', '', ...
|
||||||
|
'result_summary', '', ...
|
||||||
|
'last_run_date', '', ...
|
||||||
|
'last_run_commit', ''), height(inventory), 1);
|
||||||
|
|
||||||
|
for idx = 1:height(inventory)
|
||||||
|
rows(idx).test_file = char(inventory.TestFile(idx));
|
||||||
|
rows(idx).target = char(inventory.Target(idx));
|
||||||
|
rows(idx).scope = char(inventory.Scope(idx));
|
||||||
|
rows(idx).last_run = char(inventory.LastRun(idx));
|
||||||
|
rows(idx).result_summary = char(inventory.ResultSummary(idx));
|
||||||
|
rows(idx).last_run_date = char(inventory.LastRunDate(idx));
|
||||||
|
rows(idx).last_run_commit = char(inventory.LastRunCommit(idx));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function entries = resultEntries(results)
|
||||||
|
entries = repmat(struct( ...
|
||||||
|
'name', '', ...
|
||||||
|
'passed', false, ...
|
||||||
|
'failed', false, ...
|
||||||
|
'incomplete', false, ...
|
||||||
|
'duration_seconds', 0), numel(results), 1);
|
||||||
|
|
||||||
|
for idx = 1:numel(results)
|
||||||
|
entries(idx).name = char(string(results(idx).Name));
|
||||||
|
entries(idx).passed = results(idx).Passed;
|
||||||
|
entries(idx).failed = results(idx).Failed;
|
||||||
|
entries(idx).incomplete = results(idx).Incomplete;
|
||||||
|
entries(idx).duration_seconds = durationToSeconds(results(idx).Duration);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function gitInfo = collectGitMetadata(repoRoot)
|
||||||
|
gitInfo = struct();
|
||||||
|
gitInfo.commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse HEAD', repoRoot)));
|
||||||
|
gitInfo.short_commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse --short HEAD', repoRoot)));
|
||||||
|
gitInfo.branch = strtrim(runCommand(sprintf('git -C "%s" rev-parse --abbrev-ref HEAD', repoRoot)));
|
||||||
|
statusOutput = runCommand(sprintf('git -C "%s" status --porcelain', repoRoot));
|
||||||
|
gitInfo.dirty = ~isempty(strtrim(statusOutput));
|
||||||
|
end
|
||||||
|
|
||||||
|
function output = runCommand(commandText)
|
||||||
|
[status, output] = system(commandText);
|
||||||
|
if status ~= 0
|
||||||
|
output = '';
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function writeTextFile(filePath, content)
|
||||||
|
fid = fopen(filePath, 'w');
|
||||||
|
assert(fid ~= -1, 'Could not write file: %s', filePath);
|
||||||
|
cleaner = onCleanup(@() fclose(fid)); %#ok<NASGU>
|
||||||
|
fprintf(fid, '%s', content);
|
||||||
|
end
|
||||||
|
|
||||||
|
function label = sanitizeFileLabel(labelText)
|
||||||
|
label = regexprep(char(string(labelText)), '[^A-Za-z0-9]+', '_');
|
||||||
|
label = regexprep(label, '^_+|_+$', '');
|
||||||
|
if isempty(label)
|
||||||
|
label = 'run';
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function value = durationToSeconds(durationValue)
|
||||||
|
try
|
||||||
|
value = seconds(durationValue);
|
||||||
|
if isa(value, 'duration')
|
||||||
|
value = str2double(char(value));
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
try
|
||||||
|
value = posixtime(datetime(durationValue) - datetime(0, 0, 0));
|
||||||
|
catch
|
||||||
|
durationText = char(string(durationValue));
|
||||||
|
tokens = regexp(durationText, '(\d+):(\d+):([\d\.]+)', 'tokens', 'once');
|
||||||
|
if isempty(tokens)
|
||||||
|
value = NaN;
|
||||||
|
else
|
||||||
|
value = str2double(tokens{1}) * 3600 + ...
|
||||||
|
str2double(tokens{2}) * 60 + ...
|
||||||
|
str2double(tokens{3});
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ~isnumeric(value) || ~isscalar(value)
|
||||||
|
value = NaN;
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user