xref: /libCEED/examples/python/ex_common.py (revision 7b3ff0698626cc2e5ce463afc10290072fd55c90)
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