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.
- anyfesolver/__init__.py +1075 -0
- anyfesolver/_b2_attachment_elimination_dev.py +261 -0
- anyfesolver/_b2_f10_f11_point_attachment.py +163 -0
- anyfesolver/_b2_loaded_point_trace_dev.py +173 -0
- anyfesolver/_b2_physical_flexibility_linear.py +707 -0
- anyfesolver/_b2_physical_linear_batch_dev.py +491 -0
- anyfesolver/_b2_physical_linear_definition.py +415 -0
- anyfesolver/_b2_physical_load_bubbles.py +282 -0
- anyfesolver/_compensated_contractions.py +497 -0
- anyfesolver/_compensated_native.py +437 -0
- anyfesolver/_corotational_batch.py +219 -0
- anyfesolver/_director_trace.py +214 -0
- anyfesolver/_dyadic_bilinear.py +76 -0
- anyfesolver/_dyadic_factor_chain.py +25 -0
- anyfesolver/_ge_beam3_adaptive_arc_program.py +286 -0
- anyfesolver/_ge_beam3_analysis_combined.py +102 -0
- anyfesolver/_ge_beam3_analysis_fibre_translation.py +130 -0
- anyfesolver/_ge_beam3_analysis_modal_snapshot.py +27 -0
- anyfesolver/_ge_beam3_analysis_nodal_program.py +140 -0
- anyfesolver/_ge_beam3_analysis_translation.py +147 -0
- anyfesolver/_ge_beam3_analysis_translation_modal.py +84 -0
- anyfesolver/_ge_beam3_arc_geometry.py +89 -0
- anyfesolver/_ge_beam3_arc_program.py +304 -0
- anyfesolver/_ge_beam3_authoritative_cell_seed.py +25 -0
- anyfesolver/_ge_beam3_cell_identity.py +52 -0
- anyfesolver/_ge_beam3_centered_batched_finite_inertia.py +132 -0
- anyfesolver/_ge_beam3_centered_finite_inertia.py +171 -0
- anyfesolver/_ge_beam3_centered_mixed.py +104 -0
- anyfesolver/_ge_beam3_centered_reference.py +187 -0
- anyfesolver/_ge_beam3_centered_so3_strain.py +76 -0
- anyfesolver/_ge_beam3_complementary_section.py +122 -0
- anyfesolver/_ge_beam3_compliance_snapshot_guard.py +20 -0
- anyfesolver/_ge_beam3_connected_dynamic.py +486 -0
- anyfesolver/_ge_beam3_connected_trajectory.py +258 -0
- anyfesolver/_ge_beam3_conservative_backtrack.py +26 -0
- anyfesolver/_ge_beam3_controlled_fibre_modes.py +105 -0
- anyfesolver/_ge_beam3_coupled_beam_subdomain.py +128 -0
- anyfesolver/_ge_beam3_coupled_modes.py +256 -0
- anyfesolver/_ge_beam3_deterministic_reduction.py +264 -0
- anyfesolver/_ge_beam3_displacement_program.py +255 -0
- anyfesolver/_ge_beam3_durable_coupled.py +251 -0
- anyfesolver/_ge_beam3_dynamic_elastic_state.py +461 -0
- anyfesolver/_ge_beam3_effective_deadline.py +58 -0
- anyfesolver/_ge_beam3_elastic_seed_continuation.py +223 -0
- anyfesolver/_ge_beam3_elastic_seed_modal.py +139 -0
- anyfesolver/_ge_beam3_equilibrium_line_program.py +98 -0
- anyfesolver/_ge_beam3_equilibrium_schur_solver.py +96 -0
- anyfesolver/_ge_beam3_factor_chain_modes.py +103 -0
- anyfesolver/_ge_beam3_fibre_cell.py +408 -0
- anyfesolver/_ge_beam3_fibre_distributed_couples.py +191 -0
- anyfesolver/_ge_beam3_fibre_kinematic_cell.py +75 -0
- anyfesolver/_ge_beam3_fibre_line_program.py +157 -0
- anyfesolver/_ge_beam3_fibre_line_work.py +87 -0
- anyfesolver/_ge_beam3_fibre_reference_modal.py +104 -0
- anyfesolver/_ge_beam3_fibre_schur_solver.py +134 -0
- anyfesolver/_ge_beam3_fibre_section.py +286 -0
- anyfesolver/_ge_beam3_fibre_static_boundary.py +113 -0
- anyfesolver/_ge_beam3_g1_analysis.py +307 -0
- anyfesolver/_ge_beam3_g1_elastic.py +161 -0
- anyfesolver/_ge_beam3_g1_element.py +185 -0
- anyfesolver/_ge_beam3_g1_operator.py +137 -0
- anyfesolver/_ge_beam3_g2_analysis.py +176 -0
- anyfesolver/_ge_beam3_g2_constraints.py +162 -0
- anyfesolver/_ge_beam3_g3_analysis.py +385 -0
- anyfesolver/_ge_beam3_g3_constraints.py +168 -0
- anyfesolver/_ge_beam3_g3b_owner.py +296 -0
- anyfesolver/_ge_beam3_g3b_q4_reference.py +186 -0
- anyfesolver/_ge_beam3_g3b_reference.py +206 -0
- anyfesolver/_ge_beam3_g3b_result_schema.py +107 -0
- anyfesolver/_ge_beam3_g3b_s3_reference.py +185 -0
- anyfesolver/_ge_beam3_g3b_weighted_reference.py +193 -0
- anyfesolver/_ge_beam3_g3c_affine_q4_chart.py +58 -0
- anyfesolver/_ge_beam3_g3c_affine_q4_increment_chart.py +257 -0
- anyfesolver/_ge_beam3_g3c_affine_q4_recovery.py +434 -0
- anyfesolver/_ge_beam3_g3c_affine_q4_registry.py +127 -0
- anyfesolver/_ge_beam3_g3c_b2_physical.py +153 -0
- anyfesolver/_ge_beam3_g3c_b2_physical_adapter.py +160 -0
- anyfesolver/_ge_beam3_g3c_definition.py +117 -0
- anyfesolver/_ge_beam3_g3c_local_beam.py +188 -0
- anyfesolver/_ge_beam3_g3c_local_shell.py +371 -0
- anyfesolver/_ge_beam3_g3c_owner.py +400 -0
- anyfesolver/_ge_beam3_g3c_physical_authority.py +203 -0
- anyfesolver/_ge_beam3_g3c_physical_owner.py +556 -0
- anyfesolver/_ge_beam3_g3c_recovery.py +91 -0
- anyfesolver/_ge_beam3_g3c_so3_numerics.py +81 -0
- anyfesolver/_ge_beam3_g3c_stable/__init__.py +1 -0
- anyfesolver/_ge_beam3_g3c_stable/authority.py +152 -0
- anyfesolver/_ge_beam3_g3c_stable/beam.py +196 -0
- anyfesolver/_ge_beam3_g3c_stable/boundary.py +38 -0
- anyfesolver/_ge_beam3_g3c_stable/chart.py +23 -0
- anyfesolver/_ge_beam3_g3c_stable/compensated.py +46 -0
- anyfesolver/_ge_beam3_g3c_stable/element.py +190 -0
- anyfesolver/_ge_beam3_g3c_stable/fit.py +54 -0
- anyfesolver/_ge_beam3_g3c_stable/joint.py +255 -0
- anyfesolver/_ge_beam3_g3c_stable/line_work.py +92 -0
- anyfesolver/_ge_beam3_g3c_stable/operator.py +147 -0
- anyfesolver/_ge_beam3_g3c_stable/owner.py +415 -0
- anyfesolver/_ge_beam3_g3c_stable/shell.py +358 -0
- anyfesolver/_ge_beam3_g4_completion.py +186 -0
- anyfesolver/_ge_beam3_g4a_station_transaction.py +385 -0
- anyfesolver/_ge_beam3_g5_completion.py +68 -0
- anyfesolver/_ge_beam3_g6_domain.py +354 -0
- anyfesolver/_ge_beam3_generalized_cell.py +172 -0
- anyfesolver/_ge_beam3_generalized_ellipsoid_section.py +215 -0
- anyfesolver/_ge_beam3_generalized_static_boundary.py +233 -0
- anyfesolver/_ge_beam3_lifted_kinetic_factor.py +26 -0
- anyfesolver/_ge_beam3_line_fibre_modes.py +121 -0
- anyfesolver/_ge_beam3_load_program.py +275 -0
- anyfesolver/_ge_beam3_loaded_modal.py +225 -0
- anyfesolver/_ge_beam3_mixed_ad.py +286 -0
- anyfesolver/_ge_beam3_native_analysis.py +484 -0
- anyfesolver/_ge_beam3_native_arc.py +269 -0
- anyfesolver/_ge_beam3_native_arc_adaptive.py +251 -0
- anyfesolver/_ge_beam3_native_arc_restart.py +88 -0
- anyfesolver/_ge_beam3_native_arc_source.py +62 -0
- anyfesolver/_ge_beam3_native_buckling.py +206 -0
- anyfesolver/_ge_beam3_native_combined_couples.py +179 -0
- anyfesolver/_ge_beam3_native_combined_restart.py +205 -0
- anyfesolver/_ge_beam3_native_definition.py +238 -0
- anyfesolver/_ge_beam3_native_distributed_element.py +220 -0
- anyfesolver/_ge_beam3_native_distributed_loading.py +92 -0
- anyfesolver/_ge_beam3_native_distributed_program.py +165 -0
- anyfesolver/_ge_beam3_native_distributed_recovery.py +34 -0
- anyfesolver/_ge_beam3_native_distributed_restart.py +221 -0
- anyfesolver/_ge_beam3_native_dynamic_analysis.py +149 -0
- anyfesolver/_ge_beam3_native_dynamic_restart.py +261 -0
- anyfesolver/_ge_beam3_native_fibre_current_modal.py +104 -0
- anyfesolver/_ge_beam3_native_fibre_recovery.py +34 -0
- anyfesolver/_ge_beam3_native_fibre_restart.py +211 -0
- anyfesolver/_ge_beam3_native_fibre_static_element.py +193 -0
- anyfesolver/_ge_beam3_native_force_adaptation.py +56 -0
- anyfesolver/_ge_beam3_native_generalized_combined_couples.py +188 -0
- anyfesolver/_ge_beam3_native_generalized_combined_restart.py +207 -0
- anyfesolver/_ge_beam3_native_generalized_element.py +222 -0
- anyfesolver/_ge_beam3_native_generalized_factor_modal.py +122 -0
- anyfesolver/_ge_beam3_native_generalized_loading.py +92 -0
- anyfesolver/_ge_beam3_native_generalized_modal.py +274 -0
- anyfesolver/_ge_beam3_native_generalized_paired_modal.py +13 -0
- anyfesolver/_ge_beam3_native_generalized_parameter.py +89 -0
- anyfesolver/_ge_beam3_native_generalized_program.py +196 -0
- anyfesolver/_ge_beam3_native_generalized_recovery.py +34 -0
- anyfesolver/_ge_beam3_native_generalized_restart.py +243 -0
- anyfesolver/_ge_beam3_native_history_profile.py +53 -0
- anyfesolver/_ge_beam3_native_line_loading.py +93 -0
- anyfesolver/_ge_beam3_native_line_program.py +150 -0
- anyfesolver/_ge_beam3_native_line_recovery.py +34 -0
- anyfesolver/_ge_beam3_native_line_restart.py +197 -0
- anyfesolver/_ge_beam3_native_line_static_element.py +220 -0
- anyfesolver/_ge_beam3_native_load_admission.py +37 -0
- anyfesolver/_ge_beam3_native_midpoint.py +1004 -0
- anyfesolver/_ge_beam3_native_multi_shell_contact.py +539 -0
- anyfesolver/_ge_beam3_native_pair_contact.py +236 -0
- anyfesolver/_ge_beam3_native_shell_contact.py +357 -0
- anyfesolver/_ge_beam3_native_spatial_couples.py +176 -0
- anyfesolver/_ge_beam3_native_spatial_restart.py +204 -0
- anyfesolver/_ge_beam3_native_transient.py +467 -0
- anyfesolver/_ge_beam3_native_translation.py +225 -0
- anyfesolver/_ge_beam3_native_translation_restart.py +54 -0
- anyfesolver/_ge_beam3_operator_validation_scope.py +53 -0
- anyfesolver/_ge_beam3_p5/__init__.py +11 -0
- anyfesolver/_ge_beam3_p5/algebra.py +91 -0
- anyfesolver/_ge_beam3_p5/arrays.py +29 -0
- anyfesolver/_ge_beam3_p5/chart.py +45 -0
- anyfesolver/_ge_beam3_p5/codec.py +82 -0
- anyfesolver/_ge_beam3_p5/committed_modal.py +191 -0
- anyfesolver/_ge_beam3_p5/compensated.py +118 -0
- anyfesolver/_ge_beam3_p5/compensated_coordinates.py +44 -0
- anyfesolver/_ge_beam3_p5/core.py +159 -0
- anyfesolver/_ge_beam3_p5/element.py +207 -0
- anyfesolver/_ge_beam3_p5/factors.py +61 -0
- anyfesolver/_ge_beam3_p5/inertia.py +120 -0
- anyfesolver/_ge_beam3_p5/mass.py +70 -0
- anyfesolver/_ge_beam3_p5/mixed.py +256 -0
- anyfesolver/_ge_beam3_p5/reference_modal.py +73 -0
- anyfesolver/_ge_beam3_p5/section.py +139 -0
- anyfesolver/_ge_beam3_p5/typed_schema.py +111 -0
- anyfesolver/_ge_beam3_p5_centered/__init__.py +6 -0
- anyfesolver/_ge_beam3_p5_centered/codec.py +84 -0
- anyfesolver/_ge_beam3_p5_centered/committed_modal.py +192 -0
- anyfesolver/_ge_beam3_p5_centered/core.py +171 -0
- anyfesolver/_ge_beam3_p5_centered/element.py +216 -0
- anyfesolver/_ge_beam3_p5_centered/mass.py +99 -0
- anyfesolver/_ge_beam3_p5_centered/positions.py +34 -0
- anyfesolver/_ge_beam3_p5_centered/reference_modal.py +73 -0
- anyfesolver/_ge_beam3_p5_coordinates/__init__.py +6 -0
- anyfesolver/_ge_beam3_p5_coordinates/codec.py +84 -0
- anyfesolver/_ge_beam3_p5_coordinates/committed_modal.py +192 -0
- anyfesolver/_ge_beam3_p5_coordinates/core.py +163 -0
- anyfesolver/_ge_beam3_p5_coordinates/element.py +213 -0
- anyfesolver/_ge_beam3_p5_coordinates/positions.py +102 -0
- anyfesolver/_ge_beam3_p5_coordinates/reference_modal.py +73 -0
- anyfesolver/_ge_beam3_p5_loads/__init__.py +7 -0
- anyfesolver/_ge_beam3_p5_loads/codec.py +85 -0
- anyfesolver/_ge_beam3_p5_loads/core.py +192 -0
- anyfesolver/_ge_beam3_p5_loads/element.py +243 -0
- anyfesolver/_ge_beam3_p5_loads/state.py +120 -0
- anyfesolver/_ge_beam3_p5_loads/work.py +45 -0
- anyfesolver/_ge_beam3_p5_seeded/__init__.py +9 -0
- anyfesolver/_ge_beam3_p5_seeded/codec.py +87 -0
- anyfesolver/_ge_beam3_p5_seeded/core.py +197 -0
- anyfesolver/_ge_beam3_p5_seeded/element.py +245 -0
- anyfesolver/_ge_beam3_p5_seeded/state.py +122 -0
- anyfesolver/_ge_beam3_p5_seeded/work.py +47 -0
- anyfesolver/_ge_beam3_pose_joint.py +268 -0
- anyfesolver/_ge_beam3_precise_geometric_work.py +147 -0
- anyfesolver/_ge_beam3_reassembled_signed_modes.py +101 -0
- anyfesolver/_ge_beam3_reference_identity.py +92 -0
- anyfesolver/_ge_beam3_refined_fibre_schur_solver.py +104 -0
- anyfesolver/_ge_beam3_refinement_capacity.py +19 -0
- anyfesolver/_ge_beam3_relative_virgin_reference_modes.py +60 -0
- anyfesolver/_ge_beam3_retained_arc.py +350 -0
- anyfesolver/_ge_beam3_retained_arc_geometry.py +66 -0
- anyfesolver/_ge_beam3_retained_arc_modal.py +125 -0
- anyfesolver/_ge_beam3_retained_elastic.py +134 -0
- anyfesolver/_ge_beam3_retained_fibre.py +209 -0
- anyfesolver/_ge_beam3_retained_fibre_control.py +270 -0
- anyfesolver/_ge_beam3_retained_fibre_element.py +58 -0
- anyfesolver/_ge_beam3_retained_fibre_modal.py +155 -0
- anyfesolver/_ge_beam3_retained_fibre_modes.py +189 -0
- anyfesolver/_ge_beam3_retained_fibre_program.py +68 -0
- anyfesolver/_ge_beam3_retained_fibre_state.py +268 -0
- anyfesolver/_ge_beam3_retained_force_program.py +72 -0
- anyfesolver/_ge_beam3_retained_generalized.py +186 -0
- anyfesolver/_ge_beam3_retained_generalized_modal.py +171 -0
- anyfesolver/_ge_beam3_retained_generalized_program.py +63 -0
- anyfesolver/_ge_beam3_retained_generalized_state.py +281 -0
- anyfesolver/_ge_beam3_retained_nodal_loading.py +111 -0
- anyfesolver/_ge_beam3_retained_plastic.py +116 -0
- anyfesolver/_ge_beam3_retained_plastic_modes.py +144 -0
- anyfesolver/_ge_beam3_retained_plastic_program.py +69 -0
- anyfesolver/_ge_beam3_retained_plastic_state.py +165 -0
- anyfesolver/_ge_beam3_retained_state.py +194 -0
- anyfesolver/_ge_beam3_retained_translation_control.py +345 -0
- anyfesolver/_ge_beam3_retained_translation_modal.py +137 -0
- anyfesolver/_ge_beam3_schur_line_program.py +98 -0
- anyfesolver/_ge_beam3_seeded_fibre_control.py +175 -0
- anyfesolver/_ge_beam3_seeded_load_program.py +301 -0
- anyfesolver/_ge_beam3_seeded_loaded_modal.py +226 -0
- anyfesolver/_ge_beam3_seeded_signed_modes.py +150 -0
- anyfesolver/_ge_beam3_shared_kinematic_split.py +53 -0
- anyfesolver/_ge_beam3_shell_joint_state.py +396 -0
- anyfesolver/_ge_beam3_shell_joint_trial.py +217 -0
- anyfesolver/_ge_beam3_signed_loaded_modes.py +140 -0
- anyfesolver/_ge_beam3_spatial_fibre_program.py +113 -0
- anyfesolver/_ge_beam3_spatial_nodal_moments.py +38 -0
- anyfesolver/_ge_beam3_station_resultant_cell.py +235 -0
- anyfesolver/_ge_beam3_variational_shell.py +211 -0
- anyfesolver/_ge_beam3_virgin_reference_modes.py +60 -0
- anyfesolver/_impact_coupling.py +162 -0
- anyfesolver/_native_authority_dag.py +77 -0
- anyfesolver/_native_dyadic_interval_inertia.py +64 -0
- anyfesolver/_native_exact_shift_chain_modes.py +140 -0
- anyfesolver/_native_exact_shift_inertia.py +86 -0
- anyfesolver/_native_factor_chain_modes.py +192 -0
- anyfesolver/_native_guard.cpp +828 -0
- anyfesolver/_native_guard_dispatch.py +140 -0
- anyfesolver/_native_large_factor_chain.py +55 -0
- anyfesolver/_native_material_protocol.py +51 -0
- anyfesolver/_native_midpoint_controller.py +90 -0
- anyfesolver/_native_modal_capacity.py +26 -0
- anyfesolver/_native_paired_factor_chain_modes.py +244 -0
- anyfesolver/_native_reassembled_factor_modes.py +185 -0
- anyfesolver/_native_reference_factor_spectrum.py +102 -0
- anyfesolver/_native_reference_modal.py +180 -0
- anyfesolver/_native_relative_factor_chain_modes.py +204 -0
- anyfesolver/_native_relative_reference_factor_spectrum.py +110 -0
- anyfesolver/_native_retained_step.py +15 -0
- anyfesolver/_native_rotation_state.py +797 -0
- anyfesolver/_native_signed_factor_modes.py +257 -0
- anyfesolver/_native_signed_kinetic_factor_modes.py +279 -0
- anyfesolver/_native_stationary_spectrum.py +79 -0
- anyfesolver/_nonlinear_events.py +150 -0
- anyfesolver/_nonlinear_seed.py +80 -0
- anyfesolver/_paired_modal_arithmetic.py +71 -0
- anyfesolver/_plate_stability_continuation.py +171 -0
- anyfesolver/_q4_assumed_strain_plasticity.py +502 -0
- anyfesolver/_qualified_authority_epoch.py +561 -0
- anyfesolver/_relative_reference_svd.py +44 -0
- anyfesolver/_stiffened_shell_affine_endpoint.py +232 -0
- anyfesolver/_stiffened_shell_algorithmic_pullback.py +38 -0
- anyfesolver/_stiffened_shell_arc_checkpoint.py +422 -0
- anyfesolver/_stiffened_shell_arc_control.py +108 -0
- anyfesolver/_stiffened_shell_arc_metric.py +222 -0
- anyfesolver/_stiffened_shell_arc_publication.py +153 -0
- anyfesolver/_stiffened_shell_arc_trial.py +310 -0
- anyfesolver/_stiffened_shell_b2_affine_resolution.py +101 -0
- anyfesolver/_stiffened_shell_b2_assembly_diagnostics.py +172 -0
- anyfesolver/_stiffened_shell_b2_definition_ownership.py +189 -0
- anyfesolver/_stiffened_shell_b2_experimental.py +153 -0
- anyfesolver/_stiffened_shell_b2_nodal_footprint.py +273 -0
- anyfesolver/_stiffened_shell_b2_physical_rigid_footprint.py +237 -0
- anyfesolver/_stiffened_shell_b2_q4_panel_experimental.py +2447 -0
- anyfesolver/_stiffened_shell_b2_q4_point_experimental.py +251 -0
- anyfesolver/_stiffened_shell_b2_reference_buckling.py +413 -0
- anyfesolver/_stiffened_shell_b3_chart.py +70 -0
- anyfesolver/_stiffened_shell_b3_local_adapter.py +141 -0
- anyfesolver/_stiffened_shell_beam_history.py +107 -0
- anyfesolver/_stiffened_shell_bl5_local_experimental.py +173 -0
- anyfesolver/_stiffened_shell_bl5_native_elastic.py +28 -0
- anyfesolver/_stiffened_shell_branch_event_trial.py +58 -0
- anyfesolver/_stiffened_shell_consumer_capabilities.py +124 -0
- anyfesolver/_stiffened_shell_coordinate_support.py +110 -0
- anyfesolver/_stiffened_shell_displacement_adaptive.py +327 -0
- anyfesolver/_stiffened_shell_displacement_checkpoint.py +360 -0
- anyfesolver/_stiffened_shell_displacement_control.py +326 -0
- anyfesolver/_stiffened_shell_displacement_corrector.py +293 -0
- anyfesolver/_stiffened_shell_displacement_increment.py +121 -0
- anyfesolver/_stiffened_shell_distributing_footprint.py +354 -0
- anyfesolver/_stiffened_shell_distributing_pose.py +267 -0
- anyfesolver/_stiffened_shell_f06_increment_arithmetic.py +205 -0
- anyfesolver/_stiffened_shell_ge_body_load.py +300 -0
- anyfesolver/_stiffened_shell_ge_checkpoint_binary.py +472 -0
- anyfesolver/_stiffened_shell_ge_distributed_load.py +210 -0
- anyfesolver/_stiffened_shell_ge_distributed_programme.py +175 -0
- anyfesolver/_stiffened_shell_ge_distributing_finite.py +242 -0
- anyfesolver/_stiffened_shell_ge_distributing_reference.py +296 -0
- anyfesolver/_stiffened_shell_ge_experimental.py +2454 -0
- anyfesolver/_stiffened_shell_ge_finite_experimental.py +2714 -0
- anyfesolver/_stiffened_shell_ge_mixed_modal_mass.py +33 -0
- anyfesolver/_stiffened_shell_ge_point_buckling.py +723 -0
- anyfesolver/_stiffened_shell_ge_pressure_load.py +366 -0
- anyfesolver/_stiffened_shell_ge_reference_modal.py +580 -0
- anyfesolver/_stiffened_shell_generalized_recovery.py +150 -0
- anyfesolver/_stiffened_shell_increment_control.py +217 -0
- anyfesolver/_stiffened_shell_l1_assembly_experimental.py +420 -0
- anyfesolver/_stiffened_shell_l1_local_experimental.py +284 -0
- anyfesolver/_stiffened_shell_l1_supported_work_experimental.py +146 -0
- anyfesolver/_stiffened_shell_native_distributed_load.py +120 -0
- anyfesolver/_stiffened_shell_native_gravity.py +236 -0
- anyfesolver/_stiffened_shell_nodal_footprint.py +255 -0
- anyfesolver/_stiffened_shell_physical_q4_mass.py +98 -0
- anyfesolver/_stiffened_shell_physical_s3_mass.py +53 -0
- anyfesolver/_stiffened_shell_point_recovery.py +49 -0
- anyfesolver/_stiffened_shell_q4_algorithmic_pullback.py +88 -0
- anyfesolver/_stiffened_shell_q4_material_event.py +114 -0
- anyfesolver/_stiffened_shell_q4_reference.py +65 -0
- anyfesolver/_stiffened_shell_rotation_charts.py +58 -0
- anyfesolver/_stiffened_shell_s3_algorithmic_pullback.py +59 -0
- anyfesolver/_stiffened_shell_s3_conservative_buckling.py +28 -0
- anyfesolver/_stiffened_shell_s3_dead_work_candidate.py +107 -0
- anyfesolver/_stiffened_shell_s3_material_event.py +30 -0
- anyfesolver/_stiffened_shell_same_parent_trial.py +224 -0
- anyfesolver/_stiffened_shell_selected_recovery.py +113 -0
- anyfesolver/_stiffened_shell_staged_load_experimental.py +392 -0
- anyfesolver/_stiffened_shell_stress_free_reference.py +204 -0
- anyfesolver/_wire_types.py +17 -0
- anyfesolver/activity.py +1537 -0
- anyfesolver/algebraic_dynamics.py +2864 -0
- anyfesolver/analysis_session.py +897 -0
- anyfesolver/anystructure_fem_mode.py +1637 -0
- anyfesolver/arc_length.py +2587 -0
- anyfesolver/assembly.py +1830 -0
- anyfesolver/b3_ge.py +179 -0
- anyfesolver/baselines.py +303 -0
- anyfesolver/beam_sections.py +292 -0
- anyfesolver/beam_shell_verification.py +4685 -0
- anyfesolver/beam_validity.py +110 -0
- anyfesolver/benchmarks.py +495 -0
- anyfesolver/boundary.py +1680 -0
- anyfesolver/buckling.py +2136 -0
- anyfesolver/buckling_validity.py +91 -0
- anyfesolver/capacity_workflow.py +431 -0
- anyfesolver/capsule_runtime_guard.py +99 -0
- anyfesolver/cases.py +173 -0
- anyfesolver/composite_strip_verification.py +297 -0
- anyfesolver/concentrated_mass.py +84 -0
- anyfesolver/constraint_audit.py +553 -0
- anyfesolver/contact.py +4785 -0
- anyfesolver/contact_performance.py +301 -0
- anyfesolver/control.py +206 -0
- anyfesolver/corotational.py +1935 -0
- anyfesolver/corotational_performance.py +157 -0
- anyfesolver/current_state_tangent.py +2780 -0
- anyfesolver/cylinder_benchmarks.py +369 -0
- anyfesolver/damage_matrix_performance.py +617 -0
- anyfesolver/dynamics.py +2833 -0
- anyfesolver/e4_pl_element.py +8265 -0
- anyfesolver/e4_pl_enriched.py +194 -0
- anyfesolver/e4_pl_enriched_nonlinear.py +159 -0
- anyfesolver/e4_pl_s3_element.py +9752 -0
- anyfesolver/e4_pl_s3_initial_fields.py +101 -0
- anyfesolver/e4_pl_s3_state.py +4723 -0
- anyfesolver/e4_pl_s3_v2_element.py +1844 -0
- anyfesolver/e4_pl_s3_v2b_element.py +1936 -0
- anyfesolver/e4_pl_s3_v2c_element.py +2196 -0
- anyfesolver/e4_pl_s3_v2d_element.py +2621 -0
- anyfesolver/e4_pl_s3_v2d_state.py +376 -0
- anyfesolver/element_capabilities.py +159 -0
- anyfesolver/element_qualification.py +432 -0
- anyfesolver/elements.py +6327 -0
- anyfesolver/experimental_csr_assembly.py +238 -0
- anyfesolver/external_references.py +1253 -0
- anyfesolver/fe_core.py +832 -0
- anyfesolver/floating_pressure_regions.py +119 -0
- anyfesolver/floating_response.py +486 -0
- anyfesolver/floating_transient.py +1281 -0
- anyfesolver/fracture.py +621 -0
- anyfesolver/ge_beam3_curved_reference.py +725 -0
- anyfesolver/ge_beam3_element.py +885 -0
- anyfesolver/ge_beam3_mixed_element.py +1216 -0
- anyfesolver/ge_beam3_mixed_state.py +814 -0
- anyfesolver/ge_beam3_native.py +129 -0
- anyfesolver/ge_beam3_native_connected_dynamics.py +24 -0
- anyfesolver/ge_beam3_native_dynamics.py +70 -0
- anyfesolver/ge_beam3_state.py +672 -0
- anyfesolver/general_contact/__init__.py +151 -0
- anyfesolver/general_contact/_enriched_multitarget_empty.py +202 -0
- anyfesolver/general_contact/_enriched_multitarget_trial.py +183 -0
- anyfesolver/general_contact/_results_archive_common.py +65 -0
- anyfesolver/general_contact/algebraic_path.py +206 -0
- anyfesolver/general_contact/arc_contact.py +237 -0
- anyfesolver/general_contact/arc_results_archive.py +158 -0
- anyfesolver/general_contact/augmentation.py +157 -0
- anyfesolver/general_contact/automatic_domain.py +621 -0
- anyfesolver/general_contact/beam_chain_consistent.py +357 -0
- anyfesolver/general_contact/beam_chain_end_geometry.py +237 -0
- anyfesolver/general_contact/beam_chain_ends.py +220 -0
- anyfesolver/general_contact/beam_chain_pair.py +416 -0
- anyfesolver/general_contact/beam_curve_end_geometry.py +254 -0
- anyfesolver/general_contact/beam_curve_ends.py +237 -0
- anyfesolver/general_contact/beam_curve_pair.py +794 -0
- anyfesolver/general_contact/beam_curve_pair_static.py +303 -0
- anyfesolver/general_contact/beam_end_radial_geometry.py +221 -0
- anyfesolver/general_contact/beam_end_topology.py +15 -0
- anyfesolver/general_contact/beam_hemisphere_coupled_tool.py +61 -0
- anyfesolver/general_contact/beam_hemisphere_cylinder.py +134 -0
- anyfesolver/general_contact/beam_hemisphere_cylinder_motion.py +89 -0
- anyfesolver/general_contact/beam_hemisphere_cylinder_work.py +216 -0
- anyfesolver/general_contact/beam_hemisphere_plane.py +175 -0
- anyfesolver/general_contact/beam_hemisphere_s3_support.py +152 -0
- anyfesolver/general_contact/beam_hemisphere_sphere.py +135 -0
- anyfesolver/general_contact/beam_hemisphere_sphere_work.py +114 -0
- anyfesolver/general_contact/beam_hemisphere_support.py +211 -0
- anyfesolver/general_contact/beam_pair.py +523 -0
- anyfesolver/general_contact/beam_pair_end_geometry.py +269 -0
- anyfesolver/general_contact/beam_pair_ends.py +222 -0
- anyfesolver/general_contact/beam_plane.py +425 -0
- anyfesolver/general_contact/beam_radial.py +500 -0
- anyfesolver/general_contact/beam_radial_ends.py +223 -0
- anyfesolver/general_contact/beam_s3_directional_rates.py +196 -0
- anyfesolver/general_contact/beam_s3_rotational_transient.py +126 -0
- anyfesolver/general_contact/beam_s3_transient.py +139 -0
- anyfesolver/general_contact/beam_shell.py +648 -0
- anyfesolver/general_contact/beam_shell_patch.py +395 -0
- anyfesolver/general_contact/beam_temporal_bounds.py +230 -0
- anyfesolver/general_contact/capability.py +101 -0
- anyfesolver/general_contact/capped_cylinder.py +618 -0
- anyfesolver/general_contact/contact_search.py +95 -0
- anyfesolver/general_contact/core.py +1769 -0
- anyfesolver/general_contact/curved_s3_q4_work.py +238 -0
- anyfesolver/general_contact/curved_shell_skin.py +520 -0
- anyfesolver/general_contact/d1_interior_profile.py +427 -0
- anyfesolver/general_contact/deformable_beam_s3_caps.py +143 -0
- anyfesolver/general_contact/deformable_beam_s3_line.py +211 -0
- anyfesolver/general_contact/deformable_beam_s3_static.py +152 -0
- anyfesolver/general_contact/descriptor_rates.py +61 -0
- anyfesolver/general_contact/enriched_cold_predictor.py +161 -0
- anyfesolver/general_contact/enriched_moment_batch.py +337 -0
- anyfesolver/general_contact/enriched_multitarget.py +308 -0
- anyfesolver/general_contact/enriched_multitarget_gap.py +156 -0
- anyfesolver/general_contact/enriched_multitarget_moments.py +194 -0
- anyfesolver/general_contact/enriched_multitarget_topology.py +171 -0
- anyfesolver/general_contact/enriched_nonlinear.py +440 -0
- anyfesolver/general_contact/enriched_overlap.py +299 -0
- anyfesolver/general_contact/enriched_path.py +253 -0
- anyfesolver/general_contact/enriched_small_cut.py +310 -0
- anyfesolver/general_contact/enriched_static.py +481 -0
- anyfesolver/general_contact/enriched_union.py +320 -0
- anyfesolver/general_contact/equilibrated_pressure_pivot.py +140 -0
- anyfesolver/general_contact/event_subdivision.py +273 -0
- anyfesolver/general_contact/finite_mixed_state.py +133 -0
- anyfesolver/general_contact/finite_rigid_patch.py +196 -0
- anyfesolver/general_contact/finite_rigid_support.py +129 -0
- anyfesolver/general_contact/finite_rigid_sweep.py +53 -0
- anyfesolver/general_contact/finite_rotation_predictor.py +171 -0
- anyfesolver/general_contact/finite_rotation_skin.py +212 -0
- anyfesolver/general_contact/finite_rotation_state.py +106 -0
- anyfesolver/general_contact/finite_rotation_sweep.py +161 -0
- anyfesolver/general_contact/finite_v2d_state.py +104 -0
- anyfesolver/general_contact/fixed_projection_graph.py +205 -0
- anyfesolver/general_contact/fixed_projection_s3_events.py +235 -0
- anyfesolver/general_contact/free_rigid.py +134 -0
- anyfesolver/general_contact/friction_point.py +378 -0
- anyfesolver/general_contact/gap_extrema.py +119 -0
- anyfesolver/general_contact/guided_pair_transient_geometry.py +138 -0
- anyfesolver/general_contact/initial_contact_seed.py +368 -0
- anyfesolver/general_contact/linearized_pivot.py +595 -0
- anyfesolver/general_contact/material_work_diagnostics.py +159 -0
- anyfesolver/general_contact/moment_batch_ad.py +129 -0
- anyfesolver/general_contact/moment_batch_points.py +75 -0
- anyfesolver/general_contact/moving.py +1031 -0
- anyfesolver/general_contact/multiple_beam_rigid.py +248 -0
- anyfesolver/general_contact/multiple_beam_s3_caps.py +199 -0
- anyfesolver/general_contact/multiple_beam_s3_transient.py +223 -0
- anyfesolver/general_contact/multiple_projected_pairs.py +196 -0
- anyfesolver/general_contact/multiple_shell_planes.py +226 -0
- anyfesolver/general_contact/native_activation_static.py +143 -0
- anyfesolver/general_contact/native_checkpoint_results.py +213 -0
- anyfesolver/general_contact/native_curve_union.py +268 -0
- anyfesolver/general_contact/native_curved_geometry.py +374 -0
- anyfesolver/general_contact/native_curved_pair_partition.py +590 -0
- anyfesolver/general_contact/native_curved_plane_work.py +864 -0
- anyfesolver/general_contact/native_curved_s3_work.py +472 -0
- anyfesolver/general_contact/native_event_polynomial.py +164 -0
- anyfesolver/general_contact/native_fibre_context.py +173 -0
- anyfesolver/general_contact/native_fibre_results.py +74 -0
- anyfesolver/general_contact/native_initial_guess.py +122 -0
- anyfesolver/general_contact/native_material_activation.py +108 -0
- anyfesolver/general_contact/native_material_plane.py +111 -0
- anyfesolver/general_contact/native_material_s3.py +209 -0
- anyfesolver/general_contact/native_multi_s3_static.py +722 -0
- anyfesolver/general_contact/native_normal_angles.py +233 -0
- anyfesolver/general_contact/native_pair_error_budget.py +49 -0
- anyfesolver/general_contact/native_replay_arithmetic.py +103 -0
- anyfesolver/general_contact/native_replay_diagnostics.py +90 -0
- anyfesolver/general_contact/native_retained_body_junction.py +99 -0
- anyfesolver/general_contact/native_retained_body_pair.py +220 -0
- anyfesolver/general_contact/native_retained_body_projection.py +160 -0
- anyfesolver/general_contact/native_retained_body_static.py +62 -0
- anyfesolver/general_contact/native_retained_cell_pair.py +185 -0
- anyfesolver/general_contact/native_retained_curve_cell.py +102 -0
- anyfesolver/general_contact/native_retained_line_plane.py +210 -0
- anyfesolver/general_contact/native_retained_line_s3.py +440 -0
- anyfesolver/general_contact/native_retained_line_s3_work.py +91 -0
- anyfesolver/general_contact/native_retained_pair_static.py +845 -0
- anyfesolver/general_contact/native_retained_s3_static.py +554 -0
- anyfesolver/general_contact/native_retained_static.py +478 -0
- anyfesolver/general_contact/native_retained_station.py +222 -0
- anyfesolver/general_contact/native_retained_whole_pair.py +2120 -0
- anyfesolver/general_contact/native_s3_swept.py +149 -0
- anyfesolver/general_contact/native_shell_static_driver.py +144 -0
- anyfesolver/general_contact/native_surface_activation.py +391 -0
- anyfesolver/general_contact/native_surface_bounds.py +198 -0
- anyfesolver/general_contact/native_surface_contributor.py +121 -0
- anyfesolver/general_contact/native_surface_crossing.py +81 -0
- anyfesolver/general_contact/native_surface_event_sequence.py +320 -0
- anyfesolver/general_contact/native_surface_extrema.py +72 -0
- anyfesolver/general_contact/native_surface_friction_integral.py +1724 -0
- anyfesolver/general_contact/native_surface_front.py +742 -0
- anyfesolver/general_contact/native_surface_front_boundary.py +160 -0
- anyfesolver/general_contact/native_surface_front_precision.py +78 -0
- anyfesolver/general_contact/native_surface_front_work.py +118 -0
- anyfesolver/general_contact/native_surface_history.py +334 -0
- anyfesolver/general_contact/native_surface_normal.py +324 -0
- anyfesolver/general_contact/native_surface_patch.py +213 -0
- anyfesolver/general_contact/native_surface_path.py +348 -0
- anyfesolver/general_contact/native_surface_quadrature.py +212 -0
- anyfesolver/general_contact/native_surface_s3_friction.py +340 -0
- anyfesolver/general_contact/native_surface_support.py +776 -0
- anyfesolver/general_contact/native_surface_transaction.py +198 -0
- anyfesolver/general_contact/native_transient_multi_shell.py +108 -0
- anyfesolver/general_contact/native_transient_pair.py +61 -0
- anyfesolver/general_contact/native_transient_shell.py +44 -0
- anyfesolver/general_contact/normal_s3_skin_path.py +163 -0
- anyfesolver/general_contact/overlap.py +443 -0
- anyfesolver/general_contact/pair.py +466 -0
- anyfesolver/general_contact/patch_broadphase.py +111 -0
- anyfesolver/general_contact/patch_mortar.py +593 -0
- anyfesolver/general_contact/patch_sliding.py +644 -0
- anyfesolver/general_contact/plane_newmark_events.py +278 -0
- anyfesolver/general_contact/pointwise_patch.py +288 -0
- anyfesolver/general_contact/polynomial_roots.py +312 -0
- anyfesolver/general_contact/pressure_basis.py +241 -0
- anyfesolver/general_contact/pressure_diagnostics.py +155 -0
- anyfesolver/general_contact/pressure_overlay.py +182 -0
- anyfesolver/general_contact/pressure_space.py +51 -0
- anyfesolver/general_contact/q4_material_checkpoint.py +276 -0
- anyfesolver/general_contact/q4_overlap_local_bound.py +1111 -0
- anyfesolver/general_contact/q4_q4_overlap_derivatives.py +234 -0
- anyfesolver/general_contact/q4_q4_swept_bound.py +58 -0
- anyfesolver/general_contact/q4_s3_overlap_derivatives.py +1159 -0
- anyfesolver/general_contact/q4_s3_tiny_wedge.py +180 -0
- anyfesolver/general_contact/q4_skin_work.py +87 -0
- anyfesolver/general_contact/q4_source_mortar_energy.py +181 -0
- anyfesolver/general_contact/q4_source_overlap.py +401 -0
- anyfesolver/general_contact/q4_target_overlap.py +181 -0
- anyfesolver/general_contact/request_identity.py +192 -0
- anyfesolver/general_contact/rigid_translation.py +199 -0
- anyfesolver/general_contact/runtime.py +27 -0
- anyfesolver/general_contact/s3_friction_chart_interval.py +104 -0
- anyfesolver/general_contact/s3_friction_coverage.py +165 -0
- anyfesolver/general_contact/s3_friction_dyadic_interval.py +39 -0
- anyfesolver/general_contact/s3_friction_history_path.py +111 -0
- anyfesolver/general_contact/s3_friction_mesh_coverage.py +145 -0
- anyfesolver/general_contact/s3_friction_mesh_gap.py +74 -0
- anyfesolver/general_contact/s3_friction_mesh_monolithic.py +150 -0
- anyfesolver/general_contact/s3_friction_monolithic.py +121 -0
- anyfesolver/general_contact/s3_friction_moving_coverage.py +114 -0
- anyfesolver/general_contact/s3_friction_pair.py +237 -0
- anyfesolver/general_contact/s3_friction_point.py +129 -0
- anyfesolver/general_contact/s3_friction_prepared_path.py +254 -0
- anyfesolver/general_contact/s3_friction_replay.py +1094 -0
- anyfesolver/general_contact/s3_friction_rim_path.py +69 -0
- anyfesolver/general_contact/s3_friction_rim_transaction.py +23 -0
- anyfesolver/general_contact/s3_friction_static.py +267 -0
- anyfesolver/general_contact/s3_friction_swept_gap.py +112 -0
- anyfesolver/general_contact/s3_friction_touch.py +94 -0
- anyfesolver/general_contact/s3_friction_weak_pressure.py +66 -0
- anyfesolver/general_contact/s3_overlap_derivatives.py +220 -0
- anyfesolver/general_contact/s3_overlap_hermite_gap.py +180 -0
- anyfesolver/general_contact/s3_pair_static.py +343 -0
- anyfesolver/general_contact/s3_patch_augmented.py +2056 -0
- anyfesolver/general_contact/s3_patch_mortar_energy.py +545 -0
- anyfesolver/general_contact/s3_patch_static.py +1725 -0
- anyfesolver/general_contact/s3_pressure_hermite_bounds.py +395 -0
- anyfesolver/general_contact/s3_pressure_polygon_bounds.py +73 -0
- anyfesolver/general_contact/s3_q4_gap_resolution.py +191 -0
- anyfesolver/general_contact/s3_q4_overlap_derivatives.py +252 -0
- anyfesolver/general_contact/s3_q4_swept.py +56 -0
- anyfesolver/general_contact/s3_skin_work.py +101 -0
- anyfesolver/general_contact/s3_sliding_friction.py +640 -0
- anyfesolver/general_contact/s3_stable_plane_gap.py +43 -0
- anyfesolver/general_contact/seed_path_vertex_interval.py +323 -0
- anyfesolver/general_contact/semismooth.py +133 -0
- anyfesolver/general_contact/semismooth_block.py +147 -0
- anyfesolver/general_contact/semismooth_path.py +871 -0
- anyfesolver/general_contact/shell_pair_hermite_sweep.py +336 -0
- anyfesolver/general_contact/shell_sphere_temporal_bounds.py +305 -0
- anyfesolver/general_contact/skin_geometry.py +106 -0
- anyfesolver/general_contact/sliding.py +349 -0
- anyfesolver/general_contact/sphere.py +691 -0
- anyfesolver/general_contact/sphere_temporal_bounds.py +241 -0
- anyfesolver/general_contact/static_path.py +2608 -0
- anyfesolver/general_contact/static_results_archive.py +140 -0
- anyfesolver/general_contact/structural_state_bridge.py +196 -0
- anyfesolver/general_contact/supported_schur.py +594 -0
- anyfesolver/general_contact/swept_patch_broadphase.py +160 -0
- anyfesolver/general_contact/transient.py +2921 -0
- anyfesolver/general_contact/transient_results_archive.py +491 -0
- anyfesolver/general_contact/triangle_mortar_energy.py +280 -0
- anyfesolver/general_contact/triangle_pair_energy.py +211 -0
- anyfesolver/general_contact/triangulated_rigid.py +94 -0
- anyfesolver/general_contact/triangulated_rigid_static.py +399 -0
- anyfesolver/general_contact/warped_q4_plane.py +185 -0
- anyfesolver/general_contact/warped_q4_skin.py +302 -0
- anyfesolver/general_contact/warped_q4_static.py +331 -0
- anyfesolver/impact_performance.py +273 -0
- anyfesolver/impact_reduced_assembly.py +294 -0
- anyfesolver/imperfections.py +397 -0
- anyfesolver/inertia_relief.py +676 -0
- anyfesolver/initial_field_state.py +54 -0
- anyfesolver/jit_compiler.py +135 -0
- anyfesolver/kernel_warmup.py +228 -0
- anyfesolver/linalg.py +1002 -0
- anyfesolver/marine_contributions.py +634 -0
- anyfesolver/marine_coupled_equilibrium.py +292 -0
- anyfesolver/marine_equilibrium.py +405 -0
- anyfesolver/marine_hydro_coordinates.py +346 -0
- anyfesolver/marine_linear.py +549 -0
- anyfesolver/marine_link.py +306 -0
- anyfesolver/marine_mechanical.py +923 -0
- anyfesolver/marine_mooring.py +533 -0
- anyfesolver/marine_morison.py +331 -0
- anyfesolver/marine_potential_flow.py +403 -0
- anyfesolver/marine_rigid.py +397 -0
- anyfesolver/marine_seabed.py +122 -0
- anyfesolver/mass_properties.py +352 -0
- anyfesolver/material_curves.py +52 -0
- anyfesolver/materials.py +43 -0
- anyfesolver/matrix_assembly.py +7330 -0
- anyfesolver/mesh_gen.py +904 -0
- anyfesolver/mesh_load_bc_verification.py +612 -0
- anyfesolver/modal.py +2494 -0
- anyfesolver/nonlinear.py +341 -0
- anyfesolver/nonlinear_analysis_diagnostics.py +466 -0
- anyfesolver/nonlinear_element_evaluation.py +126 -0
- anyfesolver/nonlinear_performance.py +2094 -0
- anyfesolver/nonlinear_performance_batch_b.py +1173 -0
- anyfesolver/nonlinear_performance_batch_c.py +850 -0
- anyfesolver/nonlinear_performance_bootstrap.py +164 -0
- anyfesolver/nonlinear_reduced_assembly.py +806 -0
- anyfesolver/nonlinear_restart.py +1708 -0
- anyfesolver/nonlinear_state.py +2800 -0
- anyfesolver/nonlinear_static.py +9407 -0
- anyfesolver/outcomes.py +694 -0
- anyfesolver/plasticity.py +2087 -0
- anyfesolver/plasticity_qualification.py +758 -0
- anyfesolver/prepared_static.py +1129 -0
- anyfesolver/prepared_static_envelopes.py +389 -0
- anyfesolver/prepared_static_loads.py +1180 -0
- anyfesolver/prepared_static_measures.py +18 -0
- anyfesolver/prepared_static_numeric.py +247 -0
- anyfesolver/prepared_static_q4.py +297 -0
- anyfesolver/prepared_static_recovery.py +2068 -0
- anyfesolver/prepared_static_response.py +1013 -0
- anyfesolver/prepared_static_shared.py +226 -0
- anyfesolver/prepared_static_snapshot.py +323 -0
- anyfesolver/prepared_static_vectorized.py +866 -0
- anyfesolver/pressure_regions.py +287 -0
- anyfesolver/production_readiness.py +343 -0
- anyfesolver/quality_control.py +1487 -0
- anyfesolver/quantities.py +1289 -0
- anyfesolver/recovery.py +4217 -0
- anyfesolver/recovery_batches.py +1985 -0
- anyfesolver/recovery_policy.py +205 -0
- anyfesolver/recovery_s4.py +289 -0
- anyfesolver/recovery_shell_frames.py +102 -0
- anyfesolver/reference_cases.py +366 -0
- anyfesolver/result_capsule.py +2175 -0
- anyfesolver/results.py +752 -0
- anyfesolver/runtime.py +9819 -0
- anyfesolver/s3_reference_batch.py +1338 -0
- anyfesolver/s3_v2c_fast_assembly.py +544 -0
- anyfesolver/s3_v2d_fast_assembly.py +447 -0
- anyfesolver/s4_validity.py +354 -0
- anyfesolver/sections.py +67 -0
- anyfesolver/sesam_fem/__init__.py +55 -0
- anyfesolver/sesam_fem/__main__.py +80 -0
- anyfesolver/sesam_fem/diagnostics.py +5 -0
- anyfesolver/sesam_fem/document.py +39 -0
- anyfesolver/sesam_fem/exporter.py +9 -0
- anyfesolver/sesam_fem/importer.py +228 -0
- anyfesolver/sesam_fem/records.py +21 -0
- anyfesolver/sesam_fem/schema.py +35 -0
- anyfesolver/sesam_fem/sif_importer.py +9 -0
- anyfesolver/sesam_fem/validation.py +5 -0
- anyfesolver/sestra_deep_diagnostics.py +173 -0
- anyfesolver/sestra_mixed_verification.py +146 -0
- anyfesolver/sestra_structure_benchmarks.py +270 -0
- anyfesolver/sestra_three_case.py +215 -0
- anyfesolver/sestra_verification.py +523 -0
- anyfesolver/shell_benchmarks.py +367 -0
- anyfesolver/shell_formulations/director_field.py +799 -0
- anyfesolver/shell_formulations/geometry_provenance.py +921 -0
- anyfesolver/shell_formulations/mitc4_plus_d_quality.py +318 -0
- anyfesolver/shell_formulations/mitc4_plus_d_reference.py +837 -0
- anyfesolver/shell_formulations/mitc4_plus_d_scalar.py +209 -0
- anyfesolver/shell_formulations/protocol.py +222 -0
- anyfesolver/shell_formulations/q4_common.py +271 -0
- anyfesolver/shell_formulations/s4_restricted_policy.py +78 -0
- anyfesolver/shell_sections.py +394 -0
- anyfesolver/solid_submodel.py +930 -0
- anyfesolver/static_batch.py +662 -0
- anyfesolver/static_envelopes.py +250 -0
- anyfesolver/stiffened_shell.py +388 -0
- anyfesolver/submodel_transfer.py +1306 -0
- anyfesolver/test_cases.py +610 -0
- anyfesolver/threading_policy.py +431 -0
- anyfesolver/validation.py +719 -0
- anyfesolver/vectorized_beam.py +139 -0
- anyfesolver/vectorized_generalized_shell.py +448 -0
- anyfesolver/vectorized_hill48.py +956 -0
- anyfesolver/vectorized_nonlinear.py +360 -0
- anyfesolver/vectorized_stiffness.py +694 -0
- anyfesolver-0.5.0a1.dist-info/METADATA +374 -0
- anyfesolver-0.5.0a1.dist-info/RECORD +751 -0
- anyfesolver-0.5.0a1.dist-info/WHEEL +5 -0
- anyfesolver-0.5.0a1.dist-info/licenses/COPYRIGHT +1 -0
- anyfesolver-0.5.0a1.dist-info/licenses/LICENSE +373 -0
- anyfesolver-0.5.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +32 -0
- 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()
|