Non-numerical Hessian for Radial[T]

This commit is contained in:
Kenneth Jao 2022-01-27 20:25:30 -05:00
parent 8cd48a1acd
commit f1a180b5a5
11 changed files with 4879 additions and 4083 deletions

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,7 @@ ctypedef Matrix2x2 (*MatrixCopySclOp)(Matrix2x2*, FLOAT_T) nogil
ctypedef struct VectorSelfOps: ctypedef struct VectorSelfOps:
Vector2D* (*neg)(Vector2D*) nogil Vector2D* (*neg)(Vector2D*) nogil
Vector2D* (*rot)(Vector2D*) nogil
VectorSelfVecOp vadd VectorSelfVecOp vadd
VectorSelfVecOp vsub VectorSelfVecOp vsub
@ -66,6 +67,7 @@ ctypedef struct VectorSelfOps:
ctypedef struct VectorCopyOps: ctypedef struct VectorCopyOps:
Vector2D (*neg)(Vector2D*) nogil Vector2D (*neg)(Vector2D*) nogil
Vector2D (*rot)(Vector2D*) nogil
VectorCopyVecOp vadd VectorCopyVecOp vadd
VectorCopyVecOp vsub VectorCopyVecOp vsub
@ -81,6 +83,7 @@ ctypedef struct VectorCopyOps:
ctypedef struct MatrixSelfOps: ctypedef struct MatrixSelfOps:
Matrix2x2* (*neg)(Matrix2x2*) nogil Matrix2x2* (*neg)(Matrix2x2*) nogil
Matrix2x2* (*T)(Matrix2x2*) nogil
MatrixSelfMatOp madd MatrixSelfMatOp madd
MatrixSelfMatOp msub MatrixSelfMatOp msub
@ -96,6 +99,7 @@ ctypedef struct MatrixSelfOps:
ctypedef struct MatrixCopyOps: ctypedef struct MatrixCopyOps:
Matrix2x2 (*neg)(Matrix2x2*) nogil Matrix2x2 (*neg)(Matrix2x2*) nogil
Matrix2x2 (*T)(Matrix2x2*) nogil
MatrixCopyMatOp madd MatrixCopyMatOp madd
MatrixCopyMatOp msub MatrixCopyMatOp msub
@ -115,7 +119,7 @@ ctypedef struct Vector2D:
VectorCopyOps copy VectorCopyOps copy
bint (*equals)(Vector2D*, Vector2D) nogil bint (*equals)(Vector2D*, Vector2D) nogil
Vector2D (*rot)(Vector2D*) nogil Matrix2x2 (*vecmul)(Vector2D*, Vector2D) nogil
FLOAT_T (*dot)(Vector2D*, Vector2D) nogil FLOAT_T (*dot)(Vector2D*, Vector2D) nogil
FLOAT_T (*mag)(Vector2D*) nogil FLOAT_T (*mag)(Vector2D*) nogil

View File

@ -18,19 +18,19 @@ cdef MatrixCopyOps MCO
VSO.neg, VSO.vadd, VSO.vsub, VSO.vmul, VSO.vdiv, VSO.sadd, VSO.ssub, VSO.smul, VSO.sdiv = \ VSO.neg, VSO.vadd, VSO.vsub, VSO.vmul, VSO.vdiv, VSO.sadd, VSO.ssub, VSO.smul, VSO.sdiv = \
v_neg_s, v_vadd_s, v_vsub_s, v_vmul_s, v_vdiv_s, v_sadd_s, v_ssub_s, v_smul_s, v_sdiv_s v_neg_s, v_vadd_s, v_vsub_s, v_vmul_s, v_vdiv_s, v_sadd_s, v_ssub_s, v_smul_s, v_sdiv_s
VSO.matmul = v_matmul_s VSO.matmul, VSO.rot = v_matmul_s, rot_s
VCO.neg, VCO.vadd, VCO.vsub, VCO.vmul, VCO.vdiv, VCO.sadd, VCO.ssub, VCO.smul, VCO.sdiv = \ VCO.neg, VCO.vadd, VCO.vsub, VCO.vmul, VCO.vdiv, VCO.sadd, VCO.ssub, VCO.smul, VCO.sdiv = \
v_neg_c, v_vadd_c, v_vsub_c, v_vmul_c, v_vdiv_c, v_sadd_c, v_ssub_c, v_smul_c, v_sdiv_c v_neg_c, v_vadd_c, v_vsub_c, v_vmul_c, v_vdiv_c, v_sadd_c, v_ssub_c, v_smul_c, v_sdiv_c
VCO.matmul = v_matmul_c VCO.matmul, VCO.rot = v_matmul_c, rot_c
MSO.neg, MSO.madd, MSO.msub, MSO.mmul, MSO.mdiv, MSO.sadd, MSO.ssub, MSO.smul, MSO.sdiv = \ MSO.neg, MSO.madd, MSO.msub, MSO.mmul, MSO.mdiv, MSO.sadd, MSO.ssub, MSO.smul, MSO.sdiv = \
m_neg_s, m_madd_s, m_msub_s, m_mmul_s, m_mdiv_s, m_sadd_s, m_ssub_s, m_smul_s, m_sdiv_s m_neg_s, m_madd_s, m_msub_s, m_mmul_s, m_mdiv_s, m_sadd_s, m_ssub_s, m_smul_s, m_sdiv_s
MSO.matmul = m_matmul_s MSO.matmul, MSO.T = m_matmul_s, m_transpose_s
MCO.neg, MCO.madd, MCO.msub, MCO.mmul, MCO.mdiv, MCO.sadd, MCO.ssub, MCO.smul, MCO.sdiv = \ MCO.neg, MCO.madd, MCO.msub, MCO.mmul, MCO.mdiv, MCO.sadd, MCO.ssub, MCO.smul, MCO.sdiv = \
m_neg_c, m_madd_c, m_msub_c, m_mmul_c, m_mdiv_c, m_sadd_c, m_ssub_c, m_smul_c, m_sdiv_c m_neg_c, m_madd_c, m_msub_c, m_mmul_c, m_mdiv_c, m_sadd_c, m_ssub_c, m_smul_c, m_sdiv_c
MCO.matmul = m_matmul_c MCO.matmul, MCO.T = m_matmul_c, m_transpose_c
""" """
If bound checking is desired, uncomment out ..._valid_indices functions. If bound checking is desired, uncomment out ..._valid_indices functions.
@ -143,7 +143,7 @@ cdef inline Vector2D _Vector2D(FLOAT_T x, FLOAT_T y) nogil:
vec.x, vec.y = x, y vec.x, vec.y = x, y
vec.self, vec.copy = VSO, VCO vec.self, vec.copy = VSO, VCO
vec.equals, vec.rot, vec.dot, vec.mag = &v_equals, &rot, &dot, &mag vec.equals, vec.vecmul, vec.dot, vec.mag = &v_equals, &v_vecmul, &dot, &mag
return vec return vec
@ -200,6 +200,10 @@ cdef inline Vector2D* v_matmul_s(Vector2D* self, Matrix2x2 m) nogil:
self.x, self.y = self.x*m.a + self.y*m.c, self.x*m.b + self.y*m.d self.x, self.y = self.x*m.a + self.y*m.c, self.x*m.b + self.y*m.d
return self return self
cdef inline Vector2D* rot_s(Vector2D* self) nogil:
self.x, self.y = -self.y, self.x
return self
cdef inline Vector2D v_neg_c(Vector2D* self) nogil: cdef inline Vector2D v_neg_c(Vector2D* self) nogil:
return _Vector2D(-self.x, -self.y) return _Vector2D(-self.x, -self.y)
@ -232,7 +236,7 @@ cdef inline Vector2D v_matmul_c(Vector2D* self, Matrix2x2 m) nogil:
self.x*m.a + self.y*m.c, self.x*m.b + self.y*m.d self.x*m.a + self.y*m.c, self.x*m.b + self.y*m.d
) )
cdef inline Vector2D rot(Vector2D* self) nogil: cdef inline Vector2D rot_c(Vector2D* self) nogil:
return _Vector2D(-self.y, self.x) return _Vector2D(-self.y, self.x)
cdef inline FLOAT_T dot(Vector2D* self, Vector2D w) nogil: cdef inline FLOAT_T dot(Vector2D* self, Vector2D w) nogil:
@ -241,6 +245,9 @@ cdef inline FLOAT_T dot(Vector2D* self, Vector2D w) nogil:
cdef inline FLOAT_T mag(Vector2D* self) nogil: cdef inline FLOAT_T mag(Vector2D* self) nogil:
return <FLOAT_T>sqrt(<double>(self.x*self.x + self.y*self.y)) return <FLOAT_T>sqrt(<double>(self.x*self.x + self.y*self.y))
cdef inline Matrix2x2 v_vecmul(Vector2D* self, Vector2D v) nogil:
return _Matrix2x2(self.x*v.x, self.x*v.y, self.y*v.x, self.y*v.y)
#### Matrix2x2 Methods #### #### Matrix2x2 Methods ####
@ -329,6 +336,10 @@ cdef inline Matrix2x2* m_matmul_s(Matrix2x2* self, Matrix2x2 m) nogil:
self.c*m.a + self.d*m.c, self.c*m.b + self.d*m.d self.c*m.a + self.d*m.c, self.c*m.b + self.d*m.d
return self return self
cdef inline Matrix2x2* m_transpose_s(Matrix2x2* self) nogil:
self.b, self.c = self.c, self.b
return self
cdef inline Matrix2x2 m_neg_c(Matrix2x2* self) nogil: cdef inline Matrix2x2 m_neg_c(Matrix2x2* self) nogil:
return _Matrix2x2(-self.a, -self.b, -self.c, -self.d) return _Matrix2x2(-self.a, -self.b, -self.c, -self.d)
@ -361,3 +372,6 @@ cdef inline Matrix2x2 m_matmul_c(Matrix2x2* self, Matrix2x2 m) nogil:
self.a*m.a + self.b*m.c, self.a*m.b + self.b*m.d, self.a*m.a + self.b*m.c, self.a*m.b + self.b*m.d,
self.c*m.a + self.d*m.c, self.c*m.b + self.d*m.d self.c*m.a + self.d*m.c, self.c*m.b + self.d*m.d
) )
cdef inline Matrix2x2 m_transpose_c(Matrix2x2* self) nogil:
return _Matrix2x2(self.a, self.c, self.b, self.d)

