How I can make my Mandelbrot plotter faster? - python

How I can make my Python program faster? This program calculates the Mandelbrot set and draws it with turtle. I think the problem is in the for loop. Maybe the steps are taking too much time.
import numpy as np
import turtle
turtle.ht()
turtle.pu()
turtle.speed(0)
turtle.delay(0) turtle.colormode(255)
i= int(input("iteration = "))
g = int(input("accuracy = "))
xmin = float(input("X-min: "))
xmax = float(input("X-max: "))
ymin = float(input("Y-min: "))
ymax = float(input("Y-max: "))
cmode = int(255/i)
input("PRESS TO START")
for x in np.arange(xmin,xmax,1/g):
for y in np.arange(ymin,ymax,1/g):
c = x + y * 1j
z = 0
t = 1
for e in range(i):
z = z * z + c
if abs(z) > 3:
turtle.setx(g*c.real)
turtle.sety(g*c.imag)
turtle.dot(2,e*cmode,e*cmode,e*cmode)
t = 0
if t == 1:
turtle.setx(g*c.real)
turtle.sety(g*c.imag)
turtle.dot(2,"black")
input("Calculated!")
turtle.mainloop()
Here is an example

The following rework should be a hundred times faster than your original:
import numpy as np
import turtle
i = int(input("iteration = "))
g = int(input("accuracy = "))
xmin = float(input("X-min: "))
xmax = float(input("X-max: "))
ymin = float(input("Y-min: "))
ymax = float(input("Y-max: "))
cmode = int(255 / i)
input("PRESS TO START")
turtle.hideturtle()
turtle.penup()
turtle.speed('fastest')
turtle.colormode(255)
turtle.setundobuffer(None) # turn off saving undo information
turtle.tracer(0, 0)
for x in np.arange(xmin, xmax, 1 / g):
for y in np.arange(ymin, ymax, 1 / g):
c = x + y * 1j
z = 0
t = True
for e in range(i):
z = z * z + c
if abs(z) > 3.0:
turtle.setposition(g * c.real, g * c.imag)
rgb = e * cmode
turtle.dot(2, rgb, rgb, rgb)
t = False
break
if t:
turtle.setposition(g * c.real, g * c.imag)
turtle.dot(2, "black")
turtle.update()
print("Calculated!")
turtle.mainloop()
The significant change is the use of the combination of tracer() and update() to avoid visually plotting every dot for the user and just drawing as each vertical column completes.

Related

How to cut down on run-time for finding dimensions of a sphere Python?

I'm supposed to write code that prints the total number of integer solutions to the inequality x^2 + y^2 + z^2 <= n, where n is a user-inputted integer that is between 1 and 2000 (inclusive), and adds all the previous numbers of solutions (E.g. n=1 returns 7 and n=2 is 19, but it returns 26 and so forth). Here is my code:
import math
import itertools
n = int(input("Please enter an integer between 1 and 2000: "))
def sphereCombos(radius):
for i in range(1, radius+1):
count = 0
CombosList = []
rad = int(math.sqrt(i))
range_for_x= range(-rad, rad + 1)
range_for_y= range(-rad, rad + 1)
range_for_z= range(-rad, rad + 1)
total_perms = list(itertools.product(range_for_x, range_for_y, range_for_z))
for x, y, z in total_perms:
if x*x+ y*y + z*z <= i:
count = count + 1
return count
possible_combos = 0
for i in range(1, n + 1):
possible_combos = possible_combos + sphereCombos(i)
print(possible_combos)
The code works exactly as it's supposed to, but the problem is when n is set to be 2000, the program takes way too long, and I need to get it to run in 2 minutes or less. I thought using .product() would make it much faster than using three nested for loops, but that didn't end up being super true. Is there any way for me to cut down on run time?
The code is working because i is in global scope. Notice 'sphereCombos(i)'
passes i to radius. But the function is actually using global i in
rad = int(math.sqrt(i)) and in if x * x + y * y + z * z <= i:
import math
import itertools
n = int(input("Please enter an integer between 1 and 2000: "))
def sphereCombos(radius):
count = 0
rad = int(math.sqrt(radius)) # Changed from i to radius
range_for_x = range(-rad, rad + 1)
range_for_y = range(-rad, rad + 1)
range_for_z = range(-rad, rad + 1)
total_perms = list(
itertools.product(range_for_x, range_for_y, range_for_z))
for x, y, z in total_perms:
if x * x + y * y + z * z <= radius: # Changed from i to radius
count = count + 1
return count
possible_combos = 0
for i in range(1, n + 1):
possible_combos = possible_combos + sphereCombos(i)
print(possible_combos)

Missalgined circles in Fourier Series/Transform using Python and Tkinter

