From 518568e8005be9368ad15c8ee35fc1dd2bb687af Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Sun, 25 Feb 2024 09:21:37 +0100 Subject: [PATCH 1/5] Update to latest version of dolfinx --- .cspell_dict.txt | 3 + demo/unit_cube.ipynb | 1 + src/fenicsx_pulse/mechanicsproblem.py | 89 +++++++++++++++++---------- tests/conftest.py | 4 +- tests/test_mechanicsproblem.py | 1 + 5 files changed, 64 insertions(+), 34 deletions(-) diff --git a/.cspell_dict.txt b/.cspell_dict.txt index 639a8b2..6c99e06 100644 --- a/.cspell_dict.txt +++ b/.cspell_dict.txt @@ -3,6 +3,8 @@ allreduce argsort astype atol +basix +cellname cofac dirichletbc dofs @@ -11,6 +13,7 @@ fenics fenicsx fenicsx_pulse finsberg +functionspace gradu Holzapfel holzapfelogden diff --git a/demo/unit_cube.ipynb b/demo/unit_cube.ipynb index d0044d4..f2f917a 100644 --- a/demo/unit_cube.ipynb +++ b/demo/unit_cube.ipynb @@ -192,6 +192,7 @@ ") -> list[dolfinx.fem.bcs.DirichletBC]:\n", " V, _ = state_space.sub(0).collapse()\n", " facets = geo.facet_tags.find(1) # Specify the marker used on the boundary\n", + " mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)\n", " dofs = dolfinx.fem.locate_dofs_topological((state_space.sub(0), V), 2, facets)\n", " u_fixed = dolfinx.fem.Function(V)\n", " u_fixed.x.array[:] = 0.0\n", diff --git a/src/fenicsx_pulse/mechanicsproblem.py b/src/fenicsx_pulse/mechanicsproblem.py index 90364aa..f2fe035 100644 --- a/src/fenicsx_pulse/mechanicsproblem.py +++ b/src/fenicsx_pulse/mechanicsproblem.py @@ -5,6 +5,7 @@ import dolfinx.fem.petsc import dolfinx.nls.petsc import ufl +import basix from . import kinematics from .boundary_conditions import BoundaryConditions @@ -13,7 +14,7 @@ @dataclass(slots=True) -class MechanicsProblem: +class BaseMechanicsProblem: model: CardiacModel geometry: Geometry bcs: BoundaryConditions = field(default_factory=BoundaryConditions) @@ -22,49 +23,33 @@ class MechanicsProblem: state_space: dolfinx.fem.FunctionSpace = field(init=False, repr=False) state: dolfinx.fem.Function = field(init=False, repr=False) test_state: dolfinx.fem.Function = field(init=False, repr=False) - _virtual_work: ufl.form.Form = field(init=False, repr=False) - _dirichlet_bc: typing.Sequence[dolfinx.fem.bcs.DirichletBC] = field( + virtual_work: ufl.form.Form = field(init=False, repr=False) + _dirichlet_bc: typing.Sequence[dolfinx.fem.bcs.DirichletBC] | None = field( + default=None, init=False, repr=False, ) + @property + def dirichlet_bc(self) -> typing.Sequence[dolfinx.fem.bcs.DirichletBC]: + return self._dirichlet_bc or [] + + @dirichlet_bc.setter + def dirichlet_bc(self, value: typing.Sequence[dolfinx.fem.bcs.DirichletBC]) -> None: + self._dirichlet_bc = value + self._set_dirichlet_bc() + self._init_solver() + def __post_init__(self): self._init_space() self._init_form() self._init_solver() - def _init_space(self) -> None: - P2 = ufl.VectorElement("Lagrange", self.geometry.mesh.ufl_cell(), 2) - P1 = ufl.FiniteElement("Lagrange", self.geometry.mesh.ufl_cell(), 1) - - self.state_space = dolfinx.fem.FunctionSpace(self.geometry.mesh, P2 * P1) - self.state = dolfinx.fem.Function(self.state_space) - self.test_state = ufl.TestFunction(self.state_space) - - def _init_form(self) -> None: - u, p = ufl.split(self.state) - v, _ = ufl.split(self.test_state) - - self.model.compressibility.register(p) - - F = kinematics.DeformationGradient(u) - psi = self.model.strain_energy(F, p) - self._virtual_work = ufl.derivative( - psi * self.geometry.dx, - coefficient=self.state, - argument=self.test_state, - ) - external_work = self._external_work(u, v) - if external_work is not None: - self._virtual_work += external_work - - self._set_dirichlet_bc() - def _init_solver(self) -> None: self._problem = dolfinx.fem.petsc.NonlinearProblem( - self._virtual_work, + self.virtual_work, self.state, - self._dirichlet_bc, + self.dirichlet_bc, ) self._solver = dolfinx.nls.petsc.NewtonSolver( self.geometry.mesh.comm, @@ -114,3 +99,43 @@ def _set_dirichlet_bc(self) -> None: def solve(self): return self._solver.solve(self.state) + + +@dataclass(slots=True) +class MechanicsProblem(BaseMechanicsProblem): + def _init_space(self) -> None: + P2 = basix.ufl.element( + family="Lagrange", + cell=self.geometry.mesh.ufl_cell().cellname(), + degree=2, + shape=(self.geometry.mesh.ufl_cell().topological_dimension(),), + ) + P1 = basix.ufl.element( + family="Lagrange", + cell=self.geometry.mesh.ufl_cell().cellname(), + degree=1, + ) + element = basix.ufl.mixed_element([P2, P1]) + + self.state_space = dolfinx.fem.functionspace(self.geometry.mesh, element) + self.state = dolfinx.fem.Function(self.state_space) + self.test_state = ufl.TestFunction(self.state_space) + + def _init_form(self) -> None: + u, p = ufl.split(self.state) + v, _ = ufl.split(self.test_state) + + self.model.compressibility.register(p) + + F = kinematics.DeformationGradient(u) + psi = self.model.strain_energy(F, p) + self.virtual_work = ufl.derivative( + psi * self.geometry.dx, + coefficient=self.state, + argument=self.test_state, + ) + external_work = self._external_work(u, v) + if external_work is not None: + self.virtual_work += external_work + + self._set_dirichlet_bc() diff --git a/tests/conftest.py b/tests/conftest.py index 915a810..6af89b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,12 +10,12 @@ def mesh(): @pytest.fixture(scope="session") def P1(mesh): - return dolfinx.fem.FunctionSpace(mesh, ("Lagrange", 1)) + return dolfinx.fem.functionspace(mesh, ("Lagrange", 1)) @pytest.fixture(scope="session") def P2(mesh): - return dolfinx.fem.VectorFunctionSpace(mesh, ("Lagrange", 2)) + return dolfinx.fem.functionspace(mesh, ("Lagrange", 2, (mesh.geometry.dim,))) @pytest.fixture diff --git a/tests/test_mechanicsproblem.py b/tests/test_mechanicsproblem.py index a668b17..938d0ea 100644 --- a/tests/test_mechanicsproblem.py +++ b/tests/test_mechanicsproblem.py @@ -37,6 +37,7 @@ def dirichlet_bc( ) -> list[dolfinx.fem.bcs.DirichletBC]: V, _ = state_space.sub(0).collapse() facets = geo.facet_tags.find(1) + mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim) dofs = dolfinx.fem.locate_dofs_topological((state_space.sub(0), V), 2, facets) u_fixed = dolfinx.fem.Function(V) u_fixed.x.array[:] = 0.0 From c3cfbd23d5a61c4616ee15d8d4e99810d907d4c7 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Sun, 25 Feb 2024 09:45:46 +0100 Subject: [PATCH 2/5] Use pyvista for visualizing in docs --- .cspell_dict.txt | 5 + .github/workflows/build_docs.yml | 13 +- .github/workflows/pypi.yml | 6 +- _config.yml | 8 +- demo/unit_cube.ipynb | 364 ------------------------------- pyproject.toml | 3 +- 6 files changed, 28 insertions(+), 371 deletions(-) delete mode 100644 demo/unit_cube.ipynb diff --git a/.cspell_dict.txt b/.cspell_dict.txt index 6c99e06..7c269d4 100644 --- a/.cspell_dict.txt +++ b/.cspell_dict.txt @@ -39,3 +39,8 @@ subplus varepsilon Venant XDMF +PYVISTA +TRAME +libgl +libxrender +xvfb diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 5983e43..6f2087c 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -2,10 +2,10 @@ name: Deploy static content to Pages on: - # Runs on pushes targeting the default branch + pull_request: push: - branches: - - "**" + branches: [main] + # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -29,11 +29,18 @@ jobs: env: DEB_PYTHON_INSTALL_LAYOUT: deb_system PUBLISH_DIR: ./_build/html + PYVISTA_TRAME_SERVER_PROXY_PREFIX: "/proxy/" + PYVISTA_TRAME_SERVER_PROXY_ENABLED: "True" + PYVISTA_OFF_SCREEN: false + PYVISTA_JUPYTER_BACKEND: "html" steps: - name: Checkout uses: actions/checkout@v3 + - name: Install dependencies for pyvista + run: apt-get update && apt-get install -y libgl1-mesa-glx libxrender1 xvfb + - name: Install dependencies run: python3 -m pip install ".[docs]" diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 0e93190..b4d149f 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,6 +1,10 @@ name: Release -on: [push] +on: + pull_request: + push: + branches: [main] + jobs: dist: diff --git a/_config.yml b/_config.yml index 447e275..6bb09f8 100644 --- a/_config.yml +++ b/_config.yml @@ -30,6 +30,12 @@ parse: sphinx: config: + html_last_updated_fmt: "%b %d, %Y" + nb_custom_formats: # https://jupyterbook.org/en/stable/file-types/jupytext.html#file-types-custom + .py: + - jupytext.reads + - fmt: py + suppress_warnings: ["mystnb.unknown_mime_type"] html_js_files: - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js @@ -38,4 +44,4 @@ sphinx: - 'sphinx.ext.napoleon' - 'sphinx.ext.viewcode' -exclude_patterns: [".pytest_cache/*", ".github/*"] +exclude_patterns: [".pytest_cache/*", ".github/*", ".tox/*"] diff --git a/demo/unit_cube.ipynb b/demo/unit_cube.ipynb deleted file mode 100644 index f2f917a..0000000 --- a/demo/unit_cube.ipynb +++ /dev/null @@ -1,364 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "f0905235", - "metadata": {}, - "source": [ - "# Unit Cube\n", - "\n", - "In this demo we will use `fenicsx_pulse` to solve a simple contracting cube with one fixed side and with the opposite side having a traction force.\n", - "\n", - "First let us do the necessary imports" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "669b7d12", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [], - "source": [ - "import dolfinx\n", - "import numpy as np\n", - "import fenicsx_pulse\n", - "from mpi4py import MPI\n", - "from petsc4py import PETSc" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "b2923a14", - "metadata": {}, - "source": [ - "Then we can create unit cube mesh" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "84e77495", - "metadata": {}, - "outputs": [], - "source": [ - "mesh = dolfinx.mesh.create_unit_cube(MPI.COMM_WORLD, 3, 3, 3)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "85ec6a83", - "metadata": {}, - "source": [ - "Next let up specify a list of boundary markers where we will set the different boundary conditions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22806878", - "metadata": {}, - "outputs": [], - "source": [ - "boundaries = [\n", - " fenicsx_pulse.Marker(marker=1, dim=2, locator=lambda x: np.isclose(x[0], 0)),\n", - " fenicsx_pulse.Marker(marker=2, dim=2, locator=lambda x: np.isclose(x[0], 1)),\n", - "]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "673b51e3", - "metadata": {}, - "source": [ - "Now collect the boundaries and mesh in to a geometry object\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a9683b9", - "metadata": {}, - "outputs": [], - "source": [ - "geo = fenicsx_pulse.Geometry(\n", - " mesh=mesh,\n", - " boundaries=boundaries,\n", - " metadata={\"quadrature_degree\": 4},\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "a8ff97d9", - "metadata": {}, - "source": [ - "We would also need to to create a passive material model. Here we will used the Holzapfel and Ogden material model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce4147cd", - "metadata": {}, - "outputs": [], - "source": [ - "material_params = fenicsx_pulse.HolzapfelOgden.transversely_isotropic_parameters()\n", - "f0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((1.0, 0.0, 0.0)))\n", - "s0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((0.0, 1.0, 0.0)))\n", - "material = fenicsx_pulse.HolzapfelOgden(f0=f0, s0=s0, **material_params)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f144a998", - "metadata": {}, - "source": [ - "We also need to create a model for the active contraction. Here we use an active stress model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "263cd5c5", - "metadata": {}, - "outputs": [], - "source": [ - "Ta = dolfinx.fem.Constant(mesh, PETSc.ScalarType(0.0))\n", - "active_model = fenicsx_pulse.ActiveStress(f0, activation=Ta)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "a3a062f9", - "metadata": {}, - "source": [ - "We also need to specify whether the model what type of compressibility we want for our model. Here we use a full incompressible model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9b61f308", - "metadata": {}, - "outputs": [], - "source": [ - "comp_model = fenicsx_pulse.Incompressible()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35761872", - "metadata": {}, - "outputs": [], - "source": [ - "# Finally we collect all the models into a cardiac model\n", - "model = fenicsx_pulse.CardiacModel(\n", - " material=material,\n", - " active=active_model,\n", - " compressibility=comp_model,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "4d9abc61", - "metadata": {}, - "source": [ - "Now we need to specify the different boundary conditions. \n", - "\n", - "We can specify the dirichlet boundary conditions using a function that takes the state space as input and return a list of dirichlet boundary conditions. Since we are using the an incompressible formulation the state space have two subspaces where the first subspace represents the displacement. Here we set the displacement to zero on the boundary with marker 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ebf4e957", - "metadata": {}, - "outputs": [], - "source": [ - "def dirichlet_bc(\n", - " state_space: dolfinx.fem.FunctionSpace,\n", - ") -> list[dolfinx.fem.bcs.DirichletBC]:\n", - " V, _ = state_space.sub(0).collapse()\n", - " facets = geo.facet_tags.find(1) # Specify the marker used on the boundary\n", - " mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)\n", - " dofs = dolfinx.fem.locate_dofs_topological((state_space.sub(0), V), 2, facets)\n", - " u_fixed = dolfinx.fem.Function(V)\n", - " u_fixed.x.array[:] = 0.0\n", - " return [dolfinx.fem.dirichletbc(u_fixed, dofs, state_space.sub(0))]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "548fb825", - "metadata": {}, - "source": [ - "We als set a traction on the opposite boundary" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a542c020", - "metadata": {}, - "outputs": [], - "source": [ - "traction = dolfinx.fem.Constant(mesh, PETSc.ScalarType(-1.0))\n", - "neumann = fenicsx_pulse.NeumannBC(traction=traction, marker=2)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "df2bb4c9", - "metadata": {}, - "source": [ - "Finally we collect all the boundary conditions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b04f745a", - "metadata": {}, - "outputs": [], - "source": [ - "bcs = fenicsx_pulse.BoundaryConditions(dirichlet=(dirichlet_bc,), neumann=(neumann,))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "75866130", - "metadata": {}, - "source": [ - "and create a mechanics problem" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ea7493c", - "metadata": {}, - "outputs": [], - "source": [ - "problem = fenicsx_pulse.MechanicsProblem(model=model, geometry=geo, bcs=bcs)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "eae42ef5", - "metadata": {}, - "source": [ - "We also set a value for the active stress" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ecbe39db", - "metadata": {}, - "outputs": [], - "source": [ - "Ta.value = 2.0" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "918218d7", - "metadata": {}, - "source": [ - "And solve the problem" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "873c4b2b", - "metadata": {}, - "outputs": [], - "source": [ - "problem.solve()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "8ac1918e", - "metadata": {}, - "source": [ - "We can get the solution (displacement)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1559fdd9", - "metadata": {}, - "outputs": [], - "source": [ - "u = problem.state.sub(0).collapse()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "22a49f57", - "metadata": {}, - "source": [ - "and save it to XDMF for visualization in Paraview" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce9e17bf", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07f36d36", - "metadata": {}, - "outputs": [], - "source": [ - "from fenicsx_plotly import plot\n", - "plot(u, component=\"magnitude\")" - ] - } - ], - "metadata": { - "jupytext": { - "cell_metadata_filter": "-all", - "main_language": "python", - "notebook_metadata_filter": "-all" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.9.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pyproject.toml b/pyproject.toml index f0739e7..8a7acbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,7 @@ dev = [ docs = [ "jupyter-book", "jupytext", - "ipython!=8.7.0", - "fenicsx-plotly" + "pyvista" ] all = [ From 4c7706000245f10b98441d1a2de50fa164b6899e Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Sun, 25 Feb 2024 10:00:43 +0100 Subject: [PATCH 3/5] Add missing demo --- demo/unit_cube.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 demo/unit_cube.py diff --git a/demo/unit_cube.py b/demo/unit_cube.py new file mode 100644 index 0000000..ed2649d --- /dev/null +++ b/demo/unit_cube.py @@ -0,0 +1,130 @@ +# # Unit Cube +# +# In this demo we will use `fenicsx_pulse` to solve a simple contracting cube with one fixed +# side and with the opposite side having a traction force. +# +# First let us do the necessary imports + +import dolfinx +import numpy as np +import fenicsx_pulse +from mpi4py import MPI +from petsc4py import PETSc + + +# Then we can create unit cube mesh + +mesh = dolfinx.mesh.create_unit_cube(MPI.COMM_WORLD, 3, 3, 3) + +# Next let up specify a list of boundary markers where we will set the different boundary conditions + +boundaries = [ + fenicsx_pulse.Marker(marker=1, dim=2, locator=lambda x: np.isclose(x[0], 0)), + fenicsx_pulse.Marker(marker=2, dim=2, locator=lambda x: np.isclose(x[0], 1)), +] + +# Now collect the boundaries and mesh in to a geometry object +# + +geo = fenicsx_pulse.Geometry( + mesh=mesh, + boundaries=boundaries, + metadata={"quadrature_degree": 4}, +) + +# We would also need to to create a passive material model. +# Here we will used the Holzapfel and Ogden material model + +material_params = fenicsx_pulse.HolzapfelOgden.transversely_isotropic_parameters() +f0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((1.0, 0.0, 0.0))) +s0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((0.0, 1.0, 0.0))) +material = fenicsx_pulse.HolzapfelOgden(f0=f0, s0=s0, **material_params) + +# We also need to create a model for the active contraction. Here we use an active stress model + +Ta = dolfinx.fem.Constant(mesh, PETSc.ScalarType(0.0)) +active_model = fenicsx_pulse.ActiveStress(f0, activation=Ta) + +# We also need to specify whether the model what type of compressibility we want for our model. +# Here we use a full incompressible model + +comp_model = fenicsx_pulse.Incompressible() + +# Finally we collect all the models into a cardiac model +model = fenicsx_pulse.CardiacModel( + material=material, + active=active_model, + compressibility=comp_model, +) + + +# Now we need to specify the different boundary conditions. +# +# We can specify the dirichlet boundary conditions using a function that takes the state +# space as input and return a list of dirichlet boundary conditions. Since we are using +# the an incompressible formulation the state space have two subspaces where the first +# subspace represents the displacement. Here we set the displacement to zero on the +# boundary with marker 1 + + +def dirichlet_bc( + state_space: dolfinx.fem.FunctionSpace, +) -> list[dolfinx.fem.bcs.DirichletBC]: + V, _ = state_space.sub(0).collapse() + facets = geo.facet_tags.find(1) # Specify the marker used on the boundary + mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim) + dofs = dolfinx.fem.locate_dofs_topological((state_space.sub(0), V), 2, facets) + u_fixed = dolfinx.fem.Function(V) + u_fixed.x.array[:] = 0.0 + return [dolfinx.fem.dirichletbc(u_fixed, dofs, state_space.sub(0))] + + +# We als set a traction on the opposite boundary + +traction = dolfinx.fem.Constant(mesh, PETSc.ScalarType(-1.0)) +neumann = fenicsx_pulse.NeumannBC(traction=traction, marker=2) + +# Finally we collect all the boundary conditions + +bcs = fenicsx_pulse.BoundaryConditions(dirichlet=(dirichlet_bc,), neumann=(neumann,)) + +# and create a mechanics problem + +problem = fenicsx_pulse.MechanicsProblem(model=model, geometry=geo, bcs=bcs) + +# We also set a value for the active stress + +Ta.value = 2.0 + +# And solve the problem + +problem.solve() + +# We can get the solution (displacement) + +u = problem.state.sub(0).collapse() + +# and visualize it using pyvista + +import pyvista + +pyvista.start_xvfb() + +# Create plotter and pyvista grid +p = pyvista.Plotter() + +topology, cell_types, geometry = dolfinx.plot.vtk_mesh( + problem.state_space.sub(0).collapse()[0], +) +grid = pyvista.UnstructuredGrid(topology, cell_types, geometry) + +# Attach vector values to grid and warp grid by vectora +grid["u"] = u.x.array.reshape((geometry.shape[0], 3)) +actor_0 = p.add_mesh(grid, style="wireframe", color="k") +warped = grid.warp_by_vector("u", factor=1.5) +actor_1 = p.add_mesh(warped, show_edges=True) +p.show_axes() +if not pyvista.OFF_SCREEN: + p.show() +else: + figure_as_array = p.screenshot("displacement.png") From 0f16db772294a348f96e59708bbfa0d005be1a1d Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Sun, 25 Feb 2024 10:26:44 +0100 Subject: [PATCH 4/5] Update actions --- .github/workflows/build_docs.yml | 10 +++++----- .github/workflows/pre-commit.yml | 8 ++++++-- .github/workflows/pypi.yml | 6 +++--- .github/workflows/test_package_coverage.yml | 6 +++--- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 6f2087c..31da26b 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -36,7 +36,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies for pyvista run: apt-get update && apt-get install -y libgl1-mesa-glx libxrender1 xvfb @@ -48,7 +48,7 @@ jobs: run: jupyter book build -W . - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: ${{ env.PUBLISH_DIR }} @@ -64,12 +64,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v4 - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9ff6765..bcb5775 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,6 +1,10 @@ name: Pre-commit -on: [push] +on: + pull_request: + push: + branches: [main] + jobs: @@ -8,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: # This action sets the current path to the root of your github repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pre-commit run: python3 -m pip install pre-commit diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index b4d149f..a6a0483 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -10,12 +10,12 @@ jobs: dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build SDist and wheel run: pipx run build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: dist/* @@ -31,7 +31,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: artifact path: dist diff --git a/.github/workflows/test_package_coverage.yml b/.github/workflows/test_package_coverage.yml index cba9788..94023ff 100644 --- a/.github/workflows/test_package_coverage.yml +++ b/.github/workflows/test_package_coverage.yml @@ -12,7 +12,7 @@ jobs: steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install package run: python3 -m pip install .[test] @@ -28,14 +28,14 @@ jobs: echo "total=$TOTAL" >> $GITHUB_ENV - name: Upload HTML report. - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: html-report path: htmlcov - name: Create coverage Badge if: github.ref == 'refs/heads/main' - uses: schneegans/dynamic-badges-action@v1.6.0 + uses: schneegans/dynamic-badges-action@v1.7.0 with: auth: ${{ secrets.GIST_SECRET }} gistID: a7290de789564f03eb6b1ee122fce423 From 57171b82c85ec6668fa123533009ddfea0c70f03 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Sun, 25 Feb 2024 10:32:21 +0100 Subject: [PATCH 5/5] Ignore typing error --- demo/unit_cube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/unit_cube.py b/demo/unit_cube.py index ed2649d..a53f4bd 100644 --- a/demo/unit_cube.py +++ b/demo/unit_cube.py @@ -38,7 +38,7 @@ material_params = fenicsx_pulse.HolzapfelOgden.transversely_isotropic_parameters() f0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((1.0, 0.0, 0.0))) s0 = dolfinx.fem.Constant(mesh, PETSc.ScalarType((0.0, 1.0, 0.0))) -material = fenicsx_pulse.HolzapfelOgden(f0=f0, s0=s0, **material_params) +material = fenicsx_pulse.HolzapfelOgden(f0=f0, s0=s0, **material_params) # type: ignore # We also need to create a model for the active contraction. Here we use an active stress model