def plotting_3d(self, plotting): # plotting 3d surfaces def function(x): return math.sin(x[0]*4) * math.cos(x[1]*4) poles = bs.make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) #quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) quad = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) z_surf = bs.Z_Surface(quad, surface_func) full_surf = z_surf.make_full_surface() z_surf.transform(np.array([[1., 0, 0], [0, 1, 0]]), np.array([2.0, 0]) ) plotting.plot_surface_3d(z_surf) plotting.plot_surface_3d(full_surf) plotting.show()
def compute_approximation(self, **kwargs): """ Compute approximation of the point set (given to constructor). Approximation parameters can be passed in through kwargs or set in the object before the call. :param quad: [(x1,y1), .. , (x4,y4)] Set vertices of different quad for the point set. :param nuv: (nu, nv) Set number of intervals of the resulting B-spline, in U and V direction :param regularization_wight: Default 0.001, is scaled by the max singular value of B. :return: B-Spline surface """ self.quad = kwargs.get("quad", self.quad) self.nuv = kwargs.get("nuv", self.nuv) self.regularization_weight = kwargs.get("regularization_weight", self.regularization_weight) logging.info('Transforming points (n={}) ...'.format(self._n_points)) start_time = time.time() if self.quad is None: self.compute_default_quad() if self.nuv is None: self.compute_default_nuv() # TODO: better logic, since this has to be recomputed only if quad is changed. self._compute_uv_points() logging.info("Using {} x {} B-spline approximation.".format( self.nuv[0], self.nuv[1])) self._u_basis = bs.SplineBasis.make_equidistant(2, self.nuv[0]) self._v_basis = bs.SplineBasis.make_equidistant(2, self.nuv[1]) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) # Approximation itself logging.info( 'Creating explicitly system of normal equations B^TBz=B^Tb ...') start_time = time.time() btb_mat, btwb_vec, point_loc = self._build_system_of_normal_equations() end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Creating A matrix ...') start_time = time.time() a_mat = self._build_sparse_reg_matrix() end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Computing A and B^TB svds approximation ...') start_time = time.time() bb_norm = scipy.sparse.linalg.eigsh(btb_mat, k=1, ncv=10, tol=1e-2, which='LM', maxiter=300, return_eigenvectors=False) a_norm = scipy.sparse.linalg.eigsh(a_mat, k=1, ncv=10, tol=1e-2, which='LM', maxiter=300, return_eigenvectors=False) a_min = scipy.sparse.linalg.eigsh(a_mat, k=1, ncv=10, tol=1e-2, which='SM', maxiter=300, return_eigenvectors=False) #c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat c_mat = btb_mat + self.regularization_weight * (bb_norm[0] * a_min[0] / a_norm[0]) * a_mat end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Solving for Z coordinates ...') start_time = time.time() z_vec = scipy.sparse.linalg.spsolve(c_mat, btwb_vec) assert not np.isnan( np.sum(z_vec)), "Singular matrix for approximation." end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Computing error ...') start_time = time.time() diff = self._compute_errors(point_loc, z_vec) self.error = max_diff = np.max(diff) logging.info("Approximation error (max norm): {}".format(max_diff)) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) # Construct Z-Surface poles_z = z_vec.reshape(self._v_basis.size, self._u_basis.size).T #poles_z *= self.grid_surf.z_scale #poles_z += self.grid_surf.z_shift surface_z = bs.Surface((self._u_basis, self._v_basis), poles_z[:, :, None]) self.surface = bs.Z_Surface(self.quad[0:3], surface_z) return self.surface
def make_z_surf(self, func, quad): poles = bs.make_function_grid(func, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface((u_basis, v_basis), poles[:, :, [2]]) return bs.Z_Surface(quad, surface_func)
def compute_adaptive_approximation(self, **kwargs): """ Approximate the point set (given to the constructor) by a B-spline surface. The knot vectors in u and V direction are adaptively refined until a prescribed tolerance is reached. In order to prevent overfitting we regularize by penalizing the gradients of the constructed surface. The regularization parameter is automatically tuned to balance the approximation error |Z - surf(b)| and the regularization |grad surf(b)|_L2. Alternatively the cross-validation method can be applied. Compute approximation of the point set . Approximation parameters can be passed in through kwargs or set in the object before the call. :param quad: [(x1,y1), .. , (x4,y4)] Set vertices of different quad for the point set. :param nuv: (nu, nv) Set number of intervals of the resulting B-spline, in U and V direction :param max_iters: determines number refinement steps of the knot vectors , default (20) :param solver: 'spsolve' (default) use the sparse direct solver scipy.sparse.linalg.spsolve , 'cg' use conjugate gradient solver scipy.sparse.linalg.cg :param adapt_type: Adaptivity type to use. Denoting 'z(x,y)' the surface value and (x_i, y_i, z_i) given points: 'absolute' (default) refine patches where |z(x_i, y_i) - z_i|_inf > max_diff 'std_dev' If the total L2 error is greater then 'std_dev', refine 'max_part' fraction of the rows/columns with highest L2 error contibution. :param max_diff: infinite norm tolerance for the 'absolute' refinement :param max_part: fraction of the raws/columns to be refined (1.0 is maximum) for the 'std_dev' refinement :param std_dev: Standard deviance of the Z components of the input points, or equivalently L2 norm tolerance. Used in 'std_dev' refinement method. achieved :param input_data_reduction: Determine regularization parameter using the cross-validation. Fit only to the random fraction 'input_data_reducion' and use the remaining data for the cross-validation. :return: B-Spline surface Two refinement algorithms: absolute norm based adaptivity maximum norm is evaluated on every patch, if it holds: patch maximum norm > max_diff (param) then both of the knot vectors ("u" AND "v") are refined in corresponding intervals finished: number of iteration achieved max_iters (param) OR maximum norm on every patch < max_diff (param) standard deviation based adaptivity Euclidean norms of the errors are computed with respect u,v knot intervals even iteration: max_part (param) ratio of the "u" knot intervals involving the largest norm are refined odd iteration: max_part (param) ratio of the "v" knot intervals involving the largest norm are refined finished: number of iteration achieved max_iters (param) OR standard deviation < std_dev (param) """ self.quad = kwargs.get("quad", self.quad) self.nuv = kwargs.get("nuv", self.nuv) self.solver = kwargs.get("solver", "spsolve") # cg self.max_iters = kwargs.get("max_iters", 20) # self.adapt_type = kwargs.get("adapt_type", "absolute") # "std_dev" self.max_diff = kwargs.get("max_diff", 10.0) # for absolute based adaptivity self.max_part = kwargs.get( "max_part", 0.2) # for standard deviation based adaptivity self.std_dev = kwargs.get( "std_dev", 1.0) # for standard deviation based adaptivity self.input_data_reduction = kwargs.get("input_data_reduction", 1.0) logging.info('Transforming points (n={}) ...'.format(self._n_points)) start_time = time.time() if self.quad is None: self.compute_default_quad() if self.nuv is None: self.compute_default_nuv() # TODO: better logic, since this has to be recomputed only if quad is changed. self._compute_uv_points() ### #self._w_quad_points = np.ones(len(self._w_quad_points)) if self.input_data_reduction != 1.0: n = len(self._uv_quad_points) lsp = np.linspace(0, n - 1, n, dtype=int) red_lsp = np.random.choice( lsp, int(np.ceil(n * self.input_data_reduction))) compl_lsp = np.setxor1d(lsp, red_lsp) self._w_quad_points = np.ones( n) # set all the weights equal to 1!!! self._w_quad_points[compl_lsp] = np.zeros(len(compl_lsp)) ### logging.info("Using {} x {} B-spline approximation.".format( self.nuv[0], self.nuv[1])) self._u_basis = bs.SplineBasis.make_equidistant(2, self.nuv[0]) self._v_basis = bs.SplineBasis.make_equidistant(2, self.nuv[1]) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) n_course = 1 iters = -1 while n_course != 0: ### Adaptivity loop iters += 1 if iters > 0: if (iters % 2) == 0: if np.sum(ref_vec_u) > 0: u_knot_new = self._refine_knots( self._u_basis.knots, ref_vec_u) self._u_basis = bs.SplineBasis.make_from_knots( 2, u_knot_new) else: if np.sum(ref_vec_v) > 0: v_knot_new = self._refine_knots( self._v_basis.knots, ref_vec_v) self._v_basis = bs.SplineBasis.make_from_knots( 2, v_knot_new) # Approximation itself logging.info( 'Creating explicitly system of normal equations B^TBz=B^Tb ...' ) start_time = time.time() self._locate_points() btb_mat, btwb_vec, avg_vec = self._build_system_of_normal_equations( ) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Creating A matrix ...') start_time = time.time() a_mat = self._build_sparse_reg_matrix() end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Computing A and B^TB svds approximation ...') start_time = time.time() if iters == 0: bb_norm = scipy.sparse.linalg.eigsh(btb_mat, k=1, ncv=10, tol=1e-2, which='LM', maxiter=300, return_eigenvectors=False) a_norm = scipy.sparse.linalg.eigsh(a_mat, k=1, ncv=10, tol=1e-2, which='LM', maxiter=300, return_eigenvectors=False) reg_coef = bb_norm[0] / a_norm[0] c_mat = btb_mat + reg_coef * a_mat end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Solving for Z coordinates ...') start_time = time.time() z_vec = self._solve_system(c_mat, btwb_vec, avg_vec) assert not np.isnan( np.sum(z_vec)), "Singular matrix for approximation." end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Computing error ...') start_time = time.time() diff, diff_mat_max, err_mat_eucl2, std_dev = self._compute_errors( z_vec) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) if self.input_data_reduction != 1.0: diff_red = diff * self._w_quad_points # make sense only fow w_i in set(0,1) ref_vec_u, ref_vec_v = self._refine_patches( diff_mat_max, err_mat_eucl2, std_dev, self.adapt_type) # Regularization coefficient print("L2 diff: ", diff.dot(diff)) print("A2 diff: ", z_vec.dot(a_mat.dot(z_vec))) #reg_coef = diff.dot(diff) / z_vec.dot(a_mat.dot(z_vec)) # shoud be replaced by a more stable formula print("reg_coef =", reg_coef) print("iteration =", iters) print("\nL2_diff =", std_dev) print("\nmax_diff =", np.max(diff)) print("area =", self._u_basis.n_intervals, 'x', self._v_basis.n_intervals, "(n_patches =", self._u_basis.n_intervals * self._v_basis.n_intervals, ")") if self.input_data_reduction != 1.0: logging.info("Efficient points ratio: {}".format( self.input_data_reduction)) logging.info( "Ratio of the errors (efficient/complete): {}".format( np.linalg.norm(diff_red) / np.linalg.norm(diff))) n_course = sum(ref_vec_u) + sum(ref_vec_v) if np.logical_or(n_course == 0, iters == self.max_iters): break self.error = max_diff = np.max(diff) logging.info("Approximation error (max norm): {}".format(max_diff)) logging.info("Standard deviation: {}".format(std_dev)) end_time = time.time() logging.info('Computed in: {} s'.format(end_time - start_time)) # Construct Z-Surface poles_z = z_vec.reshape(self._v_basis.size, self._u_basis.size).T #poles_z *= self.grid_surf.z_scale #poles_z += self.grid_surf.z_shift surface_z = bs.Surface((self._u_basis, self._v_basis), poles_z[:, :, None]) self.surface = bs.Z_Surface(self.quad[0:3], surface_z) return self.surface