Date: Wed, 11 Sep 2024 17:27:21 +0200
Subject: [PATCH 010/125] Fix unicode symbols on Windows
---
README.md | 2 +-
docs/index.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 5e320f7e..a4af59fc 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://felupe.readthedocs.io) [](https://pypi.python.org/pypi/felupe/) [](https://felupe.readthedocs.io/en/latest/?badge=latest) [](https://www.gnu.org/licenses/gpl-3.0) [](https://codecov.io/gh/adtzlr/felupe) [](https://zenodo.org/badge/latestdoi/360657894)   [](https://adtzlr.github.io/felupe-web/lab?path=01_Getting-Started.ipynb)
-FElupe is a Python 3.8+ 🐍 finite element analysis package 📦 focusing on the formulation and numerical solution of nonlinear problems in continuum mechanics 🔧 of solid bodies 🪨. This package is intended for scientific research 💻, but is also suitable for running nonlinear simulations 🚂 in general 🏎️. In addition to the transformation of general weak forms into sparse vectors and matrices, FElupe provides an efficient high-level abstraction layer for the simulation of the deformation of solid bodies.
+FElupe is a Python 3.8+ 🐍 finite element analysis package 📦 focusing on the formulation and numerical solution of nonlinear problems in continuum mechanics of solid bodies 🔧. This package is intended for scientific research 💻, but is also suitable for running nonlinear simulations 🚂 in general 🏎️. In addition to the transformation of general weak forms into sparse vectors and matrices, FElupe provides an efficient high-level abstraction layer for the simulation of the deformation of solid bodies.
Date: Wed, 11 Sep 2024 17:27:23 +0200
Subject: [PATCH 011/125] Update ex20_third-medium-contact.py
---
examples/ex20_third-medium-contact.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index dde874b7..94829f37 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -74,13 +74,13 @@
# :label: huhu-regularization
#
# \Pi &= \frac{1}{2} \int_V
-# \mathbb{H} \boldsymbol{u} \vdots \mathbb{H} \boldsymbol{u} \ dV
+# \mathbb{H}(\boldsymbol{u})~\vdots~\mathbb{H}(\boldsymbol{u})~dV
#
# \delta \Pi &= \int_V
-# \mathbb{H} \delta \boldsymbol{u} \vdots \mathbb{H} \boldsymbol{u} \ dV
+# \mathbb{H}(\delta \boldsymbol{u})~\vdots~\mathbb{H}(\boldsymbol{u})~dV
#
# \Delta \delta \Pi &= \int_V
-# \mathbb{H} \delta \boldsymbol{u} \vdots \mathbb{H} \Delta \boldsymbol{u} \ dV
+# \mathbb{H}(\delta \boldsymbol{u})~\vdots~\mathbb{H}(\Delta \boldsymbol{u})~dV
#
from felupe.math import dddot, hess
From b052184387b78918f1483b2a9405d4482213a1be Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Wed, 11 Sep 2024 17:29:29 +0200
Subject: [PATCH 012/125] Update ex20_third-medium-contact.py
---
examples/ex20_third-medium-contact.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index 94829f37..e0329435 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -68,7 +68,7 @@
# %%
# The so-called HuHu-regularization is created by two weak-:func:`form `
-# expressions, see Eq. :eq:`huhu-regularization` [3_].
+# expressions, see Eq. :eq:`huhu-regularization` [3]_.
#
# .. math::
# :label: huhu-regularization
From 965d3d19039640c6dd5dc40c6978c275d7cb1770 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Wed, 11 Sep 2024 17:30:03 +0200
Subject: [PATCH 013/125] Update ex20_third-medium-contact.py
---
examples/ex20_third-medium-contact.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index e0329435..fe291e87 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -139,7 +139,7 @@ def record(stepnumber, substepnumber, substep, plotter):
#
# .. [3] A. H. Frederiksen, O. Sigmund, and K. Poulios, "Topology optimization of self-
# contacting structures", Computational Mechanics, vol. 73, no. 4. Springer
-# Science and Business Media LLC, pp. 967–981, Oct. 07, 2023. doi: |DOI-3|.
+# Science and Business Media LLC, pp. 967–981, Oct. 07, 2023. |DOI-3|.
#
# .. |DOI-2| image:: https://zenodo.org/badge/DOI/10.1007/s00466-021-01974-x.svg
# :target: https://www.doi.org/10.1007/s00466-021-01974-x
From 2bcbdcc8519d0e0155f6c82ae2d07079ce840813 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Thu, 12 Sep 2024 11:39:36 +0200
Subject: [PATCH 014/125] Examples: reduce the number of frames in the GIF
files
to speed-up the build time for read-the-docs
---
examples/ex17_torsion-gif.py | 19 ++++++++++---------
examples/ex20_third-medium-contact.py | 21 ++++++++++++++-------
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/examples/ex17_torsion-gif.py b/examples/ex17_torsion-gif.py
index a137d492..76b42061 100644
--- a/examples/ex17_torsion-gif.py
+++ b/examples/ex17_torsion-gif.py
@@ -59,18 +59,19 @@
plotter = field.plot(
"Principal Values of Logarithmic Strain", clim=[0, 0.2], off_screen=True
)
-plotter.open_gif("result.gif", fps=10)
+plotter.open_gif("result.gif", fps=5)
def record(stepnumber, substepnumber, substep, plotter):
- # update the mesh-points and the scalars of the plotter
- name = plotter.mesh.active_scalars_info.name
- data = substep.x.evaluate.log_strain(tensor=False).mean(-2)[-1]
-
- plotter.mesh.points[:] = mesh.points + field[0].values
- plotter.mesh[name] = data
- # plotter.update_scalar_bar_range(clim=[min(data), max(data)])
- plotter.write_frame()
+ "Update the mesh-points and the scalars of the plotter."
+ if substepnumber in np.arange(len(move), step=2):
+ name = plotter.mesh.active_scalars_info.name
+ data = substep.x.evaluate.log_strain(tensor=False).mean(-2)[-1]
+
+ plotter.mesh.points[:] = mesh.points + field[0].values
+ plotter.mesh[name] = data
+
+ plotter.write_frame()
# evaluate the reaction moment at the centerpoint of the right end face
forces = substep.fun
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index fe291e87..e2156457 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -15,6 +15,8 @@
body. All sub meshes are merged by stacking the meshes of the
:class:`mesh container ` into a :class:`mesh `.
"""
+import numpy as np
+
import felupe as fem
t = 0.1
@@ -86,17 +88,21 @@
@fem.Form(v=fields[1], u=fields[1], kwargs={"kr": None})
-def Δδ_ψ():
+def bilinearform():
return [lambda v, u, kr: kr * dddot(hess(u), hess(v))]
@fem.Form(v=fields[1], kwargs={"kr": 1.0})
-def δ_ψ():
+def linearform():
u = fields[1][0]
return [lambda v, kr: kr * dddot(hess(v), hess(u)[:2, :2, :2])]
-regularization = fem.FormItem(Δδ_ψ, δ_ψ, kwargs=dict(kr=K * L**2 * gamma))
+regularization = fem.FormItem(
+ bilinearform=bilinearform,
+ linearform=linearform,
+ kwargs={"kr": K * L**2 * gamma},
+)
# %%
# The prescribed displacement is ramped up to the maximum value and released until zero.
@@ -112,14 +118,15 @@ def δ_ψ():
# ``x0``-argument for :func:`Newton's method `, which is called on
# evaluation. After all frames are recorded, it is important to ``close()`` the plotter.
plotter = container.plot(colors=["grey", "white"], line_width=3, off_screen=True)
-plotter.open_gif("third-medium-contact.gif", fps=30)
+plotter.open_gif("third-medium-contact.gif", fps=5)
def record(stepnumber, substepnumber, substep, plotter):
"Update the mesh of the plotter and write a frame."
- for m in plotter.meshes:
- m.points[:, :2] = region.mesh.points + field[0].values
- plotter.write_frame()
+ if substepnumber in np.arange(len(move), step=5):
+ for m in plotter.meshes:
+ m.points[:, :2] = region.mesh.points + field[0].values
+ plotter.write_frame()
job = fem.Job([step], callback=record, plotter=plotter)
From d8fb6cad9bb046377c632f9ea8e946cb5d7a14ec Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Thu, 12 Sep 2024 13:07:08 +0200
Subject: [PATCH 015/125] Update ex20_third-medium-contact.py
---
examples/ex20_third-medium-contact.py | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index e2156457..f0c8232b 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -69,21 +69,16 @@
)
# %%
-# The so-called HuHu-regularization is created by two weak-:func:`form `
-# expressions, see Eq. :eq:`huhu-regularization` [3]_.
+# The so-called HuHu-regularization is created by two weak-:func:`forms `,
+# which are derived from the regularization potential, see Eq.
+# :eq:`huhu-regularization` [3]_.
#
# .. math::
# :label: huhu-regularization
#
-# \Pi &= \frac{1}{2} \int_V
+# \Pi = \frac{1}{2} \int_V
# \mathbb{H}(\boldsymbol{u})~\vdots~\mathbb{H}(\boldsymbol{u})~dV
#
-# \delta \Pi &= \int_V
-# \mathbb{H}(\delta \boldsymbol{u})~\vdots~\mathbb{H}(\boldsymbol{u})~dV
-#
-# \Delta \delta \Pi &= \int_V
-# \mathbb{H}(\delta \boldsymbol{u})~\vdots~\mathbb{H}(\Delta \boldsymbol{u})~dV
-#
from felupe.math import dddot, hess
From 9cdbe2d97b4adaca8b986cc260a9784621254adb Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Thu, 12 Sep 2024 15:09:37 +0200
Subject: [PATCH 016/125] Update ex20_third-medium-contact.py
---
examples/ex20_third-medium-contact.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/ex20_third-medium-contact.py b/examples/ex20_third-medium-contact.py
index f0c8232b..af43ad3d 100644
--- a/examples/ex20_third-medium-contact.py
+++ b/examples/ex20_third-medium-contact.py
@@ -82,7 +82,7 @@
from felupe.math import dddot, hess
-@fem.Form(v=fields[1], u=fields[1], kwargs={"kr": None})
+@fem.Form(v=fields[1], u=fields[1], kwargs={"kr": 1.0})
def bilinearform():
return [lambda v, u, kr: kr * dddot(hess(u), hess(v))]
From 727dbd037aff800f6f804b02c06b68664571e456 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 14:16:43 +0200
Subject: [PATCH 017/125] Update _scene.py
---
src/felupe/view/_scene.py | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/felupe/view/_scene.py b/src/felupe/view/_scene.py
index 24e9d06d..50a300ba 100644
--- a/src/felupe/view/_scene.py
+++ b/src/felupe/view/_scene.py
@@ -20,7 +20,7 @@
class Scene:
- """Base class for plotting a static scene.
+ r"""Base class for plotting a static scene.
Attributes
----------
@@ -28,6 +28,28 @@ class Scene:
A generalized Dataset with the mesh as well as point- and cell-data. This is
not an instance of :class:`felupe.Mesh`.
+ Examples
+ --------
+ .. pyvista-plot::
+ :force_static:
+
+ >>> import numpy as np
+ >>> import felupe as fem
+ >>>
+ >>> scene = fem.view.Scene()
+ >>> scene.mesh = fem.Cube(n=3).as_unstructured_grid()
+ >>> scene.mesh.point_data["Displacement"] = np.arange(81).reshape(27, 3) / 300
+ >>> scene.mesh.set_active_scalars(None)
+ >>>
+ >>> scene.plot("Displacement", component=None).show()
+
+ See Also
+ --------
+ felupe.view.ViewMesh : Provide Visualization methods for a mesh with optional given
+ dicts of point- and cell-data items.
+ felupe.view.ViewField : Provide Visualization methods for a field container.
+ felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ solid body.
"""
def plot(
From 9317a4c38e8884ba53671e0996286e55efce68e8 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 14:23:57 +0200
Subject: [PATCH 018/125] Update _field.py
---
src/felupe/view/_field.py | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/felupe/view/_field.py b/src/felupe/view/_field.py
index ec7e5fd5..d05f3f2e 100644
--- a/src/felupe/view/_field.py
+++ b/src/felupe/view/_field.py
@@ -21,7 +21,7 @@
class ViewField(ViewMesh):
- """Provide Visualization methods for :class:`felupe.FieldContainer`. The warped
+ r"""Provide Visualization methods for :class:`felupe.FieldContainer`. The warped
(deformed) mesh is created from the values of the first field (displacements). By
default, the "Deformation Gradient" tensor, the "Logarithmic Strain" tensor and the
"Principal Values of Logarithmic Strain" are evaluated as field-related items of the
@@ -49,10 +49,30 @@ class ViewField(ViewMesh):
A generalized Dataset with the mesh as well as point- and cell-data. This is
not an instance of :class:`felupe.Mesh`.
+ Examples
+ --------
+ .. pyvista-plot::
+ :force_static:
+
+ >>> import numpy as np
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=3)
+ >>> region = fem.RegionHexahedron(mesh)
+ >>> u = np.sqrt(1 + np.arange(81)).reshape(27, 3) / 100
+ >>> field = fem.FieldContainer([fem.Field(region, values=u)])
+ >>>
+ >>> view = fem.view.ViewField(field, project=fem.project)
+ >>> view.plot("Principal Values of Logarithmic Strain").show()
+
See Also
--------
+ felupe.view.Scene : Base class for plotting a static scene.
+ felupe.view.ViewMesh : Provide Visualization methods for a mesh with optional given
+ dicts of point- and cell-data items.
+ felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ solid body.
felupe.project: Project given values at quadrature-points to mesh-points.
-
"""
def __init__(
From 92b73ec6fb055929baca9853dcd71481e0ad6286 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 14:23:59 +0200
Subject: [PATCH 019/125] Update _mesh.py
---
src/felupe/view/_mesh.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/felupe/view/_mesh.py b/src/felupe/view/_mesh.py
index 5c5c1eac..a54d61d8 100644
--- a/src/felupe/view/_mesh.py
+++ b/src/felupe/view/_mesh.py
@@ -20,7 +20,7 @@
class ViewMesh(Scene):
- """Provide Visualization methods for :class:`felupe.Mesh` with optional given
+ r"""Provide Visualization methods for :class:`felupe.Mesh` with optional given
dicts of point- and cell-data items.
Parameters
@@ -40,6 +40,27 @@ class ViewMesh(Scene):
A generalized Dataset with the mesh as well as point- and cell-data. This is
not an instance of :class:`felupe.Mesh`.
+ Examples
+ --------
+ .. pyvista-plot::
+ :force_static:
+
+ >>> import numpy as np
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=3)
+ >>> displacement = np.arange(81).reshape(27, 3) / 300
+ >>> view = fem.view.ViewMesh(mesh, point_data={"Displacement": displacement})
+ >>>
+ >>> view.plot("Displacement", component=None).show()
+
+ See Also
+ --------
+ felupe.view.Scene : Base class for plotting a static scene.
+ felupe.view.ViewField : Provide Visualization methods for a field container.
+ felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ solid body.
+
"""
def __init__(self, mesh, point_data=None, cell_data=None, cell_type=None):
From ff9ea3e3a0b641c763e510ea46691fd86397ba59 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 18:06:22 +0200
Subject: [PATCH 020/125] enhance docstrings of view-classes
---
src/felupe/view/_field.py | 6 +++---
src/felupe/view/_mesh.py | 6 +++---
src/felupe/view/_scene.py | 6 +++---
src/felupe/view/_solid.py | 23 ++++++++++++++++++++++-
4 files changed, 31 insertions(+), 10 deletions(-)
diff --git a/src/felupe/view/_field.py b/src/felupe/view/_field.py
index d05f3f2e..dbc3575d 100644
--- a/src/felupe/view/_field.py
+++ b/src/felupe/view/_field.py
@@ -62,15 +62,15 @@ class ViewField(ViewMesh):
>>> u = np.sqrt(1 + np.arange(81)).reshape(27, 3) / 100
>>> field = fem.FieldContainer([fem.Field(region, values=u)])
>>>
- >>> view = fem.view.ViewField(field, project=fem.project)
+ >>> view = fem.ViewField(field, project=fem.project)
>>> view.plot("Principal Values of Logarithmic Strain").show()
See Also
--------
felupe.view.Scene : Base class for plotting a static scene.
- felupe.view.ViewMesh : Provide Visualization methods for a mesh with optional given
+ felupe.ViewMesh : Provide Visualization methods for a mesh with optional given
dicts of point- and cell-data items.
- felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ felupe.ViewSolid : Provide Visualization methods for a field container or a
solid body.
felupe.project: Project given values at quadrature-points to mesh-points.
"""
diff --git a/src/felupe/view/_mesh.py b/src/felupe/view/_mesh.py
index a54d61d8..4f179542 100644
--- a/src/felupe/view/_mesh.py
+++ b/src/felupe/view/_mesh.py
@@ -50,15 +50,15 @@ class ViewMesh(Scene):
>>>
>>> mesh = fem.Cube(n=3)
>>> displacement = np.arange(81).reshape(27, 3) / 300
- >>> view = fem.view.ViewMesh(mesh, point_data={"Displacement": displacement})
+ >>> view = fem.ViewMesh(mesh, point_data={"Displacement": displacement})
>>>
>>> view.plot("Displacement", component=None).show()
See Also
--------
felupe.view.Scene : Base class for plotting a static scene.
- felupe.view.ViewField : Provide Visualization methods for a field container.
- felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ felupe.ViewField : Provide Visualization methods for a field container.
+ felupe.ViewSolid : Provide Visualization methods for a field container or a
solid body.
"""
diff --git a/src/felupe/view/_scene.py b/src/felupe/view/_scene.py
index 50a300ba..8a86028c 100644
--- a/src/felupe/view/_scene.py
+++ b/src/felupe/view/_scene.py
@@ -45,10 +45,10 @@ class Scene:
See Also
--------
- felupe.view.ViewMesh : Provide Visualization methods for a mesh with optional given
+ felupe.ViewMesh : Provide Visualization methods for a mesh with optional given
dicts of point- and cell-data items.
- felupe.view.ViewField : Provide Visualization methods for a field container.
- felupe.view.ViewSolid : Provide Visualization methods for a field container or a
+ felupe.ViewField : Provide Visualization methods for a field container.
+ felupe.ViewSolid : Provide Visualization methods for a field container or a
solid body.
"""
diff --git a/src/felupe/view/_solid.py b/src/felupe/view/_solid.py
index 325220a1..7ecfbbd0 100644
--- a/src/felupe/view/_solid.py
+++ b/src/felupe/view/_solid.py
@@ -35,7 +35,7 @@ class ViewSolid(ViewField):
solid : felupe.SolidBody or felupe.SolidBodyIncompressible or None, optional
A solid body to evaluate the (Cauchy) stress (default is None).
stress_type : str, optional
- The type of stress, either "Cauchy" or "Kirchhoff, which is exported (default is
+ The type of stress which is exported, either "Cauchy" or "Kirchhoff" (default is
"Cauchy").
point_data : dict or None, optional
Additional point-data dict (default is None).
@@ -54,8 +54,29 @@ class ViewSolid(ViewField):
A generalized Dataset with the mesh as well as point- and cell-data. This is
not an instance of :class:`felupe.Mesh`.
+ Examples
+ --------
+ .. pyvista-plot::
+ :force_static:
+
+ >>> import numpy as np
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=3)
+ >>> region = fem.RegionHexahedron(mesh)
+ >>> u = np.sqrt(1 + np.arange(81)).reshape(27, 3) / 100
+ >>> field = fem.FieldContainer([fem.Field(region, values=u)])
+ >>> solid = fem.SolidBody(umat=fem.NeoHooke(mu=1, bulk=2), field=field)
+ >>>
+ >>> view = fem.ViewSolid(field, solid, project=fem.project)
+ >>> view.plot("Principal Values of Cauchy Stress").show()
+
See Also
--------
+ felupe.view.Scene : Base class for plotting a static scene.
+ felupe.ViewMesh : Provide Visualization methods for a mesh with optional given
+ dicts of point- and cell-data items.
+ felupe.ViewField : Provide Visualization methods for a field container.
felupe.project: Project given values at quadrature-points to mesh-points.
"""
From c2fd524d47029bf2ebb42a0befcc7c518cb81b63 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 18:19:01 +0200
Subject: [PATCH 021/125] Add more examples in the docstrings of lower-level
classes
---
src/felupe/field/_base.py | 30 +++++++++-------
src/felupe/field/_container.py | 66 ++++++++++++++++++----------------
src/felupe/field/_dual.py | 26 +++++++++-----
src/felupe/field/_evaluate.py | 21 +++++++++++
4 files changed, 92 insertions(+), 51 deletions(-)
diff --git a/src/felupe/field/_base.py b/src/felupe/field/_base.py
index 39465b04..0a12b9b3 100644
--- a/src/felupe/field/_base.py
+++ b/src/felupe/field/_base.py
@@ -65,14 +65,17 @@ class Field:
Examples
--------
- >>> import felupe as fem
-
- >>> mesh = fem.Cube(n=6)
- >>> region = fem.RegionHexahedron(mesh)
- >>> displacement = fem.Field(region, dim=3)
-
- >>> u = displacement.interpolate()
- >>> dudX = displacement.grad()
+ .. pyvista-plot::
+ :context:
+
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=6)
+ >>> region = fem.RegionHexahedron(mesh)
+ >>> displacement = fem.Field(region, dim=3)
+ >>>
+ >>> u = displacement.interpolate()
+ >>> dudX = displacement.grad()
To obtain deformation-related quantities like the right Cauchy-Green deformation
tensor or the principal stretches, use the math-helpers from FElupe. These
@@ -82,11 +85,14 @@ class Field:
\boldsymbol{C} = \boldsymbol{F}^T \boldsymbol{F}
- >>> from felupe.math import dot, transpose, eigvalsh, sqrt
+ .. pyvista-plot::
+ :context:
- >>> F = displacement.extract(grad=True, add_identity=True)
- >>> C = dot(transpose(F), F)
- >>> λ = sqrt(eigvalsh(C))
+ >>> from felupe.math import dot, transpose, eigvalsh, sqrt
+ >>>
+ >>> F = displacement.extract(grad=True, add_identity=True)
+ >>> C = dot(transpose(F), F)
+ >>> λ = sqrt(eigvalsh(C))
"""
diff --git a/src/felupe/field/_container.py b/src/felupe/field/_container.py
index 8920795b..6de65a9f 100644
--- a/src/felupe/field/_container.py
+++ b/src/felupe/field/_container.py
@@ -30,7 +30,7 @@ class FieldContainer:
Parameters
----------
- fields : list or tuple of Field, FieldAxisymmetric or FieldPlaneStrain
+ fields : list or tuple of :class:`~felupe.Field`, :class:``~felupe.FieldAxisymmetric` or :class:``~felupe.FieldPlaneStrain`
List with fields. The region is linked to the first field.
Attributes
@@ -42,40 +42,46 @@ class FieldContainer:
Examples
--------
- >>> import felupe as fem
- >>>
- >>> mesh = fem.Cube(n=3)
- >>> region = fem.RegionHexahedron(mesh)
- >>> region_dual = fem.RegionConstantHexahedron(mesh.dual(points_per_cell=1))
- >>> displacement = fem.Field(region, dim=3)
- >>> pressure = fem.Field(region_dual)
- >>> field = fem.FieldContainer([displacement, pressure])
- >>> field
-
- Number of fields: 2
- Dimension of fields:
- Field: 3
- Field: 1
+ .. pyvista-plot::
+ :context:
+
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=3)
+ >>> region = fem.RegionHexahedron(mesh)
+ >>> region_dual = fem.RegionConstantHexahedron(mesh.dual(points_per_cell=1))
+ >>> displacement = fem.Field(region, dim=3)
+ >>> pressure = fem.Field(region_dual)
+ >>> field = fem.FieldContainer([displacement, pressure])
+ >>> field
+
+ Number of fields: 2
+ Dimension of fields:
+ Field: 3
+ Field: 1
A new :class:`~felupe.FieldContainer` is also created by one of the logical-and
combinations of a :class:`~felupe.Field`, :class:`~felupe.FieldAxisymmetric`,
:class:`~felupe.FieldPlaneStrain` or :class:`~felupe.FieldContainer`.
- >>> displacement & pressure
-
- Number of fields: 2
- Dimension of fields:
- Field: 3
- Field: 1
-
- >>> volume_ratio = fem.Field(region_dual)
- >>> field & volume_ratio # displacement & pressure & volume_ratio
-
- Number of fields: 3
- Dimension of fields:
- Field: 3
- Field: 1
- Field: 1
+ .. pyvista-plot::
+ :context:
+
+ >>> displacement & pressure
+
+ Number of fields: 2
+ Dimension of fields:
+ Field: 3
+ Field: 1
+
+ >>> volume_ratio = fem.Field(region_dual)
+ >>> field & volume_ratio # displacement & pressure & volume_ratio
+
+ Number of fields: 3
+ Dimension of fields:
+ Field: 3
+ Field: 1
+ Field: 1
See Also
--------
diff --git a/src/felupe/field/_dual.py b/src/felupe/field/_dual.py
index 694800bc..93d5dfd0 100644
--- a/src/felupe/field/_dual.py
+++ b/src/felupe/field/_dual.py
@@ -65,15 +65,23 @@ class FieldDual(Field):
Examples
--------
- >>> import felupe as fem
- >>>
- >>> mesh = fem.Cube(n=6)
- >>> region = fem.RegionHexahedron(mesh)
- >>>
- >>> displacement = fem.Field(region, dim=3)
- >>> pressure = fem.FieldDual(region)
- >>>
- >>> field = fem.FieldContainer([displacement, pressure])
+ .. pyvista-plot::
+
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Cube(n=6)
+ >>> region = fem.RegionHexahedron(mesh)
+ >>>
+ >>> displacement = fem.Field(region, dim=3)
+ >>> pressure = fem.FieldDual(region)
+ >>>
+ >>> field = fem.FieldContainer([displacement, pressure])
+ >>> field
+
+ Number of fields: 2
+ Dimension of fields:
+ Field: 3
+ FieldDual: 1
See Also
--------
diff --git a/src/felupe/field/_evaluate.py b/src/felupe/field/_evaluate.py
index 3843b021..05cc920c 100644
--- a/src/felupe/field/_evaluate.py
+++ b/src/felupe/field/_evaluate.py
@@ -27,6 +27,27 @@ class EvaluateFieldContainer:
----------
field : FieldContainer
A container for fields.
+
+ Examples
+ --------
+ .. pyvista-plot::
+
+ >>> import felupe as fem
+ >>>
+ >>> mesh = fem.Rectangle(n=4)
+ >>> region = fem.RegionQuad(mesh)
+ >>> field = fem.FieldContainer([fem.FieldPlaneStrain(region, dim=2)])
+ >>>
+ >>> evaluate = fem.field.EvaluateFieldContainer(field)
+ >>> F = evaluate.deformation_gradient()
+ >>>
+ >>> F.shape # (3, 3, nquadraturepoints, ncells)
+ (3, 3, 4, 9)
+
+ >>> F[..., 0, 0] # deformation gradient of first cell, first quadrature point
+ array([[1., 0., 0.],
+ [0., 1., 0.],
+ [0., 0., 1.]])
"""
def __init__(self, field):
From 68e205de99075c8e3a1fc15c470ea1f802d710d9 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 18:19:28 +0200
Subject: [PATCH 022/125] Update _evaluate.py
---
src/felupe/field/_evaluate.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/felupe/field/_evaluate.py b/src/felupe/field/_evaluate.py
index 05cc920c..3250e918 100644
--- a/src/felupe/field/_evaluate.py
+++ b/src/felupe/field/_evaluate.py
@@ -20,7 +20,7 @@
class EvaluateFieldContainer:
- """Methods to evaluate the deformation gradient and strain measures of a field
+ r"""Methods to evaluate the deformation gradient and strain measures of a field
container.
Parameters
@@ -48,6 +48,10 @@ class EvaluateFieldContainer:
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
+
+ See Also
+ --------
+ felupe.FieldContainer : A container which holds one or multiple (mixed) fields.
"""
def __init__(self, field):
From dd2d8bd5ead2e8169100207cb87c6c30334fecda Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sat, 21 Sep 2024 23:55:27 +0200
Subject: [PATCH 023/125] Update issue templates (#855)
---
.github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++
.../ISSUE_TEMPLATE/enhancement-suggestion.md | 20 +++++++++++++
2 files changed, 48 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
create mode 100644 .github/ISSUE_TEMPLATE/enhancement-suggestion.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..f7460bd0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,28 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Environment (please complete the following information):**
+ - OS: [e.g. win11]
+ - Python Interpreter [e.g. 3.12]
+ - FElupe version [e.g. 9.0.0]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/enhancement-suggestion.md b/.github/ISSUE_TEMPLATE/enhancement-suggestion.md
new file mode 100644
index 00000000..f07a6f17
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement-suggestion.md
@@ -0,0 +1,20 @@
+---
+name: Enhancement suggestion
+about: Suggest an enhancement for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
From 60e73846f9cfef7800288cd3499e22a724a6adc5 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 00:37:31 +0200
Subject: [PATCH 024/125] Fix pyOpenSci basic checks (#854)
* Update CONTRIBUTING.md
fix links and refer also to discussions
* Update CODE_OF_CONDUCT.md
* Simplify the install section in README.md
* Add development install instructions to the docs
* Update README.md
---
CODE_OF_CONDUCT.md | 4 +---
CONTRIBUTING.md | 49 ++++++++++++++++++++--------------------------
README.md | 13 +-----------
docs/index.rst | 14 +++++++++++++
4 files changed, 37 insertions(+), 43 deletions(-)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 831c20ff..98b844ff 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,4 +1,3 @@
-
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -61,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
-a.dutzler@gmail.com.
+.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -131,4 +130,3 @@ For answers to common questions about this code of conduct, see the FAQ at
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ef0451c8..65a6eaed 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,4 @@
+
# Contributing to FElupe
First off, thanks for taking the time to contribute! ❤️
@@ -10,7 +11,7 @@ All types of contributions are encouraged and valued. See the [Table of Contents
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
-
+
## Table of Contents
- [Code of Conduct](#code-of-conduct)
@@ -19,63 +20,58 @@ All types of contributions are encouraged and valued. See the [Table of Contents
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
-
## Code of Conduct
This project and everyone participating in it is governed by the
-[CONTRIBUTING.md Code of Conduct](blob/main/CODE_OF_CONDUCT.md).
+[CONTRIBUTING.md Code of Conduct](https://github.com/adtzlr/felupe/blob/main/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
-to <>.
-
+to .
## I Have a Question
-> If you want to ask a question, we assume that you have read the available [Documentation]().
+> If you want to ask a question, we assume that you have read the available [Documentation](https://felupe.readthedocs.io/).
-Before you ask a question, it is best to search for existing [Issues](/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
+Before you ask a question, it is best to search for existing [Issues](https://github.com/adtzlr/felupe/issues) and [Discussions](https://github.com/adtzlr/felupe/discussions) that might help you. In case you have found a suitable issue or discussion and still need clarification, you can write your question in this issue or discussion. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
-- Open an [Issue](/issues/new).
+- Open a [Discussion](https://github.com/adtzlr/felupe/discussions/new/choose).
- Provide as much context as you can about what you're running into.
-- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
+- Provide project and platform versions, depending on what seems relevant.
We will then take care of the issue as soon as possible.
-
-
## I Want To Contribute
> ### Legal Notice
-> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project [license](blob/main/LICENSE).
+> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project [license](https://github.com/adtzlr/felupe/blob/main/LICENSE).
### Reporting Bugs
-
+
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://felupe.readthedocs.io/). If you are looking for support, you might want to check [this section](#i-have-a-question)).
-- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](issues?q=label%3Abug).
+- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/adtzlr/felupe/issues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
-- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
+- Version of the interpreter, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
-
+
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
-
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
-- Open an [Issue](/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
+- Open an [Issue](https://github.com/adtzlr/felupe/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
@@ -86,25 +82,23 @@ Once it's filed:
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps.
- If the team is able to reproduce the issue, the issue will be left to be [implemented by someone](#your-first-code-contribution).
-
-
-
+
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for FElupe, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
-
+
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation](https://felupe.readthedocs.io/) carefully and find out if the functionality is already covered.
-- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
-- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
-
+- Perform a [search](https://github.com/adtzlr/felupe/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
+- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an [add-on/plugin library](https://github.com/adtzlr/felupe?tab=readme-ov-file#extension-packages).
+
#### How Do I Submit a Good Enhancement Suggestion?
-Enhancement suggestions are tracked as [GitHub issues](/issues).
+Enhancement suggestions are tracked as [GitHub issues](https://github.com/adtzlr/felupe/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
@@ -112,7 +106,6 @@ Enhancement suggestions are tracked as [GitHub issues](/issues).
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to.
- **Explain why this enhancement would be useful** to most FElupe users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
-
-
+
## Attribution
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
diff --git a/README.md b/README.md
index a4af59fc..80a0aeaf 100644
--- a/README.md
+++ b/README.md
@@ -51,18 +51,7 @@ Install Python, fire up 🔥 a terminal and run 🏃
pip install felupe[all]
```
-where `[all]` is a combination of `[io,parallel,plot,progress,view]` and installs all optional dependencies. FElupe has minimal requirements, all available at PyPI supporting all platforms.
-* [`numpy`](https://github.com/numpy/numpy) for array operations
-* [`scipy`](https://github.com/scipy/scipy) for sparse matrices
-* [`tensortrax`](https://github.com/adtzlr/tensortrax) for automatic differentiation
-
-In order to make use of all features of FElupe 💎💰💍👑💎, it is suggested to install all optional dependencies.
-* [`einsumt`](https://github.com/mrkwjc/einsumt) for parallel (threaded) assembly
-* [`h5py`](https://github.com/h5py/h5py) for writing XDMF result files
-* [`matplotlib`](https://github.com/matplotlib/matplotlib) for plotting graphs
-* [`meshio`](https://github.com/nschloe/meshio) for mesh-related I/O
-* [`pyvista`](https://github.com/pyvista/pyvista) for interactive visualizations
-* [`tqdm`](https://github.com/tqdm/tqdm) for showing progress bars during job evaluations
+The [documentation](https://felupe.readthedocs.io/) covers more details, like required and optional dependencies and how to install the latest development version.
# Getting Started
This tutorial covers the essential high-level parts of creating and solving problems with FElupe. As an introductory example 👨🏫, a quarter model of a solid cube with hyperelastic material behaviour is subjected to a uniaxial elongation applied at a clamped end-face.
diff --git a/docs/index.rst b/docs/index.rst
index 1d9fb431..5fd248e0 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -78,6 +78,20 @@ In order to make use of all features of FElupe 💎💰💍👑💎, it is sugge
* `pyvista `_ for interactive visualizations
* `tqdm `_ for showing progress bars during job evaluations
+The development version may contain not yet released bug fixes and features. Consider using the ``--user`` option to install the package into your home directory (see `pip documentation `_ for more details). To install FElupe from the latest development branch, use
+
+.. code-block:: shell
+
+ pip install git+https://github.com/adtzlr/felupe.git@main
+
+or clone the repository and install the package in editable mode.
+
+.. code-block:: shell
+
+ git clone https://github.com/adtzlr/felupe.git
+ cd felupe
+ pip install --editable .
+
Extension Packages
------------------
From 3f8557d09a7b9bb7b987c21241e71dfe6027b959 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 00:40:31 +0200
Subject: [PATCH 025/125] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 65a6eaed..88a776ce 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,7 +80,7 @@ Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps.
-- If the team is able to reproduce the issue, the issue will be left to be [implemented by someone](#your-first-code-contribution).
+- If the team is able to reproduce the issue, the issue will be left to be implemented by someone.
### Suggesting Enhancements
From 5e30f78981bbd81649d687547e7ec30cb35bf67e Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 00:41:23 +0200
Subject: [PATCH 026/125] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 88a776ce..efc730a0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -90,7 +90,7 @@ This section guides you through submitting an enhancement suggestion for FElupe,
#### Before Submitting an Enhancement
-- Make sure that you are using the latest version.
+- Make sure that you are using the [latest development version](https://felupe.readthedocs.io/en/latest/#installation).
- Read the [documentation](https://felupe.readthedocs.io/) carefully and find out if the functionality is already covered.
- Perform a [search](https://github.com/adtzlr/felupe/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an [add-on/plugin library](https://github.com/adtzlr/felupe?tab=readme-ov-file#extension-packages).
From c4227f1a14ba8f943feff02bd801e77a4ad7da8f Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 00:42:44 +0200
Subject: [PATCH 027/125] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index efc730a0..dc1e5d7e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -53,7 +53,7 @@ We will then take care of the issue as soon as possible.
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
-- Make sure that you are using the latest version.
+- Make sure that you are using the [latest version](https://felupe.readthedocs.io/en/latest/#installation).
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://felupe.readthedocs.io/). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/adtzlr/felupe/issues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
From fc3ed0c0240ff823fd96a5bc38746bf451c3b3c5 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 00:46:42 +0200
Subject: [PATCH 028/125] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dc1e5d7e..b841903f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -69,9 +69,9 @@ A good bug report shouldn't leave others needing to chase you up for more inform
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
-We use GitHub issues to track bugs and errors. If you run into an issue with the project:
+We use [GitHub issues](https://github.com/adtzlr/felupe/issues) to track bugs and errors. If you run into an issue with the project:
-- Open an [Issue](https://github.com/adtzlr/felupe/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
+- Open an [Issue](https://github.com/adtzlr/felupe/issues/new/choose).
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
From de2ea6bcb4761ebdaff26eae4e78156f54259ed1 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 12:38:35 +0200
Subject: [PATCH 029/125] Enhance the highlights section
add the base cartesian field
---
README.md | 2 +-
docs/index.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 80a0aeaf..30fd43fa 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ FElupe is a Python 3.8+ 🐍 finite element analysis package 📦 focusing on th
- [x] nonlinear deformation of [solid bodies](https://felupe.readthedocs.io/en/latest/felupe/mechanics.html#felupe.SolidBody)
- [x] interactive views on meshes, fields and solid bodies (using [PyVista](https://pyvista.org/))
- [x] typical [finite elements](https://felupe.readthedocs.io/en/latest/felupe/element.html)
-- [x] axisymmetric, plane strain and mixed fields
+- [x] cartesian, axisymmetric, plane strain and mixed fields
- [x] [hyperelastic material models](https://felupe.readthedocs.io/en/latest/felupe/constitution/hyperelasticity.html)
- [x] strain energy density functions with [automatic differentiation](https://felupe.readthedocs.io/en/latest/felupe/constitution/hyperelasticity.html#felupe.Hyperelastic)
diff --git a/docs/index.rst b/docs/index.rst
index 5fd248e0..2a97847b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -45,7 +45,7 @@ FElupe is a Python 3.8+ 🐍 finite element analysis package 📦 focusing on th
+ typical :ref:`finite elements `
- + axisymmetric, plane strain and mixed fields
+ + cartesian, axisymmetric, plane strain and mixed fields
+ :ref:`hyperelastic material models `
From 2961ad1e98286acc9dda2520a28bc6426b9a1de1 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Sun, 22 Sep 2024 12:43:08 +0200
Subject: [PATCH 030/125] Update umat.rst
---
docs/howto/umat.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/howto/umat.rst b/docs/howto/umat.rst
index 921dbe7f..36bffce7 100644
--- a/docs/howto/umat.rst
+++ b/docs/howto/umat.rst
@@ -14,7 +14,9 @@ A user material (``umat``) based on the incremental small-strain tensor, e.g. su
+----------+---------------+---------------------------------------+
| Argument | ζn | list of old state variables |
+----------+---------------+---------------------------------------+
-| Return | σ | tangent modulus |
+| Return | dσdε | tangent modulus |
++----------+---------------+---------------------------------------+
+| Return | σ | new stress tensor |
+----------+---------------+---------------------------------------+
| Return | ζ | list of new state variables |
+----------+---------------+---------------------------------------+
From 9a83fdd04615bc50a405dc26f63c29b3a9c0b22a Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Mon, 23 Sep 2024 21:54:08 +0200
Subject: [PATCH 031/125] Extract C-contiguous arrays by default from fields
(#856)
* Extract field data as C-contiguous array
this enhances the time spent on assembly
* Change to default `np.einsum(..., order="C")` in `Field`
* Update _container.py
---
CHANGELOG.md | 4 +++
src/felupe/field/_axi.py | 33 ++++++++++++++------
src/felupe/field/_base.py | 43 +++++++++++++++++++++-----
src/felupe/field/_container.py | 19 ++++++++++--
src/felupe/field/_planestrain.py | 52 ++++++++++++++++++++++++--------
src/felupe/region/_region.py | 4 +--
6 files changed, 122 insertions(+), 33 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a983f0e..439c021a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format
### Added
- Add the hessian of the element shape functions for a quadratic quad element `QuadraticQuad.hessian()`.
+- Add the `order`-argument to `FieldContainer.extract(order="C")` to return C-contiguous arrays by default.
+
+### Changed
+- Change default `np.einsum(..., order="K")` to `np.einsum(..., order="C")` in the methods of `Field`, `FieldAxisymmetric` and `FieldPlaneStrain`.
## [9.0.0] - 2024-09-06
diff --git a/src/felupe/field/_axi.py b/src/felupe/field/_axi.py
index 231204d1..66857e69 100644
--- a/src/felupe/field/_axi.py
+++ b/src/felupe/field/_axi.py
@@ -88,7 +88,7 @@ def __init__(self, region, dim=2, values=0.0, dtype=None):
# in the region
self.radius = self.scalar.interpolate()
- def _interpolate_2d(self, dtype=None, out=None):
+ def _interpolate_2d(self, dtype=None, out=None, order="C"):
"""Interpolate 2D field values at points and evaluate them at the
integration points of all cells in the region."""
@@ -101,20 +101,22 @@ def _interpolate_2d(self, dtype=None, out=None):
self.region.h,
dtype=dtype,
out=out,
+ order=order,
)
- def interpolate(self, dtype=None, out=None):
+ def interpolate(self, dtype=None, out=None, order="C"):
# out-argument is not supported
# if out is not None:
# out = out[:2]
- # extend dimension of in-plane 2d-gradient
+ # extend dimension of in-plane 2d-gradient (out-keyword can't be used here)
return np.pad(
- self._interpolate_2d(dtype=dtype, out=None), ((0, 1), (0, 0), (0, 0))
+ self._interpolate_2d(dtype=dtype, out=None, order=order),
+ ((0, 1), (0, 0), (0, 0)),
)
- def _grad_2d(self, sym=False, dtype=None, out=None):
- """In-plane 2D gradient as partial derivative of field values at points
+ def _grad_2d(self, sym=False, dtype=None, out=None, order="C"):
+ r"""In-plane 2D gradient as partial derivative of field values at points
w.r.t. the undeformed coordinates, evaluated at the integration points
of all cells in the region. Optionally, the symmetric part of the
gradient is returned.
@@ -130,6 +132,12 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -148,6 +156,7 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
self.region.dhdX,
dtype=dtype,
out=out,
+ order=order,
)
if sym:
@@ -155,7 +164,7 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
else:
return g
- def grad(self, sym=False, dtype=None, out=None):
+ def grad(self, sym=False, dtype=None, out=None, order="C"):
r"""3D-gradient as partial derivative of field values at points w.r.t.
the undeformed coordinates, evaluated at the integration points of all
cells in the region. Optionally, the symmetric part of the gradient is
@@ -182,6 +191,12 @@ def grad(self, sym=False, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -197,11 +212,11 @@ def grad(self, sym=False, dtype=None, out=None):
# extend dimension of in-plane 2d-gradient
g = np.pad(
- self._grad_2d(sym=sym, dtype=dtype, out=None),
+ self._grad_2d(sym=sym, dtype=dtype, out=None, order=order),
((0, 1), (0, 1), (0, 0), (0, 0)),
)
# set dudX_33 = u_r / R
- g[-1, -1] = self.interpolate(dtype=dtype)[1] / self.radius
+ g[-1, -1] = self.interpolate(dtype=dtype, order=order)[1] / self.radius
return g
diff --git a/src/felupe/field/_base.py b/src/felupe/field/_base.py
index 0a12b9b3..835f56b1 100644
--- a/src/felupe/field/_base.py
+++ b/src/felupe/field/_base.py
@@ -138,7 +138,7 @@ def _indices_per_cell(self, cells, dim):
return cai, ai
- def grad(self, sym=False, dtype=None, out=None):
+ def grad(self, sym=False, dtype=None, out=None, order="C"):
r"""Gradient as partial derivative of field values w.r.t. undeformed
coordinates, evaluated at the integration points of all cells in the region.
Optionally, the symmetric part the gradient is evaluated.
@@ -159,6 +159,12 @@ def grad(self, sym=False, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -177,6 +183,7 @@ def grad(self, sym=False, dtype=None, out=None):
self.region.dhdX,
dtype=dtype,
out=out,
+ order=order,
)
if sym:
@@ -184,7 +191,7 @@ def grad(self, sym=False, dtype=None, out=None):
else:
return g
- def hess(self, dtype=None, out=None):
+ def hess(self, dtype=None, out=None, order="C"):
r"""Hessian as second partial derivative of field values w.r.t. undeformed
coordinates, evaluated at the integration points of all cells in the region.
@@ -204,6 +211,12 @@ def hess(self, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -222,11 +235,12 @@ def hess(self, dtype=None, out=None):
self.region.d2hdXdX,
dtype=dtype,
out=out,
+ order=order,
)
return h
- def interpolate(self, dtype=None, out=None):
+ def interpolate(self, dtype=None, out=None, order="C"):
r"""Interpolate field values located at mesh-points to the quadrature points
``q`` of cells ``c`` in the region.
@@ -243,6 +257,12 @@ def interpolate(self, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -259,10 +279,13 @@ def interpolate(self, dtype=None, out=None):
self.values[self.region.mesh.cells],
self.region.h,
dtype=dtype,
- out=None,
+ out=out,
+ order=order,
)
- def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None):
+ def extract(
+ self, grad=True, sym=False, add_identity=True, dtype=None, out=None, order="C"
+ ):
"""Generalized extraction method which evaluates either the gradient or the
field values at the integration points of all cells in the region. Optionally,
the symmetric part of the gradient is evaluated and/or the identity matrix is
@@ -284,6 +307,12 @@ def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None)
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -298,7 +327,7 @@ def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None)
"""
if grad:
- gr = self.grad(out=out, dtype=dtype)
+ gr = self.grad(out=out, dtype=dtype, order=order)
if sym:
gr = symmetric(gr, out=gr)
@@ -308,7 +337,7 @@ def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None)
return gr
else:
- return self.interpolate(out=out, dtype=dtype)
+ return self.interpolate(out=out, dtype=dtype, order=order)
def copy(self):
"Return a copy of the field."
diff --git a/src/felupe/field/_container.py b/src/felupe/field/_container.py
index 6de65a9f..5d359883 100644
--- a/src/felupe/field/_container.py
+++ b/src/felupe/field/_container.py
@@ -117,7 +117,9 @@ def __repr__(self):
return "\n".join([header, size, fields_header, *fields])
- def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None):
+ def extract(
+ self, grad=True, sym=False, add_identity=True, dtype=None, out=None, order="C"
+ ):
"""Generalized extraction method which evaluates either the gradient or the
field values at the integration points of all cells in the region. Optionally,
the symmetric part of the gradient is evaluated and/or the identity matrix is
@@ -142,6 +144,12 @@ def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None)
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ orders : str or list of str, optional
+ Controls the memory layout of the outputs. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -153,13 +161,18 @@ def extract(self, grad=True, sym=False, add_identity=True, dtype=None, out=None)
if isinstance(grad, bool):
grad = (grad,)
+ if isinstance(order, str):
+ order = (order,)
+
if out is None:
out = [None] * len(self.fields)
grads = np.pad(grad, (0, len(self.fields) - 1))
+ orders = order * len(self.fields)
+
return tuple(
- f.extract(g, sym, add_identity=add_identity, dtype=dtype, out=res)
- for g, f, res in zip(grads, self.fields, out)
+ f.extract(g, sym, add_identity=add_identity, dtype=dtype, out=res, order=od)
+ for g, f, res, od in zip(grads, self.fields, out, orders)
)
def values(self):
diff --git a/src/felupe/field/_planestrain.py b/src/felupe/field/_planestrain.py
index af93ed13..406a0ed9 100644
--- a/src/felupe/field/_planestrain.py
+++ b/src/felupe/field/_planestrain.py
@@ -67,7 +67,7 @@ def __init__(self, region, dim=2, values=0.0, dtype=None):
# init base Field
super().__init__(region, dim=dim, values=values, dtype=dtype)
- def _interpolate_2d(self, dtype=None, out=None):
+ def _interpolate_2d(self, dtype=None, out=None, order="C"):
"""Interpolate 2D field values at points and evaluate them at the
integration points of all cells in the region."""
@@ -80,19 +80,21 @@ def _interpolate_2d(self, dtype=None, out=None):
self.region.h,
dtype=dtype,
out=out,
+ order=order,
)
- def interpolate(self, dtype=None, out=None):
+ def interpolate(self, dtype=None, out=None, order="C"):
# out-argument is not supported
# if out is not None:
# out = out[:2]
- # extend dimension of in-plane 2d-gradient
+ # extend dimension of in-plane 2d-gradient (out-keyword can't be used here)
return np.pad(
- self._interpolate_2d(dtype=dtype, out=None), ((0, 1), (0, 0), (0, 0))
+ self._interpolate_2d(dtype=dtype, out=None, order=order),
+ ((0, 1), (0, 0), (0, 0)),
)
- def _grad_2d(self, sym=False, dtype=None, out=None):
+ def _grad_2d(self, sym=False, dtype=None, out=None, order="C"):
"""In-plane 2D gradient as partial derivative of field values at points
w.r.t. the undeformed coordinates, evaluated at the integration points
of all cells in the region. Optionally, the symmetric part of the
@@ -109,6 +111,12 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -127,6 +135,7 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
self.region.dhdX,
dtype=dtype,
out=out,
+ order=order,
)
if sym:
@@ -134,7 +143,7 @@ def _grad_2d(self, sym=False, dtype=None, out=None):
else:
return g
- def _hess_2d(self, dtype=None, out=None):
+ def _hess_2d(self, dtype=None, out=None, order="C"):
r"""In-plane 2D Hessian as second partial derivative of field values w.r.t.
undeformed coordinates, evaluated at the integration points of all cells in the
region.
@@ -148,6 +157,12 @@ def _hess_2d(self, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -166,11 +181,12 @@ def _hess_2d(self, dtype=None, out=None):
self.region.d2hdXdX,
dtype=dtype,
out=out,
+ order=order,
)
return h
- def grad(self, sym=False, dtype=None, out=None):
+ def grad(self, sym=False, dtype=None, out=None, order="C"):
"""3D-gradient as partial derivative of field values at points w.r.t.
the undeformed coordinates, evaluated at the integration points of all
cells in the region. Optionally, the symmetric part of the gradient is
@@ -193,6 +209,12 @@ def grad(self, sym=False, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -206,15 +228,15 @@ def grad(self, sym=False, dtype=None, out=None):
# if out is not None:
# out = out[:2, :2]
- # extend dimension of in-plane 2d-gradient
+ # extend dimension of in-plane 2d-gradient (out-keyword can't be used here)
g = np.pad(
- self._grad_2d(sym=sym, dtype=dtype, out=None),
+ self._grad_2d(sym=sym, dtype=dtype, out=None, order=order),
((0, 1), (0, 1), (0, 0), (0, 0)),
)
return g
- def hess(self, dtype=None, out=None):
+ def hess(self, dtype=None, out=None, order="C"):
"""3D-Hessian as second partial derivative of field values at points w.r.t.
the undeformed coordinates, evaluated at the integration points of all
cells in the region. Optionally, the symmetric part of the gradient is
@@ -231,6 +253,12 @@ def hess(self, dtype=None, out=None):
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a freshly-
allocated array is returned (default is None).
+ order : {'C', 'F', 'A', 'K'}, optional
+ Controls the memory layout of the output. 'C' means it should be C
+ contiguous. 'F' means it should be Fortran contiguous, 'A' means it should
+ be 'F' if the inputs are all 'F', 'C' otherwise. 'K' means it should be as
+ close to the layout as the inputs as is possible, including arbitrarily
+ permuted axes. Default is 'C'.
Returns
-------
@@ -240,9 +268,9 @@ def hess(self, dtype=None, out=None):
of all cells in the region.
"""
- # extend dimension of in-plane 2d-hessian
+ # extend dimension of in-plane 2d-hessian (out-keyword can't be used here)
h = np.pad(
- self._hess_2d(dtype=dtype, out=None),
+ self._hess_2d(dtype=dtype, out=None, order=order),
((0, 1), (0, 1), (0, 1), (0, 0), (0, 0)),
)
diff --git a/src/felupe/region/_region.py b/src/felupe/region/_region.py
index b5f2dade..cc088cc5 100644
--- a/src/felupe/region/_region.py
+++ b/src/felupe/region/_region.py
@@ -344,8 +344,8 @@ def reload(
if uniform:
cells = cells[:1]
- region.dXdr = np.ascontiguousarray(
- np.einsum("caI,aJqc->IJqc", region.mesh.points[cells], region.dhdr)
+ region.dXdr = np.einsum(
+ "caI,aJqc->IJqc", region.mesh.points[cells], region.dhdr, order="C"
)
# determinant and inverse of dXdr
From c918ce13779a3ce33e614d89fdbba36e691f9568 Mon Sep 17 00:00:00 2001
From: Andreas Dutzler
Date: Mon, 23 Sep 2024 22:13:50 +0200
Subject: [PATCH 032/125] Docs: update benchmark results
---
docs/_static/benchmark.png | Bin 29928 -> 68153 bytes
docs/index.rst | 4 ++--
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/_static/benchmark.png b/docs/_static/benchmark.png
index c25747398ab799be2a225fa384f1c2a5f8511400..dc75000f534032e2a6916dc0fecb444c62761a35 100644
GIT binary patch
literal 68153
zcmeGEWmHvd8#auBhy{v)NJuCaf`EXcq=|@xV$dxh(%oQ!N*RQ-L8wRxEV@KMTBTW}
zba&Tx%=@{Y=l#Yz#vWt;+5a|UFhpX_x#qmC^E~RD%j0*}jc=8wCZ$_N&sD6e%b+
zCQ(pOactRy?+A8=ZoofhZ7wU@+%z|^vDdNEr;yXJu`n^WF)`BRu+z7)HZnI8T>0gpbekf4{(MZe_@KDmg6^S2zr
zM%wHhZWWVDCdXsmJh@KwaOZ_Y%E4ooMczMtv6ntc_|}N@<{R#(Z_)1^)Gfa7sy&L6
zL)>P|;hj!bFF4KRxc5|rBptQgv-|sMWQ3Vx{MwVpZ)tGP1)<;
zLA7(&zaM4KHYgwb_e1d=*Mr0V{SS4>|NjRE5B`69Nv^rko!ge!)TBH&(oFRrF_Dj#
zSETLIe?H7EKY#vQJ;Q?Xfz#|zS67$i&1I!a=cg(o#GUUod{uFyyZG-fTKx_@p?0q&
zbx>~fCfAey-U!8aMw-1>PR2Sr+!%p#0iU
zG12jMg8JFxw?4h}`}prKy=r~QZmiDz^!mTYxwK7)I_bY(N1eM#aqYkV`TyAutjoqJ
zpWXezPn)2XFMsLM#^mJWLx&Id&cDQwqaIk<*_9L(QL(eL=j7xhDf29fAHu1Z9p0vW
z^`U;6>RHFBLnlw3Tok9rcQdIE9XYal-@ei`
zdFbrR`KHWsMpU%F`xEi{9M@!IzVp*k=<4d8!Am`S_%P%-*16)AV-F5f|Ct?bG%e&d
zG&EdUS(%udYe@fumwL?`S1)tx7QJCj#9=P3=WpLK;>3N^)9u~pN~gPfexmY0avQlsDXas$`t(+tw`EOqHI#J5=6
z+uKR0=i{Tfa2{uf^H3}|dt6;z9pXsrz~;X_J$KcFZ;CQ{J$pvK;|1;icJIR&iopN=
zhyLC+?dSj9*7!k#sljkVyv=Zf&);IN?%v+waQpVM#Qe38j-uWFE`_T>#7Z@bsE7uJKXyVH?#NDeZHA9*J8XVe(B#o*(DnC&^A
wDp#`5naH@cf_c~_8qRc37$osbb^+D
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