Fredrik Arentz
Fredrik Arentz

Reputation: 3

Pendulum simulation only performing one period

I am making a simulation of a pendulum, but it only performs one swing before sending close to random positions for the bob to be at. Essentially, it does not go backwards.

I have tried to change the direction using the goingForward boolean, but it still doesnt work.

public class AnimationPane extends JPanel {
    // START CHANGEABLE VARIABLES
    private double startAngle = -60.0; // degrees
    private double mass = 1; // kilogrammes
    private int radius = 10; // m
    private double gravity = 9.80665; // m/s^2 // on earth: 9.80665
    // END CHANGEABLE VARIABLEs
    private BufferedImage ball;
    private BufferedImage rope;
    private int pointX = 180;
    private int pointY = 50;
    private double endAngle = Math.abs(startAngle); // absolute value of startAngle
    private double angle = startAngle; // current angle
    private double circum = (2 * Math.PI * radius); // m
    private double distance = 0; // m
    private double velocity = 0; // m/s
    private double totalEnergy = ((radius) - (Math.cos(Math.toRadians(angle)) * radius)) * gravity * mass + 0.00001;
    private double previousE;
    private int xPos = 0; // for program
    private int yPos = 0; // for program
    private boolean goingForward = true;
    private double height = 0;

    public AnimationPane() {
        try {
            ball = ImageIO.read(new File("rsz_black-circle-mask-to-fill-compass-outline.png"));
            Timer timer = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    double angleRad = Math.toRadians(Math.abs(angle));

                    double potentialE = ((radius) - (Math.cos(angleRad) * radius)) * gravity * mass;
                    Double pE = new Double(potentialE);
                    height = (radius - (Math.cos(angleRad) * radius));
                    double kineticE = totalEnergy - pE;

                    if (kineticE <= 0 || angle >= endAngle) {

                        if (goingForward == true) {
                            goingForward = false;
                        }
                        else 
                        {
                            goingForward = true; 
                        }
                        kineticE = 0.1; 
                        angle = 60;
                    }

                    velocity = Math.sqrt(2 * kineticE / mass);
                    double ratio = distance / circum;

                    if (goingForward == true) {                           
                        distance = distance + (velocity / 10);
                        angle = startAngle + (360 * ratio);
                    }
                    else {
                        distance = distance - (velocity / 10);
                        angle = startAngle - (360 * ratio);
                    }                        

                    double angles = Math.toRadians(angle);

                    double xDouble = Math.sin(angles) * (radius * 10);
                    Double x = new Double(xDouble);
                    xPos = x.intValue() + 150;

                    double yDouble = Math.cos(angles) * (radius * 10);
                    Double y = new Double(yDouble);
                    yPos = y.intValue() + 50;

                    repaint();
                }

            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();
        } catch (IOException ex) {
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.drawLine(xPos + 20, yPos + 20, pointX, pointY);
        g.drawImage(ball, xPos, yPos, this);

    }

}

I would really appreciate some help getting this to work. Thank you

Upvotes: 0

Views: 356

Answers (2)

Andy Turner
Andy Turner

Reputation: 140494

I really can't follow your code. If I had to say where I thought the problem is, I would say that it is the distance variable, and its consequent use in ratio: this seems to be defined as being zero when the pendulum starts. When you switch the direction of the pendulum, I think that it needs to be rebased to zero... Somehow.

You should also separate out your GUI code from your simulation. You can just make it print out the x and y coordinates while you are debugging. For instance, you can write the values to a CSV file, so you can visualize them in Matlab (or whatever) to ensure that your simulation looks right over time.

I would make two suggestions to help you with the simulation:

  1. Don't model it in terms of energy, which is a scalar quantity, meaning that you have to use additional state like a "direction flag" to model which way it is going;
  2. You are going to need an additional state variable. Storing x and y is redundant, since these can be derived directly from the angle (and R, although that is constant). You are modelling a second-order system, so you need that second state variable which models the movement of the system at an instant, as well as its position. For example, you could model the angle and angular velocity over time.

