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.
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
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.
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.
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.