% SPIDER_simulation.m
% Spectral phase interferometry for direct electric-field reconstruction
%
% This script simulates SPIDER acquisition and reconstruction.
% A pulse with chirp according to the dispersion in BK7 is generated and a SPIDER
% interferogram as recorded by a spectrometer (i.e. with noise and quantisation) is
% simulated. The interferogram is then inverted using the SPIDER reconstruction
% algorithm. The reconstructed & simulated pulses are then compared.
%% Some useful functions / constants
c = 299.792458; % Speed of light (nm/fs)
chng_rng = @(x) 2*pi*c./x; % Change nm <--> rad/fs
% Gaussian function:
% x = Co-ordinate
% x0 = Centre
% dx = FWHM
% = sqrt(2*log(2))*sigma_x, where sigma_x is 1/e^2 half width
% n = Order (normal = 1)
gauss1D = @(x, x0, dx, n) exp(-log(2)*(2*(x-x0)/dx).^(2*abs(floor(n))));
%% Data ranges
Nw = 1024; % Number of freq/wavelength data points
% Spectrometer range
% Note:
% The data acquired in an experiment is taken with a spectrometer
% which is approximately linear in wavelength, thus all calculations
% will be used with a linear wavelength grid (lambda) and it's
% corresponding nonlinear angular frequency grid (omega). Later on, I
% shall create a linear angular frequency grid (w). From now on, I
% refer to angular frequency as just frequency (or freq.)
% Wavelength
lambda_min = 650;
lambda_max = 950;
dlambda = (lambda_max-lambda_min)/(Nw-1);
lambda = (0:Nw-1)'*dlambda + lambda_min;
% Angular frequency
omega = chng_rng(lambda);
omega_max = max(omega);
omega_min = min(omega);
domega = (omega_max-omega_min)/(Nw-1);
%% Dispersion of BK7
% Sellmeier co-efficients (from Wiki)
B = [1.03961212, 2.31792344e-1, 1.01046945];
C = [6.00069867e-3, 2.00179144e-2, 1.03560653e2];
% Refractive index
% NOTE: lambda is assumed to be in micrometers!!
n = @(lambda) sqrt(1 + sum(((lambda.^2)*B) ./ ...
((lambda.^2)*ones(1,length(C)) - ones(Nw,1)*C), 2));
% Phase accumulated (rad)
% NOTE: lambda & z are assumed to be in nm
phi_z = @(lambda, z) n(lambda*1e-3).*(2*pi./lambda)*z;
%% Generate pulse
Dt = 30; % Fourier transform limited (FTL) FWHM intensity pulse duration (fs)
Dw = 4*log(2)/Dt; % FWHM intensity bandwidth (rad/fs) for FTL duration Dt
lambda_0 = 800; % Central wavelength (nm)
w0 = chng_rng(lambda_0); % Central frequency (rad/fs)
% Notes:
% The delay cannot be found, only relative to some other pulse,
% therefore the absolute delay needs to be removed. Zero delay is at
% the entrance of our imaginary glass (BK7), so the delay at w0 is
% removed (equal to the gradient of the phase at w0). The absolute
% phase is also removed. Technically, this does not need to be done
% as SPIDER (or any other self-referencing technique) cannot measure
% these quantities, and so this is only for a comparison between the
% reconstructed and 'actual' phase.
%
% The full width half max (FWHM) values are quoted for the intensity
% (in boh the time and (angular) frequency domains, so this value
% needs to be scaled fo the electric field (i.e. by sqrt(2) for
% Gaussian pulses). For non-Gaussian pulses, the sqrt of the
% intensity can be taken.
phi = phi_z(lambda, 2e7); % Phase accumulated from 2cm BK7
t0 = (spline(omega, phi, w0+w0/1e3) ...
- spline(omega, phi, w0-w0/1e3))/(w0/.5e3); % Delay from BK7
phi = phi-omega*t0; % Remove delay
phi = phi - spline(omega, phi, w0); % Remove absolute phase
Ew = gauss1D(omega, w0, sqrt(2)*Dw, 1).*exp(i*phi); % Pulse in freq domain
%% SPIDER Parameters
delay = 500;
shear = 2*pi/(30*Dt); % Maximum pulse window is 10*Dt
% Note:
% In a real experiment, the data will contain noise (additive,
% multiplicative & quantisation). The data should always be taken
% with a small background, which can be removed via software
% processing. Hardware removal might remove real data which cannot be
% recovered. I assume noise is noramally distributed.
alpha_a = .01; % Addative noise
alpha_m = .01; % Multiplicative noise
alpha_q = 16; % Bit depth (quantisation error)
err_delay = 0e-3; % Pulse-pulse fluctuations in the delay
% Normally, the trace is acquired over many laser shots, with a
% fluctuating delay between the pulses, thus an average over many pulses
% is acquired.
N_acq = 1; % Number of pulses to integrate over
Iw = zeros(Nw, N_acq);
Iw_cal = Iw;
Ew1 = spline(omega, Ew, omega-shear).*(omega>=shear);
Iw_true = abs(Ew + Ew1.*exp(i*omega*delay)).^2;
Iwc_true = abs(Ew.*(1+exp(i*omega*delay))).^2;
for n=1:N_acq
% SPIDER Interferogram
Iw(:,n) = abs(Ew + Ew1.*exp(i*omega*delay*(1+randn(1)*err_delay))).^2;
% Calibration Interferogram
Iw_cal(:,n) = abs(Ew.*(1+exp(i*omega*delay*(1+randn(1)*err_delay)))).^2;
end
% Add noise & quantise
Iw_n = sum(Iw, 2); % Integrate
Iw_n = (Iw_n + randn(Nw, 1)*alpha_a*max(Iw_n)); % Additive noise
Iw_n = Iw_n.*(1 + randn(Nw, 1)*alpha_m); % Multiplicative noise
Iw_n = Iw_n/max(Iw_n); % Normalise
Iw_n = floor((2^(alpha_q-1)-1)*Iw_n); % Quantisation
Iwc_n = sum(Iw_cal, 2); % Integrate
Iwc_n = Iwc_n + randn(Nw, 1)*alpha_a*max(Iwc_n); % Additive noise
Iwc_n = Iwc_n.*(1 + randn(Nw, 1)*alpha_m); % Multiplicative noise
Iwc_n = Iwc_n/max(Iwc_n); % Normalise
Iwc_n = floor((2^(alpha_q-1)-1)*Iwc_n); % Quantisation
%% SPIDER Reconstruction
% Note:
% For large bandwidths, the fringes in the calibration trace will not
% appear to have the same period due to nonlinear frequency grid.
% Thus, one should interpolate the data onto a linear frequency grid
% 1st (or perform a non-linear FFT routine), otherwise it may not be
% possible to isolate the sidebands.
omega_lin = (0:Nw-1)'*domega + omega_min; % Generate linear freq grid
pxls = (1:Nw)'; % pxl grid (for filter)
% Fourier transform data.
% Note:
% It doesn't matter which Fourier transform you perform (forward or
% backward). The only difference is the phase will have opposite
% signs, so it may be necessary to flip the sign of the extracted
% phase before integration/concatenation. Note that this is a
% deterministic effect (do the maths with the correct Fourier
% transform, shear and sidelobe.)
It = fftshift(fft(Iw_n));
Itc = fftshift(fft(Iwc_n));
It_lin = fftshift(fft(spline(omega, Iw_n, omega_lin)));
Itc_lin = fftshift(fft(spline(omega, Iwc_n, omega_lin)));
% Filter
% Note:
% Both sidelobed contain the exact amount of data, thus it does not
% matter which one you filter. However, as they are complex
% conjugates, the sign of the phase is opposite for each. Thus, if
% you find the reconstructed phase is inverted, simply flip the
% sign of the phase before integration/concatenation. Note that this is a
% deterministic effect (do the maths with the correct Fourier
% transform, shear and sidelobe.)
ft = gauss1D(pxls, 583, 40, 4);
Iwf = ifft(ifftshift(It.*ft));
Iwcf = ifft(ifftshift(Itc.*ft));
Iwf_lin = ifft(ifftshift(It_lin.*ft));
Iwcf_lin = ifft(ifftshift(Itc_lin.*ft));
% Extract phase & remove calibration
% Note:
% Because omega = 2*pi/lambda = (0:-1:-(Nw-1))'*dw +wmax, the phase
% for the linearly interpolated data is inverted with respect to the
% other.
%
% If the shear is small enough to allow the pulse to be sampled
% adequately, then the phase extracted should not go outside +/-pi,
% therefore this is a good check of the routine. This also means
% unwrapping is unecessary, although it could be used if one needed.
theta = unwrap(angle(Iwf.*conj(Iwcf)));
theta_lin = -unwrap(angle(Iwf_lin.*conj(Iwcf_lin)));
% New frequency ranges - this is beacuse the reconstructed phase falls on
% the frequency grid between the two sheared pulses.
omega_s = omega-shear/2;
omega_s_lin = omega_lin-shear/2;
% Integrate phase via trapezium (mid-point) rule
phi_i = cumtrapz(omega, (theta-spline(omega_s, theta, w0))/shear); % Integrate (removing linear slope)
phi_i = phi_i - spline(omega_s, phi_i, w0); % Remove absolute phase
phi_i_lin = cumtrapz(omega_s_lin, (theta_lin-spline(omega_s_lin, theta_lin, w0))/shear); % Integrate (removing slope)
phi_i_lin = phi_i_lin - spline(omega_s_lin, phi_i_lin, w0); % Remove absolute phase
% Concatenate phase
% Note:
% The gradient is taken half-way between the two sheared pulses (i.e.
% at omega-shear/2 = omega_s;
omega_c = (omega_min:abs(shear):omega_max)';
phi_c = cumsum(spline(omega, theta, omega_c)...
-spline(omega_s, theta, w0)); % Concatenate, removing linear phase
phi_c = phi_c - spline(omega_c, phi_c, w0); % Remove absolute phase
phi_c_lin = cumsum(spline(omega_lin, theta_lin, omega_c)...
-spline(omega_s_lin, theta_lin, w0)); % Concatenate, removing linear phase
phi_c_lin = phi_c_lin - spline(omega_c, phi_c_lin, w0); % Remove absolute phase
% plot(omega, phi, '.', omega_c, [phi_c phi_c_lin], omega_s, phi_i, omega_s_lin, phi_i_lin);
%% Obtain spectral intensity
% Assuming an accurate, independent spectral measurement can be obtained
Ew_sp = abs(Ew)/max(abs(Ew));
% Obtain |Ew| = sqrt(Iw) from the dc term in the interferogram, allowing
% online retrieval. Note that this only works when the shear is small,
% and the spectrum is relatively smooth.
ft0 = gauss1D(pxls, 513, 40, 4);
Iwf0 = ifft(ifftshift(It.*ft0));
Iwf0_lin = ifft(ifftshift(It_lin.*ft0));
Ew_int = sqrt(abs(Iwf0)/max(abs(Iwf0)));
Ew_int_lin = sqrt(abs(Iwf0_lin)/max(abs(Iwf0_lin)));
%% Temporal info
% Linear range in frequency
dw = shear;
w = (0:Nw-1)'*dw;
Nt = 4096;
dt = 2*pi/(Nt*dw);
t = ((0:Nt-1)'-floor(Nt/2))*dt;
% Interpolate data onto linear freq grid
% Naming convention:
% sp = Used actual spectrum
% int = Used spectrum from interferogram
% i = Used integrated phase
% c = Used concatenated phase
% lin = Used interferogram which was interpolated on linear grid
fw = (w>=omega_min).*(w<=omega_max);
Ew_true = spline(omega, Ew, w).*fw;
Ew_sp_ = spline(omega, Ew_sp, w).*fw;
Ew_int_ = spline(omega_s, Ew_int, w).*fw;
Ew_int_lin_ = spline(omega_s_lin, Ew_int_lin, w).*fw;
phi_i_ = spline(omega_s, phi_i, w).*fw;
phi_c_ = spline(omega_c, phi_c, w).*fw;
phi_i_lin_ = spline(omega_s_lin, phi_i_lin, w).*fw;
phi_c_lin_ = spline(omega_c, phi_c_lin, w).*fw;
Ew_sp_i = Ew_sp_.*exp(i*phi_i_);
Ew_sp_c = Ew_sp_.*exp(i*phi_c_);
Ew_int_i = Ew_int_.*exp(i*phi_i_);
Ew_int_c = Ew_int_.*exp(i*phi_c_);
Ew_sp_i_lin = Ew_sp_.*exp(i*phi_i_lin_);
Ew_sp_c_lin = Ew_sp_.*exp(i*phi_c_lin_);
Ew_int_i_lin = Ew_int_.*exp(i*phi_i_lin_);
Ew_int_c_lin = Ew_int_.*exp(i*phi_c_lin_);
% Calculate temporal profile
% The phase term is to shift the carrier frequency to the true carrier
% frequency. Padding is used to obtain a higher temporal resolution
Et_true = fftshift(fft(Ew_true, Nt));%.*exp(-i*min(w)*t);
Et_sp_i = fftshift(fft(Ew_sp_i, Nt));%.*exp(-i*min(w)*t);
Et_sp_c = fftshift(fft(Ew_sp_c, Nt));%.*exp(-i*min(w)*t);
Et_int_i = fftshift(fft(Ew_int_i, Nt));%.*exp(-i*min(w)*t);
Et_int_c = fftshift(fft(Ew_int_c, Nt));%.*exp(-i*min(w)*t);
Et_sp_i_lin = fftshift(fft(Ew_sp_i_lin, Nt));%.*exp(-i*min(w)*t);
Et_sp_c_lin = fftshift(fft(Ew_sp_c_lin, Nt));%.*exp(-i*min(w)*t);
Et_int_i_lin = fftshift(fft(Ew_int_i_lin, Nt));%.*exp(-i*min(w)*t);
Et_int_c_lin = fftshift(fft(Ew_int_c_lin, Nt));%.*exp(-i*min(w)*t);
%% Plot results
figure(1)
plot(lambda, [Iw_true/4 Iw_n/max(Iw_n)], lambda, .25*abs([Ew Ew1]).^2, '.-')
title('SPIDER interferograms and original sheared spectra');
xlabel('Wavelength /nm');
ylabel('Intensity /arb.u.')
legend('Perfect interferogram', 'Measured interferogram', 'E(\omega)', 'E(\omega+\Omega)');
figure(2)
plot(t, real(Et_true), '.-', t, real([Et_sp_i Et_sp_c]));
title('True and reconstructed temporal field: Independent spectrum');
xlabel('Time /fs');
ylabel('Electric field /arb.u.');
legend('True', 'Integrated', 'Concatenated');
figure(3)
plot(t, real(Et_true), '.-', t, real([Et_sp_i_lin Et_sp_c_lin]));
title('True and reconstructed temporal field: Independent spectrum (linear interpolation)');
xlabel('Time /fs');
ylabel('Electric field /arb.u.');
legend('True', 'Integrated', 'Concatenated');
figure(4)
plot(t, real(Et_true), '.-', t, real([Et_int_i Et_int_c]));
title('True and reconstructed temporal field: Extracted spectrum');
xlabel('Time /fs');
ylabel('Electric field /arb.u.');
legend('True', 'Integrated', 'Concatenated');
figure(5)
plot(t, real(Et_true), '.-', t, real([Et_int_i_lin Et_int_c_lin]));
title('True and reconstructed temporal field: Extracted spectrum (linear interpolation)');
xlabel('Time /fs');
ylabel('Electric field /arb.u.');
legend('True', 'Integrated', 'Concatenated');