Smallest enclosing circle problem: Difference between revisions

Content added Content deleted
m (→‎{{header|Phix}}: minor simplifications)
Line 411: Line 411:
Points {{0,0},{0,1},{1,0}} ==> centre {0.5,0.5} radius 0.70710678118655
Points {{0,0},{0,1},{1,0}} ==> centre {0.5,0.5} radius 0.70710678118655
Points {{5,-2},{-3,-2},{-2,5},{1,6},{0,2}} ==> centre {1,1} radius 5
Points {{5,-2},{-3,-2},{-2,5},{1,6},{0,2}} ==> centre {1,1} radius 5
</pre>

=={{header|Python}}==
{{trans|Julia}}
<lang python>import numpy as np

class ProjectorStack:
"""
Stack of points that are shifted / projected to put first one at origin.
"""
def __init__(self, vec):
self.vs = np.array(vec)
def push(self, v):
if len(self.vs) == 0:
self.vs = np.array([v])
else:
self.vs = np.append(self.vs, [v], axis=0)
return self
def pop(self):
if len(self.vs) > 0:
ret, self.vs = self.vs[-1], self.vs[:-1]
return ret
def __mul__(self, v):
s = np.zeros(len(v))
for vi in self.vs:
s = s + vi * np.dot(vi, v)
return s
class GaertnerBoundary:
"""
GärtnerBoundary

See the passage regarding M_B in Section 4 of Gärtner's paper.
"""
def __init__(self, pts):
self.projector = ProjectorStack([])
self.centers, self.square_radii = np.array([]), np.array([])
self.empty_center = np.array([np.NaN for _ in pts[0]])


def push_if_stable(bound, pt):
if len(bound.centers) == 0:
bound.square_radii = np.append(bound.square_radii, 0.0)
bound.centers = np.array([pt])
return True
q0, center = bound.centers[0], bound.centers[-1]
C, r2 = center - q0, bound.square_radii[-1]
Qm, M = pt - q0, bound.projector
Qm_bar = M * Qm
residue, e = Qm - Qm_bar, sqdist(Qm, C) - r2
z, tol = 2 * sqnorm(residue), np.finfo(float).eps * max(r2, 1.0)
isstable = np.abs(z) > tol
if isstable:
center_new = center + (e / z) * residue
r2new = r2 + (e * e) / (2 * z)
bound.projector.push(residue / np.linalg.norm(residue))
bound.centers = np.append(bound.centers, np.array([center_new]), axis=0)
bound.square_radii = np.append(bound.square_radii, r2new)
return isstable

def pop(bound):
n = len(bound.centers)
bound.centers = bound.centers[:-1]
bound.square_radii = bound.square_radii[:-1]
if n >= 2:
bound.projector.pop()
return bound


class NSphere:
def __init__(self, c, sqr):
self.center = np.array(c)
self.sqradius = sqr

def isinside(pt, nsphere, atol=1e-6, rtol=0.0):
r2, R2 = sqdist(pt, nsphere.center), nsphere.sqradius
return r2 <= R2 or np.isclose(r2, R2, atol=atol**2,rtol=rtol**2)

def allinside(pts, nsphere, atol=1e-6, rtol=0.0):
for p in pts:
if not isinside(p, nsphere, atol, rtol):
return False
return True

def move_to_front(pts, i):
pt = pts[i]
for j in range(len(pts)):
pts[j], pt = pt, np.array(pts[j])
if j == i:
break
return pts

def dist(p1, p2):
return np.linalg.norm(p1 - p2)

def sqdist(p1, p2):
return sqnorm(p1 - p2)

def sqnorm(p):
return np.sum(np.array([x * x for x in p]))

def ismaxlength(bound):
len(bound.centers) == len(bound.empty_center) + 1

def makeNSphere(bound):
if len(bound.centers) == 0:
return NSphere(bound.empty_center, 0.0)
return NSphere(bound.centers[-1], bound.square_radii[-1])

def _welzl(pts, pos, bdry):
support_count, nsphere = 0, makeNSphere(bdry)
if ismaxlength(bdry):
return nsphere, 0
for i in range(pos):
if not isinside(pts[i], nsphere):
isstable = push_if_stable(bdry, pts[i])
if isstable:
nsphere, s = _welzl(pts, i, bdry)
pop(bdry)
move_to_front(pts, i)
support_count = s + 1
return nsphere, support_count

def find_max_excess(nsphere, pts, k1):
err_max, k_max = -np.Inf, k1 - 1
for (k, pt) in enumerate(pts[k_max:]):
err = sqdist(pt, nsphere.center) - nsphere.sqradius
if err > err_max:
err_max, k_max = err, k + k1
return err_max, k_max - 1

def welzl(points, maxiterations=2000):
pts, eps = np.array(points, copy=True), np.finfo(float).eps
bdry, t = GaertnerBoundary(pts), 1
nsphere, s = _welzl(pts, t, bdry)
for i in range(maxiterations):
e, k = find_max_excess(nsphere, pts, t + 1)
if e <= eps:
break
pt = pts[k]
push_if_stable(bdry, pt)
nsphere_new, s_new = _welzl(pts, s, bdry)
pop(bdry)
move_to_front(pts, k)
nsphere = nsphere_new
t, s = s + 1, s_new + 1
return nsphere

if __name__ == '__main__':
TESTDATA =[
np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0]]),
np.array([[5.0, -2.0], [-3.0, -2.0], [-2.0, 5.0], [1.0, 6.0], [0.0, 2.0]]),
np.array([[2.0, 4.0, -1.0], [1.0, 5.0, -3.0], [8.0, -4.0, 1.0], [3.0, 9.0, -5.0]]),
np.random.normal(size=(8, 5))
]
for test in TESTDATA:
nsphere = welzl(test)
print("For points: ", test)
print(" Center is at: ", nsphere.center)
print(" Radius is: ", np.sqrt(nsphere.sqradius), "\n")
</lang>{{out}}
<pre>
For points: [[0. 0.]
[0. 1.]
[1. 0.]]
Center is at: [0.5 0.5]
Radius is: 0.7071067811865476

For points: [[ 5. -2.]
[-3. -2.]
[-2. 5.]
[ 1. 6.]
[ 0. 2.]]
Center is at: [1. 1.]
Radius is: 5.0

For points: [[ 2. 4. -1.]
[ 1. 5. -3.]
[ 8. -4. 1.]
[ 3. 9. -5.]]
Center is at: [ 5.5 2.5 -2. ]
Radius is: 7.582875444051551

For points: [[-0.28550471 -0.12787614 -0.65666165 -0.57319826 -0.09253894]
[ 0.08947276 0.64778564 -0.54953015 0.05332253 -0.93055218]
[ 1.26721546 -1.26410984 -0.79937865 -0.45096792 -1.23946668]
[-0.62653779 0.56561466 0.62403237 0.23226903 -0.95820264]
[ 0.90973949 -0.9821474 0.41400032 -1.11268937 0.19568717]
[-0.4931165 0.94778404 -0.10534124 0.96431358 0.36504087]
[ 1.43895269 -0.69957774 -0.31486014 -1.14980913 0.42550193]
[ 1.4722404 1.19711551 -0.18735466 1.69208268 1.04594225]]
Center is at: [ 1.00038333 0.08534583 -0.22414667 0.50927606 -0.15936026]
Radius is: 2.076492284522762
</pre>
</pre>