I want to limit infinite Voronoi regions to be on stage. So for the sample:
I want to have regions not infinite.
I'm trying to understand scipy.spartial.Voronoi documentation for many days. I've managed to link points with regions, but regions with vertices (my code). I also modified voronoi_plot_2d function to get limited ridges:
import numpy as np
from scipy.spatial import Voronoi
import matplotlib.pyplot as plt
from plotutils_moje import voronoi_plot_2d
class VoronoiPlaceholders:
def __init__(self, vertex1, point_idxs, points, vertex_placeholder=None, vertex2=None):
self.vertex1 = vertex1
self.vertex2 = vertex2
self.vertex_placeholder = vertex_placeholder
self.point_idxs = point_idxs
self.points = points
def __str__(self):
text = f'VoronoiPlaceholders(v1:{self.vertex1},'
if self.vertex2:
text = f'{text} v2:{self.vertex2};'
if self.vertex_placeholder:
text = f'{text} v2~{self.vertex_placeholder};'
text = f'{text} p1:{self.points[0]} ({self.point_idxs[0]}) and p2:{self.points[1]} ({self.point_idxs[1]}))'
return text
def __repr__(self):
return str(self)
def point_index_belongs_to(self, point_index):
return point_index in self.point_idxs
def is_precise_vertex2_available(self):
if self.vertex2:
return True
return False
def calculate_placeholders_for_all_points(vor):
""" inspiration: https://github.com/scipy/scipy/blob/main/scipy/spatial/_plotutils.py#L231-L261 """
center = vor.points.mean(axis=0)
ptp_bound = vor.points.ptp(axis=0)
infinite_segments = []
finite_segments = []
index = -1
for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices):
simplex = np.asarray(simplex)
index += 1
point1 = vor.points[pointidx[0]]
point2 = vor.points[pointidx[1]]
p1, p2 = pointidx
if np.all(simplex >= 0):
vertex1, vertex2 = vor.vertices[simplex]
x1, y1 = vertex1
x2, y2 = vertex2
vor_obj = VoronoiPlaceholders(vertex1=(x1, y1), vertex2=(x2, y2),
point_idxs=(p1, p2), points=(point1, point2))
finite_segments.append(vor_obj)
continue
i = simplex[simplex >= 0][0] # finite end Voronoi vertex
t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal
midpoint = vor.points[pointidx].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
if vor.furthest_site:
direction = -direction
far_point = vor.vertices[i] + direction * ptp_bound.max()
x1, y1 = vor.vertices[i]
x2, y2 = far_point
vor_obj = VoronoiPlaceholders(vertex1=(x1, y1), vertex_placeholder=(x2, y2),
point_idxs=(p1, p2), points=(point1, point2))
infinite_segments.append(vor_obj)
return infinite_segments + finite_segments
But I don't have idea how to connect ridges with finite vertices with regions. Could somebody help?
Related
I am attempting to create a program which simulates particles colliding in a 2D box, but each particle is labeled with a random 5 character string name and each collision is tracked in a list along each particle. So after the simulation, I would like a list from each particle listing which particles it has hit. I have forked this great simulation https://github.com/xnx/collision and added the name and history attributes to the particle class. However, whenever I attempt to access .name or .history, my kernel dies. The output says:
Kernel died, restarting
Restarting kernel...
The failure happens in the handle_collisions function (line 197), or whenever I try to access the history or the name, so there must be something wrong in my implementation of the name and history attributes. I have also tried to instantiate name and history in the init_particles function instead of place_particles but that had the same results. I'm not exactly sure how to correctly implement them. Thanks for your help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
from itertools import combinations
import random
import string
class Particle:
"""A class representing a two-dimensional particle."""
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
"""Initialize the particle's position, velocity, name, history and radius.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor.
"""
self.r = np.array((x, y))
self.v = np.array((vx, vy))
self.radius = radius
self.mass = self.radius**2
self.styles = styles
if not self.styles:
# Default circle styles
self.styles = {'edgecolor': 'b', 'fill': False}
# For convenience, map the components of the particle's position and
# velocity vector onto the attributes x, y, vx and vy.
#property
def x(self):
return self.r[0]
#x.setter
def x(self, value):
self.r[0] = value
#property
def y(self):
return self.r[1]
#y.setter
def y(self, value):
self.r[1] = value
#property
def vx(self):
return self.v[0]
#vx.setter
def vx(self, value):
self.v[0] = value
#property
def vy(self):
return self.v[1]
#vy.setter
def vy(self, value):
self.v[1] = value
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
#property
def name(self):
return self.name
#name.setter
def name(self,value):
self.name=value
def overlaps(self, other):
"""Does the circle of this Particle overlap that of other?"""
return np.hypot(*(self.r - other.r)) < self.radius + other.radius
def draw(self, ax):
"""Add this Particle's Circle patch to the Matplotlib Axes ax."""
circle = Circle(xy=self.r, radius=self.radius, **self.styles)
ax.add_patch(circle)
return circle
def advance(self, dt):
"""Advance the Particle's position forward in time by dt."""
self.r += self.v * dt
class Simulation:
"""A class for a simple hard-circle molecular dynamics simulation.
The simulation is carried out on a square domain: 0 <= x < 1, 0 <= y < 1.
"""
ParticleClass = Particle
def __init__(self, n, radius=0.01, styles=None):
"""Initialize the simulation with n Particles with radii radius.
Each particle is initialized with a 10 letter string name and an empty history.
radius can be a single value or a sequence with n values.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor when drawing
the Particles.
"""
self.init_particles(n, radius, styles)
self.dt = 0.01
def place_particle(self, rad, styles):
# Choose x, y so that the Particle is entirely inside the
# domain of the simulation.
x, y = rad + (1 - 2*rad) * np.random.random(2)
# Choose a random velocity (within some reasonable range of
# values) for the Particle.
vr = 0.1 * np.sqrt(np.random.random()) + 0.05
vphi = 2*np.pi * np.random.random()
vx, vy = vr * np.cos(vphi), vr * np.sin(vphi)
name = self.assignname
history = []
particle = self.ParticleClass(x, y, vx, vy, name, history, rad, styles)
# Check that the Particle doesn't overlap one that's already
# been placed.
for p2 in self.particles:
if p2.overlaps(particle):
break
else:
self.particles.append(particle)
return True
return False
def assignname(self):
letters = string.ascii_lowercase
name=''.join(random.choice(letters) for i in range(5))
return name
def init_particles(self, n, radius, styles=None):
"""Initialize the n Particles of the simulation.
Positions and velocities are chosen randomly; radius can be a single
value or a sequence with n values.
"""
try:
iterator = iter(radius)
assert n == len(radius)
except TypeError:
# r isn't iterable: turn it into a generator that returns the
# same value n times.
def r_gen(n, radius):
for i in range(n):
yield radius
radius = r_gen(n, radius)
self.n = n
self.particles = []
for i, rad in enumerate(radius):
# Try to find a random initial position for this particle.
while not self.place_particle(rad, styles):
pass
def change_velocities(self, p1, p2):
"""
Particles p1 and p2 have collided elastically: update their
velocities.
"""
m1, m2 = p1.mass, p2.mass
M = m1 + m2
r1, r2 = p1.r, p2.r
d = np.linalg.norm(r1 - r2)**2
v1, v2 = p1.v, p2.v
u1 = v1 - 2*m2 / M * np.dot(v1-v2, r1-r2) / d * (r1 - r2)
u2 = v2 - 2*m1 / M * np.dot(v2-v1, r2-r1) / d * (r2 - r1)
p1.v = u1
p2.v = u2
def handle_collisions(self):
"""Detect and handle any collisions between the Particles.
When two Particles collide, they do so elastically: their velocities
change such that both energy and momentum are conserved.
"""
# We're going to need a sequence of all of the pairs of particles when
# we are detecting collisions. combinations generates pairs of indexes
# into the self.particles list of Particles on the fly.
#particles share history when they collide
pairs = combinations(range(self.n), 2)
for i,j in pairs:
if self.particles[i].overlaps(self.particles[j]):
self.change_velocities(self.particles[i], self.particles[j])
#FAILS HERE
#self.particles[i].history.append(self.particles[j].name)
#self.particles[j].history.append(self.particles[i].name)
def handle_boundary_collisions(self, p):
"""Bounce the particles off the walls elastically."""
if p.x - p.radius < 0:
p.x = p.radius
p.vx = -p.vx
if p.x + p.radius > 1:
p.x = 1-p.radius
p.vx = -p.vx
if p.y - p.radius < 0:
p.y = p.radius
p.vy = -p.vy
if p.y + p.radius > 1:
p.y = 1-p.radius
p.vy = -p.vy
def apply_forces(self):
"""Override this method to accelerate the particles."""
pass
def advance_animation(self):
"""Advance the animation by dt, returning the updated Circles list."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.circles[i].center = p.r
self.handle_collisions()
self.apply_forces()
return self.circles
def advance(self):
"""Advance the animation by dt."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.handle_collisions()
self.apply_forces()
def init(self):
"""Initialize the Matplotlib animation."""
self.circles = []
for particle in self.particles:
self.circles.append(particle.draw(self.ax))
return self.circles
def animate(self, i):
"""The function passed to Matplotlib's FuncAnimation routine."""
self.advance_animation()
return self.circles
def setup_animation(self):
self.fig, self.ax = plt.subplots()
for s in ['top','bottom','left','right']:
self.ax.spines[s].set_linewidth(2)
self.ax.set_aspect('equal', 'box')
self.ax.set_xlim(0, 1)
self.ax.set_ylim(0, 1)
self.ax.xaxis.set_ticks([])
self.ax.yaxis.set_ticks([])
def save_or_show_animation(self, anim, save, filename='collision.mp4'):
if save:
Writer = animation.writers['ffmpeg']
writer = Writer(fps=10, bitrate=1800)
anim.save(filename, writer=writer)
else:
plt.show()
def do_animation(self, save=False, interval=1, filename='collision.mp4'):
"""Set up and carry out the animation of the molecular dynamics.
To save the animation as a MP4 movie, set save=True.
"""
self.setup_animation()
anim = animation.FuncAnimation(self.fig, self.animate,
init_func=self.init, frames=800, interval=interval, blit=True)
self.save_or_show_animation(anim, save, filename)
if __name__ == '__main__':
nparticles = 20
radii = .02
styles = {'edgecolor': 'C0', 'linewidth': 2, 'fill': None}
sim = Simulation(nparticles, radii, styles)
sim.do_animation(save=False)
I see two immediate problems in your code.
First: you did add history as a Particle.__init__ parameter but you never initialize the property itself.
Add something like this:
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
self._history = history
And bigger problem: you have an infinite recursion in your #property definition:
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
So in your getter you called history you call itself return self.history which will loop itself until program will crash.
Rename internal property to _history:
#property
def history(self):
return self._history
#history.setter
def history(self,value):
self._history=value
So i have here my main program (which i absolutely can't make any changes because this is how our instructor wants it to be run):
from class_point import Point
from class_polygon import Polygon
pt1 = Point(0,0)
pt2 = Point(0,4)
pt3 = Point(3,0)
polygon1 = Polygon([pt1,pt2,pt3]) #yes, the class Polygon will be initialized using a list
print(polygon1.get_perimeter())
So basically, i have two separate files containing the definitions of class Polygon and class Point.
This is my code for class Point which has the function for calculating the distance between two given points:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, second):
x_d = self.x - second.x
y_d = self.y - second.y
return (x_d**2 + y_d**2) **0.5
And this is my code for class Polygon which will use the defined points and the distance function to calculate the perimeter of my polygon:
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
len_points = len(self.points)
return len_points
def get_perimeter(self,points,len_points):
perimeter = 0
for i in range(0, len_points):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len_points:
perimeter += points[-1].distance(points[0])
else:
continue
return perimeter
But whenever i try to run the code, i get the error:
File "C:/Users/Dust/Desktop/polygon_trial.py", line 8, in <module>
print(polygon1.get_perimeter())
TypeError: get_perimeter() missing 2 required positional arguments: 'points' and 'len_points'
This should work
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
return len(self.points)
def get_perimeter(self):
perimeter = 0
for i in range(0, len(self.points)):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len(self.points):
perimeter += self.points[-1].distance(self.points[0])
else:
continue
return perimeter
Here I have made a code to create random sized bubbles which can be destroyed by collision of another object:
import tkinter
window = tkinter.Tk()
window.title("...")
c = tkinter.Canvas(width=800, height=500, bg="...")
ojct_id1 = c.create_polygon(...)
ojct_id2 = c.create_oval(...) # A polygon in an oval should constitute the object
def move ojct(event):
...
from random import randint
bubbles = list()
bubbles_r = list() # radius
bubbles_speed = list()
def create_bub():
...
def move_bubbles():
...
from time import sleep
while True:
if randint(1, 10) == 1:
create_bub()
move_bubbles()
window.update()
sleep(0.01)
The following code determines the position of any bubble:That helps to find out collision.
def hole_coord(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
Now I have to make func. for deleting bubbles:
def del_bubbles():
del bubbles_r[i]
del bubbles_speed[i]
c.delete(bubbles[i])
del bubbles[i]
The following code determines, if the two objects are colliding:
from math import sqrt
def distance(id1, id2):
x1, y1 = hole_coord(id1)
x2, y2 = hole_coord(id2)
return sqrt((x2 - x1)/2 + (y2 - y1)/2)
def collision():
for bub in range(len(bubbles)-1, -1, -1):
if distance(ojct_id2, bubbles[bub]) < (15 + bubbles_r[bub]):
del_bubbles(bub)
Here it is sth. wrong: bubbles get deleted without a hit but if they are hit
often they don't get deleted. Can anybody help me? Thanks!
You are not computing the euclidean distance correctly.
def distance(id1, id2):
x1, y1 = hole_coord(id1)
x2, y2 = hole_coord(id2)
return sqrt((x2 - x1)/2 + (y2 - y1)/2) # <----- this is wrong
should be
def distance(id1, id2):
x1, y1 = hole_coord(id1)
x2, y2 = hole_coord(id2)
return sqrt((x2 - x1)**2 + (y2 - y1)**2) # <----- square instead of halve
I've been stuck on this annoying problems for eons. I'm trying to write code so that I can scale a line segment meaning if the amount that I was to scale by(for example) is 2 and the current length of the line is 33 it will increase the entire length to 67. Meaning I add half to the beginning and half to the end...
new front ---a--------b--- new back... But I'm having trouble translating it into code. Here is an example of the code.. The endpoints method should return the endpoints in a tuple such as (p1, p2)
from point import Point
import math
class Line:
def __init__(self,aPoint=Point(), bPoint=Point()):
self.firstPoint = aPoint
self.secondPoint = bPoint
def getEndPoints(self):
return (self.firstPoint, self.secondPoint)
def scale(self,factor):
if factor < 1:
x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor)
x2 = self.secondPoint.x +(self.firstPoint.x - self.secondPoint.x) * (factor)
print(str(x1))
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
y2 = self.secondPoint.y +(self.firstPoint.y - self.secondPoint.y) * (factor)
else:
x1 = -(self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor))
x2 = -(self.secondPoint.x +(self.firstPoint.x - self.secondPoint.x) * (factor))
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
y2 = self.secondPoint.y +(self.firstPoint.y - self.secondPoint.y) * (factor)
self.firstPoint = Point(x1, y1)
self.secondPoint = Point(x2, y2)
if __name__ == "__main__":
p1 = Point(5,5)
p2 = Point(20,35)
l1 = Line(p1,p2)
l1.scale(2)
p5 = Point(-2.5,-10)
p6 = Point(27.5,50)
assert l1.getEndPoints() == (p5,p6)
These tests are not working correctly but the above are.. I'm getting a(5.0, 5.0) and b(20.0, 35.0)
l1.scale(0.5)
p5 = Point(8.75,12.5)
p6 = Point(16.25,27.5)
class Point:
'''Point class represents and manipulates
x,y coordinates.'''
def __init__(self,x=0,y=0):
'''Create a new point with default
x,y coordinates at 0,0.'''
self.x = x
self.y = y
def distanceTo(self,aPoint):
return ((self.x-aPoint.x) ** 2 + (self.y-aPoint.y) ** 2)** .5
not sure if I get it right but
use linear interpolation (parametric line equation)
You got line defined by endpoints p0,p1 in form of vectors so any point on it is defined as:
p(t)=p0+(p1-p0)*t
where p(t) is the point (vector) and t is scalar parameter in range
t=<0.0,1.0>
if you do not know the vector math then rewrite it to scalars
x(t)=x0+(x1-x0)*t
y(t)=y0+(y1-y0)*t
so if t=0 then you get the point p0 and if t=1 then you get the point p1
Now just rescale the t range
so you have scale s
t0=0.5-(0.5*s)` ... move from half of line by scale towards p0
t1=0.5+(0.5*s)` ... move from half of line by scale towards p1
so new endpoints are
q0=p0+(p1-p0)*t0
q1=p0+(p1-p0)*t1
[edit1] I see it like this
def scale(self,factor):
t0=0.5*(1.0-factor)
t1=0.5*(1.0+factor)
x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t0
y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t0
x2 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t1
y2 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t1
self.firstPoint = Point(x1, y1)
self.secondPoint = Point(x2, y2)
Take in mind I do not code in python so handle with prejudice ...
For a scale factor s, the coordinates of the new points are given by
Xa' = Xa (1+s)/2 + Xb (1-s)/2
Ya' = Ya (1+s)/2 + Yb (1-s)/2
Xb' = Xb (1+s)/2 + Xa (1-s)/2
Yb' = Yb (1+s)/2 + Ya (1-s)/2
With the common metrik, you only need to adjust each dimension seperately.
I rewrote some parts of the code, to fit it better to the usual Python style
You might want to work through the things, you are unfamiliar with to save yourself a lot of time in the future.
class Line:
def __init__(self, point_one, point_two):
self.point_one = point_one
self.point_two = point_two
def __str__(self):
return 'Line(p1:{},p2:{})'.format(self.point_one, self.point_two)
#property
def points(self):
return self.point_one, self.point_two
#property
def length(self):
return ((self.point_one.x - self.point_two.x)**2 + (self.point_one.y - self.point_two.y)**2)**0.5
def scale(self, factor):
self.point_one.x, self.point_two.x = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
self.point_one.y, self.point_two.y = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)
#staticmethod
def scale_dimension(dim1, dim2, factor):
base_length = dim2 - dim1
ret1 = dim1 - (base_length * (factor-1) / 2)
ret2 = dim2 + (base_length * (factor-1) / 2)
return ret1, ret2
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return 'Point(x={},y={})'.format(self.x, self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
if __name__ == "__main__":
p1 = Point(5, 5)
p2 = Point(20, 35)
l1 = Line(p1, p2)
print(l1)
print(l1.length)
l1.scale(2)
print(l1)
print(l1.length)
p5 = Point(-2.5, -10)
p6 = Point(27.5, 50)
assert l1.points == (p5, p6)
Note, that the scale method modifies the orginal line and points. If you want to get a new line, the method should be:
def scale(self, factor):
x1, x2 = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
y1, y2 = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)
return Line(Point(x1, y1), Point(x2, y2))
I am coding basic PSO (particle swarm optimization) and have been getting this error that particle instance has no attribute __getitem__. I think everything is fine but the particle class seems to have some error. Take a look at the particle class.
from numpy import array
from random import random
from math import sin, sqrt, cos, pi
import matplotlib.pyplot as plt
import pylab
## Settings
c1 = 2
c2 = 2
size = 100
bad_size = 10
dim = 10
max_iterations = 20
Error_limit = 0.00001
def functR(k):
val = 10*dim
for i in range(dim):
val = val + (k[i])**2 - 10*cos(2*pi*k[i])
return val
#print functR([0]*20)
class particle():
def __init__(self, pos, fitness,vel, pbestpos, pbestfit):
self.pos = pos
self.fitness = fitness
self.vel = vel
self.pbestpos = pbestpos
self.pbestfitness = pbestfit
class swarm():
def __init__(self, size, bad_size, dim):
#self.gbest = gbest
self.size = size
self.bad_size = bad_size
self.dim = dim
def create(self):
particles = []
for i in range(size + bad_size):
p = particle()
p.pos = array([random() for i in range(dim)])
p.vel = 0.0
p.fitness = 0.0
p.pbestpos = p.pos
p.pbestfit = p.fitness
#p = particle(pos, fitness,vel, pbestpos, pbestfit)
particles.append(p)
return particles
def optimizer():
s = swarm(size, bad_size, dim)
new_swarm = s.create()
gbest = new_swarm[0]
gbestfit = functR(gbest)
i = 0
## The iterative loop
while i < max_iterations:
for p in s:
fitness = functR(p.pos)
if fitness > p.fitness:
p.fitness = fitness
p.pbestpos = p.pos
if fitness > gbestfit:
gbest = p
## Plotting
pylab.xlim([0,10])
pylab.ylim([0,1.5])
plt.plot(i,gbest.fitness, "bo")
## Velocity and Position update
vel = p.vel + c1 * random() * (p.pbestpos - p.pos) \
+ c2 * random() * (gbest.pos - p.pos)
p.pos = p.pos + vel
plt.show()
i += 1
print "gbest fitness :", gbestfit
print "Best particle :", gbest.pos
print optimizer()
You are treating a single particle() instance as a list here:
val = val + (k[i])**2 - 10*cos(2*pi*k[i])
k is an instance of particle(), the [i] syntax translates to a __getitem__ call on that instance.
You are passing in that instance here:
gbest = new_swarm[0]
gbestfit = functR(gbest)
while elsewhere you pass in the .pos parameter instead:
for p in s:
fitness = functR(p.pos)
so perhaps you meant to do the same for the gbestfit line:
gbest = new_swarm[0]
gbestfit = functR(gbest.pos)