# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Any, Callable, Dict, List, Union
import numpy as np
from topoptlab.utils import check_meshdata, elid_to_coords, nodeid_to_coords
[docs]
def cube_mask(coords : np.ndarray,
low : np.ndarray,
upp : np.ndarray,
**kwargs : Any) -> np.ndarray:
"""
Take coordinate and return mask of coordinates lying between the lower
and upper boundaries.
Parameters
----------
coords : np.ndarray
element coordinates of shape shape (ncoords,ndim).
low : np.ndarray
lower boundaries of the cuboid of shape shape (ndim).
upp : np.ndarray
upper boundaries of the cuboid of shape shape (ndim).
Returns
-------
mask : np.ndarray
mask for coordinates of shape (ncoords).
"""
#
return np.all((coords <= upp[None,:]) &\
(coords >= low[None,:]),
axis=1)
[docs]
def elids_in_mask(el: np.ndarray,
spatial_mask_fnc : Callable,
mask_kw : Dict,
nelx: int,
nely: int,
nelz: Union[None,int] = None,
l: Union[float,List,np.ndarray] = [1.,1.,1.],
g: Union[List,np.ndarray] = [0.,0.],
**kwargs: Any) -> np.ndarray:
"""
Find element IDs within an interval of cartesian coordinates in the usual
regular grid.
Parameters
----------
el : np.ndarray
element IDs of shape (nel).
spatial_mask_fnc : callable
function that creates the spatial mask based on coordinates and mask_kw.
mask_kw :
keywords for the spatial mask.
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
nelz : int or None
number of elements in z direction.
l : float or list
side length of elements.
g : float or list
angle of elements. if both angles zero, element is rectangular/cuboid.
Returns
-------
indices : np.ndarray
element indices shape (n).
"""
#
if nelz is None:
ndim = 2
else:
ndim = 3
#
l,g = check_meshdata(l=l,
g=g,
ndim=ndim)
# find coordinates of each element
coords = elid_to_coords(el = el,
nelx = nelx,
nely = nely,
nelz = nelz,
l = l,
g = g)
#
return np.nonzero(spatial_mask_fnc(coords=coords, **mask_kw))[0]
[docs]
def nodeids_in_mask(node_id: np.ndarray,
spatial_mask_fnc : Callable,
mask_kw : Dict,
nelx: int,
nely: int,
nelz: Union[None,int] = None,
l: Union[float,List,np.ndarray] = [1.,1.,1.],
g: Union[List,np.ndarray] = [0.,0.],
**kwargs: Any) -> np.ndarray:
"""
Find node IDs within an interval of cartesian coordinates in the usual
regular grid.
Parameters
----------
nd_id : np.ndarray
node IDs of shape (n_node).
spatial_mask_fnc : callable
function that creates the spatial mask based on coordinates and mask_kw.
mask_kw :
keywords for the spatial mask.
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
nelz : int or None
number of elements in z direction.
l : float or list
side length of elements.
g : float or list
angle of elements. if both angles zero, element is rectangular/cuboid.
Returns
-------
indices : np.ndarray
node indices shape (n).
"""
#
if nelz is None:
ndim = 2
else:
ndim = 3
#
l,g = check_meshdata(l=l,
g=g,
ndim=ndim)
# find coordinates of each element
coords = nodeid_to_coords(nd = node_id,
nelx = nelx,
nely = nely,
nelz = nelz,
l = l,
g = g)
#
return np.nonzero(spatial_mask_fnc(coords=coords, **mask_kw))[0]
[docs]
def sphere(nelx: int, nely: int, center: np.ndarray,
radius: float,
fill_value: int =1) -> np.ndarray:
"""
Create element flags for a sphere located at the specified center with the
specified radius.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
center : np.ndarray
coordinates of sphere center.
radius : float
sphere radius.
fill_value: int
value that is prescribed to elements within sphere.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely)
"""
n = nelx*nely
el = np.arange(n, dtype=np.int32)
i,j = np.divmod(el,nely)
mask = (i-center[0])**2 + (j-center[1])**2 <= radius**2
#
el_flags = np.zeros(n,dtype=np.int32)
el_flags[mask] = fill_value
return el_flags
[docs]
def ellipse(nelx: int, nely: int,
center: np.ndarray,
ax_half_lengths: np.ndarray,
fill_value: int = 1) -> np.ndarray:
"""
Create element flags for an axis-aligned ellipse.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
center : np.ndarray
(cx, cy) coordinates of ellipse center.
ax_half_lengths : np.ndarray
(a, b) ellipse semi-axes lengths.
fill_value : int
value assigned to elements inside the ellipse.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely)
"""
n = nelx * nely
el = np.arange(n, dtype=np.int32)
i, j = np.divmod(el, nely)
# ellipse equation: ((x-cx)^2)/a^2 + ((y-cy)^2)/b^2 ≤ 1
a, b = ax_half_lengths
mask = ((i - center[0])**2) / a**2 + ((j - center[1])**2) / b**2 <= 1.0
el_flags = np.zeros(n, dtype=np.int32)
el_flags[mask] = fill_value
return el_flags
[docs]
def ball(nelx: int, nely: int, nelz: int,
center: np.ndarray, radius: float,
fill_value: int = 1) -> np.ndarray:
"""
Create element flags for a ball located at the specified center with the
specified radius.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
nelz : int
number of elements in z direction.
center : list or tuple or np.ndarray
coordinates of sphere center.
radius : float
sphere radius.
fill_value: int
value that is prescribed to elements within ball.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely*nelz)
"""
n = nelx*nely*nelz
el = np.arange(n, dtype=np.int32)
k,ij = np.divmod(el,nelx*nely)
i,j = np.divmod(ij,nely)
mask = (i-center[0])**2 + (j-center[1])**2 + (k-center[2])**2 <= radius**2
#
el_flags = np.zeros(n, dtype=np.int32)
el_flags[mask] = fill_value
return el_flags
[docs]
def ellipsoid(nelx: int, nely: int, nelz: int,
center: np.ndarray,
ax_half_lengths: np.ndarray,
fill_value: int = 1) -> np.ndarray:
"""
Create element flags for an axis-aligned ellipse.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
center : np.ndarray
(cx, cy, cz) coordinates of ellipse center.
ax_half_lengths : np.ndarray
(a, b,c) ellipse semi-axes lengths.
fill_value : int
value assigned to elements inside the ellipse.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely)
"""
n = nelx*nely*nelz
el = np.arange(n, dtype=np.int32)
k,ij = np.divmod(el,nelx*nely)
i,j = np.divmod(ij,nely)
# ellipse equation: ((x-cx)^2)/a^2 + ((y-cy)^2)/b^2 ≤ 1
a, b = ax_half_lengths
mask = ((i - center[0])**2) / a**2 + ((j - center[1])**2) / b**2 <= 1.0
el_flags = np.zeros(n, dtype=np.int32)
el_flags[mask] = fill_value
return el_flags
[docs]
def diracdelta(nelx: int, nely: int, nelz: Union[None,int] = None,
location: Union[None,int] = None) -> np.ndarray:
"""
Create element flags for a Dirac delta located at the specified location.
Depending on the location and the number of elements in each direction this
results in either a single element with flag 1 or 4/8 elements in 2/3
dimensions.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
nelz : int
number of elements in z direction.
location : list or tuple or np.ndarray
coordinate of Dirac delta.
Returns
-------
el_flags : np.ndarray shape (nelx*nely) or shape (nelx*nely*nelz)
element flags / densities
"""
if location is None and nelz is None:
location = ((nelx-1)/2,(nely-1)/2)
elif location is None and nelz is not None:
location = ((nelx-1)/2,(nely-1)/2,(nelz-1)/2)
# densities
if nelz is None:
x = sphere(nelx=nelx,nely=nely,
center=location,
radius=1,fill_value=1.)
else:
x = ball(nelx=nelx,nely=nely,nelz=nelz,
center=location,
radius=1,fill_value=1.)
return x
[docs]
def bounding_rectangle(nelx: int, nely: int,
faces: List = ["b","t","r","l"]) -> np.ndarray:
"""
Create element flags for a bounding box of one element thickness. It is
possible to draw only specified faces of the bounding box.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
faces : list of str
which faces of bounding box are supposed to be drawn. Possible
values are "b" for bottom, "t" for top, "l" for left and "r" for right.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely)
"""
# collect indices
indices = []
# append corner indices
if "t" in faces or "l" in faces:
indices.append(0)
if "t" in faces or "r" in faces:
indices.append((nelx-1)*nely)
if "b" in faces or "l" in faces:
indices.append(nely - 1)
if "b" in faces or "r" in faces:
indices.append(nelx*nely-1)
# append faces without corner indices
if "t" in faces:
indices.append(np.arange(nely,(nelx-1)*nely,nely))
if "b" in faces:
indices.append(np.arange(nely-1,nelx*nely,nely))
if "l" in faces:
indices.append(np.arange(1,nely-1))
if "r" in faces:
indices.append(np.arange((nelx-1)*nely + 1,nelx*nely-1))
#
indices = np.hstack(indices)
el_flags = np.zeros(nelx*nely,dtype=int)
# set to active
el_flags[indices] = 2
return el_flags
[docs]
def slab(nelx: int, nely: int, center: np.ndarray,
widths: Union[None,List] = None, fill_value: int = 1) -> np.ndarray:
"""
Create element flags for a slab located at the specified center with the
specified width in each dimension.
Parameters
----------
nelx : int
number of elements in x direction.
nely : int
number of elements in y direction.
center : list or tuple or np.ndarray
coordinates of slab center.
widths : iterable of floats and None
width in x and y direction. If one entry is None or it is width is None,
then nelx/nely is taken as width in this direction.
fill_value: int
value that is prescribed to elements within sphere.
Returns
-------
el_flags : np.ndarray
element flags of shape (nelx*nely)
"""
#
widths = [ [nelx,nely][i] if w is None else w for i,w in enumerate(widths)]
#
n = nelx*nely
el = np.arange(n, dtype=np.int32)
i,j = np.divmod(el,nely)
#
mask = (np.abs(i-center[0]) <= widths[0]/2 ) & (np.abs(j-center[1]) <= widths[1]/2)
#
el_flags = np.zeros(n,dtype=np.int32)
el_flags[mask] = fill_value
return el_flags