xref: /libCEED/examples/python/ex_common.py (revision 9ba83ac0e4b1fca39d6fa6737a318a9f0cbc172d)
1*9ba83ac0SJeremy L Thompson# Copyright (c) 2017-2026, Lawrence Livermore National Security, LLC and other CEED contributors
27b3ff069SJeremy L Thompson# All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
37b3ff069SJeremy L Thompson#
47b3ff069SJeremy L Thompson# SPDX-License-Identifier: BSD-2-Clause
57b3ff069SJeremy L Thompson#
67b3ff069SJeremy L Thompson# This file is part of CEED:  http://github.com/ceed
77b3ff069SJeremy L Thompson
87b3ff069SJeremy L Thompsonimport sys
97b3ff069SJeremy L Thompsonimport os
107b3ff069SJeremy L Thompsonfrom sysconfig import get_config_var
117b3ff069SJeremy L Thompsonimport argparse
127b3ff069SJeremy L Thompsonimport math
137b3ff069SJeremy L Thompsonimport numpy as np
147b3ff069SJeremy L Thompsonimport libceed
157b3ff069SJeremy L Thompsonimport ctypes
167b3ff069SJeremy L Thompson
177b3ff069SJeremy L Thompson
187b3ff069SJeremy L Thompsondef parse_arguments():
197b3ff069SJeremy L Thompson    """Parse command line arguments for surface area computation
207b3ff069SJeremy L Thompson
217b3ff069SJeremy L Thompson    Returns:
227b3ff069SJeremy L Thompson        Namespace: Parsed arguments with fields:
237b3ff069SJeremy L Thompson            ceed: CEED resource specifier
247b3ff069SJeremy L Thompson            dim: Problem dimension (1-3)
257b3ff069SJeremy L Thompson            mesh_degree: Mesh polynomial degree
267b3ff069SJeremy L Thompson            solution_degree: Solution polynomial degree
277b3ff069SJeremy L Thompson            num_qpts: Number of quadrature points
287b3ff069SJeremy L Thompson            problem_size: Approximate problem size
297b3ff069SJeremy L Thompson            test: Test mode flag
307b3ff069SJeremy L Thompson            quiet: Suppress output flag
317b3ff069SJeremy L Thompson            gallery: Use gallery QFunctions flag
327b3ff069SJeremy L Thompson    """
337b3ff069SJeremy L Thompson    parser = argparse.ArgumentParser(description="libCEED surface area example")
347b3ff069SJeremy L Thompson    parser.add_argument("-c", "--ceed", default="/cpu/self",
357b3ff069SJeremy L Thompson                        help="libCEED resource specifier (default: /cpu/self)")
367b3ff069SJeremy L Thompson    parser.add_argument("-d", "--dim", type=int, default=3,
377b3ff069SJeremy L Thompson                        help="Problem dimension (1-3) (default: 3)")
387b3ff069SJeremy L Thompson    parser.add_argument("-m", "--mesh-degree", type=int, default=4,
397b3ff069SJeremy L Thompson                        help="Mesh polynomial degree (default: 4)")
407b3ff069SJeremy L Thompson    parser.add_argument("-p", "--solution-degree", type=int, default=4,
417b3ff069SJeremy L Thompson                        help="Solution polynomial degree (default: 4)")
427b3ff069SJeremy L Thompson    parser.add_argument("-q", "--quadrature-points", type=int, default=6,
437b3ff069SJeremy L Thompson                        help="Number of quadrature points (default: 6)")
447b3ff069SJeremy L Thompson    parser.add_argument("-s", "--problem-size", type=int, default=-1,
457b3ff069SJeremy L Thompson                        help="Approximate problem size (default: ~256k)")
467b3ff069SJeremy L Thompson    parser.add_argument("-t", "--test", action="store_true",
477b3ff069SJeremy L Thompson                        help="Test mode (reduced problem size)")
487b3ff069SJeremy L Thompson    parser.add_argument("--quiet", action="store_true",
497b3ff069SJeremy L Thompson                        help="Suppress output")
507b3ff069SJeremy L Thompson    parser.add_argument("-g", "--gallery", action="store_true",
517b3ff069SJeremy L Thompson                        help="Use gallery QFunctions")
527b3ff069SJeremy L Thompson
537b3ff069SJeremy L Thompson    args = parser.parse_args()
547b3ff069SJeremy L Thompson    if args.dim not in [1, 2, 3]:
557b3ff069SJeremy L Thompson        parser.error("Dimension must be 1, 2, or 3")
567b3ff069SJeremy L Thompson    return args
577b3ff069SJeremy L Thompson
587b3ff069SJeremy L Thompson
597b3ff069SJeremy L Thompsondef get_cartesian_mesh_size(dim, degree, prob_size):
607b3ff069SJeremy L Thompson    """Determine Cartesian mesh size for given problem size
617b3ff069SJeremy L Thompson
627b3ff069SJeremy L Thompson    Args:
637b3ff069SJeremy L Thompson        dim: Spatial dimension (1-3)
647b3ff069SJeremy L Thompson        degree: Polynomial degree
657b3ff069SJeremy L Thompson        prob_size: Target problem size
667b3ff069SJeremy L Thompson
677b3ff069SJeremy L Thompson    Returns:
687b3ff069SJeremy L Thompson        list: Number of elements in each dimension
697b3ff069SJeremy L Thompson    """
707b3ff069SJeremy L Thompson    # Calculate number of elements needed
717b3ff069SJeremy L Thompson    num_elem = prob_size // (degree ** dim)
727b3ff069SJeremy L Thompson
737b3ff069SJeremy L Thompson    # Find smallest power of 2 >= num_elem
747b3ff069SJeremy L Thompson    s = 0
757b3ff069SJeremy L Thompson    while num_elem > 1:
767b3ff069SJeremy L Thompson        num_elem = num_elem / 2
777b3ff069SJeremy L Thompson        s += 1
787b3ff069SJeremy L Thompson
797b3ff069SJeremy L Thompson    # Distribute across dimensions
807b3ff069SJeremy L Thompson    r = s % dim
817b3ff069SJeremy L Thompson    num_xyz = []
827b3ff069SJeremy L Thompson    for d in range(dim):
837b3ff069SJeremy L Thompson        sd = s // dim
847b3ff069SJeremy L Thompson        if r > 0:
857b3ff069SJeremy L Thompson            sd += 1
867b3ff069SJeremy L Thompson            r -= 1
877b3ff069SJeremy L Thompson        num_xyz.append(1 << sd)
887b3ff069SJeremy L Thompson    return num_xyz
897b3ff069SJeremy L Thompson
907b3ff069SJeremy L Thompson
917b3ff069SJeremy L Thompsondef build_cartesian_restriction(ceed, dim, num_xyz, degree, num_comp, num_q_comp, num_qpts, create_qdata=False):
927b3ff069SJeremy L Thompson    """Build element restriction for Cartesian grid
937b3ff069SJeremy L Thompson
947b3ff069SJeremy L Thompson    Args:
957b3ff069SJeremy L Thompson        ceed: libCEED context
967b3ff069SJeremy L Thompson        dim: Spatial dimension
977b3ff069SJeremy L Thompson        num_xyz: Elements per dimension
987b3ff069SJeremy L Thompson        degree: Polynomial degree
997b3ff069SJeremy L Thompson        num_comp: Number of components
1007b3ff069SJeremy L Thompson        num_q_comp: Number of quadrature data components
1017b3ff069SJeremy L Thompson        num_qpts: Quadrature points per dimension
1027b3ff069SJeremy L Thompson        build_qdata: Flag to build restriction for quadrature data
1037b3ff069SJeremy L Thompson
1047b3ff069SJeremy L Thompson    Returns:
1057b3ff069SJeremy L Thompson        tuple: (elem_restriction, size, q_data_restriction, num_elem, elem_qpts)
1067b3ff069SJeremy L Thompson    """
1077b3ff069SJeremy L Thompson    p = degree + 1  # Nodes per element per dimension
1087b3ff069SJeremy L Thompson    num_nodes = p ** dim
1097b3ff069SJeremy L Thompson    elem_qpts = num_qpts ** dim
1107b3ff069SJeremy L Thompson
1117b3ff069SJeremy L Thompson    # Calculate grid parameters
1127b3ff069SJeremy L Thompson    nd = []
1137b3ff069SJeremy L Thompson    num_elem = 1
1147b3ff069SJeremy L Thompson    scalar_size = 1
1157b3ff069SJeremy L Thompson    for d in range(dim):
1167b3ff069SJeremy L Thompson        num_elem *= num_xyz[d]
1177b3ff069SJeremy L Thompson        nd.append(num_xyz[d] * (p - 1) + 1)  # Nodes per dimension
1187b3ff069SJeremy L Thompson        scalar_size *= nd[d]
1197b3ff069SJeremy L Thompson
1207b3ff069SJeremy L Thompson    size = scalar_size * num_comp
1217b3ff069SJeremy L Thompson
1227b3ff069SJeremy L Thompson    # Create element connectivity
1237b3ff069SJeremy L Thompson    elem_nodes = np.zeros(num_elem * num_nodes, dtype=np.int32)
1247b3ff069SJeremy L Thompson    for e in range(num_elem):
1257b3ff069SJeremy L Thompson        # Get element coordinates
1267b3ff069SJeremy L Thompson        e_xyz = [0] * dim
1277b3ff069SJeremy L Thompson        re = e
1287b3ff069SJeremy L Thompson        for d in range(dim):
1297b3ff069SJeremy L Thompson            e_xyz[d] = re % num_xyz[d]
1307b3ff069SJeremy L Thompson            re //= num_xyz[d]
1317b3ff069SJeremy L Thompson
1327b3ff069SJeremy L Thompson        # Calculate global node numbers
1337b3ff069SJeremy L Thompson        for n in range(num_nodes):
1347b3ff069SJeremy L Thompson            g_node = 0
1357b3ff069SJeremy L Thompson            g_stride = 1
1367b3ff069SJeremy L Thompson            r_node = n
1377b3ff069SJeremy L Thompson            for d in range(dim):
1387b3ff069SJeremy L Thompson                g_node += (e_xyz[d] * (p - 1) + r_node % p) * g_stride
1397b3ff069SJeremy L Thompson                g_stride *= nd[d]
1407b3ff069SJeremy L Thompson                r_node //= p
1417b3ff069SJeremy L Thompson            elem_nodes[e * num_nodes + n] = g_node
1427b3ff069SJeremy L Thompson
1437b3ff069SJeremy L Thompson    # Create restrictions
1447b3ff069SJeremy L Thompson    elem_restriction = ceed.ElemRestriction(
1457b3ff069SJeremy L Thompson        num_elem, num_nodes, num_comp, scalar_size, size, elem_nodes)
1467b3ff069SJeremy L Thompson
1477b3ff069SJeremy L Thompson    q_data_restriction = None
1487b3ff069SJeremy L Thompson    if create_qdata:
1497b3ff069SJeremy L Thompson        strides = np.array([1, elem_qpts, elem_qpts * num_q_comp], dtype=np.int32)
1507b3ff069SJeremy L Thompson        q_data_restriction = ceed.StridedElemRestriction(
1517b3ff069SJeremy L Thompson            num_elem, elem_qpts, num_q_comp, num_elem * elem_qpts * num_q_comp, strides)
1527b3ff069SJeremy L Thompson
1537b3ff069SJeremy L Thompson    return elem_restriction, size, q_data_restriction, num_elem, elem_qpts
1547b3ff069SJeremy L Thompson
1557b3ff069SJeremy L Thompson
1567b3ff069SJeremy L Thompsondef set_cartesian_mesh_coords(ceed, dim, num_xyz, mesh_degree, mesh_coords):
1577b3ff069SJeremy L Thompson    """Create Cartesian mesh coordinates
1587b3ff069SJeremy L Thompson
1597b3ff069SJeremy L Thompson    Args:
1607b3ff069SJeremy L Thompson        ceed: libCEED context
1617b3ff069SJeremy L Thompson        dim: Spatial dimension
1627b3ff069SJeremy L Thompson        num_xyz: Elements per dimension
1637b3ff069SJeremy L Thompson        mesh_degree: Mesh polynomial degree
1647b3ff069SJeremy L Thompson        mesh_coords: CeedVector to hold mesh coordinates
1657b3ff069SJeremy L Thompson
1667b3ff069SJeremy L Thompson    Returns:
1677b3ff069SJeremy L Thompson        Vector: Mesh coordinates
1687b3ff069SJeremy L Thompson    """
1697b3ff069SJeremy L Thompson    p = mesh_degree + 1
1707b3ff069SJeremy L Thompson    nd = []
1717b3ff069SJeremy L Thompson    scalar_size = 1
1727b3ff069SJeremy L Thompson    for d in range(dim):
1737b3ff069SJeremy L Thompson        nd.append(num_xyz[d] * (p - 1) + 1)
1747b3ff069SJeremy L Thompson        scalar_size *= nd[d]
1757b3ff069SJeremy L Thompson
1767b3ff069SJeremy L Thompson    # Get Lobatto nodes (quadrature points)
1777b3ff069SJeremy L Thompson    nodes, _ = ceed.lobatto_quadrature(p)
1787b3ff069SJeremy L Thompson    nodes = 0.5 + 0.5 * nodes  # Map from [-1,1] to [0,1]
1797b3ff069SJeremy L Thompson
1807b3ff069SJeremy L Thompson    # Create coordinates
1817b3ff069SJeremy L Thompson    coords = np.zeros(scalar_size * dim)
1827b3ff069SJeremy L Thompson    for gs_node in range(scalar_size):
1837b3ff069SJeremy L Thompson        r_node = gs_node
1847b3ff069SJeremy L Thompson        for d in range(dim):
1857b3ff069SJeremy L Thompson            d_1d = r_node % nd[d]
1867b3ff069SJeremy L Thompson            elem_id = d_1d // (p - 1)
1877b3ff069SJeremy L Thompson            node_id = d_1d % (p - 1)
1887b3ff069SJeremy L Thompson            coords[gs_node + scalar_size * d] = (elem_id + nodes[node_id]) / num_xyz[d]
1897b3ff069SJeremy L Thompson            r_node //= nd[d]
1907b3ff069SJeremy L Thompson
1917b3ff069SJeremy L Thompson    mesh_coords.set_array(coords, cmode=libceed.COPY_VALUES)
1927b3ff069SJeremy L Thompson    return mesh_coords
1937b3ff069SJeremy L Thompson
1947b3ff069SJeremy L Thompson
1957b3ff069SJeremy L Thompsondef transform_mesh_coords(dim, mesh_size, mesh_coords, use_sin=True):
1967b3ff069SJeremy L Thompson    """Transform mesh coordinates and return exact surface area
1977b3ff069SJeremy L Thompson
1987b3ff069SJeremy L Thompson    Args:
1997b3ff069SJeremy L Thompson        dim: Spatial dimension
2007b3ff069SJeremy L Thompson        mesh_size: Total mesh vector size
2017b3ff069SJeremy L Thompson        mesh_coords: Mesh coordinates vector
2027b3ff069SJeremy L Thompson        use_sin: Use sinusoidal transformation
2037b3ff069SJeremy L Thompson
2047b3ff069SJeremy L Thompson    Returns:
2057b3ff069SJeremy L Thompson        float: Tuple with exact volume and surface area for transformed mesh
2067b3ff069SJeremy L Thompson    """
2077b3ff069SJeremy L Thompson    exact_volume = {1: 1.0, 2: 3. / 4. * np.pi, 3: 3. / 4. * np.pi}[dim]
2087b3ff069SJeremy L Thompson    exact_area = {1: 2.0, 2: 4.0, 3: 6.0}[dim]
2097b3ff069SJeremy L Thompson
2107b3ff069SJeremy L Thompson    # Apply transformation to coordinates
2117b3ff069SJeremy L Thompson    num_nodes = mesh_size // dim
2127b3ff069SJeremy L Thompson    with mesh_coords.array_write() as coords:
2137b3ff069SJeremy L Thompson        if dim == 1:
2147b3ff069SJeremy L Thompson            for i in range(num_nodes):
2157b3ff069SJeremy L Thompson                x = coords[i] - 0.5
2167b3ff069SJeremy L Thompson                coords[i] = 0.5 + (1.0 / np.sqrt(3.0)) * np.sin((2.0 / 3.0) * np.pi * x)
2177b3ff069SJeremy L Thompson        else:
2187b3ff069SJeremy L Thompson            if use_sin:
2197b3ff069SJeremy L Thompson                for i in range(num_nodes):
2207b3ff069SJeremy L Thompson                    u = 1. + coords[i]
2217b3ff069SJeremy L Thompson                    v = np.pi / 2. * coords[i + num_nodes]
2227b3ff069SJeremy L Thompson                    coords[i] = u * np.cos(v)
2237b3ff069SJeremy L Thompson                    coords[i + num_nodes] = u * np.sin(v)
2247b3ff069SJeremy L Thompson            else:
2257b3ff069SJeremy L Thompson                for i in range(num_nodes):
2267b3ff069SJeremy L Thompson                    x = coords[i] - 0.5
2277b3ff069SJeremy L Thompson                    coords[i] = 0.5 + (1.0 / np.sqrt(3.0)) * np.sin((2.0 / 3.0) * np.pi * x)
2287b3ff069SJeremy L Thompson
2297b3ff069SJeremy L Thompson    return (exact_volume, exact_area)
2307b3ff069SJeremy L Thompson
2317b3ff069SJeremy L Thompson
2327b3ff069SJeremy L Thompsondef find_qfs_so(name, path):
2337b3ff069SJeremy L Thompson    """Find the QFunctions shared library.
2347b3ff069SJeremy L Thompson    Returns:
2357b3ff069SJeremy L Thompson        Filepath to shared library object
2367b3ff069SJeremy L Thompson    """
2377b3ff069SJeremy L Thompson    for root, dirs, files in os.walk(path):
2387b3ff069SJeremy L Thompson        if name in files:
2397b3ff069SJeremy L Thompson            return os.path.join(root, name)
2407b3ff069SJeremy L Thompson
2417b3ff069SJeremy L Thompson
2427b3ff069SJeremy L Thompsondef load_qfs_so():
2437b3ff069SJeremy L Thompson    """Load the QFunctions shared library.
2447b3ff069SJeremy L Thompson    Returns:
2457b3ff069SJeremy L Thompson        Loaded shared library object
2467b3ff069SJeremy L Thompson    """
2477b3ff069SJeremy L Thompson    file_dir = os.path.join(
2487b3ff069SJeremy L Thompson        os.path.dirname(os.path.abspath(__file__)),
2497b3ff069SJeremy L Thompson        "build")
2507b3ff069SJeremy L Thompson    qfs_so = find_qfs_so(
2517b3ff069SJeremy L Thompson        "libceed_c_qfunctions" + get_config_var("EXT_SUFFIX"),
2527b3ff069SJeremy L Thompson        file_dir)
2537b3ff069SJeremy L Thompson
2547b3ff069SJeremy L Thompson    # Load library
2557b3ff069SJeremy L Thompson    return ctypes.cdll.LoadLibrary(qfs_so)
256