View File

@ -1,7 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Tuple, List, Optional from typing import Tuple, List, Optional
import matplotlib.pyplot as plt, numpy as np, os import matplotlib as mpl
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
import numpy as np, os, math
from matplotlib.ticker import MaxNLocator, FormatStrFormatter from matplotlib.ticker import MaxNLocator, FormatStrFormatter
from scipy.spatial import Voronoi, voronoi_plot_2d from scipy.spatial import Voronoi, voronoi_plot_2d
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
@ -123,18 +129,49 @@ class Diagram:
raise ValueError("Not a valid mode for diagrams!") raise ValueError("Not a valid mode for diagrams!")
shape = self.diagrams.shape shape = self.diagrams.shape
fig, axes = plt.subplots(*shape, figsize=(shape[1] * 8, shape[0] * 8))
plt.rcParams.update(
{
"axes.titlesize": 45,
"axes.labelsize": 45,
"xtick.labelsize": 40,
"ytick.labelsize": 40,
"xtick.major.width": 2,
"ytick.major.width": 2,
"xtick.major.size": 5,
"ytick.major.size": 5,
"xtick.minor.width": 1,
"ytick.minor.width": 1,
"xtick.minor.size": 3,
"ytick.minor.size": 3,
"legend.fontsize": 40,
"lines.linewidth": 3,
"font.family": "cm",
"font.size": 40,
"text.usetex": True,
"text.latex.preamble": r"\usepackage{amsmath}",
"figure.constrained_layout.use": True,
}
)
fig = plt.figure(figsize=(shape[1] * 15, shape[0] * 15))
gs = fig.add_gridspec(shape[0], shape[1])
# fig, axes = plt.subplots(*shape, figsize=(shape[1] * 15, shape[0] * 15))
if self.diagrams.shape == (1, 1): if self.diagrams.shape == (1, 1):
getattr(self, str(self.diagrams[0][0]) + "_plot")(frame, axes) ax = fig.add_subplot(gs[0])
getattr(self, str(self.diagrams[0][0]) + "_plot")(frame, ax)
else: else:
axes = np.atleast_2d(axes) # axes = np.atleast_2d(axes)
it = np.nditer(self.diagrams, flags=["multi_index"]) it = np.nditer(self.diagrams, flags=["multi_index"])
for diagram in it: for diagram in it:
if diagram == "": if diagram == "":
continue continue
getattr(self, str(diagram) + "_plot")(frame, axes[it.multi_index])
plt.tight_layout() ax = fig.add_subplot(gs[it.multi_index])
getattr(self, str(diagram) + "_plot")(frame, ax)
# plt.tight_layout()
if name is None: if name is None:
name = f"img{frame:05}.png" name = f"img{frame:05}.png"
@ -148,28 +185,60 @@ class Diagram:
domain = self.sim.domains[i] domain = self.sim.domains[i]
n, w, h = domain.n, domain.w, domain.h n, w, h = domain.n, domain.w, domain.h
scale = 1.5 scale = 1.5
area = n <= 60 area = n <= 50
offset = (
2
- 2 * domain.r * (6 * 3 ** (-0.25) * math.sqrt(2) * math.atanh(0.5))
+ 2 * math.pi * domain.r ** 2
)
# Make color map axis.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
voronoi_plot_2d( voronoi_plot_2d(
self.sim.voronois[i], ax, show_vertices=False, point_size=7 - n / 100 self.sim.voronois[i], ax, show_vertices=False, show_points=False
) )
ax.plot([-w, 2 * w], [0, 0], "r")
ax.plot([-w, 2 * w], [h, h], "r") # Mark location axis with 3 ticks.
ax.plot([0, 0], [-h, 2 * h], "r") ax.set_xticks([0, w / 2, w])
ax.plot([w, w], [-h, 2 * h], "r") ax.set_yticks([0, h / 2, h])
# Obtain site energies, and make color map.
energies = self.sim.stats[i]["site_energies"][:n]
norm = mpl.colors.Normalize(vmin=-2, vmax=2, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.magma)
cbar = plt.colorbar(mapper, cax=cax, ticks=np.linspace(-2, 2, 5))
# Fill polygons with energy colormap.
for j, p in enumerate(self.sim.voronois[i].point_region):
region = self.sim.voronois[i].regions[p]
if not -1 in region:
polygon = [self.sim.voronois[i].vertices[k] for k in region]
ax.fill(
*zip(*polygon),
color=mapper.to_rgba(energies[j % n] - offset),
zorder=0,
)
line_color, point_color = "white", "lightseagreen"
# Periodic border
ax.plot([-w, 2 * w], [0, 0], line_color, linewidth="4")
ax.plot([-w, 2 * w], [h, h], line_color, linewidth="4")
ax.plot([0, 0], [-h, 2 * h], line_color, linewidth="4")
ax.plot([w, w], [-h, 2 * h], line_color, linewidth="4")
ax.axis("equal") ax.axis("equal")
ax.set_xlim([(1 - scale) * w / 2, (1 + scale) * w / 2]) ax.set_xlim([(1 - 0.85 * scale) * w / 2, (1 + 0.85 * scale) * w / 2])
ax.set_ylim([(1 - scale) * h / 2, (1 + scale) * h / 2]) ax.set_ylim([(1 - scale) * h / 2, (1 + scale) * h / 2])
ax.title.set_text("Voronoi Visualization") ax.set_title("Voronoi Tessellation")
props = dict(boxstyle="round", facecolor="wheat", alpha=0.8)
# Marking sites and defects.
defects = {5: {"x": [], "y": []}, 7: {"x": [], "y": []}} defects = {5: {"x": [], "y": []}, 7: {"x": [], "y": []}}
for j in range(n): for j in range(n):
for s in SYMM: for s in SYMM:
vec = self.sim.voronois[i].points[j] + s * self.sim.domains[i].dim vec = self.sim.voronois[i].points[j] + s * self.sim.domains[i].dim
if area: if area:
txt = ax.text( txt = ax.text(
*vec, str(round(self.sim.stats[i]["site_areas"][j], 3)) *vec, str(round(self.sim.stats[i]["site_areas"][j], 3))
@ -182,16 +251,35 @@ class Diagram:
elif self.sim.stats[i]["site_edge_count"][j] == 7: elif self.sim.stats[i]["site_edge_count"][j] == 7:
defects[7]["x"].append(vec[0]) defects[7]["x"].append(vec[0])
defects[7]["y"].append(vec[1]) defects[7]["y"].append(vec[1])
else:
ax.scatter(
vec[0], vec[1], s=(40 - n / 100), color=point_color, zorder=1
)
ax.scatter(defects[5]["x"], defects[5]["y"], marker="p", color="C0") ax.scatter(
ax.scatter(defects[7]["x"], defects[7]["y"], marker="*", color="C0") defects[5]["x"],
defects[5]["y"],
marker="p",
s=(200 - n / 100),
color=point_color,
zorder=1,
)
ax.scatter(
defects[7]["x"],
defects[7]["y"],
marker="*",
s=(200 - n / 100),
color=point_color,
zorder=1,
)
# Make VEE text in top left.
props = dict(boxstyle="round", facecolor="white", alpha=0.8, zorder=20)
ax.text( ax.text(
0.05, 0.065,
0.95, 0.935,
f"Energy: {self.sim.energies[i]}", f"VEE: {round(sum(energies)/n - offset, 8)}",
transform=ax.transAxes, transform=ax.transAxes,
fontsize=14,
verticalalignment="top", verticalalignment="top",
bbox=props, bbox=props,
) )
@ -305,14 +393,32 @@ class Diagram:
def eigs_plot(self, i: int, ax: AxesSubplot) -> None: def eigs_plot(self, i: int, ax: AxesSubplot) -> None:
try: try:
eigs = self.sim.stats[i]["eigs"] eigs = self.sim.stats[i]["eigs"]
for i, eig in enumerate(eigs):
if eig > 1e-4:
ax.annotate(
f"Coercivity: {eig:.5f}",
xy=(i, eig),
xytext=(50, -50),
textcoords="offset points",
arrowprops={"arrowstyle": "->", "color": "black"},
)
break
ax.plot( ax.plot(
list(range(len(eigs))), eigs, marker="o", linestyle="dashed", color="C0" list(range(40)),
eigs[:40],
marker="o",
linestyle="dashed",
color="C0",
markersize=8,
) )
ax.plot([0, len(eigs)], [0, 0], color="red")
ax.plot([0, 40], [0, 0], color="red")
except KeyError: except KeyError:
ax.text(0.5, 0.5, "Not Computed") ax.text(0.5, 0.5, "Not Computed")
ax.title.set_text("Hessian Eigenvalues") ax.grid()
ax.set_title("Sorted Hessian Eigenvalues")
ax.set_xlabel("") ax.set_xlabel("")
ax.set_ylabel("Value") ax.set_ylabel("Value")

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,10 @@ from cython.parallel import parallel, prange
cimport numpy as np cimport numpy as np
from libc.math cimport NAN, pi as PI, atanh from libc.math cimport NAN, pi as PI, atanh
from squish.core cimport INT_T, FLOAT_T, Vector2D, Matrix2x2, BitSet, \ from squish.core cimport INT_T, FLOAT_T, Vector2D, Matrix2x2, BitSet, \
_Vector2D, _Matrix2x2, _BitSet _Vector2D, _Matrix2x2, _BitSet
from squish.voronoi cimport Site, HalfEdge, EdgeCacheMap, VoronoiInfo, \ from squish.voronoi cimport Site, HalfEdge, EdgeCacheMap, VoronoiInfo, \
_Site, _HalfEdge, _EdgeCacheMap, _VoronoiInfo, VoronoiContainer, \ _Site, _HalfEdge, _EdgeCacheMap, _VoronoiInfo, VoronoiContainer, \
R, NAN_MATRIX, NAN_VECTOR R, NAN_MATRIX, NAN_VECTOR
#### Constants #### #### Constants ####
@ -18,228 +18,405 @@ cdef EdgeCacheMap AREA_ECM = _EdgeCacheMap(0, 4, 6, 8, 10, 12, 13, -1, -1, 14)
cdef EdgeCacheMap RADIALT_ECM = _EdgeCacheMap(0, 4, 6, 8, -1, 10, 11, 12, 13, 14) cdef EdgeCacheMap RADIALT_ECM = _EdgeCacheMap(0, 4, 6, 8, -1, 10, 11, 12, 13, 14)
cdef class AreaEnergy(VoronoiContainer): cdef class AreaEnergy(VoronoiContainer):
""" """
Class for formulas relevant to the Area energy. Class for formulas relevant to the Area energy.
:param n: [int] how many sites to generate. :param n: [int] how many sites to generate.
:param w: [float] width of the bounding domain. :param w: [float] width of the bounding domain.
:param h: [float] height of the bounding domain. :param h: [float] height of the bounding domain.
:param r: [float] radius of zero energy circle. :param r: [float] radius of zero energy circle.
:param sites: [np.ndarray] collection of sites. :param sites: [np.ndarray] collection of sites.
""" """
attr_str = "area" attr_str = "area"
title_str = "Area" title_str = "Area"
def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r, def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r,
np.ndarray[FLOAT_T, ndim=2] site_arr): np.ndarray[FLOAT_T, ndim=2] site_arr):
self.edge_cache_map = &AREA_ECM self.edge_cache_map = &AREA_ECM
self.energy = 0.0 self.energy = 0.0
super().__init__(n, w, h, r, site_arr) super().__init__(n, w, h, r, site_arr)
self.minimum = (<FLOAT_T>n)*(w*h/(<FLOAT_T>n)-PI*r**2)**2 self.minimum = (<FLOAT_T>n)*(w*h/(<FLOAT_T>n)-PI*r**2)**2
cdef void precompute(self) except *: cdef void precompute(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
cdef Site xi cdef Site xi
cdef HalfEdge em, e, ep cdef HalfEdge em, e, ep
cdef Vector2D vdiff cdef Vector2D vdiff
cdef FLOAT_T [:] site_energy = np.full(self.sites.shape[0], PI*self.r**2) cdef FLOAT_T [:] site_energy = np.full(self.sites.shape[0], PI*self.r**2)
cdef INT_T i, j cdef INT_T i, j
for i in prange(self.sites.shape[0], nogil=True): for i in prange(self.sites.shape[0], nogil=True):
xi = _Site(i, &info) xi = _Site(i, &info)
e = xi.edge(&xi) e = xi.edge(&xi)
site_energy[i] = (xi.cache.area(&xi, NAN) - site_energy[i])**2 site_energy[i] = (xi.cache.area(&xi, NAN) - site_energy[i])**2
xi.cache.energy(&xi, site_energy[i]) xi.cache.energy(&xi, site_energy[i])
j = 0 j = 0
while j < xi.edge_num(&xi): while j < xi.edge_num(&xi):
em, ep = e.prev(&e), e.next(&e) em, ep = e.prev(&e), e.next(&e)
vdiff = em.origin(&em) vdiff = em.origin(&em)
vdiff.self.vsub(&vdiff, ep.origin(&ep)) vdiff.self.vsub(&vdiff, ep.origin(&ep))
e.cache.dVdv(&e, R.vecmul(&R, vdiff)) e.cache.dVdv(&e, R.vecmul(&R, vdiff))
e.cache.H(&e, VoronoiContainer.calc_H(em, e)) e.cache.H(&e, VoronoiContainer.calc_H(em, e))
e = e.next(&e) e = e.next(&e)
j = j + 1 j = j + 1
self.energy = np.sum(site_energy[:self.n]) self.energy = np.sum(site_energy[:self.n])
cdef void calc_grad(self) except *: cdef void calc_grad(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
cdef Site xi, xf cdef Site xi, xf
cdef HalfEdge e, f cdef HalfEdge e, f
cdef Vector2D dedxi_p cdef Vector2D dedxi_p
cdef BitSet edge_set cdef BitSet edge_set
cdef INT_T num_edges = self.edges.shape[0] cdef INT_T num_edges = self.edges.shape[0]
cdef FLOAT_T A = PI*self.r**2 cdef FLOAT_T A = PI*self.r**2
cdef FLOAT_T [:, ::1] dedx = np.zeros((self.n, 2), dtype=FLOAT) cdef FLOAT_T [:, ::1] dedx = np.zeros((self.n, 2), dtype=FLOAT)
cdef INT_T i, j cdef INT_T i, j
for i in prange(self.n, nogil=True): for i in prange(self.n, nogil=True):
xi = _Site(i, &info) xi = _Site(i, &info)
e = xi.edge(&xi) e = xi.edge(&xi)
edge_set = _BitSet(num_edges) edge_set = _BitSet(num_edges)
j = 0 j = 0
while j < xi.edge_num(&xi): # Looping through site edges. while j < xi.edge_num(&xi): # Looping through site edges.
f = e f = e
while True: # Circling this vertex. while True: # Circling this vertex.
if not edge_set.add(&edge_set, f.arr_index): if not edge_set.add(&edge_set, f.arr_index):
xf = f.face(&f) xf = f.face(&f)
dedxi_p = f.cache.dVdv(&f, NAN_VECTOR) #dVdv dedxi_p = f.cache.dVdv(&f, NAN_VECTOR) #dVdv
dedxi_p.self.smul(&dedxi_p, xf.cache.area(&xf, NAN) - A) dedxi_p.self.smul(&dedxi_p, xf.cache.area(&xf, NAN) - A)
dedxi_p.self.matmul(&dedxi_p, e.cache.H(&e, NAN_MATRIX)) dedxi_p.self.matmul(&dedxi_p, e.cache.H(&e, NAN_MATRIX))
dedx[i][0] += dedxi_p.x dedx[i][0] += dedxi_p.x
dedx[i][1] += dedxi_p.y dedx[i][1] += dedxi_p.y
f = f.twin(&f) f = f.twin(&f)
f = f.next(&f) f = f.next(&f)
if f.arr_index == e.arr_index: if f.arr_index == e.arr_index:
break break
e = e.next(&e) e = e.next(&e)
j = j + 1 j = j + 1
edge_set.free(&edge_set) edge_set.free(&edge_set)
self.grad = dedx self.grad = dedx
cdef void calc_hess(self) except *:
d = 10e-5
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]
self.hess = HE
cdef class RadialALEnergy(VoronoiContainer): cdef class RadialALEnergy(VoronoiContainer):
""" """
Class for formulas relevant to the Area energy. Class for formulas relevant to the Area energy.
:param n: [int] how many sites to generate. :param n: [int] how many sites to generate.
:param w: [float] width of the bounding domain. :param w: [float] width of the bounding domain.
:param h: [float] height of the bounding domain. :param h: [float] height of the bounding domain.
:param r: [float] radius of zero energy circle. :param r: [float] radius of zero energy circle.
:param sites: [np.ndarray] collection of sites. :param sites: [np.ndarray] collection of sites.
""" """
attr_str = "radial-al" attr_str = "radial-al"
title_str = "Radial[AL]" title_str = "Radial[AL]"
def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r, def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r,
np.ndarray[FLOAT_T, ndim=2] site_arr): np.ndarray[FLOAT_T, ndim=2] site_arr):
#self.edge_cache_map = &AREA_EDGE_CACHE_MAP #self.edge_cache_map = &AREA_EDGE_CACHE_MAP
self.energy = 0.0 self.energy = 0.0
super().__init__(n, w, h, r, site_arr) super().__init__(n, w, h, r, site_arr)
cdef void precompute(self) except *: cdef void precompute(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
pass pass
cdef void calc_grad(self) except *: cdef void calc_grad(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
pass pass
cdef void calc_hess(self) except *:
pass
cdef class RadialTEnergy(VoronoiContainer): cdef class RadialTEnergy(VoronoiContainer):
""" """
Class for formulas relevant to the Area energy. Class for formulas relevant to the Area energy.
:param n: [int] how many sites to generate. :param n: [int] how many sites to generate.
:param w: [float] width of the bounding domain. :param w: [float] width of the bounding domain.
:param h: [float] height of the bounding domain. :param h: [float] height of the bounding domain.
:param r: [float] radius of zero energy circle. :param r: [float] radius of zero energy circle.
:param sites: [np.ndarray] collection of sites. :param sites: [np.ndarray] collection of sites.
""" """
attr_str = "radial-t" attr_str = "radial-t"
title_str = "Radial[T]" title_str = "Radial[T]"
def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r, def __init__(AreaEnergy self, INT_T n, FLOAT_T w, FLOAT_T h, FLOAT_T r,
np.ndarray[FLOAT_T, ndim=2] site_arr): np.ndarray[FLOAT_T, ndim=2] site_arr):
self.edge_cache_map = &RADIALT_ECM self.edge_cache_map = &RADIALT_ECM
self.energy = 0.0 self.energy = 0.0
super().__init__(n, w, h, r, site_arr) super().__init__(n, w, h, r, site_arr)
cdef void precompute(self) except *: cdef void precompute(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
cdef Site xi cdef Site xi
cdef HalfEdge e, ep cdef HalfEdge e, ep
cdef Vector2D la cdef Vector2D la
# All energy has a 2pir_0 term. # All energy has a 2pir_0 term.
cdef FLOAT_T [:] site_energy = np.full(self.sites.shape[0], 2*PI*self.r**2) cdef FLOAT_T [:] site_energy = np.full(self.sites.shape[0], 2*PI*self.r**2)
cdef FLOAT_T [:] avg_radii = np.zeros(self.sites.shape[0]) cdef FLOAT_T [:] avg_radii = np.zeros(self.sites.shape[0])
cdef FLOAT_T sm, sp cdef FLOAT_T sm, sp
cdef INT_T i, j cdef INT_T i, j
for i in prange(self.sites.shape[0], nogil=True): for i in prange(self.sites.shape[0], nogil=True):
xi = _Site(i, &info) xi = _Site(i, &info)
e = xi.edge(&xi) e = xi.edge(&xi)
j = 0 j = 0
while j < xi.edge_num(&xi): while j < xi.edge_num(&xi):
ep = e.next(&e) ep = e.next(&e)
#e.cache.H(&e, VoronoiContainer.calc_H(em, e)) #e.cache.H(&e, VoronoiContainer.calc_H(em, e))
la = e.cache.la(&e, NAN_VECTOR) la = e.cache.la(&e, NAN_VECTOR)
sp = la.dot(&la, ep.cache.da(&ep, NAN_VECTOR)) # dap . la sp = la.dot(&la, ep.cache.da(&ep, NAN_VECTOR)) # dap . la
sm = la.dot(&la, e.cache.da(&e, NAN_VECTOR)) # da . la sm = la.dot(&la, e.cache.da(&e, NAN_VECTOR)) # da . la
sp = sp / (ep.cache.da_mag(&ep, NAN) * e.cache.la_mag(&e, NAN)) sp = sp / (ep.cache.da_mag(&ep, NAN) * e.cache.la_mag(&e, NAN))
sm = sm / (e.cache.da_mag(&e, NAN) * e.cache.la_mag(&e, NAN)) sm = sm / (e.cache.da_mag(&e, NAN) * e.cache.la_mag(&e, NAN))
e.cache.calI(&e, <FLOAT_T>(atanh(<double>sp) - atanh(<double>sm))) e.cache.calI(&e, <FLOAT_T>(atanh(<double>sp) - atanh(<double>sm)))
avg_radii[i] += e.cache.ya_mag(&e, NAN) * e.cache.calI(&e, NAN) / 2 avg_radii[i] += e.cache.ya_mag(&e, NAN) * e.cache.calI(&e, NAN) / 2
e = e.next(&e) e = e.next(&e)
j = j + 1 j = j + 1
site_energy[i] += 2*(xi.cache.area(&xi, NAN) - self.r*avg_radii[i]) site_energy[i] += 2*(xi.cache.area(&xi, NAN) - self.r*avg_radii[i])
xi.cache.avg_radius(&xi, avg_radii[i]/(2*PI)) xi.cache.avg_radius(&xi, avg_radii[i]/(2*PI))
xi.cache.energy(&xi, site_energy[i]) xi.cache.energy(&xi, site_energy[i])
self.energy = np.sum(site_energy[:self.n]) self.energy = np.sum(site_energy[:self.n])
cdef void calc_grad(self) except *: cdef void calc_grad(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)
cdef Site xi cdef Site xi
cdef HalfEdge e cdef HalfEdge e
cdef Vector2D dedxi_p cdef Vector2D dedxi_p
cdef FLOAT_T [:, ::1] dedx = np.zeros((self.n, 2), dtype=FLOAT) cdef FLOAT_T [:, ::1] dedx = np.zeros((self.n, 2), dtype=FLOAT)
cdef INT_T i, j cdef INT_T i, j
for i in prange(self.n, nogil=True): for i in prange(self.n, nogil=True):
xi = _Site(i, &info) xi = _Site(i, &info)
e = xi.edge(&xi) e = xi.edge(&xi)
j = 0 j = 0
while j < xi.edge_num(&xi): # Looping through site edges. while j < xi.edge_num(&xi): # Looping through site edges.
dedxi_p = e.cache.ya(&e, NAN_VECTOR) dedxi_p = e.cache.ya(&e, NAN_VECTOR)
dedxi_p.self.smul( dedxi_p.self.smul(
&dedxi_p, &dedxi_p,
e.cache.calI(&e, NAN) / e.cache.ya_mag(&e, NAN) e.cache.calI(&e, NAN) / e.cache.ya_mag(&e, NAN)
) )
dedx[i][0] += 2*self.r*dedxi_p.x dedx[i][0] += 2*self.r*dedxi_p.x
dedx[i][1] += 2*self.r*dedxi_p.y dedx[i][1] += 2*self.r*dedxi_p.y
e = e.next(&e) e = e.next(&e)
j = j + 1 j = j + 1
self.grad = dedx self.grad = dedx
cdef void calc_hess(self) except *:
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache,
self.edge_cache, self.edge_cache_map)
cdef Site xi, xk
cdef HalfEdge em, e, ep, f
cdef Vector2D temp1, temp2
cdef Matrix2x2 dsite, q, tempm
cdef BitSet edge_set
cdef INT_T num_edges = self.edges.shape[0]
cdef FLOAT_T [:,:] HE = np.zeros((2*self.n, 2*self.n))
cdef INT_T i, j, k
for i in prange(self.sites.shape[0], nogil=True):
xi = _Site(i, &info)
e = xi.edge(&xi)
j = 0
while j < xi.edge_num(&xi):
em, ep = e.prev(&e), e.next(&e)
e.cache.H(&e, VoronoiContainer.calc_H(em, e))
e = e.next(&e)
j = j + 1
for i in range(self.n):
xi = _Site(i, &info)
e = xi.edge(&xi)
edge_set = _BitSet(num_edges)
j = 0
while j < xi.edge_num(&xi):
em, ep = e.prev(&e), e.next(&e)
# Calculating of p
temp1 = em.cache.la(&em, NAN_VECTOR)
temp1.self.rot(&temp1)
temp1.self.sdiv(
&temp1,
em.cache.la_mag(&em, NAN) * em.cache.ya_mag(&em, NAN) / 2
)
temp2 = e.cache.la(&e, NAN_VECTOR)
temp2.self.rot(&temp2)
temp2.self.sdiv(
&temp2,
e.cache.la_mag(&e, NAN) * e.cache.ya_mag(&e, NAN) / 2
)
temp1.self.vsub(&temp1, temp2) # (lm/Am - l/A)
temp2 = e.cache.da(&e, NAN_VECTOR) # rot(d) / |d|
temp2.self.sdiv(&temp2, e.cache.da_mag(&e, NAN))
temp2.self.rot(&temp2)
dsite = temp1.vecmul(&temp1, temp2)
HE[2*i, 2*i] -= dsite.a
HE[2*i, 2*i+1] -= dsite.b
HE[2*i+1, 2*i] -= dsite.c
HE[2*i+1, 2*i+1] -= dsite.d
# Calculating of q
temp2 = e.cache.la(&e, NAN_VECTOR)
temp1 = temp2.copy.rot(&temp2)
q = temp1.vecmul(&temp1, temp2)
q.self.sdiv(&q, e.cache.la_mag(&e, NAN)**2)
q.self.msub(&q, R)
q.self.smul(
&q,
e.cache.calI(&e, NAN) / e.cache.la_mag(&e, NAN)
)
temp1 = e.cache.la(&e, NAN_VECTOR)
temp1.self.rot(&temp1)
tempm = temp1.vecmul(&temp1, temp1)
tempm.self.smul(
&tempm,
ep.cache.da_mag(&ep, NAN) - e.cache.da_mag(&e, NAN)
)
tempm.self.sdiv(
&tempm,
e.cache.la_mag(&e, NAN)**3 * e.cache.ya_mag(&e, NAN) / 2
)
q.self.madd(&q, tempm)
# Minus portion
temp2 = em.cache.la(&em, NAN_VECTOR)
temp1 = temp2.copy.rot(&temp2)
tempm = temp1.vecmul(&temp1, temp2)
tempm.self.sdiv(&tempm, em.cache.la_mag(&em, NAN)**2)
tempm = R.copy.msub(&R, tempm)
tempm.self.smul(
&tempm,
em.cache.calI(&em, NAN) / em.cache.la_mag(&em, NAN)
)
q.self.madd(&q, tempm)
temp1 = em.cache.la(&em, NAN_VECTOR)
temp1.self.rot(&temp1)
tempm = temp1.vecmul(&temp1, temp1)
tempm.self.smul(
&tempm,
e.cache.da_mag(&e, NAN) - em.cache.da_mag(&em, NAN)
)
tempm.self.sdiv(
&tempm,
em.cache.la_mag(&em, NAN)**3 * em.cache.ya_mag(&em, NAN) / 2
)
q.self.msub(&q, tempm)
# Add p to q, so p = p, q = p+q
q.self.madd(&q, dsite)
f = e
while True:
xk = f.face(&f)
k = xk.index(&xk) % self.n
if k < 0:
k = k + self.n
if not edge_set.add(&edge_set, f.arr_index):
tempm = q.copy.matmul(&q, f.cache.H(&f, NAN_MATRIX))
HE[2*i, 2*k] += tempm.a
HE[2*i, 2*k+1] += tempm.b
HE[2*i+1, 2*k] += tempm.c
HE[2*i+1, 2*k+1] += tempm.d
f = f.twin(&f)
f = f.next(&f)
if f.arr_index == e.arr_index:
break
e = e.next(&e)
j = j + 1
edge_set.free(&edge_set)
self.hess = -2*self.r*np.asarray(HE, dtype=FLOAT)
self.hess = (
( np.asarray(self.hess, dtype=FLOAT)
+ np.asarray(self.hess, dtype=FLOAT).T )
/ 2
)

View File

@ -2,11 +2,21 @@ from __future__ import annotations
from typing import List, Tuple from typing import List, Tuple
import numpy as np import numpy as np
from numpy.linalg import norm as mag from numpy.linalg import norm as mag
from math import gcd, sqrt, log, tan, atan, pi from math import gcd, sqrt, log, tan, atan, atanh, pi
import cmath
from fractions import Fraction
Config = Tuple[int, int] Config = Tuple[int, int]
def e_hex(domain: DomainParams) -> float:
return (
2
- 2 * domain.r * (6 * 3 ** (-0.25) * sqrt(2) * atanh(0.5))
+ 2 * pi * domain.r ** 2
)
def configurations(domain: DomainParams) -> List[Config]: def configurations(domain: DomainParams) -> List[Config]:
n = domain.n n = domain.n
coprimes, valid = [], [] coprimes, valid = [], []
@ -25,6 +35,9 @@ def configurations(domain: DomainParams) -> List[Config]:
except KeyError: except KeyError:
pass pass
for i in range(len(valid)):
valid.append((valid[i][1], valid[i][0]))
return valid return valid
@ -106,3 +119,71 @@ def rot(v: numpy.ndarray) -> numpy.ndarray:
w = np.copy(v) w = np.copy(v)
w[0], w[1] = -w[1], w[0] w[0], w[1] = -w[1], w[0]
return w return w
def divisors(n: int) -> List[int]:
divs = [[i, n // i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0]
return sorted(set(list(sum(divs, []))))
def factorize(n: int) -> Dict[int, int]:
primes = [i for i in range(1, n + 1) if len(divisors(i)) == 2]
prime_fac = {}
for prime in primes:
i = 0
while n % prime ** (i + 1) == 0:
i += 1
if i > 0:
prime_fac[prime] = i
return prime_fac
def hexagon_alpha(n: int, fraction: bool = False) -> List[int]:
if n % 2 == 1:
return []
fac = factorize(n)
q = 1
for prime in fac:
if prime % 3 != 2:
q *= prime ** fac[prime]
divq = divisors(q)
ratios, thres = [], 1 / sqrt(3)
for g in divq:
us = n // (2 * g)
divu = divisors(us)
for u in divu:
d = 2 * g * u * u
f = Fraction(n, d)
if f <= thres:
ratios.append(f)
else:
ratios.append(Fraction(d, 3 * n))
ratios = sorted(set(ratios))
if fraction:
return ratios
else:
return [float(x) * sqrt(3) for x in ratios]
def hexagon_alpha_brute(n: int):
w = cmath.rect(1, 2 * pi / 3)
divs = divisors(n / 2)
ratios, thres = [], 1 / sqrt(3)
for a in range(n):
for b in range(1, a + 1):
if a == 0 and b == 0:
continue
z2, g = a * a - a * b + b * b, gcd(a - 2 * b, 2 * a - b)
if z2 // g in divs:
f = Fraction(n, 2 * z2)
if f <= thres:
ratios.append(f)
else:
ratios.append(Fraction(2 * z2, 3 * n))
ratios = sorted(set([float(x) * sqrt(3) for x in ratios]))
return ratios

View File

@ -159,6 +159,7 @@ class Simulation:
sim, frames = Simulation.load(path) sim, frames = Simulation.load(path)
for frame in frames: for frame in frames:
sim.frames.append(sim.energy.mode(*frame["domain"], frame["arr"])) sim.frames.append(sim.energy.mode(*frame["domain"], frame["arr"]))
sim.frames[-1].stats = frame["stats"]
return sim return sim
@ -238,8 +239,8 @@ class Flow(Simulation):
if self.adaptive: if self.adaptive:
error = change - grad * self.step_size error = change - grad * self.step_size
tol = 10 ** min(-3, -2 + log10(grad_norm)) tol = 10 ** min(-4, -3 + log10(grad_norm))
# tol = 10 ** -7
self.step_size *= (tol / np.linalg.norm(error)) ** 0.5 self.step_size *= (tol / np.linalg.norm(error)) ** 0.5
if not save: if not save:
@ -315,7 +316,7 @@ class Search(Simulation):
new_sites: Optional[numpy.ndarray] = None, new_sites: Optional[numpy.ndarray] = None,
) -> None: ) -> None:
if log: if log:
print(f"Travel - {self.domain}", flush=True) print(f"Search - {self.domain} - {len(self)}", flush=True)
if save and len(self) == 0: if save and len(self) == 0:
self.save(self.initial_data, True) self.save(self.initial_data, True)
@ -328,16 +329,15 @@ class Search(Simulation):
sim.run(False, log, log_steps) sim.run(False, log, log_steps)
self.frames.append(sim[-1]) self.frames.append(sim[-1])
# Get Hessian,and check nullity. If > 2, perturb.
hess = self.frames[i].hessian
eigs = np.sort(np.linalg.eig(hess)[0])
self.frames[i].stats["eigs"] = eigs
if save: if save:
self.save(self.frame_data(i)) self.save(self.frame_data(i))
if log: if log:
print(f"Equilibrium: {i:04}\n", flush=True) print(f"Equilibrium: {i:04}\n", flush=True)
# Get Hessian,and check nullity. If > 2, perturb.
hess = self.frames[i].hessian(10e-5)
eigs = np.sort(np.linalg.eig(hess)[0])
self.frames[i].stats["eigs"] = eigs
zero_eigs = np.count_nonzero( zero_eigs = np.count_nonzero(
np.isclose(eigs, np.zeros((len(eigs),)), atol=1e-4) np.isclose(eigs, np.zeros((len(eigs),)), atol=1e-4)
) )

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@ cdef class VoronoiContainer:
cdef readonly INT_T n cdef readonly INT_T n
cdef readonly FLOAT_T w, h, r, energy cdef readonly FLOAT_T w, h, r, energy
cdef FLOAT_T [2] dim cdef FLOAT_T [2] dim
cdef FLOAT_T [:, ::1] points, vertices, site_cache, edge_cache, grad cdef FLOAT_T [:, ::1] points, vertices, site_cache, edge_cache, grad, hess
cdef INT_T [:, ::1] sites, edges cdef INT_T [:, ::1] sites, edges
cdef EdgeCacheMap* edge_cache_map cdef EdgeCacheMap* edge_cache_map
cdef dict __dict__ cdef dict __dict__
@ -77,6 +77,7 @@ cdef class VoronoiContainer:
cdef void common_cache(VoronoiContainer self) except * cdef void common_cache(VoronoiContainer self) except *
cdef void precompute(self) except * cdef void precompute(self) except *
cdef void calc_grad(self) except * cdef void calc_grad(self) except *
cdef void calc_hess(self) except *
cdef void get_statistics(VoronoiContainer self) except * cdef void get_statistics(VoronoiContainer self) except *
@staticmethod @staticmethod

View File

@ -1,4 +1,4 @@
import array, scipy.spatial, numpy as np import array, scipy.spatial, numpy as np, math
from cython.parallel import parallel, prange from cython.parallel import parallel, prange
cimport numpy as np cimport numpy as np
@ -6,8 +6,8 @@ from cpython cimport array
from libc.math cimport isnan, NAN, pi as PI from libc.math cimport isnan, NAN, pi as PI
from squish.core cimport INT_T, FLOAT_T, \ from squish.core cimport INT_T, FLOAT_T, \
IArray, FArray, Vector2D, Matrix2x2, \ IArray, FArray, Vector2D, Matrix2x2, BitSet, \
_IArray, _FArray, _Vector2D, _Matrix2x2 _IArray, _FArray, _Vector2D, _Matrix2x2, _BitSet
from squish.voronoi cimport SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge from squish.voronoi cimport SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge
@ -554,8 +554,8 @@ cdef class VoronoiContainer:
# vp - vm, vm - xi # vp - vm, vm - xi
la, da = q.copy.vsub(&q, p), p.copy.vsub(&p, xi.vec(&xi)) la, da = q.copy.vsub(&q, p), p.copy.vsub(&p, xi.vec(&xi))
la_mag = la.mag(&la) la_mag = la.mag(&la)
area_p = la.dot(&la, da.rot(&da)) area_p = la.dot(&la, da.copy.rot(&da))
Rla = la.rot(&la) Rla = la.copy.rot(&la)
ya = Rla.copy.smul(&Rla, -2*area_p/la.dot(&la, la)) ya = Rla.copy.smul(&Rla, -2*area_p/la.dot(&la, la))
# Calculating centroid. # Calculating centroid.
@ -591,14 +591,14 @@ cdef class VoronoiContainer:
xj, xk = em.cache.ya(&em, NAN_VECTOR), ep.cache.ya(&ep, NAN_VECTOR) xj, xk = em.cache.ya(&em, NAN_VECTOR), ep.cache.ya(&ep, NAN_VECTOR)
Rxjk = xk.copy.vsub(&xk, xj) Rxjk = xk.copy.vsub(&xk, xj)
Rxjk.self.smul(&Rxjk, 2) Rxjk.self.smul(&Rxjk, 2)
Rxjk = Rxjk.rot(&Rxjk) Rxjk.self.rot(&Rxjk)
v = ep.cache.da(&ep, NAN_VECTOR) v = ep.cache.da(&ep, NAN_VECTOR)
top = R.copy.smul(&R, xj.dot(&xj, xj) - xk.dot(&xk, xk)) top = R.copy.smul(&R, xj.dot(&xj, xj) - xk.dot(&xk, xk))
top.self.msub(&top, _Matrix2x2(v.x*Rxjk.x, v.x*Rxjk.y, v.y*Rxjk.x, v.y*Rxjk.y)) top.self.msub(&top, _Matrix2x2(v.x*Rxjk.x, v.x*Rxjk.y, v.y*Rxjk.x, v.y*Rxjk.y))
top.self.sdiv(&top, -Rxjk.dot(&Rxjk, xj)) top.self.sdiv(&top, -Rxjk.dot(&Rxjk, xj))
return _Matrix2x2(top.a, top.c, top.b, top.d) return top
@staticmethod @staticmethod
cdef inline bint sign(FLOAT_T [::1] ref, FLOAT_T [::1] p, FLOAT_T [::1] q): cdef inline bint sign(FLOAT_T [::1] ref, FLOAT_T [::1] p, FLOAT_T [::1] q):
@ -621,6 +621,9 @@ cdef class VoronoiContainer:
cdef void calc_grad(self) except *: cdef void calc_grad(self) except *:
pass pass
cdef void calc_hess(self) except *:
pass
cdef void get_statistics(self) except *: cdef void get_statistics(self) except *:
self.stats = {} self.stats = {}
cache = self.site_cache[:self.n, :] cache = self.site_cache[:self.n, :]
@ -660,6 +663,11 @@ cdef class VoronoiContainer:
def gradient(self): def gradient(self):
return np.asarray(self.grad, dtype=FLOAT) return np.asarray(self.grad, dtype=FLOAT)
@property
def hessian(self):
self.calc_hess()
return np.asarray(self.hess, dtype=FLOAT)
def add_sites(self, add): def add_sites(self, add):
return (self.site_arr + add) % np.asarray(self.dim, dtype=FLOAT) return (self.site_arr + add) % np.asarray(self.dim, dtype=FLOAT)
@ -671,146 +679,6 @@ cdef class VoronoiContainer:
return -(step/2)*(k1+k2), -k1 return -(step/2)*(k1+k2), -k1
def approx_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
def radialt_hessian(self) -> np.ndarray:
HE = np.zeros((2*self.n, 2*self.n))
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache,
self.edge_cache, self.edge_cache_map)
cdef Site xi, xk
cdef HalfEdge e, em, ep, fj, fk
cdef Vector2D temp1, temp2, dau, dapu
cdef Matrix2x2 sigI, sigJ, sigK, p, q, tempm, toI, toJ, toK
cdef INT_T i, j, k, z
for i in range(self.n):
xi = _Site(i, &info)
ep = xi.edge(&xi)
e = ep.prev(&ep)
j = 0
while j < xi.edge_num(&xi):
e.cache.H(&e, VoronoiContainer.calc_H(e, ep))
e = e.next(&e)
j = j + 1
for i in range(self.n):
xi = _Site(i, &info)
e = xi.edge(&xi)
z = 0
while z < xi.edge_num(&xi):
em, ep = e.prev(&e), e.next(&e)
fj, fk = em.twin(&em), e.twin(&e)
fj, fk = fj.next(&fj), fk.next(&fk)
xj, xk = fj.face(&fj), fk.face(&fk)
j, k = xj.index(&xj) % self.n, xk.index(&xk) % self.n
if k < 0:
k = k + self.n
if j < 0:
j = j + self.n
sigI = e.cache.H(&e, NAN_MATRIX)
sigJ = fj.cache.H(&fj, NAN_MATRIX)
sigK = fk.cache.H(&fk, NAN_MATRIX)
### Calculating of p
temp1 = e.cache.la(&e, NAN_VECTOR)
temp1 = temp1.rot(&temp1)
temp1.self.sdiv(
&temp1,
e.cache.la_mag(&e, NAN) * e.cache.ya_mag(&e, NAN) / 2
)
dau = e.cache.da(&e, NAN_VECTOR)
dau.self.sdiv(&dau, e.cache.da_mag(&e, NAN))
dapu = ep.cache.da(&ep, NAN_VECTOR)
dapu.self.sdiv(&dapu, ep.cache.da_mag(&ep, NAN))
temp2 = dapu.copy.vsub(&dapu, dau)
temp2 = temp2.rot(&temp2)
p = _Matrix2x2(
temp1.x*temp2.x, temp1.x*temp2.y,
temp1.y*temp2.x, temp1.y*temp2.y
)
### Calculating of q
temp2 = e.cache.la(&e, NAN_VECTOR)
temp1 = temp2.rot(&temp2)
q = _Matrix2x2(
temp1.x*temp2.x, temp1.x*temp2.y,
temp1.y*temp2.x, temp1.y*temp2.y
)
q.self.sdiv(&q, e.cache.la_mag(&e, NAN)**2)
q.self.msub(&q, R)
q.self.smul(
&q,
e.cache.calI(&e, NAN) / e.cache.la_mag(&e, NAN)
)
temp2 = em.cache.la(&em, NAN_VECTOR)
temp1 = temp2.rot(&temp2)
tempm = _Matrix2x2(
temp1.x*temp2.x, temp1.x*temp2.y,
temp1.y*temp2.x, temp1.y*temp2.y
)
tempm.self.sdiv(&tempm, em.cache.la_mag(&em, NAN)**2)
tempm = R.copy.msub(&R, tempm)
tempm.self.smul(
&tempm,
em.cache.calI(&em, NAN) / em.cache.la_mag(&em, NAN)
)
q.self.madd(&q, tempm)
# Calculating components that go to the respective sites
toI = sigI.copy.mmul(&sigI, p.copy.madd(&p, q))
toI.self.msub(&toI, p)
toJ = sigJ.copy.mmul(&sigJ, p.copy.madd(&p, q))
toK = sigK.copy.mmul(&sigK, p.copy.madd(&p, q))
HE[2*i: 2*(i+1), 2*i: 2*(i+1)] += np.array([[toI.a, toI.b], [toI.c, toI.d]])
HE[2*i: 2*(i+1), 2*j: 2*(j+1)] += np.array([[toJ.a, toJ.b], [toJ.c, toJ.d]])
HE[2*i: 2*(i+1), 2*k: 2*(k+1)] += np.array([[toK.a, toK.b], [toK.c, toK.d]])
e = e.next(&e)
z = z + 1
return -2*self.r*HE
def site_vert_arr(self): # -> List[np.ndarray] def site_vert_arr(self): # -> List[np.ndarray]
cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points, cdef VoronoiInfo info = _VoronoiInfo(self.sites, self.edges, self.points,
self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map) self.vertices, self.site_cache, self.edge_cache, self.edge_cache_map)