% Matlab Init                                              :noexport:ignore:

%% dcm_hac_iff.m
% Development of the HAC-IFF control strategy

%% Clear Workspace and Close figures
clear; close all; clc;

%% Intialize Laplace variable
s = zpk('s');

%% Path for functions, data and scripts
addpath('./mat/'); % Path for data

%% Simscape Model - Nano Hexapod
addpath('./STEPS/')

%% Initialize Parameters for Simscape model
controller.type = 0; % Open Loop Control

%% Options for Linearization
options = linearizeOptions;
options.SampleTime = 0;

%% Open Simulink Model
mdl = 'simscape_dcm';

open(mdl)

%% Colors for the figures
colors = colororder;

%% Frequency Vector
freqs = logspace(1, 3, 1000);

% System Identification
% Let's identify the damped plant.


%% Input/Output definition
clear io; io_i = 1;

%% Inputs
% Control Input {3x1} [N]
io(io_i) = linio([mdl, '/control_system'], 1, 'input');  io_i = io_i + 1;

%% Outputs
% Force Sensor {3x1} [m]
io(io_i) = linio([mdl, '/DCM'], 1, 'openoutput'); io_i = io_i + 1;

%% Load DCM Kinematics and IFF controller
load('dcm_kinematics.mat');
load('Kiff.mat');

%% Identification of the damped plant with IFF
controller.type = 1; % IFF
G_dp = J_a_111*inv(J_s_111)*linearize(mdl, io);
G_dp.InputName  = {'u_ur',  'u_uh',  'u_d'};
G_dp.OutputName = {'d_ur',  'd_uh',  'd_d'};

%% Comparison of the damped and undamped plant
figure;
tiledlayout(3, 1, 'TileSpacing', 'Compact', 'Padding', 'None');

ax1 = nexttile([2,1]);
hold on;
plot(freqs, abs(squeeze(freqresp(G_dp(1,1), freqs, 'Hz'))), '-', ...
     'DisplayName', 'd - IFF');
plot(freqs, abs(squeeze(freqresp(G_dp(2,2), freqs, 'Hz'))), '-', ...
     'DisplayName', 'uh - IFF');
plot(freqs, abs(squeeze(freqresp(G_dp(3,3), freqs, 'Hz'))), '-', ...
     'DisplayName', 'ur - IFF');
for i = 1:2
    for j = i+1:3
        plot(freqs, abs(squeeze(freqresp(G_dp(i,j), freqs, 'Hz'))), 'color', [0, 0, 0, 0.2], ...
             'HandleVisibility', 'off');
    end
end
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
ylabel('Amplitude [m/N]'); set(gca, 'XTickLabel',[]);
legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 2);
ylim([1e-12, 1e-8]);

ax2 = nexttile;
hold on;
plot(freqs, 180/pi*angle(squeeze(freqresp(G_dp(1,1), freqs, 'Hz'))), '-');
plot(freqs, 180/pi*angle(squeeze(freqresp(G_dp(2,2), freqs, 'Hz'))), '-');
plot(freqs, 180/pi*angle(squeeze(freqresp(G_dp(3,3), freqs, 'Hz'))), '-');
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin');
xlabel('Frequency [Hz]'); ylabel('Phase [deg]');
hold off;
yticks(-360:90:360);
ylim([-180, 0]);

linkaxes([ax1,ax2],'x');
xlim([freqs(1), freqs(end)]);

% High Authority Controller
% Let's design a controller with a bandwidth of 100Hz.
% As the plant is well decoupled and well approximated by a constant at low frequency, the high authority controller can easily be designed with SISO loop shaping.


%% Controller design
wc = 2*pi*100; % Wanted crossover frequency [rad/s]
a = 2; % Lead parameter

Khac = diag(1./diag(abs(evalfr(G_dp, 1j*wc)))) * ... % Diagonal controller
        wc/s * ... % Integrator
        1/(sqrt(a))*(1 + s/(wc/sqrt(a)))/(1 + s/(wc*sqrt(a))) * ... % Lead
        1/(s^2/(4*wc)^2 + 2*s/(4*wc) + 1); % Low pass filter

%% Save the HAC controller
save('mat/Khac_iff.mat', 'Khac');

%% Loop Gain
L_hac_lac = G_dp * Khac;

%% Bode Plot of the Loop Gain
figure;
tiledlayout(3, 1, 'TileSpacing', 'Compact', 'Padding', 'None');

ax1 = nexttile([2,1]);
hold on;
plot(freqs, abs(squeeze(freqresp(L_hac_lac(1,1), freqs, 'Hz'))), '-', ...
     'DisplayName', 'd');
