Source code for sfepy.fem.region

import re
from copy import copy

import numpy as nm

from sfepy.base.base import assert_, Struct

_depends = re.compile('r\.([a-zA-Z_0-9.]+)').findall

[docs]def get_parents(selector): """ Given a region selector, return names of regions it is based on. """ parents = _depends(selector) return parents
[docs]def get_dependency_graph(region_defs): """ Return a dependency graph and a name-sort name mapping for given region definitions. """ graph = {} name_to_sort_name = {} for sort_name, rdef in region_defs.iteritems(): name, sel = rdef.name, rdef.select if name_to_sort_name.has_key(name): msg = 'region %s/%s already defined!' % (sort_name, name) raise ValueError(msg) name_to_sort_name[name] = sort_name if not graph.has_key(name): graph[name] = [0] for parent in get_parents(sel): graph[name].append(parent) return graph, name_to_sort_name
[docs]def sort_by_dependency(graph): out = [] n_nod = len(graph) idone = 0 idone0 = -1 while idone < n_nod: dep_removed = 0 for node, deps in graph.iteritems(): if (len(deps) == 1) and not deps[0]: out.append(node) deps[0] = 1 idone += 1 elif not deps[0]: for ii, dep in enumerate(deps[1:]): if graph[dep][0]: ir = deps.index(dep) deps.pop(ir) dep_removed += 1 if (idone <= idone0) and not dep_removed: raise ValueError, 'circular dependency' idone0 = idone return out
def _join(def1, op, def2): return '(' + def1 + ' ' + op + ' ' + def2 + ')' def _try_delete(obj, ig): try: del obj[ig] except KeyError: pass
[docs]class Region(Struct): """ Region defines a subset of a FE domain. Created: 31.10.2005 """ @staticmethod
[docs] def from_vertices(vertices, domain, name='region', igs=None, can_cells=False, surface_integral=False): """ Create a new region containing given vertices. Parameters ---------- vertices : array The array of vertices. domain : Domain instance The domain containing the vertices. name : str, optional The name of the region. igs : list, optional The allowed element groups. Other groups will be ignored, even though the region might have vertices in them - the same effect the 'forbid' flag has. can_cells : bool, optional If True, the region can have cells. surface_integral : bool, optional If True, then each region surface facet (edge in 2D, face in 3D) can be listed only in one group. Returns ------- obj : Region instance The new region. """ obj = Region(name, 'given vertices', domain, '') obj.set_vertices(vertices) if igs is not None: forbidden = nm.setdiff1d(obj.igs, igs) obj.delete_groups(forbidden) obj.switch_cells(can_cells) obj.complete_description(domain.ed, domain.fa, surface_integral=surface_integral) return obj
@staticmethod
[docs] def from_faces(faces, domain, name='region', igs=None, can_cells=False): """ Create a new region containing given faces. Parameters ---------- faces : array The array with indices to `domain.fa`. domain : Domain instance The domain containing the faces. name : str, optional The name of the region. igs : list, optional The allowed element groups. Other groups will be ignored, even though the region might have faces in them - the same effect the 'forbid' flag has. can_cells : bool, optional If True, the region can have cells. Returns ------- obj : Region instance The new region. """ obj = Region(name, 'given faces', domain, '') obj.set_faces(faces, igs=igs, can_cells=can_cells) return obj
def __init__(self, name, definition, domain, parse_def): """ Create region instance. Parameters ---------- name : str The region name, either given, or automatic for intermediate regions. definition : str The region selector definition. domain : Domain instance The domain of the region. parse_def : str The parsed definition of the region. Notes ----- conns, vertex_groups are links to domain data. """ Struct.__init__(self, name=name, definition=definition, n_v_max=domain.shape.n_nod, domain=domain, parse_def=parse_def, all_vertices=None, igs=[], vertices={}, edges={}, faces={}, cells={}, fis={}, can_cells=True, true_cells={}, must_update=True, is_complete=False, mirror_region=None, ig_map=None, ig_map_i=None)
[docs] def light_copy(self, name, parse_def): return Region(name, self.definition, self.domain, parse_def)
[docs] def update_groups(self, force = False): """ Vertices common to several groups are listed only in all of them - fa, ed.unique_indx contain no edge/face duplicates already. """ if self.must_update or force: self.igs = [] self.vertices = {} self.cells = {} for group in self.domain.iter_groups(): ig = group.ig vv = nm.intersect1d(group.vertices, self.all_vertices) if len(vv) == 0: continue self.igs.append(ig) self.vertices[ig] = vv if self.can_cells: mask = nm.zeros(self.n_v_max, nm.int32) mask[vv] = 1 conn = group.conn aux = nm.sum(mask[conn], 1, dtype=nm.int32) rcells = nm.where(aux == conn.shape[1])[0] self.cells[ig] = nm.asarray(rcells, dtype=nm.int32) self.must_update = False
[docs] def update_vertices(self): self.all_vertices, self.vertices = self.get_vertices_of_cells(True)
[docs] def set_vertices(self, vertices): self.all_vertices = nm.array(vertices, dtype=nm.int32) self.update_groups(force = True) self.is_complete = False
[docs] def set_cells(self, cells): self.igs = [] self.cells = {} for ig, rcells in cells.iteritems(): self.cells[ig] = nm.array(rcells, dtype=nm.int32, ndmin=1) self.igs.append(ig) self.update_vertices() self.is_complete = False self.must_update = False
[docs] def set_from_group(self, ig, vertices, n_cell): """ Set region to contain the given element group. """ self.igs = [ig] self.cells = {ig : nm.arange(n_cell, dtype=nm.int32)} self.vertices = {ig: vertices.copy()} self.all_vertices = vertices.copy() self.must_update = False
[docs] def set_faces(self, faces, igs=None, can_cells=False): """ Set region data using given faces. The region description is complete afterwards. Parameters ---------- faces : array The array with indices to `domain.fa`. igs : list, optional The allowed element groups. Other groups will be ignored, even though the region might have faces in them. can_cells : bool, optional If True, the region can have cells. """ faces = nm.asarray(faces) fa = self.domain.fa ed = self.domain.ed indices = fa.indices[faces] facets = fa.facets[faces] faces_igs = indices[:, 0] self.igs = nm.unique(faces_igs) if igs is not None: self.igs = nm.intersect1d(self.igs, igs) all_vertices = [] self.vertices = {} self.edges = {} self.faces = {} self.cells = {} mask = nm.zeros(self.n_v_max, dtype=nm.bool) for ig, group in self.domain.iter_groups(self.igs): n_fp = fa.n_fps[ig] ii = faces_igs == ig vv = nm.unique(facets[ii, :n_fp]) if len(vv) == 0: continue self.vertices[ig] = vv all_vertices.append(vv) self.faces[ig] = faces[ii] self.edges[ig] = ed.get_complete_facets(vv, ig, mask) if can_cells: mask.fill(False) mask[vv] = True conn = group.conn aux = nm.sum(mask[conn], 1, dtype=nm.int32) rcells = nm.where(aux == conn.shape[1])[0] self.cells[ig] = nm.asarray(rcells, dtype=nm.int32) self.all_vertices = nm.unique(nm.hstack(all_vertices)) self.update_shape() self.is_complete = True self.must_update = False
[docs] def delete_groups(self, digs): """ Delete given element groups from the region. """ for ig in digs: _try_delete(self.vertices, ig) _try_delete(self.cells, ig) _try_delete(self.true_cells, ig) _try_delete(self.faces, ig) _try_delete(self.fis, ig) _try_delete(self.edges, ig) try: self.igs.remove(ig) except ValueError: pass self.all_vertices = nm.unique(nm.r_[self.vertices.values()]) self.update_shape()
[docs] def switch_cells(self, can_cells): if self.can_cells: self.can_cells = can_cells if not can_cells: self.cells = {} else: self.can_cells = can_cells if can_cells: self.update_groups(force=True)
[docs] def complete_description(self, ed, fa, surface_integral=False): """ Complete the region description by listing edges and faces for each element group. Parameters ---------- ed : Facets instance The edge facets. fa : Facets instance The face facets. surface_integral : bool If True, the each region surface facet (edge in 2D, face in 3D) can be listed only in one group. Sub-entities are updated accordingly (vertices in 2D, vertices and edges in 3D). Notes ------ If `surface_integral` is False, `self.edges`, `self.faces` simply list edge/face indices per group (pointers to `ed.facets`, `fa.facets`) - repetitions among groups are possible. """ ## # Get edges, faces, etc. par subdomain. mask = nm.zeros(self.n_v_max, dtype=nm.bool) self.edges = {} self.faces = {} if surface_integral: if self.domain.shape.dim == 2: allowed = nm.ones(ed.n_unique, dtype=nm.bool) facets = ed surf = self.edges else: allowed = nm.ones(fa.n_unique, dtype=nm.bool) facets = fa surf = self.faces # Get unique surface facets. empty_igs = [] for ig, group in self.domain.iter_groups(self.igs): vv = self.vertices[ig] if len(vv) == 0: continue mask.fill(False) mask[vv] = True ifacets = facets.get_complete_facets(vv, ig, mask) ii = facets.uid_i[ifacets] surf[ig] = ifacets[allowed[ii]] allowed[ii] = False if not len(surf[ig]): empty_igs.append(ig) self.delete_groups(empty_igs) # Update vertices, cells, and, in 3D, edges. for ig, group in self.domain.iter_groups(self.igs): n_fp = facets.n_fps[ig] vv = nm.unique(facets.facets[surf[ig], :n_fp]) self.vertices[ig] = vv mask.fill(False) mask[vv] = True if self.can_cells: conn = group.conn aux = nm.sum(mask[conn], 1, dtype=nm.int32) rcells = nm.where(aux == conn.shape[1])[0] self.cells[ig] = nm.asarray(rcells, dtype=nm.int32) if self.domain.shape.dim == 3: self.edges[ig] = ed.get_complete_facets(vv, ig, mask) else: for ig, group in self.domain.iter_groups(self.igs): vv = self.vertices[ig] if len(vv) == 0: continue mask.fill(False) mask[vv] = True # Points to ed.facets. self.edges[ig] = ed.get_complete_facets(vv, ig, mask) if fa is None: continue # Points to fa.facets. self.faces[ig] = fa.get_complete_facets(vv, ig, mask) for ig in self.igs: self.true_cells[ig] = self.can_cells self.delete_zero_faces() self.update_shape() self.is_complete = True
[docs] def delete_zero_faces(self, eps=1e-14): """ Delete faces with zero area. """ from sfepy.linalg import get_face_areas fa = self.domain.fa for ig, faces in self.faces.iteritems(): fav = fa.facets[faces] areas = get_face_areas(fav, self.domain.mesh.coors) self.faces[ig] = faces[areas > eps]
[docs] def update_shape(self): """ Update shape of each group according to region vertices, edges, faces and cells. """ aux = nm.array([]) self.shape = {} for ig in self.igs: n_vertex = self.vertices.get(ig, aux).shape[0] n_edge = self.edges.get(ig, aux).shape[0] n_face = self.faces.get(ig, aux).shape[0] n_cell = self.cells.get(ig, aux).shape[0] self.shape[ig] = Struct(n_vertex=n_vertex, n_edge=n_edge, n_face=n_face, n_cell=n_cell)
[docs] def setup_face_indices(self, reset=True): """ Initialize an array (per group) of (iel, ifa) for each face. """ if reset or not self.fis: fa = self.domain.get_facets(force_faces=True)[1] if self.faces: faces = self.faces else: faces = self.edges self.fis = {} for ig in self.igs: rfaces = faces[ig] fi = fa.indices[rfaces] assert_(nm.all(fi[:,0] == ig)) self.fis[ig] = fi[:,1:].copy()
[docs] def select_cells(self, n_verts): """ Select cells containing at least n_verts[ii] vertices per group ii. """ if not self.can_cells: raise ValueError('region %s cannot have cells!' % self.name) self.cells = {} for ig, group in self.domain.iter_groups(self.igs): vv = self.vertices[ig] if len(vv) == 0: continue mask = nm.zeros(self.n_v_max, nm.int32) mask[vv] = 1 aux = nm.sum(mask[group.conn], 1) rcells = nm.where(aux >= n_verts[ig])[0] self.cells[ig] = rcells self.true_cells[ig] = False
[docs] def select_cells_of_surface(self, reset=True): """ Select cells corresponding to faces (or edges in 2D). """ if not self.can_cells: raise ValueError('region %s cannot have cells!' % self.name) self.setup_face_indices(reset=reset) self.cells = {} for ig in self.igs: rcells = self.fis[ig][:,0] self.cells[ig] = nm.ascontiguousarray(rcells) self.true_cells[ig] = False
[docs] def copy(self): """ Vertices-based copy. """ tmp = self.light_copy('copy', self.parse_def) tmp.set_vertices(copy(self.all_vertices)) return tmp
[docs] def sub_n(self, other): tmp = self.light_copy('op', _join(self.parse_def, '-n', other.parse_def)) tmp.set_vertices(nm.setdiff1d(self.all_vertices, other.all_vertices)) return tmp
[docs] def add_n(self, other): tmp = self.light_copy('op', _join(self.parse_def, '+n', other.parse_def)) tmp.set_vertices(nm.union1d(self.all_vertices, other.all_vertices)) return tmp
[docs] def intersect_n(self, other): tmp = self.light_copy('op', _join(self.parse_def, '*n', other.parse_def)) tmp.set_vertices(nm.intersect1d(self.all_vertices, other.all_vertices)) return tmp
[docs] def sub_e(self, other): tmp = self.light_copy('op', _join(self.parse_def, '-e', other.parse_def)) for ig in self.igs: if ig not in other.igs: tmp.igs.append(ig) tmp.cells[ig] = self.cells[ig].copy() continue aux = nm.setdiff1d(self.cells[ig], other.cells[ig]) if not len(aux): continue tmp.cells[ig] = aux tmp.igs.append(ig) tmp.update_vertices() return tmp
[docs] def add_e(self, other): tmp = self.light_copy('op', _join(self.parse_def, '+e', other.parse_def)) for ig in self.igs: tmp.igs.append(ig) if ig not in other.igs: tmp.cells[ig] = self.cells[ig].copy() continue tmp.cells[ig] = nm.union1d(self.cells[ig], other.cells[ig]) for ig in other.igs: if ig in tmp.igs: continue tmp.igs.append(ig) tmp.cells[ig] = other.cells[ig].copy() tmp.update_vertices() return tmp
[docs] def intersect_e(self, other): tmp = self.light_copy('op', _join(self.parse_def, '*e', other.parse_def)) for ig in self.igs: if ig not in other.igs: continue aux = nm.intersect1d(self.cells[ig], other.cells[ig]) if not len(aux): continue tmp.igs.append(ig) tmp.cells[ig] = aux tmp.update_vertices() return tmp
[docs] def setup_mirror_region(self): """ Find the corresponding mirror region, set up element mapping. """ for reg in self.domain.regions: if (reg is not self) and \ (len(reg.igs) == len(self.igs)) and \ nm.all(self.all_vertices == reg.all_vertices): mirror_region = reg break else: raise ValueError('cannot find mirror region! (%s)' % self.name) ig_map = {} ig_map_i = {} for igr in self.igs: for igc in mirror_region.igs: if nm.all(self.vertices[igr] == mirror_region.vertices[igc]): ig_map[igc] = igr ig_map_i[igr] = igc break else: raise ValueError('cannot find mirror region group! (%d)' % igr) self.mirror_region = mirror_region self.ig_map = ig_map self.ig_map_i = ig_map_i if self.domain.shape.dim == 2: self.domain.ed.setup_group_interfaces() elif self.domain.shape.dim == 3: self.domain.fa.setup_group_interfaces()
[docs] def get_mirror_region(self): return self.mirror_region, self.ig_map, self.ig_map_i
[docs] def create_mapping(self, kind, ig): """ Create mapping from reference elements to physical elements, given the integration kind ('v' or 's'). This mapping can be used to compute the physical quadrature points. Returns ------- mapping : VolumeMapping or SurfaceMapping instance The requested mapping. """ from sfepy.fem.mappings import VolumeMapping, SurfaceMapping from sfepy.fem.fe_surface import FESurface coors = self.domain.get_mesh_coors() if kind == 's': coors = coors[self.all_vertices] gel = self.domain.groups[ig].gel conn = self.domain.groups[ig].conn if kind == 'v': cells = self.cells[ig] mapping = VolumeMapping(coors, conn[cells], gel=gel) elif kind == 's': aux = FESurface('aux', self, gel.get_surface_entities(), conn , ig) mapping = SurfaceMapping(coors, aux.leconn, gel=gel.surface_facet) return mapping
[docs] def get_n_cells(self, ig=None, is_surface=False): """ Get number of region cells. Parameters ---------- ig : int, optional The group index. If None, counts from all groups are added together. is_surface : bool If True, number of edges or faces according to domain dimension is returned instead. Returns ------- n_cells : int The number of cells. """ if ig is not None: if is_surface: if self.domain.groups[ig].shape.dim == 2: return self.shape[ig].n_edge else: return self.shape[ig].n_face else: return self.shape[ig].n_cell else: return sum(self.get_n_cells(ig, is_surface=is_surface) for ig in self.igs)
[docs] def get_vertices_of_cells(self, return_per_group=False): """ Return all vertices, that are in some cell of the region. Parameters ---------- return_per_group : bool It True, return also a dict of vertices per element group. """ all_vertices = nm.zeros((0,), dtype=nm.int32) vertices = {} for ig, group in self.domain.iter_groups(self.igs): rcells = self.cells[ig] conn = group.conn nods = conn[rcells,:].ravel() vertices[ig] = nm.unique(nods) all_vertices = nm.unique(nm.r_[all_vertices, vertices[ig]]) if return_per_group: out = all_vertices, vertices else: out = all_vertices return out
[docs] def get_vertices(self, ig): return self.vertices[ig]
[docs] def get_edges(self, ig): return self.edges[ig]
[docs] def get_faces(self, ig): return self.faces[ig]
[docs] def get_surface_entities(self, ig): """ Return either region edges (in 2D) or faces (in 3D) . """ if self.domain.shape.dim == 2: return self.edges[ig] else: return self.faces[ig]
[docs] def get_cells(self, ig, true_cells_only=True): """ Get cells of the region. Raises ValueError if true_cells_only is True and the cells are not true cells (e.g. surface integration region). """ if true_cells_only and not self.true_cells[ig]: msg = 'region %s has not true cells! (surface integration?)' \ % self.name raise ValueError(msg) return self.cells[ig]
[docs] def iter_cells(self): ii = 0 for ig, cells in self.cells.iteritems(): for iel in cells: yield ig, ii, iel ii += 1
[docs] def has_cells(self): if self.can_cells: for cells in self.cells.itervalues(): if cells.size: return True return False else: return False
[docs] def has_cells_if_can(self): if self.can_cells: for cells in self.cells.itervalues(): if cells.size: return True return False else: return True
[docs] def contains(self, other): """ Tests only igs for now!!! """ return set(other.igs).issubset(set(self.igs))
[docs] def get_cell_offsets(self): offs = {} off = 0 for ig in self.igs: offs[ig] = off off += self.shape[ig].n_cell return offs
[docs] def get_charfun(self, by_cell=False, val_by_id=False): """ Return the characteristic function of the region as a vector of values defined either in the mesh nodes (by_cell == False) or cells. The values are either 1 (val_by_id == False) or sequential id + 1. """ if by_cell: chf = nm.zeros((self.domain.shape.n_el,), dtype=nm.float64) offs = self.get_cell_offsets() for ig, cells in self.cells.iteritems(): iel = offs[ig] + cells if val_by_id: chf[iel] = iel + 1 else: chf[iel] = 1.0 else: chf = nm.zeros((self.domain.shape.n_nod,), dtype=nm.float64) if val_by_id: chf[self.all_vertices] = self.all_vertices + 1 else: chf[self.all_vertices] = 1.0 return chf
[docs] def get_edge_graph(self): """ Return the graph of region edges as a sparse matrix having uid(k) + 1 at (i, j) if vertex[i] is connected with vertex[j] by the edge k. Degenerate edges are ignored. """ from scipy.sparse import csr_matrix ed = self.domain.ed rows, cols, vals = [], [], [] for ig, edges in self.edges.iteritems(): e_verts = ed.facets[edges] ii = nm.where(e_verts[:, 0] != e_verts[:, 1])[0] edges = edges[ii] e_verts = e_verts[ii] vals.append(ed.uid_i[edges] + 1) rows.append(e_verts[:, 0]) cols.append(e_verts[:, 1]) vals, indx = nm.unique(nm.concatenate(vals), return_index=True) rows = nm.concatenate(rows)[indx] cols = nm.concatenate(cols)[indx] num = self.all_vertices.max() + 1 graph = csr_matrix((vals, (rows, cols)), shape=(num, num)) nnz = graph.nnz # Symmetrize. graph = graph + graph.T assert_(graph.nnz == 2 * nnz) return graph