So I created a recurring pattern using two different designs. The problem now is making the colours, so I have to ask the user to pick 3 different colours and use them to make certain areas of the designs that colour. For example what I mean is, if I were to choose the 5x5 patch layout then the first 2 boxes of patterns across and below should be one colour (so that's four boxes) and so on.
EDIT: to clear up the confusion.
So if you look at the 500x500 one. type '5' when it asks for what patch size you want.
you should see two different pattern designs.
all I want is the x = 0 to x = 200 and y = 0 to y = 200 to be blue colour. Meaning that everything in that area (by area, I mean all the pattern designs that are in there) should be a blue colour. The same colour should be used for x = 300 to x = 500 and y = 300 to y = 500.
the x = 300 to x = 500 and y = 0 to y = 200 should be red colour and the x = 0 to x = 200 and y = 300 to y = 500 should also be red.
and all that is left is the '+' part in the middle which should have the third colour, green.
https://prntscr.com/m48xxd this is what I mean by the colours. so the top left and bottom right are the same colours. the top right and bottom left are te same colours and the '+' is another colour.
7.https://prntscr.com/m48xzm here is the 7x7 version
I just put colours, for now, to make it easier to look at.
I have already tried doing the 'getX' and 'getY' but I can't seem to get it right.
from graphics import *
def main():
global patchWorkSize
patchWorkSize = int(input("What patchwork size would you like?"))
if patchWorkSize == 5:
patchWork5()
elif patchWorkSize == 7:
patchWork7()
elif patchWorkSize == 9:
patchWork9()
else:
print('Only patchwork sizes 5, 7 and 9 are avaliable')
def patchWork5():
layout()
for y in range(0,500,100):
for x in range(0,500,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,500,100):
for t in range(100,500,200):
for o in range(0,500,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork7():
layout()
for y in range(0,700,100):
for x in range(0,700,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,700,100):
for t in range(100,700,200):
for o in range(0,700,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork9():
layout()
for y in range(0,900,100):
for x in range(0,900,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,900,100):
for t in range(100,900,200):
for o in range(0,900,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def layout():
global win
win = GraphWin('Patchwork',patchWorkSize*100, patchWorkSize*100)
for i in range(0, patchWorkSize*100,100):
line = Line(Point(i,0), Point(i, patchWorkSize*100))
line2 = Line(Point(0,i), Point(patchWorkSize*100, i))
line.draw(win)
line2.draw(win)
main()
The key is to paramertize everything into cubes of a convenient size, and then turn the filling code into subroutines that can be called to fill those cubes. Below is a rework of your code along these lines that can handle any odd number 3 or greater as an input:
from graphics import *
UNIT = 100
def patchWork(win, size):
blocks = size // 2
def hatch_box(x, y):
for n in range(0, UNIT, UNIT//5):
line = Line(Point(n + x * UNIT, y * UNIT), Point((x + 1) * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point(n + x * UNIT, (y + 1) * UNIT), Point(x * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point((x + 1) * UNIT, n + y * UNIT), Point(n + x * UNIT, (y + 1) * UNIT))
line.setFill('red')
line.draw(win)
for n in range(0, UNIT, UNIT//5):
line = Line(Point(x * UNIT, UNIT - n + y * UNIT), Point(UNIT - n + x * UNIT, y * UNIT))
line.setFill('red')
line.draw(win)
for y in range(blocks):
for x in range(blocks):
hatch_box(x, y)
for y in range(blocks + 1, 2 * blocks + 1):
for x in range(blocks + 1, 2 * blocks + 1):
hatch_box(x, y)
def draw_circles(x, y):
for o in range(0, UNIT, UNIT // 10):
for c in range(0, UNIT, UNIT // 10):
circle = Circle(Point(c + UNIT // 20 + x * UNIT, o + UNIT // 20 + y * UNIT), UNIT // 20)
circle.setFill('blue')
circle.draw(win)
for y in range(blocks):
for x in range(blocks + 1, 2 * blocks + 1):
draw_circles(x, y)
draw_circles(y, x)
def draw_cube(x, y):
cube = Rectangle(Point(x * UNIT, y * UNIT), Point((x + 1) * UNIT, (y + 1) * UNIT))
cube.setFill('yellow')
cube.draw(win)
x = blocks
for y in range(0, 2 * blocks + 1):
draw_cube(x, y)
draw_cube(y, x)
def layout(size):
win = GraphWin('Patchwork', size * UNIT, size * UNIT)
for i in range(0, size * UNIT, UNIT):
Line(Point(i, 0), Point(i, size * UNIT)).draw(win)
Line(Point(0, i), Point(size * UNIT, i)).draw(win)
return win
def main():
patchWorkSize = int(input("What patchwork size would you like? "))
if patchWorkSize % 2 == 1 and patchWorkSize > 2:
win = layout(patchWorkSize)
patchWork(win, patchWorkSize)
win.getMouse() # pause for click in window
win.close()
else:
print('Only odd sizes 3 or greater are available')
main()
You should be able to change UNIT, within reason.
I'm not going to write everything for you, but the code below shows how to choose the color for each area based on its position within the patchwork grid (i and j). The color is determined in color_func().
Hopefully this will be enough for you to figure out how to apply the relatively simple coding pattern shown to draw the desired graphics.
Hint: Compute the coordinates of each graphics element based on (or relative to) these same position values (i.e. don't write a separate function for every possible patchwork size).
This code still seems overly repetitive to me, and could probably be made more concise, but I'll leave that to you, too... ;¬)
def main():
# size = int(input("What patchwork size would you like?"))
# Hardcoded for testing.
#
# if size % 2 == 0:
# raise RuntimeError('patchwork size must be odd')
patch_work(5)
print()
patch_work(7)
def patch_work(n):
colors = '0', '1', '2'
mid = n // 2
# Call color_func() for every possible position in patchwork and
# prints the results in rows.
for i in range(n):
row = []
for j in range(n):
row.append(color_func(n, i, j, colors))
print(''.join(row))
def color_func(n, i, j, colors):
mid = n // 2
if i == mid:
index = 2
elif i < mid:
if j < mid:
index = 0
elif j == mid:
index = 2
elif j > mid:
index = 1
elif i > mid:
if j < mid:
index = 1
elif j == mid:
index = 2
elif j > mid:
index = 0
return colors[index]
if __name__ == '__main__':
main()
Output:
00211
00211
22222
11200
11200
0002111
0002111
0002111
2222222
1112000
1112000
1112000
Related
I was trying to recreate Conway's Game of Life in python using Tkinter, but I got stuck while checking for the neighbors. I can't seem to find the solution to my problem.
If a cell is alive it is stored using numpy, 0 for the dead cells and 1 for the living cells. The function "get_neighbours" checks if the cell is not in a corner or next to a side and it returns the ammount of neighbours each cell has. The function "recalculate" basically makes a new board with 0s and 1s which then replaces the original board and redraws everything. But when compared to an already existing game the progress is different.
import tkinter as tk
import numpy as np
win = tk.Tk()
WIDTH = 500
HEIGHT = 500
vs = 10
absvs = vs
cells = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
cells_new = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
def get_neighbours(x, y):
total = 0
if x > 0:
total += cells[x - 1, y]
if x > 0 and y > 0:
total += cells[x - 1, y - 1]
if y > 0:
total += cells[x, y - 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
if x < (WIDTH // absvs - 1):
total += cells[x + 1, y]
if x < (WIDTH // absvs - 1) and y < (HEIGHT // absvs - 1):
total += cells[x + 1, y + 1]
if y < (HEIGHT // absvs - 1):
total += cells[x, y + 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
return total
def recalculate():
global cells, cells_new
for y in range(HEIGHT//absvs):
for x in range(WIDTH//absvs):
temp = get_neighbours(x, y)
if (temp == 2 and cells[x, y] == 1) or (temp == 3 and cells[x, y] == 1):
cells_new[x, y] = 1
elif temp == 3 and cells[x, y] == 0:
cells_new[x, y] = 1
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
cells = cells_new
canvas.delete("all")
create_stage()
redraw_cell()
def slider_changer(e):
global vs
canvas.delete("all")
vs = w.get()
create_stage()
redraw_cell()
def create_cell(e):
global cells
tx = e.x // vs
ty = e.y // vs
x = tx * vs
y = ty * vs
canvas.create_rectangle(x, y, x + vs, y + vs, fill="gray")
cells[tx, ty] = 1
print(get_neighbours(tx, ty))
def redraw_cell():
for x in range(WIDTH//vs):
for y in range(HEIGHT//vs):
if cells[x, y] == 1:
canvas.create_rectangle(x * vs, y * vs, x * vs + vs, y * vs + vs, fill="gray")
def create_stage():
for x in range(WIDTH//vs):
canvas.create_line(x*vs, 0, x*vs, HEIGHT)
for y in range(HEIGHT//vs):
canvas.create_line(0, y*vs, WIDTH, y*vs)
canvas = tk.Canvas(width = WIDTH, height = HEIGHT, bg = "white")
canvas.pack()
w = tk.Scale(win, from_=10, to=50, orient="horizontal", command = slider_changer, length = 500)
w.pack()
w2 = tk.Button(win, text = "PRESS ME!!!", command = recalculate)
w2.pack()
create_stage()
canvas.bind("<Button-1>", create_cell)
win.mainloop()
There are these issues in your code:
In get_neighbours the last if block is the same as the fourth if block, and so there is a neighbor that is counted twice and another neighbor that isn't counted at all (the one at x + 1 and y - 1). So replace this:
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
with:
if x < (WIDTH // absvs - 1) and y > 0:
total += cells[x + 1, y - 1]
In recalculate the assignment to cells at the end is making you lose one of the two arrays. Now both cells and new_cells reference the very same array1. This means the next iteration will not be calculated correctly. Instead, you should copy the content of cells_new into cells. So replace:
cells = cells_new
with:
cells = cells_new.copy()
In recalculate the if...elif..elif construct (inside the double loop) does not deal with the case where temp == 2 and cells[x, y] == 1 and so cells_new[x, y] might not be reset to 0 when it should. In fact, the last part of this construct should not have a condition; it should be a catch-all for all states that the previous checks did not deal with. So replace:
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
with:
else:
cells_new[x, y] = 0
1 It is not helping that several websites, including https://www.geeksforgeeks.org/how-to-copy-numpy-array-into-another-array/ and https://www.askpython.com/python-modules/numpy/numpy-copy, wrongly assert that an assignment of one numpy array to another makes a copy. That is not true.
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)
I am new to programming, so I hope my stupid questions do not bug you.
I am now trying to calculate the poisson sphere distribution(a 3D version of the poisson disk) using python and then plug in the result to POV-RAY so that I can generate some random distributed packing rocks.
I am following these two links:
[https://github.com/CodingTrain/Rainbow-Code/blob/master/CodingChallenges/CC_33_poisson_disc/sketch.js#L13]
[https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf]
tl;dr
0.Create an n-dimensional grid array and cell size = r/sqrt(n) where r is the minimum distance between each sphere. All arrays are set to be default -1 which stands for 'without point'
1.Create an initial sample. (it should be placed randomly but I choose to put it in the middle). Put it in the grid array. Also, intialize an active array. Put the initial sample in the active array.
2.While the active list is not empty, pick a random index. Generate points near it and make sure the points are not overlapping with nearby points(only test with the nearby arrays). If no sample can be created near the 'random index', kick the 'random index' out. Loop the process.
And here is my code:
import math
from random import uniform
import numpy
import random
radius = 1 #you can change the size of each sphere
mindis = 2 * radius
maxx = 10 #you can change the size of the container
maxy = 10
maxz = 10
k = 30
cellsize = mindis / math.sqrt(3)
nrofx = math.floor(maxx / cellsize)
nrofy = math.floor(maxy / cellsize)
nrofz = math.floor(maxz / cellsize)
grid = []
active = []
default = numpy.array((-1, -1, -1))
for fillindex in range(nrofx * nrofy * nrofz):
grid.append(default)
x = uniform(0, maxx)
y = uniform(0, maxy)
z = uniform(0, maxz)
firstpos = numpy.array((x, y, z))
firsti = maxx // 2
firstj = maxy // 2
firstk = maxz // 2
grid[firsti + nrofx * (firstj + nrofy * firstk)] = firstpos
active.append(firstpos)
while (len(active) > 0) :
randindex = math.floor(uniform(0,len(active)))
pos = active[randindex]
found = False
for attempt in range(k):
offsetx = uniform(mindis, 2 * mindis)
offsety = uniform(mindis, 2 * mindis)
offsetz = uniform(mindis, 2 * mindis)
samplex = offsetx * random.choice([1,-1])
sampley = offsety * random.choice([1,-1])
samplez = offsetz * random.choice([1,-1])
sample = numpy.array((samplex, sampley, samplez))
sample = numpy.add(sample, pos)
xcoor = math.floor(sample.item(0) / cellsize)
ycoor = math.floor(sample.item(1) / cellsize)
zcoor = math.floor(sample.item(2) / cellsize)
attemptindex = xcoor + nrofx * (ycoor + nrofy * zcoor)
if attemptindex >= 0 and attemptindex < nrofx * nrofy * nrofz and numpy.all([sample, default]) == True and xcoor > 0 and ycoor > 0 and zcoor > 0 :
test = True
for testx in range(-1,2):
for testy in range(-1, 2):
for testz in range(-1, 2):
testindex = (xcoor + testx) + nrofx * ((ycoor + testy) + nrofy * (zcoor + testz))
if testindex >=0 and testindex < nrofx * nrofy * nrofz :
neighbour = grid[testindex]
if numpy.all([neighbour, sample]) == False:
if numpy.all([neighbour, default]) == False:
distance = numpy.linalg.norm(sample - neighbour)
if distance > mindis:
test = False
if test == True and len(active)<len(grid):
found = True
grid[attemptindex] = sample
active.append(sample)
if found == False:
del active[randindex]
for printout in range(len(grid)):
print("<" + str(active[printout][0]) + "," + str(active[printout][1]) + "," + str(active[printout][2]) + ">")
print(len(grid))
My code seems to run forever.
Therefore I tried to add a print(len(active)) in the last of the while loop.
Surprisingly, I think I discovered the bug as the length of the active list just keep increasing! (It is supposed to be the same length as the grid) I think the problem is caused by the active.append(), but I can't figure out where is the problem as the code is literally the 90% the same as the one made by Mr.Shiffman.
I don't want to free ride this but I have already checked again and again while correcting again and again for this code :(. Still, I don't know where the bug is. (why do the active[] keep appending!?)
Thank you for the precious time.
I want to find pattern in some spectra.
Spectrum image
Pattern should look like 2 in gray circles on picture, all data looks similarly. Light blue line is the original data, dotted dark blue line - average over 6 points. I was trying to do window with some size and scan data and check whether the y-flux value drops/rise below 60 ish % but that seems to find other regions and the one that I want, or only this I don't want.
The width of pattern is not always the same in spectra that I have. There is a picture of spectrum with pattern black dashed line but my program didn't found it.
not found picture
I tried changing size of window but it doesn't help. Can I use some pattern recognition algorithm to find this patterns? Could somebody point me in some direction? Or explain in easy way since I'm kinda lost in this, please?
That's my code:
import numpy as np
import matplotlib.pyplot as plt
from astropy.io import ascii
import glob
def reading(file_name):
data = ascii.read(file_name)
lam = data['col0'][1:-1]
#data offset *10**17 + 5
flux = data['col1'][1:-1]*10**17 + 5
return lam, flux
def percentChange(startPoint,currentPoint):
return abs(((currentPoint-startPoint)/startPoint))*100.00
def window(data, size):
n = len(data)
out = []
wind = data[0 : size]
i = size
while i + size/2 < n:
wind = data[i - size/2 : i + size/2]
tmp = percentChange(wind[0], wind[-1])
if tmp > 50.:
out.append([tmp, i - size/2, i + size/2])
i = i + size
return out
def window2(data, size):
n = len(data)
out = []
wind = data[0 : size]
i = size
while i + size/2 < n:
wind = data[i - size/2 : i + size/2]
tmp = percentChange(wind[0], wind[len(wind)/2])
if tmp > 50.:
out.append([tmp, i - size/2, i + size/2])
i = i + size
return out
def plotting(lamb, flux):
plt.rcParams['font.family'] = 'freeserif'
plt.rcParams['font.size'] = 12
plt.rcParams['axes.labelsize'] = 15
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
plt.rcParams['legend.fontsize'] = 12
plt.rcParams['figure.titlesize'] = 12
plt.rcParams['xtick.minor.visible'] = True
plt.rcParams['ytick.minor.visible'] = True
plt.plot(lamb, flux)
plt.xlabel("wavelenght [A]")
plt.ylabel("flux [erg/cm^2/s/A]")
def averaging(lamb, flux, param):
end = 1480
bin_flux_1 = [np.mean(flux[i : i + param]) for i in range(0, end, param)]
bin_lam_1 = [np.mean(lamb[i : i + param]) for i in range(0, end, param)]
return bin_lam_1, bin_flux_1
def main():
param = 6
stack = 6
for name in glob.glob('TRAIN/*.dat'):
print name
lamb, flux = reading(name)
lamb_a, flux_a = averaging(lamb, flux, param)
plotting(lamb, flux)
plotting(lamb_a, flux_a)
change = window(flux_a, stack)
change2 = window2(flux_a, stack)
minim = flux_a.index(min(flux_a))
for i in range(len(change)):
plt.axvline(lamb_a[change[i][1]], color='r', linestyle='--',linewidth=1)
plt.axvline(lamb_a[change[i][2]], color='r', linestyle='--',linewidth=1)
for i in range(len(change2)):
plt.axvline(lamb_a[change2[i][1]], color='y', linestyle='-',linewidth=1)
plt.axvline(lamb_a[change2[i][2]], color='y', linestyle='-',linewidth=1)
plt.axvline(lamb_a[minim], color='k', linestyle='--',linewidth=1)
plt.show()
if __name__ == "__main__":
main()
You can do it by using Knuth–Morris–Pratt algorithm in linear O(n + m) time complexity where n and m are the lengths of text and pattern.
KMP algorithm is basically a pattern matching algorithm (finding the starting position of a needle in haystack) which works on character string.
def kmp_matcher(t, d):
n=len(t)
m=len(d)
pi = compute_prefix_function(d)
q = 0
i = 0
while i < n:
if d[q]==t[i]:
q=q+1
i = i + 1
else:
if q != 0:
q = pi[q-1]
else:
i = i + 1
if q == m:
print "pattern occurs with shift "+str(i-q)
q = pi[q-1]
def compute_prefix_function(p):
m=len(p)
pi =range(m)
k=1
l = 0
while k < m:
if p[k] <= p[l]:
l = l + 1
pi[k] = l
k = k + 1
else:
if l != 0:
l = pi[l-1]
else:
pi[k] = 0
k = k + 1
return pi
t = 'brownfoxlazydog'
p = 'lazy'
kmp_matcher(t, p)
Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.