Skip to content

Instantly share code, notes, and snippets.

@Tomic-Riedel
Last active January 23, 2024 20:29
Show Gist options
  • Save Tomic-Riedel/9d9053e05f86b2a0686f0e5aba927605 to your computer and use it in GitHub Desktop.
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
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);
});
}
}
if (_timer?.isActive ?? false) {
setState(() {
_timer?.cancel();
});
} else {
setState(() {
_timer = Timer.periodic(
Duration(
milliseconds:
(pendulum.timeInterval * 1000).toInt(),
),
(timer) {
tick();
},
);
});
}
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,
);
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,
);
}
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(),
({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);
}
_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