How to simulate a pendulum with just using forces.

edited July 2017 in Questions about Code

I know simulating a pendulum can be done by using polar coordinates and angular velocity etc. But I want to do it, by just using the Tension and Gravity forces acting on the 'bob', the resultant of which will 'supposedly' perform the swinging motion. The code I have is as follows:

The Main Class :

Mover bob;
PVector anchor;
float len;
PVector tension;
PVector gravity;

void setup() {
  size(640, 380);
  anchor = new PVector(width/2, 0);
  bob = new Mover();
  gravity = new PVector(0, 0.4);
  tension = new PVector(0, 0);
  len = dist(bob.pos.x, bob.pos.y, anchor.x, anchor.y);
}

void applyForces() {
  tension = PVector.sub(anchor, bob.pos);
  float str = bob.vel.magSq()/len + gravity.mag()*cos(bob.getAngle(anchor));
  tension.setMag(str);
  bob.applyForce(tension);
  bob.applyForce(gravity);

}

void draw() {
  background(50);
  println(dist(bob.pos.x, bob.pos.y, anchor.x, anchor.y));
  applyForces();
  bob.update();
  bob.render();
}

The Mover class :

class Mover {

  PVector pos;
  PVector vel;
  PVector acc;
  float mass = 1;

  Mover() {
    pos = new PVector(width/2.2, height/4);
    vel = new PVector();
    acc = new PVector();
  }
  float getAngle(PVector anchor) {
    PVector temp1 = PVector.sub(pos, anchor);
    PVector temp2 = PVector.sub(new PVector(anchor.x, height), anchor);
    float angle = PVector.angleBetween(temp1, temp2);

    return angle;
  }
  void applyForce(PVector force) {
    PVector f = PVector.div(force, mass);
    acc.add(f);
  }

  void update() {
    vel.add(acc);
    pos.add(vel);

    acc.mult(0);
  }

  void render() {
    fill(255);
    ellipse(pos.x, pos.y, 30, 30);
  }
}

Answers

  • @aizen -- you have said what you are trying to do, and you have shared code. What is your specific question at this point? What is not working, or where are you stuck or confused?

  • The problem is that if you run the sketch and check the console output, the length is not constant, and moreover, if I set the bob's position higher than the anchor's position, it behaves weirdly, (maybe it is because of the way the angle is calculated in this code). So, is there some way to correct these things.

  • Numbers stored using the float only have an accuracy of 6-7 significant digits and the problem is exaggerated because the pos and vel vectors are cumulative so any error is accumulated as well. This is happening every frame, 60 times a second so errors can soon accumulate.

    In the AI for 2D Games library the autonomous agents are updated using Newtonian physics i.e. velocity/acceleration/force. Internally the library uses the double data type throughout to avoid this problem.

  • so, you are suggesting that using 'double' instead of float should fix the first problem. I will definitely check that out.. Regarding the second issue of the weird behavior when the bob's position is set higher than the anchor, do you have any solution? Would love to get that problem solved too..

  • @quark well, I tried with double, but the cosine function and the PVector class doesn't take in double values, they only accept float. So no progress with that.

  • edited July 2017

    You will have to create your own vector class with double attributes and use that instead of PVector.

    Alternatively use the Vector2D class I created in the AI library. To do that

    1) Create a new tab on your sketch called exactly Vector2D.java

    2) Copy the source code from the Vector2Dclass but without the package statement` line

    Instead of using Processing's trig functions use the ones from Java's Math class e.g.

    Math.cos(double), Math.sin(double) etc.

    I have not tried moving the bob's position above the anchor, but the tension force should be zero if the bob is above the anchor or inside the radius of the pendulum

  • edited July 2017

    ... but without the package statement line.

    @quark, dunno exactly when, but package is compatible w/ the PDE now! $-)

    That can be proved in 1 of my recent sketches below:
    https://Forum.Processing.org/two/discussion/23471/combining-two-pieces-of-code/p2#Item_1

    Where I can use import deadpixel.command.Command; to access the ".java" tab:
    https://GitHub.com/GoToLoop/command/blob/patch-1/src/deadpixel/command/Command.java

    Which contains statement: package deadpixel.command; in it. :>
    Although we can still get it working w/o the import keyword in the ".pde" tab! @-)

  • edited October 2017

    I think there is a mistake in your force formula too.. the tension in the string is equal to mg * cos theta, not mg * cos theta + Vel^2 / len

    ignore that. :) I can't find the delete button

  • edited October 2017

    Doubles are better, but they still accumulate error. Another (more robust) way to work around cumulative errors is to avoid accumulating processes. There are many ways using the built-in PVector methods. For one example, you could periodically reassert the length of the vector with setMag(), containing drift:

    A common pattern to avoid cumulative drift is to always apply a time step to calculating the position from the origin -- rather than calculating each position from the previous position. But since you've explicitly said you don't want to do that, you could think of this process as being like rigidity in the pendulum string/rod material.

  • The problem is that if you run the sketch and check the console output, the length is not constant

    When you said the length wasn't constant, do you mean it gradually increased? I tried this same approach to model a pendulum, but in my sketch, the pendulum constantly floats downwards.

    class GPendulum extends Mover{
      float anchorX;
      float anchorY;
      float len;
    
      public GPendulum(float _anchorX, float _anchorY , float m, float l){
        super(_anchorX+20,_anchorY+l,m);
        anchorX = _anchorX;
        anchorY = _anchorY;
        len = l;
    
      }
      ///*** PI - heading = theta
    
      public void show(){
        ellipse(pos.x,pos.y,30,30);
        line(pos.x,pos.y,anchorX,anchorY);
      }
    
      public void swing(){
    
        PVector gravity = new PVector(0,1); //gravity points straight down
        gravity.normalize();
        gravity.mult(mass*0.1);
        float theta = asin((pos.x-anchorX)/len);
        PVector stringForce = new PVector(0,-1); //start by pointing straight upwards
        stringForce.normalize();
        stringForce.mult(gravity.mag()*cos(theta)+ vel.magSq()/len);
        stringForce.rotate(-theta);
        applyForce(gravity);
        applyForce(stringForce);
        update();
      }
    }
    
    class Mover {
      PVector pos;
      PVector vel;
      PVector accel;
    
      float mass;
      float topSpeed = 7;
      int size = 10;
    
    
      public Mover(float x, float y, float m) {
        pos = new PVector(x, y);
        vel = new PVector(0, 0);
        accel = new PVector(0, 0);
        mass =  m;
      }
      public void show() {
        stroke(#FC6924);
        ellipseMode(CENTER);
        fill(#FC6924);
        ellipse(pos.x,pos.y,size,size);
      }
    
      public void update() {
        vel.add(accel);
        //vel.limit(topSpeed);
        pos.add(vel);
        accel.mult(0);
        //println("New Vel:"+ vel.y);
      }
    
      public void applyForce(PVector f) {
        //println("Applying");
        PVector fc = f.copy();
        accel.add(fc.div(mass));
      }
    }
    
Sign In or Register to comment.