Skip to content

Commit cd1b6db

Browse files
committed
Initial commit
0 parents  commit cd1b6db

File tree

10 files changed

+682
-0
lines changed

10 files changed

+682
-0
lines changed

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Windows default autosave extension
2+
*.asv
3+
4+
# OSX / *nix default autosave extension
5+
*.m~
6+
7+
# Compiled MEX binaries (all platforms)
8+
*.mex*
9+
10+
# Packaged app and toolbox files
11+
*.mlappinstall
12+
*.mltbx
13+
14+
# Generated helpsearch folders
15+
helpsearch*/
16+
17+
# Simulink code generation folders
18+
slprj/
19+
sccprj/
20+
21+
# Matlab code generation folders
22+
codegen/
23+
24+
# Simulink autosave extension
25+
*.autosave
26+
27+
# Simulink cache files
28+
*.slxc
29+
30+
# Octave session info
31+
octave-workspace

conservatism.m

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
clear
2+
3+
addpath('graphs', 'performance')
4+
5+
%% Set up the problem
6+
kappa = 0.1;
7+
sysD = ss(1, 1, 1, 0);
8+
sysC = ss(-kappa, 0, 0, 0);
9+
10+
% Grid the probability axis. Higher density where larger changes are
11+
% expected
12+
p = [0, 1e-6, 1e-5, 1e-4, 1e-3:1e-3:9e-3, 0.01:0.01:0.29, 0.3:0.025:1];
13+
14+
% Calculate the norms for two examples, large and small networks
15+
h = [4, 50];
16+
N = h.*(h+1)/2;
17+
18+
%%
19+
[H, P] = meshgrid(h, p);
20+
sz = size(H);
21+
22+
% Pre-allocate storage
23+
norm_mean = NaN(length(p),length(h));
24+
norm_enum = NaN(length(p),length(h));
25+
norm_decom = NaN(length(p),length(h));
26+
27+
tic
28+
parfor i = 1:length(h)*length(p)
29+
L = full(laplace_matrix(triangle_graph(H(i))));
30+
31+
norm_mean(i) = performance_mean(sysD, sysC, L, P(i));
32+
norm_decom(i) = performance_decomposed(sysD, sysC, L, P(i));
33+
34+
% The enumerated norm is only calculated for the small network, since
35+
% its scalling exponentially and thus infeasible to calculate for the
36+
% large network.
37+
[~, j] = ind2sub(sz, i);
38+
if j == 1
39+
norm_enum(i) = performance_enumerated(sysD, sysC, L, P(i), true);
40+
end
41+
end
42+
toc
43+
44+
%% Plot data like in the paper
45+
figure(1)
46+
plot(p, norm_mean(:,1), 'k-.', p, norm_enum(:,1), 'k-', p, norm_decom(:,1), 'k--')
47+
ylim([0, 44])
48+
title('Small Network Performance')
49+
xlabel('Probability p')
50+
ylabel('H_2-norm')
51+
legend('Mean', 'Enumerated', 'Decomposed')
52+
53+
figure(2)
54+
semilogy(p, norm_mean(:,2), 'k-.', p, norm_decom(:,2), 'k--')
55+
ylim([3e1, 2e4])
56+
title('Large Network Performance')
57+
xlabel('Probability p')
58+
ylabel('H_2-norm')
59+
legend('Mean', 'Decomposed')
60+
61+
%% CSV Export
62+
p = p';
63+
small_mean = norm_mean(:,1);
64+
small_enum = norm_enum(:,1);
65+
small_decom = norm_decom(:,1);
66+
small_table = table(p, small_mean, small_enum, small_decom);
67+
68+
large_mean = norm_mean(:,2);
69+
large_decom = norm_decom(:,2);
70+
large_table = table(p, large_mean, large_decom);
71+
72+
name = sprintf('conservatism_small_%d.csv', uint32(posixtime(datetime())));
73+
writetable(small_table, name)
74+
name = sprintf('conservatism_large_%d.csv', uint32(posixtime(datetime())));
75+
writetable(large_table, name)