plot(freqs, abs(squeeze(freqresp(L_hac_lac(2,2), freqs, 'Hz'))), '-', ...
     'DisplayName', 'uh');
plot(freqs, abs(squeeze(freqresp(L_hac_lac(3,3), freqs, 'Hz'))), '-', ...
     'DisplayName', 'ur');
for i = 1:2
    for j = i+1:3
        plot(freqs, abs(squeeze(freqresp(L_hac_lac(i,j), freqs, 'Hz'))), 'color', [0, 0, 0, 0.2], ...
             'HandleVisibility', 'off');
    end
end
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
ylabel('Loop Gain'); set(gca, 'XTickLabel',[]);
legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 2);
ylim([1e-2, 1e1]);

ax2 = nexttile;
hold on;
plot(freqs, 180/pi*angle(squeeze(freqresp(L_hac_lac(1,1), freqs, 'Hz'))), '-');
plot(freqs, 180/pi*angle(squeeze(freqresp(L_hac_lac(2,2), freqs, 'Hz'))), '-');
plot(freqs, 180/pi*angle(squeeze(freqresp(L_hac_lac(3,3), freqs, 'Hz'))), '-');
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin');
xlabel('Frequency [Hz]'); ylabel('Phase [deg]');
hold off;
yticks(-360:90:360);
ylim([-180, 0]);

linkaxes([ax1,ax2],'x');
xlim([freqs(1), freqs(end)]);



% #+name: fig:hac_iff_loop_gain_bode_plot
% #+caption: Bode Plot of the Loop gain for the High Authority Controller
% #+RESULTS:
% [[file:figs/hac_iff_loop_gain_bode_plot.png]]


%% Compute the Eigenvalues of the loop gain
Ldet = zeros(3, length(freqs));

Lmimo = squeeze(freqresp(L_hac_lac, freqs, 'Hz'));
for i_f = 1:length(freqs)
    Ldet(:, i_f) = eig(squeeze(Lmimo(:,:,i_f)));
end



% As shown in the Root Locus plot in Figure [[fig:loci_hac_iff_fast_jack]], the closed loop system should be stable.


%% Plot of the eigenvalues of L in the complex plane
figure;
hold on;
% Angle used to draw the circles
theta = linspace(0, 2*pi, 100);
% Unit circle
plot(cos(theta), sin(theta), '--');
% Circle for module margin
plot(-1 + min(min(abs(Ldet + 1)))*cos(theta), min(min(abs(Ldet + 1)))*sin(theta), '--');

for i = 1:3
    plot(real(squeeze(Ldet(i,:))), imag(squeeze(Ldet(i,:))), 'k.');
    plot(real(squeeze(Ldet(i,:))), -imag(squeeze(Ldet(i,:))), 'k.');
end
% Unstable Point
plot(-1, 0, 'kx', 'HandleVisibility', 'off');
hold off;
set(gca, 'XScale', 'lin'); set(gca, 'YScale', 'lin');
xlabel('Real'); ylabel('Imag');
axis square;
xlim([-3, 1]); ylim([-2, 2]);

% Performances
% In order to estimate the performances of the HAC-IFF control strategy, the transfer function from motion errors of the stepper motors to the motion error of the crystal is identified both in open loop and with the HAC-IFF strategy.


%% Input/Output definition
clear io; io_i = 1;

%% Inputs
% Jack Motion Erros {3x1} [m]
io(io_i) = linio([mdl, '/stepper_errors'], 1, 'input');  io_i = io_i + 1;

%% Outputs
% Interferometer Output {3x1} [m]
io(io_i) = linio([mdl, '/DCM'], 1, 'output'); io_i = io_i + 1;

%% Identification of the transmissibility of errors in open-loop
controller.type = 0; % Open Loop
T_ol = inv(J_s_111)*linearize(mdl, io)*J_a_111;
T_ol.InputName  = {'e_dz',  'e_ry',  'e_rx'};
T_ol.OutputName = {'dx',  'ry',  'rx'};

%% Load DCM Kinematics and IFF controller
load('dcm_kinematics.mat');
load('Kiff.mat');

%% Identification of the transmissibility of errors with HAC-IFF
controller.type = 3; % IFF
T_hl = inv(J_s_111)*linearize(mdl, io)*J_a_111;
T_hl.InputName  = {'e_dz',  'e_ry',  'e_rx'};
T_hl.OutputName = {'dx',  'ry',  'rx'};



% #+RESULTS:
% : 1

