ANYfeSolver 0.5.0a1__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.
Files changed (751) hide show
  1. anyfesolver/__init__.py +1075 -0
  2. anyfesolver/_b2_attachment_elimination_dev.py +261 -0
  3. anyfesolver/_b2_f10_f11_point_attachment.py +163 -0
  4. anyfesolver/_b2_loaded_point_trace_dev.py +173 -0
  5. anyfesolver/_b2_physical_flexibility_linear.py +707 -0
  6. anyfesolver/_b2_physical_linear_batch_dev.py +491 -0
  7. anyfesolver/_b2_physical_linear_definition.py +415 -0
  8. anyfesolver/_b2_physical_load_bubbles.py +282 -0
  9. anyfesolver/_compensated_contractions.py +497 -0
  10. anyfesolver/_compensated_native.py +437 -0
  11. anyfesolver/_corotational_batch.py +219 -0
  12. anyfesolver/_director_trace.py +214 -0
  13. anyfesolver/_dyadic_bilinear.py +76 -0
  14. anyfesolver/_dyadic_factor_chain.py +25 -0
  15. anyfesolver/_ge_beam3_adaptive_arc_program.py +286 -0
  16. anyfesolver/_ge_beam3_analysis_combined.py +102 -0
  17. anyfesolver/_ge_beam3_analysis_fibre_translation.py +130 -0
  18. anyfesolver/_ge_beam3_analysis_modal_snapshot.py +27 -0
  19. anyfesolver/_ge_beam3_analysis_nodal_program.py +140 -0
  20. anyfesolver/_ge_beam3_analysis_translation.py +147 -0
  21. anyfesolver/_ge_beam3_analysis_translation_modal.py +84 -0
  22. anyfesolver/_ge_beam3_arc_geometry.py +89 -0
  23. anyfesolver/_ge_beam3_arc_program.py +304 -0
  24. anyfesolver/_ge_beam3_authoritative_cell_seed.py +25 -0
  25. anyfesolver/_ge_beam3_cell_identity.py +52 -0
  26. anyfesolver/_ge_beam3_centered_batched_finite_inertia.py +132 -0
  27. anyfesolver/_ge_beam3_centered_finite_inertia.py +171 -0
  28. anyfesolver/_ge_beam3_centered_mixed.py +104 -0
  29. anyfesolver/_ge_beam3_centered_reference.py +187 -0
  30. anyfesolver/_ge_beam3_centered_so3_strain.py +76 -0
  31. anyfesolver/_ge_beam3_complementary_section.py +122 -0
  32. anyfesolver/_ge_beam3_compliance_snapshot_guard.py +20 -0
  33. anyfesolver/_ge_beam3_connected_dynamic.py +486 -0
  34. anyfesolver/_ge_beam3_connected_trajectory.py +258 -0
  35. anyfesolver/_ge_beam3_conservative_backtrack.py +26 -0
  36. anyfesolver/_ge_beam3_controlled_fibre_modes.py +105 -0
  37. anyfesolver/_ge_beam3_coupled_beam_subdomain.py +128 -0
  38. anyfesolver/_ge_beam3_coupled_modes.py +256 -0
  39. anyfesolver/_ge_beam3_deterministic_reduction.py +264 -0
  40. anyfesolver/_ge_beam3_displacement_program.py +255 -0
  41. anyfesolver/_ge_beam3_durable_coupled.py +251 -0
  42. anyfesolver/_ge_beam3_dynamic_elastic_state.py +461 -0
  43. anyfesolver/_ge_beam3_effective_deadline.py +58 -0
  44. anyfesolver/_ge_beam3_elastic_seed_continuation.py +223 -0
  45. anyfesolver/_ge_beam3_elastic_seed_modal.py +139 -0
  46. anyfesolver/_ge_beam3_equilibrium_line_program.py +98 -0
  47. anyfesolver/_ge_beam3_equilibrium_schur_solver.py +96 -0
  48. anyfesolver/_ge_beam3_factor_chain_modes.py +103 -0
  49. anyfesolver/_ge_beam3_fibre_cell.py +408 -0
  50. anyfesolver/_ge_beam3_fibre_distributed_couples.py +191 -0
  51. anyfesolver/_ge_beam3_fibre_kinematic_cell.py +75 -0
  52. anyfesolver/_ge_beam3_fibre_line_program.py +157 -0
  53. anyfesolver/_ge_beam3_fibre_line_work.py +87 -0
  54. anyfesolver/_ge_beam3_fibre_reference_modal.py +104 -0
  55. anyfesolver/_ge_beam3_fibre_schur_solver.py +134 -0
  56. anyfesolver/_ge_beam3_fibre_section.py +286 -0
  57. anyfesolver/_ge_beam3_fibre_static_boundary.py +113 -0
  58. anyfesolver/_ge_beam3_g1_analysis.py +307 -0
  59. anyfesolver/_ge_beam3_g1_elastic.py +161 -0
  60. anyfesolver/_ge_beam3_g1_element.py +185 -0
  61. anyfesolver/_ge_beam3_g1_operator.py +137 -0
  62. anyfesolver/_ge_beam3_g2_analysis.py +176 -0
  63. anyfesolver/_ge_beam3_g2_constraints.py +162 -0
  64. anyfesolver/_ge_beam3_g3_analysis.py +385 -0
  65. anyfesolver/_ge_beam3_g3_constraints.py +168 -0
  66. anyfesolver/_ge_beam3_g3b_owner.py +296 -0
  67. anyfesolver/_ge_beam3_g3b_q4_reference.py +186 -0
  68. anyfesolver/_ge_beam3_g3b_reference.py +206 -0
  69. anyfesolver/_ge_beam3_g3b_result_schema.py +107 -0
  70. anyfesolver/_ge_beam3_g3b_s3_reference.py +185 -0
  71. anyfesolver/_ge_beam3_g3b_weighted_reference.py +193 -0
  72. anyfesolver/_ge_beam3_g3c_affine_q4_chart.py +58 -0
  73. anyfesolver/_ge_beam3_g3c_affine_q4_increment_chart.py +257 -0
  74. anyfesolver/_ge_beam3_g3c_affine_q4_recovery.py +434 -0
  75. anyfesolver/_ge_beam3_g3c_affine_q4_registry.py +127 -0
  76. anyfesolver/_ge_beam3_g3c_b2_physical.py +153 -0
  77. anyfesolver/_ge_beam3_g3c_b2_physical_adapter.py +160 -0
  78. anyfesolver/_ge_beam3_g3c_definition.py +117 -0
  79. anyfesolver/_ge_beam3_g3c_local_beam.py +188 -0
  80. anyfesolver/_ge_beam3_g3c_local_shell.py +371 -0
  81. anyfesolver/_ge_beam3_g3c_owner.py +400 -0
  82. anyfesolver/_ge_beam3_g3c_physical_authority.py +203 -0
  83. anyfesolver/_ge_beam3_g3c_physical_owner.py +556 -0
  84. anyfesolver/_ge_beam3_g3c_recovery.py +91 -0
  85. anyfesolver/_ge_beam3_g3c_so3_numerics.py +81 -0
  86. anyfesolver/_ge_beam3_g3c_stable/__init__.py +1 -0
  87. anyfesolver/_ge_beam3_g3c_stable/authority.py +152 -0
  88. anyfesolver/_ge_beam3_g3c_stable/beam.py +196 -0
  89. anyfesolver/_ge_beam3_g3c_stable/boundary.py +38 -0
  90. anyfesolver/_ge_beam3_g3c_stable/chart.py +23 -0
  91. anyfesolver/_ge_beam3_g3c_stable/compensated.py +46 -0
  92. anyfesolver/_ge_beam3_g3c_stable/element.py +190 -0
  93. anyfesolver/_ge_beam3_g3c_stable/fit.py +54 -0
  94. anyfesolver/_ge_beam3_g3c_stable/joint.py +255 -0
  95. anyfesolver/_ge_beam3_g3c_stable/line_work.py +92 -0
  96. anyfesolver/_ge_beam3_g3c_stable/operator.py +147 -0
  97. anyfesolver/_ge_beam3_g3c_stable/owner.py +415 -0
  98. anyfesolver/_ge_beam3_g3c_stable/shell.py +358 -0
  99. anyfesolver/_ge_beam3_g4_completion.py +186 -0
  100. anyfesolver/_ge_beam3_g4a_station_transaction.py +385 -0
  101. anyfesolver/_ge_beam3_g5_completion.py +68 -0
  102. anyfesolver/_ge_beam3_g6_domain.py +354 -0
  103. anyfesolver/_ge_beam3_generalized_cell.py +172 -0
  104. anyfesolver/_ge_beam3_generalized_ellipsoid_section.py +215 -0
  105. anyfesolver/_ge_beam3_generalized_static_boundary.py +233 -0
  106. anyfesolver/_ge_beam3_lifted_kinetic_factor.py +26 -0
  107. anyfesolver/_ge_beam3_line_fibre_modes.py +121 -0
  108. anyfesolver/_ge_beam3_load_program.py +275 -0
  109. anyfesolver/_ge_beam3_loaded_modal.py +225 -0
  110. anyfesolver/_ge_beam3_mixed_ad.py +286 -0
  111. anyfesolver/_ge_beam3_native_analysis.py +484 -0
  112. anyfesolver/_ge_beam3_native_arc.py +269 -0
  113. anyfesolver/_ge_beam3_native_arc_adaptive.py +251 -0
  114. anyfesolver/_ge_beam3_native_arc_restart.py +88 -0
  115. anyfesolver/_ge_beam3_native_arc_source.py +62 -0
  116. anyfesolver/_ge_beam3_native_buckling.py +206 -0
  117. anyfesolver/_ge_beam3_native_combined_couples.py +179 -0
  118. anyfesolver/_ge_beam3_native_combined_restart.py +205 -0
  119. anyfesolver/_ge_beam3_native_definition.py +238 -0
  120. anyfesolver/_ge_beam3_native_distributed_element.py +220 -0
  121. anyfesolver/_ge_beam3_native_distributed_loading.py +92 -0
  122. anyfesolver/_ge_beam3_native_distributed_program.py +165 -0
  123. anyfesolver/_ge_beam3_native_distributed_recovery.py +34 -0
  124. anyfesolver/_ge_beam3_native_distributed_restart.py +221 -0
  125. anyfesolver/_ge_beam3_native_dynamic_analysis.py +149 -0
  126. anyfesolver/_ge_beam3_native_dynamic_restart.py +261 -0
  127. anyfesolver/_ge_beam3_native_fibre_current_modal.py +104 -0
  128. anyfesolver/_ge_beam3_native_fibre_recovery.py +34 -0
  129. anyfesolver/_ge_beam3_native_fibre_restart.py +211 -0
  130. anyfesolver/_ge_beam3_native_fibre_static_element.py +193 -0
  131. anyfesolver/_ge_beam3_native_force_adaptation.py +56 -0
  132. anyfesolver/_ge_beam3_native_generalized_combined_couples.py +188 -0
  133. anyfesolver/_ge_beam3_native_generalized_combined_restart.py +207 -0
  134. anyfesolver/_ge_beam3_native_generalized_element.py +222 -0
  135. anyfesolver/_ge_beam3_native_generalized_factor_modal.py +122 -0
  136. anyfesolver/_ge_beam3_native_generalized_loading.py +92 -0
  137. anyfesolver/_ge_beam3_native_generalized_modal.py +274 -0
  138. anyfesolver/_ge_beam3_native_generalized_paired_modal.py +13 -0
  139. anyfesolver/_ge_beam3_native_generalized_parameter.py +89 -0
  140. anyfesolver/_ge_beam3_native_generalized_program.py +196 -0
  141. anyfesolver/_ge_beam3_native_generalized_recovery.py +34 -0
  142. anyfesolver/_ge_beam3_native_generalized_restart.py +243 -0
  143. anyfesolver/_ge_beam3_native_history_profile.py +53 -0
  144. anyfesolver/_ge_beam3_native_line_loading.py +93 -0
  145. anyfesolver/_ge_beam3_native_line_program.py +150 -0
  146. anyfesolver/_ge_beam3_native_line_recovery.py +34 -0
  147. anyfesolver/_ge_beam3_native_line_restart.py +197 -0
  148. anyfesolver/_ge_beam3_native_line_static_element.py +220 -0
  149. anyfesolver/_ge_beam3_native_load_admission.py +37 -0
  150. anyfesolver/_ge_beam3_native_midpoint.py +1004 -0
  151. anyfesolver/_ge_beam3_native_multi_shell_contact.py +539 -0
  152. anyfesolver/_ge_beam3_native_pair_contact.py +236 -0
  153. anyfesolver/_ge_beam3_native_shell_contact.py +357 -0
  154. anyfesolver/_ge_beam3_native_spatial_couples.py +176 -0
  155. anyfesolver/_ge_beam3_native_spatial_restart.py +204 -0
  156. anyfesolver/_ge_beam3_native_transient.py +467 -0
  157. anyfesolver/_ge_beam3_native_translation.py +225 -0
  158. anyfesolver/_ge_beam3_native_translation_restart.py +54 -0
  159. anyfesolver/_ge_beam3_operator_validation_scope.py +53 -0
  160. anyfesolver/_ge_beam3_p5/__init__.py +11 -0
  161. anyfesolver/_ge_beam3_p5/algebra.py +91 -0
  162. anyfesolver/_ge_beam3_p5/arrays.py +29 -0
  163. anyfesolver/_ge_beam3_p5/chart.py +45 -0
  164. anyfesolver/_ge_beam3_p5/codec.py +82 -0
  165. anyfesolver/_ge_beam3_p5/committed_modal.py +191 -0
  166. anyfesolver/_ge_beam3_p5/compensated.py +118 -0
  167. anyfesolver/_ge_beam3_p5/compensated_coordinates.py +44 -0
  168. anyfesolver/_ge_beam3_p5/core.py +159 -0
  169. anyfesolver/_ge_beam3_p5/element.py +207 -0
  170. anyfesolver/_ge_beam3_p5/factors.py +61 -0
  171. anyfesolver/_ge_beam3_p5/inertia.py +120 -0
  172. anyfesolver/_ge_beam3_p5/mass.py +70 -0
  173. anyfesolver/_ge_beam3_p5/mixed.py +256 -0
  174. anyfesolver/_ge_beam3_p5/reference_modal.py +73 -0
  175. anyfesolver/_ge_beam3_p5/section.py +139 -0
  176. anyfesolver/_ge_beam3_p5/typed_schema.py +111 -0
  177. anyfesolver/_ge_beam3_p5_centered/__init__.py +6 -0
  178. anyfesolver/_ge_beam3_p5_centered/codec.py +84 -0
  179. anyfesolver/_ge_beam3_p5_centered/committed_modal.py +192 -0
  180. anyfesolver/_ge_beam3_p5_centered/core.py +171 -0
  181. anyfesolver/_ge_beam3_p5_centered/element.py +216 -0
  182. anyfesolver/_ge_beam3_p5_centered/mass.py +99 -0
  183. anyfesolver/_ge_beam3_p5_centered/positions.py +34 -0
  184. anyfesolver/_ge_beam3_p5_centered/reference_modal.py +73 -0
  185. anyfesolver/_ge_beam3_p5_coordinates/__init__.py +6 -0
  186. anyfesolver/_ge_beam3_p5_coordinates/codec.py +84 -0
  187. anyfesolver/_ge_beam3_p5_coordinates/committed_modal.py +192 -0
  188. anyfesolver/_ge_beam3_p5_coordinates/core.py +163 -0
  189. anyfesolver/_ge_beam3_p5_coordinates/element.py +213 -0
  190. anyfesolver/_ge_beam3_p5_coordinates/positions.py +102 -0
  191. anyfesolver/_ge_beam3_p5_coordinates/reference_modal.py +73 -0
  192. anyfesolver/_ge_beam3_p5_loads/__init__.py +7 -0
  193. anyfesolver/_ge_beam3_p5_loads/codec.py +85 -0
  194. anyfesolver/_ge_beam3_p5_loads/core.py +192 -0
  195. anyfesolver/_ge_beam3_p5_loads/element.py +243 -0
  196. anyfesolver/_ge_beam3_p5_loads/state.py +120 -0
  197. anyfesolver/_ge_beam3_p5_loads/work.py +45 -0
  198. anyfesolver/_ge_beam3_p5_seeded/__init__.py +9 -0
  199. anyfesolver/_ge_beam3_p5_seeded/codec.py +87 -0
  200. anyfesolver/_ge_beam3_p5_seeded/core.py +197 -0
  201. anyfesolver/_ge_beam3_p5_seeded/element.py +245 -0
  202. anyfesolver/_ge_beam3_p5_seeded/state.py +122 -0
  203. anyfesolver/_ge_beam3_p5_seeded/work.py +47 -0
  204. anyfesolver/_ge_beam3_pose_joint.py +268 -0
  205. anyfesolver/_ge_beam3_precise_geometric_work.py +147 -0
  206. anyfesolver/_ge_beam3_reassembled_signed_modes.py +101 -0
  207. anyfesolver/_ge_beam3_reference_identity.py +92 -0
  208. anyfesolver/_ge_beam3_refined_fibre_schur_solver.py +104 -0
  209. anyfesolver/_ge_beam3_refinement_capacity.py +19 -0
  210. anyfesolver/_ge_beam3_relative_virgin_reference_modes.py +60 -0
  211. anyfesolver/_ge_beam3_retained_arc.py +350 -0
  212. anyfesolver/_ge_beam3_retained_arc_geometry.py +66 -0
  213. anyfesolver/_ge_beam3_retained_arc_modal.py +125 -0
  214. anyfesolver/_ge_beam3_retained_elastic.py +134 -0
  215. anyfesolver/_ge_beam3_retained_fibre.py +209 -0
  216. anyfesolver/_ge_beam3_retained_fibre_control.py +270 -0
  217. anyfesolver/_ge_beam3_retained_fibre_element.py +58 -0
  218. anyfesolver/_ge_beam3_retained_fibre_modal.py +155 -0
  219. anyfesolver/_ge_beam3_retained_fibre_modes.py +189 -0
  220. anyfesolver/_ge_beam3_retained_fibre_program.py +68 -0
  221. anyfesolver/_ge_beam3_retained_fibre_state.py +268 -0
  222. anyfesolver/_ge_beam3_retained_force_program.py +72 -0
  223. anyfesolver/_ge_beam3_retained_generalized.py +186 -0
  224. anyfesolver/_ge_beam3_retained_generalized_modal.py +171 -0
  225. anyfesolver/_ge_beam3_retained_generalized_program.py +63 -0
  226. anyfesolver/_ge_beam3_retained_generalized_state.py +281 -0
  227. anyfesolver/_ge_beam3_retained_nodal_loading.py +111 -0
  228. anyfesolver/_ge_beam3_retained_plastic.py +116 -0
  229. anyfesolver/_ge_beam3_retained_plastic_modes.py +144 -0
  230. anyfesolver/_ge_beam3_retained_plastic_program.py +69 -0
  231. anyfesolver/_ge_beam3_retained_plastic_state.py +165 -0
  232. anyfesolver/_ge_beam3_retained_state.py +194 -0
  233. anyfesolver/_ge_beam3_retained_translation_control.py +345 -0
  234. anyfesolver/_ge_beam3_retained_translation_modal.py +137 -0
  235. anyfesolver/_ge_beam3_schur_line_program.py +98 -0
  236. anyfesolver/_ge_beam3_seeded_fibre_control.py +175 -0
  237. anyfesolver/_ge_beam3_seeded_load_program.py +301 -0
  238. anyfesolver/_ge_beam3_seeded_loaded_modal.py +226 -0
  239. anyfesolver/_ge_beam3_seeded_signed_modes.py +150 -0
  240. anyfesolver/_ge_beam3_shared_kinematic_split.py +53 -0
  241. anyfesolver/_ge_beam3_shell_joint_state.py +396 -0
  242. anyfesolver/_ge_beam3_shell_joint_trial.py +217 -0
  243. anyfesolver/_ge_beam3_signed_loaded_modes.py +140 -0
  244. anyfesolver/_ge_beam3_spatial_fibre_program.py +113 -0
  245. anyfesolver/_ge_beam3_spatial_nodal_moments.py +38 -0
  246. anyfesolver/_ge_beam3_station_resultant_cell.py +235 -0
  247. anyfesolver/_ge_beam3_variational_shell.py +211 -0
  248. anyfesolver/_ge_beam3_virgin_reference_modes.py +60 -0
  249. anyfesolver/_impact_coupling.py +162 -0
  250. anyfesolver/_native_authority_dag.py +77 -0
  251. anyfesolver/_native_dyadic_interval_inertia.py +64 -0
  252. anyfesolver/_native_exact_shift_chain_modes.py +140 -0
  253. anyfesolver/_native_exact_shift_inertia.py +86 -0
  254. anyfesolver/_native_factor_chain_modes.py +192 -0
  255. anyfesolver/_native_guard.cpp +828 -0
  256. anyfesolver/_native_guard_dispatch.py +140 -0
  257. anyfesolver/_native_large_factor_chain.py +55 -0
  258. anyfesolver/_native_material_protocol.py +51 -0
  259. anyfesolver/_native_midpoint_controller.py +90 -0
  260. anyfesolver/_native_modal_capacity.py +26 -0
  261. anyfesolver/_native_paired_factor_chain_modes.py +244 -0
  262. anyfesolver/_native_reassembled_factor_modes.py +185 -0
  263. anyfesolver/_native_reference_factor_spectrum.py +102 -0
  264. anyfesolver/_native_reference_modal.py +180 -0
  265. anyfesolver/_native_relative_factor_chain_modes.py +204 -0
  266. anyfesolver/_native_relative_reference_factor_spectrum.py +110 -0
  267. anyfesolver/_native_retained_step.py +15 -0
  268. anyfesolver/_native_rotation_state.py +797 -0
  269. anyfesolver/_native_signed_factor_modes.py +257 -0
  270. anyfesolver/_native_signed_kinetic_factor_modes.py +279 -0
  271. anyfesolver/_native_stationary_spectrum.py +79 -0
  272. anyfesolver/_nonlinear_events.py +150 -0
  273. anyfesolver/_nonlinear_seed.py +80 -0
  274. anyfesolver/_paired_modal_arithmetic.py +71 -0
  275. anyfesolver/_plate_stability_continuation.py +171 -0
  276. anyfesolver/_q4_assumed_strain_plasticity.py +502 -0
  277. anyfesolver/_qualified_authority_epoch.py +561 -0
  278. anyfesolver/_relative_reference_svd.py +44 -0
  279. anyfesolver/_stiffened_shell_affine_endpoint.py +232 -0
  280. anyfesolver/_stiffened_shell_algorithmic_pullback.py +38 -0
  281. anyfesolver/_stiffened_shell_arc_checkpoint.py +422 -0
  282. anyfesolver/_stiffened_shell_arc_control.py +108 -0
  283. anyfesolver/_stiffened_shell_arc_metric.py +222 -0
  284. anyfesolver/_stiffened_shell_arc_publication.py +153 -0
  285. anyfesolver/_stiffened_shell_arc_trial.py +310 -0
  286. anyfesolver/_stiffened_shell_b2_affine_resolution.py +101 -0
  287. anyfesolver/_stiffened_shell_b2_assembly_diagnostics.py +172 -0
  288. anyfesolver/_stiffened_shell_b2_definition_ownership.py +189 -0
  289. anyfesolver/_stiffened_shell_b2_experimental.py +153 -0
  290. anyfesolver/_stiffened_shell_b2_nodal_footprint.py +273 -0
  291. anyfesolver/_stiffened_shell_b2_physical_rigid_footprint.py +237 -0
  292. anyfesolver/_stiffened_shell_b2_q4_panel_experimental.py +2447 -0
  293. anyfesolver/_stiffened_shell_b2_q4_point_experimental.py +251 -0
  294. anyfesolver/_stiffened_shell_b2_reference_buckling.py +413 -0
  295. anyfesolver/_stiffened_shell_b3_chart.py +70 -0
  296. anyfesolver/_stiffened_shell_b3_local_adapter.py +141 -0
  297. anyfesolver/_stiffened_shell_beam_history.py +107 -0
  298. anyfesolver/_stiffened_shell_bl5_local_experimental.py +173 -0
  299. anyfesolver/_stiffened_shell_bl5_native_elastic.py +28 -0
  300. anyfesolver/_stiffened_shell_branch_event_trial.py +58 -0
  301. anyfesolver/_stiffened_shell_consumer_capabilities.py +124 -0
  302. anyfesolver/_stiffened_shell_coordinate_support.py +110 -0
  303. anyfesolver/_stiffened_shell_displacement_adaptive.py +327 -0
  304. anyfesolver/_stiffened_shell_displacement_checkpoint.py +360 -0
  305. anyfesolver/_stiffened_shell_displacement_control.py +326 -0
  306. anyfesolver/_stiffened_shell_displacement_corrector.py +293 -0
  307. anyfesolver/_stiffened_shell_displacement_increment.py +121 -0
  308. anyfesolver/_stiffened_shell_distributing_footprint.py +354 -0
  309. anyfesolver/_stiffened_shell_distributing_pose.py +267 -0
  310. anyfesolver/_stiffened_shell_f06_increment_arithmetic.py +205 -0
  311. anyfesolver/_stiffened_shell_ge_body_load.py +300 -0
  312. anyfesolver/_stiffened_shell_ge_checkpoint_binary.py +472 -0
  313. anyfesolver/_stiffened_shell_ge_distributed_load.py +210 -0
  314. anyfesolver/_stiffened_shell_ge_distributed_programme.py +175 -0
  315. anyfesolver/_stiffened_shell_ge_distributing_finite.py +242 -0
  316. anyfesolver/_stiffened_shell_ge_distributing_reference.py +296 -0
  317. anyfesolver/_stiffened_shell_ge_experimental.py +2454 -0
  318. anyfesolver/_stiffened_shell_ge_finite_experimental.py +2714 -0
  319. anyfesolver/_stiffened_shell_ge_mixed_modal_mass.py +33 -0
  320. anyfesolver/_stiffened_shell_ge_point_buckling.py +723 -0
  321. anyfesolver/_stiffened_shell_ge_pressure_load.py +366 -0
  322. anyfesolver/_stiffened_shell_ge_reference_modal.py +580 -0
  323. anyfesolver/_stiffened_shell_generalized_recovery.py +150 -0
  324. anyfesolver/_stiffened_shell_increment_control.py +217 -0
  325. anyfesolver/_stiffened_shell_l1_assembly_experimental.py +420 -0
  326. anyfesolver/_stiffened_shell_l1_local_experimental.py +284 -0
  327. anyfesolver/_stiffened_shell_l1_supported_work_experimental.py +146 -0
  328. anyfesolver/_stiffened_shell_native_distributed_load.py +120 -0
  329. anyfesolver/_stiffened_shell_native_gravity.py +236 -0
  330. anyfesolver/_stiffened_shell_nodal_footprint.py +255 -0
  331. anyfesolver/_stiffened_shell_physical_q4_mass.py +98 -0
  332. anyfesolver/_stiffened_shell_physical_s3_mass.py +53 -0
  333. anyfesolver/_stiffened_shell_point_recovery.py +49 -0
  334. anyfesolver/_stiffened_shell_q4_algorithmic_pullback.py +88 -0
  335. anyfesolver/_stiffened_shell_q4_material_event.py +114 -0
  336. anyfesolver/_stiffened_shell_q4_reference.py +65 -0
  337. anyfesolver/_stiffened_shell_rotation_charts.py +58 -0
  338. anyfesolver/_stiffened_shell_s3_algorithmic_pullback.py +59 -0
  339. anyfesolver/_stiffened_shell_s3_conservative_buckling.py +28 -0
  340. anyfesolver/_stiffened_shell_s3_dead_work_candidate.py +107 -0
  341. anyfesolver/_stiffened_shell_s3_material_event.py +30 -0
  342. anyfesolver/_stiffened_shell_same_parent_trial.py +224 -0
  343. anyfesolver/_stiffened_shell_selected_recovery.py +113 -0
  344. anyfesolver/_stiffened_shell_staged_load_experimental.py +392 -0
  345. anyfesolver/_stiffened_shell_stress_free_reference.py +204 -0
  346. anyfesolver/_wire_types.py +17 -0
  347. anyfesolver/activity.py +1537 -0
  348. anyfesolver/algebraic_dynamics.py +2864 -0
  349. anyfesolver/analysis_session.py +897 -0
  350. anyfesolver/anystructure_fem_mode.py +1637 -0
  351. anyfesolver/arc_length.py +2587 -0
  352. anyfesolver/assembly.py +1830 -0
  353. anyfesolver/b3_ge.py +179 -0
  354. anyfesolver/baselines.py +303 -0
  355. anyfesolver/beam_sections.py +292 -0
  356. anyfesolver/beam_shell_verification.py +4685 -0
  357. anyfesolver/beam_validity.py +110 -0
  358. anyfesolver/benchmarks.py +495 -0
  359. anyfesolver/boundary.py +1680 -0
  360. anyfesolver/buckling.py +2136 -0
  361. anyfesolver/buckling_validity.py +91 -0
  362. anyfesolver/capacity_workflow.py +431 -0
  363. anyfesolver/capsule_runtime_guard.py +99 -0
  364. anyfesolver/cases.py +173 -0
  365. anyfesolver/composite_strip_verification.py +297 -0
  366. anyfesolver/concentrated_mass.py +84 -0
  367. anyfesolver/constraint_audit.py +553 -0
  368. anyfesolver/contact.py +4785 -0
  369. anyfesolver/contact_performance.py +301 -0
  370. anyfesolver/control.py +206 -0
  371. anyfesolver/corotational.py +1935 -0
  372. anyfesolver/corotational_performance.py +157 -0
  373. anyfesolver/current_state_tangent.py +2780 -0
  374. anyfesolver/cylinder_benchmarks.py +369 -0
  375. anyfesolver/damage_matrix_performance.py +617 -0
  376. anyfesolver/dynamics.py +2833 -0
  377. anyfesolver/e4_pl_element.py +8265 -0
  378. anyfesolver/e4_pl_enriched.py +194 -0
  379. anyfesolver/e4_pl_enriched_nonlinear.py +159 -0
  380. anyfesolver/e4_pl_s3_element.py +9752 -0
  381. anyfesolver/e4_pl_s3_initial_fields.py +101 -0
  382. anyfesolver/e4_pl_s3_state.py +4723 -0
  383. anyfesolver/e4_pl_s3_v2_element.py +1844 -0
  384. anyfesolver/e4_pl_s3_v2b_element.py +1936 -0
  385. anyfesolver/e4_pl_s3_v2c_element.py +2196 -0
  386. anyfesolver/e4_pl_s3_v2d_element.py +2621 -0
  387. anyfesolver/e4_pl_s3_v2d_state.py +376 -0
  388. anyfesolver/element_capabilities.py +159 -0
  389. anyfesolver/element_qualification.py +432 -0
  390. anyfesolver/elements.py +6327 -0
  391. anyfesolver/experimental_csr_assembly.py +238 -0
  392. anyfesolver/external_references.py +1253 -0
  393. anyfesolver/fe_core.py +832 -0
  394. anyfesolver/floating_pressure_regions.py +119 -0
  395. anyfesolver/floating_response.py +486 -0
  396. anyfesolver/floating_transient.py +1281 -0
  397. anyfesolver/fracture.py +621 -0
  398. anyfesolver/ge_beam3_curved_reference.py +725 -0
  399. anyfesolver/ge_beam3_element.py +885 -0
  400. anyfesolver/ge_beam3_mixed_element.py +1216 -0
  401. anyfesolver/ge_beam3_mixed_state.py +814 -0
  402. anyfesolver/ge_beam3_native.py +129 -0
  403. anyfesolver/ge_beam3_native_connected_dynamics.py +24 -0
  404. anyfesolver/ge_beam3_native_dynamics.py +70 -0
  405. anyfesolver/ge_beam3_state.py +672 -0
  406. anyfesolver/general_contact/__init__.py +151 -0
  407. anyfesolver/general_contact/_enriched_multitarget_empty.py +202 -0
  408. anyfesolver/general_contact/_enriched_multitarget_trial.py +183 -0
  409. anyfesolver/general_contact/_results_archive_common.py +65 -0
  410. anyfesolver/general_contact/algebraic_path.py +206 -0
  411. anyfesolver/general_contact/arc_contact.py +237 -0
  412. anyfesolver/general_contact/arc_results_archive.py +158 -0
  413. anyfesolver/general_contact/augmentation.py +157 -0
  414. anyfesolver/general_contact/automatic_domain.py +621 -0
  415. anyfesolver/general_contact/beam_chain_consistent.py +357 -0
  416. anyfesolver/general_contact/beam_chain_end_geometry.py +237 -0
  417. anyfesolver/general_contact/beam_chain_ends.py +220 -0
  418. anyfesolver/general_contact/beam_chain_pair.py +416 -0
  419. anyfesolver/general_contact/beam_curve_end_geometry.py +254 -0
  420. anyfesolver/general_contact/beam_curve_ends.py +237 -0
  421. anyfesolver/general_contact/beam_curve_pair.py +794 -0
  422. anyfesolver/general_contact/beam_curve_pair_static.py +303 -0
  423. anyfesolver/general_contact/beam_end_radial_geometry.py +221 -0
  424. anyfesolver/general_contact/beam_end_topology.py +15 -0
  425. anyfesolver/general_contact/beam_hemisphere_coupled_tool.py +61 -0
  426. anyfesolver/general_contact/beam_hemisphere_cylinder.py +134 -0
  427. anyfesolver/general_contact/beam_hemisphere_cylinder_motion.py +89 -0
  428. anyfesolver/general_contact/beam_hemisphere_cylinder_work.py +216 -0
  429. anyfesolver/general_contact/beam_hemisphere_plane.py +175 -0
  430. anyfesolver/general_contact/beam_hemisphere_s3_support.py +152 -0
  431. anyfesolver/general_contact/beam_hemisphere_sphere.py +135 -0
  432. anyfesolver/general_contact/beam_hemisphere_sphere_work.py +114 -0
  433. anyfesolver/general_contact/beam_hemisphere_support.py +211 -0
  434. anyfesolver/general_contact/beam_pair.py +523 -0
  435. anyfesolver/general_contact/beam_pair_end_geometry.py +269 -0
  436. anyfesolver/general_contact/beam_pair_ends.py +222 -0
  437. anyfesolver/general_contact/beam_plane.py +425 -0
  438. anyfesolver/general_contact/beam_radial.py +500 -0
  439. anyfesolver/general_contact/beam_radial_ends.py +223 -0
  440. anyfesolver/general_contact/beam_s3_directional_rates.py +196 -0
  441. anyfesolver/general_contact/beam_s3_rotational_transient.py +126 -0
  442. anyfesolver/general_contact/beam_s3_transient.py +139 -0
  443. anyfesolver/general_contact/beam_shell.py +648 -0
  444. anyfesolver/general_contact/beam_shell_patch.py +395 -0
  445. anyfesolver/general_contact/beam_temporal_bounds.py +230 -0
  446. anyfesolver/general_contact/capability.py +101 -0
  447. anyfesolver/general_contact/capped_cylinder.py +618 -0
  448. anyfesolver/general_contact/contact_search.py +95 -0
  449. anyfesolver/general_contact/core.py +1769 -0
  450. anyfesolver/general_contact/curved_s3_q4_work.py +238 -0
  451. anyfesolver/general_contact/curved_shell_skin.py +520 -0
  452. anyfesolver/general_contact/d1_interior_profile.py +427 -0
  453. anyfesolver/general_contact/deformable_beam_s3_caps.py +143 -0
  454. anyfesolver/general_contact/deformable_beam_s3_line.py +211 -0
  455. anyfesolver/general_contact/deformable_beam_s3_static.py +152 -0
  456. anyfesolver/general_contact/descriptor_rates.py +61 -0
  457. anyfesolver/general_contact/enriched_cold_predictor.py +161 -0
  458. anyfesolver/general_contact/enriched_moment_batch.py +337 -0
  459. anyfesolver/general_contact/enriched_multitarget.py +308 -0
  460. anyfesolver/general_contact/enriched_multitarget_gap.py +156 -0
  461. anyfesolver/general_contact/enriched_multitarget_moments.py +194 -0
  462. anyfesolver/general_contact/enriched_multitarget_topology.py +171 -0
  463. anyfesolver/general_contact/enriched_nonlinear.py +440 -0
  464. anyfesolver/general_contact/enriched_overlap.py +299 -0
  465. anyfesolver/general_contact/enriched_path.py +253 -0
  466. anyfesolver/general_contact/enriched_small_cut.py +310 -0
  467. anyfesolver/general_contact/enriched_static.py +481 -0
  468. anyfesolver/general_contact/enriched_union.py +320 -0
  469. anyfesolver/general_contact/equilibrated_pressure_pivot.py +140 -0
  470. anyfesolver/general_contact/event_subdivision.py +273 -0
  471. anyfesolver/general_contact/finite_mixed_state.py +133 -0
  472. anyfesolver/general_contact/finite_rigid_patch.py +196 -0
  473. anyfesolver/general_contact/finite_rigid_support.py +129 -0
  474. anyfesolver/general_contact/finite_rigid_sweep.py +53 -0
  475. anyfesolver/general_contact/finite_rotation_predictor.py +171 -0
  476. anyfesolver/general_contact/finite_rotation_skin.py +212 -0
  477. anyfesolver/general_contact/finite_rotation_state.py +106 -0
  478. anyfesolver/general_contact/finite_rotation_sweep.py +161 -0
  479. anyfesolver/general_contact/finite_v2d_state.py +104 -0
  480. anyfesolver/general_contact/fixed_projection_graph.py +205 -0
  481. anyfesolver/general_contact/fixed_projection_s3_events.py +235 -0
  482. anyfesolver/general_contact/free_rigid.py +134 -0
  483. anyfesolver/general_contact/friction_point.py +378 -0
  484. anyfesolver/general_contact/gap_extrema.py +119 -0
  485. anyfesolver/general_contact/guided_pair_transient_geometry.py +138 -0
  486. anyfesolver/general_contact/initial_contact_seed.py +368 -0
  487. anyfesolver/general_contact/linearized_pivot.py +595 -0
  488. anyfesolver/general_contact/material_work_diagnostics.py +159 -0
  489. anyfesolver/general_contact/moment_batch_ad.py +129 -0
  490. anyfesolver/general_contact/moment_batch_points.py +75 -0
  491. anyfesolver/general_contact/moving.py +1031 -0
  492. anyfesolver/general_contact/multiple_beam_rigid.py +248 -0
  493. anyfesolver/general_contact/multiple_beam_s3_caps.py +199 -0
  494. anyfesolver/general_contact/multiple_beam_s3_transient.py +223 -0
  495. anyfesolver/general_contact/multiple_projected_pairs.py +196 -0
  496. anyfesolver/general_contact/multiple_shell_planes.py +226 -0
  497. anyfesolver/general_contact/native_activation_static.py +143 -0
  498. anyfesolver/general_contact/native_checkpoint_results.py +213 -0
  499. anyfesolver/general_contact/native_curve_union.py +268 -0
  500. anyfesolver/general_contact/native_curved_geometry.py +374 -0
  501. anyfesolver/general_contact/native_curved_pair_partition.py +590 -0
  502. anyfesolver/general_contact/native_curved_plane_work.py +864 -0
  503. anyfesolver/general_contact/native_curved_s3_work.py +472 -0
  504. anyfesolver/general_contact/native_event_polynomial.py +164 -0
  505. anyfesolver/general_contact/native_fibre_context.py +173 -0
  506. anyfesolver/general_contact/native_fibre_results.py +74 -0
  507. anyfesolver/general_contact/native_initial_guess.py +122 -0
  508. anyfesolver/general_contact/native_material_activation.py +108 -0
  509. anyfesolver/general_contact/native_material_plane.py +111 -0
  510. anyfesolver/general_contact/native_material_s3.py +209 -0
  511. anyfesolver/general_contact/native_multi_s3_static.py +722 -0
  512. anyfesolver/general_contact/native_normal_angles.py +233 -0
  513. anyfesolver/general_contact/native_pair_error_budget.py +49 -0
  514. anyfesolver/general_contact/native_replay_arithmetic.py +103 -0
  515. anyfesolver/general_contact/native_replay_diagnostics.py +90 -0
  516. anyfesolver/general_contact/native_retained_body_junction.py +99 -0
  517. anyfesolver/general_contact/native_retained_body_pair.py +220 -0
  518. anyfesolver/general_contact/native_retained_body_projection.py +160 -0
  519. anyfesolver/general_contact/native_retained_body_static.py +62 -0
  520. anyfesolver/general_contact/native_retained_cell_pair.py +185 -0
  521. anyfesolver/general_contact/native_retained_curve_cell.py +102 -0
  522. anyfesolver/general_contact/native_retained_line_plane.py +210 -0
  523. anyfesolver/general_contact/native_retained_line_s3.py +440 -0
  524. anyfesolver/general_contact/native_retained_line_s3_work.py +91 -0
  525. anyfesolver/general_contact/native_retained_pair_static.py +845 -0
  526. anyfesolver/general_contact/native_retained_s3_static.py +554 -0
  527. anyfesolver/general_contact/native_retained_static.py +478 -0
  528. anyfesolver/general_contact/native_retained_station.py +222 -0
  529. anyfesolver/general_contact/native_retained_whole_pair.py +2120 -0
  530. anyfesolver/general_contact/native_s3_swept.py +149 -0
  531. anyfesolver/general_contact/native_shell_static_driver.py +144 -0
  532. anyfesolver/general_contact/native_surface_activation.py +391 -0
  533. anyfesolver/general_contact/native_surface_bounds.py +198 -0
  534. anyfesolver/general_contact/native_surface_contributor.py +121 -0
  535. anyfesolver/general_contact/native_surface_crossing.py +81 -0
  536. anyfesolver/general_contact/native_surface_event_sequence.py +320 -0
  537. anyfesolver/general_contact/native_surface_extrema.py +72 -0
  538. anyfesolver/general_contact/native_surface_friction_integral.py +1724 -0
  539. anyfesolver/general_contact/native_surface_front.py +742 -0
  540. anyfesolver/general_contact/native_surface_front_boundary.py +160 -0
  541. anyfesolver/general_contact/native_surface_front_precision.py +78 -0
  542. anyfesolver/general_contact/native_surface_front_work.py +118 -0
  543. anyfesolver/general_contact/native_surface_history.py +334 -0
  544. anyfesolver/general_contact/native_surface_normal.py +324 -0
  545. anyfesolver/general_contact/native_surface_patch.py +213 -0
  546. anyfesolver/general_contact/native_surface_path.py +348 -0
  547. anyfesolver/general_contact/native_surface_quadrature.py +212 -0
  548. anyfesolver/general_contact/native_surface_s3_friction.py +340 -0
  549. anyfesolver/general_contact/native_surface_support.py +776 -0
  550. anyfesolver/general_contact/native_surface_transaction.py +198 -0
  551. anyfesolver/general_contact/native_transient_multi_shell.py +108 -0
  552. anyfesolver/general_contact/native_transient_pair.py +61 -0
  553. anyfesolver/general_contact/native_transient_shell.py +44 -0
  554. anyfesolver/general_contact/normal_s3_skin_path.py +163 -0
  555. anyfesolver/general_contact/overlap.py +443 -0
  556. anyfesolver/general_contact/pair.py +466 -0
  557. anyfesolver/general_contact/patch_broadphase.py +111 -0
  558. anyfesolver/general_contact/patch_mortar.py +593 -0
  559. anyfesolver/general_contact/patch_sliding.py +644 -0
  560. anyfesolver/general_contact/plane_newmark_events.py +278 -0
  561. anyfesolver/general_contact/pointwise_patch.py +288 -0
  562. anyfesolver/general_contact/polynomial_roots.py +312 -0
  563. anyfesolver/general_contact/pressure_basis.py +241 -0
  564. anyfesolver/general_contact/pressure_diagnostics.py +155 -0
  565. anyfesolver/general_contact/pressure_overlay.py +182 -0
  566. anyfesolver/general_contact/pressure_space.py +51 -0
  567. anyfesolver/general_contact/q4_material_checkpoint.py +276 -0
  568. anyfesolver/general_contact/q4_overlap_local_bound.py +1111 -0
  569. anyfesolver/general_contact/q4_q4_overlap_derivatives.py +234 -0
  570. anyfesolver/general_contact/q4_q4_swept_bound.py +58 -0
  571. anyfesolver/general_contact/q4_s3_overlap_derivatives.py +1159 -0
  572. anyfesolver/general_contact/q4_s3_tiny_wedge.py +180 -0
  573. anyfesolver/general_contact/q4_skin_work.py +87 -0
  574. anyfesolver/general_contact/q4_source_mortar_energy.py +181 -0
  575. anyfesolver/general_contact/q4_source_overlap.py +401 -0
  576. anyfesolver/general_contact/q4_target_overlap.py +181 -0
  577. anyfesolver/general_contact/request_identity.py +192 -0
  578. anyfesolver/general_contact/rigid_translation.py +199 -0
  579. anyfesolver/general_contact/runtime.py +27 -0
  580. anyfesolver/general_contact/s3_friction_chart_interval.py +104 -0
  581. anyfesolver/general_contact/s3_friction_coverage.py +165 -0
  582. anyfesolver/general_contact/s3_friction_dyadic_interval.py +39 -0
  583. anyfesolver/general_contact/s3_friction_history_path.py +111 -0
  584. anyfesolver/general_contact/s3_friction_mesh_coverage.py +145 -0
  585. anyfesolver/general_contact/s3_friction_mesh_gap.py +74 -0
  586. anyfesolver/general_contact/s3_friction_mesh_monolithic.py +150 -0
  587. anyfesolver/general_contact/s3_friction_monolithic.py +121 -0
  588. anyfesolver/general_contact/s3_friction_moving_coverage.py +114 -0
  589. anyfesolver/general_contact/s3_friction_pair.py +237 -0
  590. anyfesolver/general_contact/s3_friction_point.py +129 -0
  591. anyfesolver/general_contact/s3_friction_prepared_path.py +254 -0
  592. anyfesolver/general_contact/s3_friction_replay.py +1094 -0
  593. anyfesolver/general_contact/s3_friction_rim_path.py +69 -0
  594. anyfesolver/general_contact/s3_friction_rim_transaction.py +23 -0
  595. anyfesolver/general_contact/s3_friction_static.py +267 -0
  596. anyfesolver/general_contact/s3_friction_swept_gap.py +112 -0
  597. anyfesolver/general_contact/s3_friction_touch.py +94 -0
  598. anyfesolver/general_contact/s3_friction_weak_pressure.py +66 -0
  599. anyfesolver/general_contact/s3_overlap_derivatives.py +220 -0
  600. anyfesolver/general_contact/s3_overlap_hermite_gap.py +180 -0
  601. anyfesolver/general_contact/s3_pair_static.py +343 -0
  602. anyfesolver/general_contact/s3_patch_augmented.py +2056 -0
  603. anyfesolver/general_contact/s3_patch_mortar_energy.py +545 -0
  604. anyfesolver/general_contact/s3_patch_static.py +1725 -0
  605. anyfesolver/general_contact/s3_pressure_hermite_bounds.py +395 -0
  606. anyfesolver/general_contact/s3_pressure_polygon_bounds.py +73 -0
  607. anyfesolver/general_contact/s3_q4_gap_resolution.py +191 -0
  608. anyfesolver/general_contact/s3_q4_overlap_derivatives.py +252 -0
  609. anyfesolver/general_contact/s3_q4_swept.py +56 -0
  610. anyfesolver/general_contact/s3_skin_work.py +101 -0
  611. anyfesolver/general_contact/s3_sliding_friction.py +640 -0
  612. anyfesolver/general_contact/s3_stable_plane_gap.py +43 -0
  613. anyfesolver/general_contact/seed_path_vertex_interval.py +323 -0
  614. anyfesolver/general_contact/semismooth.py +133 -0
  615. anyfesolver/general_contact/semismooth_block.py +147 -0
  616. anyfesolver/general_contact/semismooth_path.py +871 -0
  617. anyfesolver/general_contact/shell_pair_hermite_sweep.py +336 -0
  618. anyfesolver/general_contact/shell_sphere_temporal_bounds.py +305 -0
  619. anyfesolver/general_contact/skin_geometry.py +106 -0
  620. anyfesolver/general_contact/sliding.py +349 -0
  621. anyfesolver/general_contact/sphere.py +691 -0
  622. anyfesolver/general_contact/sphere_temporal_bounds.py +241 -0
  623. anyfesolver/general_contact/static_path.py +2608 -0
  624. anyfesolver/general_contact/static_results_archive.py +140 -0
  625. anyfesolver/general_contact/structural_state_bridge.py +196 -0
  626. anyfesolver/general_contact/supported_schur.py +594 -0
  627. anyfesolver/general_contact/swept_patch_broadphase.py +160 -0
  628. anyfesolver/general_contact/transient.py +2921 -0
  629. anyfesolver/general_contact/transient_results_archive.py +491 -0
  630. anyfesolver/general_contact/triangle_mortar_energy.py +280 -0
  631. anyfesolver/general_contact/triangle_pair_energy.py +211 -0
  632. anyfesolver/general_contact/triangulated_rigid.py +94 -0
  633. anyfesolver/general_contact/triangulated_rigid_static.py +399 -0
  634. anyfesolver/general_contact/warped_q4_plane.py +185 -0
  635. anyfesolver/general_contact/warped_q4_skin.py +302 -0
  636. anyfesolver/general_contact/warped_q4_static.py +331 -0
  637. anyfesolver/impact_performance.py +273 -0
  638. anyfesolver/impact_reduced_assembly.py +294 -0
  639. anyfesolver/imperfections.py +397 -0
  640. anyfesolver/inertia_relief.py +676 -0
  641. anyfesolver/initial_field_state.py +54 -0
  642. anyfesolver/jit_compiler.py +135 -0
  643. anyfesolver/kernel_warmup.py +228 -0
  644. anyfesolver/linalg.py +1002 -0
  645. anyfesolver/marine_contributions.py +634 -0
  646. anyfesolver/marine_coupled_equilibrium.py +292 -0
  647. anyfesolver/marine_equilibrium.py +405 -0
  648. anyfesolver/marine_hydro_coordinates.py +346 -0
  649. anyfesolver/marine_linear.py +549 -0
  650. anyfesolver/marine_link.py +306 -0
  651. anyfesolver/marine_mechanical.py +923 -0
  652. anyfesolver/marine_mooring.py +533 -0
  653. anyfesolver/marine_morison.py +331 -0
  654. anyfesolver/marine_potential_flow.py +403 -0
  655. anyfesolver/marine_rigid.py +397 -0
  656. anyfesolver/marine_seabed.py +122 -0
  657. anyfesolver/mass_properties.py +352 -0
  658. anyfesolver/material_curves.py +52 -0
  659. anyfesolver/materials.py +43 -0
  660. anyfesolver/matrix_assembly.py +7330 -0
  661. anyfesolver/mesh_gen.py +904 -0
  662. anyfesolver/mesh_load_bc_verification.py +612 -0
  663. anyfesolver/modal.py +2494 -0
  664. anyfesolver/nonlinear.py +341 -0
  665. anyfesolver/nonlinear_analysis_diagnostics.py +466 -0
  666. anyfesolver/nonlinear_element_evaluation.py +126 -0
  667. anyfesolver/nonlinear_performance.py +2094 -0
  668. anyfesolver/nonlinear_performance_batch_b.py +1173 -0
  669. anyfesolver/nonlinear_performance_batch_c.py +850 -0
  670. anyfesolver/nonlinear_performance_bootstrap.py +164 -0
  671. anyfesolver/nonlinear_reduced_assembly.py +806 -0
  672. anyfesolver/nonlinear_restart.py +1708 -0
  673. anyfesolver/nonlinear_state.py +2800 -0
  674. anyfesolver/nonlinear_static.py +9407 -0
  675. anyfesolver/outcomes.py +694 -0
  676. anyfesolver/plasticity.py +2087 -0
  677. anyfesolver/plasticity_qualification.py +758 -0
  678. anyfesolver/prepared_static.py +1129 -0
  679. anyfesolver/prepared_static_envelopes.py +389 -0
  680. anyfesolver/prepared_static_loads.py +1180 -0
  681. anyfesolver/prepared_static_measures.py +18 -0
  682. anyfesolver/prepared_static_numeric.py +247 -0
  683. anyfesolver/prepared_static_q4.py +297 -0
  684. anyfesolver/prepared_static_recovery.py +2068 -0
  685. anyfesolver/prepared_static_response.py +1013 -0
  686. anyfesolver/prepared_static_shared.py +226 -0
  687. anyfesolver/prepared_static_snapshot.py +323 -0
  688. anyfesolver/prepared_static_vectorized.py +866 -0
  689. anyfesolver/pressure_regions.py +287 -0
  690. anyfesolver/production_readiness.py +343 -0
  691. anyfesolver/quality_control.py +1487 -0
  692. anyfesolver/quantities.py +1289 -0
  693. anyfesolver/recovery.py +4217 -0
  694. anyfesolver/recovery_batches.py +1985 -0
  695. anyfesolver/recovery_policy.py +205 -0
  696. anyfesolver/recovery_s4.py +289 -0
  697. anyfesolver/recovery_shell_frames.py +102 -0
  698. anyfesolver/reference_cases.py +366 -0
  699. anyfesolver/result_capsule.py +2175 -0
  700. anyfesolver/results.py +752 -0
  701. anyfesolver/runtime.py +9819 -0
  702. anyfesolver/s3_reference_batch.py +1338 -0
  703. anyfesolver/s3_v2c_fast_assembly.py +544 -0
  704. anyfesolver/s3_v2d_fast_assembly.py +447 -0
  705. anyfesolver/s4_validity.py +354 -0
  706. anyfesolver/sections.py +67 -0
  707. anyfesolver/sesam_fem/__init__.py +55 -0
  708. anyfesolver/sesam_fem/__main__.py +80 -0
  709. anyfesolver/sesam_fem/diagnostics.py +5 -0
  710. anyfesolver/sesam_fem/document.py +39 -0
  711. anyfesolver/sesam_fem/exporter.py +9 -0
  712. anyfesolver/sesam_fem/importer.py +228 -0
  713. anyfesolver/sesam_fem/records.py +21 -0
  714. anyfesolver/sesam_fem/schema.py +35 -0
  715. anyfesolver/sesam_fem/sif_importer.py +9 -0
  716. anyfesolver/sesam_fem/validation.py +5 -0
  717. anyfesolver/sestra_deep_diagnostics.py +173 -0
  718. anyfesolver/sestra_mixed_verification.py +146 -0
  719. anyfesolver/sestra_structure_benchmarks.py +270 -0
  720. anyfesolver/sestra_three_case.py +215 -0
  721. anyfesolver/sestra_verification.py +523 -0
  722. anyfesolver/shell_benchmarks.py +367 -0
  723. anyfesolver/shell_formulations/director_field.py +799 -0
  724. anyfesolver/shell_formulations/geometry_provenance.py +921 -0
  725. anyfesolver/shell_formulations/mitc4_plus_d_quality.py +318 -0
  726. anyfesolver/shell_formulations/mitc4_plus_d_reference.py +837 -0
  727. anyfesolver/shell_formulations/mitc4_plus_d_scalar.py +209 -0
  728. anyfesolver/shell_formulations/protocol.py +222 -0
  729. anyfesolver/shell_formulations/q4_common.py +271 -0
  730. anyfesolver/shell_formulations/s4_restricted_policy.py +78 -0
  731. anyfesolver/shell_sections.py +394 -0
  732. anyfesolver/solid_submodel.py +930 -0
  733. anyfesolver/static_batch.py +662 -0
  734. anyfesolver/static_envelopes.py +250 -0
  735. anyfesolver/stiffened_shell.py +388 -0
  736. anyfesolver/submodel_transfer.py +1306 -0
  737. anyfesolver/test_cases.py +610 -0
  738. anyfesolver/threading_policy.py +431 -0
  739. anyfesolver/validation.py +719 -0
  740. anyfesolver/vectorized_beam.py +139 -0
  741. anyfesolver/vectorized_generalized_shell.py +448 -0
  742. anyfesolver/vectorized_hill48.py +956 -0
  743. anyfesolver/vectorized_nonlinear.py +360 -0
  744. anyfesolver/vectorized_stiffness.py +694 -0
  745. anyfesolver-0.5.0a1.dist-info/METADATA +374 -0
  746. anyfesolver-0.5.0a1.dist-info/RECORD +751 -0
  747. anyfesolver-0.5.0a1.dist-info/WHEEL +5 -0
  748. anyfesolver-0.5.0a1.dist-info/licenses/COPYRIGHT +1 -0
  749. anyfesolver-0.5.0a1.dist-info/licenses/LICENSE +373 -0
  750. anyfesolver-0.5.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +32 -0
  751. anyfesolver-0.5.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2068 @@