I made a Fourier Series/Transform Tkinter app, and so far everything works as I want it to, except that I am having issues with the circles misaligning.
Here is an image explaining my issue (the green and pink were added after the fact to better explain the issue):
I have narrowed down the problem to the start of the lines, as it seems that they end in the correct place, and the circles are in their correct places.
The distance between the correct positions and the position where the lines start seems to grow, but is actually proportional to the speed of the circle rotating, as the circle rotates by larger amounts, thus going faster.
Here is the code:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
I am aware that this is not the best way to achieve what I am trying to achieve, as using classes would be much better. I plan to do that in case nobody can find a solution, and hope that when it is re-written, this issue does not persist.
The main problem that you are facing is that you receive floating point numbers from your calculations but you can only use integers for pixels. In the following I will show you where you fail and the quickest way to solve the issue.
First your goal is to have connected lines and you calculate the points here:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
when you add the following code into this function you see that it fails there.
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
Because x and y should always match with the last point (end of the previous line) that will be endCoord[i-1][0] and endCoord[i-1][1].
to solve your problem I simply skipt the match for the sarting point of the follow up lines and took the coordinates of the previous line with the following alternated function:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
Additional proposals are:
don't use wildcard imports
import just what you really use in the code random isnt used in your example
the use of global in the global namespace is useless
create functions to avoid repetitive code
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
use .after(ms,func,*args) instead of a interrupting while loop and blocking call time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
read the PEP 8 -- Style Guide for Python
use intuitive variable names to make your code easier to read for others and yourself in the future
list_of_radii = [200] #instead of rList
as said pixels will be expressed with integers not with floating point numbers
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
using classes and a canvas for each animation will become handy if you want to show more cycles
dont use tkinters update method
As #Thingamabobs said, the main reason for the misalignment is that pixel coordinates work with integer values. I got excited about your project and decided to make an example using matplotlib, this way I do not have to work with integer values for the coordinates. The example was made to work with any function, I implemented samples with sine, square and sawtooth functions.
I also tried to follow some good practices for naming, type annotations and so on, I hope this helps you
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()

how can i get turtle to draw a web spiral?

