From 6b589b2f39cc5996b7c14006d5be8010afc67e89 Mon Sep 17 00:00:00 2001
From: Nao Pross <np@0hm.ch>
Date: Fri, 31 May 2024 01:39:04 +0200
Subject: Update DK iteration and model structure

---
 pbhtest.m                   |   8 +-
 uav.m                       | 197 +++++++++++++++-------------
 uav_model.m                 | 309 +++++++++++++++++++++++++++++++++-----------
 uav_model_uncertain.slx     | Bin 62600 -> 62649 bytes
 uav_model_uncertain_clp.slx | Bin 67378 -> 66499 bytes
 uav_params.m                |  31 +++--
 uav_performance_hinf.m      |  45 ++++---
 uav_performance_musyn.m     |  57 +++++---
 uav_sim_step.m              | 260 +++++++++++++++++++------------------
 uav_uncertainty.m           |  27 ++--
 10 files changed, 579 insertions(+), 355 deletions(-)

diff --git a/pbhtest.m b/pbhtest.m
index f56aa21..546bbc6 100644
--- a/pbhtest.m
+++ b/pbhtest.m
@@ -1,4 +1,4 @@
-% Do A PBH test on system. Given SYS returns:
+% Do A PBH test on system. Given a state space model SYS returns:
 %
 %  - Nr. of states
 %  - Nr. of stable states
@@ -23,9 +23,9 @@ end
 % Check system controllability / stabilizability
 Wc = ctrb(sys);
 nctrb = rank(Wc);
+nustab = 0;
 if nctrb < nx
   % Is the system at least stabilizable?
-  nustab = 0;
   for i = 1:nx
     if real(eigvals(i)) >= 0
       % PBH test
@@ -40,13 +40,13 @@ end
 % Check system observability / detectability
 Wo = obsv(sys);
 nobsv = rank(Wo);
+nudetb = 0;
 if nobsv < nx
   % is the system at least detectable?
-  nudetb = 0;
   for i = 1:nx
     if real(eigvals(i)) >= 0
       % PBH test
