def torsion(self, t, above=True): """ Evaluate the torsion for a 3D curve at specified point(s). The torsion is defined as .. math:: \\frac{(\\boldsymbol{v}\\times \\boldsymbol{a})\\cdot (d\\boldsymbol{a}/dt)}{|\\boldsymbol{v}\\times \\boldsymbol{a}|^2} :param t: Parametric coordinates in which to evaluate :type t: float or [float] :param bool above: Evaluation in the limit from above :return: Derivative array :rtype: numpy.array """ if self.dimension == 2: # no torsion for 2D curves t = ensure_listlike(t) return np.zeros(len(t)) elif self.dimension == 3: # only allow 3D curves pass else: raise ValueError('dimension must be 2 or 3') # compute derivative v = self.derivative(t, d=1, above=above) a = self.derivative(t, d=2, above=above) da = self.derivative(t, d=3, above=above) w = np.cross(v, a) if len(v.shape) == 1: # single evaluation point magnitude = np.linalg.norm(w) nominator = np.dot(w, a) else: # multiple evaluation points magnitude = np.apply_along_axis(np.linalg.norm, -1, w) nominator = np.array([np.dot(w1, da1) for (w1, da1) in zip(w, da)]) return nominator / np.power(magnitude, 2)
def torsion(self, t, above=True): """ Evaluate the torsion for a 3D curve at specified point(s). The torsion is defined as .. math:: \\frac{(\\boldsymbol{v}\\times \\boldsymbol{a})\\cdot (d\\boldsymbol{a}/dt)}{|\\boldsymbol{v}\\times \\boldsymbol{a}|^2} :param t: Parametric coordinates in which to evaluate :type t: float or [float] :param bool above: Evaluation in the limit from above :return: Derivative array :rtype: numpy.array """ if self.dimension == 2: # no torsion for 2D curves t = ensure_listlike(t) return np.zeros(len(t)) elif self.dimension == 3: # only allow 3D curves pass else: raise ValueError('dimension must be 2 or 3') # compute derivative v = self.derivative(t, d=1, above=above) a = self.derivative(t, d=2, above=above) da = self.derivative(t, d=3, above=above) w = np.cross(v,a) if len(v.shape) == 1: # single evaluation point magnitude = np.linalg.norm(w) nominator = np.dot(w, a) else: # multiple evaluation points magnitude = np.apply_along_axis(np.linalg.norm, -1, w) nominator = np.array([np.dot(w1,da1) for (w1,da1) in zip(w, da)]) return nominator / np.power(magnitude, 2)
def evaluate(self, t, d=0, from_right=True, sparse=False): """ Evaluate all basis functions in a given set of points. :param t: The parametric coordinate(s) in which to evaluate :type t: float or [float] :param int d: Number of derivatives to compute :param bool from_right: True if evaluation should be done in the limit from above :param bool sparse: True if computed matrix should be returned as sparse :return: A matrix *N[i,j]* of all basis functions *j* evaluated in all points *i* :rtype: numpy.array """ # for single-value input, wrap it into a list so it don't crash on the loop below t = ensure_listlike(t) t = np.array(t, dtype=np.float64) basis_eval.snap(self.knots, t, state.knot_tolerance) if self.order <= d: # requesting more derivatives than polymoial degree: return all zeros return np.zeros((len(t), self.num_functions())) (data, size) = basis_eval.evaluate(self.knots, self.order, t, self.periodic, state.knot_tolerance, d, from_right) N = csr_matrix(data, size) if not sparse: N = N.toarray() return N
def subdivide(objs, n): """Subdivide a list of objects by splitting them up along existing knot lines. The resulting partition will roughly the same number of elements on all pieces. By splitting along *n* lines, we generate *n* + 1 new blocks. The number of subdivisions can be a list of integers: one for each direction, or a single integer for uniform sudivision. :param objs: Objects to split :param n: Number of subdivisions to perform :type n: int or [int] :return: New objects :rtype: [:class:`splipy.SplineObject`] """ pardim = objs[0].pardim # 1 for curves, 2 for surfaces, 3 for volumes n = ensure_listlike(n, pardim) result = objs for d in range(pardim): # split all objects so far along direction d new_results = [] for obj in result: splitting_points = [ obj.knots(d)[i] for i in _splitvector(len(obj.knots(d)), n[d] + 1) ] new_results += obj.split(splitting_points[1:], d) # only keep the smallest pieces in our result list result = new_results return result
def subdivide(objs, n): """Subdivide a list of objects by splitting them up along existing knot lines. The resulting partition will roughly the same number of elements on all pieces. By splitting along *n* lines, we generate *n* + 1 new blocks. The number of subdivisions can be a list of integers: one for each direction, or a single integer for uniform sudivision. :param objs: Objects to split :param n: Number of subdivisions to perform :type n: int or [int] :return: New objects :rtype: [:class:`splipy.SplineObject`] """ pardim = objs[0].pardim # 1 for curves, 2 for surfaces, 3 for volumes n = ensure_listlike(n, pardim) result = objs for d in range(pardim): # split all objects so far along direction d new_results = [] for obj in result: splitting_points = [obj.knots(d)[i] for i in _splitvector(len(obj.knots(d)), n[d]+1)] new_results += obj.split(splitting_points[1:], d) # only keep the smallest pieces in our result list result = new_results return result
def rebuild(self, p, n): """ Creates an approximation to this volume by resampling it using uniform knot vectors of order *p* with *n* control points. :param (int) p: Tuple of polynomial discretization order in each direction :param (int) n: Tuple of number of control points in each direction :return: A new approximate volume :rtype: Volume """ p = ensure_listlike(p, dups=3) n = ensure_listlike(n, dups=3) old_basis = [self.bases[0], self.bases[1], self.bases[2]] basis = [] u = [] N = [] # establish uniform open knot vectors for i in range(3): knot = [0] * p[i] + list(range( 1, n[i] - p[i] + 1)) + [n[i] - p[i] + 1] * p[i] basis.append(BSplineBasis(p[i], knot)) # make these span the same parametric domain as the old ones basis[i].normalize() t0 = old_basis[i].start() t1 = old_basis[i].end() basis[i] *= (t1 - t0) basis[i] += t0 # fetch evaluation points and evaluate basis functions u.append(basis[i].greville()) N.append(basis[i].evaluate(u[i])) # find interpolation points as evaluation of existing volume x = self.evaluate(u[0], u[1], u[2]) # solve interpolation problem cp = np.tensordot(np.linalg.inv(N[2]), x, axes=(1, 2)) cp = np.tensordot(np.linalg.inv(N[1]), cp, axes=(1, 2)) cp = np.tensordot(np.linalg.inv(N[0]), cp, axes=(1, 2)) # re-order controlpoints so they match up with Volume constructor cp = cp.transpose((2, 1, 0, 3)) cp = cp.reshape(n[0] * n[1] * n[2], cp.shape[3]) # return new resampled curve return Volume(basis[0], basis[1], basis[2], cp)
def rebuild(self, p, n): """Creates an approximation to this volume by resampling it using uniform knot vectors of order *p* with *n* control points. :param int p: Polynomial discretization order :param int n: Number of control points :return: A new approximate volume :rtype: Volume """ p = ensure_listlike(p, dups=3) n = ensure_listlike(n, dups=3) old_basis = [self.bases[0], self.bases[1], self.bases[2]] basis = [] u = [] N = [] # establish uniform open knot vectors for i in range(3): knot = [0] * p[i] + list(range(1, n[i] - p[i] + 1)) + [n[i] - p[i] + 1] * p[i] basis.append(BSplineBasis(p[i], knot)) # make these span the same parametric domain as the old ones basis[i].normalize() t0 = old_basis[i].start() t1 = old_basis[i].end() basis[i] *= (t1 - t0) basis[i] += t0 # fetch evaluation points and evaluate basis functions u.append(basis[i].greville()) N.append(basis[i].evaluate(u[i])) # find interpolation points as evaluation of existing volume x = self.evaluate(u[0], u[1], u[2]) # solve interpolation problem cp = np.tensordot(np.linalg.inv(N[2]), x, axes=(1, 2)) cp = np.tensordot(np.linalg.inv(N[1]), cp, axes=(1, 2)) cp = np.tensordot(np.linalg.inv(N[0]), cp, axes=(1, 2)) # re-order controlpoints so they match up with Volume constructor cp = cp.transpose((2, 1, 0, 3)) cp = cp.reshape(n[0] * n[1] * n[2], cp.shape[3]) # return new resampled curve return Volume(basis[0], basis[1], basis[2], cp)
def derivative(self, t, d=1, above=True, tensor=True): """ Evaluate the derivative of the curve at the given parametric values. This function returns an *n* × *dim* array, where *n* is the number of evaluation points, and *dim* is the physical dimension of the curve. If there is only one evaluation point, a vector of length *dim* is returned instead. :param t: Parametric coordinates in which to evaluate :type t: float or [float] :param int d: Number of derivatives to compute :param bool above: Evaluation in the limit from above :param bool tensor: Not used in this method :return: Derivative array :rtype: numpy.array """ if not is_singleton(d): d = d[0] if not self.rational or d < 2 or d > 3: return super(Curve, self).derivative(t, d=d, above=above, tensor=tensor) t = ensure_listlike(t) result = np.zeros((len(t), self.dimension)) d2 = np.array(self.bases[0].evaluate(t, 2, above) @ self.controlpoints) d1 = np.array(self.bases[0].evaluate(t, 1, above) @ self.controlpoints) d0 = np.array(self.bases[0].evaluate(t) @ self.controlpoints) W = d0[:, -1] # W(t) W1 = d1[:, -1] # W'(t) W2 = d2[:, -1] # W''(t) if d == 2: for i in range(self.dimension): result[:, i] = (d2[:, i] * W * W - 2 * W1 * (d1[:, i] * W - d0[:, i] * W1) - d0[:, i] * W2 * W) / W / W / W if d == 3: d3 = np.array( self.bases[0].evaluate(t, 3, above) @ self.controlpoints) W3 = d3[:, -1] # W'''(t) W6 = W * W * W * W * W * W # W^6 for i in range(self.dimension): H = d1[:, i] * W - d0[:, i] * W1 H1 = d2[:, i] * W - d0[:, i] * W2 H2 = d3[:, i] * W + d2[:, i] * W1 - d1[:, i] * W2 - d0[:, i] * W3 G = H1 * W - 2 * H * W1 G1 = H2 * W - 2 * H * W2 - H1 * W1 result[:, i] = (G1 * W - 3 * G * W1) / W / W / W / W if result.shape[ 0] == 1: # in case of single value input t, return vector instead of matrix result = np.array(result[0, :]).reshape(self.dimension) return result
def write_surface(self, surface, n=None): # choose evaluation points as one of three cases: # 1. specified with input # 2. linear splines, only picks knots # 3. general splines choose 2*order-1 per knot span if n != None: n = ensure_listlike(n, 2) if n != None: u = np.linspace(surface.start(0), surface.end(0), n[0]) elif surface.order(0) == 2: u = surface.knots(0) else: knots = surface.knots(0) p = surface.order(0) u = [ np.linspace(k0, k1, 2 * p - 3, endpoint=False) for (k0, k1) in zip(knots[:-1], knots[1:]) ] u = [point for element in u for point in element] + knots u = np.sort(u) if n != None: v = np.linspace(surface.start(1), surface.end(1), n[1]) elif surface.order(1) == 2: v = surface.knots(1) else: knots = surface.knots(1) p = surface.order(1) v = [ np.linspace(k0, k1, 2 * p - 3, endpoint=False) for (k0, k1) in zip(knots[:-1], knots[1:]) ] v = [point for element in v for point in element] + knots v = np.sort(v) # perform evaluation and make sure that we have 3 components (in case of 2D geometries) x = surface(u, v) if x.shape[2] != 3: x.resize((x.shape[0], x.shape[1], 3)) # compute tiny quad pieces faces = [[x[i, j], x[i, j + 1], x[i + 1, j + 1], x[i + 1, j]] for i in range(x.shape[0] - 1) for j in range(x.shape[1] - 1)] self.writer.add_faces(faces)
def derivative(self, t, d=1, above=True, tensor=True): """ Evaluate the derivative of the curve at the given parametric values. This function returns an *n* × *dim* array, where *n* is the number of evaluation points, and *dim* is the physical dimension of the curve. If there is only one evaluation point, a vector of length *dim* is returned instead. :param t: Parametric coordinates in which to evaluate :type t: float or [float] :param int d: Number of derivatives to compute :param bool above: Evaluation in the limit from above :param bool tensor: Not used in this method :return: Derivative array :rtype: numpy.array """ if not self.rational or d != 2: return super(Curve, self).derivative(t, d=d, above=above, tensor=tensor) t = ensure_listlike(t) dN = self.bases[0].evaluate(t, d, above) result = np.array(dN * self.controlpoints) d2 = result d1 = np.array(self.bases[0].evaluate(t, 1, above) * self.controlpoints) d0 = np.array(self.bases[0].evaluate(t) * self.controlpoints) W = d0[:, -1] # W(t) W1 = d1[:, -1] # W'(t) W2 = d2[:, -1] # W''(t) for i in range(self.dimension): result[:, i] = (d2[:, i] * W * W - 2 * W1 * (d1[:, i] * W - d0[:, i] * W1) - d0[:, i] * W2 * W) / W / W / W result = np.delete(result, self.dimension, 1) # remove the weight column if result.shape[ 0] == 1: # in case of single value input t, return vector instead of matrix result = np.array(result[0, :]).reshape(self.dimension) return result
def derivative(self, t, d=1, above=True): """derivative(u, [d=1]) Evaluate the derivative of the curve at the given parametric values. This function returns an *n* × *dim* array, where *n* is the number of evaluation points, and *dim* is the physical dimension of the curve. If there is only one evaluation point, a vector of length *dim* is returned instead. :param u: Parametric coordinates in which to evaluate :type u: float or [float] :param int d: Number of derivatives to compute :param bool above: Evaluation in the limit from above :return: Derivative array :rtype: numpy.array """ if not self.rational or d != 2: return super(Curve, self).derivative(t, d=d, above=above) t = ensure_listlike(t) dN = self.bases[0].evaluate(t, d, above) result = np.array(dN * self.controlpoints) d2 = result d1 = np.array(self.bases[0].evaluate(t, 1, above) * self.controlpoints) d0 = np.array(self.bases[0].evaluate(t) * self.controlpoints) W = d0[:, -1] # W(t) W1 = d1[:, -1] # W'(t) W2 = d2[:, -1] # W''(t) for i in range(self.dimension): result[:, i] = (d2[:, i] * W * W - 2 * W1 * (d1[:, i] * W - d0[:, i] * W1) - d0[:, i] * W2 * W) / W / W / W result = np.delete(result, self.dimension, 1) # remove the weight column if result.shape[0] == 1: # in case of single value input t, return vector instead of matrix result = np.array(result[0, :]).reshape(self.dimension) return result
def write_surface(self, surface, n=None): # choose evaluation points as one of three cases: # 1. specified with input # 2. linear splines, only picks knots # 3. general splines choose 2*order-1 per knot span if n != None: n = ensure_listlike(n,2) if n != None: u = np.linspace(surface.start(0), surface.end(0), n[0]) elif surface.order(0) == 2: u = surface.knots(0) else: knots = surface.knots(0) p = surface.order(0) u = [np.linspace(k0,k1, 2*p-3, endpoint=False) for (k0,k1) in zip(knots[:-1], knots[1:])] u = [point for element in u for point in element] + knots u = np.sort(u) if n != None: v = np.linspace(surface.start(1), surface.end(1), n[1]) elif surface.order(1) == 2: v = surface.knots(1) else: knots = surface.knots(1) p = surface.order(1) v = [np.linspace(k0,k1, 2*p-3, endpoint=False) for (k0,k1) in zip(knots[:-1], knots[1:])] v = [point for element in v for point in element] + knots v = np.sort(v) # perform evaluation and make sure that we have 3 components (in case of 2D geometries) x = surface(u,v) if x.shape[2] != 3: x.resize((x.shape[0],x.shape[1],3)) # compute tiny quad pieces faces = [[x[i,j], x[i,j+1], x[i+1,j+1], x[i+1,j]] for i in range(x.shape[0]-1) for j in range(x.shape[1]-1)] self.writer.add_faces(faces)
def evaluate(self, *params): """evaluate(u, v, ...) Evaluate the object at given parametric values. This function returns an *n1* × *n2* × ... × *dim* array, where *ni* is the number of evaluation points in direction *i*, and *dim* is the physical dimension of the object. If there is only one evaluation point, a vector of length *dim* is returned instead. :param u,v,...: Parametric coordinates in which to evaluate :type u,v,...: float or [float] :return: Geometry coordinates :rtype: numpy.array """ squeeze = is_singleton(params[0]) params = [ensure_listlike(p) for p in params] self._validate_domain(*params) # Evaluate the derivatives of the corresponding bases at the corresponding points # and build the result array N = self.bases[0].evaluate(params[0], sparse=True) result = N * self.controlpoints # For rational objects, we divide out the weights, which are stored in the # last coordinate if self.rational: for i in range(self.dimension): result[..., i] /= result[..., -1] result = np.delete(result, self.dimension, -1) # Squeeze the singleton dimensions if we only have one point if squeeze: result = result.reshape(self.dimension) return result
def evaluate(self, *params): """ Evaluate the object at given parametric values. This function returns an *n1* × *n2* × ... × *dim* array, where *ni* is the number of evaluation points in direction *i*, and *dim* is the physical dimension of the object. If there is only one evaluation point, a vector of length *dim* is returned instead. :param u,v,...: Parametric coordinates in which to evaluate :type u,v,...: float or [float] :return: Geometry coordinates :rtype: numpy.array """ squeeze = is_singleton(params[0]) params = [ensure_listlike(p) for p in params] self._validate_domain(*params) # Evaluate the derivatives of the corresponding bases at the corresponding points # and build the result array N = self.bases[0].evaluate(params[0], sparse=True) result = N @ self.controlpoints # For rational objects, we divide out the weights, which are stored in the # last coordinate if self.rational: for i in range(self.dimension): result[..., i] /= result[..., -1] result = np.delete(result, self.dimension, -1) # Squeeze the singleton dimensions if we only have one point if squeeze: result = result.reshape(self.dimension) return result
def evaluate_old(self, t, d=0, from_right=True, sparse=False): """ Evaluate all basis functions in a given set of points. :param t: The parametric coordinate(s) in which to evaluate :type t: float or [float] :param int d: Number of derivatives to compute :param bool from_right: True if evaluation should be done in the limit from above :param bool sparse: True if computed matrix should be returned as sparse :return: A matrix *N[i,j]* of all basis functions *j* evaluated in all points *i* :rtype: numpy.array """ # for single-value input, wrap it into a list so it don't crash on the loop below t = ensure_listlike(t) self.snap(t) p = self.order # knot vector order n_all = len( self.knots) - p # number of basis functions (without periodicity) n = len(self.knots) - p - ( self.periodic + 1) # number of basis functions (with periodicity) m = len(t) data = np.zeros(m * p) indices = np.zeros(m * p, dtype='int32') indptr = np.array(range(0, m * p + 1, p), dtype='int32') if p <= d: # requesting more derivatives than polymoial degree: return all zeros return np.zeros((m, n)) if self.periodic >= 0: t = copy.deepcopy(t) # Wrap periodic evaluation into domain for i in range(len(t)): if t[i] < self.start() or t[i] > self.end(): t[i] = (t[i] - self.start()) % ( self.end() - self.start()) + self.start() for i in range(len(t)): right = from_right evalT = t[i] # Special-case the endpoint, so the user doesn't need to if abs(t[i] - self.end()) < state.knot_tolerance: right = False # Skip non-periodic evaluation points outside the domain if t[i] < self.start() or t[i] > self.end(): continue # mu = index of last non-zero basis function if right: mu = bisect_right(self.knots, evalT) else: mu = bisect_left(self.knots, evalT) mu = min(mu, n_all) M = np.zeros( p) # temp storage to keep all the function evaluations M[-1] = 1 # the last entry is a dummy-zero which is never used for q in range(1, p - d): for j in range(p - q - 1, p): k = mu - p + j # 'i'-index in global knot vector (ref Hughes book pg.21) if j != p - q - 1: M[j] = M[j] * float(evalT - self.knots[k]) / ( self.knots[k + q] - self.knots[k]) if j != p - 1: M[j] = M[j] + M[j + 1] * float( self.knots[k + q + 1] - evalT) / ( self.knots[k + q + 1] - self.knots[k + 1]) for q in range(p - d, p): for j in range(p - q - 1, p): k = mu - p + j # 'i'-index in global knot vector (ref Hughes book pg.21) if j != p - q - 1: M[j] = M[j] * float(q) / (self.knots[k + q] - self.knots[k]) if j != p - 1: M[j] = M[j] - M[j + 1] * float(q) / ( self.knots[k + q + 1] - self.knots[k + 1]) data[i * p:(i + 1) * p] = M indices[i * p:(i + 1) * p] = np.arange(mu - p, mu) % n N = csr_matrix((data, indices, indptr), (m, n)) if not sparse: N = N.toarray() return N
def texture(self, p, ngeom, ntexture, method='full', irange=[None,None], jrange=[None,None]): # Set the dimensions of geometry and texture map # ngeom = np.floor(self.n / (p-1)) # ntexture = np.floor(self.n * n) # ngeom = ngeom.astype(np.int32) # ntexture = ntexture.astype(np.int32) ngeom = ensure_listlike(ngeom, 3) ntexture = ensure_listlike(ntexture, 3) p = ensure_listlike(p, 3) # Create the geometry ngx, ngy, ngz = ngeom b1 = BSplineBasis(p[0], [0]*(p[0]-1) + [i/ngx for i in range(ngx+1)] + [1]*(p[0]-1)) b2 = BSplineBasis(p[1], [0]*(p[1]-1) + [i/ngy for i in range(ngy+1)] + [1]*(p[1]-1)) b3 = BSplineBasis(p[2], [0]*(p[2]-1) + [i/ngz for i in range(ngz+1)] + [1]*(p[2]-1)) l2_fit = surface_factory.least_square_fit vol = self.get_c0_mesh() i = slice(irange[0], irange[1], None) j = slice(jrange[0], jrange[1], None) # special case number of evaluation points for full domain if irange[1] == None: irange[1] = vol.shape[0] if jrange[1] == None: jrange[1] = vol.shape[1] if irange[0] == None: irange[0] = 0 if jrange[0] == None: jrange[0] = 0 nu = np.diff(irange) nv = np.diff(jrange) nw = vol.shape[2] u = np.linspace(0, 1, nu) v = np.linspace(0, 1, nv) w = np.linspace(0, 1, nw) crvs = [] crvs.append(curve_factory.polygon(vol[i ,jrange[0] , 0,:].squeeze())) crvs.append(curve_factory.polygon(vol[i ,jrange[0] ,-1,:].squeeze())) crvs.append(curve_factory.polygon(vol[i ,jrange[1]-1, 0,:].squeeze())) crvs.append(curve_factory.polygon(vol[i ,jrange[1]-1,-1,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[0] ,j , 0,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[0] ,j ,-1,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[1]-1,j , 0,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[1]-1,j ,-1,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[0] ,jrange[0] , :,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[0] ,jrange[1]-1, :,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[1]-1,jrange[0] , :,:].squeeze())) crvs.append(curve_factory.polygon(vol[irange[1]-1,jrange[1]-1, :,:].squeeze())) # with G2('curves.g2') as myfile: # myfile.write(crvs) # print('Written curve.g2') if method == 'full': bottom = l2_fit(vol[i, j, 0,:].squeeze(), [b1, b2], [u, v]) top = l2_fit(vol[i, j, -1,:].squeeze(), [b1, b2], [u, v]) left = l2_fit(vol[irange[0] ,j, :,:].squeeze(), [b2, b3], [v, w]) right = l2_fit(vol[irange[1]-1,j, :,:].squeeze(), [b2, b3], [v, w]) front = l2_fit(vol[i, jrange[0], :,:].squeeze(), [b1, b3], [u, w]) back = l2_fit(vol[i, jrange[1]-1,:,:].squeeze(), [b1, b3], [u, w]) volume = volume_factory.edge_surfaces([left, right, front, back, bottom, top]) elif method == 'z': bottom = l2_fit(vol[i,j, 0,:].squeeze(), [b1, b2], [u, v]) top = l2_fit(vol[i,j,-1,:].squeeze(), [b1, b2], [u, v]) volume = volume_factory.edge_surfaces([bottom, top]) volume.set_order(*p) volume.refine(ngz - 1, direction='w') volume.reverse(direction=2) # Point-to-cell mapping # TODO: Optimize more eps = 1e-2 u = [np.linspace(eps, 1-eps, n) for n in ntexture] points = volume(*u).reshape(-1, 3) cellids = np.zeros(points.shape[:-1], dtype=int) cell = None nx, ny, nz = self.n for ptid, point in enumerate(tqdm(points, desc='Inverse mapping')): i, j, k = cell = self.raw.cell_at(point) # , guess=cell) cellid = i*ny*nz + j*nz + k cellids[ptid] = cellid cellids = cellids.reshape(tuple(ntexture)) all_textures = {} for name in self.attribute: data = self.attribute[name][cellids] # TODO: This flattens the image if it happens to be 3D (or higher...) # do we need a way to communicate the structure back to the caller? # data = data.reshape(-1, data.shape[-1]) # TODO: This normalizes the image, # but we need a way to communicate the ranges back to the caller # a, b = min(data.flat), max(data.flat) # data = ((data - a) / (b - a) * 255).astype(np.uint8) all_textures[name] = data all_textures['cellids'] = cellids return volume, all_textures
def derivative(self, u, v, d=(1, 1), above=True, tensor=True): """ Evaluate the derivative of the surface at the given parametric values. This function returns an *n* × *m* x *dim* array, where *n* is the number of evaluation points in u, *m* is the number of evaluation points in v, and *dim* is the physical dimension of the curve. If there is only one evaluation point, a vector of length *dim* is returned instead. :param u: Parametric coordinate(s) in the first direction :type u: float or [float] :param v: Parametric coordinate(s) in the second direction :type v: float or [float] :param int d: Number of derivatives to compute in each direction :type d: [int] :param (bool) above: Evaluation in the limit from above :param tensor: Whether to evaluate on a tensor product grid :type tensor: bool :return: Derivative array *X[i,j,k]* of component *xk* evaluated at *(u[i], v[j])* :rtype: numpy.array """ squeeze = all(is_singleton(t) for t in [u, v]) derivs = ensure_listlike(d, self.pardim) if not self.rational or np.sum(derivs) < 2 or np.sum(derivs) > 3: return super(Surface, self).derivative(u, v, d=derivs, above=above, tensor=tensor) u = ensure_listlike(u) v = ensure_listlike(v) result = np.zeros((len(u), len(v), self.dimension)) # dNus = [self.bases[0].evaluate(u, d, above) for d in range(derivs[0]+1)] # dNvs = [self.bases[1].evaluate(v, d, above) for d in range(derivs[1]+1)] dNus = [ self.bases[0].evaluate(u, d, above) for d in range(np.sum(derivs) + 1) ] dNvs = [ self.bases[1].evaluate(v, d, above) for d in range(np.sum(derivs) + 1) ] d0ud0v = evaluate([dNus[0], dNvs[0]], self.controlpoints, tensor) d1ud0v = evaluate([dNus[1], dNvs[0]], self.controlpoints, tensor) d0ud1v = evaluate([dNus[0], dNvs[1]], self.controlpoints, tensor) d1ud1v = evaluate([dNus[1], dNvs[1]], self.controlpoints, tensor) d2ud0v = evaluate([dNus[2], dNvs[0]], self.controlpoints, tensor) d0ud2v = evaluate([dNus[0], dNvs[2]], self.controlpoints, tensor) W = d0ud0v[:, :, -1] dWdu = d1ud0v[:, :, -1] dWdv = d0ud1v[:, :, -1] d2Wduv = d1ud1v[:, :, -1] d2Wdu = d2ud0v[:, :, -1] d2Wdv = d0ud2v[:, :, -1] for i in range(self.dimension): H1 = d1ud0v[:, :, i] * W - d0ud0v[:, :, i] * dWdu H2 = d0ud1v[:, :, i] * W - d0ud0v[:, :, i] * dWdv dH1du = d2ud0v[:, :, i] * W - d0ud0v[:, :, i] * d2Wdu dH1dv = d1ud1v[:, :, i] * W + d1ud0v[:, :, i] * dWdv - d0ud1v[:, :, i] * dWdu - d0ud0v[:, :, i] * d2Wduv dH2du = d1ud1v[:, :, i] * W + d0ud1v[:, :, i] * dWdu - d1ud0v[:, :, i] * dWdv - d0ud0v[:, :, i] * d2Wduv dH2dv = d0ud2v[:, :, i] * W - d0ud0v[:, :, i] * d2Wdv G1 = dH1du * W - 2 * H1 * dWdu G2 = dH2dv * W - 2 * H2 * dWdv if derivs == (1, 0): result[:, :, i] = H1 / W / W elif derivs == (0, 1): result[:, :, i] = H2 / W / W elif derivs == (1, 1): result[:, :, i] = (dH1dv * W - 2 * H1 * dWdv) / W / W / W elif derivs == (2, 0): result[:, :, i] = G1 / W / W / W elif derivs == (0, 2): result[:, :, i] = G2 / W / W / W if np.sum(derivs) > 2: d2ud1v = evaluate([dNus[2], dNvs[1]], self.controlpoints, tensor) d1ud2v = evaluate([dNus[1], dNvs[2]], self.controlpoints, tensor) d3ud0v = evaluate([dNus[3], dNvs[0]], self.controlpoints, tensor) d0ud3v = evaluate([dNus[0], dNvs[3]], self.controlpoints, tensor) d3Wdu = d3ud0v[:, :, -1] d3Wdv = d0ud3v[:, :, -1] d3Wduuv = d2ud1v[:, :, -1] d3Wduvv = d1ud2v[:, :, -1] d2H1du = d3ud0v[:, :, i] * W + d2ud0v[:, :, i] * dWdu - d1ud0v[:, :, i] * d2Wdu - d0ud0v[:, :, i] * d3Wdu d2H1duv = d2ud1v[:, :, i] * W + d2ud0v[:, :, i] * dWdv - d0ud1v[:, :, i] * d2Wdu - d0ud0v[:, :, i] * d3Wduuv d2H2dv = d0ud3v[:, :, i] * W + d0ud2v[:, :, i] * dWdv - d0ud1v[:, :, i] * d2Wdv - d0ud0v[:, :, i] * d3Wdv d2H2duv = d1ud2v[:, :, i] * W + d0ud2v[:, :, i] * dWdu - d1ud0v[:, :, i] * d2Wdv - d0ud0v[:, :, i] * d3Wduvv dG1du = d2H1du * W + dH1du * dWdu - 2 * dH1du * dWdu - 2 * H1 * d2Wdu dG1dv = d2H1duv * W + dH1du * dWdv - 2 * dH1dv * dWdu - 2 * H1 * d2Wduv dG2du = d2H2duv * W + dH2dv * dWdu - 2 * dH2du * dWdv - 2 * H2 * d2Wduv dG2dv = d2H2dv * W + dH2dv * dWdv - 2 * dH2dv * dWdv - 2 * H2 * d2Wdv if derivs == (3, 0): result[:, :, i] = (dG1du * W - 3 * G1 * dWdu) / W / W / W / W elif derivs == (0, 3): result[:, :, i] = (dG2dv * W - 3 * G2 * dWdv) / W / W / W / W elif derivs == (2, 1): result[:, :, i] = (dG1dv * W - 3 * G1 * dWdv) / W / W / W / W elif derivs == (1, 2): result[:, :, i] = (dG2du * W - 3 * G2 * dWdu) / W / W / W / W # Squeeze the singleton dimensions if we only have one point if squeeze: result = result.reshape(self.dimension) return result
def evaluate(self, t, d=0, from_right=True, sparse=False): """evaluate(t, [d=0], [from_right=True]) Evaluate all basis functions in a given set of points. :param t: The parametric coordinate(s) in which to evaluate :type t: float or [float] :param int d: Number of derivatives to compute :param bool from_right: True if evaluation should be done in the limit from above :param bool sparse: True if computed matrix should be returned as sparse :return: A matrix *N[i,j]* of all basis functions *j* evaluated in all points *i* :rtype: numpy.array """ # for single-value input, wrap it into a list so it don't crash on the loop below t = ensure_listlike(t) p = self.order # knot vector order n_all = len(self.knots) - p # number of basis functions (without periodicity) n = len(self.knots) - p - (self.periodic+1) # number of basis functions (with periodicity) m = len(t) data = np.zeros(m*p) indices = np.zeros(m*p) indptr = range(0,m*p+1,p) if p <= d: # requesting more derivatives than polymoial degree: return all zeros return np.matrix(np.zeros((m,n))) if self.periodic >= 0: t = copy.deepcopy(t) # Wrap periodic evaluation into domain for i in range(len(t)): if t[i] < self.start() or t[i] > self.end(): t[i] = (t[i] - self.start()) % (self.end() - self.start()) + self.start() for i in range(len(t)): right = from_right evalT = t[i] # Special-case the endpoint, so the user doesn't need to if abs(t[i] - self.end()) < state.knot_tolerance: right = False # Skip non-periodic evaluation points outside the domain if t[i] < self.start() or t[i] > self.end(): continue # mu = index of last non-zero basis function if right: mu = bisect_right(self.knots, evalT) else: mu = bisect_left(self.knots, evalT) mu = min(mu, n_all) M = np.zeros(p) # temp storage to keep all the function evaluations M[-1] = 1 # the last entry is a dummy-zero which is never used for q in range(1, p-d): for j in range(p - q - 1, p): k = mu - p + j # 'i'-index in global knot vector (ref Hughes book pg.21) if j != p-q-1: M[j] = M[j] * float(evalT - self.knots[k]) / (self.knots[k + q] - self.knots[k]) if j != p-1: M[j] = M[j] + M[j + 1] * float(self.knots[k + q + 1] - evalT) / (self.knots[k + q + 1] - self.knots[k + 1]) for q in range(p-d, p): for j in range(p - q - 1, p): k = mu - p + j # 'i'-index in global knot vector (ref Hughes book pg.21) if j != p-q-1: M[j] = M[j] * float(q) / (self.knots[k + q] - self.knots[k]) if j != p-1: M[j] = M[j] - M[j + 1] * float(q) / (self.knots[k + q + 1] - self.knots[k + 1]) data[i*p:(i+1)*p] = M indices[i*p:(i+1)*p] = np.arange(mu-p, mu) % n N = csr_matrix((data, indices, indptr), (m,n)) if not sparse: N = N.todense() return N