Last active
January 23, 2024 20:29
-
-
Save Tomic-Riedel/9d9053e05f86b2a0686f0e5aba927605 to your computer and use it in GitHub Desktop.
Code for the simulation of a Double Pendulum in Dart & Flutter used in https://medium.com/@tomicriedel/embracing-chaos-simulating-a-double-pendulum-with-flutter-7da722357b40
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void tick() { | |
final newState = | |
pendulum.calculateNextStateOfPendulum(pendulum.previousStates.last); | |
List<StateOfPendulum> newStates = pendulum.previousStates.toList(); | |
newStates.add(newState); | |
// to prevent calling setState when the widget is not in the widget tree | |
if (mounted) { | |
setState(() { | |
pendulum = pendulum.copyWith(previousStates: newStates); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (_timer?.isActive ?? false) { | |
setState(() { | |
_timer?.cancel(); | |
}); | |
} else { | |
setState(() { | |
_timer = Timer.periodic( | |
Duration( | |
milliseconds: | |
(pendulum.timeInterval * 1000).toInt(), | |
), | |
(timer) { | |
tick(); | |
}, | |
); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final Offset coordinateOrigin = Offset(size.width / 2, size.height / 2); | |
// Draw points | |
paint.color = Colors.red; | |
Path path = Path(); | |
paint.style = PaintingStyle.stroke; | |
path.moveTo(coordinateOrigin.dx + points[0].$2.dx, | |
coordinateOrigin.dy - points[0].$2.dy); | |
// Draw smooth curve between points | |
for (int i = 1; i < points.length - 1; i++) { | |
final Offset controlPoint = Offset( | |
(coordinateOrigin.dx + | |
points[i].$2.dx + | |
coordinateOrigin.dx + | |
points[i + 1].$2.dx) / | |
2, | |
(coordinateOrigin.dy - | |
points[i].$2.dy + | |
coordinateOrigin.dy - | |
points[i + 1].$2.dy) / | |
2, | |
); | |
path.quadraticBezierTo( | |
coordinateOrigin.dx + points[i].$2.dx, | |
coordinateOrigin.dy - points[i].$2.dy, | |
controlPoint.dx, | |
controlPoint.dy, | |
); | |
} | |
path.lineTo( | |
coordinateOrigin.dx + points[points.length - 1].$2.dx, | |
coordinateOrigin.dy - points[points.length - 1].$2.dy, | |
); | |
// Draw the path | |
canvas.drawPath(path, paint); | |
// Draw line from point (0|0) to end of first pendulum | |
paint.color = Colors.green; | |
paint.strokeWidth = 3.5; | |
canvas.drawLine( | |
coordinateOrigin, | |
Offset(coordinateOrigin.dx + points.last.$1.dx, | |
coordinateOrigin.dy - points.last.$1.dy), | |
paint, | |
); | |
paint.color = Colors.lightGreen; | |
canvas.drawLine( | |
Offset(coordinateOrigin.dx + points.last.$1.dx, | |
coordinateOrigin.dy - points.last.$1.dy), | |
Offset(coordinateOrigin.dx + points.last.$2.dx, | |
coordinateOrigin.dy - points.last.$2.dy), | |
paint, | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
StateOfPendulum calculateNextStateOfPendulum(StateOfPendulum previousState) { | |
// Helper function to calculate derivatives | |
List<double> derivatives( | |
double theta1, double theta2, double omega1, double omega2) { | |
final denominator = (2 * m1 + m2 - m2 * cos(2 * theta1 - 2 * theta2)); | |
final theta1Dot = omega1; | |
final theta2Dot = omega2; | |
// Our first equation we looked at translated into Dart code | |
final omega1Dot = (-g * (2 * m1 + m2) * sin(theta1) - | |
m2 * g * sin(theta1 - 2 * theta2) - | |
2 * | |
sin(theta1 - theta2) * | |
m2 * | |
(pow(omega2, 2) * l2 + | |
pow(omega1, 2) * l1 * cos(theta1 - theta2))) / | |
(l1 * denominator); | |
// Our second equation we looked at translated into Dart code | |
final omega2Dot = (2 * | |
sin(theta1 - theta2) * | |
(pow(omega1, 2) * l1 * (m1 + m2) + | |
g * (m1 + m2) * cos(theta1) + | |
pow(omega2, 2) * l2 * m2 * cos(theta1 - theta2))) / | |
(l2 * denominator); | |
return [theta1Dot, theta2Dot, omega1Dot, omega2Dot]; | |
} | |
// Runge-Kutta 4th order method | |
final k1 = derivatives(previousState.theta1, previousState.theta2, | |
previousState.omega1, previousState.omega2); | |
final k2 = derivatives( | |
previousState.theta1 + 0.5 * timeInterval * k1[0], | |
previousState.theta2 + 0.5 * timeInterval * k1[1], | |
previousState.omega1 + 0.5 * timeInterval * k1[2], | |
previousState.omega2 + 0.5 * timeInterval * k1[3]); | |
final k3 = derivatives( | |
previousState.theta1 + 0.5 * timeInterval * k2[0], | |
previousState.theta2 + 0.5 * timeInterval * k2[1], | |
previousState.omega1 + 0.5 * timeInterval * k2[2], | |
previousState.omega2 + 0.5 * timeInterval * k2[3]); | |
final k4 = derivatives( | |
previousState.theta1 + timeInterval * k3[0], | |
previousState.theta2 + timeInterval * k3[1], | |
previousState.omega1 + timeInterval * k3[2], | |
previousState.omega2 + timeInterval * k3[3]); | |
final nextTheta1 = previousState.theta1 + | |
(timeInterval / 6.0) * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); | |
final nextTheta2 = previousState.theta2 + | |
(timeInterval / 6.0) * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); | |
final nextOmega1 = previousState.omega1 + | |
(timeInterval / 6.0) * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]); | |
final nextOmega2 = previousState.omega2 + | |
(timeInterval / 6.0) * (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]); | |
return StateOfPendulum( | |
theta1: nextTheta1, | |
theta2: nextTheta2, | |
omega1: nextOmega1, | |
omega2: nextOmega2, | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
points: pendulum.previousStates.map((e) { | |
final firstPendulumCoordinates = | |
e.calculatePointInRoomOfFirstPendulum( | |
pendulum.l1, pendulum.l2); | |
final secondPendulumCoordinates = | |
e.calculatePointInRoomOfSecondPendulum( | |
pendulum.l1, pendulum.l2); | |
return ( | |
Offset(firstPendulumCoordinates.x * 100, | |
firstPendulumCoordinates.y * 100), | |
Offset(secondPendulumCoordinates.x * 100, | |
secondPendulumCoordinates.y * 100) | |
); | |
}).toList(), |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
({double x, double y}) calculatePointInRoomOfFirstPendulum( | |
double l1, double l2) { | |
final x = l1 * sin(theta1); | |
final y = -l1 * cos(theta1); | |
return (x: x, y: y); | |
} | |
({double x, double y}) calculatePointInRoomOfSecondPendulum( | |
double l1, double l2) { | |
final x = l1 * sin(theta1) + l2 * sin(theta2); | |
final y = -l1 * cos(theta1) - l2 * cos(theta2); | |
return (x: x, y: y); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_timer?.cancel(); | |
var currentStates = pendulum.previousStates.toList(); | |
setState(() { | |
pendulum = pendulum.copyWith( | |
previousStates: [currentStates.first], | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment