I was working on creating a python script that could model electric field lines, but the quiver plot comes out with arrows that are way too large. I've tried changing the units and the scale, but the documentation on matplotlib makes no sense too me... This seems to only be a major issue when there is only one charge in the system, but the arrows are still slightly oversized with any number of charges. The arrows tend to be oversized in all situations, but it is most evident with only one particle.
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
import astropy as astro
k = 9 * 10 ** 9
def get_inputs():
inputs_loop = False
while inputs_loop is False:
""""
get inputs
"""
inputs_loop = True
particles_loop = False
while particles_loop is False:
try:
particles_loop = True
"""
get n particles with n charges.
"""
num_particles = int(raw_input('How many particles are in the system? '))
parts = []
for i in range(num_particles):
parts.append([float(raw_input("What is the charge of particle %s in Coulombs? " % (str(i + 1)))),
[float(raw_input("What is the x position of particle %s? " % (str(i + 1)))),
float(raw_input('What is the y position of particle %s? ' % (str(i + 1))))]])
except ValueError:
print 'Could not convert input to proper data type. Please try again.'
particles_loop = False
return parts
def vec_addition(vectors):
x_sum = 0
y_sum = 0
for b in range(len(vectors)):
x_sum += vectors[b][0]
y_sum += vectors[b][1]
return [x_sum,y_sum]
def electric_field(particle, point):
if particle[0] > 0:
"""
Electric field exitation is outwards
If the x position of the particle is > the point, then a different calculation must be made than in not.
"""
field_vector_x = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * \
(np.cos(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
field_vector_y = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * \
(np.sin(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
"""
Defining the direction of the components
"""
if point[1] < particle[1][1] and field_vector_y > 0:
print field_vector_y
field_vector_y *= -1
elif point[1] > particle[1][1] and field_vector_y < 0:
print field_vector_y
field_vector_y *= -1
else:
pass
if point[0] < particle[1][0] and field_vector_x > 0:
print field_vector_x
field_vector_x *= -1
elif point[0] > particle[1][0] and field_vector_x < 0:
print field_vector_x
field_vector_x *= -1
else:
pass
"""
If the charge is negative
"""
elif particle[0] < 0:
field_vector_x = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * (
np.cos(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
field_vector_y = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * (
np.sin(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
"""
Defining the direction of the components
"""
if point[1] > particle[1][1] and field_vector_y > 0:
print field_vector_y
field_vector_y *= -1
elif point[1] < particle[1][1] and field_vector_y < 0:
print field_vector_y
field_vector_y *= -1
else:
pass
if point[0] > particle[1][0] and field_vector_x > 0:
print field_vector_x
field_vector_x *= -1
elif point[0] < particle[1][0] and field_vector_x < 0:
print field_vector_x
field_vector_x *= -1
else:
pass
return [field_vector_x, field_vector_y]
def main(particles):
"""
Graphs the electrical field lines.
:param particles:
:return:
"""
"""
plot particle positions
"""
particle_x = 0
particle_y = 0
for i in range(len(particles)):
if particles[i][0]<0:
particle_x = particles[i][1][0]
particle_y = particles[i][1][1]
plt.plot(particle_x,particle_y,'r+',linewidth=1.5)
else:
particle_x = particles[i][1][0]
particle_y = particles[i][1][1]
plt.plot(particle_x,particle_y,'r_',linewidth=1.5)
"""
Plotting out the quiver plot.
"""
parts_x = [particles[i][1][0] for i in range(len(particles))]
graph_x_min = min(parts_x)
graph_x_max = max(parts_x)
x,y = np.meshgrid(np.arange(graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)),
np.arange(graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)))
if len(particles)<2:
for x_pos in range(int(particles[0][1][0]-10),int(particles[0][1][0]+10)):
for y_pos in range(int(particles[0][1][0]-10),int(particles[0][1][0]+10)):
vecs = []
for particle_n in particles:
vecs.append(electric_field(particle_n, [x_pos, y_pos]))
final_vector = vec_addition(vecs)
distance = np.sqrt((final_vector[0] - x_pos) ** 2 + (final_vector[1] - y_pos) ** 2)
plt.quiver(x_pos, y_pos, final_vector[0], final_vector[1], distance, angles='xy', scale_units='xy',
scale=1, width=0.05)
plt.axis([particles[0][1][0]-10,particles[0][1][0]+10,
particles[0][1][0] - 10, particles[0][1][0] + 10])
else:
for x_pos in range(int(graph_x_min-(graph_x_max-graph_x_min)),int(graph_x_max+(graph_x_max-graph_x_min))):
for y_pos in range(int(graph_x_min-(graph_x_max-graph_x_min)),int(graph_x_max+(graph_x_max-graph_x_min))):
vecs = []
for particle_n in particles:
vecs.append(electric_field(particle_n,[x_pos,y_pos]))
final_vector = vec_addition(vecs)
distance = np.sqrt((final_vector[0]-x_pos)**2+(final_vector[1]-y_pos)**2)
plt.quiver(x_pos,y_pos,final_vector[0],final_vector[1],distance,angles='xy',units='xy')
plt.axis([graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min),graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)])
plt.grid()
plt.show()
g = get_inputs()
main(g)}
You may set the scale such that it roughly corresponds to the u and v vectors.
plt.quiver(x_pos, y_pos, final_vector[0], final_vector[1], scale=1e9, units="xy")
This would result in something like this:
If I interprete it correctly, you want to draw the field vectors for point charges. Looking around at how other people have done that, one finds e.g. this blog entry by Christian Hill. He uses a streamplot instead of a quiver but we might take the code for calculating the field and replace the plot.
In any case, we do not want and do not need 100 different quiver plots, as in the code from the question, but only one single quiver plot that draws the entire field. We will of course run into a problem if we want to have the field vector's length denote the field strength, as the magnitude goes with the distance from the particles by the power of 3. A solution might be to scale the field logarithmically before plotting, such that the arrow lengths are still somehow visible, even at some distance from the particles. The quiver plot's scale parameter then can be used to adapt the lengths of the arrows such that they somehow fit to other plot parameters.
""" Original code by Christian Hill
http://scipython.com/blog/visualizing-a-vector-field-with-matplotlib/
Changes made to display the field as a quiver plot instead of streamlines
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def E(q, r0, x, y):
"""Return the electric field vector E=(Ex,Ey) due to charge q at r0."""
den = ((x-r0[0])**2 + (y-r0[1])**2)**1.5
return q * (x - r0[0]) / den, q * (y - r0[1]) / den
# Grid of x, y points
nx, ny = 32, 32
x = np.linspace(-2, 2, nx)
y = np.linspace(-2, 2, ny)
X, Y = np.meshgrid(x, y)
charges = [[5.,[-1,0]],[-5.,[+1,0]]]
# Electric field vector, E=(Ex, Ey), as separate components
Ex, Ey = np.zeros((ny, nx)), np.zeros((ny, nx))
for charge in charges:
ex, ey = E(*charge, x=X, y=Y)
Ex += ex
Ey += ey
fig = plt.figure()
ax = fig.add_subplot(111)
f = lambda x:np.sign(x)*np.log10(1+np.abs(x))
ax.quiver(x, y, f(Ex), f(Ey), scale=33)
# Add filled circles for the charges themselves
charge_colors = {True: 'red', False: 'blue'}
for q, pos in charges:
ax.add_artist(Circle(pos, 0.05, color=charge_colors[q>0]))
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
ax.set_aspect('equal')
plt.show()
(Note that the field here is not normalized in any way, which should no matter for visualization at all.)
A different option is to look at e.g. this code which also draws field lines from point charges.
Related
I am using the arcpy module for arcGIS to implement a peano curve algorithm and provide each object in the GIS Project with a spatial order value.
I have currently defined the Peano curve but need to write cursor functions that will compute and add the outputs to the new field after calling the Peano.
This is the code that I have so far. Areas related to the question are in bold. Thank you!
#return the fractional part from a double number
def GetFractionalPart(dbl):
return dbl - math.floor(dbl)
#Return the peano curve coordinate for a given x, y value
def Peano(x,y,k):
if (k==0 or (x==1 and y==1):
return 0.5
if x <= 0.5:
if y <= 0.5:
quad = 0
elif y <= 0.5:
quad = 3
else:
quad = 2
subpos = Peano(2 * abs(x - 0.5), 2 * abs(y - 0.5), k-1)
if (quad == 1 or quad == 3):
subpos = 1 - subpos
return GetFractionalPart((quad + subpos - 0.5)/4.0)
#Import modules and create geoprocessor
import arcpy
arcpy.env.OverwriteOutput = True
#Prepare two inputs as parameters
inp_fc = arcpy.GetParameterAsText(0)
PeanoOrder_fld = arcpy.GetParameterAsText(1)
#Add the double field
arcpy.AddField_management(inp_fc, PeanoOrder_fld, "DOUBLE")
#Get the extent for the feature class
desc = arcpy.Describe(inp_fc)
extent = desc.Extent
xmin = extent.XMin
ymin = extent.YMin
xmax = extent.XMax
ymax = extent.YMax
#Compute constants to scale the coordinates
dx = xmax - xmin
dy = ymax - ymin
if dx >= dy:
offsetx = 0.0
offsety = (1.0-dy/dx)/2.0
scale = dx
else:
offsetx = (1.0 - dx/dy)/2.0
offsety = 0.0
scale = dy
**#Get each object and compute its Peano curve spatial order
#Create an update cursor
rows = arcpy.UpdateCursor(inp_fc)
rows.___?___
row = rows.next( )**
#get the X,Y coordinate for each feature
#If a polygon use centroid, If a point use point itself
while row:
if desc.ShapeType.lower() in ["polyline", "polygon"]:
pnt = row.shape.centroid
else:
pnt = row.shape.getPart(0)
#Normalization
unitx = (pnt.X - xmin) / scale + offsetx
unity = (pnt.Y - ymin) / scale + offset
**#Call the Peano Function and add to attribute field
peanoPos = Peano(unitx, unity, 32)
row.__?__
rows.__?__
row =__?___
del row, rows**
I have a function that is intended to rotate polygons by 5 degrees left or right and return their new points. This function is as follows, along with the function player_center that it requires.
# finds center of player shape
# finds slope and midpoint of each vertice-midpoint line on the longer sides,
# then the intercept of them all
def player_center(player):
left_mid = line_midpoint(player[0], player[1])
right_mid = line_midpoint(player[0], player[2])
left_mid_slope = line_slope(left_mid, player[2])
right_mid_slope = line_slope(right_mid, player[1])
left_mid_line = find_equation(player[2], left_mid_slope, True)
right_mid_line = find_equation(player[1], right_mid_slope, True)
standard_left_mid_line = slope_intercept_to_standard(left_mid_line[0], left_mid_line[1], left_mid_line[2])
standard_right_mid_line = slope_intercept_to_standard(right_mid_line[0], right_mid_line[1], right_mid_line[2])
lines = sym.Matrix([standard_left_mid_line, standard_right_mid_line])
return (float(lines.rref()[0].row(0).col(2)[0]), float(lines.rref()[0].row(1).col(2)[0]))
# rotates the player using SOHCAHTOA
# divides x coordinate by radius to find angle, then adds or subtracts increment of 5 to it depending on direction
# calculates the position of point at incremented angle, then appends to new set of points
# finally, new set is returned
# direction; 1 is left, 0 is right
def rotate_player(player, direction):
increment = math.pi/36 # radian equivalent of 5 degrees
full_circle = 2 * math.pi # radian equivalent of 360 degrees
center = player_center(player)
new_player = []
for point in player:
radius = line_distance(point, center)
point_sin = (center[1] - point[1])/radius
while (point_sin > 1):
point_sin -= 1
point_angle = math.asin(point_sin)
if (direction == 1):
if ((point_angle+increment) > math.pi * 2):
new_angle = (point_angle+increment) - math.pi * 2
else:
new_angle = point_angle + increment
else:
if ((point_angle-increment) < 0):
new_angle = 2 * math.pi + (point_angle-increment)
else:
new_angle = point_angle-increment
print("The angle was {}".format(math.degrees(point_angle)))
print("The angle is now {}".format(math.degrees(new_angle))) # print lines are for debug purposes
new_point = ((radius * math.cos(new_angle)) + center[0], -(radius * math.sin(new_angle)) + center[1])
new_player.append(new_point)
print(new_player)
return new_player
The geometric functions that it relies on are all defined in this file here:
import math
import sympy as sym
# shape is in form of list of tuples e.g [(1,1), (2,1), (1,0), (2,0)]
# angle is in degrees
# def rotate_shape(shape, angle):
def line_distance(first_point, second_point):
return math.sqrt( (second_point[0] - first_point[0]) ** 2 + (second_point[1] - first_point[1]) ** 2)
# undefined is represented by None in this program
def line_slope(first_point, second_point):
if (second_point[0] - first_point[0] == 0):
return None
elif (second_point[1] - first_point[1] == 0):
return 0
else:
return (second_point[1] - first_point[1])/(second_point[0] - first_point[0])
def line_midpoint(first_point, second_point):
return ( (first_point[0] + second_point[0])/2, (first_point[1] + second_point[1])/2 )
def find_equation(coord_pair, slope, array_form):
# Array form useful for conversion into standard form
if (array_form == True):
if (slope == 0):
intercept = coord_pair[1]
return [0, 1, intercept]
elif (slope == None):
intercept = coord_pair[0]
return [1, 0, intercept]
else:
intercept = coord_pair[1] - (coord_pair[0] * slope)
return [slope, 1, intercept]
else:
if (slope == 0):
intercept = coord_pair[1]
print("y = {0}".format(intercept))
return
elif (slope == None):
intercept = coord_pair[0]
print("x = {0}".format(intercept))
return
else:
intercept = coord_pair[1] - (coord_pair[0] * slope)
if (intercept >= 0):
print("y = {0}x + {1}".format(slope, intercept))
return
else:
print("y = {0}x - {1}".format(slope, intercept))
def find_perpendicular(slope):
if (slope == 0):
return None
elif (slope == None):
return 0
else:
return -(1/slope)
def find_perp_bisector(first_point, second_point):
# This function finds the perpendicular bisector between two points, using funcs made previously
midpoint = line_midpoint(first_point, second_point)
slope = line_slope(first_point, second_point)
return find_equation(midpoint, -(1/slope))
def find_perp_equation(x, y, m, array_form):
# returns the perpendicular equation of a given line
if (array_form == True):
return [find_perpendicular(x), y, m]
else:
if (m >= 0):
print("{0}y = {1}x + {2}".format(y, find_perpendicular(x), m))
else:
print("{0}y = {1}x - {2}".format(y, find_perpendicular(x), m))
def find_hyp(a, b):
return math.sqrt((a**2) + (b**2))
def find_tri_area(a, b, c):
# finds area of triangle using Heron's formula
semi = (a+b+c)/2
return math.sqrt(semi * (semi - a) * (semi - b) * (semi - c) )
def r_tri_check(a, b, c):
if (a**2) + (b**2) != (c**2):
print("This thing fake, bro.")
def find_point_section(first_point, second_point, ratio):
# I coded this half a year ago and can't remember what for, but kept it here anyway.
# separtions aren't necessary, but good for code readability
first_numerator = (ratio[0] * second_point[0]) + (ratio[1] * first_point[0])
second_numerator = (ratio[0] * second_point[1]) + (ratio[1] * first_point[1])
return ( first_numerator/(ratio[0]+ratio[1]), second_numerator/(ratio[0] + ratio[1]))
def slope_intercept_to_standard(x, y, b):
# x and y are the coeffients of the said variables, for example 5y = 3x + 8 would be inputted as (3, 5, 8)
if (x == 0):
return [0, 1, b]
elif (y == 0):
return [x, 0, b]
else:
return [x, -y, -b]
It mathematically seems sound, but when I try to apply it, all hell breaks loose.
For example when trying to apply it with the set polygon_points equal to [(400, 300), (385, 340), (415, 340)], All hell breaks loose.
An example of the output among repeated calls to the function upon polygon_points(outputs manually spaced for clarity):
The angle was 90.0
The angle is now 95.0
The angle was -41.633539336570394
The angle is now -36.633539336570394
The angle was -41.63353933657017
The angle is now -36.63353933657017
The angle was 64.4439547804165
The angle is now 69.4439547804165
The angle was -64.44395478041695
The angle is now -59.44395478041695
The angle was -64.44395478041623
The angle is now -59.44395478041624
The angle was 80.94458887142648
The angle is now 85.94458887142648
The angle was -80.9445888714264
The angle is now -75.9445888714264
The angle was -80.94458887142665
The angle is now -75.94458887142665
Can anyone explain this?
Too much irrelevant code, a lot of magic like this while (point_sin > 1): point_sin -= 1 - so hard to reproduce.
To rotate point around some center, you need just this (where cos(fi), sin(fi) are precalculated value in your case):
new_x = center_x + (old_x - center_x) * cos(fi) - (old_y - center_y) * sin(fi)
new_y = center_y + (old_x - center_x) * sin(fi) + (old_y - center_y) * cos(fi)
This is a built-in capability of RegularPolygon in SymPy:
>>> from sympy import RegularPolygon, rad
>>> p = RegularPolygon((0,0), 1, 5)
>>> p.vertices[0]
Point2D(1, 0)
>>> p.rotate(rad(30)) # or rad(-30)
>>> p.vertices[0]
Point2D(sqrt(3)/2, 1/2)
https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.peak_widths.html
I think the linked function can only calculate the peak widths at a relative height. Does anyone know if there is a function that calculates the width at a fixed value (peak_amplitude - x) for all peaks?
Currently I am trying to change the original inner function "_peak_widths". Fail already with the cimport. Understand the source code here only partially. I added in the code where I would make a modification.
with nogil:
for p in range(peaks.shape[0]):
i_min = left_bases[p]
i_max = right_bases[p]
peak = peaks[p]
# Validate bounds and order
if not 0 <= i_min <= peak <= i_max < x.shape[0]:
with gil:
raise ValueError("prominence data is invalid for peak {}"
.format(peak))
height = width_heights[p] = x[peak] - prominences[p] * rel_height
CHANGE HERE TO x[peak] - 3
# Find intersection point on left side
i = peak
while i_min < i and height < x[i]:
i -= 1
left_ip = <np.float64_t>i
if x[i] < height:
# Interpolate if true intersection height is between samples
left_ip += (height - x[i]) / (x[i + 1] - x[i])
# Find intersection point on right side
i = peak
while i < i_max and height < x[i]:
i += 1
right_ip = <np.float64_t>i
if x[i] < height:
# Interpolate if true intersection height is between samples
right_ip -= (height - x[i]) / (x[i - 1] - x[i])
widths[p] = right_ip - left_ip
if widths[p] == 0:
show_warning = True
left_ips[p] = left_ip
right_ips[p] = right_ip
In case this is still relevant to you, you can use scipy.signal.peak_widths "as is" to achieve what you want by passing in modified prominence_data. Based on your own answer:
import numpy as np
from scipy.signal import find_peaks, peak_prominences, peak_widths
# Create sample data
x = np.linspace(0, 6 * np.pi, 1000)
x = np.sin(x) + 0.6 * np.sin(2.6 * x)
# Find peaks
peaks, _ = find_peaks(x)
prominences, left_bases, right_bases = peak_prominences(x, peaks)
As stated in peak_widths's documentation the height at which the width is measured is calculated as
h_eval = h_peak - prominence * relative_height
We can control the latter two variables through the parameters prominence_data and rel_height. So instead of passing in the calculated prominence which differs for each peak we can create an array where all values are the same and use that to create an absolute height:
# Create constant offset as a replacement for prominences
offset = np.ones_like(prominences)
# Calculate widths at x[peaks] - offset * rel_height
widths, h_eval, left_ips, right_ips = peak_widths(
x, peaks,
rel_height=1,
prominence_data=(offset, left_bases, right_bases)
)
# Check that h_eval is 1 everywhere
np.testing.assert_equal(x[peaks] - h_eval, 1)
# Visualize result
import matplotlib.pyplot as plt
plt.plot(x)
plt.plot(peaks, x[peaks], "x")
plt.hlines(h_eval, left_ips, right_ips, color="C2")
plt.show()
As you can see the width is evaluated for each peak at the same constant offset of 1. By using the original left_bases and right_bases as provided by peak_prominences we are limiting the maximal measured width (e.g. see peaks at 299 and 533). If you want to remove that limitation you must create these arrays yourself.
I just removed the c content. Thats my solution:
def gauss(x, p): # p[0]==mean, p[1]==stdev
return 1.0/(p[1]*np.sqrt(2*np.pi))*np.exp(-(x-p[0])**2/(2*p[1]**2))
def _peak_widths(x,peaks,prop,val=3):
i_min = prop['left_bases']
i_max = prop['right_bases']
peak = peaks[0]
# Validate bounds and order
height = x[peak] - val
# Find intersection point on left side
i = peak
while i_min < i and height < x[i]:
i -= 1
left_ip = i
if x[i] < height:
# Interpolate if true intersection height is between samples
left_ip += (height - x[i]) / (x[i + 1] - x[i])
# Find intersection point on right side
i = peak
while i < i_max and height < x[i]:
i += 1
right_ip = i
if x[i] < height:
# Interpolate if true intersection height is between samples
right_ip -= (height - x[i]) / (x[i - 1] - x[i])
widths = right_ip - left_ip
left_ips = left_ip
right_ips = right_ip
return [height, widths, int(left_ips), int(right_ips)]
if __name__ == '__main__':
# Create some sample data
known_param = np.array([2.0, 0.07])
xmin,xmax = -1.0, 5.0
N = 1000
X = np.linspace(xmin,xmax,N)
Y = gauss(X, known_param)
fig, ax= plt.subplots()
ax.plot(X,Y)
#find peaks
peaks, prop = signal.find_peaks(Y, prominence = 3.1)
ax.scatter(X[peaks],Y[peaks], color='r')
#calculate peak width
y, widths, x1, x2 = _peak_widths(Y,peaks, prop)
print(f'width = { X[x1] - X[x2]}')
l = mlines.Line2D([X[x1],X[x2]], [y,y], color='r')
ax.add_line(l)
plt.show()
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.
Hi :)
i have the following python code that generates points lying on a sphere's surface
from math import sin, cos, pi
toRad = pi / 180
ox = 10
oy = -10
oz = 50
radius = 10.0
radBump = 3.0
angleMin = 0
angleMax = 360
angleOffset = angleMin * toRad
angleRange = (angleMax - angleMin) * toRad
steps = 48
angleStep = angleRange / steps
latMin = 0
latMax = 180
latOffset = latMin * toRad
if (latOffset < 0):
latOffset = 0;
latRange = (latMax - latMin) * toRad
if (latRange > pi):
latRange = pi - latOffset;
latSteps = 48
latAngleStep = latRange / latSteps
for lat in range(0, latSteps):
ang = lat * latAngleStep + latOffset
z = cos(ang) * radius + oz
radMod = sin(ang) * radius
for a in range(0, steps):
x = sin(a * angleStep + angleOffset) * radMod + ox
y = cos(a * angleStep + angleOffset) * radMod + oy
print "%f %f %f"%(x,y,z)
after that i plot the points with gnuplot using splot 'datafile'
can you give any hints on how to create deformations on that sphere?
like "mountains" or "spikes" on it?
(something like the openbsd logo ;) : https://https.openbsd.org/images/tshirt-23.gif )
i know it is a trivial question :( but thanks for your time :)
DsP
The approach that springs to my mind, especially with the way you compute a set of points that are not explicitly connected, is to find where the point goes on the sphere's surface, then move it by a distance and direction determined by a set of control points. The control points could have smaller effects the further away they are. For example:
# we have already computed a points position on the sphere, and
# called it x,y,z
for p in controlPoints:
dx = p.x - x
dy = p.y - y
dz = p.z - z
xDisplace += 1/(dx*dx)
yDisplace += 1/(dy*dy)
zDisplace += 1/(dz*dz) # using distance^2 displacement
x += xDisplace
y += yDisplace
z += zDisplace
By changing the control points you can alter the sphere's shape
By changing the movement function, you can alter the way the points shape the sphere
You could get really tricky and have different functions for different points:
# we have already computed a points position on the sphere, and
# called it x,y,z
for p in controlPoints:
xDisplace += p.displacementFunction(x)
yDisplace += p.displacementFunction(y)
zDisplace += p.displacementFunction(z)
x += xDisplace
y += yDisplace
z += zDisplace
If you do not want all control points affecting every point in the sphere, just build that into the displacement function.
How's this?
from math import sin, cos, pi, radians, ceil
import itertools
try:
rng = xrange # Python 2.x
except NameError:
rng = range # Python 3.x
# for the following calculations,
# - all angles are in radians (unless otherwise specified)
# - latitude is in [-pi/2..pi/2]
# - longitude is in [-pi..pi)
MIN_LAT = -pi/2 # South Pole
MAX_LAT = pi/2 # North Pole
MIN_LON = -pi # Far West
MAX_LON = pi # Far East
def floatRange(start, end=None, step=1.0):
"Floating-point range generator"
start += 0.0 # cast to float
if end is None:
end = start
start = 0.0
steps = int(ceil((end-start)/step))
return (start + k*step for k in rng(0, steps+1))
def patch2d(xmin, xmax, ymin, ymax, step=1.0):
"2d rectangular grid generator"
if xmin>xmax:
xmin,xmax = xmax,xmin
xrange = floatRange(xmin, xmax, step)
if ymin>ymax:
ymin,ymax = ymax,ymin
yrange = floatRange(ymin, ymax, step)
return itertools.product(xrange, yrange)
def patch2d_to_3d(xyIter, zFn):
"Convert 2d field to 2.5d height-field"
mapFn = lambda a: (a[0], a[1], zFn(a[0],a[1]))
return itertools.imap(mapFn, xyIter)
#
# Representation conversion functions
#
def to_spherical(lon, lat, rad):
"Map from spherical to spherical coordinates (identity function)"
return lon, lat, rad
def to_cylindrical(lon, lat, rad):
"Map from spherical to cylindrical coordinates"
# angle, z, radius
return lon, rad*sin(lat), rad*cos(lat)
def to_cartesian(lon, lat, rad):
"Map from spherical to Cartesian coordinates"
# x, y, z
cos_lat = cos(lat)
return rad*cos_lat*cos(lon), rad*cos_lat*sin(lon), rad*sin(lat)
def bumpySphere(gridSize, radiusFn, outConv):
lonlat = patch2d(MIN_LON, MAX_LON, MIN_LAT, MAX_LAT, gridSize)
return list(outConv(*lonlatrad) for lonlatrad in patch2d_to_3d(lonlat, radiusFn))
# make a plain sphere of radius 10
sphere = bumpySphere(radians(5.0), lambda x,y: 10.0, to_cartesian)
# spiky-star-function maker
def starFnMaker(xWidth, xOffset, yWidth, yOffset, minRad, maxRad):
# make a spiky-star function:
# longitudinal and latitudinal triangular waveforms,
# joined as boolean intersection,
# resulting in a grid of positive square pyramids
def starFn(x, y, xWidth=xWidth, xOffset=xOffset, yWidth=yWidth, yOffset=yOffset, minRad=minRad, maxRad=maxRad):
xo = ((x-xOffset)/float(xWidth)) % 1.0 # xo in [0.0..1.0), progress across a single pattern-repetition
xh = 2 * min(xo, 1.0-xo) # height at xo in [0.0..1.0]
xHeight = minRad + xh*(maxRad-minRad)
yo = ((y-yOffset)/float(yWidth)) % 1.0
yh = 2 * min(yo, 1.0-yo)
yHeight = minRad + yh*(maxRad-minRad)
return min(xHeight, yHeight)
return starFn
# parameters to spike-star-function maker
width = 2*pi
horDivs = 20 # number of waveforms longitudinally
horShift = 0.0 # longitudinal offset in [0.0..1.0) of a wave
height = pi
verDivs = 10
verShift = 0.5 # leave spikes at the poles
minRad = 10.0
maxRad = 15.0
deathstarFn = starFnMaker(width/horDivs, width*horShift/horDivs, height/verDivs, height*verShift/verDivs, minRad, maxRad)
deathstar = bumpySphere(radians(2.0), deathstarFn, to_cartesian)
so i finally created the deformation using a set of control points that "pull" the spherical
surface. it is heavilly OO and ugly though ;)
thanks for all the help !!!
to use it > afile and with gnuplot : splot 'afile' w l
DsP
from math import sin, cos, pi ,sqrt,exp
class Point:
"""a 3d point class"""
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return "%f %f %f\n"%(self.x,self.y,self.z)
def __str__(self):
return "point centered: %f %f %f\n"%(self.x,self.y,self.z)
def distance(self,b):
return sqrt((self.x - b.x)**2 +(self.y - b.y)**2 +(self.z -b.z)**2)
def displaceTowards(self,b):
self.x
class ControlPoint(Point):
"""a control point that deforms positions of other points"""
def __init__(self,p):
Point.__init__(self,p.x,p.y,p.z)
self.deformspoints=[]
def deforms(self,p):
self.deformspoints.append(p)
def deformothers(self):
self.deformspoints.sort()
#print self.deformspoints
for i in range(0,len(self.deformspoints)):
self.deformspoints[i].x += (self.x - self.deformspoints[i].x)/2
self.deformspoints[i].y += (self.y - self.deformspoints[i].y)/2
self.deformspoints[i].z += (self.z - self.deformspoints[i].z)/2
class Sphere:
"""returns points on a sphere"""
def __init__(self,radius,angleMin,angleMax,latMin,latMax,discrStep,ox,oy,oz):
toRad = pi/180
self.ox=ox
self.oy=oy
self.oz=oz
self.radius=radius
self.angleMin=angleMin
self.angleMax=angleMax
self.latMin=latMin
self.latMax=latMax
self.discrStep=discrStep
self.angleRange = (self.angleMax - self.angleMin)*toRad
self.angleOffset = self.angleMin*toRad
self.angleStep = self.angleRange / self.discrStep
self.latOffset = self.latMin*toRad
self.latRange = (self.latMax - self.latMin) * toRad
self.latAngleStep = self.latRange / self.discrStep
if(self.latOffset <0):
self.latOffset = 0
if(self.latRange > pi):
self.latRange = pi - latOffset
def CartesianPoints(self):
PointList = []
for lat in range(0,self.discrStep):
ang = lat * self.latAngleStep + self.latOffset
z = cos(ang) * self.radius + self.oz
radMod = sin(ang)*self.radius
for a in range(0,self.discrStep):
x = sin(a*self.angleStep+self.angleOffset)*radMod+self.ox
y = cos(a*self.angleStep+self.angleOffset)*radMod+self.oy
PointList.append(Point(x,y,z))
return PointList
mysphere = Sphere(10.0,0,360,0,180,50,10,10,10)
mylist = mysphere.CartesianPoints()
cpoints = [ControlPoint(Point(0.0,0.0,0.0)),ControlPoint(Point(20.0,0.0,0.0))]
deforpoints=[]
for cp in cpoints:
for p in mylist:
if(p.distance(cp) < 15.0):
cp.deforms(p)
"""print "cp ",cp,"deforms:"
for dp in cp.deformspoints:
print dp ,"at distance", dp.distance(cp)"""
cp.deformothers()
out= mylist.__repr__()
s = out.replace(","," ")
print s