Photo by Timur Kozmenko on Unsplash
Chaos is beautiful.
It’s everywhere around us, creating the most beautiful scenes' humankind has ever seen. Take the unpredictable cascade of a waterfall, or the random, vibrant dance of autumn leaves. Mesmerizing. But today, we’re shifting our focus from nature’s chaos to a man-made fascination — the Double Pendulum, also known as the Chaos Pendulum.
We’ll dive into why it is such an interesting construction, what the math behind all of this looks like, and most importantly, how we can simulate its fascinating movement using Flutter and Dart. So, prepare for an exciting journey into the captivating world of chaos.
The Math Behind a Chaos Pendulum — In Simple Language
Alright, let’s talk about the math behind a chaos pendulum. Imagine a regular playground swing — when you push it, it goes back and forth predictably, right? Now, a chaos pendulum is like a swing with a twist — it has more than one point where it can pivot, so it doesn’t just move back and forth in a simple line. It can swing in all sorts of directions, and that’s where the chaos comes in.
Photo by Nick Monica on Unsplash
Now, to the math part: Each of the points where the pendulum can pivot is like a decision point that affects where the pendulum will go next. It’s kind of like when you’re at an intersection and decide to go left or right — your choice changes your path. In the chaos pendulum, these choices are made by physics, not by you, and they depend on things like gravity, the angle of the swing, and how much energy it has.
To predict the pendulum’s movement, we use equations that account for these factors. These equations are pretty complex (we are going to take a look at them in the next section), but they tell us that the pendulum’s next move is based on its current state — like how fast it’s going and in which direction it’s moving. But because it can move in so many ways, these equations can give us lots of different answers, making it hard to predict exactly what will happen next. That’s the chaotic part — it’s sensitive to its starting conditions, and a tiny change can make a big difference in where it ends up.
When we simulate this in Flutter, a programming framework, we use Dart, its programming language, to write the rules for these equations. The computer then does all the hard math work, calculating the pendulum’s movement step by step and showing us the wild, beautiful pattern it creates. It’s like giving the computer a set of instructions: “If the pendulum is here and moving this fast, where should it go next?” And we keep asking this question over and over, really fast, to simulate the chaos pendulum’s motion on the screen.
The Complex Math Behind a Chaos Pendulum
Photo by Pawel Czerwinski on Unsplash
Let’s finally talk about equations. Nothing in our simulation would work without these, so let’s take a look at them, by first defining some things:
- ω1 and ω2 are the angular velocities of the first and second pendulum arms
- θ1 and θ2 are the angles of the first and second pendulum arms from the vertical.
Our two most important equations are going to be the ones helping us calculate the speed at which each arm of a double pendulum is spinning:
STAY WITH ME! It’s not as hard as it looks, I promise. And if you don’t want to deal with this, just skip this section, as you don’t need to understand it if you just want to get your simulation up and running.
So, let’s break the first formula down and explore what each part is calculating:
First Formula:
This term calculates the component of gravitational force acting on the first pendulum arm, which influences its acceleration.
This term accounts for the gravitational force acting on the second pendulum arm, which also affects the first arm’s movement because they are connected.
This complex term considers the interaction between the two pendulum arms. It includes the effects of the angular velocities of both arms (ω1) and (ω2) and the lengths of the arms (l1 and l2), as well as how the angle difference (θ1 — θ2) impacts the first arm’s acceleration.
The denominator (bottom part of the fraction) is:
This part normalizes the calculation, taking into account the lengths of the first pendulum arm (l1) and the masses (m1 and m2), as well as the angle between the two arms. It ensures that the forces are correctly adjusted for how the pendulum arms are positioned relative to each other.
Second Formula:
This term is a multiplier that accounts for the interaction between the two pendulum arms based on their angle difference.
This part calculates the influence of the first arm’s motion on the second arm, factoring in the square of the first arm’s angular velocity (ω1²), the length of the first arm (l1), and the combined mass of both arms (m1 + m2).
This component accounts for the gravitational pull on the combined mass of the pendulum system, with the direction of the first arm’s angle factored in as (cos(θ1)).
The last part of the numerator includes the effect of the second arm’s angular velocity (ω2²), its length (l2), and its mass (m2), as well as how the angle between the two arms affects the second arm’s motion.
The denominator is:
This part normalizes the calculation, similar to the first equation, by considering the length of the second arm (l2) and the masses involved, as well as the relative angles of both arms.
Whew, this was A LOT OF MATH! But we are not done yet, as we still don’t know how to calculate these differential equations. So let’s take a look at this in the next step!
Using the Runge-Kutta Method to solve our Equations
The Runge-Kutta procedure, specifically the fourth-order Runge-Kutta method (often abbreviated as RK4), is a numerical technique used to solve ordinary differential equations (ODEs), like our equations of motion. It’s needed because these equations are typically too complex to solve with simple algebra. They describe how things change over time in a way that depends on their current state, which can lead to very complicated behavior.
But: How does the Runge-Kutta method work?
The Runge-Kutta method is a step-by-step approach that helps us predict the future behavior of systems described by differential equations, like our chaos pendulum.
Imagine you’re on a hike and you can’t see the path ahead because it’s covered by fog. To figure out where you’ll end up, you take a few tentative steps in different directions to see how steep the hill is. Then, you use that information to choose the best path forward.
Similarly, the Runge-Kutta method doesn’t just blindly follow the initial direction; it takes several “test steps” at different points within each time interval to get a better idea of where to go next. It calculates the slopes (rates of change) at these points and combines them to make a well-informed guess about the system’s state at the end of the step. By repeating this process, the Runge-Kutta method moves the system forward in time, step by step, tracing out the path of the pendulum’s motion.
Here’s why we use the Runge-Kutta method:
- Accuracy: RK4 is known for being accurate and reliable. It calculates the future state of a system by considering not just the immediate rate of change (like Euler’s method does) but also the rates of change at several points within the step. This makes it better at predicting what actually happens.
- Stability: For many problems, including chaotic systems like the double pendulum, RK4 provides relatively stable solutions over time, which means it’s less likely to produce errors that grow larger with each step. There are more stable methods, but they are even more complex.
- Applicability: The RK4 method doesn’t require the differential equations to be linear or to have constant coefficients, which makes it suitable for a wide range of problems, including those with complex, nonlinear dynamics like the chaos pendulum.
- Ease of Use: Despite its sophistication, the Runge-Kutta method is relatively easy to implement in computer code. It doesn’t require complex programming techniques or advanced mathematical tools, which makes it accessible for many applications. Programmers and engineers can apply the RK4 method using straightforward algorithms, allowing them to focus on the problem at hand rather than on the intricacies of the numerical method itself.
In essence, when we want to simulate the double pendulum on a computer, we can’t just solve the equations once and be done with it. We need to keep solving them again and again, moving forward in tiny steps through time. The Runge-Kutta method helps us do this in a way that captures the chaotic motion accurately, so we can see the beautiful and unpredictable patterns it creates.
If you want to learn more about the 4th Order Runge-Kutta Method, check out this great article.
Preparing our Code Environment for Implementation
Photo by Joshua Woroniecki on Unsplash
Now, let’s finally start coding! To do so, I ask you to clone this GitHub Repository. To follow along, please go to the initialSetup
folder.
You will see the 3 following files in the lib/
folder:
main.dart
— Our standard main file, running the app and displaying our PendulumWidget.pendulum_calculations.dart
— This file is going to take care of all the calculations for the pendulum. It currently consists of two classes: DoublePendulum, responsible for storing basic information about the Pendulum, like the length and mass of each arm, as well as all previous states the pendulum had during one cycle.StateOfPendulum
is responsible for storing one state of the pendulum over time. It stores the angle and velocity of the first pendulum, as well as the second pendulumpendulum_widget.dart
— This file is, as the name suggests, our pendulum widget as well as some basic buttons. It currently features 2 different buttons that do not work yet, as well as a CustomPainter drawing a very basic chart, which we will expand soon.
Great, let’s start with the most important part: Implementing our math.
Implementing the Math Behind Our Pendulum
Let’s open pendulum_calculations.dart and locate the comment //TODO: Implement Calculation
We are going to replace this comment with the following code:
What we see here now is the heart of our pendulum simulation in action. This Dart code is where the math we talked about earlier is converted into actual programming terms (seen in omega1Dot
and omega2Dot
).
The calculateNextStateOfPendulum
function is our main tool for predicting where our Chaos Pendulum will be after a tiny slice of time has passed. It takes the current state of the pendulum, which includes the angles of the arms and their speeds, and calculates what the state will be just a moment later.
Inside the function, we first define a helper function called derivatives
. This is where we translate our mathematical equations into code that the computer can understand. We input the current angles and speeds, and it spits out how fast these values are changing—the theta1Dot
and theta2Dot
are the speeds of the angles, and omega1Dot
and omega2Dot
are the changes in those speeds.
Then comes the Runge-Kutta method. We create four sets of “test steps” called k1
, k2
, k3
, and k4
. These steps are like our predictions at different points in time, and each one is based on the information from the previous step. By combining these predictions, we get a really good estimate of where the pendulum will be after the full-time step.
Finally, we calculate the next angles and speeds (nextTheta1
, nextTheta2
, nextOmega1
, nextOmega2
) by combining our initial state with the weighted average of our four "test steps". The magic number 6.0 in the code is part of this weighting—it comes from the Runge-Kutta method and ensures that our average step size is just right.
By calling this function over and over, each time using the state it just calculated as the “previous state” for the next calculation, we can simulate the pendulum’s motion over time.
Calculating the Arms Positions Based on the Current State
Now, we want to calculate where the ends of the arms are based on the state of the pendulum. Stay in the pendulum_calculation.dart
file and locate the //TODO: Implement Point Calculation
comment.
Replace this with the following Code:
In this part of the code, we’re focusing on visualizing the chaos by calculating the exact positions of the pendulum arms’ ends in the space of our simulation. The calculatePointInRoomOfFirstPendulum
function computes the (x, y) coordinates of the end of the first pendulum arm using its length l1
and its current angle theta1
. It's like swinging the arm from the center point and marking where the tip ends up.
Similarly, the calculatePointInRoomOfSecondPendulum
function finds the position of the second arm's end. But this time, it's a bit trickier because the second arm swings from the end of the first arm, not the center. So we take the position of the first arm's end and swing the second arm from there, using its length l2
and angle theta2
.
By using the sine function for the x-coordinate and the cosine function for the y-coordinate (and flipping the y-coordinate to account for the fact that in most computer graphics, the y-axis is positive down the screen), we get the positions of the pendulum ends relative to the starting point in our room or simulation space. This lets us draw the pendulum on the screen in the correct place, bringing the simulation to life.
Let’s Start Animating our Pendulum
We finally reached the step where we want to execute the calculation of our different points in time. Go to pendulum_widget.dart
and locate //TODO: Calculate Points in Time
.
Replace the comment and the throw UnimplementedError();
with the following code:
This function calls calculateNextStateOfPendulum
to determine the pendulum's new position based on the last recorded state. It then adds this new state to a list of previous states. This growing list helps us track the pendulum's path and is key to plotting its path on the screen later.
To ensure we keep our app’s performance smooth and only update the visuals if our widget is active, we check if it’s ‘mounted’ before calling setState
.
Let’s start our timer by searching for //TODO: Implement Calculation Start
and replace it with the following code:
This code is pretty self-explanatory. If the _timer
is already animating, we stop the timer. Otherwise, we will initialize it (and thus automatically start it).
While we are at it, let’s also implement resetting the animation. Locate //TODO: Implement Animation Reset
and insert the following code:
This code simply cancels the timer and replaces all the existing states with the first one.
Display the Pendulum
We now calculate our points over and over again (You can test this by printing out the points in the tick
function). But we can’t see anything on screen. It’s time we finally show our pendulum on the screen. For that, we will first finish the paint
function in CoordinatePainter. To do so, locate //TODO: Implement point painting
. Replace it with the following code:
We initiate a Path
object and move to the starting point, which is the first position in our list of calculated points, offset from the coordinate origin. Then, we loop through the points, drawing a smooth curve between them. This is done using a quadratic Bézier curve, which requires a control point to determine the curvature. By calculating the midpoint between successive points, we create a gentle, flowing path that represents the pendulum's trajectory.
Next, we draw this path onto the canvas, visualizing the trail that the pendulum’s end follows as it swings through chaos. After that, we switch the paint color to green and increase the stroke width to make the pendulum’s arms more visible. We draw a line from the origin to the end of the first pendulum arm and then another line in light green from the end of the first arm to the end of the second arm, showing the connection between them.
With this code, we’re not just plotting static points; we’re essentially animating our pendulum. Each frame of the animation draws the pendulum in a new position based on the calculations of its motion, creating a dynamic simulation that allows us to watch the chaotic pendulum as it dances on our screens.
Now, locate //TODO: Pass Points
. We obviously need to pass the points that we want to draw to our Painter. Replace the comment with the following code:
Here, we go over each state we have and calculate the end of the first and second pendulum. We have implemented the function at the top.
We then return the position of the first and second arms. Notice that we multiply it by 100. Our “real” coordinate system is, when having an arm's length of 1m each, 2m on the positive & negative x & y-axis (Or 2 length units). But because our widget has a size of 600x600, we simply say one length unit in our calculation equals 100 length units in our widget.
A Few Notes about the Math & Code
Now, let’s talk about the flaws of this code and math.
- First, and most noticeable is the fact, that our calculations do not take into account friction between the pendulums, air resistance, or other factors. It is an idealized system which does not represent our real world.
- Secondly, even if we want a pendulum that does not “lose” energy, the energy of our pendulum does change with each calculation. This is due to the inaccuracy of the Runge-Kutta method, as it calculates based on the previous state and does not calculate a 100% correct value. This creates the problem, that our system will gain (or lose) energy out of nowhere over time. Because of the new energy, the velocity of each pendulum goes up over time, resulting at some point in an error because Dart cannot handle such large integers.
You can see this issue very easily: Set your time interval to 4s instead of 0.01s. After only a few iterations, you will get this error, because the accuracy of the calculation is strongly connected to the span between the last calculation and the new calculations. The smaller the time intervals, the better the calculation, and vice versa. - Thirdly: The code handling the state may have been way cleaner when using state management solutions like Riverpod or Bloc. I purposefully didn’t choose one of them, as I wanted to show a general approach to create a double pendulum in Flutter, not a specific one that only people with Riverpod or Bloc knowledge understand. I still strongly recommend using a good state management solution if you need this code in a more complex scenario (Honestly, I can’t think of any, but that’s up to your imagination).
- Fourthly: You can experiment with the pendulum a bit by adding some Sliders that change the values of the mass, length, initial angle, or velocity. It is very interesting to see the different patterns emerging from small changes. You can also add an option to show multiple pendulums at the same time, making it even more visually appealing and interesting.
Wrap Up
In our journey through simulating the Chaos Pendulum in Flutter, we’ve learned how beauty can emerge from chaos and complexity. We’ve translated the “unpredictable” motion of a double pendulum into lines of Dart code, revealing the underlying mathematics that determines the pendulum’s motion. By implementing the Runge-Kutta method, we’ve seen how to make precise predictions about the pendulum’s chaotic behavior over time. We’ve also learned to visualize this motion, drawing the pendulum’s path and animating it on the screen. Through this process, we’ve not only gained insights into a fascinating physical system but also strengthened our skills in programming and problem-solving within the Flutter framework.
I hope that you were able to learn something new! If so, I invite you to follow me, as I will continue posting this type of content. For you, it’s just one click, but for me, it means the world!
Have a great day!
PS: I did my best to apply the correct mathematical principles to our pendulum, but I can’t guarantee 100% accuracy. If you notice anything incorrect, I would appreciate your constructive feedback!