241 lines
10 KiB
Matlab
241 lines
10 KiB
Matlab
classdef DP_Fiber
|
|
% Dual-Polarization fiber propagation (CNLSE / Manakov) — class version
|
|
% Runs full setup + propagation inside process_ (no external loop).
|
|
|
|
properties (Access=public)
|
|
% ---- User options (public) ----
|
|
L % [km] fiber length
|
|
dz % [m] step size target (kept for compatibility; adaptive dz uses SS_* below)
|
|
lambda % [nm] reference wavelength
|
|
rng % RNG seed
|
|
gamma % [1/W/m] nonlinear coefficient
|
|
|
|
% Optional / advanced options (match legacy names where possible)
|
|
fa % [Hz] sampling frequency
|
|
X_alpha % [dB/100km] attenuation per 100 km (per-pol, used for X and Y)
|
|
X_beta % [1..4] dispersion coefficients vector for X (Y mirrors X)
|
|
D % [ps/(nm*km)]
|
|
Ds % [ps/(nm^2*km)] dispersion slope
|
|
Dpmd % [ps/sqrt(km)] PMD coefficient
|
|
beat_len % [m] beat length (for beta(1) if X_beta is zero)
|
|
corr_len % [m] correlation length (not directly used; kept for compatibility)
|
|
manakov % 1=Manakov (legacy behavior: manakov=eq-1)
|
|
SS_dphimax % [rad] max nonlinear phase per step (adaptive SSFM)
|
|
SS_dzmax % [m] max dz (adaptive SSFM)
|
|
SS_dzmin % [m] min dz (adaptive SSFM)
|
|
n_waveplates % number of PMD waveplates
|
|
|
|
% ---- Internal state (persistent between calls) ----
|
|
state % struct mirroring legacy 'state'
|
|
end
|
|
|
|
methods (Access=public)
|
|
function obj = DP_Fiber(options)
|
|
% Constructor — copies fields from 'options' and sets defaults.
|
|
|
|
arguments
|
|
options.L
|
|
options.dz
|
|
options.lambda
|
|
options.rng = 0
|
|
options.gamma
|
|
|
|
% optional but recommended
|
|
options.fa
|
|
|
|
% legacy-compatible optional params
|
|
options.X_alpha = 4.605170185988092e-05 % dB/100km
|
|
options.X_beta = [0,0,-2.16826193914149e-26,3.56839456298263e-41]
|
|
options.D = 17 % ps/(nm*km)
|
|
options.Ds = 0.06 % ps/(nm^2*km)
|
|
options.Dpmd = 3 % ps/sqrt(km)
|
|
options.beat_len = 50 % m
|
|
options.corr_len = 50 % m (kept)
|
|
options.manakov = 0 % 1=Manakov
|
|
options.SS_dphimax = 5e-3 % rad
|
|
options.SS_dzmax = 2e4 % m
|
|
options.SS_dzmin = 100 % m
|
|
options.n_waveplates = 100
|
|
end
|
|
|
|
% Copy provided options into properties
|
|
fn = fieldnames(options);
|
|
for n = 1:numel(fn)
|
|
obj.(fn{n}) = options.(fn{n});
|
|
end
|
|
|
|
% Initialize empty state; will be built on first process_ call
|
|
obj.state = struct();
|
|
end
|
|
|
|
function signalclass_out = process(obj, signalclass_in)
|
|
% Public entry point: takes a signal class with .signal (2xN)
|
|
% and writes back the propagated signal.
|
|
|
|
signalclass_in.signal = obj.process_(signalclass_in.signal, signalclass_in.fs);
|
|
|
|
% logbook (kept as in new framework skeleton)
|
|
lbdesc = 'DP_Fiber propagation (CNLSE_plain)';
|
|
if ismethod(signalclass_in, 'logbookentry')
|
|
signalclass_in = signalclass_in.logbookentry(lbdesc);
|
|
end
|
|
|
|
signalclass_out = signalclass_in;
|
|
end
|
|
|
|
function signal_out = process_(obj, signal_in,fs)
|
|
% Core processing — builds legacy 'state' and calls CNLSE_plain
|
|
% data_in: [2 x N] complex, dual-pol envelope
|
|
arguments (Input)
|
|
obj
|
|
signal_in
|
|
fs
|
|
end
|
|
|
|
% ---- Basic checks
|
|
if isempty(signal_in) || size(signal_in,2) ~= 2
|
|
error('DP_Fiber:Input','Expected data_in of size [2 x N].');
|
|
end
|
|
if isempty(fs)
|
|
error('DP_Fiber:Config','Sampling frequency options.fa is required.');
|
|
end
|
|
|
|
obj.fa = fs;
|
|
|
|
% ---- RNG (legacy behavior)
|
|
R = RandStream("twister","Seed",obj.rng);
|
|
|
|
% ---- (Re)build state if empty or size-dependent fields changed
|
|
need_rebuild = ~isfield(obj.state,'nt') || (obj.state.nt ~= size(signal_in,2));
|
|
|
|
if need_rebuild
|
|
% Constants
|
|
c0 = 299792458; % [m/s]
|
|
|
|
% Legacy state mapping
|
|
st = struct();
|
|
|
|
% High-level
|
|
st.L = obj.L * 1000; % [m] legacy expects meters
|
|
st.polNames = {'X','Y'};
|
|
st.dt = 1/obj.fa;
|
|
st.nt = max(size(signal_in));
|
|
st.omega = 2*pi*[(0:st.nt/2-1),(-st.nt/2:-1)]/(st.dt*st.nt);
|
|
st.lambda = obj.lambda * 1e-9; % [m]
|
|
st.D = obj.D * 1e-6; % ps/(nm*km) -> s/(nm*m)
|
|
st.Dpmd = obj.Dpmd * 1e-6; % ps/sqrt(km) -> s/sqrt(km)
|
|
st.Ds = obj.Ds * 1e3; % ps/(nm^2*km) -> s/(nm^2*m)
|
|
st.beat_len = obj.beat_len;
|
|
st.SS_dzmax = obj.SS_dzmax;
|
|
st.SS_dzmin = obj.SS_dzmin;
|
|
st.SS_dphimax= obj.SS_dphimax;
|
|
|
|
st.chi = 0; % legacy placeholders
|
|
st.psi = 0;
|
|
|
|
st.manakov = obj.manakov; % 1 if eq==2 (Manakov), 0 if eq==1 (CNLSE)
|
|
st.wave_plates = obj.n_waveplates;
|
|
|
|
% Alpha (same X/Y)
|
|
st.alpha.X = obj.X_alpha;
|
|
st.alpha_lin.X = st.alpha.X/10*log(10)/1000;
|
|
st.alpha.Y = st.alpha.X;
|
|
st.alpha_lin.Y = st.alpha_lin.X;
|
|
|
|
% Beta (dispersion) for X (Y mirrors X)
|
|
if ~any(obj.X_beta)
|
|
% Populate from (beat_len, D, Ds, lambda) like legacy
|
|
if obj.beat_len ~= 0
|
|
b1 = pi/obj.beat_len;
|
|
else
|
|
b1 = 0;
|
|
end
|
|
b2 = 0; % PMD freq term handled elsewhere in legacy
|
|
b3 = -(st.lambda.^2/(2*pi*c0))*st.D;
|
|
b4 = (st.lambda^2/(2*pi*c0))^2*st.Ds + (2/st.lambda)*(st.lambda.^2/(2*pi*c0)).^2*st.D;
|
|
|
|
st.beta.X = [b1, b2, b3, b4]; % keep 4 terms, legacy had 0 for beta0; b2 unused
|
|
% Note: legacy stored [b0,b1,b2,b3]? Here we mirror their usage.
|
|
% We follow their X_beta layout length=4.
|
|
else
|
|
st.beta.X = obj.X_beta;
|
|
end
|
|
st.beta.Y = st.beta.X;
|
|
|
|
% Gamma
|
|
st.gamma = obj.gamma;
|
|
|
|
% PMD / birefringence (legacy waveplate model)
|
|
st.corr_length = st.L / st.wave_plates;
|
|
|
|
% DGD formula (Agrawal 1.1.18)
|
|
st.dgd = st.Dpmd * sqrt(st.L/1000); % Dpmd in s/sqrt(km), L in m -> convert: sqrt(m/1000)
|
|
% For exact legacy match, they used: state.dgd = Dpmd * sqrt(L) wisth L in meters and Dpmd already scaled.
|
|
% Using their pattern:
|
|
st.dgd = obj.Dpmd*1e-6 * sqrt(st.L); % match old: state.Dpmd already 1e-6*ps/sqrt(km); they used sqrt(L) with L [m]
|
|
|
|
brf_multiplier = 1;
|
|
if st.Dpmd == 0
|
|
brf_multiplier = 0;
|
|
end
|
|
|
|
% Waveplate random parameters
|
|
st.pauli_mats.s0 = eye(2);
|
|
st.pauli_mats.s2 = [0 1; 1 0];
|
|
st.pauli_mats.s3i = [0 1; -1 0];
|
|
|
|
st.brf.theta = (R.rand(st.wave_plates,1)*pi - 0.5*pi) * brf_multiplier;
|
|
st.brf.epsilon = 0.5*asin(R.rand(st.wave_plates,1)*2-1) * brf_multiplier;
|
|
|
|
st.brf.stokes = NaN(st.wave_plates,3);
|
|
st.Ttest = zeros(1, st.wave_plates);
|
|
st.brf.matR = cell(st.wave_plates,1);
|
|
|
|
for n=1:st.wave_plates
|
|
matRth = cos(st.brf.theta(n)) * st.pauli_mats.s0 - sin(st.brf.theta(n)) * st.pauli_mats.s3i;
|
|
matRepsilon = complex(cos(st.brf.epsilon(n))*st.pauli_mats.s0, sin(st.brf.epsilon(n))*st.pauli_mats.s2);
|
|
matR = matRth * matRepsilon;
|
|
|
|
st.brf.matR{n} = matR;
|
|
|
|
u1 = matR(1,1);
|
|
u2 = matR(1,2);
|
|
|
|
st.Ttest(n) = abs(u1).^2 + abs(u2).^2;
|
|
st.brf.stokes(n,:) = [abs(u1).^2 - abs(u2).^2 , (u1) * conj(u2) + conj(u1) .* u2 , 1i*(u1) * conj(u2) - conj(u1) .* u2];
|
|
end
|
|
|
|
% Frequency-dependent PMD phase term (legacy form)
|
|
st.brf.db0 = (R.rand(st.wave_plates,1)*2*pi - pi) * brf_multiplier;
|
|
st.brf.db1 = sqrt(3*pi/8)*(st.dgd/obj.fa)/st.wave_plates .* st.omega;
|
|
st.brf.simdgd = 0;
|
|
% cumsum used in legacy only for debug; keep compatibility variable:
|
|
~cumsum(st.brf.db0); % no-op to mirror legacy path
|
|
|
|
% Bookkeeping
|
|
st.missing_dz = 0;
|
|
st.n_plates_done = 0;
|
|
st.test_plates = [];
|
|
st.test_plate_numbers = [];
|
|
st.lin_z_test = 0;
|
|
st.propagated_length = 0;
|
|
|
|
% Cache
|
|
obj.state = st;
|
|
end
|
|
|
|
% ---- Call the exact same CNLSE_plain as in the old framework
|
|
x_in = signal_in(:,1).';
|
|
y_in = signal_in(:,2).';
|
|
|
|
[x_out, y_out, obj.state] = CNLSE_plain(x_in, y_in, obj.state);
|
|
|
|
obj.state.propagated_length = obj.state.propagated_length + obj.state.L;
|
|
|
|
signal_out = [x_out; y_out].';
|
|
|
|
|
|
end
|
|
end
|
|
end
|