diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 27be958..67e5721 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,26 +14,18 @@ on: jobs: build-and-test: runs-on: ubuntu-latest - container: ghcr.io/fenics/dolfinx/dolfinx:v0.7.2 + container: ghcr.io/fenics/dolfinx/dolfinx:nightly steps: - name: Checkout uses: actions/checkout@v3 - - name: Flake8 checks + - name: ruff checks run: | - python3 -m flake8 fenicsx_shells - cd demo && python3 -m flake8 . && cd ../ - python3 -m flake8 test - - name: isort checks (non-blocking) - continue-on-error: true - run: | - python3 -m isort --check fenicsx_shells - python3 -m isort --check demo - python3 -m isort --check test + ruff check . + ruff format --check . - name: Install FEniCSx-Shells run: | - pip install scikit-build-core[pyproject] # TO REMOVE ONCE 0.8.0 RELEASED python3 -m pip install --no-build-isolation --check-build-dependencies . - name: Build documentation diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 53bb8f8..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.18) - -project(fenicsx_shells VERSION "0.1.0.0") - -find_package(Python COMPONENTS Interpreter Development REQUIRED) -find_package(DOLFINX REQUIRED) - -# Set C++ standard before finding pybind11 -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# Now we can find pybind11 -find_package(pybind11 CONFIG REQUIRED) - -pybind11_add_module(_fenicsx_shellscpp MODULE fenicsx_shells/cpp/wrapper.cpp) - -target_link_libraries(_fenicsx_shellscpp PRIVATE pybind11::module) diff --git a/demo/conftest.py b/demo/conftest.py index 66dbb04..87ce4b1 100644 --- a/demo/conftest.py +++ b/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. mpiexex") - 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. mpiexex", + ) + parser.addoption("--num-proc", action="store", default=1, help="Number of MPI processes to use") @pytest.fixture diff --git a/demo/demo_kirchhoff-love-clamped.py b/demo/demo_kirchhoff-love-clamped.py index 76ddd53..c02b598 100644 --- a/demo/demo_kirchhoff-love-clamped.py +++ b/demo/demo_kirchhoff-love-clamped.py @@ -35,17 +35,17 @@ # We begin by importing the necessary functionality from DOLFINx, UFL and # PETSc. +from mpi4py import MPI + import numpy as np import dolfinx import ufl -from dolfinx.fem import FunctionSpace, dirichletbc +from basix.ufl import element, mixed_element +from dolfinx.fem import dirichletbc, functionspace from dolfinx.fem.petsc import LinearProblem from dolfinx.mesh import CellType, create_unit_square -from ufl import (FacetNormal, FiniteElement, Identity, Measure, MixedElement, - grad, inner, sym, tr) - -from mpi4py import MPI +from ufl import FacetNormal, Identity, Measure, grad, inner, sym, tr # We then create a two-dimensional mesh of the mid-plane of the plate $\Omega = # [0, 1] \times [0, 1]$. @@ -65,9 +65,10 @@ # + k = 2 -U_el = MixedElement([FiniteElement("Lagrange", ufl.triangle, k + 1), - FiniteElement("HHJ", ufl.triangle, k)]) -U = FunctionSpace(mesh, U_el) +U_el = mixed_element( + [element("Lagrange", mesh.basix_cell(), k + 1), element("HHJ", mesh.basix_cell(), k)] +) +U = functionspace(mesh, U_el) w, M = ufl.TrialFunctions(U) w_t, M_t = ufl.TestFunctions(U) @@ -148,7 +149,7 @@ def k_theta(theta): def k_M(M): """Bending strain tensor in terms of bending moments""" - return (12.0/(E*t**3))*((1.0 + nu)*M - nu*Identity(2)*tr(M)) + return (12.0 / (E * t**3)) * ((1.0 + nu) * M - nu * Identity(2) * tr(M)) def nn(M): @@ -163,14 +164,16 @@ def inner_divdiv(M, theta): """Discrete div-div inner product""" n = FacetNormal(M.ufl_domain()) M_nn = nn(M) - result = -inner(M, k_theta(theta))*dx + inner(M_nn("+"), - ufl.jump(theta, n))*dS + inner(M_nn, ufl.dot(theta, n))*ds + result = ( + -inner(M, k_theta(theta)) * dx + + inner(M_nn("+"), ufl.jump(theta, n)) * dS + + inner(M_nn, ufl.dot(theta, n)) * ds + ) return result -a = inner(k_M(M), M_t)*dx + inner_divdiv(M_t, theta(w)) + \ - inner_divdiv(M, theta(w_t)) -L = -inner(t**3, w_t)*dx +a = inner(k_M(M), M_t) * dx + inner_divdiv(M_t, theta(w)) + inner_divdiv(M, theta(w_t)) +L = -inner(t**3, w_t) * dx def all_boundary(x): @@ -187,15 +190,14 @@ def all_boundary(x): # TODO: Add table like TDNNS example. # + -boundary_entities = dolfinx.mesh.locate_entities_boundary( - mesh, mesh.topology.dim - 1, all_boundary) +boundary_entities = dolfinx.mesh.locate_entities_boundary(mesh, mesh.topology.dim - 1, all_boundary) bcs = [] # Transverse displacement boundary_dofs_displacement = dolfinx.fem.locate_dofs_topological( - U.sub(0), mesh.topology.dim - 1, boundary_entities) -bcs.append(dirichletbc(np.array(0.0, dtype=np.float64), - boundary_dofs_displacement, U.sub(0))) + U.sub(0), mesh.topology.dim - 1, boundary_entities +) +bcs.append(dirichletbc(np.array(0.0, dtype=np.float64), boundary_dofs_displacement, U.sub(0))) # - @@ -204,15 +206,18 @@ def all_boundary(x): # centre of the plate. # + -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"}, +) u_h = problem.solve() bb_tree = dolfinx.geometry.bb_tree(mesh, 2) point = np.array([[0.5, 0.5, 0.0]], dtype=np.float64) cell_candidates = dolfinx.geometry.compute_collisions_points(bb_tree, point) -cells = dolfinx.geometry.compute_colliding_cells( - mesh, cell_candidates, point) +cells = dolfinx.geometry.compute_colliding_cells(mesh, cell_candidates, point) w, M = u_h.split() diff --git a/demo/demo_reissner-mindlin-clamped-tdnns.py b/demo/demo_reissner-mindlin-clamped-tdnns.py index 1962894..d2eaa17 100644 --- a/demo/demo_reissner-mindlin-clamped-tdnns.py +++ b/demo/demo_reissner-mindlin-clamped-tdnns.py @@ -15,8 +15,8 @@ # conditions using the TDNNS (tangential displacement normal-normal stress) # element developed in: # -# Pechstein, A. S., Schöberl, J. The TDNNS method for Reissner–Mindlin plates. -# Numer. Math. 137, 713–740 (2017). +# Pechstein, A. S., Schöberl, J. The TDNNS method for Reissner-Mindlin plates. +# Numer. Math. 137, 713-740 (2017). # [doi:10.1007/s00211-017-0883-9](https://doi.org/10.1007/s00211-017-0883-9) # # The idea behind this element construction is that the rotations, transverse @@ -37,17 +37,17 @@ # PETSc. # + +from mpi4py import MPI + import numpy as np import dolfinx import ufl -from dolfinx.fem import Function, FunctionSpace, dirichletbc +from basix.ufl import element, mixed_element +from dolfinx.fem import Function, dirichletbc, functionspace from dolfinx.fem.petsc import LinearProblem from dolfinx.mesh import CellType, create_unit_square -from ufl import (FacetNormal, FiniteElement, Identity, Measure, MixedElement, - grad, inner, split, sym, tr) - -from mpi4py import MPI +from ufl import FacetNormal, Identity, Measure, grad, inner, split, sym, tr # - @@ -74,9 +74,11 @@ # + k = 3 -U_el = MixedElement([FiniteElement("N2curl", ufl.triangle, k), FiniteElement("Lagrange", ufl.triangle, k + 1), - FiniteElement("HHJ", ufl.triangle, k)]) -U = FunctionSpace(mesh, U_el) +cell = mesh.basix_cell() +U_el = mixed_element( + [element("N2curl", cell, k), element("Lagrange", cell, k + 1), element("HHJ", cell, k)] +) +U = functionspace(mesh, U_el) u = ufl.TrialFunction(U) u_t = ufl.TestFunction(U) @@ -91,7 +93,7 @@ # + E = 10920.0 nu = 0.3 -kappa = 5.0/6.0 +kappa = 5.0 / 6.0 t = 0.001 # - @@ -143,13 +145,13 @@ def k_theta(theta): def k_M(M): """Bending strain tensor in terms of bending moments""" - return (12.0/(E*t**3))*((1.0 + nu)*M - nu*Identity(2)*tr(M)) + return (12.0 / (E * t**3)) * ((1.0 + nu) * M - nu * Identity(2) * tr(M)) def nn(M): """Normal-normal component of tensor""" n = FacetNormal(M.ufl_domain()) - M_n = M*n + M_n = M * n M_nn = ufl.dot(M_n, n) return M_nn @@ -158,8 +160,11 @@ def inner_divdiv(M, theta): """Discrete div-div inner product""" n = FacetNormal(M.ufl_domain()) M_nn = nn(M) - result = -inner(M, k_theta(theta))*dx + inner(M_nn("+"), - ufl.jump(theta, n))*dS + inner(M_nn, ufl.dot(theta, n))*ds + result = ( + -inner(M, k_theta(theta)) * dx + + inner(M_nn("+"), ufl.jump(theta, n)) * dS + + inner(M_nn, ufl.dot(theta, n)) * ds + ) return result @@ -168,9 +173,13 @@ def gamma(theta, w): return grad(w) - theta -a = inner(k_M(M), M_t)*dx + inner_divdiv(M_t, theta) + inner_divdiv(M, theta_t) - \ - ((E*kappa*t)/(2.0*(1.0 + nu)))*inner(gamma(theta, w), gamma(theta_t, w_t))*dx -L = -inner(1.0*t**3, w_t)*dx +a = ( + inner(k_M(M), M_t) * dx + + inner_divdiv(M_t, theta) + + inner_divdiv(M, theta_t) + - ((E * kappa * t) / (2.0 * (1.0 + nu))) * inner(gamma(theta, w), gamma(theta_t, w_t)) * dx +) +L = -inner(1.0 * t**3, w_t) * dx # - @@ -206,19 +215,20 @@ def all_boundary(x): return np.full(x.shape[1], True, dtype=bool) -boundary_entities = dolfinx.mesh.locate_entities_boundary( - mesh, mesh.topology.dim - 1, all_boundary) +boundary_entities = dolfinx.mesh.locate_entities_boundary(mesh, mesh.topology.dim - 1, all_boundary) bcs = [] # Transverse displacement boundary_dofs = dolfinx.fem.locate_dofs_topological( - U.sub(1), mesh.topology.dim - 1, boundary_entities) + U.sub(1), mesh.topology.dim - 1, boundary_entities +) bcs.append(dirichletbc(np.array(0.0, dtype=np.float64), boundary_dofs, U.sub(1))) # Fix tangential component of rotation R = U.sub(0).collapse()[0] boundary_dofs = dolfinx.fem.locate_dofs_topological( - (U.sub(0), R), mesh.topology.dim - 1, boundary_entities) + (U.sub(0), R), mesh.topology.dim - 1, boundary_entities +) theta_bc = Function(R) bcs.append(dirichletbc(theta_bc, boundary_dofs, U.sub(0))) @@ -229,15 +239,18 @@ def all_boundary(x): # centre of the plate. # + -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"}, +) u_h = problem.solve() bb_tree = dolfinx.geometry.bb_tree(mesh, 2) point = np.array([[0.5, 0.5, 0.0]], dtype=np.float64) cell_candidates = dolfinx.geometry.compute_collisions_points(bb_tree, point) -cells = dolfinx.geometry.compute_colliding_cells( - mesh, cell_candidates, point) +cells = dolfinx.geometry.compute_colliding_cells(mesh, cell_candidates, point) theta, w, M = u_h.split() diff --git a/demo/demo_reissner-mindlin-clamped.py b/demo/demo_reissner-mindlin-clamped.py index 0bd4d4a..370242b 100644 --- a/demo/demo_reissner-mindlin-clamped.py +++ b/demo/demo_reissner-mindlin-clamped.py @@ -27,18 +27,18 @@ # We begin by importing the necessary functionality from DOLFINx, UFL and # PETSc. +from mpi4py import MPI + import numpy as np import dolfinx import ufl -from dolfinx.fem import Function, FunctionSpace, dirichletbc +from basix.ufl import element, mixed_element +from dolfinx.fem import Function, dirichletbc, functionspace from dolfinx.fem.petsc import LinearProblem from dolfinx.io.utils import XDMFFile from dolfinx.mesh import CellType, create_unit_square -from ufl import (FiniteElement, MixedElement, VectorElement, dx, grad, inner, - split, sym, tr) - -from mpi4py import MPI +from ufl import dx, grad, inner, split, sym, tr # We then create a two-dimensional mesh of the mid-plane of the plate $\Omega = # [0, 1] \times [0, 1]$. `GhostMode.shared_facet` is required as the Form will @@ -64,9 +64,16 @@ # The final element definition is # + -U_el = MixedElement([VectorElement("Lagrange", ufl.triangle, 2), FiniteElement("Lagrange", ufl.triangle, 1), - FiniteElement("N1curl", ufl.triangle, 1), FiniteElement("N1curl", ufl.triangle, 1)]) -U = FunctionSpace(mesh, U_el) +cell = mesh.basix_cell() +U_el = mixed_element( + [ + element("Lagrange", cell, 2, shape=(2,)), + element("Lagrange", cell, 1), + element("N1curl", cell, 1), + element("N1curl", cell, 1), + ] +) +U = functionspace(mesh, U_el) u_ = Function(U) u = ufl.TrialFunction(U) @@ -80,7 +87,7 @@ E = 10920.0 nu = 0.3 -kappa = 5.0/6.0 +kappa = 5.0 / 6.0 t = 0.001 # The bending strain tensor $k$ for the Reissner-Mindlin model can be expressed @@ -94,15 +101,16 @@ # function of the bending strain tensor $k$ # # $$ -# \psi_b(k) = \frac{1}{2} D \left( (1 - \nu) \, \mathrm{tr}\,(k^2) + \nu \, (\mathrm{tr} \,k)^2 \right) \qquad +# \psi_b(k) = \frac{1}{2} D \left( (1 - \nu) \, \mathrm{tr}\,(k^2) +# + \nu \, (\mathrm{tr} \,k)^2 \right) \qquad # D = \frac{Et^3}{12(1 - \nu^2)} # $$ # # which can be expressed in UFL as -D = (E*t**3)/(24.0*(1.0 - nu**2)) +D = (E * t**3) / (24.0 * (1.0 - nu**2)) k = sym(grad(theta_)) -psi_b = D*((1.0 - nu)*tr(k*k) + nu*(tr(k))**2) +psi_b = D * ((1.0 - nu) * tr(k * k) + nu * (tr(k)) ** 2) # Because we are using a mixed variational formulation, we choose to write the # shear energy density $\psi_s$ is a function of the reduced shear strain @@ -112,7 +120,7 @@ # # or in UFL: -psi_s = ((E*kappa*t)/(4.0*(1.0 + nu)))*inner(R_gamma_, R_gamma_) +psi_s = ((E * kappa * t) / (4.0 * (1.0 + nu))) * inner(R_gamma_, R_gamma_) # Finally, we can write out external work due to the uniform loading in the out-of-plane direction # @@ -126,7 +134,7 @@ # # In UFL this can be expressed as -W_ext = inner(1.0*t**3, w_)*dx +W_ext = inner(1.0 * t**3, w_) * dx # With all of the standard mechanical terms defined, we can turn to defining # the Duran-Liberman reduction operator. This operator 'ties' our reduced shear @@ -162,16 +170,15 @@ # choose to write this function in full here. # + -dSp = ufl.Measure('dS', metadata={'quadrature_degree': 1}) -dsp = ufl.Measure('ds', metadata={'quadrature_degree': 1}) +dSp = ufl.Measure("dS", metadata={"quadrature_degree": 1}) +dsp = ufl.Measure("ds", metadata={"quadrature_degree": 1}) n = ufl.FacetNormal(mesh) t = ufl.as_vector((-n[1], n[0])) def inner_e(x, y): - return (inner(x, t)*inner(y, t))('+') * \ - dSp + (inner(x, t)*inner(y, t))*dsp + return (inner(x, t) * inner(y, t))("+") * dSp + (inner(x, t) * inner(y, t)) * dsp Pi_R = inner_e(gamma - R_gamma_, p_) @@ -181,7 +188,7 @@ def inner_e(x, y): # residual and Jacobian automatically using the standard UFL `derivative` # function -Pi = psi_b*dx + psi_s*dx + Pi_R - W_ext +Pi = psi_b * dx + psi_s * dx + Pi_R - W_ext F = ufl.derivative(Pi, u_, u_t) J = ufl.derivative(F, u_, u) @@ -206,21 +213,22 @@ def all_boundary(x): u_boundary = Function(U) -boundary_entities = dolfinx.mesh.locate_entities_boundary( - mesh, mesh.topology.dim - 1, all_boundary) -boundary_dofs = dolfinx.fem.locate_dofs_topological( - U, mesh.topology.dim - 1, boundary_entities) +boundary_entities = dolfinx.mesh.locate_entities_boundary(mesh, mesh.topology.dim - 1, all_boundary) +boundary_dofs = dolfinx.fem.locate_dofs_topological(U, mesh.topology.dim - 1, boundary_entities) bcs = [dirichletbc(u_boundary, boundary_dofs)] -problem = LinearProblem(J, -F, bcs=bcs, petsc_options={ - "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}) +problem = LinearProblem( + J, + -F, + bcs=bcs, + petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}, +) u_ = problem.solve() bb_tree = dolfinx.geometry.bb_tree(mesh, 2) point = np.array([[0.5, 0.5, 0.0]], dtype=np.float64) cell_candidates = dolfinx.geometry.compute_collisions_points(bb_tree, point) -cells = dolfinx.geometry.compute_colliding_cells( - mesh, cell_candidates, point) +cells = dolfinx.geometry.compute_colliding_cells(mesh, cell_candidates, point) theta, w, R_gamma, p = u_.split() @@ -232,7 +240,8 @@ def all_boundary(x): # mesh. def test_center_displacement(): - assert np.isclose(value[0], 1.285E-6, atol=1E-3, rtol=1E-3) + assert np.isclose(value[0], 1.285e-6, atol=1e-3, rtol=1e-3) + with XDMFFile(MPI.COMM_WORLD, "w.xdmf", "w") as f: f.write_mesh(mesh) diff --git a/demo/demo_reissner-mindlin-simply-supported.py b/demo/demo_reissner-mindlin-simply-supported.py index b671baf..ef5401e 100644 --- a/demo/demo_reissner-mindlin-simply-supported.py +++ b/demo/demo_reissner-mindlin-simply-supported.py @@ -27,17 +27,17 @@ # We begin by importing the necessary functionality from DOLFINx, UFL and # PETSc. +from mpi4py import MPI + import numpy as np import dolfinx import ufl -from dolfinx.fem import Function, FunctionSpace, dirichletbc +from basix.ufl import element, mixed_element +from dolfinx.fem import Function, dirichletbc, functionspace from dolfinx.fem.petsc import LinearProblem from dolfinx.mesh import CellType, create_unit_square -from ufl import (FiniteElement, MixedElement, VectorElement, dx, grad, inner, - split, sym, tr) - -from mpi4py import MPI +from ufl import dx, grad, inner, split, sym, tr # We then create a two-dimensional mesh of the mid-plane of the plate $\Omega = # [0, 1] \times [0, 1]$. `GhostMode.shared_facet` is required as the Form will @@ -63,9 +63,16 @@ # The final element definition is # + -U_el = MixedElement([VectorElement("Lagrange", ufl.quadrilateral, 1), FiniteElement("Lagrange", ufl.quadrilateral, 1), - FiniteElement("RTCE", ufl.quadrilateral, 1), FiniteElement("RTCE", ufl.quadrilateral, 1)]) -U = FunctionSpace(mesh, U_el) +cell = mesh.basix_cell() +U_el = mixed_element( + [ + element("Lagrange", cell, 1, shape=(2,)), + element("Lagrange", cell, 1), + element("RTCE", cell, 1), + element("RTCE", cell, 1), + ] +) +U = functionspace(mesh, U_el) u_ = Function(U) u = ufl.TrialFunction(U) @@ -79,7 +86,7 @@ E = 10920.0 nu = 0.3 -kappa = 5.0/6.0 +kappa = 5.0 / 6.0 t = 0.001 # The bending strain tensor $k$ for the Reissner-Mindlin model can be expressed @@ -93,15 +100,16 @@ # function of the bending strain tensor $k$ # # $$ -# \psi_b(k) = \frac{1}{2} D \left( (1 - \nu) \, \mathrm{tr}\,(k^2) + \nu \, (\mathrm{tr} \,k)^2 \right) \qquad +# \psi_b(k) = \frac{1}{2} D \left( (1 - \nu) \, \mathrm{tr}\,(k^2) +# + \nu \, (\mathrm{tr} \,k)^2 \right) \qquad # D = \frac{Et^3}{12(1 - \nu^2)} # $$ # # which can be expressed in UFL as -D = (E*t**3)/(24.0*(1.0 - nu**2)) +D = (E * t**3) / (24.0 * (1.0 - nu**2)) k = sym(grad(theta_)) -psi_b = D*((1.0 - nu)*tr(k*k) + nu*(tr(k))**2) +psi_b = D * ((1.0 - nu) * tr(k * k) + nu * (tr(k)) ** 2) # Because we are using a mixed variational formulation, we choose to write the # shear energy density $\psi_s$ is a function of the reduced shear strain @@ -111,7 +119,7 @@ # # or in UFL: -psi_s = ((E*kappa*t)/(4.0*(1.0 + nu)))*inner(R_gamma_, R_gamma_) +psi_s = ((E * kappa * t) / (4.0 * (1.0 + nu))) * inner(R_gamma_, R_gamma_) # Finally, we can write out external work due to the uniform loading in the out-of-plane direction # @@ -125,7 +133,7 @@ # # In UFL this can be expressed as -W_ext = inner(1.0*t**3, w_)*dx +W_ext = inner(1.0 * t**3, w_) * dx # With all of the standard mechanical terms defined, we can turn to defining # the Duran-Liberman reduction operator. This operator 'ties' our reduced shear @@ -161,16 +169,15 @@ # choose to write this function in full here. # + -dSp = ufl.Measure('dS', metadata={'quadrature_degree': 1}) -dsp = ufl.Measure('ds', metadata={'quadrature_degree': 1}) +dSp = ufl.Measure("dS", metadata={"quadrature_degree": 1}) +dsp = ufl.Measure("ds", metadata={"quadrature_degree": 1}) n = ufl.FacetNormal(mesh) t = ufl.as_vector((-n[1], n[0])) def inner_e(x, y): - return (inner(x, t)*inner(y, t))('+') * \ - dSp + (inner(x, t)*inner(y, t))*dsp + return (inner(x, t) * inner(y, t))("+") * dSp + (inner(x, t) * inner(y, t)) * dsp Pi_R = inner_e(gamma - R_gamma_, p_) @@ -180,7 +187,7 @@ def inner_e(x, y): # residual and Jacobian automatically using the standard UFL `derivative` # function -Pi = psi_b*dx + psi_s*dx + Pi_R - W_ext +Pi = psi_b * dx + psi_s * dx + Pi_R - W_ext F = ufl.derivative(Pi, u_, u_t) J = ufl.derivative(F, u_, u) @@ -209,9 +216,9 @@ def top_or_bottom(x): def make_bc(value, V, on_boundary): boundary_entities = dolfinx.mesh.locate_entities_boundary( - mesh, mesh.topology.dim - 1, on_boundary) - boundary_dofs = dolfinx.fem.locate_dofs_topological( - V, mesh.topology.dim - 1, boundary_entities) + mesh, mesh.topology.dim - 1, on_boundary + ) + boundary_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, boundary_entities) bc = dirichletbc(value, boundary_dofs, V) return bc @@ -227,15 +234,18 @@ def make_bc(value, V, on_boundary): bcs.append(make_bc(np.array(0.0, dtype=np.float64), U.sub(0).sub(1), left_or_right)) -problem = LinearProblem(J, -F, bcs=bcs, petsc_options={ - "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}) +problem = LinearProblem( + J, + -F, + bcs=bcs, + petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}, +) u_ = problem.solve() bb_tree = dolfinx.geometry.bb_tree(mesh, 2) point = np.array([[0.5, 0.5, 0.0]], dtype=np.float64) cell_candidates = dolfinx.geometry.compute_collisions_points(bb_tree, point) -cells = dolfinx.geometry.compute_colliding_cells( - mesh, cell_candidates, point) +cells = dolfinx.geometry.compute_colliding_cells(mesh, cell_candidates, point) theta, w, R_gamma, p = u_.split() diff --git a/demo/test_demos.py b/demo/test_demos.py index 9be8986..4713d15 100644 --- a/demo/test_demos.py +++ b/demo/test_demos.py @@ -15,7 +15,7 @@ # Build list of demo programs demos = [] -demo_files = list(path.glob('**/demo_*.py')) +demo_files = list(path.glob("**/demo_*.py")) for f in demo_files: demos.append((f.parent, f.name)) diff --git a/doc/source/conf.py b/doc/source/conf.py index 5d8a040..ffe1a0f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -9,42 +9,51 @@ import os import sys -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(".")) import jupytext_process jupytext_process.process() -project = 'FEniCSx-Shells' -copyright = '2022, FEniCSx-Shells Authors' -author = 'FEniCSx-Shells Authors' -release = '0.5.0.dev0' +project = "FEniCSx-Shells" +copyright = "2022, FEniCSx-Shells Authors" +author = "FEniCSx-Shells Authors" +release = "0.5.0.dev0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.mathjax', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', - 'myst_parser', ] - -templates_path = ['_templates'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "myst_parser", +] + +templates_path = ["_templates"] exclude_patterns = [] -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] html_theme = "sphinx_rtd_theme" -myst_enable_extensions = ["dollarmath",] +myst_enable_extensions = [ + "dollarmath", +] -autodoc_default_options = {'members': True, 'show-inheritance': True, 'imported-members': True, 'undoc-members': True} +autodoc_default_options = { + "members": True, + "show-inheritance": True, + "imported-members": True, + "undoc-members": True, +} autosummary_generate = True autoclass_content = "both" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'alabaster' -html_static_path = ['_static'] +html_theme = "alabaster" +html_static_path = ["_static"] diff --git a/doc/source/jupytext_process.py b/doc/source/jupytext_process.py index fad2b3c..bb3221e 100644 --- a/doc/source/jupytext_process.py +++ b/doc/source/jupytext_process.py @@ -21,11 +21,11 @@ def process(): # Iterate over subdirectories containing demos for subdir in subdirs: # Make demo doc directory - demo_dir = pathlib.Path('./demo') + demo_dir = pathlib.Path("./demo") 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"): # If demo saves matplotlib images, run the demo if "savefig" in demo.read_text(): here = os.getcwd() diff --git a/fenicsx_shells/cpp/assembler.cpp b/fenicsx_shells/cpp/assembler.cpp deleted file mode 100644 index f89b269..0000000 --- a/fenicsx_shells/cpp/assembler.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// What is the proper way to get the location of these pybind11 casting headers? -#include "/usr/local/dolfinx-real/lib/python3.8/dist-packages/dolfinx/wrappers/caster_petsc.h" -#include - -using namespace dolfinx; - -// TODO: -// Boundary condition application (can it be done with the standard routines?) -// Convert to active_cells pattern as in standard assemblers. -// Work out how to get include directory of caster_petsc.h -// Split down into inlined functions for improved readability. - -template -std::pair assemble(const fem::Form& a, const fem::Form& L) -{ - assert(a.rank() == 2); - assert(L.rank() == 1); - - // Get data from form - const auto mesh = a.mesh(); - assert(mesh); - assert(mesh == L.mesh()); - - const std::shared_ptr dofmap0 - = a.function_space(0)->dofmap(); - const std::shared_ptr dofmap1 - = a.function_space(1)->dofmap(); - // Assumption we are working with bilinear form leading to square operator. - assert(dofmap0); - assert(dofmap1); - assert(dofmap0 == dofmap1); - - const std::shared_ptr element - = a.function_space(0)->element(); - assert(element->num_sub_elements() == 2); - - const auto primal_dofmap = std::make_shared( - dofmap0->extract_sub_dofmap({0})); - const auto primal_element = element->extract_sub_element({0}); - - // Make data structures for global assembly - // Create sparsity pattern - // std::array primal_dofmaps{primal_dofmap.get(), primal_dofmap.get()}; - const std::array primal_dofmaps{a.function_space(0)->dofmap().get(), - a.function_space(1)->dofmap().get()}; - const std::set types = a.integrals().types(); - la::SparsityPattern pattern - = fem::create_sparsity_pattern(mesh->topology(), primal_dofmaps, types); - pattern.assemble(); - - auto A = la::PETScMatrix(mesh->mpi_comm(), pattern); - auto b = la::PETScVector(*(primal_dofmap->index_map)); - // Create mat_set_values function pointer - std::function - mat_set_values = la::PETScMatrix::add_fn(A.mat()); - MatZeroEntries(A.mat()); - - // Extract raw view into PETSc Vec memory. - Vec b_local; - VecGhostGetLocalForm(b.vec(), &b_local); - PetscInt n = 0; - VecGetSize(b_local, &n); - PetscScalar* array = nullptr; - VecGetArray(b_local, &array); - Eigen::Map> _b(array, n); - - // Calculate offsets into larger local tensor for slicing. - auto dual_element = element->extract_sub_element({1}); - assert(dual_element->num_sub_elements() == 2); - auto offsets = Eigen::Array(); - auto sizes = Eigen::Array(); - - offsets[0] = 0; - offsets[1] = offsets[0] + primal_element->space_dimension(); - offsets[2] - = offsets[1] + dual_element->extract_sub_element({0})->space_dimension(); - - sizes[0] = offsets[1] - offsets[0]; - sizes[1] = offsets[2] - offsets[1]; - sizes[2] = element->space_dimension() - offsets[2]; - - // Local tensors - Eigen::Matrix Ae( - element->space_dimension(), element->space_dimension()); - Eigen::Matrix be(element->space_dimension()); - - Eigen::Matrix A_macro( - 2 * element->space_dimension(), 2 * element->space_dimension()); - Eigen::Matrix b_macro(2 * element->space_dimension()); - - // Block splitting (views into Ae and be). - const auto A_00 = Ae.block(offsets[0], offsets[0], sizes[0], sizes[0]); - const auto A_02 = Ae.block(offsets[0], offsets[2], sizes[0], sizes[2]); - const auto A_11 = Ae.block(offsets[1], offsets[1], sizes[1], sizes[1]); - const auto A_12 = Ae.block(offsets[1], offsets[2], sizes[1], sizes[2]); - const auto A_20 = Ae.block(offsets[2], offsets[0], sizes[2], sizes[0]); - const auto A_21 = Ae.block(offsets[2], offsets[1], sizes[2], sizes[1]); - - const auto b_0 = be.segment(offsets[0], sizes[0]); - const auto b_1 = be.segment(offsets[1], sizes[1]); - const auto b_2 = be.segment(offsets[2], sizes[2]); - - // Iterate over active cells - const int tdim = mesh->topology().dim(); - const auto map = mesh->topology().index_map(tdim); - assert(map); - const int num_cells = map->size_local(); - - const fem::FormIntegrals& integrals = a.integrals(); - using type = fem::IntegralType; - - assert(a.integrals().num_integrals(type::cell) == 1); - assert(a.integrals().num_integrals(type::interior_facet) == 1); - assert(a.integrals().num_integrals(type::exterior_facet) == 1); - const auto a_kernel_domain_integral - = a.integrals().get_tabulate_tensor(type::cell, 0); - const auto a_kernel_interior_facet - = a.integrals().get_tabulate_tensor(type::interior_facet, 0); - const auto a_kernel_exterior_facet - = a.integrals().get_tabulate_tensor(type::exterior_facet, 0); - assert(L.integrals().num_integrals(type::cell) == 1); - assert(L.integrals().num_integrals(type::interior_facet) == 1); - assert(L.integrals().num_integrals(type::exterior_facet) == 1); - const auto L_kernel_domain_integral - = L.integrals().get_tabulate_tensor(type::cell, 0); - const auto L_kernel_interior_facet - = L.integrals().get_tabulate_tensor(type::interior_facet, 0); - const auto L_kernel_exterior_facet - = L.integrals().get_tabulate_tensor(type::exterior_facet, 0); - - // Prepare cell geometry - const int gdim = mesh->geometry().dim(); - const graph::AdjacencyList& x_dofmap - = mesh->geometry().dofmap(); - - // FIXME: Add proper interface for num coordinate dofs - const int num_dofs_g = x_dofmap.num_links(0); - const Eigen::Array& x_g - = mesh->geometry().x(); - Eigen::Array - coordinate_dofs(num_dofs_g, gdim); - - Eigen::Array - coordinate_dofs_macro(2 * num_dofs_g, gdim); - - // Prepare constants - if (!a.all_constants_set()) - throw std::runtime_error("Unset constant in bilinear form a."); - if (!L.all_constants_set()) - throw std::runtime_error("Unset constant in linear form L."); - const Eigen::Array a_constants - = fem::pack_constants(a); - const Eigen::Array L_constants - = fem::pack_constants(L); - - // Prepare coefficients - const Eigen::Array - a_coeffs = pack_coefficients(a); - const Eigen::Array - L_coeffs = pack_coefficients(L); - - const std::vector a_offsets = a.coefficients().offsets(); - Eigen::Array a_coeff_array_macro(2 * a_offsets.back()); - const std::vector L_offsets = L.coefficients().offsets(); - Eigen::Array L_coeff_array_macro(2 * L_offsets.back()); - - // Needed for all integrals - mesh->topology_mutable().create_entity_permutations(); - const Eigen::Array& cell_info - = mesh->topology().get_cell_permutation_info(); - - // Needed for facet integrals - const Eigen::Array& perms - = mesh->topology().get_facet_permutations(); - - // For static condensation step - Eigen::Array Ae_projected( - primal_element->space_dimension(), primal_element->space_dimension()); - Eigen::Array be_projected( - primal_element->space_dimension()); - - mesh->topology_mutable().create_connectivity(tdim - 1, tdim); - mesh->topology_mutable().create_connectivity(tdim, tdim - 1); - - const auto f_to_c = mesh->topology().connectivity(tdim - 1, tdim); - assert(f_to_c); - const auto c_to_f = mesh->topology().connectivity(tdim, tdim - 1); - assert(c_to_f); - - for (int c = 0; c < num_cells; ++c) - { - // Get cell vertex coordinates - auto x_dofs = x_dofmap.links(c); - for (int i = 0; i < num_dofs_g; ++i) - coordinate_dofs.row(i) = x_g.row(x_dofs[i]).head(gdim); - - Ae.setZero(); - be.setZero(); - - const auto a_coeff_array = a_coeffs.row(c); - const auto L_coeff_array = L_coeffs.row(c); - a_kernel_domain_integral(Ae.data(), a_coeff_array.data(), - a_constants.data(), coordinate_dofs.data(), - nullptr, nullptr, cell_info[c]); - L_kernel_domain_integral(be.data(), L_coeff_array.data(), - L_constants.data(), coordinate_dofs.data(), - nullptr, nullptr, cell_info[c]); - - // Loop over attached facets - const auto c_f = c_to_f->links(c); - assert(c_to_f->num_links(c) == 3); - - for (int local_facet = 0; local_facet < 3; ++local_facet) - { - // Get attached cell indices - const std::int32_t f = c_f[local_facet]; - const auto f_c = f_to_c->links(f); - assert(f_c.rows() < 3); - - if (f_c.rows() == 1) - { - // Exterior facet - // Get local index of facet with respect to cell. - const std::uint8_t perm = perms(local_facet, c); - a_kernel_exterior_facet(Ae.data(), a_coeff_array.data(), - a_constants.data(), coordinate_dofs.data(), - &local_facet, &perm, cell_info[c]); - L_kernel_exterior_facet(be.data(), L_coeff_array.data(), - L_constants.data(), coordinate_dofs.data(), - &local_facet, &perm, cell_info[c]); - } - else - { - // Interior facet - // Create attached cells - // Find local facet numbering - std::array local_facets; - for (int k = 0; k < 2; ++k) - { - const auto c_f = c_to_f->links(f_c[k]); - const auto* end = c_f.data() + c_f.rows(); - const auto* it = std::find(c_f.data(), end, f); - assert(it != end); - local_facets[k] = std::distance(c_f.data(), it); - } - - // Orientation - const std::array perm{perms(local_facets[0], f_c[0]), - perms(local_facets[1], f_c[1])}; - - // Get cell geometry - const auto x_dofs0 = x_dofmap.links(f_c[0]); - const auto x_dofs1 = x_dofmap.links(f_c[1]); - for (int k = 0; k < num_dofs_g; ++k) - { - for (int l = 0; l < gdim; ++l) - { - coordinate_dofs_macro(k, l) = x_g(x_dofs0[k], l); - coordinate_dofs_macro(k + num_dofs_g, l) = x_g(x_dofs1[k], l); - } - } - - // Layout for the restricted coefficients is flattened - // w[coefficient][restriction][dof] - const auto a_coeff_cell0 = a_coeffs.row(f_c[0]); - const auto a_coeff_cell1 = a_coeffs.row(f_c[1]); - const auto L_coeff_cell0 = L_coeffs.row(f_c[0]); - const auto L_coeff_cell1 = L_coeffs.row(f_c[1]); - - // Loop over coefficients for a - for (std::size_t i = 0; i < a_offsets.size() - 1; ++i) - { - // Loop over entries for coefficient i - const int num_entries = a_offsets[i + 1] - a_offsets[i]; - a_coeff_array_macro.segment(2 * a_offsets[i], num_entries) - = a_coeff_cell0.segment(a_offsets[i], num_entries); - a_coeff_array_macro.segment(a_offsets[i + 1] + a_offsets[i], - num_entries) - = a_coeff_cell1.segment(a_offsets[i], num_entries); - } - - // Loop over coefficients for L - for (std::size_t i = 0; i < L_offsets.size() - 1; ++i) - { - // Loop over entries for coefficient i - const int num_entries = L_offsets[i + 1] - L_offsets[i]; - L_coeff_array_macro.segment(2 * L_offsets[i], num_entries) - = L_coeff_cell0.segment(L_offsets[i], num_entries); - L_coeff_array_macro.segment(L_offsets[i + 1] + L_offsets[i], - num_entries) - = L_coeff_cell1.segment(L_offsets[i], num_entries); - } - - A_macro.setZero(); - b_macro.setZero(); - - a_kernel_interior_facet( - A_macro.data(), a_coeff_array_macro.data(), a_constants.data(), - coordinate_dofs_macro.data(), local_facets.data(), perm.data(), - cell_info[f_c[0]]); - L_kernel_interior_facet( - b_macro.data(), L_coeff_array_macro.data(), L_constants.data(), - coordinate_dofs_macro.data(), local_facets.data(), perm.data(), - cell_info[f_c[0]]); - - // Assemble appropriate part of A_macro/b_macro into Ae/be. - int local_cell = (f_c[0] == c ? 0 : 1); - int offset = local_cell * element->space_dimension(); - Ae += A_macro.block(offset, offset, Ae.rows(), Ae.cols()); - be += b_macro.block(offset, 0, be.rows(), 1); - } - } - - // Perform static condensation. - // TODO: Two calls to same .inverse(). - // Verified that Ae_projected is the same as old fenics-shells code. - Ae_projected = A_00 + A_02 * A_12.inverse() * A_11 * A_21.inverse() * A_20; - be_projected = b_0 + A_02 * A_12.inverse() * A_11 * A_21.inverse() * b_2 - - A_02 * A_12.inverse() * b_1; - - // Assembly. - const auto dofs = primal_dofmap->list().links(c); - mat_set_values(dofs.size(), dofs.data(), dofs.size(), dofs.data(), - Ae_projected.data()); - - for (Eigen::Index i = 0; i < dofs.size(); ++i) - _b[dofs[i]] += be_projected[i]; - } - - VecRestoreArray(b_local, &array); - VecGhostRestoreLocalForm(b.vec(), &b_local); - - // The DOLFINX la::PETscVector and la::PETScMatrix objects dereference the - // underlying PETSc Vec and Mat objects when they go out of scope. So, we - // need to increment the PETSc reference counter before passing them out - // to conversion by pybind11 to petsc4py wrapped objects. - PetscObjectReference((PetscObject)A.mat()); - PetscObjectReference((PetscObject)b.vec()); - return std::make_pair(A.mat(), b.vec()); -} diff --git a/fenicsx_shells/cpp/assembler.h b/fenicsx_shells/cpp/assembler.h deleted file mode 100644 index e69de29..0000000 diff --git a/fenicsx_shells/cpp/wrapper.cpp b/fenicsx_shells/cpp/wrapper.cpp deleted file mode 100644 index 0222070..0000000 --- a/fenicsx_shells/cpp/wrapper.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -namespace py = pybind11; - -PYBIND11_MODULE(_fenicsx_shellscpp, m) -{ - m.attr("__version__") = "0.1.0.0"; - - m.def("test", []() { return 0; }); -} diff --git a/pyproject.toml b/pyproject.toml index 1f0556c..3110062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,52 @@ [build-system] -requires = ["scikit-build-core[pyproject]", "wheel", "pybind11"] -build-backend = "scikit_build_core.build" +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" [project] name = "fenicsx-shells" -version = "0.1.0.dev0" +version = "0.8.0.dev0" description = "FEniCSx-Shells" readme = "README.md" -requires-python = ">=3.8.0" +requires-python = ">=3.9.0" license = { file = "COPYING.LESSER" } authors = [ { email = "jack.hale@uni.lu" }, { name = "Jack S. Hale" }, ] dependencies = [ - "fenics-dolfinx>=0.7.2,<0.8.0", + "fenics-dolfinx>=0.8.0.dev0,<0.9.0", ] + +[tool.ruff] +line-length = 100 +indent-width = 4 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "W", # pycodestyle + "F", # pyflakes + "I", # isort - use standalone isort + "RUF", # Ruff-specific rules + "UP", # pyupgrade + "ICN", # flake8-import-conventions + "NPY", # numpy-specific rules + "FLY", # use f-string not static joins +] +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/setup.cfg b/setup.cfg deleted file mode 100644 index 87b0c90..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 120 -ignore = W503, E226, E241 diff --git a/fenicsx_shells/__init__.py b/src/fenicsx_shells/__init__.py similarity index 100% rename from fenicsx_shells/__init__.py rename to src/fenicsx_shells/__init__.py