im trying to get my turtle to draw a spiralling spiderweb but i just cant get the spiral to spiral or to loop until it hits the edge. ive tried several different things but i cant work it out. im pretty new to coding :)
from turtle import *
from math import sin, cos, pi
from random import randint
shape("circle")
turtlesize(0.3)
speed(5)
n=int(input("give number of main lines: "))
r=int(input("give length of main lines: "))
spiraldistance=r/10
angle=360/n
rad=(pi*angle)/180
for i in range(n):
forward(r)
backward(r)
right(hoek)
x=cos(rad*0)*spiraldistance
y=sin(rad*0)*spiraldistance
goto(x,y)
integers = []
for j in range(0, r):
p = 10/n
integers.append(j)
integers.append(p)
x=cos(rad*j)*(spiraldistance+p)
y=sin(rad*j)*(spiraldistance+p)
goto(x,y)
input("Press enter to finish")
i need it to spiral this way look at the screenshots
https://gyazo.com/028228823b7aab611db144436cf93868
https://gyazo.com/5c9ca19cfa34be5559bdbc3365f65f0d
pls help :(
Inside loop you have to change p but you always use the same value
p = 10/n
If you use += instead of =
p += 10/n
then you can get spiral
Example code:
from turtle import *
from math import sin, cos, pi
shape("circle")
turtlesize(0.3)
speed(0)
#---
#n = int(input("give number of main lines: "))
n = 5
#r = int(input("give length of main lines: "))
r = 200
#---
angle = 360/n
for i in range(n):
forward(r)
backward(r)
right(angle)
#---
spiraldistance = r/10
rad = (pi*angle)/180
p = 0
for j in range(r):
x = cos(rad*j) * (spiraldistance+p)
y = sin(rad*j) * (spiraldistance+p)
goto(x, y)
p += 10/n
exitonclick()
Result for n = 5:
Result for n = 15:
EDIT: To stop spiral before end of lines which have leght r you would have to compare spiraldistance+p with r` - ie
if spiraldistance+p >= r:
break
Or better use while loop for this
spiraldistance = r/10
rad = (pi*angle)/180
p = 0
j = 0
while spiraldistance+p < r:
x = cos(rad*j) * (spiraldistance+p)
y = sin(rad*j) * (spiraldistance+p)
goto(x, y)
p += 10/n
j += 1
EDIT: I added steps to choose how many times spiral "cross" every line.
from turtle import *
from math import sin, cos, pi
shape("circle")
turtlesize(0.3)
speed(0)
#--- settings ---
# number of main lines
#n = int(input("give number of main lines: "))
n = 15
# length of main lines
#r = int(input("give length of main lines: "))
length = 200
# number of steps on every main line
steps = 15
#--- main lines ---
angle = 360/n
for i in range(n):
forward(length)
backward(length)
right(angle)
#--- spiral ---
p = (length/n)/steps
rad = (pi*angle)/180
spiraldistance = 0
j = 0
while spiraldistance < length:
spiraldistance += p
x = cos(j) * spiraldistance
y = sin(j) * spiraldistance
goto(x, y)
j += rad
#--- keep open ---
#mainloop()
exitonclick()
Steps 5 and 15:

How to keep random walk scenario from going outside graphics window

I have created a random walk scenario where it takes one step in a random direction for a specific number of times. The one thing that I have run in to is that sometimes it will go off of the graphics window that I have set up and I can no longer see where it is at.
Here is the code:
from random import *
from graphics import *
from math import *
def walker():
win = GraphWin('Random Walk', 800, 800)
win.setCoords(-50, -50, 50, 50)
center = Point(0, 0)
x = center.getX()
y = center.getY()
while True:
try:
steps = int(input('How many steps do you want to take? (Positive integer only) '))
if steps > 0:
break
else:
print('Please enter a positive number')
except ValueError:
print('ERROR... Try again')
for i in range(steps):
angle = random() * 2 * pi
newX = x + cos(angle)
newY = y + sin(angle)
newpoint = Point(newX, newY).draw(win)
Line(Point(x, y), newpoint).draw(win)
x = newX
y = newY
walker()
My question is, Is there a way that I can set parameters on the graphics window so that the walker can not go outside the window? And if it tries to, it would just turn around and try another direction?
Try defining upper and lower bounds for x and y. Then use a while loop that keeps trying random points until the next one is in bounds.
from random import *
from graphics import *
from math import *
def walker():
win = GraphWin('Random Walk', 800, 800)
win.setCoords(-50, -50, 50, 50)
center = Point(0, 0)
x = center.getX()
y = center.getY()
while True:
try:
steps = int(input('How many steps do you want to take? (Positive integer only) '))
if steps > 0:
break
else:
print('Please enter a positive number')
except ValueError:
print('ERROR... Try again')
# set upper and lower bounds for next point
upper_X_bound = 50.0
lower_X_bound = -50.0
upper_Y_bound = 50.0
lower_Y_bound = -50.0
for i in range(steps):
point_drawn = 0 # initialize point not drawn yet
while point_drawn == 0: # do until point is drawn
drawpoint = 1 # assume in bounds
angle = random() * 2 * pi
newX = x + cos(angle)
newY = y + sin(angle)
if newX > upper_X_bound or newX < lower_X_bound:
drawpoint = 0 # do not draw, x out of bounds
if newY > upper_Y_bound or newY < lower_Y_bound:
drawpoint = 0 # do not draw, y out of bounds
if drawpoint == 1: # only draw points that are in bounds
newpoint = Point(newX, newY).draw(win)
Line(Point(x, y), newpoint).draw(win)
x = newX
y = newY
point_drawn = 1 # set this to exit while loop
walker()

Python random seed for Perlin noise

I've been recently looking to make procedurally generated terrain for games. I saw that Perlin noise was useful for this, and so I gave it a shot. So far, the terrain is generated beautifully. However, whenever I run the program multiple times, the terrain is the exact same. Is there any way to randomize the Perlin noise that's generated?
Code:
from opensimplex import OpenSimplex
import random
from time import time
height = 40
width = height
scale = height / 10
value = [[0 for x in range(width)] for y in range(height)]
gen = OpenSimplex()
def noise(nx, ny):
# Rescale from -1.0:+1.0 to 0.0:1.0
return gen.noise2d(nx, ny) / 2.0 + 0.5
def printBiome(y, x):
if value[y][x] <= 2:
print('O', end = " ")
elif value[y][x] >= 8:
print('M', end = " ")
else:
print('L', end = " ")
for y in range(height):
for x in range(width):
nx = x/width - 0.5
ny = y/height - 0.5
value[y][x] = 10 * noise(1 * scale * nx, 1 * scale * ny) + 0.5 * noise(2 * scale * nx, 2 * scale* ny) + 0.25 * noise(4 * scale * nx, 4 * scale * ny)
for y in range(height):
for x in range(width):
printBiome(y, x)
print()
The OpenSimplex class defaults to using seed=0. To generate a different terrain, input a different seed value:
import uuid
# http://stackoverflow.com/a/3530326/190597
seed = uuid.uuid1().int>>64
gen = OpenSimplex(seed=seed)

Categories