How to draw cycloid on curve of other function (other cycloid)? - python

I would like to draw cycloid that is going on other cycloid but I don't know exactly how to do this. Here is my code.
import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import animation
#r = float(input('write r\n'))
#R = float(input('write R\n'))
r = 1
R = 1
x = []
y = []
x2 = []
y2 = []
x3 = []
y3 = []
length=[0]
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'r', animated=True)
f = np.linspace(0, 10*r*math.pi, 1000)
def init():
ax.set_xlim(-r, 12*r*math.pi)
ax.set_ylim(-4*r, 4*r)
return ln,
def update2(frame):
#parametric equations of cycloid
x0 = r * (frame - math.sin(frame))
y0 = r * (1 - math.cos(frame))
x.append(x0)
y.append(y0)
#derivative of cycloid
dx = r * (1 - math.cos(frame))
dy = r * math.sin(frame)
#center of circle
a = dy * dy + dx * dx
b = (-2 * x0 * dy) - (2 * frame * dy * dy) + (2 * y0 * dx) - (2 * frame * dx * dx)
c = (x0 * x0) + (2 * frame * x0 * dy) + (frame * frame * dy * dy) + (y0 * y0) - (2 * frame * y0 * dx) + (frame * frame * dx * dx) -1
t1 = (-b - math.sqrt(b * b - 4 * a * c)) / (2 * a)
#t2 = (-b + math.sqrt(b * b - 4 * a * c)) / (2 * a)
center1x=(x0-dy*(t1-x0))*R
center1y=(y0+dx*(t1-x0))*R
#center2x=(x0-dy*(t2-x0))*R
#center2y=(y0+dx*(t2-x0))*R
#length of cycloid
length.append(math.sqrt(x0*x0 + y0*y0))
dl=sum(length)
param = dl / R
W1x = center1x + R * math.cos(-param)
W1y = center1y + R * math.sin(-param)
#W2x = center2x + R * math.cos(-param)
#W2y = center2y + R * math.sin(-param)
x2.append(W1x)
y2.append(W1y)
#x3.append(W2x)
#y3.append(W2y)
ln.set_data([x, x2], [y, y2])
return ln,
ani = animation.FuncAnimation(fig, update2, frames=f,init_func=init, blit=True, interval = 0.1, repeat = False)
plt.show()
In my function update2 I created parametric equations of first cycloid and then tried to obtain co-ordinates of points of second cycloid that should go on the first one.
My idea is based on that that typical cycloid is moving on straight line, and cycloid that is moving on other curve must moving on tangent of that curve, so center of circle that's creating this cycloid is always placed on normal of curve. From parametric equations of normal I have tried to obtain center of circle that creating cycloid but I think that isn't good way.
My goal is to get something like this:

Here is one way. Calculus gives us the formulas to find the direction angles at any point on the cycloid and the arc lengths along the cycloid. Analytic Geometry tells us how to use that information to find your desired points.
By the way, a plot made by rolling a figure along another figure is called a roulette. My code is fairly simple and could be optimized, but it works now, can be used for other problems, and is broken up to make the math and algorithm easier to understand. To understand my code, use this diagram. The cycloid is the blue curve, the black circles are the rolling circle on the cycloid, point A is an "anchor point" (a point where the rim point touches the cycloid--I wanted to make this code general), and point F is the moving rim point. The two red arcs are the same length, which is what we mean by rolling the circle along the cycloid.
And here is my code. Ask if you need help with the source of the various formulas, but the direction angles and arc lengths use calculus.
"""Numpy-compatible routines for a standard cycloid (one caused by a
circle of radius r above the y-axis rolling along the positive x-axis
starting from the origin).
"""
import numpy as np
def x(t, r):
"""Return the x-coordinate of a point on the cycloid with parameter t."""
return r * (t - np.sin(t))
def y(t, r):
"""Return the y-coordinate of a point on the cycloid with parameter t."""
return r * (1.0 - np.cos(t))
def dir_angle_norm_in(t, r):
"""Return the direction angle of the vector normal to the cycloid at
the point with parameter t that points into the cycloid."""
return -t / 2.0
def dir_angle_norm_out(t, r):
"""Return the direction angle of the vector normal to the cycloid at
the point with parameter t that points out of the cycloid."""
return np.pi - t / 2.0
def arclen(t, r):
"""Return the arc length of the cycloid between the origin and the
point on the cycloid with parameter t."""
return 4.0 * r * (1.0 - np.cos(t / 2.0))
# Roulette problem
def xy_roulette(t, r, T, R):
"""Return the x-y coordinates of a rim point on a circle of radius
R rolling on a cycloid of radius r starting at the anchor point
with parameter T currently at the point with parameter t. (Such a
rolling curve on another curve is called a roulette.)
"""
# Find the coordinates of the contact point P between circle and cycloid
px, py = x(t, r), y(t, r)
# Find the direction angle of PC from the contact point to circle's center
a1 = dir_angle_norm_out(t, r)
# Find the coordinates of the center C of the circle
cx, cy = px + R * np.cos(a1), py + R * np.sin(a1)
# Find cycloid's arc distance AP between anchor and current contact points
d = arclen(t, r) - arclen(T, r) # equals arc PF
# Find the angle φ the circle turned while rolling from the anchor pt
phi = d / R
# Find the direction angle of CF from circle's center to rim point
a2 = dir_angle_norm_in(t, r) - phi # subtract: circle rolls clockwise
# Find the coordinates of the final point F
fx, fy = cx + R * np.cos(a2), cy + R * np.sin(a2)
# Return those coordinates
return fx, fy
import matplotlib.pyplot as plt
r = 1
R = 0.75
T = np.pi / 3
t_array = np.linspace(0, 2*np.pi, 201)
cycloid_x = x(t_array, r)
cycloid_y = y(t_array, r)
roulette_x, roulette_y = xy_roulette(t_array, r, T, R)
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
ax.plot(cycloid_x, cycloid_y)
ax.plot(roulette_x, roulette_y)
plt.show()
And here is the resulting graphic. You can pretty this up as you choose. Note that this only has the circle rolling along one arch of the cycloid. If you clarify what should happen at the cusps, this could be extended.
Or, if you want a smaller circle and a curve that ends at the cusps (here r = 1, T = 0 n = 6 (the number of little arches), and R = 4 * r / np.pi / n),

