diff --git a/poisson/continuous/shapes.py b/poisson/continuous/shapes.py index 1a322fa9ee0bd5daa04d6250c6766cbc31667cd4..d1420ef2e8422af2407dde6cab82573d6717cba7 100644 --- a/poisson/continuous/shapes.py +++ b/poisson/continuous/shapes.py @@ -344,7 +344,31 @@ class Shape(ABC): return General(func=new_func) + def translate(self, vect): + vect = np.asarray(vect) + func = self.geometry + def newfunc(x): + return func(translate(x, -vect)) + self.geometry = newfunc + def rotate(self, angle, axis=None, center='self'): + '''3D not tested !!!''' + func = self.geometry + try: + angle = -angle + except TypeError: + angle = -np.asarray(angle) + + def newfunc(x): + return func(rotate(x, angle, axis=axis, center=center)) + self.geometry = newfunc + + def reflect(self, axis): + func = self.geometry + def newfunc(x): + return func(reflect(x, axis)) + self.geometry = newfunc +(), class PiecewiseFunction(object): ''' When called apllies self.evaluate_coordinates to a vector of @@ -461,13 +485,12 @@ class General(Shape): def geometry(self, x): return self.func(x) - class Rectangle(General): ''' Define a rectangle ''' - def __init__(self, length, corner=None): + def __init__(self, length, corner=None, center=None): ''' Class that defines a rectangular shape. When called and a numpy array is given as parameter it returns a boolean @@ -482,6 +505,11 @@ class Rectangle(General): the lengths of the sides if Ly (Lz) is None, Ly (Lz) will be equal to Lx (usefull to quickly draw cubes) + center = (x0, y0, z0) : numbers + overwrites the corner argument + if center=None the corner will be used + the lower left corner of the (if z0 si None will be 2d) + Returns: -------- func: function @@ -491,9 +519,9 @@ class Rectangle(General): super().__init__() self.length=length self.corner=corner + self.center=center self.prepare_param() - def prepare_param(self): if isinstance(self.length, (int, float)): @@ -508,6 +536,9 @@ class Rectangle(General): self.corner = np.asanyarray(self.corner) + if self.center is not None: + self.center = np.asarray(self.center) + assert len(self.length) == len(self.corner), ( 'The corner does not have the same' + ' size as length / or a corner has' @@ -520,37 +551,25 @@ class Rectangle(General): It returns True if the point is within the shape defined by the function or False otherwise ''' + x = np.asarray(x) + if len(x.shape) == 1: + ndim, npt = x.shape[0], -1 + x = x[None, :] + else: + npt, ndim = x.shape + assert (ndim == 2 or ndim == 3) - x = np.asanyarray(x) - if len(x.shape) > 1: - - if len(self.length) == 3: - return ((self.corner[0] <= x[:, 0]) - * (x[:, 0] <= self.corner[0] + self.length[0]) - * (self.corner[1] <= x[:, 1]) - * (x[:, 1] <= self.corner[1] + self.length[1]) - * (self.corner[2] <= x[:, 2]) - * (x[:, 2] <= self.corner[2] + self.length[2])) - else: - return ((self.corner[0] <= x[:, 0]) - * (x[:, 0] <= self.corner[0] + self.length[0]) - * (self.corner[1] <= x[:, 1]) - * (x[:, 1] <= self.corner[1] + self.length[1])) - + if self.center is None: + isin = np.all(x <= self.corner + self.length, axis=1) \ + * np.all(x >= self.corner, axis=1) else: - if len(self.length) == 3: - return ((self.corner[0] <= x[0] - <= self.corner[0] + self.length[0]) - and (self.corner[1] <= x[1] - <= self.corner[1] + self.length[1]) - and (self.corner[2] <= x[2] - <= self.corner[2] + self.length[2])) - else: - return ((self.corner[0] <= x[0] - <= self.corner[0] + self.length[0]) - and (self.corner[1] <= x[1] - <= self.corner[1] + self.length[1])) + isin = np.all(x <= self.center + self.length/2, axis=1) \ + * np.all(x >= self.center - self.length/2, axis=1) + if npt is -1: + return isin[0] + else: + return isin class Ellipsoid(General): ''' @@ -560,7 +579,7 @@ class Ellipsoid(General): def __init__(self, radius, center=None): ''' Class that defines a ellipsoid shape. - When called and a numpy array is given as parameter it returns a + When called and a numpy array is given as parameter it retu(),rns a boolean numpy array of the same length of the latter. Returns true if inside an 2d of 3d ellipsoid @@ -694,20 +713,10 @@ class InHull(Shape): ''' if self.points_coordinates is None: self.points_coordinates = self.generate_points() - vect_parent = vect - def fun(): - vect = np.asarray(vect_parent) - if len(self.points_coordinates.shape) < 2: - # translate a point - return self.points_coordinates + np.asarray(vect) - - elif len(self.points_coordinates.shape) == 2: - return (np.repeat([vect], len(self.points_coordinates), axis=0) - + self.points_coordinates) - - self.points_coordinates = fun() - self.points_operations.append(fun) + self.points_coordinates = translate(self.points_coordinates, vect) + self.points_operations.append( + lambda : translate(self.points_coordinates, vect)) def rotate(self, angle, axis=None, center='self'): '''Rotate a point or a list of points in 2 or 3d around a center @@ -720,7 +729,7 @@ class InHull(Shape): angle of rotation. if 3d, tx is either the angle around the x axis or the angle to rotate around the specified axis - ty: number + ty: numbernp.repeat([rot_mat], len(x), axis=0) if axis is None, the angle of the rotation around y will be ignored if axis is not None tz: number (opt, defaults to None) @@ -739,49 +748,229 @@ class InHull(Shape): if self.points_coordinates is None: self.points_coordinates = self.generate_points() - if not isinstance(angle, (list, tuple)): - angle = (angle,) - angle = np.asarray(angle) - - center_parent = center - - def fun(): - - if len(self.points_coordinates.shape) == 2: - npt, ndim = self.points_coordinates.shape - else: raise ValueError - - if ndim is 2: - rot_mat = np.array([[np.cos(angle[0]), -np.sin(angle[0])], - [np.sin(angle[0]), np.cos(angle[0])]]) - elif ndim is 3: - rot_mat = _rotation_matrix(angle, axis) - else: raise ValueError - - if center_parent is None: - center = np.zeros(ndim) - elif center_parent is 'self': - center = np.mean(self.points_coordinates, axis=0) - elif isinstance(center_parent, (tuple, list, np.ndarray)): - center = np.asarray(center_parent) - else: raise ValueError - - center_mat = np.repeat([center], - len(self.points_coordinates), axis=0) - ret = (np.einsum( - 'kij, kj -> ki', - np.repeat([rot_mat], len(self.points_coordinates), axis=0), - self.points_coordinates - center_mat) - + center_mat) - - if npt is 1: - return ret[0] - else: - return ret + self.points_coordinates = rotate(self.points_coordinates, angle, + axis=axis, center=center) + self.points_operations.append( + lambda : rotate(self.points_coordinates, angle, + axis=axis, center=center)) + + def reflect(self, axis): + '''returns reflections of point(s) x wrt to axis + + Parameters: + ----------- + axis: array like + 2 (3) points that generate the line (plan) + wrt which to reflect in 2d (3d) + ''' + if self.points_coordinates is None: + self.points_coordinates = self.generate_points() + + self.points_coordinates = reflect(self.points_coordinates, axis) + self.points_operations.append(lambda : reflect(self.points_coordinates, + axis)) +##################### + +def box(length, corner=None, center=None): + ''' + Returns points of a 2d of 3d box + + Parameters: + ----------- + length = (Lx, Ly, Lz): numbers + the lengths of the sides + if Ly (Lz) is None, Ly (Lz) will be equal to Lx + (usefull to quickly draw cubes) + + corner = (x0, y0, z0) : numbers + the lower left corner of the (if z0 si None will be 2d) + + center = (x0, y0, z0) : numbers + overwrites the corner argument + if center=None the corner will be used + the lower left corner of the (if z0 si None will be 2d) + + Returns: + -------- + points: np.ndarray + the points defining the box + ''' + if isinstance(length, (int, float)): + length = (length, length) - self.points_coordinates = fun() - self.points_operations.append(fun) + length = np.asarray(length) + ndim = len(length) + if corner is None: + corner = np.zeros(ndim) + elif isinstance(corner, int): + corner = corner * np.ones(ndim) + + corner = np.asarray(corner) + + if center is not None: + center = np.asarray(center) + + assert len(length) == len(corner), ( + 'The corner does not have the same' + + ' size as length / or a corner has' + + ' been given and length is an int') + + points = np.array([[0, 0], [0, length[1]], length[:2], [length[0], 0]]) + if ndim == 3: + points = np.vstack((np.c_[points, np.zeros(4)], + np.c_[points, np.ones(4) * length[2]])) + + if center is None: + points = points + corner + else: + points = points - length / 2 + center + + return points + +def translate(x, vect): + ''' + Translate a point or a list of points by a vector vect + + Parameters: + ----------- + x: tuple or liste of tuple + the points to translate + vect: list, tuple or numpy array + the translation vector + ''' + x = np.asarray(x) + vect = np.asarray(vect) + + if len(x.shape) < 2: + # translate a point + return x + np.asarray(vect) + + elif len(x.shape) == 2: + return np.repeat([vect], len(x), axis=0) + x + +def rotate(x, angle, axis=None, center='self'): + '''Rotate a point or a list of points in 2 or 3d around a center + of an angle or a combination of angles or around an axis + + Parameters: + ----------- + angle: (tx, ty, tz) or number + tx: number + angle of rotation. + if 3d, tx is either the angle around the x axis or + the angle to rotate around the specified axis + ty: number + if axis is None, the angle of the rotation around y + will be ignored if axis is not None + tz: number (opt, defaults to None) + if axis is None, the angle of the rotation around z + will be ignored if axis is not None + axis: arraylike (opt, defaults to None) + if axis is None, rotation around main axes + if not None, the axis of rotation + center: None, arraylike or 'self'(opt, defaults to None) + the center of the rotation + if None, will be the origin according to the dimension + if self, will be the average of the points + (center of regular polygon for 'inplace' rotation) + ''' + x = np.asarray(x) + center_parent = center + if not isinstance(angle, (list, tuple)): + angle = (angle,) + angle = np.asarray(angle) + + if len(x.shape) == 2: + npt, ndim = x.shape + elif len(x.shape) == 1: + npt, ndim = -1, x.shape[0] + x = x[None, :] + else: raise ValueError + + if ndim is 2: + rot_mat = np.array([[np.cos(angle[0]), -np.sin(angle[0])], + [np.sin(angle[0]), np.cos(angle[0])]]) + elif ndim is 3: + rot_mat = _rotation_matrix(angle, axis) + else: raise ValueError + + if center_parent is None: + center = np.zeros(ndim) + elif center_parent is 'self': + center = np.mean(x, axis=0) + elif isinstance(center_parent, (tuple, list, np.ndarray)): + center = np.asarray(center_parent) + else: raise ValueError + + center_mat = np.repeat([center], + len(x), axis=0) + + ret = (np.einsum( + 'kij, kj -> ki', + np.repeat([rot_mat], len(x), axis=0), x - center_mat) + + center_mat) + + if npt is -1: + return ret[0] + else: + return ret + +def reflect(x, axis): + '''returns reflections of point(s) x wrt to axis + + Parameters: + ----------- + x: array like + point or list of points in 2 or 3d + axis: array like + 2 (3) points that generate the line (plan) + wrt which to reflect in 2d (3d) + + Returns: + -------- + ret: array like (shape of x) + the reflected point(s) + ''' + x = np.asarray(x) + if len(x.shape) == 1: + ndim, npt = x.shape[0], -1 + x = x[None, :] + else: + npt, ndim = x.shape + assert (ndim == 2 or ndim == 3) + + assert isinstance(axis, (list, np.ndarray)) + axis = np.asarray(axis) + assert axis.shape[1] == ndim + A = axis[0] + Amat = np.repeat([A], len(x), axis=0) + + if ndim == 2: + u = (axis[1:] - A)[0] + v = np.array([u[1], -u[0]]) + # u, v is othogonal basis + mirror = np.array([[1, 0],[0, -1]]) + Mat = np.array([u, v]) + + elif ndim == 3: + u, C = axis[1:] - A + w = np.cross(u, C) + v = np.cross(w, u) + # u, v, w is othogonal basis + mirror = np.array([[1, 0, 0],[0, 1, 0], [0 ,0 ,-1]]) + Mat = np.array([u, v, w]) + + Matinv = np.linalg.inv(Mat) + transf = Mat.T @ mirror @ Matinv.T + ret = np.einsum('kij, kj -> ki', np.repeat([transf], len(x), axis=0), + (x - Amat)) + Amat + if npt is -1: + return ret[0] + else: + return ret + +################# class Delaunay(InHull): ''' @@ -833,7 +1022,7 @@ class RegularPolygon(InHull): Parameters: ----------- - n: integer (opt, defaults to 3) + npoints: integer (opt, defaults to 3) the number of sides (points) of the regular polygon center: 2d tuple (opt, defaults to [0, 0]) the center of the regular polygon @@ -845,20 +1034,17 @@ class RegularPolygon(InHull): flat: bool (opt, defaults to True) it True, the polygon is rotated to have his lowest edge parallel to x axis - - ''' - super().__init__() self.radius = radius self.npoints = npoints self.center = center self.clockwise = clockwise self.flat = flat + super().__init__() def generate_points(self): ''' - Returns a numpy array containing the coordinates of the points defining the shape. ''' @@ -918,14 +1104,13 @@ class ExtrudedRegularPolygon(InHull): ''' - - super().__init__() self.radius = radius self.npoints = npoints self.center = center self.clockwise = clockwise self.flat = flat self.Lz = Lz + super().__init__() def generate_points(self): ''' @@ -961,11 +1146,11 @@ class ExtrudedRegularPolygon(InHull): ########################################################## # test plots - def __test_plot_ellipse(): ellipse = Ellipsoid(radius=2) - mm = continuous.grid._2d_mesh(*continuous.grid._points_from_2dbounding_box([-3, 3, -3, 3], - 0.1)) + mm = continuous.grid._2d_mesh( + *continuous.grid._points_from_2dbounding_box([-3, 3, -3, 3], 0.1)) + x, y = list(zip(*mm)) xx, yy = list(zip(*continuous.grid._apply_stencil(mm, ellipse))) plt.scatter(x, y, label='bbox') @@ -994,6 +1179,26 @@ def __test_plot_rectangle(): plt.scatter(x, y, c=mesh.point_label) plt.show() +def __test_plot_rectangle_center(): + rect1 = Rectangle(length=4, center=[2, 2]) + rect2 = Rectangle(length=2, center=[2, 2]) + rect3 = Rectangle(length=1, center=[2, 2]) + + bbox1 = [0, 4, 0, 4] + bbox2 = [1, 3, 1, 3] + + mesh1 = (bbox1, 0.15, rect1, 0) + mesh2 = (bbox2, 0.1, rect2, 1) + hole1 = (rect3) + points = ([[2, 2]], 2) + + mesh = GridBuilder(meshs=[mesh1, mesh2], holes=[hole1], + points=[points]) + + x, y = list(zip(*mesh.points)) + plt.scatter(x, y, c=mesh.point_label) + plt.show() + def __test_plot_ellipse_circ(): @@ -1062,6 +1267,34 @@ def __test_plot_rotation_translation(): plt.axes().set_aspect('equal', 'datalim') plt.show() +def __test_reflection(): + '''test if two reflexions is equal to identity''' + np.random.seed(112) + + ndim = 2 + axis = np.random.rand(ndim, ndim) + pt = np.random.rand(150, ndim) + assert(np.allclose(pt, reflect(reflect(pt, axis), axis))) + + ndim = 3 + axis = np.random.rand(ndim, ndim) + pt = np.random.rand(150, ndim) + assert(np.allclose(pt, reflect(reflect(pt, axis), axis))) + + print('test reflexion passed') + +def __test_plot_reflection(): + ndim = 2 + axis = np.random.rand(ndim, ndim) - 0.5 + pt = np.random.rand(10, ndim) + newpt = reflect(pt, axis) + + plt.figure(figsize=(12,12)) + plt.plot(*pt.T, marker='o') + plt.plot(*axis.T, lw=4) + plt.plot(*newpt.T, marker='^') + plt.axes().set_aspect('equal') + plt.show() #test_plot_ellipse() #test_plot_rectangle()