Source code for ddg.datastructures.indexedfaceset.ifs

"""Module 'ifs' defines the indexed face set

"""
import copy

import numpy as np

from ddg.datastructures.indexedfaceset import utils
from ddg.math import grids
from ddg.math.euclidean import (
    circle_fct,
    mobius_strip_parametrization,
    torus_parametrization,
)


[docs]class GeneralizedIndexedFaceSet: """ Implementation of a combinatorial indexed face set data structure with a list of faces and some utility functions. Parameters ---------- faces: list List of face tuples that will generate the indexed face set. """ def __init__(self, faces=[]): """ Construct an GeneralizedIndexedFaceSet with a list of faces Parameters ---------- faces: list of tuples List of face tuples that will generate the indexed face set. """ self._edge_list = set() self._face_boundaries = dict() self._edge_face_map = dict() self._face_list = [tuple(f) for f in faces] self._face_dict = {tuple(f): True for f in faces} self._oriented = False self._init_edge_face_map() self.general_vertex_attributes = dict() self.general_edge_attributes = dict() self.general_face_attributes = dict()
[docs] def get_vertex_set(self): """ Returns the set of vertices of an indexed face set """ vertex = set() for face in self.face_list(): for vert in face: vertex.add(vert) return vertex
def _return_cells(self, cell_type): """ Private utility method to return the corresponding cells and their corresponding instance attribute name from a given cell type Parameters ---------- cell_type : str The cell type to be returned Returns ------- cells : set() the cell of cells corresponding to the cell type attribute_string : str the name of the attribute corresponding to the cell type """ if cell_type == "verts": cells = self.get_vertex_set() attribute_string = "general_vertex_attributes" elif cell_type == "edges": cells = self.edge_set() attribute_string = "general_edge_attributes" elif cell_type == "faces": cells = self.face_list() attribute_string = "general_face_attributes" else: raise ValueError( "Cell type must be one the following: 'verts', 'edges' or 'faces'" ) return cells, attribute_string
[docs] def set_attribute(self, attr_name, cell_type, attribute, cell_subset=None): """ Method to add attributes to cells of an ifs. If the attribute already exists, it changes it. If only a subset is specified, it only updates the subset values. Parameters ---------- cell_type: str Type of cell to which the attribute must be added attr_name : str Name of the attribute attribute : Subscriptable object A subscriptable object of the attributes to be assigned to each vertex cell_subset: set or list depending on the cell type if the cells in question are not the whole cells of the ifs but a subset of it, they must be given as input in this parameter Returns ------- dict A nested dictionary of all attributes of vertices """ if not isinstance(attr_name, str): raise TypeError("add_attribute: The attribute name must be of type 'str'") all_cells, attribute_string = self._return_cells(cell_type) if cell_subset is None: cells = all_cells else: cells = cell_subset if isinstance(cells, (list, np.ndarray)) and len(cells) != len(attribute): raise ValueError( f"The sizes of the attribute list {attribute} is {len(attribute)} but" f" the size of cells list {cells} is {len(cells)}. Size mismatch" ) general_dict = getattr(self, attribute_string) specific_attr_dict = dict() if attr_name in general_dict.keys(): # and cell_subset is not None: specific_attr_dict = general_dict[attr_name] # Case: Single cell if cell_subset is not None and isinstance(cell_subset, (int, tuple, float)): if cell_subset not in all_cells: raise ValueError( f"The cell '{cell_subset}' could not be found in the self.__name__" ) specific_attr_dict[cells] = attribute else: for index, cell in enumerate(cells): specific_attr_dict[cell] = attribute[index] general_dict[attr_name] = specific_attr_dict return
[docs] def delete_attribute(self, attr_name, cell_type): """ Deletes the attribute given by its name Parameters ---------- attr_name : str Name of the attribute cell_type: str Type of cell to which the attribute must be added """ _, attribute_string = self._return_cells(cell_type) general_dict = getattr(self, attribute_string) if attr_name not in general_dict.keys(): raise ValueError(f"The name '{attr_name}' does not exist as an attribute") else: general_dict.pop(attr_name)
[docs] def get_attribute(self, attr_name, cell_type, cell): """ get cell attribute Parameters ---------- attr_name : str Name of the attribute. Represents keys for the attribute dictionaries cell_type : str Type of cell from which the attribute is to be retrieved cell : float or int for verts, tuples for edges and faces The cell to which the attribute is assigned. Type depends on the type of the inputs given to create the class. Returns ------- The attribute in the specified position. Type depends on the attribute type. """ cells, attribute_string = self._return_cells(cell_type) attribute = getattr(self, attribute_string) if cell not in cells: raise ValueError(f"The cell ''{cell}'' could not be found in the surface") return attribute[attr_name][cell]
def _valid_face(self, face): if face not in self._face_dict: raise ValueError( f"Face {face} is not a face of the GeneralizedIndexedFaceSet" ) def _generate_edge_set(self): self._edge_list = set() for face in self._face_list: boundary = self.face_boundary(face) for boundary_edge in boundary: boundary_edge = tuple(sorted(boundary_edge)) self._edge_list.add(boundary_edge) def _add_face_boundary_to_map(self, face): self._face_boundaries[face] = utils.face_boundary(face) def _init_edge_face_map(self): for face in self._face_list: for boundary_edge in self.face_boundary(face): self._add_to_edge_face_map(boundary_edge, face) def _add_to_edge_face_map(self, edge, face): if edge not in self._edge_face_map: self._edge_face_map[edge] = [face] else: # oriented face sets can only be called by manifold structures if self._oriented: raise ValueError( "The face " + str(face) + " uses an edge in the same orientation as a previous face of " + str(self._face_list) ) # non oriented face sets can be anything for now unless you ask for # is_manifold self._edge_face_map[edge].append(face) def _update_edge_face_map(self, face): for boundary_edge in utils.face_boundary(face): self._add_to_edge_face_map(boundary_edge, face)
[docs] def adjacent_faces_with_orientation(self, edge): """ Get a list of oriented faces adjacent to an given edge. The resulting faces are of the class OrientedFace with self.orientation = 1 if they contain the edge in the given orientation or self.orientation = -1 if they contain the edge in reversed orientation. Parameters ---------- edge : tuple Tuple of two integers. Returns ------- list List of instances of the class OrientedFace that are adjacent to the input edge. """ adjacent_faces = [] if edge in self._edge_face_map.keys(): adjacent_faces += [ OrientedFace(f, orientation=1) for f in self._edge_face_map[edge] ] reversed_edge = tuple(reversed(edge)) if reversed_edge in self._edge_face_map.keys(): adjacent_faces += [ OrientedFace(f, orientation=-1) for f in self._edge_face_map[reversed_edge] ] return adjacent_faces
[docs] def adjacent_faces(self, edge): """ Get a list of faces, i.e. tuples adjacent to an given edge. Parameters ---------- edge : tuple Tuple of two integers. Returns ------- list List of tuples that represent all adjacent faces despite of orientation. """ return [f.get_tuple() for f in self.adjacent_faces_with_orientation(edge)]
[docs] def opposite_face(self, face, edge): """ Get the other face adjacent to a given edge of a face. If the edge is adjacent to only one or more than two faces return None. Parameters ---------- edge : tuple Tuple of two integers. face : tuple The input face incuding the edge from which the other face will be determined. Returns ------- tuple The other face incident to the given edge. None If edge is adjacent to only one or more than two faces. """ return self.opposite_face_with_orientation(face, edge).get_tuple()
[docs] def opposite_face_with_orientation(self, face, edge): """ Get the other oriented face adjacent to a given edge of a face. If the edge is adjacent to only one or more than two faces return None. Parameters ---------- edge : tuple Tuple of two integers. face : tuple The input face incuding the edge from which the other face will be determined. Returns ------- OrientedFace The other face incident to the given edge. NoneFace If edge is adjacent to only one or more than two faces. """ self._valid_face(face) if edge not in self.face_boundary(face): raise ValueError("Edge is not an edge of the face") adjacent_faces = [ f for f in self.adjacent_faces_with_orientation(edge) if f.get_tuple() != face ] if len(adjacent_faces) != 1: return NoneFace() return adjacent_faces[0]
[docs] def neighboring_faces(self, face): """ Get all faces that are adjacent to a given a face. If an edge has none or more than one other neighbouring faces, None will be returned. Parameters ---------- face : tuple The input face whose neighbouring faces will be determined. Returns ------- list | List of neighbouring faces in cyclic order. | Includes | tuple | If there exist exactly one neighbouring face to an edge. | None | If edge is adjacent to only one or more than two faces. """ return [f.get_tuple() for f in self.neighboring_faces_with_orientation(face)]
[docs] def neighboring_faces_with_orientation(self, face): """ Get all oriented faces that are adjacent to a given a face. If an edge has none or more than one other neighbouring faces, None will be returned. Parameters ---------- face : tuple The input face whose neighbouring faces will be determined. Returns ------- list | List of neighbouring faces in cyclic order. | Includes | OrientedFace | If there exist exactly one neighbouring face to an edge. | NoneFace | If edge is adjacent to only one or more than two faces. """ if face not in self._face_dict: raise ValueError("Face is not a face of the GeneralizedIndexedFaceSet") neighboring_faces = [ self.opposite_face_with_orientation(face, e) for e in self.face_boundary(face) ] return neighboring_faces
[docs] def face_list(self): """ Get all faces of the indexed face set. Returns ------- list List of tuples representing the faces of the indexed face set. """ return self._face_list
[docs] def number_of_faces(self): """ Get the number of faces of the indexed face set. Returns ------- int Number of faces of the indexed face set. """ return len(self._face_list)
[docs] def edge_set(self): """ Get all edges of the indexed face set. Returns ------- set Unordered set of tuples of two integers representing all edges. """ if not self._edge_list: self._generate_edge_set() return copy.deepcopy(self._edge_list)
[docs] def face_boundary(self, face): """ Returns a set of edges bounding the face. The edges are tuples orded by the orientation of the face. Parameters ---------- face : tuple The face to be investigated. Returns ------- tuple tuple with ordered edge tuples Examples -------- >>> from ddg.datastructures.indexedfaceset.ifs import GeneralizedIndexedFaceSet >>> faceSet = GeneralizedIndexedFaceSet([(1, 2, 4), (2, 3, 4)]) >>> faceSet.face_boundary((1, 2, 4)) ((1, 2), (2, 4), (4, 1)) """ face_tuple = face if face_tuple not in self._face_boundaries: self._add_face_boundary_to_map(face) return self._face_boundaries[face_tuple]
[docs] def add_face(self, face): """ Adds face to the indexed face set. Simply adds the face without paying attention to the maifold property. Parameters ---------- face : tuple The face to be investigated. """ self._face_list.append(face) self._face_dict[face] = True self._update_edge_face_map(face)
[docs]class OrientedIndexedFaceSet(GeneralizedIndexedFaceSet): def __init__(self, face_list=[]): super(OrientedIndexedFaceSet, self).__init__(face_list) self._oriented = True
[docs] def adjacent_faces(self, edge): """ Get a list of faces adjacent to an edge given as a tuple of integers. The faces are ordered such that they contain the given edge in the given orientation. """ adjacent_faces = [] if edge in self._edge_face_map: adjacent_faces += self._edge_face_map[edge] return adjacent_faces
[docs] def add_face(self, face): if self.number_of_faces() == 0 or self._is_addable_face(face): super(OrientedIndexedFaceSet, self).add_face(face) else: raise ValueError( "Cannot add given face " + str(face) + " to oriented IFS with faces " + str(self._face_list) )
def _is_addable_face(self, face): for edge in self.face_boundary(face): if edge in self._edge_face_map: return False return True
[docs]class OrientedFace: def __init__(self, index_tuple, orientation=1): self._tuple = index_tuple self._orientation = orientation self._oriented_tuple = ( tuple(self._tuple) if (self._orientation == 1) else tuple(reversed(self._tuple)) )
[docs] def get_orientation(self): return self._orientation
[docs] def get_oriented_tuple(self): return self._oriented_tuple
[docs] def get_tuple(self): return self._tuple
[docs] def set_orientation(self, new_orientation): if new_orientation != self._orientation: self._orientation = new_orientation if self._orientation == 1: self._oriented_tuple = tuple(self._tuple) else: self._oriented_tuple = tuple(reversed(self._tuple))
[docs]class IndexedFaceSet(GeneralizedIndexedFaceSet): """ An indexed face set whose vertices must be integers of indices 0 to n-1 only. The attributes of cells for this class are also restricted to numpy arrays only. """ def __init__(self, faces=[]): super(IndexedFaceSet, self).__init__(faces) if any( [ not isinstance(vertex, (int, np.integer)) for vertex in self.get_vertex_set() ] ): raise ValueError( "The vertices of an IndexedFaceSet must be given as integers from 0 to" " n-1 only" ) if not self._check_enumeration(): raise ValueError( "The vertices of an IndexedFaceSet must be given as integers from 0 to" " n-1 only" ) self.verts = np.array(list(self.get_vertex_set())) edge_list = [tuple(e) for e in self.edge_set()] self.edges = np.array(edge_list) self.faces = np.array(self.face_list(), dtype=object) self.face_dict = self._create_face_dict() self.vertex_attributes = dict() self.edge_attributes = dict() self.face_attributes = dict() def _check_enumeration(self): number_of_vertices = len(self.get_vertex_set()) return self.get_vertex_set() == set(range(number_of_vertices)) def _create_face_dict(self): """ Creates a dictionary of the faces with the number of edges of each face as keys and the vertex list as value """ face_dict = dict() for element in self.face_list(): value = np.array(element) key = str(len(element)) if key in face_dict: face_dict[key] = np.vstack([face_dict[key], value]) else: face_dict[key] = value return face_dict def _return_cells(self, cell_type): """ Method to return the corresponding cells and their corresponding instance attribute name from a given cell type Parameters ---------- cell_type : str The cell type to be returned Returns ------- cells : set() the cell of cells corresponding to the cell type attribute_string : str the name of the attribute corresponding to the cell type """ if cell_type == "verts": cells = self.verts attribute_string = "vertex_attributes" elif cell_type == "edges": cells = self.edges attribute_string = "edge_attributes" elif cell_type == "faces": cells = self.faces attribute_string = "face_attributes" else: raise ValueError( "Cell type must be one the following: 'verts', 'edges' or 'faces'" ) return cells, attribute_string
[docs] def cell_index(self, cell_type, cell): """ Method to get the index of the given cell. Vertices are already indices. Mostly used to get the cell of edges and faces Parameters ---------- cell_type : str type of cells to be indiced. "verts", "edges" or "faces" are the proper values. cell : int for verts, tuples for edges and faces the cell to be indiced. Returns ------- The index of the cell """ cells = getattr(self, cell_type) for index, cell_iterator in enumerate(cells): if cell_iterator == cell: return index raise ValueError(f"The given cell '{cell}' was not found in the list of cells")
[docs] def get_attribute(self, attr_name, cell_type, cell): """ 'Get' method corresponding the attribute structure of the subclass to override the same method in the parent class Parameters ---------- cell_type : str Type of cell from which the attribute are to be recalled cell_type : str Type of cell from which the attribute is to be retrieved cell : int for verts, int or tuples for edges and faces The cell or its index. For vertices, these two are the same. For edges and faces these are different. Both form are accepted. Returns ------- numpy.ndarray The attribute in the specified position. """ if cell_type == "verts": if not isinstance(cell, int): raise ValueError( "For vertices the input cell ('{cell}') must be integers" ) index = cell else: if isinstance(cell, int): index = cell elif isinstance(cell, tuple): index = self.cell_index(cell_type, cell) else: raise ValueError( f"The given cell '{cell}' for edges and faces must be either" " integer index or the cell as tuple" ) cells, attribute_string = self._return_cells(cell_type) attr_dict = getattr(self, attribute_string) if attr_name not in attr_dict.keys(): raise ValueError( f"The attribute name {attr_name} does not exist in the attribute" " dictionary" ) return attr_dict[attr_name][index]
[docs] def set_attribute(self, attr_name, cell_type, attribute, dtype=float): """ Method to create a dictionary of attributes for a given cell type. Parameters ---------- attr_name : str Name of the attribute to get in string attribute : np.ndarray An array of the attributes to be set Returns ------- Attribute dictionary """ if isinstance(attribute, (list, int)): attribute = np.array(attribute, dtype=dtype) elif attribute is None: raise ValueError("No attribute is given") _, attribute_string = self._return_cells(cell_type) attr_dict = getattr(self, attribute_string) if len(attribute) != len(getattr(self, cell_type)): raise ValueError( f"The given attribute {attr_name} must have length" f" '{len(getattr(self, cell_type))}' but it has '{len(attribute)}'" ) attr_dict[attr_name] = attribute
[docs] def edge_vertex_list(self, vertex_index): """ Method to find the edges which share a specific vertex. Parameters ---------- vertex_index : int The index of the vertex to be found (0,..., n-1) Returns ------- numpy.ndarray 2D array of edges as tuples """ idx = np.any(self.edges == vertex_index, axis=1) return np.arange(self.edges.shape[0])[idx]
[docs] def face_vertex_list(self, vertex_index): """ Method to find the faces which share a specific vertex Parameters ---------- vertex_index : int The index of the vertex to be found (0,..., n-1) Returns ------- numpy.ndarray returns indices of faces """ return np.array( [i for i, face in enumerate(self.faces) if vertex_index in face] )
[docs] def face_vertex_dict(self, vertex_index): """ Method to create a dictionary which has as values the arrays of faces with which they share a specific vertex and as keys the number of edges of the faces Parameters ---------- vertex_index : int The index of the vertex to be found (0,..., n-1) Returns ------- dict Keys : the number of edges of faces Values : thoses faces with the key number of edges which share the input vertex """ face_vertex_dict = dict() face_vertex_list = self.face_vertex_list(vertex_index) for index, element in enumerate(self.faces[face_vertex_list]): key = str(len(element)) if key in face_vertex_dict: face_vertex_dict[key] = np.append( face_vertex_dict[key], np.array(face_vertex_list[index]) ) else: face_vertex_dict[key] = np.array(face_vertex_list[index]) return face_vertex_dict
[docs] def face_edge_list(self, edge_index): """ Method to find the faces which share a specific edge Parameters ---------- edge_index : int The index of the edge Returns ------- numpy.ndarray For non-uniform meshes the return value is a 1D array of faces as tuples For uniform meshes the return value is a 2D array """ return np.array( [ i for i, face in enumerate(self.faces) if ( self.edges[edge_index, 0] in face and self.edges[edge_index, 1] in face ) ] )
[docs] def is_boundary_vertex(self, vertex_index): """ checks if a vertex is a boundary vertex or not Parameters ---------- vertex_index : 1D list or array the index of the vertex in question Returns ------- True if the the vertex is a boundary vertex, otherwise False """ edge_list = self.edge_vertex_list(vertex_index) face_list = self.face_vertex_list(vertex_index) if len(edge_list) > len(face_list): return True else: return False
@property def boundary_vertices(self): """ Method to create a set of boundary vertices of the ifs """ boundary_verts = set() for v in self.verts: if self.is_boundary_vertex(v): boundary_verts.add(v) return boundary_verts
[docs]class NgonalIndexedFaceSet(IndexedFaceSet): """ An indexed face set whose faces are of constant valency of k. """ def __init__(self, faces=np.array([])): """Creates an IFS with constant face valency Parameters ---------- faces : np.array the list of faces of the surface In case only one face exists, it should also be passed as a 2D array """ if not isinstance(faces, np.ndarray): raise ValueError( "NgonalIndexedFaceSet only accepts numpy arrays as face array." ) if faces.ndim != 2: raise ValueError( "The face array must be a 2D numpy array. Even only one face exists, it" " must be passed as a 2D array" ) super(NgonalIndexedFaceSet, self).__init__(faces) self.faces = self.faces.astype(faces.dtype)
[docs] def face_vertex_array(self, vertex_index): """ Method to find the faces which share a specific vertex. Overrides the method in parent class. Parameters ---------- vertex_index : int The index of the vertex to be found (0,..., n-1) Returns ------- numpy.ndarray returns indices of faces """ idx = np.any(self.edges == vertex_index, axis=1) return np.arange(self.edges.shape[0])[idx]
[docs] def face_vertex_incidence(self): """ Method to create the valencies. Returns ------- list the list of valencies in the same order of vertex index """ return [self.face_vertex_array(i) for i in range(len(self.verts))]
[docs] def face_edge_incidence(self): """ Method to create the valency for edge-face. Returns ------- list the list of face-edge-valency in the same order of edge index """ return [self.face_edge_list(i) for i in range(len(self.edges))]
[docs]class Grid(NgonalIndexedFaceSet): r"""A grid of either :math:`\mathbb{Z}^2` or :math:`\mathbb{Z}^3` combinatorics. Parameters ---------- shape : tuple or list The shape of the grid. The shape consists of the number of vertices in each axis of the grid. periodicity : tuple (default=None) Periodicity of the grid. For more information, see "Attributes". default_coordinates : boolean (default=True) Indicator for assigining default coordinates to the grid. Attributes ---------- m : int Number of vertices in the first coordinate direction n : int Number of vertices in the second coordinate direction l : int (default=None) Number of vertices in the third coordinate direction *only* in 3D case periodicity : tuple (default = None) The periodicity of the grid. This is only meaningful in case of 2D grid. For 3D shape the attribute remains default value which is None. In the following cases, m and n would be the resolution of vertices on each axis AFTER the gluing. E.g. a cylinder of shape shape glued along the first axis would have 4 vertices around the circle. +-------------+---------------+-----------------------------------------------+ | Periodicity | Topology | Gluing Axis | +=============+===============+===============================================+ | (0, 0) | Disk | None | +-------------+---------------+-----------------------------------------------+ | (1, 0) | Cylinder | along the first axis | +-------------+---------------+-----------------------------------------------+ | (0, 1) | Cylinder | along the second axis | +-------------+---------------+-----------------------------------------------+ | (1, 1) | Torus | along both axes | +-------------+---------------+-----------------------------------------------+ | (-1, 0) | Moebius Band | along the first axis in reversed orientation | +-------------+---------------+-----------------------------------------------+ | (0, -1) | Moebius Band | along the second axis in reversed orientation | +-------------+---------------+-----------------------------------------------+ """ def __init__(self, shape, periodicity=None, default_coordinates=False): self.periodicity = periodicity self.l = None if 0 in shape or 1 in shape: raise ValueError( f"{shape} is not a valid shape for a grid. " "None of the entries can be 0 or 1." ) if len(shape) == 2: m, n = shape[0], shape[1] self.m = m self.n = n faces, coordinates = grids.quad_grid(shape) if periodicity != (0, 0): periodic_faces = self._faces_from_gluing_boundaries() faces = np.vstack((faces, periodic_faces)) super(Grid, self).__init__(faces) if default_coordinates: self.set_attribute("co", "verts", self._default_coordinates()) elif len(shape) == 3: if self.periodicity: raise NotImplementedError( "Periodicity is not implemented for a 3D grid." ) m, n, l = shape[0], shape[1], shape[2] self.m = m self.n = n self.l = l faces, coordinates = grids.quad_grid(shape) super(Grid, self).__init__(faces) if default_coordinates: self.set_attribute("co", "verts", coordinates) else: raise NotImplementedError( "Grid object only implemented for dimensions 2 and 3." ) def _raw_grid_boundary_lines(self, axis=0): """The boundary lines of the grid (without gluing sides) along some axis.""" array = np.arange(self.m * self.n).reshape(self.n, self.m) return np.column_stack((array.take(0, axis), array.take(-1, axis))) def _faces_from_gluing_boundaries(self): """Creates the boundary lines and edges of the grid with respect to the orientation of the lines given by periodicity. Then creates the faces by gluing the boundary lines. Returns ------- np.ndarray the boundary lines each stored in the columns """ horizontal_boundary_side = self._raw_grid_boundary_lines(axis=0) vertical_boundary_side = self._raw_grid_boundary_lines(axis=1) edges = np.vstack( (horizontal_boundary_side[0, :], horizontal_boundary_side[-1, :]) ) faces = [] if self.periodicity == (0, 0): pass # Cylinder x-axis elif self.periodicity == (0, 1): faces = grids._quad_faces2D(horizontal_boundary_side) # Cylinder y-axis elif self.periodicity == (1, 0): faces = grids._quad_faces2D(vertical_boundary_side) # Torus elif self.periodicity == (1, 1): faces = grids._quad_faces2D(horizontal_boundary_side) faces = np.vstack((faces, grids._quad_faces2D(vertical_boundary_side))) face_from_edges = np.concatenate((edges[0, :], edges[-1, ::-1])) faces = np.vstack((faces, face_from_edges)) # Mobius x-axis elif self.periodicity == (0, -1): faces = grids._quad_faces2D( np.column_stack( (horizontal_boundary_side[::-1, 0], horizontal_boundary_side[:, 1]) ) ) # reverse orientation of one boundary side # Mobius y-axis elif self.periodicity == (-1, 0): faces = grids._quad_faces2D( np.column_stack( (vertical_boundary_side[::-1, 0], vertical_boundary_side[:, 1]) ) ) # reverse orientation of one boundary side elif self.periodicity in [(-1, -1), (1, -1), (-1, 1)]: raise NotImplementedError( f"Periodicity {self.periodicity} is not implemented." ) else: raise ValueError( f"{self.periodicity} is not a valid value for periodicity." ) return faces def _default_coordinates(self): """Creates default coordinate for visualization of the grid for each specific periodicity. """ coordinates = [] if self.periodicity == (0, 0): _, coordinates2D = grids.quad_grid((self.m, self.n)) coordinates = np.column_stack( (coordinates2D, np.zeros((coordinates2D.shape[0]))) ) # Cylinder x-axis elif self.periodicity == (1, 0): t = np.linspace(0, 2 * np.pi, self.m, endpoint=False) coordinates = [ circle_fct(t[i], (0, j, 0), 1, [0, 1, 0]) for j in range(self.n) for i in range(len(t)) ] # Cylinder y-axis elif self.periodicity == (0, 1): t = np.linspace(0, 2 * np.pi, self.n, endpoint=False) coordinates = [ circle_fct(t[i], (j, 0, 0), 1, [1, 0, 0]) for i in range(len(t)) for j in range(self.m) ] # Torus elif self.periodicity == (1, 1): t1 = np.linspace(0, 2 * np.pi, min(self.m, self.n), endpoint=False) t2 = np.linspace(0, 2 * np.pi, max(self.m, self.n), endpoint=False) coordinates = [ torus_parametrization(t2_, t1_, 2, 0.5) for t1_ in t1 for t2_ in t2 ] # Torus with maximum vertex numbers on the large circle # Mobius x-axis elif self.periodicity == (-1, 0): t1 = np.linspace(0, 2 * np.pi, self.m, endpoint=False) t2 = np.linspace(-1, 1, self.n, endpoint=False) coordinates = [ mobius_strip_parametrization(t1_, t2_, 1) for t2_ in t2 for t1_ in t1 ] # Mobius y-axis elif self.periodicity == (0, -1): t1 = np.linspace(0, 2 * np.pi, self.n, endpoint=False) t2 = np.linspace(-1, 1, self.m, endpoint=False) coordinates = [ mobius_strip_parametrization(t1_, t2_, 1) for t1_ in t1 for t2_ in t2 ] else: raise ValueError("Wrong value of periodicity for a 2D grid.") return np.array(coordinates)
[docs]class NoneFace(OrientedFace): def __init__(self): super().__init__(index_tuple=()) self._tuple = None self._oriented_tuple = None self._orientation = 0