You can generate coordinates of the center of rolling circle using parallel curve definition. Parametric equations for this center are rather simple (if I did not make mistakes):
for big cycloid:
X = R(t - sin(t))
Y = R(1 - cos(t))
X' = R(1 - cos(t))
Y' = R*sin(t)
parallel curve (center of small circle):
Sqrt(X'^2+Y'^2)=R*Sqrt(1-2*cos(t)+cos^2(t)+sin^2(t)) =
R*Sqrt(2-2*cos(t))=
R*Sqrt(4*sin^2(t/2))=
2*R*sin(t/2)
x(t) = X(t) + r*R*sin(t)/(2R*sin(t/2)) =
R(t - sin(t)) + r*2*sin(t/2)*cos(t/2) / (2*sin(t/2)) =
R(t - sin(t)) + r*cos(t/2)
y(t) = Y(t) - r*R*(1-cos(t))/(2*R*sin(t/2)) =
R(1 - cos(t)) - r*(2*sin^2(t/2)/(2*sin(t/2)) =
R(1 - cos(t)) - r*sin(t/2)
But trajectory of point on the circumference is superposition of center position and rotation around it with angular velocity that depends on length of main cycloid plus rotation of main tangent.
Added from dicussion in comments:
cycloid arc length
L(t) = 4R*(1-cos(t/2))
to use it for small circle rotation, divide by r
tangent rotation derivation
fi(t) = atan(Y'/X') = atan(sin(t)/(1-cos(t)) =
atan(2*sin(t/2)*cos(t/2)/(2(sin^2(t/2))) =
atan(ctg(t/2)) = Pi/2 - t/2
so tangent direction change is proportional to big cycloid parameter
and final result is (perhaps some signs are not correct)
theta(t) = L(t)/r + t/2 + Phase
ox(t) = x(t) + r * cos(theta(t))
oy(t) = y(t) + r * sin(theta(t))

Thanks everyone. Somehow I have managed to accomplish that. Solution is maybe ugly but sufficient for me.
import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import animation
r = float(input('write r\n'))
R = float(input('write R\n'))
#r=1
#R=0.1
x = []
y = []
x2 = []
y2 = []
x_1=0
x_2=0
lengthX=[0]
lengthY=[0]
lengthabs=[0]
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'r', animated=True)
f = np.linspace(0, 2*math.pi, 1000)
def init():
ax.set_xlim(-r, 4*r*math.pi)
ax.set_ylim(0, 4*r)
return ln,
def update2(frame):
#cycloid's equations
x0 = r * (frame - math.sin(frame))
y0 = r * (1 - math.cos(frame))
x.append(r * (frame - math.sin(frame)))
y.append(r * (1 - math.cos(frame)))
#arc's length
lengthabs.append(math.sqrt((x0-lengthX[-1])*(x0-lengthX[-1])+(y0-lengthY[-1])*(y0-lengthY[-1])))
lengthX.append(x0)
lengthY.append(y0)
dl=sum(lengthabs)
param = dl / R
#center of circle
center1x = r * (frame - math.sin(frame)) + R * math.cos((frame+2*math.pi) / 2)
center1y = r * (1 - math.cos(frame)) - R * math.sin((frame+2*math.pi) / 2)
if(frame<2*math.pi):
W1x = center1x + R * math.cos(-param)
W1y = center1y + R * math.sin(-param)
else:
W1x = center1x + R * math.cos(param)
W1y = center1y + R * math.sin(param)
x2.append(W1x)
y2.append(W1y)
ln.set_data([x,x2], [y,y2])
return ln,
ani = animation.FuncAnimation(fig, update2, frames=f,init_func=init, blit=True, interval = 0.1, repeat = False)
plt.show()

Related

How to smooth pyvista.StructuredGrid?

Here is a Hopf torus made in Python with PyVista:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
The color is not entirely smooth: on the lobe at the bottom right, you can see a line which separates pale gray and dark gray. How to get rid of this line?
I think what's going on here is that there's no connectivity information where the two ends of your structured grid meet. One way to fix this is to turn your grid into a PolyData using the extract_geometry() method, and then using clean with a larger tolerance. This will force pyvista to realise that there's a seam in the mesh where points are doubled, causing the points to be merged and the seam closed:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh, show seam
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
# convert to PolyData and clean to remove the seam
cleaned_poly = grid.extract_geometry().clean(tolerance=1e-6)
cleaned_poly.plot(smooth_shading=True)
Your mileage for the tolerance parameter may vary.
Just as a piece of trivia, we can visualize the original seam by extracting the feature edges of your original grid:
grid.extract_feature_edges().plot()
These curves correspond to the open edges in your original grid:
>>> grid.extract_surface().n_open_edges
1196
Since your surface is closed and watertight, it should have 0 open edges:
>>> cleaned_poly.n_open_edges
0

Generate random point positons on a torus

I'm working on a code to simulate propagation of particles on a torus surface. Initially, particles are gather on a "local" space, as shown on the following pic :
Blue points are initial particles. The problem is, when I zoom in, particles don't seem to be spread randomly :
First, I see they form like raws. Moreover, I'm working with N = 5 particles, but there seems to be 25 particles here (5 raws of 5). I suppose it comes from meshgrid() since 25 = 5^2, but I don't really understand how this function works to be honest.
Here is my code :
Creating the red torus :
angle = np.linspace(0, 2*np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)
Placing initial points :
thetaInit = ((np.random.rand(N)-0.5)*np.pi/16)
phiInit = ((np.random.rand(N)-0.5)*np.pi/32)
theta2, phi2 = np.meshgrid(phiInit, thetaInit)
Xinit = (R + r * np.cos(phi2)) * np.cos(theta2)
Yinit = (R + r * np.cos(phi2)) * np.sin(theta2)
Zinit = r * np.sin(phi2)
Where N is the number of particles, and r, R are respectively minor and major radius of the torus.

How to generate squares (randomly located, equally sized, randomly rotated) that don't intersect each other?

I've been working on generating a layer of randomly rotated and placed squares on a 1x1 grid. I have been able to generate a single square that is randomly placed and rotated on the grid, but I'm not sure how to improve the code to generate more random squares that do not intersect with each other. Current code seen below:
Example of my One Randomized Square
from math import cos, pi, sin
from random import randint
from matplotlib.mlab import frange
from matplotlib.pyplot import plot, axis, show
def flake_position_layer1(): #Determines the initial position of one corner of the square
x0 = randint(0, 100) / 100
y0 = randint(0, 100) / 100
theta = randint(0, 90) * pi / 180 #Angle of rotation for the square
return x0, y0, theta
def flake_shape(): #generates the other 3 corners of the square
x0, y0, z, theta = flake_position_layer1()
x1 = x0 + (0.1 * cos(theta))
x2 = x1 + (0.1 * cos((90 * pi/180) + theta))
x3 = x2 + (0.1 * cos((180 * pi/180) + theta))
y1 = y0 + (0.1 * sin(theta))
y2 = y1 + (0.1 * sin((90 * pi/180) + theta))
y3 = y2 + (0.1 * sin((180 * pi/180) + theta))
return x0, x1, x2, x3, y0, y1, y2, y3
def display(): #connects the 4 corners on a plot
x0, x1, x2, x3, y0, y1, y2, y3 = flake_shape()
return plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])
display()
axis([0,1,0,1]) #1x1 grid
show()
I do not have a CS background (I'm an environmental engineering major) and I am extremely inexperienced with coding. Please give me any recommendations that you may have for me to try and tackle this problem with!
Math background
1. Algebra
1st degree function (or [Wikipedia]: Linear function) is a function ([Wikipedia]: function) whose:
Expression can be written as: f(x) = a * x + b (a and b constants, x variable)
Graphical representation is a straight line in the xOy plane ([Wikipedia]: Cartesian coordinate system)
2. Plane Geometry
A plane consists of an (infinite) number of points: let's refer to a point by its coordinates which can be referred to as:
abscissa or horizontal coordinate or (simply) x
ordinate or vertical coordinate or (simply) y
The points in the plane spread across 2 dimensions
In the plane, every point can be uniquely identified by its x and y
Some of the points in the plane might have some common characteristics: e.g. a bunch of points that are on a straight line... a point that is on a straight line satisfies the line straight line equation (which is an expression generally defined as ${function (from previous paragraph) result} = ${value})
So, in short: for a point P0(x0, y0), if y0 == f(x0), the point is located on that straight line (and more: depending on y0 being greater / lower than f(x0), P0 is located above / below the straight line in the xOy plane). Again, I want to state that for non linear functions, y = f(x) still applies (as it's the general equation formula), but other operators (e.g. <, >) don't
! Important Note !: everything discussed here applies to a variety of functions (don't want to say all), but I'm limiting to linear functions, for clarity's sake
A straight line is determined by 2 distinct points (e.g. P0(x0, y0), P1(x1, y1)) - the equation for that straight line would be y = a * x + b (in our example: y = ((y0 - y1) / (x0 - x1)) * x + (y0 - x0 * ((y0 - y1) / (x0 - x1)))); !! Of course it worth mentioning the "vertical" (parallel to Oy) line which is !! not a function !!
Example: having 2 distinct parallel (non Bolyai :) ) lines: f0(x) = a * x + b0 and f1(x) = a * x + b1 (a is the same for both lines - this is the condition for them to be parallel) and an external point P0(x0, y0) (that obviously doesn't belong to any of the lines). How to determine if P0 is between the 2 lines? Well, the point must be above (the lower) one and below the other (the higher one). Translated into math (considering f0 being the lower one):
y0 > f0(x0) (y0 - f0(x0) > 0)
y0 < f1(x0) (y0 - f1(x0) < 0)
From the above observations (and there may be more wisdom), this is the condition that the point coordinates should satisfy: (y0 - f0(x0)) * (y0 - f1(x0)) < 0
Going further: a square consists of 2 sets of parallel lines (its sides); if a point is between each lines pair, then the point is in the square.
code00.py:
#!/usr/bin/env python3
import sys
from random import random, seed
from math import pi, sin, cos, sqrt
import matplotlib.pyplot as plt
pi_2 = pi / 2
MINX = MINY = 0
MAXX = MAXY = 1
DEFAULT_SIDE = 0.1
DEFAULT_SAFETY_MARGIN = DEFAULT_SIDE * sqrt(2)
MAX_SQUARES = 30
__global_generation_counter = 0
def get_func_deg1(p0, p1):
(x0, y0), (x1, y1) = p0, p1
if x0 == x1:
return None
a = (y0 - y1)/(x0 - x1)
b = y0 - x0 * a
return lambda x: a * x + b
def is_point_in_square(p, sq):
x, y = p
p0, p1, p2, p3 = sq
side_func0 = get_func_deg1(p0, p1)
side_func1 = get_func_deg1(p1, p2)
side_func2 = get_func_deg1(p2, p3)
side_func3 = get_func_deg1(p3, p0)
if not side_func0 or not side_func1 or not side_func2 or not side_func3:
xmin = min(p0[0], p2[0])
xmax = max(p0[0], p2[0])
ymin = min(p0[1], p2[1])
ymax = max(p0[1], p2[1])
return xmin <= x <= xmax and ymin <= y <= ymax
return ((y - side_func0(x)) * (y - side_func2(x))) <= 0 and \
((y - side_func1(x)) * (y - side_func3(x))) <= 0
def squares_overlap(square0, square1):
for p0 in square0:
if is_point_in_square(p0, square1):
return True
for p1 in square1:
if is_point_in_square(p1, square0):
return True
xc0 = (square0[0][0] + square0[2][0]) / 2
yc0 = (square0[0][1] + square0[2][1]) / 2
if is_point_in_square((xc0, yc0), square1):
return True
# The "reverse center check" not needed, since squares are congruent
"""
xc1 = (square1[0][0] + square1[2][0]) / 2
yc1 = (square1[0][1] + square1[2][1]) / 2
if is_point_in_square((xc1, yc1), square0):
return True
"""
return False
def __generation_monitor():
global __global_generation_counter
__global_generation_counter += 1
def generate_random_point(minx=MINX, miny=MINY, maxx=MAXX, maxy=MAXY, safety_margin=DEFAULT_SAFETY_MARGIN):
if maxx - minx < 2 * safety_margin or maxy - miny < 2 * safety_margin:
print("MUEEE")
safety_margin = 0
x = safety_margin + random() * (maxx - minx - 2 * safety_margin)
y = safety_margin + random() * (maxy - miny - 2 * safety_margin)
__generation_monitor()
return x, y
def generate_random_angle(max_val=pi_2):
return random() * max_val
def generate_random_square(side=DEFAULT_SIDE, squares_to_avoid=()):
while 1:
restart = False
x0, y0 = generate_random_point()
angle = generate_random_angle()
x1 = x0 + side * cos(angle)
y1 = y0 + side * sin(angle)
angle += pi_2
x2 = x1 + side * cos(angle)
y2 = y1 + side * sin(angle)
angle += pi_2
x3 = x2 + side * cos(angle)
y3 = y2 + side * sin(angle)
ret = (x0, y0), (x1, y1), (x2, y2), (x3, y3)
for square in squares_to_avoid:
if squares_overlap(ret, square):
restart = True
if restart:
continue
return ret
def square_to_plot(square):
xs, ys = zip(square[0], square[1], square[2], square[3])
return xs + (xs[0],), ys + (ys[0],)
def main():
seed()
squares = list()
allow_overlapping = False # CHANGE to True to allow square to overlap
for _ in range(MAX_SQUARES):
#print("Generating:", _)
if allow_overlapping:
square = generate_random_square()
else:
square = generate_random_square(squares_to_avoid=squares)
squares.append(square)
plot_squares = tuple()
for sq in squares:
plot_squares += square_to_plot(sq)
print("STATS:\n Squares: {}\n Allow overlapping: {}\n Generated values: {}".format(MAX_SQUARES, allow_overlapping, __global_generation_counter))
plt.plot(*plot_squares)
plt.axis([MINX, MAXX, MINY, MAXY])
plt.show()
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Notes:
I didn't work with matplotlib before (actually, I pip installed it for this task)
General comments:
A point is represented by a tuple representing its coordinates: (x, y)
A square is a tuple consisting of 4 points (p0, p1, p2, p3)
get_func_deg1:
Returns the function that describes the line that contains the 2 points given as arguments
If the 2 points are on a line that is parallel to Oy (there's no "normal" function to describe it), simply return None
is_point_in_square:
Determines if a point is inside a square
Uses the logic explained above, except
For the special case when the square edges are parallel to Ox and Oy when it uses some simple arithmetic operations
squares_overlap:
Determines whether 2 squares overlap (I'm sure there are faster "algorithms")
Checks if any of the 1st square corners are inside the 2nd one
The other way around: checks if any of the 2nd square corners are inside the 1st one
Since the above 2 checks are not enough (imagine a regular [Wikipedia]: Octagon and unifying its vertices every 2nd one: there will be 2 squares neither having its corners in the other one, but sharing their "central" areas), also check that one square's center is inside the other one
generate_random_point:
Generates a point in the given bounding box
safety_margin specifies the (minimum) distance that the generated point should be away from any of the bounding box sides, in order that any square that will have this point as a corner, would fit entirely in the bounding box
generate_random_angle:
Generates a random angle between 0 and (π / 2)
generate_random_square:
Generates a random point, a random angle, and constructs a square starting from there, using the specified side
squares_to_avoid is a list of squares. After the square is generated, it is checked against every square from that list. If the 2 squares overlap, the square is regenerated
square_to_plot:
Converts a square (from a tuple of points) to matplotlib format (2 tuples consisting of xs and ys with the 1st element duplicated as the last)
main:
The main function
__generation_monitor (0):
Internal function used for profiling
In order to change the number of squares, modify MAX_SQUARES
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q046081491]> "e:\Work\Dev\VEnvs\py_064_03.05.04_test0\Scripts\python.exe" code00.py
STATS:
Squares: 30
Allow overlapping: False
Generated values: 1135
Few words about the squares generation
As seen in the output, for 30 displayed squares, 1135 were generated (at this run). That is because they were overlapping
If changing from main allow_overlapping = True, Generated values in the output will match the number of squares (MAX_SQUARES)
If increasing MAX_SQUARES to values let's say higher than 50, the number of generated values will increase exponentially (so will the time needed to generate them), and the chance that the program will enter an infinite loop (because it won't be able to generate a square that doesn't overlap to another one) will grow as well
Okay, here is what I've come up with with a little help from the shapely package. Installation help is at the bottom here. The ultimate result:
Code walkthrough
distance is a helper function using the Point class from shapely to find the distance between two coordinates. Just a helper function for later.
Square instantiations a new polygon. It has 4 corners, each an (x,y) pair, one coordinate for its center, and a scalar value equal to half the distance of its diagonal.
test_overlap is pretty self explanatory by title. But logically what it does is this: find the distance from center-to-center between the two shapes. Then find the sum of the half-diagonal of each shape. If the center-to-center distance is greater than the sum, the squares cannot overlap.
Squares starts out with an empty container (empty list) and attempts to add squares to it. But for each possible new addition, it firsts tests that there is no overlap with existing squares.
Code
import math
import random
from shapely.geometry import Polygon, Point
def distance(a, b):
return Point(a).distance(Point(b))
class Square(object):
def __init__(self):
self.x0, self.y0 = random.random(), random.random()
theta = random.randint(0, 90) * math.pi / 180 # Angle of rotation
self.x1 = self.x0 + (0.1 * math.cos(theta))
self.x2 = self.x1 + (0.1 * math.cos((90 * math.pi/180) + theta))
self.x3 = self.x2 + (0.1 * math.cos((180 * math.pi/180) + theta))
self.y1 = self.y0 + (0.1 * math.sin(theta))
self.y2 = self.y1 + (0.1 * math.sin((90 * math.pi/180) + theta))
self.y3 = self.y2 + (0.1 * math.sin((180 * math.pi/180) + theta))
self.corners = ((self.x0, self.y0), (self.x1, self.y1),
(self.x2, self.y2), (self.x3, self.y3))
#property
def center(self):
"""(x, y) of the center of the polygon."""
return Polygon(self.corners).centroid.coords[0]
#property
def half_diag(self):
"""The distance of 1/2 the shape's diagonal (center-to-corner)."""
p0, p1, p2, p3 = self.corners
return 0.5 * distance(p0, p1) * math.sqrt(2)
def test_overlap(square1, square2):
"""Do two shapes overlap?
Note this is a 'conservative' test. May return True if they do not
(false positive), but will never return False if they do (false negative).
"""
# Distance between two centers
ctc = distance(square1.center, square2.center)
# Sum of half-diagonals
halfdiags = square1.half_diag + square2.half_diag
res = ctc < halfdiags
return res
class Squares(object):
def __init__(self):
self.squares = []
def add_square(self):
new_square = Square()
if not self.squares:
# Initial empty list/container - just add without any tests
self.squares.append(new_square)
else:
while True:
# Test that new_square overlaps with existing
res = [test_overlap(square, new_square) for square in self.squares]
if any(res):
# We have at least 1 case of overlap (1 True)
new_square = Square()
else:
# Safe to add
self.squares.append(new_square)
break
def plot_squares(self):
for square in self.squares:
(x0, y0), (x1, y1), (x2, y2), (x3, y3) = square.corners
plt.plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])
Example
import itertools
%matplotlib inline
for _ in itertools.repeat(None, 10):
# Add 10 squares; you could also just build this into the class
sqs.add_square()
sqs.plot_squares()
Installing shapely
Install the Anaconda distribution if you don't already. Then just use conda-forge to install shapely. From cmd run:
conda config --add channels conda-forge
conda install shapely
Glaring deficiency
At a certain point, your container of squares gets filled up and there is minimal room left to add new shapes. Even if there is available space, the function is basically trial-and-error, so will take long at high shape counts. That happens at around 20-25 squares (in a 1.0x1.0 box) at the moment.
You need a function to determine whether two cube intersect.
from math import cos, pi, sin
from random import random
from matplotlib.mlab import frange
from matplotlib.pyplot import plot, axis, show,axes
LEN = 0.1
def rotate(point, theta):
x = point[0]
y = point[1]
x_ = x * cos(theta) + y * sin(theta)
y_ = - x * sin(theta) + y * cos(theta)
return x_, y_
class CUBE(object):
def __init__(self, x, y, theta):
self.corner = [(LEN / 2, LEN / 2),
(-LEN / 2, LEN / 2),
(-LEN / 2, -LEN / 2),
(LEN / 2, -LEN / 2)
]
self.theta = theta
self.x = x
self.y = y
for i in range(4):
self.corner[i] = rotate(self.corner[i], theta)
self.corner[i] = (self.corner[i][0] + x,
self.corner[i][1] + y)
def is_include(cube, point):
point = [point[0] - cube.x, point[1] - cube.y]
point = rotate(point, -cube.theta)
if (point[0] < -LEN / 2
or point[0] > LEN / 2
or point[1] < -LEN / 2
or point[1] > LEN / 2
):
return False
else:
return True
def is_intersect(cube1, cube2):
if (any([is_include(cube1, point) for point in cube2.corner])
or any([is_include(cube2, point) for point in cube1.corner])
or is_include(cube1, (cube2.x, cube2.y))):
return True
else:
return False
def plot_cube(cube,n):
plot(
[cube.corner[i][0] for i in [0, 1, 2, 3, 0]],
[cube.corner[i][1] for i in [0, 1, 2, 3, 0]])
ax = axes()
ax.text(cube.x,cube.y,str(n))
def display(cubelist): # connects the 4 corners on a plot
for i,cube in enumerate(cubelist):
plot_cube(cube,i)
axis([0, 1, 0, 1]) # 1x1 grid
show()
cubelist = []
for i in range(100):
x0 = random()
y0 = random()
theta = random() * pi
cube = CUBE(x0, y0, theta)
if any(is_intersect(cube,cb) for cb in cubelist):
continue
else:
cubelist.append(cube)
display(cubelist)

How to generate random points in a circular distribution

I am wondering how i could generate random numbers that appear in a circular distribution.
I am able to generate random points in a rectangular distribution such that the points are generated within the square of (0 <= x < 1000, 0 <= y < 1000):
How would i go upon to generate the points within a circle such that:
(x−500)^2 + (y−500)^2 < 250000 ?
import random
import math
# radius of the circle
circle_r = 10
# center of the circle (x, y)
circle_x = 5
circle_y = 7
# random angle
alpha = 2 * math.pi * random.random()
# random radius
r = circle_r * math.sqrt(random.random())
# calculating coordinates
x = r * math.cos(alpha) + circle_x
y = r * math.sin(alpha) + circle_y
print("Random point", (x, y))
In your example circle_x is 500 as circle_y is. circle_r is 500.
Another version of calculating radius to get uniformly distributed points, based on this answer
u = random.random() + random.random()
r = circle_r * (2 - u if u > 1 else u)
FIRST ANSWER:
An easy solution would be to do a check to see if the result satisfies your equation before proceeding.
Generate x, y (there are ways to randomize into a select range)
Check if ((x−500)^2 + (y−500)^2 < 250000) is true
if not, regenerate.
The only downside would be inefficiency.
SECOND ANSWER:
OR, you could do something similar to riemann sums like for approximating integrals. Approximate your circle by dividing it up into many rectangles. (the more rectangles, the more accurate), and use your rectangle algorithm for each rectangle within your circle.
What you need is to sample from (polar form):
r, theta = [math.sqrt(random.randint(0,500))*math.sqrt(500), 2*math.pi*random.random()]
You can then transform r and theta back to cartesian coordinates x and y via
x = 500 + r * math.cos(theta)
y = 500 + r * math.sin(theta)
Related (although not Python), but gives the idea.
You can use below the code and if want to learn more
https://programming.guide/random-point-within-circle.html
import random
import math
circle_x = 500
circle_y = 500
a = random.randint(0,500) * 2 * math.pi
r = 1 * math.sqrt(random.randint(0,500))
x = r * math.cos(a) + circle_x
y = r * math.sin(a) + circle_y
here's an example hope could help someone :).
randProba = lambda a: a/sum(a)
npoints = 5000 # points to chose from
r = 1 # radius of the circle
plt.figure(figsize=(5,5))
t = np.linspace(0, 2*np.pi, npoints, endpoint=False)
x = r * np.cos(t)
y = r * np.sin(t)
plt.scatter(x, y, c='0.8')
n = 2 # number of points to chose
t = np.linspace(0, 2*np.pi, npoints, endpoint=False)[np.random.choice(range(npoints), n, replace=False, p=randProba(np.random.random(npoints)))]
x = r * np.cos(t)
y = r * np.sin(t)
plt.scatter(x, y)
You can use rejection sampling, generate a random point within the (2r)×(2r) square that covers the circle, repeat until get one point within the circle.
I would use polar coordinates:
r_squared, theta = [random.randint(0,250000), 2*math.pi*random.random()]
Then r is always less than or equal to the radius, and theta always between 0 and 2*pi radians.
Since r is not at the origin, you will always convert it to a vector centered at 500, 500, if I understand correctly
x = 500 + math.sqrt(r_squared)*math.cos(theta)
y = 500 + math.sqrt(r_squared)*math.sin(theta)
Choose r_squared randomly because of this

How can I rotating a line not at its end point? [duplicate]

I've been trying to rotate a bunch of lines by 90 degrees (that together form a polyline). Each line contains two vertices, say (x1, y1) and (x2, y2). What I'm currently trying to do is rotate around the center point of the line, given center points |x1 - x2| and |y1 - y2|. For some reason (I'm not very mathematically savvy) I can't get the lines to rotate correctly.
Could someone verify that the math here is correct? I'm thinking that it could be correct, however, when I set the line's vertices to the new rotated vertices, the next line may not be grabbing the new (x2, y2) vertex from the previous line, causing the lines to rotate incorrectly.
Here's what I've written:
def rotate_lines(self, deg=-90):
# Convert from degrees to radians
theta = math.radians(deg)
for pl in self.polylines:
self.curr_pl = pl
for line in pl.lines:
# Get the vertices of the line
# (px, py) = first vertex
# (ox, oy) = second vertex
px, ox = line.get_xdata()
py, oy = line.get_ydata()
# Get the center of the line
cx = math.fabs(px-ox)
cy = math.fabs(py-oy)
# Rotate line around center point
p1x = cx - ((px-cx) * math.cos(theta)) - ((py-cy) * math.sin(theta))
p1y = cy - ((px-cx) * math.sin(theta)) + ((py-cy) * math.cos(theta))
p2x = cx - ((ox-cx) * math.cos(theta)) - ((oy-cy) * math.sin(theta))
p2y = cy - ((ox-cx) * math.sin(theta)) + ((oy-cy) * math.cos(theta))
self.curr_pl.set_line(line, [p1x, p2x], [p1y, p2y])
The coordinates of the center point (cx,cy) of a line segment between points (x1,y1) and (x2,y2) are:
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
In other words it's just the average, or arithmetic mean, of the two pairs of x and y coordinate values.
For a multi-segmented line, or polyline, its logical center point's x and y coordinates are just the corresponding average of x and y values of all the points. An average is just the sum of the values divided by the number of them.
The general formulas to rotate a 2D point (x,y) θ radians around the origin (0,0) are:
x′ = x * cos(θ) - y * sin(θ)
y′ = x * sin(θ) + y * cos(θ)
To perform a rotation about a different center (cx, cy), the x and y values of the point need to be adjusted by first subtracting the coordinate of the desired center of rotation from the point's coordinate, which has the effect of moving (known in geometry as translating) it is expressed mathematically like this:
tx = x - cx
ty = y - cy
then rotating this intermediate point by the angle desired, and finally adding the x and y values of the point of rotation back to the x and y of each coordinate. In geometric terms, it's the following sequence of operations:  Tʀᴀɴsʟᴀᴛᴇ ─► Rᴏᴛᴀᴛᴇ ─► Uɴᴛʀᴀɴsʟᴀᴛᴇ.
This concept can be extended to allow rotating a whole polyline about any arbitrary point—such as its own logical center—by just applying the math described to each point of each line segment within it.
To simplify implementation of this computation, the numerical result of all three sets of calculations can be combined and expressed with a pair of mathematical formulas which perform them all simultaneously. So a new point (x′,y′) can be obtained by rotating an existing point (x,y), θ radians around the point (cx, cy) by using:
x′ = ( (x - cx) * cos(θ) + (y - cy) * sin(θ) ) + cx
y′ = ( -(x - cx) * sin(θ) + (y - cy) * cos(θ) ) + cy
Incorporating this mathematical/geometrical concept into your function produces the following:
from math import sin, cos, radians
def rotate_lines(self, deg=-90):
""" Rotate self.polylines the given angle about their centers. """
theta = radians(deg) # Convert angle from degrees to radians
cosang, sinang = cos(theta), sin(theta)
for pl in self.polylines:
# Find logical center (avg x and avg y) of entire polyline
n = len(pl.lines)*2 # Total number of points in polyline
cx = sum(sum(line.get_xdata()) for line in pl.lines) / n
cy = sum(sum(line.get_ydata()) for line in pl.lines) / n
for line in pl.lines:
# Retrieve vertices of the line
x1, x2 = line.get_xdata()
y1, y2 = line.get_ydata()
# Rotate each around whole polyline's center point
tx1, ty1 = x1-cx, y1-cy
p1x = ( tx1*cosang + ty1*sinang) + cx
p1y = (-tx1*sinang + ty1*cosang) + cy
tx2, ty2 = x2-cx, y2-cy
p2x = ( tx2*cosang + ty2*sinang) + cx
p2y = (-tx2*sinang + ty2*cosang) + cy
# Replace vertices with updated values
pl.set_line(line, [p1x, p2x], [p1y, p2y])
Your center point is going to be:
centerX = (x2 - x1) / 2 + x1
centerY = (y2 - y1) / 2 + y1
because you take half the length (x2 - x1) / 2 and add it to where your line starts to get to the middle.
As an exercise, take two lines:
line1 = (0, 0) -> (5, 5)
then: |x1 - x2| = 5, when the center x value is at 2.5.
line2 = (2, 2) -> (7, 7)
then: |x1 - x2| = 5, which can't be right because that's the center for
the line that's parallel to it but shifted downwards and to the left

Categories

Resources