1
+ """Recovery and reaction diagnostics for the prepared static evaluator.
2
+
3
+ Stress recovery is compiled at preparation time by canonical zero and
4
+ local-unit-basis probes of the package's own recovery oracle. Admission is
5
+ by exact native class identity plus reviewed-hook identity (never by
6
+ formulation strings); the numerical affinity probe that follows is
7
+ verification, not admission.
8
+
9
+ Components are classified into three groups:
10
+
11
+ - frozen constants (plain scalars, strings, booleans, nested tuples of plain
12
+ values, and arrays that do not respond to any local probe);
13
+ - canonical linear fields, compiled affinely as ``baseline + matrix @ u``;
14
+ - derived measures (von Mises and equivalent-stress fields), which are
15
+ never compiled affinely: they are computed afterward from the compiled
16
+ canonical linear fields with the reviewed measure formulas and verified
17
+ against the oracle at a random probe.
18
+
19
+ Any component outside these groups, or any verification mismatch, refuses
20
+ the element explicitly.
21
+
22
+ Reaction and constraint-force diagnostics are reproduced exactly from the
23
+ immutable snapshot: the raw residual convention ``K u - F`` on the unreduced
24
+ global system with fixed/slave/master separation and complete residual
25
+ records, matching the live oracle's diagnostics structure.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from dataclasses import dataclass
31
+ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Sequence, Tuple
32
+
33
+ import copy
34
+ import sys
35
+ from types import MappingProxyType
36
+ from time import perf_counter_ns as _recovery_preparation_clock_ns
37
+ from contextlib import contextmanager, ExitStack, nullcontext
38
+ from contextvars import ContextVar
39
+
40
+ _OWNED_RECOVERY_SCOPE = ContextVar("prepared_owned_recovery", default=None)
41
+ # M25f (owner decision): the trusted capture (review item S3) is opt-in. Capture is
42
+ # fully guarded (the pre-M25 route) unless a preparation that the caller opted in
43
+ # sets this for the duration of its own snapshot capture.
44
+ class _TrustedCaptureToken:
45
+ """Revocable opt-in lease; a context copied during preparation shares the token,
46
+ so revoking it at the end of the scope disables the opt-in there too."""
47
+ __slots__ = ("live",)
48
+
49
+ def __init__(self):
50
+ self.live = True
51
+
52
+
53
+ _TRUSTED_CAPTURE = ContextVar("prepared_trusted_capture", default=None)
54
+
55
+
56
+ @contextmanager
57
+ def trusted_capture_scope(enabled):
58
+ """Enable (True) or keep disabled (False) the opt-in trusted capture for the
59
+ duration of one preparation's snapshot capture."""
60
+ if type(enabled) is not bool:
61
+ raise ValueError("trusted_capture must be True or False")
62
+ token = _TrustedCaptureToken() if enabled else None
63
+ marker = _TRUSTED_CAPTURE.set(token)
64
+ try:
65
+ yield
66
+ finally:
67
+ if token is not None:
68
+ token.live = False # revoked on every exit, before the reset
69
+ _TRUSTED_CAPTURE.reset(marker)
70
+
71
+
72
+ def _trusted_capture_active():
73
+ token = _TRUSTED_CAPTURE.get()
74
+ return type(token) is _TrustedCaptureToken and token.live is True
75
+
76
+
77
+ def _authenticate_trusted_capture():
78
+ """Capture callers authenticate the trusted-capture helpers only when it is enabled."""
79
+ if _trusted_capture_active():
80
+ _require_capture_span_authority()
81
+
82
+ import numpy as np
83
+ from .prepared_static_measures import PRINCIPAL_COMPONENTS, shell_principal_stresses
84
+ from .recovery import (_compute_one_element_stress as _canonical_one_element_stress,
85
+ estimate_model_memory, enforce_memory_limit, MemoryEstimate,
86
+ ResourcePolicyError)
87
+ from .boundary import _callable_authority, _callable_authority_matches
88
+
89
+ _PROBE_HELPER_AUTHORITY = _callable_authority(_canonical_one_element_stress)
90
+
91
+ # Scope factories receive private native elements. Pin their module bindings,
92
+ # code/defaults/closure executables and closure-function global dependencies
93
+ # before lending them those owners.
94
+ from . import e4_pl_element as _q4_scope_module
95
+ from . import e4_pl_s3_element as _s3_scope_module
96
+ # M25 review fix: one table pairs each factory with its element class; the
97
+ # factory and class tables are derived from it, so their order cannot drift.
98
+ _SCOPE_PAIRS = tuple((module, name, getattr(module, name), _callable_authority(getattr(module, name)),
99
+ class_name, getattr(module, class_name)) for module, name, class_name in (
100
+ (_q4_scope_module, '_q4_trusted_operation_scope', 'QualifiedE4PLShellElement'),
101
+ (_s3_scope_module, '_s3_trusted_operation_scope', 'QualifiedE4PLS3ShellElement')))
102
+ _SCOPE_FACTORIES = tuple((module, name, factory, authority) for module, name, factory, authority, _cname, _cls in _SCOPE_PAIRS)
103
+ _SCOPE_CLASSES = tuple((module, class_name, cls) for module, _name, _factory, _authority, class_name, cls in _SCOPE_PAIRS)
104
+ _SCOPE_ELEMENT_TYPES = tuple(cls for *_rest, cls in _SCOPE_PAIRS)
105
+
106
+ def _capture_scope_globals():
107
+ import types
108
+ seen = set()
109
+ captured = []
110
+ def visit(function):
111
+ if type(function) is not types.FunctionType or id(function) in seen:
112
+ return
113
+ seen.add(id(function))
114
+ captured.append((function, tuple((name, function.__globals__[name])
115
+ for name in function.__code__.co_names if name in function.__globals__)))
116
+ for cell in function.__closure__ or ():
117
+ visit(cell.cell_contents)
118
+ for _module, _name, factory, _authority in _SCOPE_FACTORIES:
119
+ visit(factory)
120
+ return tuple(captured)
121
+
122
+ _SCOPE_GLOBALS = _capture_scope_globals()
123
+
124
+ def _require_owned_scope_factory_authority():
125
+ # The pre-M25 check, unchanged (it runs on the default route through
126
+ # owned_recovery_scope). M25o (opt-in review round 9): the table-binding check
127
+ # added in M25b runs only on the opt-in path (_require_capture_scope_tables).
128
+ if (any(getattr(module, name, None) is not factory or not _callable_authority_matches(factory, authority)
129
+ for module, name, factory, authority in _SCOPE_FACTORIES)
130
+ or any(getattr(module, name, None) is not cls for module, name, cls in _SCOPE_CLASSES)
131
+ or any(function.__globals__.get(name) is not value
132
+ for function, bindings in _SCOPE_GLOBALS for name, value in bindings)):
133
+ raise PreparedStaticCapabilityError('native trusted scope factory authority changed')
134
+
135
+
136
+ def _make_capture_scope_tables_check():
137
+ # Opt-in path only (M25b, moved in M25o): the tables live in this closure and
138
+ # this module's bindings must still be those objects, so a rebound table cannot
139
+ # pass the factory check that reads them.
140
+ namespace = globals()
141
+ tables = (('_SCOPE_PAIRS', _SCOPE_PAIRS), ('_SCOPE_FACTORIES', _SCOPE_FACTORIES), ('_SCOPE_CLASSES', _SCOPE_CLASSES),
142
+ ('_SCOPE_ELEMENT_TYPES', _SCOPE_ELEMENT_TYPES), ('_SCOPE_GLOBALS', _SCOPE_GLOBALS))
143
+
144
+ def require():
145
+ if any(namespace.get(name) is not table for name, table in tables):
146
+ raise PreparedStaticCapabilityError('native trusted scope factory authority changed')
147
+
148
+ require.__name__ = require.__qualname__ = '_require_capture_scope_tables'
149
+ return require
150
+
151
+
152
+ _require_capture_scope_tables = _make_capture_scope_tables_check()
153
+
154
+ from .prepared_static_loads import (
155
+ PreparedStaticCapabilityError,
156
+ PreparedStaticError,
157
+ exact_element_authority,
158
+ native_material_section_authority,
159
+ require_native_source_authority,
160
+ )
161
+
162
+ from .boundary import (_numpy_error_routes_are_builtin as _capture_numpy_routes_are_builtin,
163
+ _load_routing_classes_are_untouched as _capture_routing_classes_are_untouched,
164
+ _exact_load_case_instance_namespace as _capture_exact_load_case_namespace)
165
+ from .prepared_static_loads import (_plain_numeric_state as _capture_plain_state,
166
+ _instance_authority as _capture_instance_authority,
167
+ _NATIVE_CLASS_FACTS as _capture_native_class_facts)
168
+ from .static_batch import _class_state as _capture_class_state
169
+ from anymaterial import IsotropicMaterial as _CaptureIsotropic, OrthotropicMaterial as _CaptureOrthotropic
170
+ from .beam_sections import GeneralizedBeamSection as _CaptureBeamSection
171
+ from .shell_sections import GeneralizedShellSection as _CaptureShellSection
172
+ # Immutable import-time facts for the materials and sections a capture span may use.
173
+
174
+
175
+ def _make_capture_material_facts():
176
+ # M25n (opt-in review round 8): read on the first opt-in admission, not at import.
177
+ facts_source = _capture_native_class_facts
178
+ cache = []
179
+
180
+ def facts():
181
+ if not cache:
182
+ cache.append(tuple((cls, facts_source[cls]) for cls in (_CaptureIsotropic, _CaptureOrthotropic)))
183
+ return cache[0]
184
+
185
+ facts.__name__ = facts.__qualname__ = '_capture_material_facts'
186
+ return facts
187
+
188
+
189
+ _capture_material_facts = _make_capture_material_facts()
190
+ _CAPTURE_SECTION_CLASSES = (('generalized_section', _CaptureBeamSection), ('shell_section', _CaptureShellSection))
191
+ del _capture_native_class_facts
192
+
193
+
194
+ def _capture_state_table():
195
+ # M25c review fix: the classes and module namespaces whose contents a callback
196
+ # during admission could change to carry code into the span and that the
197
+ # native factory does not re-check at entry. M25m (opt-in review round 7): built
198
+ # on the first opt-in admission, after the module check has established that
199
+ # warnings, sys and builtins are plain modules (never at import).
200
+ import builtins
201
+ import sys
202
+ import warnings
203
+ from numpy._core import _ufunc_config
204
+ from abc import ABCMeta
205
+ from .boundary import LoadCase
206
+ from .fe_core import FEModel, FEMesh, Node, DOFManager, _QualifiedStateMapping
207
+ classes = (LoadCase, FEModel, FEMesh, Node, DOFManager, _QualifiedStateMapping, ABCMeta,
208
+ _CaptureIsotropic, _CaptureOrthotropic)
209
+ namespaces = tuple(object.__getattribute__(module, '__dict__') for module in (warnings, sys, builtins))
210
+ executables = []
211
+ for cls in classes:
212
+ for value in type.__getattribute__(cls, '__dict__').values():
213
+ if type(value) is property:
214
+ executables.extend(accessor for accessor in (value.fget, value.fset, value.fdel) if accessor is not None)
215
+ elif type(value) in (staticmethod, classmethod):
216
+ executables.append(value.__func__)
217
+ else:
218
+ executables.append(value)
219
+ executables = tuple(value for value in executables if type(value) is _CaptureFunctionTypeEarly)
220
+ return classes, namespaces, _ufunc_config._get_extobj_dict, executables
221
+
222
+
223
+ from types import FunctionType as _CaptureFunctionTypeEarly
224
+ from operator import attrgetter as _capture_attrgetter
225
+
226
+
227
+ def _make_capture_state_tables():
228
+ cache = []
229
+
230
+ def tables():
231
+ if not cache:
232
+ cache.append(_capture_state_table())
233
+ return cache[0]
234
+
235
+ tables.__name__ = tables.__qualname__ = '_capture_state_tables'
236
+ return tables
237
+
238
+
239
+ _capture_state_tables = _make_capture_state_tables()
240
+ _capture_code_of = _capture_attrgetter('__code__')
241
+ _capture_defaults_of = _capture_attrgetter('__defaults__')
242
+
243
+ from . import prepared_static_q4 as _prepared_q4
244
+ _Q4_STAGE_REQUIRE = _prepared_q4.require_q4_kernel_authority
245
+ _Q4_STAGE_REQUIRE_AUTHORITY = _callable_authority(_Q4_STAGE_REQUIRE)
246
+
247
+ def _require_prepared_q4_authority():
248
+ if (_prepared_q4.require_q4_kernel_authority is not _Q4_STAGE_REQUIRE
249
+ or not _callable_authority_matches(_Q4_STAGE_REQUIRE, _Q4_STAGE_REQUIRE_AUTHORITY)):
250
+ raise PreparedStaticCapabilityError('prepared Q4 authority guard changed')
251
+ _Q4_STAGE_REQUIRE()
252
+
253
+ if TYPE_CHECKING:
254
+ from .fe_core import FEModel
255
+ from .recovery import RecoveryConfig
256
+
257
+ _AFFINITY_RELATIVE_TOLERANCE = 1e-9
258
+ _AFFINITY_ABSOLUTE_TOLERANCE = 1e-12
259
+
260
+
261
+ @dataclass(frozen=True)
262
+ class OwnedLocalRecovery:
263
+ """Native private execution owner plus immutable numerical recipe identity."""
264
+ numeric_identity: Any
265
+ retained_bytes: int
266
+ execution_owner: Any
267
+ staged_kernel: Any = None
268
+ staged_fallback_reason: str = ""
269
+
270
+
271
+ def _capture_plain_arguments(pressure_probe, warm_stress):
272
+ """M25b: the span consumes these values, so they must be plain (no __bool__ or
273
+ __float__ callback inside the span); type checks only."""
274
+ if type(warm_stress) is not bool:
275
+ return False
276
+ if pressure_probe is None or type(pressure_probe) is float or type(pressure_probe) is int:
277
+ return True
278
+ return type(pressure_probe) is tuple and all(type(value) is float for value in pressure_probe)
279
+
280
+
281
+ def _capture_material_is_native(owned_element, owned_material):
282
+ """M25b: callback-free material/section proof on the private clones that run
283
+ inside the span: an exact reviewed elastic material class whose executables
284
+ are unchanged since import, plain instance data without executable shadows,
285
+ and exact plain sections. No mapping or attribute provider is called."""
286
+ cls = type(owned_material)
287
+ facts = None
288
+ for native, native_facts in _capture_material_facts():
289
+ if cls is native:
290
+ facts = native_facts
291
+ if facts is None or _capture_class_state(cls) != facts or not _capture_instance_authority(owned_material):
292
+ return False
293
+ namespace = object.__getattribute__(owned_material, "__dict__")
294
+ if type(namespace) is not dict or not all(type(key) is str and _capture_plain_state(value)
295
+ for key, value in dict.items(namespace)):
296
+ return False
297
+ element_namespace = object.__getattribute__(owned_element, "__dict__")
298
+ if type(element_namespace) is not dict:
299
+ return False
300
+ for name, expected in _CAPTURE_SECTION_CLASSES:
301
+ section = dict.get(element_namespace, name)
302
+ if section is not None and (type(section) is not expected or not _capture_plain_state(section)):
303
+ return False
304
+ return True
305
+
306
+
307
+ def _capture_trust_state(owned_material=None, probe_case=None):
308
+ """M25c/M25e: callback-free identity snapshot of the state admission must not
309
+ change: LoadCase, routing and material class namespaces, the warnings, sys,
310
+ builtins and boundary namespaces and warning filters, NumPy's error state, the
311
+ probe LoadCase instance and (for admission) the material clone. The snapshot
312
+ keeps the objects alive, so equal ids mean the same objects; NumPy's error
313
+ modes compare by exact value. The owner's numerical inputs are checked by value
314
+ after every capture (_capture_owner_matches_identity)."""
315
+ made = []
316
+ state_classes, state_namespaces, numpy_error_state, state_executables = _capture_state_tables()
317
+ if probe_case is not None:
318
+ namespace = object.__getattribute__(probe_case, '__dict__')
319
+ made.append(namespace)
320
+ made.extend(dict.keys(namespace))
321
+ for value in dict.values(namespace):
322
+ made.append(value)
323
+ if type(value) is dict:
324
+ made.extend(dict.keys(value))
325
+ made.extend(dict.values(value))
326
+ for cls in state_classes:
327
+ namespace = type.__getattribute__(cls, '__dict__')
328
+ made.extend(namespace.keys())
329
+ made.extend(namespace.values())
330
+ for namespace in state_namespaces:
331
+ made.extend(dict.keys(namespace))
332
+ made.extend(dict.values(namespace))
333
+ # In-place executable changes of the classes' methods (code and defaults objects).
334
+ made.extend(map(_capture_code_of, state_executables))
335
+ made.extend(map(_capture_defaults_of, state_executables))
336
+ filters = dict.get(state_namespaces[0], 'filters')
337
+ made.append(filters)
338
+ if type(filters) is list:
339
+ made.extend(list.__iter__(filters))
340
+ errors = numpy_error_state()
341
+ modes = tuple(value if type(value) is str or type(value) is int else id(value) for value in dict.values(errors))
342
+ material = None
343
+ if owned_material is not None:
344
+ namespace = object.__getattribute__(owned_material, '__dict__')
345
+ material = (namespace, tuple(dict.keys(namespace)), tuple(dict.values(namespace)))
346
+ material = (material, tuple(map(id, material[1])) + tuple(map(id, material[2])))
347
+ return (made, tuple(map(id, made)), modes, material, errors) # errors keeps the 'call' object alive
348
+
349
+
350
+ def _capture_same_state(before, after, *, material=True):
351
+ """Shared part by ids and NumPy modes; the material part when requested."""
352
+ if before[1] != after[1] or before[2] != after[2]:
353
+ return False
354
+ if material and (before[3] is None or after[3] is None or before[3][1] != after[3][1]):
355
+ return False
356
+ return True
357
+
358
+
359
+ def _capture_frozen_equal(left, right):
360
+ """Equality of frozen plain values (tuples, scalars, bytes, str); NaN equals NaN."""
361
+ if type(left) is not type(right):
362
+ return False
363
+ if type(left) is tuple:
364
+ return len(left) == len(right) and all(_capture_frozen_equal(a, b) for a, b in zip(left, right))
365
+ if type(left) is float:
366
+ return left == right or (left != left and right != right)
367
+ return left == right
368
+
369
+
370
+ def _capture_owner_matches_identity(owned_element, owned_material, owned_mesh, freeze, frozen_inputs,
371
+ frozen_material, geometry, ignored, inputs):
372
+ """M25e: the private owner still holds exactly the inputs, material data and
373
+ geometry frozen into its recipe identity (by value)."""
374
+ namespace = object.__getattribute__(owned_element, '__dict__')
375
+ if type(namespace) is not dict:
376
+ return False
377
+ current = {key: value for key, value in dict.items(namespace) if key not in ignored}
378
+ if set(current) != set(inputs) or not _capture_frozen_equal(freeze(current), frozen_inputs):
379
+ return False
380
+ material_namespace = object.__getattribute__(owned_material, '__dict__')
381
+ if type(material_namespace) is not dict or not _capture_frozen_equal(freeze(dict(material_namespace)), frozen_material):
382
+ return False
383
+ nodes = dict.get(object.__getattribute__(owned_mesh, '__dict__'), 'nodes')
384
+ if type(nodes) is not dict and type(nodes) is not _CaptureStateMapping:
385
+ return False
386
+ for node_id, x, y, z in geometry:
387
+ node = dict.get(nodes, node_id)
388
+ state = None if node is None else object.__getattribute__(node, '__dict__')
389
+ if type(state) is not dict or not all(
390
+ type(dict.get(state, axis)) in (float, int) and _capture_frozen_equal(float(dict.get(state, axis)), value)
391
+ for axis, value in (('x', x), ('y', y), ('z', z))):
392
+ return False
393
+ return True
394
+
395
+
396
+ def _capture_span_proofs_hold(owned_element, owned_material, probe_case, pressure_probe, warm_stress):
397
+ """M25 (review S3, fixed three times): plain span arguments, builtin NumPy error
398
+ and warning routes, native material/section (on the clones), untouched
399
+ model/mesh/node routing classes and an exact LoadCase for the pressure probe
400
+ (the proofs of boundary._run_foreign_code_free_load_vector, in its order). The
401
+ caller brackets the proofs with a module-hook check and a state snapshot, so
402
+ a callback reached during the proofs cannot carry code into the span."""
403
+ return bool(_capture_plain_arguments(pressure_probe, warm_stress)
404
+ and _capture_numpy_routes_are_builtin()
405
+ and _capture_material_is_native(owned_element, owned_material)
406
+ and _capture_routing_classes_are_untouched()
407
+ and (probe_case is None or (_capture_exact_load_case_namespace(probe_case) is not None
408
+ and _capture_pressure_helpers_authentic())))
409
+
410
+
411
+ def _capture_span_factory(owned_element, owned_material, probe_case, pressure_probe, warm_stress, span_authority):
412
+ """(native trusted scope factory, admission snapshot) for a freshly cloned private
413
+ Q4/S3 element, or (None, None) to keep the per-call guarded route (the pre-M25
414
+ behaviour). The scope itself checks the element's runtime authority at entry
415
+ and exit.
416
+
417
+ M25e order: the module check first (so attribute lookups below do not reach a
418
+ substituted module hook), the scope tables, a snapshot, then the proofs (an
419
+ exception in a proof means "not admitted"). The final block before the span
420
+ repeats the module check, compares the snapshot and re-runs the helper
421
+ authentication; it is intended to be callback-free, but the round-five review
422
+ found that it is not in every case (see the M25 report; opt-in path only). If
423
+ anything changed during admission the capture is refused rather than continued
424
+ on possibly changed owners."""
425
+ if not _capture_modules_unhooked():
426
+ return None, None
427
+ _require_capture_scope_tables()
428
+ _require_owned_scope_factory_authority()
429
+ factory = None
430
+ for _module, _name, candidate, _authority, _class_name, cls in _SCOPE_PAIRS:
431
+ if type(owned_element) is cls:
432
+ factory = candidate
433
+ if factory is None:
434
+ return None, None
435
+ before = _capture_trust_state(owned_material, probe_case)
436
+ try:
437
+ admitted = _capture_span_proofs_hold(owned_element, owned_material, probe_case, pressure_probe, warm_stress)
438
+ except Exception:
439
+ admitted = False
440
+ if not _capture_modules_unhooked() or not _capture_same_state(before, _capture_trust_state(owned_material, probe_case)):
441
+ raise PreparedStaticCapabilityError("state changed during trusted-capture admission")
442
+ span_authority()
443
+ return (factory, before) if admitted else (None, None)
444
+
445
+
446
+ def _capture_owned_local_recovery(model, element, *, warm_stress=True, pressure_probe=None, _span_authority=None):
447
+ from .fe_core import FEMesh, Node, _ELEMENT_LOCAL_CACHE_NAMES
448
+ from .e4_pl_element import QualifiedE4PLShellElement
449
+ from .e4_pl_s3_element import QualifiedE4PLS3ShellElement
450
+ from .prepared_static_loads import _plain_numeric_state
451
+ import dataclasses
452
+ import sys
453
+ ignored = set(_ELEMENT_LOCAL_CACHE_NAMES) | {"_qualified_direct_state_token", "_qualified_direct_state_tokens", "_qualified_component_guard"}
454
+ inputs = {key: value for key, value in vars(element).items() if key not in ignored}
455
+ if not all(_plain_numeric_state(value) for value in inputs.values()):
456
+ raise PreparedStaticCapabilityError("local canonical recipe contains nonplain element inputs")
457
+ material = model.get_material(element.material_name)
458
+ source_q4_policy = None
459
+ if warm_stress and type(element) is QualifiedE4PLShellElement:
460
+ # Authenticate the new helper before fetching it from its module. Keep
461
+ # only its builtin bool, never the original mesh/scalar providers.
462
+ _require_prepared_q4_authority()
463
+ try:
464
+ source_q4_policy = _prepared_q4._capture_original_q4_policy(element, model.mesh, material)
465
+ except ValueError:
466
+ # Native policy capture checks the Q4 module before the opt-in
467
+ # prepared span is entered. Preserve its typed refusal when a
468
+ # trusted factory was rebound, without probing hooked modules.
469
+ if _trusted_capture_active() and _capture_modules_unhooked():
470
+ _require_capture_scope_tables()
471
+ _require_owned_scope_factory_authority()
472
+ raise
473
+ def freeze(value):
474
+ if type(value) is np.ndarray:
475
+ return (value.dtype.str, tuple(value.shape), value.tobytes())
476
+ if isinstance(value, np.generic):
477
+ return value.item()
478
+ if dataclasses.is_dataclass(value):
479
+ return (type(value).__module__, type(value).__qualname__, tuple((field.name, freeze(getattr(value, field.name))) for field in dataclasses.fields(value)))
480
+ if type(value) is dict:
481
+ return tuple((key, freeze(item)) for key, item in sorted(value.items()))
482
+ if type(value) in (list, tuple):
483
+ return tuple(freeze(item) for item in value)
484
+ if value is None or type(value) in (bool, int, float, str, bytes):
485
+ return value
486
+ raise PreparedStaticCapabilityError("local canonical recipe contains opaque input")
487
+ geometry = tuple((int(node_id), float(model.mesh.nodes[node_id].x), float(model.mesh.nodes[node_id].y), float(model.mesh.nodes[node_id].z)) for node_id in element.node_ids)
488
+ identity = ((type(element).__module__, type(element).__qualname__), freeze(inputs), freeze(vars(material)), geometry)
489
+ frozen_inputs, frozen_material = identity[1], identity[2] # the pre-M25 expression, unchanged
490
+ def clone_plain(value):
491
+ # Avoid copyreg: generic deepcopy can mutate native dataclass classes
492
+ # by creating __slotnames__, and V2D intentionally refuses restart.
493
+ if type(value) is np.ndarray:
494
+ return np.frombuffer(value.tobytes(), dtype=value.dtype).reshape(value.shape)
495
+ if dataclasses.is_dataclass(value):
496
+ made = object.__new__(type(value))
497
+ for field in dataclasses.fields(value):
498
+ object.__setattr__(made, field.name, clone_plain(getattr(value, field.name)))
499
+ return made
500
+ if type(value) is dict:
501
+ return {key: clone_plain(item) for key, item in value.items()}
502
+ if type(value) in (tuple, list):
503
+ return type(value)(clone_plain(item) for item in value)
504
+ return value
505
+ owned_element = object.__new__(type(element))
506
+ for key, value in vars(element).items():
507
+ if key in {"_qualified_direct_state_token", "_qualified_direct_state_tokens"}:
508
+ continue
509
+ object.__setattr__(owned_element, key, None if key in ignored else clone_plain(value))
510
+ owned_material = object.__new__(type(material))
511
+ for key, value in vars(material).items():
512
+ object.__setattr__(owned_material, key, clone_plain(value))
513
+ owned_mesh = FEMesh()
514
+ for node_id, x, y, z in geometry:
515
+ owned_mesh.add_node(node_id, x, y, z)
516
+ owned_mesh.add_element(int(element.element_id), owned_element)
517
+ owner = (owned_element, owned_mesh, owned_material)
518
+ staged = None
519
+ fallback_reason = 'pressure_only_recipe'
520
+ if not _trusted_capture_active():
521
+ # Default (M25f): the pre-M25 fully guarded capture, unchanged.
522
+ if pressure_probe is not None:
523
+ from .boundary import LoadCase
524
+ LoadCase("prepared_owned_pressure_warmup")._consistent_pressure_load(owned_element, owned_mesh, pressure_probe, None)
525
+ if warm_stress:
526
+ _require_prepared_q4_authority()
527
+ staged, fallback_reason = _prepared_q4.compile_q4_stages(
528
+ owned_element, owned_mesh, owned_material, _source_compensated=source_q4_policy)
529
+ if staged is not None:
530
+ identity = (identity, staged)
531
+ else:
532
+ # Materialize fixed geometry/material caches before accounting retained
533
+ # bytes. M21: a staged compile already evaluates this native zero-
534
+ # displacement recovery, so the warm-up runs only without a staged kernel.
535
+ owned_element.compute_stresses(owned_mesh, np.zeros(6 * len(geometry)), owned_material, return_global=False)
536
+ else:
537
+ # Opt-in trusted capture (M25, review item S3), admission-checked.
538
+ probe_case = None
539
+ if pressure_probe is not None:
540
+ from .boundary import LoadCase
541
+ probe_case = LoadCase("prepared_owned_pressure_warmup")
542
+ if warm_stress:
543
+ _require_prepared_q4_authority()
544
+ # M25 (S3): a trusted native span only for exact Q4/S3 clones that pass the
545
+ # admission checks (not a proof that no foreign code can run; see the M25
546
+ # report); everything else keeps per-call guards.
547
+ factory = admitted = None
548
+ scoped_type = any(type(owned_element) is cls for cls in _SCOPE_ELEMENT_TYPES)
549
+ if scoped_type:
550
+ _span_authority() # checker made at import, facts built on first use; held as a keyword default (M25b)
551
+ factory, admitted = _capture_span_factory(owned_element, owned_material, probe_case, pressure_probe,
552
+ warm_stress, _span_authority)
553
+ with (nullcontext() if factory is None else factory((owned_element,))):
554
+ if probe_case is not None:
555
+ probe_case._consistent_pressure_load(owned_element, owned_mesh, pressure_probe, None)
556
+ if warm_stress:
557
+ staged, fallback_reason = _prepared_q4.compile_q4_stages(
558
+ owned_element, owned_mesh, owned_material, _source_compensated=source_q4_policy)
559
+ if staged is not None:
560
+ identity = (identity, staged)
561
+ else:
562
+ # Materialize fixed geometry/material caches before accounting retained
563
+ # bytes. M21: a staged compile already evaluates this native zero-
564
+ # displacement recovery, so the warm-up runs only without a staged kernel.
565
+ owned_element.compute_stresses(owned_mesh, np.zeros(6 * len(geometry)), owned_material, return_global=False)
566
+ if factory is not None and (not _capture_modules_unhooked() or not _capture_same_state(
567
+ admitted, _capture_trust_state(None, probe_case), material=False)):
568
+ # The span may cache on the private material clone; everything else it
569
+ # relies on must be unchanged after the span.
570
+ raise PreparedStaticCapabilityError("capture trusted span route changed during capture")
571
+ if scoped_type and not _capture_owner_matches_identity(
572
+ owned_element, owned_material, owned_mesh, freeze, frozen_inputs, frozen_material, geometry, ignored, inputs):
573
+ # M25e: whichever route ran, the private owner must still hold exactly the
574
+ # inputs its recipe identity records (a change during admission or the span
575
+ # would otherwise yield a recipe that disagrees with its identity).
576
+ raise PreparedStaticCapabilityError("captured owner state differs from its recipe identity")
577
+ seen = set()
578
+ def storage(value):
579
+ if id(value) in seen:
580
+ return 0
581
+ seen.add(id(value))
582
+ size = sys.getsizeof(value)
583
+ if isinstance(value, np.ndarray):
584
+ return size + value.nbytes
585
+ if isinstance(value, dict):
586
+ return (size + sum(storage(key) + storage(item) for key, item in dict.items(value))
587
+ + (storage(vars(value)) if hasattr(value, "__dict__") else 0))
588
+ if type(value) in (tuple, list):
589
+ return size + sum(storage(item) for item in value)
590
+ if hasattr(value, "__dict__") and not callable(value) and not isinstance(value, type):
591
+ return size + storage(vars(value))
592
+ return size
593
+ if source_q4_policy is not None:
594
+ _require_prepared_q4_authority()
595
+ if _prepared_q4._capture_original_q4_policy(element, model.mesh, material) is not source_q4_policy:
596
+ raise PreparedStaticCapabilityError('source Q4 arithmetic policy changed during capture')
597
+ return OwnedLocalRecovery(identity, storage(owner) + storage(identity), owner, staged, fallback_reason)
598
+
599
+
600
+ @contextmanager
601
+ def owned_recovery_scope(recipes):
602
+ """Authority-bounded package-only span; never crosses caller observation."""
603
+ from .boundary import _numpy_error_routes_are_builtin
604
+ owners = tuple(recipe.execution_owner[0] for recipe in recipes)
605
+ current = _OWNED_RECOVERY_SCOPE.get()
606
+ if current is not None and current[0] and all(id(owner) in current[1] for owner in owners):
607
+ yield
608
+ return
609
+ require_native_source_authority()
610
+ _require_owned_scope_factory_authority()
611
+ _require_prepared_q4_authority()
612
+ if not _numpy_error_routes_are_builtin():
613
+ raise PreparedStaticCapabilityError("native NumPy error routes changed")
614
+ owners = tuple(recipe.execution_owner[0] for recipe in recipes)
615
+ live = [True, frozenset(id(element) for element in owners)]
616
+ marker = None
617
+ try:
618
+ with ExitStack() as stack:
619
+ stack.enter_context(_SCOPE_FACTORIES[0][2](tuple(e for e in owners if type(e) is _SCOPE_CLASSES[0][2])))
620
+ stack.enter_context(_SCOPE_FACTORIES[1][2](tuple(e for e in owners if type(e) is _SCOPE_CLASSES[1][2])))
621
+ marker = _OWNED_RECOVERY_SCOPE.set(live)
622
+ yield
623
+ finally:
624
+ live[0] = False
625
+ if marker is not None:
626
+ _OWNED_RECOVERY_SCOPE.reset(marker)
627
+ import sys
628
+ primary = sys.exc_info()[1]
629
+ try:
630
+ require_native_source_authority()
631
+ _require_owned_scope_factory_authority()
632
+ _require_prepared_q4_authority()
633
+ if not _numpy_error_routes_are_builtin():
634
+ raise PreparedStaticCapabilityError("native NumPy error routes changed")
635
+ except BaseException as authority_error:
636
+ if primary is None:
637
+ raise
638
+ primary.add_note("owned recovery output authority also failed: " + str(authority_error))
639
+
640
+
641
+ _OWNED_SCOPE_FACTORY = owned_recovery_scope
642
+ _OWNED_SCOPE_FACTORY_FACTS = _callable_authority(owned_recovery_scope)
643
+ from .static_batch import _class_state as _prepared_class_state
644
+ _OWNED_RECIPE_CLASS = OwnedLocalRecovery
645
+ _OWNED_RECIPE_CLASS_FACTS = _prepared_class_state(OwnedLocalRecovery)
646
+
647
+
648
+ def _recover_owned_local_columns(recipe, columns):
649
+ """M21: staged Q4 recovery of several displacement columns in one pass, else None."""
650
+ scope = _OWNED_RECOVERY_SCOPE.get()
651
+ element = recipe.execution_owner[0]
652
+ if scope is None or not scope[0] or id(element) not in scope[1]:
653
+ require_native_source_authority()
654
+ _require_prepared_q4_authority()
655
+ if recipe.staged_kernel is None:
656
+ return None
657
+ return _prepared_q4.recover_q4_stage_columns(recipe.staged_kernel, columns)
658
+
659
+
660
+ def _recover_owned_local(recipe, local):
661
+ element, mesh, material = recipe.execution_owner
662
+ scope = _OWNED_RECOVERY_SCOPE.get()
663
+ if scope is None or not scope[0] or id(element) not in scope[1]:
664
+ require_native_source_authority()
665
+ _require_prepared_q4_authority()
666
+ if recipe.staged_kernel is not None:
667
+ return _prepared_q4.recover_q4_stages(recipe.staged_kernel, local)
668
+ return element.compute_stresses(mesh, local, material, return_global=False)
669
+
670
+
671
+ @dataclass(frozen=True)
672
+ class CompiledElementRecovery:
673
+ """One element's compiled recovery: constants, linear maps, derived names."""
674
+
675
+ element_id: int
676
+ formulation: str
677
+ admitted_by: str
678
+ dof_mapping: np.ndarray
679
+ array_components: Tuple[Tuple[str, np.ndarray, np.ndarray, Tuple[int, ...]], ...]
680
+ constant_components: Tuple[Tuple[str, Any], ...]
681
+ derived_components: Tuple[Tuple[str, str], ...]
682
+ verification_relative_residual: float
683
+ local_recipe: Any = None
684
+
685
+
686
+ @dataclass(frozen=True)
687
+ class PreparedRecoveryCompilation:
688
+ """All compiled recovery maps for the selected elements."""
689
+
690
+ elements: Mapping[int, CompiledElementRecovery]
691
+ retained_bytes: int
692
+ probe_count: int
693
+ setup_seconds: float
694
+
695
+
696
+ def _recovery_admission(model: "FEModel", element: Any, recovery: Any) -> Tuple[bool, str, str]:
697
+ """Return (admitted, formulation label, authority) for one element."""
698
+
699
+ from .recovery_batches import _formulation_name
700
+
701
+ formulation = _formulation_name(model, element)
702
+ admitted, reason = exact_element_authority(element)
703
+ if not admitted:
704
+ return False, formulation, reason
705
+ admitted, reason = native_material_section_authority(model, element)
706
+ if not admitted:
707
+ return False, formulation, reason
708
+ material = model.get_material(element.material_name)
709
+ if getattr(material, "hill_yield", None) is not None and (recovery.components is None or set(recovery.components) & {"hill_utilization", "equivalent_stress"}):
710
+ return False, formulation, (
711
+ "Hill48 material measures are outside the admitted elastic "
712
+ "prepared recovery"
713
+ )
714
+ cls = type(element).__name__
715
+ if cls == "NativeParityE4PLS3V2DShellElement":
716
+ authority = "source_reviewed_native_s3_v2d"
717
+ elif cls == "QualifiedE4PLS3ShellElement":
718
+ authority = "source_reviewed_native_s3"
719
+ elif cls == "QualifiedE4PLShellElement":
720
+ authority = "source_reviewed_native_q4"
721
+ elif cls == "QuadraticBeamElement":
722
+ authority = "source_reviewed_beam3"
723
+ elif cls == "BeamElement":
724
+ authority = "source_reviewed_beam2"
725
+ else:
726
+ authority = "source_reviewed_classic_shell"
727
+ return True, formulation, authority
728
+
729
+
730
+ def _probe_element_stress(
731
+ model: "FEModel",
732
+ displacements: np.ndarray,
733
+ element_id: int,
734
+ recovery: "RecoveryConfig",
735
+ *, _publication_authority=None,
736
+ ) -> Dict[str, Any]:
737
+ # A held checker is installed at module initialization. Caller-owned
738
+ # RecoveryConfig observations must not change delayed private publication.
739
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
740
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
741
+ _publication_authority[0]()
742
+ item = _canonical_one_element_stress(
743
+ model, displacements, int(element_id), return_global=False,
744
+ )
745
+ recovered = None if item is None else item[1]
746
+ if not isinstance(recovered, dict):
747
+ raise PreparedStaticError(
748
+ "stress recovery oracle returned no result for a selected element",
749
+ diagnostics={"stage": "recovery compilation", "element_id": int(element_id)},
750
+ )
751
+ requested = recovery.selected_components()
752
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
753
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
754
+ _publication_authority[0]()
755
+ if requested is not None:
756
+ for name in set(requested).intersection(PRINCIPAL_COMPONENTS):
757
+ try:
758
+ recovered[name] = shell_principal_stresses(recovered, name.removeprefix("principal_stresses_"))
759
+ except ValueError as error:
760
+ raise PreparedStaticCapabilityError(str(error), diagnostics={"stage": "recovery compilation", "element_id": element_id, "component": name}) from error
761
+ missing = set(requested) - set(recovered)
762
+ if missing:
763
+ raise PreparedStaticCapabilityError("requested recovery components are unavailable: " + ", ".join(sorted(missing)), diagnostics={"stage": "recovery compilation", "element_id": element_id, "components": sorted(missing)})
764
+ wanted = set(requested)
765
+ if wanted & {"von_mises", "equivalent_stress", "hill_utilization", *PRINCIPAL_COMPONENTS}:
766
+ wanted.update({"von_mises", "membrane_xx", "membrane_yy", "membrane_xy", "bending_xx", "bending_yy", "bending_xy", "shear_xz", "shear_yz", "axial_stress", "bending_stress_y", "bending_stress_z", "shear_stress_y", "shear_stress_z", "torsional_stress"})
767
+ recovered = {name: value for name, value in recovered.items() if name in wanted}
768
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
769
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
770
+ _publication_authority[0]()
771
+ return recovered
772
+
773
+
774
+ def _plain_constant(value: Any, *, depth: int = 0) -> Any:
775
+ """Return a frozen plain copy, or raise for a live/nested object."""
776
+
777
+ if depth > 6:
778
+ raise ValueError("recovery component nesting too deep")
779
+ if value is None or type(value) in (bool, int, float, str, bytes):
780
+ return value
781
+ if isinstance(value, np.generic) and value.dtype.kind in "biuf":
782
+ return value.item()
783
+ if isinstance(value, Mapping):
784
+ return {str(key): _plain_constant(item, depth=depth + 1) for key, item in value.items()}
785
+ if isinstance(value, np.ndarray):
786
+ from .analysis_session import _immutable_array
787
+ return _immutable_array(value)
788
+ if type(value) in (tuple, list):
789
+ return tuple(_plain_constant(item, depth=depth + 1) for item in value)
790
+ raise ValueError(f"component of type {type(value).__name__} is not plain")
791
+
792
+
793
+ def _constant_equal(left: Any, right: Any) -> bool:
794
+ if isinstance(left, Mapping) and isinstance(right, Mapping):
795
+ return left.keys() == right.keys() and all(_constant_equal(left[key], right[key]) for key in left)
796
+ if type(left) in (tuple, list) or type(right) in (tuple, list):
797
+ try:
798
+ return len(left) == len(right) and all(
799
+ _constant_equal(a, b) for a, b in zip(left, right)
800
+ )
801
+ except TypeError:
802
+ return False
803
+ if isinstance(left, np.ndarray) or isinstance(right, np.ndarray):
804
+ try:
805
+ return np.array_equal(np.asarray(left), np.asarray(right))
806
+ except Exception:
807
+ return False
808
+ try:
809
+ return bool(left == right)
810
+ except Exception:
811
+ return False
812
+
813
+
814
+ def _array_value(value: Any) -> Optional[np.ndarray]:
815
+ """Return the float content of an array/scalar component, else None."""
816
+
817
+ if isinstance(value, np.ndarray):
818
+ if value.dtype.kind != "f":
819
+ return None
820
+ return np.asarray(value, dtype=float)
821
+ if type(value) in (int, float):
822
+ return np.asarray(float(value), dtype=float).reshape(())
823
+ if isinstance(value, np.generic) and value.dtype.kind in "biuf":
824
+ return np.asarray(float(value), dtype=float).reshape(())
825
+ return None
826
+
827
+
828
+ def _shell_surface_von_mises(record: Dict[str, Any]) -> Any:
829
+ """Reviewed shell top/bottom surface von Mises from canonical fields.
830
+
831
+ Source: ``elements.ShellElement.compute_stresses`` and
832
+ ``e4_pl_s3_element.QualifiedE4PLS3ShellElement`` elastic recovery: the
833
+ measure is the max of the top/bottom plane-stress von Mises with the
834
+ shared transverse shear. The formula is shared with the offline capsule
835
+ runtime through :mod:`anyfesolver_results.formulas`.
836
+ """
837
+
838
+ from anyfesolver_results.formulas import shell_surface_von_mises
839
+ return shell_surface_von_mises(record)
840
+
841
+
842
+ def _beam_von_mises(record: Dict[str, Any]) -> Any:
843
+ """Reviewed beam von Mises from canonical section fields.
844
+
845
+ Source: ``elements.BeamElement.compute_stresses`` elastic path. The
846
+ formula is shared with the offline capsule runtime through
847
+ :mod:`anyfesolver_results.formulas`.
848
+ """
849
+
850
+ from anyfesolver_results.formulas import beam_von_mises
851
+ return beam_von_mises(record)
852
+
853
+
854
+ def _derived_measure(name: str, record: Dict[str, Any]) -> Any:
855
+ """Compute one derived measure from the compiled canonical linear fields."""
856
+
857
+ if name in PRINCIPAL_COMPONENTS:
858
+ return shell_principal_stresses(record, name.removeprefix("principal_stresses_"))
859
+ if name == "von_mises":
860
+ if all(key in record for key in (
861
+ "membrane_xx", "membrane_yy", "membrane_xy",
862
+ "bending_xx", "bending_yy", "bending_xy",
863
+ "shear_xz", "shear_yz",
864
+ )):
865
+ return _shell_surface_von_mises(record)
866
+ if all(key in record for key in (
867
+ "axial_stress", "bending_stress_y", "bending_stress_z",
868
+ "shear_stress_y", "shear_stress_z", "torsional_stress",
869
+ )):
870
+ return _beam_von_mises(record)
871
+ raise KeyError("von_mises")
872
+ if name == "equivalent_stress":
873
+ return np.asarray(record["von_mises"], dtype=float).copy()
874
+ if name == "hill_utilization":
875
+ return np.zeros_like(np.asarray(record["von_mises"], dtype=float))
876
+ raise KeyError(name)
877
+
878
+
879
+ def _derived_measure_inputs(name: str) -> Tuple[str, ...]:
880
+ if name == "von_mises":
881
+ return (
882
+ "membrane_xx", "membrane_yy", "membrane_xy",
883
+ "bending_xx", "bending_yy", "bending_xy",
884
+ "shear_xz", "shear_yz",
885
+ "axial_stress", "bending_stress_y", "bending_stress_z",
886
+ "shear_stress_y", "shear_stress_z", "torsional_stress",
887
+ )
888
+ if name == "equivalent_stress":
889
+ return ("von_mises",)
890
+ if name == "hill_utilization":
891
+ return ("von_mises",)
892
+ return ()
893
+
894
+
895
+ # Derived measures are evaluated in this order so that dependents
896
+ # (equivalent_stress, hill_utilization) see the already computed von Mises.
897
+ _DERIVED_ORDER: Tuple[str, ...] = ("von_mises", "equivalent_stress", "hill_utilization", *PRINCIPAL_COMPONENTS)
898
+
899
+
900
+ def _ordered_derived(names: Mapping[str, str]) -> Tuple[str, ...]:
901
+ return tuple(name for name in _DERIVED_ORDER if name in names)
902
+
903
+
904
+ _VERIFICATION_RECIPE_TILE = 64
905
+ _VERIFICATION_PENDING_BYTES = 4 * 1024**2
906
+
907
+
908
+ def _verification_storage_bytes(value):
909
+ """Conservative incoming copy/storage bound, without making that copy."""
910
+ if isinstance(value,np.ndarray):
911
+ if value.dtype.hasobject:
912
+ raise PreparedStaticCapabilityError('verification record contains object arrays')
913
+ return sys.getsizeof(value)+2*value.nbytes+128
914
+ if isinstance(value,Mapping):
915
+ size=0
916
+ count=0
917
+ for key,item in value.items():
918
+ size+=_verification_storage_bytes(key)+_verification_storage_bytes(item)
919
+ count+=1
920
+ # A Mapping subclass can be smaller than its normalized plain dict.
921
+ return max(sys.getsizeof(value)+40,sys.getsizeof({})+128*count)+size
922
+ if type(value) in (tuple,list):
923
+ return sys.getsizeof(value)+sum(_verification_storage_bytes(item) for item in value)
924
+ if value is None or type(value) in (bool,int,float,str,bytes) or isinstance(value,np.generic):
925
+ return sys.getsizeof(value)
926
+ raise PreparedStaticCapabilityError('verification record contains nonplain values')
927
+
928
+
929
+ def _freeze_verification_value(value):
930
+ """Alias-free source dtype/shape/bytes; never retain mutable native caches."""
931
+ if isinstance(value,np.ndarray):
932
+ array=np.asarray(value)
933
+ return np.frombuffer(array.tobytes(order='C'),dtype=array.dtype).reshape(array.shape)
934
+ if isinstance(value,Mapping):
935
+ return MappingProxyType({key:_freeze_verification_value(item) for key,item in value.items()})
936
+ if type(value) in (tuple,list):
937
+ return tuple(_freeze_verification_value(item) for item in value)
938
+ return value
939
+
940
+
941
+ def _check_verification_memory(memory_base,extra,resource_config,timing):
942
+ required=int(memory_base.total_bytes_estimate)+int(extra)
943
+ timing['pending_copy_peak_bytes']=max(timing['pending_copy_peak_bytes'],int(extra))
944
+ estimate=MemoryEstimate(**{**vars(memory_base),'total_bytes_estimate':required})
945
+ enforce_memory_limit(estimate,resource_config,context='prepared_static.recovery_verification_staging')
946
+
947
+
948
+ def _compare_owner_scale(recipe,local_check,oracle_check,element_id,amplitude):
949
+ """Original source comparisons and tolerances, on frozen native operands."""
950
+ owned_check = _recover_owned_local(recipe,local_check)
951
+ for component,expected_value in oracle_check.items():
952
+ if component in PRINCIPAL_COMPONENTS:
953
+ owned_check[component] = _derived_measure(component,owned_check)
954
+ actual_value = owned_check[component]
955
+ expected_array = _array_value(expected_value)
956
+ if expected_array is not None:
957
+ if not np.allclose(actual_value,expected_array,rtol=1e-12,atol=1e-12):
958
+ raise PreparedStaticCapabilityError('owned canonical recovery failed multiple-scale verification',diagnostics={'element_id':int(element_id),'component':component,'scale':amplitude})
959
+ elif not _constant_equal(actual_value,expected_value):
960
+ raise PreparedStaticCapabilityError('owned canonical recovery metadata changed')
961
+
962
+
963
+ def _verify_staged_recipes(pending,compiled,refused,timing,*,_publication_authority=None):
964
+ """No live model/config/caller observation; publish only after full scope exit."""
965
+ accepted=[]
966
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
967
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
968
+ _publication_authority[0]()
969
+ started=_recovery_preparation_clock_ns()
970
+ with owned_recovery_scope(tuple(entry[0].local_recipe for entry in pending)):
971
+ entered=_recovery_preparation_clock_ns()
972
+ timing['verification_scope_entry_ns']+=entered-started
973
+ for candidate,checks,_size in pending:
974
+ try:
975
+ for amplitude,local,oracle in checks:
976
+ _compare_owner_scale(candidate.local_recipe,local,oracle,candidate.element_id,amplitude)
977
+ timing['owner_verification_calls']+=1
978
+ accepted.append(candidate)
979
+ except PreparedStaticCapabilityError as error:
980
+ refused.append(dict(element_id=candidate.element_id,formulation=candidate.formulation,reason=str(error)))
981
+ except Exception as error:
982
+ from .control import SolveCancelled
983
+ from .recovery import ResourcePolicyError
984
+ if isinstance(error,(SolveCancelled,ResourcePolicyError)):
985
+ raise
986
+ refused.append(dict(element_id=candidate.element_id,formulation=candidate.formulation,
987
+ reason='recovery oracle probe failed: '+str(error),original_exception_type=type(error).__name__))
988
+ finished=_recovery_preparation_clock_ns()
989
+ timing['owner_verification_ns']+=finished-entered
990
+ timing['verification_scope_exit_ns']+=_recovery_preparation_clock_ns()-finished
991
+ timing['verification_scope_count']+=1
992
+ timing['verification_recipe_max']=max(timing['verification_recipe_max'],len(pending))
993
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
994
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
995
+ _publication_authority[0]()
996
+ for candidate in accepted:
997
+ compiled[candidate.element_id]=candidate
998
+ timing['admitted_elements']+=1
999
+
1000
+
1001
+ def compile_affine_recovery(
1002
+ model: "FEModel",
1003
+ recovery: "RecoveryConfig",
1004
+ selected_element_ids: Sequence[int],
1005
+ *, _preparation_timings=None, _resource_config=None, _publication_authority=None,
1006
+ ) -> PreparedRecoveryCompilation:
1007
+ """Compile recovery maps for the selected, admitted elements."""
1008
+
1009
+ import time
1010
+
1011
+ from .analysis_session import _immutable_array
1012
+
1013
+ started = time.perf_counter()
1014
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
1015
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
1016
+ _publication_authority[0]()
1017
+ if _preparation_timings is not None and type(_preparation_timings) is not dict:
1018
+ raise PreparedStaticCapabilityError('preparation timing sink must be a source-owned plain dict')
1019
+ timing = dict(admission_ns=0, global_probe_allocation_ns=0, native_probe_ns=0,
1020
+ schema_ns=0, affine_validation_ns=0, owner_capture_ns=0,
1021
+ owner_verification_ns=0, publication_ns=0, initial_authority_ns=0,
1022
+ admitted_elements=0, native_probe_calls=0, owner_verification_calls=0,
1023
+ verification_scope_entry_ns=0,verification_scope_exit_ns=0,
1024
+ verification_scope_count=0,verification_recipe_max=0,
1025
+ pending_copy_peak_bytes=0,original_verification_fallback_elements=0)
1026
+ memory_base=estimate_model_memory(model,num_rhs=0,recovery_config=recovery)
1027
+ pending=[]
1028
+ pending_bytes=0
1029
+ flush_failed=False
1030
+ def flush_pending():
1031
+ nonlocal pending_bytes,flush_failed
1032
+ if pending:
1033
+ try:
1034
+ _verify_staged_recipes(pending,compiled,refused,timing)
1035
+ except Exception:
1036
+ flush_failed=True
1037
+ raise
1038
+ finally:
1039
+ pending.clear()
1040
+ pending_bytes=0
1041
+ total_dofs = int(model.mesh.dof_manager.total_dofs)
1042
+ compiled: Dict[int, CompiledElementRecovery] = {}
1043
+ retained = 0
1044
+ probes = 0
1045
+ refused: List[Dict[str, Any]] = []
1046
+ phase_started = _recovery_preparation_clock_ns()
1047
+ require_native_source_authority()
1048
+ if not _callable_authority_matches(_canonical_one_element_stress, _PROBE_HELPER_AUTHORITY):
1049
+ raise PreparedStaticCapabilityError("canonical element recovery helper authority changed")
1050
+ _authenticate_trusted_capture()
1051
+ timing['initial_authority_ns'] = _recovery_preparation_clock_ns()-phase_started
1052
+ for element_id in selected_element_ids:
1053
+ element = model.mesh.elements.get(int(element_id))
1054
+ if element is None:
1055
+ continue
1056
+ phase_started = _recovery_preparation_clock_ns()
1057
+ admitted, formulation, authority = _recovery_admission(model, element, recovery)
1058
+ timing['admission_ns'] += _recovery_preparation_clock_ns()-phase_started
1059
+ if not admitted:
1060
+ refused.append({
1061
+ "element_id": int(element_id),
1062
+ "formulation": formulation,
1063
+ "reason": authority,
1064
+ })
1065
+ continue
1066
+ dof_mapping = _immutable_array(
1067
+ np.asarray(element.get_dof_mapping(model.mesh), dtype=np.intp).reshape(-1)
1068
+ )
1069
+ local_size = int(dof_mapping.size)
1070
+ try:
1071
+ phase_started = _recovery_preparation_clock_ns()
1072
+ baseline_probe = np.zeros(total_dofs, dtype=float)
1073
+ timing['global_probe_allocation_ns'] += _recovery_preparation_clock_ns()-phase_started
1074
+ phase_started = _recovery_preparation_clock_ns()
1075
+ baseline = _probe_element_stress(model, baseline_probe,int(element_id), recovery)
1076
+ del baseline_probe
1077
+ timing['native_probe_ns'] += _recovery_preparation_clock_ns()-phase_started
1078
+ timing['native_probe_calls'] += 1
1079
+ probes += 1
1080
+ phase_started = _recovery_preparation_clock_ns()
1081
+ names = sorted(baseline)
1082
+ constants: Dict[str, Any] = {}
1083
+ arrays: Dict[str, np.ndarray] = {}
1084
+ shapes: Dict[str, Tuple[int, ...]] = {}
1085
+ unclassifiable: List[str] = []
1086
+ for name in names:
1087
+ value = baseline[name]
1088
+ content = _array_value(value)
1089
+ if content is not None:
1090
+ arrays[name] = content.reshape(-1)
1091
+ shapes[name] = tuple(int(size) for size in content.shape)
1092
+ continue
1093
+ try:
1094
+ constants[name] = _plain_constant(value)
1095
+ except ValueError:
1096
+ unclassifiable.append(name)
1097
+ if unclassifiable:
1098
+ raise PreparedStaticCapabilityError(
1099
+ "recovery components cannot be represented as plain "
1100
+ "constants or float fields: " + ", ".join(unclassifiable),
1101
+ diagnostics={"stage": "recovery compilation",
1102
+ "element_id": int(element_id),
1103
+ "components": unclassifiable},
1104
+ )
1105
+ columns: Dict[str, List[np.ndarray]] = {name: [] for name in arrays}
1106
+ timing['schema_ns'] += _recovery_preparation_clock_ns()-phase_started
1107
+ for local_index in range(local_size):
1108
+ phase_started = _recovery_preparation_clock_ns()
1109
+ probe = np.zeros(total_dofs, dtype=float)
1110
+ probe[dof_mapping[local_index]] = 1.0
1111
+ timing['global_probe_allocation_ns'] += _recovery_preparation_clock_ns()-phase_started
1112
+ phase_started = _recovery_preparation_clock_ns()
1113
+ observed = _probe_element_stress(model, probe, int(element_id), recovery)
1114
+ timing['native_probe_ns'] += _recovery_preparation_clock_ns()-phase_started
1115
+ timing['native_probe_calls'] += 1
1116
+ probes += 1
1117
+ phase_started = _recovery_preparation_clock_ns()
1118
+ if set(observed) != set(baseline):
1119
+ raise PreparedStaticCapabilityError(
1120
+ "recovery oracle changed its component set under local probes",
1121
+ diagnostics={"stage": "recovery compilation",
1122
+ "element_id": int(element_id)},
1123
+ )
1124
+ for name, value in constants.items():
1125
+ if not _constant_equal(value, observed[name]):
1126
+ raise PreparedStaticCapabilityError(
1127
+ "recovery component is not constant under local probes",
1128
+ diagnostics={"stage": "recovery compilation",
1129
+ "element_id": int(element_id),
1130
+ "component": str(name)},
1131
+ )
1132
+ for name in arrays:
1133
+ content = _array_value(observed[name])
1134
+ if (content is None
1135
+ or tuple(int(size) for size in content.shape) != shapes[name]):
1136
+ raise PreparedStaticCapabilityError(
1137
+ "recovery component changed shape under local probes",
1138
+ diagnostics={"stage": "recovery compilation",
1139
+ "element_id": int(element_id),
1140
+ "component": str(name)},
1141
+ )
1142
+ columns[name].append(content.reshape(-1) - arrays[name])
1143
+ timing['schema_ns'] += _recovery_preparation_clock_ns()-phase_started
1144
+ rng = np.random.default_rng(int(element_id))
1145
+ random_local = rng.uniform(-1.0, 1.0, local_size)
1146
+ phase_started = _recovery_preparation_clock_ns()
1147
+ random_probe = np.zeros(total_dofs, dtype=float)
1148
+ random_probe[dof_mapping] = random_local
1149
+ timing['global_probe_allocation_ns'] += _recovery_preparation_clock_ns()-phase_started
1150
+ phase_started = _recovery_preparation_clock_ns()
1151
+ observed = _probe_element_stress(model, random_probe, int(element_id), recovery)
1152
+ timing['native_probe_ns'] += _recovery_preparation_clock_ns()-phase_started
1153
+ timing['native_probe_calls'] += 1
1154
+ probes += 1
1155
+ phase_started = _recovery_preparation_clock_ns()
1156
+ linear: Dict[str, Tuple[np.ndarray, np.ndarray]] = {}
1157
+ frozen_constants = dict(constants)
1158
+ derived: Dict[str, str] = {}
1159
+ verification = 0.0
1160
+ for name in sorted(arrays):
1161
+ matrix = (
1162
+ np.column_stack(columns[name]) if columns[name]
1163
+ else np.zeros((arrays[name].size, 0))
1164
+ )
1165
+ expected = arrays[name] + (
1166
+ matrix @ random_local if matrix.size else 0.0
1167
+ )
1168
+ made = _array_value(observed[name])
1169
+ if made is None or made.shape != arrays[name].reshape(shapes[name]).shape:
1170
+ raise PreparedStaticCapabilityError(
1171
+ "recovery component changed shape under the random probe",
1172
+ diagnostics={"stage": "recovery compilation",
1173
+ "element_id": int(element_id),
1174
+ "component": str(name)},
1175
+ )
1176
+ made = made.reshape(-1)
1177
+ scale = max(
1178
+ float(np.linalg.norm(expected)), float(np.linalg.norm(made)), 1.0
1179
+ )
1180
+ residual = float(np.linalg.norm(made - expected) / scale)
1181
+ if name in {"von_mises", "equivalent_stress", "hill_utilization", *PRINCIPAL_COMPONENTS}:
1182
+ derived[name] = "derived"
1183
+ continue
1184
+ if np.isfinite(residual) and residual <= _AFFINITY_RELATIVE_TOLERANCE:
1185
+ if not matrix.size or not np.any(matrix):
1186
+ frozen_constants[name] = _immutable_array(
1187
+ arrays[name].reshape(shapes[name])
1188
+ )
1189
+ else:
1190
+ linear[name] = (_immutable_array(arrays[name]),
1191
+ _immutable_array(matrix))
1192
+ verification = max(verification, residual)
1193
+ continue
1194
+ derived[name] = "derived"
1195
+ record_probe: Dict[str, Any] = {}
1196
+ for name in sorted(arrays):
1197
+ if name in linear:
1198
+ base, matrix = linear[name]
1199
+ record_probe[name] = (base + matrix @ random_local).reshape(
1200
+ shapes[name]
1201
+ )
1202
+ elif name in frozen_constants and name not in derived:
1203
+ record_probe[name] = frozen_constants[name]
1204
+ for name in _ordered_derived(derived):
1205
+ try:
1206
+ value = _derived_measure(name, record_probe)
1207
+ except KeyError:
1208
+ refused.append({
1209
+ "element_id": int(element_id),
1210
+ "formulation": formulation,
1211
+ "reason": (
1212
+ f"derived measure {name!r} has no reviewed canonical "
1213
+ "computation from the compiled linear fields"
1214
+ ),
1215
+ })
1216
+ derived = {}
1217
+ break
1218
+ record_probe[name] = value
1219
+ made = _array_value(observed[name])
1220
+ if made is None:
1221
+ refused.append({
1222
+ "element_id": int(element_id),
1223
+ "formulation": formulation,
1224
+ "reason": f"derived measure {name!r} changed type",
1225
+ })
1226
+ derived = {}
1227
+ break
1228
+ expected = np.asarray(value, dtype=float)
1229
+ scale = max(
1230
+ float(np.linalg.norm(expected)),
1231
+ float(np.linalg.norm(made)),
1232
+ 1.0,
1233
+ )
1234
+ residual = float(
1235
+ np.linalg.norm(np.asarray(made, dtype=float).reshape(-1)
1236
+ - expected.reshape(-1)) / scale
1237
+ )
1238
+ if not np.isfinite(residual) or residual > _AFFINITY_RELATIVE_TOLERANCE:
1239
+ refused.append({
1240
+ "element_id": int(element_id),
1241
+ "formulation": formulation,
1242
+ "reason": (
1243
+ f"derived measure {name!r} does not reproduce the "
1244
+ "recovery oracle"
1245
+ ),
1246
+ "verification_relative_residual": residual,
1247
+ })
1248
+ derived = {}
1249
+ break
1250
+ verification = max(verification, residual)
1251
+ if not derived and any(name not in linear and name not in frozen_constants
1252
+ for name in arrays):
1253
+ continue
1254
+ array_components = []
1255
+ for name in sorted(linear):
1256
+ base, matrix = linear[name]
1257
+ array_components.append((name, base, matrix, shapes[name]))
1258
+ retained += int(matrix.nbytes + base.nbytes)
1259
+ constant_items = []
1260
+ for name in sorted(frozen_constants):
1261
+ if name in linear:
1262
+ continue
1263
+ value = frozen_constants[name]
1264
+ if isinstance(value, np.ndarray):
1265
+ retained += int(value.nbytes)
1266
+ constant_items.append((name, value))
1267
+ timing['affine_validation_ns'] += _recovery_preparation_clock_ns()-phase_started
1268
+ phase_started = _recovery_preparation_clock_ns()
1269
+ local_recipe = _capture_owned_local_recovery(model, element)
1270
+ timing['owner_capture_ns'] += _recovery_preparation_clock_ns()-phase_started
1271
+ scale_checks=[]
1272
+ scale_bytes=0
1273
+ immediate=False
1274
+ # Check owned canonical arithmetic at distinct scales and under a
1275
+ # rigid displacement offset; affinity probes alone are insufficient.
1276
+ for amplitude in (1e-8, 1e-3):
1277
+ local_check = random_local * amplitude
1278
+ local_check[0::6] += .125
1279
+ phase_started = _recovery_preparation_clock_ns()
1280
+ global_check = np.zeros(total_dofs)
1281
+ global_check[dof_mapping] = local_check
1282
+ timing['global_probe_allocation_ns'] += _recovery_preparation_clock_ns()-phase_started
1283
+ phase_started = _recovery_preparation_clock_ns()
1284
+ oracle_check = _probe_element_stress(model, global_check, int(element_id), recovery)
1285
+ timing['native_probe_ns'] += _recovery_preparation_clock_ns()-phase_started
1286
+ timing['native_probe_calls'] += 1
1287
+ incoming=_verification_storage_bytes(oracle_check)+_verification_storage_bytes(local_check)+128
1288
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
1289
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
1290
+ _publication_authority[0]()
1291
+ # Account scratch while older immutable entries remain live. Freeze
1292
+ # before any private verification can mutate a native shared cache.
1293
+ _check_verification_memory(memory_base,pending_bytes+scale_bytes+2*incoming,_resource_config,timing)
1294
+ saved_oracle=_freeze_verification_value(oracle_check)
1295
+ saved_local=_freeze_verification_value(local_check)
1296
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
1297
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
1298
+ _publication_authority[0]()
1299
+ if pending_bytes+scale_bytes+incoming>_VERIFICATION_PENDING_BYTES:
1300
+ flush_pending()
1301
+ if immediate or scale_bytes+incoming>_VERIFICATION_PENDING_BYTES:
1302
+ # Preserve unbounded/default caller admission for a single
1303
+ # oversized source record using original per-call authority.
1304
+ if not immediate:
1305
+ timing['original_verification_fallback_elements']+=1
1306
+ for prior_amplitude,prior_local,prior_oracle in scale_checks:
1307
+ _compare_owner_scale(local_recipe,prior_local,prior_oracle,element_id,prior_amplitude)
1308
+ timing['owner_verification_calls']+=1
1309
+ scale_checks.clear()
1310
+ scale_bytes=0
1311
+ phase_started=_recovery_preparation_clock_ns()
1312
+ _compare_owner_scale(local_recipe,saved_local,saved_oracle,element_id,amplitude)
1313
+ timing['owner_verification_ns']+=_recovery_preparation_clock_ns()-phase_started
1314
+ timing['owner_verification_calls']+=1
1315
+ immediate=True
1316
+ else:
1317
+ scale_checks.append((amplitude,saved_local,saved_oracle))
1318
+ scale_bytes+=incoming
1319
+ probes += 1
1320
+ phase_started = _recovery_preparation_clock_ns()
1321
+ candidate = CompiledElementRecovery(
1322
+ element_id=int(element_id),
1323
+ formulation=formulation,
1324
+ admitted_by=authority,
1325
+ dof_mapping=dof_mapping,
1326
+ array_components=tuple(array_components),
1327
+ constant_components=tuple(constant_items),
1328
+ derived_components=tuple(
1329
+ (name, derived[name]) for name in _ordered_derived(derived)
1330
+ ),
1331
+ verification_relative_residual=float(verification),
1332
+ local_recipe=local_recipe,
1333
+ )
1334
+ if immediate:
1335
+ compiled[int(element_id)]=candidate
1336
+ timing['admitted_elements']+=1
1337
+ else:
1338
+ pending.append((candidate,tuple(scale_checks),scale_bytes))
1339
+ pending_bytes+=scale_bytes
1340
+ if len(pending)>=_VERIFICATION_RECIPE_TILE:
1341
+ flush_pending()
1342
+ timing['publication_ns'] += _recovery_preparation_clock_ns()-phase_started
1343
+ except PreparedStaticCapabilityError as error:
1344
+ if flush_failed:
1345
+ raise
1346
+ refused.append({
1347
+ "element_id": int(element_id),
1348
+ "formulation": formulation,
1349
+ "reason": str(error),
1350
+ })
1351
+ continue
1352
+ except Exception as error:
1353
+ from .control import SolveCancelled
1354
+
1355
+ if flush_failed or isinstance(error, (SolveCancelled,ResourcePolicyError)):
1356
+ raise
1357
+ refused.append({
1358
+ "element_id": int(element_id),
1359
+ "formulation": formulation,
1360
+ "reason": "recovery oracle probe failed: " + str(error),
1361
+ "original_exception_type": type(error).__name__,
1362
+ })
1363
+ continue
1364
+ flush_pending()
1365
+ if not _callable_authority_matches(_publication_authority[0],_publication_authority[1]):
1366
+ raise PreparedStaticCapabilityError('recovery publication checker authority changed')
1367
+ _publication_authority[0]()
1368
+ if refused:
1369
+ raise PreparedStaticCapabilityError(
1370
+ "stress recovery was requested for elements outside the source-reviewed "
1371
+ "affine families; restrict RecoveryConfig.element_ids or disable "
1372
+ "include_stresses",
1373
+ diagnostics={"stage": "recovery compilation", "refused": refused},
1374
+ )
1375
+ retained += sum(item.local_recipe.retained_bytes for item in compiled.values())
1376
+ if _preparation_timings is not None:
1377
+ _preparation_timings.update(timing)
1378
+ return PreparedRecoveryCompilation(
1379
+ elements=compiled,
1380
+ retained_bytes=int(retained),
1381
+ probe_count=int(probes),
1382
+ setup_seconds=float(time.perf_counter() - started),
1383
+ )
1384
+
1385
+
1386
+ def apply_compiled_recovery(
1387
+ compilation: PreparedRecoveryCompilation,
1388
+ displacements: np.ndarray,
1389
+ ) -> Tuple[Dict[int, Dict[str, Any]], Dict[str, Any], Dict[str, Any]]:
1390
+ """Apply the compiled maps and derived measures to one displacement field."""
1391
+
1392
+ made = np.asarray(displacements, dtype=float).reshape(-1)
1393
+ stresses: Dict[int, Dict[str, Any]] = {}
1394
+ worst = 0.0
1395
+ refined = []
1396
+ for element_id, element in compilation.elements.items():
1397
+ local = made[element.dof_mapping]
1398
+ record: Dict[str, Any] = {}
1399
+ for name, baseline, matrix, shape in element.array_components:
1400
+ values = baseline + matrix @ local
1401
+ record[name] = values.reshape(shape) if shape else float(values.reshape(()))
1402
+ for name, value in element.constant_components:
1403
+ record[name] = (
1404
+ np.asarray(value).copy() if isinstance(value, np.ndarray) else copy.deepcopy(value)
1405
+ )
1406
+ principal_requested = any(name in PRINCIPAL_COMPONENTS for name, _ in element.derived_components)
1407
+ cancellation_sensitive = any(np.any(64 * np.finfo(float).eps * (np.abs(matrix) @ np.abs(local)) > 1e-12 + 1e-12 * np.abs(np.asarray(record[name]).reshape(-1))) for name, _base, matrix, _shape in element.array_components)
1408
+ canonical = None
1409
+ if element.local_recipe is not None:
1410
+ canonical = _recover_owned_local(element.local_recipe, local)
1411
+ refined.append(int(element_id))
1412
+ # Preserve only the admitted requested/dependency schema.
1413
+ for name in tuple(record):
1414
+ if name in canonical:
1415
+ record[name] = copy.deepcopy(canonical[name])
1416
+ for name, _kind in element.derived_components:
1417
+ record[name] = (copy.deepcopy(canonical[name]) if canonical is not None and name in canonical
1418
+ else _derived_measure(name, record))
1419
+ stresses[int(element_id)] = record
1420
+ worst = max(worst, element.verification_relative_residual)
1421
+ provenance = {
1422
+ "method": "prepared_affine_zero_local_basis_with_derived_measures",
1423
+ "compiled_elements": len(compilation.elements),
1424
+ "probe_count": int(compilation.probe_count),
1425
+ "worst_verification_relative_residual": float(worst),
1426
+ "setup_seconds": float(compilation.setup_seconds),
1427
+ }
1428
+ execution = {
1429
+ "prepared_affine_recovery": True,
1430
+ "compiled_elements": len(compilation.elements),
1431
+ "oracle_probes_at_preparation": int(compilation.probe_count),
1432
+ "canonical_local_refined_elements": refined,
1433
+ "prepared_q4_staged_elements": [eid for eid, item in compilation.elements.items() if item.local_recipe.staged_kernel is not None],
1434
+ }
1435
+ return stresses, provenance, execution
1436
+
1437
+
1438
+ @dataclass(frozen=True)
1439
+ class ReactionMaps:
1440
+ """Immutable data needed to reproduce constraint-force diagnostics."""
1441
+
1442
+ fixed_dofs: Tuple[int, ...]
1443
+ mpc_constraints: Tuple[Mapping[str, Any], ...]
1444
+ dof_info: Mapping[int, Tuple[int, int, str]]
1445
+ retained_bytes: int
1446
+
1447
+
1448
+ def capture_reaction_maps(model: "FEModel") -> ReactionMaps:
1449
+ """Capture fixed DOFs, MPC records and DOF-to-node info."""
1450
+
1451
+ from .assembly import _collect_mpc_constraints
1452
+
1453
+ mesh = model.mesh
1454
+ dof_manager = mesh.dof_manager
1455
+ fixed = sorted(int(dof) for dof in getattr(dof_manager, "_constrained_dofs", set()))
1456
+ constraints: List[Mapping[str, Any]] = []
1457
+ retained = 0
1458
+ for constraint in _collect_mpc_constraints(model):
1459
+ made = {
1460
+ "slave": int(constraint["slave"]),
1461
+ "masters": {int(dof): float(value) for dof, value in constraint["masters"].items()},
1462
+ "value": float(constraint["value"]),
1463
+ "label": str(constraint["label"]),
1464
+ "kind": str(constraint.get("kind", "mpc")),
1465
+ }
1466
+ constraints.append(made)
1467
+ retained += 64 + 24 * len(made["masters"])
1468
+ needed = set(fixed)
1469
+ for constraint in constraints:
1470
+ needed.add(int(constraint["slave"]))
1471
+ needed.update(int(dof) for dof in constraint["masters"])
1472
+ dof_info: Dict[int, Tuple[int, int, str]] = {}
1473
+ for dof in sorted(needed):
1474
+ node_id, local_index, component = dof_manager.get_dof_info(int(dof))
1475
+ dof_info[int(dof)] = (int(node_id), int(local_index), str(component))
1476
+ retained += 64
1477
+ return ReactionMaps(
1478
+ fixed_dofs=tuple(fixed),
1479
+ mpc_constraints=tuple(constraints),
1480
+ dof_info=dof_info,
1481
+ retained_bytes=int(retained),
1482
+ )
1483
+
1484
+
1485
+ def _add_dof_force(
1486
+ force_map: Dict[int, np.ndarray],
1487
+ dof_info: Mapping[int, Tuple[int, int, str]],
1488
+ dof: int,
1489
+ value: float,
1490
+ ) -> None:
1491
+ node_id, local_index, _component = dof_info[int(dof)]
1492
+ if node_id < 0 or local_index < 0:
1493
+ return
1494
+ force_map.setdefault(int(node_id), np.zeros(6, dtype=float))[int(local_index)] += float(value)
1495
+
1496
+
1497
+ def _compact_force_map(force_map: Mapping[int, np.ndarray]) -> Dict[int, np.ndarray]:
1498
+ compact: Dict[int, np.ndarray] = {}
1499
+ for node_id, values in force_map.items():
1500
+ vector = np.asarray(values, dtype=float).reshape(6)
1501
+ if np.any(vector != 0.0):
1502
+ compact[int(node_id)] = vector
1503
+ return compact
1504
+
1505
+
1506
+ def constraint_force_diagnostics(
1507
+ reaction_maps: ReactionMaps,
1508
+ stiffness: Any,
1509
+ transformation: Any,
1510
+ rigid_modes: np.ndarray,
1511
+ displacements: np.ndarray,
1512
+ external_load: np.ndarray,
1513
+ *,
1514
+ constraint_info: Mapping[str, Any],
1515
+ nullspace_info: Mapping[str, Any],
1516
+ metadata_copies: Any = None,
1517
+ ) -> Dict[str, Any]:
1518
+ """Reproduce the live constraint-force diagnostics from the snapshot.
1519
+
1520
+ ``metadata_copies`` (M20) may supply a callable returning fresh independent
1521
+ copies of (constraint_info, nullspace_info); the default deep-copies them.
1522
+ """
1523
+
1524
+ u = np.asarray(displacements, dtype=float).reshape(-1)
1525
+ F_ext = np.asarray(external_load, dtype=float).reshape(-1)
1526
+ residual = np.asarray(stiffness @ u - F_ext, dtype=float).reshape(-1)
1527
+ fixed_dofs = [int(dof) for dof in reaction_maps.fixed_dofs]
1528
+
1529
+ support_reactions: Dict[int, np.ndarray] = {}
1530
+ mpc_slave_forces: Dict[int, np.ndarray] = {}
1531
+ mpc_master_equivalent_forces: Dict[int, np.ndarray] = {}
1532
+ mpc_constraint_forces: List[Dict[str, Any]] = []
1533
+
1534
+ for dof in fixed_dofs:
1535
+ _add_dof_force(support_reactions, reaction_maps.dof_info, dof, residual[dof])
1536
+
1537
+ for index, constraint in enumerate(reaction_maps.mpc_constraints):
1538
+ slave = int(constraint["slave"])
1539
+ slave_force = float(residual[slave])
1540
+ slave_node, slave_local, slave_component = reaction_maps.dof_info[slave]
1541
+ _add_dof_force(mpc_slave_forces, reaction_maps.dof_info, slave, slave_force)
1542
+ master_entries: List[Dict[str, Any]] = []
1543
+ for master, coefficient in constraint["masters"].items():
1544
+ master_dof = int(master)
1545
+ value = float(coefficient) * slave_force
1546
+ _add_dof_force(mpc_master_equivalent_forces, reaction_maps.dof_info, master_dof, value)
1547
+ master_node, master_local, master_component = reaction_maps.dof_info[master_dof]
1548
+ master_entries.append({
1549
+ "dof": master_dof,
1550
+ "node_id": int(master_node),
1551
+ "local_index": int(master_local),
1552
+ "component": str(master_component),
1553
+ "coefficient": float(coefficient),
1554
+ "equivalent_force": value,
1555
+ })
1556
+ mpc_constraint_forces.append({
1557
+ "index": int(index),
1558
+ "label": str(constraint["label"]),
1559
+ "slave_dof": slave,
1560
+ "slave_node_id": int(slave_node),
1561
+ "slave_local_index": int(slave_local),
1562
+ "slave_component": str(slave_component),
1563
+ "slave_force": slave_force,
1564
+ "master_equivalent_forces": master_entries,
1565
+ "master_equivalent_norm": float(
1566
+ np.linalg.norm([entry["equivalent_force"] for entry in master_entries])
1567
+ ),
1568
+ })
1569
+
1570
+ reduced_residual = np.asarray(transformation.T @ residual, dtype=float).reshape(-1)
1571
+ if rigid_modes.shape[1] > 0:
1572
+ nullspace_generalized_forces = np.asarray(
1573
+ rigid_modes.T @ reduced_residual, dtype=float
1574
+ ).reshape(-1)
1575
+ else:
1576
+ nullspace_generalized_forces = np.zeros(0, dtype=float)
1577
+
1578
+ return {
1579
+ "residual": residual,
1580
+ "residual_norm": float(np.linalg.norm(residual)),
1581
+ "reduced_residual_norm": float(np.linalg.norm(reduced_residual)),
1582
+ "fixed_dofs": fixed_dofs,
1583
+ "mpc_slave_dofs": sorted(
1584
+ int(constraint["slave"]) for constraint in reaction_maps.mpc_constraints
1585
+ ),
1586
+ "support_reactions": _compact_force_map(support_reactions),
1587
+ "mpc_slave_forces": _compact_force_map(mpc_slave_forces),
1588
+ "mpc_master_equivalent_forces": _compact_force_map(mpc_master_equivalent_forces),
1589
+ "mpc_constraint_forces": mpc_constraint_forces,
1590
+ "support_reaction_norm": float(
1591
+ np.linalg.norm(np.concatenate(list(support_reactions.values())))
1592
+ if support_reactions else 0.0
1593
+ ),
1594
+ "mpc_slave_force_norm": float(
1595
+ np.linalg.norm(np.concatenate(list(mpc_slave_forces.values())))
1596
+ if mpc_slave_forces else 0.0
1597
+ ),
1598
+ "mpc_master_equivalent_force_norm": float(
1599
+ np.linalg.norm(np.concatenate(list(mpc_master_equivalent_forces.values())))
1600
+ if mpc_master_equivalent_forces else 0.0
1601
+ ),
1602
+ "nullspace_generalized_forces": nullspace_generalized_forces,
1603
+ "nullspace_generalized_force_norm": float(np.linalg.norm(nullspace_generalized_forces)),
1604
+ **_metadata_entries(constraint_info, nullspace_info, metadata_copies),
1605
+ }
1606
+
1607
+
1608
+ def _metadata_entries(constraint_info, nullspace_info, metadata_copies):
1609
+ if metadata_copies is None:
1610
+ return {"constraint_info": copy.deepcopy(dict(constraint_info)),
1611
+ "nullspace_info": copy.deepcopy(dict(nullspace_info))}
1612
+ made_constraint, made_nullspace = metadata_copies()
1613
+ return {"constraint_info": made_constraint, "nullspace_info": made_nullspace}
1614
+
1615
+
1616
+ def constraint_force_maps(reaction_maps: ReactionMaps, stiffness: Any, displacements: np.ndarray,
1617
+ external_load: np.ndarray) -> Dict[str, Dict[int, np.ndarray]]:
1618
+ """M20: only the three force maps, with the arithmetic of constraint_force_diagnostics."""
1619
+ u = np.asarray(displacements, dtype=float).reshape(-1)
1620
+ F_ext = np.asarray(external_load, dtype=float).reshape(-1)
1621
+ residual = np.asarray(stiffness @ u - F_ext, dtype=float).reshape(-1)
1622
+ support: Dict[int, np.ndarray] = {}
1623
+ slave_forces: Dict[int, np.ndarray] = {}
1624
+ master_forces: Dict[int, np.ndarray] = {}
1625
+ for dof in reaction_maps.fixed_dofs:
1626
+ dof = int(dof)
1627
+ _add_dof_force(support, reaction_maps.dof_info, dof, residual[dof])
1628
+ for constraint in reaction_maps.mpc_constraints:
1629
+ slave = int(constraint["slave"])
1630
+ slave_force = float(residual[slave])
1631
+ _add_dof_force(slave_forces, reaction_maps.dof_info, slave, slave_force)
1632
+ for master, coefficient in constraint["masters"].items():
1633
+ _add_dof_force(master_forces, reaction_maps.dof_info, int(master), float(coefficient) * slave_force)
1634
+ return {"support_reactions": _compact_force_map(support),
1635
+ "mpc_slave_forces": _compact_force_map(slave_forces),
1636
+ "mpc_master_equivalent_forces": _compact_force_map(master_forces)}
1637
+
1638
+
1639
+
1640
+ class ReactionPlan:
1641
+ """M22: index arrays and record templates for constraint_force_diagnostics.
1642
+
1643
+ planned_constraint_force_diagnostics reproduces constraint_force_diagnostics
1644
+ exactly: same residual, the same per-slot accumulation order (ufunc.at is
1645
+ unbuffered and applies repeated indices in order), first-touch node order,
1646
+ identical record key order and Python float values, and the same norm calls.
1647
+ """
1648
+
1649
+ def __init__(self, reaction_maps):
1650
+ dof_info = reaction_maps.dof_info
1651
+
1652
+ def slots(dofs):
1653
+ order, position, rows, cols, keep = [], {}, [], [], []
1654
+ for dof in dofs:
1655
+ node, local, _component = dof_info[int(dof)]
1656
+ if node < 0 or local < 0:
1657
+ keep.append(False)
1658
+ continue
1659
+ if int(node) not in position:
1660
+ position[int(node)] = len(order)
1661
+ order.append(int(node))
1662
+ rows.append(position[int(node)])
1663
+ cols.append(int(local))
1664
+ keep.append(True)
1665
+ return (tuple(order), np.asarray(rows, dtype=np.intp), np.asarray(cols, dtype=np.intp),
1666
+ np.asarray(keep, dtype=bool))
1667
+
1668
+ self.fixed_dofs = tuple(int(dof) for dof in reaction_maps.fixed_dofs)
1669
+ self.fixed_index = np.asarray(self.fixed_dofs, dtype=np.intp)
1670
+ self.support = slots(self.fixed_dofs)
1671
+ slaves = [int(constraint["slave"]) for constraint in reaction_maps.mpc_constraints]
1672
+ self.slave_index = np.asarray(slaves, dtype=np.intp)
1673
+ self.slave = slots(slaves)
1674
+ self.mpc_slave_dofs = tuple(sorted(slaves))
1675
+ master_dofs, coefficients, owners, templates, ranges = [], [], [], [], []
1676
+ for index, constraint in enumerate(reaction_maps.mpc_constraints):
1677
+ slave = int(constraint["slave"])
1678
+ slave_node, slave_local, slave_component = dof_info[slave]
1679
+ start = len(master_dofs)
1680
+ entries = []
1681
+ for master, coefficient in constraint["masters"].items():
1682
+ master_dof = int(master)
1683
+ master_node, master_local, master_component = dof_info[master_dof]
1684
+ master_dofs.append(master_dof)
1685
+ coefficients.append(float(coefficient))
1686
+ owners.append(index)
1687
+ entries.append({"dof": master_dof, "node_id": int(master_node), "local_index": int(master_local),
1688
+ "component": str(master_component), "coefficient": float(coefficient)})
1689
+ ranges.append((start, len(master_dofs)))
1690
+ templates.append(({"index": int(index), "label": str(constraint["label"]), "slave_dof": slave,
1691
+ "slave_node_id": int(slave_node), "slave_local_index": int(slave_local),
1692
+ "slave_component": str(slave_component)}, tuple(entries)))
1693
+ self.master = slots(master_dofs)
1694
+ self.master_coefficients = np.asarray(coefficients, dtype=float)
1695
+ self.master_owners = np.asarray(owners, dtype=np.intp)
1696
+ self.templates = tuple(templates)
1697
+ self.ranges = tuple(ranges)
1698
+
1699
+
1700
+ def _planned_map(spec, values):
1701
+ order, rows, cols, keep = spec
1702
+ made = np.zeros((len(order), 6), dtype=float)
1703
+ np.add.at(made, (rows, cols), np.asarray(values, dtype=float)[keep])
1704
+ return order, made
1705
+
1706
+
1707
+ def _planned_norm(order, made):
1708
+ if not order:
1709
+ return 0.0
1710
+ return float(np.linalg.norm(np.concatenate([made[i] for i in range(len(order))])))
1711
+
1712
+
1713
+ def _planned_compact(order, made):
1714
+ # M30: one vectorised non-zero test per map instead of one per node; the same
1715
+ # rows are copied in the same order.
1716
+ keep = np.any(made != 0.0, axis=1)
1717
+ return {node: made[i].copy() for i, node in enumerate(order) if keep[i]}
1718
+
1719
+
1720
+ def planned_constraint_force_diagnostics(plan, stiffness, transformation, rigid_modes, displacements, external_load,
1721
+ *, constraint_info, nullspace_info, metadata_copies=None, maps_only=False):
1722
+ u = np.asarray(displacements, dtype=float).reshape(-1)
1723
+ F_ext = np.asarray(external_load, dtype=float).reshape(-1)
1724
+ residual = np.asarray(stiffness @ u - F_ext, dtype=float).reshape(-1)
1725
+ support = _planned_map(plan.support, residual[plan.fixed_index])
1726
+ slave_values = residual[plan.slave_index]
1727
+ slave = _planned_map(plan.slave, slave_values)
1728
+ master_values = plan.master_coefficients * slave_values[plan.master_owners]
1729
+ master = _planned_map(plan.master, master_values)
1730
+ maps = {"support_reactions": _planned_compact(*support),
1731
+ "mpc_slave_forces": _planned_compact(*slave),
1732
+ "mpc_master_equivalent_forces": _planned_compact(*master)}
1733
+ if maps_only:
1734
+ return maps
1735
+ slave_floats = slave_values.tolist()
1736
+ master_floats = master_values.tolist()
1737
+ records = []
1738
+ for (head, entries), (start, stop), slave_force in zip(plan.templates, plan.ranges, slave_floats):
1739
+ master_entries = [dict(entry, equivalent_force=value)
1740
+ for entry, value in zip(entries, master_floats[start:stop])]
1741
+ records.append({**head, "slave_force": slave_force, "master_equivalent_forces": master_entries,
1742
+ "master_equivalent_norm": float(np.linalg.norm(
1743
+ [entry["equivalent_force"] for entry in master_entries]))})
1744
+ reduced_residual = np.asarray(transformation.T @ residual, dtype=float).reshape(-1)
1745
+ if rigid_modes.shape[1] > 0:
1746
+ nullspace_generalized_forces = np.asarray(rigid_modes.T @ reduced_residual, dtype=float).reshape(-1)
1747
+ else:
1748
+ nullspace_generalized_forces = np.zeros(0, dtype=float)
1749
+ return {
1750
+ "residual": residual,
1751
+ "residual_norm": float(np.linalg.norm(residual)),
1752
+ "reduced_residual_norm": float(np.linalg.norm(reduced_residual)),
1753
+ "fixed_dofs": list(plan.fixed_dofs),
1754
+ "mpc_slave_dofs": list(plan.mpc_slave_dofs),
1755
+ **maps,
1756
+ "mpc_constraint_forces": records,
1757
+ "support_reaction_norm": _planned_norm(*support),
1758
+ "mpc_slave_force_norm": _planned_norm(*slave),
1759
+ "mpc_master_equivalent_force_norm": _planned_norm(*master),
1760
+ "nullspace_generalized_forces": nullspace_generalized_forces,
1761
+ "nullspace_generalized_force_norm": float(np.linalg.norm(nullspace_generalized_forces)),
1762
+ **_metadata_entries(constraint_info, nullspace_info, metadata_copies),
1763
+ }
1764
+
1765
+
1766
+ # M25e review fix: the module check, built after every module-level import of this
1767
+ # module (so prepared_static_q4 and the other span modules are included).
1768
+ from types import FunctionType as _CaptureFunctionType
1769
+ from itertools import repeat as _capture_repeat
1770
+ from .fe_core import _QualifiedStateMapping as _CaptureStateMapping
1771
+
1772
+
1773
+ def _make_capture_module_check():
1774
+ import sys
1775
+ import types
1776
+ from importlib.machinery import ModuleSpec
1777
+ families = ('numpy', 'math', 'cmath', 'warnings', 'sys', 'types', 'io', 'dataclasses', 'builtins',
1778
+ 'functools', 'operator', 'contextlib', 'contextvars', 'copy', 'linecache', 'anyfesolver',
1779
+ 'anymaterial', 'numba') # scipy is used by neither the proofs nor the span code
1780
+ missing = object()
1781
+ held = [None] # sys.modules, captured on the first opt-in admission
1782
+ unsafe = [False] # sys.modules holds a key that is not an exact str
1783
+ fixed = [] # (name, module, kind, namespace, hook)
1784
+ late = [] # [name, module, kind, namespace, pinned hook or None, authority]
1785
+ uninspectable = [] # (name, object): namespace not readable without a Python-level descriptor
1786
+ seen = [0]
1787
+
1788
+ def safe_namespace(obj):
1789
+ # M25i (opt-in review round 3): read an instance namespace without invoking any
1790
+ # Python-level descriptor. This runs on the first opt-in admission (M25k: never
1791
+ # at import) and must not call code from arbitrary sys.modules entries: every class in the MRO must
1792
+ # have the plain metaclass and define __dict__ only as a builtin descriptor and
1793
+ # __getattribute__ only as a builtin slot wrapper. None when that does not hold.
1794
+ # M25j (round 4): identity comparisons only (no __eq__ of a foreign metaclass),
1795
+ # and each builtin descriptor must belong to the class that defines it.
1796
+ kind = type(obj)
1797
+ if type(kind) is not type:
1798
+ return None
1799
+ for cls in type.__getattribute__(kind, '__mro__'):
1800
+ if type(cls) is not type:
1801
+ return None
1802
+ members = type.__getattribute__(cls, '__dict__')
1803
+ for key in members: # M25k: exact str keys before any keyed lookup (no foreign __eq__)
1804
+ if type(key) is not str:
1805
+ return None
1806
+ member = members.get('__dict__')
1807
+ if member is not None and not (
1808
+ (type(member) is types.MemberDescriptorType or type(member) is types.GetSetDescriptorType)
1809
+ and member.__objclass__ is cls):
1810
+ return None
1811
+ member = members.get('__getattribute__')
1812
+ if member is not None and not (type(member) is types.WrapperDescriptorType and member.__objclass__ is cls):
1813
+ return None
1814
+ try:
1815
+ namespace = object.__getattribute__(obj, '__dict__')
1816
+ except Exception: # only builtin descriptors can run here (checked above)
1817
+ return None
1818
+ if type(namespace) is not dict:
1819
+ return None
1820
+ for key in namespace: # M25k: exact str keys before any keyed lookup (no foreign __eq__)
1821
+ if type(key) is not str:
1822
+ return None
1823
+ return namespace
1824
+
1825
+ def initializing(namespace):
1826
+ spec = dict.get(namespace, '__spec__')
1827
+ if type(spec) is not ModuleSpec:
1828
+ return False
1829
+ state = safe_namespace(spec)
1830
+ return type(state) is dict and dict.get(state, '_initializing') is True
1831
+
1832
+ def track(name, module):
1833
+ namespace = safe_namespace(module)
1834
+ if namespace is None:
1835
+ # Not inspectable without running its own code: never read, and the opt-in
1836
+ # keeps the guarded route while this object is registered under that name.
1837
+ uninspectable.append((name, module))
1838
+ return
1839
+ if initializing(namespace):
1840
+ late.append([name, module, type(module), namespace, None, None])
1841
+ else:
1842
+ fixed.append((name, module, type(module), namespace, dict.get(namespace, '__getattr__', missing)))
1843
+
1844
+ def current_modules():
1845
+ # M25k: sys.modules, read without a keyed lookup in a namespace that holds a
1846
+ # non-str key (such a key's __eq__ could run during the lookup).
1847
+ if type(sys) is not types.ModuleType:
1848
+ return None
1849
+ namespace = object.__getattribute__(sys, '__dict__')
1850
+ if type(namespace) is not dict:
1851
+ return None
1852
+ for key in namespace:
1853
+ if type(key) is not str:
1854
+ return None
1855
+ found = dict.get(namespace, 'modules')
1856
+ return found if type(found) is dict else None
1857
+
1858
+ def scan(modules):
1859
+ tracked = ({id(entry[1]) for entry in fixed} | {id(entry[1]) for entry in late}
1860
+ | {id(entry[1]) for entry in uninspectable})
1861
+ unsafe[0] = False
1862
+ for name, module in tuple(dict.items(modules)):
1863
+ if type(name) is not str:
1864
+ unsafe[0] = True
1865
+ continue
1866
+ if name.split('.', 1)[0] in families and module is not None and id(module) not in tracked:
1867
+ track(name, module)
1868
+ seen[0] = len(modules)
1869
+ return (tuple(entry[0] for entry in fixed), tuple(entry[1] for entry in fixed),
1870
+ tuple(entry[3] for entry in fixed), tuple(id(entry[1]) for entry in fixed),
1871
+ tuple(id(entry[2]) for entry in fixed), tuple(id(entry[4]) for entry in fixed))
1872
+
1873
+ # M25k (opt-in review round 5): built on the first opt-in admission, not at import,
1874
+ # so the default path never inspects sys.modules.
1875
+ tables = [None]
1876
+
1877
+ def canonical_hook(hook, namespace):
1878
+ file = dict.get(namespace, '__file__')
1879
+ return (type(hook) is _CaptureFunctionType and hook.__globals__ is namespace and type(file) is str
1880
+ and type(hook.__code__.co_filename) is str and hook.__code__.co_filename == file
1881
+ and hook.__code__.co_name == '__getattr__')
1882
+
1883
+ def check():
1884
+ modules = current_modules()
1885
+ if modules is None:
1886
+ return False
1887
+ if held[0] is None:
1888
+ held[0] = modules
1889
+ if modules is not held[0]:
1890
+ return False
1891
+ if tables[0] is None or len(modules) != seen[0]:
1892
+ tables[0] = scan(modules) # first opt-in use, then newly imported modules (pinned on first sight)
1893
+ if unsafe[0]:
1894
+ return False
1895
+ names, objects, namespaces, object_ids, type_ids, hook_ids = tables[0]
1896
+ for name, obj in uninspectable:
1897
+ if dict.get(modules, name) is obj:
1898
+ return False
1899
+ if (tuple(map(id, map(dict.get, _capture_repeat(modules), names))) != object_ids
1900
+ or tuple(map(id, map(type, objects))) != type_ids
1901
+ or tuple(map(id, map(dict.get, namespaces, _capture_repeat('__getattr__'),
1902
+ _capture_repeat(missing)))) != hook_ids):
1903
+ return False
1904
+ for entry in late:
1905
+ name, module, kind, namespace, pinned, authority = entry
1906
+ if dict.get(modules, name) is not module or type(module) is not kind:
1907
+ return False
1908
+ current = dict.get(namespace, '__getattr__', missing)
1909
+ if pinned is not None:
1910
+ if current is not pinned or (authority is not None and not _callable_authority_matches(current, authority)):
1911
+ return False
1912
+ continue
1913
+ if current is not missing and not canonical_hook(current, namespace):
1914
+ return False
1915
+ if not initializing(namespace):
1916
+ # Finished executing: pin its hook (or its absence) with the code fingerprint.
1917
+ entry[4] = current
1918
+ entry[5] = None if current is missing else _callable_authority(current)
1919
+ return True
1920
+
1921
+ check.__name__ = check.__qualname__ = '_capture_modules_unhooked'
1922
+ return check
1923
+
1924
+
1925
+ _capture_modules_unhooked = _make_capture_module_check()
1926
+
1927
+
1928
+ # M25e review fix: the LoadCase pressure helper the span runs, and the boundary
1929
+ # functions it calls, with code and global bindings. A change keeps the guarded
1930
+ # route (as a substituted helper did before M25) instead of refusing.
1931
+ def _make_pressure_helper_check():
1932
+ # M25m: objects from import statements; the boundary module's globals dict (not
1933
+ # the module object) is the owner of its functions. M25n (opt-in review round 8):
1934
+ # the facts are built on the first check (opt-in only), never at import.
1935
+ from .boundary import LoadCase as load_case
1936
+ built = []
1937
+
1938
+ def build():
1939
+ helper = type.__getattribute__(load_case, '__dict__')['_consistent_pressure_load']
1940
+ boundary_globals = helper.__globals__
1941
+ boundary_name = dict.get(boundary_globals, '__name__')
1942
+ made = [(load_case, '_consistent_pressure_load', helper)]
1943
+ for name in helper.__code__.co_names:
1944
+ value = dict.get(boundary_globals, name)
1945
+ if type(value) is _CaptureFunctionType and value.__module__ == boundary_name:
1946
+ made.append((boundary_globals, name, value))
1947
+ return tuple((owner, name, function, _callable_authority(function),
1948
+ tuple((key, function.__globals__[key]) for key in function.__code__.co_names
1949
+ if key in function.__globals__))
1950
+ for owner, name, function in made)
1951
+
1952
+ def check():
1953
+ if not built:
1954
+ built.append(build())
1955
+ for owner, name, function, authority, bindings in built[0]:
1956
+ namespace = type.__getattribute__(owner, '__dict__') if type(owner) is type else owner
1957
+ if (namespace.get(name) is not function or not _callable_authority_matches(function, authority)
1958
+ or any(function.__globals__.get(key) is not value for key, value in bindings)):
1959
+ return False
1960
+ return True
1961
+
1962
+ check.__name__ = check.__qualname__ = '_capture_pressure_helpers_authentic'
1963
+ return check
1964
+
1965
+
1966
+ _capture_pressure_helpers_authentic = _make_pressure_helper_check()
1967
+
1968
+
1969
+ # M25b review fix: the executables a capture span relies on (the capture itself,
1970
+ # its admission helpers, the proof functions, this checker) with their global
1971
+ # bindings. The facts live only in this closure, so no module binding can empty
1972
+ # them; every capture caller runs the check before capturing.
1973
+ def _make_capture_span_authority():
1974
+ # M25m (opt-in review round 7): the proof functions come from import statements and
1975
+ # their modules' globals dicts are the owners, so nothing here reads a module object.
1976
+ from .boundary import (_numpy_error_routes_are_builtin, _load_routing_classes_are_untouched,
1977
+ _exact_load_case_instance_namespace)
1978
+ from .prepared_static_loads import _plain_numeric_state, _instance_authority
1979
+ from .static_batch import _class_state
1980
+ # M25l (opt-in review round 6): this module's own globals, not a sys.modules lookup.
1981
+ namespace = globals()
1982
+ built = []
1983
+ code = None
1984
+
1985
+ def require():
1986
+ # The checker verifies its own module binding and code, then the facts.
1987
+ # M25n (opt-in review round 8): the facts are recorded on the first check
1988
+ # (only an opted-in preparation calls this), never at import.
1989
+ if not built:
1990
+ built.append(build())
1991
+ facts = built[0]
1992
+ if (dict.get(namespace, '_require_capture_span_authority') is not require
1993
+ or require.__code__ is not code or require.__defaults__ is not None
1994
+ or any((dict.get(owner, name) if type(owner) is dict else getattr(owner, name, None)) is not function
1995
+ or not _callable_authority_matches(function, authority)
1996
+ or any(function.__globals__.get(key) is not value for key, value in bindings)
1997
+ for owner, name, function, authority, bindings in facts)):
1998
+ raise PreparedStaticCapabilityError('capture trusted span authority changed')
1999
+
2000
+ require.__name__ = require.__qualname__ = '_require_capture_span_authority'
2001
+ code = require.__code__
2002
+ # Capture calls this checker through a keyword default, so rebinding the module
2003
+ # name cannot replace it; set before the capture's own facts are recorded.
2004
+ _capture_owned_local_recovery.__kwdefaults__['_span_authority'] = require
2005
+
2006
+ def build():
2007
+ made = []
2008
+
2009
+ def record(owner, name, function):
2010
+ if any(entry[2] is function for entry in made):
2011
+ return
2012
+ made.append((owner, name, function, _callable_authority(function),
2013
+ tuple((key, function.__globals__[key]) for key in function.__code__.co_names
2014
+ if key in function.__globals__)))
2015
+
2016
+ for name in ('_capture_owned_local_recovery', '_capture_span_factory', '_capture_span_proofs_hold',
2017
+ '_capture_plain_arguments', '_capture_material_is_native', '_capture_modules_unhooked',
2018
+ '_capture_trust_state', '_capture_same_state', '_capture_owner_matches_identity',
2019
+ '_capture_frozen_equal', '_capture_pressure_helpers_authentic', '_capture_state_tables',
2020
+ '_capture_material_facts', '_require_owned_scope_factory_authority',
2021
+ '_require_capture_scope_tables', 'compile_affine_recovery'):
2022
+ record(namespace, name, namespace[name])
2023
+ for name, function in (('_numpy_error_routes_are_builtin', _numpy_error_routes_are_builtin),
2024
+ ('_load_routing_classes_are_untouched', _load_routing_classes_are_untouched),
2025
+ ('_exact_load_case_instance_namespace', _exact_load_case_instance_namespace),
2026
+ ('_plain_numeric_state', _plain_numeric_state), ('_instance_authority', _instance_authority),
2027
+ ('_class_state', _class_state)):
2028
+ record(function.__globals__, name, function)
2029
+ return tuple(made)
2030
+
2031
+ return require
2032
+
2033
+
2034
+ _require_capture_span_authority = _make_capture_span_authority()
2035
+
2036
+
2037
+ def _make_verification_publication_authority():
2038
+ """Pin delayed publication before any live model/config observation."""
2039
+ namespace=globals()
2040
+ matches=_callable_authority_matches
2041
+ records=[]
2042
+ def require():
2043
+ for name,function,authority,bindings in records:
2044
+ if (dict.get(namespace,name) is not function or not matches(function,authority)
2045
+ or any(dict.get(function.__globals__,key) is not value for key,value in bindings)):
2046
+ raise PreparedStaticCapabilityError('recovery verification publication authority changed')
2047
+ if (sys.getsizeof is not storage_size or np.frombuffer is not frombuffer
2048
+ or np.asarray is not asarray or np.allclose is not allclose):
2049
+ raise PreparedStaticCapabilityError('recovery verification operation authority changed')
2050
+ storage_size=sys.getsizeof
2051
+ frombuffer=np.frombuffer
2052
+ asarray=np.asarray
2053
+ allclose=np.allclose
2054
+ proof=(require,_callable_authority(require))
2055
+ for name in ('_probe_element_stress','compile_affine_recovery','_verify_staged_recipes'):
2056
+ namespace[name].__kwdefaults__['_publication_authority']=proof
2057
+ for name in ('_probe_element_stress','compile_affine_recovery','_verify_staged_recipes',
2058
+ '_compare_owner_scale','_freeze_verification_value','_verification_storage_bytes',
2059
+ '_check_verification_memory','_recover_owned_local','_array_value',
2060
+ '_constant_equal','_derived_measure'):
2061
+ function=namespace[name]
2062
+ bindings=tuple((key,function.__globals__[key]) for key in function.__code__.co_names
2063
+ if key in function.__globals__)
2064
+ records.append((name,function,_callable_authority(function),bindings))
2065
+ return require
2066
+
2067
+
2068
+ _require_verification_publication_authority=_make_verification_publication_authority()