% And both transmissibilities are compared in Figure [[fig:stepper_transmissibility_comp_ol_hac_iff]].


%% Transmissibility of stepper errors
f = logspace(0, 3, 1000);

figure;
hold on;
plot(f, abs(squeeze(freqresp(T_ol(1,1), f, 'Hz'))), '-', ...
     'DisplayName', '$d_z$ - OL');
plot(f, abs(squeeze(freqresp(T_ol(2,2), f, 'Hz'))), '-', ...
     'DisplayName', '$r_y$ - OL');
plot(f, abs(squeeze(freqresp(T_ol(3,3), f, 'Hz'))), '-', ...
     'DisplayName', '$r_x$ - OL');
set(gca,'ColorOrderIndex',1)
plot(f, abs(squeeze(freqresp(T_hl(1,1), f, 'Hz'))), '--', ...
     'DisplayName', '$d_z$ - HAC-IFF');
plot(f, abs(squeeze(freqresp(T_hl(2,2), f, 'Hz'))), '--', ...
     'DisplayName', '$r_y$ - HAC-IFF');
plot(f, abs(squeeze(freqresp(T_hl(3,3), f, 'Hz'))), '--', ...
     'DisplayName', '$r_x$ - HAC-IFF');
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
xlabel('Frequency [Hz]'); ylabel('Stepper transmissibility');
legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 2);
ylim([1e-2, 1e2]);
xlim([f(1), f(end)]);

% Close Loop noise budget

%% Load disturbances
load('asd_noises_disturbances.mat');



% Let's compute the amplitude spectral density of the jack motion errors due to the sensor noise, the actuator noise and disturbances.


%% Computation of ASD of contribution of inputs to the closed-loop motion
% Error due to disturbances
asd_d = abs(squeeze(freqresp(Wd*(1/(1 + G_dp(1,1)*Khac(1,1))), f, 'Hz')));
% Error due to actuator noise
asd_u = abs(squeeze(freqresp(Wu*(G_dp(1,1)/(1 + G_dp(1,1)*Khac(1,1))), f, 'Hz')));
% Error due to sensor noise
asd_n = abs(squeeze(freqresp(Wn*(G_dp(1,1)*Khac(1,1)/(1 + G_dp(1,1)*Khac(1,1))), f, 'Hz')));



% The closed-loop ASD is then:

%% ASD of the closed-loop motion
asd_cl = sqrt(asd_d.^2 + asd_u.^2 + asd_n.^2);



% The obtained ASD are shown in Figure [[fig:close_loop_asd_noise_budget_hac_iff]].


%% Noise Budget (ASD)
f = logspace(-1, 3, 1000);

figure;
hold on;
plot(f, asd_n, 'DisplayName', '$n$');
plot(f, asd_u, 'DisplayName', '$d_u$');
plot(f, asd_d, 'DisplayName', '$d$');
plot(f, asd_cl, 'k--', 'DisplayName', '$y$');
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
xlabel('Frequency [Hz]'); ylabel('ASD [$m/\sqrt{Hz}$]');
legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 2);
xlim([f(1), f(end)]);
ylim([1e-16, 1e-8]);



% #+name: fig:close_loop_asd_noise_budget_hac_iff
% #+caption: Closed Loop noise budget
% #+RESULTS:
% [[file:figs/close_loop_asd_noise_budget_hac_iff.png]]

% Let's compare the open-loop and close-loop cases (Figure [[fig:cps_comp_ol_cl_hac_iff]]).

% Amplitude spectral density of the open loop motion errors [m/sqrt(Hz)]
asd_ol = abs(squeeze(freqresp(Wd, f, 'Hz')));

% CPS of open-loop motion [m^2]
cps_ol = flip(-cumtrapz(flip(f), flip(asd_ol.^2)));
% CPS of closed-loop motion [m^2]
cps_cl = flip(-cumtrapz(flip(f), flip(asd_cl.^2)));

%% Cumulative Power Spectrum - Motion error of fast jack
figure;
hold on;
plot(f, cps_ol, 'DisplayName', sprintf('OL, $\\epsilon_d = %.0f$ [nm,rms]', 1e9*sqrt(cps_ol(1))));
plot(f, cps_cl, 'DisplayName', sprintf('CL, $\\epsilon_d = %.0f$ [nm,rms]', 1e9*sqrt(cps_cl(1))));
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
xlabel('Frequency [Hz]'); ylabel('CPS [$m^2$]');
legend('location', 'southwest', 'FontSize', 8);
xlim([f(1), f(end)]);
% ylim([1e-16, 1e-8]);