-      W = [sys.C; (sys.A - eigvals(i) * eye(nx))];
+      W = [(sys.A' - eigvals(i) * eye(nx)), sys.C'];
       if rank(W) < nx
         nudetb = nudetb + 1;
       end
diff --git a/uav.m b/uav.m
index 4810943..2ee58da 100644
--- a/uav.m
+++ b/uav.m
@@ -9,8 +9,8 @@
 clear; clc; close all; s = tf('s');
 
 do_plots = true; % runs faster without
-do_hinf = false; % midterm
-do_musyn = true; % endterm
+do_hinf = true;
+do_musyn = true;
 
 fprintf('Controller synthesis for ducted fan VTOL micro-UAV\n')
 fprintf('Will do:\n')
@@ -46,16 +46,13 @@ perf_musyn = uav_performance_musyn(params, do_plots);
 fprintf('Generating stability requirements...\n')
 uncert = uav_uncertainty(params, do_plots);
 
-%% ------------------------------------------------------------------------
-% Create UAV model
-
-fprintf('Generating system model...\n');
-model = uav_model(params, perf_musyn, uncert);
-
 %% ------------------------------------------------------------------------
 % Perform H-infinity design
 
 if do_hinf
+  fprintf('Generating system model for H-infinity design...\n');
+  model = uav_model(params, perf_hinf, uncert);
+
   fprintf('Performing H-infinty controller design...\n')
 
   idx = model.uncertain.index;
@@ -125,17 +122,17 @@ if do_hinf
 
   fprintf('Simulating closed loop...\n');
 
-  T = 40;
-  nsamples = 800;
+  T = 60;
+  nsamples = 5000;
   do_noise = false;
-  simout = uav_sim_step(params, model, ctrl.hinf, nsamples, T, do_plots, do_noise);
+  simout = uav_sim_step(params, model, ctrl.hinf, uncert, nsamples, T, do_plots, do_noise);
 
   fprintf(' - Writing simulation results...\n');
   cols = [
       simout.StepX(:, simout.index.Position), ...
       simout.StepX(:, simout.index.Velocity), ...
-      simout.StepX(:, simout.index.FlapAngles) * 180 / pi, ...
-      simout.StepX(:, simout.index.Angles) * 180 / pi];
+      simout.StepX(:, simout.index.PlotAlpha) * 180 / pi, ...
+      simout.StepX(:, simout.index.EulerAngles) * 180 / pi];
 
   writematrix([simout.Time', cols], 'fig/stepsim.dat', 'Delimiter', 'tab')
 end
@@ -144,6 +141,8 @@ end
 drawnow;
 
 if do_musyn
+  fprintf('Generating system model for mu-synthesis design...\n');
+  model = uav_model(params, perf_musyn, uncert);
 
   fprintf('Performing mu-synthesis controller design...\n')
 
@@ -157,12 +156,12 @@ if do_musyn
   % Options for H-infinity
   nmeas = model.uncertain.Ny;
   nctrl = model.uncertain.Nu;
-  hinfopt = hinfsynOptions('Display', 'on', 'Method', 'RIC', ...
+  hinfopt = hinfsynOptions('Display', 'off', 'Method', 'RIC', ...
     'AutoScale', 'on', 'RelTol', 1e-2);
 
   % Frequency raster resolution to fit D scales
-  omega_max = 3;
-  omega_min = -2;
+  omega_max = 2.5;
+  omega_min = -3.5;
   nsamples = (omega_max - omega_min) * 100;
 
   omega = logspace(omega_min, omega_max, nsamples);
@@ -172,10 +171,6 @@ if do_musyn
   D_left = tf(eye(model.uncertain.Nz + model.uncertain.Ne + model.uncertain.Ny));
   D_right = tf(eye(model.uncertain.Nv + model.uncertain.Nw + model.uncertain.Nu));
 
-  % Maximum number of D-K iterations
-  niters = 5;
-  fprintf(' - Will do (max) %d iterations.\n', niters);
-
   % Maximum degree of D-scales and error
   d_scales_max_degree = 4;
   d_scales_max_err = 15;
@@ -203,21 +198,25 @@ if do_musyn
   gamma_max = 20;
   gamma_min = .8;
 
+  % Maximum number of D-K iterations
+  niters = 4;
+  fprintf(' - Will do (max) %d iterations.\n', niters);
+
   %% Run D-K iteration
 
   % hand tuned degrees, inf means will pick automatically best fit
   % according to parameters given above
   d_scales_degrees = {
-      0,   0,   0, inf, inf; % alpha
-      0, inf, inf, inf, inf; % omega
-      0, inf, inf, inf, inf; % state
-      0,   0,   0, inf, inf; % perf
+      0,   0,   0,   0,   0, inf, inf; % alpha
+      1,   1,   3,   3,   0, inf, inf; % omega
+      0,   1,   0,   0,   0, inf, inf; % state
+      0,   0,   0,   0,   0, inf, inf; % perf
   };
 
   if size(d_scales_degrees, 2) < niters
     error('Number of columns of d_scales_degrees must match niter');
   end
-  
+
   % if iterations fails set warmit and re-run this code section
   if warmit > 0
     fprintf("\n");
@@ -225,7 +224,7 @@ if do_musyn
 
     K = K_prev{warmit};
     gamma_max = gamma_prev{warmit};
-    
+
     mu_bounds_rp = mu_bounds_rp_prev{warmit};
     mu_bounds_rs = mu_bounds_rs_prev{warmit};
 
@@ -239,6 +238,7 @@ if do_musyn
 
   dkstart = tic;
   for it = s:niters
+    fprintf('\n');
     fprintf(' * Running D-K iteration %d / %d...\n', it, niters);
     itstart = tic();
 
@@ -250,8 +250,7 @@ if do_musyn
     P_scaled = minreal(D_left * P * inv(D_right), [], false);
     [P_scaled, ~] = prescale(P_scaled, omega_range);
     n = size(P_scaled.A, 1);
-    
-    % disabled
+
     if false && n > scaled_plant_reduce_ord
       R = reducespec(P_scaled, 'balanced'); % or ncf
       R.Options.FreqIntervals = [omega_range{1}, omega_range{2}];
@@ -265,56 +264,66 @@ if do_musyn
 
       fprintf('   Scaled plant was reduced from order %d to %d\n', ...
         n, size(P_scaled.A, 1))
+
+      % update n
+      n = size(P_scaled.A, 1);
     end
 
-    % Check scaled plant meets H-infinity assumptions
-    A = P_scaled(idx.OutputUncertain, idx.InputUncertain);
-    Bu = P_scaled(idx.OutputUncertain, idx.InputNominal);
-    Cy = P_scaled(idx.OutputNominal, idx.InputUncertain);
+    % For higher number of states we run into numerical problems
+    if n < 70
+      % Scaled plant (whole)
+      [nx, nsta, nctrb, nustab, nobsv, nudetb] = pbhtest(P_scaled);
 
-    [~, ~, ~, nustab, ~, nudetb] = pbhtest(A);
-    if nustab > 0 || nudetb > 0
-      fprintf('   Scaled plant has unstabilizable or undetectable A.\n');
-    end
+      fprintf('   Scaled plant has %d modes:\n', nx);
+      fprintf('       %d stable, %d unstable\n', nsta, nx - nsta);
+      fprintf('       %d controllable, %d unstabilizable\n', nctrb, nustab);
+      fprintf('       %d observable, %d undetectable\n', nobsv, nudetb);
 
-    [~, ~, ~, nustab, ~, nudetb] = pbhtest(Bu);
-    if nustab > 0 || nudetb > 0
-      fprintf('   Scaled plant has unstabilizable or undetectable Bu.\n');
-    end
+      % Check scaled plant parts meet H-infinity assumptions
+      A = P_scaled(idx.OutputUncertain, idx.InputUncertain);
+      Bu = P_scaled(idx.OutputUncertain, idx.InputNominal);
+      Cy = P_scaled(idx.OutputNominal, idx.InputUncertain);
 
-    [~, ~, ~, nustab, ~, nudetb] = pbhtest(Cy);
-    if nustab > 0 || nudetb > 0
-      fprintf('   Scaled plant has unstabilizable or undetectable Cy.\n');
-    end
-    
-    Deu = P_scaled(idx.OutputError, idx.InputNominal);
-    Dyw = P_scaled(idx.OutputNominal, idx.InputExogenous);
+      [~, ~, ~, A_nustab, ~, A_nudetb] = pbhtest(A);
+      [~, ~, ~, Bu_nustab, ~, Bu_nudetb] = pbhtest(Bu);
+      [~, ~, ~, Cy_nustab, ~, Cy_nudetb] = pbhtest(Cy);
 
-    if rank(ctrb(Deu)) < length(Deu.A) || rank(obsv(Deu)) < lenth(Deu.A) 
-      fprintf('   Scaled plant has Deu that is not full rank.\n');
-    end
+      Deu = P_scaled(idx.OutputError, idx.InputNominal);
+      Dyw = P_scaled(idx.OutputNominal, idx.InputExogenous);
 
-    if rank(ctrb(Dyw)) < length(Dyw.A) || rank(obsv(Dyw)) < lenth(Dyw.A) 
-      fprintf('   Scaled plant has Dyw that is not full rank.\n');
-    end
+      Deu_fullrank = rank(ctrb(Deu)) < length(Deu.A) || rank(obsv(Deu)) < lenth(Deu.A);
+      Dyw_fullrank = rank(ctrb(Dyw)) < length(Dyw.A) || rank(obsv(Dyw)) < lenth(Dyw.A);
 
-    % Scaled pant
-    eigvals = eig(P_scaled.A);
-    stable_modes = 0;
-    for i = 1:n
-      if real(eigvals(i)) < 0
-        stable_modes = stable_modes +1;
+      if A_nustab > 0 || A_nudetb > 0
+        fprintf('       A has %d unstabilizable modes and %d undetectable modes!\n', ...
+          A_nustab, A_nudetb);
+      end
+
+      if Bu_nustab > 0 || Bu_nudetb > 0
+        fprintf('       Bu has %d unstabilizable modes and %d undetectable modes!\n', ...
+          Bu_nustab, Bu_nudetb);
       end
-    end
 
-    % For higher number of states we get numerical problems
-    if n < 100
-      ctrb_modes = rank(ctrb(P_scaled));
-      obsv_modes = rank(obsv(P_scaled));
+      if Cy_nustab > 0 || Cy_nudetb > 0
+        fprintf('       Cy has %d unstabilizable modes and %d undetectable modes!\n', ...
+          Cy_nustab, Cy_nudetb);
+      end
 
-      fprintf('   Scaled plant has %d modes: %d ctrb %d obsv %d stable %d unstable.\n', ...
-        n, ctrb_modes, obsv_modes, stable_modes, n - stable_modes);
+      if ~ Deu_fullrank
+        fprintf('       Deu is not full rank!\n');
+      end
+
+      if ~ Dyw_fullrank
+        fprintf('       Dyw is not full rank!\n');
+      end
     else
+      eigvals = eig(P_scaled.A);
+      stable_modes = 0;
+      for i = 1:n
+        if real(eigvals(i)) < 0
+          stable_modes = stable_modes +1;
+        end
+      end
       fprintf('   Scaled plant has %d modes: %d stable %d unstable.\n', ...
         n, stable_modes, n - stable_modes);
     end
@@ -325,12 +334,12 @@ if do_musyn
       gamma_prev{it} = gamma;
     end
 
-    gamma_max=inf;
-    fprintf('   Running H-infinity with gamma in [%g, %g]\n', ...
-      gamma_min, gamma_max);
+    [K, ~, gamma, ~] = hinfsyn(P_scaled, nmeas, nctrl, hinfopt);
+
+    % fprintf('   Running H-infinity with gamma in [%g, %g]\n', gamma_min, gamma_max);
+    % [K, ~, gamma, ~] = hinfsyn(P_scaled, nmeas, nctrl, [gamma_min, gamma_max], hinfopt);
+    % gamma_max = 1.2 * gamma;
 
-    [K, ~, gamma, ~] = hinfsyn(P_scaled, nmeas, nctrl, [gamma_min, gamma_max], hinfopt);
-    gamma_max = 2 * gamma;
     fprintf('   H-infinity synthesis gamma: %g\n', gamma);
     if gamma == inf
       error('Failed to synethesize H-infinity controller');
@@ -364,13 +373,13 @@ if do_musyn
       if do_plots
         fprintf('   Plotting SSV mu\n');
         figure(100); hold on;
-  
+
         bodemag(mu_bounds_rp(1,1));
         mu_plot_legend = {mu_plot_legend{:}, sprintf('$\\mu_{P,%d}$', it)};
-  
+
         bodemag(mu_bounds_rs(1,1), 'k:');
         mu_plot_legend = {mu_plot_legend{:}, sprintf('$\\mu_{S,%d}$', it)};
-  
+
         title('\bfseries $\mu_\Delta(\omega)$ for both Stability and Performance', ...
               'interpreter', 'latex');
         legend(mu_plot_legend, 'interpreter', 'latex');
@@ -396,6 +405,11 @@ if do_musyn
                           'mu_rp', mu_rp, 'mu_rs', mu_rs);
     end
 
+    % No need to find scales for last iteration
+    if it == niters
+      break;
+    end
+
     % Fit D-scales
     [D_left_frd, D_right_frd] = mussvunwrap(mu_info_rp);
 
@@ -428,7 +442,7 @@ if do_musyn
     % for each block
     for j = 1:4
       fprintf('      %s', D_names{j});
-      
+
       % tuned by hand?
       if d_scales_degrees{j, it} < inf
         % D_fit = fitmagfrd(D_frd{j}, d_scales_degrees{j, it});
@@ -451,11 +465,11 @@ if do_musyn
           % Fit D-scale
           % D_fit = fitmagfrd(D_frd{j}, deg);
           D_fit = fitfrd(genphase(D_frd{j}), deg);
-  
+
           % Check if it is a good fit
           max_sv = max(max(sigma(D_fit, omega)));
           fit_err = abs(D_max_sv{j} - max_sv);
-  
+
           if fit_err < best_fit_err
             % Choose higher degree only if we improve by at least a 
             % specified percentage over the previous best fit (or we are
@@ -464,7 +478,7 @@ if do_musyn
             % the order of the D-scales.
             improvement = abs(best_fit_err - fit_err);
             improvement_p = improvement / best_fit_err;
-            
+
             if improvement_p > d_scales_improvement_p ...
               || best_fit_err == inf
 
@@ -473,7 +487,7 @@ if do_musyn
                 D_fitted{j} = D_fit;
             end
           end
-  
+
           if fit_err / D_max_sv{j} < d_scales_max_err_p ...
               || fit_err < d_scales_max_err
             break;
@@ -489,15 +503,15 @@ if do_musyn
     D_left_prev{it} = D_left;
     D_right_prev{it} = D_right;
 
-    D_left = blkdiag(D_fitted{1} * eye(4), ...
-                     D_fitted{2} * eye(1), ...
-                     D_fitted{3} * eye(12), ...
+    D_left = blkdiag(D_fitted{1} * eye(4), ... % alpha uncert
+                     D_fitted{2} * eye(1), ... % omega uncert
+                     D_fitted{3} * eye(9), ... % state uncert
                      D_fitted{4} * eye(14), ...
                      eye(12));
 
-    D_right = blkdiag(D_fitted{1} * eye(4), ...
-                      D_fitted{2} * eye(1), ...
-                      D_fitted{3} * eye(12), ...
+    D_right = blkdiag(D_fitted{1} * eye(4), ... % alpha uncert
+                      D_fitted{2} * eye(1), ... % omega uncert
+                      D_fitted{3} * eye(9), ... % state uncert
                       D_fitted{4} * eye(10), ...
                       eye(5));
 
@@ -581,6 +595,15 @@ if do_musyn
 %%  ------------------------------------------------------------------------
 % Measure Performance of mu synthesis design
 
+  index = struct( ...
+    'Ix', 1, 'Iy', 2, 'Iz', 3, ...
+    'IPdot', (4:6)', ...
+    'Iroll', 7, 'Ipitch', 8, 'Iyaw', 9, ...
+    'ITheta', (10:12)', ...
+    'Ialpha', (1:4)', ...
+    'Iomega', 5 ...
+  );
+
   if do_plots
     fprintf(' - Plotting resulting controller...\n');
 
@@ -611,11 +634,11 @@ if do_musyn
 
   fprintf('Simulating nominal closed loop...\n');
 
-  T = 40;
+  T = 60;
   nsamples = 5000;
   do_noise = false;
 
-  simout = uav_sim_step(params, model, ctrl.musyn, nsamples, T, do_plots, do_noise);
+  simout = uav_sim_step(params, model, ctrl.musyn, uncert, nsamples, T, do_plots, do_noise);
 
   fprintf('Simulating worst-case closed loop...\n');
 
diff --git a/uav_model.m b/uav_model.m
index 6a7f9da..af0be20 100644
--- a/uav_model.m
+++ b/uav_model.m
@@ -221,16 +221,13 @@ C = eye(size(A));
 D = zeros(12, 5);
 
 % ------------------------------------------------------------------------
-% Model actuators
+% Model actuators (normalized to [-1, 1]!)
 
-% TODO: better model?
-w = params.actuators.ServoNominalAngularVelocity;
-zeta = 1;
-G_servo = tf(w^2, [1, 2 * zeta * w, w^2]);
+T = params.actuators.ServoSecondsTo60Deg;
+G_servo = tf(1, [T, 1]);
 
-w = 60;
-zeta = 1;
-G_prop = tf(w^2, [1, 2 * zeta * w, w^2]);
+% TODO: measurements for propeller
+G_prop = tf(1, [.1, 1]);
 
 model.actuators = struct( ...
   'FlapServo', G_servo, ...
@@ -240,28 +237,43 @@ model.actuators = struct( ...
 % ------------------------------------------------------------------------
 % Scale inputs and outputs of linearized model
 
+% All scaling matrices scale UP FROM RANGE (-1, 1), i.e. for outputs they can
+% be used as-is, while for inputs they need to be inverted
+
 S_actuators = blkdiag(...
-  eye(4) * 1 / params.actuators.ServoAbsMaxAngle, ...
-  eye(1) * 1 / params.actuators.PropellerMaxAngularVelocity);
+  eye(4) * params.normalization.FlapAngle, ...
+  eye(1) * params.normalization.ThrustAngularVelocity ...
+);
 
-S_state = blkdiag(...
+S_positions = blkdiag(...
   eye(2) * params.normalization.HPosition, ...
-  eye(1) * params.normalization.VPosition, ...
+  eye(1) * params.normalization.VPosition ...
+);
+
+S_velocities = blkdiag(...
   eye(2) * params.normalization.HSpeed, ...
-  eye(1) * params.normalization.VSpeed, ...
+  eye(1) * params.normalization.VSpeed ...
+);
+
+S_angles = blkdiag(...
   eye(2) * params.normalization.PitchRollAngle, ...
-  eye(1) * params.normalization.YawAngle, ...
-  eye(3) * params.normalization.AngularRate);
+  eye(1) * params.normalization.YawAngle ...
+);
+
+S_angular_velocities = eye(3) * params.normalization.AngularRate;
+
+S_state = blkdiag(S_positions, S_velocities, S_angles, S_angular_velocities);
 
 % Scale system matrices to have inputs and outputs between zero and one
-B = B * inv(S_actuators);
+B = B * S_actuators;
 C = inv(S_state) * C;
-D = D * inv(S_actuators);
+D = D * S_actuators;
 
 % Create state space object
 T = params.measurements.SensorFusionDelay;
 n = params.linearization.PadeApproxOrder;
 sys = pade(ss(A, B, C, D, 'OutputDelay', T), n);
+% sys = ss(A, B, C, D);
 
 % Add actuators
 sys = sys * model.actuators.StateSpace;
@@ -279,7 +291,7 @@ model.linear = struct(...
   'State', xi, 'Inputs', u, ... % state and input variables
   'StateEq', xi_eq, 'InputEq', u_eq, ... % where the system was linearized
   'StateSpace', sys, ... % state space objec
-  'InputScalingMatrix', S_actuators, 'OutputScalingMatrix', S_state ...
+  'InputScaling', S_actuators, 'OutputScaling', S_state ...
 );
 
 % ------------------------------------------------------------------------
@@ -293,18 +305,13 @@ fprintf('   %d controllable modes, %d unstabilizable modes.\n', nctrb, nustab);
 fprintf('   %d observable modes, %d undetectable modes.\n', nobsv, nustab);
 
 if nctrb < 12
-  error('Linearized model has less than 12 controllable modes!');
+  fprintf('   Linearized model has less than 12 controllable modes!\n');
 end
 
 if nustab > 0 || nudetb > 0
   error('Linearized model has unstabilizable or undetectable modes!');
 end
 
-% ------------------------------------------------------------------------
-% Compute absolute value of error caused by linearization around set point
-
-% TODO
-
 % ------------------------------------------------------------------------
 % Add uncertainties using SIMULINK model
 
@@ -320,13 +327,13 @@ hws.assignin('uncert', uncert);
 
 % Get uncertain model
 ulmod = linmod('uav_model_uncertain');
-usys = ss(ulmod.a, ulmod.b, ulmod.c, ulmod.d);
+usys = minreal(ss(ulmod.a, ulmod.b, ulmod.c, ulmod.d), [], false);
 
 % Specify uncertainty block structure for mussv command
 blk_stab = [
   4, 4; % alpha uncert, full
   1, 1; % omega uncert, full
-  12, 12; % state uncert, full
+  9, 9; % state uncert, full
 ];
 
 blk_perf = [
@@ -334,29 +341,17 @@ blk_perf = [
   10, 14 % always full
 ];
 
-% ------------------------------------------------------------------------
-% Scale inputs and outputs of uncertain model
-
-% Scaling of reference is same as position
-S_ref = blkdiag(...
-  eye(2) * 1 / params.normalization.HPosition, ...
-  eye(1) * 1 / params.normalization.VPosition);
-
-% TODO: finish
-S_uncert_out = blkdiag(...
-  S_actuators, ...
-  S_state, ...
-  S_actuators, ...
-  S_state(1:9, 1:9));
-
 % Save uncertain model
 model.uncertain = struct(... 
-  ... % 'Simulink', ulmod, ...
+  'Simulink', ulmod, ...
   'BlockStructure', blk_stab, ...
   'BlockStructurePerf', blk_perf, ...
   'StateSpace', usys ...
 );
 
+% ------------------------------------------------------------------------
+% Create indices to take subsystems
+
 % The uncertain system is partitioned into the following matrix
 %
 % [ z ]   [ A    B_w   B_u  ] [ v ]
@@ -369,33 +364,189 @@ model.uncertain = struct(...
 %   - model.uncertain.Simulink.InputName(model.uncertain.index.InputX)
 %   - model.uncertain.Simulink.OutputName(model.uncertain.index.OutputX)
 %
-% Function make_idx(start, size) is defined below.
-model.uncertain.index = struct(...
-  'InputUncertain',    make_idx( 1, 17), ... % 'v' inputs
-  'InputDisturbance',  make_idx(18,  7), ... % 'w' inputs for noise
-  'InputReference',    make_idx(25,  3), ... % 'w' inputs for reference
-  'InputExogenous',    make_idx(18, 10), ... % 'w' inputs (all of them)
-  'InputNominal',      make_idx(28,  5), ... % 'u' inputs
-  'OutputUncertain',   make_idx( 1, 17), ... % 'z' outputs
-  'OutputError',       make_idx(18, 14), ... % 'e' outputs
-  'OutputNominal',     make_idx(32, 12), ... % 'y' outputs
-  'OutputPlots',       make_idx(44, 10)  ... % 'p' outputs for plots in closed loop
+
+% Size of signals to generate indices
+input_dims = struct(...
+  'InputUncertainAlpha',    4, ... % 'v' inputs
+  'InputUncertainOmega',    1, ... %  .
+  'InputUncertainState',    9, ... %  .
+  'InputDisturbanceWind',   3, ... % 'w' inputs
+  'InputDisturbanceAlpha',  4, ... %  .
+  'InputReference',         3, ... %  .
+  'InputNominalAlpha',      4, ... % 'u' inputs
+  'InputNominalOmega',      1 ...  %  .
 );
 
-idx = model.uncertain.index;
+output_dims = struct(...
+  'OutputUncertainAlpha',      4, ... % 'z' outputs
+  'OutputUncertainOmega',      1, ... %  .
+  'OutputUncertainState',      9, ... %  .
+  'OutputErrorAlpha',          4, ... % 'e' outputs
+  'OutputErrorOmega',          1, ... %  .
+  'OutputErrorPosition',       3, ... %  .
+  'OutputErrorVelocity',       3, ... %  .
+  'OutputErrorEulerAngles',    3, ... %  .
+  'OutputNominalPosition',     3, ... % 'y' outputs
+  'OutputNominalVelocity',     3, ... %  .
+  'OutputNominalEulerAngles',  3, ... %  .
+  'OutputNominalAngularRates', 3, ... %  .
+  'OutputPlotAlpha',           4, ... %  plot / "debugging" outputs
+  'OutputPlotOmega',           1, ... %  .
+  'OutputPlotReference',       3, ... %  .
+  'OutputPlotPosition',        3 ...  %  .
+);
 
-% Number of inputs
-model.uncertain.Nv = max(size(idx.InputUncertain));
-model.uncertain.Nw = max(size(idx.InputExogenous));
-model.uncertain.Nu = max(size(idx.InputNominal));
+model.uncertain.dims = struct(...
+  'input', input_dims, ...
+  'output', output_dims ...
+);
+
+% model.uncertain.dims.OutputError = sum([
+%   model.uncertain.dims.OutputErrorAlpha;
+%   model.uncertain.dims.OutputErrorOmega;
+%   model.uncertain.dims.OutputErrorPosition;
+%   model.uncertain.dims.OutputErrorVelocity;
+%   model.uncertain.dims.OutputErrorEulerAngles;
+% ]);
+% 
+% model.uncertain.dims.OutputNominal; = sum([
+%   model.uncertain.dims.OutputNominalPosition;
+%   model.uncertain.dims.OutputNominalVelocity;
+%   model.uncertain.dims.OutputNominalEulerAngles;
+%   model.uncertain.dims.OutputNominalAngularRates;
+% ]);
+% 
+% model.uncertain.dims.OutputPlot = sum([
+%   model.uncertain.dims.OutputPlotAlpha;
+%   model.uncertain.dims.OutputPlotOmega;
+%   model.uncertain.dims.OutputPlotReference;
+% ]);
+
+% Create indices
+model.uncertain.index = struct();
+
+% Create indices for inputs
+start = 1; fields = fieldnames(input_dims);
+for f = fields'
+  dim = getfield(input_dims, f{1});
+  i = (start:(start + dim - 1))';
+  model.uncertain.index = setfield(model.uncertain.index, f{1}, i);
+  start = start + dim;
+end
+
+% Create indices for output
+start = 1; fields = fieldnames(output_dims);
+for f = fields'
+  dim = getfield(output_dims, f{1});
+  i = (start:(start + dim - 1))';
+  model.uncertain.index = setfield(model.uncertain.index, f{1}, i);
+  start = start + dim;
+end
+
+% Create groups of inputs
+model.uncertain.index.InputUncertain = [
+  model.uncertain.index.InputUncertainAlpha;
+  model.uncertain.index.InputUncertainOmega;
+  model.uncertain.index.InputUncertainState;
+];
+
+model.uncertain.index.InputDisturbance = [
+  model.uncertain.index.InputDisturbanceWind;
+  model.uncertain.index.InputDisturbanceAlpha;
+];
+
+model.uncertain.index.InputExogenous = [
+  model.uncertain.index.InputDisturbance;
+  model.uncertain.index.InputReference;
+];
+
+model.uncertain.index.InputNominal = [
+  model.uncertain.index.InputNominalAlpha;
+  model.uncertain.index.InputNominalOmega;
+];
 
-model.uncertain.Nr = max(size(idx.InputReference));
+% Create groups of outputs
+model.uncertain.index.OutputUncertain = [
+  model.uncertain.index.OutputUncertainAlpha;
+  model.uncertain.index.OutputUncertainOmega;
+  model.uncertain.index.OutputUncertainState;
+];
+
+model.uncertain.index.OutputError = [
+  model.uncertain.index.OutputErrorAlpha;
+  model.uncertain.index.OutputErrorOmega;
+  model.uncertain.index.OutputErrorPosition;
+  model.uncertain.index.OutputErrorVelocity;
+  model.uncertain.index.OutputErrorEulerAngles;
+];
+
+model.uncertain.index.OutputNominal = [
+  model.uncertain.index.OutputNominalPosition;
+  model.uncertain.index.OutputNominalVelocity;
+  model.uncertain.index.OutputNominalEulerAngles;
+  model.uncertain.index.OutputNominalAngularRates;
+];
+
+model.uncertain.index.OutputPlot = [
+  model.uncertain.index.OutputPlotAlpha;
+  model.uncertain.index.OutputPlotOmega;
+  model.uncertain.index.OutputPlotReference;
+];
+
+% Number of inputs
+model.uncertain.Nv = max(size(model.uncertain.index.InputUncertain));
+model.uncertain.Nw = max(size(model.uncertain.index.InputExogenous));
+model.uncertain.Nu = max(size(model.uncertain.index.InputNominal));
+model.uncertain.Nr = max(size(model.uncertain.index.InputReference));
 % size of noise is (Nw - Nr)
 
 % Number of outputs
-model.uncertain.Nz = max(size(idx.OutputUncertain));
-model.uncertain.Ne = max(size(idx.OutputError));
-model.uncertain.Ny = max(size(idx.OutputNominal));
+model.uncertain.Nz = max(size(model.uncertain.index.OutputUncertain));
+model.uncertain.Ne = max(size(model.uncertain.index.OutputError));
+model.uncertain.Ny = max(size(model.uncertain.index.OutputNominal));
+
+% ------------------------------------------------------------------------
+% Scaling matrices for uncertain model
+
+% Note howerver that the uncertain (SIMULINK) model is entirely described in
+% normalized coordinates, so these matrices are to scale the result e.g. for
+% plotting.
+
+% Input matrices scale DOWN TO range (-1, 1)
+% Output matrices scale UP FROM range (-1, 1)
+
+model.uncertain.scaling = struct();
+
+% TODO Scale for 'v' inputs if necessary
+
+% Scale for 'w' inputs
+model.uncertain.scaling.InputUncertainScaling = blkdiag(...
+  S_actuators, ...
+  S_positions ... % reference use same as position
+);
+
+% Scale for 'u' inputs
+model.uncertain.scaling.InputNominalScaling = S_actuators;
+
+% TODO Scale for 'z' outputs if necessary
+
+% Scale for 'e' outputs
+model.uncertain.scaling.OutputErrorScaling = blkdiag(...
+  S_actuators, ...
+  S_positions, ...
+  S_velocities, ...
+  S_angles ...
+);
+
+% Scale for 'y' outputs
+model.uncertain.scaling.OutputNominalScaling = S_state;
+
+% Scale for 'p' outputs
+model.uncertain.scaling.OutputPlotScaling = blkdiag(...
+  S_actuators, ...
+  S_positions, ... % reference same as position
+  S_positions ...
+);
 
 % ------------------------------------------------------------------------
 % Check properties of uncertain model
@@ -408,7 +559,7 @@ fprintf('   %d controllable modes, %d unstabilizable modes.\n', nctrb, nustab);
 fprintf('   %d observable modes, %d undetectable modes.\n', nobsv, nustab);
 
 if nctrb < 12
-  error('Uncertain model has less than 12 controllable modes!');
+  fprintf('   Uncertain model has less than 12 controllable modes!');
 end
 
 if nustab > 0 || nudetb > 0
@@ -424,8 +575,14 @@ A = model.uncertain.StateSpace(...
 
 [~, ~, ~, nustab, ~, nudetb] = pbhtest(A);
 
-if nustab > 0 || nudetb > 0
-  fprintf('   Uncertain system has undetectable or uncontrollable A.\n');
+if nustab > 0
+  fprintf('   Uncertain system has unstabilizable A! (%d modes)\n', ...
+    nustab);
+end
+
+if nudetb > 0
+  fprintf('   Uncertain system has undetectable A! (%d modes)\n', ...
+    nudetb);
 end
 
 B_u = model.uncertain.StateSpace(...
@@ -435,8 +592,14 @@ B_u = model.uncertain.StateSpace(...
 
 [~, ~, ~, nustab, ~, nudetb] = pbhtest(B_u);
 
-if nustab > 0 || nudetb > 0
-  fprintf('   Uncertain system has undetectable or uncontrollable Bu.\n');
+if nustab > 0
+    fprintf('   Uncertain system has unstabilizable Bu! (%d modes)\n', ...
+      nustab);
+end
+
+if nudetb > 0
+  fprintf('   Uncertain system has undetectable Bu! (%d modes)\n', ...
+    nudetb);
 end
 
 C_y = model.uncertain.StateSpace(...
@@ -446,8 +609,12 @@ C_y = model.uncertain.StateSpace(...
 
 [~, ~, ~, nustab, ~, nudetb] = pbhtest(C_y);
 
-if nustab > 0 || nudetb > 0
-  fprintf('   Uncertain system has undetectable or uncontrollable Cy.\n');
+if nustab > 0
+    fprintf('   Uncertain system has unstabilizable Cy!\n');
+end
+
+if nudetb > 0
+  fprintf('   Uncertain system has undetectable Cy!\n');
 end
 
 
@@ -474,8 +641,4 @@ end
 
 end
 
-function [indices] = make_idx(start, size)
-  indices = (start:(start + size - 1))';
-end
-
 % vim: ts=2 sw=2 et:
diff --git a/uav_model_uncertain.slx b/uav_model_uncertain.slx
index fd94980..6f1b050 100644
Binary files a/uav_model_uncertain.slx and b/uav_model_uncertain.slx differ
diff --git a/uav_model_uncertain_clp.slx b/uav_model_uncertain_clp.slx
index 36be668..10df3a6 100644
Binary files a/uav_model_uncertain_clp.slx and b/uav_model_uncertain_clp.slx differ
diff --git a/uav_params.m b/uav_params.m
index fdd519a..2ac187e 100644
--- a/uav_params.m
+++ b/uav_params.m
@@ -35,7 +35,7 @@ J = [
 ] * 1e-9;
 
 % Approximate propeller with a disk
-m_prop = 200e-3; % mass of propeller
+m_prop = 500e-3; % mass of propeller
 r_prop = 85e-3; % radius of propeller
 J_prop = .5 * m_prop * r_prop^2;
 
@@ -46,26 +46,23 @@ params.mechanical = struct(...
   'FlapZDistance', 98e-3, ... % flap distance along z from center of mass
   'InertiaTensor', J, ...
   'GyroscopicInertiaZ', J_prop, ... % assume small angle
-  'GyroscopicInertiaZUncertainty', .01 ... % in %
+  'GyroscopicInertiaZUncertainty', .05 ... % in %
 );
 
 % ------------------------------------------------------------------------
 % Actuator limits and measurements
 
 % Servos usually give a "speed" in seconds / 60 degrees without load
-servo_speed = 0.13; % seconds / 60 deg
-servo_angular_velocity = (60 / servo_speed) * (pi / 180); % rad / s
-
 params.actuators = struct(...
   'PropellerMaxAngularVelocity', 620.7, ... % in rad / s
   'ServoAbsMaxAngle', 20 * pi / 180, ... % in radians
   'ServoMaxTorque', 4.3 * 1e-2, ... % in kg / m
-  'ServoNominalAngularVelocity', servo_angular_velocity ...
+  'ServoSecondsTo60Deg', 0.13 ... % in s / 60 deg
 );
 
 % IMU runs in NDOF mode
 params.measurements = struct(...
-  'SensorFusionDelay', 30e-3, ... % in s
+  'SensorFusionDelay', 10e-3, ... % in s, from 100 Hz sampling rate
   'LIDARAccuracy', 10e-3, ... % in m
   'LIDARMaxDistance', 40, ... % inm
   'LIDARBandwidth', 100, ... % in Hz for z position
@@ -91,13 +88,13 @@ k_T = F_max / omega_max^2;
 % https://scienceworld.wolfram.com/physics/LiftCoefficient.html
 params.aerodynamics = struct(...
   'ThrustOmegaProp', k_T, ... % in s^2 / (rad * N)
-  'ThrustOmegaPropUncertainty', .01, ... % in %
+  'ThrustOmegaPropUncertainty', .05, ... % in %
   'FlapArea', 23e-3 * 10e-3, ... % in m^2
-  'FlapAreaUncertainty', .02, ... % in %
+  'FlapAreaUncertainty', .1, ... % in %
   'DragCoefficients', [0, 0], ... % TODO
   'DragCoefficientsUncertainties', [.1, .1], ... % in %
   'LiftCoefficient', 2 * pi, ... % TODO
-  'LiftCoefficientUncertainty', .05 ...% in %
+  'LiftCoefficientUncertainty', .1 ...% in %
 );
 
 
@@ -126,14 +123,16 @@ params.linearization = struct(...
 % Normalization (maximum values)
 
 params.normalization = struct(...
-  'HPosition', 1, ... % m
-  'VPosition', 1, ...% m
-  'HSpeed', 2, ... % m / s
-  'VSpeed', 2, ... % m / s
+  'FlapAngle', params.actuators.ServoAbsMaxAngle, ...
+  'ThrustAngularVelocity', params.actuators.PropellerMaxAngularVelocity, ...
+  'HPosition', .5, ... % m
+  'VPosition', .5, ...% m
+  'HSpeed', .5, ... % m / s
+  'VSpeed', .5, ... % m / s
   'PitchRollAngle', 10 * pi / 180, ... % rad
   'YawAngle', 5 * pi / 180, ... % rad
-  'AngularRate', 2 * pi / 180, ... % rad / s
-  'WindSpeed', 2 ... % m / s
+  'AngularRate', 1 * pi / 180, ... % rad / s
+  'WindSpeed', .01 ... % m / s
 );
 end
 % vim: ts=2 sw=2 et:
diff --git a/uav_performance_hinf.m b/uav_performance_hinf.m
index ba2fd3b..4a6de8a 100644
--- a/uav_performance_hinf.m
+++ b/uav_performance_hinf.m
@@ -19,41 +19,43 @@ function [perf] = uav_performance_hinf(params, do_plots)
 s = tf('s');
 
 % Bandwitdhs
-bw_alpha = .7 * params.actuators.ServoNominalAngularVelocity;
-bw_omega = 8;
+T_alpha = params.actuators.ServoSecondsTo60Deg;
+T_omega = 0.1;
 
-bw_xy = .1;
-bw_z = .4;
+T_xy = 5;
+T_z = 10;
 
-bw_xydot = .5;
-bw_zdot = .1;
+% Inverse performance functions
+W_Palpha = make_weight(1/T_alpha, 10, 2);
+W_Pomega = make_weight(1/T_omega, 10, 2);
 
-bw_phitheta = bw_xy;
-bw_psi = .08;
+W_Rxy = 1 / ((s * T_xy)^2 + 2 * T_xy * .8 * s + 1);
+W_Rz = 1 / (s * T_z + 1);
 
-% Inverse performance functions
-W_Palpha = .25 / (s / bw_alpha + 1); 
-W_Pomega = .1 / (s / bw_omega + 1);
+W_Pxy = tf(5);
+W_Pz = tf(1);
 
-W_Pxy = 5 * bw_xy^2 / (s^2 + 2 * .9 * bw_xy * s + bw_xy^2);
-W_Pz = bw_z^2 / (s^2  + 2 * 1 * bw_z * s + bw_z^2);
+W_Pxydot = tf(.01);
+W_Pzdot = tf(.01);
 
-W_Pxydot = tf(.1); % .2 / (s / bw_xydot + 1);
-W_Pzdot = tf(.1); % .5 / (s / bw_zdot + 1);
- 
-W_Pphitheta = .01 / (s / bw_phitheta + 1);
-W_Ppsi = tf(.1); % .1 / (s / bw_psi + 1);
+W_Pphitheta = .001 / (s * T_xy + 1);
+W_Ppsi = tf(.1);
 
 % Construct performance vector by combining xy and z
+W_ref = blkdiag(W_Rxy * eye(2), W_Rz);
+
 W_PP = blkdiag(W_Pxy * eye(2), W_Pz);
 W_PPdot = blkdiag(W_Pxydot * eye(2), W_Pzdot);
 W_PTheta = blkdiag(W_Pphitheta * eye(2), W_Ppsi);
+
 perf = struct(...
   'FlapAngle', W_Palpha * eye(4), ...
   'Thrust', W_Pomega, ...
+  'ReferenceFilter', W_ref, ...
   'Position', W_PP, ...
   'Velocity', W_PPdot, ...
-  'Angles', W_PTheta);
+  'Angles', W_PTheta ...
+);
 
 if do_plots
   % Bode plots of performance requirements
@@ -61,6 +63,8 @@ if do_plots
 
   bodemag(W_Palpha);
   bodemag(W_Pomega);
+  bodemag(W_Rxy);
+  bodemag(W_Rz);
   bodemag(W_Pxy);
   bodemag(W_Pz);
   bodemag(W_Pxydot);
@@ -70,6 +74,7 @@ if do_plots
 
   grid on;
   legend('$W_{P,\alpha}$', '$W_{P,\omega}$', ...
+    '$W_{R,xy}$', '$W_{R,z}$', ...
     '$W_{P,xy}$', '$W_{P,z}$', ...
     '$W_{P,\dot{x}\dot{y}}$', '$W_{P,\dot{z}}$', ...
     '$W_{P,\phi\theta}$', '$W_{P,\psi}$', ...
@@ -80,11 +85,13 @@ if do_plots
   % Step response of position requirements
   figure; hold on;
   step(W_Pxy); step(W_Pz);
+  step(W_Rxy); step(W_Rz);
   step(W_Pxydot); step(W_Pzdot);
   step(W_Palpha);
   step(W_Pomega);
   grid on;
   legend('$W_{P,xy}$', '$W_{P,z}$', ...
+    '$W_{R,xy}$', '$W_{R,z}$', ...
     '$W_{P,\dot{x}\dot{y}}$', '$W_{P,\dot{z}}$', ...
     '$W_{P,\alpha}$', '$W_{P,\omega}$', ...
     'interpreter', 'latex', 'fontSize', 8);
diff --git a/uav_performance_musyn.m b/uav_performance_musyn.m
index 57237fa..5ad5fa5 100644
--- a/uav_performance_musyn.m
+++ b/uav_performance_musyn.m
@@ -19,32 +19,32 @@ function [perf] = uav_performance_musyn(params, do_plots)
 s = tf('s');
 
 % Bandwitdhs
-bw_alpha = .7 * params.actuators.ServoNominalAngularVelocity;
-bw_omega = 8;
+T_alpha = params.actuators.ServoSecondsTo60Deg;
+T_omega = 0.1;
 
-bw_xy = .1;
-bw_z = .4;
+T_xy = 8;
+T_z = 10;
 
-bw_xydot = .5;
-bw_zdot = .1;
+% Inverse performance functions
 
-bw_phitheta = bw_xy;
-bw_psi = .08;
+W_Palpha = make_weight(1/T_alpha, 10, 4);
+W_Pomega = make_weight(1/T_omega, 10, 2);
 
-% Inverse performance functions
-W_Palpha = .25 / (s / bw_alpha + 1); 
-W_Pomega = .1 / (s / bw_omega + 1);
+W_Rxy = 1 / ((s * T_xy)^2 + 2 * T_xy * .8 * s + 1);
+W_Rz = 1 / (s * T_z + 1);
+
+W_Pxy = tf(5);
+W_Pz = tf(1);
 
-W_Pxy = 2 * bw_xy^2 / (s^2 + 2 * .9 * bw_xy * s + bw_xy^2);
-W_Pz = bw_z^2 / (s^2  + 2 * 1 * bw_z * s + bw_z^2);
+W_Pxydot = tf(.01);
+W_Pzdot = tf(.1);
 
-W_Pxydot = tf(.1); % .2 / (s / bw_xydot + 1);
-W_Pzdot = tf(.1); % .5 / (s / bw_zdot + 1);
- 
-W_Pphitheta = .01 / (s / bw_phitheta + 1);
-W_Ppsi = tf(.1); % .1 / (s / bw_psi + 1);
+W_Pphitheta = .001 / (s * T_xy + 1);
+W_Ppsi = tf(.1);
 
 % Construct performance vector by combining xy and z
+W_ref = blkdiag(W_Rxy * eye(2), W_Rz);
+
 W_PP = blkdiag(W_Pxy * eye(2), W_Pz);
 W_PPdot = blkdiag(W_Pxydot * eye(2), W_Pzdot);
 W_PTheta = blkdiag(W_Pphitheta * eye(2), W_Ppsi);
@@ -52,6 +52,7 @@ W_PTheta = blkdiag(W_Pphitheta * eye(2), W_Ppsi);
 perf = struct(...
   'FlapAngle', W_Palpha * eye(4), ...
   'Thrust', W_Pomega, ...
+  'ReferenceFilter', W_ref, ...
   'Position', W_PP, ...
   'Velocity', W_PPdot, ...
   'Angles', W_PTheta);
@@ -99,5 +100,25 @@ if do_plots
   title('Step responses of performance requirements');
 end
 
+end
+
+% Make a n-order performance weight function
+%
+% Arguments:
+%   OMEGA  Cutting frequency (-3dB)
+%   A      Magnitude at DC, i.e. |Wp(0)|
+%   M      Magnitude at infinity, i.e. |Wp(inf)|
+%   ORD    Order
+function [Wp] = make_weight(omega, A, M, ord)
+
+if nargin > 3
+  n = ord;
+else
+  n = 1;
+end
+
+s = tf('s');
+Wp = (s / (M^(1/n)) + omega)^n / (s + omega * A^(1/n))^n;
+
 end
 % vim: ts=2 sw=2 et:
diff --git a/uav_sim_step.m b/uav_sim_step.m
index 82c05e9..65fb36f 100644
--- a/uav_sim_step.m
+++ b/uav_sim_step.m
@@ -3,127 +3,141 @@
 % Copyright (C) 2024, Naoki Sean Pross, ETH Zürich
 % This work is distributed under a permissive license, see LICENSE.txt
 
-function [simout] = uav_sim_step_musyn(params, model, ctrl, nsamp, T, do_plots, do_noise)
+function [simout] = uav_sim_step(params, model, ctrl, uncert, nsamp, T, do_plots, do_noise)
 
 % Load closed loop model and add controller
 % more or less equivalent to doing usys = lft(Pnom, K)
 h = load_system('uav_model_uncertain_clp');
 hws = get_param('uav_model_uncertain_clp', 'modelworkspace');
-hws.assignin('K', ctrl.K);
 
-ulmod_clp = linmod('uav_model_uncertain_clp');
-usys_clp = ss(ulmod_clp.a, ulmod_clp.b, ulmod_clp.c, ulmod_clp.d);
+if isfield(ctrl, 'K')
+  hws.assignin('K', ctrl.K);
+else
+  error('You need to provide a controller ctrl.K');
+end
 
-% Take dynamics without uncertainty
-% Also nominal ouput to make plots
-P_nom_clp = minreal(usys_clp(...
-  [model.uncertain.index.OutputError; 
-    model.uncertain.index.OutputNominal;
-    model.uncertain.index.OutputPlots], ...
-  model.uncertain.index.InputExogenous), [], false);
+% There is an uncertainty block
+if isfield(uncert, 'Delta')
+  hws.assignin('Delta', uncert.Delta);
+else
+  fprintf(' - No uncertainty error Delta was provided, setting to zero.\n');
+  Delta = tf(0) * zeros(sum(model.uncertain.BlockStructure));
+  hws.assignin('Delta', Delta);
+end
 
-% Check that closed loop is actually stable
-eigvals = eig(P_nom_clp.A);
-nx = size(P_nom_clp.A, 1);
+ulmod_clp = linmod('uav_model_uncertain_clp');
+P_clp = minreal(ss(ulmod_clp.a, ulmod_clp.b, ulmod_clp.c, ulmod_clp.d), [], false);
 
-for i = 1:nx
-  if real(eigvals(i)) >= 0
+% Check that closed loop is actually stable
+nx = size(P_clp.A, 1);
+fprintf(' - Closed loop dynamics have %d states\n', nx);
+if nx < 60
+  [~, nsta, nctrb, nustab, nobsv, nudetb] = pbhtest(P_clp);
+  if nx ~= nsta
     error('Closed loop is not stable!');
   end
-end
-
-% Indices for exogenous inputs
-Iwwind = (1:3)';
-Iwalpha = (4:7)';
-Ir = (8:10)';
 
-% Indices for outputs
-Iealpha = (1:4)';
-Ieomega = 5;
-IeP = (6:8)';
-IePdot = (9:11)';
-IeTheta = (12:14)';
-
-% Indices for y outputs (for plots)
-IP = (15:17)';
-IPdot = (18:20)';
-ITheta = (21:23)';
-IOmega = (24:26)';
+  fprintf('      %d stable modes, %d unstable modes\n', nsta, nx - nsta);
+  fprintf('      %d controllable modes, %d unstabilizable\n', nctrb, nustab);
+  fprintf('      %d observable modes, %d undetectable\n', nobsv, nudetb);
+else
+  eigvals = eig(P_clp.A);
+  for i = 1:nx
+    if real(eigvals(i)) >= 0
+      error('Closed loop is not stable!');
+    end
+  end
+end
 
-% Indices for p outputs (for plots)
-Ialpha = (27:30)';
-Iomega = 31;
+% Generate indices for closed loop plant
+o = model.uncertain.dims.output;
+i = model.uncertain.dims.input;
+
+dims = struct(...
+  'ErrorAlpha', o.OutputErrorAlpha, ...
+  'ErrorOmega', o.OutputErrorOmega, ...
+  'ErrorPosition', o.OutputErrorPosition, ...
+  'ErrorVelocity', o.OutputErrorVelocity, ...
+  'ErrorEulerAngles', o.OutputErrorEulerAngles, ...
+  'Position', o.OutputNominalPosition, ...
+  'Velocity', o.OutputNominalVelocity, ...
+  'EulerAngles', o.OutputNominalEulerAngles, ...
+  'AngularRates', o.OutputNominalAngularRates, ...
+  'PlotAlpha', o.OutputPlotAlpha, ...
+  'PlotOmega', o.OutputPlotOmega, ...
+  'PlotReference', o.OutputPlotReference, ...
+  'PlotPosition', o.OutputPlotPosition, ...
+  'InputAlpha', i.InputNominalAlpha, ...
+  'InputOmega', i.InputNominalOmega ...
+);
+
+idx = struct();
+start = 1; fields = fieldnames(dims);
+for f = fields'
+  dim = getfield(dims, f{1});
+  i = (start:(start + dim - 1))';
+  idx = setfield(idx, f{1}, i);
+  start = start + dim;
+end
 
-Iualpha = (32:35)';
-Iuomega = 36;
+% Input indices
+idx.InputDisturbanceWind = (1:3)';
+idx.InputDisturbanceFlaps = (4:7)';
+idx.InputReference = (8:10)';
 
-noise = zeros(7, nsamp); % no noise
+% Create noise
+noise = zeros(7, nsamp);
 if do_noise
-  % Noise in percentage
+  % Noise (normalized)
   noise_alpha_amp = (.5 * (pi / 180)) / params.actuators.ServoAbsMaxAngle;
-  noise_wind_amp = .1;
+  noise_wind_amp = .01;
   noise = [noise_wind_amp * randn(3, nsamp);
     noise_alpha_amp * randn(4, nsamp)];
 end
 
+% Step size
+step_size_h = .5  / params.normalization.HPosition;
+step_size_v = .5  / params.normalization.VPosition;
+
 % Create step inputs (normalized)
-ref_step = .5 * ones(1, nsamp); % 1d step function
+ref_step = ones(1, nsamp); % 1d step function
 
-in_step_x = [ noise; ref_step; zeros(2, nsamp) ];
-in_step_y = [ noise; zeros(1, nsamp); ref_step; zeros(1, nsamp) ];
-in_step_z = [ noise; zeros(2, nsamp); ref_step ];
+in_step_x = [ noise; step_size_h * ref_step; zeros(2, nsamp) ];
+in_step_y = [ noise; zeros(1, nsamp); step_size_h * ref_step; zeros(1, nsamp) ];
+in_step_z = [ noise; zeros(2, nsamp); step_size_v * ref_step ];
 
 % Simulation time
-n_settle_times = 10;
 t = linspace(0, T, nsamp);
 
+% Scale simulation outputs
+S = blkdiag(...
+  model.uncertain.scaling.OutputErrorScaling, ...
+  model.uncertain.scaling.OutputNominalScaling, ...
+  model.uncertain.scaling.OutputPlotScaling, ...
+  model.uncertain.scaling.InputNominalScaling ...
+);
+
 % Simulate step responses
-out_step_x_norm = lsim(P_nom_clp, in_step_x, t);
-out_step_y_norm = lsim(P_nom_clp, in_step_y, t);
-out_step_z_norm = lsim(P_nom_clp, in_step_z, t);
-
-% Scale outputs
-S_actuators = blkdiag(...
-  eye(4) * params.actuators.ServoAbsMaxAngle, ...
-  eye(1) * params.actuators.PropellerMaxAngularVelocity);
-
-S_state = blkdiag(...
-  eye(2) * params.normalization.HPosition, ...
-  eye(1) * params.normalization.VPosition, ...
-  eye(2) * params.normalization.HSpeed, ...
-  eye(1) * params.normalization.VSpeed, ...
-  eye(2) * params.normalization.PitchRollAngle, ...
-  eye(1) * params.normalization.YawAngle, ...
-  eye(3) * params.normalization.AngularRate);
-
-S = blkdiag(S_actuators, S_state(1:9, 1:9), S_state, S_actuators, S_actuators);
-
-out_step_x = out_step_x_norm * S';
-out_step_y = out_step_y_norm * S';
-out_step_z = out_step_z_norm * S';
+out_step_x_norm = lsim(P_clp, in_step_x, t, 'foh');
+out_step_y_norm = lsim(P_clp, in_step_y, t, 'foh');
+out_step_z_norm = lsim(P_clp, in_step_z, t, 'foh');
+
+out_step_x = out_step_x_norm * S;
+out_step_y = out_step_y_norm * S;
+out_step_z = out_step_z_norm * S;
 
 % Return simulation
 simout = struct(...
   'Time', t, ...
-  'StepX', out_step_x_norm, ...
-  'StepY', out_step_y_norm, ...
-  'StepZ', out_step_z_norm, ...
+  'StepX', out_step_x, ...
+  'StepY', out_step_y, ...
+  'StepZ', out_step_z, ...
+  'StepXNorm', out_step_x_norm, ...
+  'StepYNorm', out_step_y_norm, ...
+  'StepZNorm', out_step_z_norm, ...
   'Simulink', ulmod_clp, ...
-  'StateSpace', P_nom_clp);
-
-simout.index = struct(...
-  'ErrorFlapAngles', Iealpha, ...
-  'ErrorThrust', Ieomega , ...
-  'ErrorPos', IeP, ...
-  'ErrorVel', IePdot, ...
-  'ErrorAngles', IeTheta, ...
-  'Position', IP, ...
-  'Velocity', IPdot, ...
-  'Angles', ITheta, ...
-  'FlapAngles', Ialpha, ...
-  'Thruster', Iomega, ...
-  'InputFlapAngles', Iualpha, ...
-  'InputThruster', Iuomega);
+  'StateSpace', P_clp, ...
+  'index', idx);
 
 if do_plots
   % Conversion factors
@@ -137,7 +151,6 @@ if do_plots
     ctrl.Name), 'Interpreter', 'latex');
 
   % Plot limits
-  ref_value     = .5;
   alpha_max_deg = params.actuators.ServoAbsMaxAngle * to_deg;
   euler_lim_deg = 1.5;
   omega_max_rpm = (params.actuators.PropellerMaxAngularVelocity ...
@@ -147,10 +160,10 @@ if do_plots
   % Plot step response from x to alpha
   subplot(2, 3, 1);
   hold on;
-  plot(t, out_step_x(:, Ialpha(1)) * to_deg);
-  plot(t, out_step_x(:, Ialpha(2)) * to_deg);
-  plot(t, out_step_x(:, Ialpha(3)) * to_deg);
-  plot(t, out_step_x(:, Ialpha(4)) * to_deg);
+  plot(t, out_step_x(:, idx.PlotAlpha(1)) * to_deg);
+  plot(t, out_step_x(:, idx.PlotAlpha(2)) * to_deg);
+  plot(t, out_step_x(:, idx.PlotAlpha(3)) * to_deg);
+  plot(t, out_step_x(:, idx.PlotAlpha(4)) * to_deg);
   plot([0, T], [1, 1] * alpha_max_deg, 'r--');
   plot([0, T], [-1, -1] * alpha_max_deg, 'r--');
   grid on;
@@ -164,10 +177,10 @@ if do_plots
 
   % Plot step response from y to alpha
   subplot(2, 3, 2); hold on;
-  plot(t, out_step_y(:, Ialpha(1)) * to_deg);
-  plot(t, out_step_y(:, Ialpha(2)) * to_deg);
-  plot(t, out_step_y(:, Ialpha(3)) * to_deg);
-  plot(t, out_step_y(:, Ialpha(4)) * to_deg);
+  plot(t, out_step_y(:, idx.PlotAlpha(1)) * to_deg);
+  plot(t, out_step_y(:, idx.PlotAlpha(2)) * to_deg);
+  plot(t, out_step_y(:, idx.PlotAlpha(3)) * to_deg);
+  plot(t, out_step_y(:, idx.PlotAlpha(4)) * to_deg);
   plot([0, T], [1, 1] * alpha_max_deg, 'r--');
   plot([0, T], [-1, -1] * alpha_max_deg, 'r--');
   grid on;
@@ -181,10 +194,10 @@ if do_plots
 
   % Plot step response from z to alpha
   subplot(2, 3, 3); hold on;
-  plot(t, out_step_z(:, Ialpha(1)) * to_deg);
-  plot(t, out_step_z(:, Ialpha(2)) * to_deg);
-  plot(t, out_step_z(:, Ialpha(3)) * to_deg);
-  plot(t, out_step_z(:, Ialpha(4)) * to_deg);
+  plot(t, out_step_z(:, idx.PlotAlpha(1)) * to_deg);
+  plot(t, out_step_z(:, idx.PlotAlpha(2)) * to_deg);
+  plot(t, out_step_z(:, idx.PlotAlpha(3)) * to_deg);
+  plot(t, out_step_z(:, idx.PlotAlpha(4)) * to_deg);
   plot([0, T], [1, 1] * alpha_max_deg, 'r--');
   plot([0, T], [-1, -1] * alpha_max_deg, 'r--');
   grid on;
@@ -198,9 +211,9 @@ if do_plots
 
   % Plot step response from x to Theta
   subplot(2, 3, 4); hold on;
-  plot(t, out_step_x(:, ITheta(1)) * to_deg);
-  plot(t, out_step_x(:, ITheta(2)) * to_deg);
-  plot(t, out_step_x(:, ITheta(3)) * to_deg);
+  plot(t, out_step_x(:, idx.EulerAngles(1)) * to_deg);
+  plot(t, out_step_x(:, idx.EulerAngles(2)) * to_deg);
+  plot(t, out_step_x(:, idx.EulerAngles(3)) * to_deg);
   grid on;
   xlim([0, T]);
   ylim([-euler_lim_deg, euler_lim_deg]);
@@ -212,9 +225,9 @@ if do_plots
 
   % Plot step response from y to Theta
   subplot(2, 3, 5); hold on;
-  plot(t, out_step_y(:, ITheta(1)) * to_deg);
-  plot(t, out_step_y(:, ITheta(2)) * to_deg);
-  plot(t, out_step_y(:, ITheta(3)) * to_deg);
+  plot(t, out_step_y(:, idx.EulerAngles(1)) * to_deg);
+  plot(t, out_step_y(:, idx.EulerAngles(2)) * to_deg);
+  plot(t, out_step_y(:, idx.EulerAngles(3)) * to_deg);
   grid on;
   xlim([0, T]);
   ylim([-euler_lim_deg, euler_lim_deg]);
@@ -226,9 +239,9 @@ if do_plots
 
   % Plot step response from z to Theta
   subplot(2, 3, 6); hold on;
-  plot(t, out_step_z(:, ITheta(1)) * to_deg);
-  plot(t, out_step_z(:, ITheta(2)) * to_deg);
-  plot(t, out_step_z(:, ITheta(3)) * to_deg);
+  plot(t, out_step_z(:, idx.EulerAngles(1)) * to_deg);
+  plot(t, out_step_z(:, idx.EulerAngles(2)) * to_deg);
+  plot(t, out_step_z(:, idx.EulerAngles(3)) * to_deg);
   grid on;
   xlim([0, T]);
   ylim([-euler_lim_deg, euler_lim_deg]);
@@ -245,7 +258,7 @@ if do_plots
     ctrl.Name), 'Interpreter', 'latex');
 
   hold on;
-  step(P_nom_clp(Iomega, Ir(3)) * to_rpm, T);
+  step(P_clp(idx.PlotOmega, idx.InputReference(3)) * to_rpm, T);
   % plot([0, T], [1, 1] * omega_min_rpm, 'r--');
   % plot([0, T], [1, 1] * omega_max_rpm, 'r--');
   grid on;
@@ -263,20 +276,19 @@ if do_plots
 
   % Plot step response from horizontal reference to horizontal position
   subplot(2, 2, 1); hold on;
-  plot(t, out_step_x(:, IP(1)));
-  plot(t, out_step_y(:, IP(2)));
-  % plot([0, T], [1, 1] * ref_value, 'r:');
-  % plot(t, out_step_xydes, 'r--');
+  plot(t, out_step_x(:, idx.PlotPosition(1)));
+  plot(t, out_step_y(:, idx.PlotPosition(2)));
+  plot(t, out_step_x(:, idx.PlotReference(1)), '--');
   grid on;
-  title('Horizontal Position Error', 'Interpreter', 'latex');
-  ylabel('Error (meters)', 'Interpreter', 'latex');
+  title('Horizontal Position', 'Interpreter', 'latex');
+  ylabel('Distance (meters)', 'Interpreter', 'latex');
   xlabel('Time (seconds)', 'Interpreter', 'latex');
   legend('$x(t)$', '$y(t)$', 'Interpreter', 'latex');
 
   % Plot step response horizontal reference to horizontal speed
   subplot(2, 2, 2); hold on;
-  plot(t, out_step_x(:, IPdot(1)));
-  plot(t, out_step_y(:, IPdot(2)));
+  plot(t, out_step_x(:, idx.Velocity(1)));
+  plot(t, out_step_y(:, idx.Velocity(2)));
   grid on;
   title('Horizontal Velocity', 'Interpreter', 'latex');
   ylabel('Velocity (m / s)', 'Interpreter', 'latex');
@@ -285,18 +297,16 @@ if do_plots
 
   % Plot step response from vertical reference to vertical position
   subplot(2, 2, 3); hold on;
-  plot(t, out_step_z(:, IP(3)));
-  % plot([0, T], [1, 1] * ref_value, 'r:');
-  % plot(t, out_step_zdes, 'r--');
+  plot(t, out_step_z(:, idx.PlotPosition(3)));
   grid on;
-  title('Vertical Position Error', 'Interpreter', 'latex');
-  ylabel('Error (meters)', 'Interpreter', 'latex');
+  title('Vertical Position', 'Interpreter', 'latex');
+  ylabel('Distance (meters)', 'Interpreter', 'latex');
   xlabel('Time (seconds)', 'Interpreter', 'latex');
   legend('$z(t)$', 'Interpreter', 'latex');
 
   % Plot step response vertical reference to vertical speed
   subplot(2, 2, 4); hold on;
-  plot(t, out_step_z(:, IPdot(3)));
+  plot(t, out_step_z(:, idx.ErrorVelocity(3)));
   grid on;
   title('Vertical Velocity', 'Interpreter', 'latex');
   ylabel('Velocity (m / s)', 'Interpreter', 'latex');
diff --git a/uav_uncertainty.m b/uav_uncertainty.m
index 7cd7e38..03b7b3b 100644
--- a/uav_uncertainty.m
+++ b/uav_uncertainty.m
@@ -26,18 +26,18 @@ eps_d = params.aerodynamics.DragCoefficientsUncertainties(1);
 % eps_0 = params.aerodynamics.DragCoefficients(2);
 
 eps_omega = max(.5 * eps_T, eps_r);
-eps_alpha = max(eps_l + eps_S + 2 * eps_omega, eps_S + eps_d + eps_omega);
+eps_alpha = max(eps_l + eps_S + 2 * eps_omega, eps_S + eps_d + eps_omega)
 
 b = 12;
-G = make_weight(b, 2, .2);
-
-W_malpha = .1 * eps_alpha * G;
-W_momega = .1 * eps_omega * G;
-W_mState = blkdiag( ...
-  .05 * tf(1) * eye(3), ...
-  .01 * tf(1) * eye(3), ...
-  .001 * tf(1) * eye(3), ...
-  .05 * tf(1) * eye(3) ...
+T = 1;
+G = make_weight(b, 4, 1);
+
+W_malpha = eps_alpha * tf(1);
+W_momega = eps_omega * tf(1);
+W_mState = (1 - tf(1, [T, 1])) * blkdiag( ...
+  .2 * eye(3), ...
+  .1 * eye(3), ...
+  .2 * eye(3) ...
 );
 
 uncert = struct(...
@@ -56,12 +56,13 @@ if do_plots
   bodemag(W_mState(1,1));
   bodemag(W_mState(4,4));
   bodemag(W_mState(7,7));
-  bodemag(W_mState(11,11));
 
   grid on;
   legend('$W_{m,\alpha}$', '$W_{m,\omega}$', ...
-         '$W_{m,\mathbf{P}}$', '$W_{m,\mathbf{\dot{P}}}$', ...
-         '$W_{m,\mathbf{\Theta}}$', '$W_{m,\mathbf{\Omega}}$', ...
+         ... % '$W_{m,\mathbf{P}}$', ...
+         '$W_{m,\mathbf{\dot{P}}}$', ...
+         '$W_{m,\mathbf{\Theta}}$', ...
+         '$W_{m,\mathbf{\Omega}}$', ...
          'interpreter', 'latex')
   title('\bfseries Stability Requirement (only for $\mu$-Synthesis)', ...
         'interpreter', 'latex')
-- 
cgit v1.2.1