From 6a41dc57a25644f889f41c136c2c47967a88dfa0 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:09:39 +0100 Subject: [PATCH 1/9] Format, still some line too long errors? --- python/demo/conftest.py | 11 +- python/demo/demo_axis/demo_axis.py | 116 +- python/demo/demo_axis/mesh_sphere_axis.py | 19 +- python/demo/demo_biharmonic.py | 35 +- python/demo/demo_cahn-hilliard.py | 5 +- python/demo/demo_elasticity.py | 37 +- python/demo/demo_gmsh.py | 13 +- python/demo/demo_helmholtz.py | 8 +- python/demo/demo_interpolation-io.py | 4 +- python/demo/demo_lagrange_variants.py | 34 +- python/demo/demo_mixed-poisson.py | 8 +- python/demo/demo_navier-stokes.py | 99 +- python/demo/demo_pml/demo_pml.py | 80 +- python/demo/demo_pml/efficiencies_pml_demo.py | 10 +- python/demo/demo_pml/mesh_wire_pml.py | 56 +- python/demo/demo_poisson.py | 18 +- python/demo/demo_pyvista.py | 83 +- .../analytical_efficiencies_wire.py | 9 +- .../demo_scattering_boundary_conditions.py | 51 +- .../mesh_wire.py | 15 +- python/demo/demo_static-condensation.py | 36 +- python/demo/demo_stokes.py | 45 +- python/demo/demo_tnt-elements.py | 55 +- python/demo/demo_types.py | 42 +- .../demo/demo_waveguide/analytical_modes.py | 21 +- .../demo_half_loaded_waveguide.py | 11 +- python/demo/test.py | 2 +- python/doc/source/conf.py | 3 +- python/doc/source/jupytext_process.py | 4 +- python/dolfinx/__init__.py | 38 +- python/dolfinx/common.py | 4 +- python/dolfinx/fem/__init__.py | 73 +- python/dolfinx/fem/assemble.py | 72 +- python/dolfinx/fem/bcs.py | 42 +- python/dolfinx/fem/element.py | 25 +- python/dolfinx/fem/forms.py | 103 +- python/dolfinx/fem/function.py | 170 +- python/dolfinx/fem/petsc.py | 391 ++- python/dolfinx/geometry.py | 65 +- python/dolfinx/io/__init__.py | 4 +- python/dolfinx/io/gmshio.py | 102 +- python/dolfinx/io/utils.py | 83 +- python/dolfinx/jit.py | 35 +- python/dolfinx/la.py | 95 +- python/dolfinx/mesh.py | 264 +- python/dolfinx/nls/petsc.py | 3 +- python/dolfinx/pkgconfig.py | 19 +- python/dolfinx/plot.py | 35 +- python/dolfinx/utils.py | 79 +- python/dolfinx/wrappers/__init__.py | 1 + python/pyproject.toml | 33 +- python/test/conftest.py | 8 +- python/test/unit/common/test_index_map.py | 24 +- python/test/unit/common/test_public_api.py | 8 +- python/test/unit/common/test_version.py | 6 +- python/test/unit/fem/test_assemble_domains.py | 56 +- python/test/unit/fem/test_assemble_submesh.py | 36 +- python/test/unit/fem/test_assembler.py | 177 +- python/test/unit/fem/test_bcs.py | 126 +- .../test/unit/fem/test_complex_assembler.py | 1 + python/test/unit/fem/test_custom_assembler.py | 78 +- .../unit/fem/test_custom_basix_element.py | 246 +- .../test/unit/fem/test_custom_jit_kernels.py | 36 +- .../test/unit/fem/test_discrete_operators.py | 150 +- python/test/unit/fem/test_dof_permuting.py | 184 +- python/test/unit/fem/test_dofmap.py | 214 +- .../test/unit/fem/test_element_integrals.py | 193 +- python/test/unit/fem/test_expression.py | 8 +- python/test/unit/fem/test_fem_pipeline.py | 204 +- python/test/unit/fem/test_forms.py | 9 +- python/test/unit/fem/test_function.py | 20 +- python/test/unit/fem/test_function_space.py | 41 +- .../test/unit/fem/test_ghost_mesh_assembly.py | 50 +- python/test/unit/fem/test_interpolation.py | 401 ++- python/test/unit/fem/test_mixed_element.py | 15 +- .../test/unit/fem/test_nonlinear_assembler.py | 185 +- .../unit/fem/test_petsc_discrete_operators.py | 99 +- .../test/unit/fem/test_quadrature_elements.py | 22 +- .../test/unit/fem/test_special_functions.py | 18 +- python/test/unit/fem/test_symmetry.py | 66 +- python/test/unit/fem/test_vector_function.py | 24 +- .../unit/geometry/test_bounding_box_tree.py | 85 +- python/test/unit/geometry/test_gjk.py | 60 +- python/test/unit/graph/test_adjacencylist.py | 4 +- python/test/unit/io/test_adios2.py | 29 +- python/test/unit/io/test_vtk.py | 263 +- python/test/unit/io/test_xdmf_function.py | 12 +- python/test/unit/io/test_xdmf_mesh.py | 46 +- python/test/unit/io/test_xdmf_meshdata.py | 4 +- python/test/unit/io/test_xdmf_meshtags.py | 11 +- python/test/unit/la/test_krylov_solver.py | 6 +- python/test/unit/la/test_matrix_csr.py | 20 +- python/test/unit/la/test_nullspace.py | 36 +- python/test/unit/la/test_sparsity_pattern.py | 7 +- python/test/unit/la/test_vector_scatter.py | 14 +- python/test/unit/mesh/test_cell.py | 4 +- python/test/unit/mesh/test_dual_graph.py | 3 +- python/test/unit/mesh/test_face.py | 4 +- python/test/unit/mesh/test_ghost_mesh.py | 37 +- .../test/unit/mesh/test_higher_order_mesh.py | 2217 +++++++++++++++-- .../unit/mesh/test_manifold_point_search.py | 11 +- python/test/unit/mesh/test_mesh.py | 161 +- .../test/unit/mesh/test_mesh_partitioners.py | 46 +- python/test/unit/mesh/test_mixed_topology.py | 30 +- python/test/unit/mesh/test_refinement.py | 102 +- python/test/unit/nls/test_newton.py | 40 +- 106 files changed, 6527 insertions(+), 2096 deletions(-) diff --git a/python/demo/conftest.py b/python/demo/conftest.py index da9450b3306..4931db37f48 100644 --- a/python/demo/conftest.py +++ b/python/demo/conftest.py @@ -2,10 +2,13 @@ def pytest_addoption(parser): - parser.addoption("--mpiexec", action="store", default="mpirun", - help="Name of program to run MPI, e.g. mpiexec") - parser.addoption("--num-proc", action="store", default=1, - help="Number of MPI processes to use") + parser.addoption( + "--mpiexec", + action="store", + default="mpirun", + help="Name of program to run MPI, e.g. mpiexec", + ) + parser.addoption("--num-proc", action="store", default=1, help="Number of MPI processes to use") @pytest.fixture diff --git a/python/demo/demo_axis/demo_axis.py b/python/demo/demo_axis/demo_axis.py index 171ec4a9061..b811548baeb 100644 --- a/python/demo/demo_axis/demo_axis.py +++ b/python/demo/demo_axis/demo_axis.py @@ -30,6 +30,7 @@ try: from dolfinx.io import VTXWriter + has_vtx = True except ImportError: print("VTXWriter not available, solution won't be saved") @@ -43,6 +44,7 @@ try: import pyvista + have_pyvista = True except ModuleNotFoundError: print("pyvista and pyvistaqt are required to visualise the solution") @@ -209,22 +211,38 @@ def curl_axis(a, m: int, rho): # representing the $m$-th order Bessel function of first kind and # $J_{m}^{\prime}$ its derivative. We implement these functions: + # + def background_field_rz(theta: float, n_bkg: float, k0: float, m: int, x): k = k0 * n_bkg - a_r = (np.cos(theta) * np.exp(1j * k * x[1] * np.cos(theta)) - * (1j)**(-m + 1) * jvp(m, k * x[0] * np.sin(theta), 1)) - a_z = (np.sin(theta) * np.exp(1j * k * x[1] * np.cos(theta)) - * (1j)**-m * jv(m, k * x[0] * np.sin(theta))) + a_r = ( + np.cos(theta) + * np.exp(1j * k * x[1] * np.cos(theta)) + * (1j) ** (-m + 1) + * jvp(m, k * x[0] * np.sin(theta), 1) + ) + a_z = ( + np.sin(theta) + * np.exp(1j * k * x[1] * np.cos(theta)) + * (1j) ** -m + * jv(m, k * x[0] * np.sin(theta)) + ) return (a_r, a_z) def background_field_p(theta: float, n_bkg: float, k0: float, m: int, x): k = k0 * n_bkg - a_p = (np.cos(theta) / (k * x[0] * np.sin(theta)) - * np.exp(1j * k * x[1] * np.cos(theta)) * m - * (1j)**(-m) * jv(m, k * x[0] * np.sin(theta))) + a_p = ( + np.cos(theta) + / (k * x[0] * np.sin(theta)) + * np.exp(1j * k * x[1] * np.cos(theta)) + * m + * (1j) ** (-m) + * jv(m, k * x[0] * np.sin(theta)) + ) return a_p + + # - # PML can be implemented in a spherical shell surrounding the background @@ -284,21 +302,21 @@ def background_field_p(theta: float, n_bkg: float, k0: float, m: int, x): def pml_coordinate(x, r, alpha: float, k0: float, radius_dom: float, radius_pml: float): - return (x + 1j * alpha / k0 * x * (r - radius_dom) / (radius_pml * r)) + return x + 1j * alpha / k0 * x * (r - radius_dom) / (radius_pml * r) def create_eps_mu(pml, rho, eps_bkg, mu_bkg): J = ufl.grad(pml) # Transform the 2x2 Jacobian into a 3x3 matrix. - J = ufl.as_matrix(((J[0, 0], J[0, 1], 0), - (J[1, 0], J[1, 1], 0), - (0, 0, pml[0] / rho))) + J = ufl.as_matrix(((J[0, 0], J[0, 1], 0), (J[1, 0], J[1, 1], 0), (0, 0, pml[0] / rho))) A = ufl.inv(J) eps_pml = ufl.det(J) * A * eps_bkg * ufl.transpose(A) mu_pml = ufl.det(J) * A * mu_bkg * ufl.transpose(A) return eps_pml, mu_pml + + # - # We can now define some constants and geometrical parameters, and then @@ -342,9 +360,19 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): if MPI.COMM_WORLD.rank == 0: # Mesh generation model = generate_mesh_sphere_axis( - radius_sph, radius_scatt, radius_dom, radius_pml, - in_sph_size, on_sph_size, scatt_size, pml_size, - au_tag, bkg_tag, pml_tag, scatt_tag) + radius_sph, + radius_scatt, + radius_dom, + radius_pml, + in_sph_size, + on_sph_size, + scatt_size, + pml_size, + au_tag, + bkg_tag, + pml_tag, + scatt_tag, + ) model = MPI.COMM_WORLD.bcast(model, root=0) msh, cell_tags, facet_tags = io.gmshio.model_to_mesh(model, MPI.COMM_WORLD, 0, gdim=2) @@ -383,7 +411,7 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # + # Measures for subdomains -dx = ufl.Measure("dx", msh, subdomain_data=cell_tags, metadata={'quadrature_degree': 5}) +dx = ufl.Measure("dx", msh, subdomain_data=cell_tags, metadata={"quadrature_degree": 5}) dDom = dx((au_tag, bkg_tag)) dPml = dx(pml_tag) # - @@ -446,8 +474,12 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): alpha = 5 r = ufl.sqrt(rho**2 + z**2) -pml_coords = ufl.as_vector((pml_coordinate(rho, r, alpha, k0, radius_dom, radius_pml), - pml_coordinate(z, r, alpha, k0, radius_dom, radius_pml))) +pml_coords = ufl.as_vector( + ( + pml_coordinate(rho, r, alpha, k0, radius_dom, radius_pml), + pml_coordinate(z, r, alpha, k0, radius_dom, radius_pml), + ) +) eps_pml, mu_pml = create_eps_mu(pml_coords, rho, eps_bkg, 1) # - @@ -468,11 +500,11 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # Marker functions for the scattering efficiency integral marker = fem.Function(D) scatt_facets = facet_tags.find(scatt_tag) -incident_cells = mesh.compute_incident_entities(msh.topology, scatt_facets, - msh.topology.dim - 1, - msh.topology.dim) +incident_cells = mesh.compute_incident_entities( + msh.topology, scatt_facets, msh.topology.dim - 1, msh.topology.dim +) midpoints = mesh.compute_midpoints(msh, msh.topology.dim, incident_cells) -inner_cells = incident_cells[(midpoints[:, 0]**2 + midpoints[:, 1]**2) < (radius_scatt)**2] +inner_cells = incident_cells[(midpoints[:, 0] ** 2 + midpoints[:, 1] ** 2) < (radius_scatt) ** 2] marker.x.array[inner_cells] = 1 # Define integration domain for the gold sphere @@ -523,11 +555,13 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): curl_Es_m = curl_axis(Es_m, m, rho) curl_v_m = curl_axis(v_m, m, rho) - F = - ufl.inner(curl_Es_m, curl_v_m) * rho * dDom \ - + eps * k0 ** 2 * ufl.inner(Es_m, v_m) * rho * dDom \ - + k0 ** 2 * (eps - eps_bkg) * ufl.inner(Eb_m, v_m) * rho * dDom \ - - ufl.inner(ufl.inv(mu_pml) * curl_Es_m, curl_v_m) * rho * dPml \ - + k0 ** 2 * ufl.inner(eps_pml * Es_m, v_m) * rho * dPml + F = ( + -ufl.inner(curl_Es_m, curl_v_m) * rho * dDom + + eps * k0**2 * ufl.inner(Es_m, v_m) * rho * dDom + + k0**2 * (eps - eps_bkg) * ufl.inner(Eb_m, v_m) * rho * dDom + - ufl.inner(ufl.inv(mu_pml) * curl_Es_m, curl_v_m) * rho * dPml + + k0**2 * ufl.inner(eps_pml * Es_m, v_m) * rho * dPml + ) a, L = ufl.lhs(F), ufl.rhs(F) problem = LinearProblem(a, L, bcs=[], petsc_options={"ksp_type": "preonly", "pc_type": "lu"}) @@ -539,13 +573,13 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # Total electric field Eh_m.x.array[:] = Eb_m.x.array[:] + Esh_m.x.array[:] -# We can add our solution to the total scattered field, which also -# includes the transformation to the $\phi$ plane: + # We can add our solution to the total scattered field, which also + # includes the transformation to the $\phi$ plane: phase.value = np.exp(-1j * m * phi) - rotate_to_phi = ufl.as_vector((phase + ufl.conj(phase), - phase + ufl.conj(phase), - phase - ufl.conj(phase))) + rotate_to_phi = ufl.as_vector( + (phase + ufl.conj(phase), phase + ufl.conj(phase), phase - ufl.conj(phase)) + ) if m == 0: # initialize and do not transform Esh = Esh_m @@ -554,30 +588,36 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): else: # transform Esh += ufl.elem_mult(Esh_m, rotate_to_phi) -# To check that our calculations are correct, we can use as reference -# quantities the absorption and scattering efficiencies: + # To check that our calculations are correct, we can use as reference + # quantities the absorption and scattering efficiencies: -# + + # + # Efficiencies calculation if m == 0: # initialize and do not add 2 factor P = np.pi * ufl.inner(-ufl.cross(Esh_m, ufl.conj(Hsh_m)), n_3d) * marker Q = np.pi * eps_au.imag * k0 * (ufl.inner(Eh_m, Eh_m)) / Z0 q_abs_fenics_proc = (fem.assemble_scalar(fem.form(Q * rho * dAu)) / gcs / I0).real - q_sca_fenics_proc = (fem.assemble_scalar(fem.form((P('+') + P('-')) * rho * dS(scatt_tag))) / gcs / I0).real + q_sca_fenics_proc = ( + fem.assemble_scalar(fem.form((P("+") + P("-")) * rho * dS(scatt_tag))) / gcs / I0 + ).real q_abs_fenics = msh.comm.allreduce(q_abs_fenics_proc, op=MPI.SUM) q_sca_fenics = msh.comm.allreduce(q_sca_fenics_proc, op=MPI.SUM) elif m == m_list[0]: # initialize and add 2 factor P = 2 * np.pi * ufl.inner(-ufl.cross(Esh_m, ufl.conj(Hsh_m)), n_3d) * marker Q = 2 * np.pi * eps_au.imag * k0 * (ufl.inner(Eh_m, Eh_m)) / Z0 / n_bkg q_abs_fenics_proc = (fem.assemble_scalar(fem.form(Q * rho * dAu)) / gcs / I0).real - q_sca_fenics_proc = (fem.assemble_scalar(fem.form((P('+') + P('-')) * rho * dS(scatt_tag))) / gcs / I0).real + q_sca_fenics_proc = ( + fem.assemble_scalar(fem.form((P("+") + P("-")) * rho * dS(scatt_tag))) / gcs / I0 + ).real q_abs_fenics = msh.comm.allreduce(q_abs_fenics_proc, op=MPI.SUM) q_sca_fenics = msh.comm.allreduce(q_sca_fenics_proc, op=MPI.SUM) else: # do not initialize and add 2 factor P = 2 * np.pi * ufl.inner(-ufl.cross(Esh_m, ufl.conj(Hsh_m)), n_3d) * marker Q = 2 * np.pi * eps_au.imag * k0 * (ufl.inner(Eh_m, Eh_m)) / Z0 / n_bkg q_abs_fenics_proc = (fem.assemble_scalar(fem.form(Q * rho * dAu)) / gcs / I0).real - q_sca_fenics_proc = (fem.assemble_scalar(fem.form((P('+') + P('-')) * rho * dS(scatt_tag))) / gcs / I0).real + q_sca_fenics_proc = ( + fem.assemble_scalar(fem.form((P("+") + P("-")) * rho * dS(scatt_tag))) / gcs / I0 + ).real q_abs_fenics += msh.comm.allreduce(q_abs_fenics_proc, op=MPI.SUM) q_sca_fenics += msh.comm.allreduce(q_sca_fenics_proc, op=MPI.SUM) @@ -637,7 +677,7 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # assert err_ext < 0.01 if has_vtx: - v_dg_el = element("DG", msh.basix_cell(), degree, shape=(3, )) + v_dg_el = element("DG", msh.basix_cell(), degree, shape=(3,)) W = fem.functionspace(msh, v_dg_el) Es_dg = fem.Function(W) Es_expr = fem.Expression(Esh, W.element.interpolation_points()) diff --git a/python/demo/demo_axis/mesh_sphere_axis.py b/python/demo/demo_axis/mesh_sphere_axis.py index 5cffce15258..45e8e39ee00 100644 --- a/python/demo/demo_axis/mesh_sphere_axis.py +++ b/python/demo/demo_axis/mesh_sphere_axis.py @@ -9,11 +9,20 @@ from numpy import pi -def generate_mesh_sphere_axis(radius_sph: float, radius_scatt: float, radius_dom: float, - radius_pml: float, in_sph_size: float, on_sph_size: float, - scatt_size: float, pml_size: float, au_tag: int, bkg_tag: int, - pml_tag: int, scatt_tag: int): - +def generate_mesh_sphere_axis( + radius_sph: float, + radius_scatt: float, + radius_dom: float, + radius_pml: float, + in_sph_size: float, + on_sph_size: float, + scatt_size: float, + pml_size: float, + au_tag: int, + bkg_tag: int, + pml_tag: int, + scatt_tag: int, +): gmsh.model.add("geometry") gmsh.model.occ.addCircle(0, 0, 0, radius_sph * 0.5, angle1=-pi / 2, angle2=pi / 2, tag=1) diff --git a/python/demo/demo_biharmonic.py b/python/demo/demo_biharmonic.py index 44fc31b087c..ea6ba2d6568 100644 --- a/python/demo/demo_biharmonic.py +++ b/python/demo/demo_biharmonic.py @@ -130,10 +130,13 @@ # finite element {py:class}`FunctionSpace ` # $V$ on the mesh. -msh = mesh.create_rectangle(comm=MPI.COMM_WORLD, - points=((0.0, 0.0), (1.0, 1.0)), n=(32, 32), - cell_type=CellType.triangle, - ghost_mode=GhostMode.shared_facet) +msh = mesh.create_rectangle( + comm=MPI.COMM_WORLD, + points=((0.0, 0.0), (1.0, 1.0)), + n=(32, 32), + cell_type=CellType.triangle, + ghost_mode=GhostMode.shared_facet, +) V = fem.functionspace(msh, ("Lagrange", 2)) # The second argument to {py:func}`functionspace @@ -149,12 +152,13 @@ # function that returns `True` for points `x` on the boundary and # `False` otherwise. -facets = mesh.locate_entities_boundary(msh, dim=1, - marker=lambda x: np.logical_or.reduce(( - np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0), - np.isclose(x[1], 0.0), - np.isclose(x[1], 1.0)))) +facets = mesh.locate_entities_boundary( + msh, + dim=1, + marker=lambda x: np.logical_or.reduce( + (np.isclose(x[0], 0.0), np.isclose(x[0], 1.0), np.isclose(x[1], 0.0), np.isclose(x[1], 1.0)) + ), +) # We now find the degrees-of-freedom that are associated with the # boundary facets using {py:func}`locate_dofs_topological @@ -183,7 +187,7 @@ alpha = ScalarType(8.0) h = CellDiameter(msh) n = FacetNormal(msh) -h_avg = (h('+') + h('-')) / 2.0 +h_avg = (h("+") + h("-")) / 2.0 # After that, we can define the variational problem consisting of the bilinear # form $a$ and the linear form $L$. The source term is prescribed as @@ -201,10 +205,12 @@ x = ufl.SpatialCoordinate(msh) f = 4.0 * pi**4 * sin(pi * x[0]) * sin(pi * x[1]) -a = inner(div(grad(u)), div(grad(v))) * dx \ - - inner(avg(div(grad(u))), jump(grad(v), n)) * dS \ - - inner(jump(grad(u), n), avg(div(grad(v)))) * dS \ +a = ( + inner(div(grad(u)), div(grad(v))) * dx + - inner(avg(div(grad(u))), jump(grad(v), n)) * dS + - inner(jump(grad(u), n), avg(div(grad(v)))) * dS + alpha / h_avg * inner(jump(grad(u), n), jump(grad(v), n)) * dS +) L = inner(f, v) * dx # - @@ -232,6 +238,7 @@ # + try: import pyvista + cells, types, x = plot.vtk_mesh(V) grid = pyvista.UnstructuredGrid(cells, types, x) grid.point_data["u"] = uh.x.array.real diff --git a/python/demo/demo_cahn-hilliard.py b/python/demo/demo_cahn-hilliard.py index 055af00fe80..da23dcbb77b 100644 --- a/python/demo/demo_cahn-hilliard.py +++ b/python/demo/demo_cahn-hilliard.py @@ -139,6 +139,7 @@ try: import pyvista as pv import pyvistaqt as pvqt + have_pyvista = True if pv.OFF_SCREEN: pv.start_xvfb(wait=0.5) @@ -221,7 +222,7 @@ # Compute the chemical potential df/dc c = ufl.variable(c) -f = 100 * c**2 * (1 - c)**2 +f = 100 * c**2 * (1 - c) ** 2 dfdc = ufl.diff(f, c) # The first line declares that `c` is a variable that some function can @@ -319,7 +320,7 @@ c = u.sub(0) u0.x.array[:] = u.x.array -while (t < T): +while t < T: t += dt r = solver.solve(u) print(f"Step {int(t / dt)}: num iterations: {r[0]}") diff --git a/python/demo/demo_elasticity.py b/python/demo/demo_elasticity.py index 3376b81bf53..0d7158d97d5 100644 --- a/python/demo/demo_elasticity.py +++ b/python/demo/demo_elasticity.py @@ -31,7 +31,15 @@ import dolfinx import ufl from dolfinx import la -from dolfinx.fem import Expression, Function, FunctionSpace, dirichletbc, form, functionspace, locate_dofs_topological +from dolfinx.fem import ( + Expression, + Function, + FunctionSpace, + dirichletbc, + form, + functionspace, + locate_dofs_topological, +) from dolfinx.fem.petsc import apply_lifting, assemble_matrix, assemble_vector, set_bc from dolfinx.io import XDMFFile from dolfinx.mesh import CellType, GhostMode, create_box, locate_entities_boundary @@ -81,7 +89,9 @@ def build_nullspace(V: FunctionSpace): dolfinx.cpp.la.orthonormalize(_basis) assert dolfinx.cpp.la.is_orthonormal(_basis) - basis_petsc = [PETSc.Vec().createWithArray(x[:bs * length0], bsize=3, comm=V.mesh.comm) for x in b] # type: ignore + basis_petsc = [ + PETSc.Vec().createWithArray(x[: bs * length0], bsize=3, comm=V.mesh.comm) for x in b + ] # type: ignore return PETSc.NullSpace().create(vectors=basis_petsc) # type: ignore @@ -90,9 +100,13 @@ def build_nullspace(V: FunctionSpace): # Create a box Mesh: -msh = create_box(MPI.COMM_WORLD, [np.array([0.0, 0.0, 0.0]), - np.array([2.0, 1.0, 1.0])], [16, 16, 16], - CellType.tetrahedron, ghost_mode=GhostMode.shared_facet) +msh = create_box( + MPI.COMM_WORLD, + [np.array([0.0, 0.0, 0.0]), np.array([2.0, 1.0, 1.0])], + [16, 16, 16], + CellType.tetrahedron, + ghost_mode=GhostMode.shared_facet, +) # Create a centripetal source term $f = \rho \omega^2 [x_0, \, x_1]$: @@ -113,6 +127,8 @@ def build_nullspace(V: FunctionSpace): def σ(v): """Return an expression for the stress σ given a displacement field""" return 2.0 * μ * ufl.sym(grad(v)) + λ * ufl.tr(ufl.sym(grad(v))) * ufl.Identity(len(v)) + + # - # A function space space is created and the elasticity variational @@ -128,11 +144,12 @@ def σ(v): # $x_1 = 1$ by finding all facets on these boundaries, and then creating # a Dirichlet boundary condition object. -facets = locate_entities_boundary(msh, dim=2, - marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[1], 1.0))) -bc = dirichletbc(np.zeros(3, dtype=dtype), - locate_dofs_topological(V, entity_dim=2, entities=facets), V=V) +facets = locate_entities_boundary( + msh, dim=2, marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[1], 1.0)) +) +bc = dirichletbc( + np.zeros(3, dtype=dtype), locate_dofs_topological(V, entity_dim=2, entities=facets), V=V +) # ## Assemble and solve # diff --git a/python/demo/demo_gmsh.py b/python/demo/demo_gmsh.py index 921dccb616f..7642b1e7426 100644 --- a/python/demo/demo_gmsh.py +++ b/python/demo/demo_gmsh.py @@ -26,6 +26,7 @@ import gmsh # type: ignore except ImportError: import sys + print("This demo requires gmsh to be installed") sys.exit(0) # - @@ -143,6 +144,8 @@ def gmsh_ring(model: gmsh.model, name: str) -> gmsh.model: model.addPhysicalGroup(3, volume_entities, tag=1) model.setPhysicalName(3, 1, "Mesh volume") return model + + # - # ## DOLFINx mesh creation and file output @@ -172,8 +175,14 @@ def create_mesh(comm: MPI.Comm, model: gmsh.model, name: str, filename: str, mod with XDMFFile(msh.comm, filename, mode) as file: msh.topology.create_connectivity(2, 3) file.write_mesh(msh) - file.write_meshtags(ct, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry") - file.write_meshtags(ft, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry") + file.write_meshtags( + ct, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry" + ) + file.write_meshtags( + ft, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry" + ) + + # - # ## Generate meshes diff --git a/python/demo/demo_helmholtz.py b/python/demo/demo_helmholtz.py index edc27355912..03b861d1049 100644 --- a/python/demo/demo_helmholtz.py +++ b/python/demo/demo_helmholtz.py @@ -66,7 +66,9 @@ problem.solve() # Save solution in XDMF format (to be viewed in ParaView, for example) -with XDMFFile(MPI.COMM_WORLD, "out_helmholtz/plane_wave.xdmf", "w", encoding=XDMFFile.Encoding.HDF5) as file: +with XDMFFile( + MPI.COMM_WORLD, "out_helmholtz/plane_wave.xdmf", "w", encoding=XDMFFile.Encoding.HDF5 +) as file: file.write_mesh(msh) file.write_function(uh) # - @@ -84,7 +86,9 @@ # H1 errors diff = uh - u_exact H1_diff = msh.comm.allreduce(assemble_scalar(form(inner(grad(diff), grad(diff)) * dx)), op=MPI.SUM) -H1_exact = msh.comm.allreduce(assemble_scalar(form(inner(grad(u_exact), grad(u_exact)) * dx)), op=MPI.SUM) +H1_exact = msh.comm.allreduce( + assemble_scalar(form(inner(grad(u_exact), grad(u_exact)) * dx)), op=MPI.SUM +) print("Relative H1 error of FEM solution:", abs(np.sqrt(H1_diff) / np.sqrt(H1_exact))) # L2 errors diff --git a/python/demo/demo_interpolation-io.py b/python/demo/demo_interpolation-io.py index 623698bed1d..b91aa15c4a5 100644 --- a/python/demo/demo_interpolation-io.py +++ b/python/demo/demo_interpolation-io.py @@ -66,6 +66,7 @@ try: from dolfinx.io import VTXWriter + with VTXWriter(msh.comm, "output_nedelec.bp", u0, "bp4") as f: f.write(0.0) except ImportError: @@ -76,10 +77,11 @@ try: import pyvista + cells, types, x = plot.vtk_mesh(V0) grid = pyvista.UnstructuredGrid(cells, types, x) values = np.zeros((x.shape[0], 3), dtype=np.float64) - values[:, :msh.topology.dim] = u0.x.array.reshape(x.shape[0], msh.topology.dim).real + values[:, : msh.topology.dim] = u0.x.array.reshape(x.shape[0], msh.topology.dim).real grid.point_data["u"] = values pl = pyvista.Plotter(shape=(2, 2)) diff --git a/python/demo/demo_lagrange_variants.py b/python/demo/demo_lagrange_variants.py index f7e375b0430..f6d762863ed 100644 --- a/python/demo/demo_lagrange_variants.py +++ b/python/demo/demo_lagrange_variants.py @@ -54,8 +54,9 @@ # array. # + -element = basix.create_element(basix.ElementFamily.P, basix.CellType.interval, 10, - basix.LagrangeVariant.equispaced) +element = basix.create_element( + basix.ElementFamily.P, basix.CellType.interval, 10, basix.LagrangeVariant.equispaced +) lattice = basix.create_lattice(basix.CellType.interval, 200, basix.LatticeType.equispaced, True) values = element.tabulate(0, lattice)[0, :, :, 0] if MPI.COMM_WORLD.size == 1: @@ -82,8 +83,9 @@ # to define the basis functions. # + -element = basix.create_element(basix.ElementFamily.P, basix.CellType.interval, 10, - basix.LagrangeVariant.gll_warped) +element = basix.create_element( + basix.ElementFamily.P, basix.CellType.interval, 10, basix.LagrangeVariant.gll_warped +) values = element.tabulate(0, lattice)[0, :, :, 0] if MPI.COMM_WORLD.size == 1: # Skip this plotting in parallel for i in range(values.shape[1]): @@ -105,21 +107,25 @@ # Elements created using Basix can be used directly with UFL via Basix's # UFL wrapper. -ufl_element = basix.ufl.element(basix.ElementFamily.P, basix.CellType.triangle, 3, - basix.LagrangeVariant.gll_warped) +ufl_element = basix.ufl.element( + basix.ElementFamily.P, basix.CellType.triangle, 3, basix.LagrangeVariant.gll_warped +) # The UFL element `ufl_element` can be used in the same way as an # element created directly in UFL. For example, we could [solve a # Poisson problem](demo_poisson) using this element. # + -msh = mesh.create_rectangle(comm=MPI.COMM_WORLD, - points=((0.0, 0.0), (2.0, 1.0)), n=(32, 16), - cell_type=mesh.CellType.triangle,) +msh = mesh.create_rectangle( + comm=MPI.COMM_WORLD, + points=((0.0, 0.0), (2.0, 1.0)), + n=(32, 16), + cell_type=mesh.CellType.triangle, +) V = fem.functionspace(msh, ufl_element) -facets = mesh.locate_entities_boundary(msh, dim=1, - marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 2.0))) +facets = mesh.locate_entities_boundary( + msh, dim=1, marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 2.0)) +) dofs = fem.locate_dofs_topological(V=V, entity_dim=1, entities=facets) bc = fem.dirichletbc(value=default_scalar_type(0), dofs=dofs, V=V) @@ -203,9 +209,9 @@ def saw_tooth(x): V = fem.functionspace(msh, ufl_element) uh = fem.Function(V) uh.interpolate(lambda x: saw_tooth(x[0])) - M = fem.form((u_exact - uh)**2 * dx) + M = fem.form((u_exact - uh) ** 2 * dx) error = msh.comm.allreduce(fem.assemble_scalar(M), op=MPI.SUM) - print(f"Computed L2 interpolation error ({variant.name}):", error ** 0.5) + print(f"Computed L2 interpolation error ({variant.name}):", error**0.5) # - # ## Available Lagrange variants diff --git a/python/demo/demo_mixed-poisson.py b/python/demo/demo_mixed-poisson.py index 9cc7c7c843e..23daf1a14cb 100644 --- a/python/demo/demo_mixed-poisson.py +++ b/python/demo/demo_mixed-poisson.py @@ -158,8 +158,12 @@ def f2(x): bcs = [bc_top, bc_bottom] -problem = LinearProblem(a, L, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu", - "pc_factor_mat_solver_type": "mumps"}) +problem = LinearProblem( + a, + L, + bcs=bcs, + petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}, +) try: w_h = problem.solve() except PETSc.Error as e: # type: ignore diff --git a/python/demo/demo_navier-stokes.py b/python/demo/demo_navier-stokes.py index 3d1e283daee..fbd1a72e582 100644 --- a/python/demo/demo_navier-stokes.py +++ b/python/demo/demo_navier-stokes.py @@ -171,8 +171,23 @@ from dolfinx import default_real_type, fem, io, mesh from dolfinx.fem.petsc import assemble_matrix_block, assemble_vector_block -from ufl import (CellDiameter, FacetNormal, TestFunction, TrialFunction, avg, conditional, div, dot, dS, ds, dx, grad, - gt, inner, outer) +from ufl import ( + CellDiameter, + FacetNormal, + TestFunction, + TrialFunction, + avg, + conditional, + div, + dot, + dS, + ds, + dx, + grad, + gt, + inner, + outer, +) if np.issubdtype(PETSc.ScalarType, np.complexfloating): # type: ignore print("Demo should only be executed with DOLFINx real mode") @@ -191,17 +206,25 @@ def norm_L2(comm, v): def domain_average(msh, v): """Compute the average of a function over the domain""" - vol = msh.comm.allreduce(fem.assemble_scalar(fem.form(fem.Constant(msh, default_real_type(1.0)) * dx)), op=MPI.SUM) + vol = msh.comm.allreduce( + fem.assemble_scalar(fem.form(fem.Constant(msh, default_real_type(1.0)) * dx)), op=MPI.SUM + ) return (1 / vol) * msh.comm.allreduce(fem.assemble_scalar(fem.form(v * dx)), op=MPI.SUM) def u_e_expr(x): """Expression for the exact velocity solution to Kovasznay flow""" - return np.vstack((1 - np.exp( - (Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) * x[0]) * np.cos(2 * np.pi * x[1]), - (Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) / (2 * np.pi) * np.exp( - (Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) * x[0]) - * np.sin(2 * np.pi * x[1]))) + return np.vstack( + ( + 1 + - np.exp((Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) * x[0]) + * np.cos(2 * np.pi * x[1]), + (Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) + / (2 * np.pi) + * np.exp((Re / 2 - np.sqrt(Re**2 / 4 + 4 * np.pi**2)) * x[0]) + * np.sin(2 * np.pi * x[1]), + ) + ) def p_e_expr(x): @@ -215,7 +238,14 @@ def f_expr(x): def boundary_marker(x): - return np.isclose(x[0], 0.0) | np.isclose(x[0], 1.0) | np.isclose(x[1], 0.0) | np.isclose(x[1], 1.0) + return ( + np.isclose(x[0], 0.0) + | np.isclose(x[0], 1.0) + | np.isclose(x[1], 0.0) + | np.isclose(x[1], 1.0) + ) + + # - # We define some simulation parameters @@ -257,6 +287,8 @@ def boundary_marker(x): def jump(phi, n): return outer(phi("+"), n("+")) + outer(phi("-"), n("-")) + + # - # We solve the Stokes problem for the initial condition, omitting the @@ -264,24 +296,26 @@ def jump(phi, n): # + -a_00 = (1.0 / Re) * (inner(grad(u), grad(v)) * dx - - inner(avg(grad(u)), jump(v, n)) * dS - - inner(jump(u, n), avg(grad(v))) * dS - + (alpha / avg(h)) * inner(jump(u, n), jump(v, n)) * dS - - inner(grad(u), outer(v, n)) * ds - - inner(outer(u, n), grad(v)) * ds - + (alpha / h) * inner(outer(u, n), outer(v, n)) * ds) -a_01 = - inner(p, div(v)) * dx -a_10 = - inner(div(u), q) * dx - -a = fem.form([[a_00, a_01], - [a_10, None]]) +a_00 = (1.0 / Re) * ( + inner(grad(u), grad(v)) * dx + - inner(avg(grad(u)), jump(v, n)) * dS + - inner(jump(u, n), avg(grad(v))) * dS + + (alpha / avg(h)) * inner(jump(u, n), jump(v, n)) * dS + - inner(grad(u), outer(v, n)) * ds + - inner(outer(u, n), grad(v)) * ds + + (alpha / h) * inner(outer(u, n), outer(v, n)) * ds +) +a_01 = -inner(p, div(v)) * dx +a_10 = -inner(div(u), q) * dx + +a = fem.form([[a_00, a_01], [a_10, None]]) f = fem.Function(W) u_D = fem.Function(V) u_D.interpolate(u_e_expr) -L_0 = inner(f, v) * dx + (1 / Re) * (- inner(outer(u_D, n), grad(v)) * ds - + (alpha / h) * inner(outer(u_D, n), outer(v, n)) * ds) +L_0 = inner(f, v) * dx + (1 / Re) * ( + -inner(outer(u_D, n), grad(v)) * ds + (alpha / h) * inner(outer(u_D, n), outer(v, n)) * ds +) L_1 = inner(fem.Constant(msh, default_real_type(0.0)), q) * dx L = fem.form([L_0, L_1]) @@ -329,7 +363,7 @@ def jump(phi, n): offset = V.dofmap.index_map.size_local * V.dofmap.index_map_bs u_h.x.array[:offset] = x.array_r[:offset] u_h.x.scatter_forward() -p_h.x.array[:(len(x.array_r) - offset)] = x.array_r[offset:] +p_h.x.array[: (len(x.array_r) - offset)] = x.array_r[offset:] p_h.x.scatter_forward() # Subtract the average of the pressure since it is only determined up to # a constant @@ -359,13 +393,14 @@ def jump(phi, n): # + lmbda = conditional(gt(dot(u_n, n), 0), 1, 0) u_uw = lmbda("+") * u("+") + lmbda("-") * u("-") -a_00 += inner(u / delta_t, v) * dx - \ - inner(u, div(outer(v, u_n))) * dx + \ - inner((dot(u_n, n))("+") * u_uw, v("+")) * dS + \ - inner((dot(u_n, n))("-") * u_uw, v("-")) * dS + \ - inner(dot(u_n, n) * lmbda * u, v) * ds -a = fem.form([[a_00, a_01], - [a_10, None]]) +a_00 += ( + inner(u / delta_t, v) * dx + - inner(u, div(outer(v, u_n))) * dx + + inner((dot(u_n, n))("+") * u_uw, v("+")) * dS + + inner((dot(u_n, n))("-") * u_uw, v("-")) * dS + + inner(dot(u_n, n) * lmbda * u, v) * ds +) +a = fem.form([[a_00, a_01], [a_10, None]]) L_0 += inner(u_n / delta_t, v) * dx - inner(dot(u_n, n) * (1 - lmbda) * u_D, v) * ds L = fem.form([L_0, L_1]) @@ -387,7 +422,7 @@ def jump(phi, n): u_h.x.array[:offset] = x.array_r[:offset] u_h.x.scatter_forward() - p_h.x.array[:(len(x.array_r) - offset)] = x.array_r[offset:] + p_h.x.array[: (len(x.array_r) - offset)] = x.array_r[offset:] p_h.x.scatter_forward() p_h.x.array[:] -= domain_average(msh, p_h) diff --git a/python/demo/demo_pml/demo_pml.py b/python/demo/demo_pml/demo_pml.py index 887d848f0ea..4d78764ce97 100644 --- a/python/demo/demo_pml/demo_pml.py +++ b/python/demo/demo_pml/demo_pml.py @@ -39,6 +39,7 @@ try: import pyvista + have_pyvista = True except ModuleNotFoundError: print("pyvista and pyvistaqt are required to visualise the solution") @@ -92,14 +93,15 @@ # The function `background_field` below implements this analytical # formula: -# + -def background_field(theta: float, n_b: float, k0: complex, - x: np.typing.NDArray[np.float64]): +# + +def background_field(theta: float, n_b: float, k0: complex, x: np.typing.NDArray[np.float64]): kx = n_b * k0 * np.cos(theta) ky = n_b * k0 * np.sin(theta) phi = kx * x[0] + ky * x[1] return (-np.sin(theta) * np.exp(1j * phi), np.cos(theta) * np.exp(1j * phi)) + + # - # For convenience, we define the $\nabla\times$ operator for a 2D vector @@ -127,7 +129,7 @@ def curl_2d(a: fem.Function): def pml_coordinates(x: ufl.indexed.Indexed, alpha: float, k0: complex, l_dom: float, l_pml: float): - return (x + 1j * alpha / k0 * x * (ufl.algebra.Abs(x) - l_dom / 2) / (l_pml / 2 - l_dom / 2)**2) + return x + 1j * alpha / k0 * x * (ufl.algebra.Abs(x) - l_dom / 2) / (l_pml / 2 - l_dom / 2) ** 2 # We use the following domain specific parameters. @@ -172,9 +174,20 @@ def pml_coordinates(x: ufl.indexed.Indexed, alpha: float, k0: complex, l_dom: fl model = None gmsh.initialize(sys.argv) if MPI.COMM_WORLD.rank == 0: - model = generate_mesh_wire(radius_wire, radius_scatt, l_dom, l_pml, - in_wire_size, on_wire_size, scatt_size, pml_size, - au_tag, bkg_tag, scatt_tag, pml_tag) + model = generate_mesh_wire( + radius_wire, + radius_scatt, + l_dom, + l_pml, + in_wire_size, + on_wire_size, + scatt_size, + pml_size, + au_tag, + bkg_tag, + scatt_tag, + pml_tag, + ) model = MPI.COMM_WORLD.bcast(model, root=0) msh, cell_tags, facet_tags = gmshio.model_to_mesh(model, MPI.COMM_WORLD, 0, gdim=2) @@ -289,8 +302,9 @@ def pml_coordinates(x: ufl.indexed.Indexed, alpha: float, k0: complex, l_dom: fl alpha = 1 # PML corners -xy_pml = ufl.as_vector((pml_coordinates(x[0], alpha, k0, l_dom, l_pml), - pml_coordinates(x[1], alpha, k0, l_dom, l_pml))) +xy_pml = ufl.as_vector( + (pml_coordinates(x[0], alpha, k0, l_dom, l_pml), pml_coordinates(x[1], alpha, k0, l_dom, l_pml)) +) # PML rectangles along x x_pml = ufl.as_vector((pml_coordinates(x[0], alpha, k0, l_dom, l_pml), x[1])) @@ -356,16 +370,15 @@ def pml_coordinates(x: ufl.indexed.Indexed, alpha: float, k0: complex, l_dom: fl # + -def create_eps_mu(pml: ufl.tensors.ListTensor, - eps_bkg: Union[float, ufl.tensors.ListTensor], - mu_bkg: Union[float, ufl.tensors.ListTensor]) -> tuple[ufl.tensors.ComponentTensor, - ufl.tensors.ComponentTensor]: +def create_eps_mu( + pml: ufl.tensors.ListTensor, + eps_bkg: Union[float, ufl.tensors.ListTensor], + mu_bkg: Union[float, ufl.tensors.ListTensor], +) -> tuple[ufl.tensors.ComponentTensor, ufl.tensors.ComponentTensor]: J = ufl.grad(pml) # Transform the 2x2 Jacobian into a 3x3 matrix. - J = ufl.as_matrix(((J[0, 0], 0, 0), - (0, J[1, 1], 0), - (0, 0, 1))) + J = ufl.as_matrix(((J[0, 0], 0, 0), (0, J[1, 1], 0), (0, 0, 1))) A = ufl.inv(J) eps_pml = ufl.det(J) * A * eps_bkg * ufl.transpose(A) @@ -403,15 +416,17 @@ def create_eps_mu(pml: ufl.tensors.ListTensor, # + # Definition of the weak form -F = - ufl.inner(curl_2d(Es), curl_2d(v)) * dDom \ - + eps * (k0**2) * ufl.inner(Es, v) * dDom \ - + (k0**2) * (eps - eps_bkg) * ufl.inner(Eb, v) * dDom \ - - ufl.inner(ufl.inv(mu_x) * curl_2d(Es), curl_2d(v)) * dPml_x \ - - ufl.inner(ufl.inv(mu_y) * curl_2d(Es), curl_2d(v)) * dPml_y \ - - ufl.inner(ufl.inv(mu_xy) * curl_2d(Es), curl_2d(v)) * dPml_xy \ - + (k0**2) * ufl.inner(eps_x * Es_3d, v_3d) * dPml_x \ - + (k0**2) * ufl.inner(eps_y * Es_3d, v_3d) * dPml_y \ +F = ( + -ufl.inner(curl_2d(Es), curl_2d(v)) * dDom + + eps * (k0**2) * ufl.inner(Es, v) * dDom + + (k0**2) * (eps - eps_bkg) * ufl.inner(Eb, v) * dDom + - ufl.inner(ufl.inv(mu_x) * curl_2d(Es), curl_2d(v)) * dPml_x + - ufl.inner(ufl.inv(mu_y) * curl_2d(Es), curl_2d(v)) * dPml_y + - ufl.inner(ufl.inv(mu_xy) * curl_2d(Es), curl_2d(v)) * dPml_xy + + (k0**2) * ufl.inner(eps_x * Es_3d, v_3d) * dPml_x + + (k0**2) * ufl.inner(eps_y * Es_3d, v_3d) * dPml_y + (k0**2) * ufl.inner(eps_xy * Es_3d, v_3d) * dPml_xy +) a, L = ufl.lhs(F), ufl.rhs(F) @@ -442,7 +457,7 @@ def create_eps_mu(pml: ufl.tensors.ListTensor, V_cells, V_types, V_x = plot.vtk_mesh(V_dg) V_grid = pyvista.UnstructuredGrid(V_cells, V_types, V_x) Esh_values = np.zeros((V_x.shape[0], 3), dtype=np.float64) - Esh_values[:, :msh.topology.dim] = Esh_dg.x.array.reshape(V_x.shape[0], msh.topology.dim).real + Esh_values[:, : msh.topology.dim] = Esh_dg.x.array.reshape(V_x.shape[0], msh.topology.dim).real V_grid.point_data["u"] = Esh_values plotter = pyvista.Plotter() @@ -480,7 +495,9 @@ def create_eps_mu(pml: ufl.tensors.ListTensor, # `calculate_analytical_efficiencies` function defined in a separate # file: -q_abs_analyt, q_sca_analyt, q_ext_analyt = calculate_analytical_efficiencies(eps_au, n_bkg, wl0, radius_wire) +q_abs_analyt, q_sca_analyt, q_ext_analyt = calculate_analytical_efficiencies( + eps_au, n_bkg, wl0, radius_wire +) # We calculate the numerical efficiencies in the same way as done in # `demo_scattering_boundary_conditions.py`, with the only difference @@ -511,11 +528,12 @@ def create_eps_mu(pml: ufl.tensors.ListTensor, # efficiency marker = fem.Function(D) scatt_facets = facet_tags.find(scatt_tag) -incident_cells = mesh.compute_incident_entities(msh.topology, scatt_facets, msh.topology.dim - 1, - msh.topology.dim) +incident_cells = mesh.compute_incident_entities( + msh.topology, scatt_facets, msh.topology.dim - 1, msh.topology.dim +) midpoints = mesh.compute_midpoints(msh, msh.topology.dim, incident_cells) -inner_cells = incident_cells[(midpoints[:, 0]**2 + midpoints[:, 1]**2) < (radius_scatt)**2] +inner_cells = incident_cells[(midpoints[:, 0] ** 2 + midpoints[:, 1] ** 2) < (radius_scatt) ** 2] marker.x.array[inner_cells] = 1 @@ -535,7 +553,9 @@ def create_eps_mu(pml: ufl.tensors.ListTensor, q_abs_fenics = msh.comm.allreduce(q_abs_fenics_proc, op=MPI.SUM) # Normalized scattering efficiency -q_sca_fenics_proc = (fem.assemble_scalar(fem.form((P('+') + P('-')) * dS(scatt_tag))) / (gcs * I0)).real +q_sca_fenics_proc = ( + fem.assemble_scalar(fem.form((P("+") + P("-")) * dS(scatt_tag))) / (gcs * I0) +).real # Sum results from all MPI processes q_sca_fenics = msh.comm.allreduce(q_sca_fenics_proc, op=MPI.SUM) diff --git a/python/demo/demo_pml/efficiencies_pml_demo.py b/python/demo/demo_pml/efficiencies_pml_demo.py index 54a4337cc0d..9be3f5a51be 100644 --- a/python/demo/demo_pml/efficiencies_pml_demo.py +++ b/python/demo/demo_pml/efficiencies_pml_demo.py @@ -104,15 +104,15 @@ def compute_a(nu: int, m: complex, alpha: float) -> float: return a_nu_num / a_nu_den -def calculate_analytical_efficiencies(eps: complex, n_bkg: float, - wl0: float, radius_wire: float, - num_n: int = 50) -> tuple[float, float, float]: +def calculate_analytical_efficiencies( + eps: complex, n_bkg: float, wl0: float, radius_wire: float, num_n: int = 50 +) -> tuple[float, float, float]: m = np.sqrt(np.conj(eps)) / n_bkg alpha = 2 * np.pi * radius_wire / wl0 * n_bkg c = 2 / alpha q_ext = c * np.real(compute_a(0, m, alpha)) - q_sca = c * np.abs(compute_a(0, m, alpha))**2 + q_sca = c * np.abs(compute_a(0, m, alpha)) ** 2 for nu in range(1, num_n + 1): q_ext += c * 2 * np.real(compute_a(nu, m, alpha)) - q_sca += c * 2 * np.abs(compute_a(nu, m, alpha))**2 + q_sca += c * 2 * np.abs(compute_a(nu, m, alpha)) ** 2 return q_ext - q_sca, q_sca, q_ext diff --git a/python/demo/demo_pml/mesh_wire_pml.py b/python/demo/demo_pml/mesh_wire_pml.py index 903eac582d0..038b57d4583 100644 --- a/python/demo/demo_pml/mesh_wire_pml.py +++ b/python/demo/demo_pml/mesh_wire_pml.py @@ -41,11 +41,20 @@ from numpy import intersect1d, pi -def generate_mesh_wire(radius_wire: float, radius_scatt: float, l_dom: float, l_pml: float, - in_wire_size: float, on_wire_size: float, scatt_size: float, - pml_size: float, au_tag: int, bkg_tag: int, scatt_tag: int, - pml_tag: int): - +def generate_mesh_wire( + radius_wire: float, + radius_scatt: float, + l_dom: float, + l_pml: float, + in_wire_size: float, + on_wire_size: float, + scatt_size: float, + pml_size: float, + au_tag: int, + bkg_tag: int, + scatt_tag: int, + pml_tag: int, +): gmsh.model.add("nanowire") dim = 2 # A dummy circle for setting a finer mesh @@ -71,10 +80,12 @@ def generate_mesh_wire(radius_wire: float, radius_scatt: float, l_dom: float, l_ separate_rectangle, _ = gmsh.model.occ.cut(inclusive_rectangle, wire, removeTool=False) _, physical_domain = gmsh.model.occ.fragment(separate_rectangle, wire) - bkg_tags = [tag[0] for tag in physical_domain[:len(separate_rectangle)]] + bkg_tags = [tag[0] for tag in physical_domain[: len(separate_rectangle)]] - wire_tags = [tag[0] for tag in physical_domain[len(separate_rectangle): - len(inclusive_rectangle) + len(wire)]] + wire_tags = [ + tag[0] + for tag in physical_domain[len(separate_rectangle) : len(inclusive_rectangle) + len(wire)] + ] # Corner PMLS pml1 = gmsh.model.occ.addRectangle(-l_pml / 2, l_dom / 2, 0, delta_pml, delta_pml) @@ -96,22 +107,37 @@ def generate_mesh_wire(radius_wire: float, radius_scatt: float, l_dom: float, l_ gmsh.model.occ.synchronize() - bkg_group = [tag[0][1] for tag in surface_map[:len(bkg_tags)]] + bkg_group = [tag[0][1] for tag in surface_map[: len(bkg_tags)]] gmsh.model.addPhysicalGroup(dim, bkg_group, tag=bkg_tag) - wire_group = [tag[0][1] for tag in surface_map[len(bkg_tags):len(bkg_tags + wire_tags)]] + wire_group = [tag[0][1] for tag in surface_map[len(bkg_tags) : len(bkg_tags + wire_tags)]] gmsh.model.addPhysicalGroup(dim, wire_group, tag=au_tag) - corner_group = [tag[0][1] for tag in surface_map[len(bkg_tags + wire_tags):len(bkg_tags + wire_tags + corner_pmls)]] + corner_group = [ + tag[0][1] + for tag in surface_map[len(bkg_tags + wire_tags) : len(bkg_tags + wire_tags + corner_pmls)] + ] gmsh.model.addPhysicalGroup(dim, corner_group, tag=pml_tag) - x_group = [tag[0][1] for tag in surface_map[len( - bkg_tags + wire_tags + corner_pmls):len(bkg_tags + wire_tags + corner_pmls + x_pmls)]] + x_group = [ + tag[0][1] + for tag in surface_map[ + len(bkg_tags + wire_tags + corner_pmls) : len( + bkg_tags + wire_tags + corner_pmls + x_pmls + ) + ] + ] gmsh.model.addPhysicalGroup(dim, x_group, tag=pml_tag + 1) - y_group = [tag[0][1] for tag in surface_map[len( - bkg_tags + wire_tags + corner_pmls + x_pmls):len(bkg_tags + wire_tags + corner_pmls + x_pmls + y_pmls)]] + y_group = [ + tag[0][1] + for tag in surface_map[ + len(bkg_tags + wire_tags + corner_pmls + x_pmls) : len( + bkg_tags + wire_tags + corner_pmls + x_pmls + y_pmls + ) + ] + ] gmsh.model.addPhysicalGroup(dim, y_group, tag=pml_tag + 2) diff --git a/python/demo/demo_poisson.py b/python/demo/demo_poisson.py index 9f8ebf73828..fcfffe7548a 100644 --- a/python/demo/demo_poisson.py +++ b/python/demo/demo_poisson.py @@ -86,9 +86,12 @@ # ` $V$ on the mesh. # + -msh = mesh.create_rectangle(comm=MPI.COMM_WORLD, - points=((0.0, 0.0), (2.0, 1.0)), n=(32, 16), - cell_type=mesh.CellType.triangle) +msh = mesh.create_rectangle( + comm=MPI.COMM_WORLD, + points=((0.0, 0.0), (2.0, 1.0)), + n=(32, 16), + cell_type=mesh.CellType.triangle, +) V = fem.functionspace(msh, ("Lagrange", 1)) # - @@ -105,9 +108,11 @@ # with a 'marker' function that returns `True` for points `x` on the # boundary and `False` otherwise. -facets = mesh.locate_entities_boundary(msh, dim=(msh.topology.dim - 1), - marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 2.0))) +facets = mesh.locate_entities_boundary( + msh, + dim=(msh.topology.dim - 1), + marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 2.0)), +) # We now find the degrees-of-freedom that are associated with the # boundary facets using {py:func}`locate_dofs_topological @@ -158,6 +163,7 @@ # + try: import pyvista + cells, types, x = plot.vtk_mesh(V) grid = pyvista.UnstructuredGrid(cells, types, x) grid.point_data["u"] = uh.x.array.real diff --git a/python/demo/demo_pyvista.py b/python/demo/demo_pyvista.py index c286150d09c..29ec0549295 100644 --- a/python/demo/demo_pyvista.py +++ b/python/demo/demo_pyvista.py @@ -80,29 +80,41 @@ def plot_scalar(): subplotter.subplot(0, 1) subplotter.add_text("Warped function", position="upper_edge", font_size=14, color="black") - sargs = dict(height=0.8, width=0.1, vertical=True, position_x=0.05, - position_y=0.05, fmt="%1.2e", title_font_size=40, color="black", label_font_size=25) + sargs = dict( + height=0.8, + width=0.1, + vertical=True, + position_x=0.05, + position_y=0.05, + fmt="%1.2e", + title_font_size=40, + color="black", + label_font_size=25, + ) subplotter.set_position([-3, 2.6, 0.3]) subplotter.set_focus([3, -1, -0.15]) subplotter.set_viewup([0, 0, 1]) subplotter.add_mesh(warped, show_edges=True, scalar_bar_args=sargs) if pyvista.OFF_SCREEN: - subplotter.screenshot("2D_function_warp.png", transparent_background=transparent, - window_size=[figsize, figsize]) + subplotter.screenshot( + "2D_function_warp.png", + transparent_background=transparent, + window_size=[figsize, figsize], + ) else: subplotter.show() # ## Mesh tags and using subplots -def plot_meshtags(): +def plot_meshtags(): # Create a mesh msh = create_unit_square(MPI.COMM_WORLD, 25, 25, cell_type=CellType.quadrilateral) # Create a geometric indicator function def in_circle(x): - return np.array((x.T[0] - 0.5)**2 + (x.T[1] - 0.5)**2 < 0.2**2, dtype=np.int32) + return np.array((x.T[0] - 0.5) ** 2 + (x.T[1] - 0.5) ** 2 < 0.2**2, dtype=np.int32) # Create cell tags - if midpoint is inside circle, it gets value 1, # otherwise 0 @@ -138,8 +150,9 @@ def in_circle(x): subplotter.add_mesh(sub_grid, show_edges=True, edge_color="black") if pyvista.OFF_SCREEN: - subplotter.screenshot("2D_markers.png", transparent_background=transparent, - window_size=[2 * figsize, figsize]) + subplotter.screenshot( + "2D_markers.png", transparent_background=transparent, window_size=[2 * figsize, figsize] + ) else: subplotter.show() @@ -148,14 +161,14 @@ def in_circle(x): # # Higher-order finite element function can also be plotted. -def plot_higher_order(): +def plot_higher_order(): # Create a mesh msh = create_unit_square(MPI.COMM_WORLD, 12, 12, cell_type=CellType.quadrilateral) # Define a geometric indicator function def in_circle(x): - return np.array((x.T[0] - 0.5)**2 + (x.T[1] - 0.5)**2 < 0.2**2, dtype=np.int32) + return np.array((x.T[0] - 0.5) ** 2 + (x.T[1] - 0.5) ** 2 < 0.2**2, dtype=np.int32) # Create mesh tags for all cells. If midpoint is inside the circle, # it gets value 1, otherwise 0. @@ -190,16 +203,25 @@ def in_circle(x): # We visualize the data plotter = pyvista.Plotter() - plotter.add_text("Second-order (P2) discontinuous elements", - position="upper_edge", font_size=14, color="black") + plotter.add_text( + "Second-order (P2) discontinuous elements", + position="upper_edge", + font_size=14, + color="black", + ) sargs = dict(height=0.1, width=0.8, vertical=False, position_x=0.1, position_y=0, color="black") plotter.add_mesh(grid, show_edges=False, scalar_bar_args=sargs, line_width=0) plotter.add_mesh(org_grid, color="white", style="wireframe", line_width=5) - plotter.add_mesh(grid.copy(), style="points", point_size=15, render_points_as_spheres=True, line_width=0) + plotter.add_mesh( + grid.copy(), style="points", point_size=15, render_points_as_spheres=True, line_width=0 + ) plotter.view_xy() if pyvista.OFF_SCREEN: - plotter.screenshot(f"DG_{MPI.COMM_WORLD.rank}.png", - transparent_background=transparent, window_size=[figsize, figsize]) + plotter.screenshot( + f"DG_{MPI.COMM_WORLD.rank}.png", + transparent_background=transparent, + window_size=[figsize, figsize], + ) else: plotter.show() @@ -209,14 +231,15 @@ def in_circle(x): # In this section we will consider how to plot vector-element functions, # e.g. Raviart-Thomas or Nédélec elements. -def plot_nedelec(): +def plot_nedelec(): msh = create_unit_cube(MPI.COMM_WORLD, 4, 3, 5, cell_type=CellType.tetrahedron) # We create a pyvista plotter plotter = pyvista.Plotter() - plotter.add_text("Mesh and corresponding vectors", - position="upper_edge", font_size=14, color="black") + plotter.add_text( + "Mesh and corresponding vectors", position="upper_edge", font_size=14, color="black" + ) # Next, we create a pyvista.UnstructuredGrid based on the mesh pyvista_cells, cell_types, x = plot.vtk_mesh(msh) @@ -229,7 +252,7 @@ def plot_nedelec(): # elements and interpolate a vector-valued expression V = functionspace(msh, ("N1curl", 2)) u = Function(V, dtype=np.float64) - u.interpolate(lambda x: (x[2]**2, np.zeros(x.shape[1]), -x[0] * x[2])) + u.interpolate(lambda x: (x[2] ** 2, np.zeros(x.shape[1]), -x[0] * x[2])) # Exact visualisation of the Nédélec spaces requires a Lagrange or # discontinuous Lagrange finite element functions. Therefore, we @@ -254,18 +277,21 @@ def plot_nedelec(): # Save as png if we are using a container with no rendering if pyvista.OFF_SCREEN: - plotter.screenshot("3D_wireframe_with_vectors.png", transparent_background=transparent, - window_size=[figsize, figsize]) + plotter.screenshot( + "3D_wireframe_with_vectors.png", + transparent_background=transparent, + window_size=[figsize, figsize], + ) else: plotter.show() + # ## Plotting streamlines # # In this section we illustrate how to visualize streamlines in 3D def plot_streamlines(): - msh = create_unit_cube(MPI.COMM_WORLD, 4, 4, 4, CellType.hexahedron) gdim = msh.geometry.dim V = functionspace(msh, ("Discontinuous Lagrange", 2, (gdim,))) @@ -275,14 +301,16 @@ def plot_streamlines(): cells, types, x = plot.vtk_mesh(V) num_dofs = x.shape[0] values = np.zeros((num_dofs, 3), dtype=np.float64) - values[:, :msh.geometry.dim] = u.x.array.reshape(num_dofs, V.dofmap.index_map_bs) + values[:, : msh.geometry.dim] = u.x.array.reshape(num_dofs, V.dofmap.index_map_bs) # Create a point cloud of glyphs grid = pyvista.UnstructuredGrid(cells, types, x) grid["vectors"] = values grid.set_active_vectors("vectors") glyphs = grid.glyph(orient="vectors", factor=0.1) - streamlines = grid.streamlines(vectors="vectors", return_source=False, source_radius=1, n_points=150) + streamlines = grid.streamlines( + vectors="vectors", return_source=False, source_radius=1, n_points=150 + ) # Create Create plotter plotter = pyvista.Plotter() @@ -292,8 +320,11 @@ def plot_streamlines(): plotter.add_mesh(streamlines.tube(radius=0.001)) plotter.view_xy() if pyvista.OFF_SCREEN: - plotter.screenshot(f"streamlines_{MPI.COMM_WORLD.rank}.png", - transparent_background=transparent, window_size=[figsize, figsize]) + plotter.screenshot( + f"streamlines_{MPI.COMM_WORLD.rank}.png", + transparent_background=transparent, + window_size=[figsize, figsize], + ) else: plotter.show() diff --git a/python/demo/demo_scattering_boundary_conditions/analytical_efficiencies_wire.py b/python/demo/demo_scattering_boundary_conditions/analytical_efficiencies_wire.py index d6b60a798f7..9be3f5a51be 100644 --- a/python/demo/demo_scattering_boundary_conditions/analytical_efficiencies_wire.py +++ b/python/demo/demo_scattering_boundary_conditions/analytical_efficiencies_wire.py @@ -104,14 +104,15 @@ def compute_a(nu: int, m: complex, alpha: float) -> float: return a_nu_num / a_nu_den -def calculate_analytical_efficiencies(eps: complex, n_bkg: float, wl0: float, radius_wire: float, - num_n: int = 50) -> tuple[float, float, float]: +def calculate_analytical_efficiencies( + eps: complex, n_bkg: float, wl0: float, radius_wire: float, num_n: int = 50 +) -> tuple[float, float, float]: m = np.sqrt(np.conj(eps)) / n_bkg alpha = 2 * np.pi * radius_wire / wl0 * n_bkg c = 2 / alpha q_ext = c * np.real(compute_a(0, m, alpha)) - q_sca = c * np.abs(compute_a(0, m, alpha))**2 + q_sca = c * np.abs(compute_a(0, m, alpha)) ** 2 for nu in range(1, num_n + 1): q_ext += c * 2 * np.real(compute_a(nu, m, alpha)) - q_sca += c * 2 * np.abs(compute_a(nu, m, alpha))**2 + q_sca += c * 2 * np.abs(compute_a(nu, m, alpha)) ** 2 return q_ext - q_sca, q_sca, q_ext diff --git a/python/demo/demo_scattering_boundary_conditions/demo_scattering_boundary_conditions.py b/python/demo/demo_scattering_boundary_conditions/demo_scattering_boundary_conditions.py index cc9fcb99043..d7aea7a937b 100644 --- a/python/demo/demo_scattering_boundary_conditions/demo_scattering_boundary_conditions.py +++ b/python/demo/demo_scattering_boundary_conditions/demo_scattering_boundary_conditions.py @@ -51,6 +51,7 @@ try: import pyvista + have_pyvista = True except ModuleNotFoundError: print("pyvista and pyvistaqt are required to visualise the solution") @@ -107,15 +108,16 @@ # + -class BackgroundElectricField: +class BackgroundElectricField: def __init__(self, theta: float, n_bkg: float, k0: complex): self.theta = theta # incident angle self.k0 = k0 # vacuum wavevector self.n_bkg = n_bkg # background refractive index - def eval(self, x: np.typing.NDArray[np.float64]) -> tuple[np.typing.NDArray[np.complex128], - np.typing.NDArray[np.complex128]]: + def eval( + self, x: np.typing.NDArray[np.float64] + ) -> tuple[np.typing.NDArray[np.complex128], np.typing.NDArray[np.complex128]]: kx = self.n_bkg * self.k0 * np.cos(self.theta) ky = self.n_bkg * self.k0 * np.sin(self.theta) phi = kx * x[0] + ky * x[1] @@ -177,16 +179,18 @@ def eval(self, x: np.typing.NDArray[np.float64]) -> tuple[np.typing.NDArray[np.c # 2D vector (in UFL syntax) is defined below. # + # + def radial_distance(x: ufl.SpatialCoordinate): """Returns the radial distance from the origin""" - return ufl.sqrt(x[0]**2 + x[1]**2) + return ufl.sqrt(x[0] ** 2 + x[1] ** 2) def curl_2d(f: fem.Function): """Returns the curl of two 2D vectors as a 3D vector""" return ufl.as_vector((0, 0, f[1].dx(0) - f[0].dx(1))) + # - # Next we define some mesh specific parameters. Please notice that the @@ -218,9 +222,9 @@ def curl_2d(f: fem.Function): boundary_size = mesh_factor * 30.0e-3 # Tags for the subdomains -au_tag = 1 # gold wire -bkg_tag = 2 # background -boundary_tag = 3 # boundary +au_tag = 1 # gold wire +bkg_tag = 2 # background +boundary_tag = 3 # boundary # - # We generate the mesh using GMSH and convert it to a @@ -230,8 +234,17 @@ def curl_2d(f: fem.Function): model = None gmsh.initialize(sys.argv) if MPI.COMM_WORLD.rank == 0: - model = generate_mesh_wire(radius_wire, radius_dom, in_wire_size, on_wire_size, bkg_size, - boundary_size, au_tag, bkg_tag, boundary_tag) + model = generate_mesh_wire( + radius_wire, + radius_dom, + in_wire_size, + on_wire_size, + bkg_size, + boundary_size, + au_tag, + bkg_tag, + boundary_tag, + ) model = MPI.COMM_WORLD.bcast(model, root=0) domain, cell_tags, facet_tags = io.gmshio.model_to_mesh(model, MPI.COMM_WORLD, 0, gdim=2) @@ -401,11 +414,14 @@ def curl_2d(f: fem.Function): # residual # Weak form -F = - ufl.inner(ufl.curl(Es), ufl.curl(v)) * dDom \ - + eps * (k0**2) * ufl.inner(Es, v) * dDom \ - + (k0**2) * (eps - eps_bkg) * ufl.inner(Eb, v) * dDom \ - + (1j * k0 * n_bkg + 1 / (2 * r)) \ - * ufl.inner(ufl.cross(Es_3d, n_3d), ufl.cross(v_3d, n_3d)) * dsbc +F = ( + -ufl.inner(ufl.curl(Es), ufl.curl(v)) * dDom + + eps * (k0**2) * ufl.inner(Es, v) * dDom + + (k0**2) * (eps - eps_bkg) * ufl.inner(Eb, v) * dDom + + (1j * k0 * n_bkg + 1 / (2 * r)) + * ufl.inner(ufl.cross(Es_3d, n_3d), ufl.cross(v_3d, n_3d)) + * dsbc +) # We split the residual into a sesquilinear (lhs) and linear (rhs) form # and solve the problem. We store the scattered field $\mathbf{E}_s$ as @@ -441,7 +457,9 @@ def curl_2d(f: fem.Function): V_cells, V_types, V_x = plot.vtk_mesh(V_dg) V_grid = pyvista.UnstructuredGrid(V_cells, V_types, V_x) Esh_values = np.zeros((V_x.shape[0], 3), dtype=np.float64) - Esh_values[:, : domain.topology.dim] = Esh_dg.x.array.reshape(V_x.shape[0], domain.topology.dim).real + Esh_values[:, : domain.topology.dim] = Esh_dg.x.array.reshape( + V_x.shape[0], domain.topology.dim + ).real V_grid.point_data["u"] = Esh_values @@ -477,7 +495,8 @@ def curl_2d(f: fem.Function): # Calculation of analytical efficiencies q_abs_analyt, q_sca_analyt, q_ext_analyt = calculate_analytical_efficiencies( - eps_au, n_bkg, wl0, radius_wire) + eps_au, n_bkg, wl0, radius_wire +) # Now we can calculate the numerical efficiencies. The formula for the # absorption, scattering and extinction are: diff --git a/python/demo/demo_scattering_boundary_conditions/mesh_wire.py b/python/demo/demo_scattering_boundary_conditions/mesh_wire.py index 42302d29737..c20015941c7 100644 --- a/python/demo/demo_scattering_boundary_conditions/mesh_wire.py +++ b/python/demo/demo_scattering_boundary_conditions/mesh_wire.py @@ -37,10 +37,17 @@ from numpy import pi -def generate_mesh_wire(radius_wire: float, radius_dom: float, in_wire_size: float, - on_wire_size: float, bkg_size: float, boundary_size: float, - au_tag: int, bkg_tag: int, boundary_tag: int): - +def generate_mesh_wire( + radius_wire: float, + radius_dom: float, + in_wire_size: float, + on_wire_size: float, + bkg_size: float, + boundary_size: float, + au_tag: int, + bkg_tag: int, + boundary_tag: int, +): gmsh.model.add("wire") # A dummy boundary is added for setting a finer mesh diff --git a/python/demo/demo_static-condensation.py b/python/demo/demo_static-condensation.py index 0f0e9573081..59f1eefbb62 100644 --- a/python/demo/demo_static-condensation.py +++ b/python/demo/demo_static-condensation.py @@ -33,8 +33,16 @@ import ufl from basix.ufl import element from dolfinx import geometry -from dolfinx.fem import (Form, Function, IntegralType, dirichletbc, form, form_cpp_class, functionspace, - locate_dofs_topological) +from dolfinx.fem import ( + Form, + Function, + IntegralType, + dirichletbc, + form, + form_cpp_class, + functionspace, + locate_dofs_topological, +) from dolfinx.fem.petsc import apply_lifting, assemble_matrix, assemble_vector, set_bc from dolfinx.io import XDMFFile from dolfinx.jit import ffcx_jit @@ -46,8 +54,12 @@ exit(0) -infile = XDMFFile(MPI.COMM_WORLD, Path(Path(__file__).parent, "data", - "cooks_tri_mesh.xdmf"), "r", encoding=XDMFFile.Encoding.ASCII) +infile = XDMFFile( + MPI.COMM_WORLD, + Path(Path(__file__).parent, "data", "cooks_tri_mesh.xdmf"), + "r", + encoding=XDMFFile.Encoding.ASCII, +) msh = infile.read_mesh(name="Grid") infile.close() @@ -85,16 +97,16 @@ def sigma_u(u): """Constitutive relation for stress-strain. Assuming plane-stress in XY""" eps = 0.5 * (ufl.grad(u) + ufl.grad(u).T) - sigma = E / (1. - nu ** 2) * ((1. - nu) * eps + nu * ufl.Identity(2) * ufl.tr(eps)) + sigma = E / (1.0 - nu**2) * ((1.0 - nu) * eps + nu * ufl.Identity(2) * ufl.tr(eps)) return sigma a00 = ufl.inner(sigma, tau) * ufl.dx -a10 = - ufl.inner(sigma, ufl.grad(v)) * ufl.dx -a01 = - ufl.inner(sigma_u(u), tau) * ufl.dx +a10 = -ufl.inner(sigma, ufl.grad(v)) * ufl.dx +a01 = -ufl.inner(sigma_u(u), tau) * ufl.dx f = ufl.as_vector([0.0, 1.0 / 16]) -b1 = form(- ufl.inner(f, v) * ds(1), dtype=PETSc.ScalarType) # type: ignore +b1 = form(-ufl.inner(f, v) * ds(1), dtype=PETSc.ScalarType) # type: ignore # JIT compile individual blocks tabulation kernels ufcx00, _, _ = ffcx_jit(msh.comm, a00, form_compiler_options={"scalar_type": PETSc.ScalarType}) # type: ignore @@ -107,7 +119,7 @@ def sigma_u(u): kernel10 = getattr(ufcx10.form_integrals[0], f"tabulate_tensor_{np.dtype(PETSc.ScalarType).name}") # type: ignore ffi = cffi.FFI() -cffi_support.register_type(ffi.typeof('double _Complex'), numba.types.complex128) +cffi_support.register_type(ffi.typeof("double _Complex"), numba.types.complex128) # Get local dofmap sizes for later local tensor tabulations Ssize = S.element.space_dimension @@ -132,7 +144,7 @@ def tabulate_A(A_, w_, c_, coords_, entity_local_index, permutation=ffi.NULL): kernel10(ffi.from_buffer(A10), w_, c_, coords_, entity_local_index, permutation) # A = - A10 * A00^{-1} * A01 - A[:, :] = - A10 @ np.linalg.solve(A00, A01) + A[:, :] = -A10 @ np.linalg.solve(A00, A01) # Prepare a Form with a condensed tabulation kernel @@ -154,7 +166,7 @@ def tabulate_A(A_, w_, c_, coords_, entity_local_index, permutation=ffi.NULL): solver.solve(b, uc.vector) # Pure displacement based formulation -a = form(- ufl.inner(sigma_u(u), ufl.grad(v)) * ufl.dx) +a = form(-ufl.inner(sigma_u(u), ufl.grad(v)) * ufl.dx) A = assemble_matrix(a, bcs=[bc]) A.assemble() @@ -170,7 +182,7 @@ def tabulate_A(A_, w_, c_, coords_, entity_local_index, permutation=ffi.NULL): if len(cells) > 0: value = uc.eval(p, cells[0]) print(value[1]) - assert np.isclose(value[1], 23.95, rtol=1.e-2) + assert np.isclose(value[1], 23.95, rtol=1.0e-2) # Check the equality of displacement based and mixed condensed global # matrices, i.e. check that condensation is exact diff --git a/python/demo/demo_stokes.py b/python/demo/demo_stokes.py index 25fc430e2f7..a628114de85 100644 --- a/python/demo/demo_stokes.py +++ b/python/demo/demo_stokes.py @@ -93,8 +93,15 @@ import ufl from basix.ufl import element, mixed_element from dolfinx import fem, la -from dolfinx.fem import (Constant, Function, dirichletbc, extract_function_spaces, form, functionspace, - locate_dofs_topological) +from dolfinx.fem import ( + Constant, + Function, + dirichletbc, + extract_function_spaces, + form, + functionspace, + locate_dofs_topological, +) from dolfinx.fem.petsc import assemble_matrix_block, assemble_vector_block from dolfinx.io import XDMFFile from dolfinx.mesh import CellType, create_rectangle, locate_entities_boundary @@ -106,14 +113,16 @@ # + # Create mesh -msh = create_rectangle(MPI.COMM_WORLD, [np.array([0, 0]), np.array([1, 1])], - [32, 32], CellType.triangle) +msh = create_rectangle( + MPI.COMM_WORLD, [np.array([0, 0]), np.array([1, 1])], [32, 32], CellType.triangle +) # Function to mark x = 0, x = 1 and y = 0 def noslip_boundary(x): - return np.logical_or(np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)), - np.isclose(x[1], 0.0)) + return np.logical_or( + np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)), np.isclose(x[1], 0.0) + ) # Function to mark the lid (y = 1) @@ -124,6 +133,8 @@ def lid(x): # Lid velocity def lid_velocity_expression(x): return np.stack((np.ones(x.shape[1]), np.zeros(x.shape[1]))) + + # - # Two {py:class}`function spaces ` are @@ -140,7 +151,7 @@ def lid_velocity_expression(x): # + # No-slip condition on boundaries where x = 0, x = 1, and y = 0 -noslip = np.zeros(msh.geometry.dim, dtype=PETSc.ScalarType) # type: ignore +noslip = np.zeros(msh.geometry.dim, dtype=PETSc.ScalarType) # type: ignore facets = locate_entities_boundary(msh, 1, noslip_boundary) bc0 = dirichletbc(noslip, locate_dofs_topological(V, 1, facets), V) @@ -163,8 +174,7 @@ def lid_velocity_expression(x): (v, q) = ufl.TestFunction(V), ufl.TestFunction(Q) f = Constant(msh, (PETSc.ScalarType(0), PETSc.ScalarType(0))) # type: ignore -a = form([[inner(grad(u), grad(v)) * dx, inner(p, div(v)) * dx], - [inner(div(u), q) * dx, None]]) +a = form([[inner(grad(u), grad(v)) * dx, inner(p, div(v)) * dx], [inner(div(u), q) * dx, None]]) L = form([inner(f, v) * dx, inner(Constant(msh, PETSc.ScalarType(0)), q) * dx]) # type: ignore # - @@ -172,8 +182,7 @@ def lid_velocity_expression(x): # solvers for this problem: a_p11 = form(inner(p, q) * dx) -a_p = [[a[0][0], None], - [None, a_p11]] +a_p = [[a[0][0], None], [None, a_p11]] # ### Nested matrix solver # @@ -267,8 +276,7 @@ def nested_iterative_solver(): # space `Q`). The vectors for `u` and `p` are combined to form a # nested vector and the system is solved. u, p = Function(V), Function(Q) - x = PETSc.Vec().createNest([la.create_petsc_vector_wrap(u.x), - la.create_petsc_vector_wrap(p.x)]) + x = PETSc.Vec().createNest([la.create_petsc_vector_wrap(u.x), la.create_petsc_vector_wrap(p.x)]) ksp.solve(b, x) # Save solution to file in XDMF format for visualization, e.g. with @@ -303,6 +311,7 @@ def nested_iterative_solver(): # (non-nested) matrices. We first create a helper function for # assembling the linear operators and the RHS vector. + def block_operators(): """Return block operators and block RHS vector for the Stokes problem""" @@ -327,6 +336,7 @@ def block_operators(): return A, P, b + # The following function solves the Stokes problem using a # block-diagonal preconditioner and monolithic PETSc matrices. @@ -344,7 +354,9 @@ def block_iterative_solver(): Q_map = Q.dofmap.index_map offset_u = V_map.local_range[0] * V.dofmap.index_map_bs + Q_map.local_range[0] offset_p = offset_u + V_map.size_local * V.dofmap.index_map_bs - is_u = PETSc.IS().createStride(V_map.size_local * V.dofmap.index_map_bs, offset_u, 1, comm=PETSc.COMM_SELF) + is_u = PETSc.IS().createStride( + V_map.size_local * V.dofmap.index_map_bs, offset_u, 1, comm=PETSc.COMM_SELF + ) is_p = PETSc.IS().createStride(Q_map.size_local, offset_p, 1, comm=PETSc.COMM_SELF) # Create a MINRES Krylov solver and a block-diagonal preconditioner @@ -382,7 +394,7 @@ def block_iterative_solver(): u, p = Function(V), Function(Q) offset = V_map.size_local * V.dofmap.index_map_bs u.x.array[:offset] = x.array_r[:offset] - p.x.array[:(len(x.array_r) - offset)] = x.array_r[offset:] + p.x.array[: (len(x.array_r) - offset)] = x.array_r[offset:] # Compute the $L^2$ norms of the solution vectors norm_u, norm_p = u.x.norm(), p.x.norm() @@ -436,7 +448,7 @@ def block_direct_solver(): u, p = Function(V), Function(Q) offset = V.dofmap.index_map.size_local * V.dofmap.index_map_bs u.x.array[:offset] = x.array_r[:offset] - p.x.array[:(len(x.array_r) - offset)] = x.array_r[offset:] + p.x.array[: (len(x.array_r) - offset)] = x.array_r[offset:] # Compute the $L^2$ norms of the u and p vectors norm_u, norm_p = u.x.norm(), p.x.norm() @@ -456,7 +468,6 @@ def block_direct_solver(): def mixed_direct(): - # Create the Taylot-Hood function space TH = mixed_element([P2, P1]) W = functionspace(msh, TH) diff --git a/python/demo/demo_tnt-elements.py b/python/demo/demo_tnt-elements.py index 3cdcc889a77..7aee1db32a7 100644 --- a/python/demo/demo_tnt-elements.py +++ b/python/demo/demo_tnt-elements.py @@ -33,7 +33,7 @@ from dolfinx.fem.petsc import LinearProblem from ufl import SpatialCoordinate, TestFunction, TrialFunction, cos, div, dx, grad, inner, sin -mpl.use('agg') +mpl.use("agg") # - # ## Defining a degree 1 TNT element @@ -74,15 +74,17 @@ # + wcoeffs2 = np.empty((8, 9)) pts, wts = basix.make_quadrature(basix.CellType.quadrilateral, 4) -evals = basix.tabulate_polynomials(basix.PolynomialType.legendre, basix.CellType.quadrilateral, 2, pts) +evals = basix.tabulate_polynomials( + basix.PolynomialType.legendre, basix.CellType.quadrilateral, 2, pts +) for j, v in enumerate(evals): wcoeffs2[0, j] = sum(v * wts) # 1 wcoeffs2[1, j] = sum(v * pts[:, 1] * wts) # y - wcoeffs2[2, j] = sum(v * pts[:, 1]**2 * wts) # y^2 + wcoeffs2[2, j] = sum(v * pts[:, 1] ** 2 * wts) # y^2 wcoeffs2[3, j] = sum(v * pts[:, 0] * pts[:, 1] * wts) # xy wcoeffs2[4, j] = sum(v * pts[:, 0] * pts[:, 1] ** 2 * wts) # xy^2 - wcoeffs2[5, j] = sum(v * pts[:, 0]**2 * pts[:, 1] * wts) # x^2y + wcoeffs2[5, j] = sum(v * pts[:, 0] ** 2 * pts[:, 1] * wts) # x^2y # - # ### Interpolation operators @@ -99,7 +101,7 @@ for v in topology[0]: x[0].append(np.array(geometry[v])) - M[0].append(np.array([[[[1.]]]])) + M[0].append(np.array([[[[1.0]]]])) # - # For each edge, we define points and a matrix that represent the @@ -133,8 +135,18 @@ # this element so that it can be used with FFCx/DOLFINx. tnt_degree1 = basix.ufl.custom_element( - basix.CellType.quadrilateral, [], wcoeffs, x, M, 0, basix.MapType.identity, - basix.SobolevSpace.H1, False, 1, 2) + basix.CellType.quadrilateral, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 1, + 2, +) # ## Creating higher degree TNT elements # @@ -169,11 +181,13 @@ def create_tnt_quad(degree): # Vertices for v in topology[0]: x[0].append(np.array(geometry[v])) - M[0].append(np.array([[[[1.]]]])) + M[0].append(np.array([[[[1.0]]]])) # Edges pts, wts = basix.make_quadrature(basix.CellType.interval, 2 * degree) - poly = basix.tabulate_polynomials(basix.PolynomialType.legendre, basix.CellType.interval, degree - 1, pts) + poly = basix.tabulate_polynomials( + basix.PolynomialType.legendre, basix.CellType.interval, degree - 1, pts + ) edge_ndofs = poly.shape[0] for e in topology[1]: v0 = geometry[e[0]] @@ -192,7 +206,9 @@ def create_tnt_quad(degree): M[2].append(np.zeros([0, 1, 0, 1])) else: pts, wts = basix.make_quadrature(basix.CellType.quadrilateral, 2 * degree - 1) - poly = basix.tabulate_polynomials(basix.PolynomialType.legendre, basix.CellType.quadrilateral, degree - 2, pts) + poly = basix.tabulate_polynomials( + basix.PolynomialType.legendre, basix.CellType.quadrilateral, degree - 2, pts + ) face_ndofs = poly.shape[0] x[2].append(pts) mat = np.zeros((face_ndofs, 1, len(pts), 1)) @@ -200,8 +216,19 @@ def create_tnt_quad(degree): mat[i, 0, :, 0] = wts[:] * poly[i, :] M[2].append(mat) - return basix.ufl.custom_element(basix.CellType.quadrilateral, [], wcoeffs, x, M, 0, - basix.MapType.identity, basix.SobolevSpace.H1, False, degree, degree + 1) + return basix.ufl.custom_element( + basix.CellType.quadrilateral, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + degree, + degree + 1, + ) # ## Comparing TNT elements and Q elements @@ -219,7 +246,7 @@ def poisson_error(V: fem.FunctionSpace): x = SpatialCoordinate(msh) u_exact = sin(10 * x[1]) * cos(15 * x[0]) - f = - div(grad(u_exact)) + f = -div(grad(u_exact)) a = inner(grad(u), grad(v)) * dx L = inner(f, v) * dx @@ -237,7 +264,7 @@ def poisson_error(V: fem.FunctionSpace): problem = LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_rtol": 1e-12}) uh = problem.solve() - M = (u_exact - uh)**2 * dx + M = (u_exact - uh) ** 2 * dx M = fem.form(M) error = msh.comm.allreduce(fem.assemble_scalar(M), op=MPI.SUM) return error**0.5 diff --git a/python/demo/demo_types.py b/python/demo/demo_types.py index aecaefc99b0..05922c7c8eb 100644 --- a/python/demo/demo_types.py +++ b/python/demo/demo_types.py @@ -46,6 +46,7 @@ def display_scalar(u, name, filter=np.real): """Plot the solution using pyvista""" try: import pyvista + cells, types, x = plot.vtk_mesh(u.function_space) grid = pyvista.UnstructuredGrid(cells, types, x) grid.point_data["u"] = filter(u.x.array) @@ -67,10 +68,13 @@ def display_vector(u, name, filter=np.real): """Plot the solution using pyvista""" try: import pyvista + V = u.function_space cells, types, x = plot.vtk_mesh(V) grid = pyvista.UnstructuredGrid(cells, types, x) - grid.point_data["u"] = filter(np.insert(u.x.array.reshape(x.shape[0], V.dofmap.index_map_bs), 2, 0, axis=1)) + grid.point_data["u"] = filter( + np.insert(u.x.array.reshape(x.shape[0], V.dofmap.index_map_bs), 2, 0, axis=1) + ) plotter = pyvista.Plotter() plotter.add_mesh(grid.warp_by_scalar(), show_edges=True) plotter.add_title(f"{name}: real" if filter is np.real else f"{name}: imag") @@ -93,11 +97,16 @@ def poisson(dtype): """ # Create a mesh and locate facets by a geometric condition - msh = mesh.create_rectangle(comm=comm, points=((0.0, 0.0), (2.0, 1.0)), n=(32, 16), - cell_type=mesh.CellType.triangle, dtype=np.real(dtype(0)).dtype) - facets = mesh.locate_entities_boundary(msh, dim=1, - marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 2.0))) + msh = mesh.create_rectangle( + comm=comm, + points=((0.0, 0.0), (2.0, 1.0)), + n=(32, 16), + cell_type=mesh.CellType.triangle, + dtype=np.real(dtype(0)).dtype, + ) + facets = mesh.locate_entities_boundary( + msh, dim=1, marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 2.0)) + ) # Define a variational problem. V = fem.functionspace(msh, ("Lagrange", 1)) @@ -145,15 +154,21 @@ def poisson(dtype): # different precision float and complex scalar types for the finite # element solution. + def elasticity(dtype) -> fem.Function: """Linearised elasticity problem solver.""" # Create a mesh and locate facets by a geometric condition - msh = mesh.create_rectangle(comm=comm, points=((0.0, 0.0), (2.0, 1.0)), n=(32, 16), - cell_type=mesh.CellType.triangle, dtype=np.real(dtype(0)).dtype) - facets = mesh.locate_entities_boundary(msh, dim=1, - marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 2.0))) + msh = mesh.create_rectangle( + comm=comm, + points=((0.0, 0.0), (2.0, 1.0)), + n=(32, 16), + cell_type=mesh.CellType.triangle, + dtype=np.real(dtype(0)).dtype, + ) + facets = mesh.locate_entities_boundary( + msh, dim=1, marker=lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 2.0)) + ) # Define the variational problem. gdim = msh.geometry.dim @@ -167,7 +182,9 @@ def elasticity(dtype) -> fem.Function: def σ(v): """Return an expression for the stress σ given a displacement field""" - return 2.0 * μ * ufl.sym(ufl.grad(v)) + λ * ufl.tr(ufl.sym(ufl.grad(v))) * ufl.Identity(len(v)) + return 2.0 * μ * ufl.sym(ufl.grad(v)) + λ * ufl.tr(ufl.sym(ufl.grad(v))) * ufl.Identity( + len(v) + ) u, v = ufl.TrialFunction(V), ufl.TestFunction(V) a = ufl.inner(σ(u), ufl.grad(v)) * ufl.dx @@ -198,6 +215,7 @@ def σ(v): return uh + # Solve problems for different types diff --git a/python/demo/demo_waveguide/analytical_modes.py b/python/demo/demo_waveguide/analytical_modes.py index 4c13656158f..abe1100a2d8 100644 --- a/python/demo/demo_waveguide/analytical_modes.py +++ b/python/demo/demo_waveguide/analytical_modes.py @@ -64,13 +64,16 @@ import numpy as np -def TMx_condition(kx_d: complex, kx_v: complex, eps_d: complex, eps_v: complex, d: float, h: float) -> float: - return (kx_d / eps_d * np.tan(kx_d * d) + kx_v / eps_v * np.tan(kx_v * (h - d))) +def TMx_condition( + kx_d: complex, kx_v: complex, eps_d: complex, eps_v: complex, d: float, h: float +) -> float: + return kx_d / eps_d * np.tan(kx_d * d) + kx_v / eps_v * np.tan(kx_v * (h - d)) def TEx_condition(kx_d: complex, kx_v: complex, d: float, h: float) -> float: return kx_d / np.tan(kx_d * d) + kx_v / np.tan(kx_v * (h - d)) + # Then, we can define the `verify_mode` function, to check whether a # certain $k_z$ satisfy the equations (below a certain threshold). In # other words, we provide a certain $k_z$, together with the geometrical @@ -79,11 +82,19 @@ def TEx_condition(kx_d: complex, kx_v: complex, d: float, h: float) -> float: # modes are close to $0$. -def verify_mode(kz: complex, w: float, h: float, d: float, lmbd0: float, - eps_d: complex, eps_v: complex, threshold: float) -> np.bool_: +def verify_mode( + kz: complex, + w: float, + h: float, + d: float, + lmbd0: float, + eps_d: complex, + eps_v: complex, + threshold: float, +) -> np.bool_: k0 = 2 * np.pi / lmbd0 ky = np.pi / w # we assume n = 1 - kx_d_target = np.sqrt(k0**2 * eps_d - ky**2 + - kz**2 + 0j) + kx_d_target = np.sqrt(k0**2 * eps_d - ky**2 + -(kz**2) + 0j) alpha = kx_d_target**2 beta = alpha - k0**2 * (eps_d - eps_v) kx_v = np.sqrt(beta) diff --git a/python/demo/demo_waveguide/demo_half_loaded_waveguide.py b/python/demo/demo_waveguide/demo_half_loaded_waveguide.py index f12ad5adb01..313e97e33f9 100644 --- a/python/demo/demo_waveguide/demo_half_loaded_waveguide.py +++ b/python/demo/demo_waveguide/demo_half_loaded_waveguide.py @@ -54,6 +54,7 @@ try: import pyvista + have_pyvista = True except ModuleNotFoundError: print("pyvista and pyvistaqt are required to visualise the solution") @@ -77,7 +78,9 @@ nx = 300 ny = int(0.4 * nx) -msh = create_rectangle(MPI.COMM_WORLD, np.array([[0, 0], [w, h]]), np.array([nx, ny]), CellType.quadrilateral) +msh = create_rectangle( + MPI.COMM_WORLD, np.array([[0, 0], [w, h]]), np.array([nx, ny]), CellType.quadrilateral +) msh.topology.create_connectivity(msh.topology.dim - 1, msh.topology.dim) # - @@ -305,7 +308,7 @@ def Omega_v(x): # that $k_z$ will be quite close to $k_0$ in value, for instance $k_z = # 0.5k_0^2$. Therefore, we can set a target value of $-(0.5k_0^2)$: -eps.setTarget(-(0.5 * k0)**2) +eps.setTarget(-((0.5 * k0) ** 2)) # Then, we need to define the number of eigenvalues we want to # calculate. We can do this with the `setDimensions` function, where we @@ -383,7 +386,9 @@ def Omega_v(x): V_cells, V_types, V_x = plot.vtk_mesh(V_dg) V_grid = pyvista.UnstructuredGrid(V_cells, V_types, V_x) Et_values = np.zeros((V_x.shape[0], 3), dtype=np.float64) - Et_values[:, : msh.topology.dim] = Et_dg.x.array.reshape(V_x.shape[0], msh.topology.dim).real + Et_values[:, : msh.topology.dim] = Et_dg.x.array.reshape( + V_x.shape[0], msh.topology.dim + ).real V_grid.point_data["u"] = Et_values diff --git a/python/demo/test.py b/python/demo/test.py index 7e8e998db40..b037aac7d18 100644 --- a/python/demo/test.py +++ b/python/demo/test.py @@ -15,7 +15,7 @@ # Build list of demo programs demos = [] -demo_files = list(path.glob('**/*.py')) +demo_files = list(path.glob("**/*.py")) for f in demo_files: demos.append((f.parent, f.name)) diff --git a/python/doc/source/conf.py b/python/doc/source/conf.py index 99e5ae94e4a..ab531bdbb54 100644 --- a/python/doc/source/conf.py +++ b/python/doc/source/conf.py @@ -6,11 +6,12 @@ import os import sys +import jupytext_process + import dolfinx sys.path.insert(0, os.path.abspath(".")) -import jupytext_process myst_heading_anchors = 3 diff --git a/python/doc/source/jupytext_process.py b/python/doc/source/jupytext_process.py index e24bab082b5..40eccbda2b0 100644 --- a/python/doc/source/jupytext_process.py +++ b/python/doc/source/jupytext_process.py @@ -24,11 +24,11 @@ def process(): # Iterate over subdirectories containing demos for subdir in subdirs: # Make demo doc directory - demo_dir = pathlib.Path('./demos') + demo_dir = pathlib.Path("./demos") demo_dir.mkdir(parents=True, exist_ok=True) # Process each demo using jupytext/myst - for demo in subdir.glob('**/demo*.py'): + for demo in subdir.glob("**/demo*.py"): python_demo = jupytext.read(demo) myst_text = jupytext.writes(python_demo, fmt="myst") diff --git a/python/dolfinx/__init__.py b/python/dolfinx/__init__.py index e18d68ad761..88dc2394e94 100644 --- a/python/dolfinx/__init__.py +++ b/python/dolfinx/__init__.py @@ -11,25 +11,38 @@ try: from petsc4py import PETSc as _PETSc + default_scalar_type = _PETSc.ScalarType # type: ignore default_real_type = _PETSc.RealType # type: ignore except ImportError: import numpy as _np + default_scalar_type = _np.float64 default_real_type = _np.float64 from dolfinx import common from dolfinx import cpp as _cpp from dolfinx import fem, geometry, graph, io, jit, la, log, mesh, nls, plot, utils + # Initialise logging -from dolfinx.common import TimingType, git_commit_hash, has_debug, has_kahip, has_parmetis, list_timings, timing +from dolfinx.common import ( + TimingType, + git_commit_hash, + has_debug, + has_kahip, + has_parmetis, + list_timings, + timing, +) from dolfinx.cpp import __version__ _cpp.common.init_logging(sys.argv) del _cpp, sys + def get_include(user=False): import os + d = os.path.dirname(__file__) if os.path.exists(os.path.join(d, "wrappers")): # Package is installed @@ -39,9 +52,24 @@ def get_include(user=False): return os.path.join(os.path.dirname(d), "src") - __all__ = [ - "fem", "common", "geometry", "graph", "io", "jit", "la", "log", "mesh", "nls", "plot", "utils", - "TimingType", "git_commit_hash", "has_debug", "has_kahip", "has_parmetis", "list_timings", - "timing" + "fem", + "common", + "geometry", + "graph", + "io", + "jit", + "la", + "log", + "mesh", + "nls", + "plot", + "utils", + "TimingType", + "git_commit_hash", + "has_debug", + "has_kahip", + "has_parmetis", + "list_timings", + "timing", ] diff --git a/python/dolfinx/common.py b/python/dolfinx/common.py index 5ef1f82a93e..2fd5fda1c3d 100644 --- a/python/dolfinx/common.py +++ b/python/dolfinx/common.py @@ -9,7 +9,9 @@ import typing from dolfinx import cpp as _cpp -from dolfinx.cpp.common import IndexMap, git_commit_hash, has_adios2, has_debug, has_kahip, has_parmetis # noqa +from dolfinx.cpp.common import ( + IndexMap, +) __all__ = ["IndexMap", "Timer", "timed"] diff --git a/python/dolfinx/fem/__init__.py b/python/dolfinx/fem/__init__.py index 5023155310c..3c9a3cb9d8b 100644 --- a/python/dolfinx/fem/__init__.py +++ b/python/dolfinx/fem/__init__.py @@ -5,16 +5,39 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Tools for assembling and manipulating finite element forms.""" -from dolfinx.cpp.fem import IntegralType, create_nonmatching_meshes_interpolation_data +from dolfinx.cpp.fem import ( + IntegralType, + create_nonmatching_meshes_interpolation_data, + transpose_dofmap, +) from dolfinx.cpp.fem import create_sparsity_pattern as _create_sparsity_pattern -from dolfinx.cpp.fem import transpose_dofmap -from dolfinx.fem.assemble import (apply_lifting, assemble_matrix, assemble_scalar, assemble_vector, create_matrix, - create_vector, set_bc) -from dolfinx.fem.bcs import DirichletBC, bcs_by_block, dirichletbc, locate_dofs_geometrical, locate_dofs_topological +from dolfinx.fem.assemble import ( + apply_lifting, + assemble_matrix, + assemble_scalar, + assemble_vector, + create_matrix, + create_vector, + set_bc, +) +from dolfinx.fem.bcs import ( + DirichletBC, + bcs_by_block, + dirichletbc, + locate_dofs_geometrical, + locate_dofs_topological, +) from dolfinx.fem.dofmap import DofMap from dolfinx.fem.element import CoordinateElement, coordinate_element from dolfinx.fem.forms import Form, extract_function_spaces, form, form_cpp_class -from dolfinx.fem.function import Constant, ElementMetaData, Expression, Function, FunctionSpace, functionspace +from dolfinx.fem.function import ( + Constant, + ElementMetaData, + Expression, + Function, + FunctionSpace, + functionspace, +) def create_sparsity_pattern(a: Form): @@ -35,11 +58,33 @@ def create_sparsity_pattern(a: Form): __all__ = [ - "Constant", "Expression", "Function", "ElementMetaData", "create_matrix", - "functionspace", "FunctionSpace", "create_sparsity_pattern", - "assemble_scalar", "assemble_matrix", "assemble_vector", "apply_lifting", "set_bc", - "DirichletBC", "dirichletbc", "bcs_by_block", "DofMap", "Form", - "form", "IntegralType", "create_vector", - "locate_dofs_geometrical", "locate_dofs_topological", - "extract_function_spaces", "transpose_dofmap", "create_nonmatching_meshes_interpolation_data", - "CoordinateElement", "coordinate_element", "form_cpp_class"] + "Constant", + "Expression", + "Function", + "ElementMetaData", + "create_matrix", + "functionspace", + "FunctionSpace", + "create_sparsity_pattern", + "assemble_scalar", + "assemble_matrix", + "assemble_vector", + "apply_lifting", + "set_bc", + "DirichletBC", + "dirichletbc", + "bcs_by_block", + "DofMap", + "Form", + "form", + "IntegralType", + "create_vector", + "locate_dofs_geometrical", + "locate_dofs_topological", + "extract_function_spaces", + "transpose_dofmap", + "create_nonmatching_meshes_interpolation_data", + "CoordinateElement", + "coordinate_element", + "form_cpp_class", +] diff --git a/python/dolfinx/fem/assemble.py b/python/dolfinx/fem/assemble.py index 44d2c538141..6d6e5dc009e 100644 --- a/python/dolfinx/fem/assemble.py +++ b/python/dolfinx/fem/assemble.py @@ -22,8 +22,9 @@ from dolfinx.fem.forms import Form -def pack_constants(form: typing.Union[Form, typing.Sequence[Form]]) -> typing.Union[np.ndarray, - typing.Sequence[np.ndarray]]: +def pack_constants( + form: typing.Union[Form, typing.Sequence[Form]], +) -> typing.Union[np.ndarray, typing.Sequence[np.ndarray]]: """Compute form constants. Pack the `constants` that appear in forms. The packed constants can @@ -41,6 +42,7 @@ def pack_constants(form: typing.Union[Form, typing.Sequence[Form]]) -> typing.Un A `constant` array for each form. """ + def _pack(form): if form is None: return None @@ -70,6 +72,7 @@ def pack_coefficients(form: typing.Union[Form, typing.Sequence[Form]]): Coefficients for each form. """ + def _pack(form): if form is None: return {} @@ -80,6 +83,7 @@ def _pack(form): return _pack(form) + # -- Vector and matrix instantiation ----------------------------------------- @@ -109,6 +113,7 @@ def create_matrix(a: Form, block_mode: typing.Optional[la.BlockMode] = None) -> # -- Scalar assembly --------------------------------------------------------- + def assemble_scalar(M: Form, constants=None, coeffs=None): """Assemble functional. The returned value is local and not accumulated across processes. @@ -139,9 +144,9 @@ def assemble_scalar(M: Form, constants=None, coeffs=None): # -- Vector assembly --------------------------------------------------------- + @functools.singledispatch -def assemble_vector(L: typing.Any, - constants=None, coeffs=None): +def assemble_vector(L: typing.Any, constants=None, coeffs=None): return _assemble_vector_form(L, constants, coeffs) @@ -209,13 +214,19 @@ def _assemble_vector_array(b: np.ndarray, L: Form, constants=None, coeffs=None): _cpp.fem.assemble_vector(b, L._cpp_object, constants, coeffs) return b + # -- Matrix assembly --------------------------------------------------------- @functools.singledispatch -def assemble_matrix(a: typing.Any, bcs: typing.Optional[list[DirichletBC]] = None, - diagonal: float = 1.0, constants=None, coeffs=None, - block_mode: typing.Optional[la.BlockMode] = None): +def assemble_matrix( + a: typing.Any, + bcs: typing.Optional[list[DirichletBC]] = None, + diagonal: float = 1.0, + constants=None, + coeffs=None, + block_mode: typing.Optional[la.BlockMode] = None, +): """Assemble bilinear form into a matrix. Args: @@ -246,8 +257,14 @@ def assemble_matrix(a: typing.Any, bcs: typing.Optional[list[DirichletBC]] = Non @assemble_matrix.register -def _assemble_matrix_csr(A: la.MatrixCSR, a: Form, bcs: typing.Optional[list[DirichletBC]] = None, - diagonal: float = 1.0, constants=None, coeffs=None) -> la.MatrixCSR: +def _assemble_matrix_csr( + A: la.MatrixCSR, + a: Form, + bcs: typing.Optional[list[DirichletBC]] = None, + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> la.MatrixCSR: """Assemble bilinear form into a matrix. Args: @@ -284,10 +301,15 @@ def _assemble_matrix_csr(A: la.MatrixCSR, a: Form, bcs: typing.Optional[list[Dir # -- Modifiers for Dirichlet conditions --------------------------------------- -def apply_lifting(b: np.ndarray, a: list[Form], - bcs: list[list[DirichletBC]], - x0: typing.Optional[list[np.ndarray]] = None, - scale: float = 1.0, constants=None, coeffs=None) -> None: +def apply_lifting( + b: np.ndarray, + a: list[Form], + bcs: list[list[DirichletBC]], + x0: typing.Optional[list[np.ndarray]] = None, + scale: float = 1.0, + constants=None, + coeffs=None, +) -> None: """Modify RHS vector b for lifting of Dirichlet boundary conditions. It modifies b such that: @@ -308,16 +330,30 @@ def apply_lifting(b: np.ndarray, a: list[Form], """ x0 = [] if x0 is None else x0 - constants = [_pack_constants(form._cpp_object) if form is not None else np.array( - [], dtype=b.dtype) for form in a] if constants is None else constants - coeffs = [{} if form is None else _pack_coefficients(form._cpp_object) for form in a] if coeffs is None else coeffs + constants = ( + [ + _pack_constants(form._cpp_object) if form is not None else np.array([], dtype=b.dtype) + for form in a + ] + if constants is None + else constants + ) + coeffs = ( + [{} if form is None else _pack_coefficients(form._cpp_object) for form in a] + if coeffs is None + else coeffs + ) _a = [None if form is None else form._cpp_object for form in a] _bcs = [[bc._cpp_object for bc in bcs0] for bcs0 in bcs] _cpp.fem.apply_lifting(b, _a, constants, coeffs, _bcs, x0, scale) -def set_bc(b: np.ndarray, bcs: list[DirichletBC], - x0: typing.Optional[np.ndarray] = None, scale: float = 1.0) -> None: +def set_bc( + b: np.ndarray, + bcs: list[DirichletBC], + x0: typing.Optional[np.ndarray] = None, + scale: float = 1.0, +) -> None: """Insert boundary condition values into vector. Only local (owned) entries are set, hence communication after diff --git a/python/dolfinx/fem/bcs.py b/python/dolfinx/fem/bcs.py index f05aefa6d90..a48fd206c8b 100644 --- a/python/dolfinx/fem/bcs.py +++ b/python/dolfinx/fem/bcs.py @@ -22,9 +22,10 @@ from dolfinx import cpp as _cpp -def locate_dofs_geometrical(V: typing.Union[dolfinx.fem.FunctionSpace, - typing.Iterable[dolfinx.fem.FunctionSpace]], - marker: typing.Callable) -> np.ndarray: +def locate_dofs_geometrical( + V: typing.Union[dolfinx.fem.FunctionSpace, typing.Iterable[dolfinx.fem.FunctionSpace]], + marker: typing.Callable, +) -> np.ndarray: """Locate degrees-of-freedom geometrically using a marker function. Args: @@ -54,10 +55,12 @@ def locate_dofs_geometrical(V: typing.Union[dolfinx.fem.FunctionSpace, return _cpp.fem.locate_dofs_geometrical(_V, marker) -def locate_dofs_topological(V: typing.Union[dolfinx.fem.FunctionSpace, - typing.Iterable[dolfinx.fem.FunctionSpace]], - entity_dim: int, entities: numpy.typing.NDArray[np.int32], - remote: bool = True) -> np.ndarray: +def locate_dofs_topological( + V: typing.Union[dolfinx.fem.FunctionSpace, typing.Iterable[dolfinx.fem.FunctionSpace]], + entity_dim: int, + entities: numpy.typing.NDArray[np.int32], + remote: bool = True, +) -> np.ndarray: """Locate degrees-of-freedom belonging to mesh entities topologically. Args: @@ -80,15 +83,19 @@ def locate_dofs_topological(V: typing.Union[dolfinx.fem.FunctionSpace, _entities = np.asarray(entities, dtype=np.int32) try: - return _cpp.fem.locate_dofs_topological(V._cpp_object, entity_dim, _entities, remote) # type: ignore + return _cpp.fem.locate_dofs_topological(V._cpp_object, entity_dim, _entities, remote) # type: ignore except AttributeError: _V = [space._cpp_object for space in V] return _cpp.fem.locate_dofs_topological(_V, entity_dim, _entities, remote) class DirichletBC: - _cpp_object: typing.Union[_cpp.fem.DirichletBC_complex64, _cpp.fem.DirichletBC_complex128, - _cpp.fem.DirichletBC_float32, _cpp.fem.DirichletBC_float64] + _cpp_object: typing.Union[ + _cpp.fem.DirichletBC_complex64, + _cpp.fem.DirichletBC_complex128, + _cpp.fem.DirichletBC_float32, + _cpp.fem.DirichletBC_float64, + ] def __init__(self, bc): """Representation of Dirichlet boundary condition which is imposed on @@ -124,9 +131,11 @@ def function_space(self) -> dolfinx.fem.FunctionSpace: return self._cpp_object.function_space -def dirichletbc(value: typing.Union[Function, Constant, np.ndarray], - dofs: numpy.typing.NDArray[np.int32], - V: typing.Optional[dolfinx.fem.FunctionSpace] = None) -> DirichletBC: +def dirichletbc( + value: typing.Union[Function, Constant, np.ndarray], + dofs: numpy.typing.NDArray[np.int32], + V: typing.Optional[dolfinx.fem.FunctionSpace] = None, +) -> DirichletBC: """Create a representation of Dirichlet boundary condition which is imposed on a linear system. @@ -184,8 +193,10 @@ def dirichletbc(value: typing.Union[Function, Constant, np.ndarray], return DirichletBC(bc) -def bcs_by_block(spaces: typing.Iterable[typing.Union[dolfinx.fem.FunctionSpace, None]], - bcs: typing.Iterable[DirichletBC]) -> list[list[DirichletBC]]: +def bcs_by_block( + spaces: typing.Iterable[typing.Union[dolfinx.fem.FunctionSpace, None]], + bcs: typing.Iterable[DirichletBC], +) -> list[list[DirichletBC]]: """Arrange Dirichlet boundary conditions by the function space that they constrain. @@ -195,6 +206,7 @@ def bcs_by_block(spaces: typing.Iterable[typing.Union[dolfinx.fem.FunctionSpace, ``space[i]``. """ + def _bc_space(V, bcs): "Return list of bcs that have the same space as V" return [bc for bc in bcs if V.contains(bc.function_space)] diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 3126a4f7e00..d3295b33f99 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -18,10 +18,14 @@ class CoordinateElement: """Coordinate element describing the geometry map for mesh cells""" - _cpp_object: typing.Union[_cpp.fem.CoordinateElement_float32, _cpp.fem.CoordinateElement_float64] - - def __init__(self, cmap: typing.Union[_cpp.fem.CoordinateElement_float32, - _cpp.fem.CoordinateElement_float64]): + _cpp_object: typing.Union[ + _cpp.fem.CoordinateElement_float32, _cpp.fem.CoordinateElement_float64 + ] + + def __init__( + self, + cmap: typing.Union[_cpp.fem.CoordinateElement_float32, _cpp.fem.CoordinateElement_float64], + ): """Create a coordinate map element. Note: @@ -32,7 +36,9 @@ def __init__(self, cmap: typing.Union[_cpp.fem.CoordinateElement_float32, Args: cmap: A C++ CoordinateElement. """ - assert isinstance(cmap, (_cpp.fem.CoordinateElement_float32, _cpp.fem.CoordinateElement_float64)) + assert isinstance( + cmap, (_cpp.fem.CoordinateElement_float32, _cpp.fem.CoordinateElement_float64) + ) self._cpp_object = cmap @property @@ -47,9 +53,12 @@ def dtype(self) -> npt.DTypeLike: @singledispatch -def coordinate_element(celltype: _cpp.mesh.CellType, degree: int, - variant=int(basix.LagrangeVariant.unset), - dtype: npt.DTypeLike = np.float64): +def coordinate_element( + celltype: _cpp.mesh.CellType, + degree: int, + variant=int(basix.LagrangeVariant.unset), + dtype: npt.DTypeLike = np.float64, +): """Create a Lagrange CoordinateElement from element metadata. Coordinate elements are typically used to create meshes. diff --git a/python/dolfinx/fem/forms.py b/python/dolfinx/fem/forms.py index 730488b6bf0..017d3490c35 100644 --- a/python/dolfinx/fem/forms.py +++ b/python/dolfinx/fem/forms.py @@ -23,13 +23,25 @@ class Form: - _cpp_object: typing.Union[_cpp.fem.Form_complex64, _cpp.fem.Form_complex128, - _cpp.fem.Form_float32, _cpp.fem.Form_float64] + _cpp_object: typing.Union[ + _cpp.fem.Form_complex64, + _cpp.fem.Form_complex128, + _cpp.fem.Form_float32, + _cpp.fem.Form_float64, + ] _code: typing.Optional[str] - def __init__(self, form: typing.Union[_cpp.fem.Form_complex64, _cpp.fem.Form_complex128, - _cpp.fem.Form_float32, _cpp.fem.Form_float64], - ufcx_form=None, code: typing.Optional[str] = None): + def __init__( + self, + form: typing.Union[ + _cpp.fem.Form_complex64, + _cpp.fem.Form_complex128, + _cpp.fem.Form_float32, + _cpp.fem.Form_float64, + ], + ufcx_form=None, + code: typing.Optional[str] = None, + ): """A finite element form Note: @@ -82,10 +94,11 @@ def integral_types(self): return self._cpp_object.integral_types -def form_cpp_class(dtype: npt.DTypeLike) -> typing.Union[_cpp.fem.Form_float32, - _cpp.fem.Form_float64, - _cpp.fem.Form_complex64, - _cpp.fem.Form_complex128]: +def form_cpp_class( + dtype: npt.DTypeLike, +) -> typing.Union[ + _cpp.fem.Form_float32, _cpp.fem.Form_float64, _cpp.fem.Form_complex64, _cpp.fem.Form_complex128 +]: """Return the wrapped C++ class of a variational form of a specific scalar type. Args: @@ -110,16 +123,20 @@ def form_cpp_class(dtype: npt.DTypeLike) -> typing.Union[_cpp.fem.Form_float32, raise NotImplementedError(f"Type {dtype} not supported.") -_ufl_to_dolfinx_domain = {"cell": IntegralType.cell, - "exterior_facet": IntegralType.exterior_facet, - "interior_facet": IntegralType.interior_facet, - "vertex": IntegralType.vertex} +_ufl_to_dolfinx_domain = { + "cell": IntegralType.cell, + "exterior_facet": IntegralType.exterior_facet, + "interior_facet": IntegralType.interior_facet, + "vertex": IntegralType.vertex, +} -def form(form: typing.Union[ufl.Form, typing.Iterable[ufl.Form]], - dtype: npt.DTypeLike = default_scalar_type, - form_compiler_options: typing.Optional[dict] = None, - jit_options: typing.Optional[dict] = None): +def form( + form: typing.Union[ufl.Form, typing.Iterable[ufl.Form]], + dtype: npt.DTypeLike = default_scalar_type, + form_compiler_options: typing.Optional[dict] = None, + jit_options: typing.Optional[dict] = None, +): """Create a Form or an array of Forms. Args: @@ -149,7 +166,7 @@ def _form(form): """Compile a single UFL form""" # Extract subdomain data from UFL form sd = form.subdomain_data() - domain, = list(sd.keys()) # Assuming single domain + (domain,) = list(sd.keys()) # Assuming single domain # Check that subdomain data for each integral type is the same for data in sd.get(domain).values(): assert all([d is data[0] for d in data]) @@ -157,9 +174,9 @@ def _form(form): mesh = domain.ufl_cargo() if mesh is None: raise RuntimeError("Expecting to find a Mesh in the form.") - ufcx_form, module, code = jit.ffcx_jit(mesh.comm, form, - form_compiler_options=form_compiler_options, - jit_options=jit_options) + ufcx_form, module, code = jit.ffcx_jit( + mesh.comm, form, form_compiler_options=form_compiler_options, jit_options=jit_options + ) # For each argument in form extract its function space V = [arg.ufl_function_space()._cpp_object for arg in form.arguments()] @@ -167,8 +184,10 @@ def _form(form): # Prepare coefficients data. For every coefficient in form take # its C++ object. original_coeffs = form.coefficients() - coeffs = [original_coeffs[ufcx_form.original_coefficient_position[i] - ]._cpp_object for i in range(ufcx_form.num_coefficients)] + coeffs = [ + original_coeffs[ufcx_form.original_coefficient_position[i]]._cpp_object + for i in range(ufcx_form.num_coefficients) + ] constants = [c._cpp_object for c in form.constants()] # NOTE Could remove this and let the user convert meshtags by @@ -183,17 +202,29 @@ def get_integration_domains(integral_type, subdomain): tdim = subdomain.topology.dim subdomain._cpp_object.topology.create_connectivity(tdim - 1, tdim) subdomain._cpp_object.topology.create_connectivity(tdim, tdim - 1) - domains = _cpp.fem.compute_integration_domains(integral_type, subdomain._cpp_object) + domains = _cpp.fem.compute_integration_domains( + integral_type, subdomain._cpp_object + ) return [(s[0], np.array(s[1])) for s in domains] except AttributeError: return [(s[0], np.array(s[1])) for s in subdomain] # Subdomain markers (possibly empty list for some integral types) - subdomains = {_ufl_to_dolfinx_domain[key]: get_integration_domains( - _ufl_to_dolfinx_domain[key], subdomain_data[0]) for (key, subdomain_data) in sd.get(domain).items()} - - f = ftype(module.ffi.cast("uintptr_t", module.ffi.addressof(ufcx_form)), V, coeffs, - constants, subdomains, mesh) + subdomains = { + _ufl_to_dolfinx_domain[key]: get_integration_domains( + _ufl_to_dolfinx_domain[key], subdomain_data[0] + ) + for (key, subdomain_data) in sd.get(domain).items() + } + + f = ftype( + module.ffi.cast("uintptr_t", module.ffi.addressof(ufcx_form)), + V, + coeffs, + constants, + subdomains, + mesh, + ) return Form(f, ufcx_form, code) def _create_form(form): @@ -208,9 +239,13 @@ def _create_form(form): return _create_form(form) -def extract_function_spaces(forms: typing.Union[typing.Iterable[Form], # type: ignore [return] - typing.Iterable[typing.Iterable[Form]]], - index: int = 0) -> typing.Iterable[typing.Union[None, function.FunctionSpace]]: +def extract_function_spaces( + forms: typing.Union[ + typing.Iterable[Form], # type: ignore [return] + typing.Iterable[typing.Iterable[Form]], + ], + index: int = 0, +) -> typing.Iterable[typing.Union[None, function.FunctionSpace]]: """Extract common function spaces from an array of forms. If `forms` is a list of linear form, this function returns of list of the corresponding test functions. If `forms` is a 2D array of bilinear @@ -228,7 +263,9 @@ def extract_function_spaces(forms: typing.Union[typing.Iterable[Form], # type: return [form.function_spaces[0] if form is not None else None for form in forms] # type: ignore[union-attr] elif _forms.ndim == 2: assert index == 0 or index == 1 - extract_spaces = np.vectorize(lambda form: form.function_spaces[index] if form is not None else None) + extract_spaces = np.vectorize( + lambda form: form.function_spaces[index] if form is not None else None + ) V = extract_spaces(_forms) def unique_spaces(V): diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index c233971e525..d7062925b4f 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -26,8 +26,12 @@ class Constant(ufl.Constant): - _cpp_object: typing.Union[_cpp.fem.Constant_complex64, _cpp.fem.Constant_complex128, - _cpp.fem.Constant_float32, _cpp.fem.Constant_float64] + _cpp_object: typing.Union[ + _cpp.fem.Constant_complex64, + _cpp.fem.Constant_complex128, + _cpp.fem.Constant_float32, + _cpp.fem.Constant_float64, + ] def __init__(self, domain, c: typing.Union[np.ndarray, typing.Sequence, float, complex]): """A constant with respect to a domain. @@ -67,25 +71,27 @@ def dtype(self) -> np.dtype: def __float__(self): if self.ufl_shape or self.ufl_free_indices: - raise TypeError( - "Cannot evaluate a nonscalar expression to a scalar value.") + raise TypeError("Cannot evaluate a nonscalar expression to a scalar value.") else: return float(self.value) def __complex__(self): if self.ufl_shape or self.ufl_free_indices: - raise TypeError( - "Cannot evaluate a nonscalar expression to a scalar value.") + raise TypeError("Cannot evaluate a nonscalar expression to a scalar value.") else: return complex(self.value) class Expression: - def __init__(self, e: ufl.core.expr.Expr, X: np.ndarray, - comm: typing.Optional[_MPI.Comm] = None, - form_compiler_options: typing.Optional[dict] = None, - jit_options: typing.Optional[dict] = None, - dtype: typing.Optional[npt.DTypeLike] = None): + def __init__( + self, + e: ufl.core.expr.Expr, + X: np.ndarray, + comm: typing.Optional[_MPI.Comm] = None, + form_compiler_options: typing.Optional[dict] = None, + jit_options: typing.Optional[dict] = None, + dtype: typing.Optional[npt.DTypeLike] = None, + ): """Create DOLFINx Expression. Represents a mathematical expression evaluated at a pre-defined @@ -123,7 +129,9 @@ def __init__(self, e: ufl.core.expr.Expr, X: np.ndarray, mesh = ufl.domain.extract_unique_domain(e).ufl_cargo() comm = mesh.comm except AttributeError: - print("Could not extract MPI communicator for Expression. Maybe you need to pass a communicator?") + print( + "Could not extract MPI communicator for Expression. Maybe you need to pass a communicator?" + ) raise # Attempt to deduce dtype @@ -137,16 +145,20 @@ def __init__(self, e: ufl.core.expr.Expr, X: np.ndarray, if form_compiler_options is None: form_compiler_options = dict() form_compiler_options["scalar_type"] = dtype - self._ufcx_expression, module, self._code = jit.ffcx_jit(comm, (e, _X), - form_compiler_options=form_compiler_options, - jit_options=jit_options) + self._ufcx_expression, module, self._code = jit.ffcx_jit( + comm, (e, _X), form_compiler_options=form_compiler_options, jit_options=jit_options + ) self._ufl_expression = e # Prepare coefficients data. For every coefficient in expression # take its C++ object. original_coefficients = ufl.algorithms.extract_coefficients(e) - coeffs = [original_coefficients[self._ufcx_expression.original_coefficient_positions[i]]._cpp_object - for i in range(self._ufcx_expression.num_coefficients)] + coeffs = [ + original_coefficients[ + self._ufcx_expression.original_coefficient_positions[i] + ]._cpp_object + for i in range(self._ufcx_expression.num_coefficients) + ] ufl_constants = ufl.algorithms.analysis.extract_constants(e) constants = [constant._cpp_object for constant in ufl_constants] arguments = ufl.algorithms.extract_arguments(e) @@ -170,10 +182,16 @@ def _create_expression(dtype): raise NotImplementedError(f"Type {dtype} not supported.") ffi = module.ffi - self._cpp_object = _create_expression(dtype)(ffi.cast("uintptr_t", ffi.addressof(self._ufcx_expression)), - coeffs, constants, self.argument_function_space) - - def eval(self, mesh: Mesh, cells: np.ndarray, values: typing.Optional[np.ndarray] = None) -> np.ndarray: + self._cpp_object = _create_expression(dtype)( + ffi.cast("uintptr_t", ffi.addressof(self._ufcx_expression)), + coeffs, + constants, + self.argument_function_space, + ) + + def eval( + self, mesh: Mesh, cells: np.ndarray, values: typing.Optional[np.ndarray] = None + ) -> np.ndarray: """Evaluate Expression in cells. Args: @@ -193,7 +211,10 @@ def eval(self, mesh: Mesh, cells: np.ndarray, values: typing.Optional[np.ndarray argument_space_dimension = 1 else: argument_space_dimension = self.argument_function_space.element.space_dimension - values_shape = (_cells.shape[0], self.X().shape[0] * self.value_size * argument_space_dimension) + values_shape = ( + _cells.shape[0], + self.X().shape[0] * self.value_size * argument_space_dimension, + ) # Allocate memory for result if u was not provided if values is None: @@ -246,11 +267,20 @@ class Function(ufl.Coefficient): (domain, element and dofmap) and a vector holding the degrees-of-freedom.""" - _cpp_object: typing.Union[_cpp.fem.Function_complex64, _cpp.fem.Function_complex128, - _cpp.fem.Function_float32, _cpp.fem.Function_float64] - - def __init__(self, V: FunctionSpace, x: typing.Optional[la.Vector] = None, - name: typing.Optional[str] = None, dtype: typing.Optional[npt.DTypeLike] = None): + _cpp_object: typing.Union[ + _cpp.fem.Function_complex64, + _cpp.fem.Function_complex128, + _cpp.fem.Function_float32, + _cpp.fem.Function_float64, + ] + + def __init__( + self, + V: FunctionSpace, + x: typing.Optional[la.Vector] = None, + name: typing.Optional[str] = None, + dtype: typing.Optional[npt.DTypeLike] = None, + ): """Initialize a finite element Function. Args: @@ -345,12 +375,15 @@ def eval(self, x: npt.ArrayLike, cells: npt.ArrayLike, u=None) -> np.ndarray: self._cpp_object.eval(_x, _cells, u) # type: ignore if num_points == 1: - u = np.reshape(u, (-1, )) + u = np.reshape(u, (-1,)) return u - def interpolate(self, u: typing.Union[typing.Callable, Expression, Function], - cells: typing.Optional[np.ndarray] = None, - nmm_interpolation_data=((), (), (), ())) -> None: + def interpolate( + self, + u: typing.Union[typing.Callable, Expression, Function], + cells: typing.Optional[np.ndarray] = None, + nmm_interpolation_data=((), (), (), ()), + ) -> None: """Interpolate an expression Args: @@ -358,6 +391,7 @@ def interpolate(self, u: typing.Union[typing.Callable, Expression, Function], cells: The cells to interpolate over. If `None` then all cells are interpolated over. """ + @singledispatch def _interpolate(u, cells: typing.Optional[np.ndarray] = None): """Interpolate a cpp.fem.Function""" @@ -397,7 +431,9 @@ def copy(self) -> Function: degree-of-freedom vector is copied. """ - return Function(self.function_space, la.Vector(type(self.x._cpp_object)(self.x._cpp_object))) + return Function( + self.function_space, la.Vector(type(self.x._cpp_object)(self.x._cpp_object)) + ) @property def x(self) -> la.Vector: @@ -418,6 +454,7 @@ def vector(self): """ if self._petsc_x is None: from dolfinx.la import create_petsc_vector_wrap + self._petsc_x = create_petsc_vector_wrap(self.x) return self._petsc_x @@ -475,8 +512,11 @@ def split(self) -> tuple[Function, ...]: def collapse(self) -> Function: u_collapsed = self._cpp_object.collapse() # type: ignore - V_collapsed = FunctionSpace(self.function_space._mesh, self.ufl_element(), # type: ignore - u_collapsed.function_space) + V_collapsed = FunctionSpace( + self.function_space._mesh, + self.ufl_element(), # type: ignore + u_collapsed.function_space, + ) return Function(V_collapsed, la.Vector(u_collapsed.x)) @@ -490,17 +530,19 @@ class ElementMetaData(typing.NamedTuple): :param symmetry: Symmetry option for blocked tensor elements. """ + family: str degree: int shape: typing.Optional[tuple[int, ...]] = None symmetry: typing.Optional[bool] = None -def functionspace(mesh: Mesh, - element: typing.Union[ufl.FiniteElementBase, ElementMetaData, - tuple[str, int, tuple, bool]], - form_compiler_options: typing.Optional[dict[str, typing.Any]] = None, - jit_options: typing.Optional[dict[str, typing.Any]] = None) -> FunctionSpace: +def functionspace( + mesh: Mesh, + element: typing.Union[ufl.FiniteElementBase, ElementMetaData, tuple[str, int, tuple, bool]], + form_compiler_options: typing.Optional[dict[str, typing.Any]] = None, + jit_options: typing.Optional[dict[str, typing.Any]] = None, +) -> FunctionSpace: """Create a finite element function space. Args: @@ -516,8 +558,14 @@ def functionspace(mesh: Mesh, # Create UFL element try: e = ElementMetaData(*element) - ufl_e = basix.ufl.element(e.family, mesh.basix_cell(), e.degree, shape=e.shape, - symmetry=e.symmetry, gdim=mesh.ufl_cell().geometric_dimension()) + ufl_e = basix.ufl.element( + e.family, + mesh.basix_cell(), + e.degree, + shape=e.shape, + symmetry=e.symmetry, + gdim=mesh.ufl_cell().geometric_dimension(), + ) except TypeError: ufl_e = element # type: ignore @@ -530,16 +578,21 @@ def functionspace(mesh: Mesh, if form_compiler_options is None: form_compiler_options = dict() form_compiler_options["scalar_type"] = dtype - (ufcx_element, ufcx_dofmap), module, code = jit.ffcx_jit(mesh.comm, ufl_e, - form_compiler_options=form_compiler_options, - jit_options=jit_options) + (ufcx_element, ufcx_dofmap), module, code = jit.ffcx_jit( + mesh.comm, ufl_e, form_compiler_options=form_compiler_options, jit_options=jit_options + ) ffi = module.ffi if dtype == np.float32: - cpp_element = _cpp.fem.FiniteElement_float32(ffi.cast("uintptr_t", ffi.addressof(ufcx_element))) + cpp_element = _cpp.fem.FiniteElement_float32( + ffi.cast("uintptr_t", ffi.addressof(ufcx_element)) + ) elif dtype == np.float64: - cpp_element = _cpp.fem.FiniteElement_float64(ffi.cast("uintptr_t", ffi.addressof(ufcx_element))) - cpp_dofmap = _cpp.fem.create_dofmap(mesh.comm, ffi.cast( - "uintptr_t", ffi.addressof(ufcx_dofmap)), mesh.topology, cpp_element) + cpp_element = _cpp.fem.FiniteElement_float64( + ffi.cast("uintptr_t", ffi.addressof(ufcx_element)) + ) + cpp_dofmap = _cpp.fem.create_dofmap( + mesh.comm, ffi.cast("uintptr_t", ffi.addressof(ufcx_dofmap)), mesh.topology, cpp_element + ) # Initialize the cpp.FunctionSpace try: @@ -552,11 +605,16 @@ def functionspace(mesh: Mesh, class FunctionSpace(ufl.FunctionSpace): """A space on which Functions (fields) can be defined.""" + _cpp_object: typing.Union[_cpp.fem.FunctionSpace_float32, _cpp.fem.FunctionSpace_float64] _mesh: Mesh - def __init__(self, mesh: Mesh, element: ufl.FiniteElementBase, - cppV: typing.Union[_cpp.fem.FunctionSpace_float32, _cpp.fem.FunctionSpace_float64]): + def __init__( + self, + mesh: Mesh, + element: ufl.FiniteElementBase, + cppV: typing.Union[_cpp.fem.FunctionSpace_float32, _cpp.fem.FunctionSpace_float64], + ): """Create a finite element function space. Note: @@ -595,10 +653,12 @@ def clone(self) -> FunctionSpace: """ try: Vcpp = _cpp.fem.FunctionSpace_float64( - self._cpp_object.mesh, self._cpp_object.element, self._cpp_object.dofmap) # type: ignore + self._cpp_object.mesh, self._cpp_object.element, self._cpp_object.dofmap + ) # type: ignore except TypeError: Vcpp = _cpp.fem.FunctionSpace_float32( - self._cpp_object.mesh, self._cpp_object.element, self._cpp_object.dofmap) # type: ignore + self._cpp_object.mesh, self._cpp_object.element, self._cpp_object.dofmap + ) # type: ignore return FunctionSpace(self._mesh, self.ufl_element(), Vcpp) @property @@ -655,7 +715,9 @@ def ufl_function_space(self) -> ufl.FunctionSpace: return self @property - def element(self) -> typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64]: + def element( + self, + ) -> typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64]: """Function space finite element.""" return self._cpp_object.element # type: ignore @@ -692,5 +754,5 @@ def tabulate_dof_coordinates(self) -> npt.NDArray[np.float64]: This method is only for elements with point evaluation degrees-of-freedom. - """ + """ return self._cpp_object.tabulate_dof_coordinates() # type: ignore diff --git a/python/dolfinx/fem/petsc.py b/python/dolfinx/fem/petsc.py index 078a8338fee..b35d3ec709b 100644 --- a/python/dolfinx/fem/petsc.py +++ b/python/dolfinx/fem/petsc.py @@ -35,12 +35,26 @@ from dolfinx.fem.function import Function as _Function from dolfinx.la import create_petsc_vector -__all__ = ["create_vector", "create_vector_block", "create_vector_nest", - "create_matrix", "create_matrix_block", "create_matrix_nest", - "assemble_vector", "assemble_vector_nest", "assemble_vector_block", - "assemble_matrix", "assemble_matrix_nest", "assemble_matrix_block", - "apply_lifting", "apply_lifting_nest", "set_bc", "set_bc_nest", - "LinearProblem", "NonlinearProblem"] +__all__ = [ + "create_vector", + "create_vector_block", + "create_vector_nest", + "create_matrix", + "create_matrix_block", + "create_matrix_nest", + "assemble_vector", + "assemble_vector_nest", + "assemble_vector_block", + "assemble_matrix", + "assemble_matrix_nest", + "assemble_matrix_block", + "apply_lifting", + "apply_lifting_nest", + "set_bc", + "set_bc_nest", + "LinearProblem", + "NonlinearProblem", +] def _extract_function_spaces(a: list[list[Form]]): @@ -54,7 +68,9 @@ def _extract_function_spaces(a: list[list[Form]]): # Extract (V0, V1) pair for each block in 'a' def fn(form): return form.function_spaces if form is not None else None + from functools import partial + Vblock: typing.Iterable = map(partial(map, fn), a) # Compute spaces for each row/column block @@ -100,8 +116,10 @@ def create_vector_block(L: list[Form]) -> PETSc.Vec: A PETSc vector with a layout that is compatible with ``L``. """ - maps = [(form.function_spaces[0].dofmap.index_map, - form.function_spaces[0].dofmap.index_map_bs) for form in L] + maps = [ + (form.function_spaces[0].dofmap.index_map, form.function_spaces[0].dofmap.index_map_bs) + for form in L + ] return _cpp.fem.petsc.create_vector_block(maps) @@ -116,13 +134,16 @@ def create_vector_nest(L: list[Form]) -> PETSc.Vec: compatible with ``L``. """ - maps = [(form.function_spaces[0].dofmap.index_map, - form.function_spaces[0].dofmap.index_map_bs) for form in L] + maps = [ + (form.function_spaces[0].dofmap.index_map, form.function_spaces[0].dofmap.index_map_bs) + for form in L + ] return _cpp.fem.petsc.create_vector_nest(maps) # -- Matrix instantiation ---------------------------------------------------- + def create_matrix(a: Form, mat_type=None) -> PETSc.Mat: """Create a PETSc matrix that is compatible with a bilinear form. @@ -171,6 +192,7 @@ def create_matrix_nest(a: list[list[Form]]) -> PETSc.Mat: # -- Vector assembly --------------------------------------------------------- + @functools.singledispatch def assemble_vector(L: typing.Any, constants=None, coeffs=None) -> PETSc.Vec: """Assemble linear form into a new PETSc vector. @@ -186,8 +208,9 @@ def assemble_vector(L: typing.Any, constants=None, coeffs=None) -> PETSc.Vec: An assembled vector. """ - b = create_petsc_vector(L.function_spaces[0].dofmap.index_map, - L.function_spaces[0].dofmap.index_map_bs) + b = create_petsc_vector( + L.function_spaces[0].dofmap.index_map, L.function_spaces[0].dofmap.index_map_bs + ) with b.localForm() as b_local: _assemble._assemble_vector_array(b_local.array_w, L, constants, coeffs) return b @@ -222,8 +245,10 @@ def assemble_vector_nest(L: typing.Any, constants=None, coeffs=None) -> PETSc.Ve accumulated on the owning processes. """ - maps = [(form.function_spaces[0].dofmap.index_map, - form.function_spaces[0].dofmap.index_map_bs) for form in L] + maps = [ + (form.function_spaces[0].dofmap.index_map, form.function_spaces[0].dofmap.index_map_bs) + for form in L + ] b = _cpp.fem.petsc.create_vector_nest(maps) for b_sub in b.getNestSubVecs(): with b_sub.localForm() as b_local: @@ -232,7 +257,9 @@ def assemble_vector_nest(L: typing.Any, constants=None, coeffs=None) -> PETSc.Ve @assemble_vector_nest.register -def _assemble_vector_nest_vec(b: PETSc.Vec, L: list[Form], constants=None, coeffs=None) -> PETSc.Vec: +def _assemble_vector_nest_vec( + b: PETSc.Vec, L: list[Form], constants=None, coeffs=None +) -> PETSc.Vec: """Assemble linear forms into a nested PETSc (``VecNest``) vector. The vector is not zeroed before assembly and it is not finalised, i.e. ghost values are not accumulated on the owning processes. @@ -248,42 +275,55 @@ def _assemble_vector_nest_vec(b: PETSc.Vec, L: list[Form], constants=None, coeff # FIXME: Revise this interface @functools.singledispatch -def assemble_vector_block(L: list[Form], - a: list[list[Form]], - bcs: list[DirichletBC] = [], - x0: typing.Optional[PETSc.Vec] = None, - scale: float = 1.0, - constants_L=None, coeffs_L=None, - constants_a=None, coeffs_a=None) -> PETSc.Vec: +def assemble_vector_block( + L: list[Form], + a: list[list[Form]], + bcs: list[DirichletBC] = [], + x0: typing.Optional[PETSc.Vec] = None, + scale: float = 1.0, + constants_L=None, + coeffs_L=None, + constants_a=None, + coeffs_a=None, +) -> PETSc.Vec: """Assemble linear forms into a monolithic vector. The vector is not finalised, i.e. ghost values are not accumulated. """ - maps = [(form.function_spaces[0].dofmap.index_map, - form.function_spaces[0].dofmap.index_map_bs) for form in L] + maps = [ + (form.function_spaces[0].dofmap.index_map, form.function_spaces[0].dofmap.index_map_bs) + for form in L + ] b = _cpp.fem.petsc.create_vector_block(maps) with b.localForm() as b_local: b_local.set(0.0) - return _assemble_vector_block_vec(b, L, a, bcs, x0, scale, constants_L, coeffs_L, - constants_a, coeffs_a) + return _assemble_vector_block_vec( + b, L, a, bcs, x0, scale, constants_L, coeffs_L, constants_a, coeffs_a + ) @assemble_vector_block.register -def _assemble_vector_block_vec(b: PETSc.Vec, - L: list[Form], - a: list[list[Form]], - bcs: list[DirichletBC] = [], - x0: typing.Optional[PETSc.Vec] = None, - scale: float = 1.0, - constants_L=None, coeffs_L=None, - constants_a=None, coeffs_a=None) -> PETSc.Vec: +def _assemble_vector_block_vec( + b: PETSc.Vec, + L: list[Form], + a: list[list[Form]], + bcs: list[DirichletBC] = [], + x0: typing.Optional[PETSc.Vec] = None, + scale: float = 1.0, + constants_L=None, + coeffs_L=None, + constants_a=None, + coeffs_a=None, +) -> PETSc.Vec: """Assemble linear forms into a monolithic vector. The vector is not zeroed and it is not finalised, i.e. ghost values are not accumulated. """ - maps = [(form.function_spaces[0].dofmap.index_map, - form.function_spaces[0].dofmap.index_map_bs) for form in L] + maps = [ + (form.function_spaces[0].dofmap.index_map, form.function_spaces[0].dofmap.index_map_bs) + for form in L + ] if x0 is not None: x0_local = _cpp.la.petsc.get_local_vectors(x0, maps) x0_sub = x0_local @@ -291,22 +331,46 @@ def _assemble_vector_block_vec(b: PETSc.Vec, x0_local = [] x0_sub = [None] * len(maps) - constants_L = [form and _pack_constants(form._cpp_object) for form in L] if constants_L is None else constants_L - coeffs_L = [{} if form is None else _pack_coefficients( - form._cpp_object) for form in L] if coeffs_L is None else coeffs_L - - constants_a = [[_pack_constants(form._cpp_object) if form is not None else np.array( - [], dtype=PETSc.ScalarType) for form in forms] for forms in a] if constants_a is None else constants_a - - coeffs_a = [[{} if form is None else _pack_coefficients( - form._cpp_object) for form in forms] for forms in a] if coeffs_a is None else coeffs_a + constants_L = ( + [form and _pack_constants(form._cpp_object) for form in L] + if constants_L is None + else constants_L + ) + coeffs_L = ( + [{} if form is None else _pack_coefficients(form._cpp_object) for form in L] + if coeffs_L is None + else coeffs_L + ) + + constants_a = ( + [ + [ + _pack_constants(form._cpp_object) + if form is not None + else np.array([], dtype=PETSc.ScalarType) + for form in forms + ] + for forms in a + ] + if constants_a is None + else constants_a + ) + + coeffs_a = ( + [ + [{} if form is None else _pack_coefficients(form._cpp_object) for form in forms] + for forms in a + ] + if coeffs_a is None + else coeffs_a + ) _bcs = [bc._cpp_object for bc in bcs] bcs1 = _bcs_by_block(_extract_spaces(a, 1), _bcs) b_local = _cpp.la.petsc.get_local_vectors(b, maps) - for b_sub, L_sub, a_sub, const_L, coeff_L, const_a, coeff_a in zip(b_local, L, a, - constants_L, coeffs_L, - constants_a, coeffs_a): + for b_sub, L_sub, a_sub, const_L, coeff_L, const_a, coeff_a in zip( + b_local, L, a, constants_L, coeffs_L, constants_a, coeffs_a + ): _cpp.fem.assemble_vector(b_sub, L_sub._cpp_object, const_L, coeff_L) _a_sub = [None if form is None else form._cpp_object for form in a_sub] _cpp.fem.apply_lifting(b_sub, _a_sub, const_a, coeff_a, bcs1, x0_local, scale) @@ -320,9 +384,9 @@ def _assemble_vector_block_vec(b: PETSc.Vec, for submap, bc, _x0 in zip(maps, bcs0, x0_sub): size = submap[0].size_local * submap[1] if _x0 is None: - _cpp.fem.set_bc(b_array[offset: offset + size], bc, scale) + _cpp.fem.set_bc(b_array[offset : offset + size], bc, scale) else: - _cpp.fem.set_bc(b_array[offset: offset + size], bc, _x0, scale) + _cpp.fem.set_bc(b_array[offset : offset + size], bc, _x0, scale) offset += size return b @@ -330,8 +394,9 @@ def _assemble_vector_block_vec(b: PETSc.Vec, # -- Matrix assembly --------------------------------------------------------- @functools.singledispatch -def assemble_matrix(a: typing.Any, bcs: list[DirichletBC] = [], - diagonal: float = 1.0, constants=None, coeffs=None): +def assemble_matrix( + a: typing.Any, bcs: list[DirichletBC] = [], diagonal: float = 1.0, constants=None, coeffs=None +): """Assemble bilinear form into a matrix. The returned matrix is not finalised, i.e. ghost values are not accumulated. @@ -357,8 +422,14 @@ def assemble_matrix(a: typing.Any, bcs: list[DirichletBC] = [], @assemble_matrix.register -def assemble_matrix_mat(A: PETSc.Mat, a: Form, bcs: list[DirichletBC] = [], - diagonal: float = 1.0, constants=None, coeffs=None) -> PETSc.Mat: +def assemble_matrix_mat( + A: PETSc.Mat, + a: Form, + bcs: list[DirichletBC] = [], + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Mat: """Assemble bilinear form into a matrix. The returned matrix is not finalised, i.e. ghost values are not accumulated. @@ -376,9 +447,14 @@ def assemble_matrix_mat(A: PETSc.Mat, a: Form, bcs: list[DirichletBC] = [], # FIXME: Revise this interface @functools.singledispatch -def assemble_matrix_nest(a: list[list[Form]], - bcs: list[DirichletBC] = [], mat_types=[], - diagonal: float = 1.0, constants=None, coeffs=None) -> PETSc.Mat: +def assemble_matrix_nest( + a: list[list[Form]], + bcs: list[DirichletBC] = [], + mat_types=[], + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Mat: """Create a nested matrix and assemble bilinear forms into the matrix. Args: @@ -402,9 +478,14 @@ def assemble_matrix_nest(a: list[list[Form]], @assemble_matrix_nest.register -def _assemble_matrix_nest_mat(A: PETSc.Mat, a: list[list[Form]], - bcs: list[DirichletBC] = [], diagonal: float = 1.0, - constants=None, coeffs=None) -> PETSc.Mat: +def _assemble_matrix_nest_mat( + A: PETSc.Mat, + a: list[list[Form]], + bcs: list[DirichletBC] = [], + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Mat: """Assemble bilinear forms into a nested matrix Args: @@ -423,10 +504,19 @@ def _assemble_matrix_nest_mat(A: PETSc.Mat, a: list[list[Form]], forms. """ - constants = [[form and _pack_constants(form._cpp_object) for form in forms] - for forms in a] if constants is None else constants - coeffs = [[{} if form is None else _pack_coefficients( - form._cpp_object) for form in forms] for forms in a] if coeffs is None else coeffs + constants = ( + [[form and _pack_constants(form._cpp_object) for form in forms] for forms in a] + if constants is None + else constants + ) + coeffs = ( + [ + [{} if form is None else _pack_coefficients(form._cpp_object) for form in forms] + for forms in a + ] + if coeffs is None + else coeffs + ) for i, (a_row, const_row, coeff_row) in enumerate(zip(a, constants, coeffs)): for j, (a_block, const, coeff) in enumerate(zip(a_row, const_row, coeff_row)): if a_block is not None: @@ -439,16 +529,20 @@ def _assemble_matrix_nest_mat(A: PETSc.Mat, a: list[list[Form]], if row_forms[0].function_spaces[0].contains(bc.function_space): raise RuntimeError( f"Diagonal sub-block ({i}, {j}) cannot be 'None' and have DirichletBC applied." - " Consider assembling a zero block.") + " Consider assembling a zero block." + ) return A # FIXME: Revise this interface @functools.singledispatch -def assemble_matrix_block(a: list[list[Form]], - bcs: list[DirichletBC] = [], - diagonal: float = 1.0, - constants=None, coeffs=None) -> PETSc.Mat: # type: ignore +def assemble_matrix_block( + a: list[list[Form]], + bcs: list[DirichletBC] = [], + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Mat: # type: ignore """Assemble bilinear forms into a blocked matrix.""" _a = [[None if form is None else form._cpp_object for form in arow] for arow in a] A = _cpp.fem.petsc.create_matrix_block(_a) @@ -456,18 +550,44 @@ def assemble_matrix_block(a: list[list[Form]], @assemble_matrix_block.register -def _assemble_matrix_block_mat(A: PETSc.Mat, a: list[list[Form]], - bcs: list[DirichletBC] = [], diagonal: float = 1.0, - constants=None, coeffs=None) -> PETSc.Mat: +def _assemble_matrix_block_mat( + A: PETSc.Mat, + a: list[list[Form]], + bcs: list[DirichletBC] = [], + diagonal: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Mat: """Assemble bilinear forms into a blocked matrix.""" - constants = [[_pack_constants(form._cpp_object) if form is not None else np.array( - [], dtype=PETSc.ScalarType) for form in forms] for forms in a] if constants is None else constants - coeffs = [[{} if form is None else _pack_coefficients( - form._cpp_object) for form in forms] for forms in a] if coeffs is None else coeffs + constants = ( + [ + [ + _pack_constants(form._cpp_object) + if form is not None + else np.array([], dtype=PETSc.ScalarType) + for form in forms + ] + for forms in a + ] + if constants is None + else constants + ) + coeffs = ( + [ + [{} if form is None else _pack_coefficients(form._cpp_object) for form in forms] + for forms in a + ] + if coeffs is None + else coeffs + ) V = _extract_function_spaces(a) - is_rows = _cpp.la.petsc.create_index_sets([(Vsub.dofmap.index_map, Vsub.dofmap.index_map_bs) for Vsub in V[0]]) - is_cols = _cpp.la.petsc.create_index_sets([(Vsub.dofmap.index_map, Vsub.dofmap.index_map_bs) for Vsub in V[1]]) + is_rows = _cpp.la.petsc.create_index_sets( + [(Vsub.dofmap.index_map, Vsub.dofmap.index_map_bs) for Vsub in V[0]] + ) + is_cols = _cpp.la.petsc.create_index_sets( + [(Vsub.dofmap.index_map, Vsub.dofmap.index_map_bs) for Vsub in V[1]] + ) # Assemble form _bcs = [bc._cpp_object for bc in bcs] @@ -475,7 +595,9 @@ def _assemble_matrix_block_mat(A: PETSc.Mat, a: list[list[Form]], for j, a_sub in enumerate(a_row): if a_sub is not None: Asub = A.getLocalSubMatrix(is_rows[i], is_cols[j]) - _cpp.fem.petsc.assemble_matrix(Asub, a_sub._cpp_object, constants[i][j], coeffs[i][j], _bcs, True) + _cpp.fem.petsc.assemble_matrix( + Asub, a_sub._cpp_object, constants[i][j], coeffs[i][j], _bcs, True + ) A.restoreLocalSubMatrix(is_rows[i], is_cols[j], Asub) elif i == j: for bc in bcs: @@ -484,7 +606,8 @@ def _assemble_matrix_block_mat(A: PETSc.Mat, a: list[list[Form]], if row_forms[0].function_spaces[0].contains(bc.function_space): raise RuntimeError( f"Diagonal sub-block ({i}, {j}) cannot be 'None' and have DirichletBC applied." - " Consider assembling a zero block.") + " Consider assembling a zero block." + ) # Flush to enable switch from add to set in the matrix A.assemble(PETSc.Mat.AssemblyType.FLUSH) @@ -503,10 +626,16 @@ def _assemble_matrix_block_mat(A: PETSc.Mat, a: list[list[Form]], # -- Modifiers for Dirichlet conditions --------------------------------------- -def apply_lifting(b: PETSc.Vec, a: list[Form], - bcs: list[list[DirichletBC]], - x0: list[PETSc.Vec] = [], - scale: float = 1.0, constants=None, coeffs=None) -> None: + +def apply_lifting( + b: PETSc.Vec, + a: list[Form], + bcs: list[list[DirichletBC]], + x0: list[PETSc.Vec] = [], + scale: float = 1.0, + constants=None, + coeffs=None, +) -> None: """Apply the function :func:`dolfinx.fem.apply_lifting` to a PETSc Vector.""" with contextlib.ExitStack() as stack: x0 = [stack.enter_context(x.localForm()) for x in x0] @@ -515,33 +644,60 @@ def apply_lifting(b: PETSc.Vec, a: list[Form], _assemble.apply_lifting(b_local.array_w, a, bcs, x0_r, scale, constants, coeffs) -def apply_lifting_nest(b: PETSc.Vec, a: list[list[Form]], - bcs: list[DirichletBC], - x0: typing.Optional[PETSc.Vec] = None, - scale: float = 1.0, constants=None, coeffs=None) -> PETSc.Vec: +def apply_lifting_nest( + b: PETSc.Vec, + a: list[list[Form]], + bcs: list[DirichletBC], + x0: typing.Optional[PETSc.Vec] = None, + scale: float = 1.0, + constants=None, + coeffs=None, +) -> PETSc.Vec: """Apply the function :func:`dolfinx.fem.apply_lifting` to each sub-vector in a nested PETSc Vector.""" x0 = [] if x0 is None else x0.getNestSubVecs() bcs1 = _bcs_by_block(_extract_spaces(a, 1), bcs) - constants = [[_pack_constants(form._cpp_object) if form is not None else np.array( - [], dtype=PETSc.ScalarType) for form in forms] for forms in a] if constants is None else constants - coeffs = [[{} if form is None else _pack_coefficients( - form._cpp_object) for form in forms] for forms in a] if coeffs is None else coeffs + constants = ( + [ + [ + _pack_constants(form._cpp_object) + if form is not None + else np.array([], dtype=PETSc.ScalarType) + for form in forms + ] + for forms in a + ] + if constants is None + else constants + ) + coeffs = ( + [ + [{} if form is None else _pack_coefficients(form._cpp_object) for form in forms] + for forms in a + ] + if coeffs is None + else coeffs + ) for b_sub, a_sub, const, coeff in zip(b.getNestSubVecs(), a, constants, coeffs): apply_lifting(b_sub, a_sub, bcs1, x0, scale, const, coeff) return b -def set_bc(b: PETSc.Vec, bcs: list[DirichletBC], - x0: typing.Optional[PETSc.Vec] = None, scale: float = 1.0) -> None: +def set_bc( + b: PETSc.Vec, bcs: list[DirichletBC], x0: typing.Optional[PETSc.Vec] = None, scale: float = 1.0 +) -> None: """Apply the function :func:`dolfinx.fem.set_bc` to a PETSc Vector.""" if x0 is not None: x0 = x0.array_r _assemble.set_bc(b.array_w, bcs, x0, scale) -def set_bc_nest(b: PETSc.Vec, bcs: list[list[DirichletBC]], - x0: typing.Optional[PETSc.Vec] = None, scale: float = 1.0) -> None: +def set_bc_nest( + b: PETSc.Vec, + bcs: list[list[DirichletBC]], + x0: typing.Optional[PETSc.Vec] = None, + scale: float = 1.0, +) -> None: """Apply the function :func:`dolfinx.fem.set_bc` to each sub-vector of a nested PETSc Vector.""" _b = b.getNestSubVecs() x0 = len(_b) * [None] if x0 is None else x0.getNestSubVecs() @@ -557,11 +713,16 @@ class LinearProblem: """ - def __init__(self, a: ufl.Form, L: ufl.Form, bcs: list[DirichletBC] = [], - u: typing.Optional[_Function] = None, - petsc_options: typing.Optional[dict] = None, - form_compiler_options: typing.Optional[dict] = None, - jit_options: typing.Optional[dict] = None): + def __init__( + self, + a: ufl.Form, + L: ufl.Form, + bcs: list[DirichletBC] = [], + u: typing.Optional[_Function] = None, + petsc_options: typing.Optional[dict] = None, + form_compiler_options: typing.Optional[dict] = None, + jit_options: typing.Optional[dict] = None, + ): """Initialize solver for a linear variational problem. Args: @@ -589,9 +750,13 @@ def __init__(self, a: ufl.Form, L: ufl.Form, bcs: list[DirichletBC] = [], "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}) """ - self._a = _create_form(a, form_compiler_options=form_compiler_options, jit_options=jit_options) + self._a = _create_form( + a, form_compiler_options=form_compiler_options, jit_options=jit_options + ) self._A = create_matrix(self._a) - self._L = _create_form(L, form_compiler_options=form_compiler_options, jit_options=jit_options) + self._L = _create_form( + L, form_compiler_options=form_compiler_options, jit_options=jit_options + ) self._b = create_vector(self._L) if u is None: @@ -691,9 +856,15 @@ class NonlinearProblem: """ - def __init__(self, F: ufl.form.Form, u: _Function, bcs: list[DirichletBC] = [], - J: ufl.form.Form = None, form_compiler_options: typing.Optional[dict] = None, - jit_options: typing.Optional[dict] = None): + def __init__( + self, + F: ufl.form.Form, + u: _Function, + bcs: list[DirichletBC] = [], + J: ufl.form.Form = None, + form_compiler_options: typing.Optional[dict] = None, + jit_options: typing.Optional[dict] = None, + ): """Initialize solver for solving a non-linear problem using Newton's method, :math:`(dF/du)(u) du = -F(u)`. Args: @@ -714,7 +885,9 @@ def __init__(self, F: ufl.form.Form, u: _Function, bcs: list[DirichletBC] = [], problem = LinearProblem(F, u, [bc0, bc1]) """ - self._L = _create_form(F, form_compiler_options=form_compiler_options, jit_options=jit_options) + self._L = _create_form( + F, form_compiler_options=form_compiler_options, jit_options=jit_options + ) # Create the Jacobian matrix, dF/du if J is None: @@ -722,7 +895,9 @@ def __init__(self, F: ufl.form.Form, u: _Function, bcs: list[DirichletBC] = [], du = ufl.TrialFunction(V) J = ufl.derivative(F, u, du) - self._a = _create_form(J, form_compiler_options=form_compiler_options, jit_options=jit_options) + self._a = _create_form( + J, form_compiler_options=form_compiler_options, jit_options=jit_options + ) self.bcs = bcs @property diff --git a/python/dolfinx/geometry.py b/python/dolfinx/geometry.py index 4576e1b0bdd..f2743b0a9ec 100644 --- a/python/dolfinx/geometry.py +++ b/python/dolfinx/geometry.py @@ -13,22 +13,31 @@ import numpy.typing as npt if typing.TYPE_CHECKING: - from dolfinx.mesh import Mesh from dolfinx.cpp.graph import AdjacencyList_int32 + from dolfinx.mesh import Mesh from dolfinx import cpp as _cpp -__all__ = ["BoundingBoxTree", "bb_tree", "compute_colliding_cells", "squared_distance", - "compute_closest_entity", "compute_collisions_trees", "compute_collisions_points", - "compute_distance_gjk", "create_midpoint_tree"] +__all__ = [ + "BoundingBoxTree", + "bb_tree", + "compute_colliding_cells", + "squared_distance", + "compute_closest_entity", + "compute_collisions_trees", + "compute_collisions_points", + "compute_distance_gjk", + "create_midpoint_tree", +] class BoundingBoxTree: """Bounding box trees used in collision detection.""" - _cpp_object: typing.Union[_cpp.geometry.BoundingBoxTree_float32, - _cpp.geometry.BoundingBoxTree_float64] + _cpp_object: typing.Union[ + _cpp.geometry.BoundingBoxTree_float32, _cpp.geometry.BoundingBoxTree_float64 + ] def __init__(self, tree): """Wrap a C++ BoundingBoxTree. @@ -62,8 +71,12 @@ def create_global_tree(self, comm) -> BoundingBoxTree: return BoundingBoxTree(self._cpp_object.create_global_tree(comm)) -def bb_tree(mesh: Mesh, dim: int, entities: typing.Optional[npt.NDArray[np.int32]] = None, - padding: float = 0.0) -> BoundingBoxTree: +def bb_tree( + mesh: Mesh, + dim: int, + entities: typing.Optional[npt.NDArray[np.int32]] = None, + padding: float = 0.0, +) -> BoundingBoxTree: """Create a bounding box tree for use in collision detection. Args: @@ -85,14 +98,20 @@ def bb_tree(mesh: Mesh, dim: int, entities: typing.Optional[npt.NDArray[np.int32 dtype = mesh.geometry.x.dtype if dtype == np.float32: - return BoundingBoxTree(_cpp.geometry.BoundingBoxTree_float32(mesh._cpp_object, dim, entities, padding)) + return BoundingBoxTree( + _cpp.geometry.BoundingBoxTree_float32(mesh._cpp_object, dim, entities, padding) + ) elif dtype == np.float64: - return BoundingBoxTree(_cpp.geometry.BoundingBoxTree_float64(mesh._cpp_object, dim, entities, padding)) + return BoundingBoxTree( + _cpp.geometry.BoundingBoxTree_float64(mesh._cpp_object, dim, entities, padding) + ) else: raise NotImplementedError(f"Type {dtype} not supported.") -def compute_collisions_trees(tree0: BoundingBoxTree, tree1: BoundingBoxTree) -> npt.NDArray[np.int32]: +def compute_collisions_trees( + tree0: BoundingBoxTree, tree1: BoundingBoxTree +) -> npt.NDArray[np.int32]: """Compute all collisions between two bounding box trees. Args: @@ -107,7 +126,9 @@ def compute_collisions_trees(tree0: BoundingBoxTree, tree1: BoundingBoxTree) -> return _cpp.geometry.compute_collisions_trees(tree0._cpp_object, tree1._cpp_object) -def compute_collisions_points(tree: BoundingBoxTree, x: npt.NDArray[np.floating]) -> _cpp.graph.AdjacencyList_int32: +def compute_collisions_points( + tree: BoundingBoxTree, x: npt.NDArray[np.floating] +) -> _cpp.graph.AdjacencyList_int32: """Compute collisions between points and leaf bounding boxes. Bounding boxes can overlap, therefore points can collide with more @@ -125,8 +146,12 @@ def compute_collisions_points(tree: BoundingBoxTree, x: npt.NDArray[np.floating] return _cpp.geometry.compute_collisions_points(tree._cpp_object, x) -def compute_closest_entity(tree: BoundingBoxTree, midpoint_tree: BoundingBoxTree, mesh: Mesh, - points: npt.NDArray[np.floating]) -> npt.NDArray[np.int32]: +def compute_closest_entity( + tree: BoundingBoxTree, + midpoint_tree: BoundingBoxTree, + mesh: Mesh, + points: npt.NDArray[np.floating], +) -> npt.NDArray[np.int32]: """Compute closest mesh entity to a point. Args: @@ -141,7 +166,9 @@ def compute_closest_entity(tree: BoundingBoxTree, midpoint_tree: BoundingBoxTree point if the bounding box tree is empty. """ - return _cpp.geometry.compute_closest_entity(tree._cpp_object, midpoint_tree._cpp_object, mesh._cpp_object, points) + return _cpp.geometry.compute_closest_entity( + tree._cpp_object, midpoint_tree._cpp_object, mesh._cpp_object, points + ) def create_midpoint_tree(mesh: Mesh, dim: int, entities: npt.NDArray[np.int32]) -> BoundingBoxTree: @@ -159,7 +186,9 @@ def create_midpoint_tree(mesh: Mesh, dim: int, entities: npt.NDArray[np.int32]) return BoundingBoxTree(_cpp.geometry.create_midpoint_tree(mesh._cpp_object, dim, entities)) -def compute_colliding_cells(mesh: Mesh, candidates: AdjacencyList_int32, x: npt.NDArray[np.floating]): +def compute_colliding_cells( + mesh: Mesh, candidates: AdjacencyList_int32, x: npt.NDArray[np.floating] +): """From a mesh, find which cells collide with a set of points. Args: @@ -196,7 +225,9 @@ def squared_distance(mesh: Mesh, dim: int, entities: list[int], points: npt.NDAr return _cpp.geometry.squared_distance(mesh._cpp_object, dim, entities, points) -def compute_distance_gjk(p: npt.NDArray[np.floating], q: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]: +def compute_distance_gjk( + p: npt.NDArray[np.floating], q: npt.NDArray[np.floating] +) -> npt.NDArray[np.floating]: """Compute the distance between two convex bodies p and q, each defined by a set of points. Uses the Gilbert-Johnson-Keerthi (GJK) distance algorithm. diff --git a/python/dolfinx/io/__init__.py b/python/dolfinx/io/__init__.py index 5804ddd3978..3a53be8d7eb 100644 --- a/python/dolfinx/io/__init__.py +++ b/python/dolfinx/io/__init__.py @@ -14,5 +14,5 @@ if _cpp.common.has_adios2: # FidesWriter and VTXWriter require ADIOS2 from dolfinx.io.utils import FidesMeshPolicy, FidesWriter, VTXMeshPolicy, VTXWriter - __all__ = [*__all__, "FidesWriter", "VTXWriter", "FidesMeshPolicy", - "VTXMeshPolicy"] + + __all__ = [*__all__, "FidesWriter", "VTXWriter", "FidesMeshPolicy", "VTXMeshPolicy"] diff --git a/python/dolfinx/io/gmshio.py b/python/dolfinx/io/gmshio.py index 19d4cf1e40c..e30773fc686 100644 --- a/python/dolfinx/io/gmshio.py +++ b/python/dolfinx/io/gmshio.py @@ -20,21 +20,36 @@ from dolfinx.cpp.graph import AdjacencyList_int32 from dolfinx.mesh import CellType, Mesh, create_mesh, meshtags, meshtags_from_entities -__all__ = ["cell_perm_array", "ufl_mesh", "extract_topology_and_markers", - "extract_geometry", "model_to_mesh", "read_from_msh"] +__all__ = [ + "cell_perm_array", + "ufl_mesh", + "extract_topology_and_markers", + "extract_geometry", + "model_to_mesh", + "read_from_msh", +] # Map from Gmsh cell type identifier (integer) to DOLFINx cell type and # degree https://gmsh.info//doc/texinfo/gmsh.html#MSH-file-format -_gmsh_to_cells = {1: ("interval", 1), 2: ("triangle", 1), - 3: ("quadrilateral", 1), 4: ("tetrahedron", 1), - 5: ("hexahedron", 1), 8: ("interval", 2), - 9: ("triangle", 2), 10: ("quadrilateral", 2), - 11: ("tetrahedron", 2), 12: ("hexahedron", 2), - 15: ("point", 0), 21: ("triangle", 3), - 26: ("interval", 3), 29: ("tetrahedron", 3), - 36: ("quadrilateral", 3), - 92: ("hexahedron", 3)} +_gmsh_to_cells = { + 1: ("interval", 1), + 2: ("triangle", 1), + 3: ("quadrilateral", 1), + 4: ("tetrahedron", 1), + 5: ("hexahedron", 1), + 8: ("interval", 2), + 9: ("triangle", 2), + 10: ("quadrilateral", 2), + 11: ("tetrahedron", 2), + 12: ("hexahedron", 2), + 15: ("point", 0), + 21: ("triangle", 3), + 26: ("interval", 3), + 29: ("tetrahedron", 3), + 36: ("quadrilateral", 3), + 92: ("hexahedron", 3), +} def ufl_mesh(gmsh_cell: int, gdim: int, dtype: npt.DTypeLike) -> ufl.Mesh: @@ -57,9 +72,15 @@ def ufl_mesh(gmsh_cell: int, gdim: int, dtype: npt.DTypeLike) -> ufl.Mesh: print(f"Unknown cell type {gmsh_cell}.") raise e cell = ufl.Cell(shape, geometric_dimension=gdim) - element = basix.ufl.element(basix.ElementFamily.P, cell.cellname(), degree, - basix.LagrangeVariant.equispaced, shape=(gdim,), - gdim=gdim, dtype=dtype) # type: ignore[arg-type] + element = basix.ufl.element( + basix.ElementFamily.P, + cell.cellname(), + degree, + basix.LagrangeVariant.equispaced, + shape=(gdim,), + gdim=gdim, + dtype=dtype, + ) # type: ignore[arg-type] return ufl.Mesh(element) @@ -134,8 +155,11 @@ def extract_topology_and_markers(model, name: typing.Optional[str] = None): entity_type = entity_types[0] if entity_type in topologies.keys(): topologies[entity_type]["topology"] = np.concatenate( - (topologies[entity_type]["topology"], topology), axis=0) - topologies[entity_type]["cell_data"] = np.hstack([topologies[entity_type]["cell_data"], marker]) + (topologies[entity_type]["topology"], topology), axis=0 + ) + topologies[entity_type]["cell_data"] = np.hstack( + [topologies[entity_type]["cell_data"], marker] + ) else: topologies[entity_type] = {"topology": topology, "cell_data": marker} @@ -176,11 +200,16 @@ def extract_geometry(model, name: typing.Optional[str] = None) -> npt.NDArray[np return points[perm_sort] -def model_to_mesh(model, comm: _MPI.Comm, rank: int, gdim: int = 3, - partitioner: typing.Optional[typing.Callable[ - [_MPI.Comm, int, int, AdjacencyList_int32], AdjacencyList_int32]] = None, - dtype=default_real_type) -> tuple[ - Mesh, _cpp.mesh.MeshTags_int32, _cpp.mesh.MeshTags_int32]: +def model_to_mesh( + model, + comm: _MPI.Comm, + rank: int, + gdim: int = 3, + partitioner: typing.Optional[ + typing.Callable[[_MPI.Comm, int, int, AdjacencyList_int32], AdjacencyList_int32] + ] = None, + dtype=default_real_type, +) -> tuple[Mesh, _cpp.mesh.MeshTags_int32, _cpp.mesh.MeshTags_int32]: """Create a Mesh from a Gmsh model. Creates a :class:`dolfinx.mesh.Mesh` from the physical entities of @@ -265,10 +294,13 @@ def model_to_mesh(model, comm: _MPI.Comm, rank: int, gdim: int = 3, # Create MeshTags for cells local_entities, local_values = _cpp.io.distribute_entity_data( - mesh._cpp_object, mesh.topology.dim, cells, cell_values) + mesh._cpp_object, mesh.topology.dim, cells, cell_values + ) mesh.topology.create_connectivity(mesh.topology.dim, 0) adj = _cpp.graph.AdjacencyList_int32(local_entities) - ct = meshtags_from_entities(mesh, mesh.topology.dim, adj, local_values.astype(np.int32, copy=False)) + ct = meshtags_from_entities( + mesh, mesh.topology.dim, adj, local_values.astype(np.int32, copy=False) + ) ct.name = "Cell tags" # Create MeshTags for facets @@ -280,12 +312,15 @@ def model_to_mesh(model, comm: _MPI.Comm, rank: int, gdim: int = 3, if topology.cell_type == CellType.prism or topology.cell_type == CellType.pyramid: raise RuntimeError(f"Unsupported cell type {topology.cell_type}") - facet_type = _cpp.mesh.cell_entity_type(_cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 1, 0) + facet_type = _cpp.mesh.cell_entity_type( + _cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 1, 0 + ) gmsh_facet_perm = cell_perm_array(facet_type, num_facet_nodes) marked_facets = marked_facets[:, gmsh_facet_perm] local_entities, local_values = _cpp.io.distribute_entity_data( - mesh._cpp_object, tdim - 1, marked_facets, facet_values) + mesh._cpp_object, tdim - 1, marked_facets, facet_values + ) mesh.topology.create_connectivity(topology.dim - 1, tdim) adj = _cpp.graph.AdjacencyList_int32(local_entities) ft = meshtags_from_entities(mesh, tdim - 1, adj, local_values.astype(np.int32, copy=False)) @@ -296,10 +331,15 @@ def model_to_mesh(model, comm: _MPI.Comm, rank: int, gdim: int = 3, return (mesh, ct, ft) -def read_from_msh(filename: str, comm: _MPI.Comm, rank: int = 0, gdim: int = 3, - partitioner: typing.Optional[typing.Callable[ - [_MPI.Comm, int, int, AdjacencyList_int32], AdjacencyList_int32]] = None) -> tuple[ - Mesh, _cpp.mesh.MeshTags_int32, _cpp.mesh.MeshTags_int32]: +def read_from_msh( + filename: str, + comm: _MPI.Comm, + rank: int = 0, + gdim: int = 3, + partitioner: typing.Optional[ + typing.Callable[[_MPI.Comm, int, int, AdjacencyList_int32], AdjacencyList_int32] + ] = None, +) -> tuple[Mesh, _cpp.mesh.MeshTags_int32, _cpp.mesh.MeshTags_int32]: """Read a Gmsh .msh file and return a distributed :class:`dolfinx.mesh.Mesh` and and cell facet markers. Note: @@ -322,7 +362,9 @@ def read_from_msh(filename: str, comm: _MPI.Comm, rank: int = 0, gdim: int = 3, except ModuleNotFoundError: # Python 3.11+ adds the add_note method to exceptions # e.add_note("Gmsh must be installed to import dolfinx.io.gmshio") - raise ModuleNotFoundError("No module named 'gmsh': dolfinx.io.gmshio.read_from_msh requires Gmsh.", name="gmsh") + raise ModuleNotFoundError( + "No module named 'gmsh': dolfinx.io.gmshio.read_from_msh requires Gmsh.", name="gmsh" + ) if comm.rank == rank: gmsh.initialize() diff --git a/python/dolfinx/io/utils.py b/python/dolfinx/io/utils.py index 0fca232ec79..3a53cd33c0a 100644 --- a/python/dolfinx/io/utils.py +++ b/python/dolfinx/io/utils.py @@ -23,8 +23,7 @@ from dolfinx.fem import Function from dolfinx.mesh import GhostMode, Mesh, MeshTags -__all__ = ["VTKFile", "XDMFFile", "cell_perm_gmsh", "cell_perm_vtk", - "distribute_entity_data"] +__all__ = ["VTKFile", "XDMFFile", "cell_perm_gmsh", "cell_perm_vtk", "distribute_entity_data"] def _extract_cpp_functions(functions: typing.Union[list[Function], Function]): @@ -38,8 +37,8 @@ def _extract_cpp_functions(functions: typing.Union[list[Function], Function]): # FidesWriter and VTXWriter require ADIOS2 if _cpp.common.has_adios2: from dolfinx.cpp.io import FidesMeshPolicy, VTXMeshPolicy # F401 - __all__ = [*__all__, "FidesWriter", "VTXWriter", "FidesMeshPolicy", - "VTXMeshPolicy"] + + __all__ = [*__all__, "FidesWriter", "VTXWriter", "FidesMeshPolicy", "VTXMeshPolicy"] class VTXWriter: """Writer for VTX files, using ADIOS2 to create the files. @@ -53,10 +52,14 @@ class VTXWriter: _cpp_object: typing.Union[_cpp.io.VTXWriter_float32, _cpp.io.VTXWriter_float64] - def __init__(self, comm: _MPI.Comm, filename: typing.Union[str, Path], - output: typing.Union[Mesh, Function, list[Function]], - engine: str = "BPFile", - mesh_policy: VTXMeshPolicy = VTXMeshPolicy.update): + def __init__( + self, + comm: _MPI.Comm, + filename: typing.Union[str, Path], + output: typing.Union[Mesh, Function, list[Function]], + engine: str = "BPFile", + mesh_policy: VTXMeshPolicy = VTXMeshPolicy.update, + ): """Initialize a writer for outputting data in the VTX format. Args: @@ -96,8 +99,9 @@ def __init__(self, comm: _MPI.Comm, filename: typing.Union[str, Path], self._cpp_object = _vtxwriter(comm, filename, output._cpp_object, engine) # type: ignore[union-attr] except (NotImplementedError, TypeError, AttributeError): # Input is a single function or a list of functions - self._cpp_object = _vtxwriter(comm, filename, _extract_cpp_functions( - output), engine, mesh_policy) # type: ignore[arg-type] + self._cpp_object = _vtxwriter( + comm, filename, _extract_cpp_functions(output), engine, mesh_policy + ) # type: ignore[arg-type] def __enter__(self): return self @@ -124,9 +128,14 @@ class FidesWriter: _cpp_object: typing.Union[_cpp.io.FidesWriter_float32, _cpp.io.FidesWriter_float64] - def __init__(self, comm: _MPI.Comm, filename: typing.Union[str, Path], - output: typing.Union[Mesh, list[Function], Function], - engine: str = "BPFile", mesh_policy: FidesMeshPolicy = FidesMeshPolicy.update): + def __init__( + self, + comm: _MPI.Comm, + filename: typing.Union[str, Path], + output: typing.Union[Mesh, list[Function], Function], + engine: str = "BPFile", + mesh_policy: FidesMeshPolicy = FidesMeshPolicy.update, + ): """Initialize a writer for outputting a mesh, a single Lagrange function or list of Lagrange functions sharing the same element family and degree @@ -151,7 +160,7 @@ def __init__(self, comm: _MPI.Comm, filename: typing.Union[str, Path], try: dtype = output.function_space.mesh.geometry.x.dtype # type: ignore except AttributeError: - dtype = output[0].function_space.mesh.geometry.x.dtype # type: ignore + dtype = output[0].function_space.mesh.geometry.x.dtype # type: ignore if dtype == np.float32: _fides_writer = _cpp.io.FidesWriter_float32 @@ -161,8 +170,9 @@ def __init__(self, comm: _MPI.Comm, filename: typing.Union[str, Path], try: self._cpp_object = _fides_writer(comm, filename, output._cpp_object, engine) # type: ignore except (NotImplementedError, TypeError, AttributeError): - self._cpp_object = _fides_writer(comm, filename, _extract_cpp_functions( - output), engine, mesh_policy) # type: ignore[arg-type] + self._cpp_object = _fides_writer( + comm, filename, _extract_cpp_functions(output), engine, mesh_policy + ) # type: ignore[arg-type] def __enter__(self): return self @@ -212,13 +222,19 @@ def write_mesh(self, mesh: Mesh, xpath: str = "/Xdmf/Domain") -> None: """Write mesh to file""" super().write_mesh(mesh._cpp_object, xpath) - def write_meshtags(self, tags: MeshTags, x: typing.Union[_cpp.mesh.Geometry_float32, _cpp.mesh.Geometry_float64], - geometry_xpath: str = "/Xdmf/Domain/Grid/Geometry", - xpath: str = "/Xdmf/Domain") -> None: + def write_meshtags( + self, + tags: MeshTags, + x: typing.Union[_cpp.mesh.Geometry_float32, _cpp.mesh.Geometry_float64], + geometry_xpath: str = "/Xdmf/Domain/Grid/Geometry", + xpath: str = "/Xdmf/Domain", + ) -> None: """Write mesh tags to file""" super().write_meshtags(tags._cpp_object, x, geometry_xpath, xpath) - def write_function(self, u: Function, t: float = 0.0, mesh_xpath="/Xdmf/Domain/Grid[@GridType='Uniform'][1]"): + def write_function( + self, u: Function, t: float = 0.0, mesh_xpath="/Xdmf/Domain/Grid[@GridType='Uniform'][1]" + ): """Write function to file for a given time. Note: @@ -235,7 +251,9 @@ def write_function(self, u: Function, t: float = 0.0, mesh_xpath="/Xdmf/Domain/G """ super().write_function(getattr(u, "_cpp_object", u), t, mesh_xpath) - def read_mesh(self, ghost_mode=GhostMode.shared_facet, name="mesh", xpath="/Xdmf/Domain") -> Mesh: + def read_mesh( + self, ghost_mode=GhostMode.shared_facet, name="mesh", xpath="/Xdmf/Domain" + ) -> Mesh: """Read mesh data from file.""" cell_shape, cell_degree = super().read_cell_type(name, xpath) cells = super().read_topology_data(name, xpath) @@ -243,11 +261,20 @@ def read_mesh(self, ghost_mode=GhostMode.shared_facet, name="mesh", xpath="/Xdmf # Build the mesh cmap = _cpp.fem.CoordinateElement_float64(cell_shape, cell_degree) - msh = _cpp.mesh.create_mesh(self.comm, cells, - cmap, x, _cpp.mesh.create_cell_partitioner(ghost_mode)) + msh = _cpp.mesh.create_mesh( + self.comm, cells, cmap, x, _cpp.mesh.create_cell_partitioner(ghost_mode) + ) msh.name = name - domain = ufl.Mesh(basix.ufl.element("Lagrange", cell_shape.name, cell_degree, - basix.LagrangeVariant.equispaced, shape=(x.shape[1], ), gdim=x.shape[1])) + domain = ufl.Mesh( + basix.ufl.element( + "Lagrange", + cell_shape.name, + cell_degree, + basix.LagrangeVariant.equispaced, + shape=(x.shape[1],), + gdim=x.shape[1], + ) + ) return Mesh(msh, domain) def read_meshtags(self, mesh, name, xpath="/Xdmf/Domain"): @@ -255,7 +282,7 @@ def read_meshtags(self, mesh, name, xpath="/Xdmf/Domain"): return MeshTags(mt) -def distribute_entity_data(mesh: Mesh, entity_dim: int, entities: npt.NDArray[np.int64], - values: npt.NDArray[np.int32]) -> tuple[npt.NDArray[np.int64], - npt.NDArray[np.int32]]: +def distribute_entity_data( + mesh: Mesh, entity_dim: int, entities: npt.NDArray[np.int64], values: npt.NDArray[np.int32] +) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int32]]: return _cpp.io.distribute_entity_data(mesh._cpp_object, entity_dim, entities, values) diff --git a/python/dolfinx/jit.py b/python/dolfinx/jit.py index f36b73f2ef9..0e031b96189 100644 --- a/python/dolfinx/jit.py +++ b/python/dolfinx/jit.py @@ -20,20 +20,16 @@ __all__ = ["ffcx_jit", "get_options", "mpi_jit_decorator"] DOLFINX_DEFAULT_JIT_OPTIONS = { - "cache_dir": - (os.getenv("XDG_CACHE_HOME", default=Path.home().joinpath(".cache")) / Path("fenics"), - "Path for storing DOLFINx JIT cache. " - "Default prefix ~/.cache/ can be changed using XDG_CACHE_HOME environment variable."), - "cffi_debug": - (False, "CFFI debug mode"), - "cffi_extra_compile_args": - (["-O2", "-g0"], "Extra C compiler arguments to pass to CFFI"), - "cffi_verbose": - (False, "CFFI verbose mode"), - "cffi_libraries": - (None, "Extra libraries to link"), - "timeout": - (10, "Timeout for JIT compilation") + "cache_dir": ( + os.getenv("XDG_CACHE_HOME", default=Path.home().joinpath(".cache")) / Path("fenics"), + "Path for storing DOLFINx JIT cache. " + "Default prefix ~/.cache/ can be changed using XDG_CACHE_HOME environment variable.", + ), + "cffi_debug": (False, "CFFI debug mode"), + "cffi_extra_compile_args": (["-O2", "-g0"], "Extra C compiler arguments to pass to CFFI"), + "cffi_verbose": (False, "CFFI verbose mode"), + "cffi_libraries": (None, "Extra libraries to link"), + "timeout": (10, "Timeout for JIT compilation"), } @@ -50,7 +46,6 @@ def mpi_jit_decorator(local_jit, *args, **kwargs): @functools.wraps(local_jit) def mpi_jit(comm, *args, **kwargs): - # Just call JIT compiler when running in serial if comm.size == 1: return local_jit(*args, **kwargs) @@ -99,8 +94,9 @@ def mpi_jit(comm, *args, **kwargs): @functools.cache def _load_options(): """Loads options from JSON files.""" - user_config_file = os.getenv("XDG_CONFIG_HOME", default=Path.home().joinpath(".config")) \ - / Path("dolfinx", "dolfinx_jit_options.json") + user_config_file = os.getenv("XDG_CONFIG_HOME", default=Path.home().joinpath(".config")) / Path( + "dolfinx", "dolfinx_jit_options.json" + ) try: with open(user_config_file) as f: user_options = json.load(f) @@ -149,8 +145,9 @@ def get_options(priority_options: Optional[dict] = None) -> dict: @mpi_jit_decorator -def ffcx_jit(ufl_object, form_compiler_options: Optional[dict] = None, - jit_options: Optional[dict] = None): +def ffcx_jit( + ufl_object, form_compiler_options: Optional[dict] = None, jit_options: Optional[dict] = None +): """Compile UFL object with FFCx and CFFI. Args: diff --git a/python/dolfinx/la.py b/python/dolfinx/la.py index 330b18e63af..41a5c87b8cf 100644 --- a/python/dolfinx/la.py +++ b/python/dolfinx/la.py @@ -14,17 +14,36 @@ from dolfinx.cpp.common import IndexMap from dolfinx.cpp.la import BlockMode, InsertMode, Norm -__all__ = ["orthonormalize", "is_orthonormal", "matrix_csr", "vector", - "MatrixCSR", "Norm", "InsertMode", "Vector", "create_petsc_vector"] +__all__ = [ + "orthonormalize", + "is_orthonormal", + "matrix_csr", + "vector", + "MatrixCSR", + "Norm", + "InsertMode", + "Vector", + "create_petsc_vector", +] class MatrixCSR: - - _cpp_object: typing.Union[_cpp.la.MatrixCSR_float32, _cpp.la.MatrixCSR_float64, - _cpp.la.MatrixCSR_complex64, _cpp.la.MatrixCSR_complex128] - - def __init__(self, A: typing.Union[_cpp.la.MatrixCSR_float32, _cpp.la.MatrixCSR_float64, - _cpp.la.MatrixCSR_complex64, _cpp.la.MatrixCSR_complex128]): + _cpp_object: typing.Union[ + _cpp.la.MatrixCSR_float32, + _cpp.la.MatrixCSR_float64, + _cpp.la.MatrixCSR_complex64, + _cpp.la.MatrixCSR_complex128, + ] + + def __init__( + self, + A: typing.Union[ + _cpp.la.MatrixCSR_float32, + _cpp.la.MatrixCSR_float64, + _cpp.la.MatrixCSR_complex64, + _cpp.la.MatrixCSR_complex128, + ], + ): """A distributed sparse matrix that uses compressed sparse row storage. Note: @@ -49,13 +68,23 @@ def block_size(self): """Block sizes for the matrix.""" return self._cpp_object.bs - def add(self, x: npt.NDArray[np.floating], rows: npt.NDArray[np.int32], - cols: npt.NDArray[np.int32], bs: int = 1) -> None: + def add( + self, + x: npt.NDArray[np.floating], + rows: npt.NDArray[np.int32], + cols: npt.NDArray[np.int32], + bs: int = 1, + ) -> None: """Add a block of values in the matrix.""" self._cpp_object.add(x, rows, cols, bs) - def set(self, x: npt.NDArray[np.floating], rows: npt.NDArray[np.int32], - cols: npt.NDArray[np.int32], bs: int = 1) -> None: + def set( + self, + x: npt.NDArray[np.floating], + rows: npt.NDArray[np.int32], + cols: npt.NDArray[np.int32], + bs: int = 1, + ) -> None: """Set a block of values in the matrix.""" self._cpp_object.set(x, rows, cols, bs) @@ -126,17 +155,27 @@ def to_scipy(self, ghosted=False): else: nrows = self.index_map(0).size_local nnzlocal = self.indptr[nrows] - data, indices, indptr = self.data[:(bs0 * bs1) * nnzlocal], self.indices[:nnzlocal], self.indptr[:nrows + 1] + data, indices, indptr = ( + self.data[: (bs0 * bs1) * nnzlocal], + self.indices[:nnzlocal], + self.indptr[: nrows + 1], + ) if bs0 == 1 and bs1 == 1: from scipy.sparse import csr_matrix as _csr + return _csr((data, indices, indptr), shape=(nrows, ncols)) else: from scipy.sparse import bsr_matrix as _bsr - return _bsr((data.reshape(-1, bs0, bs1), indices, indptr), shape=(bs0 * nrows, bs1 * ncols)) + + return _bsr( + (data.reshape(-1, bs0, bs1), indices, indptr), shape=(bs0 * nrows, bs1 * ncols) + ) -def matrix_csr(sp: _cpp.la.SparsityPattern, block_mode=BlockMode.compact, dtype=np.float64) -> MatrixCSR: +def matrix_csr( + sp: _cpp.la.SparsityPattern, block_mode=BlockMode.compact, dtype=np.float64 +) -> MatrixCSR: """Create a distributed sparse matrix. The matrix uses compressed sparse row storage. @@ -164,12 +203,22 @@ def matrix_csr(sp: _cpp.la.SparsityPattern, block_mode=BlockMode.compact, dtype= class Vector: - - _cpp_object: typing.Union[_cpp.la.Vector_float32, _cpp.la.Vector_float64, - _cpp.la.Vector_complex64, _cpp.la.Vector_complex128] - - def __init__(self, x: typing.Union[_cpp.la.Vector_float32, _cpp.la.Vector_float64, - _cpp.la.Vector_complex64, _cpp.la.Vector_complex128]): + _cpp_object: typing.Union[ + _cpp.la.Vector_float32, + _cpp.la.Vector_float64, + _cpp.la.Vector_complex64, + _cpp.la.Vector_complex128, + ] + + def __init__( + self, + x: typing.Union[ + _cpp.la.Vector_float32, + _cpp.la.Vector_float64, + _cpp.la.Vector_complex64, + _cpp.la.Vector_complex128, + ], + ): """A distributed vector object. Args: @@ -261,6 +310,7 @@ def create_petsc_vector_wrap(x: Vector): object. """ from petsc4py import PETSc + map = x.index_map ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore bs = x.block_size @@ -280,6 +330,7 @@ def create_petsc_vector(map, bs: int): PETSc Vec object. """ from petsc4py import PETSc + ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore size = (map.size_local * bs, map.size_global * bs) return PETSc.Vec().createGhost(ghosts, size=size, bsize=bs, comm=map.comm) # type: ignore @@ -300,7 +351,7 @@ def is_orthonormal(basis, eps: float = 1.0e-12) -> bool: if abs(x.norm() - 1.0) > eps: return False for i, x in enumerate(basis[:-1]): - for y in basis[i + 1:]: + for y in basis[i + 1 :]: if abs(x.dot(y)) > eps: return False return True diff --git a/python/dolfinx/mesh.py b/python/dolfinx/mesh.py index 6a64d9d2935..6ff5302053e 100644 --- a/python/dolfinx/mesh.py +++ b/python/dolfinx/mesh.py @@ -19,19 +19,49 @@ import ufl from dolfinx import cpp as _cpp from dolfinx import default_real_type -from dolfinx.cpp.mesh import (CellType, DiagonalType, GhostMode, build_dual_graph, cell_dim, create_cell_partitioner, - exterior_facet_indices, to_string, to_type) +from dolfinx.cpp.mesh import ( + CellType, + DiagonalType, + GhostMode, + build_dual_graph, + cell_dim, + create_cell_partitioner, + exterior_facet_indices, + to_string, + to_type, +) from dolfinx.cpp.refinement import RefinementOption from dolfinx.fem import CoordinateElement as _CoordinateElement from dolfinx.fem import coordinate_element as _coordinate_element -__all__ = ["meshtags_from_entities", "locate_entities", "locate_entities_boundary", - "refine", "create_mesh", "Mesh", "MeshTags", "meshtags", "CellType", - "GhostMode", "build_dual_graph", "cell_dim", "compute_midpoints", - "exterior_facet_indices", "compute_incident_entities", "create_cell_partitioner", - "create_interval", "create_unit_interval", "create_rectangle", "create_unit_square", - "create_box", "create_unit_cube", "to_type", "to_string", "refine_plaza", - "transfer_meshtag"] +__all__ = [ + "meshtags_from_entities", + "locate_entities", + "locate_entities_boundary", + "refine", + "create_mesh", + "Mesh", + "MeshTags", + "meshtags", + "CellType", + "GhostMode", + "build_dual_graph", + "cell_dim", + "compute_midpoints", + "exterior_facet_indices", + "compute_incident_entities", + "create_cell_partitioner", + "create_interval", + "create_unit_interval", + "create_rectangle", + "create_unit_square", + "create_box", + "create_unit_cube", + "to_type", + "to_string", + "refine_plaza", + "transfer_meshtag", +] class Mesh: @@ -242,38 +272,46 @@ def locate_entities_boundary(mesh: Mesh, dim: int, marker: typing.Callable) -> n "quadrilateral": CellType.quadrilateral, "quadrilateral3D": CellType.quadrilateral, "tetrahedron": CellType.tetrahedron, - "hexahedron": CellType.hexahedron + "hexahedron": CellType.hexahedron, } -def transfer_meshtag(meshtag: MeshTags, mesh1: Mesh, parent_cell: npt.NDArray[np.int32], - parent_facet: typing.Optional[npt.NDArray[np.int8]] = None) -> MeshTags: +def transfer_meshtag( + meshtag: MeshTags, + mesh1: Mesh, + parent_cell: npt.NDArray[np.int32], + parent_facet: typing.Optional[npt.NDArray[np.int8]] = None, +) -> MeshTags: """Generate cell mesh tags on a refined mesh from the mesh tags on the coarse parent mesh. - Args: - meshtag: Mesh tags on the coarse, parent mesh. - mesh1: The refined mesh. - parent_cell: Index of the parent cell for each cell in the - refined mesh. - parent_facet: Index of the local parent facet for each cell - in the refined mesh. Only required for transfer tags on - facets. + Args: + meshtag: Mesh tags on the coarse, parent mesh. + mesh1: The refined mesh. + parent_cell: Index of the parent cell for each cell in the + refined mesh. + parent_facet: Index of the local parent facet for each cell + in the refined mesh. Only required for transfer tags on + facets. - Returns: - Mesh tags on the refined mesh. + Returns: + Mesh tags on the refined mesh. """ if meshtag.dim == meshtag.topology.dim: mt = _cpp.refinement.transfer_cell_meshtag(meshtag._cpp_object, mesh1.topology, parent_cell) return MeshTags(mt) elif meshtag.dim == meshtag.topology.dim - 1: assert parent_facet is not None - mt = _cpp.refinement.transfer_facet_meshtag(meshtag._cpp_object, mesh1.topology, parent_cell, parent_facet) + mt = _cpp.refinement.transfer_facet_meshtag( + meshtag._cpp_object, mesh1.topology, parent_cell, parent_facet + ) return MeshTags(mt) else: raise RuntimeError("MeshTag transfer is supported on on cells or facets.") -def refine(mesh: Mesh, edges: typing.Optional[np.ndarray] = None, redistribute: bool = True) -> Mesh: +def refine( + mesh: Mesh, edges: typing.Optional[np.ndarray] = None, redistribute: bool = True +) -> Mesh: """Refine a mesh. Args: @@ -293,9 +331,12 @@ def refine(mesh: Mesh, edges: typing.Optional[np.ndarray] = None, redistribute: return Mesh(mesh1, mesh._ufl_domain) -def refine_plaza(mesh: Mesh, edges: typing.Optional[np.ndarray] = None, redistribute: bool = True, - option: RefinementOption = RefinementOption.none) -> tuple[Mesh, npt.NDArray[np.int32], - npt.NDArray[np.int32]]: +def refine_plaza( + mesh: Mesh, + edges: typing.Optional[np.ndarray] = None, + redistribute: bool = True, + option: RefinementOption = RefinementOption.none, +) -> tuple[Mesh, npt.NDArray[np.int32], npt.NDArray[np.int32]]: """Refine a mesh. Args: @@ -313,14 +354,24 @@ def refine_plaza(mesh: Mesh, edges: typing.Optional[np.ndarray] = None, redistri if edges is None: mesh1, cells, facets = _cpp.refinement.refine_plaza(mesh._cpp_object, redistribute, option) else: - mesh1, cells, facets = _cpp.refinement.refine_plaza(mesh._cpp_object, edges, redistribute, option) + mesh1, cells, facets = _cpp.refinement.refine_plaza( + mesh._cpp_object, edges, redistribute, option + ) return Mesh(mesh1, mesh._ufl_domain), cells, facets -def create_mesh(comm: _MPI.Comm, cells: npt.NDArray[np.int64], x: npt.NDArray[np.floating], - e: typing.Union[ufl.Mesh, basix.finite_element.FiniteElement, - basix.ufl._BasixElement, _CoordinateElement, ], - partitioner: typing.Optional[typing.Callable] = None) -> Mesh: +def create_mesh( + comm: _MPI.Comm, + cells: npt.NDArray[np.int64], + x: npt.NDArray[np.floating], + e: typing.Union[ + ufl.Mesh, + basix.finite_element.FiniteElement, + basix.ufl._BasixElement, + _CoordinateElement, + ], + partitioner: typing.Optional[typing.Callable] = None, +) -> Mesh: """Create a mesh from topology and geometry arrays. Args: @@ -342,7 +393,7 @@ def create_mesh(comm: _MPI.Comm, cells: npt.NDArray[np.int64], x: npt.NDArray[np if partitioner is None and comm.size > 1: partitioner = _cpp.mesh.create_cell_partitioner(GhostMode.none) - x = np.asarray(x, order='C') + x = np.asarray(x, order="C") if x.ndim == 1: gdim = 1 else: @@ -380,30 +431,47 @@ def create_mesh(comm: _MPI.Comm, cells: npt.NDArray[np.int64], x: npt.NDArray[np domain = None dtype = cmap.dtype - x = np.asarray(x, dtype=dtype, order='C') - cells = np.asarray(cells, dtype=np.int64, order='C') + x = np.asarray(x, dtype=dtype, order="C") + cells = np.asarray(cells, dtype=np.int64, order="C") try: mesh = _cpp.mesh.create_mesh(comm, cells, cmap._cpp_object, x, partitioner) except TypeError: - mesh = _cpp.mesh.create_mesh(comm, _cpp.graph.AdjacencyList_int64(np.asarray(cells, dtype=np.int64)), - cmap._cpp_object, x, partitioner) + mesh = _cpp.mesh.create_mesh( + comm, + _cpp.graph.AdjacencyList_int64(np.asarray(cells, dtype=np.int64)), + cmap._cpp_object, + x, + partitioner, + ) return Mesh(mesh, domain) def create_submesh(msh, dim, entities): - submsh, entity_map, vertex_map, geom_map = _cpp.mesh.create_submesh(msh._cpp_object, dim, entities) + submsh, entity_map, vertex_map, geom_map = _cpp.mesh.create_submesh( + msh._cpp_object, dim, entities + ) submsh_ufl_cell = ufl.Cell(submsh.topology.cell_name(), geometric_dimension=submsh.geometry.dim) - submsh_domain = ufl.Mesh(basix.ufl.element("Lagrange", submsh_ufl_cell.cellname(), - submsh.geometry.cmap.degree, - basix.LagrangeVariant(submsh.geometry.cmap.variant), - shape=(submsh.geometry.dim,), - gdim=submsh.geometry.dim, dtype=submsh.geometry.x.dtype)) + submsh_domain = ufl.Mesh( + basix.ufl.element( + "Lagrange", + submsh_ufl_cell.cellname(), + submsh.geometry.cmap.degree, + basix.LagrangeVariant(submsh.geometry.cmap.variant), + shape=(submsh.geometry.dim,), + gdim=submsh.geometry.dim, + dtype=submsh.geometry.x.dtype, + ) + ) return (Mesh(submsh, submsh_domain), entity_map, vertex_map, geom_map) -def meshtags(mesh: Mesh, dim: int, entities: npt.NDArray[np.int32], - values: typing.Union[np.ndarray, int, float]) -> MeshTags: +def meshtags( + mesh: Mesh, + dim: int, + entities: npt.NDArray[np.int32], + values: typing.Union[np.ndarray, int, float], +) -> MeshTags: """Create a MeshTags object that associates data with a subset of mesh entities. Args: @@ -444,8 +512,9 @@ def meshtags(mesh: Mesh, dim: int, entities: npt.NDArray[np.int32], return MeshTags(ftype(mesh.topology, dim, np.asarray(entities, dtype=np.int32), values)) -def meshtags_from_entities(mesh: Mesh, dim: int, entities: _cpp.graph.AdjacencyList_int32, - values: npt.NDArray[typing.Any]): +def meshtags_from_entities( + mesh: Mesh, dim: int, entities: _cpp.graph.AdjacencyList_int32, values: npt.NDArray[typing.Any] +): """Create a :class:dolfinx.mesh.MeshTags` object that associates data with a subset of mesh entities, where the entities are defined by their vertices. @@ -474,9 +543,14 @@ def meshtags_from_entities(mesh: Mesh, dim: int, entities: _cpp.graph.AdjacencyL return MeshTags(_cpp.mesh.create_meshtags(mesh.topology, dim, entities, values)) -def create_interval(comm: _MPI.Comm, nx: int, points: npt.ArrayLike, - dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, partitioner=None) -> Mesh: +def create_interval( + comm: _MPI.Comm, + nx: int, + points: npt.ArrayLike, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, +) -> Mesh: """Create an interval mesh. Args: @@ -506,8 +580,13 @@ def create_interval(comm: _MPI.Comm, nx: int, points: npt.ArrayLike, return Mesh(mesh, domain) -def create_unit_interval(comm: _MPI.Comm, nx: int, dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, partitioner=None) -> Mesh: +def create_unit_interval( + comm: _MPI.Comm, + nx: int, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, +) -> Mesh: """Create a mesh on the unit interval. Args: @@ -527,10 +606,16 @@ def create_unit_interval(comm: _MPI.Comm, nx: int, dtype: npt.DTypeLike = defaul return create_interval(comm, nx, [0.0, 1.0], dtype, ghost_mode, partitioner) -def create_rectangle(comm: _MPI.Comm, points: npt.ArrayLike, n: npt.ArrayLike, - cell_type=CellType.triangle, dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, - partitioner=None, diagonal: DiagonalType = DiagonalType.right) -> Mesh: +def create_rectangle( + comm: _MPI.Comm, + points: npt.ArrayLike, + n: npt.ArrayLike, + cell_type=CellType.triangle, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, + diagonal: DiagonalType = DiagonalType.right, +) -> Mesh: """Create a rectangle mesh. Args: @@ -563,10 +648,16 @@ def create_rectangle(comm: _MPI.Comm, points: npt.ArrayLike, n: npt.ArrayLike, return Mesh(mesh, domain) -def create_unit_square(comm: _MPI.Comm, nx: int, ny: int, cell_type=CellType.triangle, - dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, partitioner=None, - diagonal: DiagonalType = DiagonalType.right) -> Mesh: +def create_unit_square( + comm: _MPI.Comm, + nx: int, + ny: int, + cell_type=CellType.triangle, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, + diagonal: DiagonalType = DiagonalType.right, +) -> Mesh: """Create a mesh of a unit square. Args: @@ -585,14 +676,27 @@ def create_unit_square(comm: _MPI.Comm, nx: int, ny: int, cell_type=CellType.tri Returns: A mesh of a square with corners at (0, 0) and (1, 1). """ - return create_rectangle(comm, [np.array([0.0, 0.0]), np.array([1.0, 1.0])], - [nx, ny], cell_type, dtype, ghost_mode, - partitioner, diagonal) - - -def create_box(comm: _MPI.Comm, points: list[npt.ArrayLike], n: list, - cell_type=CellType.tetrahedron, dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, partitioner=None) -> Mesh: + return create_rectangle( + comm, + [np.array([0.0, 0.0]), np.array([1.0, 1.0])], + [nx, ny], + cell_type, + dtype, + ghost_mode, + partitioner, + diagonal, + ) + + +def create_box( + comm: _MPI.Comm, + points: list[npt.ArrayLike], + n: list, + cell_type=CellType.tetrahedron, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, +) -> Mesh: """Create a box mesh. Args: @@ -622,9 +726,16 @@ def create_box(comm: _MPI.Comm, points: list[npt.ArrayLike], n: list, return Mesh(mesh, domain) -def create_unit_cube(comm: _MPI.Comm, nx: int, ny: int, nz: int, cell_type=CellType.tetrahedron, - dtype: npt.DTypeLike = default_real_type, - ghost_mode=GhostMode.shared_facet, partitioner=None) -> Mesh: +def create_unit_cube( + comm: _MPI.Comm, + nx: int, + ny: int, + nz: int, + cell_type=CellType.tetrahedron, + dtype: npt.DTypeLike = default_real_type, + ghost_mode=GhostMode.shared_facet, + partitioner=None, +) -> Mesh: """Create a mesh of a unit cube. Args: @@ -643,5 +754,12 @@ def create_unit_cube(comm: _MPI.Comm, nx: int, ny: int, nz: int, cell_type=CellT A mesh of an axis-aligned unit cube with corners at ``(0, 0, 0)`` and ``(1, 1, 1)``. """ - return create_box(comm, [np.array([0.0, 0.0, 0.0]), np.array([1.0, 1.0, 1.0])], - [nx, ny, nz], cell_type, dtype, ghost_mode, partitioner) + return create_box( + comm, + [np.array([0.0, 0.0, 0.0]), np.array([1.0, 1.0, 1.0])], + [nx, ny, nz], + cell_type, + dtype, + ghost_mode, + partitioner, + ) diff --git a/python/dolfinx/nls/petsc.py b/python/dolfinx/nls/petsc.py index e337ce4d86e..115e2e6febc 100644 --- a/python/dolfinx/nls/petsc.py +++ b/python/dolfinx/nls/petsc.py @@ -10,10 +10,11 @@ import typing if typing.TYPE_CHECKING: - from dolfinx.fem.problem import NonlinearProblem from mpi4py import MPI from petsc4py import PETSc + from dolfinx.fem.problem import NonlinearProblem + import types from dolfinx import cpp as _cpp diff --git a/python/dolfinx/pkgconfig.py b/python/dolfinx/pkgconfig.py index 32adabe5fc4..41150a9fbef 100644 --- a/python/dolfinx/pkgconfig.py +++ b/python/dolfinx/pkgconfig.py @@ -15,13 +15,12 @@ def _pkgconfig_query(s): - pkg_config_exe = os.environ.get('PKG_CONFIG', None) or 'pkg-config' + pkg_config_exe = os.environ.get("PKG_CONFIG", None) or "pkg-config" cmd = [pkg_config_exe, *s.split()] - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() rc = proc.returncode - return (rc, out.rstrip().decode('utf-8')) + return (rc, out.rstrip().decode("utf-8")) def exists(package) -> bool: @@ -32,17 +31,17 @@ def exists(package) -> bool: def parse(package): """Return a dict containing compile-time definitions.""" parse_map = { - '-D': 'define_macros', - '-I': 'include_dirs', - '-L': 'library_dirs', - '-l': 'libraries' + "-D": "define_macros", + "-I": "include_dirs", + "-L": "library_dirs", + "-l": "libraries", } result = {x: [] for x in parse_map.values()} # Execute the query to pkg-config and clean the result - out = _pkgconfig_query(package + ' --cflags --libs')[1] - out = out.replace('\\"', '') + out = _pkgconfig_query(package + " --cflags --libs")[1] + out = out.replace('\\"', "") # Iterate through each token in the output for token in out.split(): diff --git a/python/dolfinx/plot.py b/python/dolfinx/plot.py index b0ccbda2f3b..34de0d8f6c3 100644 --- a/python/dolfinx/plot.py +++ b/python/dolfinx/plot.py @@ -22,11 +22,13 @@ # # Cell types can be found at # https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html -_first_order_vtk = {mesh.CellType.interval: 3, - mesh.CellType.triangle: 5, - mesh.CellType.quadrilateral: 9, - mesh.CellType.tetrahedron: 10, - mesh.CellType.hexahedron: 12} +_first_order_vtk = { + mesh.CellType.interval: 3, + mesh.CellType.triangle: 5, + mesh.CellType.quadrilateral: 9, + mesh.CellType.tetrahedron: 10, + mesh.CellType.hexahedron: 12, +} @functools.singledispatch @@ -78,7 +80,9 @@ def vtk_mesh(msh: mesh.Mesh, dim: typing.Optional[int] = None, entities=None): topology[:, 1:] = vtk_topology # Array holding the cell type (shape) for each cell - vtk_type = _first_order_vtk[cell_type] if degree == 1 else _cpp.io.get_vtk_cell_type(cell_type, tdim) + vtk_type = ( + _first_order_vtk[cell_type] if degree == 1 else _cpp.io.get_vtk_cell_type(cell_type, tdim) + ) cell_types = np.full(len(entities), vtk_type) return topology.reshape(-1), cell_types, msh.geometry.x @@ -104,8 +108,17 @@ def _(V: fem.FunctionSpace, entities=None): Topology, type for each cell, and geometry in VTK-ready format. """ - if V.ufl_element().family_name not in ['Discontinuous Lagrange', "Lagrange", "DQ", "Q", "DP", "P"]: - raise RuntimeError("Can only create meshes from continuous or discontinuous Lagrange spaces") + if V.ufl_element().family_name not in [ + "Discontinuous Lagrange", + "Lagrange", + "DQ", + "Q", + "DP", + "P", + ]: + raise RuntimeError( + "Can only create meshes from continuous or discontinuous Lagrange spaces" + ) degree = V.ufl_element().degree if degree == 0: @@ -122,12 +135,14 @@ def _(V: fem.FunctionSpace, entities=None): cell_type = msh.topology.cell_type perm = np.argsort(_cpp.io.perm_vtk(cell_type, num_dofs_per_cell)) - vtk_type = _first_order_vtk[cell_type] if degree == 1 else _cpp.io.get_vtk_cell_type(cell_type, tdim) + vtk_type = ( + _first_order_vtk[cell_type] if degree == 1 else _cpp.io.get_vtk_cell_type(cell_type, tdim) + ) cell_types = np.full(len(entities), vtk_type) topology = np.zeros((len(entities), num_dofs_per_cell + 1), dtype=np.int32) topology[:, 0] = num_dofs_per_cell dofmap_ = dofmap.list - topology[:, 1:] = dofmap_[:len(entities), perm] + topology[:, 1:] = dofmap_[: len(entities), perm] return topology.reshape(1, -1)[0], cell_types, V.tabulate_dof_coordinates() diff --git a/python/dolfinx/utils.py b/python/dolfinx/utils.py index 445d5c4b4d6..6649ecc4b17 100644 --- a/python/dolfinx/utils.py +++ b/python/dolfinx/utils.py @@ -29,10 +29,12 @@ def get_petsc_lib() -> pathlib.Path: """ import petsc4py as _petsc4py - petsc_dir = _petsc4py.get_config()['PETSC_DIR'] + petsc_dir = _petsc4py.get_config()["PETSC_DIR"] petsc_arch = _petsc4py.lib.getPathArchPETSc()[1] - candidate_paths = [os.path.join(petsc_dir, petsc_arch, "lib", "libpetsc.so"), - os.path.join(petsc_dir, petsc_arch, "lib", "libpetsc.dylib")] + candidate_paths = [ + os.path.join(petsc_dir, petsc_arch, "lib", "libpetsc.so"), + os.path.join(petsc_dir, petsc_arch, "lib", "libpetsc.dylib"), + ] exists_paths = [] for candidate_path in candidate_paths: if os.path.exists(candidate_path): @@ -67,11 +69,13 @@ def set_vals(A: int, data: npt.NDArray[PETSc.ScalarTYpe], mode: int): MatSetValuesLocal(A, m, rows.ctypes, n, cols.ctypes, data.ctypes, mode) """ + try: import petsc4py.PETSc as _PETSc import llvmlite as _llvmlite import numba as _numba + _llvmlite.binding.load_library_permanently(str(get_petsc_lib())) _int = _numba.from_dtype(_PETSc.IntType) # type: ignore @@ -79,15 +83,26 @@ def set_vals(A: int, _real = _numba.from_dtype(_PETSc.RealType) # type: ignore _int_ptr = _numba.core.types.CPointer(_int) _scalar_ptr = _numba.core.types.CPointer(_scalar) - _MatSetValues_sig = _numba.core.typing.signature(_numba.core.types.intc, - _numba.core.types.uintp, _int, _int_ptr, - _int, _int_ptr, _scalar_ptr, _numba.core.types.intc) - MatSetValuesLocal = _numba.core.types.ExternalFunction("MatSetValuesLocal", _MatSetValues_sig) + _MatSetValues_sig = _numba.core.typing.signature( + _numba.core.types.intc, + _numba.core.types.uintp, + _int, + _int_ptr, + _int, + _int_ptr, + _scalar_ptr, + _numba.core.types.intc, + ) + MatSetValuesLocal = _numba.core.types.ExternalFunction( + "MatSetValuesLocal", _MatSetValues_sig + ) """See PETSc `MatSetValuesLocal `_ documentation.""" - MatSetValuesBlockedLocal = _numba.core.types.ExternalFunction("MatSetValuesBlockedLocal", _MatSetValues_sig) + MatSetValuesBlockedLocal = _numba.core.types.ExternalFunction( + "MatSetValuesBlockedLocal", _MatSetValues_sig + ) """See PETSc `MatSetValuesBlockedLocal `_ documentation.""" @@ -112,7 +127,9 @@ def set_vals(A: int, data: npt.NDArray[PETSc.ScalarTYpe], mode: int): MatSetValuesLocal(A, m, rows.ctypes, n, cols.ctypes, data.ctypes, mode) """ + import petsc4py.PETSc as _PETSc + _lib_ctypes = _ctypes.cdll.LoadLibrary(str(get_petsc_lib())) # Note: ctypes does not have complex types, hence we use void* for @@ -123,15 +140,29 @@ def set_vals(A: int, """See PETSc `MatSetValuesLocal `_ documentation.""" - MatSetValuesLocal.argtypes = [_ctypes.c_void_p, _int, _ctypes.POINTER(_int), _int, - _ctypes.POINTER(_int), _ctypes.c_void_p, _ctypes.c_int] + MatSetValuesLocal.argtypes = [ + _ctypes.c_void_p, + _int, + _ctypes.POINTER(_int), + _int, + _ctypes.POINTER(_int), + _ctypes.c_void_p, + _ctypes.c_int, + ] MatSetValuesBlockedLocal = _lib_ctypes.MatSetValuesBlockedLocal """See PETSc `MatSetValuesBlockedLocal `_ documentation.""" - MatSetValuesBlockedLocal.argtypes = [_ctypes.c_void_p, _int, _ctypes.POINTER(_int), _int, - _ctypes.POINTER(_int), _ctypes.c_void_p, _ctypes.c_int] + MatSetValuesBlockedLocal.argtypes = [ + _ctypes.c_void_p, + _int, + _ctypes.POINTER(_int), + _int, + _ctypes.POINTER(_int), + _ctypes.c_void_p, + _ctypes.c_int, + ] class cffi_utils: @@ -157,6 +188,7 @@ def set_vals(A: int, MatSetValuesLocal(A, m, ffi.from_buffer(rows), n, ffi.from_buffer(cols), ffi.from_buffer(rows(data), mode) """ + try: from petsc4py import PETSc as _PETSc @@ -166,25 +198,32 @@ def set_vals(A: int, # Register complex types _ffi = _cffi.FFI() - _cffi_support.register_type(_ffi.typeof('float _Complex'), _numba.types.complex64) - _cffi_support.register_type(_ffi.typeof('double _Complex'), _numba.types.complex128) + _cffi_support.register_type(_ffi.typeof("float _Complex"), _numba.types.complex64) + _cffi_support.register_type(_ffi.typeof("double _Complex"), _numba.types.complex128) _lib_cffi = _ffi.dlopen(str(get_petsc_lib())) - _CTYPES = {np.int32: "int32_t", np.int64: "int64_t", - np.float32: "float", np.float64: "double", - np.complex64: "float _Complex", np.complex128: "double _Complex", - np.longlong: "long long"} + _CTYPES = { + np.int32: "int32_t", + np.int64: "int64_t", + np.float32: "float", + np.float64: "double", + np.complex64: "float _Complex", + np.complex128: "double _Complex", + np.longlong: "long long", + } _c_int_t = _CTYPES[_PETSc.IntType] # type: ignore _c_scalar_t = _CTYPES[_PETSc.ScalarType] # type: ignore - _ffi.cdef(f""" + _ffi.cdef( + f""" int MatSetValuesLocal(void* mat, {_c_int_t} nrow, const {_c_int_t}* irow, {_c_int_t} ncol, const {_c_int_t}* icol, const {_c_scalar_t}* y, int addv); int MatSetValuesBlockedLocal(void* mat, {_c_int_t} nrow, const {_c_int_t}* irow, {_c_int_t} ncol, const {_c_int_t}* icol, const {_c_scalar_t}* y, int addv); - """) + """ + ) MatSetValuesLocal = _lib_cffi.MatSetValuesLocal """See PETSc `MatSetValuesLocal diff --git a/python/dolfinx/wrappers/__init__.py b/python/dolfinx/wrappers/__init__.py index 66a70d8592e..9757a1d6da5 100644 --- a/python/dolfinx/wrappers/__init__.py +++ b/python/dolfinx/wrappers/__init__.py @@ -8,4 +8,5 @@ def get_include_path(): """Return path to nanobind wrapper header files""" import pathlib + return pathlib.Path(__file__).parent diff --git a/python/pyproject.toml b/python/pyproject.toml index 7f2d58097d7..0990526988f 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -72,23 +72,9 @@ warn_unused_ignores = false show_error_codes = true ignore_missing_imports = true -[tool.isort] -line_length = 120 -src_paths = ["demo", "dolfinx", "test"] -known_first_party = ["basix", "dolfinx", "ffcx", "ufl"] -known_third_party = ["gmsh", "numba", "numpy", "pytest", "pyvista"] -known_mpi = ["mpi4py", "petsc4py"] -sections = [ - "FUTURE", - "STDLIB", - "MPI", - "THIRDPARTY", - "FIRSTPARTY", - "LOCALFOLDER", -] [tool.ruff] -line-length = 120 +line-length = 100 indent-width = 4 [tool.ruff.lint] @@ -96,7 +82,7 @@ select = [ "E", # pycodestyle "W", # pycodestyle "F", # pyflakes - # "I", # isort - use standalone isort + "I", # isort - use standalone isort "RUF", # Ruff-specific rules "UP", # pyupgrade "ICN", # flake8-import-conventions @@ -105,3 +91,18 @@ select = [ ] ignore = ["UP007", "RUF012"] allowed-confusables = ["σ"] + +[tool.ruff.lint.isort] +known-first-party = ["basix", "dolfinx", "ffcx", "ufl"] +known-third-party = ["gmsh", "numba", "numpy", "pytest", "pyvista"] +section-order = [ + "future", + "standard-library", + "mpi", + "third-party", + "first-party", + "local-folder", +] + +[tool.ruff.lint.isort.sections] +"mpi" = ["mpi4py", "petsc4py"] diff --git a/python/test/conftest.py b/python/test/conftest.py index 4d8af59a4e2..b589c831557 100644 --- a/python/test/conftest.py +++ b/python/test/conftest.py @@ -144,8 +144,8 @@ def tempdir(request): @pytest.fixture(scope="function") def cg_solver(): """Simple Conjugate Gradient solver for SPD problems, - which can work in serial or parallel for testing use. - Not suitable for large problems.""" + which can work in serial or parallel for testing use. + Not suitable for large problems.""" # Basic Conjugate Gradient solver def _cg(comm, A, b, x, maxit=500, rtol=None): @@ -173,7 +173,7 @@ def _global_dot(comm, v0, v1): rnorm0 = _global_dot(comm, r, r) rnorm = rnorm0 k = 0 - while (k < maxit): + while k < maxit: k += 1 p.scatter_forward() y = A_op @ p.array @@ -183,7 +183,7 @@ def _global_dot(comm, v0, v1): rnorm_new = _global_dot(comm, r, r) beta = rnorm_new / rnorm rnorm = rnorm_new - if (rnorm / rnorm0 < rtol2): + if rnorm / rnorm0 < rtol2: x.scatter_forward() return p.array[:nr] = beta * p.array[:nr] + r diff --git a/python/test/unit/common/test_index_map.py b/python/test/unit/common/test_index_map.py index 8dc8cbbf921..dd70cf95efa 100644 --- a/python/test/unit/common/test_index_map.py +++ b/python/test/unit/common/test_index_map.py @@ -30,18 +30,24 @@ def test_sub_index_map(): # 0, the second of rank 2 etc. # Ghost one index from from every other rank dest_ranks = np.delete(np.arange(0, comm.size, dtype=np.int32), my_rank) - map_ghosts = np.array([map_local_size * dest_ranks[r] + r % - map_local_size for r in range(len(dest_ranks))], dtype=np.int64) + map_ghosts = np.array( + [map_local_size * dest_ranks[r] + r % map_local_size for r in range(len(dest_ranks))], + dtype=np.int64, + ) src_ranks = dest_ranks # Create index map - map = dolfinx.common.IndexMap(comm, map_local_size, [dest_ranks, src_ranks], map_ghosts, src_ranks) + map = dolfinx.common.IndexMap( + comm, map_local_size, [dest_ranks, src_ranks], map_ghosts, src_ranks + ) assert map.size_global == map_local_size * comm.size # Build list for each rank of the first (myrank + myrank % 2) local # indices submap_local_size = [int(rank + rank % 2) for rank in range(comm.size)] - local_indices = [np.arange(submap_local_size[rank], dtype=np.int32) for rank in range(comm.size)] + local_indices = [ + np.arange(submap_local_size[rank], dtype=np.int32) for rank in range(comm.size) + ] # Create sub index map and a map from the ghost position in new map # to the position in old map @@ -86,7 +92,9 @@ def test_index_map_ghost_lifetime(): assert comm.size < n + 1 local_size = math.factorial(n) dest = np.delete(np.arange(0, comm.size, dtype=np.int32), comm.rank) - map_ghosts = np.array([local_size * dest[r] + r % local_size for r in range(len(dest))], dtype=np.int64) + map_ghosts = np.array( + [local_size * dest[r] + r % local_size for r in range(len(dest))], dtype=np.int64 + ) src = dest map = dolfinx.common.IndexMap(comm, local_size, [dest, src], map_ghosts, src) assert map.size_global == local_size * comm.size @@ -99,7 +107,7 @@ def test_index_map_ghost_lifetime(): # Create marker for all indices that are on process (local or ghost) on_process = np.zeros(map.size_global, dtype=bool) - on_process[map.local_range[0]:map.local_range[1]] = True + on_process[map.local_range[0] : map.local_range[1]] = True on_process[map.ghosts] = True assert (local_indices[on_process] >= 0).all() assert np.allclose(local_indices[np.invert(on_process)], -1) @@ -180,5 +188,7 @@ def test_create_submap_owner_change(): assert np.array_equal(sub_imap.ghosts, [2 * (comm.rank + 1)]) assert np.array_equal(sub_imap.owners, [comm.rank + 1]) assert np.array_equal(sub_imap_to_imap, [0, 2, 3]) - global_indices = sub_imap.local_to_global(np.arange(sub_imap.size_local + sub_imap.num_ghosts, dtype=np.int32)) + global_indices = sub_imap.local_to_global( + np.arange(sub_imap.size_local + sub_imap.num_ghosts, dtype=np.int32) + ) assert np.array_equal(global_indices, np.arange(comm.rank * 2, comm.rank * 2 + 3)) diff --git a/python/test/unit/common/test_public_api.py b/python/test/unit/common/test_public_api.py index c9cfd760a83..555072e748e 100644 --- a/python/test/unit/common/test_public_api.py +++ b/python/test/unit/common/test_public_api.py @@ -12,10 +12,10 @@ def collect_pkg_modules_recursive(name): module = importlib.import_module(name) - submodules = list(a.name for a in pkgutil.iter_modules( - module.__path__, prefix=f"{name}.")) - subpackages = list(a.name for a in pkgutil.iter_modules( - module.__path__, prefix=f"{name}.") if a.ispkg) + submodules = list(a.name for a in pkgutil.iter_modules(module.__path__, prefix=f"{name}.")) + subpackages = list( + a.name for a in pkgutil.iter_modules(module.__path__, prefix=f"{name}.") if a.ispkg + ) for subpackage in subpackages: pkg_submodules = collect_pkg_modules_recursive(subpackage) submodules.extend(pkg_submodules) diff --git a/python/test/unit/common/test_version.py b/python/test/unit/common/test_version.py index 55e977d081e..197084ce57f 100644 --- a/python/test/unit/common/test_version.py +++ b/python/test/unit/common/test_version.py @@ -13,7 +13,9 @@ def test_version(): """Test that installed Python version matches C++ version.""" py_version = version("fenics-dolfinx") # Change any final '.dev0' to '.0' - py_version = py_version.replace('dev', '') + py_version = py_version.replace("dev", "") cpp_version = dolfinx.__version__ if py_version != cpp_version: - raise RuntimeError(f"Incorrect versions. Python version: {py_version}, Core version: {cpp_version}") + raise RuntimeError( + f"Incorrect versions. Python version: {py_version}, Core version: {cpp_version}" + ) diff --git a/python/test/unit/fem/test_assemble_domains.py b/python/test/unit/fem/test_assemble_domains.py index 0140b6353d7..99e38d7c0e4 100644 --- a/python/test/unit/fem/test_assemble_domains.py +++ b/python/test/unit/fem/test_assemble_domains.py @@ -14,8 +14,15 @@ from dolfinx import cpp as _cpp from dolfinx import default_scalar_type, fem, la from dolfinx.fem import Constant, Function, assemble_scalar, dirichletbc, form, functionspace -from dolfinx.mesh import (GhostMode, Mesh, create_unit_square, locate_entities, locate_entities_boundary, meshtags, - meshtags_from_entities) +from dolfinx.mesh import ( + GhostMode, + Mesh, + create_unit_square, + locate_entities, + locate_entities_boundary, + meshtags, + meshtags_from_entities, +) @pytest.fixture @@ -26,15 +33,30 @@ def mesh(): def create_cell_meshtags_from_entities(mesh: Mesh, dim: int, cells: np.ndarray, values: np.ndarray): mesh.topology.create_connectivity(mesh.topology.dim, 0) cell_to_vertices = mesh.topology.connectivity(mesh.topology.dim, 0) - entities = _cpp.graph.AdjacencyList_int32(np.array([cell_to_vertices.links(cell) for cell in cells])) + entities = _cpp.graph.AdjacencyList_int32( + np.array([cell_to_vertices.links(cell) for cell in cells]) + ) return meshtags_from_entities(mesh, dim, entities, values) -parametrize_ghost_mode = pytest.mark.parametrize("mode", [ - pytest.param(GhostMode.none, marks=pytest.mark.skipif(condition=MPI.COMM_WORLD.size > 1, - reason="Unghosted interior facets fail in parallel")), - pytest.param(GhostMode.shared_facet, marks=pytest.mark.skipif(condition=MPI.COMM_WORLD.size == 1, - reason="Shared ghost modes fail in serial"))]) +parametrize_ghost_mode = pytest.mark.parametrize( + "mode", + [ + pytest.param( + GhostMode.none, + marks=pytest.mark.skipif( + condition=MPI.COMM_WORLD.size > 1, + reason="Unghosted interior facets fail in parallel", + ), + ), + pytest.param( + GhostMode.shared_facet, + marks=pytest.mark.skipif( + condition=MPI.COMM_WORLD.size == 1, reason="Shared ghost modes fail in serial" + ), + ), + ], +) @pytest.mark.parametrize("mode", [GhostMode.none, GhostMode.shared_facet]) @@ -54,7 +76,7 @@ def test_assembly_dx_domains(mode, meshtags_factory): values[0] = 1 values[1] = 2 marker = meshtags_factory(mesh, mesh.topology.dim, indices, values) - dx = ufl.Measure('dx', subdomain_data=marker, domain=mesh) + dx = ufl.Measure("dx", subdomain_data=marker, domain=mesh) w = Function(V) w.x.array[:] = 0.5 @@ -131,7 +153,7 @@ def right(x): indices, pos = np.unique(indices, return_index=True) marker = meshtags(mesh, mesh.topology.dim - 1, indices, values[pos]) - ds = ufl.Measure('ds', subdomain_data=marker, domain=mesh) + ds = ufl.Measure("ds", subdomain_data=marker, domain=mesh) w = Function(V) w.x.array[:] = 0.5 @@ -257,10 +279,13 @@ def test_manual_integration_domains(): dS_mt = ufl.Measure("dS", subdomain_data=mt_facets, domain=msh) g = Function(V) - g.interpolate(lambda x: x[1]**2) + g.interpolate(lambda x: x[1] ** 2) def create_forms(dx, ds, dS): - a = form(ufl.inner(g * u, v) * (dx(0) + dx(7) + ds(6)) + ufl.inner(g * u("+"), v("+") + v("-")) * dS(3)) + a = form( + ufl.inner(g * u, v) * (dx(0) + dx(7) + ds(6)) + + ufl.inner(g * u("+"), v("+") + v("-")) * dS(3) + ) L = form(ufl.inner(g, v) * (dx(0) + dx(7) + ds(6)) + ufl.inner(g, v("+") + v("-")) * dS(3)) return (a, L) @@ -272,9 +297,10 @@ def create_forms(dx, ds, dS): # Manually specify cells to integrate over (removing ghosts # to give same result as above) - cell_domains = [(domain_id, cell_indices[(cell_values == domain_id) - & (cell_indices < cell_map.size_local)]) - for domain_id in [0, 7]] + cell_domains = [ + (domain_id, cell_indices[(cell_values == domain_id) & (cell_indices < cell_map.size_local)]) + for domain_id in [0, 7] + ] # Manually specify exterior facets to integrate over as # (cell, local facet) pairs diff --git a/python/test/unit/fem/test_assemble_submesh.py b/python/test/unit/fem/test_assemble_submesh.py index 91681b8fd4a..6dcb0ed110b 100644 --- a/python/test/unit/fem/test_assemble_submesh.py +++ b/python/test/unit/fem/test_assemble_submesh.py @@ -13,8 +13,16 @@ import ufl from dolfinx import default_scalar_type, fem, la -from dolfinx.mesh import (GhostMode, create_box, create_rectangle, create_submesh, create_unit_cube, create_unit_square, - locate_entities, locate_entities_boundary) +from dolfinx.mesh import ( + GhostMode, + create_box, + create_rectangle, + create_submesh, + create_unit_cube, + create_unit_square, + locate_entities, + locate_entities_boundary, +) def assemble(mesh, space, k): @@ -46,7 +54,9 @@ def assemble(mesh, space, k): fem.apply_lifting(b.array, [a], bcs=[[bc]]) b.scatter_reverse(la.InsertMode.add) fem.set_bc(b.array, [bc]) - s = mesh.comm.allreduce(fem.assemble_scalar(fem.form(ufl.inner(c * f, f) * (dx + ds))), op=MPI.SUM) + s = mesh.comm.allreduce( + fem.assemble_scalar(fem.form(ufl.inner(c * f, f) * (dx + ds))), op=MPI.SUM + ) return A, b, s @@ -54,19 +64,21 @@ def assemble(mesh, space, k): @pytest.mark.parametrize("n", [2, 6]) @pytest.mark.parametrize("k", [1, 4]) @pytest.mark.parametrize("space", ["Lagrange", "Discontinuous Lagrange"]) -@pytest.mark.parametrize("ghost_mode", [GhostMode.none, - GhostMode.shared_facet]) +@pytest.mark.parametrize("ghost_mode", [GhostMode.none, GhostMode.shared_facet]) def test_submesh_cell_assembly(d, n, k, space, ghost_mode): """Check that assembling a form over a unit square gives the same result as assembling over half of a 2x1 rectangle with the same triangulation.""" if d == 2: mesh_0 = create_unit_square(MPI.COMM_WORLD, n, n, ghost_mode=ghost_mode) - mesh_1 = create_rectangle(MPI.COMM_WORLD, ((0.0, 0.0), (2.0, 1.0)), (2 * n, n), ghost_mode=ghost_mode) + mesh_1 = create_rectangle( + MPI.COMM_WORLD, ((0.0, 0.0), (2.0, 1.0)), (2 * n, n), ghost_mode=ghost_mode + ) else: mesh_0 = create_unit_cube(MPI.COMM_WORLD, n, n, n, ghost_mode=ghost_mode) - mesh_1 = create_box(MPI.COMM_WORLD, ((0.0, 0.0, 0.0), (2.0, 1.0, 1.0)), - (2 * n, n, n), ghost_mode=ghost_mode) + mesh_1 = create_box( + MPI.COMM_WORLD, ((0.0, 0.0, 0.0), (2.0, 1.0, 1.0)), (2 * n, n, n), ghost_mode=ghost_mode + ) A_mesh_0, b_mesh_0, s_mesh_0 = assemble(mesh_0, space, k) @@ -75,7 +87,9 @@ def test_submesh_cell_assembly(d, n, k, space, ghost_mode): submesh = create_submesh(mesh_1, edim, entities)[0] A_submesh, b_submesh, s_submesh = assemble(submesh, space, k) - assert A_mesh_0.squared_norm() == pytest.approx(A_submesh.squared_norm(), rel=1.0e-4, abs=1.0e-4) + assert A_mesh_0.squared_norm() == pytest.approx( + A_submesh.squared_norm(), rel=1.0e-4, abs=1.0e-4 + ) assert b_mesh_0.norm() == pytest.approx(b_submesh.norm(), rel=1.0e-4) assert np.isclose(s_mesh_0, s_submesh) @@ -97,6 +111,8 @@ def test_submesh_facet_assembly(n, k, space, ghost_mode): square_mesh = create_unit_square(MPI.COMM_WORLD, n, n, ghost_mode=ghost_mode) A_square_mesh, b_square_mesh, s_square_mesh = assemble(square_mesh, space, k) - assert A_submesh.squared_norm() == pytest.approx(A_square_mesh.squared_norm(), rel=1.0e-5, abs=1.0e-5) + assert A_submesh.squared_norm() == pytest.approx( + A_square_mesh.squared_norm(), rel=1.0e-5, abs=1.0e-5 + ) assert b_submesh.norm() == pytest.approx(b_square_mesh.norm()) assert np.isclose(s_submesh, s_square_mesh) diff --git a/python/test/unit/fem/test_assembler.py b/python/test/unit/fem/test_assembler.py index d8a9d2138b4..849e985b35b 100644 --- a/python/test/unit/fem/test_assembler.py +++ b/python/test/unit/fem/test_assembler.py @@ -19,8 +19,18 @@ from basix.ufl import element, mixed_element from dolfinx import cpp as _cpp from dolfinx import default_real_type, fem, graph, la -from dolfinx.fem import (Constant, Function, assemble_scalar, bcs_by_block, dirichletbc, extract_function_spaces, form, - functionspace, locate_dofs_geometrical, locate_dofs_topological) +from dolfinx.fem import ( + Constant, + Function, + assemble_scalar, + bcs_by_block, + dirichletbc, + extract_function_spaces, + form, + functionspace, + locate_dofs_geometrical, + locate_dofs_topological, +) from dolfinx.fem.petsc import apply_lifting as petsc_apply_lifting from dolfinx.fem.petsc import apply_lifting_nest as petsc_apply_lifting_nest from dolfinx.fem.petsc import assemble_matrix as petsc_assemble_matrix @@ -31,9 +41,16 @@ from dolfinx.fem.petsc import assemble_vector_nest as petsc_assemble_vector_nest from dolfinx.fem.petsc import set_bc as petsc_set_bc from dolfinx.fem.petsc import set_bc_nest as petsc_set_bc_nest -from dolfinx.mesh import (CellType, GhostMode, create_mesh, create_rectangle, create_unit_cube, create_unit_square, - locate_entities_boundary) -from ufl import derivative, ds, dS, dx, inner +from dolfinx.mesh import ( + CellType, + GhostMode, + create_mesh, + create_rectangle, + create_unit_cube, + create_unit_square, + locate_entities_boundary, +) +from ufl import derivative, dS, ds, dx, inner from ufl.geometry import SpatialCoordinate @@ -141,7 +158,7 @@ def test_basic_assembly(mode, dtype): assert normb == pytest.approx(b.norm()) # Vector re-assembly - no zeroing (but need to zero ghost entries) - b.array[b.index_map.size_local * b.block_size:] = 0 + b.array[b.index_map.size_local * b.block_size :] = 0 fem.assemble_vector(b.array, L) b.scatter_reverse(la.InsertMode.add) assert 2 * normb == pytest.approx(b.norm()) @@ -190,7 +207,9 @@ def test_assembly_bcs(mode): a = form(inner(u, v) * dx + inner(u, v) * ds) L = form(inner(1.0, v) * dx) - bdofsV = locate_dofs_geometrical(V, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0))) + bdofsV = locate_dofs_geometrical( + V, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ) bc = dirichletbc(PETSc.ScalarType(1), bdofsV, V) # Assemble and apply 'global' lifting of bcs @@ -220,12 +239,21 @@ def test_assembly_bcs(mode): def test_petsc_assemble_manifold(): """Test assembly of poisson problem on a mesh with topological dimension 1 but embedded in 2D (gdim=2)""" - points = np.array([[0.0, 0.0], [0.2, 0.0], [0.4, 0.0], - [0.6, 0.0], [0.8, 0.0], [1.0, 0.0]], dtype=default_real_type) + points = np.array( + [[0.0, 0.0], [0.2, 0.0], [0.4, 0.0], [0.6, 0.0], [0.8, 0.0], [1.0, 0.0]], + dtype=default_real_type, + ) cells = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]], dtype=np.int32) - domain = ufl.Mesh(element( - basix.ElementFamily.P, basix.CellType.interval, 1, gdim=points.shape[1], shape=(points.shape[1],), - dtype=default_real_type)) + domain = ufl.Mesh( + element( + basix.ElementFamily.P, + basix.CellType.interval, + 1, + gdim=points.shape[1], + shape=(points.shape[1],), + dtype=default_real_type, + ) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) assert mesh.geometry.dim == 2 assert mesh.topology.dim == 1 @@ -267,8 +295,9 @@ def test_matrix_assembly_block(mode): # Locate facets on boundary facetdim = mesh.topology.dim - 1 - bndry_facets = locate_entities_boundary(mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))) + bndry_facets = locate_entities_boundary( + mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ) bdofsV1 = locate_dofs_topological(V1, facetdim, bndry_facets) u_bc = PETSc.ScalarType(50.0) bc = dirichletbc(u_bc, bdofsV1, V1) @@ -315,9 +344,11 @@ def blocked(): def nest(): """Nested (MatNest)""" - A = petsc_assemble_matrix_nest(a_block, bcs=[bc], mat_types=[["baij", "aij", "aij"], - ["aij", "", "aij"], - ["aij", "aij", "aij"]]) + A = petsc_assemble_matrix_nest( + a_block, + bcs=[bc], + mat_types=[["baij", "aij", "aij"], ["aij", "", "aij"], ["aij", "aij", "aij"]], + ) A.assemble() with pytest.raises(RuntimeError): petsc_assemble_matrix_nest(a_block_none, bcs=[bc]) @@ -329,7 +360,7 @@ def nest(): bcs0 = bcs_by_block([L.function_spaces[0] for L in L_block], [bc]) petsc_set_bc_nest(b, bcs0) b.assemble() - bnorm = math.sqrt(sum([x.norm()**2 for x in b.getNestSubVecs()])) + bnorm = math.sqrt(sum([x.norm() ** 2 for x in b.getNestSubVecs()])) Anorm = nest_matrix_norm(A) A.destroy(), b.destroy() return Anorm, bnorm @@ -339,9 +370,17 @@ def monolithic(): W = functionspace(mesh, mixed_element([P0, P1, P2])) u0, u1, u2 = ufl.TrialFunctions(W) v0, v1, v2 = ufl.TestFunctions(W) - a = inner(u0, v0) * dx + inner(u1, v1) * dx + inner(u0, v1) * dx + inner( - u1, v0) * dx + inner(u0, v2) * dx + inner(u1, v2) * dx + inner(u2, v2) * dx \ - + inner(u2, v0) * dx + inner(u2, v1) * dx + a = ( + inner(u0, v0) * dx + + inner(u1, v1) * dx + + inner(u0, v1) * dx + + inner(u1, v0) * dx + + inner(u0, v2) * dx + + inner(u1, v2) * dx + + inner(u2, v2) * dx + + inner(u2, v0) * dx + + inner(u2, v1) * dx + ) L = zero * inner(f, v0) * ufl.dx + inner(g, v1) * dx + inner(g, v2) * dx a, L = form(a), form(L) @@ -369,10 +408,7 @@ def monolithic(): assert bnorm2 == pytest.approx(bnorm0, 1.0e-6) -@pytest.mark.parametrize("mode", [ - GhostMode.none, - GhostMode.shared_facet -]) +@pytest.mark.parametrize("mode", [GhostMode.none, GhostMode.shared_facet]) def test_assembly_solve_block(mode): """Solve a two-field mass-matrix like problem with block matrix approaches and test that solution is the same""" @@ -383,8 +419,9 @@ def test_assembly_solve_block(mode): # Locate facets on boundary facetdim = mesh.topology.dim - 1 - bndry_facets = locate_entities_boundary(mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))) + bndry_facets = locate_entities_boundary( + mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ) bdofsV0 = locate_dofs_topological(V0, facetdim, bndry_facets) bdofsV1 = locate_dofs_topological(V1, facetdim, bndry_facets) @@ -421,7 +458,7 @@ def blocked(): ksp.create(mesh.comm) ksp.setOperators(A) ksp.setMonitor(monitor) - ksp.setType('cg') + ksp.setType("cg") ksp.setTolerances(rtol=1.0e-14) ksp.setFromOptions() ksp.solve(b, x) @@ -449,7 +486,7 @@ def nested(): ksp.create(mesh.comm) ksp.setMonitor(monitor) ksp.setOperators(A) - ksp.setType('cg') + ksp.setType("cg") ksp.setTolerances(rtol=1.0e-12) ksp.setFromOptions() ksp.solve(b, x) @@ -458,7 +495,7 @@ def nested(): # bnorm = b.norm() bnorm = 0.0 for b_sub in b.getNestSubVecs(): - bnorm += b_sub.norm()**2 + bnorm += b_sub.norm() ** 2 bnorm = np.sqrt(bnorm) xnorm = x.norm() ksp.destroy(), A.destroy(), b.destroy(), x.destroy() @@ -491,8 +528,8 @@ def monolithic(): ksp.create(mesh.comm) ksp.setMonitor(monitor) ksp.setOperators(A) - ksp.setType('cg') - ksp.getPC().setType('jacobi') + ksp.setType("cg") + ksp.getPC().setType("jacobi") ksp.setTolerances(rtol=1.0e-12) ksp.setFromOptions() ksp.solve(b, x) @@ -514,11 +551,15 @@ def monolithic(): assert xnorm2 == pytest.approx(xnorm0, 1.0e-6) -@pytest.mark.parametrize("mesh", [ - create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.none), - create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.shared_facet), - create_unit_cube(MPI.COMM_WORLD, 3, 7, 3, ghost_mode=GhostMode.none), - create_unit_cube(MPI.COMM_WORLD, 3, 7, 3, ghost_mode=GhostMode.shared_facet)]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.none), + create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.shared_facet), + create_unit_cube(MPI.COMM_WORLD, 3, 7, 3, ghost_mode=GhostMode.none), + create_unit_cube(MPI.COMM_WORLD, 3, 7, 3, ghost_mode=GhostMode.shared_facet), + ], +) def test_assembly_solve_taylor_hood(mesh): """Assemble Stokes problem with Taylor-Hood elements and solve.""" gdim = mesh.geometry.dim @@ -566,11 +607,13 @@ def boundary1(x): def nested_solve(): """Nested solver""" - A = petsc_assemble_matrix_nest(form([[a00, a01], [a10, a11]]), bcs=[bc0, bc1], - mat_types=[["baij", "aij"], ["aij", ""]]) + A = petsc_assemble_matrix_nest( + form([[a00, a01], [a10, a11]]), bcs=[bc0, bc1], mat_types=[["baij", "aij"], ["aij", ""]] + ) A.assemble() - P = petsc_assemble_matrix_nest(form([[p00, p01], [p10, p11]]), bcs=[bc0, bc1], - mat_types=[["aij", "aij"], ["aij", ""]]) + P = petsc_assemble_matrix_nest( + form([[p00, p01], [p10, p11]]), bcs=[bc0, bc1], mat_types=[["aij", "aij"], ["aij", ""]] + ) P.assemble() b = petsc_assemble_vector_nest(form([L0, L1])) petsc_apply_lifting_nest(b, form([[a00, a01], [a10, a11]]), [bc0, bc1]) @@ -590,7 +633,7 @@ def nested_solve(): pc.setFieldSplitIS(["u", nested_IS[0][0]], ["p", nested_IS[1][1]]) ksp_u, ksp_p = pc.getFieldSplitSubKSP() ksp_u.setType("preonly") - ksp_u.getPC().setType('lu') + ksp_u.getPC().setType("lu") ksp_p.setType("preonly") def monitor(ksp, its, rnorm): @@ -614,14 +657,16 @@ def blocked_solve(): A.assemble() P = petsc_assemble_matrix_block(form([[p00, p01], [p10, p11]]), bcs=[bc0, bc1]) P.assemble() - b = petsc_assemble_vector_block(form([L0, L1]), form([[a00, a01], [a10, a11]]), bcs=[bc0, bc1]) + b = petsc_assemble_vector_block( + form([L0, L1]), form([[a00, a01], [a10, a11]]), bcs=[bc0, bc1] + ) ksp = PETSc.KSP() ksp.create(mesh.comm) ksp.setOperators(A, P) ksp.setType("minres") pc = ksp.getPC() - pc.setType('lu') + pc.setType("lu") ksp.setTolerances(rtol=1.0e-8, max_it=50) ksp.setFromOptions() x = A.createVecRight() @@ -676,7 +721,7 @@ def monolithic_solve(): ksp.setOperators(A, P) ksp.setType("minres") pc = ksp.getPC() - pc.setType('lu') + pc.setType("lu") def monitor(ksp, its, rnorm): # print("Num it, rnorm:", its, rnorm) @@ -706,9 +751,13 @@ def monitor(ksp, its, rnorm): def test_basic_interior_facet_assembly(): - mesh = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]), np.array([1.0, 1.0])], - [5, 5], cell_type=CellType.triangle, - ghost_mode=GhostMode.shared_facet) + mesh = create_rectangle( + MPI.COMM_WORLD, + [np.array([0.0, 0.0]), np.array([1.0, 1.0])], + [5, 5], + cell_type=CellType.triangle, + ghost_mode=GhostMode.shared_facet, + ) V = functionspace(mesh, ("DG", 1)) u, v = ufl.TrialFunction(V), ufl.TestFunction(V) a = ufl.inner(ufl.avg(u), ufl.avg(v)) * ufl.dS @@ -725,11 +774,14 @@ def test_basic_interior_facet_assembly(): b.destroy() -@pytest.mark.parametrize("mesh", [ - create_unit_square(MPI.COMM_WORLD, 5, 5, ghost_mode=GhostMode.shared_facet), - create_unit_cube(MPI.COMM_WORLD, 5, 5, 5, ghost_mode=GhostMode.shared_facet)]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 5, 5, ghost_mode=GhostMode.shared_facet), + create_unit_cube(MPI.COMM_WORLD, 5, 5, 5, ghost_mode=GhostMode.shared_facet), + ], +) def test_symmetry_interior_facet_assembly(mesh): - def bc(V): facetdim = mesh.topology.dim - 1 bndry_facets = locate_entities_boundary(mesh, facetdim, lambda x: np.isclose(x[0], 0.0)) @@ -774,8 +826,8 @@ def bc(V): n = ufl.FacetNormal(mesh) a00 = inner(u0, v0) * dx a11 = inner(u1, v1) * dx - a01 = inner(ufl.dot(ufl.avg(u1), n('+')), ufl.avg(v0)) * dS - a10 = inner(ufl.avg(u0), ufl.dot(ufl.avg(v1), n('+'))) * dS + a01 = inner(ufl.dot(ufl.avg(u1), n("+")), ufl.avg(v0)) * dS + a10 = inner(ufl.avg(u0), ufl.dot(ufl.avg(v1), n("+"))) * dS a = form([[a00, a01], [a10, a11]]) L0 = inner(ufl.unit_vector(0, mesh.geometry.dim), ufl.avg(v0)) * dS L1 = inner(ufl.unit_matrix(1, 1, mesh.geometry.dim), ufl.avg(v1)) * dS @@ -936,19 +988,19 @@ def test_coefficents_non_constant(): V = functionspace(mesh, ("Lagrange", 3)) # degree 3 so that interpolation is exact u = Function(V) - u.interpolate(lambda x: x[0] * x[1]**2) + u.interpolate(lambda x: x[0] * x[1] ** 2) x = SpatialCoordinate(mesh) v = ufl.TestFunction(V) # -- Volume integral vector - F = form((ufl.inner(u, v) - ufl.inner(x[0] * x[1]**2, v)) * dx) + F = form((ufl.inner(u, v) - ufl.inner(x[0] * x[1] ** 2, v)) * dx) b0 = petsc_assemble_vector(F) b0.assemble() assert np.linalg.norm(b0.array) == pytest.approx(0.0, abs=1.0e-7) # -- Exterior facet integral vector - F = form((ufl.inner(u, v) - ufl.inner(x[0] * x[1]**2, v)) * ds) + F = form((ufl.inner(u, v) - ufl.inner(x[0] * x[1] ** 2, v)) * ds) b0 = petsc_assemble_vector(F) b0.assemble() assert np.linalg.norm(b0.array) == pytest.approx(0.0, abs=1.0e-7) @@ -957,14 +1009,16 @@ def test_coefficents_non_constant(): V = functionspace(mesh, ("DG", 3)) # degree 3 so that interpolation is exact u0 = Function(V) - u0.interpolate(lambda x: x[1]**2) + u0.interpolate(lambda x: x[1] ** 2) u1 = Function(V) u1.interpolate(lambda x: x[0]) x = SpatialCoordinate(mesh) v = ufl.TestFunction(V) - F = (ufl.inner(u1('+') * u0('-'), ufl.avg(v)) - ufl.inner(x[0] * x[1]**2, ufl.avg(v))) * ufl.dS + F = ( + ufl.inner(u1("+") * u0("-"), ufl.avg(v)) - ufl.inner(x[0] * x[1] ** 2, ufl.avg(v)) + ) * ufl.dS F = form(F) b0 = petsc_assemble_vector(F) b0.assemble() @@ -1025,7 +1079,7 @@ def partitioner(comm, nparts, local_graph, num_ghost_nodes): if comm.rank == 0: # Put cells on rank 0 cells = np.array([[0, 1, 2], [0, 2, 3]], dtype=np.int64) - x = np.array([[0., 0.], [1., 0.], [1., 1.], [0., 1.]], dtype=default_real_type) + x = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=default_real_type) else: # No cells on other ranks cells = np.empty((0, 3), dtype=np.int64) @@ -1081,8 +1135,7 @@ def single(): return A def block(): - a = form([[ufl.inner(u, v0) * ufl.dx], - [ufl.inner(u, v1) * ufl.dx]]) + a = form([[ufl.inner(u, v0) * ufl.dx], [ufl.inner(u, v1) * ufl.dx]]) A0 = petsc_assemble_matrix_block(a, bcs=[]) A0.assemble() A1 = petsc_assemble_matrix_nest(a, bcs=[]) diff --git a/python/test/unit/fem/test_bcs.py b/python/test/unit/fem/test_bcs.py index 3287c7c7796..1763b34f465 100644 --- a/python/test/unit/fem/test_bcs.py +++ b/python/test/unit/fem/test_bcs.py @@ -12,9 +12,21 @@ import ufl from basix.ufl import element, mixed_element from dolfinx import default_real_type, default_scalar_type, la -from dolfinx.fem import (Constant, Function, apply_lifting, assemble_matrix, assemble_vector, create_matrix, - create_vector, dirichletbc, form, functionspace, locate_dofs_geometrical, - locate_dofs_topological, set_bc) +from dolfinx.fem import ( + Constant, + Function, + apply_lifting, + assemble_matrix, + assemble_vector, + create_matrix, + create_vector, + dirichletbc, + form, + functionspace, + locate_dofs_geometrical, + locate_dofs_topological, + set_bc, +) from dolfinx.mesh import CellType, create_unit_cube, create_unit_square, locate_entities_boundary from ufl import dx, inner @@ -72,8 +84,10 @@ def test_overlapping_bcs(): # Check only one dof pair is found globally assert len(set(np.concatenate(MPI.COMM_WORLD.allgather(dof_corner)))) == 1 - bcs = [dirichletbc(default_scalar_type(0), dofs_left, V), - dirichletbc(default_scalar_type(123.456), dofs_top, V)] + bcs = [ + dirichletbc(default_scalar_type(0), dofs_left, V), + dirichletbc(default_scalar_type(123.456), dofs_top, V), + ] A, b = create_matrix(a), create_vector(L) assemble_matrix(A, a, bcs=bcs) @@ -105,7 +119,9 @@ def test_constant_bc_constructions(): V2 = functionspace(msh, ("Lagrange", 1, (gdim, gdim))) tdim = msh.topology.dim - boundary_facets = locate_entities_boundary(msh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) + boundary_facets = locate_entities_boundary( + msh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) boundary_dofs0 = locate_dofs_topological(V0, tdim - 1, boundary_facets) boundary_dofs1 = locate_dofs_topological(V1, tdim - 1, boundary_facets) boundary_dofs2 = locate_dofs_topological(V2, tdim - 1, boundary_facets) @@ -125,18 +141,23 @@ def test_constant_bc_constructions(): assert bc1.g.value.shape == (tdim,) assert (bc1.g.value == [dtype(1.0 + 2.2j), dtype(3.0 + 2.2j)]).all() - bc2 = dirichletbc(np.array([[1.0, 3.0], [3.0, -2.0]], dtype=default_real_type), boundary_dofs2, V2) + bc2 = dirichletbc( + np.array([[1.0, 3.0], [3.0, -2.0]], dtype=default_real_type), boundary_dofs2, V2 + ) assert bc2.g.value.dtype == default_real_type assert bc2.g.value.shape == (tdim, tdim) assert (bc2.g.value == [[1.0, 3.0], [3.0, -2.0]]).all() -@pytest.mark.parametrize('mesh_factory', - [(create_unit_square, (MPI.COMM_WORLD, 4, 4)), - (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)) - ]) +@pytest.mark.parametrize( + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 4, 4)), + (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)), + ], +) def test_constant_bc(mesh_factory): """Test that setting a dirichletbc with a constant yields the same result as setting it with a function""" @@ -145,7 +166,9 @@ def test_constant_bc(mesh_factory): V = functionspace(mesh, ("Lagrange", 1)) c = default_scalar_type(2) tdim = mesh.topology.dim - boundary_facets = locate_entities_boundary(mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) + boundary_facets = locate_entities_boundary( + mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) boundary_dofs = locate_dofs_topological(V, tdim - 1, boundary_facets) @@ -164,11 +187,14 @@ def test_constant_bc(mesh_factory): @pytest.mark.parametrize( - 'mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 4, 4)), - (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)) - ]) + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 4, 4)), + (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)), + ], +) def test_vector_constant_bc(mesh_factory): """Test that setting a dirichletbc with a vector valued constant yields the same result as setting it with a function""" @@ -179,12 +205,16 @@ def test_vector_constant_bc(mesh_factory): V = functionspace(mesh, ("Lagrange", 1, (gdim,))) assert V.num_sub_spaces == gdim c = np.arange(1, mesh.geometry.dim + 1, dtype=default_scalar_type) - boundary_facets = locate_entities_boundary(mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) + boundary_facets = locate_entities_boundary( + mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) # Set using sub-functions Vs = [V.sub(i).collapse()[0] for i in range(V.num_sub_spaces)] - boundary_dofs = [locate_dofs_topological((V.sub(i), Vs[i]), tdim - 1, boundary_facets) - for i in range(len(Vs))] + boundary_dofs = [ + locate_dofs_topological((V.sub(i), Vs[i]), tdim - 1, boundary_facets) + for i in range(len(Vs)) + ] u_bcs = [Function(Vs[i]) for i in range(len(Vs))] bcs_f = [] for i, u in enumerate(u_bcs): @@ -204,12 +234,14 @@ def test_vector_constant_bc(mesh_factory): @pytest.mark.parametrize( - 'mesh_factory', [ + "mesh_factory", + [ (create_unit_square, (MPI.COMM_WORLD, 4, 4)), (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)) - ]) + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)), + ], +) def test_sub_constant_bc(mesh_factory): """Test that setting a dirichletbc with on a component of a vector valued function yields the same result as setting it with a @@ -220,7 +252,9 @@ def test_sub_constant_bc(mesh_factory): V = functionspace(mesh, ("Lagrange", 1, (gdim,))) c = Constant(mesh, default_scalar_type(3.14)) tdim = mesh.topology.dim - boundary_facets = locate_entities_boundary(mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) + boundary_facets = locate_entities_boundary( + mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) for i in range(V.num_sub_spaces): Vi = V.sub(i).collapse()[0] @@ -240,20 +274,26 @@ def test_sub_constant_bc(mesh_factory): @pytest.mark.parametrize( - 'mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 4, 4)), - (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron))]) + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 4, 4)), + (create_unit_square, (MPI.COMM_WORLD, 8, 8, CellType.quadrilateral)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3)), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron)), + ], +) def test_mixed_constant_bc(mesh_factory): """Test that setting a dirichletbc with on a component of a mixed function yields the same result as setting it with a function""" func, args = mesh_factory mesh = func(*args) tdim = mesh.topology.dim - boundary_facets = locate_entities_boundary(mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) - TH = mixed_element([ - element("Lagrange", mesh.basix_cell(), 1), - element("Lagrange", mesh.basix_cell(), 2)]) + boundary_facets = locate_entities_boundary( + mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) + TH = mixed_element( + [element("Lagrange", mesh.basix_cell(), 1), element("Lagrange", mesh.basix_cell(), 2)] + ) W = functionspace(mesh, TH) u = Function(W) @@ -272,7 +312,9 @@ def test_mixed_constant_bc(mesh_factory): # Apply BC to scalar component of a mixed space using a Function ubc = u.sub(i).collapse() ubc.interpolate(lambda x: np.full(x.shape[1], bc_val)) - dofs_both = locate_dofs_topological((W.sub(i), ubc.function_space), tdim - 1, boundary_facets) + dofs_both = locate_dofs_topological( + (W.sub(i), ubc.function_space), tdim - 1, boundary_facets + ) bc_func = dirichletbc(ubc, dofs_both, W.sub(i)) set_bc(u_func.x.array, [bc_func]) @@ -285,10 +327,16 @@ def test_mixed_blocked_constant(): Dirichlet BC based on a vector valued Constant.""" mesh = create_unit_square(MPI.COMM_WORLD, 4, 4) tdim = mesh.topology.dim - boundary_facets = locate_entities_boundary(mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool)) - - TH = mixed_element([element("Lagrange", mesh.basix_cell(), 1), - element("Lagrange", mesh.basix_cell(), 2, shape=(mesh.geometry.dim,))]) + boundary_facets = locate_entities_boundary( + mesh, tdim - 1, lambda x: np.ones(x.shape[1], dtype=bool) + ) + + TH = mixed_element( + [ + element("Lagrange", mesh.basix_cell(), 1), + element("Lagrange", mesh.basix_cell(), 2, shape=(mesh.geometry.dim,)), + ] + ) W = functionspace(mesh, TH) u = Function(W) c0 = default_scalar_type(3) diff --git a/python/test/unit/fem/test_complex_assembler.py b/python/test/unit/fem/test_complex_assembler.py index 6600ed79606..3a672725152 100644 --- a/python/test/unit/fem/test_complex_assembler.py +++ b/python/test/unit/fem/test_complex_assembler.py @@ -106,6 +106,7 @@ def test_complex_assembly_solve(complex_dtype, cg_solver): # Reference Solution def ref_eval(x): return np.cos(2 * np.pi * x[0]) * np.cos(2 * np.pi * x[1]) + u_ref = Function(V, dtype=real_dtype) u_ref.interpolate(ref_eval) diff --git a/python/test/unit/fem/test_custom_assembler.py b/python/test/unit/fem/test_custom_assembler.py index 6e05e21b954..f3314ec5222 100644 --- a/python/test/unit/fem/test_custom_assembler.py +++ b/python/test/unit/fem/test_custom_assembler.py @@ -49,7 +49,9 @@ def set_vals_numba(A, m, rows, n, cols, data, mode): @numba.njit def set_vals_cffi(A, m, rows, n, cols, data, mode): - MatSetValuesLocal_abi(A, m, ffi.from_buffer(rows), n, ffi.from_buffer(cols), ffi.from_buffer(data), mode) + MatSetValuesLocal_abi( + A, m, ffi.from_buffer(rows), n, ffi.from_buffer(cols), ffi.from_buffer(data), mode + ) @numba.njit @@ -71,27 +73,35 @@ def get_matsetvalues_cffi_api(): else: raise RuntimeError("Could not find DOLFINx pkgconfig file") - cffi_support.register_type(ffi.typeof('float _Complex'), numba.types.complex64) - cffi_support.register_type(ffi.typeof('double _Complex'), numba.types.complex128) + cffi_support.register_type(ffi.typeof("float _Complex"), numba.types.complex64) + cffi_support.register_type(ffi.typeof("double _Complex"), numba.types.complex128) - petsc_dir = PETSc_get_config()['PETSC_DIR'] + petsc_dir = PETSc_get_config()["PETSC_DIR"] petsc_arch = petsc4py.lib.getPathArchPETSc()[1] - worker = os.getenv('PYTEST_XDIST_WORKER', None) + worker = os.getenv("PYTEST_XDIST_WORKER", None) module_name = f"_petsc_cffi_{worker}" if MPI.COMM_WORLD.Get_rank() == 0: ffibuilder = cffi.FFI() - ffibuilder.cdef("""typedef int... PetscInt; + ffibuilder.cdef( + """typedef int... PetscInt; typedef ... PetscScalar; typedef int... InsertMode; int MatSetValuesLocal(void* mat, PetscInt nrow, const PetscInt* irow, - PetscInt ncol, const PetscInt* icol, const PetscScalar* y, InsertMode addv);""") - ffibuilder.set_source(module_name, '#include "petscmat.h"', - libraries=['petsc'], - include_dirs=[os.path.join(petsc_dir, petsc_arch, 'include'), - os.path.join(petsc_dir, 'include')] + dolfinx_pc["include_dirs"], - library_dirs=[os.path.join(petsc_dir, petsc_arch, 'lib')], - extra_compile_args=[]) + PetscInt ncol, const PetscInt* icol, const PetscScalar* y, InsertMode addv);""" + ) + ffibuilder.set_source( + module_name, + '#include "petscmat.h"', + libraries=["petsc"], + include_dirs=[ + os.path.join(petsc_dir, petsc_arch, "include"), + os.path.join(petsc_dir, "include"), + ] + + dolfinx_pc["include_dirs"], + library_dirs=[os.path.join(petsc_dir, petsc_arch, "lib")], + extra_compile_args=[], + ) # Build module in same directory as test file path = pathlib.Path(__file__).parent.absolute() @@ -116,9 +126,9 @@ def sink(*args): @numba.njit(fastmath=True) def area(x0, x1, x2) -> float: """Compute the area of a triangle embedded in 2D from the three vertices""" - a = (x1[0] - x2[0])**2 + (x1[1] - x2[1])**2 - b = (x0[0] - x2[0])**2 + (x0[1] - x2[1])**2 - c = (x0[0] - x1[0])**2 + (x0[1] - x1[1])**2 + a = (x1[0] - x2[0]) ** 2 + (x1[1] - x2[1]) ** 2 + b = (x0[0] - x2[0]) ** 2 + (x0[1] - x2[1]) ** 2 + c = (x0[0] - x1[0]) ** 2 + (x0[1] - x1[1]) ** 2 return math.sqrt(2 * (a * b + a * c + b * c) - (a**2 + b**2 + c**2)) / 4.0 @@ -171,10 +181,14 @@ def assemble_vector_ufc(b, kernel, mesh, dofmap, num_cells, dtype): for j in range(3): geometry[j] = x[v[cell, j], :] b_local.fill(0.0) - kernel(ffi.from_buffer(b_local), ffi.from_buffer(coeffs), - ffi.from_buffer(constants), - ffi.from_buffer(geometry), ffi.from_buffer(entity_local_index), - ffi.from_buffer(perm)) + kernel( + ffi.from_buffer(b_local), + ffi.from_buffer(coeffs), + ffi.from_buffer(constants), + ffi.from_buffer(geometry), + ffi.from_buffer(entity_local_index), + ffi.from_buffer(perm), + ) for j in range(3): b[dofmap[cell, j]] += b_local[j] @@ -231,7 +245,7 @@ def test_custom_mesh_loop_rank1(dtype): end = time.time() print(f"Time (numba, pass {i}): {end - start}") b0.x.scatter_reverse(dolfinx.la.InsertMode.add) - b0sum = np.sum(b0.x.array[:b0.x.index_map.size_local * b0.x.block_size]) + b0sum = np.sum(b0.x.array[: b0.x.index_map.size_local * b0.x.block_size]) assert mesh.comm.allreduce(b0sum, op=MPI.SUM) == pytest.approx(1.0) # NOTE: Parallel (threaded) Numba can cause problems with MPI @@ -270,7 +284,9 @@ def test_custom_mesh_loop_rank1(dtype): # Assemble using generated tabulate_tensor kernel and Numba # assembler b3 = Function(V, dtype=dtype) - ufcx_form, module, code = dolfinx.jit.ffcx_jit(mesh.comm, L, form_compiler_options={"scalar_type": dtype}) + ufcx_form, module, code = dolfinx.jit.ffcx_jit( + mesh.comm, L, form_compiler_options={"scalar_type": dtype} + ) # Get the one and only kernel kernel = getattr(ufcx_form.form_integrals[0], f"tabulate_tensor_{np.dtype(dtype).name}") @@ -285,12 +301,14 @@ def test_custom_mesh_loop_rank1(dtype): assert np.linalg.norm(b3.x.array - b0.x.array) == pytest.approx(0.0, abs=1e-8) -@pytest.mark.parametrize("set_vals,backend", - [ - (set_vals_numba, "numba"), - (set_vals_ctypes, "ctypes"), - (set_vals_cffi, "cffi_abi"), - ]) +@pytest.mark.parametrize( + "set_vals,backend", + [ + (set_vals_numba, "numba"), + (set_vals_ctypes, "ctypes"), + (set_vals_cffi, "cffi_abi"), + ], +) def test_custom_mesh_loop_petsc_rank2(set_vals, backend): """Test numba assembler for a bilinear form.""" @@ -320,7 +338,9 @@ def test_custom_mesh_loop_petsc_rank2(set_vals, backend): for i in range(2): A1.zeroEntries() start = time.time() - assemble_petsc_matrix(A1.handle, (x_dofs, x), dofmap, num_owned_cells, set_vals, PETSc.InsertMode.ADD_VALUES) + assemble_petsc_matrix( + A1.handle, (x_dofs, x), dofmap, num_owned_cells, set_vals, PETSc.InsertMode.ADD_VALUES + ) end = time.time() print(f"Time (Numba/{backend}, pass {i}): {end - start}") A1.assemble() diff --git a/python/test/unit/fem/test_custom_basix_element.py b/python/test/unit/fem/test_custom_basix_element.py index 0559ed9eaa2..5b8ac322936 100644 --- a/python/test/unit/fem/test_custom_basix_element.py +++ b/python/test/unit/fem/test_custom_basix_element.py @@ -7,7 +7,14 @@ import basix import basix.ufl import ufl -from dolfinx.fem import Function, assemble_scalar, dirichletbc, form, functionspace, locate_dofs_topological +from dolfinx.fem import ( + Function, + assemble_scalar, + dirichletbc, + form, + functionspace, + locate_dofs_topological, +) from dolfinx.fem.petsc import apply_lifting, assemble_matrix, assemble_vector, set_bc from dolfinx.mesh import CellType, create_unit_cube, create_unit_square, exterior_facet_indices from ufl import SpatialCoordinate, TestFunction, TrialFunction, div, dx, grad, inner @@ -20,21 +27,25 @@ def run_scalar_test(V, degree): # Get quadrature degree for bilinear form integrand (ignores effect of non-affine map) a = inner(grad(u), grad(v)) * dx(metadata={"quadrature_degree": -1}) - a.integrals()[0].metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(a) + a.integrals()[0].metadata()[ + "quadrature_degree" + ] = ufl.algorithms.estimate_total_polynomial_degree(a) a = form(a) # Source term x = SpatialCoordinate(mesh) - u_exact = x[1]**degree - f = - div(grad(u_exact)) + u_exact = x[1] ** degree + f = -div(grad(u_exact)) # Set quadrature degree for linear form integrand (ignores effect of non-affine map) L = inner(f, v) * dx(metadata={"quadrature_degree": -1}) - L.integrals()[0].metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(L) + L.integrals()[0].metadata()[ + "quadrature_degree" + ] = ufl.algorithms.estimate_total_polynomial_degree(L) L = form(L) u_bc = Function(V) - u_bc.interpolate(lambda x: x[1]**degree) + u_bc.interpolate(lambda x: x[1] ** degree) # Create Dirichlet boundary condition facetdim = mesh.topology.dim - 1 @@ -62,7 +73,7 @@ def run_scalar_test(V, degree): solver.solve(b, uh.vector) uh.x.scatter_forward() - M = (u_exact - uh)**2 * dx + M = (u_exact - uh) ** 2 * dx M = form(M) error = mesh.comm.allreduce(assemble_scalar(M), op=MPI.SUM) assert np.abs(error) < 1.0e-6 @@ -75,7 +86,8 @@ def run_scalar_test(V, degree): @pytest.mark.parametrize("degree", range(1, 6)) def test_basix_element_wrapper(degree): ufl_element = basix.ufl.element( - basix.ElementFamily.P, basix.CellType.triangle, degree, basix.LagrangeVariant.gll_isaac) + basix.ElementFamily.P, basix.CellType.triangle, degree, basix.LagrangeVariant.gll_isaac + ) mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) V = functionspace(mesh, ufl_element) run_scalar_test(V, degree) @@ -84,13 +96,27 @@ def test_basix_element_wrapper(degree): def test_custom_element_triangle_degree1(): wcoeffs = np.eye(3) z = np.zeros((0, 2)) - x = [[np.array([[0., 0.]]), np.array([[1., 0.]]), np.array([[0., 1.]])], - [z, z, z], [z], []] + x = [ + [np.array([[0.0, 0.0]]), np.array([[1.0, 0.0]]), np.array([[0.0, 1.0]])], + [z, z, z], + [z], + [], + ] z = np.zeros((0, 1, 0, 1)) - M = [[np.array([[[[1.]]]]), np.array([[[[1.]]]]), np.array([[[[1.]]]])], - [z, z, z], [z], []] - ufl_element = basix.ufl.custom_element(basix.CellType.triangle, [], wcoeffs, - x, M, 0, basix.MapType.identity, basix.SobolevSpace.H1, False, 1, 1) + M = [[np.array([[[[1.0]]]]), np.array([[[[1.0]]]]), np.array([[[[1.0]]]])], [z, z, z], [z], []] + ufl_element = basix.ufl.custom_element( + basix.CellType.triangle, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 1, + 1, + ) mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) V = functionspace(mesh, ufl_element) run_scalar_test(V, 1) @@ -98,16 +124,37 @@ def test_custom_element_triangle_degree1(): def test_custom_element_triangle_degree4(): wcoeffs = np.eye(15) - x = [[np.array([[0., 0.]]), np.array([[1., 0.]]), np.array([[0., 1.]])], - [np.array([[.75, .25], [.5, .5], [.25, .75]]), np.array([[0., .25], [0., .5], [0., .75]]), - np.array([[.25, 0.], [.5, 0.], [.75, 0.]])], - [np.array([[.25, .25], [.5, .25], [.25, .5]])], []] - id = np.array([[[[1.], [0.], [0.]]], [[[0.], [1.], [0.]]], [[[0.], [0.], [1.]]]]) - M = [[np.array([[[[1.]]]]), np.array([[[[1.]]]]), np.array([[[[1.]]]])], - [id, id, id], [id], []] - - ufl_element = basix.ufl.custom_element(basix.CellType.triangle, [], wcoeffs, - x, M, 0, basix.MapType.identity, basix.SobolevSpace.H1, False, 4, 4) + x = [ + [np.array([[0.0, 0.0]]), np.array([[1.0, 0.0]]), np.array([[0.0, 1.0]])], + [ + np.array([[0.75, 0.25], [0.5, 0.5], [0.25, 0.75]]), + np.array([[0.0, 0.25], [0.0, 0.5], [0.0, 0.75]]), + np.array([[0.25, 0.0], [0.5, 0.0], [0.75, 0.0]]), + ], + [np.array([[0.25, 0.25], [0.5, 0.25], [0.25, 0.5]])], + [], + ] + id = np.array([[[[1.0], [0.0], [0.0]]], [[[0.0], [1.0], [0.0]]], [[[0.0], [0.0], [1.0]]]]) + M = [ + [np.array([[[[1.0]]]]), np.array([[[[1.0]]]]), np.array([[[[1.0]]]])], + [id, id, id], + [id], + [], + ] + + ufl_element = basix.ufl.custom_element( + basix.CellType.triangle, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 4, + 4, + ) mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) V = functionspace(mesh, ufl_element) run_scalar_test(V, 4) @@ -115,13 +162,20 @@ def test_custom_element_triangle_degree4(): def test_custom_element_triangle_degree4_integral(): pts, wts = basix.make_quadrature(basix.CellType.interval, 10) - tab = basix.create_element(basix.ElementFamily.P, basix.CellType.interval, 2).tabulate(0, pts)[0, :, :, 0] + tab = basix.create_element(basix.ElementFamily.P, basix.CellType.interval, 2).tabulate(0, pts)[ + 0, :, :, 0 + ] wcoeffs = np.eye(15) - x = [[np.array([[0., 0.]]), np.array([[1., 0.]]), np.array([[0., 1.]])], - [np.array([[1. - p[0], p[0]] for p in pts]), - np.array([[0., p[0]] for p in pts]), - np.array([[p[0], 0.] for p in pts])], - [np.array([[.25, .25], [.5, .25], [.25, .5]])], []] + x = [ + [np.array([[0.0, 0.0]]), np.array([[1.0, 0.0]]), np.array([[0.0, 1.0]])], + [ + np.array([[1.0 - p[0], p[0]] for p in pts]), + np.array([[0.0, p[0]] for p in pts]), + np.array([[p[0], 0.0] for p in pts]), + ], + [np.array([[0.25, 0.25], [0.5, 0.25], [0.25, 0.5]])], + [], + ] assert pts.shape[0] != 3 quadrature_mat = np.zeros([3, 1, pts.shape[0], 1]) @@ -129,12 +183,26 @@ def test_custom_element_triangle_degree4_integral(): for p in range(pts.shape[0]): quadrature_mat[dof, 0, p, 0] = wts[p] * tab[p, dof] - M = [[np.array([[[[1.]]]]), np.array([[[[1.]]]]), np.array([[[[1.]]]])], - [quadrature_mat, quadrature_mat, quadrature_mat], - [np.array([[[[1.], [0.], [0.]]], [[[0.], [1.], [0.]]], [[[0.], [0.], [1.]]]])], []] - - ufl_element = basix.ufl.custom_element(basix.CellType.triangle, [], wcoeffs, - x, M, 0, basix.MapType.identity, basix.SobolevSpace.H1, False, 4, 4) + M = [ + [np.array([[[[1.0]]]]), np.array([[[[1.0]]]]), np.array([[[[1.0]]]])], + [quadrature_mat, quadrature_mat, quadrature_mat], + [np.array([[[[1.0], [0.0], [0.0]]], [[[0.0], [1.0], [0.0]]], [[[0.0], [0.0], [1.0]]]])], + [], + ] + + ufl_element = basix.ufl.custom_element( + basix.CellType.triangle, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 4, + 4, + ) mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) V = functionspace(mesh, ufl_element) run_scalar_test(V, 4) @@ -143,22 +211,60 @@ def test_custom_element_triangle_degree4_integral(): def test_custom_element_quadrilateral_degree1(): wcoeffs = np.eye(4) z = np.zeros((0, 2)) - x = [[np.array([[0., 0.]]), np.array([[1., 0.]]), np.array([[0., 1.]]), np.array([[1., 1.]])], - [z, z, z, z], [z], []] + x = [ + [ + np.array([[0.0, 0.0]]), + np.array([[1.0, 0.0]]), + np.array([[0.0, 1.0]]), + np.array([[1.0, 1.0]]), + ], + [z, z, z, z], + [z], + [], + ] z = np.zeros((0, 1, 0, 1)) - M = [[np.array([[[[1.]]]]), np.array([[[[1.]]]]), np.array( - [[[[1.]]]]), np.array([[[[1.]]]])], [z, z, z, z], [z], []] - ufl_element = basix.ufl.custom_element(basix.CellType.quadrilateral, [], wcoeffs, - x, M, 0, basix.MapType.identity, basix.SobolevSpace.H1, False, 1, 1) + M = [ + [ + np.array([[[[1.0]]]]), + np.array([[[[1.0]]]]), + np.array([[[[1.0]]]]), + np.array([[[[1.0]]]]), + ], + [z, z, z, z], + [z], + [], + ] + ufl_element = basix.ufl.custom_element( + basix.CellType.quadrilateral, + [], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 1, + 1, + ) mesh = create_unit_square(MPI.COMM_WORLD, 10, 10, CellType.quadrilateral) V = functionspace(mesh, ufl_element) run_scalar_test(V, 1) -@pytest.mark.parametrize("cell_type", [ - CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron]) -@pytest.mark.parametrize("element_family", [ - basix.ElementFamily.N1E, basix.ElementFamily.N2E, basix.ElementFamily.RT, basix.ElementFamily.BDM]) +@pytest.mark.parametrize( + "cell_type", + [CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron], +) +@pytest.mark.parametrize( + "element_family", + [ + basix.ElementFamily.N1E, + basix.ElementFamily.N2E, + basix.ElementFamily.RT, + basix.ElementFamily.BDM, + ], +) def test_vector_copy_degree1(cell_type, element_family): if cell_type in [CellType.triangle, CellType.quadrilateral]: tdim = 2 @@ -170,13 +276,21 @@ def test_vector_copy_degree1(cell_type, element_family): def func(x): return x[:tdim] - e1 = basix.ufl.element( - element_family, getattr(basix.CellType, cell_type.name), 1) + e1 = basix.ufl.element(element_family, getattr(basix.CellType, cell_type.name), 1) e2 = basix.ufl.custom_element( - e1._element.cell_type, e1._element.value_shape, e1._element.wcoeffs, e1._element.x, - e1._element.M, 0, e1._element.map_type, e1._element.sobolev_space, - e1._element.discontinuous, e1._element.embedded_subdegree, e1._element.embedded_superdegree) + e1._element.cell_type, + e1._element.value_shape, + e1._element.wcoeffs, + e1._element.x, + e1._element.M, + 0, + e1._element.map_type, + e1._element.sobolev_space, + e1._element.discontinuous, + e1._element.embedded_subdegree, + e1._element.embedded_superdegree, + ) space1 = functionspace(mesh, e1) space2 = functionspace(mesh, e2) @@ -191,13 +305,15 @@ def func(x): assert np.isclose(error, 0) -@pytest.mark.parametrize("cell_type", [ - CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron]) -@pytest.mark.parametrize("element_family", [ - basix.ElementFamily.P, basix.ElementFamily.serendipity]) +@pytest.mark.parametrize( + "cell_type", + [CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron], +) +@pytest.mark.parametrize("element_family", [basix.ElementFamily.P, basix.ElementFamily.serendipity]) def test_scalar_copy_degree1(cell_type, element_family): if element_family == basix.ElementFamily.serendipity and cell_type in [ - CellType.triangle, CellType.tetrahedron + CellType.triangle, + CellType.tetrahedron, ]: pytest.xfail("Serendipity elements cannot be created on simplices") @@ -209,12 +325,20 @@ def test_scalar_copy_degree1(cell_type, element_family): def func(x): return x[0] - e1 = basix.ufl.element( - element_family, getattr(basix.CellType, cell_type.name), 1) + e1 = basix.ufl.element(element_family, getattr(basix.CellType, cell_type.name), 1) e2 = basix.ufl.custom_element( - e1._element.cell_type, e1._element.value_shape, e1._element.wcoeffs, e1._element.x, - e1._element.M, 0, e1._element.map_type, e1._element.sobolev_space, - e1._element.discontinuous, e1._element.embedded_subdegree, e1._element.embedded_superdegree) + e1._element.cell_type, + e1._element.value_shape, + e1._element.wcoeffs, + e1._element.x, + e1._element.M, + 0, + e1._element.map_type, + e1._element.sobolev_space, + e1._element.discontinuous, + e1._element.embedded_subdegree, + e1._element.embedded_superdegree, + ) space1 = functionspace(mesh, e1) space2 = functionspace(mesh, e2) diff --git a/python/test/unit/fem/test_custom_jit_kernels.py b/python/test/unit/fem/test_custom_jit_kernels.py index edccce2f42b..7675d8b3369 100644 --- a/python/test/unit/fem/test_custom_jit_kernels.py +++ b/python/test/unit/fem/test_custom_jit_kernels.py @@ -17,9 +17,8 @@ import dolfinx import dolfinx.utils import ffcx.codegeneration.utils -from dolfinx import TimingType +from dolfinx import TimingType, fem, la, list_timings from dolfinx import cpp as _cpp -from dolfinx import fem, la, list_timings from dolfinx.fem import Form, Function, IntegralType, form_cpp_class, functionspace from dolfinx.mesh import create_unit_square @@ -42,8 +41,11 @@ def tabulate(A_, w_, c_, coords_, entity_local_index, cell_orientation): # 2x Element area Ae Ae = abs((x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1)) - B = np.array([y1 - y2, y2 - y0, y0 - y1, x2 - x1, x0 - x2, x1 - x0], dtype=dtype).reshape(2, 3) + B = np.array([y1 - y2, y2 - y0, y0 - y1, x2 - x1, x0 - x2, x1 - x0], dtype=dtype).reshape( + 2, 3 + ) A[:, :] = np.dot(B.T, B) / (2 * Ae) + return tabulate @@ -59,6 +61,7 @@ def tabulate(b_, w_, c_, coords_, local_index, orientation): # 2x Element area Ae Ae = abs((x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1)) b[:] = Ae / 6.0 + return tabulate @@ -75,6 +78,7 @@ def tabulate(b_, w_, c_, coords_, local_index, orientation): # 2x Element area Ae Ae = abs((x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1)) b[:] = w[0] * Ae / 6.0 + return tabulate @@ -86,9 +90,13 @@ def test_numba_assembly(dtype): mesh = create_unit_square(MPI.COMM_WORLD, 13, 13, dtype=xdtype) V = functionspace(mesh, ("Lagrange", 1)) cells = np.arange(mesh.topology.index_map(mesh.topology.dim).size_local, dtype=np.int32) - integrals = {IntegralType.cell: [(-1, k2.address, cells), - (12, k2.address, np.arange(0)), - (2, k2.address, np.arange(0))]} + integrals = { + IntegralType.cell: [ + (-1, k2.address, cells), + (12, k2.address, np.arange(0)), + (2, k2.address, np.arange(0)), + ] + } formtype = form_cpp_class(dtype) a = Form(formtype([V._cpp_object, V._cpp_object], integrals, [], [], False, None)) integrals = {IntegralType.cell: [(-1, k1.address, cells)]} @@ -116,7 +124,7 @@ def test_coefficient(dtype): V = functionspace(mesh, ("Lagrange", 1)) DG0 = functionspace(mesh, ("DG", 0)) vals = Function(DG0, dtype=dtype) - vals.x.array[:vals.x.index_map.size_local] = 2 + vals.x.array[: vals.x.index_map.size_local] = 2 tdim = mesh.topology.dim num_cells = mesh.topology.index_map(tdim).size_local + mesh.topology.index_map(tdim).num_ghosts @@ -137,8 +145,11 @@ def test_cffi_assembly(): V = functionspace(mesh, ("Lagrange", 1)) if mesh.comm.rank == 0: from cffi import FFI + ffibuilder = FFI() - ffibuilder.set_source("_cffi_kernelA", r""" + ffibuilder.set_source( + "_cffi_kernelA", + r""" #include void tabulate_tensor_poissonA(double* restrict A, const double* w, const double* c, @@ -214,8 +225,10 @@ def test_cffi_assembly(): A[1] = 0.1666666666666667 * sp[3]; A[2] = 0.1666666666666667 * sp[3]; } - """) - ffibuilder.cdef(""" + """, + ) + ffibuilder.cdef( + """ void tabulate_tensor_poissonA(double* restrict A, const double* w, const double* c, const double* restrict coordinate_dofs, @@ -226,7 +239,8 @@ def test_cffi_assembly(): const double* restrict coordinate_dofs, const int* entity_local_index, const int* cell_orientation); - """) + """ + ) ffibuilder.compile(verbose=True) diff --git a/python/test/unit/fem/test_discrete_operators.py b/python/test/unit/fem/test_discrete_operators.py index 44f29fc54ba..0813853f845 100644 --- a/python/test/unit/fem/test_discrete_operators.py +++ b/python/test/unit/fem/test_discrete_operators.py @@ -18,14 +18,19 @@ from dolfinx.mesh import CellType, GhostMode, create_unit_cube, create_unit_square -@pytest.mark.parametrize("mesh", [create_unit_square(MPI.COMM_WORLD, 11, 6, - ghost_mode=GhostMode.none, dtype=np.float32), - create_unit_square(MPI.COMM_WORLD, 11, 6, - ghost_mode=GhostMode.shared_facet, dtype=np.float64), - create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, - ghost_mode=GhostMode.none, dtype=np.float64), - create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, - ghost_mode=GhostMode.shared_facet, dtype=np.float32)]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none, dtype=np.float32), + create_unit_square( + MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.shared_facet, dtype=np.float64 + ), + create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.none, dtype=np.float64), + create_unit_cube( + MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.shared_facet, dtype=np.float32 + ), + ], +) def test_gradient(mesh): """Test discrete gradient computation for lowest order elements.""" V = functionspace(mesh, ("Lagrange", 1)) @@ -43,24 +48,111 @@ def test_gradient(mesh): @pytest.mark.parametrize("p", range(1, 4)) @pytest.mark.parametrize("q", range(1, 4)) -@pytest.mark.parametrize("cell_type", [ - (create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none, - cell_type=CellType.triangle, dtype=np.float32), "Lagrange", "Nedelec 1st kind H(curl)"), - (create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none, - cell_type=CellType.triangle, dtype=np.float64), "Lagrange", "Nedelec 1st kind H(curl)"), - (create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none, - cell_type=CellType.quadrilateral, dtype=np.float32), "Q", "RTCE"), - (create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none, - cell_type=CellType.quadrilateral, dtype=np.float64), "Q", "RTCE"), - (create_unit_cube(MPI.COMM_WORLD, 3, 3, 2, ghost_mode=GhostMode.none, - cell_type=CellType.tetrahedron, dtype=np.float32), "Lagrange", "Nedelec 1st kind H(curl)"), - (create_unit_cube(MPI.COMM_WORLD, 3, 3, 2, ghost_mode=GhostMode.none, - cell_type=CellType.tetrahedron, dtype=np.float64), "Lagrange", "Nedelec 1st kind H(curl)"), - (create_unit_cube(MPI.COMM_WORLD, 3, 3, 2, ghost_mode=GhostMode.none, - cell_type=CellType.hexahedron, dtype=np.float32), "Q", "NCE"), - (create_unit_cube(MPI.COMM_WORLD, 3, 2, 2, ghost_mode=GhostMode.none, - cell_type=CellType.hexahedron, dtype=np.float64), "Q", "NCE") -]) +@pytest.mark.parametrize( + "cell_type", + [ + ( + create_unit_square( + MPI.COMM_WORLD, + 11, + 6, + ghost_mode=GhostMode.none, + cell_type=CellType.triangle, + dtype=np.float32, + ), + "Lagrange", + "Nedelec 1st kind H(curl)", + ), + ( + create_unit_square( + MPI.COMM_WORLD, + 11, + 6, + ghost_mode=GhostMode.none, + cell_type=CellType.triangle, + dtype=np.float64, + ), + "Lagrange", + "Nedelec 1st kind H(curl)", + ), + ( + create_unit_square( + MPI.COMM_WORLD, + 11, + 6, + ghost_mode=GhostMode.none, + cell_type=CellType.quadrilateral, + dtype=np.float32, + ), + "Q", + "RTCE", + ), + ( + create_unit_square( + MPI.COMM_WORLD, + 11, + 6, + ghost_mode=GhostMode.none, + cell_type=CellType.quadrilateral, + dtype=np.float64, + ), + "Q", + "RTCE", + ), + ( + create_unit_cube( + MPI.COMM_WORLD, + 3, + 3, + 2, + ghost_mode=GhostMode.none, + cell_type=CellType.tetrahedron, + dtype=np.float32, + ), + "Lagrange", + "Nedelec 1st kind H(curl)", + ), + ( + create_unit_cube( + MPI.COMM_WORLD, + 3, + 3, + 2, + ghost_mode=GhostMode.none, + cell_type=CellType.tetrahedron, + dtype=np.float64, + ), + "Lagrange", + "Nedelec 1st kind H(curl)", + ), + ( + create_unit_cube( + MPI.COMM_WORLD, + 3, + 3, + 2, + ghost_mode=GhostMode.none, + cell_type=CellType.hexahedron, + dtype=np.float32, + ), + "Q", + "NCE", + ), + ( + create_unit_cube( + MPI.COMM_WORLD, + 3, + 2, + 2, + ghost_mode=GhostMode.none, + cell_type=CellType.hexahedron, + dtype=np.float64, + ), + "Q", + "NCE", + ), + ], +) def test_gradient_interpolation(cell_type, p, q): """Test discrete gradient computation with verification using Expression.""" mesh, family0, family1 = cell_type @@ -75,7 +167,7 @@ def test_gradient_interpolation(cell_type, p, q): # Vector for 'u' needs additional ghosts defined in columns of G uvec = dolfinx.la.vector(G.index_map(1), dtype=dtype) u = Function(V, uvec, dtype=dtype) - u.interpolate(lambda x: 2 * x[0]**p + 3 * x[1]**p) + u.interpolate(lambda x: 2 * x[0] ** p + 3 * x[1] ** p) grad_u = Expression(ufl.grad(u), W.element.interpolation_points(), dtype=dtype) w_expr = Function(W, dtype=dtype) @@ -87,7 +179,9 @@ def test_gradient_interpolation(cell_type, p, q): # Get the local part of G (no ghost rows) nrlocal = G.index_map(0).size_local nnzlocal = G.indptr[nrlocal] - Glocal = scipy.sparse.csr_matrix((G.data[:nnzlocal], G.indices[:nnzlocal], G.indptr[:nrlocal + 1])) + Glocal = scipy.sparse.csr_matrix( + (G.data[:nnzlocal], G.indices[:nnzlocal], G.indptr[: nrlocal + 1]) + ) # MatVec w.x.array[:nrlocal] = Glocal @ u.x.array diff --git a/python/test/unit/fem/test_dof_permuting.py b/python/test/unit/fem/test_dof_permuting.py index 5d913de2116..c12cdf3f2a0 100644 --- a/python/test/unit/fem/test_dof_permuting.py +++ b/python/test/unit/fem/test_dof_permuting.py @@ -28,15 +28,16 @@ def randomly_ordered_mesh(cell_type): elif cell_type == "tetrahedron" or cell_type == "hexahedron": gdim = 3 - domain = ufl.Mesh(element("Lagrange", cell_type, 1, shape=(gdim,), - dtype=default_real_type)) + domain = ufl.Mesh(element("Lagrange", cell_type, 1, shape=(gdim,), dtype=default_real_type)) # Create a mesh if MPI.COMM_WORLD.rank == 0: N = 6 if cell_type == "triangle" or cell_type == "quadrilateral": temp_points = np.array([[x / 2, y / 2] for y in range(N) for x in range(N)]) elif cell_type == "tetrahedron" or cell_type == "hexahedron": - temp_points = np.array([[x / 2, y / 2, z / 2] for z in range(N) for y in range(N) for x in range(N)]) + temp_points = np.array( + [[x / 2, y / 2, z / 2] for z in range(N) for y in range(N) for x in range(N)] + ) order = [i for i, j in enumerate(temp_points)] random.shuffle(order) @@ -72,13 +73,15 @@ def randomly_ordered_mesh(cell_type): for x in range(N - 1): for y in range(N - 1): for z in range(N - 1): - a = N ** 2 * z + N * y + x - for c in [[a + N, a + N ** 2 + 1, a, a + 1], - [a + N, a + N ** 2 + 1, a + 1, a + N + 1], - [a + N, a + N ** 2 + 1, a + N + 1, a + N ** 2 + N + 1], - [a + N, a + N ** 2 + 1, a + N ** 2 + N + 1, a + N ** 2 + N], - [a + N, a + N ** 2 + 1, a + N ** 2 + N, a + N ** 2], - [a + N, a + N ** 2 + 1, a + N ** 2, a]]: + a = N**2 * z + N * y + x + for c in [ + [a + N, a + N**2 + 1, a, a + 1], + [a + N, a + N**2 + 1, a + 1, a + N + 1], + [a + N, a + N**2 + 1, a + N + 1, a + N**2 + N + 1], + [a + N, a + N**2 + 1, a + N**2 + N + 1, a + N**2 + N], + [a + N, a + N**2 + 1, a + N**2 + N, a + N**2], + [a + N, a + N**2 + 1, a + N**2, a], + ]: cell = [order[i] for i in c] cells.append(cell) @@ -87,10 +90,20 @@ def randomly_ordered_mesh(cell_type): for x in range(N - 1): for y in range(N - 1): for z in range(N - 1): - a = N ** 2 * z + N * y + x - cell = [order[i] for i in [a, a + 1, a + N, a + N + 1, - a + N ** 2, a + 1 + N ** 2, a + N + N ** 2, - a + N + 1 + N ** 2]] + a = N**2 * z + N * y + x + cell = [ + order[i] + for i in [ + a, + a + 1, + a + N, + a + N + 1, + a + N**2, + a + 1 + N**2, + a + N + N**2, + a + N + 1 + N**2, + ] + ] cells.append(cell) # On process 0, input mesh data and distribute to other @@ -98,22 +111,37 @@ def randomly_ordered_mesh(cell_type): return create_mesh(MPI.COMM_WORLD, cells, points, domain) else: if cell_type == "triangle": - return create_mesh(MPI.COMM_WORLD, np.ndarray((0, 3)), - np.ndarray((0, 2), dtype=default_real_type), domain) + return create_mesh( + MPI.COMM_WORLD, + np.ndarray((0, 3)), + np.ndarray((0, 2), dtype=default_real_type), + domain, + ) elif cell_type == "quadrilateral": - return create_mesh(MPI.COMM_WORLD, np.ndarray((0, 4)), - np.ndarray((0, 2), dtype=default_real_type), domain) + return create_mesh( + MPI.COMM_WORLD, + np.ndarray((0, 4)), + np.ndarray((0, 2), dtype=default_real_type), + domain, + ) elif cell_type == "tetrahedron": - return create_mesh(MPI.COMM_WORLD, np.ndarray((0, 4)), - np.ndarray((0, 3), dtype=default_real_type), domain) + return create_mesh( + MPI.COMM_WORLD, + np.ndarray((0, 4)), + np.ndarray((0, 3), dtype=default_real_type), + domain, + ) elif cell_type == "hexahedron": - return create_mesh(MPI.COMM_WORLD, np.ndarray((0, 8)), - np.ndarray((0, 3), dtype=default_real_type), domain) + return create_mesh( + MPI.COMM_WORLD, + np.ndarray((0, 8)), + np.ndarray((0, 3), dtype=default_real_type), + domain, + ) -@pytest.mark.parametrize('space_type', [("P", 1), ("P", 2), ("P", 3), ("P", 4)]) -@pytest.mark.parametrize('cell_type', ["triangle", "tetrahedron", - "quadrilateral", "hexahedron"]) +@pytest.mark.parametrize("space_type", [("P", 1), ("P", 2), ("P", 3), ("P", 4)]) +@pytest.mark.parametrize("cell_type", ["triangle", "tetrahedron", "quadrilateral", "hexahedron"]) def test_dof_positions(cell_type, space_type): """Checks that dofs on shared triangle edges match up""" mesh = randomly_ordered_mesh(cell_type) @@ -166,21 +194,40 @@ def random_evaluation_mesh(cell_type): domain = ufl.Mesh(element("Lagrange", cell_type, 1, shape=(gdim,), dtype=default_real_type)) if cell_type == "triangle": - temp_points = np.array([[-1., -1.], [0., 0.], [1., 0.], [0., 1.]], dtype=default_real_type) + temp_points = np.array( + [[-1.0, -1.0], [0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], dtype=default_real_type + ) temp_cells = [[0, 1, 3], [1, 2, 3]] elif cell_type == "quadrilateral": - temp_points = np.array([[-1., -1.], [0., 0.], [1., 0.], - [-1., 1.], [0., 1.], [2., 2.]], dtype=default_real_type) + temp_points = np.array( + [[-1.0, -1.0], [0.0, 0.0], [1.0, 0.0], [-1.0, 1.0], [0.0, 1.0], [2.0, 2.0]], + dtype=default_real_type, + ) temp_cells = [[0, 1, 3, 4], [1, 2, 4, 5]] elif cell_type == "tetrahedron": - temp_points = np.array([[-1., 0., -1.], [0., 0., 0.], [1., 0., 1.], - [0., 1., 0.], [0., 0., 1.]], dtype=default_real_type) + temp_points = np.array( + [[-1.0, 0.0, -1.0], [0.0, 0.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], + dtype=default_real_type, + ) temp_cells = [[0, 1, 3, 4], [1, 2, 3, 4]] elif cell_type == "hexahedron": - temp_points = np.array([[-1., 0., -1.], [0., 0., 0.], [1., 0., 1.], - [-1., 1., 1.], [0., 1., 0.], [1., 1., 1.], - [-1., 0., 0.], [0., 0., 1.], [1., 0., 2.], - [-1., 1., 2.], [0., 1., 1.], [1., 1., 2.]], dtype=default_real_type) + temp_points = np.array( + [ + [-1.0, 0.0, -1.0], + [0.0, 0.0, 0.0], + [1.0, 0.0, 1.0], + [-1.0, 1.0, 1.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 1.0], + [-1.0, 0.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 2.0], + [-1.0, 1.0, 2.0], + [0.0, 1.0, 1.0], + [1.0, 1.0, 2.0], + ], + dtype=default_real_type, + ) temp_cells = [[0, 1, 3, 4, 6, 7, 9, 10], [1, 2, 4, 5, 7, 8, 10, 11]] order = [i for i, j in enumerate(temp_points)] @@ -200,18 +247,32 @@ def random_evaluation_mesh(cell_type): start = random.choice(range(4)) cell_order = [start] for i in range(2): - diff = random.choice([i for i in connections[start] if i not in cell_order]) - cell_order[0] + diff = ( + random.choice([i for i in connections[start] if i not in cell_order]) + - cell_order[0] + ) cell_order += [c + diff for c in cell_order] elif cell_type == "tetrahedron": cell_order = list(range(4)) random.shuffle(cell_order) elif cell_type == "hexahedron": - connections = {0: [1, 2, 4], 1: [0, 3, 5], 2: [0, 3, 6], 3: [1, 2, 7], - 4: [0, 5, 6], 5: [1, 4, 7], 6: [2, 4, 7], 7: [3, 5, 6]} + connections = { + 0: [1, 2, 4], + 1: [0, 3, 5], + 2: [0, 3, 6], + 3: [1, 2, 7], + 4: [0, 5, 6], + 5: [1, 4, 7], + 6: [2, 4, 7], + 7: [3, 5, 6], + } start = random.choice(range(8)) cell_order = [start] for i in range(3): - diff = random.choice([i for i in connections[start] if i not in cell_order]) - cell_order[0] + diff = ( + random.choice([i for i in connections[start] if i not in cell_order]) + - cell_order[0] + ) cell_order += [c + diff for c in cell_order] cells.append([order[cell[i]] for i in cell_order]) @@ -221,18 +282,11 @@ def random_evaluation_mesh(cell_type): @pytest.mark.skip_in_parallel @pytest.mark.parametrize( "cell_type,space_type", - [ - (c, s) for c in ["triangle", "tetrahedron"] - for s in ["P", "N1curl", "RT", "BDM", "N2curl"] - ] + [ - ("quadrilateral", s) - for s in ["Q", "S", "RTCE", "RTCF", "BDMCE", "BDMCF"] - ] + [ - ("hexahedron", s) - for s in ["Q", "S", "NCE", "NCF", "AAE", "AAF"] - ] + [(c, s) for c in ["triangle", "tetrahedron"] for s in ["P", "N1curl", "RT", "BDM", "N2curl"]] + + [("quadrilateral", s) for s in ["Q", "S", "RTCE", "RTCF", "BDMCE", "BDMCF"]] + + [("hexahedron", s) for s in ["Q", "S", "NCE", "NCF", "AAE", "AAF"]], ) -@pytest.mark.parametrize('space_order', range(1, 4)) +@pytest.mark.parametrize("space_order", range(1, 4)) def test_evaluation(cell_type, space_type, space_order): if cell_type == "hexahedron" and space_order > 3: pytest.skip("Skipping expensive test on hexahedron") @@ -245,13 +299,19 @@ def test_evaluation(cell_type, space_type, space_order): N = 5 if cell_type == "tetrahedron": - eval_points = np.array([[0., i / N, j / N] for i in range(N + 1) - for j in range(N + 1 - i)], dtype=default_real_type) + eval_points = np.array( + [[0.0, i / N, j / N] for i in range(N + 1) for j in range(N + 1 - i)], + dtype=default_real_type, + ) elif cell_type == "hexahedron": - eval_points = np.array([[0., i / N, j / N] for i in range(N + 1) - for j in range(N + 1)], dtype=default_real_type) + eval_points = np.array( + [[0.0, i / N, j / N] for i in range(N + 1) for j in range(N + 1)], + dtype=default_real_type, + ) else: - eval_points = np.array([[0., i / N, 0.] for i in range(N + 1)], dtype=default_real_type) + eval_points = np.array( + [[0.0, i / N, 0.0] for i in range(N + 1)], dtype=default_real_type + ) for d in dofs: v = Function(V) @@ -276,18 +336,11 @@ def test_evaluation(cell_type, space_type, space_order): @pytest.mark.skip_in_parallel @pytest.mark.parametrize( "cell_type,space_type", - [ - (c, s) for c in ["triangle", "tetrahedron"] - for s in ["P", "N1curl", "RT", "BDM", "N2curl"] - ] + [ - ("quadrilateral", s) - for s in ["Q", "S", "RTCE", "RTCF", "BDMCE", "BDMCF"] - ] + [ - ("hexahedron", s) - for s in ["Q", "S", "NCE", "NCF", "AAE", "AAF"] - ] + [(c, s) for c in ["triangle", "tetrahedron"] for s in ["P", "N1curl", "RT", "BDM", "N2curl"]] + + [("quadrilateral", s) for s in ["Q", "S", "RTCE", "RTCF", "BDMCE", "BDMCF"]] + + [("hexahedron", s) for s in ["Q", "S", "NCE", "NCF", "AAE", "AAF"]], ) -@pytest.mark.parametrize('space_order', range(1, 4)) +@pytest.mark.parametrize("space_order", range(1, 4)) def test_integral(cell_type, space_type, space_order): if cell_type == "hexahedron" and space_order >= 3: pytest.skip("Skipping expensive test on hexahedron") @@ -325,6 +378,7 @@ def tangent(x): t.interpolate(tangent) _form = ufl.inner(ufl.jump(v), t) * ufl.dS if tdim == 3: + def tangent2(x): values = np.zeros((3, x.shape[1])) values[2] = [1 for i in values[2]] diff --git a/python/test/unit/fem/test_dofmap.py b/python/test/unit/fem/test_dofmap.py index 5c8ea8da166..b3e047b3bd4 100644 --- a/python/test/unit/fem/test_dofmap.py +++ b/python/test/unit/fem/test_dofmap.py @@ -16,7 +16,13 @@ import ufl from basix.ufl import element, mixed_element from dolfinx.fem import functionspace -from dolfinx.mesh import CellType, create_mesh, create_unit_cube, create_unit_interval, create_unit_square +from dolfinx.mesh import ( + CellType, + create_mesh, + create_unit_cube, + create_unit_interval, + create_unit_square, +) xfail = pytest.mark.xfail(strict=True) @@ -28,9 +34,12 @@ def mesh(): @pytest.mark.skip @pytest.mark.parametrize( - 'mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 4, 4)), - (create_unit_square, - (MPI.COMM_WORLD, 4, 4, CellType.quadrilateral))]) + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 4, 4)), + (create_unit_square, (MPI.COMM_WORLD, 4, 4, CellType.quadrilateral)), + ], +) def test_tabulate_dofs(mesh_factory): func, args = mesh_factory mesh = func(*args) @@ -94,16 +103,19 @@ def test_entity_dofs(mesh): V = functionspace(mesh, ("Lagrange", 1, (gdim,))) bs = V.dofmap.dof_layout.block_size for i, cdofs in enumerate([[0, 1], [2, 3], [4, 5]]): - dofs = [bs * d + b for d in V.dofmap.dof_layout.entity_dofs(0, i) - for b in range(bs)] + dofs = [bs * d + b for d in V.dofmap.dof_layout.entity_dofs(0, i) for b in range(bs)] assert all(d == cd for d, cd in zip(dofs, cdofs)) @pytest.mark.skip @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 2, 2)), - (create_unit_square, - (MPI.COMM_WORLD, 2, 2, CellType.quadrilateral))]) +@pytest.mark.parametrize( + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 2, 2)), + (create_unit_square, (MPI.COMM_WORLD, 2, 2, CellType.quadrilateral)), + ], +) def test_entity_closure_dofs(mesh_factory): func, args = mesh_factory mesh = func(*args) @@ -120,17 +132,16 @@ def test_entity_closure_dofs(mesh_factory): for entity in all_entities: entities = np.array([entity], dtype=np.uintp) dofs_on_this_entity = V.dofmap.entity_dofs(mesh, d, entities) - closure_dofs = V.dofmap.entity_closure_dofs( - mesh, d, entities) + closure_dofs = V.dofmap.entity_closure_dofs(mesh, d, entities) assert len(dofs_on_this_entity) == V.dofmap.dof_layout.num_entity_dofs(d) assert len(dofs_on_this_entity) <= len(closure_dofs) covered.update(dofs_on_this_entity) covered2.update(closure_dofs) - dofs_on_all_entities = V.dofmap.entity_dofs( - mesh, d, all_entities) - closure_dofs_on_all_entities = V.dofmap.entity_closure_dofs( - mesh, d, all_entities) - assert len(dofs_on_all_entities) == V.dofmap.dof_layout.num_entity_dofs(d) * num_entities + dofs_on_all_entities = V.dofmap.entity_dofs(mesh, d, all_entities) + closure_dofs_on_all_entities = V.dofmap.entity_closure_dofs(mesh, d, all_entities) + assert ( + len(dofs_on_all_entities) == V.dofmap.dof_layout.num_entity_dofs(d) * num_entities + ) assert covered == set(dofs_on_all_entities) assert covered2 == set(closure_dofs_on_all_entities) @@ -142,10 +153,12 @@ def test_entity_closure_dofs(mesh_factory): def test_block_size(): - meshes = [create_unit_square(MPI.COMM_WORLD, 8, 8), - create_unit_cube(MPI.COMM_WORLD, 4, 4, 4), - create_unit_square(MPI.COMM_WORLD, 8, 8, CellType.quadrilateral), - create_unit_cube(MPI.COMM_WORLD, 4, 4, 4, CellType.hexahedron)] + meshes = [ + create_unit_square(MPI.COMM_WORLD, 8, 8), + create_unit_cube(MPI.COMM_WORLD, 4, 4, 4), + create_unit_square(MPI.COMM_WORLD, 8, 8, CellType.quadrilateral), + create_unit_cube(MPI.COMM_WORLD, 4, 4, 4, CellType.hexahedron), + ] for mesh in meshes: P2 = element("Lagrange", mesh.basix_cell(), 2) V = functionspace(mesh, P2) @@ -167,16 +180,20 @@ def test_block_size(): @pytest.mark.skip def test_block_size_real(): mesh = create_unit_interval(MPI.COMM_WORLD, 12) - V = element('DG', mesh.basix_cell(), 0) - R = element('R', mesh.basix_cell(), 0) + V = element("DG", mesh.basix_cell(), 0) + R = element("R", mesh.basix_cell(), 0) X = functionspace(mesh, V * R) assert X.dofmap.index_map_bs == 1 @pytest.mark.skip -@pytest.mark.parametrize('mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 4, 4)), - (create_unit_square, - (MPI.COMM_WORLD, 4, 4, CellType.quadrilateral))]) +@pytest.mark.parametrize( + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 4, 4)), + (create_unit_square, (MPI.COMM_WORLD, 4, 4, CellType.quadrilateral)), + ], +) def test_local_dimension(mesh_factory): func, args = mesh_factory mesh = func(*args) @@ -224,26 +241,78 @@ def test_readonly_view_local_to_global_unwoned(mesh): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("points, celltype, order", [ - (np.array([[0, 0], [1, 0], [0, 2], [1, 2]], dtype=np.float64), - CellType.quadrilateral, 1), - (np.array([[0, 0], [1, 0], [0, 2], [1, 2], - [0.5, 0], [0, 1], [1, 1], [0.5, 2], [0.5, 1]], dtype=np.float64), - CellType.quadrilateral, 2), - (np.array([[0, 0], [1, 0], [0, 2], [0.5, 1], [0, 1], [0.5, 0]], dtype=np.float64), - CellType.triangle, 2), - (np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [1, 2, 0], - [0, 0, 3], [1, 0, 3], [0, 2, 3], [1, 2, 3]], dtype=np.float64), - CellType.hexahedron, 1), - (np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [1, 2, 0], - [0, 0, 3], [1, 0, 3], [0, 2, 3], [1, 2, 3], - [0.5, 0, 0], [0, 1, 0], [0, 0, 1.5], [1, 1, 0], - [1, 0, 1.5], [0.5, 2, 0], [0, 2, 1.5], [1, 2, 1.5], - [0.5, 0, 3], [0, 1, 3], [1, 1, 3], [0.5, 2, 3], - [0.5, 1, 0], [0.5, 0, 1.5], [0, 1, 1.5], [1, 1, 1.5], - [0.5, 2, 1.5], [0.5, 1, 3], [0.5, 1, 1.5]], dtype=np.float64), - CellType.hexahedron, 2) -]) +@pytest.mark.parametrize( + "points, celltype, order", + [ + (np.array([[0, 0], [1, 0], [0, 2], [1, 2]], dtype=np.float64), CellType.quadrilateral, 1), + ( + np.array( + [[0, 0], [1, 0], [0, 2], [1, 2], [0.5, 0], [0, 1], [1, 1], [0.5, 2], [0.5, 1]], + dtype=np.float64, + ), + CellType.quadrilateral, + 2, + ), + ( + np.array([[0, 0], [1, 0], [0, 2], [0.5, 1], [0, 1], [0.5, 0]], dtype=np.float64), + CellType.triangle, + 2, + ), + ( + np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 3], + [1, 0, 3], + [0, 2, 3], + [1, 2, 3], + ], + dtype=np.float64, + ), + CellType.hexahedron, + 1, + ), + ( + np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 3], + [1, 0, 3], + [0, 2, 3], + [1, 2, 3], + [0.5, 0, 0], + [0, 1, 0], + [0, 0, 1.5], + [1, 1, 0], + [1, 0, 1.5], + [0.5, 2, 0], + [0, 2, 1.5], + [1, 2, 1.5], + [0.5, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0.5, 2, 3], + [0.5, 1, 0], + [0.5, 0, 1.5], + [0, 1, 1.5], + [1, 1, 1.5], + [0.5, 2, 1.5], + [0.5, 1, 3], + [0.5, 1, 1.5], + ], + dtype=np.float64, + ), + CellType.hexahedron, + 2, + ), + ], +) def test_higher_order_coordinate_map(points, celltype, order): """Computes physical coordinates of a cell, based on the coordinate map.""" cells = np.array([range(len(points))]) @@ -260,7 +329,7 @@ def test_higher_order_coordinate_map(points, celltype, order): i = 0 for node in range(len(points)): - x_coord_new[i] = x_g[coord_dofs[0, node], :mesh.geometry.dim] + x_coord_new[i] = x_g[coord_dofs[0, node], : mesh.geometry.dim] i += 1 x = cmap.push_forward(X, x_coord_new) @@ -276,23 +345,50 @@ def test_higher_order_coordinate_map(points, celltype, order): def test_higher_order_tetra_coordinate_map(order): """Compute physical coordinates of a cell from the coordinate map.""" celltype = CellType.tetrahedron - points = np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [0, 0, 3], - [0, 4 / 3, 1], [0, 2 / 3, 2], - [2 / 3, 0, 1], [1 / 3, 0, 2], - [2 / 3, 2 / 3, 0], [1 / 3, 4 / 3, 0], - [0, 0, 1], [0, 0, 2], - [0, 2 / 3, 0], [0, 4 / 3, 0], - [1 / 3, 0, 0], [2 / 3, 0, 0], - [1 / 3, 2 / 3, 1], [0, 2 / 3, 1], - [1 / 3, 0, 1], [1 / 3, 2 / 3, 0]], dtype=np.float64) + points = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [0, 0, 3], + [0, 4 / 3, 1], + [0, 2 / 3, 2], + [2 / 3, 0, 1], + [1 / 3, 0, 2], + [2 / 3, 2 / 3, 0], + [1 / 3, 4 / 3, 0], + [0, 0, 1], + [0, 0, 2], + [0, 2 / 3, 0], + [0, 4 / 3, 0], + [1 / 3, 0, 0], + [2 / 3, 0, 0], + [1 / 3, 2 / 3, 1], + [0, 2 / 3, 1], + [1 / 3, 0, 1], + [1 / 3, 2 / 3, 0], + ], + dtype=np.float64, + ) assert order <= 2 if order == 1: points = np.array([points[0, :], points[1, :], points[2, :], points[3, :]]) elif order == 2: - points = np.array([points[0, :], points[1, :], points[2, :], points[3, :], - [0, 1, 3 / 2], [1 / 2, 0, 3 / 2], [1 / 2, 1, 0], [0, 0, 3 / 2], - [0, 1, 0], [1 / 2, 0, 0]]) + points = np.array( + [ + points[0, :], + points[1, :], + points[2, :], + points[3, :], + [0, 1, 3 / 2], + [1 / 2, 0, 3 / 2], + [1 / 2, 1, 0], + [0, 0, 3 / 2], + [0, 1, 0], + [1 / 2, 0, 0], + ] + ) cells = np.array([range(len(points))]) domain = ufl.Mesh(element("Lagrange", celltype.name, order, shape=(3,))) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) @@ -303,7 +399,7 @@ def test_higher_order_tetra_coordinate_map(order): x_coord_new = np.zeros([len(points), mesh.geometry.dim]) for node in range(points.shape[0]): - x_coord_new[node] = x_g[x_dofs[0, node], :mesh.geometry.dim] + x_coord_new[node] = x_g[x_dofs[0, node], : mesh.geometry.dim] x = mesh.geometry.cmap.push_forward(X, x_coord_new) assert np.allclose(x[:, 0], X[:, 0]) diff --git a/python/test/unit/fem/test_element_integrals.py b/python/test/unit/fem/test_element_integrals.py index b8ddcfb5952..092d8fe658d 100644 --- a/python/test/unit/fem/test_element_integrals.py +++ b/python/test/unit/fem/test_element_integrals.py @@ -16,36 +16,66 @@ import dolfinx import ufl from basix.ufl import element -from dolfinx.fem import Constant, Function, assemble_matrix, assemble_scalar, assemble_vector, form, functionspace +from dolfinx.fem import ( + Constant, + Function, + assemble_matrix, + assemble_scalar, + assemble_vector, + form, + functionspace, +) from dolfinx.mesh import CellType, create_mesh, meshtags parametrize_cell_types = pytest.mark.parametrize( "cell_type", - [CellType.interval, CellType.triangle, CellType.tetrahedron, CellType.quadrilateral, CellType.hexahedron]) + [ + CellType.interval, + CellType.triangle, + CellType.tetrahedron, + CellType.quadrilateral, + CellType.hexahedron, + ], +) def unit_cell_points(cell_type, dtype): if cell_type == CellType.interval: - return np.array([[0.], [1.]], dtype=dtype) + return np.array([[0.0], [1.0]], dtype=dtype) if cell_type == CellType.triangle: # Define equilateral triangle with area 1 - root = 3 ** 0.25 # 4th root of 3 - return np.array([[0., 0.], [2 / root, 0.], - [1 / root, root]], dtype=dtype) + root = 3**0.25 # 4th root of 3 + return np.array([[0.0, 0.0], [2 / root, 0.0], [1 / root, root]], dtype=dtype) if cell_type == CellType.tetrahedron: # Define regular tetrahedron with volume 1 - s = 2 ** 0.5 * 3 ** (1 / 3) # side length - return np.array([[0., 0., 0.], [s, 0., 0.], - [s / 2, s * np.sqrt(3) / 2, 0.], - [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)]], dtype=dtype) + s = 2**0.5 * 3 ** (1 / 3) # side length + return np.array( + [ + [0.0, 0.0, 0.0], + [s, 0.0, 0.0], + [s / 2, s * np.sqrt(3) / 2, 0.0], + [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)], + ], + dtype=dtype, + ) elif cell_type == CellType.quadrilateral: # Define unit quadrilateral (area 1) - return np.array([[0., 0.], [1., 0.], [0., 1.], [1., 1.]], dtype=dtype) + return np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=dtype) elif cell_type == CellType.hexahedron: # Define unit hexahedron (volume 1) - return np.array([[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], - [1., 1., 0.], [0., 0., 1.], [1., 0., 1.], - [0., 1., 1.], [1., 1., 1.]], dtype=dtype) + return np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [0.0, 1.0, 1.0], + [1.0, 1.0, 1.0], + ], + dtype=dtype, + ) def unit_cell(cell_type, dtype, random_order=True): @@ -61,51 +91,75 @@ def unit_cell(cell_type, dtype, random_order=True): ordered_points[j] = points[i] cells = np.array([order]) - domain = ufl.Mesh(element("Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), dtype=dtype)) + domain = ufl.Mesh( + element("Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), dtype=dtype) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, ordered_points, domain) return mesh def two_unit_cells(cell_type, dtype, agree=False, random_order=True, return_order=False): if cell_type == CellType.interval: - points = np.array([[0.], [1.], [-1.]], dtype=dtype) + points = np.array([[0.0], [1.0], [-1.0]], dtype=dtype) if agree: cells = [[0, 1], [2, 0]] else: cells = [[0, 1], [0, 2]] if cell_type == CellType.triangle: # Define equilateral triangles with area 1 - root = 3 ** 0.25 # 4th root of 3 - points = np.array([[0., 0.], [2 / root, 0.], - [1 / root, root], [1 / root, -root]], dtype=dtype) + root = 3**0.25 # 4th root of 3 + points = np.array( + [[0.0, 0.0], [2 / root, 0.0], [1 / root, root], [1 / root, -root]], dtype=dtype + ) if agree: cells = [[0, 1, 2], [0, 3, 1]] else: cells = [[0, 1, 2], [1, 0, 3]] elif cell_type == CellType.tetrahedron: # Define regular tetrahedra with volume 1 - s = 2 ** 0.5 * 3 ** (1 / 3) # side length - points = np.array([[0., 0., 0.], [s, 0., 0.], - [s / 2, s * np.sqrt(3) / 2, 0.], - [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)], - [s / 2, s / 2 / np.sqrt(3), -s * np.sqrt(2 / 3)]], dtype=dtype) + s = 2**0.5 * 3 ** (1 / 3) # side length + points = np.array( + [ + [0.0, 0.0, 0.0], + [s, 0.0, 0.0], + [s / 2, s * np.sqrt(3) / 2, 0.0], + [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)], + [s / 2, s / 2 / np.sqrt(3), -s * np.sqrt(2 / 3)], + ], + dtype=dtype, + ) if agree: cells = [[0, 1, 2, 3], [0, 1, 4, 2]] else: cells = [[0, 1, 2, 3], [0, 2, 1, 4]] elif cell_type == CellType.quadrilateral: # Define unit quadrilaterals (area 1) - points = np.array([[0., 0.], [1., 0.], [0., 1.], [1., 1.], [0., -1.], [1., -1.]], dtype=dtype) + points = np.array( + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.0, -1.0], [1.0, -1.0]], dtype=dtype + ) if agree: cells = [[0, 1, 2, 3], [4, 5, 0, 1]] else: cells = [[0, 1, 2, 3], [5, 1, 4, 0]] elif cell_type == CellType.hexahedron: # Define unit hexahedra (volume 1) - points = np.array([[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], - [1., 1., 0.], [0., 0., 1.], [1., 0., 1.], - [0., 1., 1.], [1., 1., 1.], [0., 0., -1.], - [1., 0., -1.], [0., 1., -1.], [1., 1., -1.]], dtype=dtype) + points = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [0.0, 1.0, 1.0], + [1.0, 1.0, 1.0], + [0.0, 0.0, -1.0], + [1.0, 0.0, -1.0], + [0.0, 1.0, -1.0], + [1.0, 1.0, -1.0], + ], + dtype=dtype, + ) if agree: cells = [[0, 1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 0, 1, 2, 3]] else: @@ -121,7 +175,9 @@ def two_unit_cells(cell_type, dtype, agree=False, random_order=True, return_orde ordered_points[j] = points[i] ordered_cells = np.array([[order[i] for i in c] for c in cells]) - domain = ufl.Mesh(element("Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), dtype=dtype)) + domain = ufl.Mesh( + element("Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), dtype=dtype) + ) mesh = create_mesh(MPI.COMM_WORLD, ordered_cells, ordered_points, domain) if return_order: return mesh, order @@ -150,14 +206,17 @@ def test_facet_integral(cell_type, dtype): # Functions that will have the same integral over each facet if cell_type == CellType.triangle: - root = 3 ** 0.25 # 4th root of 3 + root = 3**0.25 # 4th root of 3 v.interpolate(lambda x: (x[0] - 1 / root) ** 2 + (x[1] - root / 3) ** 2) elif cell_type == CellType.quadrilateral: v.interpolate(lambda x: x[0] * (1 - x[0]) + x[1] * (1 - x[1])) elif cell_type == CellType.tetrahedron: - s = 2 ** 0.5 * 3 ** (1 / 3) # side length - v.interpolate(lambda x: (x[0] - s / 2) ** 2 + (x[1] - s / 2 / np.sqrt(3)) ** 2 - + (x[2] - s * np.sqrt(2 / 3) / 4) ** 2) + s = 2**0.5 * 3 ** (1 / 3) # side length + v.interpolate( + lambda x: (x[0] - s / 2) ** 2 + + (x[1] - s / 2 / np.sqrt(3)) ** 2 + + (x[2] - s * np.sqrt(2 / 3) / 4) ** 2 + ) elif cell_type == CellType.hexahedron: v.interpolate(lambda x: x[0] * (1 - x[0]) + x[1] * (1 - x[1]) + x[2] * (1 - x[2])) @@ -214,23 +273,32 @@ def test_facet_normals(cell_type, dtype): # Vector function that is zero at `co` and points away # from `co` so that there is no normal component on # three faces and the integral over the other edge is 1 - v.interpolate(lambda x: ((x[0] - co[0]) / 3, (x[1] - co[1]) / 3, (x[2] - co[2]) / 3)) + v.interpolate( + lambda x: ((x[0] - co[0]) / 3, (x[1] - co[1]) / 3, (x[2] - co[2]) / 3) + ) elif cell_type == CellType.quadrilateral: # function that is 0 on one edge and points away from # that edge so that there is no normal component on # three edges - v.interpolate(lambda x: tuple(x[j] - i % 2 if j == i // 2 else 0 * x[j] for j in range(2))) + v.interpolate( + lambda x: tuple(x[j] - i % 2 if j == i // 2 else 0 * x[j] for j in range(2)) + ) elif cell_type == CellType.hexahedron: # function that is 0 on one face and points away from # that face so that there is no normal component on five # faces - v.interpolate(lambda x: tuple(x[j] - i % 2 if j == i // 3 else 0 * x[j] for j in range(3))) + v.interpolate( + lambda x: tuple(x[j] - i % 2 if j == i // 3 else 0 * x[j] for j in range(3)) + ) # Check that integrals these functions dotted with the # normal over a face is 1 on one face and 0 on the others ones = 0 for j in range(num_facets): - a = form(ufl.inner(v, normal) * ufl.ds(subdomain_data=marker, subdomain_id=j), dtype=dtype) + a = form( + ufl.inner(v, normal) * ufl.ds(subdomain_data=marker, subdomain_id=j), + dtype=dtype, + ) result = assemble_scalar(a) if np.isclose(result, 1): ones += 1 @@ -240,7 +308,7 @@ def test_facet_normals(cell_type, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('space_type', ["Lagrange", "DG"]) +@pytest.mark.parametrize("space_type", ["Lagrange", "DG"]) @parametrize_cell_types @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_plus_minus(cell_type, space_type, dtype): @@ -262,7 +330,7 @@ def test_plus_minus(cell_type, space_type, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('pm', ["+", "-"]) +@pytest.mark.parametrize("pm", ["+", "-"]) @parametrize_cell_types @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_plus_minus_simple_vector(cell_type, pm, dtype): @@ -302,7 +370,9 @@ def test_plus_minus_simple_vector(cell_type, pm, dtype): for dof0, point0 in zip(spaces[0].dofmap.cell_dofs(cell), dofmap0[cell]): # Find the point in the cell 0 in the second mesh for dof1, point1 in zip(space.dofmap.cell_dofs(cell), dofmap1[cell]): - if np.allclose(spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1]): + if np.allclose( + spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1] + ): break else: # If no matching point found, fail @@ -312,8 +382,8 @@ def test_plus_minus_simple_vector(cell_type, pm, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('pm1', ["+", "-"]) -@pytest.mark.parametrize('pm2', ["+", "-"]) +@pytest.mark.parametrize("pm1", ["+", "-"]) +@pytest.mark.parametrize("pm2", ["+", "-"]) @parametrize_cell_types @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_plus_minus_vector(cell_type, pm1, pm2, dtype): @@ -355,7 +425,9 @@ def test_plus_minus_vector(cell_type, pm1, pm2, dtype): for dof0, point0 in zip(spaces[0].dofmap.cell_dofs(cell), dofmap0[cell]): # Find the point in the cell 0 in the second mesh for dof1, point1 in zip(space.dofmap.cell_dofs(cell), dofmap1[cell]): - if np.allclose(spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1]): + if np.allclose( + spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1] + ): break else: # If no matching point found, fail @@ -365,8 +437,8 @@ def test_plus_minus_vector(cell_type, pm1, pm2, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('pm1', ["+", "-"]) -@pytest.mark.parametrize('pm2', ["+", "-"]) +@pytest.mark.parametrize("pm1", ["+", "-"]) +@pytest.mark.parametrize("pm2", ["+", "-"]) @parametrize_cell_types @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_plus_minus_matrix(cell_type, pm1, pm2, dtype): @@ -405,7 +477,9 @@ def test_plus_minus_matrix(cell_type, pm1, pm2, dtype): for dof0, point0 in zip(spaces[0].dofmap.cell_dofs(cell), dofmap0[cell]): # Find the point in the cell 0 in the second mesh for dof1, point1 in zip(space.dofmap.cell_dofs(cell), dofmap1[cell]): - if np.allclose(spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1]): + if np.allclose( + spaces[0].mesh.geometry.x[point0], space.mesh.geometry.x[point1] + ): break else: # If no matching point found, fail @@ -419,10 +493,12 @@ def test_plus_minus_matrix(cell_type, pm1, pm2, dtype): assert np.isclose(results[0][a, c], result[b, d]) -@pytest.mark.skip(reason="This test relies on the mesh constructor not re-ordering the mesh points. Needs replacing.") +@pytest.mark.skip( + reason="This test relies on the mesh constructor not re-ordering the mesh points. Needs replacing." +) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', [1, 2]) -@pytest.mark.parametrize('space_type', ["N1curl", "N2curl"]) +@pytest.mark.parametrize("order", [1, 2]) +@pytest.mark.parametrize("space_type", ["N1curl", "N2curl"]) @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_curl(space_type, order, dtype): """Test that curl is consistent for different cell permutations of a tetrahedron.""" @@ -456,12 +532,13 @@ def test_curl(space_type, order, dtype): # Loop over cell edges for i, edge in enumerate(V0.mesh.topology.connectivity(tdim, 1).links(0)): - # Get the edge vertices vertices0 = c10_0.links(edge) # Need to map back # Get assembled values on edge - values0 = sorted([result[V0.dofmap.cell_dofs(0)[a]] for a in V0.dofmap.dof_layout.entity_dofs(1, i)]) + values0 = sorted( + [result[V0.dofmap.cell_dofs(0)[a]] for a in V0.dofmap.dof_layout.entity_dofs(1, i)] + ) for V, result in zip(spaces[1:], results[1:]): # Get edge->vertex connectivity @@ -470,7 +547,12 @@ def test_curl(space_type, order, dtype): # Loop over cell edges for j, e in enumerate(V.mesh.topology.connectivity(tdim, 1).links(0)): if sorted(c10.links(e)) == sorted(vertices0): # need to map back c.links(e) - values = sorted([result[V.dofmap.cell_dofs(0)[a]] for a in V.dofmap.dof_layout.entity_dofs(1, j)]) + values = sorted( + [ + result[V.dofmap.cell_dofs(0)[a]] + for a in V.dofmap.dof_layout.entity_dofs(1, j) + ] + ) assert np.allclose(values0, values) break else: @@ -481,10 +563,7 @@ def test_curl(space_type, order, dtype): def create_quad_mesh(offset, dtype): """Creates a mesh of a single square element if offset = 0, or a trapezium element if |offset| > 0.""" - x = np.array([[0, 0], - [1, 0], - [0, 0.5 + offset], - [1, 0.5 - offset]], dtype=dtype) + x = np.array([[0, 0], [1, 0], [0, 0.5 + offset], [1, 0.5 - offset]], dtype=dtype) cells = np.array([[0, 1, 2, 3]]) ufl_mesh = ufl.Mesh(element("Lagrange", "quadrilateral", 1, shape=(2,), dtype=dtype)) mesh = create_mesh(MPI.COMM_WORLD, cells, x, ufl_mesh) diff --git a/python/test/unit/fem/test_expression.py b/python/test/unit/fem/test_expression.py index 3d64f416f2f..20743739e24 100644 --- a/python/test/unit/fem/test_expression.py +++ b/python/test/unit/fem/test_expression.py @@ -120,7 +120,7 @@ def scatter(A, array_evaluated, dofmap0, dofmap1): # Interpolate RT1 into vdP1 (compiled, mat-vec interpolation) h2 = Function(vdP1, dtype=dtype) - h2.x.array[:A1.shape[0]] += A1 @ g.x.array + h2.x.array[: A1.shape[0]] += A1 @ g.x.array h2.x.scatter_forward() assert np.linalg.norm(h2.x.array - h.x.array) == pytest.approx(0.0, abs=1.0e-4) @@ -232,7 +232,7 @@ def test_assembly_into_quadrature_function(dtype): quadrature_degree = 2 quadrature_points, _ = basix.make_quadrature(basix.CellType.triangle, quadrature_degree) quadrature_points = quadrature_points.astype(xtype) - Q_element = quadrature_element("triangle", (2, ), degree=quadrature_degree, scheme="default") + Q_element = quadrature_element("triangle", (2,), degree=quadrature_degree, scheme="default") Q = functionspace(mesh, Q_element) P2 = functionspace(mesh, ("P", 2)) @@ -282,7 +282,9 @@ def e_exact(x): bs = Q.dofmap.bs Q_dofs_unrolled = bs * np.repeat(Q_dofs, bs).reshape(-1, bs) + np.arange(bs) - Q_dofs_unrolled = Q_dofs_unrolled.reshape(-1, bs * quadrature_points.shape[0]).astype(Q_dofs.dtype) + Q_dofs_unrolled = Q_dofs_unrolled.reshape(-1, bs * quadrature_points.shape[0]).astype( + Q_dofs.dtype + ) local = e_Q.x.array e_exact_eval = np.zeros_like(local) for cell in range(num_cells): diff --git a/python/test/unit/fem/test_fem_pipeline.py b/python/test/unit/fem/test_fem_pipeline.py index 2f53b01db2e..58cfac5dff1 100644 --- a/python/test/unit/fem/test_fem_pipeline.py +++ b/python/test/unit/fem/test_fem_pipeline.py @@ -16,13 +16,39 @@ import ufl from basix.ufl import element, mixed_element from dolfinx import default_real_type -from dolfinx.fem import Function, assemble_scalar, dirichletbc, form, functionspace, locate_dofs_topological +from dolfinx.fem import ( + Function, + assemble_scalar, + dirichletbc, + form, + functionspace, + locate_dofs_topological, +) from dolfinx.fem.petsc import apply_lifting, assemble_matrix, assemble_vector, set_bc from dolfinx.io import XDMFFile -from dolfinx.mesh import (CellType, create_rectangle, create_unit_cube, create_unit_square, exterior_facet_indices, - locate_entities_boundary) -from ufl import (CellDiameter, FacetNormal, SpatialCoordinate, TestFunction, TrialFunction, avg, div, ds, dS, dx, grad, - inner, jump) +from dolfinx.mesh import ( + CellType, + create_rectangle, + create_unit_cube, + create_unit_square, + exterior_facet_indices, + locate_entities_boundary, +) +from ufl import ( + CellDiameter, + FacetNormal, + SpatialCoordinate, + TestFunction, + TrialFunction, + avg, + div, + dS, + ds, + dx, + grad, + inner, + jump, +) def run_scalar_test(mesh, V, degree): @@ -35,21 +61,25 @@ def run_scalar_test(mesh, V, degree): # Get quadrature degree for bilinear form integrand (ignores effect of non-affine map) a = inner(grad(u), grad(v)) * dx(metadata={"quadrature_degree": -1}) - a.integrals()[0].metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(a) + a.integrals()[0].metadata()[ + "quadrature_degree" + ] = ufl.algorithms.estimate_total_polynomial_degree(a) a = form(a) # Source term x = SpatialCoordinate(mesh) - u_exact = x[1]**degree - f = - div(grad(u_exact)) + u_exact = x[1] ** degree + f = -div(grad(u_exact)) # Set quadrature degree for linear form integrand (ignores effect of non-affine map) L = inner(f, v) * dx(metadata={"quadrature_degree": -1}) - L.integrals()[0].metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(L) + L.integrals()[0].metadata()[ + "quadrature_degree" + ] = ufl.algorithms.estimate_total_polynomial_degree(L) L = form(L) u_bc = Function(V) - u_bc.interpolate(lambda x: x[1]**degree) + u_bc.interpolate(lambda x: x[1] ** degree) # Create Dirichlet boundary condition facetdim = mesh.topology.dim - 1 @@ -76,7 +106,7 @@ def run_scalar_test(mesh, V, degree): solver.solve(b, uh.vector) uh.x.scatter_forward() - M = (u_exact - uh)**2 * dx + M = (u_exact - uh) ** 2 * dx M = form(M) error = mesh.comm.allreduce(assemble_scalar(M), op=MPI.SUM) assert np.abs(error) < 1.0e-9 @@ -114,9 +144,9 @@ def run_vector_test(mesh, V, degree): uh.x.scatter_forward() # Calculate error - M = (u_exact - uh[0])**2 * dx + M = (u_exact - uh[0]) ** 2 * dx for i in range(1, mesh.topology.dim): - M += uh[i]**2 * dx + M += uh[i] ** 2 * dx M = form(M) error = mesh.comm.allreduce(assemble_scalar(M), op=MPI.SUM) @@ -142,7 +172,7 @@ def run_dg_test(mesh, V, degree): k.x.array[:] = 2.0 # Source term - f = - div(k * grad(u_exact)) + f = -div(k * grad(u_exact)) # Mesh normals and element size n = FacetNormal(mesh) @@ -156,20 +186,29 @@ def run_dg_test(mesh, V, degree): ds_ = ds(metadata={"quadrature_degree": -1}) dS_ = dS(metadata={"quadrature_degree": -1}) - a = inner(k * grad(u), grad(v)) * dx_ \ - - k("+") * inner(avg(grad(u)), jump(v, n)) * dS_ \ - - k("+") * inner(jump(u, n), avg(grad(v))) * dS_ \ - + k("+") * (alpha / h_avg) * inner(jump(u, n), jump(v, n)) * dS_ \ - - inner(k * grad(u), v * n) * ds_ \ - - inner(u * n, k * grad(v)) * ds_ \ + a = ( + inner(k * grad(u), grad(v)) * dx_ + - k("+") * inner(avg(grad(u)), jump(v, n)) * dS_ + - k("+") * inner(jump(u, n), avg(grad(v))) * dS_ + + k("+") * (alpha / h_avg) * inner(jump(u, n), jump(v, n)) * dS_ + - inner(k * grad(u), v * n) * ds_ + - inner(u * n, k * grad(v)) * ds_ + (alpha / h) * inner(k * u, v) * ds_ - L = inner(f, v) * dx_ - inner(k * u_exact * n, grad(v)) * ds_ \ + ) + L = ( + inner(f, v) * dx_ + - inner(k * u_exact * n, grad(v)) * ds_ + (alpha / h) * inner(k * u_exact, v) * ds_ + ) for integral in a.integrals(): - integral.metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(a) + integral.metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree( + a + ) for integral in L.integrals(): - integral.metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree(L) + integral.metadata()["quadrature_degree"] = ufl.algorithms.estimate_total_polynomial_degree( + L + ) a, L = form(a), form(L) @@ -190,7 +229,7 @@ def run_dg_test(mesh, V, degree): uh.x.scatter_forward() # Calculate error - M = (u_exact - uh)**2 * dx + M = (u_exact - uh) ** 2 * dx M = form(M) error = mesh.comm.allreduce(assemble_scalar(M), op=MPI.SUM) @@ -212,8 +251,12 @@ def test_curl_curl_eigenvalue(family, order): slepc4py = pytest.importorskip("slepc4py") # noqa: F841 from slepc4py import SLEPc - mesh = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]), - np.array([np.pi, np.pi])], [24, 24], CellType.triangle) + mesh = create_rectangle( + MPI.COMM_WORLD, + [np.array([0.0, 0.0]), np.array([np.pi, np.pi])], + [24, 24], + CellType.triangle, + ) e = element(family, basix.CellType.triangle, order) V = functionspace(mesh, e) @@ -225,7 +268,8 @@ def test_curl_curl_eigenvalue(family, order): b = inner(u, v) * dx boundary_facets = locate_entities_boundary( - mesh, mesh.topology.dim - 1, lambda x: np.full(x.shape[1], True, dtype=bool)) + mesh, mesh.topology.dim - 1, lambda x: np.full(x.shape[1], True, dtype=bool) + ) boundary_dofs = locate_dofs_topological(V, mesh.topology.dim - 1, boundary_facets) zero_u = Function(V) @@ -257,36 +301,50 @@ def test_curl_curl_eigenvalue(family, order): assert np.isclose(np.imag(eigenvalues_unsorted), 0.0).all() eigenvalues_sorted = np.sort(np.real(eigenvalues_unsorted))[:-1] - eigenvalues_sorted = eigenvalues_sorted[np.logical_not(eigenvalues_sorted < 1E-8)] + eigenvalues_sorted = eigenvalues_sorted[np.logical_not(eigenvalues_sorted < 1e-8)] eigenvalues_exact = np.array([1.0, 1.0, 2.0, 4.0, 4.0, 5.0, 5.0, 8.0, 9.0]) - assert np.isclose(eigenvalues_sorted[0:eigenvalues_exact.shape[0]], eigenvalues_exact, rtol=1E-2).all() + assert np.isclose( + eigenvalues_sorted[0 : eigenvalues_exact.shape[0]], eigenvalues_exact, rtol=1e-2 + ).all() eps.destroy() A.destroy() B.destroy() -@pytest.mark.skipif(np.issubdtype(PETSc.ScalarType, np.complexfloating), # type: ignore - reason="This test does not work in complex mode.") +@pytest.mark.skipif( + np.issubdtype(PETSc.ScalarType, np.complexfloating), # type: ignore + reason="This test does not work in complex mode.", +) @pytest.mark.parametrize("family", ["HHJ", "Regge"]) def test_biharmonic(family): """Manufactured biharmonic problem. Solved using rotated Regge or the Hellan-Herrmann-Johnson (HHJ) mixed finite element method in two-dimensions.""" - mesh = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]), - np.array([1.0, 1.0])], [16, 16], CellType.triangle) + mesh = create_rectangle( + MPI.COMM_WORLD, [np.array([0.0, 0.0]), np.array([1.0, 1.0])], [16, 16], CellType.triangle + ) - e = mixed_element([element(family, basix.CellType.triangle, 1), - element(basix.ElementFamily.P, basix.CellType.triangle, 2)]) + e = mixed_element( + [ + element(family, basix.CellType.triangle, 1), + element(basix.ElementFamily.P, basix.CellType.triangle, 2), + ] + ) V = functionspace(mesh, e) sigma, u = ufl.TrialFunctions(V) tau, v = ufl.TestFunctions(V) x = ufl.SpatialCoordinate(mesh) - u_exact = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1]) * ufl.sin(ufl.pi * x[1]) + u_exact = ( + ufl.sin(ufl.pi * x[0]) + * ufl.sin(ufl.pi * x[0]) + * ufl.sin(ufl.pi * x[1]) + * ufl.sin(ufl.pi * x[1]) + ) f_exact = div(grad(div(grad(u_exact)))) sigma_exact = grad(grad(u_exact)) @@ -313,9 +371,11 @@ def S(tau): # Discrete duality inner product eq. 4.5 Lizao Li's PhD thesis def b(tau_S, v): n = FacetNormal(mesh) - return inner(tau_S, grad(grad(v))) * dx \ - - ufl.dot(ufl.dot(tau_S('+'), n('+')), n('+')) * jump(grad(v), n) * dS \ + return ( + inner(tau_S, grad(grad(v))) * dx + - ufl.dot(ufl.dot(tau_S("+"), n("+")), n("+")) * jump(grad(v), n) * dS - ufl.dot(ufl.dot(tau_S, n), n) * ufl.dot(grad(v), n) * ds + ) # Non-symmetric formulation a = form(inner(sigma_S, tau_S) * dx - b(tau_S, u) + b(sigma_S, v)) @@ -326,8 +386,9 @@ def b(tau_S, v): zero_u.x.array[:] = 0 # Strong (Dirichlet) boundary condition - boundary_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, - lambda x: np.full(x.shape[1], True, dtype=bool)) + boundary_facets = locate_entities_boundary( + mesh, mesh.topology.dim - 1, lambda x: np.full(x.shape[1], True, dtype=bool) + ) boundary_dofs = locate_dofs_topological((V.sub(1), V_1), mesh.topology.dim - 1, boundary_facets) bcs = [dirichletbc(zero_u, boundary_dofs, V.sub(1))] @@ -349,10 +410,25 @@ def b(tau_S, v): x_h.x.scatter_forward() # Recall that x_h has flattened indices - u_error_numerator = np.sqrt(mesh.comm.allreduce(assemble_scalar( - form(inner(u_exact - x_h[4], u_exact - x_h[4]) * dx(mesh, metadata={"quadrature_degree": 6}))), op=MPI.SUM)) - u_error_denominator = np.sqrt(mesh.comm.allreduce(assemble_scalar( - form(inner(u_exact, u_exact) * dx(mesh, metadata={"quadrature_degree": 6}))), op=MPI.SUM)) + u_error_numerator = np.sqrt( + mesh.comm.allreduce( + assemble_scalar( + form( + inner(u_exact - x_h[4], u_exact - x_h[4]) + * dx(mesh, metadata={"quadrature_degree": 6}) + ) + ), + op=MPI.SUM, + ) + ) + u_error_denominator = np.sqrt( + mesh.comm.allreduce( + assemble_scalar( + form(inner(u_exact, u_exact) * dx(mesh, metadata={"quadrature_degree": 6})) + ), + op=MPI.SUM, + ) + ) assert np.abs(u_error_numerator / u_error_denominator) < 0.05 # Reconstruct tensor from flattened indices. @@ -364,11 +440,25 @@ def b(tau_S, v): else: raise ValueError(f"Family {family} not supported.") - sigma_error_numerator = np.sqrt(mesh.comm.allreduce(assemble_scalar( - form(inner(sigma_exact - sigma_h, sigma_exact - sigma_h) * dx(mesh, metadata={"quadrature_degree": 6}))), - op=MPI.SUM)) - sigma_error_denominator = np.sqrt(mesh.comm.allreduce(assemble_scalar( - form(inner(sigma_exact, sigma_exact) * dx(mesh, metadata={"quadrature_degree": 6}))), op=MPI.SUM)) + sigma_error_numerator = np.sqrt( + mesh.comm.allreduce( + assemble_scalar( + form( + inner(sigma_exact - sigma_h, sigma_exact - sigma_h) + * dx(mesh, metadata={"quadrature_degree": 6}) + ) + ), + op=MPI.SUM, + ) + ) + sigma_error_denominator = np.sqrt( + mesh.comm.allreduce( + assemble_scalar( + form(inner(sigma_exact, sigma_exact) * dx(mesh, metadata={"quadrature_degree": 6})) + ), + op=MPI.SUM, + ) + ) assert np.abs(sigma_error_numerator / sigma_error_denominator) < 0.05 solver.destroy() @@ -386,14 +476,22 @@ def get_mesh(cell_type, datadir): filename = "create_unit_cube_tetra.xdmf" elif cell_type == CellType.hexahedron: filename = "create_unit_cube_hexahedron.xdmf" - with XDMFFile(MPI.COMM_WORLD, Path(datadir, filename), "r", encoding=XDMFFile.Encoding.ASCII) as xdmf: + with XDMFFile( + MPI.COMM_WORLD, Path(datadir, filename), "r", encoding=XDMFFile.Encoding.ASCII + ) as xdmf: return xdmf.read_mesh(name="Grid") parametrize_cell_types = pytest.mark.parametrize( - "cell_type", [CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron]) -parametrize_cell_types_simplex = pytest.mark.parametrize("cell_type", [CellType.triangle, CellType.tetrahedron]) -parametrize_cell_types_tp = pytest.mark.parametrize("cell_type", [CellType.quadrilateral, CellType.hexahedron]) + "cell_type", + [CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron], +) +parametrize_cell_types_simplex = pytest.mark.parametrize( + "cell_type", [CellType.triangle, CellType.tetrahedron] +) +parametrize_cell_types_tp = pytest.mark.parametrize( + "cell_type", [CellType.quadrilateral, CellType.hexahedron] +) parametrize_cell_types_quad = pytest.mark.parametrize("cell_type", [CellType.quadrilateral]) parametrize_cell_types_hex = pytest.mark.parametrize("cell_type", [CellType.hexahedron]) diff --git a/python/test/unit/fem/test_forms.py b/python/test/unit/fem/test_forms.py index 5c490c0ca7c..30e4b0c9932 100644 --- a/python/test/unit/fem/test_forms.py +++ b/python/test/unit/fem/test_forms.py @@ -29,23 +29,20 @@ def test_extract_forms(): v2, u2 = TestFunction(V2), TrialFunction(V2) v3, u3 = TestFunction(V3), TrialFunction(V3) - a = form([[inner(u0, v0) * dx, inner(u1, v1) * dx], - [inner(u2, v2) * dx, inner(u3, v3) * dx]]) + a = form([[inner(u0, v0) * dx, inner(u1, v1) * dx], [inner(u2, v2) * dx, inner(u3, v3) * dx]]) with pytest.raises(AssertionError): extract_function_spaces(a, 0) with pytest.raises(AssertionError): extract_function_spaces(a, 1) - a = form([[inner(u0, v0) * dx, inner(u2, v1) * dx], - [inner(u0, v2) * dx, inner(u2, v2) * dx]]) + a = form([[inner(u0, v0) * dx, inner(u2, v1) * dx], [inner(u0, v2) * dx, inner(u2, v2) * dx]]) with pytest.raises(AssertionError): extract_function_spaces(a, 0) Vc = extract_function_spaces(a, 1) assert Vc[0] is V0._cpp_object assert Vc[1] is V2._cpp_object - a = form([[inner(u0, v0) * dx, inner(u1, v0) * dx], - [inner(u2, v1) * dx, inner(u3, v1) * dx]]) + a = form([[inner(u0, v0) * dx, inner(u1, v0) * dx], [inner(u2, v1) * dx, inner(u3, v1) * dx]]) Vr = extract_function_spaces(a, 0) assert Vr[0] is V0._cpp_object assert Vr[1] is V1._cpp_object diff --git a/python/test/unit/fem/test_function.py b/python/test/unit/fem/test_function.py index 2327c54c1a0..32768cbcd84 100644 --- a/python/test/unit/fem/test_function.py +++ b/python/test/unit/fem/test_function.py @@ -28,19 +28,19 @@ def mesh(): @pytest.fixture def V(mesh): - return functionspace(mesh, ('Lagrange', 1)) + return functionspace(mesh, ("Lagrange", 1)) @pytest.fixture def W(mesh): gdim = mesh.geometry.dim - return functionspace(mesh, ('Lagrange', 1, (gdim,))) + return functionspace(mesh, ("Lagrange", 1, (gdim,))) @pytest.fixture def Q(mesh): gdim = mesh.geometry.dim - return functionspace(mesh, ('Lagrange', 1, (gdim, gdim))) + return functionspace(mesh, ("Lagrange", 1, (gdim, gdim))) def test_name_argument(W): @@ -101,12 +101,14 @@ def e3(x): @pytest.mark.skip_in_parallel def test_eval_manifold(): # Simple two-triangle surface in 3d - vertices = np.array([(0.0, 0.0, 1.0), - (1.0, 1.0, 1.0), - (1.0, 0.0, 0.0), - (0.0, 1.0, 0.0)], dtype=default_real_type) + vertices = np.array( + [(0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], + dtype=default_real_type, + ) cells = [(0, 1, 2), (0, 1, 3)] - domain = ufl.Mesh(element("Lagrange", "triangle", 1, gdim=3, shape=(2,), dtype=default_real_type)) + domain = ufl.Mesh( + element("Lagrange", "triangle", 1, gdim=3, shape=(2,), dtype=default_real_type) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, vertices, domain) Q = functionspace(mesh, ("Lagrange", 1)) u = Function(Q) @@ -178,7 +180,7 @@ def f(x): @pytest.mark.parametrize("dtype,cdtype", [(np.float32, "float"), (np.float64, "double")]) def test_cffi_expression(dtype, cdtype): mesh = create_unit_cube(MPI.COMM_WORLD, 3, 3, 3, dtype=dtype) - V = functionspace(mesh, ('Lagrange', 1)) + V = functionspace(mesh, ("Lagrange", 1)) code_h = f"void eval({cdtype}* values, int num_points, int value_size, const {cdtype}* x);" code_c = """ diff --git a/python/test/unit/fem/test_function_space.py b/python/test/unit/fem/test_function_space.py index 1d173b50d61..4179edbe4c0 100644 --- a/python/test/unit/fem/test_function_space.py +++ b/python/test/unit/fem/test_function_space.py @@ -25,19 +25,19 @@ def mesh(): @pytest.fixture def V(mesh): - return functionspace(mesh, ('Lagrange', 1)) + return functionspace(mesh, ("Lagrange", 1)) @pytest.fixture def W(mesh): gdim = mesh.geometry.dim - return functionspace(mesh, ('Lagrange', 1, (gdim,))) + return functionspace(mesh, ("Lagrange", 1, (gdim,))) @pytest.fixture def Q(mesh): - W = element('Lagrange', mesh.basix_cell(), 1, shape=(mesh.geometry.dim,)) - V = element('Lagrange', mesh.basix_cell(), 1) + W = element("Lagrange", mesh.basix_cell(), 1, shape=(mesh.geometry.dim,)) + V = element("Lagrange", mesh.basix_cell(), 1) return functionspace(mesh, mixed_element([W, V])) @@ -100,14 +100,20 @@ def test_sub(Q, W): assert W.dofmap.dof_layout.num_dofs == X.dofmap.dof_layout.num_dofs for dim, entity_count in enumerate([4, 6, 4, 1]): assert W.dofmap.dof_layout.num_entity_dofs(dim) == X.dofmap.dof_layout.num_entity_dofs(dim) - assert W.dofmap.dof_layout.num_entity_closure_dofs(dim) == X.dofmap.dof_layout.num_entity_closure_dofs(dim) + assert W.dofmap.dof_layout.num_entity_closure_dofs( + dim + ) == X.dofmap.dof_layout.num_entity_closure_dofs(dim) for i in range(entity_count): - assert len(W.dofmap.dof_layout.entity_dofs(dim, i)) \ - == len(X.dofmap.dof_layout.entity_dofs(dim, i)) \ + assert ( + len(W.dofmap.dof_layout.entity_dofs(dim, i)) + == len(X.dofmap.dof_layout.entity_dofs(dim, i)) == len(X.dofmap.dof_layout.entity_dofs(dim, 0)) - assert len(W.dofmap.dof_layout.entity_closure_dofs(dim, i)) \ - == len(X.dofmap.dof_layout.entity_closure_dofs(dim, i)) \ + ) + assert ( + len(W.dofmap.dof_layout.entity_closure_dofs(dim, i)) + == len(X.dofmap.dof_layout.entity_closure_dofs(dim, i)) == len(X.dofmap.dof_layout.entity_closure_dofs(dim, 0)) + ) assert W.dofmap.dof_layout.block_size == X.dofmap.dof_layout.block_size assert W.dofmap.bs * len(W.dofmap.cell_dofs(0)) == len(X.dofmap.cell_dofs(0)) @@ -169,7 +175,7 @@ def test_collapse(W, V): bs = W.dofmap.index_map_bs for c in range(num_cells): cell_dofs = W.dofmap.cell_dofs(c) - for (i, dof) in enumerate(cell_dofs): + for i, dof in enumerate(cell_dofs): for k in range(bs): new_dof = Ws[k][0].dofmap.cell_dofs(c)[i] new_to_old = Ws[k][1] @@ -241,7 +247,9 @@ def test_cell_mismatch(mesh): def test_basix_element(V, W, Q, V2): for V_ in (V, W, V2): e = V_.element.basix_element - assert isinstance(e, (basix._basixcpp.FiniteElement_float64, basix._basixcpp.FiniteElement_float32)) + assert isinstance( + e, (basix._basixcpp.FiniteElement_float64, basix._basixcpp.FiniteElement_float32) + ) # Mixed spaces do not yet return a basix element with pytest.raises(RuntimeError): @@ -259,20 +267,21 @@ def test_vector_function_space_cell_type(): cell = Cell("interval", geometric_dimension=gdim) domain = Mesh(element("Lagrange", "interval", 1, gdim=gdim, shape=(1,))) cells = np.array([[0, 1]], dtype=np.int64) - x = np.array([[0., 0.], [1., 1.]]) + x = np.array([[0.0, 0.0], [1.0, 1.0]]) mesh = create_mesh(comm, cells, x, domain) # Create functions space over mesh, and check element cell # is correct - V = functionspace(mesh, ('Lagrange', 1, (gdim,))) + V = functionspace(mesh, ("Lagrange", 1, (gdim,))) assert V.ufl_element().cell == cell @pytest.mark.skip_in_parallel def test_manifold_spaces(): - vertices = np.array([ - (0.0, 0.0, 1.0), (1.0, 1.0, 1.0), - (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], dtype=default_real_type) + vertices = np.array( + [(0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], + dtype=default_real_type, + ) cells = [(0, 1, 2), (0, 1, 3)] domain = Mesh(element("Lagrange", "triangle", 1, gdim=3, shape=(2,), dtype=default_real_type)) mesh = create_mesh(MPI.COMM_WORLD, cells, vertices, domain) diff --git a/python/test/unit/fem/test_ghost_mesh_assembly.py b/python/test/unit/fem/test_ghost_mesh_assembly.py index e94514c30e1..61b35bbcfd4 100644 --- a/python/test/unit/fem/test_ghost_mesh_assembly.py +++ b/python/test/unit/fem/test_ghost_mesh_assembly.py @@ -29,10 +29,17 @@ def dS_from_ufl(mesh): return ufl.dS -@pytest.mark.parametrize("mode", - [GhostMode.none, GhostMode.shared_facet, - pytest.param(GhostMode.shared_vertex, - marks=pytest.mark.xfail(reason="Shared vertex currently disabled"))]) +@pytest.mark.parametrize( + "mode", + [ + GhostMode.none, + GhostMode.shared_facet, + pytest.param( + GhostMode.shared_vertex, + marks=pytest.mark.xfail(reason="Shared vertex currently disabled"), + ), + ], +) @pytest.mark.parametrize("dx", [dx_from_ufl]) @pytest.mark.parametrize("ds", [ds_from_ufl]) def test_ghost_mesh_assembly(mode, dx, ds): @@ -56,19 +63,28 @@ def test_ghost_mesh_assembly(mode, dx, ds): # Check that the norms are the same for all three modes normA = np.sqrt(A.squared_norm()) - assert normA == pytest.approx(0.6713621455570528, rel=1.e-5, abs=1.e-8) + assert normA == pytest.approx(0.6713621455570528, rel=1.0e-5, abs=1.0e-8) normb = b.norm() - assert normb == pytest.approx(1.582294032953906, rel=1.e-5, abs=1.e-8) - - -@pytest.mark.parametrize("mode", - [GhostMode.shared_facet, - pytest.param(GhostMode.none, - marks=pytest.mark.skipif(condition=MPI.COMM_WORLD.size > 1, - reason="Unghosted interior facets fail in parallel")), - pytest.param(GhostMode.shared_vertex, - marks=pytest.mark.xfail( - reason="Shared vertex currently disabled"))]) + assert normb == pytest.approx(1.582294032953906, rel=1.0e-5, abs=1.0e-8) + + +@pytest.mark.parametrize( + "mode", + [ + GhostMode.shared_facet, + pytest.param( + GhostMode.none, + marks=pytest.mark.skipif( + condition=MPI.COMM_WORLD.size > 1, + reason="Unghosted interior facets fail in parallel", + ), + ), + pytest.param( + GhostMode.shared_vertex, + marks=pytest.mark.xfail(reason="Shared vertex currently disabled"), + ), + ], +) @pytest.mark.parametrize("dS", [dS_from_ufl]) def test_ghost_mesh_dS_assembly(mode, dS): mesh = create_unit_square(MPI.COMM_WORLD, 12, 12, ghost_mode=mode) @@ -84,4 +100,4 @@ def test_ghost_mesh_dS_assembly(mode, dS): # Check that the norms are the same for all three modes normA = np.sqrt(A.squared_norm()) - assert normA == pytest.approx(2.1834054713561906, rel=1.e-5, abs=1.e-12) + assert normA == pytest.approx(2.1834054713561906, rel=1.0e-5, abs=1.0e-12) diff --git a/python/test/unit/fem/test_interpolation.py b/python/test/unit/fem/test_interpolation.py index 7d2ecd65400..98cc689cdda 100644 --- a/python/test/unit/fem/test_interpolation.py +++ b/python/test/unit/fem/test_interpolation.py @@ -16,20 +16,36 @@ import ufl from basix.ufl import blocked_element, custom_element, element, enriched_element, mixed_element from dolfinx import default_real_type -from dolfinx.fem import (Expression, Function, assemble_scalar, create_nonmatching_meshes_interpolation_data, form, - functionspace) +from dolfinx.fem import ( + Expression, + Function, + assemble_scalar, + create_nonmatching_meshes_interpolation_data, + form, + functionspace, +) from dolfinx.geometry import bb_tree, compute_collisions_points -from dolfinx.mesh import (CellType, create_mesh, create_rectangle, create_unit_cube, create_unit_square, - locate_entities, locate_entities_boundary, meshtags) +from dolfinx.mesh import ( + CellType, + create_mesh, + create_rectangle, + create_unit_cube, + create_unit_square, + locate_entities, + locate_entities_boundary, + meshtags, +) parametrize_cell_types = pytest.mark.parametrize( - "cell_type", [ + "cell_type", + [ CellType.interval, CellType.triangle, CellType.tetrahedron, CellType.quadrilateral, - CellType.hexahedron - ]) + CellType.hexahedron, + ], +) def random_point_in_reference(cell_type): @@ -64,7 +80,7 @@ def random_point_in_cell(mesh): if cell_type == CellType.interval: origin = mesh.geometry.x[0] - axes = (mesh.geometry.x[1], ) + axes = (mesh.geometry.x[1],) elif cell_type == CellType.triangle: origin = mesh.geometry.x[0] axes = (mesh.geometry.x[1], mesh.geometry.x[2]) @@ -78,22 +94,39 @@ def random_point_in_cell(mesh): origin = mesh.geometry.x[0] axes = (mesh.geometry.x[1], mesh.geometry.x[2], mesh.geometry.x[4]) - return tuple(origin[i] + sum((axis[i] - origin[i]) * p for axis, p in zip(axes, point)) for i in range(3)) + return tuple( + origin[i] + sum((axis[i] - origin[i]) * p for axis, p in zip(axes, point)) for i in range(3) + ) def one_cell_mesh(cell_type): if cell_type == CellType.interval: - points = np.array([[-1.], [2.]], dtype=default_real_type) + points = np.array([[-1.0], [2.0]], dtype=default_real_type) if cell_type == CellType.triangle: - points = np.array([[-1., -1.], [2., 0.], [0., 0.5]], dtype=default_real_type) + points = np.array([[-1.0, -1.0], [2.0, 0.0], [0.0, 0.5]], dtype=default_real_type) elif cell_type == CellType.tetrahedron: - points = np.array([[-1., -1., -1.], [2., 0., 0.], [0., 0.5, 0.], [0., 0., 1.]], dtype=default_real_type) + points = np.array( + [[-1.0, -1.0, -1.0], [2.0, 0.0, 0.0], [0.0, 0.5, 0.0], [0.0, 0.0, 1.0]], + dtype=default_real_type, + ) elif cell_type == CellType.quadrilateral: - points = np.array([[-1., 0.], [1., 0.], [-1., 1.5], [1., 1.5]], dtype=default_real_type) + points = np.array( + [[-1.0, 0.0], [1.0, 0.0], [-1.0, 1.5], [1.0, 1.5]], dtype=default_real_type + ) elif cell_type == CellType.hexahedron: - points = np.array([[-1., -0.5, 0.], [1., -0.5, 0.], [-1., 1.5, 0.], - [1., 1.5, 0.], [0., -0.5, 1.], [1., -0.5, 1.], - [-1., 1.5, 1.], [1., 1.5, 1.]], dtype=default_real_type) + points = np.array( + [ + [-1.0, -0.5, 0.0], + [1.0, -0.5, 0.0], + [-1.0, 1.5, 0.0], + [1.0, 1.5, 0.0], + [0.0, -0.5, 1.0], + [1.0, -0.5, 1.0], + [-1.0, 1.5, 1.0], + [1.0, 1.5, 1.0], + ], + dtype=default_real_type, + ) num_points = len(points) # Randomly number the points and create the mesh @@ -103,42 +136,71 @@ def one_cell_mesh(cell_type): for i, j in enumerate(order): ordered_points[j] = points[i] cells = np.array([order]) - domain = ufl.Mesh(element("Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), - dtype=default_real_type)) + domain = ufl.Mesh( + element( + "Lagrange", cell_type.name, 1, shape=(ordered_points.shape[1],), dtype=default_real_type + ) + ) return create_mesh(MPI.COMM_WORLD, cells, ordered_points, domain) def two_cell_mesh(cell_type): if cell_type == CellType.interval: - points = np.array([[0.], [1.], [-1.]], dtype=default_real_type) + points = np.array([[0.0], [1.0], [-1.0]], dtype=default_real_type) cells = [[0, 1], [0, 2]] if cell_type == CellType.triangle: # Define equilateral triangles with area 1 - root = 3 ** 0.25 # 4th root of 3 - points = np.array([[0., 0.], [2 / root, 0.], [1 / root, root], [1 / root, -root]], dtype=default_real_type) + root = 3**0.25 # 4th root of 3 + points = np.array( + [[0.0, 0.0], [2 / root, 0.0], [1 / root, root], [1 / root, -root]], + dtype=default_real_type, + ) cells = [[0, 1, 2], [1, 0, 3]] elif cell_type == CellType.tetrahedron: # Define regular tetrahedra with volume 1 - s = 2 ** 0.5 * 3 ** (1 / 3) # side length - points = np.array([[0., 0., 0.], [s, 0., 0.], - [s / 2, s * np.sqrt(3) / 2, 0.], - [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)], - [s / 2, s / 2 / np.sqrt(3), -s * np.sqrt(2 / 3)]], dtype=default_real_type) + s = 2**0.5 * 3 ** (1 / 3) # side length + points = np.array( + [ + [0.0, 0.0, 0.0], + [s, 0.0, 0.0], + [s / 2, s * np.sqrt(3) / 2, 0.0], + [s / 2, s / 2 / np.sqrt(3), s * np.sqrt(2 / 3)], + [s / 2, s / 2 / np.sqrt(3), -s * np.sqrt(2 / 3)], + ], + dtype=default_real_type, + ) cells = [[0, 1, 2, 3], [0, 2, 1, 4]] elif cell_type == CellType.quadrilateral: # Define unit quadrilaterals (area 1) - points = np.array([[0., 0.], [1., 0.], [0., 1.], [1., 1.], [0., -1.], [1., -1.]], dtype=default_real_type) + points = np.array( + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.0, -1.0], [1.0, -1.0]], + dtype=default_real_type, + ) cells = [[0, 1, 2, 3], [5, 1, 4, 0]] elif cell_type == CellType.hexahedron: # Define unit hexahedra (volume 1) - points = np.array([[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], - [1., 1., 0.], [0., 0., 1.], [1., 0., 1.], - [0., 1., 1.], [1., 1., 1.], [0., 0., -1.], - [1., 0., -1.], [0., 1., -1.], [1., 1., -1.]], dtype=default_real_type) + points = np.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [0.0, 1.0, 1.0], + [1.0, 1.0, 1.0], + [0.0, 0.0, -1.0], + [1.0, 0.0, -1.0], + [0.0, 1.0, -1.0], + [1.0, 1.0, -1.0], + ], + dtype=default_real_type, + ) cells = [[0, 1, 2, 3, 4, 5, 6, 7], [9, 11, 8, 10, 1, 3, 0, 2]] - domain = ufl.Mesh(element("Lagrange", cell_type.name, 1, shape=(points.shape[1],), - dtype=default_real_type)) + domain = ufl.Mesh( + element("Lagrange", cell_type.name, 1, shape=(points.shape[1],), dtype=default_real_type) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) return mesh @@ -148,14 +210,19 @@ def run_scalar_test(V, poly_order): random.seed(13) tdim = V.mesh.topology.dim if tdim == 1: + def f(x): return x[0] ** poly_order elif tdim == 2: + def f(x): return x[1] ** poly_order + 2 * x[0] ** min(poly_order, 1) else: + def f(x): - return x[1] ** poly_order + 2 * x[0] ** min(poly_order, 1) - 3 * x[2] ** min(poly_order, 2) + return ( + x[1] ** poly_order + 2 * x[0] ** min(poly_order, 1) - 3 * x[2] ** min(poly_order, 2) + ) v = Function(V) v.interpolate(f) @@ -173,14 +240,21 @@ def run_vector_test(V, poly_order): tdim = V.mesh.topology.dim if tdim == 1: + def f(x): return x[0] ** poly_order elif tdim == 2: + def f(x): return (x[1] ** min(poly_order, 1), 2 * x[0] ** poly_order) else: + def f(x): - return (x[1] ** min(poly_order, 1), 2 * x[0] ** poly_order, 3 * x[2] ** min(poly_order, 2)) + return ( + x[1] ** min(poly_order, 1), + 2 * x[0] ** poly_order, + 3 * x[2] ** min(poly_order, 2), + ) v = Function(V) v.interpolate(f) @@ -202,7 +276,9 @@ def test_Lagrange_interpolation(cell_type, order): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("cell_type", [CellType.interval, CellType.quadrilateral, CellType.hexahedron]) +@pytest.mark.parametrize( + "cell_type", [CellType.interval, CellType.quadrilateral, CellType.hexahedron] +) @pytest.mark.parametrize("order", range(1, 5)) def test_serendipity_interpolation(cell_type, order): """Test that interpolation is correct in a function space""" @@ -213,7 +289,7 @@ def test_serendipity_interpolation(cell_type, order): @pytest.mark.skip_in_parallel @parametrize_cell_types -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) def test_vector_interpolation(cell_type, order): """Test that interpolation is correct in a blocked (vector) function space.""" mesh = one_cell_mesh(cell_type) @@ -391,7 +467,9 @@ def test_interpolation_n2curl_to_bdm(tdim, order): u, v = Function(V), Function(V1) u.interpolate(lambda x: x[:tdim] ** order) v.interpolate(u) - assert assemble_scalar(form(ufl.inner(u - v, u - v) * ufl.dx)) == pytest.approx(0.0, abs=1.0e-10) + assert assemble_scalar(form(ufl.inner(u - v, u - v) * ufl.dx)) == pytest.approx( + 0.0, abs=1.0e-10 + ) @pytest.mark.parametrize("order1", [1, 2, 3, 4, 5]) @@ -431,13 +509,38 @@ def test_interpolation_vector_elements(order1, order2): @pytest.mark.skip_in_parallel def test_interpolation_non_affine(): - points = np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [1, 2, 0], - [0, 0, 3], [1, 0, 3], [0, 2, 3], [1, 2, 3], - [0.5, 0, 0], [0, 1, 0], [0, 0, 1.5], [1, 1, 0], - [1, 0, 1.5], [0.5, 2, 0], [0, 2, 1.5], [1, 2, 1.5], - [0.5, 0, 3], [0, 1, 3], [1, 1, 3], [0.5, 2, 3], - [0.5, 1, 0], [0.5, 0, 1.5], [0, 1, 1.5], [1, 1, 1.5], - [0.5, 2, 1.5], [0.5, 1, 3], [0.5, 1, 1.5]], dtype=default_real_type) + points = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 3], + [1, 0, 3], + [0, 2, 3], + [1, 2, 3], + [0.5, 0, 0], + [0, 1, 0], + [0, 0, 1.5], + [1, 1, 0], + [1, 0, 1.5], + [0.5, 2, 0], + [0, 2, 1.5], + [1, 2, 1.5], + [0.5, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0.5, 2, 3], + [0.5, 1, 0], + [0.5, 0, 1.5], + [0, 1, 1.5], + [1, 1, 1.5], + [0.5, 2, 1.5], + [0.5, 1, 3], + [0.5, 1, 1.5], + ], + dtype=default_real_type, + ) cells = np.array([range(len(points))], dtype=np.int32) domain = ufl.Mesh(element("Lagrange", "hexahedron", 2, shape=(3,), dtype=default_real_type)) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) @@ -451,13 +554,38 @@ def test_interpolation_non_affine(): @pytest.mark.skip_in_parallel def test_interpolation_non_affine_nonmatching_maps(): - points = np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [1, 2, 0], - [0, 0, 3], [1, 0, 3], [0, 2, 3], [1, 2, 3], - [0.5, 0, 0], [0, 1, 0], [0, 0, 1.5], [1, 1, 0], - [1, 0, 1.5], [0.5, 2, 0], [0, 2, 1.5], [1, 2, 1.5], - [0.5, 0, 3], [0, 1, 3], [1, 1, 3], [0.5, 2, 3], - [0.5, 1, 0], [0.5, -0.1, 1.5], [0, 1, 1.5], [1, 1, 1.5], - [0.5, 2, 1.5], [0.5, 1, 3], [0.5, 1, 1.5]], dtype=default_real_type) + points = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 3], + [1, 0, 3], + [0, 2, 3], + [1, 2, 3], + [0.5, 0, 0], + [0, 1, 0], + [0, 0, 1.5], + [1, 1, 0], + [1, 0, 1.5], + [0.5, 2, 0], + [0, 2, 1.5], + [1, 2, 1.5], + [0.5, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0.5, 2, 3], + [0.5, 1, 0], + [0.5, -0.1, 1.5], + [0, 1, 1.5], + [1, 1, 1.5], + [0.5, 2, 1.5], + [0.5, 1, 3], + [0.5, 1, 1.5], + ], + dtype=default_real_type, + ) cells = np.array([range(len(points))], dtype=np.int32) domain = ufl.Mesh(element("Lagrange", "hexahedron", 2, shape=(3,), dtype=default_real_type)) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) @@ -487,7 +615,9 @@ def test_nedelec_spatial(order, dim): f_ex = x f = Expression(f_ex, V.element.interpolation_points()) u.interpolate(f) - assert np.abs(assemble_scalar(form(ufl.inner(u - f_ex, u - f_ex) * ufl.dx))) == pytest.approx(0, abs=1e-10) + assert np.abs(assemble_scalar(form(ufl.inner(u - f_ex, u - f_ex) * ufl.dx))) == pytest.approx( + 0, abs=1e-10 + ) # The target expression is also contained in N2curl space of any # order @@ -514,7 +644,7 @@ def test_vector_interpolation_spatial(order, dim, affine): x = ufl.SpatialCoordinate(mesh) # The expression (x,y,z)^n is contained in space - f = ufl.as_vector([x[i]**order for i in range(dim)]) + f = ufl.as_vector([x[i] ** order for i in range(dim)]) u.interpolate(Expression(f, V.element.interpolation_points())) assert np.abs(assemble_scalar(form(ufl.inner(u - f, u - f) * ufl.dx))) == pytest.approx(0) @@ -540,24 +670,28 @@ def test_de_rahm_2D(order): mesh = create_unit_square(MPI.COMM_WORLD, 3, 4) W = functionspace(mesh, ("Lagrange", order)) w = Function(W) - w.interpolate(lambda x: x[0] + x[0] * x[1] + 2 * x[1]**2) + w.interpolate(lambda x: x[0] + x[0] * x[1] + 2 * x[1] ** 2) g = ufl.grad(w) Q = functionspace(mesh, ("N2curl", order - 1)) q = Function(Q) q.interpolate(Expression(g, Q.element.interpolation_points())) x = ufl.SpatialCoordinate(mesh) g_ex = ufl.as_vector((1 + x[1], 4 * x[1] + x[0])) - assert np.abs(assemble_scalar(form(ufl.inner(q - g_ex, q - g_ex) * ufl.dx))) == pytest.approx(0, abs=1e-10) + assert np.abs(assemble_scalar(form(ufl.inner(q - g_ex, q - g_ex) * ufl.dx))) == pytest.approx( + 0, abs=1e-10 + ) V = functionspace(mesh, ("BDM", order - 1)) v = Function(V) def curl2D(u): - return ufl.as_vector((ufl.Dx(u[1], 0), - ufl.Dx(u[0], 1))) + return ufl.as_vector((ufl.Dx(u[1], 0), -ufl.Dx(u[0], 1))) v.interpolate(Expression(curl2D(ufl.grad(w)), V.element.interpolation_points())) h_ex = ufl.as_vector((1, -1)) - assert np.abs(assemble_scalar(form(ufl.inner(v - h_ex, v - h_ex) * ufl.dx))) == pytest.approx(0, abs=1.0e-6) + assert np.abs(assemble_scalar(form(ufl.inner(v - h_ex, v - h_ex) * ufl.dx))) == pytest.approx( + 0, abs=1.0e-6 + ) @pytest.mark.parametrize("order", [1, 2, 3, 4]) @@ -579,17 +713,17 @@ def test_interpolate_subset(order, dim, affine, callable_): num_local_cells = mesh.topology.index_map(mesh.topology.dim).size_local cells_local = cells[cells < num_local_cells] x = ufl.SpatialCoordinate(mesh) - f = x[1]**order + f = x[1] ** order if not callable_: expr = Expression(f, V.element.interpolation_points()) u.interpolate(expr, cells_local) else: - u.interpolate(lambda x: x[1]**order, cells_local) + u.interpolate(lambda x: x[1] ** order, cells_local) mt = meshtags(mesh, mesh.topology.dim, cells_local, np.ones(cells_local.size, dtype=np.int32)) dx = ufl.Measure("dx", domain=mesh, subdomain_data=mt) assert np.abs(form(assemble_scalar(form(ufl.inner(u - f, u - f) * dx(1))))) == pytest.approx(0) integral = mesh.comm.allreduce(assemble_scalar(form(u * dx)), op=MPI.SUM) - assert integral == pytest.approx(1 / (order + 1) * 0.5**(order + 1), abs=1.0e-6) + assert integral == pytest.approx(1 / (order + 1) * 0.5 ** (order + 1), abs=1.0e-6) def test_interpolate_callable(): @@ -599,7 +733,7 @@ def test_interpolate_callable(): V = functionspace(mesh, ("Lagrange", 2)) u0, u1 = Function(V), Function(V) - @ numba.njit + @numba.njit def f(x): return x[0] @@ -627,23 +761,28 @@ def test_interpolate_callable_subset(bound): assert np.allclose(u0.x.array, u1.x.array, rtol=1.0e-6, atol=1.0e-6) -@pytest.mark.parametrize("scalar_element", [ - element("P", "triangle", 1), - element("P", "triangle", 2), - element("P", "triangle", 3), - element("Q", "quadrilateral", 1), - element("Q", "quadrilateral", 2), - element("Q", "quadrilateral", 3), - element("S", "quadrilateral", 1), - element("S", "quadrilateral", 2), - element("S", "quadrilateral", 3), - enriched_element([element("P", "triangle", 1), element("Bubble", "triangle", 3)]), - enriched_element([element("P", "quadrilateral", 1), element("Bubble", "quadrilateral", 2)]), -]) +@pytest.mark.parametrize( + "scalar_element", + [ + element("P", "triangle", 1), + element("P", "triangle", 2), + element("P", "triangle", 3), + element("Q", "quadrilateral", 1), + element("Q", "quadrilateral", 2), + element("Q", "quadrilateral", 3), + element("S", "quadrilateral", 1), + element("S", "quadrilateral", 2), + element("S", "quadrilateral", 3), + enriched_element([element("P", "triangle", 1), element("Bubble", "triangle", 3)]), + enriched_element([element("P", "quadrilateral", 1), element("Bubble", "quadrilateral", 2)]), + ], +) def test_vector_element_interpolation(scalar_element): """Test interpolation into a range of vector elements.""" - mesh = create_unit_square(MPI.COMM_WORLD, 10, 10, getattr(CellType, scalar_element.cell.cellname())) - V = functionspace(mesh, blocked_element(scalar_element, shape=(2, ))) + mesh = create_unit_square( + MPI.COMM_WORLD, 10, 10, getattr(CellType, scalar_element.cell.cellname()) + ) + V = functionspace(mesh, blocked_element(scalar_element, shape=(2,))) u = Function(V) u.interpolate(lambda x: (x[0], x[1])) u2 = Function(V) @@ -657,20 +796,31 @@ def test_custom_vector_element(): mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) wcoeffs = np.eye(6) x = [[], [], [], []] - x[0].append(np.array([[0., 0.]])) - x[0].append(np.array([[1., 0.]])) - x[0].append(np.array([[0., 1.]])) + x[0].append(np.array([[0.0, 0.0]])) + x[0].append(np.array([[1.0, 0.0]])) + x[0].append(np.array([[0.0, 1.0]])) for _ in range(3): x[1].append(np.zeros((0, 2))) x[2].append(np.zeros((0, 2))) M = [[], [], [], []] for _ in range(3): - M[0].append(np.array([[[[1.]], [[0.]]], [[[0.]], [[1.]]]])) + M[0].append(np.array([[[[1.0]], [[0.0]]], [[[0.0]], [[1.0]]]])) for _ in range(3): M[1].append(np.zeros((0, 2, 0, 1))) M[2].append(np.zeros((0, 2, 0, 1))) - e = custom_element(basix.CellType.triangle, [2], wcoeffs, x, M, 0, basix.MapType.identity, - basix.SobolevSpace.H1, False, 1, 1) + e = custom_element( + basix.CellType.triangle, + [2], + wcoeffs, + x, + M, + 0, + basix.MapType.identity, + basix.SobolevSpace.H1, + False, + 1, + 1, + ) V = functionspace(mesh, e) gdim = mesh.geometry.dim @@ -722,9 +872,9 @@ def test_nonmatching_mesh_interpolation(xtype, cell_type0, cell_type1): def f(x): return (7 * x[1], 3 * x[0], x[2] + 0.4) - el0 = element("Lagrange", mesh0.basix_cell(), 1, shape=(3, )) + el0 = element("Lagrange", mesh0.basix_cell(), 1, shape=(3,)) V0 = functionspace(mesh0, el0) - el1 = element("Lagrange", mesh1.basix_cell(), 1, shape=(3, )) + el1 = element("Lagrange", mesh1.basix_cell(), 1, shape=(3,)) V1 = functionspace(mesh1, el1) # Interpolate on 3D mesh @@ -734,10 +884,15 @@ def f(x): padding = 1e-14 # Interpolate 3D->2D u1 = Function(V1, dtype=xtype) - u1.interpolate(u0, nmm_interpolation_data=create_nonmatching_meshes_interpolation_data( - u1.function_space.mesh._cpp_object, - u1.function_space.element, - u0.function_space.mesh._cpp_object, padding=padding)) + u1.interpolate( + u0, + nmm_interpolation_data=create_nonmatching_meshes_interpolation_data( + u1.function_space.mesh._cpp_object, + u1.function_space.element, + u0.function_space.mesh._cpp_object, + padding=padding, + ), + ) u1.x.scatter_forward() # Exact interpolation on 2D mesh @@ -749,17 +904,27 @@ def f(x): # Interpolate 2D->3D u0_2 = Function(V0, dtype=xtype) - u0_2.interpolate(u1, nmm_interpolation_data=create_nonmatching_meshes_interpolation_data( - u0_2.function_space.mesh._cpp_object, - u0_2.function_space.element, - u1.function_space.mesh._cpp_object, padding=padding)) + u0_2.interpolate( + u1, + nmm_interpolation_data=create_nonmatching_meshes_interpolation_data( + u0_2.function_space.mesh._cpp_object, + u0_2.function_space.element, + u1.function_space.mesh._cpp_object, + padding=padding, + ), + ) # Check that function values over facets of 3D mesh of the twice interpolated property is preserved def locate_bottom_facets(x): return np.isclose(x[2], 0) + facets = locate_entities_boundary(mesh0, mesh0.topology.dim - 1, locate_bottom_facets) - facet_tag = meshtags(mesh0, mesh0.topology.dim - 1, facets, np.full(len(facets), 1, dtype=np.int32)) - residual = ufl.inner(u0 - u0_2, u0 - u0_2) * ufl.ds(domain=mesh0, subdomain_data=facet_tag, subdomain_id=1) + facet_tag = meshtags( + mesh0, mesh0.topology.dim - 1, facets, np.full(len(facets), 1, dtype=np.int32) + ) + residual = ufl.inner(u0 - u0_2, u0 - u0_2) * ufl.ds( + domain=mesh0, subdomain_data=facet_tag, subdomain_id=1 + ) assert np.isclose(assemble_scalar(form(residual, dtype=xtype)), 0) @@ -770,13 +935,23 @@ def test_nonmatching_mesh_single_cell_overlap_interpolation(xtype): # Test interpolation from mesh1 to mesh2 n_mesh1 = 2 - mesh1 = create_rectangle(MPI.COMM_WORLD, [[0.0, 0.0], [1.0, 1.0]], [n_mesh1, n_mesh1], - cell_type=CellType.quadrilateral, dtype=xtype) + mesh1 = create_rectangle( + MPI.COMM_WORLD, + [[0.0, 0.0], [1.0, 1.0]], + [n_mesh1, n_mesh1], + cell_type=CellType.quadrilateral, + dtype=xtype, + ) n_mesh2 = 2 p0_mesh2 = 1.0 / n_mesh1 - mesh2 = create_rectangle(MPI.COMM_WORLD, [[0.0, 0.0], [p0_mesh2, p0_mesh2]], [n_mesh2, n_mesh2], - cell_type=CellType.triangle, dtype=xtype) + mesh2 = create_rectangle( + MPI.COMM_WORLD, + [[0.0, 0.0], [p0_mesh2, p0_mesh2]], + [n_mesh2, n_mesh2], + cell_type=CellType.triangle, + dtype=xtype, + ) u1 = Function(functionspace(mesh1, ("Lagrange", 1)), name="u1", dtype=xtype) u2 = Function(functionspace(mesh2, ("Lagrange", 1)), name="u2", dtype=xtype) @@ -790,7 +965,9 @@ def f_test1(x): u1_2_u2_nmm_data = create_nonmatching_meshes_interpolation_data( u2.function_space.mesh._cpp_object, u2.function_space.element, - u1.function_space.mesh._cpp_object, padding=padding) + u1.function_space.mesh._cpp_object, + padding=padding, + ) u2.interpolate(u1, nmm_interpolation_data=u1_2_u2_nmm_data) u2.x.scatter_forward() @@ -800,7 +977,7 @@ def f_test1(x): u2_exact.interpolate(f_test1) u2_exact.x.scatter_forward() - l2_error = assemble_scalar(form((u2 - u2_exact)**2 * ufl.dx, dtype=xtype)) + l2_error = assemble_scalar(form((u2 - u2_exact) ** 2 * ufl.dx, dtype=xtype)) assert np.isclose(l2_error, 0.0, rtol=np.finfo(xtype).eps, atol=np.finfo(xtype).eps) # Test interpolation from mesh2 to mesh1 @@ -813,11 +990,12 @@ def f_test2(x): u2.interpolate(f_test2) u2.x.scatter_forward() padding = 1e-14 - u2_2_u1_nmm_data = \ - create_nonmatching_meshes_interpolation_data( - u1.function_space.mesh._cpp_object, - u1.function_space.element, - u2.function_space.mesh._cpp_object, padding) + u2_2_u1_nmm_data = create_nonmatching_meshes_interpolation_data( + u1.function_space.mesh._cpp_object, + u1.function_space.element, + u2.function_space.mesh._cpp_object, + padding, + ) u1.interpolate(u2, nmm_interpolation_data=u2_2_u1_nmm_data) u1.x.scatter_forward() @@ -828,14 +1006,17 @@ def f_test2(x): # Find the single cell in mesh1 which is overlapped by mesh2 tree1 = bb_tree(mesh1, mesh1.topology.dim) - cells_overlapped1 = compute_collisions_points(tree1, np.array([p0_mesh2, p0_mesh2, 0.0]) / 2).array + cells_overlapped1 = compute_collisions_points( + tree1, np.array([p0_mesh2, p0_mesh2, 0.0]) / 2 + ).array assert cells_overlapped1.shape[0] <= 1 # Construct the error measure on the overlapped cell cell_label = 1 - cts = meshtags(mesh1, mesh1.topology.dim, cells_overlapped1, - np.full_like(cells_overlapped1, cell_label)) + cts = meshtags( + mesh1, mesh1.topology.dim, cells_overlapped1, np.full_like(cells_overlapped1, cell_label) + ) dx_cell = ufl.Measure("dx", subdomain_data=cts) - l2_error = assemble_scalar(form((u1 - u1_exact)**2 * dx_cell(cell_label), dtype=xtype)) + l2_error = assemble_scalar(form((u1 - u1_exact) ** 2 * dx_cell(cell_label), dtype=xtype)) assert np.isclose(l2_error, 0.0, rtol=np.finfo(xtype).eps, atol=np.finfo(xtype).eps) diff --git a/python/test/unit/fem/test_mixed_element.py b/python/test/unit/fem/test_mixed_element.py index 3c079425ffc..fe902dcab01 100644 --- a/python/test/unit/fem/test_mixed_element.py +++ b/python/test/unit/fem/test_mixed_element.py @@ -22,9 +22,13 @@ @pytest.mark.parametrize("rank, family", [(0, "Lagrange"), (1, "Lagrange"), (1, "N1curl")]) def test_mixed_element(rank, family, cell, degree): if cell == ufl.triangle: - mesh = create_unit_square(MPI.COMM_WORLD, 1, 1, CellType.triangle, ghost_mode=GhostMode.shared_facet) + mesh = create_unit_square( + MPI.COMM_WORLD, 1, 1, CellType.triangle, ghost_mode=GhostMode.shared_facet + ) else: - mesh = create_unit_cube(MPI.COMM_WORLD, 1, 1, 1, CellType.tetrahedron, ghost_mode=GhostMode.shared_facet) + mesh = create_unit_cube( + MPI.COMM_WORLD, 1, 1, 1, CellType.tetrahedron, ghost_mode=GhostMode.shared_facet + ) shape = (mesh.geometry.dim,) * rank norms = [] @@ -48,8 +52,9 @@ def test_mixed_element(rank, family, cell, degree): @pytest.mark.skip_in_parallel def test_vector_element(): # Function space containing a scalar should work - mesh = create_unit_square(MPI.COMM_WORLD, 1, 1, CellType.triangle, - ghost_mode=GhostMode.shared_facet) + mesh = create_unit_square( + MPI.COMM_WORLD, 1, 1, CellType.triangle, ghost_mode=GhostMode.shared_facet + ) gdim = mesh.geometry.dim U = functionspace(mesh, ("P", 2, (gdim,))) u, v = ufl.TrialFunction(U), ufl.TestFunction(U) @@ -61,7 +66,7 @@ def test_vector_element(): # Function space containing a vector should throw an error rather # than segfaulting gdim = mesh.geometry.dim - U = functionspace(mesh, ("RT", 2, (gdim + 1, ))) + U = functionspace(mesh, ("RT", 2, (gdim + 1,))) u, v = ufl.TrialFunction(U), ufl.TestFunction(U) a = form(ufl.inner(u, v) * ufl.dx) A = dolfinx.fem.assemble_matrix(a) diff --git a/python/test/unit/fem/test_nonlinear_assembler.py b/python/test/unit/fem/test_nonlinear_assembler.py index f4917aa69e0..6d3fa858c10 100644 --- a/python/test/unit/fem/test_nonlinear_assembler.py +++ b/python/test/unit/fem/test_nonlinear_assembler.py @@ -16,12 +16,33 @@ import ufl from basix.ufl import element, mixed_element from dolfinx.cpp.la.petsc import scatter_local_vectors -from dolfinx.fem import (Function, bcs_by_block, dirichletbc, extract_function_spaces, form, functionspace, - locate_dofs_topological) -from dolfinx.fem.petsc import (apply_lifting, apply_lifting_nest, assemble_matrix, assemble_matrix_block, - assemble_matrix_nest, assemble_vector, assemble_vector_block, assemble_vector_nest, - create_matrix, create_matrix_block, create_matrix_nest, create_vector, - create_vector_block, create_vector_nest, set_bc, set_bc_nest) +from dolfinx.fem import ( + Function, + bcs_by_block, + dirichletbc, + extract_function_spaces, + form, + functionspace, + locate_dofs_topological, +) +from dolfinx.fem.petsc import ( + apply_lifting, + apply_lifting_nest, + assemble_matrix, + assemble_matrix_block, + assemble_matrix_nest, + assemble_vector, + assemble_vector_block, + assemble_vector_nest, + create_matrix, + create_matrix_block, + create_matrix_nest, + create_vector, + create_vector_block, + create_vector_nest, + set_bc, + set_bc_nest, +) from dolfinx.mesh import GhostMode, create_unit_cube, create_unit_square, locate_entities_boundary from ufl import derivative, dx, inner @@ -55,14 +76,15 @@ def initial_guess_u(x): return np.sin(x[0]) * np.sin(x[1]) def initial_guess_p(x): - return -x[0]**2 - x[1]**3 + return -(x[0] ** 2) - x[1] ** 3 def bc_value(x): return np.cos(x[0]) * np.cos(x[1]) facetdim = mesh.topology.dim - 1 - bndry_facets = locate_entities_boundary(mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))) + bndry_facets = locate_entities_boundary( + mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ) u_bc = Function(V1) u_bc.interpolate(bc_value) @@ -83,16 +105,25 @@ def bc_value(x): F0 = inner(u, v) * dx + inner(p, v) * dx - inner(f, v) * dx F1 = inner(u, q) * dx + inner(p, q) * dx - inner(g, q) * dx - a_block = form([[derivative(F0, u, du), derivative(F0, p, dp)], - [derivative(F1, u, du), derivative(F1, p, dp)]]) + a_block = form( + [ + [derivative(F0, u, du), derivative(F0, p, dp)], + [derivative(F1, u, du), derivative(F1, p, dp)], + ] + ) L_block = form([F0, F1]) def blocked(): """Monolithic blocked""" x = create_vector_block(L_block) - scatter_local_vectors(x, [u.vector.array_r, p.vector.array_r], - [(u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), - (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs)]) + scatter_local_vectors( + x, + [u.vector.array_r, p.vector.array_r], + [ + (u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), + (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs), + ], + ) x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) # Ghosts are updated inside assemble_vector_block @@ -113,7 +144,9 @@ def nested(): x = create_vector_nest(L_block) for x1_soln_pair in zip(x.getNestSubVecs(), (u, p)): x1_sub, soln_sub = x1_soln_pair - soln_sub.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) + soln_sub.vector.ghostUpdate( + addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD + ) soln_sub.vector.copy(result=x1_sub) x1_sub.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) @@ -146,8 +179,14 @@ def monolithic(): U.sub(0).interpolate(initial_guess_u) U.sub(1).interpolate(initial_guess_p) - F = inner(u0, v0) * dx + inner(u1, v0) * dx + inner(u0, v1) * dx + inner(u1, v1) * dx \ - - inner(f, v0) * ufl.dx - inner(g, v1) * dx + F = ( + inner(u0, v0) * dx + + inner(u1, v0) * dx + + inner(u0, v1) * dx + + inner(u1, v1) * dx + - inner(f, v0) * ufl.dx + - inner(g, v1) * dx + ) J = derivative(F, U, dU) F, J = form(F), form(J) @@ -215,7 +254,7 @@ def F_block(self, snes, x, F): x_array = x.getArray(readonly=True) for var in self.soln_vars: size_local = var.vector.getLocalSize() - var.vector.array[:] = x_array[offset: offset + size_local] + var.vector.array[:] = x_array[offset : offset + size_local] var.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) offset += size_local @@ -278,7 +317,7 @@ def test_assembly_solve_block_nl(): V1 = V0.clone() def bc_val_0(x): - return x[0]**2 + x[1]**2 + return x[0] ** 2 + x[1] ** 2 def bc_val_1(x): return np.sin(x[0]) * np.cos(x[1]) @@ -287,11 +326,12 @@ def initial_guess_u(x): return np.sin(x[0]) * np.sin(x[1]) def initial_guess_p(x): - return -x[0]**2 - x[1]**3 + return -(x[0] ** 2) - x[1] ** 3 facetdim = mesh.topology.dim - 1 - bndry_facets = locate_entities_boundary(mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))) + bndry_facets = locate_entities_boundary( + mesh, facetdim, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ) u_bc0 = Function(V0) u_bc0.interpolate(bc_val_0) @@ -307,10 +347,14 @@ def initial_guess_p(x): v, q = ufl.TestFunction(V0), ufl.TestFunction(V1) f, g = 1.0, -3.0 - F = [inner((u**2 + 1) * ufl.grad(u), ufl.grad(v)) * dx - inner(f, v) * dx, - inner((p**2 + 1) * ufl.grad(p), ufl.grad(q)) * dx - inner(g, q) * dx] - J = [[derivative(F[0], u, du), derivative(F[0], p, dp)], - [derivative(F[1], u, du), derivative(F[1], p, dp)]] + F = [ + inner((u**2 + 1) * ufl.grad(u), ufl.grad(v)) * dx - inner(f, v) * dx, + inner((p**2 + 1) * ufl.grad(p), ufl.grad(q)) * dx - inner(g, q) * dx, + ] + J = [ + [derivative(F[0], u, du), derivative(F[0], p, dp)], + [derivative(F[1], u, du), derivative(F[1], p, dp)], + ] F, J = form(F), form(J) def blocked_solve(): @@ -327,9 +371,14 @@ def blocked_solve(): p.interpolate(initial_guess_p) x = create_vector_block(F) - scatter_local_vectors(x, [u.vector.array_r, p.vector.array_r], - [(u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), - (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs)]) + scatter_local_vectors( + x, + [u.vector.array_r, p.vector.array_r], + [ + (u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), + (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs), + ], + ) x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) snes.solve(None, x) assert snes.getKSP().getConvergedReason() > 0 @@ -366,7 +415,9 @@ def nested_solve(): assert x.getType() == "nest" for x_soln_pair in zip(x.getNestSubVecs(), (u, p)): x_sub, soln_sub = x_soln_pair - soln_sub.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) + soln_sub.vector.ghostUpdate( + addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD + ) soln_sub.vector.copy(result=x_sub) x_sub.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) @@ -389,9 +440,12 @@ def monolithic_solve(): u0, u1 = ufl.split(U) v0, v1 = ufl.TestFunctions(W) - F = inner((u0**2 + 1) * ufl.grad(u0), ufl.grad(v0)) * dx \ - + inner((u1**2 + 1) * ufl.grad(u1), ufl.grad(v1)) * dx \ - - inner(f, v0) * ufl.dx - inner(g, v1) * dx + F = ( + inner((u0**2 + 1) * ufl.grad(u0), ufl.grad(v0)) * dx + + inner((u1**2 + 1) * ufl.grad(u1), ufl.grad(v1)) * dx + - inner(f, v0) * ufl.dx + - inner(g, v1) * dx + ) J = derivative(F, U, dU) F, J = form(F), form(J) @@ -433,18 +487,23 @@ def monolithic_solve(): norm2 = monolithic_solve() # FIXME: PETSc nested solver mis-behaves in parallel an single # precision. Investigate further. - if not ((PETSc.ScalarType == np.float32 or PETSc.ScalarType == np.complex64) and mesh.comm.size > 1): + if not ( + (PETSc.ScalarType == np.float32 or PETSc.ScalarType == np.complex64) and mesh.comm.size > 1 + ): norm1 = nested_solve() assert norm1 == pytest.approx(norm0, 1.0e-6) assert norm2 == pytest.approx(norm0, 1.0e-6) -@pytest.mark.parametrize("mesh", [ - create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.none), - create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.shared_facet), - create_unit_cube(MPI.COMM_WORLD, 3, 5, 4, ghost_mode=GhostMode.none), - create_unit_cube(MPI.COMM_WORLD, 3, 5, 4, ghost_mode=GhostMode.shared_facet) -]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.none), + create_unit_square(MPI.COMM_WORLD, 12, 11, ghost_mode=GhostMode.shared_facet), + create_unit_cube(MPI.COMM_WORLD, 3, 5, 4, ghost_mode=GhostMode.none), + create_unit_cube(MPI.COMM_WORLD, 3, 5, 4, ghost_mode=GhostMode.shared_facet), + ], +) def test_assembly_solve_taylor_hood_nl(mesh): """Assemble Stokes problem with Taylor-Hood elements and solve.""" gdim = mesh.geometry.dim @@ -460,14 +519,13 @@ def boundary1(x): return np.isclose(x[0], 1.0) def initial_guess_u(x): - u_init = np.row_stack((np.sin(x[0]) * np.sin(x[1]), - np.cos(x[0]) * np.cos(x[1]))) + u_init = np.row_stack((np.sin(x[0]) * np.sin(x[1]), np.cos(x[0]) * np.cos(x[1]))) if gdim == 3: u_init = np.row_stack((u_init, np.cos(x[2]))) return u_init def initial_guess_p(x): - return -x[0]**2 - x[1]**3 + return -(x[0] ** 2) - x[1] ** 3 u_bc_0 = Function(P2) u_bc_0.interpolate(lambda x: np.row_stack(tuple(x[j] + float(j) for j in range(gdim)))) @@ -488,12 +546,15 @@ def initial_guess_p(x): du, dp = ufl.TrialFunction(P2), ufl.TrialFunction(P1) v, q = ufl.TestFunction(P2), ufl.TestFunction(P1) - F = [inner(ufl.grad(u), ufl.grad(v)) * dx + inner(p, ufl.div(v)) * dx, - inner(ufl.div(u), q) * dx] - J = [[derivative(F[0], u, du), derivative(F[0], p, dp)], - [derivative(F[1], u, du), derivative(F[1], p, dp)]] - P = [[J[0][0], None], - [None, inner(dp, q) * dx]] + F = [ + inner(ufl.grad(u), ufl.grad(v)) * dx + inner(p, ufl.div(v)) * dx, + inner(ufl.div(u), q) * dx, + ] + J = [ + [derivative(F[0], u, du), derivative(F[0], p, dp)], + [derivative(F[1], u, du), derivative(F[1], p, dp)], + ] + P = [[J[0][0], None], [None, inner(dp, q) * dx]] F, J, P = form(F), form(J), form(P) def blocked(): @@ -514,9 +575,14 @@ def blocked(): p.interpolate(initial_guess_p) x = create_vector_block(F) with u.vector.localForm() as _u, p.vector.localForm() as _p: - scatter_local_vectors(x, [_u.array_r, _p.array_r], - [(u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), - (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs)]) + scatter_local_vectors( + x, + [_u.array_r, _p.array_r], + [ + (u.function_space.dofmap.index_map, u.function_space.dofmap.index_map_bs), + (p.function_space.dofmap.index_map, p.function_space.dofmap.index_map_bs), + ], + ) x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) snes.solve(None, x) @@ -553,7 +619,9 @@ def nested(): x = create_vector_nest(F) for x1_soln_pair in zip(x.getNestSubVecs(), (u, p)): x1_sub, soln_sub = x1_soln_pair - soln_sub.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) + soln_sub.vector.ghostUpdate( + addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD + ) soln_sub.vector.copy(result=x1_sub) x1_sub.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) @@ -581,14 +649,21 @@ def monolithic(): du, dp = ufl.split(dU) v, q = ufl.TestFunctions(W) - F = inner(ufl.grad(u), ufl.grad(v)) * dx + inner(p, ufl.div(v)) * dx + inner(ufl.div(u), q) * dx + F = ( + inner(ufl.grad(u), ufl.grad(v)) * dx + + inner(p, ufl.div(v)) * dx + + inner(ufl.div(u), q) * dx + ) J = derivative(F, U, dU) P = inner(ufl.grad(du), ufl.grad(v)) * dx + inner(dp, q) * dx F, J, P = form(F), form(J), form(P) bdofsW0_P2_0 = locate_dofs_topological((W.sub(0), P2), facetdim, bndry_facets0) bdofsW0_P2_1 = locate_dofs_topological((W.sub(0), P2), facetdim, bndry_facets1) - bcs = [dirichletbc(u_bc_0, bdofsW0_P2_0, W.sub(0)), dirichletbc(u_bc_1, bdofsW0_P2_1, W.sub(0))] + bcs = [ + dirichletbc(u_bc_0, bdofsW0_P2_0, W.sub(0)), + dirichletbc(u_bc_1, bdofsW0_P2_1, W.sub(0)), + ] Jmat = create_matrix(J) Pmat = create_matrix(P) diff --git a/python/test/unit/fem/test_petsc_discrete_operators.py b/python/test/unit/fem/test_petsc_discrete_operators.py index 1e6dcca40b3..391c44a7cd3 100644 --- a/python/test/unit/fem/test_petsc_discrete_operators.py +++ b/python/test/unit/fem/test_petsc_discrete_operators.py @@ -20,10 +20,15 @@ @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("mesh", [create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none), - create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.shared_facet), - create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.none), - create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.shared_facet)]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.none), + create_unit_square(MPI.COMM_WORLD, 11, 6, ghost_mode=GhostMode.shared_facet), + create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.none), + create_unit_cube(MPI.COMM_WORLD, 4, 3, 7, ghost_mode=GhostMode.shared_facet), + ], +) def test_gradient_petsc(mesh): """Test discrete gradient computation for lowest order elements.""" V = functionspace(mesh, ("Lagrange", 1)) @@ -41,27 +46,35 @@ def test_gradient_petsc(mesh): @pytest.mark.parametrize("p", range(1, 4)) @pytest.mark.parametrize("q", range(1, 4)) -@pytest.mark.parametrize("cell_type", [CellType.quadrilateral, - CellType.triangle, - CellType.tetrahedron, - CellType.hexahedron]) +@pytest.mark.parametrize( + "cell_type", + [CellType.quadrilateral, CellType.triangle, CellType.tetrahedron, CellType.hexahedron], +) def test_gradient_interpolation_petsc(cell_type, p, q): """Test discrete gradient computation with verification using Expression.""" comm = MPI.COMM_WORLD if cell_type == CellType.triangle: - mesh = create_unit_square(comm, 11, 6, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type) + mesh = create_unit_square( + comm, 11, 6, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type + ) family0 = "Lagrange" family1 = "Nedelec 1st kind H(curl)" elif cell_type == CellType.quadrilateral: - mesh = create_unit_square(comm, 11, 6, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type) + mesh = create_unit_square( + comm, 11, 6, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type + ) family0 = "Q" family1 = "RTCE" elif cell_type == CellType.hexahedron: - mesh = create_unit_cube(comm, 3, 3, 2, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type) + mesh = create_unit_cube( + comm, 3, 3, 2, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type + ) family0 = "Q" family1 = "NCE" elif cell_type == CellType.tetrahedron: - mesh = create_unit_cube(comm, 3, 2, 2, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type) + mesh = create_unit_cube( + comm, 3, 2, 2, ghost_mode=GhostMode.none, cell_type=cell_type, dtype=default_real_type + ) family0 = "Lagrange" family1 = "Nedelec 1st kind H(curl)" @@ -71,7 +84,7 @@ def test_gradient_interpolation_petsc(cell_type, p, q): G.assemble() u = Function(V) - u.interpolate(lambda x: 2 * x[0]**p + 3 * x[1]**p) + u.interpolate(lambda x: 2 * x[0] ** p + 3 * x[1] ** p) grad_u = Expression(ufl.grad(u), W.element.interpolation_points()) w_expr = Function(W) @@ -89,17 +102,11 @@ def test_gradient_interpolation_petsc(cell_type, p, q): @pytest.mark.parametrize("p", range(1, 4)) @pytest.mark.parametrize("q", range(1, 4)) -@pytest.mark.parametrize("from_lagrange", - [ - True, - False - ]) -@pytest.mark.parametrize("cell_type", [ - CellType.quadrilateral, - CellType.triangle, - CellType.tetrahedron, - CellType.hexahedron -]) +@pytest.mark.parametrize("from_lagrange", [True, False]) +@pytest.mark.parametrize( + "cell_type", + [CellType.quadrilateral, CellType.triangle, CellType.tetrahedron, CellType.hexahedron], +) def test_interpolation_matrix_petsc(cell_type, p, q, from_lagrange): """Test that discrete interpolation matrix yields the same result as interpolation.""" comm = MPI.COMM_WORLD @@ -137,9 +144,10 @@ def test_interpolation_matrix_petsc(cell_type, p, q, from_lagrange): def f(x): if mesh.geometry.dim == 2: - return (x[1]**p, x[0]**p) + return (x[1] ** p, x[0] ** p) else: - return (x[0]**p, x[2]**p, x[1]**p) + return (x[0] ** p, x[2] ** p, x[1] ** p) + u.interpolate(f) w_vec = Function(W) w_vec.interpolate(u) @@ -158,13 +166,38 @@ def f(x): def test_nonaffine_discrete_operator_petsc(): """Check that discrete operator is consistent with normal interpolation between non-matching maps on non-affine geometries""" - points = np.array([[0, 0, 0], [1, 0, 0], [0, 2, 0], [1, 2, 0], - [0, 0, 3], [1, 0, 3], [0, 2, 3], [1, 2, 3], - [0.5, 0, 0], [0, 1, 0], [0, 0, 1.5], [1, 1, 0], - [1, 0, 1.5], [0.5, 2, 0], [0, 2, 1.5], [1, 2, 1.5], - [0.5, 0, 3], [0, 1, 3], [1, 1, 3], [0.5, 2, 3], - [0.5, 1, 0], [0.5, -0.1, 1.5], [0, 1, 1.5], [1, 1, 1.5], - [0.5, 2, 1.5], [0.5, 1, 3], [0.5, 1, 1.5]], dtype=default_real_type) + points = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 3], + [1, 0, 3], + [0, 2, 3], + [1, 2, 3], + [0.5, 0, 0], + [0, 1, 0], + [0, 0, 1.5], + [1, 1, 0], + [1, 0, 1.5], + [0.5, 2, 0], + [0, 2, 1.5], + [1, 2, 1.5], + [0.5, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0.5, 2, 3], + [0.5, 1, 0], + [0.5, -0.1, 1.5], + [0, 1, 1.5], + [1, 1, 1.5], + [0.5, 2, 1.5], + [0.5, 1, 3], + [0.5, 1, 1.5], + ], + dtype=default_real_type, + ) cells = np.array([range(len(points))], dtype=np.int32) cell_type = CellType.hexahedron diff --git a/python/test/unit/fem/test_quadrature_elements.py b/python/test/unit/fem/test_quadrature_elements.py index f4b6469dc32..34830a5a159 100644 --- a/python/test/unit/fem/test_quadrature_elements.py +++ b/python/test/unit/fem/test_quadrature_elements.py @@ -25,7 +25,9 @@ def test_default(degree): u = dolfinx.fem.Function(Quad) v = ufl.TrialFunction(CG2_vect) - dx_m = ufl.Measure("dx", domain=msh, metadata={"quadrature_degree": 1, "quadrature_scheme": "default"}) + dx_m = ufl.Measure( + "dx", domain=msh, metadata={"quadrature_degree": 1, "quadrature_scheme": "default"} + ) ds = ufl.Measure("ds", domain=msh) residual = u * v * dx_m @@ -49,15 +51,19 @@ def test_points_and_weights(): CG2_vect = dolfinx.fem.functionspace(msh, ("Lagrange", 1)) Qe = basix.ufl.quadrature_element( - msh.topology.cell_name(), value_shape=(), + msh.topology.cell_name(), + value_shape=(), points=np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1 / 3, 1 / 3]]), - weights=np.array([0.2, 0.2, 0.2, 0.4])) + weights=np.array([0.2, 0.2, 0.2, 0.4]), + ) Quad = dolfinx.fem.functionspace(msh, Qe) u = dolfinx.fem.Function(Quad) v = ufl.TrialFunction(CG2_vect) - dx_m = ufl.Measure("dx", domain=msh, metadata={"quadrature_degree": 1, "quadrature_scheme": "default"}) + dx_m = ufl.Measure( + "dx", domain=msh, metadata={"quadrature_degree": 1, "quadrature_scheme": "default"} + ) ds = ufl.Measure("ds", domain=msh) residual = u * v * dx_m @@ -101,9 +107,9 @@ def test_interpolation(degree): def test_interpolation_blocked(degree): msh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10) - e = basix.ufl.quadrature_element(msh.topology.cell_name(), value_shape=(2, ), degree=degree) + e = basix.ufl.quadrature_element(msh.topology.cell_name(), value_shape=(2,), degree=degree) space = dolfinx.fem.functionspace(msh, e) - p4 = dolfinx.fem.functionspace(msh, ("Lagrange", 4, (2, ))) + p4 = dolfinx.fem.functionspace(msh, ("Lagrange", 4, (2,))) f_p4 = dolfinx.fem.Function(p4) f_p4.interpolate(lambda x: ([x[1] ** 4, x[0] ** 3])) @@ -125,12 +131,12 @@ def extract_diagonal(mat): for i in range(start, end): if mat.indices[i] == row: for block in range(bs): - diag[bs * row + block] = mat.data[bs ** 2 * i + (bs + 1) * block] + diag[bs * row + block] = mat.data[bs**2 * i + (bs + 1) * block] return diag @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("shape", [(), (1, ), (2, ), (3, ), (4, ), (2, 2), (3, 3)]) +@pytest.mark.parametrize("shape", [(), (1,), (2,), (3,), (4,), (2, 2), (3, 3)]) def test_vector_element(shape): msh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10) diff --git a/python/test/unit/fem/test_special_functions.py b/python/test/unit/fem/test_special_functions.py index 847e9547f79..5363dbc06da 100644 --- a/python/test/unit/fem/test_special_functions.py +++ b/python/test/unit/fem/test_special_functions.py @@ -30,13 +30,17 @@ def test_facet_area1D(): assert np.isclose(a0.real, 2) -@pytest.mark.parametrize('mesh_factory', [(create_unit_square, (MPI.COMM_WORLD, 3, 3), 1. / 3), - # (create_unit_square, - # (MPI.COMM_WORLD, 3, 3, CellType.quadrilateral), 1. / 3), - (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3), 1 / 18.), - # (create_unit_cube, - # (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron), 1. / 9) - ]) +@pytest.mark.parametrize( + "mesh_factory", + [ + (create_unit_square, (MPI.COMM_WORLD, 3, 3), 1.0 / 3), + # (create_unit_square, + # (MPI.COMM_WORLD, 3, 3, CellType.quadrilateral), 1. / 3), + (create_unit_cube, (MPI.COMM_WORLD, 3, 3, 3), 1 / 18.0), + # (create_unit_cube, + # (MPI.COMM_WORLD, 3, 3, 3, CellType.hexahedron), 1. / 9) + ], +) def test_facet_area(mesh_factory): """Compute facet area of cell. UFL currently only supports affine cells for this computation""" diff --git a/python/test/unit/fem/test_symmetry.py b/python/test/unit/fem/test_symmetry.py index 007f8178dc1..9a167e3ca88 100644 --- a/python/test/unit/fem/test_symmetry.py +++ b/python/test/unit/fem/test_symmetry.py @@ -43,18 +43,34 @@ def run_symmetry_test(cell_type, e, form_f): check_symmetry(A, tol) -parametrize_elements = pytest.mark.parametrize("cell_type, family", [ - (CellType.triangle, "Lagrange"), (CellType.triangle, "N1curl"), (CellType.triangle, "RT"), - (CellType.triangle, "Regge"), - (CellType.quadrilateral, "Lagrange"), (CellType.quadrilateral, "RTCE"), (CellType.quadrilateral, "RTCF"), - (CellType.tetrahedron, "Lagrange"), (CellType.tetrahedron, "N1curl"), (CellType.tetrahedron, "RT"), - (CellType.tetrahedron, "Regge"), - (CellType.hexahedron, "Lagrange"), (CellType.hexahedron, "NCE"), (CellType.hexahedron, "NCF") -]) -parametrize_lagrange_elements = pytest.mark.parametrize("cell_type, family", [ - (CellType.triangle, "Lagrange"), (CellType.quadrilateral, "Lagrange"), - (CellType.tetrahedron, "Lagrange"), (CellType.hexahedron, "Lagrange") -]) +parametrize_elements = pytest.mark.parametrize( + "cell_type, family", + [ + (CellType.triangle, "Lagrange"), + (CellType.triangle, "N1curl"), + (CellType.triangle, "RT"), + (CellType.triangle, "Regge"), + (CellType.quadrilateral, "Lagrange"), + (CellType.quadrilateral, "RTCE"), + (CellType.quadrilateral, "RTCF"), + (CellType.tetrahedron, "Lagrange"), + (CellType.tetrahedron, "N1curl"), + (CellType.tetrahedron, "RT"), + (CellType.tetrahedron, "Regge"), + (CellType.hexahedron, "Lagrange"), + (CellType.hexahedron, "NCE"), + (CellType.hexahedron, "NCF"), + ], +) +parametrize_lagrange_elements = pytest.mark.parametrize( + "cell_type, family", + [ + (CellType.triangle, "Lagrange"), + (CellType.quadrilateral, "Lagrange"), + (CellType.tetrahedron, "Lagrange"), + (CellType.hexahedron, "Lagrange"), + ], +) @pytest.mark.skip_in_parallel @@ -98,12 +114,16 @@ def test_mass_matrix_dS(cell_type, family, order, sign): @pytest.mark.parametrize("order", range(1, 2)) @pytest.mark.parametrize("sign", ["+", "-"]) def test_stiffness_matrix_dS(cell_type, family, order, sign): - run_symmetry_test(cell_type, (family, order), lambda u, v: inner(grad(u), grad(v))(sign) * ufl.dS) + run_symmetry_test( + cell_type, (family, order), lambda u, v: inner(grad(u), grad(v))(sign) * ufl.dS + ) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("cell_type", [CellType.triangle, CellType.quadrilateral, - CellType.tetrahedron, CellType.hexahedron]) +@pytest.mark.parametrize( + "cell_type", + [CellType.triangle, CellType.quadrilateral, CellType.tetrahedron, CellType.hexahedron], +) @pytest.mark.parametrize("sign", ["+", "-"]) @pytest.mark.parametrize("order", range(1, 2)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) @@ -113,8 +133,12 @@ def test_mixed_element_form(cell_type, sign, order, dtype): else: mesh = create_unit_cube(MPI.COMM_WORLD, 2, 2, 2, cell_type, dtype=dtype) - U_el = mixed_element([element(basix.ElementFamily.P, cell_type.name, order), - element(basix.ElementFamily.N1E, cell_type.name, order)]) + U_el = mixed_element( + [ + element(basix.ElementFamily.P, cell_type.name, order), + element(basix.ElementFamily.N1E, cell_type.name, order), + ] + ) U = functionspace(mesh, U_el) u, p = ufl.TrialFunctions(U) @@ -138,8 +162,12 @@ def test_mixed_element_vector_element_form(cell_type, sign, order, dtype): else: mesh = create_unit_cube(MPI.COMM_WORLD, 2, 2, 2, cell_type, dtype=dtype) - U_el = mixed_element([element(basix.ElementFamily.P, cell_type.name, order, shape=(mesh.geometry.dim,)), - element(basix.ElementFamily.N1E, cell_type.name, order)]) + U_el = mixed_element( + [ + element(basix.ElementFamily.P, cell_type.name, order, shape=(mesh.geometry.dim,)), + element(basix.ElementFamily.N1E, cell_type.name, order), + ] + ) U = functionspace(mesh, U_el) u, p = ufl.TrialFunctions(U) diff --git a/python/test/unit/fem/test_vector_function.py b/python/test/unit/fem/test_vector_function.py index d74d0207112..d3d436ee6a0 100644 --- a/python/test/unit/fem/test_vector_function.py +++ b/python/test/unit/fem/test_vector_function.py @@ -18,10 +18,11 @@ @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('space_type', ["RT"]) -@pytest.mark.parametrize('order', [1, 2, 3, 4, 5]) +@pytest.mark.parametrize("space_type", ["RT"]) +@pytest.mark.parametrize("order", [1, 2, 3, 4, 5]) def test_div_conforming_triangle(space_type, order): """Checks that the vectors in div conforming spaces on a triangle are correctly oriented""" + # Create simple triangle mesh def perform_test(points, cells): domain = ufl.Mesh(element("Lagrange", "triangle", 1, shape=(2,), dtype=default_real_type)) @@ -33,10 +34,10 @@ def perform_test(points, cells): for dof in range(len(x)): x[:] = 0.0 x[dof] = 1 - points = np.array([[.5, .5, 0], [.5, .5, 0]]) + points = np.array([[0.5, 0.5, 0], [0.5, 0.5, 0]]) cells = np.array([0, 1]) result = f.eval(points, cells) - normal = np.array([-1., 1.]) + normal = np.array([-1.0, 1.0]) output.append(result.dot(normal)) return output @@ -48,13 +49,16 @@ def perform_test(points, cells): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('space_type', ["RT"]) -@pytest.mark.parametrize('order', [1, 2, 3, 4, 5]) +@pytest.mark.parametrize("space_type", ["RT"]) +@pytest.mark.parametrize("order", [1, 2, 3, 4, 5]) def test_div_conforming_tetrahedron(space_type, order): """Checks that the vectors in div conforming spaces on a tetrahedron are correctly oriented""" + # Create simple tetrahedron cell mesh def perform_test(points, cells): - domain = ufl.Mesh(element("Lagrange", "tetrahedron", 1, shape=(3,), dtype=default_real_type)) + domain = ufl.Mesh( + element("Lagrange", "tetrahedron", 1, shape=(3,), dtype=default_real_type) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) V = functionspace(mesh, (space_type, order)) f = Function(V) @@ -66,11 +70,13 @@ def perform_test(points, cells): points = np.array([[1 / 3, 1 / 3, 1 / 3], [1 / 3, 1 / 3, 1 / 3]]) cells = np.array([0, 1]) result = f.eval(points, cells) - normal = np.array([1., 1., 1.]) + normal = np.array([1.0, 1.0, 1.0]) output.append(result.dot(normal)) return output - points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 1]], dtype=default_real_type) + points = np.array( + [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 1]], dtype=default_real_type + ) cells = np.array([[0, 1, 2, 3], [1, 3, 2, 4]]) result = perform_test(points, cells) for i, j in result: diff --git a/python/test/unit/geometry/test_bounding_box_tree.py b/python/test/unit/geometry/test_bounding_box_tree.py index 16d70c1d2bd..2cc328300b6 100644 --- a/python/test/unit/geometry/test_bounding_box_tree.py +++ b/python/test/unit/geometry/test_bounding_box_tree.py @@ -11,10 +11,25 @@ import pytest from dolfinx import cpp as _cpp -from dolfinx.geometry import (bb_tree, compute_closest_entity, compute_colliding_cells, compute_collisions_points, - compute_collisions_trees, compute_distance_gjk, create_midpoint_tree) -from dolfinx.mesh import (CellType, create_box, create_unit_cube, create_unit_interval, create_unit_square, - exterior_facet_indices, locate_entities, locate_entities_boundary) +from dolfinx.geometry import ( + bb_tree, + compute_closest_entity, + compute_colliding_cells, + compute_collisions_points, + compute_collisions_trees, + compute_distance_gjk, + create_midpoint_tree, +) +from dolfinx.mesh import ( + CellType, + create_box, + create_unit_cube, + create_unit_interval, + create_unit_square, + exterior_facet_indices, + locate_entities, + locate_entities_boundary, +) def extract_geometricial_data(mesh, dim, entities): @@ -22,7 +37,9 @@ def extract_geometricial_data(mesh, dim, entities): vertices""" mesh_nodes = [] geom = mesh.geometry - g_indices = _cpp.mesh.entities_to_geometry(mesh._cpp_object, dim, np.array(entities, dtype=np.int32), False) + g_indices = _cpp.mesh.entities_to_geometry( + mesh._cpp_object, dim, np.array(entities, dtype=np.int32), False + ) for cell in g_indices: nodes = np.zeros((len(cell), 3), dtype=np.float64) for j, entity in enumerate(cell): @@ -33,14 +50,19 @@ def extract_geometricial_data(mesh, dim, entities): def expand_bbox(bbox, dtype): """Expand min max bbox to convex hull""" - return np.array([[bbox[0][0], bbox[0][1], bbox[0][2]], - [bbox[0][0], bbox[0][1], bbox[1][2]], - [bbox[0][0], bbox[1][1], bbox[0][2]], - [bbox[1][0], bbox[0][1], bbox[0][2]], - [bbox[1][0], bbox[0][1], bbox[1][2]], - [bbox[1][0], bbox[1][1], bbox[0][2]], - [bbox[0][0], bbox[1][1], bbox[1][2]], - [bbox[1][0], bbox[1][1], bbox[1][2]]], dtype=dtype) + return np.array( + [ + [bbox[0][0], bbox[0][1], bbox[0][2]], + [bbox[0][0], bbox[0][1], bbox[1][2]], + [bbox[0][0], bbox[1][1], bbox[0][2]], + [bbox[1][0], bbox[0][1], bbox[0][2]], + [bbox[1][0], bbox[0][1], bbox[1][2]], + [bbox[1][0], bbox[1][1], bbox[0][2]], + [bbox[0][0], bbox[1][1], bbox[1][2]], + [bbox[1][0], bbox[1][1], bbox[1][2]], + ], + dtype=dtype, + ) def find_colliding_cells(mesh, bbox, dtype): @@ -50,8 +72,9 @@ def find_colliding_cells(mesh, bbox, dtype): # Find actual cells using known bounding box tree colliding_cells = [] num_cells = mesh.topology.index_map(mesh.topology.dim).size_local - x_indices = _cpp.mesh.entities_to_geometry(mesh._cpp_object, mesh.topology.dim, - np.arange(num_cells, dtype=np.int32), False) + x_indices = _cpp.mesh.entities_to_geometry( + mesh._cpp_object, mesh.topology.dim, np.arange(num_cells, dtype=np.int32), False + ) points = mesh.geometry.x bounding_box = expand_bbox(bbox, dtype) for cell in range(num_cells): @@ -113,9 +136,9 @@ def rotation_matrix(axis, angle): n_axis = axis / np.sqrt(np.inner(axis, axis)) # Define cross product matrix of axis - axis_x = np.array([[0, -n_axis[2], n_axis[1]], - [n_axis[2], 0, -n_axis[0]], - [-n_axis[1], n_axis[0], 0]]) + axis_x = np.array( + [[0, -n_axis[2], n_axis[1]], [n_axis[2], 0, -n_axis[0]], [-n_axis[1], n_axis[0], 0]] + ) id = np.cos(angle) * np.eye(3) outer = (1 - np.cos(angle)) * np.outer(n_axis, n_axis) return np.sin(angle) * axis_x + id + outer @@ -154,14 +177,14 @@ def test_compute_collisions_point_1d(dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("point", [np.array([0.52, 0, 0]), - np.array([0.9, 0, 0])]) +@pytest.mark.parametrize("point", [np.array([0.52, 0, 0]), np.array([0.9, 0, 0])]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_compute_collisions_tree_1d(point, dtype): mesh_A = create_unit_interval(MPI.COMM_WORLD, 16, dtype=dtype) def locator_A(x): return x[0] >= point[0] + # Locate all vertices of mesh A that should collide vertices_A = _cpp.mesh.locate_entities(mesh_A._cpp_object, 0, locator_A) mesh_A.topology.create_connectivity(0, mesh_A.topology.dim) @@ -196,8 +219,7 @@ def locator_B(x): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("point", [np.array([0.52, 0.51, 0.0]), - np.array([0.9, -0.9, 0.0])]) +@pytest.mark.parametrize("point", [np.array([0.52, 0.51, 0.0]), np.array([0.9, -0.9, 0.0])]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_compute_collisions_tree_2d(point, dtype): mesh_A = create_unit_square(MPI.COMM_WORLD, 3, 3, dtype=dtype) @@ -217,8 +239,7 @@ def test_compute_collisions_tree_2d(point, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("point", [np.array([0.52, 0.51, 0.3]), - np.array([0.9, -0.9, 0.3])]) +@pytest.mark.parametrize("point", [np.array([0.52, 0.51, 0.3]), np.array([0.9, -0.9, 0.3])]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_compute_collisions_tree_3d(point, dtype): mesh_A = create_unit_cube(MPI.COMM_WORLD, 2, 2, 2, dtype=dtype) @@ -246,7 +267,9 @@ def test_compute_closest_entity_1d(dim, dtype): points = np.array([[-ref_distance, 0, 0], [2 / N, 2 * ref_distance, 0]], dtype=dtype) mesh = create_unit_interval(MPI.COMM_WORLD, N, dtype=dtype) tree = bb_tree(mesh, dim) - num_entities_local = mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + num_entities_local = ( + mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + ) entities = np.arange(num_entities_local, dtype=np.int32) midpoint_tree = create_midpoint_tree(mesh, dim, entities) closest_entities = compute_closest_entity(tree, midpoint_tree, mesh, points) @@ -278,7 +301,9 @@ def test_compute_closest_entity_2d(dim, dtype): mesh = create_unit_square(MPI.COMM_WORLD, 15, 15, dtype=dtype) mesh.topology.create_entities(dim) tree = bb_tree(mesh, dim) - num_entities_local = mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + num_entities_local = ( + mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + ) entities = np.arange(num_entities_local, dtype=np.int32) midpoint_tree = create_midpoint_tree(mesh, dim, entities) @@ -308,7 +333,9 @@ def test_compute_closest_entity_3d(dim, dtype): mesh.topology.create_entities(dim) tree = bb_tree(mesh, dim) - num_entities_local = mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + num_entities_local = ( + mesh.topology.index_map(dim).size_local + mesh.topology.index_map(dim).num_ghosts + ) entities = np.arange(num_entities_local, dtype=np.int32) midpoint_tree = create_midpoint_tree(mesh, dim, entities) closest_entities = compute_closest_entity(tree, midpoint_tree, mesh, points) @@ -403,15 +430,13 @@ def test_sub_bbtree_codim1(dtype): @pytest.mark.parametrize("comm", [MPI.COMM_WORLD, MPI.COMM_SELF]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_serial_global_bb_tree(dtype, comm): - # Test if global bb tree with only one node returns the correct collision mesh = create_unit_cube(comm, 4, 5, 3) # First point should not be in any tree # Second point should always be in the global tree, but only in # entity tree with a serial mesh - x = np.array([[2.0, 2.0, 3.0], - [0.3, 0.2, 0.1]], dtype=dtype) + x = np.array([[2.0, 2.0, 3.0], [0.3, 0.2, 0.1]], dtype=dtype) tree = bb_tree(mesh, mesh.topology.dim) global_tree = tree.create_global_tree(mesh.comm) diff --git a/python/test/unit/geometry/test_gjk.py b/python/test/unit/geometry/test_gjk.py index 15b99ddda6c..30db93c5d43 100644 --- a/python/test/unit/geometry/test_gjk.py +++ b/python/test/unit/geometry/test_gjk.py @@ -24,8 +24,9 @@ def distance_point_to_line_3D(P1, P2, point): def distance_point_to_plane_3D(P1, P2, P3, point): """Distance from point to plane""" - return np.abs(np.dot(np.cross(P2 - P1, P3 - P1) - / np.linalg.norm(np.cross(P2 - P1, P3 - P1)), point - P2)) + return np.abs( + np.dot(np.cross(P2 - P1, P3 - P1) / np.linalg.norm(np.cross(P2 - P1, P3 - P1)), point - P2) + ) @pytest.mark.parametrize("delta", [0.1, 1e-12, 0, -2]) @@ -54,7 +55,7 @@ def test_line_line_distance(delta, dtype): assert np.isclose(distance, actual_distance, atol=1e-7) -@pytest.mark.parametrize("delta", [0.1**(3 * i) for i in range(6)]) +@pytest.mark.parametrize("delta", [0.1 ** (3 * i) for i in range(6)]) @pytest.mark.parametrize("dtype", [np.float64]) def test_tri_distance(delta, dtype): tri_1 = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]], dtype=dtype) @@ -67,7 +68,7 @@ def test_tri_distance(delta, dtype): assert np.isclose(distance, actual_distance, atol=1e-6) -@pytest.mark.parametrize("delta", [0.1 * 0.1**(3 * i) for i in range(6)]) +@pytest.mark.parametrize("delta", [0.1 * 0.1 ** (3 * i) for i in range(6)]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_quad_distance2d(delta, dtype): quad_1 = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]], dtype=dtype) @@ -80,7 +81,7 @@ def test_quad_distance2d(delta, dtype): assert np.isclose(distance, actual_distance, atol=1e-8) -@pytest.mark.parametrize("delta", [1 * 0.5**(3 * i) for i in range(7)]) +@pytest.mark.parametrize("delta", [1 * 0.5 ** (3 * i) for i in range(7)]) def test_tetra_distance_3d(delta): tetra_1 = np.array([[0, 0, 0.2], [1, 0, 0.1], [0, 1, 0.3], [0, 0, 1]], dtype=np.float64) tetra_2 = np.array([[0, 0, -3], [1, 0, -3], [0, 1, -3], [0.5, 0.3, -delta]], dtype=np.float64) @@ -89,8 +90,7 @@ def test_tetra_distance_3d(delta): assert np.isclose(distance, actual_distance, atol=1e-15) -@pytest.mark.parametrize("delta", [(-1)**i * np.sqrt(2) * 0.1**(3 * i) - for i in range(6)]) +@pytest.mark.parametrize("delta", [(-1) ** i * np.sqrt(2) * 0.1 ** (3 * i) for i in range(6)]) def test_tetra_collision_3d(delta): tetra_1 = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float64) tetra_2 = np.array([[0, 0, -3], [1, 0, -3], [0, 1, -3], [0.5, 0.3, -delta]], dtype=np.float64) @@ -104,18 +104,18 @@ def test_tetra_collision_3d(delta): @pytest.mark.parametrize("delta", [0, -0.1, -0.49, -0.51]) def test_hex_collision_3d(delta): - hex_1 = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], - [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]], - dtype=np.float64) + hex_1 = np.array( + [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]], + dtype=np.float64, + ) P0 = np.array([1.5 + delta, 1.5 + delta, 0.5], dtype=np.float64) P1 = np.array([2, 2, 1], dtype=np.float64) P2 = np.array([2, 1.25, 0.25], dtype=np.float64) P3 = P1 + P2 - P0 quad_1 = np.array([P0, P1, P2, P3], dtype=np.float64) - n = (np.cross(quad_1[1] - quad_1[0], quad_1[2] - quad_1[0]) - / np.linalg.norm( - np.cross(quad_1[1] - quad_1[0], - quad_1[2] - quad_1[0]))) + n = np.cross(quad_1[1] - quad_1[0], quad_1[2] - quad_1[0]) / np.linalg.norm( + np.cross(quad_1[1] - quad_1[0], quad_1[2] - quad_1[0]) + ) quad_2 = quad_1 + n hex_2 = np.zeros((8, 3), dtype=np.float64) hex_2[:4, :] = quad_1 @@ -132,26 +132,39 @@ def test_hex_collision_3d(delta): @pytest.mark.parametrize("scale", [1000.0, 1.0, 1e-4]) @pytest.mark.parametrize("dtype", [np.float64]) def test_cube_distance(delta, scale, dtype): - cubes = [scale * np.array([[-1, -1, -1], [1, -1, -1], [-1, 1, -1], [1, 1, -1], - [-1, -1, 1], [1, -1, 1], [-1, 1, 1], [1, 1, 1]], - dtype=dtype)] + cubes = [ + scale + * np.array( + [ + [-1, -1, -1], + [1, -1, -1], + [-1, 1, -1], + [1, 1, -1], + [-1, -1, 1], + [1, -1, 1], + [-1, 1, 1], + [1, 1, 1], + ], + dtype=dtype, + ) + ] # Rotate cube 45 degrees around z, so that an edge faces along # x-axis (vertical) - r = Rotation.from_euler('z', 45, degrees=True) + r = Rotation.from_euler("z", 45, degrees=True) cubes.append(r.apply(cubes[0])) # Rotate cube around y, so that a corner faces along the x-axis - r = Rotation.from_euler('y', np.arctan2(1.0, np.sqrt(2))) + r = Rotation.from_euler("y", np.arctan2(1.0, np.sqrt(2))) cubes.append(r.apply(cubes[1])) # Rotate cube 45 degrees around y, so that an edge faces along # x-axis (horizontal) - r = Rotation.from_euler('y', 45, degrees=True) + r = Rotation.from_euler("y", 45, degrees=True) cubes.append(r.apply(cubes[0])) # Rotate scene through an arbitrary angle - r = Rotation.from_euler('xyz', [22, 13, -47], degrees=True) + r = Rotation.from_euler("xyz", [22, 13, -47], degrees=True) for c0 in range(4): for c1 in range(4): @@ -168,7 +181,9 @@ def test_cube_distance(delta, scale, dtype): @pytest.mark.skip_in_parallel @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_collision_2nd_order_triangle(dtype): - points = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.65, 0.65], [0.0, 0.5], [0.5, 0.0]], dtype=dtype) + points = np.array( + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.65, 0.65], [0.0, 0.5], [0.5, 0.0]], dtype=dtype + ) cells = np.array([[0, 1, 2, 3, 4, 5]]) domain = ufl.Mesh(element("Lagrange", "triangle", 2, gdim=2, shape=(2,), dtype=dtype)) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) @@ -189,6 +204,7 @@ def test_collision_2nd_order_triangle(dtype): # curved facet def line_through_points(p0, p1): return lambda x: (p1[1] - p0[1]) / (p1[0] - p0[0]) * (x - p0[0]) + p0[1] + line_func = line_through_points(points[2], points[3]) point = np.array([0.2, line_func(0.2), 0]) diff --git a/python/test/unit/graph/test_adjacencylist.py b/python/test/unit/graph/test_adjacencylist.py index 01e2b732e3b..55165acc3b2 100644 --- a/python/test/unit/graph/test_adjacencylist.py +++ b/python/test/unit/graph/test_adjacencylist.py @@ -15,4 +15,6 @@ def test_create_adj2d(dtype): data = np.zeros([2, 4], dtype=dtype) adj = adjacencylist(data) num_nodes, num_links = data.shape[0], data.shape[1] - assert np.array_equal(adj.offsets, np.arange(0, num_nodes * num_links + num_links, num_links, dtype=np.int32)) + assert np.array_equal( + adj.offsets, np.arange(0, num_nodes * num_links + num_links, num_links, dtype=np.int32) + ) diff --git a/python/test/unit/io/test_adios2.py b/python/test/unit/io/test_adios2.py index a48c0b203c3..89663477194 100644 --- a/python/test/unit/io/test_adios2.py +++ b/python/test/unit/io/test_adios2.py @@ -34,8 +34,7 @@ def generate_mesh(dim: int, simplex: bool, N: int = 5, dtype=None): if simplex: return create_unit_square(MPI.COMM_WORLD, N, N, dtype=dtype) else: - return create_unit_square(MPI.COMM_WORLD, 2 * N, N, CellType.quadrilateral, - dtype=dtype) + return create_unit_square(MPI.COMM_WORLD, 2 * N, N, CellType.quadrilateral, dtype=dtype) elif dim == 3: if simplex: return create_unit_cube(MPI.COMM_WORLD, N, N, N, dtype=dtype) @@ -49,7 +48,7 @@ def generate_mesh(dim: int, simplex: bool, N: int = 5, dtype=None): @pytest.mark.parametrize("dim", [2, 3]) @pytest.mark.parametrize("simplex", [True, False]) def test_fides_mesh(tempdir, dim, simplex): - """ Test writing of a single Fides mesh with changing geometry""" + """Test writing of a single Fides mesh with changing geometry""" filename = Path(tempdir, "mesh_fides.bp") mesh = generate_mesh(dim, simplex) with FidesWriter(mesh.comm, filename, mesh) as f: @@ -76,6 +75,7 @@ def vel(x): values[0] = x[1] values[1] = x[0] return values + v.interpolate(vel) q.interpolate(lambda x: x[0]) f.write(1) @@ -114,7 +114,7 @@ def test_fides_function_at_nodes(tempdir, dim, simplex): with FidesWriter(mesh.comm, filename, [v, q]) as f: for t in [0.1, 0.5, 1]: # Only change one function - q.interpolate(lambda x: t * (x[0] - 0.5)**2) + q.interpolate(lambda x: t * (x[0] - 0.5) ** 2) f.write(t) mesh.geometry.x[:, :2] += 0.1 @@ -131,8 +131,11 @@ def test_second_order_vtx(tempdir): filename = Path(tempdir, "mesh_fides.bp") points = np.array([[0, 0, 0], [1, 0, 0], [0.5, 0, 0]], dtype=default_real_type) cells = np.array([[0, 1, 2]], dtype=np.int32) - domain = ufl.Mesh(element("Lagrange", "interval", 2, gdim=points.shape[1], shape=(1,), - dtype=default_real_type)) + domain = ufl.Mesh( + element( + "Lagrange", "interval", 2, gdim=points.shape[1], shape=(1,), dtype=default_real_type + ) + ) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) with VTXWriter(mesh.comm, filename, mesh) as f: f.write(0.0) @@ -197,12 +200,7 @@ def test_vtx_single_function(tempdir, dim, simplex): @pytest.mark.skipif(not has_adios2, reason="Requires ADIOS2.") -@pytest.mark.parametrize("dtype", [ - np.float32, - np.float64, - np.complex64, - np.complex128 -]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) @pytest.mark.parametrize("dim", [2, 3]) @pytest.mark.parametrize("simplex", [True, False]) def test_vtx_functions(tempdir, dtype, dim, simplex): @@ -219,6 +217,7 @@ def vel(x): values[0] = x[1] values[1] = x[0] return values + v.interpolate(vel) W = functionspace(mesh, ("DG", 2)) @@ -275,7 +274,7 @@ def partitioner(comm, nparts, local_graph, num_ghost_nodes): if comm.rank == 0: cells = np.array([[0, 1, 2], [0, 2, 3]], dtype=np.int64) - x = np.array([[0., 0.], [1., 0.], [1., 1.], [0., 1.]], dtype=default_real_type) + x = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=default_real_type) else: cells = np.empty((0, 3), dtype=np.int64) x = np.empty((0, 2), dtype=default_real_type) @@ -315,7 +314,9 @@ def test_vtx_reuse_mesh(tempdir, dim, simplex, reuse): reuse_variables = ["NumberOfEntities", "NumberOfNodes", "connectivity", "geometry", "types"] target_all = 3 # For all other variables the step count is number of writes - target_mesh = 1 if reuse else 3 # For mesh variables the step count is 1 if reuse else number of writes + target_mesh = ( + 1 if reuse else 3 + ) # For mesh variables the step count is 1 if reuse else number of writes adios_file = adios2.open(str(filename), "r", comm=mesh.comm, engine_type="BP4") for name, var in adios_file.available_variables().items(): diff --git a/python/test/unit/io/test_vtk.py b/python/test/unit/io/test_vtk.py index d5738e5b28f..52611320a41 100644 --- a/python/test/unit/io/test_vtk.py +++ b/python/test/unit/io/test_vtk.py @@ -18,7 +18,13 @@ from dolfinx.fem import Function, functionspace from dolfinx.io import VTKFile from dolfinx.io.utils import cell_perm_vtk # F401 -from dolfinx.mesh import CellType, create_mesh, create_unit_cube, create_unit_interval, create_unit_square +from dolfinx.mesh import ( + CellType, + create_mesh, + create_unit_cube, + create_unit_interval, + create_unit_square, +) from dolfinx.plot import vtk_mesh cell_types_2D = [CellType.triangle, CellType.quadrilateral] @@ -38,8 +44,8 @@ def test_save_2d_mesh(tempdir, cell_type): mesh = create_unit_square(MPI.COMM_WORLD, 32, 32, cell_type=cell_type) filename = Path(tempdir, f"mesh_{cell_type.name}.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_mesh(mesh, 0.) - vtk.write_mesh(mesh, 2.) + vtk.write_mesh(mesh, 0.0) + vtk.write_mesh(mesh, 2.0) @pytest.mark.parametrize("cell_type", cell_types_3D) @@ -47,8 +53,8 @@ def test_save_3d_mesh(tempdir, cell_type): mesh = create_unit_cube(MPI.COMM_WORLD, 8, 8, 8, cell_type=cell_type) filename = Path(tempdir, f"mesh_{cell_type.name}.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_mesh(mesh, 0.) - vtk.write_mesh(mesh, 2.) + vtk.write_mesh(mesh, 0.0) + vtk.write_mesh(mesh, 2.0) def test_save_1d_scalar(tempdir): @@ -57,7 +63,7 @@ def test_save_1d_scalar(tempdir): u.interpolate(lambda x: x[0]) filename = Path(tempdir, "u.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) @pytest.mark.parametrize("cell_type", cell_types_2D) @@ -68,8 +74,8 @@ def test_save_2d_scalar(tempdir, cell_type): filename = Path(tempdir, "u.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) - vtk.write_function(u, 1.) + vtk.write_function(u, 0.0) + vtk.write_function(u, 1.0) @pytest.mark.parametrize("cell_type", cell_types_3D) @@ -80,8 +86,8 @@ def test_save_3d_scalar(tempdir, cell_type): filename = Path(tempdir, "u.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) - vtk.write_function(u, 1.) + vtk.write_function(u, 0.0) + vtk.write_function(u, 1.0) def test_save_1d_vector(tempdir): @@ -93,12 +99,12 @@ def f(x): vals[1] = 2 * x[0] * x[0] return vals - e = element("Lagrange", mesh.basix_cell(), 2, shape=(2, )) + e = element("Lagrange", mesh.basix_cell(), 2, shape=(2,)) u = Function(functionspace(mesh, e)) u.interpolate(f) filename = Path(tempdir, "u.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) @pytest.mark.parametrize("cell_type", cell_types_2D) @@ -116,19 +122,21 @@ def f(x): u.interpolate(f) filename = Path(tempdir, "u.pvd") with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) - vtk.write_function(u, 1.) + vtk.write_function(u, 0.0) + vtk.write_function(u, 1.0) @pytest.mark.skip_in_parallel def test_save_2d_vector_CG2(tempdir): - points = np.array([[0, 0], [1, 0], [1, 2], [0, 2], - [1 / 2, 0], [1, 1], [1 / 2, 2], - [0, 1], [1 / 2, 1]], dtype=default_real_type) - points = np.array([[0, 0], [1, 0], [0, 2], [0.5, 1], [0, 1], [0.5, 0], - [1, 2], [0.5, 2], [1, 1]], dtype=default_real_type) - cells = np.array([[0, 1, 2, 3, 4, 5], - [1, 6, 2, 7, 3, 8]]) + points = np.array( + [[0, 0], [1, 0], [1, 2], [0, 2], [1 / 2, 0], [1, 1], [1 / 2, 2], [0, 1], [1 / 2, 1]], + dtype=default_real_type, + ) + points = np.array( + [[0, 0], [1, 0], [0, 2], [0.5, 1], [0, 1], [0.5, 0], [1, 2], [0.5, 2], [1, 1]], + dtype=default_real_type, + ) + cells = np.array([[0, 1, 2, 3, 4, 5], [1, 6, 2, 7, 3, 8]]) domain = ufl.Mesh(element("Lagrange", "triangle", 2, shape=(2,), dtype=default_real_type)) mesh = create_mesh(MPI.COMM_WORLD, cells, points, domain) gdim = mesh.geometry.dim @@ -136,7 +144,7 @@ def test_save_2d_vector_CG2(tempdir): u.interpolate(lambda x: np.vstack((x[0], x[1]))) filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) def test_save_vtk_mixed(tempdir): @@ -159,9 +167,9 @@ def test_save_vtk_mixed(tempdir): filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function([U2, U1], 0.) + vtk.write_function([U2, U1], 0.0) with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function([U1, U2], 0.) + vtk.write_function([U1, U2], 0.0) Up = U.sub(1) Up.name = "psub" @@ -188,14 +196,14 @@ def f(x): filename = Path(tempdir, "u.pvd") with pytest.raises(RuntimeError): with VTKFile(MPI.COMM_WORLD, filename, "w") as vtk: - vtk.write_function(u, 0.) - vtk.write_function(u, 1.) + vtk.write_function(u, 0.0) + vtk.write_function(u, 1.0) def test_save_vtk_cell_point(tempdir): """Test writing cell-wise and point-wise data""" mesh = create_unit_cube(MPI.COMM_WORLD, 3, 3, 3) - P2 = element("Lagrange", mesh.basix_cell(), 1, shape=(3, )) + P2 = element("Lagrange", mesh.basix_cell(), 1, shape=(3,)) P1 = element("Discontinuous Lagrange", mesh.basix_cell(), 0) V2, V1 = functionspace(mesh, P2), functionspace(mesh, P1) @@ -207,9 +215,9 @@ def test_save_vtk_cell_point(tempdir): filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function([U2, U1], 0.) + vtk.write_function([U2, U1], 0.0) with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function((U1, U2), 0.) + vtk.write_function((U1, U2), 0.0) def test_save_1d_tensor(tempdir): @@ -219,7 +227,7 @@ def test_save_1d_tensor(tempdir): u.x.array[:] = 1.0 filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) def test_save_2d_tensor(tempdir): @@ -229,9 +237,9 @@ def test_save_2d_tensor(tempdir): u.x.array[:] = 1.0 filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) u.x.array[:] = 2.0 - vtk.write_function(u, 1.) + vtk.write_function(u, 1.0) def test_save_3d_tensor(tempdir): @@ -241,7 +249,7 @@ def test_save_3d_tensor(tempdir): u.x.array[:] = 1.0 filename = Path(tempdir, "u.pvd") with VTKFile(mesh.comm, filename, "w") as vtk: - vtk.write_function(u, 0.) + vtk.write_function(u, 0.0) def test_triangle_perm_vtk(): @@ -249,15 +257,186 @@ def test_triangle_perm_vtk(): 10: np.array([0, 1, 2, 5, 6, 8, 7, 3, 4, 9]), 15: np.array([0, 1, 2, 6, 7, 8, 11, 10, 9, 3, 4, 5, 12, 13, 14]), 21: np.array([0, 1, 2, 7, 8, 9, 10, 14, 13, 12, 11, 3, 4, 5, 6, 15, 18, 16, 20, 19, 17]), - 28: np.array([0, 1, 2, 8, 9, 10, 11, 12, 17, 16, 15, 14, 13, 3, 4, 5, 6, 7, 18, 21, 22, - 19, 26, 27, 23, 25, 24, 20]), - 36: np.array([0, 1, 2, 9, 10, 11, 12, 13, 14, 20, 19, 18, 17, 16, 15, 3, 4, 5, 6, 7, 8, - 21, 24, 25, 26, 22, 32, 33, 34, 27, 31, 35, 28, 30, 29, 23]), - 45: np.array([0, 1, 2, 10, 11, 12, 13, 14, 15, 16, 23, 22, 21, 20, 19, 18, 17, 3, 4, 5, - 6, 7, 8, 9, 24, 27, 28, 29, 30, 25, 38, 39, 42, 40, 31, 37, 44, 43, 32, 36, 41, 33, 35, 34, 26]), - 55: np.array([0, 1, 2, 11, 12, 13, 14, 15, 16, 17, 18, 26, 25, 24, 23, 22, 21, 20, 19, - 3, 4, 5, 6, 7, 8, 9, 10, 27, 30, 31, 32, 33, 34, 28, 44, 45, 48, 49, 46, - 35, 43, 53, 54, 50, 36, 42, 52, 51, 37, 41, 47, 38, 40, 39, 29]) + 28: np.array( + [ + 0, + 1, + 2, + 8, + 9, + 10, + 11, + 12, + 17, + 16, + 15, + 14, + 13, + 3, + 4, + 5, + 6, + 7, + 18, + 21, + 22, + 19, + 26, + 27, + 23, + 25, + 24, + 20, + ] + ), + 36: np.array( + [ + 0, + 1, + 2, + 9, + 10, + 11, + 12, + 13, + 14, + 20, + 19, + 18, + 17, + 16, + 15, + 3, + 4, + 5, + 6, + 7, + 8, + 21, + 24, + 25, + 26, + 22, + 32, + 33, + 34, + 27, + 31, + 35, + 28, + 30, + 29, + 23, + ] + ), + 45: np.array( + [ + 0, + 1, + 2, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 24, + 27, + 28, + 29, + 30, + 25, + 38, + 39, + 42, + 40, + 31, + 37, + 44, + 43, + 32, + 36, + 41, + 33, + 35, + 34, + 26, + ] + ), + 55: np.array( + [ + 0, + 1, + 2, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 27, + 30, + 31, + 32, + 33, + 34, + 28, + 44, + 45, + 48, + 49, + 46, + 35, + 43, + 53, + 54, + 50, + 36, + 42, + 52, + 51, + 37, + 41, + 47, + 38, + 40, + 39, + 29, + ] + ), } for p_test, v_test in higher_order_triangle_perm.items(): v = cell_perm_vtk(CellType.triangle, p_test) diff --git a/python/test/unit/io/test_xdmf_function.py b/python/test/unit/io/test_xdmf_function.py index 673c5cf3edb..b231bb17a9a 100644 --- a/python/test/unit/io/test_xdmf_function.py +++ b/python/test/unit/io/test_xdmf_function.py @@ -43,7 +43,7 @@ def mesh_factory(tdim, n): @pytest.mark.parametrize("dtype", [np.double, np.complex128]) def test_save_1d_scalar(tempdir, encoding, dtype, use_pathlib): xtype = np.real(dtype(0)).dtype - filename2 = (Path(tempdir).joinpath("u1_.xdmf")if use_pathlib else Path(tempdir, "u1_.xdmf")) + filename2 = Path(tempdir).joinpath("u1_.xdmf") if use_pathlib else Path(tempdir, "u1_.xdmf") mesh = create_unit_interval(MPI.COMM_WORLD, 32, dtype=xtype) V = functionspace(mesh, ("Lagrange", 2)) u = Function(V, dtype=dtype) @@ -223,7 +223,7 @@ def test_save_3d_vector_series(tempdir, encoding, dtype, cell_type): def test_higher_order_function(tempdir): """Test Function output for higher-order meshes.""" - gmsh = pytest.importorskip('gmsh') + gmsh = pytest.importorskip("gmsh") from dolfinx.io import gmshio gmsh.initialize() @@ -338,7 +338,9 @@ def gmsh_hex_model(order): file.write_function(u) # Write P3 GLL Function (exception expected) - ufl_e = basix.ufl.element(basix.ElementFamily.P, basix.CellType.tetrahedron, 3, basix.LagrangeVariant.gll_warped) + ufl_e = basix.ufl.element( + basix.ElementFamily.P, basix.CellType.tetrahedron, 3, basix.LagrangeVariant.gll_warped + ) u = Function(functionspace(msh, ufl_e)) with pytest.raises(RuntimeError): filename = Path(tempdir, "u3D_P3.xdmf") @@ -347,7 +349,9 @@ def gmsh_hex_model(order): file.write_function(u) # Write P3 equispaced Function - ufl_e = basix.ufl.element(basix.ElementFamily.P, basix.CellType.tetrahedron, 3, basix.LagrangeVariant.equispaced) + ufl_e = basix.ufl.element( + basix.ElementFamily.P, basix.CellType.tetrahedron, 3, basix.LagrangeVariant.equispaced + ) u1 = Function(functionspace(msh, ufl_e)) u1.interpolate(u) filename = Path(tempdir, "u3D_P3.xdmf") diff --git a/python/test/unit/io/test_xdmf_mesh.py b/python/test/unit/io/test_xdmf_mesh.py index 8711f39b575..46f65caff0e 100644 --- a/python/test/unit/io/test_xdmf_mesh.py +++ b/python/test/unit/io/test_xdmf_mesh.py @@ -15,8 +15,16 @@ from dolfinx import default_real_type from dolfinx.io import XDMFFile from dolfinx.io.gmshio import cell_perm_array, ufl_mesh -from dolfinx.mesh import (CellType, GhostMode, create_mesh, create_submesh, create_unit_cube, create_unit_interval, - create_unit_square, locate_entities) +from dolfinx.mesh import ( + CellType, + GhostMode, + create_mesh, + create_submesh, + create_unit_cube, + create_unit_interval, + create_unit_square, + locate_entities, +) # Supported XDMF file encoding if MPI.COMM_WORLD.size > 1: @@ -47,8 +55,10 @@ def test_save_and_load_1d_mesh(tempdir, encoding): with XDMFFile(MPI.COMM_WORLD, filename, "r", encoding=encoding) as file: mesh2 = file.read_mesh() assert mesh.topology.index_map(0).size_global == mesh2.topology.index_map(0).size_global - assert mesh.topology.index_map(mesh.topology.dim).size_global == mesh2.topology.index_map( - mesh.topology.dim).size_global + assert ( + mesh.topology.index_map(mesh.topology.dim).size_global + == mesh2.topology.index_map(mesh.topology.dim).size_global + ) @pytest.mark.skipif(default_real_type != np.float64, reason="float32 not supported yet") @@ -68,7 +78,10 @@ def test_save_and_load_2d_mesh(tempdir, encoding, cell_type): assert mesh2.name == mesh.name topology, topology2 = mesh.topology, mesh2.topology assert topology.index_map(0).size_global == topology2.index_map(0).size_global - assert topology.index_map(topology.dim).size_global == topology2.index_map(topology.dim).size_global + assert ( + topology.index_map(topology.dim).size_global + == topology2.index_map(topology.dim).size_global + ) @pytest.mark.skipif(default_real_type != np.float64, reason="float32 not supported yet") @@ -85,7 +98,10 @@ def test_save_and_load_3d_mesh(tempdir, encoding, cell_type): topology, topology2 = mesh.topology, mesh2.topology assert topology.index_map(0).size_global == topology2.index_map(0).size_global - assert topology.index_map(topology.dim).size_global == topology2.index_map(topology.dim).size_global + assert ( + topology.index_map(topology.dim).size_global + == topology2.index_map(topology.dim).size_global + ) @pytest.mark.skipif(default_real_type != np.float64, reason="float32 not supported yet") @@ -116,11 +132,18 @@ def test_read_write_p2_mesh(tempdir, encoding): x = points[srt] element_types, element_tags, node_tags = model.mesh.getElements(dim=3) - name, dim, order, num_nodes, local_coords, num_first_order_nodes = model.mesh.getElementProperties( - element_types[0]) + ( + name, + dim, + order, + num_nodes, + local_coords, + num_first_order_nodes, + ) = model.mesh.getElementProperties(element_types[0]) cells = node_tags[0].reshape(-1, num_nodes) - 1 num_nodes, gmsh_cell_id = MPI.COMM_WORLD.bcast( - [cells.shape[1], model.mesh.getElementType("tetrahedron", 2)], root=0) + [cells.shape[1], model.mesh.getElementType("tetrahedron", 2)], root=0 + ) gmsh.finalize() else: num_nodes, gmsh_cell_id = MPI.COMM_WORLD.bcast([None, None], root=0) @@ -140,7 +163,10 @@ def test_read_write_p2_mesh(tempdir, encoding): topology, topology2 = mesh.topology, mesh2.topology assert topology.index_map(0).size_global == topology2.index_map(0).size_global - assert topology.index_map(topology.dim).size_global == topology2.index_map(topology.dim).size_global + assert ( + topology.index_map(topology.dim).size_global + == topology2.index_map(topology.dim).size_global + ) @pytest.mark.parametrize("d", [2, 3]) diff --git a/python/test/unit/io/test_xdmf_meshdata.py b/python/test/unit/io/test_xdmf_meshdata.py index e0e7dd771d6..c1309752d38 100644 --- a/python/test/unit/io/test_xdmf_meshdata.py +++ b/python/test/unit/io/test_xdmf_meshdata.py @@ -51,5 +51,7 @@ def test_read_mesh_data(tempdir, tdim, n): assert cell_shape == mesh.topology.cell_type assert cell_degree == 1 - assert mesh.topology.index_map(tdim).size_global == mesh.comm.allreduce(cells.shape[0], op=MPI.SUM) + assert mesh.topology.index_map(tdim).size_global == mesh.comm.allreduce( + cells.shape[0], op=MPI.SUM + ) assert mesh.geometry.index_map().size_global == mesh.comm.allreduce(x.shape[0], op=MPI.SUM) diff --git a/python/test/unit/io/test_xdmf_meshtags.py b/python/test/unit/io/test_xdmf_meshtags.py index c9804e6c110..fd777a75b46 100644 --- a/python/test/unit/io/test_xdmf_meshtags.py +++ b/python/test/unit/io/test_xdmf_meshtags.py @@ -80,14 +80,19 @@ def test_3d(tempdir, cell_type, encoding): file.write_meshtags(mt_in, mesh_in.geometry) # Check number of owned and marked entities - lines_local = comm.allreduce((mt_lines.indices < mesh.topology.index_map(1).size_local).sum(), op=MPI.SUM) + lines_local = comm.allreduce( + (mt_lines.indices < mesh.topology.index_map(1).size_local).sum(), op=MPI.SUM + ) lines_local_in = comm.allreduce( - (mt_lines_in.indices < mesh_in.topology.index_map(1).size_local).sum(), op=MPI.SUM) + (mt_lines_in.indices < mesh_in.topology.index_map(1).size_local).sum(), op=MPI.SUM + ) assert lines_local == lines_local_in # Check that only owned data is written to file - facets_local = comm.allreduce((mt.indices < mesh.topology.index_map(2).size_local).sum(), op=MPI.SUM) + facets_local = comm.allreduce( + (mt.indices < mesh.topology.index_map(2).size_local).sum(), op=MPI.SUM + ) parser = ElementTree.XMLParser() tree = ElementTree.parse(Path(tempdir, "meshtags_3d_out.xdmf"), parser) num_lines = int(tree.findall(".//Grid[@Name='lines']/Topology")[0].get("NumberOfElements")) diff --git a/python/test/unit/la/test_krylov_solver.py b/python/test/unit/la/test_krylov_solver.py index ca795da22e9..c8f42c04638 100644 --- a/python/test/unit/la/test_krylov_solver.py +++ b/python/test/unit/la/test_krylov_solver.py @@ -22,7 +22,6 @@ def test_krylov_solver_lu(): - mesh = create_unit_square(MPI.COMM_WORLD, 12, 12) V = functionspace(mesh, ("Lagrange", 1)) u, v = TrialFunction(V), TestFunction(V) @@ -89,13 +88,12 @@ def amg_solve(N, method): # Stress computation def sigma(v): - return 2.0 * mu * sym(grad(v)) + lmbda * tr(sym( - grad(v))) * Identity(2) + return 2.0 * mu * sym(grad(v)) + lmbda * tr(sym(grad(v))) * Identity(2) # Define problem mesh = create_unit_square(MPI.COMM_WORLD, N, N) gdim = mesh.geometry.dim - V = functionspace(mesh, ('Lagrange', 1, (gdim,))) + V = functionspace(mesh, ("Lagrange", 1, (gdim,))) u = TrialFunction(V) v = TestFunction(V) diff --git a/python/test/unit/la/test_matrix_csr.py b/python/test/unit/la/test_matrix_csr.py index 48cb2837962..be44d27d8ff 100644 --- a/python/test/unit/la/test_matrix_csr.py +++ b/python/test/unit/la/test_matrix_csr.py @@ -32,7 +32,7 @@ def create_test_sparsity(n, bs): return sp -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_add(dtype): # Regular CSR Matrix 6x6 with bs=1 sp = create_test_sparsity(6, 1) @@ -76,7 +76,7 @@ def test_add(dtype): assert mat3.squared_norm() == 0.0 # /NOSONAR -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_set(dtype): mpi_size = MPI.COMM_WORLD.size # Regular CSR Matrix 6x6 with bs=1 @@ -86,7 +86,7 @@ def test_set(dtype): # Set a block with bs=1 mat1.set([2.0, 3.0, 4.0, 5.0], [2, 3], [4, 5], 1) n1 = mat1.squared_norm() - assert (n1 == 54.0 * mpi_size) # /NOSONAR + assert n1 == 54.0 * mpi_size # /NOSONAR # Set same block with bs=2 mat1.set([2.0, 3.0, 4.0, 5.0], [1], [2], 2) @@ -94,7 +94,7 @@ def test_set(dtype): assert n1 == n2 -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_set_blocked(dtype): mpi_size = MPI.COMM_WORLD.size # Blocked CSR Matrix 3x3 with bs=2 @@ -104,10 +104,10 @@ def test_set_blocked(dtype): # Set a block with bs=1 mat1.set([2.0, 3.0, 4.0, 5.0], [2, 3], [4, 5], 1) n1 = mat1.squared_norm() - assert (n1 == 54.0 * mpi_size) # /NOSONAR + assert n1 == 54.0 * mpi_size # /NOSONAR -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_distributed_csr(dtype): # global size N N = 36 @@ -144,7 +144,7 @@ def test_distributed_csr(dtype): assert np.isclose(mat.data.sum(), pre_final_sum) -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_set_diagonal_distributed(dtype): mesh_dtype = np.real(dtype(0)).dtype ghost_mode = GhostMode.shared_facet @@ -213,11 +213,11 @@ def test_set_diagonal_distributed(dtype): # Update matrix: # this will zero ghost rows and diagonal values are already zero. A.scatter_reverse() - assert (As.diagonal()[nlocal:] == dtype(0.)).all() - assert (As.diagonal()[:nlocal] == dtype(1.)).all() + assert (As.diagonal()[nlocal:] == dtype(0.0)).all() + assert (As.diagonal()[:nlocal] == dtype(1.0)).all() -@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.complex64, np.complex128]) +@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.complex64, np.complex128]) def test_bad_entry(dtype): sp = create_test_sparsity(6, 1) mat1 = matrix_csr(sp, dtype=dtype) diff --git a/python/test/unit/la/test_nullspace.py b/python/test/unit/la/test_nullspace.py index 282cb22feb1..a8ea7bbfb34 100644 --- a/python/test/unit/la/test_nullspace.py +++ b/python/test/unit/la/test_nullspace.py @@ -90,15 +90,15 @@ def build_broken_elastic_nullspace(V): return ns -@pytest.mark.parametrize("mesh", [ - create_unit_square(MPI.COMM_WORLD, 12, 13), - create_unit_cube(MPI.COMM_WORLD, 12, 18, 15) -]) +@pytest.mark.parametrize( + "mesh", + [create_unit_square(MPI.COMM_WORLD, 12, 13), create_unit_cube(MPI.COMM_WORLD, 12, 18, 15)], +) @pytest.mark.parametrize("degree", [1, 2]) def test_nullspace_orthogonal(mesh, degree): """Test that null spaces orthogonalisation""" gdim = mesh.geometry.dim - V = functionspace(mesh, ('Lagrange', degree, (gdim,))) + V = functionspace(mesh, ("Lagrange", degree, (gdim,))) nullspace = build_elastic_nullspace(V) assert not la.is_orthonormal(nullspace, eps=1.0e-4) la.orthonormalize(nullspace) @@ -107,18 +107,23 @@ def test_nullspace_orthogonal(mesh, degree): x.destroy() -@pytest.mark.parametrize("mesh", [ - create_unit_square(MPI.COMM_WORLD, 12, 13), - create_box(MPI.COMM_WORLD, - [np.array([0.8, -0.2, 1.2]), - np.array([3.0, 11.0, -5.0])], [12, 18, 25], - cell_type=CellType.tetrahedron, - ghost_mode=GhostMode.none), -]) +@pytest.mark.parametrize( + "mesh", + [ + create_unit_square(MPI.COMM_WORLD, 12, 13), + create_box( + MPI.COMM_WORLD, + [np.array([0.8, -0.2, 1.2]), np.array([3.0, 11.0, -5.0])], + [12, 18, 25], + cell_type=CellType.tetrahedron, + ghost_mode=GhostMode.none, + ), + ], +) @pytest.mark.parametrize("degree", [1, 2]) def test_nullspace_check(mesh, degree): gdim = mesh.geometry.dim - V = functionspace(mesh, ('Lagrange', degree, (gdim,))) + V = functionspace(mesh, ("Lagrange", degree, (gdim,))) u, v = TrialFunction(V), TestFunction(V) E, nu = 2.0e2, 0.3 @@ -126,8 +131,7 @@ def test_nullspace_check(mesh, degree): lmbda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) def sigma(w, gdim): - return 2.0 * mu * ufl.sym(grad(w)) + lmbda * ufl.tr( - grad(w)) * ufl.Identity(gdim) + return 2.0 * mu * ufl.sym(grad(w)) + lmbda * ufl.tr(grad(w)) * ufl.Identity(gdim) a = form(inner(sigma(u, mesh.geometry.dim), grad(v)) * dx) diff --git a/python/test/unit/la/test_sparsity_pattern.py b/python/test/unit/la/test_sparsity_pattern.py index 6730e53c718..96bb74c1994 100644 --- a/python/test/unit/la/test_sparsity_pattern.py +++ b/python/test/unit/la/test_sparsity_pattern.py @@ -17,8 +17,11 @@ def test_add_diagonal(): mesh = create_unit_square(MPI.COMM_WORLD, 10, 10) gdim = mesh.geometry.dim V = functionspace(mesh, ("Lagrange", 1, (gdim,))) - pattern = SparsityPattern(mesh.comm, [V.dofmap.index_map, V.dofmap.index_map], - [V.dofmap.index_map_bs, V.dofmap.index_map_bs]) + pattern = SparsityPattern( + mesh.comm, + [V.dofmap.index_map, V.dofmap.index_map], + [V.dofmap.index_map_bs, V.dofmap.index_map_bs], + ) mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim) facets = exterior_facet_indices(mesh.topology) blocks = locate_dofs_topological(V, mesh.topology.dim - 1, facets) diff --git a/python/test/unit/la/test_vector_scatter.py b/python/test/unit/la/test_vector_scatter.py index c4145062859..d746da7c77f 100644 --- a/python/test/unit/la/test_vector_scatter.py +++ b/python/test/unit/la/test_vector_scatter.py @@ -17,11 +17,10 @@ from dolfinx.mesh import create_unit_square -@pytest.mark.parametrize("e", [ - element("Lagrange", "triangle", 1), - element("Lagrange", "triangle", 1, shape=(2,))]) +@pytest.mark.parametrize( + "e", [element("Lagrange", "triangle", 1), element("Lagrange", "triangle", 1, shape=(2,))] +) def test_scatter_forward(e): - mesh = create_unit_square(MPI.COMM_WORLD, 5, 5) V = functionspace(mesh, e) u = Function(V) @@ -47,11 +46,10 @@ def test_scatter_forward(e): assert np.allclose(u.x.array[local_size:], ghost_owners) -@pytest.mark.parametrize("e", [ - element("Lagrange", "triangle", 1), - element("Lagrange", "triangle", 1, shape=(2, ))]) +@pytest.mark.parametrize( + "e", [element("Lagrange", "triangle", 1), element("Lagrange", "triangle", 1, shape=(2,))] +) def test_scatter_reverse(e): - comm = MPI.COMM_WORLD mesh = create_unit_square(MPI.COMM_WORLD, 5, 5) V = functionspace(mesh, e) diff --git a/python/test/unit/mesh/test_cell.py b/python/test/unit/mesh/test_cell.py index c5f90a44d81..628c6a45cc6 100644 --- a/python/test/unit/mesh/test_cell.py +++ b/python/test/unit/mesh/test_cell.py @@ -29,7 +29,7 @@ def test_distance_interval(): def test_distance_triangle(): shape, degree = "triangle", 1 domain = basix.create_element(basix.ElementFamily.P, basix.cell.string_to_type(shape), degree) - x = np.array([[0., 0., 0.], [0., 1., 0.], [1., 1., 0.]], dtype=np.float64) + x = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]], dtype=np.float64) cells = [[0, 1, 2]] mesh = create_mesh(MPI.COMM_WORLD, cells, x, domain) d = np.array([-1.0, -1.0, 0.0]) @@ -46,7 +46,7 @@ def test_distance_tetrahedron(): shape = "tetrahedron" degree = 1 domain = ufl.Mesh(element("Lagrange", shape, degree, gdim=gdim, shape=(3,), dtype=np.float64)) - x = np.array([[0., 0., 0.], [0., 1., 0.], [0., 1., 1.], [1, 1., 1]], dtype=np.float64) + x = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1, 1.0, 1]], dtype=np.float64) cells = [[0, 1, 2, 3]] mesh = create_mesh(MPI.COMM_WORLD, cells, x, domain) d = np.array([-1.0, -1.0, -1.0]) diff --git a/python/test/unit/mesh/test_dual_graph.py b/python/test/unit/mesh/test_dual_graph.py index b31740a2027..b1a7675cb20 100644 --- a/python/test/unit/mesh/test_dual_graph.py +++ b/python/test/unit/mesh/test_dual_graph.py @@ -1,4 +1,3 @@ - from mpi4py import MPI import numpy as np @@ -23,7 +22,7 @@ def test_dgrsph_1d(): size = MPI.COMM_WORLD.Get_size() n0 = rank * 3 x = n0 + 3 - if (rank == size - 1): + if rank == size - 1: x = 0 # Circular chain of interval cells cells = [[n0, n0 + 1], [n0 + 1, n0 + 2], [n0 + 2, x]] diff --git a/python/test/unit/mesh/test_face.py b/python/test/unit/mesh/test_face.py index 13aa57d5308..c7c66accf6a 100644 --- a/python/test/unit/mesh/test_face.py +++ b/python/test/unit/mesh/test_face.py @@ -43,9 +43,11 @@ def test_area(cube, square): def test_normals(cube, square): - """ Test cell normals for a subset of facets """ + """Test cell normals for a subset of facets""" + def left_side(x): return np.isclose(x[0], 0) + fdim = cube.topology.dim - 1 facets = locate_entities_boundary(cube, fdim, left_side) normals = cell_normals(cube._cpp_object, fdim, facets) diff --git a/python/test/unit/mesh/test_ghost_mesh.py b/python/test/unit/mesh/test_ghost_mesh.py index acf42918769..6a8cd78e246 100644 --- a/python/test/unit/mesh/test_ghost_mesh.py +++ b/python/test/unit/mesh/test_ghost_mesh.py @@ -9,7 +9,13 @@ import numpy as np import pytest -from dolfinx.mesh import GhostMode, compute_midpoints, create_unit_cube, create_unit_interval, create_unit_square +from dolfinx.mesh import ( + GhostMode, + compute_midpoints, + create_unit_cube, + create_unit_interval, + create_unit_square, +) @pytest.mark.xfail(reason="Shared vertex currently disabled") @@ -26,10 +32,16 @@ def test_ghost_facet_1d(): assert mesh.topology.index_map(1).size_global == N -@pytest.mark.parametrize("mode", - [GhostMode.shared_facet, - pytest.param(GhostMode.shared_vertex, - marks=pytest.mark.xfail(reason="Shared vertex currently disabled"))]) +@pytest.mark.parametrize( + "mode", + [ + GhostMode.shared_facet, + pytest.param( + GhostMode.shared_vertex, + marks=pytest.mark.xfail(reason="Shared vertex currently disabled"), + ), + ], +) def test_ghost_2d(mode): N = 8 num_cells = N * N * 2 @@ -55,10 +67,17 @@ def test_ghost_3d(mode): assert mesh.topology.index_map(3).size_global == num_cells -@pytest.mark.parametrize("mode", - [GhostMode.none, GhostMode.shared_facet, - pytest.param(GhostMode.shared_vertex, - marks=pytest.mark.xfail(reason="Shared vertex currently disabled"))]) +@pytest.mark.parametrize( + "mode", + [ + GhostMode.none, + GhostMode.shared_facet, + pytest.param( + GhostMode.shared_vertex, + marks=pytest.mark.xfail(reason="Shared vertex currently disabled"), + ), + ], +) def test_ghost_connectivities(mode): # Ghosted mesh meshG = create_unit_square(MPI.COMM_WORLD, 4, 4, ghost_mode=mode) diff --git a/python/test/unit/mesh/test_higher_order_mesh.py b/python/test/unit/mesh/test_higher_order_mesh.py index a153596ac19..2ea3aa4ef20 100644 --- a/python/test/unit/mesh/test_higher_order_mesh.py +++ b/python/test/unit/mesh/test_higher_order_mesh.py @@ -45,25 +45,33 @@ def check_cell_volume(points, cell, domain, volume, dtype): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_submesh(order, dtype): # Generate a single cell higher order mesh points = [] points += [[i / order, j / order, 0] for j in range(order + 1) for i in range(order + 1 - j)] for k in range(1, order): - points += [[i / order, j / order + 0.1, k / order] - for j in range(order + 1 - k) for i in range(order + 1 - k - j)] + points += [ + [i / order, j / order + 0.1, k / order] + for j in range(order + 1 - k) + for i in range(order + 1 - k - j) + ] points += [[0, 0, 1]] def coord_to_vertex(x, y, z): - return z * ( - 3 * order ** 2 - 3 * order * z + 12 * order + z ** 2 - 6 * z + 11 - ) // 6 + y * (2 * (order - z) + 3 - y) // 2 + x + return ( + z * (3 * order**2 - 3 * order * z + 12 * order + z**2 - 6 * z + 11) // 6 + + y * (2 * (order - z) + 3 - y) // 2 + + x + ) # Define a cell using DOLFINx ordering - cell = [coord_to_vertex(x, y, z) for x, y, z in [(0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)]] + cell = [ + coord_to_vertex(x, y, z) + for x, y, z in [(0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)] + ] if order > 1: for i in range(1, order): @@ -97,9 +105,17 @@ def coord_to_vertex(x, y, z): for i in range(1, order - j - k): cell.append(coord_to_vertex(i, j, k)) - domain = ufl.Mesh(element("Lagrange", "tetrahedron", order, gdim=3, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(3,), - dtype=dtype)) + domain = ufl.Mesh( + element( + "Lagrange", + "tetrahedron", + order, + gdim=3, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(3,), + dtype=dtype, + ) + ) points = np.array(points, dtype=dtype) mesh = create_mesh(MPI.COMM_WORLD, [cell], points, domain) for i in range(mesh.topology.dim): @@ -121,7 +137,7 @@ def coord_to_vertex(x, y, z): @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_triangle_mesh(order, dtype): points = [] @@ -147,31 +163,47 @@ def coord_to_vertex(x, y): for i in range(1, order - j): cell.append(coord_to_vertex(i, j)) - domain = ufl.Mesh(element("Lagrange", "triangle", order, gdim=2, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(2, ), - dtype=dtype)) + domain = ufl.Mesh( + element( + "Lagrange", + "triangle", + order, + gdim=2, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(2,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 0.5, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_tetrahedron_mesh(order, dtype): points = [] points += [[i / order, j / order, 0] for j in range(order + 1) for i in range(order + 1 - j)] for k in range(1, order): - points += [[i / order, j / order + 0.1, k / order] for j in range(order + 1 - k) - for i in range(order + 1 - k - j)] + points += [ + [i / order, j / order + 0.1, k / order] + for j in range(order + 1 - k) + for i in range(order + 1 - k - j) + ] points += [[0, 0, 1]] def coord_to_vertex(x, y, z): - return z * ( - 3 * order ** 2 - 3 * order * z + 12 * order + z ** 2 - 6 * z + 11 - ) // 6 + y * (2 * (order - z) + 3 - y) // 2 + x + return ( + z * (3 * order**2 - 3 * order * z + 12 * order + z**2 - 6 * z + 11) // 6 + + y * (2 * (order - z) + 3 - y) // 2 + + x + ) # Define a cell using DOLFINx ordering - cell = [coord_to_vertex(x, y, z) for x, y, z in [(0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)]] + cell = [ + coord_to_vertex(x, y, z) + for x, y, z in [(0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)] + ] if order > 1: for i in range(1, order): @@ -205,14 +237,22 @@ def coord_to_vertex(x, y, z): for i in range(1, order - j - k): cell.append(coord_to_vertex(i, j, k)) - domain = ufl.Mesh(element( - "Lagrange", "tetrahedron", order, gdim=3, lagrange_variant=basix.LagrangeVariant.equispaced, - shape=(3, ), dtype=dtype)) + domain = ufl.Mesh( + element( + "Lagrange", + "tetrahedron", + order, + gdim=3, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(3,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1 / 6, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', [1, 2, 3, 4]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_quadrilateral_mesh(order, dtype): random.seed(13) @@ -242,22 +282,33 @@ def coord_to_vertex(x, y): for i in range(1, order): cell.append(coord_to_vertex(i, j)) - domain = ufl.Mesh(element("Q", "quadrilateral", order, gdim=2, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(2, ), - dtype=dtype)) + domain = ufl.Mesh( + element( + "Q", + "quadrilateral", + order, + gdim=2, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(2,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', [1, 2, 3, 4]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_hexahedron_mesh(order, dtype): random.seed(13) points = [] points += [[i / order, j / order, 0] for j in range(order + 1) for i in range(order + 1)] for k in range(1, order): - points += [[i / order, j / order + 0.1, k / order] for j in range(order + 1) - for i in range(order + 1)] + points += [ + [i / order, j / order + 0.1, k / order] + for j in range(order + 1) + for i in range(order + 1) + ] points += [[i / order, j / order, 1] for j in range(order + 1) for i in range(order + 1)] @@ -265,9 +316,19 @@ def coord_to_vertex(x, y, z): return (order + 1) ** 2 * z + (order + 1) * y + x # Define a cell using DOLFINx ordering - cell = [coord_to_vertex(x, y, z) for x, y, z in [ - (0, 0, 0), (order, 0, 0), (0, order, 0), (order, order, 0), - (0, 0, order), (order, 0, order), (0, order, order), (order, order, order)]] + cell = [ + coord_to_vertex(x, y, z) + for x, y, z in [ + (0, 0, 0), + (order, 0, 0), + (0, order, 0), + (order, order, 0), + (0, 0, order), + (order, 0, order), + (0, order, order), + (order, order, order), + ] + ] if order > 1: for i in range(1, order): @@ -319,13 +380,22 @@ def coord_to_vertex(x, y, z): for i in range(1, order): cell.append(coord_to_vertex(i, j, k)) - domain = ufl.Mesh(element("Q", "hexahedron", order, gdim=3, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(3, ), dtype=dtype)) + domain = ufl.Mesh( + element( + "Q", + "hexahedron", + order, + gdim=3, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(3,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_triangle_mesh_vtk(order, dtype): points = [] @@ -358,14 +428,22 @@ def coord_to_vertex(x, y): raise NotImplementedError cell = np.array(cell)[perm_vtk(CellType.triangle, len(cell))] - domain = ufl.Mesh(element("Lagrange", "triangle", order, gdim=2, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(2,), - dtype=dtype)) + domain = ufl.Mesh( + element( + "Lagrange", + "triangle", + order, + gdim=2, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(2,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 0.5, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 5)) +@pytest.mark.parametrize("order", range(1, 5)) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_tetrahedron_mesh_vtk(order, dtype): if order > 3: @@ -373,20 +451,27 @@ def test_tetrahedron_mesh_vtk(order, dtype): points = [] points += [[i / order, j / order, 0] for j in range(order + 1) for i in range(order + 1 - j)] for k in range(1, order): - points += [[i / order, j / order + 0.1, k / order] for j in range(order + 1 - k) - for i in range(order + 1 - k - j)] + points += [ + [i / order, j / order + 0.1, k / order] + for j in range(order + 1 - k) + for i in range(order + 1 - k - j) + ] points += [[0, 0, 1]] def coord_to_vertex(x, y, z): - return z * ( - 3 * order ** 2 - 3 * order * z + 12 * order + z ** 2 - 6 * z + 11 - ) // 6 + y * (2 * (order - z) + 3 - y) // 2 + x + return ( + z * (3 * order**2 - 3 * order * z + 12 * order + z**2 - 6 * z + 11) // 6 + + y * (2 * (order - z) + 3 - y) // 2 + + x + ) # Make the cell, following # https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/ - cell = [coord_to_vertex(x, y, z) for x, y, z in [ - (0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)]] + cell = [ + coord_to_vertex(x, y, z) + for x, y, z in [(0, 0, 0), (order, 0, 0), (0, order, 0), (0, 0, order)] + ] if order > 1: for i in range(1, order): @@ -452,14 +537,22 @@ def coord_to_vertex(x, y, z): cell.append(coord_to_vertex(i, j, k)) cell = np.array(cell)[perm_vtk(CellType.tetrahedron, len(cell))] - domain = ufl.Mesh(element("Lagrange", "tetrahedron", order, gdim=3, - lagrange_variant=basix.LagrangeVariant.equispaced, - shape=(3,), dtype=dtype)) + domain = ufl.Mesh( + element( + "Lagrange", + "tetrahedron", + order, + gdim=3, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(3,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1 / 6, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', [1, 2, 3, 4]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_quadrilateral_mesh_vtk(order, dtype): random.seed(13) @@ -475,8 +568,7 @@ def coord_to_vertex(x, y): # Make the cell, following # https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/ - cell = [coord_to_vertex(i, j) - for i, j in [(0, 0), (order, 0), (order, order), (0, order)]] + cell = [coord_to_vertex(i, j) for i, j in [(0, 0), (order, 0), (order, order), (0, order)]] if order > 1: for i in range(1, order): cell.append(coord_to_vertex(i, 0)) @@ -492,13 +584,22 @@ def coord_to_vertex(x, y): cell.append(coord_to_vertex(i, j)) cell = np.array(cell)[perm_vtk(CellType.quadrilateral, len(cell))] - domain = ufl.Mesh(element("Q", "quadrilateral", order, gdim=2, - lagrange_variant=basix.LagrangeVariant.equispaced, shape=(2, ), dtype=dtype)) + domain = ufl.Mesh( + element( + "Q", + "quadrilateral", + order, + gdim=2, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(2,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', [1, 2, 3, 4]) +@pytest.mark.parametrize("order", [1, 2, 3, 4]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_hexahedron_mesh_vtk(order, dtype): if order > 2: @@ -508,7 +609,11 @@ def test_hexahedron_mesh_vtk(order, dtype): points = [] points += [[i / order, j / order, 0] for j in range(order + 1) for i in range(order + 1)] for k in range(1, order): - points += [[i / order, j / order + 0.1, k / order] for j in range(order + 1) for i in range(order + 1)] + points += [ + [i / order, j / order + 0.1, k / order] + for j in range(order + 1) + for i in range(order + 1) + ] points += [[i / order, j / order, 1] for j in range(order + 1) for i in range(order + 1)] @@ -517,9 +622,19 @@ def coord_to_vertex(x, y, z): # Make the cell, following # https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/ - cell = [coord_to_vertex(x, y, z) for x, y, z in [ - (0, 0, 0), (order, 0, 0), (order, order, 0), (0, order, 0), - (0, 0, order), (order, 0, order), (order, order, order), (0, order, order)]] + cell = [ + coord_to_vertex(x, y, z) + for x, y, z in [ + (0, 0, 0), + (order, 0, 0), + (order, order, 0), + (0, order, 0), + (0, 0, order), + (order, 0, order), + (order, order, order), + (0, order, order), + ] + ] if order > 1: for i in range(1, order): @@ -576,18 +691,29 @@ def coord_to_vertex(x, y, z): cell.append(coord_to_vertex(i, j, k)) cell = np.array(cell)[perm_vtk(CellType.hexahedron, len(cell))] - domain = ufl.Mesh(element( - "Q", "hexahedron", order, gdim=3, lagrange_variant=basix.LagrangeVariant.equispaced, shape=(3,), - dtype=dtype)) + domain = ufl.Mesh( + element( + "Q", + "hexahedron", + order, + gdim=3, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(3,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 1, dtype=dtype) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize("vtk,dolfin,cell_type", [ - ([0, 1, 2, 3, 4, 5], [0, 1, 2, 4, 5, 3], CellType.triangle), - ([0, 1, 2, 3], [0, 1, 3, 2], CellType.quadrilateral), - ([0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 3, 2, 4, 5, 7, 6], CellType.hexahedron) -]) +@pytest.mark.parametrize( + "vtk,dolfin,cell_type", + [ + ([0, 1, 2, 3, 4, 5], [0, 1, 2, 4, 5, 3], CellType.triangle), + ([0, 1, 2, 3], [0, 1, 3, 2], CellType.quadrilateral), + ([0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 3, 2, 4, 5, 7, 6], CellType.hexahedron), + ], +) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_map_vtk_to_dolfin(vtk, dolfin, cell_type, dtype): p = perm_vtk(cell_type, len(vtk)) @@ -603,15 +729,17 @@ def test_map_vtk_to_dolfin(vtk, dolfin, cell_type, dtype): @pytest.mark.skip_in_parallel @pytest.mark.parametrize("dtype", [np.float64]) def test_xdmf_input_tri(datadir, dtype): - with XDMFFile(MPI.COMM_WORLD, Path(datadir, "mesh.xdmf"), "r", encoding=XDMFFile.Encoding.ASCII) as xdmf: + with XDMFFile( + MPI.COMM_WORLD, Path(datadir, "mesh.xdmf"), "r", encoding=XDMFFile.Encoding.ASCII + ) as xdmf: mesh = xdmf.read_mesh(name="Grid") surface = assemble_scalar(form(1 * dx(mesh), dtype=dtype)) assert mesh.comm.allreduce(surface, op=MPI.SUM) == pytest.approx(4 * np.pi, rel=1e-4) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 4)) -@pytest.mark.parametrize('cell_type', [CellType.triangle, CellType.quadrilateral]) +@pytest.mark.parametrize("order", range(1, 4)) +@pytest.mark.parametrize("cell_type", [CellType.triangle, CellType.quadrilateral]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_gmsh_input_2d(order, cell_type, dtype): try: @@ -643,8 +771,14 @@ def test_gmsh_input_2d(order, cell_type, dtype): x = points[srt] element_types, element_tags, node_tags = gmsh.model.mesh.getElements(dim=2) - name, dim, order, num_nodes, local_coords, num_first_order_nodes = gmsh.model.mesh.getElementProperties( - element_types[0]) + ( + name, + dim, + order, + num_nodes, + local_coords, + num_first_order_nodes, + ) = gmsh.model.mesh.getElementProperties(element_types[0]) cells = node_tags[0].reshape(-1, num_nodes) - 1 if cell_type == CellType.triangle: @@ -658,12 +792,14 @@ def test_gmsh_input_2d(order, cell_type, dtype): mesh = create_mesh(MPI.COMM_WORLD, cells, x, ufl_mesh(gmsh_cell_id, x.shape[1], dtype=dtype)) surface = assemble_scalar(form(1 * dx(mesh), dtype=dtype)) - assert mesh.comm.allreduce(surface, op=MPI.SUM) == pytest.approx(4 * np.pi, rel=10 ** (-1 - order)) + assert mesh.comm.allreduce(surface, op=MPI.SUM) == pytest.approx( + 4 * np.pi, rel=10 ** (-1 - order) + ) @pytest.mark.skip_in_parallel -@pytest.mark.parametrize('order', range(1, 4)) -@pytest.mark.parametrize('cell_type', [CellType.tetrahedron, CellType.hexahedron]) +@pytest.mark.parametrize("order", range(1, 4)) +@pytest.mark.parametrize("cell_type", [CellType.tetrahedron, CellType.hexahedron]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_gmsh_input_3d(order, cell_type, dtype): try: @@ -701,14 +837,24 @@ def test_gmsh_input_3d(order, cell_type, dtype): x = points[srt] element_types, element_tags, node_tags = gmsh.model.mesh.getElements(dim=3) - name, dim, order, num_nodes, local_coords, num_first_order_nodes = gmsh.model.mesh.getElementProperties( - element_types[0]) + ( + name, + dim, + order, + num_nodes, + local_coords, + num_first_order_nodes, + ) = gmsh.model.mesh.getElementProperties(element_types[0]) cells = node_tags[0].reshape(-1, num_nodes) - 1 if cell_type == CellType.tetrahedron: - gmsh_cell_id = MPI.COMM_WORLD.bcast(gmsh.model.mesh.getElementType("tetrahedron", order), root=0) + gmsh_cell_id = MPI.COMM_WORLD.bcast( + gmsh.model.mesh.getElementType("tetrahedron", order), root=0 + ) elif cell_type == CellType.hexahedron: - gmsh_cell_id = MPI.COMM_WORLD.bcast(gmsh.model.mesh.getElementType("hexahedron", order), root=0) + gmsh_cell_id = MPI.COMM_WORLD.bcast( + gmsh.model.mesh.getElementType("hexahedron", order), root=0 + ) gmsh.finalize() # Permute the mesh topology from Gmsh ordering to DOLFINx ordering @@ -725,22 +871,39 @@ def test_gmsh_input_3d(order, cell_type, dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_quadrilateral_cell_order_3(dtype): points = [ - [0., 0.], [1., 0.], [0., 1.], [1., 1.], - [1 / 3, 2 / 9], [2 / 3, 2 / 9], - [0., 1 / 3], [0., 2 / 3], - [1., 1 / 3], [1., 2 / 3], - [1 / 3, 1.], [2 / 3, 1.], - [1 / 3, 13 / 27], [2 / 3, 13 / 27], - [1 / 3, 20 / 27], [2 / 3, 20 / 27] + [0.0, 0.0], + [1.0, 0.0], + [0.0, 1.0], + [1.0, 1.0], + [1 / 3, 2 / 9], + [2 / 3, 2 / 9], + [0.0, 1 / 3], + [0.0, 2 / 3], + [1.0, 1 / 3], + [1.0, 2 / 3], + [1 / 3, 1.0], + [2 / 3, 1.0], + [1 / 3, 13 / 27], + [2 / 3, 13 / 27], + [1 / 3, 20 / 27], + [2 / 3, 20 / 27], ] cell = list(range(16)) - domain = ufl.Mesh(element( - "Q", "quadrilateral", 3, gdim=2, lagrange_variant=basix.LagrangeVariant.equispaced, - shape=(2,), dtype=dtype)) + domain = ufl.Mesh( + element( + "Q", + "quadrilateral", + 3, + gdim=2, + lagrange_variant=basix.LagrangeVariant.equispaced, + shape=(2,), + dtype=dtype, + ) + ) check_cell_volume(points, cell, domain, 5 / 6, dtype=dtype) -@pytest.mark.parametrize('order', range(1, 11)) +@pytest.mark.parametrize("order", range(1, 11)) def test_vtk_perm_tetrahedron(order): size = (order + 1) * (order + 2) * (order + 3) // 6 p = perm_vtk(CellType.tetrahedron, size) @@ -752,68 +915,992 @@ def test_vtk_perm_tetrahedron(order): if order == 3: q = [0, 1, 2, 3, 14, 15, 8, 9, 13, 12, 10, 11, 6, 7, 4, 5, 18, 16, 17, 19] if order == 4: - q = [0, 1, 2, 3, 19, 20, 21, 10, 11, 12, 18, 17, 16, 13, 14, 15, 7, 8, 9, 4, 5, 6, - 28, 29, 30, 23, 24, 22, 25, 27, 26, 31, 33, 32, 34] + q = [ + 0, + 1, + 2, + 3, + 19, + 20, + 21, + 10, + 11, + 12, + 18, + 17, + 16, + 13, + 14, + 15, + 7, + 8, + 9, + 4, + 5, + 6, + 28, + 29, + 30, + 23, + 24, + 22, + 25, + 27, + 26, + 31, + 33, + 32, + 34, + ] if order == 5: - q = [0, 1, 2, 3, 24, 25, 26, 27, 12, 13, 14, 15, 23, 22, 21, 20, 16, 17, 18, 19, 8, - 9, 10, 11, 4, 5, 6, 7, 40, 42, 45, 41, 44, 43, 30, 33, 28, 32, 31, 29, 34, 39, - 36, 37, 38, 35, 46, 51, 48, 49, 50, 47, 52, 53, 54, 55] + q = [ + 0, + 1, + 2, + 3, + 24, + 25, + 26, + 27, + 12, + 13, + 14, + 15, + 23, + 22, + 21, + 20, + 16, + 17, + 18, + 19, + 8, + 9, + 10, + 11, + 4, + 5, + 6, + 7, + 40, + 42, + 45, + 41, + 44, + 43, + 30, + 33, + 28, + 32, + 31, + 29, + 34, + 39, + 36, + 37, + 38, + 35, + 46, + 51, + 48, + 49, + 50, + 47, + 52, + 53, + 54, + 55, + ] if order == 6: - q = [0, 1, 2, 3, 29, 30, 31, 32, 33, 14, 15, 16, 17, 18, 28, 27, 26, 25, 24, 19, 20, - 21, 22, 23, 9, 10, 11, 12, 13, 4, 5, 6, 7, 8, 54, 57, 63, 55, 56, 60, 62, 61, - 58, 59, 37, 43, 34, 40, 42, 41, 38, 35, 36, 39, 44, 53, 47, 48, 51, 52, 50, 46, - 45, 49, 64, 73, 67, 68, 71, 72, 70, 66, 65, 69, 74, 76, 79, 83, 75, 78, 77, 80, - 81, 82] + q = [ + 0, + 1, + 2, + 3, + 29, + 30, + 31, + 32, + 33, + 14, + 15, + 16, + 17, + 18, + 28, + 27, + 26, + 25, + 24, + 19, + 20, + 21, + 22, + 23, + 9, + 10, + 11, + 12, + 13, + 4, + 5, + 6, + 7, + 8, + 54, + 57, + 63, + 55, + 56, + 60, + 62, + 61, + 58, + 59, + 37, + 43, + 34, + 40, + 42, + 41, + 38, + 35, + 36, + 39, + 44, + 53, + 47, + 48, + 51, + 52, + 50, + 46, + 45, + 49, + 64, + 73, + 67, + 68, + 71, + 72, + 70, + 66, + 65, + 69, + 74, + 76, + 79, + 83, + 75, + 78, + 77, + 80, + 81, + 82, + ] if order == 7: - q = [0, 1, 2, 3, 34, 35, 36, 37, 38, 39, 16, 17, 18, 19, 20, 21, 33, 32, 31, 30, 29, - 28, 22, 23, 24, 25, 26, 27, 10, 11, 12, 13, 14, 15, 4, 5, 6, 7, 8, 9, 70, 74, - 84, 71, 72, 73, 78, 81, 83, 82, 79, 75, 76, 77, 80, 44, 54, 40, 48, 51, 53, 52, - 49, 45, 41, 42, 43, 47, 50, 46, 55, 69, 59, 60, 64, 67, 68, 66, 63, 58, 57, 56, - 61, 65, 62, 85, 99, 89, 90, 94, 97, 98, 96, 93, 88, 87, 86, 91, 95, 92, 100, - 103, 109, 119, 101, 102, 106, 108, 107, 104, 110, 116, 112, 117, 115, 118, 111, - 114, 113, 105] + q = [ + 0, + 1, + 2, + 3, + 34, + 35, + 36, + 37, + 38, + 39, + 16, + 17, + 18, + 19, + 20, + 21, + 33, + 32, + 31, + 30, + 29, + 28, + 22, + 23, + 24, + 25, + 26, + 27, + 10, + 11, + 12, + 13, + 14, + 15, + 4, + 5, + 6, + 7, + 8, + 9, + 70, + 74, + 84, + 71, + 72, + 73, + 78, + 81, + 83, + 82, + 79, + 75, + 76, + 77, + 80, + 44, + 54, + 40, + 48, + 51, + 53, + 52, + 49, + 45, + 41, + 42, + 43, + 47, + 50, + 46, + 55, + 69, + 59, + 60, + 64, + 67, + 68, + 66, + 63, + 58, + 57, + 56, + 61, + 65, + 62, + 85, + 99, + 89, + 90, + 94, + 97, + 98, + 96, + 93, + 88, + 87, + 86, + 91, + 95, + 92, + 100, + 103, + 109, + 119, + 101, + 102, + 106, + 108, + 107, + 104, + 110, + 116, + 112, + 117, + 115, + 118, + 111, + 114, + 113, + 105, + ] if order == 8: - q = [0, 1, 2, 3, 39, 40, 41, 42, 43, 44, 45, 18, 19, 20, 21, 22, 23, 24, 38, 37, 36, - 35, 34, 33, 32, 25, 26, 27, 28, 29, 30, 31, 11, 12, 13, 14, 15, 16, 17, 4, 5, 6, - 7, 8, 9, 10, 88, 93, 108, 89, 90, 91, 92, 98, 102, 105, 107, 106, 103, 99, 94, - 95, 97, 104, 96, 101, 100, 51, 66, 46, 56, 60, 63, 65, 64, 61, 57, 52, 47, 48, - 49, 50, 55, 62, 53, 59, 58, 54, 67, 87, 72, 73, 78, 82, 85, 86, 84, 81, 77, 71, - 70, 69, 68, 74, 83, 76, 79, 80, 75, 109, 129, 114, 115, 120, 124, 127, 128, 126, - 123, 119, 113, 112, 111, 110, 116, 125, 118, 121, 122, 117, 130, 134, 144, 164, - 131, 132, 133, 138, 141, 143, 142, 139, 135, 145, 155, 161, 148, 157, 162, 154, - 160, 163, 146, 147, 156, 153, 159, 151, 149, 158, 152, 136, 140, 137, 150] + q = [ + 0, + 1, + 2, + 3, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 38, + 37, + 36, + 35, + 34, + 33, + 32, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 88, + 93, + 108, + 89, + 90, + 91, + 92, + 98, + 102, + 105, + 107, + 106, + 103, + 99, + 94, + 95, + 97, + 104, + 96, + 101, + 100, + 51, + 66, + 46, + 56, + 60, + 63, + 65, + 64, + 61, + 57, + 52, + 47, + 48, + 49, + 50, + 55, + 62, + 53, + 59, + 58, + 54, + 67, + 87, + 72, + 73, + 78, + 82, + 85, + 86, + 84, + 81, + 77, + 71, + 70, + 69, + 68, + 74, + 83, + 76, + 79, + 80, + 75, + 109, + 129, + 114, + 115, + 120, + 124, + 127, + 128, + 126, + 123, + 119, + 113, + 112, + 111, + 110, + 116, + 125, + 118, + 121, + 122, + 117, + 130, + 134, + 144, + 164, + 131, + 132, + 133, + 138, + 141, + 143, + 142, + 139, + 135, + 145, + 155, + 161, + 148, + 157, + 162, + 154, + 160, + 163, + 146, + 147, + 156, + 153, + 159, + 151, + 149, + 158, + 152, + 136, + 140, + 137, + 150, + ] if order == 9: - q = [0, 1, 2, 3, 44, 45, 46, 47, 48, 49, 50, 51, 20, 21, 22, 23, 24, 25, 26, 27, 43, - 42, 41, 40, 39, 38, 37, 36, 28, 29, 30, 31, 32, 33, 34, 35, 12, 13, 14, 15, 16, - 17, 18, 19, 4, 5, 6, 7, 8, 9, 10, 11, 108, 114, 135, 109, 110, 111, 112, 113, - 120, 125, 129, 132, 134, 133, 130, 126, 121, 115, 116, 119, 131, 117, 118, 124, - 128, 127, 122, 123, 58, 79, 52, 64, 69, 73, 76, 78, 77, 74, 70, 65, 59, 53, 54, - 55, 56, 57, 63, 75, 60, 68, 72, 71, 66, 61, 62, 67, 80, 107, 86, 87, 93, 98, - 102, 105, 106, 104, 101, 97, 92, 85, 84, 83, 82, 81, 88, 103, 91, 94, 99, 100, - 96, 90, 89, 95, 136, 163, 142, 143, 149, 154, 158, 161, 162, 160, 157, 153, 148, - 141, 140, 139, 138, 137, 144, 159, 147, 150, 155, 156, 152, 146, 145, 151, 164, - 169, 184, 219, 165, 166, 167, 168, 174, 178, 181, 183, 182, 179, 175, 170, 185, - 200, 210, 216, 189, 203, 212, 217, 199, 209, 215, 218, 186, 188, 211, 187, 202, - 201, 198, 214, 193, 208, 206, 196, 190, 213, 197, 204, 207, 194, 171, 180, 173, - 176, 177, 172, 191, 192, 195, 205] + q = [ + 0, + 1, + 2, + 3, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 43, + 42, + 41, + 40, + 39, + 38, + 37, + 36, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 108, + 114, + 135, + 109, + 110, + 111, + 112, + 113, + 120, + 125, + 129, + 132, + 134, + 133, + 130, + 126, + 121, + 115, + 116, + 119, + 131, + 117, + 118, + 124, + 128, + 127, + 122, + 123, + 58, + 79, + 52, + 64, + 69, + 73, + 76, + 78, + 77, + 74, + 70, + 65, + 59, + 53, + 54, + 55, + 56, + 57, + 63, + 75, + 60, + 68, + 72, + 71, + 66, + 61, + 62, + 67, + 80, + 107, + 86, + 87, + 93, + 98, + 102, + 105, + 106, + 104, + 101, + 97, + 92, + 85, + 84, + 83, + 82, + 81, + 88, + 103, + 91, + 94, + 99, + 100, + 96, + 90, + 89, + 95, + 136, + 163, + 142, + 143, + 149, + 154, + 158, + 161, + 162, + 160, + 157, + 153, + 148, + 141, + 140, + 139, + 138, + 137, + 144, + 159, + 147, + 150, + 155, + 156, + 152, + 146, + 145, + 151, + 164, + 169, + 184, + 219, + 165, + 166, + 167, + 168, + 174, + 178, + 181, + 183, + 182, + 179, + 175, + 170, + 185, + 200, + 210, + 216, + 189, + 203, + 212, + 217, + 199, + 209, + 215, + 218, + 186, + 188, + 211, + 187, + 202, + 201, + 198, + 214, + 193, + 208, + 206, + 196, + 190, + 213, + 197, + 204, + 207, + 194, + 171, + 180, + 173, + 176, + 177, + 172, + 191, + 192, + 195, + 205, + ] if order == 10: - q = [0, 1, 2, 3, 49, 50, 51, 52, 53, 54, 55, 56, 57, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 48, 47, 46, 45, 44, 43, 42, 41, 40, 31, 32, 33, 34, 35, 36, 37, 38, 39, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 4, 5, 6, 7, 8, 9, 10, 11, 12, 130, 137, 165, - 131, 132, 133, 134, 135, 136, 144, 150, 155, 159, 162, 164, 163, 160, 156, 151, - 145, 138, 139, 143, 161, 140, 141, 142, 149, 154, 158, 157, 152, 146, 147, 148, - 153, 65, 93, 58, 72, 78, 83, 87, 90, 92, 91, 88, 84, 79, 73, 66, 59, 60, 61, 62, - 63, 64, 71, 89, 67, 77, 82, 86, 85, 80, 74, 68, 69, 70, 76, 81, 75, 94, 129, - 101, 102, 109, 115, 120, 124, 127, 128, 126, 123, 119, 114, 108, 100, 99, 98, - 97, 96, 95, 103, 125, 107, 110, 116, 121, 122, 118, 113, 106, 105, 104, 111, - 117, 112, 166, 201, 173, 174, 181, 187, 192, 196, 199, 200, 198, 195, 191, 186, - 180, 172, 171, 170, 169, 168, 167, 175, 197, 179, 182, 188, 193, 194, 190, 185, - 178, 177, 176, 183, 189, 184, 202, 208, 229, 285, 203, 204, 205, 206, 207, 214, - 219, 223, 226, 228, 227, 224, 220, 215, 209, 230, 251, 266, 276, 282, 235, 255, - 269, 278, 283, 250, 265, 275, 281, 284, 231, 234, 277, 232, 233, 254, 268, 267, - 252, 253, 249, 280, 240, 264, 274, 272, 259, 244, 247, 262, 236, 279, 248, 256, - 270, 273, 263, 245, 241, 260, 210, 225, 213, 216, 221, 222, 218, 212, 211, 217, - 237, 239, 246, 271, 238, 243, 242, 257, 258, 261] + q = [ + 0, + 1, + 2, + 3, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 41, + 40, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 130, + 137, + 165, + 131, + 132, + 133, + 134, + 135, + 136, + 144, + 150, + 155, + 159, + 162, + 164, + 163, + 160, + 156, + 151, + 145, + 138, + 139, + 143, + 161, + 140, + 141, + 142, + 149, + 154, + 158, + 157, + 152, + 146, + 147, + 148, + 153, + 65, + 93, + 58, + 72, + 78, + 83, + 87, + 90, + 92, + 91, + 88, + 84, + 79, + 73, + 66, + 59, + 60, + 61, + 62, + 63, + 64, + 71, + 89, + 67, + 77, + 82, + 86, + 85, + 80, + 74, + 68, + 69, + 70, + 76, + 81, + 75, + 94, + 129, + 101, + 102, + 109, + 115, + 120, + 124, + 127, + 128, + 126, + 123, + 119, + 114, + 108, + 100, + 99, + 98, + 97, + 96, + 95, + 103, + 125, + 107, + 110, + 116, + 121, + 122, + 118, + 113, + 106, + 105, + 104, + 111, + 117, + 112, + 166, + 201, + 173, + 174, + 181, + 187, + 192, + 196, + 199, + 200, + 198, + 195, + 191, + 186, + 180, + 172, + 171, + 170, + 169, + 168, + 167, + 175, + 197, + 179, + 182, + 188, + 193, + 194, + 190, + 185, + 178, + 177, + 176, + 183, + 189, + 184, + 202, + 208, + 229, + 285, + 203, + 204, + 205, + 206, + 207, + 214, + 219, + 223, + 226, + 228, + 227, + 224, + 220, + 215, + 209, + 230, + 251, + 266, + 276, + 282, + 235, + 255, + 269, + 278, + 283, + 250, + 265, + 275, + 281, + 284, + 231, + 234, + 277, + 232, + 233, + 254, + 268, + 267, + 252, + 253, + 249, + 280, + 240, + 264, + 274, + 272, + 259, + 244, + 247, + 262, + 236, + 279, + 248, + 256, + 270, + 273, + 263, + 245, + 241, + 260, + 210, + 225, + 213, + 216, + 221, + 222, + 218, + 212, + 211, + 217, + 237, + 239, + 246, + 271, + 238, + 243, + 242, + 257, + 258, + 261, + ] pt = [0 for i in p] for i, j in enumerate(p): @@ -825,7 +1912,7 @@ def test_vtk_perm_tetrahedron(order): assert q[j] == i -@pytest.mark.parametrize('order', range(1, 7)) +@pytest.mark.parametrize("order", range(1, 7)) def test_vtk_perm_hexahedron(order): size = (order + 1) ** 3 p = perm_vtk(CellType.hexahedron, size) @@ -833,57 +1920,795 @@ def test_vtk_perm_hexahedron(order): if order == 1: q = [0, 1, 3, 2, 4, 5, 7, 6] if order == 2: - q = [0, 1, 3, 2, 4, 5, 7, 6, 8, 11, 13, 9, 16, 18, 19, 17, 10, 12, 15, 14, 22, 23, - 21, 24, 20, 25, 26] + q = [ + 0, + 1, + 3, + 2, + 4, + 5, + 7, + 6, + 8, + 11, + 13, + 9, + 16, + 18, + 19, + 17, + 10, + 12, + 15, + 14, + 22, + 23, + 21, + 24, + 20, + 25, + 26, + ] if order == 3: - q = [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 14, 15, 18, 19, 10, 11, 24, 25, 28, 29, 30, 31, - 26, 27, 12, 13, 16, 17, 22, 23, 20, 21, 40, 41, 42, 43, 44, 45, 46, 47, 36, - 37, 38, 39, 48, 49, 50, 51, 32, 33, 34, 35, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63] + q = [ + 0, + 1, + 3, + 2, + 4, + 5, + 7, + 6, + 8, + 9, + 14, + 15, + 18, + 19, + 10, + 11, + 24, + 25, + 28, + 29, + 30, + 31, + 26, + 27, + 12, + 13, + 16, + 17, + 22, + 23, + 20, + 21, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 36, + 37, + 38, + 39, + 48, + 49, + 50, + 51, + 32, + 33, + 34, + 35, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + ] if order == 4: - q = [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 10, 17, 18, 19, 23, 24, 25, 11, 12, 13, 32, 33, - 34, 38, 39, 40, 41, 42, 43, 35, 36, 37, 14, 15, 16, 20, 21, 22, 29, 30, 31, - 26, 27, 28, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, - 78, 79, 53, 54, 55, 56, 57, 58, 59, 60, 61, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 44, 45, 46, 47, 48, 49, 50, 51, 52, 89, 90, 91, 92, 93, 94, 95, 96, 97, - 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, - 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124] + q = [ + 0, + 1, + 3, + 2, + 4, + 5, + 7, + 6, + 8, + 9, + 10, + 17, + 18, + 19, + 23, + 24, + 25, + 11, + 12, + 13, + 32, + 33, + 34, + 38, + 39, + 40, + 41, + 42, + 43, + 35, + 36, + 37, + 14, + 15, + 16, + 20, + 21, + 22, + 29, + 30, + 31, + 26, + 27, + 28, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + ] if order == 5: - q = [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 10, 11, 20, 21, 22, 23, 28, 29, 30, 31, 12, 13, - 14, 15, 40, 41, 42, 43, 48, 49, 50, 51, 52, 53, 54, 55, 44, 45, 46, 47, 16, - 17, 18, 19, 24, 25, 26, 27, 36, 37, 38, 39, 32, 33, 34, 35, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 72, 73, 74, 75, 76, 77, - 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 120, 121, 122, 123, 124, 125, 126, - 127, 128, 129, 130, 131, 132, 133, 134, 135, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 136, 137, 138, 139, 140, 141, 142, 143, 144, - 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, - 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, - 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, - 209, 210, 211, 212, 213, 214, 215] + q = [ + 0, + 1, + 3, + 2, + 4, + 5, + 7, + 6, + 8, + 9, + 10, + 11, + 20, + 21, + 22, + 23, + 28, + 29, + 30, + 31, + 12, + 13, + 14, + 15, + 40, + 41, + 42, + 43, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 44, + 45, + 46, + 47, + 16, + 17, + 18, + 19, + 24, + 25, + 26, + 27, + 36, + 37, + 38, + 39, + 32, + 33, + 34, + 35, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + ] if order == 6: - q = [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 10, 11, 12, 23, 24, 25, 26, 27, 33, 34, 35, 36, - 37, 13, 14, 15, 16, 17, 48, 49, 50, 51, 52, 58, 59, 60, 61, 62, 63, 64, 65, 66, - 67, 53, 54, 55, 56, 57, 18, 19, 20, 21, 22, 28, 29, 30, 31, 32, 43, 44, 45, 46, - 47, 38, 39, 40, 41, 42, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, - 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, - 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, - 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 168, - 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, - 185, 186, 187, 188, 189, 190, 191, 192, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, - 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 193, 194, 195, 196, - 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, - 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, - 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, - 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, - 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, - 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, - 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, - 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, - 341, 342] + q = [ + 0, + 1, + 3, + 2, + 4, + 5, + 7, + 6, + 8, + 9, + 10, + 11, + 12, + 23, + 24, + 25, + 26, + 27, + 33, + 34, + 35, + 36, + 37, + 13, + 14, + 15, + 16, + 17, + 48, + 49, + 50, + 51, + 52, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 53, + 54, + 55, + 56, + 57, + 18, + 19, + 20, + 21, + 22, + 28, + 29, + 30, + 31, + 32, + 43, + 44, + 45, + 46, + 47, + 38, + 39, + 40, + 41, + 42, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + 297, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308, + 309, + 310, + 311, + 312, + 313, + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + ] for i, j in enumerate(p): assert q[j] == i diff --git a/python/test/unit/mesh/test_manifold_point_search.py b/python/test/unit/mesh/test_manifold_point_search.py index 36fa1ef7458..46d2338b1c1 100644 --- a/python/test/unit/mesh/test_manifold_point_search.py +++ b/python/test/unit/mesh/test_manifold_point_search.py @@ -16,7 +16,7 @@ def test_manifold_point_search(): # Simple two-triangle surface in 3d vertices = np.array([[0.0, 0.0, 1.0], [1.0, 1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) cells = np.array([[0, 1, 2], [0, 1, 3]], dtype=np.int64) - domain = ufl.Mesh(element("Lagrange", "triangle", 1, gdim=3, shape=(2, ))) + domain = ufl.Mesh(element("Lagrange", "triangle", 1, gdim=3, shape=(2,))) mesh = create_mesh(MPI.COMM_WORLD, cells, vertices, domain) bb = bb_tree(mesh, mesh.topology.dim) @@ -26,9 +26,12 @@ def test_manifold_point_search(): colliding_cells = geometry.compute_colliding_cells(mesh, cell_candidates, points) # Extract vertices of cell - indices = _cpp.mesh.entities_to_geometry(mesh._cpp_object, mesh.topology.dim, - np.array([colliding_cells.links(0)[0], - colliding_cells.links(1)[0]]), False) + indices = _cpp.mesh.entities_to_geometry( + mesh._cpp_object, + mesh.topology.dim, + np.array([colliding_cells.links(0)[0], colliding_cells.links(1)[0]]), + False, + ) cell_vertices = mesh.geometry.x[indices] # Compare vertices with input diff --git a/python/test/unit/mesh/test_mesh.py b/python/test/unit/mesh/test_mesh.py index 34ea9f1a0f2..74bfd58d1b9 100644 --- a/python/test/unit/mesh/test_mesh.py +++ b/python/test/unit/mesh/test_mesh.py @@ -20,9 +20,21 @@ from dolfinx import mesh as _mesh from dolfinx.cpp.mesh import create_cell_partitioner, entities_to_geometry, is_simplex from dolfinx.fem import assemble_scalar, coordinate_element, form -from dolfinx.mesh import (CellType, DiagonalType, GhostMode, create_box, create_interval, create_rectangle, - create_submesh, create_unit_cube, create_unit_interval, create_unit_square, - exterior_facet_indices, locate_entities, locate_entities_boundary) +from dolfinx.mesh import ( + CellType, + DiagonalType, + GhostMode, + create_box, + create_interval, + create_rectangle, + create_submesh, + create_unit_cube, + create_unit_interval, + create_unit_square, + exterior_facet_indices, + locate_entities, + locate_entities_boundary, +) def submesh_topology_test(mesh, submesh, entity_map, vertex_map, entity_dim): @@ -55,7 +67,10 @@ def submesh_topology_test(mesh, submesh, entity_map, vertex_map, entity_dim): def submesh_geometry_test(mesh, submesh, entity_map, geom_map, entity_dim): submesh_geom_index_map = submesh.geometry.index_map() - assert submesh_geom_index_map.size_local + submesh_geom_index_map.num_ghosts == submesh.geometry.x.shape[0] + assert ( + submesh_geom_index_map.size_local + submesh_geom_index_map.num_ghosts + == submesh.geometry.x.shape[0] + ) # Some processes might not own or ghost entities if len(entity_map) > 0: @@ -69,24 +84,33 @@ def submesh_geometry_test(mesh, submesh, entity_map, geom_map, entity_dim): mesh_x_dofs = e_to_g[submesh_entity] for i in range(len(submesh_x_dofs)): assert mesh_x_dofs[i] == geom_map[submesh_x_dofs[i]] - assert np.allclose(mesh.geometry.x[mesh_x_dofs[i]], submesh.geometry.x[submesh_x_dofs[i]]) + assert np.allclose( + mesh.geometry.x[mesh_x_dofs[i]], submesh.geometry.x[submesh_x_dofs[i]] + ) def mesh_1d(dtype): """Create 1D mesh with degenerate cell""" mesh1d = create_unit_interval(MPI.COMM_WORLD, 4, dtype=dtype) - i1 = np.where((np.isclose(mesh1d.geometry.x, (0.75, 0., 0.))).all(axis=1))[0][0] - i2 = np.where((np.isclose(mesh1d.geometry.x, (1., 0., 0.))).all(axis=1))[0][0] + i1 = np.where((np.isclose(mesh1d.geometry.x, (0.75, 0.0, 0.0))).all(axis=1))[0][0] + i2 = np.where((np.isclose(mesh1d.geometry.x, (1.0, 0.0, 0.0))).all(axis=1))[0][0] mesh1d.geometry.x[i2] = mesh1d.geometry.x[i1] return mesh1d def mesh_2d(dtype): """Create 2D mesh with one equilateral triangle""" - mesh2d = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]), np.array([1., 1.])], [1, 1], - CellType.triangle, dtype, GhostMode.none, - create_cell_partitioner(GhostMode.none), DiagonalType.left) - i1 = np.where((np.isclose(mesh2d.geometry.x, (1., 1., 0.))).all(axis=1))[0][0] + mesh2d = create_rectangle( + MPI.COMM_WORLD, + [np.array([0.0, 0.0]), np.array([1.0, 1.0])], + [1, 1], + CellType.triangle, + dtype, + GhostMode.none, + create_cell_partitioner(GhostMode.none), + DiagonalType.left, + ) + i1 = np.where((np.isclose(mesh2d.geometry.x, (1.0, 1.0, 0.0))).all(axis=1))[0][0] mesh2d.geometry.x[i1, :2] += 0.5 * (math.sqrt(3.0) - 1.0) return mesh2d @@ -95,8 +119,8 @@ def mesh_2d(dtype): def mesh3d(dtype=np.float64): """Create 3D mesh with regular tetrahedron and degenerate cells""" mesh3d = create_unit_cube(MPI.COMM_WORLD, 1, 1, 1, dtype=dtype) - i1 = np.where((np.isclose(mesh3d.geometry.x, (0., 1., 0.))).all(axis=1))[0][0] - i2 = np.where((np.isclose(mesh3d.geometry.x, (1., 1., 1.))).all(axis=1))[0][0] + i1 = np.where((np.isclose(mesh3d.geometry.x, (0.0, 1.0, 0.0))).all(axis=1))[0][0] + i2 = np.where((np.isclose(mesh3d.geometry.x, (1.0, 1.0, 1.0))).all(axis=1))[0][0] mesh3d.geometry.x[i1][0] = 1.0 mesh3d.geometry.x[i2][1] = 0.0 return mesh3d @@ -105,8 +129,8 @@ def mesh3d(dtype=np.float64): def mesh_3d(dtype): """Create 3D mesh with regular tetrahedron and degenerate cells""" mesh3d = create_unit_cube(MPI.COMM_WORLD, 1, 1, 1, dtype=dtype) - i1 = np.where((np.isclose(mesh3d.geometry.x, (0., 1., 0.))).all(axis=1))[0][0] - i2 = np.where((np.isclose(mesh3d.geometry.x, (1., 1., 1.))).all(axis=1))[0][0] + i1 = np.where((np.isclose(mesh3d.geometry.x, (0.0, 1.0, 0.0))).all(axis=1))[0][0] + i2 = np.where((np.isclose(mesh3d.geometry.x, (1.0, 1.0, 1.0))).all(axis=1))[0][0] mesh3d.geometry.x[i1][0] = 1.0 mesh3d.geometry.x[i2][1] = 0.0 return mesh3d @@ -142,8 +166,14 @@ def square(): @pytest.fixture def rectangle(): - return create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]), np.array([2.0, 2.0])], - [5, 5], CellType.triangle, np.float64, GhostMode.none) + return create_rectangle( + MPI.COMM_WORLD, + [np.array([0.0, 0.0]), np.array([2.0, 2.0])], + [5, 5], + CellType.triangle, + np.float64, + GhostMode.none, + ) @pytest.fixture @@ -153,8 +183,14 @@ def cube(): @pytest.fixture def box(): - return create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([2, 2, 2])], - [2, 2, 5], CellType.tetrahedron, np.float64, GhostMode.none) + return create_box( + MPI.COMM_WORLD, + [np.array([0, 0, 0]), np.array([2, 2, 2])], + [2, 2, 5], + CellType.tetrahedron, + np.float64, + GhostMode.none, + ) @pytest.fixture @@ -170,6 +206,7 @@ def new_comm(comm): def test_UFLCell(interval, square, rectangle, cube, box): import ufl + assert ufl.interval == interval.ufl_cell() assert ufl.triangle == square.ufl_cell() assert ufl.triangle == rectangle.ufl_cell() @@ -238,8 +275,14 @@ def test_create_unit_square_hex(comm, dtype): def test_create_box_prism(): - mesh = create_box(MPI.COMM_WORLD, [[0., 0., 0.], [1., 1., 1.]], [2, 3, 4], CellType.prism, - np.float64, GhostMode.none) + mesh = create_box( + MPI.COMM_WORLD, + [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], + [2, 3, 4], + CellType.prism, + np.float64, + GhostMode.none, + ) assert mesh.topology.index_map(0).size_global == 60 assert mesh.topology.index_map(3).size_global == 48 @@ -290,7 +333,9 @@ def test_cell_h_prism(): def test_facet_h(ct): N = 3 mesh = create_unit_cube(MPI.COMM_WORLD, N, N, N, ct) - left_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, lambda x: np.isclose(x[0], 0)) + left_facets = locate_entities_boundary( + mesh, mesh.topology.dim - 1, lambda x: np.isclose(x[0], 0) + ) h = _cpp.mesh.h(mesh._cpp_object, mesh.topology.dim - 1, left_facets) assert np.allclose(h, np.sqrt(2 / (N**2))) @@ -303,19 +348,21 @@ def test_cell_radius_ratio(c0, c1, c5): assert _cpp.mesh.radius_ratio(c5[0], c5[2]) == pytest.approx(1.0) -@pytest.fixture(params=['dir1_fixture', 'dir2_fixture']) +@pytest.fixture(params=["dir1_fixture", "dir2_fixture"]) def dirname(request): return request.getfixturevalue(request.param) @pytest.mark.skip_in_parallel @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -@pytest.mark.parametrize("_mesh,hmin,hmax", - [ - # (mesh_1d, 0.0, 0.25), - (mesh_2d, math.sqrt(2.0), math.sqrt(2.0)), - (mesh_3d, math.sqrt(2.0), math.sqrt(2.0)), - ]) +@pytest.mark.parametrize( + "_mesh,hmin,hmax", + [ + # (mesh_1d, 0.0, 0.25), + (mesh_2d, math.sqrt(2.0), math.sqrt(2.0)), + (mesh_3d, math.sqrt(2.0), math.sqrt(2.0)), + ], +) def test_hmin_hmax(_mesh, dtype, hmin, hmax): mesh = _mesh(dtype) tdim = mesh.topology.dim @@ -359,14 +406,15 @@ def xfail_ghosted_quads_hexes(mesh_factory, ghost_mode): Needs implementing.""" if mesh_factory in [create_unit_square, create_unit_cube]: if ghost_mode == GhostMode.shared_vertex: - pytest.xfail(reason=f"Missing functionality in \'{mesh_factory}\' with \'{ghost_mode}\' mode") + pytest.xfail( + reason=f"Missing functionality in '{mesh_factory}' with '{ghost_mode}' mode" + ) -@pytest.mark.parametrize("ghost_mode", - [GhostMode.none, - GhostMode.shared_facet, - GhostMode.shared_vertex]) -@pytest.mark.parametrize('mesh_factory', mesh_factories) +@pytest.mark.parametrize( + "ghost_mode", [GhostMode.none, GhostMode.shared_facet, GhostMode.shared_vertex] +) +@pytest.mark.parametrize("mesh_factory", mesh_factories) def xtest_mesh_topology_against_basix(mesh_factory, ghost_mode): """Test that mesh cells have topology matching to Basix reference cell they were created from.""" @@ -388,15 +436,13 @@ def xtest_mesh_topology_against_basix(mesh_factory, ghost_mode): # Loop over all dimensions of reference cell topology for d, d_topology in enumerate(basix.topology(basix_celltype)): - # Get entities of dimension d on the cell entities = mesh.topology.connectivity(mesh.topology.dim, d).links(i) if len(entities) == 0: # Fixup for highest dimension - entities = (i, ) + entities = (i,) # Loop over all entities of fixed dimension d for entity_index, entity_topology in enumerate(d_topology): - # Check that entity vertices map to cell vertices in # correct order vertices = mesh.topology.connectivity(d, 0).links(entities[entity_index]) @@ -477,9 +523,7 @@ def test_submesh_full(d, n, codim, marker, ghost_mode, simplex): @pytest.mark.parametrize("d", [2, 3]) @pytest.mark.parametrize("n", [3, 6]) -@pytest.mark.parametrize("boundary", [boundary_0, - boundary_1, - boundary_2]) +@pytest.mark.parametrize("boundary", [boundary_0, boundary_1, boundary_2]) @pytest.mark.parametrize("ghost_mode", [GhostMode.none, GhostMode.shared_facet]) def test_submesh_boundary(d, n, boundary, ghost_mode): if d == 2: @@ -508,7 +552,7 @@ def partitioner(comm, nparts, local_graph, num_ghost_nodes): if comm.rank == 0: cells = np.array([[0, 1, 2], [0, 2, 3]], dtype=np.int64) - x = np.array([[0., 0.], [1., 0.], [1., 1.], [0., 1.]], dtype=dtype) + x = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=dtype) else: cells = np.empty((0, 3), dtype=np.int64) x = np.empty((0, 2), dtype=dtype) @@ -592,19 +636,24 @@ def test_boundary_facets(n, d, ghost_mode, dtype): @pytest.mark.parametrize("n", [3, 5]) @pytest.mark.parametrize("d", [2, 3]) -@pytest.mark.parametrize("ghost_mode", [GhostMode.none, - GhostMode.shared_facet]) +@pytest.mark.parametrize("ghost_mode", [GhostMode.none, GhostMode.shared_facet]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_submesh_codim_0_boundary_facets(n, d, ghost_mode, dtype): """Test that the correct number of boundary facets are computed for a submesh of codim 0""" if d == 2: - mesh_1 = create_rectangle(MPI.COMM_WORLD, ((0.0, 0.0), (2.0, 1.0)), - (2 * n, n), ghost_mode=ghost_mode, dtype=dtype) + mesh_1 = create_rectangle( + MPI.COMM_WORLD, ((0.0, 0.0), (2.0, 1.0)), (2 * n, n), ghost_mode=ghost_mode, dtype=dtype + ) expected_num_boundary_facets = 4 * n else: - mesh_1 = create_box(MPI.COMM_WORLD, ((0.0, 0.0, 0.0), (2.0, 1.0, 1.0)), - (2 * n, n, n), ghost_mode=ghost_mode, dtype=dtype) + mesh_1 = create_box( + MPI.COMM_WORLD, + ((0.0, 0.0, 0.0), (2.0, 1.0, 1.0)), + (2 * n, n, n), + ghost_mode=ghost_mode, + dtype=dtype, + ) expected_num_boundary_facets = 6 * n**2 * 2 # Create submesh of half of the rectangle / box mesh to get unit @@ -634,31 +683,33 @@ def test_submesh_codim_1_boundary_facets(n, ghost_mode, dtype): def test_mesh_create_cmap(dtype): gdim, shape, degree = 2, "triangle", 1 - x = np.array([[0., 0.], [0., 1.], [1., 1.]], dtype=dtype) + x = np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]], dtype=dtype) cells = [[0, 1, 2]] # ufl.Mesh case - domain = ufl.Mesh(element("Lagrange", shape, degree, gdim=gdim, shape=(2, ), dtype=dtype)) + domain = ufl.Mesh(element("Lagrange", shape, degree, gdim=gdim, shape=(2,), dtype=dtype)) msh = _mesh.create_mesh(MPI.COMM_WORLD, cells, x, domain) assert msh.geometry.cmap.dim == 3 assert msh.ufl_domain().ufl_coordinate_element().value_shape == (2,) # basix.ufl.element - domain = element("Lagrange", shape, degree, gdim=gdim, shape=(2, ), dtype=dtype) + domain = element("Lagrange", shape, degree, gdim=gdim, shape=(2,), dtype=dtype) msh = _mesh.create_mesh(MPI.COMM_WORLD, cells, x, domain) assert msh.geometry.cmap.dim == 3 assert msh.ufl_domain().ufl_coordinate_element().value_shape == (2,) # basix.finite_element - domain = basix.create_element(basix.ElementFamily.P, - basix.cell.string_to_type(shape), degree, dtype=dtype) + domain = basix.create_element( + basix.ElementFamily.P, basix.cell.string_to_type(shape), degree, dtype=dtype + ) msh = _mesh.create_mesh(MPI.COMM_WORLD, cells, x, domain) assert msh.geometry.cmap.dim == 3 assert msh.ufl_domain().ufl_coordinate_element().value_shape == (2,) # cpp.fem.CoordinateElement - e = basix.create_element(basix.ElementFamily.P, basix.cell.string_to_type(shape), - degree, dtype=dtype) + e = basix.create_element( + basix.ElementFamily.P, basix.cell.string_to_type(shape), degree, dtype=dtype + ) domain = coordinate_element(e) msh = _mesh.create_mesh(MPI.COMM_WORLD, cells, x, domain) assert msh.geometry.cmap.dim == 3 diff --git a/python/test/unit/mesh/test_mesh_partitioners.py b/python/test/unit/mesh/test_mesh_partitioners.py index fc090b0bf77..96e337a4ba8 100644 --- a/python/test/unit/mesh/test_mesh_partitioners.py +++ b/python/test/unit/mesh/test_mesh_partitioners.py @@ -17,24 +17,40 @@ from basix.ufl import element from dolfinx import default_real_type from dolfinx.io import XDMFFile -from dolfinx.mesh import CellType, GhostMode, compute_midpoints, create_box, create_cell_partitioner, create_mesh +from dolfinx.mesh import ( + CellType, + GhostMode, + compute_midpoints, + create_box, + create_cell_partitioner, + create_mesh, +) partitioners = [dolfinx.graph.partitioner()] try: from dolfinx.graph import partitioner_scotch + partitioners.append(partitioner_scotch()) except ImportError: - partitioners.append(pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx build without SCOTCH"))) + partitioners.append( + pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx build without SCOTCH")) + ) try: from dolfinx.graph import partitioner_parmetis + partitioners.append(partitioner_parmetis()) except ImportError: - partitioners.append(pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx built without Parmetis"))) + partitioners.append( + pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx built without Parmetis")) + ) try: from dolfinx.graph import partitioner_kahip + partitioners.append(partitioner_kahip()) except ImportError: - partitioners.append(pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx built without KaHiP"))) + partitioners.append( + pytest.param(None, marks=pytest.mark.skip(reason="DOLFINx built without KaHiP")) + ) @pytest.mark.parametrize("gpart", partitioners) @@ -42,13 +58,19 @@ @pytest.mark.parametrize("cell_type", [CellType.tetrahedron, CellType.hexahedron]) def test_partition_box_mesh(gpart, Nx, cell_type): part = create_cell_partitioner(gpart) - mesh = create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([1, 1, 1])], [Nx, Nx, Nx], - cell_type, ghost_mode=GhostMode.shared_facet, partitioner=part) + mesh = create_box( + MPI.COMM_WORLD, + [np.array([0, 0, 0]), np.array([1, 1, 1])], + [Nx, Nx, Nx], + cell_type, + ghost_mode=GhostMode.shared_facet, + partitioner=part, + ) tdim = mesh.topology.dim c = 6 if cell_type == CellType.tetrahedron else 1 assert mesh.topology.index_map(tdim).size_global == Nx**3 * c assert mesh.topology.index_map(tdim).size_local != 0 - assert mesh.topology.index_map(0).size_global == (Nx + 1)**3 + assert mesh.topology.index_map(0).size_global == (Nx + 1) ** 3 @pytest.mark.skipif(default_real_type != np.float64, reason="float32 not supported yet") @@ -81,9 +103,9 @@ def test_custom_partitioner(tempdir, Nx, cell_type): # Testing the premise: coordinates are read contiguously in chunks rank = mpi_comm.rank - assert np.all(x_global[all_ranges[rank]:all_ranges[rank + 1]] == x) + assert np.all(x_global[all_ranges[rank] : all_ranges[rank + 1]] == x) - domain = ufl.Mesh(element("Lagrange", cell_shape.name, cell_degree, shape=(3, ))) + domain = ufl.Mesh(element("Lagrange", cell_shape.name, cell_degree, shape=(3,))) # Partition mesh in layers, capture geometrical data and topological # data from outer scope @@ -95,7 +117,9 @@ def partitioner(*args): new_mesh = create_mesh(mpi_comm, topo, x, domain, partitioner) tdim = new_mesh.topology.dim - assert mesh.topology.index_map(tdim).size_global == new_mesh.topology.index_map(tdim).size_global + assert ( + mesh.topology.index_map(tdim).size_global == new_mesh.topology.index_map(tdim).size_global + ) num_cells = new_mesh.topology.index_map(tdim).size_local cell_midpoints = compute_midpoints(new_mesh, tdim, np.arange(num_cells)) assert num_cells > 0 @@ -140,7 +164,7 @@ def partitioner(comm, n, m, topo): return dolfinx.cpp.graph.AdjacencyList_int32(dests, offsets) new_mesh = create_mesh(mpi_comm, topo, x, domain, partitioner) - if (r == 0 and n > 1): + if r == 0 and n > 1: assert new_mesh.topology.index_map(2).num_ghosts == 20 else: assert new_mesh.topology.index_map(2).num_ghosts == 0 diff --git a/python/test/unit/mesh/test_mixed_topology.py b/python/test/unit/mesh/test_mixed_topology.py index 1a0db9ff888..6f1a2a2ca42 100644 --- a/python/test/unit/mesh/test_mixed_topology.py +++ b/python/test/unit/mesh/test_mixed_topology.py @@ -10,8 +10,14 @@ def test_triquad(): ghost_owners = [[], []] boundary_vertices = [] - topology = create_topology(MPI.COMM_SELF, [CellType.triangle, CellType.quadrilateral], - cells, orig_index, ghost_owners, boundary_vertices) + topology = create_topology( + MPI.COMM_SELF, + [CellType.triangle, CellType.quadrilateral], + cells, + orig_index, + ghost_owners, + boundary_vertices, + ) maps = topology.index_maps(topology.dim) assert len(maps) == 2 @@ -44,8 +50,14 @@ def test_mixed_mesh_3d(): ghost_owners = [[], [], []] boundary_vertices = [] - topology = create_topology(MPI.COMM_SELF, [CellType.tetrahedron, CellType.prism, CellType.hexahedron], - cells, orig_index, ghost_owners, boundary_vertices) + topology = create_topology( + MPI.COMM_SELF, + [CellType.tetrahedron, CellType.prism, CellType.hexahedron], + cells, + orig_index, + ghost_owners, + boundary_vertices, + ) entity_types = topology.entity_types assert len(entity_types[0]) == 1 @@ -140,8 +152,14 @@ def test_parallel_mixed_mesh(): # All vertices are on boundary boundary_vertices = [3 * rank + i for i in range(6)] - topology = create_topology(MPI.COMM_WORLD, [CellType.triangle, CellType.quadrilateral], - cells, orig_index, ghost_owners, boundary_vertices) + topology = create_topology( + MPI.COMM_WORLD, + [CellType.triangle, CellType.quadrilateral], + cells, + orig_index, + ghost_owners, + boundary_vertices, + ) # Cell types appear in order as in create_topology assert topology.entity_types[2][0] == CellType.triangle diff --git a/python/test/unit/mesh/test_refinement.py b/python/test/unit/mesh/test_refinement.py index a7feb298f06..c0294a62c40 100644 --- a/python/test/unit/mesh/test_refinement.py +++ b/python/test/unit/mesh/test_refinement.py @@ -12,9 +12,21 @@ import ufl from dolfinx.fem import assemble_matrix, form, functionspace -from dolfinx.mesh import (CellType, DiagonalType, GhostMode, RefinementOption, compute_incident_entities, - create_unit_cube, create_unit_square, locate_entities, locate_entities_boundary, meshtags, - refine, refine_plaza, transfer_meshtag) +from dolfinx.mesh import ( + CellType, + DiagonalType, + GhostMode, + RefinementOption, + compute_incident_entities, + create_unit_cube, + create_unit_square, + locate_entities, + locate_entities_boundary, + meshtags, + refine, + refine_plaza, + transfer_meshtag, +) def test_refine_create_unit_square(): @@ -80,8 +92,9 @@ def test_refinement_gdim(): def test_sub_refine(): """Test that refinement of a subset of edges works""" - mesh = create_unit_square(MPI.COMM_WORLD, 3, 4, diagonal=DiagonalType.left, - ghost_mode=GhostMode.none) + mesh = create_unit_square( + MPI.COMM_WORLD, 3, 4, diagonal=DiagonalType.left, ghost_mode=GhostMode.none + ) mesh.topology.create_entities(1) def left_corner_edge(x, tol=1e-7): @@ -99,11 +112,14 @@ def test_refine_from_cells(): """Check user interface for using local cells to define edges""" Nx, Ny = 8, 3 assert Nx % 2 == 0 - msh = create_unit_square(MPI.COMM_WORLD, Nx, Ny, diagonal=DiagonalType.left, ghost_mode=GhostMode.none) + msh = create_unit_square( + MPI.COMM_WORLD, Nx, Ny, diagonal=DiagonalType.left, ghost_mode=GhostMode.none + ) msh.topology.create_entities(1) def left_side(x, tol=1e-16): return x[0] <= 0.5 + tol + cells = locate_entities(msh, msh.topology.dim, left_side) if MPI.COMM_WORLD.size == 0: assert cells.__len__() == Nx * Ny @@ -118,16 +134,27 @@ def left_side(x, tol=1e-16): @pytest.mark.parametrize("tdim", [2, 3]) -@pytest.mark.parametrize("refine_plaza_wrapper", [ - lambda mesh: refine_plaza(mesh, None, False, RefinementOption.parent_cell_and_facet), - lambda mesh: refine_plaza( - mesh, np.arange(mesh.topology.index_map(1).size_local), False, RefinementOption.parent_cell_and_facet) -]) +@pytest.mark.parametrize( + "refine_plaza_wrapper", + [ + lambda mesh: refine_plaza(mesh, None, False, RefinementOption.parent_cell_and_facet), + lambda mesh: refine_plaza( + mesh, + np.arange(mesh.topology.index_map(1).size_local), + False, + RefinementOption.parent_cell_and_facet, + ), + ], +) def test_refine_facet_meshtag(tdim, refine_plaza_wrapper): if tdim == 3: - mesh = create_unit_cube(MPI.COMM_WORLD, 2, 3, 5, CellType.tetrahedron, ghost_mode=GhostMode.none) + mesh = create_unit_cube( + MPI.COMM_WORLD, 2, 3, 5, CellType.tetrahedron, ghost_mode=GhostMode.none + ) else: - mesh = create_unit_square(MPI.COMM_WORLD, 2, 5, CellType.triangle, ghost_mode=GhostMode.none) + mesh = create_unit_square( + MPI.COMM_WORLD, 2, 5, CellType.triangle, ghost_mode=GhostMode.none + ) mesh.topology.create_entities(tdim - 1) mesh.topology.create_connectivity(tdim - 1, tdim) mesh.topology.create_entities(1) @@ -136,8 +163,12 @@ def test_refine_facet_meshtag(tdim, refine_plaza_wrapper): for f in range(mesh.topology.index_map(tdim - 1).size_local): if len(f_to_c.links(f)) == 1: facet_indices += [f] - meshtag = meshtags(mesh, tdim - 1, np.array(facet_indices, dtype=np.int32), - np.arange(len(facet_indices), dtype=np.int32)) + meshtag = meshtags( + mesh, + tdim - 1, + np.array(facet_indices, dtype=np.int32), + np.arange(len(facet_indices), dtype=np.int32), + ) fine_mesh, parent_cell, parent_facet = refine_plaza_wrapper(mesh) fine_mesh.topology.create_entities(tdim - 1) @@ -152,28 +183,47 @@ def test_refine_facet_meshtag(tdim, refine_plaza_wrapper): # Now mark all facets (including internal) facet_indices = np.arange(mesh.topology.index_map(tdim - 1).size_local) - meshtag = meshtags(mesh, tdim - 1, np.array(facet_indices, dtype=np.int32), - np.arange(len(facet_indices), dtype=np.int32)) + meshtag = meshtags( + mesh, + tdim - 1, + np.array(facet_indices, dtype=np.int32), + np.arange(len(facet_indices), dtype=np.int32), + ) new_meshtag = transfer_meshtag(meshtag, fine_mesh, parent_cell, parent_facet) assert len(new_meshtag.indices) == (tdim * 2 - 2) * len(meshtag.indices) @pytest.mark.parametrize("tdim", [2, 3]) -@pytest.mark.parametrize("refine_plaza_wrapper", [ - lambda mesh: refine_plaza(mesh, None, False, RefinementOption.parent_cell_and_facet), - lambda mesh: refine_plaza( - mesh, np.arange(mesh.topology.index_map(1).size_local), False, RefinementOption.parent_cell_and_facet) -]) +@pytest.mark.parametrize( + "refine_plaza_wrapper", + [ + lambda mesh: refine_plaza(mesh, None, False, RefinementOption.parent_cell_and_facet), + lambda mesh: refine_plaza( + mesh, + np.arange(mesh.topology.index_map(1).size_local), + False, + RefinementOption.parent_cell_and_facet, + ), + ], +) def test_refine_cell_meshtag(tdim, refine_plaza_wrapper): if tdim == 3: - mesh = create_unit_cube(MPI.COMM_WORLD, 2, 3, 5, CellType.tetrahedron, ghost_mode=GhostMode.none) + mesh = create_unit_cube( + MPI.COMM_WORLD, 2, 3, 5, CellType.tetrahedron, ghost_mode=GhostMode.none + ) else: - mesh = create_unit_square(MPI.COMM_WORLD, 2, 5, CellType.triangle, ghost_mode=GhostMode.none) + mesh = create_unit_square( + MPI.COMM_WORLD, 2, 5, CellType.triangle, ghost_mode=GhostMode.none + ) mesh.topology.create_entities(1) cell_indices = np.arange(mesh.topology.index_map(tdim).size_local) - meshtag = meshtags(mesh, tdim, np.array(cell_indices, dtype=np.int32), - np.arange(len(cell_indices), dtype=np.int32)) + meshtag = meshtags( + mesh, + tdim, + np.array(cell_indices, dtype=np.int32), + np.arange(len(cell_indices), dtype=np.int32), + ) fine_mesh, parent_cell, _ = refine_plaza_wrapper(mesh) new_meshtag = transfer_meshtag(meshtag, fine_mesh, parent_cell) diff --git a/python/test/unit/nls/test_newton.py b/python/test/unit/nls/test_newton.py index 2c167332c23..bca2d52fb26 100644 --- a/python/test/unit/nls/test_newton.py +++ b/python/test/unit/nls/test_newton.py @@ -14,7 +14,14 @@ from dolfinx import cpp as _cpp from dolfinx import default_real_type from dolfinx.fem import Function, dirichletbc, form, functionspace, locate_dofs_geometrical -from dolfinx.fem.petsc import apply_lifting, assemble_matrix, assemble_vector, create_matrix, create_vector, set_bc +from dolfinx.fem.petsc import ( + apply_lifting, + assemble_matrix, + assemble_vector, + create_matrix, + create_vector, + set_bc, +) from dolfinx.la import create_petsc_vector from dolfinx.mesh import create_unit_square from ufl import TestFunction, TrialFunction, derivative, dx, grad, inner @@ -94,9 +101,13 @@ def test_linear_pde(): v = TestFunction(V) F = inner(10.0, v) * dx - inner(grad(u), grad(v)) * dx - bc = dirichletbc(PETSc.ScalarType(1.0), - locate_dofs_geometrical(V, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))), V) + bc = dirichletbc( + PETSc.ScalarType(1.0), + locate_dofs_geometrical( + V, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ), + V, + ) # Create nonlinear problem problem = NonlinearPDEProblem(F, u, bc) @@ -129,12 +140,15 @@ def test_nonlinear_pde(): V = functionspace(mesh, ("Lagrange", 1)) u = Function(V) v = TestFunction(V) - F = inner(5.0, v) * dx - ufl.sqrt(u * u) * inner( - grad(u), grad(v)) * dx - inner(u, v) * dx + F = inner(5.0, v) * dx - ufl.sqrt(u * u) * inner(grad(u), grad(v)) * dx - inner(u, v) * dx - bc = dirichletbc(PETSc.ScalarType(1.0), - locate_dofs_geometrical(V, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0))), V) + bc = dirichletbc( + PETSc.ScalarType(1.0), + locate_dofs_geometrical( + V, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ), + V, + ) # Create nonlinear problem problem = NonlinearPDEProblem(F, u, bc) @@ -168,8 +182,12 @@ def test_nonlinear_pde_snes(): u_bc = Function(V) u_bc.x.array[:] = 1.0 - bc = dirichletbc(u_bc, locate_dofs_geometrical(V, lambda x: np.logical_or(np.isclose(x[0], 0.0), - np.isclose(x[0], 1.0)))) + bc = dirichletbc( + u_bc, + locate_dofs_geometrical( + V, lambda x: np.logical_or(np.isclose(x[0], 0.0), np.isclose(x[0], 1.0)) + ), + ) # Create nonlinear problem problem = NonlinearPDE_SNESProblem(F, u, bc) From a5b0a14b92bf560c06c147b25d952a71a489bccd Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:12:25 +0100 Subject: [PATCH 2/9] Modify CI. --- .circleci/config.yml | 4 ++-- .github/workflows/ccpp.yml | 8 ++++---- python/pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 44a4552e549..bc7ab4b69c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,11 +12,11 @@ install-python-components: &install-python-components pip3 install git+https://github.com/FEniCS/ffcx.git ruff-isort-python-code: &ruff-isort-python-code - name: ruff and isort checks on Python code + name: ruff checks on Python code command: | cd python/ ruff check . - isort --check . + ruff format --check . configure-cpp: &configure-cpp name: Configure (C++) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 0b14e9ab262..c5eac76617e 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -60,16 +60,16 @@ jobs: python3 -m pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} python3 -m pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} - - name: ruff and isort C++ .py file checks + - name: ruff C++ .py file checks run: | cd cpp/ ruff check . - python3 -m isort --check . - - name: ruff and isort Python interface checks + ruff format --check . + - name: ruff Python interface checks run: | cd python/ ruff check . - python3 -m isort --check . + ruff format --check . - name: mypy checks run: | cd python/ diff --git a/python/pyproject.toml b/python/pyproject.toml index 0990526988f..68fcb70d43c 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ [project.optional-dependencies] docs = ["markdown", "pyyaml", "sphinx", "sphinx_rtd_theme"] -lint = ["ruff", "isort"] +lint = ["ruff"] optional = ["numba"] test = ["pytest", "sympy", "scipy", "matplotlib", "fenics-dolfinx[optional]"] ci = [ From 5c88d85c8efbf3f95e021a7e9c2f64dc6d648bb1 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:13:13 +0100 Subject: [PATCH 3/9] Remove flake8 and isort from image. --- docker/Dockerfile.test-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.test-env b/docker/Dockerfile.test-env index 93a655209c9..f6a99c3ec35 100644 --- a/docker/Dockerfile.test-env +++ b/docker/Dockerfile.test-env @@ -154,7 +154,7 @@ RUN if [ "$MPI" = "mpich" ]; then \ # documentation or run tests. RUN pip3 install --no-cache-dir --upgrade setuptools pip && \ pip3 install --no-cache-dir cffi mpi4py numba numpy==${NUMPY_VERSION} scikit-build-core[pyproject] && \ - pip3 install --no-cache-dir breathe clang-format cmakelang flake8 isort jupytext matplotlib mypy myst-parser nanobind==${NANOBIND_VERSION} pytest pytest-xdist ruff scipy sphinx sphinx_rtd_theme types-setuptools + pip3 install --no-cache-dir breathe clang-format cmakelang jupytext matplotlib mypy myst-parser nanobind==${NANOBIND_VERSION} pytest pytest-xdist ruff scipy sphinx sphinx_rtd_theme types-setuptools # Install KaHIP RUN wget -nc --quiet https://github.com/kahip/kahip/archive/v${KAHIP_VERSION}.tar.gz && \ From 12a339decba92277cf991cdb43333b2c5bb92ce4 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:14:10 +0100 Subject: [PATCH 4/9] Format C++ part. --- cpp/cmake/scripts/generate-cmakefiles.py | 21 ++++--------- cpp/demo/biharmonic/biharmonic.py | 30 ++++++++++++++----- cpp/demo/hyperelasticity/hyperelasticity.py | 33 ++++++++++++++------- cpp/demo/poisson/poisson.py | 14 +++++++-- cpp/demo/poisson_matrix_free/poisson.py | 14 +++++++-- cpp/doc/source/conf.py | 23 +++++++------- cpp/test/poisson.py | 13 ++++++-- 7 files changed, 99 insertions(+), 49 deletions(-) diff --git a/cpp/cmake/scripts/generate-cmakefiles.py b/cpp/cmake/scripts/generate-cmakefiles.py index 6a564bf398f..143ff694caf 100644 --- a/cpp/cmake/scripts/generate-cmakefiles.py +++ b/cpp/cmake/scripts/generate-cmakefiles.py @@ -7,8 +7,7 @@ import os import warnings -cmakelists_noufl_str = \ - """# This file was generated by running +cmakelists_noufl_str = """# This file was generated by running # # python cmake/scripts/generate-cmakefiles.py from dolfinx/cpp # @@ -51,8 +50,7 @@ """ -cmakelists_nocomplex_str = \ - """# This file was generated by running +cmakelists_nocomplex_str = """# This file was generated by running # # python cmake/scripts/generate-cmakefiles from dolfinx/cpp # @@ -117,8 +115,7 @@ endif() """ -cmakelists_str = \ - """# This file was generated by running +cmakelists_str = """# This file was generated by running # # python cmake/scripts/generate-cmakefiles from dolfinx/cpp # @@ -205,7 +202,6 @@ def generate_cmake_files(subdirectory, generated_files): executable_prefix = executable_prefixes[subdirectory] main_file_name = main_file_names[subdirectory] for root, dirs, files in os.walk(cwd + "/" + subdirectory): - cpp_files = set() ufl_files = set() ufl_c_files = set() @@ -224,9 +220,7 @@ def generate_cmake_files(subdirectory, generated_files): continue if "main.cpp" in os.listdir(program_dir): - name_forms = dict( - project_name=executable_prefix + program_name, src_files="NOT_SET" - ) + name_forms = dict(project_name=executable_prefix + program_name, src_files="NOT_SET") for f in os.listdir(program_dir): filename, extension = os.path.splitext(f) if extension == ".cpp": @@ -242,8 +236,7 @@ def generate_cmake_files(subdirectory, generated_files): continue if len(ufl_files) > 1: - raise RuntimeError( - "CMake generation supports exactly one UFL file") + raise RuntimeError("CMake generation supports exactly one UFL file") # Name of demo and cpp source files # print("**, ", main_file_name, cpp_files) @@ -260,9 +253,7 @@ def generate_cmake_files(subdirectory, generated_files): if program_name not in executable_names: executable_names.add(program_name) else: - warnings.warn( - "Duplicate executable names found when generating CMakeLists.txt files." - ) + warnings.warn("Duplicate executable names found when generating CMakeLists.txt files.") # Write file filename = os.path.join(program_dir, "CMakeLists.txt") diff --git a/cpp/demo/biharmonic/biharmonic.py b/cpp/demo/biharmonic/biharmonic.py index f299ce90cef..09c2b4e5a3f 100644 --- a/cpp/demo/biharmonic/biharmonic.py +++ b/cpp/demo/biharmonic/biharmonic.py @@ -5,9 +5,23 @@ # the variational problem in UFL terms in a separate form file # :download:`biharmonic.py`. We begin by defining the finite element:: from basix.ufl import element -from ufl import (CellDiameter, Coefficient, Constant, FacetNormal, - FunctionSpace, Mesh, TestFunction, TrialFunction, avg, div, - dS, dx, grad, inner, jump) +from ufl import ( + CellDiameter, + Coefficient, + Constant, + FacetNormal, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + avg, + div, + dS, + dx, + grad, + inner, + jump, +) e = element("Lagrange", "triangle", 2) @@ -41,7 +55,7 @@ # Normal component, mesh size and right-hand side n = FacetNormal(mesh) h = CellDiameter(mesh) -h_avg = (h('+') + h('-')) / 2 +h_avg = (h("+") + h("-")) / 2 alpha = Constant(mesh) # Finally, we define the bilinear and linear forms according to the @@ -49,10 +63,12 @@ # internal facets are indicated by ``*dS``. :: # Bilinear form -a = inner(div(grad(u)), div(grad(v))) * dx \ - - inner(avg(div(grad(u))), jump(grad(v), n)) * dS \ - - inner(jump(grad(u), n), avg(div(grad(v)))) * dS \ +a = ( + inner(div(grad(u)), div(grad(v))) * dx + - inner(avg(div(grad(u))), jump(grad(v), n)) * dS + - inner(jump(grad(u), n), avg(div(grad(v)))) * dS + alpha / h_avg * inner(jump(grad(u), n), jump(grad(v), n)) * dS +) # Linear form L = inner(f, v) * dx diff --git a/cpp/demo/hyperelasticity/hyperelasticity.py b/cpp/demo/hyperelasticity/hyperelasticity.py index b31f101f999..9a622900ec8 100644 --- a/cpp/demo/hyperelasticity/hyperelasticity.py +++ b/cpp/demo/hyperelasticity/hyperelasticity.py @@ -9,9 +9,22 @@ # dimensions, so first we need the appropriate finite element space and # trial and test functions on this space:: from basix.ufl import element -from ufl import (Coefficient, FunctionSpace, Identity, Mesh, TestFunction, - TrialFunction, derivative, det, diff, dx, grad, ln, tr, - variable) +from ufl import ( + Coefficient, + FunctionSpace, + Identity, + Mesh, + TestFunction, + TrialFunction, + derivative, + det, + diff, + dx, + grad, + ln, + tr, + variable, +) # Function spaces e = element("Lagrange", "tetrahedron", 1, shape=(3,)) @@ -19,8 +32,8 @@ V = FunctionSpace(mesh, e) # Trial and test functions -du = TrialFunction(V) # Incremental displacement -v = TestFunction(V) # Test function +du = TrialFunction(V) # Incremental displacement +v = TestFunction(V) # Test function # Note that ``element`` with `shape=(3,)` creates a finite element space # of vector fields. @@ -29,7 +42,7 @@ # traction ``T`` and the displacement solution itself ``u``:: # Functions -u = Coefficient(V) # Displacement from previous iteration +u = Coefficient(V) # Displacement from previous iteration # B = Coefficient(element) # Body force per unit volume # T = Coefficient(element) # Traction force on the boundary @@ -37,9 +50,9 @@ # Kinematics d = len(u) -I = Identity(d) # Identity tensor # noqa: E741 -F = variable(I + grad(u)) # Deformation gradient -C = F.T * F # Right Cauchy-Green tensor +I = Identity(d) # Identity tensor # noqa: E741 +F = variable(I + grad(u)) # Deformation gradient +C = F.T * F # Right Cauchy-Green tensor # Invariants of deformation tensors Ic = tr(C) @@ -60,7 +73,7 @@ # ``derivative``:: # Stored strain energy density (compressible neo-Hookean model) -psi = (mu / 2) * (Ic - 3) - mu * ln(J) + (lmbda / 2) * (ln(J))**2 +psi = (mu / 2) * (Ic - 3) - mu * ln(J) + (lmbda / 2) * (ln(J)) ** 2 # Total potential energy Pi = psi * dx # - inner(B, u) * dx - inner(T, u) * ds diff --git a/cpp/demo/poisson/poisson.py b/cpp/demo/poisson/poisson.py index e4f86b29569..141191f793d 100644 --- a/cpp/demo/poisson/poisson.py +++ b/cpp/demo/poisson/poisson.py @@ -5,8 +5,18 @@ # the variational problem in UFL terms in a separate form file # :download:`poisson.py`. We begin by defining the finite element:: from basix.ufl import element -from ufl import (Coefficient, Constant, FunctionSpace, Mesh, TestFunction, - TrialFunction, ds, dx, grad, inner) +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + ds, + dx, + grad, + inner, +) e = element("Lagrange", "triangle", 1) diff --git a/cpp/demo/poisson_matrix_free/poisson.py b/cpp/demo/poisson_matrix_free/poisson.py index 35f3dda31c8..76b10faf375 100644 --- a/cpp/demo/poisson_matrix_free/poisson.py +++ b/cpp/demo/poisson_matrix_free/poisson.py @@ -1,8 +1,18 @@ # UFL input for the Matrix-free Poisson Demo # ================================== from basix.ufl import element -from ufl import (Coefficient, Constant, FunctionSpace, Mesh, TestFunction, - TrialFunction, action, dx, grad, inner) +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + action, + dx, + grad, + inner, +) coord_element = element("Lagrange", "triangle", 1, shape=(2,)) mesh = Mesh(coord_element) diff --git a/cpp/doc/source/conf.py b/cpp/doc/source/conf.py index ccfbfac33f6..c75b0cd2e21 100644 --- a/cpp/doc/source/conf.py +++ b/cpp/doc/source/conf.py @@ -17,12 +17,12 @@ # -- Project information ----------------------------------------------------- -project = 'DOLFINx' -copyright = '2022, FEniCS Project' -author = 'FEniCS Project' +project = "DOLFINx" +copyright = "2022, FEniCS Project" +author = "FEniCS Project" # The full version, including alpha/beta/rc tags -release = '0.3.1' +release = "0.3.1" # -- General configuration --------------------------------------------------- @@ -30,12 +30,13 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.mathjax', - 'breathe', - ] +extensions = [ + "sphinx.ext.mathjax", + "breathe", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -61,10 +62,10 @@ breathe_projects = {"DOLFINx": "../xml/"} breathe_default_project = "DOLFINx" -breathe_implementation_filename_extensions = ['.c', '.cc', '.cpp'] +breathe_implementation_filename_extensions = [".c", ".cc", ".cpp"] # Tell sphinx what the primary language being documented is. -primary_domain = 'cpp' +primary_domain = "cpp" # Tell sphinx what the pygments highlight language should be. -highlight_language = 'cpp' +highlight_language = "cpp" diff --git a/cpp/test/poisson.py b/cpp/test/poisson.py index 2713085b418..2580e43342c 100644 --- a/cpp/test/poisson.py +++ b/cpp/test/poisson.py @@ -1,6 +1,15 @@ from basix.ufl import element -from ufl import (Coefficient, Constant, FunctionSpace, Mesh, TestFunction, - TrialFunction, dx, grad, inner) +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dx, + grad, + inner, +) e = element("Lagrange", "tetrahedron", 2) coord_element = element("Lagrange", "tetrahedron", 1, shape=(3,)) From 57c772734ca482f644dfcd587a01b430f3938a9e Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:29:17 +0100 Subject: [PATCH 5/9] Fix remaining check issues. Removed periodic bc test file, no longer needed. --- python/demo/demo_axis/demo_axis.py | 5 ++- python/dolfinx/fem/function.py | 3 +- python/dolfinx/fem/petsc.py | 16 ++++--- python/dolfinx/io/gmshio.py | 2 +- python/test/unit/fem/test_custom_assembler.py | 3 +- .../test/unit/fem/test_custom_jit_kernels.py | 24 +++++++---- .../test/unit/fem/test_element_integrals.py | 2 +- python/test/unit/fem/test_interpolation.py | 3 +- .../test_periodic_boundary_computation.py | 42 ------------------- 9 files changed, 37 insertions(+), 63 deletions(-) delete mode 100644 python/test/unit/mesh/test_periodic_boundary_computation.py diff --git a/python/demo/demo_axis/demo_axis.py b/python/demo/demo_axis/demo_axis.py index b811548baeb..defe96b7cd2 100644 --- a/python/demo/demo_axis/demo_axis.py +++ b/python/demo/demo_axis/demo_axis.py @@ -626,7 +626,7 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # The quantities `P` and `Q` have an additional `2` factor for `m != 0` # due to parity. - +# # We now compare the numerical and analytical efficiencies (he latter # were obtained with the following routine provided by the # [`scattnlay`](https://github.com/ovidiopr/scattnlay) library): @@ -637,7 +637,8 @@ def create_eps_mu(pml, rho, eps_bkg, mu_bkg): # m = np.sqrt(eps_au)/n_bkg # x = 2*np.pi*radius_sph/wl0*n_bkg # -# q_sca_analyt, q_abs_analyt = scattnlay(np.array([x], dtype=np.complex128), np.array([m], dtype=np.complex128))[2:4] +# q_sca_analyt, q_abs_analyt = scattnlay(np.array([x], dtype=np.complex128), +# np.array([m], dtype=np.complex128))[2:4] # ``` # # The numerical values are reported here below: diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index d7062925b4f..9345303eac4 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -130,7 +130,8 @@ def __init__( comm = mesh.comm except AttributeError: print( - "Could not extract MPI communicator for Expression. Maybe you need to pass a communicator?" + "Could not extract MPI communicator for Expression. " + + "Maybe you need to pass a communicator?" ) raise diff --git a/python/dolfinx/fem/petsc.py b/python/dolfinx/fem/petsc.py index b35d3ec709b..36f2a3bef0b 100644 --- a/python/dolfinx/fem/petsc.py +++ b/python/dolfinx/fem/petsc.py @@ -177,7 +177,7 @@ def create_matrix_block(a: list[list[Form]]) -> PETSc.Mat: def create_matrix_nest(a: list[list[Form]]) -> PETSc.Mat: - """Create a PETSc matrix (``MatNest``) that is compatible with a rectangular array of bilinear forms. + """Create a PETSc matrix (``MatNest``) that is compatible with an array of bilinear forms. Args: a: Rectangular array of bilinear forms. @@ -528,7 +528,8 @@ def _assemble_matrix_nest_mat( assert len(row_forms) > 0 if row_forms[0].function_spaces[0].contains(bc.function_space): raise RuntimeError( - f"Diagonal sub-block ({i}, {j}) cannot be 'None' and have DirichletBC applied." + f"Diagonal sub-block ({i}, {j}) cannot be 'None'" + " and have DirichletBC applied." " Consider assembling a zero block." ) return A @@ -605,7 +606,8 @@ def _assemble_matrix_block_mat( assert len(row_forms) > 0 if row_forms[0].function_spaces[0].contains(bc.function_space): raise RuntimeError( - f"Diagonal sub-block ({i}, {j}) cannot be 'None' and have DirichletBC applied." + f"Diagonal sub-block ({i}, {j}) cannot be 'None' " + " and have DirichletBC applied." " Consider assembling a zero block." ) @@ -653,7 +655,8 @@ def apply_lifting_nest( constants=None, coeffs=None, ) -> PETSc.Vec: - """Apply the function :func:`dolfinx.fem.apply_lifting` to each sub-vector in a nested PETSc Vector.""" + """Apply the function :func:`dolfinx.fem.apply_lifting` to each sub-vector + in a nested PETSc Vector.""" x0 = [] if x0 is None else x0.getNestSubVecs() bcs1 = _bcs_by_block(_extract_spaces(a, 1), bcs) @@ -748,7 +751,8 @@ def __init__( problem = LinearProblem(a, L, [bc0, bc1], petsc_options={"ksp_type": "preonly", "pc_type": "lu", - "pc_factor_mat_solver_type": "mumps"}) + "pc_factor_mat_solver_type": + "mumps"}) """ self._a = _create_form( a, form_compiler_options=form_compiler_options, jit_options=jit_options @@ -865,7 +869,7 @@ def __init__( form_compiler_options: typing.Optional[dict] = None, jit_options: typing.Optional[dict] = None, ): - """Initialize solver for solving a non-linear problem using Newton's method, :math:`(dF/du)(u) du = -F(u)`. + """Initialize solver for solving a non-linear problem using Newton's method`. Args: F: The PDE residual F(u, v) diff --git a/python/dolfinx/io/gmshio.py b/python/dolfinx/io/gmshio.py index e30773fc686..fc63d55c3f5 100644 --- a/python/dolfinx/io/gmshio.py +++ b/python/dolfinx/io/gmshio.py @@ -340,7 +340,7 @@ def read_from_msh( typing.Callable[[_MPI.Comm, int, int, AdjacencyList_int32], AdjacencyList_int32] ] = None, ) -> tuple[Mesh, _cpp.mesh.MeshTags_int32, _cpp.mesh.MeshTags_int32]: - """Read a Gmsh .msh file and return a distributed :class:`dolfinx.mesh.Mesh` and and cell facet markers. + """Read a Gmsh .msh file and return a :class:`dolfinx.mesh.Mesh` and cell facet markers. Note: This function requires the Gmsh Python module. diff --git a/python/test/unit/fem/test_custom_assembler.py b/python/test/unit/fem/test_custom_assembler.py index f3314ec5222..c63148b78ee 100644 --- a/python/test/unit/fem/test_custom_assembler.py +++ b/python/test/unit/fem/test_custom_assembler.py @@ -88,7 +88,8 @@ def get_matsetvalues_cffi_api(): typedef ... PetscScalar; typedef int... InsertMode; int MatSetValuesLocal(void* mat, PetscInt nrow, const PetscInt* irow, - PetscInt ncol, const PetscInt* icol, const PetscScalar* y, InsertMode addv);""" + PetscInt ncol, const PetscInt* icol, + const PetscScalar* y, InsertMode addv);""" ) ffibuilder.set_source( module_name, diff --git a/python/test/unit/fem/test_custom_jit_kernels.py b/python/test/unit/fem/test_custom_jit_kernels.py index 7675d8b3369..fcbd530c017 100644 --- a/python/test/unit/fem/test_custom_jit_kernels.py +++ b/python/test/unit/fem/test_custom_jit_kernels.py @@ -163,10 +163,14 @@ def test_cffi_assembly(): // PM* dimensions: [entities][dofs][dofs] static const double FE3_C0_D01_Q1[1][1][2] = { { { -1.0, 1.0 } } }; // Unstructured piecewise computations - const double J_c0 = coordinate_dofs[0] * FE3_C0_D01_Q1[0][0][0] + coordinate_dofs[3] * FE3_C0_D01_Q1[0][0][1]; - const double J_c3 = coordinate_dofs[1] * FE3_C0_D01_Q1[0][0][0] + coordinate_dofs[7] * FE3_C0_D01_Q1[0][0][1]; - const double J_c1 = coordinate_dofs[0] * FE3_C0_D01_Q1[0][0][0] + coordinate_dofs[6] * FE3_C0_D01_Q1[0][0][1]; - const double J_c2 = coordinate_dofs[1] * FE3_C0_D01_Q1[0][0][0] + coordinate_dofs[4] * FE3_C0_D01_Q1[0][0][1]; + const double J_c0 = coordinate_dofs[0] * FE3_C0_D01_Q1[0][0][0] + + coordinate_dofs[3] * FE3_C0_D01_Q1[0][0][1]; + const double J_c3 = coordinate_dofs[1] * FE3_C0_D01_Q1[0][0][0] + + coordinate_dofs[7] * FE3_C0_D01_Q1[0][0][1]; + const double J_c1 = coordinate_dofs[0] * FE3_C0_D01_Q1[0][0][0] + + coordinate_dofs[6] * FE3_C0_D01_Q1[0][0][1]; + const double J_c2 = coordinate_dofs[1] * FE3_C0_D01_Q1[0][0][0] + + coordinate_dofs[4] * FE3_C0_D01_Q1[0][0][1]; double sp[20]; sp[0] = J_c0 * J_c3; sp[1] = J_c1 * J_c2; @@ -212,10 +216,14 @@ def test_cffi_assembly(): // PM* dimensions: [entities][dofs][dofs] static const double FE4_C0_D01_Q1[1][1][2] = { { { -1.0, 1.0 } } }; // Unstructured piecewise computations - const double J_c0 = coordinate_dofs[0] * FE4_C0_D01_Q1[0][0][0] + coordinate_dofs[3] * FE4_C0_D01_Q1[0][0][1]; - const double J_c3 = coordinate_dofs[1] * FE4_C0_D01_Q1[0][0][0] + coordinate_dofs[7] * FE4_C0_D01_Q1[0][0][1]; - const double J_c1 = coordinate_dofs[0] * FE4_C0_D01_Q1[0][0][0] + coordinate_dofs[6] * FE4_C0_D01_Q1[0][0][1]; - const double J_c2 = coordinate_dofs[1] * FE4_C0_D01_Q1[0][0][0] + coordinate_dofs[4] * FE4_C0_D01_Q1[0][0][1]; + const double J_c0 = coordinate_dofs[0] * FE4_C0_D01_Q1[0][0][0] + + coordinate_dofs[3] * FE4_C0_D01_Q1[0][0][1]; + const double J_c3 = coordinate_dofs[1] * FE4_C0_D01_Q1[0][0][0] + + coordinate_dofs[7] * FE4_C0_D01_Q1[0][0][1]; + const double J_c1 = coordinate_dofs[0] * FE4_C0_D01_Q1[0][0][0] + + coordinate_dofs[6] * FE4_C0_D01_Q1[0][0][1]; + const double J_c2 = coordinate_dofs[1] * FE4_C0_D01_Q1[0][0][0] + + coordinate_dofs[4] * FE4_C0_D01_Q1[0][0][1]; double sp[4]; sp[0] = J_c0 * J_c3; sp[1] = J_c1 * J_c2; diff --git a/python/test/unit/fem/test_element_integrals.py b/python/test/unit/fem/test_element_integrals.py index 092d8fe658d..418212e9d78 100644 --- a/python/test/unit/fem/test_element_integrals.py +++ b/python/test/unit/fem/test_element_integrals.py @@ -494,7 +494,7 @@ def test_plus_minus_matrix(cell_type, pm1, pm2, dtype): @pytest.mark.skip( - reason="This test relies on the mesh constructor not re-ordering the mesh points. Needs replacing." + reason="Test needs replacing because it assumes the mesh constructor doesn't re-order points." ) @pytest.mark.skip_in_parallel @pytest.mark.parametrize("order", [1, 2]) diff --git a/python/test/unit/fem/test_interpolation.py b/python/test/unit/fem/test_interpolation.py index 98cc689cdda..35df3141020 100644 --- a/python/test/unit/fem/test_interpolation.py +++ b/python/test/unit/fem/test_interpolation.py @@ -914,7 +914,8 @@ def f(x): ), ) - # Check that function values over facets of 3D mesh of the twice interpolated property is preserved + # Check that function values over facets of 3D mesh of the twice + # interpolated property is preserved def locate_bottom_facets(x): return np.isclose(x[2], 0) diff --git a/python/test/unit/mesh/test_periodic_boundary_computation.py b/python/test/unit/mesh/test_periodic_boundary_computation.py deleted file mode 100644 index b8072ebee52..00000000000 --- a/python/test/unit/mesh/test_periodic_boundary_computation.py +++ /dev/null @@ -1,42 +0,0 @@ -# # Copyright (C) 2013 Mikael Mortensen -# # -# # This file is part of DOLFINx (https://www.fenicsproject.org) -# # -# # SPDX-License-Identifier: LGPL-3.0-or-later - -# import numpy as np - -# from dolfinx import create_unit_square -# from dolfinx.cpp.mesh import PeriodicBoundaryComputation - - -# def periodic_boundary(x): -# return np.isclose(x[:, 0], 0.0) - - -# @pytest.fixture -# def mesh(): -# return create_unit_square(MPI.COMM_WORLD, 4, 4) - - -# @pytest.mark.skip_in_parallel -# def test_ComputePeriodicPairs(mesh): -# # Verify that correct number of periodic pairs are computed -# vertices = PeriodicBoundaryComputation.compute_periodic_pairs( -# mesh, periodic_boundary, 0, np.finfo(float).eps) -# edges = PeriodicBoundaryComputation.compute_periodic_pairs( -# mesh, periodic_boundary, 1, np.finfo(float).eps) -# assert len(vertices) == 5 -# assert len(edges) == 4 - - -# @pytest.mark.skip_in_parallel -# def test_MastersSlaves(mesh): -# # Verify that correct number of masters and slaves are marked -# mf = PeriodicBoundaryComputation.masters_slaves(mesh, periodic_boundary, 0, np.finfo(float).eps) -# assert len(np.where(mf.array() == 1)[0]) == 5 -# assert len(np.where(mf.array() == 2)[0]) == 5 - -# mf = PeriodicBoundaryComputation.masters_slaves(mesh, periodic_boundary, 1, np.finfo(float).eps) -# assert len(np.where(mf.array() == 1)[0]) == 4 -# assert len(np.where(mf.array() == 2)[0]) == 4 From ccab813846a43e4f2fbaa2b9f7951cf3f36721b7 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:45:51 +0100 Subject: [PATCH 6/9] Fix mypy --- python/dolfinx/common.py | 16 +++++++++++++++- python/dolfinx/fem/forms.py | 4 ++-- python/dolfinx/io/gmshio.py | 4 ++-- python/dolfinx/io/utils.py | 16 +++++++++------- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/python/dolfinx/common.py b/python/dolfinx/common.py index 2fd5fda1c3d..3480af0f6f1 100644 --- a/python/dolfinx/common.py +++ b/python/dolfinx/common.py @@ -11,9 +11,23 @@ from dolfinx import cpp as _cpp from dolfinx.cpp.common import ( IndexMap, + git_commit_hash, + has_adios2, + has_debug, + has_kahip, + has_parmetis, ) -__all__ = ["IndexMap", "Timer", "timed"] +__all__ = [ + "IndexMap", + "Timer", + "timed", + "git_commit_hash", + "has_adios2", + "has_debug", + "has_kahip", + "has_parmetis", +] TimingType = _cpp.common.TimingType Reduction = _cpp.common.Reduction diff --git a/python/dolfinx/fem/forms.py b/python/dolfinx/fem/forms.py index 017d3490c35..a9e5e64180a 100644 --- a/python/dolfinx/fem/forms.py +++ b/python/dolfinx/fem/forms.py @@ -288,5 +288,5 @@ def unique_spaces(V): return list(unique_spaces(V)) elif index == 1: return list(unique_spaces(V.transpose())) - else: - raise RuntimeError("Unsupported array of forms") + + raise RuntimeError("Unsupported array of forms") diff --git a/python/dolfinx/io/gmshio.py b/python/dolfinx/io/gmshio.py index fc63d55c3f5..a9a1b63c673 100644 --- a/python/dolfinx/io/gmshio.py +++ b/python/dolfinx/io/gmshio.py @@ -79,8 +79,8 @@ def ufl_mesh(gmsh_cell: int, gdim: int, dtype: npt.DTypeLike) -> ufl.Mesh: basix.LagrangeVariant.equispaced, shape=(gdim,), gdim=gdim, - dtype=dtype, - ) # type: ignore[arg-type] + dtype=dtype, # type: ignore[arg-type] + ) return ufl.Mesh(element) diff --git a/python/dolfinx/io/utils.py b/python/dolfinx/io/utils.py index 3a53cd33c0a..8fbb956e643 100644 --- a/python/dolfinx/io/utils.py +++ b/python/dolfinx/io/utils.py @@ -23,11 +23,13 @@ from dolfinx.fem import Function from dolfinx.mesh import GhostMode, Mesh, MeshTags +from collections.abc import Iterable + __all__ = ["VTKFile", "XDMFFile", "cell_perm_gmsh", "cell_perm_vtk", "distribute_entity_data"] -def _extract_cpp_functions(functions: typing.Union[list[Function], Function]): - """Extract C++ object for a single function or a list of functions""" +def _extract_cpp_objects(functions: typing.Union[Mesh, Function, tuple[Function], list[Function]]): + """Extract C++ objects""" if isinstance(functions, (list, tuple)): return [getattr(u, "_cpp_object", u) for u in functions] else: @@ -56,7 +58,7 @@ def __init__( self, comm: _MPI.Comm, filename: typing.Union[str, Path], - output: typing.Union[Mesh, Function, list[Function]], + output: typing.Union[Mesh, Function, list[Function], tuple[Function]], engine: str = "BPFile", mesh_policy: VTXMeshPolicy = VTXMeshPolicy.update, ): @@ -67,7 +69,7 @@ def __init__( filename: The output filename output: The data to output. Either a mesh, a single (discontinuous) Lagrange Function or list of - (discontinuous Lagrange Functions. + (discontinuous) Lagrange Functions. engine: ADIOS2 engine to use for output. See ADIOS2 documentation for options. mesh_policy: Controls if the mesh is written to file at @@ -100,7 +102,7 @@ def __init__( except (NotImplementedError, TypeError, AttributeError): # Input is a single function or a list of functions self._cpp_object = _vtxwriter( - comm, filename, _extract_cpp_functions(output), engine, mesh_policy + comm, filename, _extract_cpp_objects(output), engine, mesh_policy ) # type: ignore[arg-type] def __enter__(self): @@ -171,7 +173,7 @@ def __init__( self._cpp_object = _fides_writer(comm, filename, output._cpp_object, engine) # type: ignore except (NotImplementedError, TypeError, AttributeError): self._cpp_object = _fides_writer( - comm, filename, _extract_cpp_functions(output), engine, mesh_policy + comm, filename, _extract_cpp_objects(output), engine, mesh_policy ) # type: ignore[arg-type] def __enter__(self): @@ -208,7 +210,7 @@ def write_mesh(self, mesh: Mesh, t: float = 0.0) -> None: def write_function(self, u: typing.Union[list[Function], Function], t: float = 0.0) -> None: """Write a single function or a list of functions to file for a given time (default 0.0)""" - super().write(_extract_cpp_functions(u), t) + super().write(_extract_cpp_objects(u), t) class XDMFFile(_cpp.io.XDMFFile): From 1946b593c814d6b47d1b636361c174ad01ed817a Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:46:19 +0100 Subject: [PATCH 7/9] Ruff. --- python/dolfinx/io/gmshio.py | 2 +- python/dolfinx/io/utils.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/python/dolfinx/io/gmshio.py b/python/dolfinx/io/gmshio.py index a9a1b63c673..2672ede0b82 100644 --- a/python/dolfinx/io/gmshio.py +++ b/python/dolfinx/io/gmshio.py @@ -79,7 +79,7 @@ def ufl_mesh(gmsh_cell: int, gdim: int, dtype: npt.DTypeLike) -> ufl.Mesh: basix.LagrangeVariant.equispaced, shape=(gdim,), gdim=gdim, - dtype=dtype, # type: ignore[arg-type] + dtype=dtype, # type: ignore[arg-type] ) return ufl.Mesh(element) diff --git a/python/dolfinx/io/utils.py b/python/dolfinx/io/utils.py index 8fbb956e643..ca085d5a42a 100644 --- a/python/dolfinx/io/utils.py +++ b/python/dolfinx/io/utils.py @@ -23,8 +23,6 @@ from dolfinx.fem import Function from dolfinx.mesh import GhostMode, Mesh, MeshTags -from collections.abc import Iterable - __all__ = ["VTKFile", "XDMFFile", "cell_perm_gmsh", "cell_perm_vtk", "distribute_entity_data"] From dc8938707848f39d7a70185e9981582e1118e76d Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:51:32 +0100 Subject: [PATCH 8/9] mypy --- python/demo/demo_elasticity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/demo/demo_elasticity.py b/python/demo/demo_elasticity.py index 0d7158d97d5..70156785309 100644 --- a/python/demo/demo_elasticity.py +++ b/python/demo/demo_elasticity.py @@ -90,8 +90,9 @@ def build_nullspace(V: FunctionSpace): assert dolfinx.cpp.la.is_orthonormal(_basis) basis_petsc = [ - PETSc.Vec().createWithArray(x[: bs * length0], bsize=3, comm=V.mesh.comm) for x in b - ] # type: ignore + PETSc.Vec().createWithArray(x[: bs * length0], bsize=3, comm=V.mesh.comm) + for x in b # type: ignore + ] return PETSc.NullSpace().create(vectors=basis_petsc) # type: ignore From 6ba92b14f6bbf8b8bb0fc5418de852f8707ec5d8 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Wed, 7 Feb 2024 10:57:37 +0100 Subject: [PATCH 9/9] Fixes for jupytext import and conf.py --- python/demo/demo_elasticity.py | 4 ++-- python/doc/source/conf.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/demo/demo_elasticity.py b/python/demo/demo_elasticity.py index 70156785309..8681eaeb536 100644 --- a/python/demo/demo_elasticity.py +++ b/python/demo/demo_elasticity.py @@ -90,8 +90,8 @@ def build_nullspace(V: FunctionSpace): assert dolfinx.cpp.la.is_orthonormal(_basis) basis_petsc = [ - PETSc.Vec().createWithArray(x[: bs * length0], bsize=3, comm=V.mesh.comm) - for x in b # type: ignore + PETSc.Vec().createWithArray(x[: bs * length0], bsize=3, comm=V.mesh.comm) # type: ignore + for x in b ] return PETSc.NullSpace().create(vectors=basis_petsc) # type: ignore diff --git a/python/doc/source/conf.py b/python/doc/source/conf.py index ab531bdbb54..39c389c5fae 100644 --- a/python/doc/source/conf.py +++ b/python/doc/source/conf.py @@ -6,11 +6,10 @@ import os import sys -import jupytext_process - import dolfinx sys.path.insert(0, os.path.abspath(".")) +import jupytext_process # isort:skip myst_heading_anchors = 3