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/mortar.py
CHANGED
|
@@ -3,13 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
|
-
from typing import Iterable, TYPE_CHECKING
|
|
6
|
+
from typing import Any, Callable, Iterable, Sequence, TYPE_CHECKING, cast
|
|
7
7
|
|
|
8
8
|
import jax
|
|
9
9
|
import jax.numpy as jnp
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
12
|
from .surface import SurfaceMesh
|
|
13
|
+
from ..core.forms import FormFieldLike
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from ..core.forms import FieldPair
|
|
15
16
|
from ..core.weakform import Params as WeakParams
|
|
@@ -54,7 +55,7 @@ _DEBUG_CONTACT_PROJ_ONCE = False
|
|
|
54
55
|
_DEBUG_PROJ_QP_CACHE = None
|
|
55
56
|
_DEBUG_PROJ_QP_SOURCE = None
|
|
56
57
|
_DEBUG_PROJ_QP_DUMPED = False
|
|
57
|
-
_PROJ_DIAG_STATS = None
|
|
58
|
+
_PROJ_DIAG_STATS: dict[str, Any] | None = None
|
|
58
59
|
_PROJ_DIAG_COUNT = 0
|
|
59
60
|
_PROJ_DIAG_CONTEXT: dict[str, int | str] = {}
|
|
60
61
|
|
|
@@ -139,6 +140,8 @@ def _quad_quadrature(order: int) -> tuple[np.ndarray, np.ndarray]:
|
|
|
139
140
|
order = 2
|
|
140
141
|
n = int(np.ceil((order + 1.0) / 2.0))
|
|
141
142
|
x1d, w1d = np.polynomial.legendre.leggauss(n)
|
|
143
|
+
X: np.ndarray
|
|
144
|
+
Y: np.ndarray
|
|
142
145
|
X, Y = np.meshgrid(x1d, x1d, indexing="xy")
|
|
143
146
|
W = np.outer(w1d, w1d)
|
|
144
147
|
pts = np.stack([X.ravel(), Y.ravel()], axis=1)
|
|
@@ -360,7 +363,7 @@ def _proj_diag_log(
|
|
|
360
363
|
if _PROJ_DIAG_STATS is None:
|
|
361
364
|
return
|
|
362
365
|
_PROJ_DIAG_STATS["fail"] += 1
|
|
363
|
-
by_code = _PROJ_DIAG_STATS["by_code"]
|
|
366
|
+
by_code = cast(dict[str, int], _PROJ_DIAG_STATS["by_code"])
|
|
364
367
|
by_code[code] = by_code.get(code, 0) + 1
|
|
365
368
|
if _PROJ_DIAG_COUNT >= _proj_diag_max():
|
|
366
369
|
return
|
|
@@ -534,7 +537,7 @@ def _barycentric(p: np.ndarray, a: np.ndarray, b: np.ndarray, c: np.ndarray):
|
|
|
534
537
|
|
|
535
538
|
|
|
536
539
|
def _point_in_tri(lam: np.ndarray, *, tol: float) -> bool:
|
|
537
|
-
return np.all(lam >= -tol) and np.all(lam <= 1.0 + tol)
|
|
540
|
+
return bool(np.all(lam >= -tol) and np.all(lam <= 1.0 + tol))
|
|
538
541
|
|
|
539
542
|
|
|
540
543
|
def _plane_basis(pts: np.ndarray, *, tol: float):
|
|
@@ -978,13 +981,13 @@ def map_surface_facets_to_tet_elements(surface: SurfaceMesh, tet_conn: np.ndarra
|
|
|
978
981
|
"""
|
|
979
982
|
Map surface triangle facets to parent tet elements by node matching (tet4/tet10).
|
|
980
983
|
"""
|
|
981
|
-
face_patterns_corner = [
|
|
984
|
+
face_patterns_corner: list[tuple[int, ...]] = [
|
|
982
985
|
(0, 1, 2),
|
|
983
986
|
(0, 1, 3),
|
|
984
987
|
(0, 2, 3),
|
|
985
988
|
(1, 2, 3),
|
|
986
989
|
]
|
|
987
|
-
face_patterns_quad = [
|
|
990
|
+
face_patterns_quad: list[tuple[int, ...]] = [
|
|
988
991
|
(0, 1, 2, 4, 5, 6),
|
|
989
992
|
(0, 1, 3, 4, 8, 7),
|
|
990
993
|
(0, 2, 3, 6, 9, 7),
|
|
@@ -997,7 +1000,7 @@ def map_surface_facets_to_tet_elements(surface: SurfaceMesh, tet_conn: np.ndarra
|
|
|
997
1000
|
mapping_quad: dict[tuple[int, ...], int] = {}
|
|
998
1001
|
for e_id, elem in enumerate(tet_conn):
|
|
999
1002
|
for pattern in face_patterns_corner:
|
|
1000
|
-
face_nodes = tuple(sorted(int(elem[i]) for i in pattern))
|
|
1003
|
+
face_nodes: tuple[int, ...] = tuple(sorted(int(elem[i]) for i in pattern))
|
|
1001
1004
|
mapping_corner.setdefault(face_nodes, e_id)
|
|
1002
1005
|
if elem.shape[0] == 10:
|
|
1003
1006
|
for pattern in face_patterns_quad:
|
|
@@ -1022,7 +1025,7 @@ def map_surface_facets_to_hex_elements(surface: SurfaceMesh, hex_conn: np.ndarra
|
|
|
1022
1025
|
hex_conn = np.asarray(hex_conn, dtype=int)
|
|
1023
1026
|
if hex_conn.shape[1] not in {8, 20, 27}:
|
|
1024
1027
|
raise NotImplementedError("Only hex8/hex20/hex27 are supported.")
|
|
1025
|
-
face_patterns_corner = [
|
|
1028
|
+
face_patterns_corner: list[tuple[int, ...]] = [
|
|
1026
1029
|
(0, 1, 2, 3),
|
|
1027
1030
|
(4, 5, 6, 7),
|
|
1028
1031
|
(0, 1, 5, 4),
|
|
@@ -1030,7 +1033,7 @@ def map_surface_facets_to_hex_elements(surface: SurfaceMesh, hex_conn: np.ndarra
|
|
|
1030
1033
|
(2, 3, 7, 6),
|
|
1031
1034
|
(3, 0, 4, 7),
|
|
1032
1035
|
]
|
|
1033
|
-
face_patterns_corner27 = [
|
|
1036
|
+
face_patterns_corner27: list[tuple[int, ...]] = [
|
|
1034
1037
|
(0, 2, 8, 6),
|
|
1035
1038
|
(18, 20, 26, 24),
|
|
1036
1039
|
(0, 2, 20, 18),
|
|
@@ -1038,7 +1041,7 @@ def map_surface_facets_to_hex_elements(surface: SurfaceMesh, hex_conn: np.ndarra
|
|
|
1038
1041
|
(0, 6, 24, 18),
|
|
1039
1042
|
(2, 8, 26, 20),
|
|
1040
1043
|
]
|
|
1041
|
-
face_patterns_quad = [
|
|
1044
|
+
face_patterns_quad: list[tuple[int, ...]] = [
|
|
1042
1045
|
(0, 1, 2, 3, 8, 9, 10, 11),
|
|
1043
1046
|
(4, 5, 6, 7, 12, 13, 14, 15),
|
|
1044
1047
|
(0, 1, 5, 4, 8, 17, 12, 16),
|
|
@@ -1046,7 +1049,7 @@ def map_surface_facets_to_hex_elements(surface: SurfaceMesh, hex_conn: np.ndarra
|
|
|
1046
1049
|
(2, 3, 7, 6, 10, 19, 14, 18),
|
|
1047
1050
|
(3, 0, 4, 7, 11, 16, 15, 19),
|
|
1048
1051
|
]
|
|
1049
|
-
face_patterns_quad9 = [
|
|
1052
|
+
face_patterns_quad9: list[tuple[int, ...]] = [
|
|
1050
1053
|
(0, 1, 2, 3, 4, 5, 6, 7, 8),
|
|
1051
1054
|
(18, 19, 20, 21, 22, 23, 24, 25, 26),
|
|
1052
1055
|
(0, 1, 2, 9, 10, 11, 18, 19, 20),
|
|
@@ -1062,7 +1065,7 @@ def map_surface_facets_to_hex_elements(surface: SurfaceMesh, hex_conn: np.ndarra
|
|
|
1062
1065
|
else:
|
|
1063
1066
|
corner_patterns = face_patterns_corner
|
|
1064
1067
|
for pattern in corner_patterns:
|
|
1065
|
-
face_nodes = tuple(sorted(int(elem[i]) for i in pattern))
|
|
1068
|
+
face_nodes: tuple[int, ...] = tuple(sorted(int(elem[i]) for i in pattern))
|
|
1066
1069
|
mapping_corner.setdefault(face_nodes, e_id)
|
|
1067
1070
|
if elem.shape[0] == 20:
|
|
1068
1071
|
for pattern in face_patterns_quad:
|
|
@@ -2186,7 +2189,13 @@ def assemble_mixed_surface_residual(
|
|
|
2186
2189
|
if offset_b is None:
|
|
2187
2190
|
offset_b = offset_a + n_a
|
|
2188
2191
|
n_total = int(offset_b + n_b)
|
|
2189
|
-
R = np.zeros((n_total,), dtype=float)
|
|
2192
|
+
R: np.ndarray = np.zeros((n_total,), dtype=float)
|
|
2193
|
+
|
|
2194
|
+
trace = os.getenv("FLUXFEM_MORTAR_TRACE", "0") not in ("0", "", "false", "False")
|
|
2195
|
+
|
|
2196
|
+
def _trace_time(msg: str, t0: float) -> None:
|
|
2197
|
+
if trace:
|
|
2198
|
+
print(f"{msg} dt={time.perf_counter() - t0:.3e}s", flush=True)
|
|
2190
2199
|
|
|
2191
2200
|
t_norm = time.perf_counter()
|
|
2192
2201
|
normals_a = None
|
|
@@ -2213,6 +2222,12 @@ def assemble_mixed_surface_residual(
|
|
|
2213
2222
|
|
|
2214
2223
|
use_elem_a = elem_conn_a is not None and facet_to_elem_a is not None
|
|
2215
2224
|
use_elem_b = elem_conn_b is not None and facet_to_elem_b is not None
|
|
2225
|
+
if use_elem_a:
|
|
2226
|
+
assert elem_conn_a is not None
|
|
2227
|
+
assert facet_to_elem_a is not None
|
|
2228
|
+
if use_elem_b:
|
|
2229
|
+
assert elem_conn_b is not None
|
|
2230
|
+
assert facet_to_elem_b is not None
|
|
2216
2231
|
|
|
2217
2232
|
if grad_source not in {"volume", "surface"}:
|
|
2218
2233
|
raise ValueError("grad_source must be 'volume' or 'surface'")
|
|
@@ -2295,8 +2310,8 @@ def assemble_mixed_surface_residual(
|
|
|
2295
2310
|
basis=_SurfaceBasis(dofs_per_node=value_dim_b),
|
|
2296
2311
|
)
|
|
2297
2312
|
fields = {
|
|
2298
|
-
field_a: FieldPair(test=field_a_obj, trial=field_a_obj),
|
|
2299
|
-
field_b: FieldPair(test=field_b_obj, trial=field_b_obj),
|
|
2313
|
+
field_a: FieldPair(test=cast("FormFieldLike", field_a_obj), trial=cast("FormFieldLike", field_a_obj)),
|
|
2314
|
+
field_b: FieldPair(test=cast("FormFieldLike", field_b_obj), trial=cast("FormFieldLike", field_b_obj)),
|
|
2300
2315
|
}
|
|
2301
2316
|
ctx = SurfaceMixedFormContext(
|
|
2302
2317
|
fields=fields,
|
|
@@ -2372,6 +2387,8 @@ def assemble_mixed_surface_residual(
|
|
|
2372
2387
|
elem_nodes_a = None
|
|
2373
2388
|
elem_coords_a = None
|
|
2374
2389
|
if use_elem_a:
|
|
2390
|
+
assert elem_conn_a is not None
|
|
2391
|
+
assert facet_to_elem_a is not None
|
|
2375
2392
|
elem_id_a = int(facet_to_elem_a[int(fa)])
|
|
2376
2393
|
if elem_id_a < 0:
|
|
2377
2394
|
raise ValueError("facet_to_elem_a has invalid mapping")
|
|
@@ -2384,6 +2401,8 @@ def assemble_mixed_surface_residual(
|
|
|
2384
2401
|
elem_nodes_b = None
|
|
2385
2402
|
elem_coords_b = None
|
|
2386
2403
|
if use_elem_b:
|
|
2404
|
+
assert elem_conn_b is not None
|
|
2405
|
+
assert facet_to_elem_b is not None
|
|
2387
2406
|
elem_id_b = int(facet_to_elem_b[int(fb)])
|
|
2388
2407
|
if elem_id_b < 0:
|
|
2389
2408
|
raise ValueError("facet_to_elem_b has invalid mapping")
|
|
@@ -2411,10 +2430,14 @@ def assemble_mixed_surface_residual(
|
|
|
2411
2430
|
dtype=float,
|
|
2412
2431
|
)
|
|
2413
2432
|
if use_elem_a and grad_source == "volume":
|
|
2433
|
+
assert elem_nodes_a is not None
|
|
2434
|
+
assert elem_coords_a is not None
|
|
2414
2435
|
local = _local_indices(elem_nodes_a, facet_a)
|
|
2415
2436
|
gradNa = _tet_gradN_at_points(x_q, elem_coords_a, local=local, tol=tol)
|
|
2416
2437
|
|
|
2417
2438
|
if use_elem_b and grad_source == "volume":
|
|
2439
|
+
assert elem_nodes_b is not None
|
|
2440
|
+
assert elem_coords_b is not None
|
|
2418
2441
|
local = _local_indices(elem_nodes_b, facet_b)
|
|
2419
2442
|
gradNb = _tet_gradN_at_points(x_q, elem_coords_b, local=local, tol=tol)
|
|
2420
2443
|
|
|
@@ -2492,8 +2515,8 @@ def assemble_mixed_surface_residual(
|
|
|
2492
2515
|
basis=_SurfaceBasis(dofs_per_node=value_dim_b),
|
|
2493
2516
|
)
|
|
2494
2517
|
fields = {
|
|
2495
|
-
field_a: FieldPair(test=field_a_obj, trial=field_a_obj),
|
|
2496
|
-
field_b: FieldPair(test=field_b_obj, trial=field_b_obj),
|
|
2518
|
+
field_a: FieldPair(test=cast("FormFieldLike", field_a_obj), trial=cast("FormFieldLike", field_a_obj)),
|
|
2519
|
+
field_b: FieldPair(test=cast("FormFieldLike", field_b_obj), trial=cast("FormFieldLike", field_b_obj)),
|
|
2497
2520
|
}
|
|
2498
2521
|
normal_q = None if normal is None else np.repeat(normal[None, :], quad_pts.shape[0], axis=0)
|
|
2499
2522
|
ctx = SurfaceMixedFormContext(
|
|
@@ -2575,6 +2598,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
2575
2598
|
to pick which field acts as the master when normal_source is "master"/"slave".
|
|
2576
2599
|
dof_source="volume" assembles into element nodes (requires elem_conn_* mappings).
|
|
2577
2600
|
"""
|
|
2601
|
+
source_facets_a = list(source_facets_a)
|
|
2602
|
+
source_facets_b = list(source_facets_b)
|
|
2578
2603
|
from ..core.forms import FieldPair
|
|
2579
2604
|
_mortar_dbg(
|
|
2580
2605
|
f"[mortar] enter assemble_mixed_surface_jacobian quad_order={quad_order} backend={backend}"
|
|
@@ -2648,7 +2673,7 @@ def assemble_mixed_surface_jacobian(
|
|
|
2648
2673
|
rows: list[int] = []
|
|
2649
2674
|
cols: list[int] = []
|
|
2650
2675
|
data: list[float] = []
|
|
2651
|
-
K_dense = np.zeros((n_total, n_total), dtype=float) if not sparse else None
|
|
2676
|
+
K_dense: np.ndarray | None = np.zeros((n_total, n_total), dtype=float) if not sparse else None
|
|
2652
2677
|
|
|
2653
2678
|
use_elem_a = elem_conn_a is not None and facet_to_elem_a is not None
|
|
2654
2679
|
use_elem_b = elem_conn_b is not None and facet_to_elem_b is not None
|
|
@@ -2746,8 +2771,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
2746
2771
|
basis=_SurfaceBasis(dofs_per_node=value_dim_b),
|
|
2747
2772
|
)
|
|
2748
2773
|
fields = {
|
|
2749
|
-
field_a: FieldPair(test=field_a_obj, trial=field_a_obj),
|
|
2750
|
-
field_b: FieldPair(test=field_b_obj, trial=field_b_obj),
|
|
2774
|
+
field_a: FieldPair(test=cast("FormFieldLike", field_a_obj), trial=cast("FormFieldLike", field_a_obj)),
|
|
2775
|
+
field_b: FieldPair(test=cast("FormFieldLike", field_b_obj), trial=cast("FormFieldLike", field_b_obj)),
|
|
2751
2776
|
}
|
|
2752
2777
|
ctx = SurfaceMixedFormContext(
|
|
2753
2778
|
fields=fields,
|
|
@@ -2831,6 +2856,7 @@ def assemble_mixed_surface_jacobian(
|
|
|
2831
2856
|
cols.append(int(gj))
|
|
2832
2857
|
data.append(val)
|
|
2833
2858
|
else:
|
|
2859
|
+
assert K_dense is not None
|
|
2834
2860
|
K_dense[int(gi), int(gj)] += val
|
|
2835
2861
|
if sparse:
|
|
2836
2862
|
return np.asarray(rows, dtype=int), np.asarray(cols, dtype=int), np.asarray(data, dtype=float), n_total
|
|
@@ -2881,8 +2907,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
2881
2907
|
basis=_SurfaceBasis(dofs_per_node=value_dim_b),
|
|
2882
2908
|
)
|
|
2883
2909
|
fields = {
|
|
2884
|
-
field_a: FieldPair(test=field_a_obj, trial=field_a_obj),
|
|
2885
|
-
field_b: FieldPair(test=field_b_obj, trial=field_b_obj),
|
|
2910
|
+
field_a: FieldPair(test=cast("FormFieldLike", field_a_obj), trial=cast("FormFieldLike", field_a_obj)),
|
|
2911
|
+
field_b: FieldPair(test=cast("FormFieldLike", field_b_obj), trial=cast("FormFieldLike", field_b_obj)),
|
|
2886
2912
|
}
|
|
2887
2913
|
normal_q = jnp.repeat(normal[None, :], x_q.shape[0], axis=0)
|
|
2888
2914
|
ctx = SurfaceMixedFormContext(
|
|
@@ -2916,7 +2942,7 @@ def assemble_mixed_surface_jacobian(
|
|
|
2916
2942
|
jac_fun = jax.vmap(jax.jacrev(_res_local_batch))
|
|
2917
2943
|
return jax.jit(jac_fun) if jit_batch else jac_fun
|
|
2918
2944
|
|
|
2919
|
-
jac_fun_cache: dict[tuple[int, int],
|
|
2945
|
+
jac_fun_cache: dict[tuple[int, int], Callable[..., jnp.ndarray]] = {}
|
|
2920
2946
|
|
|
2921
2947
|
def _emit_batch(
|
|
2922
2948
|
Na_b,
|
|
@@ -2999,9 +3025,13 @@ def assemble_mixed_surface_jacobian(
|
|
|
2999
3025
|
facet_b = facets_b[int(fb)]
|
|
3000
3026
|
x_q = np.array([a + r * (b - a) + s * (c - a) for r, s in quad_pts], dtype=float)
|
|
3001
3027
|
|
|
3028
|
+
assert facet_to_elem_a is not None
|
|
3029
|
+
assert elem_conn_a is not None
|
|
3002
3030
|
elem_id_a = int(facet_to_elem_a[int(fa)])
|
|
3003
3031
|
elem_nodes_a = np.asarray(elem_conn_a[elem_id_a], dtype=int)
|
|
3004
3032
|
elem_coords_a = coords_a[elem_nodes_a]
|
|
3033
|
+
assert facet_to_elem_b is not None
|
|
3034
|
+
assert elem_conn_b is not None
|
|
3005
3035
|
elem_id_b = int(facet_to_elem_b[int(fb)])
|
|
3006
3036
|
elem_nodes_b = np.asarray(elem_conn_b[elem_id_b], dtype=int)
|
|
3007
3037
|
elem_coords_b = coords_b[elem_nodes_b]
|
|
@@ -3072,6 +3102,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3072
3102
|
normal_b = jnp.asarray(np.stack(normal_b, axis=0))
|
|
3073
3103
|
u_local_b = jnp.asarray(np.stack(u_local_batch, axis=0))
|
|
3074
3104
|
dofs_batch_np = np.asarray(dofs_batch, dtype=int)
|
|
3105
|
+
assert n_a_local_const is not None
|
|
3106
|
+
assert n_b_local_const is not None
|
|
3075
3107
|
_emit_batch(
|
|
3076
3108
|
Na_b,
|
|
3077
3109
|
Nb_b,
|
|
@@ -3108,6 +3140,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3108
3140
|
normal_b = jnp.asarray(np.stack(normal_b, axis=0))
|
|
3109
3141
|
u_local_b = jnp.asarray(np.stack(u_local_batch, axis=0))
|
|
3110
3142
|
dofs_batch_np = np.asarray(dofs_batch, dtype=int)
|
|
3143
|
+
assert n_a_local_const is not None
|
|
3144
|
+
assert n_b_local_const is not None
|
|
3111
3145
|
_emit_batch(
|
|
3112
3146
|
Na_b,
|
|
3113
3147
|
Nb_b,
|
|
@@ -3139,6 +3173,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3139
3173
|
normal_b = jnp.asarray(np.stack(normal_b, axis=0))
|
|
3140
3174
|
u_local_b = jnp.asarray(np.stack(u_local_batch, axis=0))
|
|
3141
3175
|
dofs_batch_np = np.asarray(dofs_batch, dtype=int)
|
|
3176
|
+
assert n_a_local_const is not None
|
|
3177
|
+
assert n_b_local_const is not None
|
|
3142
3178
|
_emit_batch(
|
|
3143
3179
|
Na_b,
|
|
3144
3180
|
Nb_b,
|
|
@@ -3158,14 +3194,14 @@ def assemble_mixed_surface_jacobian(
|
|
|
3158
3194
|
if not batch_failed and (batch_rows or (not sparse and K_dense is not None)):
|
|
3159
3195
|
if sparse:
|
|
3160
3196
|
if batch_rows:
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3197
|
+
rows_np = np.concatenate(batch_rows)
|
|
3198
|
+
cols_np = np.concatenate(batch_cols)
|
|
3199
|
+
data_np = np.concatenate(batch_data)
|
|
3164
3200
|
else:
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
return
|
|
3201
|
+
rows_np = np.zeros((0,), dtype=int)
|
|
3202
|
+
cols_np = np.zeros((0,), dtype=int)
|
|
3203
|
+
data_np = np.zeros((0,), dtype=float)
|
|
3204
|
+
return rows_np, cols_np, data_np, n_total
|
|
3169
3205
|
assert K_dense is not None
|
|
3170
3206
|
return K_dense
|
|
3171
3207
|
|
|
@@ -3250,6 +3286,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3250
3286
|
elem_coords_a = None
|
|
3251
3287
|
local_a = None
|
|
3252
3288
|
if use_elem_a:
|
|
3289
|
+
assert elem_conn_a is not None
|
|
3290
|
+
assert facet_to_elem_a is not None
|
|
3253
3291
|
elem_id_a = int(facet_to_elem_a[int(fa)])
|
|
3254
3292
|
if elem_id_a < 0:
|
|
3255
3293
|
raise ValueError("facet_to_elem_a has invalid mapping")
|
|
@@ -3263,6 +3301,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3263
3301
|
elem_coords_b = None
|
|
3264
3302
|
local_b = None
|
|
3265
3303
|
if use_elem_b:
|
|
3304
|
+
assert elem_conn_b is not None
|
|
3305
|
+
assert facet_to_elem_b is not None
|
|
3266
3306
|
elem_id_b = int(facet_to_elem_b[int(fb)])
|
|
3267
3307
|
if elem_id_b < 0:
|
|
3268
3308
|
raise ValueError("facet_to_elem_b has invalid mapping")
|
|
@@ -3291,10 +3331,14 @@ def assemble_mixed_surface_jacobian(
|
|
|
3291
3331
|
dtype=float,
|
|
3292
3332
|
)
|
|
3293
3333
|
if use_elem_a and grad_source == "volume":
|
|
3334
|
+
assert elem_nodes_a is not None
|
|
3335
|
+
assert elem_coords_a is not None
|
|
3294
3336
|
local_a = _local_indices(elem_nodes_a, facet_a)
|
|
3295
3337
|
gradNa = _tet_gradN_at_points(x_q, elem_coords_a, local=local_a, tol=tol)
|
|
3296
3338
|
|
|
3297
3339
|
if use_elem_b and grad_source == "volume":
|
|
3340
|
+
assert elem_nodes_b is not None
|
|
3341
|
+
assert elem_coords_b is not None
|
|
3298
3342
|
local_b = _local_indices(elem_nodes_b, facet_b)
|
|
3299
3343
|
gradNb = _tet_gradN_at_points(x_q, elem_coords_b, local=local_b, tol=tol)
|
|
3300
3344
|
|
|
@@ -3325,8 +3369,16 @@ def assemble_mixed_surface_jacobian(
|
|
|
3325
3369
|
|
|
3326
3370
|
global _DEBUG_CONTACT_MAP_ONCE
|
|
3327
3371
|
if diag_map and not _DEBUG_CONTACT_MAP_ONCE:
|
|
3328
|
-
|
|
3329
|
-
|
|
3372
|
+
if use_elem_a:
|
|
3373
|
+
assert facet_to_elem_a is not None
|
|
3374
|
+
elem_id_a = int(facet_to_elem_a[int(fa)])
|
|
3375
|
+
else:
|
|
3376
|
+
elem_id_a = -1
|
|
3377
|
+
if use_elem_b:
|
|
3378
|
+
assert facet_to_elem_b is not None
|
|
3379
|
+
elem_id_b = int(facet_to_elem_b[int(fb)])
|
|
3380
|
+
else:
|
|
3381
|
+
elem_id_b = -1
|
|
3330
3382
|
print("[fluxfem][diag][contact-map] first facet")
|
|
3331
3383
|
print(f" fa={int(fa)} fb={int(fb)} elem_a={elem_id_a} elem_b={elem_id_b}")
|
|
3332
3384
|
print(f" facet_nodes_a={facet_a.tolist()}")
|
|
@@ -3389,8 +3441,8 @@ def assemble_mixed_surface_jacobian(
|
|
|
3389
3441
|
basis=_SurfaceBasis(dofs_per_node=value_dim_b),
|
|
3390
3442
|
)
|
|
3391
3443
|
fields = {
|
|
3392
|
-
field_a: FieldPair(test=field_a_obj, trial=field_a_obj),
|
|
3393
|
-
field_b: FieldPair(test=field_b_obj, trial=field_b_obj),
|
|
3444
|
+
field_a: FieldPair(test=cast("FormFieldLike", field_a_obj), trial=cast("FormFieldLike", field_a_obj)),
|
|
3445
|
+
field_b: FieldPair(test=cast("FormFieldLike", field_b_obj), trial=cast("FormFieldLike", field_b_obj)),
|
|
3394
3446
|
}
|
|
3395
3447
|
normal_q = None if normal is None else np.repeat(normal[None, :], quad_pts.shape[0], axis=0)
|
|
3396
3448
|
ctx = SurfaceMixedFormContext(
|
|
@@ -3501,6 +3553,7 @@ def assemble_mixed_surface_jacobian(
|
|
|
3501
3553
|
_trace_time(f"[CONTACT] tri {it} fd_block r_m", t_rm)
|
|
3502
3554
|
cols = (r_p - r_m) / (2.0 * fd_eps)
|
|
3503
3555
|
else:
|
|
3556
|
+
assert r0 is not None
|
|
3504
3557
|
cols = (r_p - r0[:, None]) / fd_eps
|
|
3505
3558
|
J_local_np[:, idxs] = np.asarray(cols, dtype=float)
|
|
3506
3559
|
if log_tri:
|
|
@@ -3543,6 +3596,7 @@ def assemble_mixed_surface_jacobian(
|
|
|
3543
3596
|
cols.extend(np.tile(dofs, n_ldofs).tolist())
|
|
3544
3597
|
data.extend(J_local_np.reshape(-1).tolist())
|
|
3545
3598
|
else:
|
|
3599
|
+
assert K_dense is not None
|
|
3546
3600
|
K_dense[np.ix_(dofs, dofs)] += J_local_np
|
|
3547
3601
|
if log_tri:
|
|
3548
3602
|
_trace_time(f"[CONTACT] tri {it} scatter_done", t_scatter)
|
|
@@ -3591,8 +3645,8 @@ def assemble_onesided_bilinear(
|
|
|
3591
3645
|
coords_m = np.asarray(surface_master.coords, dtype=float) if surface_master is not None else coords_s
|
|
3592
3646
|
facets_m = np.asarray(surface_master.conn, dtype=int) if surface_master is not None else facets_s
|
|
3593
3647
|
n_s = int(coords_s.shape[0] * value_dim)
|
|
3594
|
-
K = np.zeros((n_s, n_s), dtype=float)
|
|
3595
|
-
f = np.zeros((n_s,), dtype=float)
|
|
3648
|
+
K: np.ndarray = np.zeros((n_s, n_s), dtype=float)
|
|
3649
|
+
f: np.ndarray = np.zeros((n_s,), dtype=float)
|
|
3596
3650
|
|
|
3597
3651
|
normals_s = surface_slave.facet_normals() if hasattr(surface_slave, "facet_normals") else None
|
|
3598
3652
|
use_elem = elem_conn is not None and facet_to_elem is not None
|
|
@@ -3654,6 +3708,8 @@ def assemble_onesided_bilinear(
|
|
|
3654
3708
|
elem_coords = None
|
|
3655
3709
|
local = None
|
|
3656
3710
|
if use_elem:
|
|
3711
|
+
assert facet_to_elem is not None
|
|
3712
|
+
assert elem_conn is not None
|
|
3657
3713
|
elem_id = int(facet_to_elem[int(f_id)])
|
|
3658
3714
|
if elem_id < 0:
|
|
3659
3715
|
raise ValueError("facet_to_elem has invalid mapping")
|
|
@@ -3668,6 +3724,7 @@ def assemble_onesided_bilinear(
|
|
|
3668
3724
|
x_q = np.array([a + r * (b - a) + s * (c - a) for r, s in quad_pts], dtype=float)
|
|
3669
3725
|
if use_master:
|
|
3670
3726
|
if dof_source == "surface":
|
|
3727
|
+
assert u_master is not None
|
|
3671
3728
|
facet_m = facets_m[int(f_id)]
|
|
3672
3729
|
u_master_local = _gather_u_local(u_master, facet_m, value_dim).reshape(-1, value_dim)
|
|
3673
3730
|
N_master = np.array(
|
|
@@ -3676,6 +3733,9 @@ def assemble_onesided_bilinear(
|
|
|
3676
3733
|
)
|
|
3677
3734
|
u_hat = N_master @ u_master_local
|
|
3678
3735
|
else:
|
|
3736
|
+
assert u_master is not None
|
|
3737
|
+
assert facet_to_elem_master is not None
|
|
3738
|
+
assert elem_conn_master is not None
|
|
3679
3739
|
elem_id_m = int(facet_to_elem_master[int(f_id)])
|
|
3680
3740
|
if elem_id_m < 0:
|
|
3681
3741
|
raise ValueError("facet_to_elem_master has invalid mapping")
|
|
@@ -3699,6 +3759,8 @@ def assemble_onesided_bilinear(
|
|
|
3699
3759
|
dtype=float,
|
|
3700
3760
|
)
|
|
3701
3761
|
if use_elem and grad_source == "volume":
|
|
3762
|
+
assert elem_nodes is not None
|
|
3763
|
+
assert elem_coords is not None
|
|
3702
3764
|
local = _local_indices(elem_nodes, facet)
|
|
3703
3765
|
gradN = _tet_gradN_at_points(x_q, elem_coords, local=local, tol=tol)
|
|
3704
3766
|
|
|
@@ -3718,7 +3780,7 @@ def assemble_onesided_bilinear(
|
|
|
3718
3780
|
value_dim=value_dim,
|
|
3719
3781
|
basis=_SurfaceBasis(dofs_per_node=value_dim),
|
|
3720
3782
|
)
|
|
3721
|
-
fields = {"u": FieldPair(test=field, trial=field)}
|
|
3783
|
+
fields = {"u": FieldPair(test=cast("FormFieldLike", field), trial=cast("FormFieldLike", field))}
|
|
3722
3784
|
normal = normals_s[int(f_id)] if normals_s is not None else None
|
|
3723
3785
|
if normal is not None:
|
|
3724
3786
|
normal = normal_sign * normal
|
|
@@ -3740,7 +3802,7 @@ def assemble_onesided_bilinear(
|
|
|
3740
3802
|
inv_h=inv_h,
|
|
3741
3803
|
u_hat=u_hat,
|
|
3742
3804
|
)
|
|
3743
|
-
u_zero = np.zeros((len(nodes) * value_dim,), dtype=float)
|
|
3805
|
+
u_zero: np.ndarray = np.zeros((len(nodes) * value_dim,), dtype=float)
|
|
3744
3806
|
u_dict = {"u": u_zero}
|
|
3745
3807
|
sizes = (u_zero.shape[0],)
|
|
3746
3808
|
slices = {"u": slice(0, sizes[0])}
|
|
@@ -3763,11 +3825,11 @@ def assemble_onesided_bilinear(
|
|
|
3763
3825
|
|
|
3764
3826
|
f_local = _res_local_np(u_zero)
|
|
3765
3827
|
n_ldofs = int(u_zero.shape[0])
|
|
3766
|
-
k_local = np.zeros((n_ldofs, n_ldofs), dtype=float)
|
|
3828
|
+
k_local: np.ndarray = np.zeros((n_ldofs, n_ldofs), dtype=float)
|
|
3767
3829
|
block = max(1, int(os.getenv("FLUXFEM_ONESIDE_BLOCK_SIZE", "16")))
|
|
3768
3830
|
for start in range(0, n_ldofs, block):
|
|
3769
|
-
idxs = np.arange(start, min(n_ldofs, start + block), dtype=int)
|
|
3770
|
-
u_block = np.zeros((n_ldofs, idxs.size), dtype=float)
|
|
3831
|
+
idxs: np.ndarray = np.arange(start, min(n_ldofs, start + block), dtype=int)
|
|
3832
|
+
u_block: np.ndarray = np.zeros((n_ldofs, idxs.size), dtype=float)
|
|
3771
3833
|
u_block[idxs, np.arange(idxs.size, dtype=int)] = 1.0
|
|
3772
3834
|
r_block = _res_local_np(u_block)
|
|
3773
3835
|
k_local[:, idxs] = r_block - f_local[:, None]
|
|
@@ -3815,8 +3877,8 @@ def assemble_contact_onesided_floor(
|
|
|
3815
3877
|
coords_s = np.asarray(surface_slave.coords, dtype=float)
|
|
3816
3878
|
facets_s = np.asarray(surface_slave.conn, dtype=int)
|
|
3817
3879
|
n_s = int(coords_s.shape[0] * value_dim)
|
|
3818
|
-
K = np.zeros((n_s, n_s), dtype=float)
|
|
3819
|
-
f = np.zeros((n_s,), dtype=float)
|
|
3880
|
+
K: np.ndarray = np.zeros((n_s, n_s), dtype=float)
|
|
3881
|
+
f: np.ndarray = np.zeros((n_s,), dtype=float)
|
|
3820
3882
|
|
|
3821
3883
|
normals_s = surface_slave.facet_normals() if hasattr(surface_slave, "facet_normals") else None
|
|
3822
3884
|
if n is not None:
|
|
@@ -3852,6 +3914,7 @@ def assemble_contact_onesided_floor(
|
|
|
3852
3914
|
if n is not None:
|
|
3853
3915
|
normal = n
|
|
3854
3916
|
else:
|
|
3917
|
+
assert normals_s is not None
|
|
3855
3918
|
normal = normal_sign * normals_s[int(f_id)]
|
|
3856
3919
|
|
|
3857
3920
|
for a, b, c_tri in triangles:
|