squish/src/voronoi_dcel.pyx

706 lines
23 KiB
Cython

from _packsim cimport SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge
#### Constants ####
init.SiteCacheMap, init.EdgeCacheMap, init.VoronoiInfo, init.Site, init.HalfEdge = \
init_sitecachemap, init_edgecachemap, init_voronoiinfo, init_site, init_halfedge
cdef SiteCacheMap SITE_CACHE_MAP = init.SiteCacheMap(0, 1, 2, 3, 4)
cdef EdgeCacheMap AREA_EDGE_CACHE_MAP = init.EdgeCacheMap(0, 4, 6, 8, 10, -1, 12, 13,
-1, -1, -1, -1, -1, 14)
cdef EdgeCacheMap RADIALT_EDGE_CACHE_MAP = init.EdgeCacheMap(0, 4, 6, 8, -1, 10, 12, 13,
14, 15, 16, 17, 18, 19)
#### SiteCacheMap Methods ####
cdef inline SiteCacheMap init_sitecachemap(INT_T iarea, INT_T iperim, INT_T iisoparam,
INT_T ienergy, INT_T iavg_radius) nogil:
cdef SiteCacheMap sc
sc.iarea, sc.iperim, sc.iisoparam, sc.ienergy, sc.iavg_radius = \
iarea, iperim, iisoparam, ienergy, iavg_radius
sc.area, sc.perim, sc.isoparam, sc.energy, sc.avg_radius = \
area, perim, isoparam, energy, avg_radius
return sc
cdef inline FLOAT_T area(Site* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.site_cache.get(&self.info.site_cache,
(self.arr_index, self.cache.iarea)
)
else:
self.info.site_cache.set(&self.info.site_cache,
(self.arr_index, self.cache.iarea), val)
return val
cdef inline FLOAT_T perim(Site* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.site_cache.get(&self.info.site_cache,
(self.arr_index, self.cache.iperim)
)
else:
self.info.site_cache.set(&self.info.site_cache,
(self.arr_index, self.cache.iperim), val)
return val
cdef inline FLOAT_T isoparam(Site* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.site_cache.get(&self.info.site_cache,
(self.arr_index, self.cache.iisoparam)
)
else:
self.info.site_cache.set(&self.info.site_cache,
(self.arr_index, self.cache.iisoparam), val)
return val
cdef inline FLOAT_T energy(Site* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.site_cache.get(&self.info.site_cache,
(self.arr_index, self.cache.ienergy)
)
else:
self.info.site_cache.set(&self.info.site_cache,
(self.arr_index, self.cache.ienergy), val)
return val
cdef inline FLOAT_T avg_radius(Site* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.site_cache.get(&self.info.site_cache,
(self.arr_index, self.cache.iavg_radius)
)
else:
self.info.site_cache.set(&self.info.site_cache,
(self.arr_index, self.cache.iavg_radius), val)
return val
#### EdgeCacheMap Methods ####
cdef inline EdgeCacheMap init_edgecachemap(INT_T iH, INT_T ila, INT_T ida, INT_T ixij,
INT_T idVdv, INT_T ii2p, INT_T ila_mag, INT_T ida_mag, INT_T iphi, INT_T iB,
INT_T iF, INT_T ilntan, INT_T icsc, INT_T size) nogil:
cdef EdgeCacheMap ec
ec.iH, ec.ila, ec.ida, ec.ixij, ec.idVdv, ec.ii2p, ec.ila_mag, ec.ida_mag, ec.iphi, \
ec.iB, ec.iF, ec.ilntan, ec.icsc = iH, ila, ida, ixij, idVdv, ii2p, \
ila_mag, ida_mag, iphi, iB, iF, ilntan, icsc
ec.size = size
ec.H, ec.la, ec.da, ec.xij, ec.dVdv, ec.i2p, ec.la_mag, ec.da_mag, ec.phi, ec.B, ec.F, \
ec.lntan, ec.csc = H, la, da, xij, dVdv, i2p, la_mag, da_mag, phi, B, F, lntan, csc
return ec
cdef inline Matrix2x2 H(HalfEdge* self, Matrix2x2 val) nogil:
if isnan(<double>val.a):
return init.Matrix2x2(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iH)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iH+1)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iH+2)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iH+3)
),
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iH), val.a)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iH+1), val.b)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iH+2), val.c)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iH+3), val.d)
return val
cdef inline Vector2D la(HalfEdge* self, Vector2D val) nogil:
if isnan(<double>val.x):
return init.Vector2D(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ila)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ila+1)
)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ila), val.x)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ila+1), val.y)
return val
cdef inline Vector2D da(HalfEdge* self, Vector2D val) nogil:
if isnan(<double>val.x):
return init.Vector2D(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ida)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ida+1)
)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ida), val.x)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ida+1), val.y)
return val
cdef inline Vector2D xij(HalfEdge* self, Vector2D val) nogil:
if isnan(<double>val.x):
return init.Vector2D(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ixij)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ixij+1)
)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ixij), val.x)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ixij+1), val.y)
return val
cdef inline Vector2D dVdv(HalfEdge* self, Vector2D val) nogil:
if isnan(<double>val.x):
return init.Vector2D(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.idVdv)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.idVdv+1)
)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.idVdv), val.x)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.idVdv+1), val.y)
return val
cdef inline Vector2D i2p(HalfEdge* self, Vector2D val) nogil:
if isnan(<double>val.x):
return init.Vector2D(
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ii2p)
),
self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ii2p+1)
)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ii2p), val.x)
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ii2p+1), val.y)
return val
cdef inline FLOAT_T la_mag(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ila_mag)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ila_mag), val)
return val
cdef inline FLOAT_T da_mag(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ida_mag)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ida_mag), val)
return val
cdef inline FLOAT_T phi(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iphi)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iphi), val)
return val
cdef inline FLOAT_T B(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iB)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iB), val)
return val
cdef inline FLOAT_T F(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.iF)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.iF), val)
return val
cdef inline FLOAT_T lntan(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.ilntan)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.ilntan), val)
return val
cdef inline FLOAT_T csc(HalfEdge* self, FLOAT_T val) nogil:
if isnan(<double>val):
return self.info.edge_cache.get(&self.info.edge_cache,
(self.arr_index, self.cache.icsc)
)
else:
self.info.edge_cache.set(&self.info.edge_cache,
(self.arr_index, self.cache.icsc), val)
return val
#### VoronoiInfo Methods ####
cdef inline VoronoiInfo init_voronoiinfo(INT_T [:, ::1] sites, INT_T [:, ::1] edges,
FLOAT_T [:, ::1] points, FLOAT_T [:, ::1] vertices,
FLOAT_T [:, ::1] site_cache, FLOAT_T [:, ::1] edge_cache,
EdgeCacheMap* edge_cache_map) nogil:
cdef VoronoiInfo info
info.sites = init_iarray(&sites[0,0], (<INT_T>sites.shape[0], <INT_T>sites.shape[1]))
info.edges = init_iarray(&edges[0,0], (<INT_T>edges.shape[0], <INT_T>edges.shape[1]))
info.points = init_farray(&points[0,0], (<INT_T>points.shape[0], <INT_T>points.shape[1]))
info.vertices = init_farray(&vertices[0,0],
(<INT_T>vertices.shape[0], <INT_T>vertices.shape[1])
)
info.site_cache = init_farray(&site_cache[0,0],
(<INT_T>site_cache.shape[0], <INT_T>site_cache.shape[1])
)
info.edge_cache = init_farray(&edge_cache[0,0],
(<INT_T>edge_cache.shape[0], <INT_T>edge_cache.shape[1])
)
info.edge_cache_map = edge_cache_map
return info
#### Site Methods ####
cdef inline Site init_site(INT_T arr_index, VoronoiInfo* info) nogil:
cdef Site site
site.arr_index, site.info, site.cache = arr_index, info, &SITE_CACHE_MAP
site.index, site.vec, site.edge, site.edge_num = index, vec, edge, edge_num
return site
cdef inline INT_T index(Site* self) nogil:
return self.info.sites.get(&self.info.sites, (self.arr_index, 0))
cdef inline Vector2D vec(Site* self) nogil:
return init.Vector2D(
self.info.points.get(&self.info.points, (self.index(self), 0)),
self.info.points.get(&self.info.points, (self.index(self), 1))
)
cdef inline HalfEdge edge(Site* self) nogil:
return init.HalfEdge(
self.info.sites.get(&self.info.sites, (self.arr_index, 1)), self.info
)
cdef inline INT_T edge_num(Site* self) nogil:
return self.info.sites.get(&self.info.sites, (self.arr_index, 2))
#### HalfEdge Methods ####
cdef inline HalfEdge init_halfedge(INT_T arr_index, VoronoiInfo* info) nogil:
cdef HalfEdge edge
edge.arr_index, edge.info, edge.cache = arr_index, info, info.edge_cache_map
edge.orig_arr_index = arr_index
edge.origin_index, edge.origin, edge.face, edge.next, edge.prev, edge.twin, edge.get_H = \
origin_index, origin, face, edge_next, prev, twin, get_H
return edge
cdef inline INT_T origin_index(HalfEdge* self) nogil:
return self.info.edges.get(&self.info.edges, (self.arr_index, 0))
cdef inline Vector2D origin(HalfEdge* self) nogil:
return init.Vector2D(
self.info.vertices.get(&self.info.vertices, (self.origin_index(self), 0)),
self.info.vertices.get(&self.info.vertices, (self.origin_index(self), 1))
)
cdef inline Site face(HalfEdge* self) nogil:
return init.Site(
self.info.edges.get(&self.info.edges, (self.arr_index, 1)), self.info
)
cdef inline HalfEdge edge_next(HalfEdge* self) nogil:
return init.HalfEdge(
self.info.edges.get(&self.info.edges, (self.arr_index, 2)), self.info
)
cdef inline HalfEdge prev(HalfEdge* self) nogil:
return init.HalfEdge(
self.info.edges.get(&self.info.edges, (self.arr_index, 3)), self.info
)
cdef inline HalfEdge twin(HalfEdge* self) nogil:
return init.HalfEdge(
self.info.edges.get(&self.info.edges, (self.arr_index, 4)), self.info
)
cdef inline Matrix2x2 get_H(HalfEdge* self, Site xi) nogil:
cdef INT_T this_e = self.origin_index(self)
cdef HalfEdge s_e = xi.edge(&xi)
cdef INT_T i
for i in range(xi.edge_num(&xi)):
if s_e.origin_index(&s_e) == this_e:
return s_e.cache.H(&s_e, NAN_MATRIX)
s_e = s_e.next(&s_e)
return init.Matrix2x2(0.0, 0.0, 0.0, 0.0)
cdef class VoronoiContainer:
"""
Class for Voronoi diagrams, stored in a modified DCEL.
:param n: [int] how many sites to generate.
:param w: [float] width of the bounding domain.
:param h: [float] height of the bounding domain.
:param r: [float] radius of zero energy circle.
:param sites: np.ndarray collection of sites.
"""
def __init__(VoronoiContainer self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r, object site_arr):
self.n, self.w, self.h, self.r = n, w, h, r
self.dim = [w, h]
self.calculate_voronoi(site_arr.astype(FLOAT))
self.generate_dcel()
self.common_cache()
self.precompute()
self.calc_grad()
cdef void calculate_voronoi(VoronoiContainer self,
np.ndarray[FLOAT_T, ndim=2] site_arr) except *:
"""
Does all necessary computation and caching once points are set.
:param site_arr: initial points for this container.
"""
global SYMM
cdef np.ndarray[FLOAT_T, ndim=2] symm = np.asarray(SYMM).reshape(9,2)
cdef np.ndarray[FLOAT_T, ndim=1] dim = np.asarray(self.dim)
cdef np.ndarray[FLOAT_T, ndim=2] full_site_arr = np.empty((self.n*9+8, 2), dtype=FLOAT)
# Generate periodic sites and sites that bound periodic sites.
cdef INT_T i
for i in range(9):
full_site_arr[self.n*i:self.n*(i+1)] = site_arr + symm[i]*dim
if i > 0:
full_site_arr[9*self.n+i-1] = dim/2 + 2*dim*symm[i]
# Use SciPy to compute the Voronoi set.
self.scipy_vor = scipy.spatial.Voronoi(full_site_arr)
self.points = self.scipy_vor.points
self.vertices = self.scipy_vor.vertices
cdef void generate_dcel(VoronoiContainer self) except *:
cdef INT_T npoints = self.n*9+8
cdef array.array int_tmplt = array.array('q', [])
cdef np.ndarray[INT_T, ndim=1] offsets = np.zeros(self.n*9+1, dtype=INT)
cdef array.array vert_indices = array.clone(int_tmplt, 0, False)
# Flatten regions into array, so it can be used later.
cdef INT_T i
for i in range(self.n*9):
verts = self.scipy_vor.regions[self.scipy_vor.point_region[i]]
offsets[i+1] = offsets[i] + len(verts) # Build offsets.
vert_indices.extend(array.array('q', verts)) # Flatten
# Get vertices of original N sites.
cdef np.ndarray[INT_T, ndim=1] vert_indices_np = np.asarray(vert_indices)
cdef np.ndarray[INT_T, ndim=1] border_sites = np.unique(np.searchsorted(
np.asarray(offsets), # Check indices where below matches would be inserted
np.nonzero(np.isin( # Indices of other verts being in bound verts.
vert_indices_np[offsets[self.n]:], # Rest of the verts to check.
np.unique(vert_indices_np[:offsets[self.n]]) # Bound verts
))[0] + offsets[self.n],
side='right' # If on index == offset_number, should be part of the next site.
) - 1) # Subtract by one to get actual site number.
cdef INT_T border_num = len(border_sites)
# Build sites array.
# [Site Index, Edge Index/Offset, Edge Count]
self.sites = np.empty((self.n+border_num, 3), dtype=INT)
self.sites.base[:self.n, 0] = np.arange(self.n, dtype=INT)
self.sites.base[self.n:, 0] = border_sites
self.sites.base[:self.n+1, 1] = offsets[:self.n+1]
for i in range(self.n):
self.sites[i, 2] = self.sites[i+1, 1] - self.sites[i, 1]
cdef INT_T edge_count = offsets[self.n]
cdef INT_T diff
for i in range(border_num):
diff = offsets[border_sites[i]+1] - offsets[border_sites[i]]
edge_count += diff
self.sites[self.n+i, 2] = diff
if i < border_num - 1:
self.sites[self.n+i+1, 1] = self.sites[self.n+i, 1] + diff
# Build edges array
# [Origin Index, Site Index, Next Index, Prev Index, Twin Index]
self.edges = np.empty((edge_count, 5), dtype=INT)
cdef np.ndarray[INT_T, ndim=1] site_verts
cdef INT_T j, site_i, edge_i, edge_offset, vert_num, twin_index, prev_res
edge_indices = dict()
for i in range(self.n + border_num):
site_i = self.sites[i, 0]
edge_offset = self.sites[i, 1]
site_verts = vert_indices_np[offsets[site_i]:offsets[site_i+1]]
# Scipy outputs sorted vertices, but reverse if not counterclockwise.
if not VoronoiContainer.sign(self.points[site_i],
self.vertices[site_verts[0]], self.vertices[site_verts[1]]):
site_verts = np.flip(site_verts)
vert_num = offsets[site_i+1] - offsets[site_i]
for j in range(vert_num):
edge_i = edge_offset+j
self.edges[edge_i, 0] = site_verts[j]
self.edges[edge_i, 1] = i
# Add vert_num because of C modulo to get always positive.
self.edges[edge_i, 2] = (j+vert_num+1) % vert_num + edge_offset
self.edges[edge_i, 3] = (j+vert_num-1) % vert_num + edge_offset
# Get reversed tuple to theck for twin.
twin_index = edge_indices.get(
(site_verts[(j+1) % vert_num], site_verts[j]
), -1)
self.edges[edge_i, 4] = twin_index
if twin_index == -1:
edge_indices[(site_verts[j], site_verts[(j+1) % vert_num])] = \
j + edge_offset
else:
self.edges[twin_index, 4] = j + edge_offset
self.site_cache = np.empty((self.n + border_num, 5), dtype=FLOAT)
self.edge_cache = np.empty((edge_count, self.edge_cache_map.size), dtype=FLOAT)
cdef void common_cache(VoronoiContainer self) except *:
cdef VoronoiInfo info = init.VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
cdef Site xi
cdef HalfEdge em, ep
cdef Vector2D p, q, la, da, Rla
cdef FLOAT_T [:] area = np.zeros(self.sites.shape[0], dtype=FLOAT)
cdef FLOAT_T [:] perim = np.zeros(self.sites.shape[0], dtype=FLOAT)
cdef INT_T i, j
cdef FLOAT_T e_area, la_mag
for i in prange(self.sites.shape[0], nogil=True):
xi = init.Site(i, &info)
em = xi.edge(&xi)
for j in prange(xi.edge_num(&xi)):
ep = em.next(&em)
p, q = em.origin(&em), ep.origin(&ep)
la, da = q.copy.vsub(&q, p), p.copy.vsub(&p, xi.vec(&xi)) # vp - vm, vm - xi
la_mag = la.mag(&la)
e_area = la.dot(&la, da.rot(&da))
Rla = la.rot(&la)
em.cache.la(&em, la)
em.cache.la_mag(&em, la_mag)
em.cache.da(&em, da)
em.cache.da_mag(&em, da.mag(&da))
em.cache.xij(&em, Rla.copy.smul(&Rla, -e_area/la.dot(&la, la)))
if info.edge_cache_map.iF != -1:
em.cache.F(&em, e_area)
area[i] += e_area
perim[i] += la_mag
em = em.next(&em)
xi.cache.area(&xi, area[i]/2)
xi.cache.perim(&xi, perim[i])
xi.cache.isoparam(&xi, 2*PI*area[i]/(perim[i]*perim[i]))
@staticmethod
cdef inline Matrix2x2 calc_H(HalfEdge em, HalfEdge ep) nogil:
cdef Vector2D xmv, xpv, im, mp, right, Rpm, Rim, f
cdef Matrix2x2 h
cdef FLOAT_T im2, mp2
# Vectors from xi to xm and xp.
xmv, xpv = em.cache.xij(&em, NAN_VECTOR), ep.cache.xij(&ep, NAN_VECTOR)
im, mp = xmv.copy.neg(&xmv), xmv.copy.vsub(&xmv, xpv) # -xmv, xmv - xpv
im2, mp2 = -(xmv.dot(&xmv, xmv)), xmv.dot(&xmv, xmv) - xpv.dot(&xpv, xpv)
# (-xmv*xmv, xmv*xmv - xpv*xpv)
right = init.Vector2D(im2, mp2)
Rpm, Rim = R.vecmul(&R, mp.copy.neg(&mp)), im.rot(&im) # R*-mp, R*im
h = init.Matrix2x2(Rpm.x, Rim.x, Rpm.y, Rim.y) # [Rpm | Rim], h is temporary.
f = h.vecmul(&h, right) # [Rpm | Rim]*right
h = R.copy.smul(&R, mp2*(2*mp.dot(&mp, Rim))) # fp*g, g is a scalar.
# (fp*g - f*gp)/(g**2). f is a column vector, gp = 2*Rpm is a row vector.
h.self.msub(&h, init.Matrix2x2(
f.x*2*Rpm.x, f.x*2*Rpm.y, f.y*2*Rpm.x, f.y*2*Rpm.y
))
h.self.sdiv(&h, (2*mp.dot(&mp, Rim))**2)
return h
@staticmethod
cdef inline bint sign(FLOAT_T [::1] ref, FLOAT_T [::1] p, FLOAT_T [::1] q):
"""
Outputs if p2 - self is counterclockwise of p1 - self.
:param p1: [List[float]] first vector
:param p2: [List[float]] second vector
:return: [bool] returns if counterclockwise.
"""
return ((q[0] - ref[0])*-(p[1] - ref[1]) + \
(q[1] - ref[1])*(p[0] - ref[0])) >= 0
# global ROT
# cdef np.ndarray[FLOAT_T, ndim=2] rot = np.asarray(ROT).reshape(2,2)
# return (q - ref).dot(rot.dot(p - ref)) >= 0
cdef void precompute(self) except *:
pass
cdef void calc_grad(self) except *:
pass
cdef void get_statistics(self) except *:
self.stats = {}
cache = self.site_cache[:self.n, :]
self.stats["site_areas"] = np.asarray(cache[:, SITE_CACHE_MAP.iarea])
self.stats["site_edge_count"] = np.asarray(self.sites[:self.n, 2])
self.stats["site_isos"] = np.asarray(cache[:, SITE_CACHE_MAP.iisoparam])
self.stats["site_energies"] = np.asarray(cache[:, SITE_CACHE_MAP.ienergy])
self.stats["avg_radius"] = np.asarray(cache[:, SITE_CACHE_MAP.iavg_radius])
self.stats["isoparam_avg"] = self.stats["site_areas"] / \
(PI*self.stats["avg_radius"]**2)
edges = np.asarray(self.edges)
mask = np.nonzero(edges[:, 0] != -1)[0]
all_edges = mask[(mask % 2 == 0)]
caches = edges[all_edges, 4]
edge_cache = np.asarray(self.edge_cache)
self.stats["edge_lengths"] = edge_cache[caches, self.edge_cache_map.ila_mag]
@property
def site_arr(self):
return np.asarray(self.points[:self.n], dtype=FLOAT)
@property
def vor_data(self):
return self.scipy_vor
@property
def gradient(self):
return np.asarray(self.grad, dtype=FLOAT)
def add_sites(self, add):
return (self.site_arr + add) % np.asarray(self.dim, dtype=FLOAT)
def iterate(self, FLOAT_T step):
k1 = self.gradient
k2 = self.__class__(self.n, self.w, self.h, self.r,
self.add_sites(step*k1)
).gradient
return (step/2)*(k1+k2), k1
# k1 = self.gradient
# k2 = self.__class__(self.n, self.w, self.h, self.r,
# self.add_sites(step*k1/2)
# ).gradient
# lower = step*(-k1+ 2*k2)
# k3 = self.__class__(self.n, self.w, self.h, self.r,
# self.add_sites(lower)
# ).gradient
# higher = (step/6)*(k1+2*k2+k3)
#new_sites = self.add_sites(higher)
#error = higher - lower
#return higher, k1
def hessian(self, d: float) -> np.ndarray:
"""
Obtains the approximate Hessian.
:param d: [float] small d for approximation.
:return: 2Nx2N array that represents Hessian.
"""
HE = np.zeros((2*self.n, 2*self.n))
new_sites = np.copy(self.site_arr) # Maintain one copy for speed.
for i in range(self.n):
for j in range(2):
mod = self.w if j == 0 else self.h
new_sites[i][j] = (new_sites[i][j] + d) % mod
Ep = self.__class__(self.n, self.w, self.h, self.r, new_sites)
new_sites[i][j] = (new_sites[i][j] - 2*d) % mod
Em = self.__class__(self.n, self.w, self.h, self.r, new_sites)
new_sites[i][j] = (new_sites[i][j] + d) % mod
HE[:, 2*i+j] = ((Ep.gradient - Em.gradient)/(2*d)).flatten()
# Average out discrepencies, since it should be symmetric.
for i in range(2*self.n):
for j in range(i, 2*self.n):
HE[i][j] = (HE[i][j] + HE[j][i])/2
HE[j][i] = HE[i][j]
return HE