Turtle slowing down for larger drawings - python

I have created some code in order to simulate sierpinski's triangle. For this, in my code, I set it so that it can simulate any number of points on the triangle, but I've noticed that increasing this to a large number, it ends up with the turtle taking an amount of time that increases as it goes on.
I did a sample output with 100 000 dots and this is what I got
0% done
Time since last update: 0.0019948482513427734
5.0% done
Time since last update: 1.2903378009796143
10.0% done
Time since last update: 1.8589198589324951
15.000000000000002% done
Time since last update: 2.325822114944458
20.0% done
Time since last update: 2.9351391792297363
25.0% done
Time since last update: 3.4773638248443604
30.0% done
Time since last update: 4.152036190032959
35.0% done
Time since last update: 4.7314231395721436
40.0% done
Time since last update: 5.260996103286743
44.99999999999999% done
Time since last update: 5.988528490066528
49.99999999999999% done
Time since last update: 6.804485559463501
54.99999999999999% done
Time since last update: 7.768667221069336
60.0% done
Time since last update: 8.379971265792847
65.0% done
Time since last update: 8.995774745941162
70.0% done
Time since last update: 15.876121282577515
75.00000000000001% done
Time since last update: 17.292492151260376
80.00000000000001% done
Time since last update: 29.57323122024536
85.00000000000001% done
Time since last update: 65.96741080284119
90.00000000000003% done
Time since last update: 148.21749567985535
95.00000000000003% done
the code in question to run the main loop of the program is this
t = turtle.Turtle()
t.penup()
curr_time = time()
for x in range(iterations):
#The code will take a while should the triangle be large, so this will give its % completion
if x / iterations > percent_done:
print(percent_done * 100, r"% done", sep='')
percent_done += 0.05
window.update()
print(f"Time since last update:\t{time() - curr_time}")
curr_time = time()
c = determine_point(t, init_c)
make_point(t, c)
determine_point and make_point do not iterate at all, so I can't find a reason for turtle to slow down this considerably. Why does turtle slow down as more points are being created? (screen tracer has already been set to (0,0) and the code itself works as intended
make point:
def make_point(turt: turtle.Turtle, point):
'''
Basically does turt.goto(*point) where point is a list of length 2, then places a dot at that place
I did not use goto because I forgot what the function was, and instead of doing a 2 second google search to find out,
I did a 15 second google search to find out how to set the angle of the turtle, and use trigonometry and pythagorean
theorem in order to move the turt to the right position
'''
if point[0] != turt.xcor():
y = point[1] - turt.ycor()
x = point[0] - turt.xcor()
if x > 0:
turt.setheading(math.degrees(math.atan(y / x)))
else:
turt.setheading(math.degrees(math.pi + math.atan(y / x)))
else:
turt.setheading(0 if point[1] < turt.ycor() else 180)
turt.fd(pythag(turt.xcor(), turt.ycor(), point[0], point[1]))
turt.dot(3)
determine_point:
def determine_point(turt: turtle.Turtle, initial_cords):
'''
Returns the midpoint between the turt's current coordinates and a random one of the of the 3 starting points
'''
coord = initial_cords[random.randint(0, 2)]
result = []
result.append((coord[0] - turt.xcor()) / 2 + turt.xcor())
result.append((coord[1] - turt.ycor()) / 2 + turt.ycor())
return result
pythag:
def pythag(a, b, x, y):
'''
Does pythagorean theorem to find the length between 2 points
'''
return math.sqrt((x - a) ** 2 + (y - b) ** 2)