And, of course, don't mess around with degrees - stick to radians :)

(On the matter of velocity, your variable of that name is actually speed - you don't know which direction it moves in, and that information is highly relevant to the dynamics of the system).

The derivation of the method using angle and angular velocity is quite straightforward, although my maths is rusty enough to caution its use without checking carefully!

The rotational form of Newton's second law of motion is:

Torque = moment of inertia * angular acceleration
  • The torque on the bob is given by -mgR sin angle (m is mass, g is gravity, R is length of pendulum, angle is the angular displacement of the pendulum from vertical). The minus sign is because the force tends to return the bob to vertical;
  • The moment of inertia of a point mass is mR^2 - this is a reasonable approximation if the pendulum is very long compared to the radius of the bob.

Therefore the angular acceleration is -g sin theta / R. Hopefully this should look like simple harmonic motion - an acceleration proportional to the distance from an equilibrium (for small theta, sin theta is approximately theta) and directed towards it.

You can now put this together into a numerical scheme to simulate the pendulum. For example, using Euler's method:

New angle            = old angle                + dt * old angular velocity
New angular velocity = old angular velocity vel - dt * g * sin(angle) / R

where dt is your time step.

You can, of course, use higher-order methods like Runge-Kutta to reduce the numerical errors in the simulation, but it is (slightly) more involved.

Upvotes: 0

Dici
Dici

Reputation: 25980

I could not debug your code, which was uneasy to work with and sometimes to understand (you use a lot of integer literals in your code, which hides their semantic, I have no idea what was your intention on some statements).

Therefore, I rewrote it using the solution of the differential equation for small oscillations. It works, you can take it as a clean base to implement it again the way you wanted. Note that as Andy Turner pointed it, you should not have to worry about the fact of going forward or backward. You have an equation, you solve it, it gives you the position of the ball at any time. If you want something which is accurate for large angles, I suggest you go on Wikipedia to see the movement equation in this case. Last option, you could numerically solve the differential equation although I would personally don't know how to do it at first glance.

package stackoverflow;

public class AnimationPane extends JPanel {
    private static final long   serialVersionUID    = 1L;
    private static final double GRAVITY             = 9.80665;

    private BufferedImage ball;

    private final Point fixedCordPoint;
    private final int cordLength;
    private final double startAngle;
    private double currentAngle; 

    private final double pulsation;
    private final Point ballPos = new Point();
    private int time = 1;

    public AnimationPane(Point fixedCordPoint, int cordLength, double startAngleRadians) {
        this.fixedCordPoint = new Point(fixedCordPoint);
        this.cordLength     = cordLength;
        this.pulsation      = Math.sqrt(GRAVITY / cordLength);
        this.startAngle     = startAngleRadians;
        this.currentAngle   = startAngleRadians;
        this.ball           = loadImage(new File("ball.jpg"));
    }

    private BufferedImage loadImage(File file) {
        try {
            return ImageIO.read(file);
        } catch (IOException e) {
            throw new RuntimeException("Could not load file : " + file, e);
        }
    }

    public void start() {
        Timer timer = new Timer(100, event -> {
            ballPos.x = fixedCordPoint.x + (int) Math.round(Math.sin(currentAngle) * cordLength);
            ballPos.y = fixedCordPoint.y + (int) Math.round(Math.cos(currentAngle) * cordLength);
            repaint();
            currentAngle = startAngle * Math.cos(pulsation * time);
            time++;
        });
        timer.setRepeats(true);
        timer.setCoalesce(true);
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawLine(ballPos.x, ballPos.y, fixedCordPoint.x, fixedCordPoint.y);
        g.drawImage(ball, ballPos.x - ball.getWidth() / 2, ballPos.y - ball.getHeight() / 2, this);
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        AnimationPane pendulumAnimationPane = new AnimationPane(new Point(160, 25), 180, - Math.PI / 10);
        frame.setContentPane(pendulumAnimationPane);
        frame.setSize(400,400);
        frame.setVisible(true);

        pendulumAnimationPane.start();
    }
}

Upvotes: 1

Related Questions