1*7b3ff069SJeremy L Thompson# Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and other CEED contributors 2*7b3ff069SJeremy L Thompson# All Rights Reserved. See the top-level LICENSE and NOTICE files for details. 3*7b3ff069SJeremy L Thompson# 4*7b3ff069SJeremy L Thompson# SPDX-License-Identifier: BSD-2-Clause 5*7b3ff069SJeremy L Thompson# 6*7b3ff069SJeremy L Thompson# This file is part of CEED: http://github.com/ceed 7*7b3ff069SJeremy L Thompson 8*7b3ff069SJeremy L Thompsonimport sys 9*7b3ff069SJeremy L Thompsonimport os 10*7b3ff069SJeremy L Thompsonfrom sysconfig import get_config_var 11*7b3ff069SJeremy L Thompsonimport argparse 12*7b3ff069SJeremy L Thompsonimport math 13*7b3ff069SJeremy L Thompsonimport numpy as np 14*7b3ff069SJeremy L Thompsonimport libceed 15*7b3ff069SJeremy L Thompsonimport ctypes 16*7b3ff069SJeremy L Thompson 17*7b3ff069SJeremy L Thompson 18*7b3ff069SJeremy L Thompsondef parse_arguments(): 19*7b3ff069SJeremy L Thompson """Parse command line arguments for surface area computation 20*7b3ff069SJeremy L Thompson 21*7b3ff069SJeremy L Thompson Returns: 22*7b3ff069SJeremy L Thompson Namespace: Parsed arguments with fields: 23*7b3ff069SJeremy L Thompson ceed: CEED resource specifier 24*7b3ff069SJeremy L Thompson dim: Problem dimension (1-3) 25*7b3ff069SJeremy L Thompson mesh_degree: Mesh polynomial degree 26*7b3ff069SJeremy L Thompson solution_degree: Solution polynomial degree 27*7b3ff069SJeremy L Thompson num_qpts: Number of quadrature points 28*7b3ff069SJeremy L Thompson problem_size: Approximate problem size 29*7b3ff069SJeremy L Thompson test: Test mode flag 30*7b3ff069SJeremy L Thompson quiet: Suppress output flag 31*7b3ff069SJeremy L Thompson gallery: Use gallery QFunctions flag 32*7b3ff069SJeremy L Thompson """ 33*7b3ff069SJeremy L Thompson parser = argparse.ArgumentParser(description="libCEED surface area example") 34*7b3ff069SJeremy L Thompson parser.add_argument("-c", "--ceed", default="/cpu/self", 35*7b3ff069SJeremy L Thompson help="libCEED resource specifier (default: /cpu/self)") 36*7b3ff069SJeremy L Thompson parser.add_argument("-d", "--dim", type=int, default=3, 37*7b3ff069SJeremy L Thompson help="Problem dimension (1-3) (default: 3)") 38*7b3ff069SJeremy L Thompson parser.add_argument("-m", "--mesh-degree", type=int, default=4, 39*7b3ff069SJeremy L Thompson help="Mesh polynomial degree (default: 4)") 40*7b3ff069SJeremy L Thompson parser.add_argument("-p", "--solution-degree", type=int, default=4, 41*7b3ff069SJeremy L Thompson help="Solution polynomial degree (default: 4)") 42*7b3ff069SJeremy L Thompson parser.add_argument("-q", "--quadrature-points", type=int, default=6, 43*7b3ff069SJeremy L Thompson help="Number of quadrature points (default: 6)") 44*7b3ff069SJeremy L Thompson parser.add_argument("-s", "--problem-size", type=int, default=-1, 45*7b3ff069SJeremy L Thompson help="Approximate problem size (default: ~256k)") 46*7b3ff069SJeremy L Thompson parser.add_argument("-t", "--test", action="store_true", 47*7b3ff069SJeremy L Thompson help="Test mode (reduced problem size)") 48*7b3ff069SJeremy L Thompson parser.add_argument("--quiet", action="store_true", 49*7b3ff069SJeremy L Thompson help="Suppress output") 50*7b3ff069SJeremy L Thompson parser.add_argument("-g", "--gallery", action="store_true", 51*7b3ff069SJeremy L Thompson help="Use gallery QFunctions") 52*7b3ff069SJeremy L Thompson 53*7b3ff069SJeremy L Thompson args = parser.parse_args() 54*7b3ff069SJeremy L Thompson if args.dim not in [1, 2, 3]: 55*7b3ff069SJeremy L Thompson parser.error("Dimension must be 1, 2, or 3") 56*7b3ff069SJeremy L Thompson return args 57*7b3ff069SJeremy L Thompson 58*7b3ff069SJeremy L Thompson 59*7b3ff069SJeremy L Thompsondef get_cartesian_mesh_size(dim, degree, prob_size): 60*7b3ff069SJeremy L Thompson """Determine Cartesian mesh size for given problem size 61*7b3ff069SJeremy L Thompson 62*7b3ff069SJeremy L Thompson Args: 63*7b3ff069SJeremy L Thompson dim: Spatial dimension (1-3) 64*7b3ff069SJeremy L Thompson degree: Polynomial degree 65*7b3ff069SJeremy L Thompson prob_size: Target problem size 66*7b3ff069SJeremy L Thompson 67*7b3ff069SJeremy L Thompson Returns: 68*7b3ff069SJeremy L Thompson list: Number of elements in each dimension 69*7b3ff069SJeremy L Thompson """ 70*7b3ff069SJeremy L Thompson # Calculate number of elements needed 71*7b3ff069SJeremy L Thompson num_elem = prob_size // (degree ** dim) 72*7b3ff069SJeremy L Thompson 73*7b3ff069SJeremy L Thompson # Find smallest power of 2 >= num_elem 74*7b3ff069SJeremy L Thompson s = 0 75*7b3ff069SJeremy L Thompson while num_elem > 1: 76*7b3ff069SJeremy L Thompson num_elem = num_elem / 2 77*7b3ff069SJeremy L Thompson s += 1 78*7b3ff069SJeremy L Thompson 79*7b3ff069SJeremy L Thompson # Distribute across dimensions 80*7b3ff069SJeremy L Thompson r = s % dim 81*7b3ff069SJeremy L Thompson num_xyz = [] 82*7b3ff069SJeremy L Thompson for d in range(dim): 83*7b3ff069SJeremy L Thompson sd = s // dim 84*7b3ff069SJeremy L Thompson if r > 0: 85*7b3ff069SJeremy L Thompson sd += 1 86*7b3ff069SJeremy L Thompson r -= 1 87*7b3ff069SJeremy L Thompson num_xyz.append(1 << sd) 88*7b3ff069SJeremy L Thompson return num_xyz 89*7b3ff069SJeremy L Thompson 90*7b3ff069SJeremy L Thompson 91*7b3ff069SJeremy L Thompsondef build_cartesian_restriction(ceed, dim, num_xyz, degree, num_comp, num_q_comp, num_qpts, create_qdata=False): 92*7b3ff069SJeremy L Thompson """Build element restriction for Cartesian grid 93*7b3ff069SJeremy L Thompson 94*7b3ff069SJeremy L Thompson Args: 95*7b3ff069SJeremy L Thompson ceed: libCEED context 96*7b3ff069SJeremy L Thompson dim: Spatial dimension 97*7b3ff069SJeremy L Thompson num_xyz: Elements per dimension 98*7b3ff069SJeremy L Thompson degree: Polynomial degree 99*7b3ff069SJeremy L Thompson num_comp: Number of components 100*7b3ff069SJeremy L Thompson num_q_comp: Number of quadrature data components 101*7b3ff069SJeremy L Thompson num_qpts: Quadrature points per dimension 102*7b3ff069SJeremy L Thompson build_qdata: Flag to build restriction for quadrature data 103*7b3ff069SJeremy L Thompson 104*7b3ff069SJeremy L Thompson Returns: 105*7b3ff069SJeremy L Thompson tuple: (elem_restriction, size, q_data_restriction, num_elem, elem_qpts) 106*7b3ff069SJeremy L Thompson """ 107*7b3ff069SJeremy L Thompson p = degree + 1 # Nodes per element per dimension 108*7b3ff069SJeremy L Thompson num_nodes = p ** dim 109*7b3ff069SJeremy L Thompson elem_qpts = num_qpts ** dim 110*7b3ff069SJeremy L Thompson 111*7b3ff069SJeremy L Thompson # Calculate grid parameters 112*7b3ff069SJeremy L Thompson nd = [] 113*7b3ff069SJeremy L Thompson num_elem = 1 114*7b3ff069SJeremy L Thompson scalar_size = 1 115*7b3ff069SJeremy L Thompson for d in range(dim): 116*7b3ff069SJeremy L Thompson num_elem *= num_xyz[d] 117*7b3ff069SJeremy L Thompson nd.append(num_xyz[d] * (p - 1) + 1) # Nodes per dimension 118*7b3ff069SJeremy L Thompson scalar_size *= nd[d] 119*7b3ff069SJeremy L Thompson 120*7b3ff069SJeremy L Thompson size = scalar_size * num_comp 121*7b3ff069SJeremy L Thompson 122*7b3ff069SJeremy L Thompson # Create element connectivity 123*7b3ff069SJeremy L Thompson elem_nodes = np.zeros(num_elem * num_nodes, dtype=np.int32) 124*7b3ff069SJeremy L Thompson for e in range(num_elem): 125*7b3ff069SJeremy L Thompson # Get element coordinates 126*7b3ff069SJeremy L Thompson e_xyz = [0] * dim 127*7b3ff069SJeremy L Thompson re = e 128*7b3ff069SJeremy L Thompson for d in range(dim): 129*7b3ff069SJeremy L Thompson e_xyz[d] = re % num_xyz[d] 130*7b3ff069SJeremy L Thompson re //= num_xyz[d] 131*7b3ff069SJeremy L Thompson 132*7b3ff069SJeremy L Thompson # Calculate global node numbers 133*7b3ff069SJeremy L Thompson for n in range(num_nodes): 134*7b3ff069SJeremy L Thompson g_node = 0 135*7b3ff069SJeremy L Thompson g_stride = 1 136*7b3ff069SJeremy L Thompson r_node = n 137*7b3ff069SJeremy L Thompson for d in range(dim): 138*7b3ff069SJeremy L Thompson g_node += (e_xyz[d] * (p - 1) + r_node % p) * g_stride 139*7b3ff069SJeremy L Thompson g_stride *= nd[d] 140*7b3ff069SJeremy L Thompson r_node //= p 141*7b3ff069SJeremy L Thompson elem_nodes[e * num_nodes + n] = g_node 142*7b3ff069SJeremy L Thompson 143*7b3ff069SJeremy L Thompson # Create restrictions 144*7b3ff069SJeremy L Thompson elem_restriction = ceed.ElemRestriction( 145*7b3ff069SJeremy L Thompson num_elem, num_nodes, num_comp, scalar_size, size, elem_nodes) 146*7b3ff069SJeremy L Thompson 147*7b3ff069SJeremy L Thompson q_data_restriction = None 148*7b3ff069SJeremy L Thompson if create_qdata: 149*7b3ff069SJeremy L Thompson strides = np.array([1, elem_qpts, elem_qpts * num_q_comp], dtype=np.int32) 150*7b3ff069SJeremy L Thompson q_data_restriction = ceed.StridedElemRestriction( 151*7b3ff069SJeremy L Thompson num_elem, elem_qpts, num_q_comp, num_elem * elem_qpts * num_q_comp, strides) 152*7b3ff069SJeremy L Thompson 153*7b3ff069SJeremy L Thompson return elem_restriction, size, q_data_restriction, num_elem, elem_qpts 154*7b3ff069SJeremy L Thompson 155*7b3ff069SJeremy L Thompson 156*7b3ff069SJeremy L Thompsondef set_cartesian_mesh_coords(ceed, dim, num_xyz, mesh_degree, mesh_coords): 157*7b3ff069SJeremy L Thompson """Create Cartesian mesh coordinates 158*7b3ff069SJeremy L Thompson 159*7b3ff069SJeremy L Thompson Args: 160*7b3ff069SJeremy L Thompson ceed: libCEED context 161*7b3ff069SJeremy L Thompson dim: Spatial dimension 162*7b3ff069SJeremy L Thompson num_xyz: Elements per dimension 163*7b3ff069SJeremy L Thompson mesh_degree: Mesh polynomial degree 164*7b3ff069SJeremy L Thompson mesh_coords: CeedVector to hold mesh coordinates 165*7b3ff069SJeremy L Thompson 166*7b3ff069SJeremy L Thompson Returns: 167*7b3ff069SJeremy L Thompson Vector: Mesh coordinates 168*7b3ff069SJeremy L Thompson """ 169*7b3ff069SJeremy L Thompson p = mesh_degree + 1 170*7b3ff069SJeremy L Thompson nd = [] 171*7b3ff069SJeremy L Thompson scalar_size = 1 172*7b3ff069SJeremy L Thompson for d in range(dim): 173*7b3ff069SJeremy L Thompson nd.append(num_xyz[d] * (p - 1) + 1) 174*7b3ff069SJeremy L Thompson scalar_size *= nd[d] 175*7b3ff069SJeremy L Thompson 176*7b3ff069SJeremy L Thompson # Get Lobatto nodes (quadrature points) 177*7b3ff069SJeremy L Thompson nodes, _ = ceed.lobatto_quadrature(p) 178*7b3ff069SJeremy L Thompson nodes = 0.5 + 0.5 * nodes # Map from [-1,1] to [0,1] 179*7b3ff069SJeremy L Thompson 180*7b3ff069SJeremy L Thompson # Create coordinates 181*7b3ff069SJeremy L Thompson coords = np.zeros(scalar_size * dim) 182*7b3ff069SJeremy L Thompson for gs_node in range(scalar_size): 183*7b3ff069SJeremy L Thompson r_node = gs_node 184*7b3ff069SJeremy L Thompson for d in range(dim): 185*7b3ff069SJeremy L Thompson d_1d = r_node % nd[d] 186*7b3ff069SJeremy L Thompson elem_id = d_1d // (p - 1) 187*7b3ff069SJeremy L Thompson node_id = d_1d % (p - 1) 188*7b3ff069SJeremy L Thompson coords[gs_node + scalar_size * d] = (elem_id + nodes[node_id]) / num_xyz[d] 189*7b3ff069SJeremy L Thompson r_node //= nd[d] 190*7b3ff069SJeremy L Thompson 191*7b3ff069SJeremy L Thompson mesh_coords.set_array(coords, cmode=libceed.COPY_VALUES) 192*7b3ff069SJeremy L Thompson return mesh_coords 193*7b3ff069SJeremy L Thompson 194*7b3ff069SJeremy L Thompson 195*7b3ff069SJeremy L Thompsondef transform_mesh_coords(dim, mesh_size, mesh_coords, use_sin=True): 196*7b3ff069SJeremy L Thompson """Transform mesh coordinates and return exact surface area 197*7b3ff069SJeremy L Thompson 198*7b3ff069SJeremy L Thompson Args: 199*7b3ff069SJeremy L Thompson dim: Spatial dimension 200*7b3ff069SJeremy L Thompson mesh_size: Total mesh vector size 201*7b3ff069SJeremy L Thompson mesh_coords: Mesh coordinates vector 202*7b3ff069SJeremy L Thompson use_sin: Use sinusoidal transformation 203*7b3ff069SJeremy L Thompson 204*7b3ff069SJeremy L Thompson Returns: 205*7b3ff069SJeremy L Thompson float: Tuple with exact volume and surface area for transformed mesh 206*7b3ff069SJeremy L Thompson """ 207*7b3ff069SJeremy L Thompson exact_volume = {1: 1.0, 2: 3. / 4. * np.pi, 3: 3. / 4. * np.pi}[dim] 208*7b3ff069SJeremy L Thompson exact_area = {1: 2.0, 2: 4.0, 3: 6.0}[dim] 209*7b3ff069SJeremy L Thompson 210*7b3ff069SJeremy L Thompson # Apply transformation to coordinates 211*7b3ff069SJeremy L Thompson num_nodes = mesh_size // dim 212*7b3ff069SJeremy L Thompson with mesh_coords.array_write() as coords: 213*7b3ff069SJeremy L Thompson if dim == 1: 214*7b3ff069SJeremy L Thompson for i in range(num_nodes): 215*7b3ff069SJeremy L Thompson x = coords[i] - 0.5 216*7b3ff069SJeremy L Thompson coords[i] = 0.5 + (1.0 / np.sqrt(3.0)) * np.sin((2.0 / 3.0) * np.pi * x) 217*7b3ff069SJeremy L Thompson else: 218*7b3ff069SJeremy L Thompson if use_sin: 219*7b3ff069SJeremy L Thompson for i in range(num_nodes): 220*7b3ff069SJeremy L Thompson u = 1. + coords[i] 221*7b3ff069SJeremy L Thompson v = np.pi / 2. * coords[i + num_nodes] 222*7b3ff069SJeremy L Thompson coords[i] = u * np.cos(v) 223*7b3ff069SJeremy L Thompson coords[i + num_nodes] = u * np.sin(v) 224*7b3ff069SJeremy L Thompson else: 225*7b3ff069SJeremy L Thompson for i in range(num_nodes): 226*7b3ff069SJeremy L Thompson x = coords[i] - 0.5 227*7b3ff069SJeremy L Thompson coords[i] = 0.5 + (1.0 / np.sqrt(3.0)) * np.sin((2.0 / 3.0) * np.pi * x) 228*7b3ff069SJeremy L Thompson 229*7b3ff069SJeremy L Thompson return (exact_volume, exact_area) 230*7b3ff069SJeremy L Thompson 231*7b3ff069SJeremy L Thompson 232*7b3ff069SJeremy L Thompsondef find_qfs_so(name, path): 233*7b3ff069SJeremy L Thompson """Find the QFunctions shared library. 234*7b3ff069SJeremy L Thompson Returns: 235*7b3ff069SJeremy L Thompson Filepath to shared library object 236*7b3ff069SJeremy L Thompson """ 237*7b3ff069SJeremy L Thompson for root, dirs, files in os.walk(path): 238*7b3ff069SJeremy L Thompson if name in files: 239*7b3ff069SJeremy L Thompson return os.path.join(root, name) 240*7b3ff069SJeremy L Thompson 241*7b3ff069SJeremy L Thompson 242*7b3ff069SJeremy L Thompsondef load_qfs_so(): 243*7b3ff069SJeremy L Thompson """Load the QFunctions shared library. 244*7b3ff069SJeremy L Thompson Returns: 245*7b3ff069SJeremy L Thompson Loaded shared library object 246*7b3ff069SJeremy L Thompson """ 247*7b3ff069SJeremy L Thompson file_dir = os.path.join( 248*7b3ff069SJeremy L Thompson os.path.dirname(os.path.abspath(__file__)), 249*7b3ff069SJeremy L Thompson "build") 250*7b3ff069SJeremy L Thompson qfs_so = find_qfs_so( 251*7b3ff069SJeremy L Thompson "libceed_c_qfunctions" + get_config_var("EXT_SUFFIX"), 252*7b3ff069SJeremy L Thompson file_dir) 253*7b3ff069SJeremy L Thompson 254*7b3ff069SJeremy L Thompson # Load library 255*7b3ff069SJeremy L Thompson return ctypes.cdll.LoadLibrary(qfs_so) 256