Although your code doesn't require more resources on each iteration, your program does because it is built atop libraries that do. This is true of both turtle and tkinter.
Although we think of turtle dot() as putting up dead ink, as opposed to stamp which can be selectively removed, to the underlying tkinter graphics, everything is live ink and adds to its (re)display lists.
I couldn't completely remove the increase in time for each iteration, but by optimizing the code, I believe I've reduced it to no longer being an issue. Some of my optimizations: put turtle into radians mode to avoid calls to math.degrees(); reduce calls into turtle, eg. by getting x and y in one step via position() rather than calling xcor() and ycor(); turn off turtle's undo buffer as it keeps a growing list of graphic commands:
from turtle import Screen, Turtle
from time import time
from random import choice
from math import sqrt, atan, pi
iterations = 100_000
init_c = [(300, -300), (300, 300), (-300, -300)]
def make_point(t, point):
'''
Basically do turtle.goto(*point) where point is a list of length 2,
then places a dot at that place
I did not use goto() because I forgot what the function was, and instead
of doing a 2 second google search to find out, I did a 15 second google
search to find out how to set the angle of the turtle, and use trigonometry
and pythagorean theorem in order to move the turtle to the right position
'''
x, y = t.position()
if point[0] != x:
dx = point[0] - x
dy = point[1] - y
if dx > 0:
t.setheading(atan(dy / dx))
else:
t.setheading(pi + atan(dy / dx))
else:
t.setheading(0 if point[1] < y else pi)
t.forward(pythag(x, y, point[0], point[1]))
t.dot(2)
def determine_point(t, initial_cords):
'''
Return the midpoint between the turtles's current coordinates
and a random one of the of the 3 starting points
'''
coord = choice(initial_cords)
x, y = t.position()
return [(coord[0] - x) / 2 + x, (coord[1] - y) / 2 + y]
def pythag(a, b, x, y):
'''
Do pythagorean theorem to find the length between 2 points
'''
return sqrt((x - a) ** 2 + (y - b) ** 2)
screen = Screen()
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.setundobuffer(None)
turtle.radians()
turtle.penup()
fraction_done = 0
curr_time = time()
for iteration in range(iterations):
# The code will take a while should the triangle be large, so this will give its % completion
if iteration / iterations > fraction_done:
screen.update()
print(fraction_done * 100, r"% done", sep='')
fraction_done += 0.05
print(f"Time since last update:\t{time() - curr_time}")
curr_time = time()
make_point(turtle, determine_point(turtle, init_c))
screen.update()
screen.exitonclick()
CONSOLE
% python3 test.py
0% done
Time since last update: 0.024140119552612305
5.0% done
Time since last update: 1.2522082328796387
10.0% done
Time since last update: 1.619455099105835
15.000000000000002% done
Time since last update: 1.9793877601623535
20.0% done
...
Time since last update: 9.460317373275757
85.00000000000001% done
Time since last update: 10.161489009857178
90.00000000000003% done
Time since last update: 10.585438966751099
95.00000000000003% done
Time since last update: 11.479820966720581

Related

pygame.Rect objects keep getting stuck at top = 0 and don't move when .move or .move_ip is used

This is a small part of some more complicated code but basically. I've narrowed down the problem to this one bit of code.
I'm using the formula s = ut + 1/2 at^2 to get the distance traveled in one instance to time, in this case, 0.001 seconds which will then further be used to draw the rects on a window.
import pygame
run = True
clock = pygame.time.Clock()
ex_s = pygame.Rect((500, 625, 50, 50))
start_height = ex_s.top
print(start_height, "m")
g = -9.8
v = 400
t = 0.001
while run:
ex_s.top -= (v * t) + ((g / 2) * (t ** 2))
print(ex_s.top - start_height)
if (ex_s.top - start_height) * -1 <= -10:
run = False
clock.tick(30)
I want the rectangles to move upwards/downwards based on their velocity.
The rectangles move up and get stuck whenever the top reaches y = 0. This doesn't happen if the value of "t" is increased to o.o1 or above but I need it to be 0.001 for the precision. The problem is also solved when the value of v is increased by an order of magnitude, changing g does nothing.
I tried using both move and move_ip and the y coordinate did not change at all.
I've also tried manually pushing the objects above y = 0 when they hit it but it doesn't do anything. The fact that the problem changes with decimal places has led me to believe it could be something to do with the integer coordinate system of pygame but I don't see how this would cause things to move first and then get stuck only at 0.
See Pygame doesn't let me use float for rect.move, but I need it. The problem is that a pygame.Rect object can only store integer data. So when you add a value less than 1.0 to the coordinate of a rectangle, than the position stays the same. You need to store the position of the rectangle in separate variables and update the position of the rectangle using these variables.
ex_s = pygame.Rect((500, 625, 50, 50))
ex_s_y = ex_s.top
# [...]
while run:
# [...]
# change position
ex_s_y = .....
ex_s.top = round(ex_s_y)
You didn't change the time t variable, and your formula for the motion is wrong. Also your initial time, initial velocity and g are wrong.
If you want to use an exact solution for the position, do like this.
dt = 0.05
t = 0
g = 9.8 # It's positive by definition.
v = -100 # It's upward.
while run:
y = 625 + v*t + 0.5*g*t**2 # The acceleration is +g, downward.
t += dt
ex_s.top = y
...
If you want to solve the equation of motion approximately and iteratively, do like this inside the loop.
...
y = 625
while run:
v += g * dt
y += v * dt
t += dt
ex_s.top = y
...
The above method is called sympletic Euler method. There're other more accurate methods, such as Runge-Kutta method.

penup() first time through for loop

