qbhf2's picture
added NvidiaWarp and GarmentCode repos
66c9c8a
import warp as wp
import numpy as np
from warp.fem.types import Coords
from warp.fem import cache
def _triangle_node_index(tx: int, ty: int, degree: int):
VERTEX_NODE_COUNT = 3
SIDE_INTERIOR_NODE_COUNT = degree - 1
# Index in similar order to e.g. VTK
# First vertices, then edge (counterclokwise) then interior points (recursively)
if tx == 0:
if ty == 0:
return 0
elif ty == degree:
return 2
else:
edge_index = 2
return VERTEX_NODE_COUNT + SIDE_INTERIOR_NODE_COUNT * edge_index + (SIDE_INTERIOR_NODE_COUNT - ty)
elif ty == 0:
if tx == degree:
return 1
else:
edge_index = 0
return VERTEX_NODE_COUNT + SIDE_INTERIOR_NODE_COUNT * edge_index + tx - 1
elif tx + ty == degree:
edge_index = 1
return VERTEX_NODE_COUNT + SIDE_INTERIOR_NODE_COUNT * edge_index + ty - 1
vertex_edge_node_count = 3 * degree
return vertex_edge_node_count + _triangle_node_index(tx - 1, ty - 1, degree - 3)
class Triangle2DPolynomialShapeFunctions:
VERTEX = wp.constant(0)
EDGE = wp.constant(1)
INTERIOR = wp.constant(2)
def __init__(self, degree: int):
self.ORDER = wp.constant(degree)
self.NODES_PER_ELEMENT = wp.constant((degree + 1) * (degree + 2) // 2)
self.NODES_PER_SIDE = wp.constant(degree + 1)
triangle_coords = np.empty((self.NODES_PER_ELEMENT, 2), dtype=int)
for tx in range(degree + 1):
for ty in range(degree + 1 - tx):
index = _triangle_node_index(tx, ty, degree)
triangle_coords[index] = [tx, ty]
CoordTypeVec = wp.mat(dtype=int, shape=(self.NODES_PER_ELEMENT, 2))
self.NODE_TRIANGLE_COORDS = wp.constant(CoordTypeVec(triangle_coords))
self.node_type_and_type_index = self._get_node_type_and_type_index()
self._node_triangle_coordinates = self._get_node_triangle_coordinates()
@property
def name(self) -> str:
return f"Tri_P{self.ORDER}"
def _get_node_triangle_coordinates(self):
NODE_TRIANGLE_COORDS = self.NODE_TRIANGLE_COORDS
def node_triangle_coordinates(
node_index_in_elt: int,
):
return wp.vec2i(NODE_TRIANGLE_COORDS[node_index_in_elt, 0], NODE_TRIANGLE_COORDS[node_index_in_elt, 1])
return cache.get_func(node_triangle_coordinates, self.name)
def _get_node_type_and_type_index(self):
ORDER = self.ORDER
def node_type_and_index(
node_index_in_elt: int,
):
if node_index_in_elt < 3:
return Triangle2DPolynomialShapeFunctions.VERTEX, node_index_in_elt
if node_index_in_elt < 3 * ORDER:
return Triangle2DPolynomialShapeFunctions.EDGE, (node_index_in_elt - 3)
return Triangle2DPolynomialShapeFunctions.INTERIOR, (node_index_in_elt - 3 * ORDER)
return cache.get_func(node_type_and_index, self.name)
def make_node_coords_in_element(self):
ORDER = self.ORDER
def node_coords_in_element(
node_index_in_elt: int,
):
tri_coords = self._node_triangle_coordinates(node_index_in_elt)
cx = float(tri_coords[0]) / float(ORDER)
cy = float(tri_coords[1]) / float(ORDER)
return Coords(1.0 - cx - cy, cx, cy)
return cache.get_func(node_coords_in_element, self.name)
def make_node_quadrature_weight(self):
if self.ORDER == 3:
# P3 intrisic quadrature
vertex_weight = 1.0 / 30
edge_weight = 0.075
interior_weight = 0.45
elif self.ORDER == 2:
# Order 1, but optimized quadrature weights for monomials of order <= 4
vertex_weight = 0.022335964126
edge_weight = 0.310997369207
interior_weight = 0.0
else:
vertex_weight = 1.0 / self.NODES_PER_ELEMENT
edge_weight = 1.0 / self.NODES_PER_ELEMENT
interior_weight = 1.0 / self.NODES_PER_ELEMENT
VERTEX_WEIGHT = wp.constant(vertex_weight)
EDGE_WEIGHT = wp.constant(edge_weight)
INTERIOR_WEIGHT = wp.constant(interior_weight)
@cache.dynamic_func(suffix=self.name)
def node_quadrature_weight(node_index_in_element: int):
node_type, type_index = self.node_type_and_type_index(node_index_in_element)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
return VERTEX_WEIGHT
elif node_type == Triangle2DPolynomialShapeFunctions.EDGE:
return EDGE_WEIGHT
return INTERIOR_WEIGHT
return node_quadrature_weight
def make_trace_node_quadrature_weight(self):
# Closed Newton-Cotes
if self.ORDER == 3:
vertex_weight = 1.0 / 8.0
edge_weight = 3.0 / 8.0
elif self.ORDER == 2:
vertex_weight = 1.0 / 6.0
edge_weight = 2.0 / 3.0
else:
vertex_weight = 1.0 / self.NODES_PER_SIDE
edge_weight = 1.0 / self.NODES_PER_SIDE
VERTEX_WEIGHT = wp.constant(vertex_weight)
EDGE_WEIGHT = wp.constant(edge_weight)
@cache.dynamic_func(suffix=self.name)
def trace_node_quadrature_weight(node_index_in_element: int):
node_type, type_index = self.node_type_and_type_index(node_index_in_element)
return wp.select(node_type == Triangle2DPolynomialShapeFunctions.VERTEX, EDGE_WEIGHT, VERTEX_WEIGHT)
return trace_node_quadrature_weight
def make_element_inner_weight(self):
ORDER = self.ORDER
def element_inner_weight_linear(
coords: Coords,
node_index_in_elt: int,
):
return coords[node_index_in_elt]
def element_inner_weight_quadratic(
coords: Coords,
node_index_in_elt: int,
):
node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
# Vertex
return coords[type_index] * (2.0 * coords[type_index] - 1.0)
# Edge
c1 = type_index
c2 = (type_index + 1) % 3
return 4.0 * coords[c1] * coords[c2]
def element_inner_weight_cubic(
coords: Coords,
node_index_in_elt: int,
):
node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
# Vertex
return 0.5 * coords[type_index] * (3.0 * coords[type_index] - 1.0) * (3.0 * coords[type_index] - 2.0)
elif node_type == Triangle2DPolynomialShapeFunctions.EDGE:
# Edge
edge = type_index // 2
k = type_index - 2 * edge
c1 = (edge + k) % 3
c2 = (edge + 1 - k) % 3
return 4.5 * coords[c1] * coords[c2] * (3.0 * coords[c1] - 1.0)
# Interior
return 27.0 * coords[0] * coords[1] * coords[2]
if ORDER == 1:
return cache.get_func(element_inner_weight_linear, self.name)
elif ORDER == 2:
return cache.get_func(element_inner_weight_quadratic, self.name)
elif ORDER == 3:
return cache.get_func(element_inner_weight_cubic, self.name)
return None
def make_element_inner_weight_gradient(self):
ORDER = self.ORDER
def element_inner_weight_gradient_linear(
coords: Coords,
node_index_in_elt: int,
):
dw_dc = wp.vec3(0.0)
dw_dc[node_index_in_elt] = 1.0
dw_du = wp.vec2(dw_dc[1] - dw_dc[0], dw_dc[2] - dw_dc[0])
return dw_du
def element_inner_weight_gradient_quadratic(
coords: Coords,
node_index_in_elt: int,
):
node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
dw_dc = wp.vec3(0.0)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
# Vertex
dw_dc[type_index] = 4.0 * coords[type_index] - 1.0
else:
# Edge
c1 = type_index
c2 = (type_index + 1) % 3
dw_dc[c1] = 4.0 * coords[c2]
dw_dc[c2] = 4.0 * coords[c1]
dw_du = wp.vec2(dw_dc[1] - dw_dc[0], dw_dc[2] - dw_dc[0])
return dw_du
def element_inner_weight_gradient_cubic(
coords: Coords,
node_index_in_elt: int,
):
node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
dw_dc = wp.vec3(0.0)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
# Vertex
dw_dc[type_index] = (
0.5 * 27.0 * coords[type_index] * coords[type_index] - 9.0 * coords[type_index] + 1.0
)
elif node_type == Triangle2DPolynomialShapeFunctions.EDGE:
# Edge
edge = type_index // 2
k = type_index - 2 * edge
c1 = (edge + k) % 3
c2 = (edge + 1 - k) % 3
dw_dc[c1] = 4.5 * coords[c2] * (6.0 * coords[c1] - 1.0)
dw_dc[c2] = 4.5 * coords[c1] * (3.0 * coords[c1] - 1.0)
else:
# Interior
dw_dc = wp.vec3(
27.0 * coords[1] * coords[2], 27.0 * coords[2] * coords[0], 27.0 * coords[0] * coords[1]
)
dw_du = wp.vec2(dw_dc[1] - dw_dc[0], dw_dc[2] - dw_dc[0])
return dw_du
if ORDER == 1:
return cache.get_func(element_inner_weight_gradient_linear, self.name)
elif ORDER == 2:
return cache.get_func(element_inner_weight_gradient_quadratic, self.name)
elif ORDER == 3:
return cache.get_func(element_inner_weight_gradient_cubic, self.name)
return None
def element_node_triangulation(self):
if self.ORDER == 1:
element_triangles = [[0, 1, 2]]
if self.ORDER == 2:
element_triangles = [[0, 3, 5], [3, 1, 4], [2, 5, 4], [3, 4, 5]]
elif self.ORDER == 3:
element_triangles = [
[0, 3, 8],
[3, 4, 9],
[4, 1, 5],
[8, 3, 9],
[4, 5, 9],
[8, 9, 7],
[9, 5, 6],
[6, 7, 9],
[7, 6, 2],
]
return np.array(element_triangles)
class Triangle2DNonConformingPolynomialShapeFunctions:
def __init__(self, degree: int):
self._tri_shape = Triangle2DPolynomialShapeFunctions(degree=degree)
self.ORDER = self._tri_shape.ORDER
self.NODES_PER_ELEMENT = self._tri_shape.NODES_PER_ELEMENT
self.element_node_triangulation = self._tri_shape.element_node_triangulation
# Coordinates (a, b, b) of embedded triangle
if self.ORDER == 1:
# Order 2
a = 2.0 / 3.0
elif self.ORDER == 2:
# Order 2, optimized for small intrinsic quadrature error up to degree 4
a = 0.7790771484375001
elif self.ORDER == 3:
# Order 3, optimized for small intrinsic quadrature error up to degree 6
a = 0.8429443359375002
else:
a = 1.0
b = 0.5 * (1.0 - a)
self._small_to_big = np.full((3, 3), b) + (a - b) * np.eye(3)
self._tri_scale = a - b
@property
def name(self) -> str:
return f"Tri_P{self.ORDER}d"
def make_node_quadrature_weight(self):
# Intrinsic quadrature -- precomputed integral of node shape functions
# over element. Order equal to self.ORDER
if self.ORDER == 2:
vertex_weight = 0.13743348
edge_weight = 0.19589985
interior_weight = 0.0
elif self.ORDER == 3:
vertex_weight = 0.07462578
edge_weight = 0.1019807
interior_weight = 0.16423881
else:
vertex_weight = 1.0 / self.NODES_PER_ELEMENT
edge_weight = 1.0 / self.NODES_PER_ELEMENT
interior_weight = 1.0 / self.NODES_PER_ELEMENT
VERTEX_WEIGHT = wp.constant(vertex_weight)
EDGE_WEIGHT = wp.constant(edge_weight)
INTERIOR_WEIGHT = wp.constant(interior_weight)
@cache.dynamic_func(suffix=self.name)
def node_quadrature_weight(node_index_in_element: int):
node_type, type_index = self._tri_shape.node_type_and_type_index(node_index_in_element)
if node_type == Triangle2DPolynomialShapeFunctions.VERTEX:
return VERTEX_WEIGHT
elif node_type == Triangle2DPolynomialShapeFunctions.EDGE:
return EDGE_WEIGHT
return INTERIOR_WEIGHT
return node_quadrature_weight
def make_trace_node_quadrature_weight(self):
# Non-conforming, zero measure on sides
@wp.func
def zero(node_index_in_elt: int):
return 0.0
return zero
def make_node_coords_in_element(self):
node_coords_in_tet = self._tri_shape.make_node_coords_in_element()
SMALL_TO_BIG = wp.constant(wp.mat33(self._small_to_big))
@cache.dynamic_func(suffix=self.name)
def node_coords_in_element(
node_index_in_elt: int,
):
tri_coords = node_coords_in_tet(node_index_in_elt)
return SMALL_TO_BIG * tri_coords
return node_coords_in_element
def make_element_inner_weight(self):
tri_inner_weight = self._tri_shape.make_element_inner_weight()
BIG_TO_SMALL = wp.constant(wp.mat33(np.linalg.inv(self._small_to_big)))
@cache.dynamic_func(suffix=self.name)
def element_inner_weight(
coords: Coords,
node_index_in_elt: int,
):
tri_coords = BIG_TO_SMALL * coords
return tri_inner_weight(tri_coords, node_index_in_elt)
return element_inner_weight
def make_element_inner_weight_gradient(self):
tri_inner_weight_gradient = self._tri_shape.make_element_inner_weight_gradient()
BIG_TO_SMALL = wp.constant(wp.mat33(np.linalg.inv(self._small_to_big)))
INV_TRI_SCALE = wp.constant(1.0 / self._tri_scale)
@cache.dynamic_func(suffix=self.name)
def element_inner_weight_gradient(
coords: Coords,
node_index_in_elt: int,
):
tri_coords = BIG_TO_SMALL * coords
grad = tri_inner_weight_gradient(tri_coords, node_index_in_elt)
return INV_TRI_SCALE * grad
return element_inner_weight_gradient