Source code for pymor.discretizers.builtin.grids.defaultimpl
# This file is part of the pyMOR project (http://www.pymor.org).
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
import numpy as np
from pymor.core.cache import cached
from pymor.discretizers.builtin.inverse import inv_transposed_two_by_two
from pymor.discretizers.builtin.relations import inverse_relation
[docs]class ConformalTopologicalGridDefaultImplementations:
"""Provides default informations for |ConformalTopologicalGrids|."""
@cached
def _subentities(self, codim, subentity_codim):
assert 0 <= codim < self.dim, 'Invalid codimension'
if subentity_codim > codim + 1:
SE = self.subentities(codim, subentity_codim - 1)
SESE = self.subentities(subentity_codim - 1, subentity_codim)
# we assume that there is only one geometry type ...
num_subsubentities = np.unique(SESE[SE[0]]).size
SSE = np.empty((SE.shape[0], num_subsubentities), dtype=np.int32)
SSE.fill(-1)
for ei in range(SE.shape[0]):
X = SESE[SE[ei]].ravel()
SSE[ei] = X[np.sort(np.unique(X, return_index=True)[1])]
return SSE
else:
raise NotImplementedError
@cached
def _superentities_with_indices(self, codim, superentity_codim):
assert 0 <= codim <= self.dim, f'Invalid codimension (was {codim})'
assert 0 <= superentity_codim <= codim, f'Invalid codimension (was {superentity_codim})'
SE = self.subentities(superentity_codim, codim)
return inverse_relation(SE, size_rhs=self.size(codim), with_indices=True)
@cached
def _superentities(self, codim, superentity_codim):
return self._superentities_with_indices(codim, superentity_codim)[0]
@cached
def _superentity_indices(self, codim, superentity_codim):
return self._superentities_with_indices(codim, superentity_codim)[1]
@cached
def _neighbours(self, codim, neighbour_codim, intersection_codim):
assert 0 <= codim <= self.dim, 'Invalid codimension'
assert 0 <= neighbour_codim <= self.dim, 'Invalid codimension'
if intersection_codim is None:
if codim == neighbour_codim:
intersection_codim = codim + 1
else:
intersection_codim = min(codim, neighbour_codim)
assert max(codim, neighbour_codim) <= intersection_codim <= self.dim, 'Invalid codimension'
if intersection_codim == min(codim, neighbour_codim):
if codim < neighbour_codim:
return self.subentities(codim, neighbour_codim)
elif codim > neighbour_codim:
return self.superentities(codim, neighbour_codim)
else:
return np.zeros((self.size(codim), 0), dtype=np.int32)
else:
EI = self.subentities(codim, intersection_codim)
ISE = self.superentities(intersection_codim, neighbour_codim)
NB = np.empty((EI.shape[0], EI.shape[1] * ISE.shape[1]), dtype=np.int32)
NB.fill(-1)
NB_COUNTS = np.zeros(EI.shape[0], dtype=np.int32)
if codim == neighbour_codim:
for ii, i in np.ndenumerate(EI):
if i >= 0:
for _, n in np.ndenumerate(ISE[i]):
if n != ii[0] and n not in NB[ii[0]]:
NB[ii[0], NB_COUNTS[ii[0]]] = n
NB_COUNTS[ii[0]] += 1
else:
for ii, i in np.ndenumerate(EI):
if i >= 0:
for _, n in np.ndenumerate(ISE[i]):
if n not in NB[ii[0]]:
NB[ii[0], NB_COUNTS[ii[0]]] = n
NB_COUNTS[ii[0]] += 1
NB = NB[:NB.shape[0], :NB_COUNTS.max()]
return NB
@cached
def _boundaries(self, codim):
assert 0 <= codim <= self.dim, 'Invalid codimension'
if codim == 1:
SE = self.superentities(1, 0)
# a codim-1 entity can have at most 2 superentities, and it is a boundary
# if it has only one superentity
if SE.shape[1] > 1:
return np.where(np.any(SE == -1, axis=1))[0].astype('int32')
else:
return np.arange(SE.shape[0], dtype='int32')
elif codim == 0:
B1 = self.boundaries(1)
if B1.size > 0:
B0 = np.unique(self.superentities(1, 0)[B1])
return B0[1:] if B0[0] == -1 else B0
else:
return np.array([], dtype=np.int32)
else:
B1 = self.boundaries(1)
if B1.size > 0:
BC = np.unique(self.subentities(1, codim)[B1])
return BC[1:] if BC[0] == -1 else BC
else:
return np.array([], dtype=np.int32)
@cached
def _boundary_mask(self, codim):
M = np.zeros(self.size(codim), dtype='bool')
B = self.boundaries(codim)
if B.size > 0:
M[self.boundaries(codim)] = True
return M
[docs]class ReferenceElementDefaultImplementations:
"""Provides default implementations for |ReferenceElements|."""
@cached
def _subentity_embedding(self, subentity_codim):
if subentity_codim > 1:
A = []
B = []
for i in range(self.size(subentity_codim)):
P = np.where(self.subentities(subentity_codim - 1, subentity_codim) == i)
parent_index, local_index = P[0][0], P[1][0]
A0, B0 = self.subentity_embedding(subentity_codim - 1)
A0 = A0[parent_index]
B0 = B0[parent_index]
A1, B1 = self.sub_reference_element(subentity_codim - 1).subentity_embedding(1)
A1 = A1[local_index]
B1 = B1[local_index]
A.append(np.dot(A0, A1))
B.append(np.dot(A0, B1) + B0)
return np.array(A), np.array(B)
else:
raise NotImplementedError
@cached
def _sub_reference_element(self, codim):
if codim > 1:
return self.sub_reference_element(1).sub_reference_element(codim - 1)
else:
raise NotImplementedError
[docs]class AffineGridDefaultImplementations:
"""Provides default implementations for |AffineGrids|."""
@cached
def _subentities(self, codim, subentity_codim):
assert 0 <= codim <= self.dim, 'Invalid codimension'
assert 0 < codim, 'Not implemented'
P = self.superentities(codim, codim - 1)[:, 0] # we assume here that superentities() is sorted by global index
I = self.superentity_indices(codim, codim - 1)[:, 0]
SE = self.subentities(codim - 1, subentity_codim)[P]
RSE = self.reference_element(codim - 1).subentities(1, subentity_codim - (codim - 1))[I]
SSE = np.empty_like(RSE)
for i in range(RSE.shape[0]):
SSE[i, :] = SE[i, RSE[i]]
return SSE
@cached
def _embeddings(self, codim):
assert codim > 0, NotImplemented
E = self.superentities(codim, codim - 1)[:, 0]
I = self.superentity_indices(codim, codim - 1)[:, 0]
A0, B0 = self.embeddings(codim - 1)
A0 = A0[E]
B0 = B0[E]
A1, B1 = self.reference_element(codim - 1).subentity_embedding(1)
A = np.zeros((E.shape[0], A0.shape[1], A1.shape[2]))
B = np.zeros((E.shape[0], A0.shape[1]))
for i in range(A1.shape[0]):
INDS = np.where(I == i)[0]
A[INDS] = np.dot(A0[INDS], A1[i])
B[INDS] = np.dot(A0[INDS], B1[i]) + B0[INDS]
return A, B
@cached
def _jacobian_inverse_transposed(self, codim):
assert 0 <= codim < self.dim,\
f'Invalid Codimension (must be between 0 and {self.dim} but was {codim})'
J = self.embeddings(codim)[0]
if J.shape[-1] == J.shape[-2] == 2:
JIT = inv_transposed_two_by_two(J)
else:
pinv = np.linalg.pinv
JIT = np.array([pinv(j) for j in J]).swapaxes(1, 2)
return JIT
@cached
def _integration_elements(self, codim):
assert 0 <= codim <= self.dim,\
f'Invalid Codimension (must be between 0 and {self.dim} but was {codim})'
if codim == self.dim:
return np.ones(self.size(codim))
J = self.embeddings(codim)[0]
JTJ = np.einsum('eji,ejk->eik', J, J)
if JTJ.shape[1] == 1:
D = JTJ.ravel()
elif JTJ.shape[1] == 2:
D = (JTJ[:, 0, 0] * JTJ[:, 1, 1] - JTJ[:, 1, 0] * JTJ[:, 0, 1]).ravel()
else:
def f(A):
return np.linalg.det(A)
D = np.array([f(j) for j in J])
return np.sqrt(D)
@cached
def _volumes(self, codim):
assert 0 <= codim <= self.dim,\
f'Invalid Codimension (must be between 0 and {self.dim} but was {codim})'
if codim == self.dim:
return np.ones(self.size(self.dim))
return self.reference_element(codim).volume * self.integration_elements(codim)
@cached
def _volumes_inverse(self, codim):
return np.reciprocal(self.volumes(codim))
@cached
def _unit_outer_normals(self):
JIT = self.jacobian_inverse_transposed(0)
N = np.dot(JIT, self.reference_element(0).unit_outer_normals().T).swapaxes(1, 2)
return N / np.apply_along_axis(np.linalg.norm, 2, N)[:, :, np.newaxis]
@cached
def _centers(self, codim):
assert 0 <= codim <= self.dim,\
f'Invalid Codimension (must be between 0 and {self.dim} but was {codim})'
A, B = self.embeddings(codim)
C = self.reference_element(codim).center()
return np.dot(A, C) + B
@cached
def _diameters(self, codim):
assert 0 <= codim <= self.dim,\
f'Invalid Codimension (must be between 0 and {self.dim} but was {codim})'
return np.reshape(self.reference_element(codim).mapped_diameter(self.embeddings(codim)[0]), (-1,))
@cached
def _quadrature_points(self, codim, order, npoints, quadrature_type):
P, _ = self.reference_element(codim).quadrature(order, npoints, quadrature_type)
A, B = self.embeddings(codim)
return np.einsum('eij,kj->eki', A, P) + B[:, np.newaxis, :]
@cached
def _bounding_box(self):
bbox = np.empty((2, self.dim))
centers = self.centers(self.dim)
for dim in range(self.dim):
bbox[0, dim] = np.min(centers[:, dim])
bbox[1, dim] = np.max(centers[:, dim])
return bbox