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,506 @@
|
|
|
1
|
+
"""Batch C runtime integration for direct reduced-coordinate assembly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from scipy import sparse
|
|
15
|
+
|
|
16
|
+
from . import nonlinear_performance_batch_b as _batch_b
|
|
17
|
+
from . import nonlinear_reduced_assembly as _reduced
|
|
18
|
+
from .jit_compiler import JIT_ENABLED
|
|
19
|
+
from .nonlinear_reduced_assembly import (
|
|
20
|
+
ReducedAssemblyPlan,
|
|
21
|
+
ReducedAssemblyPlanLimit,
|
|
22
|
+
_identity_transformation,
|
|
23
|
+
_maximum_map_bytes,
|
|
24
|
+
assemble_reduced_system,
|
|
25
|
+
build_reduced_assembly_plan,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class _ReducedVectorPayload:
|
|
31
|
+
values: np.ndarray
|
|
32
|
+
token: object
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class _ReducedMatrixPayload:
|
|
37
|
+
matrix: sparse.csr_matrix
|
|
38
|
+
token: object
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class _ReducedLeftProduct:
|
|
43
|
+
matrix: sparse.csr_matrix
|
|
44
|
+
token: object
|
|
45
|
+
|
|
46
|
+
def __matmul__(self, other: Any):
|
|
47
|
+
if (
|
|
48
|
+
isinstance(other, _DirectReductionTransform)
|
|
49
|
+
and other._batch_c_token is self.token
|
|
50
|
+
):
|
|
51
|
+
return self.matrix
|
|
52
|
+
return NotImplemented
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _DirectReductionTranspose(sparse.csc_matrix):
|
|
56
|
+
"""Sparse transpose that recognizes already-reduced assembly payloads."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
arg1: Any,
|
|
61
|
+
*,
|
|
62
|
+
token: Optional[object] = None,
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> None:
|
|
65
|
+
super().__init__(arg1, **kwargs)
|
|
66
|
+
self._batch_c_token = token
|
|
67
|
+
|
|
68
|
+
def __matmul__(self, other: Any):
|
|
69
|
+
if (
|
|
70
|
+
isinstance(other, _ReducedVectorPayload)
|
|
71
|
+
and other.token is self._batch_c_token
|
|
72
|
+
):
|
|
73
|
+
return other.values
|
|
74
|
+
if (
|
|
75
|
+
isinstance(other, _ReducedMatrixPayload)
|
|
76
|
+
and other.token is self._batch_c_token
|
|
77
|
+
):
|
|
78
|
+
return _ReducedLeftProduct(other.matrix, self._batch_c_token)
|
|
79
|
+
return sparse.csc_matrix(self).__matmul__(other)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _DirectReductionTransform(sparse.csr_matrix):
|
|
83
|
+
"""CSR transformation retaining a token for direct-reduction interception."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
arg1: Any,
|
|
88
|
+
*,
|
|
89
|
+
token: Optional[object] = None,
|
|
90
|
+
**kwargs: Any,
|
|
91
|
+
) -> None:
|
|
92
|
+
super().__init__(arg1, **kwargs)
|
|
93
|
+
self._batch_c_token = token
|
|
94
|
+
|
|
95
|
+
def transpose(self, axes=None, copy: bool = False):
|
|
96
|
+
base = sparse.csr_matrix(self).transpose(axes=axes, copy=copy)
|
|
97
|
+
return _DirectReductionTranspose(base, token=self._batch_c_token)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class _SolveContext:
|
|
102
|
+
requested_model: Any
|
|
103
|
+
token: object = field(default_factory=object)
|
|
104
|
+
bound_model: Any = None
|
|
105
|
+
transformation: Optional[_DirectReductionTransform] = None
|
|
106
|
+
reduced_plan: Optional[ReducedAssemblyPlan] = None
|
|
107
|
+
fallback_reason: Optional[str] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
_CONTEXT = threading.local()
|
|
111
|
+
_STATUS_LOCK = threading.RLock()
|
|
112
|
+
_STATUS: Dict[str, Any] = {
|
|
113
|
+
"installed": False,
|
|
114
|
+
"contexts_entered": 0,
|
|
115
|
+
"reduced_plan_builds": 0,
|
|
116
|
+
"reduced_assemblies": 0,
|
|
117
|
+
"full_coordinate_fallbacks": 0,
|
|
118
|
+
"last_fallback_reason": None,
|
|
119
|
+
"last_plan": None,
|
|
120
|
+
}
|
|
121
|
+
_PATCHES: List[Tuple[ModuleType, str, Any, Any]] = []
|
|
122
|
+
_INSTALLED = False
|
|
123
|
+
_BASE_ASSEMBLER = None
|
|
124
|
+
_ORIGINAL_STATIC_SOLVER = None
|
|
125
|
+
_ORIGINAL_ARC_SOLVER = None
|
|
126
|
+
_ORIGINAL_CONSTRAINT_BUILDER = None
|
|
127
|
+
_ORIGINAL_LOCAL_EVALUATOR = _reduced._evaluate_local_responses
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _context_stack() -> List[_SolveContext]:
|
|
131
|
+
stack = getattr(_CONTEXT, "stack", None)
|
|
132
|
+
if stack is None:
|
|
133
|
+
stack = []
|
|
134
|
+
_CONTEXT.stack = stack
|
|
135
|
+
return stack
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _active_context(model: Any) -> Optional[_SolveContext]:
|
|
139
|
+
for context in reversed(_context_stack()):
|
|
140
|
+
if context.bound_model is model:
|
|
141
|
+
return context
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _run_with_context(original, *args: Any, **kwargs: Any):
|
|
146
|
+
model = args[0] if args else kwargs.get("model")
|
|
147
|
+
context = _SolveContext(requested_model=model)
|
|
148
|
+
stack = _context_stack()
|
|
149
|
+
stack.append(context)
|
|
150
|
+
with _STATUS_LOCK:
|
|
151
|
+
_STATUS["contexts_entered"] += 1
|
|
152
|
+
try:
|
|
153
|
+
return original(*args, **kwargs)
|
|
154
|
+
finally:
|
|
155
|
+
if stack and stack[-1] is context:
|
|
156
|
+
stack.pop()
|
|
157
|
+
else:
|
|
158
|
+
try:
|
|
159
|
+
stack.remove(context)
|
|
160
|
+
except ValueError:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _make_solver_wrapper(original):
|
|
165
|
+
@functools.wraps(original)
|
|
166
|
+
def wrapper(*args: Any, **kwargs: Any):
|
|
167
|
+
return _run_with_context(original, *args, **kwargs)
|
|
168
|
+
|
|
169
|
+
wrapper._batch_c_original = original
|
|
170
|
+
return wrapper
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _constraint_builder_wrapper(K, F, model):
|
|
174
|
+
result = _ORIGINAL_CONSTRAINT_BUILDER(K, F, model)
|
|
175
|
+
K_red, F_red, transformation, u0, independent_dofs, info = result
|
|
176
|
+
stack = _context_stack()
|
|
177
|
+
if not stack:
|
|
178
|
+
return result
|
|
179
|
+
context = stack[-1]
|
|
180
|
+
if context.bound_model is None:
|
|
181
|
+
context.bound_model = model
|
|
182
|
+
if context.bound_model is not model or _identity_transformation(transformation):
|
|
183
|
+
return result
|
|
184
|
+
wrapped = _DirectReductionTransform(transformation, token=context.token)
|
|
185
|
+
context.transformation = wrapped
|
|
186
|
+
return K_red, F_red, wrapped, u0, independent_dofs, info
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _batch_c_evaluate_local_responses(
|
|
190
|
+
nonlinear_plan: Any,
|
|
191
|
+
displacements: np.ndarray,
|
|
192
|
+
committed_states: Mapping[int, Any],
|
|
193
|
+
tangent: bool,
|
|
194
|
+
):
|
|
195
|
+
"""Fill local buffers while retaining Batch B's elastic shell fast path."""
|
|
196
|
+
|
|
197
|
+
start = time.perf_counter()
|
|
198
|
+
nonlinear_plan.force_values.fill(0.0)
|
|
199
|
+
if tangent:
|
|
200
|
+
nonlinear_plan.tangent_values.fill(0.0)
|
|
201
|
+
trial_states: Dict[int, Any] = {}
|
|
202
|
+
displacement_array = np.asarray(displacements, dtype=float)
|
|
203
|
+
|
|
204
|
+
for batch in nonlinear_plan.shell_batches:
|
|
205
|
+
if getattr(batch, "_batch_b_elastic", False):
|
|
206
|
+
kernel_start = time.perf_counter()
|
|
207
|
+
_batch_b._elastic_shell_batch_into_buffers(
|
|
208
|
+
displacement_array,
|
|
209
|
+
batch.dof_mappings,
|
|
210
|
+
batch.T0,
|
|
211
|
+
batch.B_m,
|
|
212
|
+
batch.B_b,
|
|
213
|
+
batch.B_d,
|
|
214
|
+
batch.Gw,
|
|
215
|
+
batch.detw,
|
|
216
|
+
batch.B_s,
|
|
217
|
+
batch.detw_shear,
|
|
218
|
+
batch._batch_b_membrane_matrix,
|
|
219
|
+
batch._batch_b_bending_matrix,
|
|
220
|
+
batch._batch_b_shear_matrix,
|
|
221
|
+
float(batch._batch_b_drilling_stiffness),
|
|
222
|
+
batch.force_positions,
|
|
223
|
+
batch.tangent_positions,
|
|
224
|
+
nonlinear_plan.force_values,
|
|
225
|
+
nonlinear_plan.tangent_values,
|
|
226
|
+
batch.u_work,
|
|
227
|
+
bool(tangent),
|
|
228
|
+
)
|
|
229
|
+
nonlinear_plan.timings.shell_kernel_seconds += (
|
|
230
|
+
time.perf_counter() - kernel_start
|
|
231
|
+
)
|
|
232
|
+
elastic_states = batch._batch_b_elastic_state_mapping
|
|
233
|
+
use_cached_mapping = True
|
|
234
|
+
for element_id in batch.element_ids:
|
|
235
|
+
existing = committed_states.get(int(element_id))
|
|
236
|
+
if (
|
|
237
|
+
isinstance(existing, dict)
|
|
238
|
+
and existing is not elastic_states[int(element_id)]
|
|
239
|
+
):
|
|
240
|
+
use_cached_mapping = False
|
|
241
|
+
break
|
|
242
|
+
if use_cached_mapping:
|
|
243
|
+
trial_states.update(elastic_states)
|
|
244
|
+
else:
|
|
245
|
+
for element_id in batch.element_ids:
|
|
246
|
+
element_key = int(element_id)
|
|
247
|
+
existing = committed_states.get(element_key)
|
|
248
|
+
trial_states[element_key] = (
|
|
249
|
+
existing
|
|
250
|
+
if isinstance(existing, dict)
|
|
251
|
+
else elastic_states[element_key]
|
|
252
|
+
)
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
force_batch, tangent_batch, batch_states, kernel_seconds = batch.evaluate(
|
|
256
|
+
displacement_array,
|
|
257
|
+
committed_states,
|
|
258
|
+
tangent,
|
|
259
|
+
)
|
|
260
|
+
nonlinear_plan.timings.shell_kernel_seconds += kernel_seconds
|
|
261
|
+
nonlinear_plan.force_values[batch.force_positions.reshape(-1)] = np.asarray(
|
|
262
|
+
force_batch,
|
|
263
|
+
dtype=float,
|
|
264
|
+
).reshape(-1)
|
|
265
|
+
if tangent and tangent_batch is not None:
|
|
266
|
+
nonlinear_plan.tangent_values[
|
|
267
|
+
batch.tangent_positions.reshape(-1)
|
|
268
|
+
] = np.asarray(tangent_batch, dtype=float).reshape(-1)
|
|
269
|
+
trial_states.update(batch_states)
|
|
270
|
+
|
|
271
|
+
non_shell_start = time.perf_counter()
|
|
272
|
+
model = nonlinear_plan.model
|
|
273
|
+
mesh = model.mesh
|
|
274
|
+
for record in nonlinear_plan.non_shell_elements:
|
|
275
|
+
material = model.get_material(record.element.material_name)
|
|
276
|
+
element_displacement = displacement_array[record.dof_mapping]
|
|
277
|
+
force_element, tangent_element, trial_state = (
|
|
278
|
+
record.element.compute_nonlinear_response(
|
|
279
|
+
mesh,
|
|
280
|
+
material,
|
|
281
|
+
element_displacement,
|
|
282
|
+
committed_states.get(record.element_id),
|
|
283
|
+
nonlinear_plan.num_layers,
|
|
284
|
+
tangent,
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
nonlinear_plan.force_values[record.force_positions] = np.asarray(
|
|
288
|
+
force_element,
|
|
289
|
+
dtype=float,
|
|
290
|
+
).reshape(-1)
|
|
291
|
+
if tangent and tangent_element is not None:
|
|
292
|
+
nonlinear_plan.tangent_values[record.tangent_positions] = np.asarray(
|
|
293
|
+
tangent_element,
|
|
294
|
+
dtype=float,
|
|
295
|
+
).reshape(-1)
|
|
296
|
+
if trial_state is not None:
|
|
297
|
+
trial_states[record.element_id] = trial_state
|
|
298
|
+
nonlinear_plan.timings.non_shell_seconds += (
|
|
299
|
+
time.perf_counter() - non_shell_start
|
|
300
|
+
)
|
|
301
|
+
return trial_states, time.perf_counter() - start
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _batch_c_assemble_nonlinear_system(
|
|
305
|
+
model,
|
|
306
|
+
displacements: np.ndarray,
|
|
307
|
+
committed_states: Dict[int, Any],
|
|
308
|
+
num_layers: int,
|
|
309
|
+
tangent: bool = True,
|
|
310
|
+
deleted_element_ids=None,
|
|
311
|
+
residual_stiffness_fraction: float = 1.0,
|
|
312
|
+
**extra,
|
|
313
|
+
):
|
|
314
|
+
deleted_tuple = tuple(deleted_element_ids or ())
|
|
315
|
+
kinematics = str(extra.pop("kinematics", "von_karman"))
|
|
316
|
+
if deleted_tuple or extra or kinematics != "von_karman":
|
|
317
|
+
# The reduced-assembly plan encodes the default von Karman element
|
|
318
|
+
# response; erosion, per-element stiffness scales and corotational
|
|
319
|
+
# kinematics take the full base assembler.
|
|
320
|
+
return _BASE_ASSEMBLER(
|
|
321
|
+
model,
|
|
322
|
+
displacements,
|
|
323
|
+
committed_states,
|
|
324
|
+
num_layers,
|
|
325
|
+
tangent=tangent,
|
|
326
|
+
deleted_element_ids=deleted_tuple,
|
|
327
|
+
residual_stiffness_fraction=float(residual_stiffness_fraction),
|
|
328
|
+
kinematics=kinematics,
|
|
329
|
+
**extra,
|
|
330
|
+
)
|
|
331
|
+
context = _active_context(model)
|
|
332
|
+
if (
|
|
333
|
+
context is None
|
|
334
|
+
or context.transformation is None
|
|
335
|
+
or context.fallback_reason is not None
|
|
336
|
+
):
|
|
337
|
+
return _BASE_ASSEMBLER(
|
|
338
|
+
model,
|
|
339
|
+
displacements,
|
|
340
|
+
committed_states,
|
|
341
|
+
num_layers,
|
|
342
|
+
tangent=tangent,
|
|
343
|
+
deleted_element_ids=deleted_tuple,
|
|
344
|
+
residual_stiffness_fraction=float(residual_stiffness_fraction),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
from .nonlinear_performance_bootstrap import get_nonlinear_assembly_plan
|
|
348
|
+
|
|
349
|
+
nonlinear_plan = get_nonlinear_assembly_plan(model, int(num_layers))
|
|
350
|
+
if (
|
|
351
|
+
context.reduced_plan is None
|
|
352
|
+
or context.reduced_plan.source_plan is not nonlinear_plan
|
|
353
|
+
):
|
|
354
|
+
try:
|
|
355
|
+
context.reduced_plan = build_reduced_assembly_plan(
|
|
356
|
+
nonlinear_plan,
|
|
357
|
+
context.transformation,
|
|
358
|
+
)
|
|
359
|
+
with _STATUS_LOCK:
|
|
360
|
+
_STATUS["reduced_plan_builds"] += 1
|
|
361
|
+
_STATUS["last_plan"] = context.reduced_plan.diagnostics()
|
|
362
|
+
except ReducedAssemblyPlanLimit as exc:
|
|
363
|
+
context.fallback_reason = str(exc)
|
|
364
|
+
with _STATUS_LOCK:
|
|
365
|
+
_STATUS["full_coordinate_fallbacks"] += 1
|
|
366
|
+
_STATUS["last_fallback_reason"] = context.fallback_reason
|
|
367
|
+
return _BASE_ASSEMBLER(
|
|
368
|
+
model,
|
|
369
|
+
displacements,
|
|
370
|
+
committed_states,
|
|
371
|
+
num_layers,
|
|
372
|
+
tangent=tangent,
|
|
373
|
+
deleted_element_ids=deleted_tuple,
|
|
374
|
+
residual_stiffness_fraction=float(residual_stiffness_fraction),
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
force_reduced, tangent_reduced, trial_states = assemble_reduced_system(
|
|
378
|
+
nonlinear_plan,
|
|
379
|
+
context.reduced_plan,
|
|
380
|
+
displacements,
|
|
381
|
+
committed_states,
|
|
382
|
+
tangent=tangent,
|
|
383
|
+
)
|
|
384
|
+
with _STATUS_LOCK:
|
|
385
|
+
_STATUS["reduced_assemblies"] += 1
|
|
386
|
+
_STATUS["last_plan"] = context.reduced_plan.diagnostics()
|
|
387
|
+
force_payload = _ReducedVectorPayload(force_reduced, context.token)
|
|
388
|
+
tangent_payload = (
|
|
389
|
+
_ReducedMatrixPayload(tangent_reduced, context.token)
|
|
390
|
+
if tangent_reduced is not None
|
|
391
|
+
else None
|
|
392
|
+
)
|
|
393
|
+
return force_payload, tangent_payload, trial_states
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _record_patch(module: ModuleType, name: str, replacement: Any) -> None:
|
|
397
|
+
current = getattr(module, name, None)
|
|
398
|
+
if current is replacement:
|
|
399
|
+
return
|
|
400
|
+
_PATCHES.append((module, name, current, replacement))
|
|
401
|
+
setattr(module, name, replacement)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _replace_function_references(original: Any, replacement: Any) -> None:
|
|
405
|
+
for module in list(sys.modules.values()):
|
|
406
|
+
if not isinstance(module, ModuleType):
|
|
407
|
+
continue
|
|
408
|
+
module_name = getattr(module, "__name__", "")
|
|
409
|
+
if not module_name.startswith("anysolver") or module_name == __name__:
|
|
410
|
+
continue
|
|
411
|
+
for name, value in list(vars(module).items()):
|
|
412
|
+
if value is original:
|
|
413
|
+
_record_patch(module, name, replacement)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def install_batch_c_optimizations() -> bool:
|
|
417
|
+
"""Install direct reduced-coordinate assembly for Numba-enabled runs."""
|
|
418
|
+
|
|
419
|
+
global _INSTALLED, _BASE_ASSEMBLER, _ORIGINAL_STATIC_SOLVER
|
|
420
|
+
global _ORIGINAL_ARC_SOLVER, _ORIGINAL_CONSTRAINT_BUILDER
|
|
421
|
+
if _INSTALLED:
|
|
422
|
+
return True
|
|
423
|
+
if not JIT_ENABLED:
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
from . import arc_length
|
|
427
|
+
from . import assembly
|
|
428
|
+
from . import nonlinear_static
|
|
429
|
+
|
|
430
|
+
_BASE_ASSEMBLER = nonlinear_static._assemble_nonlinear_system
|
|
431
|
+
_ORIGINAL_STATIC_SOLVER = nonlinear_static.solve_static_nonlinear
|
|
432
|
+
_ORIGINAL_ARC_SOLVER = arc_length.solve_static_arc_length
|
|
433
|
+
_ORIGINAL_CONSTRAINT_BUILDER = assembly.build_constraint_transformation
|
|
434
|
+
|
|
435
|
+
static_wrapper = _make_solver_wrapper(_ORIGINAL_STATIC_SOLVER)
|
|
436
|
+
arc_wrapper = _make_solver_wrapper(_ORIGINAL_ARC_SOLVER)
|
|
437
|
+
_replace_function_references(_ORIGINAL_STATIC_SOLVER, static_wrapper)
|
|
438
|
+
_replace_function_references(_ORIGINAL_ARC_SOLVER, arc_wrapper)
|
|
439
|
+
_replace_function_references(
|
|
440
|
+
_ORIGINAL_CONSTRAINT_BUILDER,
|
|
441
|
+
_constraint_builder_wrapper,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
for module in list(sys.modules.values()):
|
|
445
|
+
if not isinstance(module, ModuleType):
|
|
446
|
+
continue
|
|
447
|
+
module_name = getattr(module, "__name__", "")
|
|
448
|
+
if not module_name.startswith("anysolver") or module_name == __name__:
|
|
449
|
+
continue
|
|
450
|
+
if hasattr(module, "_assemble_nonlinear_system"):
|
|
451
|
+
_record_patch(
|
|
452
|
+
module,
|
|
453
|
+
"_assemble_nonlinear_system",
|
|
454
|
+
_batch_c_assemble_nonlinear_system,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
_reduced._evaluate_local_responses = _batch_c_evaluate_local_responses
|
|
458
|
+
_INSTALLED = True
|
|
459
|
+
with _STATUS_LOCK:
|
|
460
|
+
_STATUS["installed"] = True
|
|
461
|
+
return True
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def uninstall_batch_c_optimizations() -> None:
|
|
465
|
+
"""Restore references changed by :func:`install_batch_c_optimizations`."""
|
|
466
|
+
|
|
467
|
+
global _INSTALLED
|
|
468
|
+
if not _INSTALLED:
|
|
469
|
+
return
|
|
470
|
+
_reduced._evaluate_local_responses = _ORIGINAL_LOCAL_EVALUATOR
|
|
471
|
+
for module, name, original, replacement in reversed(_PATCHES):
|
|
472
|
+
if getattr(module, name, None) is replacement:
|
|
473
|
+
setattr(module, name, original)
|
|
474
|
+
_PATCHES.clear()
|
|
475
|
+
_context_stack().clear()
|
|
476
|
+
_INSTALLED = False
|
|
477
|
+
with _STATUS_LOCK:
|
|
478
|
+
_STATUS["installed"] = False
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def reset_batch_c_counters() -> None:
|
|
482
|
+
with _STATUS_LOCK:
|
|
483
|
+
installed = bool(_STATUS["installed"])
|
|
484
|
+
_STATUS.update(
|
|
485
|
+
{
|
|
486
|
+
"installed": installed,
|
|
487
|
+
"contexts_entered": 0,
|
|
488
|
+
"reduced_plan_builds": 0,
|
|
489
|
+
"reduced_assemblies": 0,
|
|
490
|
+
"full_coordinate_fallbacks": 0,
|
|
491
|
+
"last_fallback_reason": None,
|
|
492
|
+
"last_plan": None,
|
|
493
|
+
}
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def batch_c_status() -> Dict[str, Any]:
|
|
498
|
+
with _STATUS_LOCK:
|
|
499
|
+
result = dict(_STATUS)
|
|
500
|
+
result["eligible"] = bool(JIT_ENABLED)
|
|
501
|
+
result["map_limit_mb"] = float(_maximum_map_bytes() / (1024.0**2))
|
|
502
|
+
result["active_context_depth"] = len(_context_stack())
|
|
503
|
+
result["batch_b_local_kernel_retained"] = bool(
|
|
504
|
+
_reduced._evaluate_local_responses is _batch_c_evaluate_local_responses
|
|
505
|
+
)
|
|
506
|
+
return result
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Bootstrap nonlinear performance batches without requiring hashable FEModel objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
import weakref
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from . import linalg as _linalg
|
|
10
|
+
from . import nonlinear_performance as _performance
|
|
11
|
+
from . import nonlinear_performance_batch_b as _batch_b
|
|
12
|
+
from . import nonlinear_performance_batch_c as _batch_c
|
|
13
|
+
from .jit_compiler import JIT_DISABLED_REASON, JIT_ENABLED
|
|
14
|
+
|
|
15
|
+
# Keep one size-aware backend policy in every environment. AutoSparseSolverBackend
|
|
16
|
+
# already falls back to SuperLU when PyPardiso is unavailable, while also
|
|
17
|
+
# recording the same provenance metadata used when PyPardiso is installed.
|
|
18
|
+
if not isinstance(_linalg.DEFAULT_BACKEND, _linalg.AutoSparseSolverBackend):
|
|
19
|
+
_linalg.DEFAULT_BACKEND = _linalg.AutoSparseSolverBackend()
|
|
20
|
+
|
|
21
|
+
_CACHE_LOCK = threading.RLock()
|
|
22
|
+
# FEModel is a mutable dataclass and therefore intentionally unhashable. Cache
|
|
23
|
+
# by object identity and keep a weak reference so entries disappear naturally.
|
|
24
|
+
_PLAN_CACHE: Dict[int, tuple[weakref.ReferenceType[Any], Dict[int, Any]]] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _purge_dead(model_id: int) -> None:
|
|
28
|
+
with _CACHE_LOCK:
|
|
29
|
+
_PLAN_CACHE.pop(int(model_id), None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_nonlinear_assembly_plan(model: Any, num_layers: int):
|
|
33
|
+
model_id = id(model)
|
|
34
|
+
with _CACHE_LOCK:
|
|
35
|
+
entry = _PLAN_CACHE.get(model_id)
|
|
36
|
+
if entry is None or entry[0]() is not model:
|
|
37
|
+
model_ref = weakref.ref(model, lambda _ref, key=model_id: _purge_dead(key))
|
|
38
|
+
plans: Dict[int, Any] = {}
|
|
39
|
+
_PLAN_CACHE[model_id] = (model_ref, plans)
|
|
40
|
+
else:
|
|
41
|
+
plans = entry[1]
|
|
42
|
+
plan = plans.get(int(num_layers))
|
|
43
|
+
if plan is None or not plan.is_valid(model, int(num_layers)):
|
|
44
|
+
plan = _performance.NonlinearAssemblyPlan.build(model, int(num_layers))
|
|
45
|
+
plans[int(num_layers)] = plan
|
|
46
|
+
return plan
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def clear_nonlinear_assembly_cache(model: Optional[Any] = None) -> None:
|
|
50
|
+
with _CACHE_LOCK:
|
|
51
|
+
if model is None:
|
|
52
|
+
_PLAN_CACHE.clear()
|
|
53
|
+
else:
|
|
54
|
+
_PLAN_CACHE.pop(id(model), None)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def nonlinear_assembly_diagnostics(model: Optional[Any] = None) -> Dict[str, Any]:
|
|
58
|
+
with _CACHE_LOCK:
|
|
59
|
+
if model is not None:
|
|
60
|
+
entry = _PLAN_CACHE.get(id(model))
|
|
61
|
+
if entry is None or entry[0]() is not model:
|
|
62
|
+
return {}
|
|
63
|
+
return {str(layers): plan.diagnostics() for layers, plan in entry[1].items()}
|
|
64
|
+
result: Dict[str, Any] = {}
|
|
65
|
+
for model_id, (model_ref, plans) in list(_PLAN_CACHE.items()):
|
|
66
|
+
cached_model = model_ref()
|
|
67
|
+
if cached_model is None:
|
|
68
|
+
continue
|
|
69
|
+
result[str(model_id)] = {
|
|
70
|
+
"model_name": getattr(cached_model, "name", None),
|
|
71
|
+
"plans": {str(layers): plan.diagnostics() for layers, plan in plans.items()},
|
|
72
|
+
}
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_BASE_INSTALL = _performance.install_nonlinear_performance_optimizations
|
|
77
|
+
_BASE_UNINSTALL = _performance.uninstall_nonlinear_performance_optimizations
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_nonlinear_performance_optimizations() -> bool:
|
|
81
|
+
active = bool(_BASE_INSTALL())
|
|
82
|
+
if active and JIT_ENABLED:
|
|
83
|
+
_batch_b.install_batch_b_optimizations()
|
|
84
|
+
_batch_c.install_batch_c_optimizations()
|
|
85
|
+
return active
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def uninstall_nonlinear_performance_optimizations() -> None:
|
|
89
|
+
_batch_c.uninstall_batch_c_optimizations()
|
|
90
|
+
_batch_b.uninstall_batch_b_optimizations()
|
|
91
|
+
_BASE_UNINSTALL()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def nonlinear_performance_status() -> Dict[str, Any]:
|
|
95
|
+
batch_b = _batch_b.batch_b_status()
|
|
96
|
+
batch_b["eligible"] = bool(JIT_ENABLED)
|
|
97
|
+
batch_b["disabled_reason"] = None if JIT_ENABLED else JIT_DISABLED_REASON
|
|
98
|
+
batch_c = _batch_c.batch_c_status()
|
|
99
|
+
batch_c["eligible"] = bool(JIT_ENABLED)
|
|
100
|
+
batch_c["disabled_reason"] = None if JIT_ENABLED else JIT_DISABLED_REASON
|
|
101
|
+
return {
|
|
102
|
+
"installed": bool(_performance._INSTALLED),
|
|
103
|
+
"batch_b": batch_b,
|
|
104
|
+
"batch_c": batch_c,
|
|
105
|
+
"cached_models": len(_PLAN_CACHE),
|
|
106
|
+
"diagnostics": nonlinear_assembly_diagnostics(),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Replace the initial WeakKeyDictionary helpers before installation. Functions
|
|
111
|
+
# in nonlinear_performance resolve these names dynamically, so the optimized
|
|
112
|
+
# assembler immediately uses the identity/weak-reference cache above.
|
|
113
|
+
_performance.get_nonlinear_assembly_plan = get_nonlinear_assembly_plan
|
|
114
|
+
_performance.clear_nonlinear_assembly_cache = clear_nonlinear_assembly_cache
|
|
115
|
+
_performance.nonlinear_assembly_diagnostics = nonlinear_assembly_diagnostics
|
|
116
|
+
_performance.nonlinear_performance_status = nonlinear_performance_status
|
|
117
|
+
_performance.install_nonlinear_performance_optimizations = install_nonlinear_performance_optimizations
|
|
118
|
+
_performance.uninstall_nonlinear_performance_optimizations = uninstall_nonlinear_performance_optimizations
|
|
119
|
+
|
|
120
|
+
NonlinearAssemblyPlan = _performance.NonlinearAssemblyPlan
|