-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.m
374 lines (281 loc) · 13.3 KB
/
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
%% Herding Behavior using Networked Control
% Jacob Kimball, Jonathan Zia
% 11/2019
load('net.mat')
% Define number of sheep and dogs
sheep = 5; dogs = 1; N = sheep + dogs;
% Define Robotarium object
r = Robotarium('NumberOfRobots', N, 'ShowFigure', true);
% Set path to save video
vidPath = 'robotarium_simulation.avi';
%% Define experimental parameters
% Define waypoints for the dogs
waypoints_dogs = [1.2; 0];
% Define waypoints for the sheep
waypoints_sheep = [-0.133 0.535;0.743 -0.207;0.234 0.329;0.219 -0.373;-0.287 -0.024]';
% Combine sheep and dog waypoints
waypoints = [waypoints_dogs waypoints_sheep];
% Set error tolerance for waypoint convergence
close_enough = 0.05;
% Set the maximum number of iterations
iterations = 1000;
% set skip parameter to help it run faster
skip = 10; % needs to be > 1 for this to work
% Specify delta disk
delta = sqrt(1.5^2 + 1);
% Set possible angles for dog trajectory
angles = [0 pi/4 pi/2 3*pi/4 pi 5*pi/4 3*pi/2, 7*pi/4];
% Set the dog and (maximum) sheep velocity
% dog_velocity = 0.5*r.max_linear_velocity;
% sheep_velocity = 0.25*r.max_linear_velocity;
dog_velocity = 0.75*r.max_linear_velocity;
sheep_velocity = 0.375*r.max_linear_velocity;
%% Initialize video recording
% Create video writer and define video quality and frame rate
vid = VideoWriter(vidPath); vid.Quality = 100; vid.FrameRate = 10; open(vid);
%% Initialize conversion tools from single-integrator to unicycle dynamics
% Create mapping functions
[~, uni_to_si_states] = create_si_to_uni_mapping();
si_to_uni_dyn = create_si_to_uni_dynamics_with_backwards_motion();
% Create barrier certificates
% uni_barrier_cert_boundary = create_uni_barrier_certificate_with_boundary('SafetyRadius', 0.06);
% Single-integrator position controller
controller = create_si_position_controller('XVelocityGain', 0.8, 'YVelocityGain', 0.8, 'VelocityMagnitudeLimit', 0.1);
% Initialize velocity vector for agents. Each agent expects a 2 x 1
% velocity vector containing the linear and angular velocity, respectively.
dxi = zeros(2, N);
% Initialize flag for stopping loop and counter
FLAG = true; counter = 1;
%% Initialize data to be plotted
% Plotting Initialization
% Color Vector for Plotting
% Note the Robotarium MATLAB instance runs in a docker container which will
% produce the same rng value every time unless seeded by the user.
CM = rand(N,3);%{'.-k','.-b','.-r','.-g','.-m','.-y','.-c'};
%Marker, font, and line sizes
marker_size_goal = determine_marker_size(r, 0.20);
font_size = determine_font_size(r, 0.05);
line_width = 5;
start_time = tic; %The start time to compute time elapsed.
for i = 1:N
% Initialize additional information plot here. Note the order of
% plotting matters, objects that are plotted later will appear over
% object plotted previously.
% Text for robot identification
robot_caption = sprintf('Robot %d', i);
% Text with goal identification
goal_caption = sprintf('G%d', i);
% Plot colored square for goal location.
d(i) = plot(waypoints(1, i), waypoints(2, i),'s','MarkerSize',marker_size_goal,'LineWidth',line_width,'Color',CM(i,:));
% Plot the goal identification text inside the goal location
goal_labels{i} = text(waypoints(1, i) - 0.05, waypoints(2, i), goal_caption, 'FontSize', font_size, 'FontWeight', 'bold');
% Plot the robot label text
robot_labels{i} = text(500, 500, robot_caption, 'FontSize', font_size, 'FontWeight', 'bold');
end
% Plot the iteration and time in the lower left. Note when run on your
% computer, this time is based on your computers simulation time. For a
% better approximation of experiment time on the Robotarium when running
% this simulation on your computer, multiply iteration by 0.033.
iteration_caption = sprintf('Iteration %d', 0);
time_caption = sprintf('Total Time Elapsed %0.2f', toc(start_time));
iteration_label = text(-1.5, -0.8, iteration_caption, 'FontSize', font_size, 'Color', 'r', 'FontWeight', 'bold');
time_label = text(-1.5, -0.9, time_caption, 'FontSize', font_size, 'Color', 'r', 'FontWeight', 'bold');
% We can change the order of plotting priority, we will plot goals on the
% bottom and have the iteration/time caption on top.
uistack([goal_labels{:}], 'bottom'); % Goal labels are at the very bottom, arrows are now only above the goal labels.
uistack(d, 'bottom');% Goal squares are at the very bottom, goal labels are above the squares and goal arrows are above those.
uistack([iteration_label], 'top'); % Iteration label is on top.
uistack([time_label], 'top'); % Time label is above iteration label.
x = r.get_poses(); r.step();
%% Send robots to waypoints
while FLAG
% Retrieve the most recent poses and convert to SI states
x = r.get_poses(); xi = uni_to_si_states(x);
% Trip the flag if it all agents are already in correct positions
distances = zeros(N, 1);
for i = 1:N; distances(i) = norm(x(1:2, i) - waypoints(:, i)); end
FLAG = false; for i = 1:N; if distances(i) > close_enough; FLAG = true; end; end
% Update plotting information and locations
for q = 1:N; robot_labels{q}.Position = x(1:2, q) + [-0.15;0.15]; end
% Define SI commands based on waypoints
for i = 1:N; dxi(:, i) = controller(x(1:2, i), waypoints(:, i)); end
% Avoid actuator error by thresholding output velocity
norms = arrayfun(@(x) norm(dxi(:, x)), 1:N);
threshold = 3/4*r.max_linear_velocity;
to_thresh = norms > threshold;
dxi(:, to_thresh) = threshold*dxi(:, to_thresh)./norms(to_thresh);
% Map SI to Uni dynamics and utilize barrier certificates
dxu = si_to_uni_dyn(dxi, x); % dxu = uni_barrier_cert_boundary(dxu, x);
% Set velocities and send velocities to agents
r.set_velocities(1:N, dxu); r.step();
% Increment the counter and determine whether to stop execution
counter = counter + 1; if counter > iterations; FLAG = false ;end
% Update Iteration and Time marker
iteration_caption = sprintf('Iteration %d', counter);
time_caption = sprintf('Total Time Elapsed %0.2f', toc(start_time));
iteration_label.String = iteration_caption;
time_label.String = time_caption;
% Add frame to video
if(mod(counter,skip) == 1); writeVideo(vid, getframe(gcf)); end
end
%%
% Reset counter and remove goal labels and boxes
counter = 1; d.delete; for i = 1:N; goal_labels{i}.delete; end
% Reset flag and reset number of iterations
FLAG = true; iterations = 2000;
% Plot initial lines on the figure
% Compute adjacency matrix
[~, A] = computeLaplacian(N, x, delta); c = 1;
% Plot lines
for i = 1:N
if A(1, i) == 1
lf{c} = line([x(1, 1), x(1, i)], [x(2, 1), x(2, i)], 'LineWidth', 1, 'Color', 'k');
c = c + 1;
end
end
%Iterate for the previously specified number of iterations
while FLAG
% Compute the delta disk graph laplacian
[L, A] = computeLaplacian(N, x, delta);
% Retrieve the most recent poses from the Robotarium. The time delay is
% approximately 0.033 seconds
x = r.get_poses();
% Convert to SI states
xi = uni_to_si_states(x);
%% Determine dog heading
% Get sheep/dog positions
sheep_positions = xi(:, dogs+1:end);
dog_position = xi(:, 1);
% For each dog...
for i = 1:dogs
if(mod(counter,skip) == 1)
% Choose an angle for the dog that maximizes the expected return (ER)
ER = zeros(size(angles)); % Initialize ER placeholder
% For each angle...
for angle = 1:length(angles)
% Move the dog by that angle
new_state = updateState(sheep_positions, dog_position, angles(angle), ...
dog_velocity, sheep_velocity, delta);
ER(angle) = net(new_state'); % Estimate ER for the angle
end; [B, I] = max(ER); dog_angle = angles(I);
end
% Return dog's heading
dxi(1, i) = dog_velocity*cos(dog_angle);
dxi(2, i) = dog_velocity*sin(dog_angle);
% Enforce boundaries
if abs(xi(1, i) + dxi(1, i)) > 1.5 && abs(xi(2, i) + dxi(2, i)) < 1.0; dxi(1, i) = 0; end
if abs(xi(2, i) + dxi(2, i)) > 1.0 && abs(xi(1, i) + dxi(1, i)) < 1.5; dxi(2, i) = 0; end
end
%% Update sheep headings
for i = 2:N
if(mod(counter,skip) == 1)
% Initialize the SI command for the agent
dxi(:, i) = [0; 0];
% Determine the topological neighbors of the agent
neighbors = topological_neighbors(L, i);
% For each neighbor...
for j = 1:length(neighbors)
% If the neighbor is a dog...
if neighbors(j) <= dogs
% Get the distance from the dog
dist = norm(xi(:, i) - xi(:, neighbors(j)));
% Compute the weight based on the distance
w = 1/dist^2;
% Compute update
dxi(:, i) = dxi(:, i) + w*(xi(:, i) - xi(:, neighbors(j)));
end
end
% Limit the sheep velocity
if norm(dxi(:, i)) > sheep_velocity
dxi(:, i) = sheep_velocity*dxi(:, i)/norm(dxi(:, i));
end
end
% Enforce boundaries
if abs(xi(1, i) + dxi(1, i)) > 1.5 && abs(xi(2, i) + dxi(2, i)) < 1.0; dxi(1, i) = 0; end
if abs(xi(2, i) + dxi(2, i)) > 1.0 && abs(xi(1, i) + dxi(1, i)) < 1.5; dxi(2, i) = 0; end
if abs(xi(2, i) + dxi(2, i)) > 1.0 && abs(xi(1, i) + dxi(1, i)) > 1.5
dxi(2, i) = 0; dxi(1, i) = 0;
end
end
% Threshold dxi to avoid errors
norms = arrayfun(@(x) norm(dxi(:, x)), 1:N);
threshold = 3/4*r.max_linear_velocity;
to_thresh = norms > threshold;
dxi(:, to_thresh) = threshold*dxi(:, to_thresh)./norms(to_thresh);
% Transform the single-integrator to unicycle dynamics and utilize
% barrier certificates
dxu = si_to_uni_dyn(dxi, x); % dxu = uni_barrier_cert_boundary(dxu, x);
% Set velocities and send to agents
r.set_velocities(1:N, dxu); r.step();
% Update plotting information and locations
for q = 1:N; robot_labels{q}.Position = x(1:2, q) + [-0.15;0.15]; end
% Increment the counter and determine whether to stop execution
counter = counter + 1; if counter > iterations; FLAG = false ;end
% Update Iteration and Time marker
iteration_caption = sprintf('Iteration %d', counter);
time_caption = sprintf('Total Time Elapsed %0.2f', toc(start_time));
iteration_label.String = iteration_caption;
time_label.String = time_caption;
% Remove lines
if exist('lf', 'var'); for i = 1:length(lf); lf{i}.delete; end; clear lf; end
% Plot new lines
c = 1;
for i = 1:N
if A(1, i) == 1
lf{c} = line([x(1, 1), x(1, i)], [x(2, 1), x(2, i)], 'LineWidth', 1, 'Color', 'k');
c = c + 1;
end
end
% Add frame to video
if(mod(counter,skip) == 1); writeVideo(vid, getframe(gcf)); end
end
% Close video
close(vid);
% We can call this function to debug our experiment!
r.debug();
%% Helper Functions
% Marker Size Helper Function to scale size with figure window
% Input: robotarium instance, desired size of the marker in meters
function marker_size = determine_marker_size(robotarium_instance, marker_size_meters)
% Get the size of the robotarium figure window in pixels
curunits = get(robotarium_instance.figure_handle, 'Units');
set(robotarium_instance.figure_handle, 'Units', 'Pixels');
cursize = get(robotarium_instance.figure_handle, 'Position');
set(robotarium_instance.figure_handle, 'Units', curunits);
% Determine the ratio of the robot size to the x-axis (the axis are
% normalized so you could do this with y and figure height as well).
marker_ratio = (marker_size_meters)/(robotarium_instance.boundaries(2) -...
robotarium_instance.boundaries(1));
% Determine the marker size in points so it fits the window. cursize(3) is
% the width of the figure window in pixels. (the axis are
% normalized so you could do this with y and figure height as well).
marker_size = cursize(3) * marker_ratio;
end
% Font Size Helper Function to scale size with figure window
% Input: robotarium instance, desired height of the font in meters
function font_size = determine_font_size(robotarium_instance, font_height_meters)
% Get the size of the robotarium figure window in point units
curunits = get(robotarium_instance.figure_handle, 'Units');
set(robotarium_instance.figure_handle, 'Units', 'Pixels');
cursize = get(robotarium_instance.figure_handle, 'Position');
set(robotarium_instance.figure_handle, 'Units', curunits);
% Determine the ratio of the font height to the y-axis
font_ratio = (font_height_meters)/(robotarium_instance.boundaries(4) -...
robotarium_instance.boundaries(3));
% Determine the font size in points so it fits the window. cursize(4) is
% the hight of the figure window in points.
font_size = cursize(4) * font_ratio;
end
% Compute the graph laplacian and return laplacian (L) and adjacency (A)
function [L, A] = computeLaplacian(N, states, delta)
A = zeros(N, N); D = zeros(N, N);
for i = 1:N
neighbors = delta_disk_neighbors(states, i, delta);
if ~isempty(neighbors)
for j = 1:length(neighbors)
A(i, neighbors(j)) = 1; A(neighbors(j), i) = 1;
end
D(i, i) = length(neighbors);
end
end; L = D - A;
end