graphs/circular_graph.m

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
function G = circular_graph(nvert, nedge, directed)
2+
%CIRCULAR_GRAPH Generates a Matlab graph object that represents a circular
3+
%graph with a given numer ob vertices.
4+
% This function genenerates a Matlab graph object that contains a
5+
% circular graph with the given amount of vertices. It can be configured
6+
% if the connections should be directed or not and to how many of the
7+
% next vertices the connection should be established.
8+
%
9+
% Arguments:
10+
% nvert -> Number of vertices
11+
% nedge -> Number of forward edges per vertex
12+
% directed -> [optional] Directed edges or not
13+
14+
if nargin <= 1 || isempty(nedge)
15+
nedge = 1;
16+
elseif nedge >= nvert
17+
error('So many neighbours are not possible!')
18+
end
19+
if nargin <= 2
20+
directed = true;
21+
end
22+
23+
% Define closure that keeps the index in the ring [1, nvert]
24+
ring = @(i) mod(i-1, nvert) + 1;
25+
26+
%% Generate graph structure
27+
A = zeros(nvert);
28+
for i = 1:nvert
29+
A(i, ring(i+(1:nedge))) = 1;
30+
31+
% If not direction, also add the other direction
32+
if ~directed
33+
A(i, ring(i-(1:nedge))) = 1;
34+
end
35+
end
36+
37+
% G needs only to be a digraph if it is directed
38+
if directed
39+
G = digraph(A);
40+
else
41+
G = graph(A);
42+
end
43+
end

graphs/laplace_matrix.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function L = laplace_matrix(G)
2+
%LAPLACE_MATRIX Calculate the Laplacian of the given graph
3+
% This function is a replacement for the Matlab built-in laplacian()
4+
% function that also works for digraphs, unlike the Matlab version.
5+
6+
A = adjacency(G);
7+
D = diag(sum(A,2));
8+
L = D - A;
9+
end

