fluxfem 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fluxfem/__init__.py +1 -13
- fluxfem/core/__init__.py +53 -71
- fluxfem/core/assembly.py +41 -32
- fluxfem/core/basis.py +2 -2
- fluxfem/core/context_types.py +36 -12
- fluxfem/core/mixed_space.py +42 -8
- fluxfem/core/mixed_weakform.py +1 -1
- fluxfem/core/space.py +68 -28
- fluxfem/core/weakform.py +95 -77
- fluxfem/mesh/base.py +3 -3
- fluxfem/mesh/contact.py +33 -17
- fluxfem/mesh/io.py +3 -2
- fluxfem/mesh/mortar.py +106 -43
- fluxfem/mesh/supermesh.py +2 -0
- fluxfem/mesh/surface.py +82 -22
- fluxfem/mesh/tet.py +7 -4
- fluxfem/physics/elasticity/hyperelastic.py +32 -3
- fluxfem/physics/elasticity/linear.py +13 -2
- fluxfem/physics/elasticity/stress.py +9 -5
- fluxfem/physics/operators.py +12 -5
- fluxfem/physics/postprocess.py +29 -3
- fluxfem/solver/__init__.py +6 -1
- fluxfem/solver/block_matrix.py +165 -13
- fluxfem/solver/block_system.py +52 -29
- fluxfem/solver/cg.py +43 -30
- fluxfem/solver/dirichlet.py +35 -12
- fluxfem/solver/history.py +15 -3
- fluxfem/solver/newton.py +25 -12
- fluxfem/solver/petsc.py +13 -7
- fluxfem/solver/preconditioner.py +7 -4
- fluxfem/solver/solve_runner.py +42 -24
- fluxfem/solver/solver.py +23 -11
- fluxfem/solver/sparse.py +32 -13
- fluxfem/tools/jit.py +19 -7
- fluxfem/tools/timer.py +14 -12
- fluxfem/tools/visualizer.py +16 -4
- {fluxfem-0.2.0.dist-info → fluxfem-0.2.1.dist-info}/METADATA +18 -7
- fluxfem-0.2.1.dist-info/RECORD +59 -0
- fluxfem-0.2.0.dist-info/RECORD +0 -59
- {fluxfem-0.2.0.dist-info → fluxfem-0.2.1.dist-info}/LICENSE +0 -0
- {fluxfem-0.2.0.dist-info → fluxfem-0.2.1.dist-info}/WHEEL +0 -0
fluxfem/mesh/surface.py
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Callable, Optional, Protocol, Sequence, TYPE_CHECKING, TypeVar, cast
|
|
5
5
|
import jax
|
|
6
6
|
import jax.numpy as jnp
|
|
7
7
|
|
|
8
8
|
from .dtypes import INDEX_DTYPE
|
|
9
9
|
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
10
11
|
|
|
11
12
|
DTYPE = jnp.float64 if jax.config.read("jax_enable_x64") else jnp.float32
|
|
12
13
|
|
|
13
14
|
from .base import BaseMesh, BaseMeshPytree
|
|
14
15
|
from .hex import HexMesh, HexMeshPytree
|
|
15
16
|
|
|
17
|
+
P = TypeVar("P")
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..solver.bc import SurfaceFormContext
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SurfaceSpaceLike(Protocol):
|
|
24
|
+
value_dim: int
|
|
25
|
+
mesh: BaseMesh
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
SurfaceLinearForm = Callable[["SurfaceFormContext", P], npt.ArrayLike]
|
|
29
|
+
|
|
16
30
|
|
|
17
31
|
def _polygon_area(pts: np.ndarray) -> float:
|
|
18
32
|
"""
|
|
@@ -26,7 +40,7 @@ def _polygon_area(pts: np.ndarray) -> float:
|
|
|
26
40
|
for i in range(1, pts.shape[0] - 1):
|
|
27
41
|
v1 = pts[i] - p0
|
|
28
42
|
v2 = pts[i + 1] - p0
|
|
29
|
-
area += 0.5 * np.linalg.norm(np.cross(v1, v2))
|
|
43
|
+
area += float(0.5 * np.linalg.norm(np.cross(v1, v2)))
|
|
30
44
|
return float(area)
|
|
31
45
|
|
|
32
46
|
|
|
@@ -103,20 +117,43 @@ class SurfaceMesh(BaseMesh):
|
|
|
103
117
|
from ..solver.bc import facet_normals
|
|
104
118
|
return facet_normals(self, outward_from=outward_from, normalize=normalize)
|
|
105
119
|
|
|
106
|
-
def assemble_load(
|
|
120
|
+
def assemble_load(
|
|
121
|
+
self,
|
|
122
|
+
load: npt.ArrayLike,
|
|
123
|
+
*,
|
|
124
|
+
dim: int,
|
|
125
|
+
n_total_nodes: int | None = None,
|
|
126
|
+
F0: npt.ArrayLike | None = None,
|
|
127
|
+
) -> np.ndarray:
|
|
107
128
|
from ..solver.bc import assemble_surface_load
|
|
108
129
|
return assemble_surface_load(self, load, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
109
130
|
|
|
110
|
-
def assemble_linear_form(
|
|
131
|
+
def assemble_linear_form(
|
|
132
|
+
self,
|
|
133
|
+
form: SurfaceLinearForm[P],
|
|
134
|
+
params: P,
|
|
135
|
+
*,
|
|
136
|
+
dim: int,
|
|
137
|
+
n_total_nodes: int | None = None,
|
|
138
|
+
F0: npt.ArrayLike | None = None,
|
|
139
|
+
) -> np.ndarray:
|
|
111
140
|
from ..solver.bc import assemble_surface_linear_form
|
|
112
141
|
return assemble_surface_linear_form(self, form, params, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
113
142
|
|
|
114
|
-
def assemble_linear_form_on_space(
|
|
143
|
+
def assemble_linear_form_on_space(
|
|
144
|
+
self,
|
|
145
|
+
space: SurfaceSpaceLike,
|
|
146
|
+
form: SurfaceLinearForm[P],
|
|
147
|
+
params: P,
|
|
148
|
+
*,
|
|
149
|
+
F0: npt.ArrayLike | None = None,
|
|
150
|
+
) -> np.ndarray:
|
|
115
151
|
"""
|
|
116
152
|
Assemble surface linear form using global size inferred from a volume space.
|
|
117
153
|
"""
|
|
118
154
|
dim = int(getattr(space, "value_dim", 1))
|
|
119
|
-
|
|
155
|
+
mesh = cast(BaseMesh, getattr(space, "mesh", self))
|
|
156
|
+
n_total_nodes = int(mesh.n_nodes)
|
|
120
157
|
return self.assemble_linear_form(form, params, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
121
158
|
|
|
122
159
|
|
|
@@ -137,13 +174,13 @@ def surface_with_elem_conn(mesh: BaseMesh, facets, *, mode: str = "touching") ->
|
|
|
137
174
|
|
|
138
175
|
def assemble_traction(
|
|
139
176
|
self,
|
|
140
|
-
traction,
|
|
177
|
+
traction: float | Sequence[float],
|
|
141
178
|
*,
|
|
142
179
|
dim: int = 3,
|
|
143
180
|
n_total_nodes: int | None = None,
|
|
144
|
-
F0=None,
|
|
145
|
-
outward_from=None,
|
|
146
|
-
):
|
|
181
|
+
F0: npt.ArrayLike | None = None,
|
|
182
|
+
outward_from: npt.ArrayLike | None = None,
|
|
183
|
+
) -> np.ndarray:
|
|
147
184
|
from ..solver.bc import assemble_surface_traction
|
|
148
185
|
return assemble_surface_traction(
|
|
149
186
|
self,
|
|
@@ -222,31 +259,54 @@ class SurfaceMeshPytree(BaseMeshPytree):
|
|
|
222
259
|
from ..solver.bc import facet_normals
|
|
223
260
|
return facet_normals(self, outward_from=outward_from, normalize=normalize)
|
|
224
261
|
|
|
225
|
-
def assemble_load(
|
|
262
|
+
def assemble_load(
|
|
263
|
+
self,
|
|
264
|
+
load: npt.ArrayLike,
|
|
265
|
+
*,
|
|
266
|
+
dim: int,
|
|
267
|
+
n_total_nodes: int | None = None,
|
|
268
|
+
F0: npt.ArrayLike | None = None,
|
|
269
|
+
) -> np.ndarray:
|
|
226
270
|
from ..solver.bc import assemble_surface_load
|
|
227
271
|
return assemble_surface_load(self, load, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
228
272
|
|
|
229
|
-
def assemble_linear_form(
|
|
273
|
+
def assemble_linear_form(
|
|
274
|
+
self,
|
|
275
|
+
form: SurfaceLinearForm[P],
|
|
276
|
+
params: P,
|
|
277
|
+
*,
|
|
278
|
+
dim: int,
|
|
279
|
+
n_total_nodes: int | None = None,
|
|
280
|
+
F0: npt.ArrayLike | None = None,
|
|
281
|
+
) -> np.ndarray:
|
|
230
282
|
from ..solver.bc import assemble_surface_linear_form
|
|
231
283
|
return assemble_surface_linear_form(self, form, params, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
232
284
|
|
|
233
|
-
def assemble_linear_form_on_space(
|
|
285
|
+
def assemble_linear_form_on_space(
|
|
286
|
+
self,
|
|
287
|
+
space: SurfaceSpaceLike,
|
|
288
|
+
form: SurfaceLinearForm[P],
|
|
289
|
+
params: P,
|
|
290
|
+
*,
|
|
291
|
+
F0: npt.ArrayLike | None = None,
|
|
292
|
+
) -> np.ndarray:
|
|
234
293
|
"""
|
|
235
294
|
Assemble surface linear form using global size inferred from a volume space.
|
|
236
295
|
"""
|
|
237
296
|
dim = int(getattr(space, "value_dim", 1))
|
|
238
|
-
|
|
297
|
+
mesh = cast(BaseMesh, getattr(space, "mesh", self))
|
|
298
|
+
n_total_nodes = int(mesh.n_nodes)
|
|
239
299
|
return self.assemble_linear_form(form, params, dim=dim, n_total_nodes=n_total_nodes, F0=F0)
|
|
240
300
|
|
|
241
301
|
def assemble_traction(
|
|
242
302
|
self,
|
|
243
|
-
traction,
|
|
303
|
+
traction: float | Sequence[float],
|
|
244
304
|
*,
|
|
245
305
|
dim: int = 3,
|
|
246
306
|
n_total_nodes: int | None = None,
|
|
247
|
-
F0=None,
|
|
248
|
-
outward_from=None,
|
|
249
|
-
):
|
|
307
|
+
F0: npt.ArrayLike | None = None,
|
|
308
|
+
outward_from: npt.ArrayLike | None = None,
|
|
309
|
+
) -> np.ndarray:
|
|
250
310
|
from ..solver.bc import assemble_surface_traction
|
|
251
311
|
return assemble_surface_traction(
|
|
252
312
|
self,
|
|
@@ -259,12 +319,12 @@ class SurfaceMeshPytree(BaseMeshPytree):
|
|
|
259
319
|
|
|
260
320
|
def assemble_flux(
|
|
261
321
|
self,
|
|
262
|
-
flux,
|
|
322
|
+
flux: npt.ArrayLike,
|
|
263
323
|
*,
|
|
264
324
|
n_total_nodes: int | None = None,
|
|
265
|
-
F0=None,
|
|
266
|
-
outward_from=None,
|
|
267
|
-
):
|
|
325
|
+
F0: npt.ArrayLike | None = None,
|
|
326
|
+
outward_from: npt.ArrayLike | None = None,
|
|
327
|
+
) -> np.ndarray:
|
|
268
328
|
from ..solver.bc import assemble_surface_flux
|
|
269
329
|
return assemble_surface_flux(
|
|
270
330
|
self,
|
fluxfem/mesh/tet.py
CHANGED
|
@@ -223,11 +223,14 @@ class StructuredTetTensorBox:
|
|
|
223
223
|
npx = len(xs)
|
|
224
224
|
npy = len(ys)
|
|
225
225
|
npz = len(zs)
|
|
226
|
+
X: np.ndarray
|
|
227
|
+
Y: np.ndarray
|
|
228
|
+
Z: np.ndarray
|
|
226
229
|
X, Y, Z = np.meshgrid(np.sort(xs), np.sort(ys), np.sort(zs))
|
|
227
230
|
p = np.vstack((X.flatten("F"), Y.flatten("F"), Z.flatten("F")))
|
|
228
|
-
ix = np.arange(npx * npy * npz)
|
|
231
|
+
ix: np.ndarray = np.arange(npx * npy * npz)
|
|
229
232
|
ne = (npx - 1) * (npy - 1) * (npz - 1)
|
|
230
|
-
t = np.zeros((8, ne), dtype=np.int64)
|
|
233
|
+
t: np.ndarray = np.zeros((8, ne), dtype=np.int64)
|
|
231
234
|
ix = ix.reshape(npy, npx, npz, order="F").copy()
|
|
232
235
|
t[0] = ix[0:(npy - 1), 0:(npx - 1), 0:(npz - 1)].reshape(ne, 1, order="F").copy().flatten()
|
|
233
236
|
t[1] = ix[1:npy, 0:(npx - 1), 0:(npz - 1)].reshape(ne, 1, order="F").copy().flatten()
|
|
@@ -238,7 +241,7 @@ class StructuredTetTensorBox:
|
|
|
238
241
|
t[6] = ix[0:(npy - 1), 1:npx, 1:npz].reshape(ne, 1, order="F").copy().flatten()
|
|
239
242
|
t[7] = ix[1:npy, 1:npx, 1:npz].reshape(ne, 1, order="F").copy().flatten()
|
|
240
243
|
|
|
241
|
-
T = np.zeros((4, 6 * ne), dtype=np.int64)
|
|
244
|
+
T: np.ndarray = np.zeros((4, 6 * ne), dtype=np.int64)
|
|
242
245
|
T[:, :ne] = t[[0, 1, 5, 7]]
|
|
243
246
|
T[:, ne:(2 * ne)] = t[[0, 1, 4, 7]]
|
|
244
247
|
T[:, (2 * ne):(3 * ne)] = t[[0, 2, 4, 7]]
|
|
@@ -247,6 +250,6 @@ class StructuredTetTensorBox:
|
|
|
247
250
|
T[:, (5 * ne):] = t[[0, 3, 6, 7]]
|
|
248
251
|
|
|
249
252
|
coords = p.T.astype(DTYPE, copy=False)
|
|
250
|
-
conn = T.T.astype(NP_INDEX_DTYPE, copy=False)
|
|
253
|
+
conn: np.ndarray = T.T.astype(NP_INDEX_DTYPE, copy=False)
|
|
251
254
|
conn = self._fix_orientation(coords, conn)
|
|
252
255
|
return TetMesh(coords=jnp.array(coords), conn=jnp.array(conn))
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
from typing import Mapping, TYPE_CHECKING, TypeAlias
|
|
2
|
+
|
|
1
3
|
import jax
|
|
2
4
|
import jax.numpy as jnp
|
|
3
5
|
import numpy as np
|
|
4
6
|
|
|
5
7
|
from ...core.forms import FormContext
|
|
8
|
+
from ...core.space import FESpace
|
|
9
|
+
from ...mesh import BaseMesh
|
|
6
10
|
from ...core.basis import build_B_matrices_finite
|
|
7
11
|
from ..postprocess import make_point_data_displacement, write_point_data_vtu
|
|
8
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from jax import Array as JaxArray
|
|
15
|
+
|
|
16
|
+
ArrayLike: TypeAlias = np.ndarray | JaxArray
|
|
17
|
+
else:
|
|
18
|
+
ArrayLike: TypeAlias = np.ndarray
|
|
19
|
+
ParamsLike: TypeAlias = Mapping[str, float] | tuple[float, float]
|
|
20
|
+
|
|
9
21
|
|
|
10
22
|
def right_cauchy_green(F: jnp.ndarray) -> jnp.ndarray:
|
|
11
23
|
"""C = F^T F (right Cauchy-Green)."""
|
|
@@ -46,7 +58,9 @@ def pk2_neo_hookean(F: jnp.ndarray, mu: float, lam: float) -> jnp.ndarray:
|
|
|
46
58
|
return mu * (I - C_inv) + lam * jnp.log(J)[..., None, None] * C_inv
|
|
47
59
|
|
|
48
60
|
|
|
49
|
-
def neo_hookean_residual_form(
|
|
61
|
+
def neo_hookean_residual_form(
|
|
62
|
+
ctx: FormContext, u_elem: jnp.ndarray, params: ParamsLike
|
|
63
|
+
) -> jnp.ndarray:
|
|
50
64
|
"""
|
|
51
65
|
Compressible Neo-Hookean residual (Total Lagrangian, PK2).
|
|
52
66
|
params: dict-like with keys \"mu\", \"lam\" or tuple (mu, lam)
|
|
@@ -92,11 +106,26 @@ __all__ = [
|
|
|
92
106
|
]
|
|
93
107
|
|
|
94
108
|
|
|
95
|
-
def make_elastic_point_data(
|
|
109
|
+
def make_elastic_point_data(
|
|
110
|
+
mesh: BaseMesh,
|
|
111
|
+
space: FESpace,
|
|
112
|
+
u: ArrayLike,
|
|
113
|
+
*,
|
|
114
|
+
compute_j: bool = True,
|
|
115
|
+
deformed_scale: float = 1.0,
|
|
116
|
+
) -> dict[str, np.ndarray]:
|
|
96
117
|
"""Alias to postprocess.make_point_data_displacement for backward compatibility."""
|
|
97
118
|
return make_point_data_displacement(mesh, space, u, compute_j=compute_j, deformed_scale=deformed_scale)
|
|
98
119
|
|
|
99
120
|
|
|
100
|
-
def write_elastic_vtu(
|
|
121
|
+
def write_elastic_vtu(
|
|
122
|
+
mesh: BaseMesh,
|
|
123
|
+
space: FESpace,
|
|
124
|
+
u: ArrayLike,
|
|
125
|
+
filepath: str,
|
|
126
|
+
*,
|
|
127
|
+
compute_j: bool = True,
|
|
128
|
+
deformed_scale: float = 1.0,
|
|
129
|
+
) -> None:
|
|
101
130
|
"""Alias to postprocess.write_point_data_vtu for backward compatibility."""
|
|
102
131
|
return write_point_data_vtu(mesh, space, u, filepath, compute_j=compute_j, deformed_scale=deformed_scale)
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
from typing import TypeAlias
|
|
2
|
+
|
|
1
3
|
import jax.numpy as jnp
|
|
2
4
|
|
|
5
|
+
from ...core.assembly import LinearReturn
|
|
3
6
|
from ...core.forms import FormContext, vector_load_form
|
|
4
|
-
from ...core.
|
|
7
|
+
from ...core.space import FESpace
|
|
5
8
|
from ...physics.operators import sym_grad
|
|
6
9
|
|
|
10
|
+
ArrayLike: TypeAlias = jnp.ndarray
|
|
11
|
+
|
|
7
12
|
# from ...mechanics.kinematics import build_B_matrices
|
|
8
13
|
|
|
9
14
|
|
|
@@ -41,7 +46,13 @@ def vector_body_force_form(ctx: FormContext, load_vec: jnp.ndarray) -> jnp.ndarr
|
|
|
41
46
|
vector_body_force_form._ff_kind = "linear"
|
|
42
47
|
vector_body_force_form._ff_domain = "volume"
|
|
43
48
|
|
|
44
|
-
def assemble_constant_body_force(
|
|
49
|
+
def assemble_constant_body_force(
|
|
50
|
+
space: FESpace,
|
|
51
|
+
gravity_vec: ArrayLike,
|
|
52
|
+
density: float,
|
|
53
|
+
*,
|
|
54
|
+
sparse: bool = False,
|
|
55
|
+
) -> LinearReturn:
|
|
45
56
|
"""
|
|
46
57
|
Convenience: assemble body force from density * gravity vector.
|
|
47
58
|
gravity_vec: length-3 array-like (direction and magnitude of g)
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TypeAlias
|
|
4
|
+
|
|
3
5
|
import jax.numpy as jnp
|
|
4
6
|
|
|
7
|
+
ArrayLike: TypeAlias = jnp.ndarray
|
|
8
|
+
|
|
5
9
|
|
|
6
|
-
def _sym(A:
|
|
10
|
+
def _sym(A: ArrayLike) -> ArrayLike:
|
|
7
11
|
return 0.5 * (A + jnp.swapaxes(A, -1, -2))
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
def principal_stresses(S:
|
|
14
|
+
def principal_stresses(S: ArrayLike) -> ArrayLike:
|
|
11
15
|
"""
|
|
12
16
|
Return principal stresses (eigvals) for symmetric 3x3 stress tensor.
|
|
13
17
|
Supports batching over leading dimensions.
|
|
@@ -16,12 +20,12 @@ def principal_stresses(S: jnp.ndarray) -> jnp.ndarray:
|
|
|
16
20
|
return jnp.linalg.eigvalsh(S_sym)
|
|
17
21
|
|
|
18
22
|
|
|
19
|
-
def principal_sum(S:
|
|
23
|
+
def principal_sum(S: ArrayLike) -> ArrayLike:
|
|
20
24
|
"""Sum of principal stresses (trace)."""
|
|
21
25
|
return jnp.trace(S, axis1=-2, axis2=-1)
|
|
22
26
|
|
|
23
27
|
|
|
24
|
-
def max_shear_stress(S:
|
|
28
|
+
def max_shear_stress(S: ArrayLike) -> ArrayLike:
|
|
25
29
|
"""
|
|
26
30
|
Maximum shear stress = (sigma_max - sigma_min) / 2.
|
|
27
31
|
"""
|
|
@@ -29,7 +33,7 @@ def max_shear_stress(S: jnp.ndarray) -> jnp.ndarray:
|
|
|
29
33
|
return 0.5 * (vals[..., -1] - vals[..., 0])
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
def von_mises_stress(S:
|
|
36
|
+
def von_mises_stress(S: ArrayLike) -> ArrayLike:
|
|
33
37
|
"""
|
|
34
38
|
von Mises equivalent stress: sqrt(3/2 * dev(S):dev(S)).
|
|
35
39
|
"""
|
fluxfem/physics/operators.py
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
# fluxfem/mechanics/operators.py
|
|
4
4
|
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, TypeAlias
|
|
7
|
+
|
|
5
8
|
import jax
|
|
6
9
|
import jax.numpy as jnp
|
|
7
10
|
|
|
11
|
+
from ..core.context_types import FormFieldLike
|
|
12
|
+
|
|
13
|
+
ArrayLike: TypeAlias = jnp.ndarray
|
|
14
|
+
|
|
8
15
|
|
|
9
|
-
def dot(a, b):
|
|
16
|
+
def dot(a: FormFieldLike | ArrayLike, b: ArrayLike) -> ArrayLike:
|
|
10
17
|
"""
|
|
11
18
|
Batched matrix product on the last two axes.
|
|
12
19
|
|
|
@@ -19,7 +26,7 @@ def dot(a, b):
|
|
|
19
26
|
return jnp.matmul(a, b)
|
|
20
27
|
|
|
21
28
|
|
|
22
|
-
def ddot(a, b, c=None):
|
|
29
|
+
def ddot(a: ArrayLike, b: ArrayLike, c: ArrayLike | None = None) -> ArrayLike:
|
|
23
30
|
"""
|
|
24
31
|
Double contraction on the last two axes.
|
|
25
32
|
|
|
@@ -32,12 +39,12 @@ def ddot(a, b, c=None):
|
|
|
32
39
|
return jnp.einsum("...ik,kl,...lm->...im", a_t, b, c)
|
|
33
40
|
|
|
34
41
|
|
|
35
|
-
def transpose_last2(a):
|
|
42
|
+
def transpose_last2(a: ArrayLike) -> ArrayLike:
|
|
36
43
|
"""Swap the last two axes (batched transpose)."""
|
|
37
44
|
return jnp.swapaxes(a, -1, -2)
|
|
38
45
|
|
|
39
46
|
|
|
40
|
-
def sym_grad(field) -> jnp.ndarray:
|
|
47
|
+
def sym_grad(field: FormFieldLike) -> jnp.ndarray:
|
|
41
48
|
"""
|
|
42
49
|
Symmetric gradient operator for vector mechanics (small strain).
|
|
43
50
|
|
|
@@ -89,7 +96,7 @@ def sym_grad(field) -> jnp.ndarray:
|
|
|
89
96
|
return jax.vmap(B_single)(gradN)
|
|
90
97
|
|
|
91
98
|
|
|
92
|
-
def sym_grad_u(field, u_elem: jnp.ndarray) -> jnp.ndarray:
|
|
99
|
+
def sym_grad_u(field: FormFieldLike, u_elem: jnp.ndarray) -> jnp.ndarray:
|
|
93
100
|
"""
|
|
94
101
|
Apply sym_grad(field) to a local displacement vector.
|
|
95
102
|
|
fluxfem/physics/postprocess.py
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING, TypeAlias
|
|
4
|
+
|
|
3
5
|
import numpy as np
|
|
4
6
|
import jax
|
|
5
7
|
import jax.numpy as jnp
|
|
6
8
|
|
|
7
9
|
# from ..core.assembly import build_form_contexts
|
|
8
10
|
from ..tools.visualizer import write_vtu
|
|
11
|
+
from ..mesh import BaseMesh
|
|
12
|
+
from ..core.space import FESpace
|
|
9
13
|
from ..core.interp import interpolate_field_at_element_points
|
|
10
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from jax import Array as JaxArray
|
|
17
|
+
|
|
18
|
+
ArrayLike: TypeAlias = np.ndarray | JaxArray
|
|
19
|
+
else:
|
|
20
|
+
ArrayLike: TypeAlias = np.ndarray
|
|
21
|
+
|
|
11
22
|
|
|
12
|
-
def make_point_data_displacement(
|
|
23
|
+
def make_point_data_displacement(
|
|
24
|
+
mesh: BaseMesh,
|
|
25
|
+
space: FESpace,
|
|
26
|
+
u: ArrayLike,
|
|
27
|
+
*,
|
|
28
|
+
compute_j: bool = True,
|
|
29
|
+
deformed_scale: float = 1.0,
|
|
30
|
+
) -> dict[str, np.ndarray]:
|
|
13
31
|
"""
|
|
14
32
|
Common postprocess helper to build point data dictionaries:
|
|
15
33
|
- displacement
|
|
@@ -58,14 +76,22 @@ def make_point_data_displacement(mesh, space, u, *, compute_j: bool = True, defo
|
|
|
58
76
|
return point_data
|
|
59
77
|
|
|
60
78
|
|
|
61
|
-
def write_point_data_vtu(
|
|
79
|
+
def write_point_data_vtu(
|
|
80
|
+
mesh: BaseMesh,
|
|
81
|
+
space: FESpace,
|
|
82
|
+
u: ArrayLike,
|
|
83
|
+
filepath: str,
|
|
84
|
+
*,
|
|
85
|
+
compute_j: bool = True,
|
|
86
|
+
deformed_scale: float = 1.0,
|
|
87
|
+
) -> None:
|
|
62
88
|
"""Write VTU with displacement/deformed_coords and optional J."""
|
|
63
89
|
pdata = make_point_data_displacement(mesh, space, u, compute_j=compute_j, deformed_scale=deformed_scale)
|
|
64
90
|
write_vtu(mesh, filepath, point_data=pdata)
|
|
65
91
|
|
|
66
92
|
|
|
67
93
|
__all__ = ["make_point_data_displacement", "write_point_data_vtu", "interpolate_at_points"]
|
|
68
|
-
def interpolate_at_points(space, u, points: np.ndarray):
|
|
94
|
+
def interpolate_at_points(space: FESpace, u: ArrayLike, points: np.ndarray) -> np.ndarray:
|
|
69
95
|
"""
|
|
70
96
|
Interpolate displacement field at given physical points (Hex8 only, structured search).
|
|
71
97
|
- points: (m,3) array of physical coordinates.
|
fluxfem/solver/__init__.py
CHANGED
|
@@ -24,8 +24,10 @@ from .dirichlet import (
|
|
|
24
24
|
from .cg import cg_solve, cg_solve_jax, build_cg_operator, CGOperator
|
|
25
25
|
from .preconditioner import make_block_jacobi_preconditioner
|
|
26
26
|
from .block_system import build_block_system, split_block_matrix, BlockSystem
|
|
27
|
-
from .block_matrix import diag as block_diag, make as make_block_matrix
|
|
27
|
+
from .block_matrix import FluxBlockMatrix, diag as block_diag, make as make_block_matrix
|
|
28
28
|
from .newton import newton_solve
|
|
29
|
+
from .result import SolverResult
|
|
30
|
+
from .history import NewtonIterRecord
|
|
29
31
|
from .solve_runner import (
|
|
30
32
|
NonlinearAnalysis,
|
|
31
33
|
NewtonLoopConfig,
|
|
@@ -68,9 +70,12 @@ __all__ = [
|
|
|
68
70
|
"build_block_system",
|
|
69
71
|
"split_block_matrix",
|
|
70
72
|
"BlockSystem",
|
|
73
|
+
"FluxBlockMatrix",
|
|
71
74
|
"block_diag",
|
|
72
75
|
"make_block_matrix",
|
|
73
76
|
"newton_solve",
|
|
77
|
+
"SolverResult",
|
|
78
|
+
"NewtonIterRecord",
|
|
74
79
|
"LinearAnalysis",
|
|
75
80
|
"LinearSolveConfig",
|
|
76
81
|
"LinearStepResult",
|