Disabled external gits
1
cs457-gc/assignment_1_3/data/dance.json
Normal file
BIN
cs457-gc/assignment_1_3/data/dance.png
Normal file
After Width: | Height: | Size: 123 KiB |
1
cs457-gc/assignment_1_3/data/dinosaur.json
Normal file
BIN
cs457-gc/assignment_1_3/data/dinosaur.png
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
cs457-gc/assignment_1_3/data/drawA.png
Normal file
After Width: | Height: | Size: 31 KiB |
2
cs457-gc/assignment_1_3/data/drawA.svg
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
cs457-gc/assignment_1_3/data/drawB.png
Normal file
After Width: | Height: | Size: 45 KiB |
2
cs457-gc/assignment_1_3/data/drawB.svg
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
cs457-gc/assignment_1_3/data/drawC.png
Normal file
After Width: | Height: | Size: 29 KiB |
2
cs457-gc/assignment_1_3/data/drawC.svg
Normal file
After Width: | Height: | Size: 67 KiB |
1
cs457-gc/assignment_1_3/data/gym.json
Normal file
937
cs457-gc/assignment_1_3/notebook/make_it_stand_nb3.ipynb
Normal file
188
cs457-gc/assignment_1_3/src/bfgs.py
Normal file
@@ -0,0 +1,188 @@
|
||||
from energies import *
|
||||
from optimization import compute_optimization_objective, compute_optimization_objective_gradient
|
||||
from linesearch import *
|
||||
import numpy as np
|
||||
import time
|
||||
import igl
|
||||
|
||||
def compute_approximate_hessian_matrix(sk, yk, B_prev):
|
||||
"""
|
||||
Compute the approximated hessian matrix
|
||||
|
||||
Input:
|
||||
- s_k : np.array(#V * 2, 1)
|
||||
s_k = x_{k+1} - x_{k}, the difference in variables at two consecutive iterations.
|
||||
Note that x_{k} is the vertices' coordinate V at the k-th iteration.
|
||||
The vertices' cooridnate V however is a np.array(#V, 3)
|
||||
We use x_{k} = V[:, 0 : 2].flatten() to flatten the vertices coordinartes.
|
||||
|
||||
- y_k : np.array(#V * 2, 1)
|
||||
y_k = grad(f(x_{k+1})) - grad(f(x_{k})),
|
||||
the difference in function gradient at two consecutive iterations.
|
||||
The grad(f(x_{k})) is the gradient of our objective energy,
|
||||
which is a np.array(#V, 2)
|
||||
To be used for BFGS, we flatten the gradient.
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
|
||||
- B_prev: np.array(#V * 2, #V * 2)
|
||||
The approximated hessian matrix from last iteration
|
||||
|
||||
Output
|
||||
-B_new: np.array(#V * 2, #V * 2)
|
||||
The approximated hessian matrix of current iteration
|
||||
|
||||
"""
|
||||
a = (yk @ yk.T)/(yk.T @ sk)
|
||||
bt = B_prev @ sk @ sk.T @ B_prev.T
|
||||
bb = sk.T @ B_prev @ sk
|
||||
B_new = B_prev + a - bt/bb
|
||||
return B_new
|
||||
|
||||
def compute_inverse_approximate_hessian_matrix(sk, yk, invB_prev):
|
||||
"""
|
||||
Compute the inverse approximated hessian matrix
|
||||
|
||||
Input:
|
||||
- s_k : np.array(#V * 2, 1)
|
||||
s_k = x_{k+1} - x_{k}, the difference in variables at two consecutive iterations.
|
||||
Note that x_{k} is the vertices' coordinate V at the k-th iteration.
|
||||
The vertices' cooridnate V however is a np.array(#V, 3)
|
||||
We use x_{k} = V[:, 0 : 2].flatten() to flatten the vertices coordinartes.
|
||||
|
||||
- y_k : np.array(#V * 2, 1)
|
||||
y_k = grad(f(x_{k+1})) - grad(f(x_{k})),
|
||||
the difference in function gradient at two consecutive iterations.
|
||||
The grad(f(x_{k})) is the gradient of our objective energy,
|
||||
which is a np.array(#V, 2)
|
||||
To be used for BFGS, we flatten the gradient.
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
|
||||
- invB_prev: np.array(#V * 2, #V * 2)
|
||||
The inversed matrix of the approximated hessian from last iteration
|
||||
|
||||
Output
|
||||
- invB_new: np.array(#V * 2, #V * 2)
|
||||
The inversed matrix of the approximated hessian at current iteration
|
||||
|
||||
"""
|
||||
d = sk.T @ yk
|
||||
e = invB_prev @ yk
|
||||
a = (d + (yk.T @ e)) * (sk @ sk.T)/(d * d)
|
||||
|
||||
b = ((e @ sk.T) + ((sk @ yk.T) @ invB_prev))/d
|
||||
invB_new = invB_prev + a - b
|
||||
return invB_new
|
||||
|
||||
|
||||
def bfgs_with_line_search(V, F, x_csl, w, obj_tol, theta, beta, c, iter):
|
||||
|
||||
"""
|
||||
Find equilibrium shape by using BFGS method
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
- w : float
|
||||
The weight for shape preservation energy.
|
||||
- obj_tol: float
|
||||
The termination condition for optimization.
|
||||
The program stop running if
|
||||
the absolute different between the objectives of two consecutive iterations is smaller than obj_tol
|
||||
- theta : float
|
||||
The initial gradient descent step size.
|
||||
- beta : float
|
||||
The backtracking ratio, alpha = beta * alpha
|
||||
- c: float
|
||||
The coefficient for armijo condition
|
||||
- iter : int
|
||||
The number of iteration for gradient descent.
|
||||
|
||||
Output:
|
||||
- V1 : np.array (#V, 3)
|
||||
The optimized mesh's vertices
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- energy: np.array(iters, 1)
|
||||
The objective function energy curve with respect to the number of iterations.
|
||||
- running_time: float
|
||||
The tot running time of the optimization
|
||||
"""
|
||||
|
||||
V1 = V.copy()
|
||||
|
||||
# this function of libigl returns an array (#edges, 2) where i-th row
|
||||
# contains the indices of the two vertices of i-th edge.
|
||||
E = igl.edges(F)
|
||||
|
||||
fix = np.where(V1[:, 1] < 1e-3)[0]
|
||||
|
||||
L0 = compute_edges_length(V1, E)
|
||||
|
||||
t0 = time.time()
|
||||
|
||||
energy = []
|
||||
|
||||
obj_prev = 0
|
||||
|
||||
it_time = 0
|
||||
gradprev= None
|
||||
vprev = V1
|
||||
Hprev = np.identity(V.shape[0]*2)/theta
|
||||
|
||||
while (True):
|
||||
|
||||
# energy
|
||||
obj = compute_optimization_objective(V1, F, E, x_csl, L0, w)
|
||||
|
||||
if abs(obj_prev - obj) < obj_tol:
|
||||
break
|
||||
|
||||
if it_time > iter:
|
||||
break
|
||||
|
||||
obj_prev = obj
|
||||
|
||||
energy.append(obj)
|
||||
|
||||
grad = compute_optimization_objective_gradient(V1, F, E, x_csl, L0, w)
|
||||
|
||||
grad[fix] = 0
|
||||
if gradprev is None:
|
||||
gradprev = grad
|
||||
### start of your code.
|
||||
gradf = grad.flatten()
|
||||
ff = F.flatten()
|
||||
vf = V[:, 0 : 2].flatten()
|
||||
unflatten = lambda x: np.concatenate((np.reshape(x,(V.shape[0],2)),np.zeros((V.shape[0],1))),axis=-1)
|
||||
|
||||
f = lambda x: compute_optimization_objective(unflatten(x), F, E, x_csl, L0, w)
|
||||
alpha = backtracking_line_search(-gradf, gradf, vf, theta, beta, c, f)
|
||||
x_cm = compute_mesh_centroid(V, F)[0]
|
||||
|
||||
HV = (V1 - vprev)[:,:2].flatten()[:,None]
|
||||
HG = (grad-gradprev).flatten()[:,None]
|
||||
|
||||
Hcur = compute_inverse_approximate_hessian_matrix(HV, HG, Hprev)
|
||||
d = np.concatenate((np.reshape(Hcur @ gradf,(V.shape[0],2)),np.zeros((V.shape[0],1))),axis=-1)
|
||||
|
||||
vprev = V1
|
||||
gradprev = grad
|
||||
Hprev = Hcur
|
||||
|
||||
V1 = V1 - (alpha * d)
|
||||
|
||||
|
||||
### end of your code.
|
||||
|
||||
it_time = it_time + 1
|
||||
|
||||
running_time = time.time() - t0
|
||||
|
||||
return [V1, F, energy, running_time]
|
244
cs457-gc/assignment_1_3/src/energies.py
Normal file
@@ -0,0 +1,244 @@
|
||||
import numpy as np
|
||||
from scipy import sparse
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Functions from assignment_1_1
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
from geometry import compute_mesh_centroid
|
||||
from geometry import compute_faces_centroid
|
||||
from geometry import compute_faces_area
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Provided functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def vertex_cells_sum(values, cells):
|
||||
"""
|
||||
Sums values at vertices from each incident n-cell.
|
||||
|
||||
Input:
|
||||
- values : np.array (#cells,) or (#cells, n)
|
||||
The cell values to be summed at vertices.
|
||||
If shape (#cells,): The value is per-cell,
|
||||
If shape (#cells, n): The value refers to the corresponding cell vertex.
|
||||
- cells : np.array (#cells, n)
|
||||
The array of cells.
|
||||
Output:
|
||||
- v_sum : np.array (#vertices,)
|
||||
A vector with the sum at each i-th vertex in i-th position.
|
||||
Note:
|
||||
If n = 2 the cell is an edge, if n = 3 the cell is a triangle,
|
||||
if n = 4 the cell can be a tetrahedron or a quadrilateral, depending on
|
||||
the data structure.
|
||||
"""
|
||||
i = cells.flatten('F')
|
||||
j = np.arange(len(cells))
|
||||
j = np.tile(j, cells.shape[1])
|
||||
v = values.flatten('F')
|
||||
if len(v) == len(cells):
|
||||
v = v[j]
|
||||
v_sum = sparse.coo_matrix((v, (i, j)), (np.max(cells) + 1, len(cells)))
|
||||
return np.array(v_sum.sum(axis=1)).flatten()
|
||||
|
||||
|
||||
def compute_edges_length(V, E):
|
||||
"""
|
||||
Computes the edge length of each mesh edge.
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- E : np.array (#edges, 2)
|
||||
The array of mesh edges.
|
||||
generated from the function E = igl.edges(F)
|
||||
returns an array (#edges, 2) where i-th row contains the indices of the two vertices of i-th edge.
|
||||
Output:
|
||||
- l : np.array (#edges,)
|
||||
The edge lengths.
|
||||
"""
|
||||
|
||||
l = np.linalg.norm(V[E[:, 0]] - V[E[:, 1]], axis=1)
|
||||
return l
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2.5.1 Target energies
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_equilibrium_energy(V, F, x_csl):
|
||||
"""
|
||||
Computes the equilibrium energy E_eq = 1/2*(x_cm - x_csl)^2.
|
||||
|
||||
Input:
|
||||
-- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
Output:
|
||||
- E_eq : float
|
||||
the equilibrium energy of the mesh with respect to the target centroid
|
||||
x_csl.
|
||||
"""
|
||||
x_cm = compute_mesh_centroid(V,F)[0]
|
||||
E_eq = 0.5 * (x_cm - x_csl)**2
|
||||
return E_eq
|
||||
|
||||
|
||||
def compute_shape_energy(V, E, L):
|
||||
"""
|
||||
Computes the energy E_sh = 1/2 sum_e (l_e - L_e)^2 in the current
|
||||
configuration V, where l_e is the length of mesh edges, and L_e the
|
||||
corresponding length in the undeformed configuration.
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- E : np.array (#edges, 2)
|
||||
The array of mesh edges.
|
||||
- L : np.array (#edges,)
|
||||
The rest lengths of mesh edges.
|
||||
Output:
|
||||
- E_sh : float
|
||||
The energy E_sh in the current configuration V.
|
||||
"""
|
||||
l = compute_edges_length(V,E)
|
||||
Lf = np.array(L)
|
||||
e_vec = np.arange(len(E))
|
||||
ldiff = (l[e_vec]-Lf[e_vec])**2
|
||||
E_sh = 0.5 * np.sum(ldiff)
|
||||
return E_sh
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2.5.2 Faces area gradient
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_faces_area_gradient(V, F):
|
||||
"""
|
||||
Computes the gradient of faces area.
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
Output:
|
||||
- dA_x : np.array (#F, 3)
|
||||
The gradient of faces areas A_i with respect to the x coordinate of each
|
||||
face vertex x_1, x_2, and x_3, with (i,0) = dA_i/dx_1, (i,1) = dA_i/dx_2,
|
||||
and (i,2) = dA_i/dx_3.
|
||||
- dA_y : np.array (#F, 3)
|
||||
The gradient of faces areas A_i with respect to the y coordinate of each
|
||||
face vertex y_1, y_2, and y_3, with (i,0) = dA_i/dy_1, (i,1) = dA_i/dy_2,
|
||||
and (i,2) = dA_i/dy_3.
|
||||
"""
|
||||
|
||||
grad = lambda a,b: (V[a]-V[b])
|
||||
gradABC = lambda fi: [grad(fi[2],fi[1]),grad(fi[0],fi[2]),grad(fi[1],fi[0])]
|
||||
dA = np.apply_along_axis(gradABC,1,F)/2
|
||||
|
||||
dA_x = -dA[:,:,1]
|
||||
dA_y = dA[:,:,0]
|
||||
return dA_x, dA_y
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2.5.3 Equilibrium energy gradient
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_equilibrium_energy_gradient(V, F, x_csl):
|
||||
"""
|
||||
Computes the gradient of the energy E_eq = 1/2*(x_cm - x_csl)^2 with respect
|
||||
to the x and y coordinates of each vertex, where x_cm is the x coordinate
|
||||
of the area centroid and x_csl x coordinate of the center of the support
|
||||
line.
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
Output:
|
||||
- grad_E_eq : np.array (#V, 2)
|
||||
The gradient of the energy E_eq with respect to vertices v_i,
|
||||
with (i, 0) = dE_eq/dx_i and (i, 1) = dE_eq/dy_i
|
||||
"""
|
||||
|
||||
cm = compute_mesh_centroid(V,F)
|
||||
x_cm = cm[0]
|
||||
|
||||
x_f = compute_faces_centroid(V,F)
|
||||
|
||||
A_f = compute_faces_area(V,F)
|
||||
|
||||
A_f_V_nonp = compute_faces_area_gradient(V, F)
|
||||
A_f_V = np.stack(A_f_V_nonp,axis=-1)
|
||||
|
||||
A_omega = np.sum(A_f)
|
||||
xfact = (x_cm - x_csl)/A_omega
|
||||
|
||||
xfV = lambda v: np.apply_along_axis(lambda x: np.array([1/3,0.0]) if np.any(x[:]==v) else np.zeros((2)),1,F)
|
||||
|
||||
AfV = lambda v: (np.where(F[:,0][:,None]==v,A_f_V[:,0,:2],
|
||||
np.where(F[:,1][:,None]==v,A_f_V[:,1,:2],
|
||||
np.where(F[:,2][:,None]==v,A_f_V[:,2,:2],np.zeros((F.shape[0],2))) )) )
|
||||
|
||||
cf = lambda v : AfV(v)*(x_f[:,0] - x_cm)[:, None] + (A_f[:, None]*xfV(v))
|
||||
|
||||
#v_vec = np.arange(len(V))
|
||||
#veq = np.sum(cf(v_vec),axis=0)*xfact
|
||||
veq = np.array([np.sum(cf(v),axis=0) for v in range(0,len(V))])*xfact
|
||||
|
||||
return veq
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2.5.4 Shape energy gradient
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_shape_energy_gradient(V, E, L):
|
||||
"""
|
||||
Computes the gradient of the energy E_sh = 1/2 sum_e (l_e - L_e)^2 with
|
||||
respect to the x and y coordinates of each vertex, where l_e is the length
|
||||
of mesh edges, and L_e the corresponding length in the undeformed
|
||||
configuration.
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- E : np.array (#edges, 2)
|
||||
The array of mesh edges.
|
||||
- L : np.array (#edges,)
|
||||
The rest lengths of mesh edges.
|
||||
Output:
|
||||
- grad_E_sh : np.array (#V, 2)
|
||||
The gradient of the energy E_sh with respect to vertices v_i,
|
||||
with (i, 0) = dE_sh/dx_i, (i, 1) = dE_sh/dy_i
|
||||
"""
|
||||
|
||||
vdif = lambda ee0, ee1: (V[ee0]-V[ee1])[:,:2]
|
||||
len_e_d = vdif(E[:,0],E[:,1])
|
||||
len_e = np.linalg.norm(len_e_d, axis=1)
|
||||
|
||||
len_e_t = lambda v: (np.where(E[:,0][:,None]==v,len_e_d[:],
|
||||
np.where(E[:,1][:,None]==v,-len_e_d[:],np.zeros(E.shape))))
|
||||
|
||||
veq = np.sum(np.array([len_e_t(v) for v in range(0,len(V))]),axis=1) - np.sum(L/len_e,axis=0)
|
||||
|
||||
return veq
|
100
cs457-gc/assignment_1_3/src/geometry.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import numpy as np
|
||||
|
||||
def compute_faces_area(V, F):
|
||||
"""
|
||||
Computes the area of the faces of a given triangle mesh (V, F).
|
||||
|
||||
Input:
|
||||
- V : np.array (|V|, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (|F|, 3)
|
||||
The array of triangle faces.
|
||||
Output:
|
||||
- area : np.array (|F|,)
|
||||
The area of the faces. The i-th position contains the area of the i-th
|
||||
face.
|
||||
"""
|
||||
|
||||
# HW.1.3.3
|
||||
return np.linalg.norm(np.cross(V[F[:,1]] - V[F[:,0]],V[F[:,2]] - V[F[:,0]], axis=1), axis=1)/2.0
|
||||
|
||||
|
||||
def compute_mesh_area(V, F):
|
||||
"""
|
||||
Computes the area of a given triangle mesh (V, F).
|
||||
|
||||
Input:
|
||||
- V : np.array (|V|, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (|F|, 3)
|
||||
The array of triangle faces.
|
||||
Output:
|
||||
- area : float
|
||||
The area of the mesh.
|
||||
"""
|
||||
|
||||
# HW.1.3.3
|
||||
return np.sum(compute_faces_area(V,F))
|
||||
|
||||
|
||||
def compute_faces_centroid(V, F):
|
||||
"""
|
||||
Computes the area centroid of each face of a given triangle mesh (V, F).
|
||||
|
||||
Input:
|
||||
- V : np.array (|V|, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (|F|, 3)
|
||||
The array of triangle faces.
|
||||
Output:
|
||||
- cf : np.array (|F|, 3)
|
||||
The area centroid of the faces.
|
||||
"""
|
||||
|
||||
# HW.1.3.4
|
||||
return (V[F[:,0]]+ V[F[:,1]] + V[F[:,2]])/3.0
|
||||
|
||||
|
||||
def compute_mesh_centroid(V, F):
|
||||
"""
|
||||
Computes the area centroid of a given triangle mesh (V, F).
|
||||
|
||||
Input:
|
||||
- V : np.array (|V|, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (|F|, 3)
|
||||
The array of triangle faces.
|
||||
Output:
|
||||
- centroid : np.array (3,)
|
||||
The area centroid of the mesh.
|
||||
"""
|
||||
|
||||
# HW.1.3.4
|
||||
face_centroid = compute_faces_centroid(V,F)
|
||||
face_area = compute_faces_area(V,F)
|
||||
return 1.0/np.sum(face_area) * np.sum(np.einsum('i,ij->ij', face_area, face_centroid), axis=0)
|
||||
|
||||
|
||||
def compute_center_support_line(V):
|
||||
"""
|
||||
Computes the x coordinate of the center of the support line
|
||||
|
||||
Input:
|
||||
- V : np.array (|V|, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
|
||||
Output:
|
||||
- x_csl : float
|
||||
the x coordinate of the center of the support line
|
||||
"""
|
||||
|
||||
# HW.1.3.5
|
||||
support_v = np.fromiter((v[0] for v in V if v[1]<10**-5), dtype=V.dtype)
|
||||
x_csl = np.amin(support_v)+(np.amax(support_v)-np.amin(support_v))/2
|
||||
|
||||
return x_csl
|
173
cs457-gc/assignment_1_3/src/linesearch.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from energies import *
|
||||
from optimization import compute_optimization_objective, compute_optimization_objective_gradient
|
||||
import numpy as np
|
||||
import time
|
||||
import igl
|
||||
|
||||
def evaluate_armijo_rule(f_x, f_x1, p, grad, c, alpha):
|
||||
"""
|
||||
Check the armijo rule, return true if the armijo condition is satisfied
|
||||
|
||||
Input:
|
||||
- f_x : float
|
||||
The function value at x
|
||||
- f_x1 : float
|
||||
The function value at x_(k+1) = x_k + alpha * p_k
|
||||
- p: np.array(2 * #V, 1)
|
||||
The flatten search direction
|
||||
Here, we use the flatten the search direction.
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
- grad: np.array(2 * #V, 1)
|
||||
The gradient of the function at x
|
||||
Here, we use the flatten the gradient
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
- c: float
|
||||
The coefficient for armijo condition
|
||||
- alpha: float
|
||||
The current step size
|
||||
|
||||
Output:
|
||||
- condition: bool
|
||||
True if the armijio condition is satisfied
|
||||
"""
|
||||
return f_x1 <= f_x + c*alpha*np.dot(p.T,grad)
|
||||
|
||||
def backtracking_line_search(p, grad, x, theta, beta, c, f, *arg):
|
||||
"""
|
||||
Computes the step size for p that satisfies the armijio condition.
|
||||
|
||||
Input:
|
||||
- p: np.array(2 * #V, 1)
|
||||
The flatten search direction
|
||||
Here, we use the flatten the search direction.
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
- grad: np.array(2 * #V, 1)
|
||||
The gradient of the function at x
|
||||
Here, we use the flatten the gradient
|
||||
The flatten process asks the all x coordinates to be first
|
||||
then all y cooridnates to be the second.
|
||||
- x : np.array(#V * 2, 1)
|
||||
The array of optimization variables
|
||||
x = V[:, 0 : 2].flatten()
|
||||
- theta: float
|
||||
The initial step size
|
||||
- beta : float
|
||||
The backtracking ratio, alpha = beta * alpha
|
||||
- c: float
|
||||
The coefficient for armijo condition
|
||||
- f: function
|
||||
The objective function (i.e., optimization.compute_optimization_objective)
|
||||
- *arg: parameters
|
||||
The rest parameters for the function f except the its first variables Vx
|
||||
|
||||
Output:
|
||||
- alpha: float
|
||||
The step size for p that satisfies the armijio condition
|
||||
"""
|
||||
|
||||
alpha = theta
|
||||
x1 = x + alpha*p
|
||||
while not evaluate_armijo_rule(f(x,*arg),f(x1, *arg), p, grad, c, alpha):
|
||||
alpha = beta*alpha
|
||||
x1 = x+alpha*p
|
||||
|
||||
return alpha
|
||||
|
||||
def gradient_descent_with_line_search(V, F, x_csl, w, obj_tol, theta, beta, c, iter):
|
||||
"""
|
||||
Find equilibrium shape by using gradient descent with backtracking line search
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row.
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
- w : float
|
||||
The weight for shape preservation energy.
|
||||
- obj_tol: float
|
||||
The termination condition for optimization.
|
||||
The program stop running if
|
||||
the absolute difference between the objectives of two consecutive iterations is smaller than obj_tol
|
||||
- theta : float
|
||||
The initial gradient descent step size.
|
||||
- beta : float
|
||||
The backtracking ratio, alpha = beta * alpha
|
||||
- c: float
|
||||
The coefficient for armijo condition.
|
||||
- iter : int
|
||||
The maximum number of iteration for gradient descent.
|
||||
|
||||
Output:
|
||||
- V1 : np.array (#V, 3)
|
||||
The optimized mesh's vertices
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- energy: np.array(iters, 1)
|
||||
The objective function energy curve with respect to the number of iterations.
|
||||
- running_time: float
|
||||
The tot running time of the optimization
|
||||
"""
|
||||
|
||||
V1 = V.copy()
|
||||
|
||||
# this function of libigl returns an array (#edges, 2) where i-th row
|
||||
# contains the indices of the two vertices of i-th edge.
|
||||
E = igl.edges(F)
|
||||
|
||||
fix = np.where(V1[:, 1] < 1e-3)[0]
|
||||
|
||||
L0 = compute_edges_length(V1, E)
|
||||
|
||||
t0 = time.time()
|
||||
|
||||
energy = []
|
||||
|
||||
obj_prev = 0
|
||||
|
||||
it_time = 0
|
||||
|
||||
while(True):
|
||||
|
||||
# energy
|
||||
obj = compute_optimization_objective(V1, F, E, x_csl, L0, w)
|
||||
|
||||
if abs(obj_prev - obj) < obj_tol:
|
||||
break
|
||||
|
||||
if it_time > iter:
|
||||
break
|
||||
|
||||
obj_prev = obj
|
||||
|
||||
energy.append(obj)
|
||||
|
||||
grad = compute_optimization_objective_gradient(V1, F, E, x_csl, L0, w)
|
||||
|
||||
grad[fix] = 0
|
||||
|
||||
### start of your code.
|
||||
gradf = grad.flatten()
|
||||
ff = F.flatten()
|
||||
vf = V[:, 0 : 2].flatten()
|
||||
unflatten = lambda x: np.concatenate((np.reshape(x,(V.shape[0],2)),np.zeros((V.shape[0],1))),axis=-1)
|
||||
|
||||
f = lambda x: compute_optimization_objective(unflatten(x), F, E, x_csl, L0, w)
|
||||
alpha = backtracking_line_search(-gradf, gradf, vf, theta, beta, c, f)
|
||||
x_cm = compute_mesh_centroid(V, F)[0]
|
||||
|
||||
grad_f = np.zeros((np.shape(grad)[0],3))
|
||||
grad_f[:,:-1] = grad
|
||||
V1 = V1 -(alpha * grad_f)
|
||||
### end of your code.
|
||||
|
||||
it_time = it_time + 1
|
||||
|
||||
running_time = time.time() - t0
|
||||
|
||||
return [V1, F, energy, running_time]
|
137
cs457-gc/assignment_1_3/src/optimization.py
Normal file
@@ -0,0 +1,137 @@
|
||||
from energies import compute_edges_length
|
||||
from energies import compute_equilibrium_energy_gradient, compute_equilibrium_energy
|
||||
from energies import compute_shape_energy_gradient, compute_shape_energy
|
||||
from geometry import compute_mesh_centroid
|
||||
import numpy as np
|
||||
import time
|
||||
import igl
|
||||
|
||||
def compute_optimization_objective(V, F, E, x_csl, L0, w):
|
||||
"""
|
||||
Compute the objective function of make-it-stand problem.
|
||||
E = E_equilibrium + w * E_shape
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- E : np.array (#edges, 2)
|
||||
The array of mesh edges.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
- L0 : np.array (#edges,)
|
||||
The rest lengths of mesh edges.
|
||||
- w : float
|
||||
The weight for shape preservation energy.
|
||||
Output:
|
||||
- obj : float
|
||||
The value of the objective function.
|
||||
"""
|
||||
|
||||
E_eq = compute_equilibrium_energy(V, F, x_csl)
|
||||
E_sh = compute_shape_energy(V, E, L0)
|
||||
obj = E_eq + w*E_sh
|
||||
|
||||
return obj
|
||||
|
||||
def compute_optimization_objective_gradient(V, F, E, x_csl, L0, w):
|
||||
"""
|
||||
Compute the gradient of the objective function of make-it-stand problem.
|
||||
D_E = D_E_equilibrium + w * D_E_shape
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- E : np.array (#edges, 2)
|
||||
The array of mesh edges.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
- l0 : np.array (#edges,)
|
||||
The rest lengths of mesh edges.
|
||||
- w : float
|
||||
The weight for shape preservation energy.
|
||||
Output:
|
||||
- grad_obj : np.array (#V, 2)
|
||||
The gradient of objective function.
|
||||
"""
|
||||
|
||||
grad_E_eq = compute_equilibrium_energy_gradient(V, F, x_csl)
|
||||
grad_E_sh = compute_shape_energy_gradient(V, E, L0)
|
||||
grad_obj = grad_E_eq + w * grad_E_sh
|
||||
|
||||
return grad_obj
|
||||
|
||||
def fixed_step_gradient_descent(V, F, x_csl, w, theta, iters):
|
||||
"""
|
||||
Find equilibrium shape by using fixed step gradient descent method
|
||||
|
||||
Input:
|
||||
- V : np.array (#V, 3)
|
||||
The array of vertices positions.
|
||||
Contains the coordinates of the i-th vertex in i-th row
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- x_csl : float
|
||||
The x coordinate of the center of the support line.
|
||||
- w : float
|
||||
The weight for shape preservation energy.
|
||||
- theta : float
|
||||
The optimization step.
|
||||
- iters : int
|
||||
The number of iteration for gradient descent.
|
||||
|
||||
Output:
|
||||
- V1 : np.array (#V, 3)
|
||||
The optimized mesh's vertices
|
||||
- F : np.array (#F, 3)
|
||||
The array of triangle faces.
|
||||
- energy: np.array(iters, 1)
|
||||
The objective function energy curve with respect to the number of iterations.
|
||||
- running_time: float
|
||||
The tot running time of the optimization
|
||||
"""
|
||||
|
||||
V1 = V.copy()
|
||||
|
||||
# this function of libigl returns an array (#edges, 2) where i-th row
|
||||
# contains the indices of the two vertices of i-th edge.
|
||||
E = igl.edges(F)
|
||||
|
||||
fix = np.where(V1[:, 1] < 1e-3)[0]
|
||||
|
||||
L0 = compute_edges_length(V1, E)
|
||||
|
||||
t0 = time.time()
|
||||
|
||||
energy = []
|
||||
|
||||
for i in range(iters):
|
||||
|
||||
grad = compute_optimization_objective_gradient(V1, F, E, x_csl, L0, w)
|
||||
|
||||
obj = compute_optimization_objective(V1, F, E, x_csl, L0, w)
|
||||
|
||||
energy.append(obj)
|
||||
|
||||
grad[fix] = 0
|
||||
|
||||
### start of your code.
|
||||
x_cm = compute_mesh_centroid(V, F)[0]
|
||||
if abs(x_csl - x_cm)<=10**-3:
|
||||
break
|
||||
if (np.linalg.norm(grad)<=10**-3):
|
||||
break
|
||||
|
||||
grad_f = np.zeros((np.shape(grad)[0],3))
|
||||
grad_f[:,:-1] = grad
|
||||
V1 = V1 -(theta * grad_f)
|
||||
### end of your code.
|
||||
|
||||
running_time = time.time() - t0
|
||||
|
||||
return [V1, F, energy, running_time]
|
45
cs457-gc/assignment_1_3/src/utility.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
import triangle as tr
|
||||
|
||||
def triangulate_mesh(Ps, Holes, area_density):
|
||||
'''
|
||||
Triangulates a set of points given by P using Delaunay scheme
|
||||
|
||||
Input:
|
||||
- P : list of N lists of 2 elements giving the vertices positions
|
||||
- Holes: list of M lists of 2 elements giving the holes positions
|
||||
- area_density: the maximum triangle area
|
||||
|
||||
Output:
|
||||
- V : array of shape (nNodes, 3) containing the vertices position
|
||||
- F : array of vertices ids (list of length nTri of lists of length 3). The ids are such that the algebraic area of
|
||||
each triangle is positively oriented
|
||||
'''
|
||||
|
||||
num_points = 0
|
||||
points = []
|
||||
segments = []
|
||||
for k, _ in enumerate(Ps):
|
||||
P = np.array(Ps[k])
|
||||
N = P.shape[0]
|
||||
points.append(P)
|
||||
index = np.arange(N)
|
||||
seg = np.stack([index, index + 1], axis=1) % N + num_points
|
||||
segments.append(seg)
|
||||
num_points += N
|
||||
|
||||
points = np.vstack(points)
|
||||
segments = np.vstack(segments)
|
||||
|
||||
data = []
|
||||
if Holes == [] or Holes == [[]] or Holes == None:
|
||||
data = dict(vertices=points, segments=segments)
|
||||
else:
|
||||
data = dict(vertices=points, segments=segments, holes = Holes)
|
||||
|
||||
tri = tr.triangulate(data, 'qpa{}'.format(area_density))
|
||||
|
||||
V = np.array([[v[0], v[1], 0] for v in tri["vertices"]])
|
||||
F = np.array([[f[0], f[1], f[2]] for f in tri["triangles"]])
|
||||
return [V, F]
|
67
cs457-gc/assignment_1_3/test/test3.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# import pytest
|
||||
import time
|
||||
import pytest
|
||||
import json
|
||||
import sys
|
||||
import igl
|
||||
import numpy as np
|
||||
sys.path.append('../')
|
||||
sys.path.append('../src')
|
||||
from src.energies import *
|
||||
from src.linesearch import *
|
||||
from src.bfgs import *
|
||||
eps = 1E-6
|
||||
|
||||
with open('test_data3.json', 'r') as infile:
|
||||
homework_datas = json.load(infile)
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.parametrize("data", homework_datas[0])
|
||||
def test_armijo_rule(data):
|
||||
Fx = data[0]
|
||||
Fy = data[1]
|
||||
p = np.array(data[2], dtype=float)
|
||||
grad = np.array(data[3], dtype=float)
|
||||
c = data[4]
|
||||
alpha = data[5]
|
||||
armijo_rule_ground_truth = data[6]
|
||||
armijo_rule_student = int(evaluate_armijo_rule(Fx, Fy, p, grad, c, alpha))
|
||||
assert armijo_rule_ground_truth == armijo_rule_student
|
||||
|
||||
def func(x):
|
||||
return np.linalg.norm(x) ** 2
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.parametrize("data", homework_datas[1])
|
||||
def test_backtracking_line_search(data):
|
||||
p = np.array(data[0], dtype=float)
|
||||
grad = np.array(data[1], dtype=float)
|
||||
x = np.array(data[2], dtype=float)
|
||||
theta = data[3]
|
||||
beta = data[4]
|
||||
c = data[5]
|
||||
backtracking_line_search_ground_truth = data[6]
|
||||
backtracking_line_search_student = backtracking_line_search(p, grad, x, theta, beta, c, func)
|
||||
|
||||
assert np.linalg.norm(backtracking_line_search_ground_truth - backtracking_line_search_student) < eps
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.parametrize("data", homework_datas[2])
|
||||
def test_compute_approximate_hessian_matrix(data):
|
||||
sk = np.array(data[0], dtype=float)
|
||||
yk = np.array(data[1], dtype=float)
|
||||
Bk = np.array(data[2], dtype=float)
|
||||
|
||||
newBk_student = compute_approximate_hessian_matrix(sk, yk, Bk)
|
||||
newBk_ground_truth = np.array(data[3], dtype=float)
|
||||
assert np.linalg.norm(newBk_student - newBk_ground_truth) < eps
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.parametrize("data", homework_datas[3])
|
||||
def test_compute_inverse_approximate_hessian_matrix(data):
|
||||
sk = np.array(data[0], dtype=float)
|
||||
yk = np.array(data[1], dtype=float)
|
||||
invBk = np.array(data[2], dtype=float)
|
||||
inv_newBk_student = compute_inverse_approximate_hessian_matrix(sk, yk, invBk)
|
||||
inv_newBk_ground_truth = np.array(data[3], dtype=float)
|
||||
assert np.linalg.norm(inv_newBk_student - inv_newBk_ground_truth) < eps
|
1
cs457-gc/assignment_1_3/test/test_data3.json
Normal file
@@ -0,0 +1 @@
|
||||
[[[2.0000000000000004, 2.0000000000000004, [-2, -2], [2, 2], 0.5, 1.0, 0], [2.0000000000000004, 0.0, [-2, -2], [2, 2], 0.5, 0.5, 1]], [[[-2, -2], [2, 2], [1, 1], 2.1, 0.5, 0.5, 0.2625]], [[[[-2.0], [-2.0]], [[-4.0], [-4.0]], [[1.0, 0.0], [0.0, 1.0]], [[1.5, 0.5], [0.5, 1.5]]]], [[[[-2.0], [-2.0]], [[-4.0], [-4.0]], [[1.0, 0.0], [0.0, 1.0]], [[0.75, -0.25], [-0.25, 0.75]]]]]
|