def nonlinear_optimization( X, cells, tol, max_num_steps, verbosity=1, step_filename_format=None ): """Optimal Delaunay Triangulation smoothing. This method minimizes the energy E = int_Omega |u_l(x) - u(x)| rho(x) dx where u(x) = ||x||^2, u_l is its piecewise linear nodal interpolation and rho is the density. Since u(x) is convex, u_l >= u everywhere and u_l(x) = sum_i phi_i(x) u(x_i) where phi_i is the hat function at x_i. With rho(x)=1, this gives E = int_Omega sum_i phi_i(x) u(x_i) - u(x) = 1/(d+1) sum_i ||x_i||^2 |omega_i| - int_Omega ||x||^2 where d is the spatial dimension and omega_i is the star of x_i (the set of all simplices containing x_i). """ import scipy.optimize # TODO remove this assertion and test # flat mesh assert X.shape[1] == 2 mesh = MeshTri(X, cells, flat_cell_correction=None) if step_filename_format: mesh.save( step_filename_format.format(0), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) if verbosity > 0: print("Before:") extra_cols = ["energy: {:.5e}".format(energy(mesh))] print_stats(mesh, extra_cols=extra_cols) def f(x): mesh.update_interior_node_coordinates(x.reshape(-1, 2)) return energy(mesh, uniform_density=True) # TODO put f and jac together def jac(x): mesh.update_interior_node_coordinates(x.reshape(-1, 2)) grad = numpy.zeros(mesh.node_coords.shape) cc = mesh.cell_circumcenters for mcn in mesh.cells["nodes"].T: fastfunc.add.at( grad, mcn, ((mesh.node_coords[mcn] - cc).T * mesh.cell_volumes).T ) gdim = 2 grad *= 2 / (gdim + 1) return grad[mesh.is_interior_node, :2].flatten() def flip_delaunay(x): flip_delaunay.step += 1 # Flip the edges mesh.update_interior_node_coordinates(x.reshape(-1, 2)) mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(flip_delaunay.step), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) if verbosity > 1: print("\nStep {}:".format(flip_delaunay.step)) print_stats(mesh, extra_cols=["energy: {}".format(f(x))]) # mesh.show() # exit(1) return flip_delaunay.step = 0 x0 = X[mesh.is_interior_node, :2].flatten() out = scipy.optimize.minimize( f, x0, jac=jac, method="CG", # method='newton-cg', tol=tol, callback=flip_delaunay, options={"maxiter": max_num_steps}, ) # Don't assert out.success; max_num_steps may be reached, that's fine. # One last edge flip mesh.update_interior_node_coordinates(out.x.reshape(-1, 2)) mesh.flip_until_delaunay() if verbosity > 0: print("\nFinal ({} steps):".format(out.nit)) extra_cols = ["energy: {:.5e}".format(energy(mesh))] print_stats(mesh, extra_cols=extra_cols) print() return mesh.node_coords, mesh.cells["nodes"]
def runner( get_new_interior_points, X, cells, tol, max_num_steps, verbosity=1, step_filename_format=None, uniform_density=False, ): if X.shape[1] == 3: # create flat mesh assert numpy.all(abs(X[:, 2]) < 1.0e-15) X = X[:, :2] mesh = MeshTri(X, cells, flat_cell_correction=None) mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(0), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) if verbosity > 0: print("Before:") print_stats(mesh) k = 0 while True: k += 1 new_interior_points = get_new_interior_points(mesh) original_orient = mesh.signed_tri_areas > 0.0 original_coords = mesh.node_coords[mesh.is_interior_node] # Step unless the orientation of any cell changes. alpha = 1.0 while True: xnew = (1 - alpha) * original_coords + alpha * new_interior_points mesh.update_interior_node_coordinates(xnew) new_orient = mesh.signed_tri_areas > 0.0 if numpy.all(original_orient == new_orient): break alpha /= 2 mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(k), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) # Abort the loop if the update is small diff = mesh.node_coords[mesh.is_interior_node] - original_coords if numpy.all(numpy.einsum("ij,ij->i", diff, diff) < tol**2): break if k >= max_num_steps: break if verbosity > 1: print("\nstep {}:".format(k)) print_stats(mesh) if verbosity > 0: print("\nFinal ({} steps):".format(k)) print_stats(mesh) print() return mesh.node_coords, mesh.cells["nodes"]
def nonlinear_optimization_uniform( X, cells, tol, max_num_steps, verbose=False, step_filename_format=None, callback=None, ): """Optimal Delaunay Tesselation smoothing. This method minimizes the energy E = int_Omega |u_l(x) - u(x)| rho(x) dx where u(x) = ||x||^2, u_l is its piecewise linear nodal interpolation and rho is the density. Since u(x) is convex, u_l >= u everywhere and u_l(x) = sum_i phi_i(x) u(x_i) where phi_i is the hat function at x_i. With rho(x)=1, this gives E = int_Omega sum_i phi_i(x) u(x_i) - u(x) = 1/(d+1) sum_i ||x_i||^2 |omega_i| - int_Omega ||x||^2 where d is the spatial dimension and omega_i is the star of x_i (the set of all simplices containing x_i). """ import scipy.optimize mesh = MeshTri(X, cells) if step_filename_format: mesh.save( step_filename_format.format(0), show_coedges=False, show_axes=False, cell_quality_coloring=("viridis", 0.0, 1.0, False), ) if verbose: print("Before:") extra_cols = ["energy: {:.5e}".format(energy(mesh))] print_stats(mesh, extra_cols=extra_cols) def f(x): mesh.set_points(x.reshape(-1, X.shape[1]), mesh.is_interior_point) return energy(mesh, uniform_density=True) # TODO put f and jac together def jac(x): mesh.set_points(x.reshape(-1, X.shape[1]), mesh.is_interior_point) grad = numpy.zeros(mesh.points.shape) n = grad.shape[0] cc = mesh.cell_circumcenters for mcn in mesh.cells["points"].T: vals = (mesh.points[mcn] - cc).T * mesh.cell_volumes # numpy.add.at(grad, mcn, vals) grad += numpy.array( [numpy.bincount(mcn, val, minlength=n) for val in vals]).T gdim = 2 grad *= 2 / (gdim + 1) return grad[mesh.is_interior_point].flatten() def flip_delaunay(x): flip_delaunay.step += 1 # Flip the edges mesh.set_points(x.reshape(-1, X.shape[1]), mesh.is_interior_point) mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(flip_delaunay.step), show_coedges=False, show_axes=False, cell_quality_coloring=("viridis", 0.0, 1.0, False), ) if callback: callback(flip_delaunay.step, mesh) # mesh.show() # exit(1) return flip_delaunay.step = 0 x0 = X[mesh.is_interior_point].flatten() if callback: callback(0, mesh) out = scipy.optimize.minimize( f, x0, jac=jac, # method="Nelder-Mead", # method="Powell", # method="CG", # method="Newton-CG", method="BFGS", # method="L-BFGS-B", # method="TNC", # method="COBYLA", # method="SLSQP", tol=tol, callback=flip_delaunay, options={"maxiter": max_num_steps}, ) # Don't assert out.success; max_num_steps may be reached, that's fine. # One last edge flip mesh.set_points(out.x.reshape(-1, X.shape[1]), mesh.is_interior_point) mesh.flip_until_delaunay() info = ( f"{out.nit} steps," + "Optimal Delaunay Tesselation (ODT), uniform density, BFGS variant") if verbose: print(f"\nFinal ({info})") extra_cols = ["energy: {:.5e}".format(energy(mesh))] print_stats(mesh, extra_cols=extra_cols) print() return mesh.points, mesh.cells["points"]
def lloyd( X, cells, tol, max_num_steps, fcc_type="boundary", verbosity=1, step_filename_format=None, ): # flat mesh if X.shape[1] == 3: assert numpy.all(numpy.abs(X[:, 2]) < 1.0e-15) X = X[:, :2] original_X = X.copy() # create mesh data structure mesh = MeshTri(X, cells, flat_cell_correction=fcc_type) mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(0), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) if verbosity > 0: print("\nBefore:") print_stats(mesh) k = 0 while True: k += 1 # move interior points into centroids new_points = mesh.control_volume_centroids[mesh.is_interior_node] original_orient = mesh.signed_tri_areas > 0.0 original_coords = mesh.node_coords[mesh.is_interior_node] # Step unless the orientation of any cell changes. alpha = 1.0 while True: xnew = (1 - alpha) * original_coords + alpha * new_points # Preserve boundary nodes original_X[mesh.is_interior_node] = xnew # A new mesh is created in every step. Ugh. We do that since meshplex # doesn't have update_node_coordinates with flat_cell_correction. mesh = MeshTri(original_X, mesh.cells["nodes"], flat_cell_correction=fcc_type) # mesh.update_node_coordinates(xnew) new_orient = mesh.signed_tri_areas > 0.0 if numpy.all(original_orient == new_orient): break alpha /= 2 mesh.flip_until_delaunay() if step_filename_format: mesh.save( step_filename_format.format(k), show_centroids=False, show_coedges=False, show_axes=False, nondelaunay_edge_color="k", ) # Abort the loop if the update is small diff = mesh.node_coords[mesh.is_interior_node] - original_coords if numpy.all(numpy.einsum("ij,ij->i", diff, diff) < tol**2): break if k >= max_num_steps: break if verbosity > 1: print("\nstep {}:".format(k)) print_stats(mesh) if verbosity > 0: print("\nFinal ({} steps):".format(k)) print_stats(mesh) print() return mesh.node_coords, mesh.cells["nodes"]