ANYsolver 0.1.0__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.
- anysolver/__init__.py +631 -0
- anysolver/anystructure_fem_mode.py +942 -0
- anysolver/arc_length.py +758 -0
- anysolver/assembly.py +950 -0
- anysolver/baselines.py +303 -0
- anysolver/beam_shell_verification.py +4276 -0
- anysolver/beam_validity.py +110 -0
- anysolver/benchmarks.py +495 -0
- anysolver/boundary.py +476 -0
- anysolver/buckling.py +442 -0
- anysolver/buckling_validity.py +91 -0
- anysolver/capacity_workflow.py +350 -0
- anysolver/cases.py +173 -0
- anysolver/composite_strip_verification.py +297 -0
- anysolver/contact.py +3461 -0
- anysolver/corotational.py +371 -0
- anysolver/cylinder_benchmarks.py +364 -0
- anysolver/dynamics.py +723 -0
- anysolver/element_qualification.py +432 -0
- anysolver/elements.py +2724 -0
- anysolver/external_references.py +369 -0
- anysolver/fe_core.py +327 -0
- anysolver/fracture.py +551 -0
- anysolver/imperfections.py +390 -0
- anysolver/jit_compiler.py +108 -0
- anysolver/kernel_warmup.py +180 -0
- anysolver/linalg.py +760 -0
- anysolver/mass_properties.py +179 -0
- anysolver/material_curves.py +231 -0
- anysolver/matrix_assembly.py +558 -0
- anysolver/mesh_gen.py +1065 -0
- anysolver/mesh_load_bc_verification.py +609 -0
- anysolver/modal.py +282 -0
- anysolver/nonlinear.py +300 -0
- anysolver/nonlinear_performance.py +920 -0
- anysolver/nonlinear_performance_batch_b.py +592 -0
- anysolver/nonlinear_performance_batch_c.py +506 -0
- anysolver/nonlinear_performance_bootstrap.py +120 -0
- anysolver/nonlinear_reduced_assembly.py +760 -0
- anysolver/nonlinear_static.py +1518 -0
- anysolver/plasticity.py +419 -0
- anysolver/plasticity_qualification.py +314 -0
- anysolver/production_readiness.py +304 -0
- anysolver/quality_control.py +1497 -0
- anysolver/recovery.py +505 -0
- anysolver/recovery_policy.py +205 -0
- anysolver/reference_cases.py +425 -0
- anysolver/results.py +503 -0
- anysolver/runtime.py +9030 -0
- anysolver/s4_validity.py +326 -0
- anysolver/sesam_fem/__init__.py +55 -0
- anysolver/sesam_fem/__main__.py +80 -0
- anysolver/sesam_fem/diagnostics.py +65 -0
- anysolver/sesam_fem/document.py +814 -0
- anysolver/sesam_fem/exporter.py +84 -0
- anysolver/sesam_fem/importer.py +365 -0
- anysolver/sesam_fem/records.py +257 -0
- anysolver/sesam_fem/schema.py +107 -0
- anysolver/sesam_fem/sif_importer.py +397 -0
- anysolver/sesam_fem/validation.py +62 -0
- anysolver/shell_benchmarks.py +367 -0
- anysolver/test_cases.py +610 -0
- anysolver/validation.py +416 -0
- anysolver/vectorized_nonlinear.py +334 -0
- anysolver/vectorized_stiffness.py +571 -0
- anysolver-0.1.0.dist-info/METADATA +165 -0
- anysolver-0.1.0.dist-info/RECORD +70 -0
- anysolver-0.1.0.dist-info/WHEEL +5 -0
- anysolver-0.1.0.dist-info/licenses/LICENSE +674 -0
- anysolver-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
"""Batch B optimizations for persistent nonlinear shell assembly.
|
|
2
|
+
|
|
3
|
+
This module completes the nonlinear-assembly redesign for ordinary Python runs
|
|
4
|
+
with Numba available. Elastic shell groups use a dedicated in-place kernel that
|
|
5
|
+
writes element force and tangent entries directly into the persistent assembly
|
|
6
|
+
plan buffers. The plastic shell path and all element formulations remain
|
|
7
|
+
unchanged.
|
|
8
|
+
|
|
9
|
+
The elastic path avoids, per Newton evaluation:
|
|
10
|
+
|
|
11
|
+
* plastic-strain and hardening-state work arrays,
|
|
12
|
+
* through-thickness layer-strain arrays,
|
|
13
|
+
* broadcast constitutive tensor batches,
|
|
14
|
+
* per-batch force/tangent result arrays, and
|
|
15
|
+
* Python reconstruction of new elastic state arrays.
|
|
16
|
+
|
|
17
|
+
The kernel relies on the shell transformation's existing 3x3 block-diagonal
|
|
18
|
+
layout (translations and rotations for every node). Each local 3x3 tangent
|
|
19
|
+
block is transformed in place, so no second dense element tangent batch is
|
|
20
|
+
required.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import time
|
|
26
|
+
from typing import Any, Dict, Mapping, Optional, Sequence, Tuple
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
from scipy import sparse
|
|
30
|
+
|
|
31
|
+
from .jit_compiler import njit, prange
|
|
32
|
+
from .elements import plane_stress_elastic_matrix
|
|
33
|
+
from . import nonlinear_performance as _performance
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@njit(cache=True, parallel=True)
|
|
37
|
+
def _elastic_shell_batch_into_buffers(
|
|
38
|
+
displacements: np.ndarray,
|
|
39
|
+
dof_mappings: np.ndarray,
|
|
40
|
+
T0_batch: np.ndarray,
|
|
41
|
+
B_m_batch: np.ndarray,
|
|
42
|
+
B_b_batch: np.ndarray,
|
|
43
|
+
B_d_batch: np.ndarray,
|
|
44
|
+
Gw_batch: np.ndarray,
|
|
45
|
+
detw_batch: np.ndarray,
|
|
46
|
+
B_s_batch: np.ndarray,
|
|
47
|
+
detw_shear_batch: np.ndarray,
|
|
48
|
+
membrane_matrix: np.ndarray,
|
|
49
|
+
bending_matrix: np.ndarray,
|
|
50
|
+
shear_matrix: np.ndarray,
|
|
51
|
+
drilling_stiffness: float,
|
|
52
|
+
force_positions: np.ndarray,
|
|
53
|
+
tangent_positions: np.ndarray,
|
|
54
|
+
force_values: np.ndarray,
|
|
55
|
+
tangent_values: np.ndarray,
|
|
56
|
+
u_local_work: np.ndarray,
|
|
57
|
+
tangent: bool,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Evaluate elastic geometrically nonlinear shells into persistent buffers."""
|
|
60
|
+
n_elem = dof_mappings.shape[0]
|
|
61
|
+
n_dof = dof_mappings.shape[1]
|
|
62
|
+
n_gp = detw_batch.shape[1]
|
|
63
|
+
n_shear = detw_shear_batch.shape[1]
|
|
64
|
+
|
|
65
|
+
for element_index in prange(n_elem):
|
|
66
|
+
T0 = T0_batch[element_index]
|
|
67
|
+
u_local = u_local_work[element_index]
|
|
68
|
+
|
|
69
|
+
# Gather global element DOFs and transform to the reference local frame.
|
|
70
|
+
for local_row in range(n_dof):
|
|
71
|
+
value = 0.0
|
|
72
|
+
for local_col in range(n_dof):
|
|
73
|
+
value += (
|
|
74
|
+
T0[local_row, local_col]
|
|
75
|
+
* displacements[dof_mappings[element_index, local_col]]
|
|
76
|
+
)
|
|
77
|
+
u_local[local_row] = value
|
|
78
|
+
|
|
79
|
+
# The persistent plan clears the flat buffers before this kernel. Work
|
|
80
|
+
# directly in the element's assigned slices.
|
|
81
|
+
force_pos = force_positions[element_index]
|
|
82
|
+
tangent_pos = tangent_positions[element_index]
|
|
83
|
+
|
|
84
|
+
B_eff = np.empty((3, n_dof), dtype=np.float64)
|
|
85
|
+
membrane_times_B = np.empty((3, n_dof), dtype=np.float64)
|
|
86
|
+
bending_times_B = np.empty((3, n_dof), dtype=np.float64)
|
|
87
|
+
|
|
88
|
+
for gp_index in range(n_gp):
|
|
89
|
+
B_m = B_m_batch[element_index, gp_index]
|
|
90
|
+
B_b = B_b_batch[element_index, gp_index]
|
|
91
|
+
B_d = B_d_batch[element_index, gp_index]
|
|
92
|
+
Gw = Gw_batch[element_index, gp_index]
|
|
93
|
+
detw = detw_batch[element_index, gp_index]
|
|
94
|
+
|
|
95
|
+
theta_0 = 0.0
|
|
96
|
+
theta_1 = 0.0
|
|
97
|
+
membrane_strain_0 = 0.0
|
|
98
|
+
membrane_strain_1 = 0.0
|
|
99
|
+
membrane_strain_2 = 0.0
|
|
100
|
+
curvature_0 = 0.0
|
|
101
|
+
curvature_1 = 0.0
|
|
102
|
+
curvature_2 = 0.0
|
|
103
|
+
drilling_rotation = 0.0
|
|
104
|
+
|
|
105
|
+
for dof_index in range(n_dof):
|
|
106
|
+
displacement = u_local[dof_index]
|
|
107
|
+
theta_0 += Gw[0, dof_index] * displacement
|
|
108
|
+
theta_1 += Gw[1, dof_index] * displacement
|
|
109
|
+
membrane_strain_0 += B_m[0, dof_index] * displacement
|
|
110
|
+
membrane_strain_1 += B_m[1, dof_index] * displacement
|
|
111
|
+
membrane_strain_2 += B_m[2, dof_index] * displacement
|
|
112
|
+
curvature_0 += B_b[0, dof_index] * displacement
|
|
113
|
+
curvature_1 += B_b[1, dof_index] * displacement
|
|
114
|
+
curvature_2 += B_b[2, dof_index] * displacement
|
|
115
|
+
drilling_rotation += B_d[0, dof_index] * displacement
|
|
116
|
+
|
|
117
|
+
membrane_strain_0 += 0.5 * theta_0 * theta_0
|
|
118
|
+
membrane_strain_1 += 0.5 * theta_1 * theta_1
|
|
119
|
+
membrane_strain_2 += theta_0 * theta_1
|
|
120
|
+
|
|
121
|
+
N_0 = (
|
|
122
|
+
membrane_matrix[0, 0] * membrane_strain_0
|
|
123
|
+
+ membrane_matrix[0, 1] * membrane_strain_1
|
|
124
|
+
+ membrane_matrix[0, 2] * membrane_strain_2
|
|
125
|
+
)
|
|
126
|
+
N_1 = (
|
|
127
|
+
membrane_matrix[1, 0] * membrane_strain_0
|
|
128
|
+
+ membrane_matrix[1, 1] * membrane_strain_1
|
|
129
|
+
+ membrane_matrix[1, 2] * membrane_strain_2
|
|
130
|
+
)
|
|
131
|
+
N_2 = (
|
|
132
|
+
membrane_matrix[2, 0] * membrane_strain_0
|
|
133
|
+
+ membrane_matrix[2, 1] * membrane_strain_1
|
|
134
|
+
+ membrane_matrix[2, 2] * membrane_strain_2
|
|
135
|
+
)
|
|
136
|
+
M_0 = (
|
|
137
|
+
bending_matrix[0, 0] * curvature_0
|
|
138
|
+
+ bending_matrix[0, 1] * curvature_1
|
|
139
|
+
+ bending_matrix[0, 2] * curvature_2
|
|
140
|
+
)
|
|
141
|
+
M_1 = (
|
|
142
|
+
bending_matrix[1, 0] * curvature_0
|
|
143
|
+
+ bending_matrix[1, 1] * curvature_1
|
|
144
|
+
+ bending_matrix[1, 2] * curvature_2
|
|
145
|
+
)
|
|
146
|
+
M_2 = (
|
|
147
|
+
bending_matrix[2, 0] * curvature_0
|
|
148
|
+
+ bending_matrix[2, 1] * curvature_1
|
|
149
|
+
+ bending_matrix[2, 2] * curvature_2
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
for dof_index in range(n_dof):
|
|
153
|
+
B_eff[0, dof_index] = B_m[0, dof_index] + theta_0 * Gw[0, dof_index]
|
|
154
|
+
B_eff[1, dof_index] = B_m[1, dof_index] + theta_1 * Gw[1, dof_index]
|
|
155
|
+
B_eff[2, dof_index] = (
|
|
156
|
+
B_m[2, dof_index]
|
|
157
|
+
+ theta_0 * Gw[1, dof_index]
|
|
158
|
+
+ theta_1 * Gw[0, dof_index]
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
force_values[force_pos[dof_index]] += (
|
|
162
|
+
B_eff[0, dof_index] * N_0
|
|
163
|
+
+ B_eff[1, dof_index] * N_1
|
|
164
|
+
+ B_eff[2, dof_index] * N_2
|
|
165
|
+
+ B_b[0, dof_index] * M_0
|
|
166
|
+
+ B_b[1, dof_index] * M_1
|
|
167
|
+
+ B_b[2, dof_index] * M_2
|
|
168
|
+
+ B_d[0, dof_index] * drilling_stiffness * drilling_rotation
|
|
169
|
+
) * detw
|
|
170
|
+
|
|
171
|
+
if not tangent:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
for row in range(3):
|
|
175
|
+
for dof_index in range(n_dof):
|
|
176
|
+
membrane_value = 0.0
|
|
177
|
+
bending_value = 0.0
|
|
178
|
+
for constitutive_index in range(3):
|
|
179
|
+
membrane_value += (
|
|
180
|
+
membrane_matrix[row, constitutive_index]
|
|
181
|
+
* B_eff[constitutive_index, dof_index]
|
|
182
|
+
)
|
|
183
|
+
bending_value += (
|
|
184
|
+
bending_matrix[row, constitutive_index]
|
|
185
|
+
* B_b[constitutive_index, dof_index]
|
|
186
|
+
)
|
|
187
|
+
membrane_times_B[row, dof_index] = membrane_value
|
|
188
|
+
bending_times_B[row, dof_index] = bending_value
|
|
189
|
+
|
|
190
|
+
for local_row in range(n_dof):
|
|
191
|
+
gw0_row = Gw[0, local_row]
|
|
192
|
+
gw1_row = Gw[1, local_row]
|
|
193
|
+
bd_row = B_d[0, local_row]
|
|
194
|
+
for local_col in range(n_dof):
|
|
195
|
+
material_value = 0.0
|
|
196
|
+
bending_value = 0.0
|
|
197
|
+
for constitutive_index in range(3):
|
|
198
|
+
material_value += (
|
|
199
|
+
B_eff[constitutive_index, local_row]
|
|
200
|
+
* membrane_times_B[constitutive_index, local_col]
|
|
201
|
+
)
|
|
202
|
+
bending_value += (
|
|
203
|
+
B_b[constitutive_index, local_row]
|
|
204
|
+
* bending_times_B[constitutive_index, local_col]
|
|
205
|
+
)
|
|
206
|
+
geometric_value = (
|
|
207
|
+
gw0_row * (N_0 * Gw[0, local_col] + N_2 * Gw[1, local_col])
|
|
208
|
+
+ gw1_row * (N_2 * Gw[0, local_col] + N_1 * Gw[1, local_col])
|
|
209
|
+
)
|
|
210
|
+
drilling_value = bd_row * drilling_stiffness * B_d[0, local_col]
|
|
211
|
+
tangent_values[
|
|
212
|
+
tangent_pos[local_row * n_dof + local_col]
|
|
213
|
+
] += (
|
|
214
|
+
material_value
|
|
215
|
+
+ bending_value
|
|
216
|
+
+ geometric_value
|
|
217
|
+
+ drilling_value
|
|
218
|
+
) * detw
|
|
219
|
+
|
|
220
|
+
for shear_index in range(n_shear):
|
|
221
|
+
B_s = B_s_batch[element_index, shear_index]
|
|
222
|
+
detw_shear = detw_shear_batch[element_index, shear_index]
|
|
223
|
+
gamma_0 = 0.0
|
|
224
|
+
gamma_1 = 0.0
|
|
225
|
+
for dof_index in range(n_dof):
|
|
226
|
+
gamma_0 += B_s[0, dof_index] * u_local[dof_index]
|
|
227
|
+
gamma_1 += B_s[1, dof_index] * u_local[dof_index]
|
|
228
|
+
|
|
229
|
+
shear_resultant_0 = (
|
|
230
|
+
shear_matrix[0, 0] * gamma_0 + shear_matrix[0, 1] * gamma_1
|
|
231
|
+
)
|
|
232
|
+
shear_resultant_1 = (
|
|
233
|
+
shear_matrix[1, 0] * gamma_0 + shear_matrix[1, 1] * gamma_1
|
|
234
|
+
)
|
|
235
|
+
for dof_index in range(n_dof):
|
|
236
|
+
force_values[force_pos[dof_index]] += (
|
|
237
|
+
B_s[0, dof_index] * shear_resultant_0
|
|
238
|
+
+ B_s[1, dof_index] * shear_resultant_1
|
|
239
|
+
) * detw_shear
|
|
240
|
+
|
|
241
|
+
if tangent:
|
|
242
|
+
for local_row in range(n_dof):
|
|
243
|
+
for local_col in range(n_dof):
|
|
244
|
+
shear_value = (
|
|
245
|
+
B_s[0, local_row]
|
|
246
|
+
* (
|
|
247
|
+
shear_matrix[0, 0] * B_s[0, local_col]
|
|
248
|
+
+ shear_matrix[0, 1] * B_s[1, local_col]
|
|
249
|
+
)
|
|
250
|
+
+ B_s[1, local_row]
|
|
251
|
+
* (
|
|
252
|
+
shear_matrix[1, 0] * B_s[0, local_col]
|
|
253
|
+
+ shear_matrix[1, 1] * B_s[1, local_col]
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
tangent_values[
|
|
257
|
+
tangent_pos[local_row * n_dof + local_col]
|
|
258
|
+
] += shear_value * detw_shear
|
|
259
|
+
|
|
260
|
+
# Transform force blocks from local to global in place. T0 maps global
|
|
261
|
+
# displacements to local displacements, hence forces use T0.T.
|
|
262
|
+
for block_start in range(0, n_dof, 3):
|
|
263
|
+
local_0 = force_values[force_pos[block_start]]
|
|
264
|
+
local_1 = force_values[force_pos[block_start + 1]]
|
|
265
|
+
local_2 = force_values[force_pos[block_start + 2]]
|
|
266
|
+
force_values[force_pos[block_start]] = (
|
|
267
|
+
T0[block_start, block_start] * local_0
|
|
268
|
+
+ T0[block_start + 1, block_start] * local_1
|
|
269
|
+
+ T0[block_start + 2, block_start] * local_2
|
|
270
|
+
)
|
|
271
|
+
force_values[force_pos[block_start + 1]] = (
|
|
272
|
+
T0[block_start, block_start + 1] * local_0
|
|
273
|
+
+ T0[block_start + 1, block_start + 1] * local_1
|
|
274
|
+
+ T0[block_start + 2, block_start + 1] * local_2
|
|
275
|
+
)
|
|
276
|
+
force_values[force_pos[block_start + 2]] = (
|
|
277
|
+
T0[block_start, block_start + 2] * local_0
|
|
278
|
+
+ T0[block_start + 1, block_start + 2] * local_1
|
|
279
|
+
+ T0[block_start + 2, block_start + 2] * local_2
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if not tangent:
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
# T0 is block diagonal with 3x3 translation/rotation blocks. Each
|
|
286
|
+
# K_global block depends only on the corresponding K_local block, so it
|
|
287
|
+
# can be transformed and overwritten safely without a second matrix.
|
|
288
|
+
local_block = np.empty((3, 3), dtype=np.float64)
|
|
289
|
+
intermediate = np.empty((3, 3), dtype=np.float64)
|
|
290
|
+
global_block = np.empty((3, 3), dtype=np.float64)
|
|
291
|
+
for row_block in range(0, n_dof, 3):
|
|
292
|
+
for col_block in range(0, n_dof, 3):
|
|
293
|
+
for row in range(3):
|
|
294
|
+
for col in range(3):
|
|
295
|
+
local_block[row, col] = tangent_values[
|
|
296
|
+
tangent_pos[
|
|
297
|
+
(row_block + row) * n_dof + col_block + col
|
|
298
|
+
]
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
for row in range(3):
|
|
302
|
+
for col in range(3):
|
|
303
|
+
value = 0.0
|
|
304
|
+
for inner in range(3):
|
|
305
|
+
value += (
|
|
306
|
+
local_block[row, inner]
|
|
307
|
+
* T0[col_block + inner, col_block + col]
|
|
308
|
+
)
|
|
309
|
+
intermediate[row, col] = value
|
|
310
|
+
|
|
311
|
+
for row in range(3):
|
|
312
|
+
for col in range(3):
|
|
313
|
+
value = 0.0
|
|
314
|
+
for inner in range(3):
|
|
315
|
+
value += (
|
|
316
|
+
T0[row_block + inner, row_block + row]
|
|
317
|
+
* intermediate[inner, col]
|
|
318
|
+
)
|
|
319
|
+
global_block[row, col] = value
|
|
320
|
+
|
|
321
|
+
for row in range(3):
|
|
322
|
+
for col in range(3):
|
|
323
|
+
tangent_values[
|
|
324
|
+
tangent_pos[
|
|
325
|
+
(row_block + row) * n_dof + col_block + col
|
|
326
|
+
]
|
|
327
|
+
] = global_block[row, col]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
_ORIGINAL_BATCH_BUILD = _performance._ShellBatchPlan.build.__func__
|
|
331
|
+
_ORIGINAL_PLAN_ASSEMBLE = _performance.NonlinearAssemblyPlan.assemble
|
|
332
|
+
_ORIGINAL_PLAN_DIAGNOSTICS = _performance.NonlinearAssemblyPlan.diagnostics
|
|
333
|
+
_INSTALLED = False
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _batch_b_shell_build(
|
|
337
|
+
cls,
|
|
338
|
+
model,
|
|
339
|
+
key,
|
|
340
|
+
items,
|
|
341
|
+
num_layers,
|
|
342
|
+
):
|
|
343
|
+
batch = _ORIGINAL_BATCH_BUILD(cls, model, key, items, num_layers)
|
|
344
|
+
if batch.has_plasticity:
|
|
345
|
+
batch._batch_b_elastic = False
|
|
346
|
+
return batch
|
|
347
|
+
|
|
348
|
+
elastic_matrix = plane_stress_elastic_matrix(
|
|
349
|
+
float(batch.material.elastic_modulus),
|
|
350
|
+
float(batch.material.poisson_ratio),
|
|
351
|
+
)
|
|
352
|
+
batch._batch_b_elastic = True
|
|
353
|
+
batch._batch_b_membrane_matrix = batch.thickness * elastic_matrix
|
|
354
|
+
batch._batch_b_bending_matrix = batch.thickness**3 / 12.0 * elastic_matrix
|
|
355
|
+
batch._batch_b_shear_matrix = (
|
|
356
|
+
float(batch.material.shear_modulus)
|
|
357
|
+
* (5.0 / 6.0)
|
|
358
|
+
* batch.thickness
|
|
359
|
+
* np.eye(2, dtype=float)
|
|
360
|
+
)
|
|
361
|
+
batch._batch_b_drilling_stiffness = (
|
|
362
|
+
float(batch.material.shear_modulus)
|
|
363
|
+
* batch.thickness
|
|
364
|
+
* batch.drilling_stabilization
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Elastic groups do not require mutable constitutive history. Release the
|
|
368
|
+
# per-element plastic work arrays allocated by the compatibility builder.
|
|
369
|
+
batch.plastic_work = np.empty((0, 0, 3), dtype=float)
|
|
370
|
+
batch.alpha_work = np.empty((0, 0), dtype=float)
|
|
371
|
+
|
|
372
|
+
points_per_element = int(batch.n_gp * batch.num_layers)
|
|
373
|
+
shared_plastic = np.zeros((points_per_element, 3), dtype=float)
|
|
374
|
+
shared_alpha = np.zeros(points_per_element, dtype=float)
|
|
375
|
+
shared_plastic.setflags(write=False)
|
|
376
|
+
shared_alpha.setflags(write=False)
|
|
377
|
+
batch.elastic_states = tuple(
|
|
378
|
+
{
|
|
379
|
+
"plastic_strain": shared_plastic,
|
|
380
|
+
"alpha": shared_alpha,
|
|
381
|
+
}
|
|
382
|
+
for _ in batch.elements
|
|
383
|
+
)
|
|
384
|
+
batch._batch_b_elastic_state_mapping = {
|
|
385
|
+
int(element_id): state
|
|
386
|
+
for element_id, state in zip(batch.element_ids, batch.elastic_states)
|
|
387
|
+
}
|
|
388
|
+
return batch
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _batch_b_plan_assemble(
|
|
392
|
+
self,
|
|
393
|
+
displacements: np.ndarray,
|
|
394
|
+
committed_states: Mapping[int, Any],
|
|
395
|
+
tangent: bool = True,
|
|
396
|
+
deleted_element_ids: Sequence[int] = (),
|
|
397
|
+
residual_stiffness_fraction: float = 1.0,
|
|
398
|
+
) -> Tuple[np.ndarray, Optional[sparse.csr_matrix], Dict[int, Any]]:
|
|
399
|
+
"""Assemble with direct-buffer elastic batches and legacy plastic batches."""
|
|
400
|
+
if tuple(deleted_element_ids or ()):
|
|
401
|
+
return _ORIGINAL_PLAN_ASSEMBLE(
|
|
402
|
+
self,
|
|
403
|
+
displacements,
|
|
404
|
+
committed_states,
|
|
405
|
+
tangent=tangent,
|
|
406
|
+
deleted_element_ids=tuple(deleted_element_ids),
|
|
407
|
+
residual_stiffness_fraction=float(residual_stiffness_fraction),
|
|
408
|
+
)
|
|
409
|
+
with self._lock:
|
|
410
|
+
start_total = time.perf_counter()
|
|
411
|
+
self.timings.calls += 1
|
|
412
|
+
if tangent:
|
|
413
|
+
self.timings.tangent_calls += 1
|
|
414
|
+
else:
|
|
415
|
+
self.timings.residual_only_calls += 1
|
|
416
|
+
|
|
417
|
+
self.force_values.fill(0.0)
|
|
418
|
+
if tangent:
|
|
419
|
+
self.tangent_values.fill(0.0)
|
|
420
|
+
trial_states: Dict[int, Any] = {}
|
|
421
|
+
|
|
422
|
+
for batch in self.shell_batches:
|
|
423
|
+
if getattr(batch, "_batch_b_elastic", False):
|
|
424
|
+
kernel_start = time.perf_counter()
|
|
425
|
+
_elastic_shell_batch_into_buffers(
|
|
426
|
+
np.asarray(displacements, dtype=float),
|
|
427
|
+
batch.dof_mappings,
|
|
428
|
+
batch.T0,
|
|
429
|
+
batch.B_m,
|
|
430
|
+
batch.B_b,
|
|
431
|
+
batch.B_d,
|
|
432
|
+
batch.Gw,
|
|
433
|
+
batch.detw,
|
|
434
|
+
batch.B_s,
|
|
435
|
+
batch.detw_shear,
|
|
436
|
+
batch._batch_b_membrane_matrix,
|
|
437
|
+
batch._batch_b_bending_matrix,
|
|
438
|
+
batch._batch_b_shear_matrix,
|
|
439
|
+
float(batch._batch_b_drilling_stiffness),
|
|
440
|
+
batch.force_positions,
|
|
441
|
+
batch.tangent_positions,
|
|
442
|
+
self.force_values,
|
|
443
|
+
self.tangent_values,
|
|
444
|
+
batch.u_work,
|
|
445
|
+
bool(tangent),
|
|
446
|
+
)
|
|
447
|
+
self.timings.shell_kernel_seconds += time.perf_counter() - kernel_start
|
|
448
|
+
elastic_states = batch._batch_b_elastic_state_mapping
|
|
449
|
+
# Preserve explicitly supplied/previous elastic states without
|
|
450
|
+
# allocating a new mapping in the normal case.
|
|
451
|
+
use_cached_mapping = True
|
|
452
|
+
for element_id in batch.element_ids:
|
|
453
|
+
existing = committed_states.get(int(element_id))
|
|
454
|
+
if isinstance(existing, dict) and existing is not elastic_states[int(element_id)]:
|
|
455
|
+
use_cached_mapping = False
|
|
456
|
+
break
|
|
457
|
+
if use_cached_mapping:
|
|
458
|
+
trial_states.update(elastic_states)
|
|
459
|
+
else:
|
|
460
|
+
for element_id in batch.element_ids:
|
|
461
|
+
element_key = int(element_id)
|
|
462
|
+
existing = committed_states.get(element_key)
|
|
463
|
+
trial_states[element_key] = (
|
|
464
|
+
existing if isinstance(existing, dict) else elastic_states[element_key]
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
F_batch, K_batch, batch_states, kernel_seconds = batch.evaluate(
|
|
468
|
+
displacements,
|
|
469
|
+
committed_states,
|
|
470
|
+
tangent,
|
|
471
|
+
)
|
|
472
|
+
self.timings.shell_kernel_seconds += kernel_seconds
|
|
473
|
+
self.force_values[batch.force_positions.reshape(-1)] = np.asarray(
|
|
474
|
+
F_batch, dtype=float
|
|
475
|
+
).reshape(-1)
|
|
476
|
+
if tangent and K_batch is not None:
|
|
477
|
+
self.tangent_values[batch.tangent_positions.reshape(-1)] = np.asarray(
|
|
478
|
+
K_batch, dtype=float
|
|
479
|
+
).reshape(-1)
|
|
480
|
+
trial_states.update(batch_states)
|
|
481
|
+
|
|
482
|
+
non_shell_start = time.perf_counter()
|
|
483
|
+
model = self.model
|
|
484
|
+
mesh = model.mesh
|
|
485
|
+
for record in self.non_shell_elements:
|
|
486
|
+
material = model.get_material(record.element.material_name)
|
|
487
|
+
u_element = np.asarray(displacements, dtype=float)[record.dof_mapping]
|
|
488
|
+
f_element, k_element, trial_state = record.element.compute_nonlinear_response(
|
|
489
|
+
mesh,
|
|
490
|
+
material,
|
|
491
|
+
u_element,
|
|
492
|
+
committed_states.get(record.element_id),
|
|
493
|
+
self.num_layers,
|
|
494
|
+
tangent,
|
|
495
|
+
)
|
|
496
|
+
self.force_values[record.force_positions] = np.asarray(
|
|
497
|
+
f_element, dtype=float
|
|
498
|
+
).reshape(-1)
|
|
499
|
+
if tangent and k_element is not None:
|
|
500
|
+
self.tangent_values[record.tangent_positions] = np.asarray(
|
|
501
|
+
k_element, dtype=float
|
|
502
|
+
).reshape(-1)
|
|
503
|
+
if trial_state is not None:
|
|
504
|
+
trial_states[record.element_id] = trial_state
|
|
505
|
+
self.timings.non_shell_seconds += time.perf_counter() - non_shell_start
|
|
506
|
+
|
|
507
|
+
scatter_start = time.perf_counter()
|
|
508
|
+
force = _performance._scatter_sum(
|
|
509
|
+
self.force_values,
|
|
510
|
+
self.force_dofs_flat,
|
|
511
|
+
self.total_dofs,
|
|
512
|
+
)
|
|
513
|
+
self.timings.force_scatter_seconds += time.perf_counter() - scatter_start
|
|
514
|
+
|
|
515
|
+
tangent_matrix: Optional[sparse.csr_matrix]
|
|
516
|
+
if tangent:
|
|
517
|
+
scatter_start = time.perf_counter()
|
|
518
|
+
csr_data = _performance._scatter_sum(
|
|
519
|
+
self.tangent_values,
|
|
520
|
+
self.tangent_scatter,
|
|
521
|
+
self.nnz,
|
|
522
|
+
)
|
|
523
|
+
tangent_matrix = sparse.csr_matrix(
|
|
524
|
+
(csr_data, self.csr_indices, self.csr_indptr),
|
|
525
|
+
shape=(self.total_dofs, self.total_dofs),
|
|
526
|
+
copy=False,
|
|
527
|
+
)
|
|
528
|
+
self.timings.tangent_scatter_seconds += time.perf_counter() - scatter_start
|
|
529
|
+
else:
|
|
530
|
+
tangent_matrix = None
|
|
531
|
+
|
|
532
|
+
self.timings.total_seconds += time.perf_counter() - start_total
|
|
533
|
+
return force, tangent_matrix, trial_states
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _batch_b_plan_diagnostics(self) -> Dict[str, Any]:
|
|
537
|
+
diagnostics = _ORIGINAL_PLAN_DIAGNOSTICS(self)
|
|
538
|
+
elastic_batches = [
|
|
539
|
+
batch
|
|
540
|
+
for batch in self.shell_batches
|
|
541
|
+
if getattr(batch, "_batch_b_elastic", False)
|
|
542
|
+
]
|
|
543
|
+
diagnostics.update(
|
|
544
|
+
{
|
|
545
|
+
"batch_b_installed": True,
|
|
546
|
+
"elastic_fast_path_batch_count": len(elastic_batches),
|
|
547
|
+
"elastic_fast_path_element_count": int(
|
|
548
|
+
sum(batch.element_ids.size for batch in elastic_batches)
|
|
549
|
+
),
|
|
550
|
+
"plastic_batch_count": int(len(self.shell_batches) - len(elastic_batches)),
|
|
551
|
+
"elastic_constitutive_state_bytes": int(
|
|
552
|
+
sum(
|
|
553
|
+
batch.elastic_states[0]["plastic_strain"].nbytes
|
|
554
|
+
+ batch.elastic_states[0]["alpha"].nbytes
|
|
555
|
+
for batch in elastic_batches
|
|
556
|
+
if batch.elastic_states
|
|
557
|
+
)
|
|
558
|
+
),
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
return diagnostics
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def install_batch_b_optimizations() -> bool:
|
|
565
|
+
global _INSTALLED
|
|
566
|
+
if _INSTALLED:
|
|
567
|
+
return True
|
|
568
|
+
_performance._ShellBatchPlan.build = classmethod(_batch_b_shell_build)
|
|
569
|
+
_performance.NonlinearAssemblyPlan.assemble = _batch_b_plan_assemble
|
|
570
|
+
_performance.NonlinearAssemblyPlan.diagnostics = _batch_b_plan_diagnostics
|
|
571
|
+
_performance.clear_nonlinear_assembly_cache()
|
|
572
|
+
_INSTALLED = True
|
|
573
|
+
return True
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def uninstall_batch_b_optimizations() -> None:
|
|
577
|
+
global _INSTALLED
|
|
578
|
+
if not _INSTALLED:
|
|
579
|
+
return
|
|
580
|
+
_performance._ShellBatchPlan.build = classmethod(_ORIGINAL_BATCH_BUILD)
|
|
581
|
+
_performance.NonlinearAssemblyPlan.assemble = _ORIGINAL_PLAN_ASSEMBLE
|
|
582
|
+
_performance.NonlinearAssemblyPlan.diagnostics = _ORIGINAL_PLAN_DIAGNOSTICS
|
|
583
|
+
_performance.clear_nonlinear_assembly_cache()
|
|
584
|
+
_INSTALLED = False
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def batch_b_status() -> Dict[str, Any]:
|
|
588
|
+
return {
|
|
589
|
+
"installed": bool(_INSTALLED),
|
|
590
|
+
"description": "in-place elastic shell assembly into persistent buffers",
|
|
591
|
+
"parallel_kernel": True,
|
|
592
|
+
}
|