import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
Simulation of a Foucault pendulum with simple forward Euler and classicalRunge-Kutta integration.
@author <michels@cs.stanford.edu>
public class FoucaultPendulum extends Applet implements ActionListener, Runnable {
double phi = Math.PI / 2;
double T = 30;
double L = 5;
double g = 9.81;
double omega = 2 * Math.PI * Math.sin(phi) / T;
double t = 0;
double x = 0, dx = 2, ddx;
double y = 0, dy = 0, ddy;
double xa, ya, dxa, dya;
int xint, yint, xinta, yinta;
double dt = 0.05;
double k1, k2, k3, k4, l1, l2, l3, l4, m1, m2, m3, m4, n1, n2, n3, n4, k, l, m, n;
double x1 = x, x2 = dx, y1 = y, y2 = dy;
double x1a, x2a, y1a, y2a;
private Image trajectoryImage;
private Graphics trajectoryGraphics;
private Image bufferingImage;
private Graphics bufferingGraphics;
Thread runner;
boolean started = false;
Button btnSP, btnST, btnSW;
boolean euler = false;
boolean initial = true;
public double f1(double x, double dy) {
return 2 * omega * dy + (Math.pow(omega, 2) - g / L) * x;
}
public double f2(double dx, double y) {
return -2 * omega * dx + (Math.pow(omega, 2) - g / L) * y;
}
public void euler() {
ddx = f1(x, dy);
ddy = f2(dx, y);
dxa = dx;
dx = dxa + ddx * dt;
dya = dy;
dy = dya + ddy * dt;
xa = x;
x = xa + dx * dt;
ya = y;
y = ya + dy * dt;
t = t + dt;
xinta = xint;
yinta = yint;
xint = (int) (75 * x + 150);
yint = (int) (75 * y + 150);
if (t > dt) {
trajectoryGraphics.drawLine(xinta, yinta, xint, yint);
}
}
public void rungeKutta() {
k1 = dt * x2;
l1 = dt * f1(x1, y2);
m1 = dt * y2;
n1 = dt * f2(x2, y1);
k2 = dt * (x2 + l1 / 2);
l2 = dt * f1(x1 + k1 / 2, y2 + n1 / 2);
m2 = dt * (y2 + n1 / 2);
n2 = dt * f2(x2 + l1 / 2, y1 + m1 / 2);
k3 = dt * (x2 + l2 / 2);
l3 = dt * f1(x1 + k2 / 2, y2 + n2 / 2);
m3 = dt * (y2 + n2 / 2);
n3 = dt * f2(x2 + l2 / 2, y1 + m2 / 2);
k4 = dt * (x2 + l3);
l4 = dt * f1(x1 + k3, y2 + n3);
m4 = dt * (y2 + n3);
n4 = dt * f2(x2 + l3, y1 + m3);
k = (k1 + 2 * (k2 + k3) + k4) / 6;
l = (l1 + 2 * (l2 + l3) + l4) / 6;
m = (m1 + 2 * (m2 + m3) + m4) / 6;
n = (n1 + 2 * (n2 + n3) + n4) / 6;
x1a = x1;
x1 = x1a + k;
x2a = x2;
x2 = x2a + l;
y1a = y1;
y1 = y1a + m;
y2a = y2;
y2 = y2a + n;
xa = x1a;
x = x1;
ya = y1a;
y = y1;
t = t + dt;
xinta = xint;
yinta = yint;
xint = (int) (75 * x + 150);
yint = (int) (75 * y + 150);
if (t > dt) {
trajectoryGraphics.drawLine(xinta, yinta, xint, yint);
}
}
void text(String txt, int x, int y, int s, Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Font f = new Font("Sans Serif", Font.LAYOUT_LEFT_TO_RIGHT, s);
FontMetrics fm = getFontMetrics(f);
LineMetrics lm = fm.getLineMetrics(txt, g);
g2d.setFont(f);
g2d.drawString(txt, x, y);
}
public void paint(Graphics g) {
if (initial) {
g.setColor(Color.black);
g.fillOval(145, 145, 10, 10);
g.drawOval(0, 0, 300, 300);
g.setColor(new Color(155, 155, 240));
}
g.setColor(Color.red);
if (euler) {
text("EULER", 0, 12, 12, g);
} else {
text("RUNGE-KUTTA", 0, 12, 12, g);
}
g.setColor(Color.black);
text("t = " + String.valueOf(Math.round(t * 10.) / 10.) + "s", 0, 24, 12, g);
g.drawLine(150, 150, xint, yint);
g.setColor(Color.red);
g.fillOval(xint - 10, yint - 10, 20, 20);
g.setColor(Color.black);
g.drawOval(xint - 10, yint - 10, 20, 20);
}
public void update(Graphics g) {
int width = 400, height = 400;
if (bufferingImage == null) {
bufferingImage = createImage(width, height);
bufferingGraphics = bufferingImage.getGraphics();
}
bufferingGraphics.setColor(getBackground());
bufferingGraphics.fillRect(0, 0, width, height);
bufferingGraphics.drawImage(trajectoryImage, 0, 0, this);
paint(bufferingGraphics);
g.drawImage(bufferingImage, 0, 0, this);
}
public void init() {
setLayout(new BorderLayout());
Panel mainPanel = new Panel();
mainPanel.setLayout(new GridLayout(1, 2));
btnSP = new Button("start/pause");
btnST = new Button("stop");
btnSW = new Button("switch");
mainPanel.add(btnSP);
mainPanel.add(btnST);
mainPanel.add(btnSW);
add(mainPanel, BorderLayout.SOUTH);
btnSP.addActionListener(this);
btnST.addActionListener(this);
btnSW.addActionListener(this);
t = -dt;
x = 0;
y = 0;
trajectoryImage = createImage(800, 800);
trajectoryGraphics = trajectoryImage.getGraphics();
trajectoryGraphics.setColor(Color.black);
trajectoryGraphics.fillOval(145, 145, 10, 10);
trajectoryGraphics.drawOval(0, 0, 300, 300);
trajectoryGraphics.setColor(new Color(155, 155, 240));
euler();
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnSP) {
if (started) {
runner.stop();
} else {
runner = new Thread(this);
runner.start();
}
started = !started;
} else if ((e.getSource() == btnST) || (e.getSource() == btnSW)) {
try {
runner.stop();
} catch (Exception oops) {
}
t = -dt;
x = 1;
dx = 0;
y = 1;
dy = 0;
x1 = x;
x2 = dx;
y1 = y;
y2 = dy;
trajectoryImage = createImage(800, 800);
trajectoryGraphics = trajectoryImage.getGraphics();
trajectoryGraphics.setColor(Color.black);
trajectoryGraphics.fillOval(145, 145, 10, 10);
trajectoryGraphics.drawOval(0, 0, 300, 300);
trajectoryGraphics.setColor(new Color(155, 155, 240));
if (e.getSource() == btnSW) {
euler = !euler;
}
if (euler) {
euler();
} else {
rungeKutta();
}
if (started) {
started = !started;
}
repaint();
}
}
public void run() {
initial = false;
while (true) {
repaint();
if (euler) {
euler();
} else {
rungeKutta();
}
try {
Thread.sleep((int) (50));
} catch (InterruptedException e) {
}
}
}
}