0

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)
Treyara
  • 47
  • 1
  • 7
  • 1
    You would need to show the code for `determine_point` and `make_point`. – AKX May 08 '22 at 08:30
  • Didn't think I'd have to. I'll edit it real quick – Treyara May 08 '22 at 09:05
  • It is done, although I'm unsure if it's slowing down because of turtle within make_point – Treyara May 08 '22 at 09:09
  • 1
    Why don't you time the individual functions and see if you can narrow it down? Or step through it in a debugger / run it in a profiler. I'm not really seeing anything here that stands out to me as being a problem. – Layne Bernardo May 08 '22 at 09:46
  • So I ended up doing that, and I found the time taken to mark the points and update the window were the things taking the time. the time taken to determine the points were too small to be of note (hundredths of seconds) – Treyara May 08 '22 at 10:06

1 Answers1

1

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
cdlane
  • 40,441
  • 5
  • 32
  • 81
  • What exactly does the setundobuffer(None) do? – Treyara May 12 '22 at 17:01
  • @yeeboi, Python 3 turtle added an UNDO feature. To implement it, every graphic operation has to register on a list what it takes to undo that graphic operation. In a situation like yours, where you have thousands of dots and don't plan to undo any of them, this is additional space and time overhead you don't need. See the turtle documentation for the `undo()`, `setundobuffer()` and `undobufferentries()` methods. – cdlane May 12 '22 at 18:01