I'm trying to get Python's Turtle graphics to plot a series of calculated x and y values based on the formulas below. All the calculations work, but I only want the pen down starting at the first calculated (x, y) point, not at the origin. My idea was to check when i = 0 (the first time through the for loop) and raise the pen before going to the first calculated (x, y) point. After that, when i is larger than 0, the pen would be down and a line would be drawn to the next calculated (x, y) point. I'm sure there's a simple to fix to my code, but I can't spot it. Maybe someone (everyone???) can point me to the error(s)??? Thanks!
penup()
for i in range(20000):
speed(10)
x1 = 200*sin(f1*t + p1)*e**(-t*d1) + 200*sin(f2*t + p2)*e**(-t*d2)
y1 = 200*sin(f3*t + p3)*e**(-t*d3) + 200*sin(f4*t + p4)*e**(-t*d4)
setpos(x1,y1)
pendown()
t += dt

Why does my pygame code become painfully slow after a certain time? Is there a way to make it run faster?

I wrote a program to model a phenomenon called diffusion limited aggregation, using the random motion of squares in pygame. The idea is that there is a stem and every particle (square) that touches it sticks to it and becomes part of the stem.
The code seems to work, but after like 30 seconds to a minute it starts to slow down quite a bit. I cannot figure out why.
import pygame
import random
#changing the row number will change the size of the squares, and bassically the size of the invisible 'array'
width = 1000
rows = 500
d = width//rows
e = {}
squares = []
accreted = []
#note: all positions are noted in array-like notation (a matrix of dimensions rows x rows)
#to convert it back to normal notation, do (square.position[0] * d, square.position[1] * d)
class square:
def __init__(self, position):
self.position = (position)
#method to move a square in a random direction (no diagonal) (brownian motion)
def move(self):
a = random.randint(0, 3)
if a == 0:
new_posi = (self.position[0] + 1)
new_posj = (self.position[1])
elif a == 1:
new_posi = (self.position[0] - 1)
new_posj = (self.position[1])
elif a == 2:
new_posi = (self.position[0])
new_posj = (self.position[1] + 1)
else:
new_posi = (self.position[0])
new_posj = (self.position[1] - 1)
if new_posj<0 or new_posi<0 or new_posi>rows or new_posj>rows:
self.move()
else:
self.position = (new_posi, new_posj)
pygame.draw.rect(win, (255, 255, 255), [new_posi * d, new_posj * d, d, d])
def accrete(square):
accreted.append(square)
if square in squares:
squares.remove(square)
def redrawWindow(win):
win.fill((0, 0, 0))
pygame.draw.rect(win, (255, 255, 255), [stem.position[0] * d, stem.position[1] * d, d, d])
for square in squares:
square.move()
# here we make it so that every square that touches the stem stops moving, then a square that touches this square stops moving, etc.
for accret in accreted:
if square.position[1] == accret.position[1]+1 and square.position[0] == accret.position[0]:
accrete(square)
elif square.position[1] == accret.position[1]-1 and square.position[0] == accret.position[0]:
accrete(square)
elif square.position[1] == accret.position[1] and square.position[0] == accret.position[0]+1:
accrete(square)
elif square.position[1] == accret.position[1] and square.position[0] == accret.position[0]-1:
accrete(square)
for accret in accreted:
pygame.draw.rect(win, (255, 255, 255), [accret.position[0] * d, accret.position[1] * d, d, d])
pygame.display.update()
def main():
global win
win = pygame.display.set_mode((width, width))
clock = pygame.time.Clock()
while True:
# pygame.time.delay(5)
# clock.tick(64)
redrawWindow(win)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
#by changing the range here, we change how many squares are created
for i in range(5000):
e["accreted{0}".format(i)] = square((random.randint(0, rows), random.randint(0, rows)))
squares.append(e["accreted{0}".format(i)])
#a stem to start accretion from
stem = square((rows/2, rows/2))
accrete(stem)
main()
I've watched your simulation run for a while on my MacBook Pro. It seems that on my system, it takes quite a bit longer than a minute...maybe more like 5 or so...before it starts to quite obviously slow down. But it does nonetheless.
I think the problem is that you are building this "accreted" structure during your simulation, and due to this, the number of "accreted" squares (those stored in the accreted list) keeps increasing. For each iteration of your program, your code needs to compare the position of each live square against the position of each "accreted" square. So over time, the number of comparisons you have to do continues to grow.
You likely need to find a way to optimize your algorithm if you want to be able to maintain the update speed (the frame rate) as the sim progresses. You need to figure out how to be smarter about performing your comparisons to somehow avoid this geometric progression in iteration time that occurs as the structure you're building grows.
UPDATE AND POSSIBLE OPTIMIZATION: I see a pretty simple optimization that you could add to your code to greatly speed it up. What you can do is maintain a bounding box around your "accreted" squares. When you add a new square to that list, you increase the size of the bounding box, if necessary, so that it contains the new square. Now, when you first check for a collision between a live square and an the list of accreted squares, you can first check for if the live square is within the bounding box of the accreted squares (with a little extra margin as appropriate) before testing for a collision between that square and any one accreted squares. This would let you immediately rule out collisions between most of the live squares and the accreted squares with just one collision test for each live square. This should have the effect of allowing your code to stay about as fast in later rounds as it is in the early rounds, since most of the live squares will always be trivially rejected as collision candidates regardless who big the accreted structure gets.
UPDATE 2: What I describe is definitely what's going on with your code. I added a little code to count the number of collision tests you perform in each round of your sim. Here's the number of tests being performed at one second intervals, along with how long one iteration of your sim is taking, in seconds:
0 5000 0.023629821000000106
1 9998 0.023406135000000106
2 24980 0.03102543400000002
...
30 99680 0.07482247300000111
31 99680 0.08382184299999551
...
59 114563 0.08984024400000123
60 114563 0.087317634999998
The first iteration of your code does 5000 hit tests, as expected, and a single iteration of your sim takes about .023 seconds. After a minute, your sim is having to do more than 20 times as many tests per iteration, 114563, and now a single iteration is taking .087 seconds. This problem just keeps growing, and your code just keeps slowing down. (It's interesting to note that most of the "progress" of your sim up to one minute has occurred in the first 30 seconds. For this run, only 3 accretes occurred in the second 30 second interval.)
The first and probably best optimization is to keep a list of "forbidden" squares that will trigger the freeze of a particle instead of iterating over all positions in accreted multiple times.
So e.g. when we start with the first stem particle (or square, or whatever you call it), we also store the positions above, beneath, left and right of that particle's position in a set. It's important to use a set because looking up items in a set is much, much faster that using a list.
After moving a square, we now check if that square's new position is in this set. If it is, we add all adjacent positions to that set, also.
The next thing we can improve is the list of squares itself. Instead of removing a freezed square from the list, we create a new list every frame and add all squares that are not freezed this turn. (Your code is actually buggy by not making a copy of the list of squares and iterating over that copy while removing items from the original list) I'll use a deque since adding to it is slightly faster that a regular list.
Another bottleneck is the huge amount of random numbers you create each frame. random.randint() becomes painfully slow. We could create a list of random numbers at the start of the script and use that so we don't have to create new random numbers while running.
We could also change the drawing. Using pygame.draw.rect 5000 times is also quite slow. Let's create a surface and blit it with pygame's new batch function Surface.blits (I guess using pygame.surfarray to manipulate the screen surface directly would be even faster).
In the code below I also implemented the bounding box suggested by CryptoFool, because why not, but the biggest speed up is using a set as I described above.
With these changes, I get ~200 FPS without any slowdown over time:
import pygame
import numpy as np
import random
from collections import deque
def get_rand_factory():
length = 100000000
sample = np.random.randint(1, 5, length).tolist()
index = -1
def inner():
nonlocal index
index += 1
if index == length:
index = 0
return sample[index]
return inner
get_rand = get_rand_factory()
def move(x, y, rows):
dx, dy = x, y
a = get_rand()
if a == 1: dx += 1
elif a == 2: dx -= 1
elif a == 3: dy += 1
else: dy -= 1
if dx<0 or dy<0 or dx>rows or dy>rows:
return move(x, y, rows)
return dx, dy
def get_adjacent(x, y):
for dx, dy in (1, 0), (-1, 0), (0, 1), (0, -1):
yield x + dx, y + dy
def get_bounds(positions):
min_x, min_y, max_x, max_y = 9999, 9999, 0, 0
for x, y in positions:
min_x = min(min_x, x)
min_y = min(min_y, y)
max_x = max(max_x, x)
max_y = max(max_y, y)
return min_x, min_y, max_x, max_y
def main():
width = 1000
rows = 500
d = width//rows
squares = deque()
accreted = set()
adjacent_accreted = set()
win = pygame.display.set_mode((width, width))
clock = pygame.time.Clock()
for i in range(5000):
pos = (random.randint(0, rows), random.randint(0, rows))
squares.append(pos)
stem = (rows/2, rows/2)
accreted.add(stem)
adjacent_accreted.add(stem)
for adj in get_adjacent(*stem):
adjacent_accreted.add(adj)
rect_white = pygame.Surface((d, d))
rect_white.fill('white')
rect_blue = pygame.Surface((d, d))
rect_blue.fill((255, 0, 255))
bounds = get_bounds(adjacent_accreted)
min_x, min_y, max_x, max_y = bounds
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
win.fill((0, 0, 0))
new_state = deque()
for x, y in squares:
die = False
new_pos = move(x, y, rows)
if min_x <= new_pos[0] <= max_x and min_y <= new_pos[1] <= max_y:
if new_pos in adjacent_accreted:
accreted.add(new_pos)
adjacent_accreted.add(new_pos)
for adj in get_adjacent(*new_pos):
adjacent_accreted.add(adj)
die = True
bounds = get_bounds(adjacent_accreted)
min_x, min_y, max_x, max_y = bounds
if not die:
new_state.append(new_pos)
squares = new_state
win.blits(blit_sequence=((rect_blue, (pos[0]*d, pos[1]*d)) for pos in accreted))
win.blits(blit_sequence=((rect_white, (pos[0]*d, pos[1]*d)) for pos in squares))
pygame.draw.rect(win, (0, 255, 255), [bounds[0] * d, bounds[1] * d, (bounds[2]-bounds[0]) * d, (bounds[3]-bounds[1]) * d], 1)
pygame.display.update()
pygame.display.set_caption(f'{clock.get_fps():.2f} {len(squares)=} {len(accreted)=}')
clock.tick()
main()
Have a look at your code:
def accrete(square):
accreted.append(square)
if square in squares:
squares.remove(square)
squares is a list containing up to 5000 items. Searching for any content needs up to 5000 comparisons because there is no index and all items have to be checked until it is found in the list. Use a set instead if the entries are unique and the order does not care. A set in indexed and searching for an item runs very fast.

Changing all items in a list at once

I am trying to create a simple "Particle in a box" in Python, where multiple particles are bouncing back and forth between boundaries.
I need to keep track of the x-pos and y-pos of the particle and the x- and y-components of the speed to plot these at the end.
By each dt, the program will calculate the new position. Instead of looping through each particle, I want to update the entire list at once. Otherwise, the calculation and replacements will take forever for more particles.
This question is already asked. However, I calculate each value each step. This is different from changing an item to a predetermined value.
So, how do I replace each item at once in a list after calculating the new value?
dt = 0.001
pos_x = []
pos_y = []
speed_x = []
speed_y = []
For-loop to set the speed of each particle:
for i in range(5):
alpha = random.random() * 360
speed = 0.1 * random.random() * alpha
speed_x.append(math.sin(speed))
speed_y.append(math.cos(speed))
pos_x.append(0.25)
pos_y.append(0.75)
For-loop to update the position of each particle:
for n in range(5):
pos_x[n] = pos_x[n] + speed_x[n] * dt
pos_y[n] = pos_y[n] + speed_y[n] * dt
After this, I will plot all the points and update the window each pause to let them move.
import numpy as np
if __name__ == "__main__":
pos = np.array([5,5,5,5,5])
speed = np.array([2,2,2,2,2])
new_pos = pos + speed * 0.01
print(new_pos)
Output:
[5.02 5.02 5.02 5.02 5.02]
With the numpy package you can easily add arrays together or multiply them with predefined values.

Modelize a spring Python

I need to model a spring in Python for one of my classes at the university and it doesn't really work. I've the second order differential equation and I translated it in 2 first order ones to have a system.
Can someone look my code and give me an idea about what could I do? For me, the equations are good, I just don't know how to draw it...
from math import pi
from time import sleep
from turtle import *
k=1
m=1
def main():
L = 5
total_time = 0
vo = 0 #Vitesse angulaire initial (rad/s)
time_step = 0.05
while total_time < 100:
total_time += time_step
vo += (-k/m) * L * time_step
L += vo * time_step
if draw(L): break
sleep(time_step)
def init():
setup()
mode('logo')
radians()
speed(0)
tracer(False)
hideturtle()
def draw(L):
if speed() != 0: return True
clear()
pendown()
setheading(L)
penup()
pensize(5)
pencolor('red')
dot(L * 10)
home
update()
if __name__ == '__main__':
init()
main()
Your equations are good, as far as I remember from my physic lessons, but the L variable is varying from -5 to +5, not from 0 to +5.
The dot(radius) function draws a circle. The radius needs to be positive.
Just replace :
dot(L * 10)
by :
dot(51 + L * 10)
(I put "51" instead of "50" to be sure to avoid errors due to crappy approximation of float numbers)
Some little tip : when you don't understand what happens, try to determine the current values of your variables.
You could have guessed that L varies from -5 to +5 by adding print("vo:", vo, "L:", L, "total_time:", total_time) in the loop of the main function.

Categories

Resources