graphs/triangle_graph.m

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
function G = triangle_graph(baselength)
2+
%TRIANGLE_GRAPH Generates a Matlab graph object that represents a
3+
%triangular graph.
4+
% This function generates a Matlab graph object that represents a
5+
% triangular graph. The generated graphs are always undirected and can be
6+
% configured only by their size, which is specified by giving the number
7+
% of vertices in the bottommost row.
8+
9+
%% Setup intermediate variables
10+
% Calculate total number of agents
11+
N = baselength * (baselength+1) / 2;
12+
13+
% Initial distributing algorithm for the first row
14+
row_start = 1; % Index of the first node of this row
15+
row_end = 1; % Index of the last node of this row
16+
row_no = 1; % Number of the row as counted from the top
17+
18+
%% Main algorithm
19+
A = zeros(N);
20+
21+
for i = 1:N
22+
% Check if node is on the bottom row, and if not, add two edges
23+
if row_no ~= baselength
24+
A(i, i+row_no) = 1;
25+
A(i, i+row_no+1) = 1;
26+
end
27+
28+
% Check if node is on the left edge of the triangle, if not, add two
29+
% edges
30+
if i ~= row_start
31+
A(i, i-row_no) = 1;
32+
A(i, i-1) = 1;
33+
end
34+
35+
% Check if node is on the right edge. If so, switch to the next row,
36+
% otherwise add two more edges
37+
if i ~= row_end
38+
A(i, i-row_no+1) = 1;
39+
A(i, i+1) = 1;
40+
else
41+
row_no = row_no + 1;
42+
row_start = row_end + 1;
43+
row_end = row_end + row_no;
44+
end
45+
end
46+
47+
G = graph(A);
48+
end
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
function [H2, Q, solver_stats] = performance_decomposed(sysD, sysC, L0, p)
2+
%PERFORMANCE_DECOMPOSED Calculate an upper bound on the H2-norm of a
3+
%decomposable jump system in a scalable manner
4+
% This function calculates an upper bound on the H2-norm of a
5+
% decomposable jump system in a scalable manner by applying a decoupled
6+
% analysis approach that is described in Theorem 7 of the accompanying
7+
% paper.
8+
%
9+
% Arguments:
10+
% sysD -> Decoupled part of the system
11+
% sysC -> Coupled part of the system
12+
% L0 -> Laplacian describing the nominal graph of the system
13+
% p -> Probability of a successful package transmission
14+
% Returns:
15+
% H2 -> Upper bound on the H2-norm of the system
16+
% Q -> Storage function matrix of the solution
17+
% solver_stats -> Timing information about the algorithm
18+
19+
% Setup the SDP solver
20+
% The offset is used to convert strict LMI into nonstrict ones by pushing
21+
% the solution away from the boundary.
22+
offset = 1e-8;
23+
opts = sdpsettings('verbose', 0);
24+
25+
% Allocate storage for time measurements
26+
solver_stats = struct;
27+
solver_stats.prep = NaN;
28+
solver_stats.solver = NaN;
29+
solver_stats.total = NaN;
30+
solver_stats.yalmip = NaN;
31+
32+
% Initialize return values with reasonable defaults
33+
H2 = inf;
34+
timer = tic;
35+
36+
%% Prepare the system description
37+
[Ac, Bc, Cc, Dc] = ssdata(sysC);
38+
[Ad, Bd, Cd, Dd] = ssdata(sysD);
39+
40+
nx = size(Ac,1);
41+
nu = size(Bc,2);
42+
N = size(L0,1);
43+
44+
lambda = eig(L0);
45+
46+
%% Solve the SDP from Theorem 7
47+
Q = sdpvar(nx, nx);
48+
Z = sdpvar(nu, nu, N-1);
49+
cost = 0;
50+
Constraints = Q >= offset * eye(nx);
51+
52+
% The eigenvalues of L0 are sorted by Matlab. By iteration only over 2:N,
53+
% we ignore lambda_1 = 0 and thus the uncontrollable and marginally stable
54+
% modal subsystem.
55+
for i = 2:N
56+
li = lambda(i);
57+
lit = p*(1-p)*li;
58+
Zi = Z(:,:,i-1);
59+
60+
Acl = Ad + p*li*Ac;
61+
Bcl = Bd + p*li*Bc;
62+
Ccl = Cd + p*li*Cc;
63+
Dcl = Dd + p*li*Dc;
64+
65+
LMI = Acl'*Q*Acl + Ccl'*Ccl - Q + 2*lit * (Ac'*Q*Ac + Cc'*Cc);
66+
TRC = Bcl'*Q*Bcl + Dcl'*Dcl - Zi + 2*lit * (Bc'*Q*Bc + Dc'*Dc);
67+
cost = cost + trace(Zi);
68+
69+
% For certain LMIs that are obviously infeasible, Yalmip will refuse to
70+
% construct the SDP and issue an error. This try catch will convert
71+
% that error into a warning so that we can successfully finish running
72+
% the remainder of the script.
73+
try
74+
Constraints = [ Constraints ,...
75+
LMI <= -offset * eye(size(LMI)) ,...
76+
TRC <= -offset * eye(size(TRC)) ];
77+
catch ME
78+
warning(ME.message)
79+
Q = [];
80+
return
81+
end
82+
end
83+
84+
solver_stats.prep = toc(timer);
85+
sol = optimize(Constraints, cost, opts);
86+
87+
if sol.problem ~= 0
88+
warning('YALMIP return an error: %s', sol.info)
89+
Q = [];
90+
else
91+
% We calculate gamma^2 with the LMI constraints, so we need to take the
92+
% square root here.
93+
H2 = sqrt(value(cost));
94+
95+
Q = value(Q);
96+
solver_stats.yalmip = sol.yalmiptime;
97+
solver_stats.solver = sol.solvertime;
98+
solver_stats.total = toc(timer);
99+
end
100+
end

0 commit comments

Comments
 (0)