257 lines
8.8 KiB
Matlab
257 lines
8.8 KiB
Matlab
%% Lesson 10, extra -- Chaos!
|
||
% Based on work for PH429: Chaos & Nonlinear Dynamics. The following was
|
||
% translated from the original NumPy/Matplotlib code. See Strogatz' Nonlinear
|
||
% Dynamics and Chaos (the source for much of this information) if you want to
|
||
% know more!
|
||
|
||
close all; clear; clc;
|
||
|
||
% Simple systems of nonlinear differential equations (also iterated nonlinear
|
||
% maps) often exhibit surprisingly complex behavior. Lorenz's "strange
|
||
% attractor," discovered in 1963, was an example of this: he discovered that an
|
||
% exceedingly simplified (and entirely deterministic!) model of the weather was
|
||
% still able to produce unpredictable fluctuations in temperature, wind speed,
|
||
% etc. -- the system exponentially amplified miniscule parameter changes, so
|
||
% every decimal place mattered. Further work with analog computers and the
|
||
% then-emerging digital computers led to the field we now call chaos theory.
|
||
|
||
% Chaos has much to do with fractal geometry, so we'll start by generating a
|
||
% few fractals. Because... why not?
|
||
|
||
%% The Chaos Game & Sierpinski Triangles
|
||
% The game is simple:
|
||
% 1. Place three vertices in the plane, corresponding to the vertices of an
|
||
% equilateral triangle.
|
||
% 2. Randomly choose a point P(0).
|
||
% 3. Each turn, choose a random vertex, and plot a point P(n) halfway between
|
||
% that vertex and point P(n - 1).
|
||
% Let's see what happens.
|
||
|
||
n1 = 5e5; % points
|
||
vertices = [0, 0; .5, .5*sqrt(3); 1, 0];
|
||
r = .5; % fraction of the way between points and vertices (here halfway)
|
||
|
||
% point coordinates
|
||
points = zeros(n1, 2);
|
||
choices = randi([1 3], n1, 1);
|
||
|
||
% randomly choose the first point somewhere in the plane
|
||
points(1, :) = 100*randn(1, 2);
|
||
|
||
% choose the rest
|
||
% note: the loop here is actually necessary, like it was in G-S -- each
|
||
% iteration depends upon the previous one.
|
||
for i = 2:n1
|
||
choice = choices(i);
|
||
vertex = vertices(choice, :);
|
||
last_point = points(i - 1, :);
|
||
|
||
points(i, :) = last_point + r*(vertex - last_point);
|
||
end
|
||
|
||
% plot the thing
|
||
figure;
|
||
scatter(points(:, 1), points(:, 2), 1);
|
||
xlim([-.1 1.1]);
|
||
ylim([-.2 1]);
|
||
xticks([]);
|
||
yticks([]);
|
||
pbaspect([1 1 1]); % make the plot box square
|
||
title('The Sierpinski Triangle, by Chance', 'Interpreter', 'latex');
|
||
|
||
% A rather important property of fractals is their fractional dimension -- this
|
||
% triangle, for example, is not quite two-dimensional but is clearly more than
|
||
% one-dimensional. So how do we measure this dimension?
|
||
|
||
% One simple measure, known as self-similarity dimension, is relatively easy to
|
||
% find for the Sierpinski triangle. The idea is we simply examine how many
|
||
% smaller versions of a given shape could fit into that shape, and what size
|
||
% they would be in relation. For example, a square with scale 1 can be made up
|
||
% of 4 smaller squares scaled by 1/2, or 9 smaller squares with scale 1/3:
|
||
% ----------------- -----------------
|
||
% | | | | |
|
||
% | | | 1/2 | 1/2 |
|
||
% | | | | |
|
||
% | 1 | = |-------|-------|
|
||
% | | | | |
|
||
% | | | 1/2 | 1/2 |
|
||
% | | | | |
|
||
% ----------------- -----------------
|
||
% A square thus has self-similarity dimension log₂4 = log₃9 = 2 (as one might
|
||
% expect). The Sierpinski triangle is composed of 3 copies of itself scaled by
|
||
% 1/2, and thus has self-similarity dimension log₂3 ≈ 1.585.
|
||
|
||
% I wonder if we could have MATLAB calculate these...
|
||
|
||
%% Iterated Maps
|
||
% One way to create chaotic behavior is to iterate a nonlinear map: e.g., plot
|
||
% the set {x, sin(x), sin(sin(x)), sin(sin(sin(x))), ...}. The values will
|
||
% generally hop unpredictably around, but will often stay closer to some values
|
||
% than others...
|
||
|
||
% The following plot is a visual representation of these iterations, called a
|
||
% cobweb. Cobwebs are often used to qualitatively analyze nonlinear maps. The
|
||
% idea is, since the output of the map will be fed back into the input, we plot
|
||
% a point on the map's curve, translate horizontally to the line y = x, then
|
||
% translate vertically back to the map and repeat. This gives a visual
|
||
% representation of plugging the output back into the input.
|
||
|
||
% When you see this, think about which points on the map will "attract" the
|
||
% cobweb, which will "repel" it, and how one might go about making that
|
||
% distinction.
|
||
|
||
n2 = 10;
|
||
map = @(x) 2*sin(x);
|
||
|
||
% create the map curve and the center line
|
||
x = linspace(0, pi, 100).';
|
||
y_map = map(x);
|
||
y_line = x;
|
||
|
||
% create the cobweb
|
||
map_pts = zeros(2*n2 + 1, 2);
|
||
map_pts(1, :) = [.2, 0];
|
||
for i = 1:n2
|
||
last_pt = map_pts(2*i - 1, :);
|
||
mapped = [last_pt(1), map(last_pt(1))];
|
||
on_the_line = [mapped(2), mapped(2)];
|
||
|
||
map_pts(2*i, :) = mapped;
|
||
map_pts(2*i + 1, :) = on_the_line;
|
||
end
|
||
|
||
% plot it
|
||
figure;
|
||
hold on;
|
||
plot(x, y_map, 'b');
|
||
plot(x, y_line, 'k');
|
||
plot(map_pts(:, 1), map_pts(:, 2), 'r');
|
||
|
||
start = map_pts(1, :);
|
||
scatter(start(1), start(2), 'r');
|
||
text(start(1) + .03, start(2) + .03, 'Start Point');
|
||
|
||
xlim([0 pi]);
|
||
xticks([0 pi/2 pi]);
|
||
ylim([0 2.1]);
|
||
xticklabels({'0', '\pi/2', '\pi'});
|
||
|
||
title('Cobweb of \(2 \sin x\)', 'Interpreter', 'latex');
|
||
|
||
%% Orbit Diagrams
|
||
% The above example was... not too crazy. The map x → α sin x *can* exhibit
|
||
% more complex behavior, but it requires α to be greater than π (so that we can
|
||
% escape the [0, π] interval). The map will first stay close to two points,
|
||
% then a few, then... altogether too many. This is usually plotted with what is
|
||
% called an orbit diagram, showing roughly how much time the map spends at
|
||
% certain values for a given α.
|
||
|
||
map_alpha = @(alpha, x) alpha.*sin(x);
|
||
|
||
n3 = 6e2; % iterations to go through
|
||
frames = 9e2; % run this many parallel alpha computations
|
||
toss = 2e2; % delete this many points from the start
|
||
|
||
min_alpha = 2.2;
|
||
max_alpha = 3.1;
|
||
alphas = linspace(min_alpha, max_alpha, frames);
|
||
|
||
% at least we can run the frames in parallel...
|
||
values = zeros(n3, frames);
|
||
values(1, :) = pi/4; % the first input each time
|
||
for i = 2:n3
|
||
values(i, :) = map_alpha(alphas, values(i - 1, :));
|
||
end
|
||
|
||
% plot this crazy thing
|
||
values = values((toss + 1):end, :);
|
||
figure;
|
||
scatter(alphas.', values, 1, [.7 0 0], 'MarkerEdgeAlpha', .3);
|
||
|
||
title('Orbit of \(\alpha \sin x\)', 'Interpreter', 'latex');
|
||
xlabel('\alpha');
|
||
ylabel('map value');
|
||
|
||
%% The Phase Plane
|
||
% In continuous time, we have differential equations rather than iterated maps.
|
||
% One way to analyze higher-order equations is using the so-called phase plane,
|
||
% which is a plot of our state variable and its derivative. The idea is that a
|
||
% second-order differential equation really acts like a system in two
|
||
% dimensions, and is best analyzed directly as such.
|
||
|
||
% We'll analyze the motion of an damped pendulum, which has the governing
|
||
% equation d²θ/dt² + b dθ/dt + (g/L) sin θ = 0. For simplicity (it won't change
|
||
% the qualitative behavior), we'll let g/L = 1. Then (the phase plane bit)
|
||
% we break this into two differential equations,
|
||
% * dθ/dt = ω and
|
||
% * dω/dt = -(sin θ + bω),
|
||
% and make a plot of the *vector* (dθ/dt, dω/dt) in θ-ω space. We can easily
|
||
% plot the vector field with `quiver`, and MATLAB's `streamline` is rather
|
||
% effective at plotting either single trajectories or a whole suite of possible
|
||
% solutions.
|
||
|
||
b = .2;
|
||
min_theta = -3*pi;
|
||
max_theta = 3*pi;
|
||
min_omega = -3*pi;
|
||
max_omega = 3*pi;
|
||
theta_range = linspace(min_theta, max_theta, 300);
|
||
omega_range = linspace(min_omega, max_omega, 200);
|
||
[theta, omega] = meshgrid(theta_range, omega_range);
|
||
theta_dot = omega;
|
||
omega_dot = -(sin(theta) + b*omega);
|
||
|
||
% to cut down on the # of points we plot
|
||
d = 10;
|
||
[sparse_x, sparse_y] = ...
|
||
meshgrid(d:d:length(theta_range), d:d:length(omega_range));
|
||
sparse = sub2ind(size(theta), sparse_y(:), sparse_x(:));
|
||
|
||
% plot this thing
|
||
figure;
|
||
hold on;
|
||
quiver(theta(sparse), omega(sparse), ...
|
||
theta_dot(sparse), omega_dot(sparse), 'm');
|
||
streamline(theta, omega, theta_dot, omega_dot, ...
|
||
theta(sparse_y, [1 end]), omega(sparse_y, [1 end]));
|
||
|
||
xlim([min_theta max_theta]);
|
||
xticks(linspace(min_theta, max_theta, 7));
|
||
ylim([min_omega max_omega]);
|
||
|
||
xlabel('pendulum angle');
|
||
ylabel('angular velocity');
|
||
title('Behavior of a Damped Pendulum', 'Interpreter', 'latex');
|
||
|
||
%% Strange Attractors
|
||
% I won't go into detail here -- suffice it to say the behavior of this system
|
||
% would be quite opaque without numerical integration. Fortunately, we have
|
||
% just that! Lorenz managed to prove analytically that this system had no
|
||
% closed cycles and yet all its trajectories eventually stayed within a bounded
|
||
% region of space, but beyond that... well, we'll see.
|
||
sigma = 10;
|
||
r = 28;
|
||
b = 8/3;
|
||
|
||
x_dot = @(x, y, z) sigma*(y - x);
|
||
y_dot = @(x, y, z) r*x - y - x.*z;
|
||
z_dot = @(x, y, z) x.*y - b*z;
|
||
|
||
[x, y, z] = meshgrid(linspace(-50, 50, 100));
|
||
u = x_dot(x, y, z);
|
||
v = y_dot(x, y, z);
|
||
w = z_dot(x, y, z);
|
||
|
||
figure;
|
||
hold on;
|
||
streamline(x, y, z, u, v, w, 0, 1, 0, [.1 2e4]);
|
||
scatter3(0, 1, 0);
|
||
view(3);
|
||
grid on;
|
||
|
||
text(0, 0, -2, 'Start Point');
|
||
xlabel('x');
|
||
ylabel('y');
|
||
zlabel('z');
|
||
title("Lorenz's Strange Attractor", 'Interpreter', 'latex');
|