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,2447 @@
|
|
|
1
|
+
"""Small elastic multi-Q4/multi-B2 panel with existing F10 point joints.
|
|
2
|
+
|
|
3
|
+
This is an experimental reference-linear owner. Shell and beam node identities
|
|
4
|
+
are explicit and separate. Reference modes require explicit physical mass.
|
|
5
|
+
Explicit nodal footprints have a distinct reference-linear law; virgin supported
|
|
6
|
+
modes retain its normal-spin locking and incomplete mechanical qualification.
|
|
7
|
+
No line, geometric stiffness, nonlinear state or qualification is inferred.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from functools import wraps
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from hashlib import sha256
|
|
15
|
+
from math import fsum, isfinite
|
|
16
|
+
from numbers import Real
|
|
17
|
+
from typing import Callable, Mapping, Sequence
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from scipy import sparse
|
|
21
|
+
from scipy.sparse.linalg import LinearOperator, eigs, ArpackError
|
|
22
|
+
from scipy.sparse.csgraph import maximum_bipartite_matching
|
|
23
|
+
|
|
24
|
+
from ._b2_f10_f11_point_attachment import LAW_ID, compile_reference_point
|
|
25
|
+
from ._stiffened_shell_b2_nodal_footprint import (
|
|
26
|
+
LAW_ID as FOOTPRINT_LAW_ID, B2NodalRigidFootprint,
|
|
27
|
+
B2NodalRigidFootprintSelection, B2NodalRigidFootprintWrench,
|
|
28
|
+
compile_footprints, checked_selection, selected_wrenches,
|
|
29
|
+
)
|
|
30
|
+
from ._stiffened_shell_b2_physical_rigid_footprint import (
|
|
31
|
+
LAW_ID as PHYSICAL_FOOTPRINT_LAW_ID, B2PhysicalRigidFootprint,
|
|
32
|
+
B2PhysicalRigidFootprintSelection, B2PhysicalRigidFootprintWrench,
|
|
33
|
+
compile_physical_footprints, checked_selection as checked_physical_selection,
|
|
34
|
+
selected_wrenches as physical_selected_wrenches,
|
|
35
|
+
)
|
|
36
|
+
from ._b2_physical_linear_definition import (
|
|
37
|
+
PROFILE_ID, CompiledPhysicalB2Member, PhysicalB2Member,
|
|
38
|
+
PhysicalB2OperatorCache,
|
|
39
|
+
)
|
|
40
|
+
from ._b2_physical_linear_batch_dev import (
|
|
41
|
+
_load_value_snapshot, _checked_affine_projection as _project_affine_work,
|
|
42
|
+
_accurate_sparse_residual, B2NumericalSolveFailure,
|
|
43
|
+
)
|
|
44
|
+
from .control import CancellationToken, SolveCancelled
|
|
45
|
+
from ._stiffened_shell_physical_q4_mass import physical_mass, POLICY as Q4_MASS_POLICY
|
|
46
|
+
from .e4_pl_s3_v2d_element import (
|
|
47
|
+
MASS_POLICY_ID as S3_V2D_TRANSLATIONAL_LUMPED_SECTION_AREAL_MASS_V1,
|
|
48
|
+
FORMULATION_ID as S3_V2D_FORMULATION_ID,
|
|
49
|
+
)
|
|
50
|
+
from ._b2_physical_flexibility_linear import RepresentabilityError
|
|
51
|
+
from ._b2_loaded_point_trace_dev import LoadedPointMember, checked_sparse_action
|
|
52
|
+
from ._stiffened_shell_b2_affine_resolution import certify_affine
|
|
53
|
+
from ._b2_attachment_elimination_dev import prepare_attachment_elimination
|
|
54
|
+
from .e4_pl_element import _retire_q4_private_mesh_caches
|
|
55
|
+
from ._stiffened_shell_b2_assembly_diagnostics import active_capture, DiagnosticRefusal
|
|
56
|
+
from .elements import create_shell_element
|
|
57
|
+
from .fe_core import FEModel
|
|
58
|
+
from .linalg import MatrixClass, factorize_cached
|
|
59
|
+
|
|
60
|
+
B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV = "B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV"
|
|
61
|
+
|
|
62
|
+
class ExperimentalB2Q4PanelError(RuntimeError):
|
|
63
|
+
"""The experimental panel cannot be prepared or used."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _NumericalFailure(RuntimeError):
|
|
67
|
+
"""A checked sparse panel solution failed numerically."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_MAX_DENSE_AUDIT_ROWS = 48
|
|
71
|
+
_MAX_DENSE_AUDIT_VALUES = 16_384
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _bounded_accurate_residual(matrix, solution, rhs):
|
|
75
|
+
"""Stored-equation residual and outward bound using exact products.
|
|
76
|
+
|
|
77
|
+
Only selected explicitly loaded rows use this exceptional path. Fraction
|
|
78
|
+
products/summation avoid FMA/fsum portability and underflow assumptions.
|
|
79
|
+
Conversion to binary64 is correctly rounded; a full ULP and outward final
|
|
80
|
+
rounding conservatively enclose the absolute rational residual. This does
|
|
81
|
+
not certify element/assembly rounding or forward displacement accuracy.
|
|
82
|
+
"""
|
|
83
|
+
if (not np.isfinite(matrix.data).all() or not np.isfinite(solution).all() or
|
|
84
|
+
not np.isfinite(rhs).all()):
|
|
85
|
+
raise B2NumericalSolveFailure("physical B2 residual is not representable")
|
|
86
|
+
defect = _accurate_sparse_residual(matrix, solution, rhs, exact_products=True)
|
|
87
|
+
magnitude = np.abs(defect)
|
|
88
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
89
|
+
rounding = np.nextafter(magnitude, np.inf)-magnitude
|
|
90
|
+
upper = np.nextafter(magnitude+rounding, np.inf)
|
|
91
|
+
return defect, upper
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _checked_affine_projection(rows, multi, singles, values: np.ndarray) -> np.ndarray:
|
|
95
|
+
"""Keep the panel's failure identity while sharing checked affine work."""
|
|
96
|
+
return _project_affine_work(rows, multi, singles, values,
|
|
97
|
+
failure_type=_NumericalFailure, label="panel")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _scaled_point_kkt(reduced_k: sparse.spmatrix,
|
|
101
|
+
reduced_c: sparse.spmatrix,
|
|
102
|
+
multiplier_compliance: sparse.spmatrix | None = None) -> tuple[sparse.csc_matrix, np.ndarray]:
|
|
103
|
+
"""Representable congruence for the elastic point-constrained KKT.
|
|
104
|
+
|
|
105
|
+
A zero-energy primal coordinate is valid when a constraint anchors it;
|
|
106
|
+
its scale then follows that constraint column. This owner admits no
|
|
107
|
+
negative-energy primal diagonal or unanchored zero-energy coordinate.
|
|
108
|
+
"""
|
|
109
|
+
diagonal = reduced_k.diagonal()
|
|
110
|
+
if not np.isfinite(diagonal).all() or np.any(diagonal < 0.):
|
|
111
|
+
raise ExperimentalB2Q4PanelError(
|
|
112
|
+
"panel factorization requires nonnegative finite primal diagonal")
|
|
113
|
+
primal_scale = np.empty(len(diagonal))
|
|
114
|
+
positive = diagonal > 0.
|
|
115
|
+
with np.errstate(over="ignore", under="ignore", divide="ignore",
|
|
116
|
+
invalid="ignore"):
|
|
117
|
+
primal_scale[positive] = 1./np.sqrt(diagonal[positive])
|
|
118
|
+
if np.any(~positive):
|
|
119
|
+
# For a positive-semidefinite elastic K, a zero diagonal has a
|
|
120
|
+
# zero row. An off-diagonal term instead signals invalid data.
|
|
121
|
+
zero_rows = reduced_k.tocsr()[np.flatnonzero(~positive)]
|
|
122
|
+
if np.any(zero_rows.data != 0.):
|
|
123
|
+
raise ExperimentalB2Q4PanelError(
|
|
124
|
+
"zero-energy primal row has nonzero stiffness coupling")
|
|
125
|
+
column_max = np.asarray(
|
|
126
|
+
abs(reduced_c).max(axis=0).toarray()).reshape(-1)
|
|
127
|
+
if (not np.isfinite(column_max[~positive]).all() or
|
|
128
|
+
np.any(column_max[~positive] <= 0.)):
|
|
129
|
+
raise ExperimentalB2Q4PanelError(
|
|
130
|
+
"unanchored zero-energy primal coordinate")
|
|
131
|
+
primal_scale[~positive] = 1./column_max[~positive]
|
|
132
|
+
if (not np.isfinite(primal_scale).all() or
|
|
133
|
+
np.any(primal_scale <= 0.)):
|
|
134
|
+
raise ExperimentalB2Q4PanelError(
|
|
135
|
+
"panel factorization scaling is not representable")
|
|
136
|
+
scaled_rows = reduced_c @ sparse.diags(primal_scale)
|
|
137
|
+
row_max = np.asarray(abs(scaled_rows).max(axis=1).toarray()).reshape(-1)
|
|
138
|
+
if multiplier_compliance is not None:
|
|
139
|
+
diagonal_h = multiplier_compliance.diagonal()
|
|
140
|
+
if (multiplier_compliance.shape != (reduced_c.shape[0],)*2 or
|
|
141
|
+
not np.isfinite(multiplier_compliance.data).all() or
|
|
142
|
+
np.any(diagonal_h < 0.)):
|
|
143
|
+
raise ExperimentalB2Q4PanelError("finite nonnegative point compliance required")
|
|
144
|
+
row_max = np.maximum(row_max, np.sqrt(diagonal_h))
|
|
145
|
+
multiplier_scale = 1./row_max
|
|
146
|
+
factor_scale = np.r_[primal_scale, multiplier_scale]
|
|
147
|
+
kkt = sparse.bmat(
|
|
148
|
+
((reduced_k, reduced_c.T), (reduced_c, None if multiplier_compliance is None
|
|
149
|
+
else -multiplier_compliance)), format="csc")
|
|
150
|
+
scaling = sparse.diags(factor_scale, format="csc")
|
|
151
|
+
scaled_kkt = (scaling @ kkt @ scaling).tocsc()
|
|
152
|
+
kkt.sort_indices()
|
|
153
|
+
scaled_kkt.sort_indices()
|
|
154
|
+
if (not np.isfinite(factor_scale).all() or
|
|
155
|
+
np.any(factor_scale <= 0.) or
|
|
156
|
+
not np.isfinite(scaled_kkt.data).all() or
|
|
157
|
+
kkt.nnz != scaled_kkt.nnz or
|
|
158
|
+
not np.array_equal(kkt.indptr, scaled_kkt.indptr) or
|
|
159
|
+
not np.array_equal(kkt.indices, scaled_kkt.indices) or
|
|
160
|
+
np.any((kkt.data != 0.) & (scaled_kkt.data == 0.))):
|
|
161
|
+
raise ExperimentalB2Q4PanelError(
|
|
162
|
+
"panel factorization scaling is not representable")
|
|
163
|
+
return scaled_kkt, factor_scale
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _require_independent_supported_point_rows(reduced_c: sparse.spmatrix) -> None:
|
|
167
|
+
"""Bound the dense audit; defer large numerical rank to KKT factorization.
|
|
168
|
+
|
|
169
|
+
A matching proves only structural rank. The subsequent sparse KKT
|
|
170
|
+
factorization must still reject numerical row dependence for larger models.
|
|
171
|
+
"""
|
|
172
|
+
rows, columns = reduced_c.shape
|
|
173
|
+
if rows > columns or not np.isfinite(reduced_c.data).all():
|
|
174
|
+
raise ValueError("independent supported point rows required")
|
|
175
|
+
if rows <= _MAX_DENSE_AUDIT_ROWS and rows*columns <= _MAX_DENSE_AUDIT_VALUES:
|
|
176
|
+
if np.linalg.matrix_rank(reduced_c.toarray()) != rows:
|
|
177
|
+
raise ValueError("independent supported point rows required")
|
|
178
|
+
return
|
|
179
|
+
pattern = reduced_c.tocsr(copy=True)
|
|
180
|
+
pattern.eliminate_zeros()
|
|
181
|
+
matching = maximum_bipartite_matching(pattern, perm_type="column")
|
|
182
|
+
if np.any(matching < 0):
|
|
183
|
+
raise ValueError("structurally independent supported point rows required")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True)
|
|
187
|
+
class Q4PanelShell:
|
|
188
|
+
shell_id: str
|
|
189
|
+
node_ids: tuple[int, int, int, int]
|
|
190
|
+
E: float
|
|
191
|
+
nu: float
|
|
192
|
+
thickness_m: float
|
|
193
|
+
reference_normal: tuple[float, float, float] = (0., 0., 1.)
|
|
194
|
+
density_kg_m3: float | None = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True)
|
|
198
|
+
class Q4B2Point:
|
|
199
|
+
point_id: str
|
|
200
|
+
shell_id: str
|
|
201
|
+
member_id: str
|
|
202
|
+
station: float
|
|
203
|
+
offset_local_m: tuple[float, float, float] = (0., 0., 0.)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass(frozen=True)
|
|
207
|
+
class S3V2DPanelShell:
|
|
208
|
+
"""Explicit native S3 V2D elastic facet; reference mass requires modal opt-in."""
|
|
209
|
+
|
|
210
|
+
shell_id: str
|
|
211
|
+
node_ids: tuple[int, int, int]
|
|
212
|
+
E: float
|
|
213
|
+
nu: float
|
|
214
|
+
thickness_m: float
|
|
215
|
+
reference_normal: tuple[float, float, float]
|
|
216
|
+
density_kg_m3: float | None = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass(frozen=True)
|
|
220
|
+
class ExperimentalB2Q4PanelCapabilities:
|
|
221
|
+
profile_id: str = PROFILE_ID
|
|
222
|
+
point_law_id: str = LAW_ID
|
|
223
|
+
status: str = "experimental"
|
|
224
|
+
production_qualified: bool = False
|
|
225
|
+
shell_domain: str = "multiple flat elastic Q4"
|
|
226
|
+
beam_domain: str = "multiple physical B2 members"
|
|
227
|
+
attachment: str = "F10 reference points only"
|
|
228
|
+
operations: tuple[str, ...] = ("solve_linear", "solve_linear_many", "close")
|
|
229
|
+
spectral_domain: str = "unavailable without explicit physical mass"
|
|
230
|
+
body_load_domain: str = "unavailable without complete physical density/mass"
|
|
231
|
+
point_trace_policy: str | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@dataclass(frozen=True)
|
|
235
|
+
class ExperimentalB2LoadedPointCapabilities(ExperimentalB2Q4PanelCapabilities):
|
|
236
|
+
point_trace_policy: str = B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV
|
|
237
|
+
spectral_domain: str = "unsupported: loaded point trace inertia unverified"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True, eq=False)
|
|
241
|
+
class B2ReferenceBodyLoad:
|
|
242
|
+
"""Owned reference-linear body load; six global coordinates per node.
|
|
243
|
+
|
|
244
|
+
Shell nodes precede beam nodes, each in the returned ID order. Acceleration
|
|
245
|
+
is global in m/s2. This is a reference load, not finite-configuration gravity.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
acceleration_m_s2: tuple[float, float, float]
|
|
249
|
+
shell_node_ids: tuple[int, ...]
|
|
250
|
+
beam_node_ids: tuple[int, ...]
|
|
251
|
+
load: np.ndarray
|
|
252
|
+
model_identity: str = ""
|
|
253
|
+
policy: str = field(default="B2_E4_PL_REFERENCE_BODY_ACCELERATION_V1", init=False)
|
|
254
|
+
production_qualified: bool = field(default=False, init=False)
|
|
255
|
+
member_recovery: str = field(default="pass typed load for particular-load equilibrated fields; .load alone retains nodal-state constitutive recovery", init=False)
|
|
256
|
+
|
|
257
|
+
def __post_init__(self):
|
|
258
|
+
# Bytes-backed storage cannot be made writable, and never aliases the
|
|
259
|
+
# owner's compiled map or the caller's acceleration.
|
|
260
|
+
values = np.asarray(self.load, dtype=np.float64)
|
|
261
|
+
object.__setattr__(self, "load", np.frombuffer(values.tobytes(), dtype=np.float64))
|
|
262
|
+
object.__setattr__(self, "acceleration_m_s2", tuple(self.acceleration_m_s2))
|
|
263
|
+
object.__setattr__(self, "shell_node_ids", tuple(self.shell_node_ids))
|
|
264
|
+
object.__setattr__(self, "beam_node_ids", tuple(self.beam_node_ids))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass(frozen=True)
|
|
268
|
+
class B2LoadedMemberSections:
|
|
269
|
+
"""Static admissible section fields; not a new loaded kinematic state."""
|
|
270
|
+
|
|
271
|
+
member_id: str
|
|
272
|
+
stations: tuple[float, ...]
|
|
273
|
+
resultants_left: tuple[tuple[float, ...], ...]
|
|
274
|
+
resultants_right: tuple[tuple[float, ...], ...]
|
|
275
|
+
strains_left: tuple[tuple[float, ...], ...]
|
|
276
|
+
strains_right: tuple[tuple[float, ...], ...]
|
|
277
|
+
endpoint_tractions_local: tuple[float, ...]
|
|
278
|
+
policy: str = field(default="B2_REFERENCE_EQUILIBRATED_MEMBER_FIELDS_V1_DEV", init=False)
|
|
279
|
+
production_qualified: bool = field(default=False, init=False)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _beam_body_map(member: CompiledPhysicalB2Member, frame: np.ndarray) -> np.ndarray:
|
|
283
|
+
"""Integrate physical section action before any rotary-mass cancellation."""
|
|
284
|
+
section = member.definition.mass.matrix_at_reference_axis()[:, :3]
|
|
285
|
+
abscissae, weights = np.polynomial.legendre.leggauss(4)
|
|
286
|
+
stations = tuple(float((x+1.)/2.) for x in abscissae)
|
|
287
|
+
trace = member.operator.trace(np.zeros(12), stations=stations)
|
|
288
|
+
local = np.zeros((12, 3))
|
|
289
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
290
|
+
for weight, jacobian in zip(weights, trace.jacobian):
|
|
291
|
+
local += member.definition.length_m*(weight/2.)*(jacobian.T@section)
|
|
292
|
+
result = frame.T@local@frame[:3, :3]
|
|
293
|
+
if not np.isfinite(result).all():
|
|
294
|
+
raise RepresentabilityError("physical B2 body-load map is not representable")
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclass(frozen=True)
|
|
299
|
+
class ExperimentalB2Q4FootprintCapabilities(ExperimentalB2Q4PanelCapabilities):
|
|
300
|
+
footprint_law_id: str = FOOTPRINT_LAW_ID
|
|
301
|
+
footprints: tuple[B2NodalRigidFootprint, ...] = ()
|
|
302
|
+
|
|
303
|
+
@dataclass(frozen=True)
|
|
304
|
+
class B2PhysicalRigidFootprintCapabilities(ExperimentalB2Q4PanelCapabilities):
|
|
305
|
+
footprint_law_id: str = PHYSICAL_FOOTPRINT_LAW_ID
|
|
306
|
+
footprints: tuple[B2PhysicalRigidFootprint, ...] = ()
|
|
307
|
+
normal_spin: str = "independent; normal torque through noncollinear translation arms"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass(frozen=True)
|
|
311
|
+
class ExperimentalB2MixedPanelCapabilities(ExperimentalB2Q4PanelCapabilities):
|
|
312
|
+
shell_domain: str = "explicit flat elastic Q4/native S3 V2D panel"
|
|
313
|
+
point_law_id: tuple[str, ...] = ()
|
|
314
|
+
shell_formulations: tuple[tuple[str, str], ...] = ()
|
|
315
|
+
point_laws: tuple[tuple[str, str], ...] = ()
|
|
316
|
+
footprint_law_id: str | None = None
|
|
317
|
+
footprints: tuple[B2NodalRigidFootprint, ...] = ()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@dataclass(frozen=True)
|
|
321
|
+
class ExperimentalB2MixedReferenceModalCapabilities(ExperimentalB2MixedPanelCapabilities):
|
|
322
|
+
inertia_policy: str = S3_V2D_TRANSLATIONAL_LUMPED_SECTION_AREAL_MASS_V1
|
|
323
|
+
shell_mass_policies: tuple[tuple[str, str], ...] = ()
|
|
324
|
+
s3_rotary_inertia: str = "omitted by S3 facet contributions; shared nodes may retain Q4 director inertia"
|
|
325
|
+
beam_mass_policy: str = "declared physical B2 sectional inertia through the station trace"
|
|
326
|
+
boundary_policy: str | None = None
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
B2_FREE_FREE_MODAL_ONLY_V1 = "B2_FREE_FREE_MODAL_ONLY_V1"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass(frozen=True)
|
|
333
|
+
class B2ReferenceModesRequest:
|
|
334
|
+
"""Elastic finite reference pairs; boundary policy belongs to the definition."""
|
|
335
|
+
|
|
336
|
+
count: int = 3
|
|
337
|
+
shift_hz: float = 1.
|
|
338
|
+
max_iterations: int = 600
|
|
339
|
+
|
|
340
|
+
def __post_init__(self):
|
|
341
|
+
if (type(self.count) is not int or self.count <= 0 or
|
|
342
|
+
type(self.max_iterations) is not int or self.max_iterations <= 0 or
|
|
343
|
+
isinstance(self.shift_hz, (bool, np.bool_)) or
|
|
344
|
+
not isinstance(self.shift_hz, (int, float, np.floating)) or
|
|
345
|
+
not np.isfinite(self.shift_hz) or self.shift_hz <= 0.):
|
|
346
|
+
raise ValueError("positive finite reference-mode controls required")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass(frozen=True)
|
|
350
|
+
class B2ReferenceModesResult:
|
|
351
|
+
status: str
|
|
352
|
+
frequencies_hz: tuple[float, ...] = ()
|
|
353
|
+
physical_modes: tuple[tuple[float, ...], ...] = ()
|
|
354
|
+
point_multiplier_modes: tuple[tuple[float, ...], ...] = ()
|
|
355
|
+
equation_residuals: tuple[float, ...] = ()
|
|
356
|
+
constraint_residuals: tuple[float, ...] = ()
|
|
357
|
+
factorization_count: int = 0
|
|
358
|
+
solve_count: int = 0
|
|
359
|
+
failure_reason: str | None = None
|
|
360
|
+
production_qualified: bool = False
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@dataclass(frozen=True)
|
|
364
|
+
class B2FreeReferenceModesResult:
|
|
365
|
+
"""Six verified physical rigid fields and selected finite flexible pairs.
|
|
366
|
+
|
|
367
|
+
This does not certify complete nullity or an eigenvalue error bound.
|
|
368
|
+
All returned numerical collections own immutable scalar tuples.
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
status: str
|
|
372
|
+
flexible: B2ReferenceModesResult
|
|
373
|
+
rigid_origin_m: tuple[float, ...] = ()
|
|
374
|
+
canonical_rigid_modes: tuple[tuple[float, ...], ...] = ()
|
|
375
|
+
rigid_modes: tuple[tuple[float, ...], ...] = ()
|
|
376
|
+
rigid_multiplier_modes: tuple[tuple[float, ...], ...] = ()
|
|
377
|
+
rigid_kinetic_gram: tuple[tuple[float, ...], ...] = ()
|
|
378
|
+
rigid_equation_residuals: tuple[float, ...] = ()
|
|
379
|
+
rigid_constraint_residuals: tuple[float, ...] = ()
|
|
380
|
+
rigid_flexible_mass_overlap: float = 0.
|
|
381
|
+
spectral_shift_rad2_s2: float = 0.
|
|
382
|
+
failure_reason: str | None = None
|
|
383
|
+
policy: str = B2_FREE_FREE_MODAL_ONLY_V1
|
|
384
|
+
production_qualified: bool = False
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@dataclass(frozen=True)
|
|
388
|
+
class B2ModalConstraintRow:
|
|
389
|
+
"""Immutable provenance for one actual physical constraint/multiplier row.
|
|
390
|
+
|
|
391
|
+
Rows follow C. Footprint gaps are global; point translation gaps use the
|
|
392
|
+
master shell reference frame and point rotational gaps use the beam
|
|
393
|
+
reference frame. Multipliers are work-conjugate covectors, not display-ready
|
|
394
|
+
global wrenches. Frame columns give the actual basis in global coordinates.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
row_index: int
|
|
398
|
+
component: str
|
|
399
|
+
point_id: str | None = None
|
|
400
|
+
footprint_id: str | None = None
|
|
401
|
+
shell_node_id: int | None = None
|
|
402
|
+
beam_node_id: int | None = None
|
|
403
|
+
coordinate_frame: str = "global"
|
|
404
|
+
reference_frame_columns: tuple[tuple[float, ...], ...] = (
|
|
405
|
+
(1., 0., 0.), (0., 1., 0.), (0., 0., 1.))
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@dataclass(frozen=True)
|
|
409
|
+
class B2FootprintReferenceModesResult(B2ReferenceModesResult):
|
|
410
|
+
"""Virgin descriptor modes of the reference-linear, normal-spin locked law.
|
|
411
|
+
|
|
412
|
+
All multipliers follow constraint_rows. Inherited point_multiplier_modes
|
|
413
|
+
contain only the true point prefix, never footprint multipliers.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
footprint_law_id: str = FOOTPRINT_LAW_ID
|
|
417
|
+
footprint_ids: tuple[str, ...] = ()
|
|
418
|
+
constraint_multiplier_modes: tuple[tuple[float, ...], ...] = ()
|
|
419
|
+
constraint_rows: tuple[B2ModalConstraintRow, ...] = ()
|
|
420
|
+
normal_spin: str = "locked"
|
|
421
|
+
mechanical_qualification: str = "incomplete"
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@dataclass(frozen=True)
|
|
425
|
+
class ExperimentalB2Q4PanelResult:
|
|
426
|
+
status: str
|
|
427
|
+
displacements: tuple[float, ...] = ()
|
|
428
|
+
point_multipliers: tuple[tuple[str, tuple[float, ...]], ...] = ()
|
|
429
|
+
affine_reactions: tuple[float, ...] = ()
|
|
430
|
+
point_gaps: tuple[tuple[str, tuple[float, ...]], ...] = ()
|
|
431
|
+
beam_resultants_local: tuple[tuple[str, tuple[tuple[float, ...], ...]], ...] = ()
|
|
432
|
+
internal_strain_energy: float | None = None
|
|
433
|
+
factorization_count: int | None = None
|
|
434
|
+
solve_count: int | None = None
|
|
435
|
+
failure_reason: str | None = None
|
|
436
|
+
production_qualified: bool = False
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@dataclass(frozen=True)
|
|
440
|
+
class ExperimentalB2Q4PanelSelectedCase:
|
|
441
|
+
case_id: str
|
|
442
|
+
selected_displacements: tuple[float, ...]
|
|
443
|
+
selected_affine_reactions: tuple[float, ...]
|
|
444
|
+
point_multipliers: tuple[tuple[str, tuple[float, ...]], ...]
|
|
445
|
+
point_gaps: tuple[tuple[str, tuple[float, ...]], ...]
|
|
446
|
+
beam_resultants_local: tuple[tuple[str, tuple[tuple[float, ...], ...]], ...]
|
|
447
|
+
internal_strain_energy: float
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@dataclass(frozen=True)
|
|
451
|
+
class ExperimentalB2Q4PanelResponseEnvelope:
|
|
452
|
+
"""One selected physical coordinate's signed complete-case extrema.
|
|
453
|
+
|
|
454
|
+
Displacements retain m/rad and affine reactions N/Nm according to the
|
|
455
|
+
selected DOF. Equal extrema are governed by the first submitted case.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
selected_dof: int
|
|
459
|
+
minimum: float
|
|
460
|
+
minimum_case_id: str
|
|
461
|
+
maximum: float
|
|
462
|
+
maximum_case_id: str
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@dataclass(frozen=True)
|
|
466
|
+
class ExperimentalB2Q4PanelMemberResultantEnvelope:
|
|
467
|
+
"""Signed local sectional-resultant extrema at one requested station.
|
|
468
|
+
|
|
469
|
+
Components are N, Qy, Qz, Mx, My, Mz in the member's local frame.
|
|
470
|
+
Station indices distinguish repeated normalized station coordinates.
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
member_id: str
|
|
474
|
+
station_index: int
|
|
475
|
+
station_normalized: float
|
|
476
|
+
component: str
|
|
477
|
+
minimum: float
|
|
478
|
+
minimum_case_id: str
|
|
479
|
+
maximum: float
|
|
480
|
+
maximum_case_id: str
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@dataclass(frozen=True)
|
|
484
|
+
class ExperimentalB2Q4PanelBatchResult:
|
|
485
|
+
status: str
|
|
486
|
+
selected_dofs: tuple[int, ...]
|
|
487
|
+
selected_points: tuple[str, ...]
|
|
488
|
+
cases: tuple[ExperimentalB2Q4PanelSelectedCase, ...]
|
|
489
|
+
factorization_count: int
|
|
490
|
+
solve_count: int
|
|
491
|
+
maximum_rhs_chunk: int = 1
|
|
492
|
+
failure_reason: str | None = None
|
|
493
|
+
production_qualified: bool = False
|
|
494
|
+
displacement_envelopes: tuple[ExperimentalB2Q4PanelResponseEnvelope, ...] = ()
|
|
495
|
+
affine_reaction_envelopes: tuple[ExperimentalB2Q4PanelResponseEnvelope, ...] = ()
|
|
496
|
+
member_resultant_envelopes: tuple[ExperimentalB2Q4PanelMemberResultantEnvelope, ...] = ()
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@dataclass(frozen=True)
|
|
500
|
+
class ExperimentalB2Q4FootprintResult(ExperimentalB2Q4PanelResult):
|
|
501
|
+
footprint_ids: tuple[str, ...] = ()
|
|
502
|
+
footprint_wrenches: tuple[B2NodalRigidFootprintWrench, ...] = ()
|
|
503
|
+
footprint_law_id: str = FOOTPRINT_LAW_ID
|
|
504
|
+
|
|
505
|
+
@dataclass(frozen=True)
|
|
506
|
+
class B2PhysicalRigidFootprintResult(ExperimentalB2Q4PanelResult):
|
|
507
|
+
footprint_ids: tuple[str, ...] = ()
|
|
508
|
+
footprint_wrenches: tuple[B2PhysicalRigidFootprintWrench, ...] = ()
|
|
509
|
+
constraint_multipliers: tuple = ()
|
|
510
|
+
constraint_gaps: tuple = ()
|
|
511
|
+
constraint_rows: tuple = ()
|
|
512
|
+
footprint_law_id: str = PHYSICAL_FOOTPRINT_LAW_ID
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@dataclass(frozen=True)
|
|
516
|
+
class B2PhysicalRigidFootprintSelectedCase(ExperimentalB2Q4PanelSelectedCase):
|
|
517
|
+
"""Only requested physical outputs from one complete batch case."""
|
|
518
|
+
footprint_wrenches: tuple[B2PhysicalRigidFootprintWrench, ...] = ()
|
|
519
|
+
constraint_multipliers: tuple[float, ...] = ()
|
|
520
|
+
constraint_gaps: tuple[float, ...] = ()
|
|
521
|
+
footprint_law_id: str = PHYSICAL_FOOTPRINT_LAW_ID
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@dataclass(frozen=True)
|
|
525
|
+
class B2PhysicalRigidFootprintBatchResult(ExperimentalB2Q4PanelBatchResult):
|
|
526
|
+
selected_footprints: tuple[str, ...] = ()
|
|
527
|
+
constraint_rows: tuple = ()
|
|
528
|
+
footprint_law_id: str = PHYSICAL_FOOTPRINT_LAW_ID
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@dataclass(frozen=True)
|
|
532
|
+
class ExperimentalB2Q4FootprintSelectedCase(ExperimentalB2Q4PanelSelectedCase):
|
|
533
|
+
footprint_wrenches: tuple[B2NodalRigidFootprintWrench, ...] = ()
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@dataclass(frozen=True)
|
|
537
|
+
class ExperimentalB2Q4FootprintBatchResult(ExperimentalB2Q4PanelBatchResult):
|
|
538
|
+
selected_footprints: tuple[str, ...] = ()
|
|
539
|
+
footprint_law_id: str = FOOTPRINT_LAW_ID
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@dataclass(frozen=True)
|
|
543
|
+
class B2BodyLoadedPanelResult(ExperimentalB2Q4PanelResult):
|
|
544
|
+
body_model_identity: str = ""
|
|
545
|
+
body_acceleration_m_s2: tuple[float, ...] = ()
|
|
546
|
+
equilibrated_sections: tuple[B2LoadedMemberSections, ...] = ()
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
@dataclass(frozen=True)
|
|
550
|
+
class B2BodyLoadedFootprintResult(ExperimentalB2Q4FootprintResult):
|
|
551
|
+
body_model_identity: str = ""
|
|
552
|
+
body_acceleration_m_s2: tuple[float, ...] = ()
|
|
553
|
+
equilibrated_sections: tuple[B2LoadedMemberSections, ...] = ()
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
@dataclass(frozen=True)
|
|
557
|
+
class B2BodyLoadedPanelSelectedCase(ExperimentalB2Q4PanelSelectedCase):
|
|
558
|
+
body_model_identity: str = ""
|
|
559
|
+
body_acceleration_m_s2: tuple[float, ...] = ()
|
|
560
|
+
equilibrated_sections: tuple[B2LoadedMemberSections, ...] = ()
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@dataclass(frozen=True)
|
|
564
|
+
class B2BodyLoadedFootprintSelectedCase(ExperimentalB2Q4FootprintSelectedCase):
|
|
565
|
+
body_model_identity: str = ""
|
|
566
|
+
body_acceleration_m_s2: tuple[float, ...] = ()
|
|
567
|
+
equilibrated_sections: tuple[B2LoadedMemberSections, ...] = ()
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@dataclass(frozen=True)
|
|
571
|
+
class B2LoadedMemberPoses:
|
|
572
|
+
member_id: str
|
|
573
|
+
stations: tuple[float, ...]
|
|
574
|
+
poses_local: tuple[tuple[float, ...], ...]
|
|
575
|
+
point_trace_policy: str = field(default=B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV, init=False)
|
|
576
|
+
production_qualified: bool = field(default=False, init=False)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
@dataclass(frozen=True)
|
|
580
|
+
class B2LoadedPointPanelResult(B2BodyLoadedPanelResult):
|
|
581
|
+
loaded_member_poses: tuple[B2LoadedMemberPoses, ...] = ()
|
|
582
|
+
loaded_strain_energy: float | None = None
|
|
583
|
+
point_trace_policy: str = field(default=B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV, init=False)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@dataclass(frozen=True)
|
|
587
|
+
class B2LoadedPointPanelSelectedCase(B2BodyLoadedPanelSelectedCase):
|
|
588
|
+
loaded_member_poses: tuple[B2LoadedMemberPoses, ...] = ()
|
|
589
|
+
loaded_strain_energy: float | None = None
|
|
590
|
+
point_trace_policy: str = field(default=B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV, init=False)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _unique_ids(items, name):
|
|
594
|
+
ids = [getattr(item, name) for item in items]
|
|
595
|
+
if any(not isinstance(value, str) or not value.strip() for value in ids):
|
|
596
|
+
raise ValueError(f"nonempty {name} required")
|
|
597
|
+
if len(set(ids)) != len(ids):
|
|
598
|
+
raise ValueError(f"unique {name} required")
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _checked_recovery(recover_members, available):
|
|
602
|
+
if recover_members is None:
|
|
603
|
+
return None
|
|
604
|
+
if not isinstance(recover_members, Mapping):
|
|
605
|
+
raise ValueError("selected physical B2 recovery mapping required")
|
|
606
|
+
selected = {}
|
|
607
|
+
for member_id, requested in recover_members.items():
|
|
608
|
+
if member_id not in available:
|
|
609
|
+
raise ValueError("selected physical B2 member is not compiled")
|
|
610
|
+
try:
|
|
611
|
+
stations = np.asarray(requested, dtype=float).copy()
|
|
612
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
613
|
+
raise ValueError("finite normalized recovery stations required") from exc
|
|
614
|
+
if (stations.ndim != 1 or not len(stations) or
|
|
615
|
+
not np.isfinite(stations).all() or
|
|
616
|
+
np.any((stations < 0.) | (stations > 1.))):
|
|
617
|
+
raise ValueError("finite normalized recovery stations required")
|
|
618
|
+
selected[member_id] = tuple(float(value) for value in stations)
|
|
619
|
+
return selected
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _flat_vertices(vertices):
|
|
623
|
+
base = vertices[0]
|
|
624
|
+
normal = np.cross(vertices[1]-base, vertices[3]-base)
|
|
625
|
+
diameter = max(np.linalg.norm(a-b) for a in vertices for b in vertices)
|
|
626
|
+
magnitude = np.linalg.norm(normal)
|
|
627
|
+
if (not isfinite(diameter) or diameter <= 0. or
|
|
628
|
+
magnitude <= 1e-12*diameter**2 or
|
|
629
|
+
abs((vertices[2]-base) @ normal) > 1e-10*diameter*magnitude):
|
|
630
|
+
raise ValueError("nondegenerate flat Q4 shell required")
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _require_connected_panel(shells, members, points, footprints=()):
|
|
634
|
+
"""Check physical incidence without forming a numerical constraint matrix."""
|
|
635
|
+
neighbours = {(0, shell.shell_id): set() for shell in shells}
|
|
636
|
+
neighbours.update({(1, member.member_id): set() for member in members})
|
|
637
|
+
|
|
638
|
+
def join(a, b):
|
|
639
|
+
neighbours[a].add(b)
|
|
640
|
+
neighbours[b].add(a)
|
|
641
|
+
|
|
642
|
+
for kind, entities in ((0, shells), (1, members)):
|
|
643
|
+
owners = {}
|
|
644
|
+
for entity in entities:
|
|
645
|
+
key = (kind, entity.shell_id if kind == 0 else entity.member_id)
|
|
646
|
+
shell_count = 3 if type(entity) is S3V2DPanelShell else 4
|
|
647
|
+
if (kind == 0 and (type(entity.node_ids) is not tuple or
|
|
648
|
+
len(entity.node_ids) != shell_count or
|
|
649
|
+
any(type(node_id) is not int for node_id in entity.node_ids))):
|
|
650
|
+
raise ValueError("three integer S3 V2D nodes required" if shell_count == 3
|
|
651
|
+
else "four integer Q4 nodes required")
|
|
652
|
+
for node_id in entity.node_ids:
|
|
653
|
+
previous = owners.setdefault(node_id, key)
|
|
654
|
+
join(key, previous)
|
|
655
|
+
for point in points:
|
|
656
|
+
shell_key, member_key = (0, point.shell_id), (1, point.member_id)
|
|
657
|
+
if shell_key not in neighbours or member_key not in neighbours:
|
|
658
|
+
raise ValueError("point host IDs must identify declared elements")
|
|
659
|
+
join(shell_key, member_key)
|
|
660
|
+
for group in footprints:
|
|
661
|
+
shell_hosts = [s for s in shells if set(s.node_ids) & set(group.shell_nodes)]
|
|
662
|
+
beam_hosts = [m for m in members if group.beam_node in m.node_ids]
|
|
663
|
+
if (not beam_hosts or set(group.shell_nodes) -
|
|
664
|
+
{n for s in shell_hosts for n in s.node_ids}):
|
|
665
|
+
raise ValueError("known shell nodes and physical B2 endpoint required")
|
|
666
|
+
for shell in shell_hosts:
|
|
667
|
+
for member in beam_hosts:
|
|
668
|
+
join((0, shell.shell_id), (1, member.member_id))
|
|
669
|
+
first = next(iter(neighbours))
|
|
670
|
+
visited = {first}
|
|
671
|
+
pending = [first]
|
|
672
|
+
while pending:
|
|
673
|
+
for other in neighbours[pending.pop()]-visited:
|
|
674
|
+
visited.add(other)
|
|
675
|
+
pending.append(other)
|
|
676
|
+
if len(visited) != len(neighbours):
|
|
677
|
+
raise ValueError("connected shell/beam point panel required")
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _supports_all_panel_rigid_fields(transformation, shell_positions, beam_positions):
|
|
681
|
+
"""Detect all six physical rigid fields using only fixed coordinate rows.
|
|
682
|
+
|
|
683
|
+
The constructor requires one connected physical incidence graph, so this
|
|
684
|
+
is its one component's necessary support check, not a stiffness/kernel or
|
|
685
|
+
mechanical qualification claim. u=a+w cross (X-origin), theta=w. Translate
|
|
686
|
+
and scale its origin/length to keep rank independent of coordinate units.
|
|
687
|
+
Stream at most six support rows through a 6x6 QR; no global dense rigid,
|
|
688
|
+
stiffness, constraint or nullspace matrix is assembled.
|
|
689
|
+
"""
|
|
690
|
+
selector = transformation.copy()
|
|
691
|
+
selector.eliminate_zeros()
|
|
692
|
+
fixed = np.flatnonzero(np.diff(selector.indptr) == 0)
|
|
693
|
+
if len(fixed) < 6:
|
|
694
|
+
return False
|
|
695
|
+
xyz = tuple(shell_positions[n] for n in sorted(shell_positions))+tuple(
|
|
696
|
+
beam_positions[n] for n in sorted(beam_positions))
|
|
697
|
+
supported_nodes = np.unique(fixed//6)
|
|
698
|
+
origin = np.asarray(xyz[int(supported_nodes[0])], dtype=float)
|
|
699
|
+
length = 0.
|
|
700
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
701
|
+
for node in supported_nodes:
|
|
702
|
+
arm = np.asarray(xyz[int(node)], dtype=float)-origin
|
|
703
|
+
if not np.isfinite(arm).all():
|
|
704
|
+
return False
|
|
705
|
+
length = max(length, float(np.max(abs(arm))))
|
|
706
|
+
length = length or 1.
|
|
707
|
+
reduced = np.zeros((6, 6))
|
|
708
|
+
start = 0
|
|
709
|
+
for node in supported_nodes:
|
|
710
|
+
stop = start
|
|
711
|
+
while stop < len(fixed) and fixed[stop]//6 == node:
|
|
712
|
+
stop += 1
|
|
713
|
+
arm = (np.asarray(xyz[int(node)], dtype=float)-origin)/length
|
|
714
|
+
x, y, z = arm
|
|
715
|
+
cross = np.array(((0., -z, y), (z, 0., -x), (-y, x, 0.)))
|
|
716
|
+
rigid = np.block([[np.eye(3), -cross], [np.zeros((3, 3)), np.eye(3)]])
|
|
717
|
+
reduced = np.linalg.qr(np.vstack((reduced, rigid[fixed[start:stop] % 6])),
|
|
718
|
+
mode="r")
|
|
719
|
+
start = stop
|
|
720
|
+
scales = np.linalg.norm(reduced, axis=0)
|
|
721
|
+
if not np.isfinite(reduced).all() or np.any(scales == 0.):
|
|
722
|
+
return False
|
|
723
|
+
singular = np.linalg.svd(reduced/scales, compute_uv=False)
|
|
724
|
+
return bool(singular[-1] > 64*np.finfo(float).eps*max(6, len(fixed))*singular[0])
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _retire_private_q4_mesh(owner):
|
|
728
|
+
mesh = owner._private_q4_mesh
|
|
729
|
+
for element in owner._private_q4_elements:
|
|
730
|
+
_retire_q4_private_mesh_caches(element, mesh)
|
|
731
|
+
owner._private_q4_elements.clear()
|
|
732
|
+
owner._private_q4_mesh = None
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _private_q4_constructor_lifetime(initialize):
|
|
736
|
+
@wraps(initialize)
|
|
737
|
+
def guarded(owner, *args, **kwargs):
|
|
738
|
+
owner._private_q4_mesh = None
|
|
739
|
+
owner._private_q4_elements = []
|
|
740
|
+
try:
|
|
741
|
+
return initialize(owner, *args, **kwargs)
|
|
742
|
+
except BaseException as original:
|
|
743
|
+
try:
|
|
744
|
+
_retire_private_q4_mesh(owner)
|
|
745
|
+
except BaseException as cleanup:
|
|
746
|
+
original.add_note("private Q4 retirement failed: " + repr(cleanup))
|
|
747
|
+
raise
|
|
748
|
+
return guarded
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class ExperimentalB2Q4PanelAnalysis:
|
|
752
|
+
"""Own one connected reference-linear panel and its sparse factorization.
|
|
753
|
+
|
|
754
|
+
The coordinate order is sorted shell node IDs, then sorted beam node IDs,
|
|
755
|
+
six global translations/rotations for each. Affine ``T/u0`` is supplied
|
|
756
|
+
in that order. Reactions are affine reactions; they are support reactions
|
|
757
|
+
only when the affine plan fixes coordinates without other MPCs.
|
|
758
|
+
"""
|
|
759
|
+
|
|
760
|
+
_mixed_shells = False
|
|
761
|
+
|
|
762
|
+
@_private_q4_constructor_lifetime
|
|
763
|
+
def __init__(self, shell_nodes_m: Mapping[int, Sequence[float]],
|
|
764
|
+
shells: Sequence[Q4PanelShell],
|
|
765
|
+
members: Sequence[PhysicalB2Member | CompiledPhysicalB2Member],
|
|
766
|
+
points: Sequence[Q4B2Point], *,
|
|
767
|
+
transformation: sparse.spmatrix, prescribed_offset,
|
|
768
|
+
footprints: Sequence[B2NodalRigidFootprint] = (),
|
|
769
|
+
reference_inertia_policy: str | None = None,
|
|
770
|
+
reference_boundary_policy: str | None = None,
|
|
771
|
+
point_trace_policy: str | None = None,
|
|
772
|
+
physical_footprints: Sequence[B2PhysicalRigidFootprint] = ()):
|
|
773
|
+
diagnostic = active_capture()
|
|
774
|
+
if diagnostic is not None:
|
|
775
|
+
diagnostic.begin()
|
|
776
|
+
if not physical_footprints or points or footprints or reference_inertia_policy is not None or reference_boundary_policy is not None or point_trace_policy is not None:
|
|
777
|
+
raise DiagnosticRefusal("capture admits physical static footprint compilation only")
|
|
778
|
+
shells = tuple(shells)
|
|
779
|
+
supplied_members = tuple(members)
|
|
780
|
+
points = tuple(points)
|
|
781
|
+
footprints = tuple(footprints)
|
|
782
|
+
physical_footprints = tuple(physical_footprints)
|
|
783
|
+
self._physical_footprint_law = bool(physical_footprints)
|
|
784
|
+
if physical_footprints:
|
|
785
|
+
if points or footprints or reference_inertia_policy is not None or reference_boundary_policy is not None or point_trace_policy is not None:
|
|
786
|
+
raise ValueError("separate physical footprint static family required")
|
|
787
|
+
footprints = physical_footprints
|
|
788
|
+
modal_mass = reference_inertia_policy is not None
|
|
789
|
+
if reference_boundary_policy is not None and (
|
|
790
|
+
type(reference_boundary_policy) is not str or
|
|
791
|
+
reference_boundary_policy != B2_FREE_FREE_MODAL_ONLY_V1 or
|
|
792
|
+
not modal_mass):
|
|
793
|
+
raise ValueError("explicit mixed free-free modal-only boundary policy required")
|
|
794
|
+
self._modal_only = reference_boundary_policy is not None
|
|
795
|
+
self._reference_boundary_policy = reference_boundary_policy
|
|
796
|
+
if modal_mass and (not self._mixed_shells or
|
|
797
|
+
type(reference_inertia_policy) is not str or
|
|
798
|
+
reference_inertia_policy != S3_V2D_TRANSLATIONAL_LUMPED_SECTION_AREAL_MASS_V1 or
|
|
799
|
+
footprints):
|
|
800
|
+
raise ValueError("explicit native-S3 reference inertia policy without footprints required")
|
|
801
|
+
has_s3 = any(type(shell) is S3V2DPanelShell for shell in shells)
|
|
802
|
+
if point_trace_policy is not None and (
|
|
803
|
+
type(point_trace_policy) is not str or
|
|
804
|
+
point_trace_policy != B2_FIXED_END_LOADED_POINT_TRACE_V1_DEV or
|
|
805
|
+
self._mixed_shells or has_s3 or footprints or modal_mass):
|
|
806
|
+
raise ValueError("explicit Q4-only fixed-end loaded point trace without footprints or modes required")
|
|
807
|
+
self._point_trace_policy = point_trace_policy
|
|
808
|
+
if has_s3 and footprints and not self._physical_footprint_law:
|
|
809
|
+
raise ValueError("S3 mixed-panel nodal footprints are unsupported")
|
|
810
|
+
admitted_shells = ((Q4PanelShell, S3V2DPanelShell) if self._mixed_shells
|
|
811
|
+
else (Q4PanelShell,))
|
|
812
|
+
footprint_type = B2PhysicalRigidFootprint if self._physical_footprint_law else B2NodalRigidFootprint
|
|
813
|
+
if any(type(g) is not footprint_type for g in footprints):
|
|
814
|
+
raise ValueError("typed experimental B2 nodal footprints required")
|
|
815
|
+
if (len(shells) < 2 or len(supplied_members) < 2 or not (points or footprints) or
|
|
816
|
+
any((type(shell) not in admitted_shells if self._mixed_shells else
|
|
817
|
+
not isinstance(shell, Q4PanelShell)) for shell in shells) or
|
|
818
|
+
any(not isinstance(member, (PhysicalB2Member,
|
|
819
|
+
CompiledPhysicalB2Member))
|
|
820
|
+
for member in supplied_members) or
|
|
821
|
+
any(not isinstance(point, Q4B2Point) for point in points)):
|
|
822
|
+
raise ValueError("multi-Q4/S3/multi-B2 physical point panel required" if
|
|
823
|
+
self._mixed_shells else
|
|
824
|
+
"multi-Q4/multi-B2 physical point panel required")
|
|
825
|
+
_unique_ids(shells, "shell_id")
|
|
826
|
+
_unique_ids(points, "point_id")
|
|
827
|
+
definitions = tuple(member.definition if isinstance(
|
|
828
|
+
member, CompiledPhysicalB2Member) else member
|
|
829
|
+
for member in supplied_members)
|
|
830
|
+
if diagnostic is not None and (any(shell.density_kg_m3 is not None for shell in shells) or
|
|
831
|
+
any(member.mass is not None for member in definitions)):
|
|
832
|
+
raise DiagnosticRefusal("capture of physical mass/body-map storage is not admitted")
|
|
833
|
+
if modal_mass:
|
|
834
|
+
for shell in shells:
|
|
835
|
+
density = shell.density_kg_m3
|
|
836
|
+
if isinstance(density, (bool, np.bool_)) or not isinstance(density, Real):
|
|
837
|
+
raise ValueError("mixed reference modes require complete positive finite shell density")
|
|
838
|
+
try:
|
|
839
|
+
density_value = float(density)
|
|
840
|
+
except OverflowError as exc:
|
|
841
|
+
raise ValueError("mixed reference modes require representable shell density") from exc
|
|
842
|
+
if not isfinite(density_value) or density_value <= 0.:
|
|
843
|
+
raise ValueError("mixed reference modes require complete positive finite shell density")
|
|
844
|
+
if any(member.mass is None for member in definitions):
|
|
845
|
+
raise ValueError("mixed reference modes require complete declared physical B2 mass")
|
|
846
|
+
if any(member.geometric is not None for member in definitions):
|
|
847
|
+
raise ValueError("mixed reference modes are virgin elastic; prestress/geometric metadata unsupported")
|
|
848
|
+
_unique_ids(definitions, "member_id")
|
|
849
|
+
_require_connected_panel(shells, definitions, points, footprints)
|
|
850
|
+
cache = PhysicalB2OperatorCache()
|
|
851
|
+
if diagnostic is not None: diagnostic.mark("member_compilation")
|
|
852
|
+
compiled = tuple(
|
|
853
|
+
member if isinstance(member, CompiledPhysicalB2Member)
|
|
854
|
+
else member.compile_local(operator_cache=cache)
|
|
855
|
+
for member in supplied_members)
|
|
856
|
+
if diagnostic is not None: diagnostic.mark("topology_mapping")
|
|
857
|
+
shell_ids = tuple(sorted(shell_nodes_m))
|
|
858
|
+
if any(type(node_id) is not int for node_id in shell_ids):
|
|
859
|
+
raise ValueError("integer shell node IDs required")
|
|
860
|
+
positions = {}
|
|
861
|
+
for node_id in shell_ids:
|
|
862
|
+
vector = np.array(shell_nodes_m[node_id], dtype=float, copy=True)
|
|
863
|
+
if vector.shape != (3,) or not np.isfinite(vector).all():
|
|
864
|
+
raise ValueError("finite shell node coordinates required")
|
|
865
|
+
positions[node_id] = vector
|
|
866
|
+
used_shell_nodes = {node_id for shell in shells for node_id in shell.node_ids}
|
|
867
|
+
if used_shell_nodes != set(shell_ids):
|
|
868
|
+
raise ValueError("every declared shell node must belong to a Q4/S3" if self._mixed_shells
|
|
869
|
+
else "every declared shell node must belong to a Q4")
|
|
870
|
+
shell_index = {node_id: i for i, node_id in enumerate(shell_ids)}
|
|
871
|
+
beam_positions = {}
|
|
872
|
+
for member in definitions:
|
|
873
|
+
for node_id, point in zip(member.node_ids, member.reference_points_m):
|
|
874
|
+
old = beam_positions.setdefault(node_id, point)
|
|
875
|
+
if old != point:
|
|
876
|
+
raise ValueError("shared B2 node has inconsistent position")
|
|
877
|
+
beam_ids = tuple(sorted(beam_positions))
|
|
878
|
+
beam_index = {node_id: i+len(shell_ids)
|
|
879
|
+
for i, node_id in enumerate(beam_ids)}
|
|
880
|
+
footprint_compiler = compile_physical_footprints if self._physical_footprint_law else compile_footprints
|
|
881
|
+
footprints, footprint_incidence = footprint_compiler(
|
|
882
|
+
footprints, positions, shells, beam_positions,
|
|
883
|
+
shell_index, beam_index, points)
|
|
884
|
+
total = 6*(len(shell_ids)+len(beam_ids))
|
|
885
|
+
for shell in shells:
|
|
886
|
+
density = shell.density_kg_m3
|
|
887
|
+
if density is not None:
|
|
888
|
+
if isinstance(density, (bool, np.bool_)) or not isinstance(density, Real):
|
|
889
|
+
raise ValueError("positive finite shell density required when supplied")
|
|
890
|
+
try:
|
|
891
|
+
density_value = float(density)
|
|
892
|
+
except (ValueError, TypeError, OverflowError) as exc:
|
|
893
|
+
raise ValueError("representable positive shell density required") from exc
|
|
894
|
+
if not isfinite(density_value) or density_value <= 0.:
|
|
895
|
+
raise ValueError("positive finite shell density required when supplied")
|
|
896
|
+
complete_body_mass = (all(shell.density_kg_m3 is not None for shell in shells)
|
|
897
|
+
and all(member.definition.mass is not None for member in compiled))
|
|
898
|
+
body_map = np.zeros((total, 3)) if complete_body_mass else None
|
|
899
|
+
body_masses = []
|
|
900
|
+
if diagnostic is not None: diagnostic.mark("shell_model_setup")
|
|
901
|
+
model = FEModel("experimental-multi-Q4-B2-point")
|
|
902
|
+
self._private_q4_mesh = model.mesh
|
|
903
|
+
for node_id in shell_ids:
|
|
904
|
+
model.add_node(node_id, *positions[node_id])
|
|
905
|
+
rows, columns, values = [], [], []
|
|
906
|
+
mass_rows, mass_columns, mass_values = [], [], []
|
|
907
|
+
complete_mass = (not has_s3 or modal_mass) and all(shell.density_kg_m3 is not None for shell in shells)
|
|
908
|
+
complete_mass = complete_mass and all(member.definition.mass is not None
|
|
909
|
+
for member in compiled)
|
|
910
|
+
shell_lookup = {}
|
|
911
|
+
pieces = []
|
|
912
|
+
for index, shell in enumerate(shells, 1):
|
|
913
|
+
count = 3 if type(shell) is S3V2DPanelShell else 4
|
|
914
|
+
if (len(shell.node_ids) != count or len(set(shell.node_ids)) != count or
|
|
915
|
+
any(node_id not in shell_index for node_id in shell.node_ids)):
|
|
916
|
+
raise ValueError("distinct declared Q4/S3 nodes required" if self._mixed_shells
|
|
917
|
+
else "four declared Q4 nodes required")
|
|
918
|
+
vertices = np.array([positions[node_id] for node_id in shell.node_ids])
|
|
919
|
+
if count == 4:
|
|
920
|
+
_flat_vertices(vertices)
|
|
921
|
+
material_id = f"panel-material-{index}"
|
|
922
|
+
if modal_mass:
|
|
923
|
+
model.add_material(material_id, shell.E, shell.nu, density=shell.density_kg_m3)
|
|
924
|
+
else:
|
|
925
|
+
model.add_material(material_id, shell.E, shell.nu)
|
|
926
|
+
if self._mixed_shells:
|
|
927
|
+
element = create_shell_element(
|
|
928
|
+
index, list(shell.node_ids), material_id,
|
|
929
|
+
formulation="e4-pl-s3-v2d" if count == 3 else "e4-pl",
|
|
930
|
+
thickness=shell.thickness_m,
|
|
931
|
+
reference_normal=np.asarray(shell.reference_normal, dtype=float))
|
|
932
|
+
else:
|
|
933
|
+
element = create_shell_element(
|
|
934
|
+
index, shell.node_ids, material_id, formulation="e4-pl", thickness=shell.thickness_m,
|
|
935
|
+
reference_normal=np.asarray(shell.reference_normal, dtype=float))
|
|
936
|
+
if count == 4:
|
|
937
|
+
self._private_q4_elements.append(element)
|
|
938
|
+
model.add_element(index, element)
|
|
939
|
+
pieces.append((shell, vertices, element, material_id, count))
|
|
940
|
+
# Native S3 scope validation must see the complete shell registry.
|
|
941
|
+
if diagnostic is not None: diagnostic.mark("shell_response_scatter")
|
|
942
|
+
for shell, vertices, element, material_id, count in pieces:
|
|
943
|
+
local = element.compute_stiffness_matrix(
|
|
944
|
+
model.mesh, model.get_material(material_id))
|
|
945
|
+
if local.shape != (6*count, 6*count) or not np.isfinite(local).all():
|
|
946
|
+
raise ExperimentalB2Q4PanelError("finite Q4/S3 stiffness required" if self._mixed_shells
|
|
947
|
+
else "finite Q4 stiffness required")
|
|
948
|
+
slots = np.array([6*shell_index[node_id]+component
|
|
949
|
+
for node_id in shell.node_ids
|
|
950
|
+
for component in range(6)])
|
|
951
|
+
_scatter(rows, columns, values, slots, local)
|
|
952
|
+
if count == 4 and shell.density_kg_m3 is not None:
|
|
953
|
+
local_mass = physical_mass(
|
|
954
|
+
vertices, density_kg_m3=shell.density_kg_m3,
|
|
955
|
+
thickness_m=shell.thickness_m,
|
|
956
|
+
reference_normal=shell.reference_normal)
|
|
957
|
+
if complete_mass:
|
|
958
|
+
_scatter(mass_rows, mass_columns, mass_values, slots, local_mass)
|
|
959
|
+
if body_map is not None:
|
|
960
|
+
translations = np.zeros((24, 3))
|
|
961
|
+
for i in range(4):
|
|
962
|
+
translations[6*i:6*i+3] = np.eye(3)
|
|
963
|
+
local_body = local_mass@translations
|
|
964
|
+
body_map[slots] += local_body
|
|
965
|
+
body_masses.append(fsum(float(v) for v in local_body[::6, 0]))
|
|
966
|
+
elif count == 3 and modal_mass:
|
|
967
|
+
local_mass = element.compute_mass_matrix(model.mesh, model.get_material(material_id))
|
|
968
|
+
angular = np.array([6*i+j for i in range(3) for j in (3, 4, 5)])
|
|
969
|
+
translation = np.array([6*i+j for i in range(3) for j in (0, 1, 2)])
|
|
970
|
+
if (local_mass.shape != (18, 18) or not np.isfinite(local_mass).all()
|
|
971
|
+
or np.any(np.diag(local_mass)[translation] <= 0.)
|
|
972
|
+
or np.any(local_mass[angular, :] != 0.)
|
|
973
|
+
or np.any(local_mass[:, angular] != 0.)):
|
|
974
|
+
raise ValueError("native S3 translational mass is not representable with zero angular inertia")
|
|
975
|
+
_scatter(mass_rows, mass_columns, mass_values, slots, local_mass)
|
|
976
|
+
if count == 3 and body_map is not None:
|
|
977
|
+
density = shell.density_kg_m3
|
|
978
|
+
area = .5*float(np.linalg.norm(np.cross(vertices[1]-vertices[0], vertices[2]-vertices[0])))
|
|
979
|
+
weight = float(density)*shell.thickness_m*area/3.
|
|
980
|
+
if not isfinite(weight) or weight <= 0.:
|
|
981
|
+
raise RepresentabilityError("S3 reference body load is not representable")
|
|
982
|
+
for i in range(3):
|
|
983
|
+
body_map[slots[6*i:6*i+3]] += weight*np.eye(3)
|
|
984
|
+
body_masses.append(3.*weight)
|
|
985
|
+
shell_lookup[shell.shell_id] = (vertices, slots)
|
|
986
|
+
if diagnostic is not None: diagnostic.mark("beam_response_scatter")
|
|
987
|
+
member_lookup = {}
|
|
988
|
+
for member in compiled:
|
|
989
|
+
frame = member.local_dof_map()
|
|
990
|
+
local = frame.T @ member.operator.evaluate(np.zeros(12)).tangent @ frame
|
|
991
|
+
if local.shape != (12, 12) or not np.isfinite(local).all():
|
|
992
|
+
raise ExperimentalB2Q4PanelError("finite physical B2 stiffness required")
|
|
993
|
+
slots = np.array([6*beam_index[node_id]+component
|
|
994
|
+
for node_id in member.definition.node_ids
|
|
995
|
+
for component in range(6)])
|
|
996
|
+
_scatter(rows, columns, values, slots, local)
|
|
997
|
+
if complete_mass:
|
|
998
|
+
local_mass = frame.T@member.local_candidate_mass_matrix()@frame
|
|
999
|
+
if not np.isfinite(local_mass).all():
|
|
1000
|
+
raise ValueError("physical B2 mass assembly is not representable")
|
|
1001
|
+
if modal_mass and np.any(np.diag(local_mass)[[0, 1, 2, 6, 7, 8]] <= 0.):
|
|
1002
|
+
raise ValueError("physical B2 translational mass is not representable")
|
|
1003
|
+
_scatter(mass_rows, mass_columns, mass_values, slots, local_mass)
|
|
1004
|
+
if body_map is not None:
|
|
1005
|
+
body_map[slots] += _beam_body_map(member, frame)
|
|
1006
|
+
body_masses.append(member.definition.length_m*member.definition.mass.matrix_per_m[0][0])
|
|
1007
|
+
member_lookup[member.definition.member_id] = (member, slots)
|
|
1008
|
+
if diagnostic is not None: diagnostic.mark("K_CSR")
|
|
1009
|
+
K = sparse.coo_matrix((values, (rows, columns)),
|
|
1010
|
+
shape=(total, total)).tocsr()
|
|
1011
|
+
_release_coo_scratch(rows, columns, values, diagnostic, "K_CSR")
|
|
1012
|
+
if diagnostic is not None: diagnostic.mark("attachment_rows")
|
|
1013
|
+
constraint_rows, constraint_cols, constraint_values = [], [], []
|
|
1014
|
+
point_reference_frames = []
|
|
1015
|
+
point_laws = []
|
|
1016
|
+
buckling_points = []
|
|
1017
|
+
section_point_actions = {}
|
|
1018
|
+
loaded_point_entries = {}
|
|
1019
|
+
for point_index, point in enumerate(points):
|
|
1020
|
+
if (point.shell_id not in shell_lookup or
|
|
1021
|
+
point.member_id not in member_lookup):
|
|
1022
|
+
raise ValueError("point host IDs must identify declared elements")
|
|
1023
|
+
vertices, shell_slots = shell_lookup[point.shell_id]
|
|
1024
|
+
member, beam_slots = member_lookup[point.member_id]
|
|
1025
|
+
law = compile_reference_point(
|
|
1026
|
+
member.definition, vertices, station=point.station,
|
|
1027
|
+
offset_local_m=point.offset_local_m)
|
|
1028
|
+
point_laws.append((point.point_id, law.law_id))
|
|
1029
|
+
buckling_points.append((point, law, vertices.copy(), member,
|
|
1030
|
+
np.r_[shell_slots, beam_slots]))
|
|
1031
|
+
if point_trace_policy is not None:
|
|
1032
|
+
port_constraint = (law.beam_constraint_differential @
|
|
1033
|
+
np.kron(np.eye(2), member.definition.local_frame_rows().T))
|
|
1034
|
+
loaded_point_entries.setdefault(point.member_id, []).append(
|
|
1035
|
+
(point_index, float(point.station), tuple(point.offset_local_m), port_constraint))
|
|
1036
|
+
if 0. < point.station < 1.:
|
|
1037
|
+
d = np.array(point.offset_local_m, dtype=float, copy=True)
|
|
1038
|
+
shift = np.eye(6)
|
|
1039
|
+
shift[3:, :3] = ((0., -d[2], d[1]), (d[2], 0., -d[0]), (-d[1], d[0], 0.))
|
|
1040
|
+
pullback = shift@np.kron(np.eye(2), member.definition.local_frame_rows())@(-law.beam_constraint_differential.T)
|
|
1041
|
+
pullback.setflags(write=False)
|
|
1042
|
+
section_point_actions.setdefault(point.member_id, []).append(
|
|
1043
|
+
(point_index, float(point.station), pullback))
|
|
1044
|
+
if footprints:
|
|
1045
|
+
point_reference_frames.append(tuple(
|
|
1046
|
+
tuple(tuple(map(float, row)) for row in frame)
|
|
1047
|
+
for frame in law.reference_frames))
|
|
1048
|
+
slots = np.r_[shell_slots, beam_slots]
|
|
1049
|
+
for local_row in range(6):
|
|
1050
|
+
for local_col, slot in enumerate(slots):
|
|
1051
|
+
constraint_rows.append(6*point_index+local_row)
|
|
1052
|
+
constraint_cols.append(int(slot))
|
|
1053
|
+
constraint_values.append(law.constraint[local_row, local_col])
|
|
1054
|
+
for item in footprint_incidence:
|
|
1055
|
+
slots = item.shell_slots+item.beam_slots
|
|
1056
|
+
width = getattr(item, "row_count", 6)
|
|
1057
|
+
local = np.column_stack((item.shell_matrix, getattr(item, "beam_matrix", np.eye(6))))
|
|
1058
|
+
for local_row in range(width):
|
|
1059
|
+
for local_col, slot in enumerate(slots):
|
|
1060
|
+
constraint_rows.append(item.row_start+local_row)
|
|
1061
|
+
constraint_cols.append(slot)
|
|
1062
|
+
constraint_values.append(local[local_row, local_col])
|
|
1063
|
+
if diagnostic is not None: diagnostic.mark("C_CSR")
|
|
1064
|
+
C = sparse.coo_matrix(
|
|
1065
|
+
(constraint_values, (constraint_rows, constraint_cols)),
|
|
1066
|
+
shape=(6*len(points)+sum(getattr(item,"row_count",6) for item in footprint_incidence), total)).tocsr()
|
|
1067
|
+
_release_coo_scratch(constraint_rows, constraint_cols, constraint_values, diagnostic, "C_CSR")
|
|
1068
|
+
if diagnostic is not None: diagnostic.mark("support_reduction_scaling_elimination")
|
|
1069
|
+
loaded_members = {}
|
|
1070
|
+
H = None
|
|
1071
|
+
if point_trace_policy is not None:
|
|
1072
|
+
h_rows, h_columns, h_values = [], [], []
|
|
1073
|
+
for member_id, (member, _) in member_lookup.items():
|
|
1074
|
+
compiled_load = LoadedPointMember(member, loaded_point_entries.get(member_id, ()),
|
|
1075
|
+
body_enabled=body_map is not None)
|
|
1076
|
+
loaded_members[member_id] = compiled_load
|
|
1077
|
+
slots = np.array([6*index+channel for index in compiled_load.point_indices
|
|
1078
|
+
for channel in range(6)])
|
|
1079
|
+
_scatter(h_rows, h_columns, h_values, slots, compiled_load.constraint_compliance)
|
|
1080
|
+
H = sparse.coo_matrix((h_values, (h_rows, h_columns)), shape=(C.shape[0],)*2).tocsr()
|
|
1081
|
+
# Stored analytical zeros carry no numerical information. Remove
|
|
1082
|
+
# them before congruence so underflow checks compare nonzero patterns.
|
|
1083
|
+
H.eliminate_zeros()
|
|
1084
|
+
try:
|
|
1085
|
+
T = transformation.tocsr(copy=True)
|
|
1086
|
+
u0 = np.array(prescribed_offset, dtype=float, copy=True)
|
|
1087
|
+
except (AttributeError, TypeError, ValueError) as exc:
|
|
1088
|
+
raise ValueError("finite sparse panel affine plan required") from exc
|
|
1089
|
+
minimum_columns = C.shape[0] if point_trace_policy is None else 0
|
|
1090
|
+
if (T.shape[0] != total or T.shape[1] <= minimum_columns or
|
|
1091
|
+
u0.shape != (total,) or not np.isfinite(T.data).all() or
|
|
1092
|
+
not np.isfinite(u0).all()):
|
|
1093
|
+
raise ValueError("finite sparse panel affine plan required")
|
|
1094
|
+
if modal_mass:
|
|
1095
|
+
if np.any(u0 != 0.):
|
|
1096
|
+
raise ValueError("virgin mixed reference modes require zero prescribed offset")
|
|
1097
|
+
selector = T.copy()
|
|
1098
|
+
selector.eliminate_zeros()
|
|
1099
|
+
if (np.any(np.diff(selector.tocsc().indptr) != 1) or
|
|
1100
|
+
np.any(np.diff(selector.indptr) > 1)):
|
|
1101
|
+
raise ValueError("mixed reference modes require coordinate supports without MPCs")
|
|
1102
|
+
if self._modal_only:
|
|
1103
|
+
delta = (T-sparse.eye(total, format="csr")) if T.shape == (total, total) else None
|
|
1104
|
+
if delta is None or np.any(delta.data != 0.):
|
|
1105
|
+
raise ValueError("free-free modal-only policy requires identity transformation without supports")
|
|
1106
|
+
elif not _supports_all_panel_rigid_fields(T, positions, beam_positions):
|
|
1107
|
+
raise ValueError("mixed reference modes require supports detecting all six rigid fields; free-free unsupported")
|
|
1108
|
+
reduced_c = (C @ T).tocsc()
|
|
1109
|
+
if point_trace_policy is None:
|
|
1110
|
+
_require_independent_supported_point_rows(reduced_c)
|
|
1111
|
+
elif not np.isfinite(reduced_c.data).all():
|
|
1112
|
+
raise ValueError("finite supported loaded-point rows required")
|
|
1113
|
+
# Loaded fixed-end compliance can anchor a multiplier row even when
|
|
1114
|
+
# its free-coordinate trace vanishes. Independence is therefore a
|
|
1115
|
+
# property of the complete [K, C.T; C, -H] factorization on this route.
|
|
1116
|
+
reduced_k = (T.T @ K @ T).tocsc()
|
|
1117
|
+
# Congruence scaling changes factorization coordinates only. The
|
|
1118
|
+
# physical displacement and multiplier are both restored after solve.
|
|
1119
|
+
# Without this, a valid slender B2 section can leave point gaps above
|
|
1120
|
+
# the physical acceptance tolerance despite a sound local operator.
|
|
1121
|
+
handle = factor_scale = None
|
|
1122
|
+
attachment_maps = None
|
|
1123
|
+
attachment_reason = "not_physical_static_family"
|
|
1124
|
+
if not self._modal_only:
|
|
1125
|
+
scaled_kkt, factor_scale = _scaled_point_kkt(reduced_k, reduced_c, H)
|
|
1126
|
+
if self._physical_footprint_law and not points and point_trace_policy is None and H is None:
|
|
1127
|
+
attachment_maps, attachment_reason = prepare_attachment_elimination(
|
|
1128
|
+
scaled_kkt, reduced_k.shape[0], T, footprint_incidence)
|
|
1129
|
+
# Ineligible attachment layouts use the exact existing factor path.
|
|
1130
|
+
# An eligible path factors once and restores the full scaled answer
|
|
1131
|
+
# before original physical checks/recovery; spectral operators are
|
|
1132
|
+
# still prepared from original K/C/T, not these static embeddings.
|
|
1133
|
+
if diagnostic is not None:
|
|
1134
|
+
diagnostic.prefactor(
|
|
1135
|
+
dict(K=K,C=C,T=T,u0=u0,H=H,reduced_k=reduced_k,reduced_c=reduced_c,
|
|
1136
|
+
scaled_kkt=scaled_kkt,factor_scale=factor_scale,attachment_maps=attachment_maps,
|
|
1137
|
+
model=model,positions=positions,beam_positions=beam_positions,shell_index=shell_index,
|
|
1138
|
+
beam_index=beam_index,shell_lookup=shell_lookup,member_lookup=member_lookup,
|
|
1139
|
+
footprint_incidence=footprint_incidence,compiled=compiled,cache=cache,
|
|
1140
|
+
private_q4_elements=self._private_q4_elements),
|
|
1141
|
+
dict(rows=rows,columns=columns,values=values,constraint_rows=constraint_rows,
|
|
1142
|
+
constraint_cols=constraint_cols,constraint_values=constraint_values,
|
|
1143
|
+
pieces=pieces,local=local,slots=slots,frame=frame),
|
|
1144
|
+
(*shells,*definitions,*footprints,*(m.section for m in definitions),
|
|
1145
|
+
*(m for m in supplied_members if isinstance(m,CompiledPhysicalB2Member))))
|
|
1146
|
+
handle = (attachment_maps.factorize() if attachment_maps is not None else
|
|
1147
|
+
factorize_cached(scaled_kkt, MatrixClass.SYMMETRIC_INDEFINITE))
|
|
1148
|
+
if handle.status != "ok":
|
|
1149
|
+
raise ExperimentalB2Q4PanelError(
|
|
1150
|
+
"panel factorization failed: " + str(handle.failure_reason))
|
|
1151
|
+
if diagnostic is not None:
|
|
1152
|
+
try: diagnostic.mark("owner_publication")
|
|
1153
|
+
except BaseException:
|
|
1154
|
+
if handle is not None:
|
|
1155
|
+
try: handle.close()
|
|
1156
|
+
except BaseException: pass
|
|
1157
|
+
raise
|
|
1158
|
+
self._modal_positions = (np.array(
|
|
1159
|
+
[positions[i] for i in shell_ids]+[beam_positions[i] for i in beam_ids],
|
|
1160
|
+
dtype=float, copy=True) if self._modal_only else None)
|
|
1161
|
+
self.shell_node_ids = shell_ids
|
|
1162
|
+
self.beam_node_ids = beam_ids
|
|
1163
|
+
self.point_ids = tuple(point.point_id for point in points)
|
|
1164
|
+
self._has_s3 = has_s3
|
|
1165
|
+
self._reference_inertia_policy = reference_inertia_policy
|
|
1166
|
+
self._shell_formulations = tuple((shell.shell_id, element.formulation_id)
|
|
1167
|
+
for shell, _, element, _, _ in pieces)
|
|
1168
|
+
self._point_laws = tuple(point_laws)
|
|
1169
|
+
self._point_reference_frames = tuple(point_reference_frames)
|
|
1170
|
+
self.footprints = footprints
|
|
1171
|
+
self.footprint_ids = tuple(g.footprint_id for g in footprints)
|
|
1172
|
+
self._footprint_incidence = footprint_incidence
|
|
1173
|
+
self._members = tuple(member_lookup.items())
|
|
1174
|
+
self._section_point_actions = {key: tuple(value) for key, value in section_point_actions.items()}
|
|
1175
|
+
self._loaded_members = loaded_members
|
|
1176
|
+
self._H = H
|
|
1177
|
+
self._abs_H = None if H is None else abs(H)
|
|
1178
|
+
self._K = K
|
|
1179
|
+
# Spectral-only captures reuse the exact prepared reference operators;
|
|
1180
|
+
# no accepted state or nonlinear material history is manufactured.
|
|
1181
|
+
self._buckling_model = model
|
|
1182
|
+
self._buckling_shells = tuple((shell, element, material_id,
|
|
1183
|
+
shell_lookup[shell.shell_id][1]) for shell, _, element, material_id, _ in pieces)
|
|
1184
|
+
self._buckling_points = tuple(buckling_points)
|
|
1185
|
+
if body_map is not None and not np.isfinite(body_map).all():
|
|
1186
|
+
raise RepresentabilityError("reference body-load map is not representable")
|
|
1187
|
+
self._body_map = body_map
|
|
1188
|
+
self._body_mass = None
|
|
1189
|
+
if self._body_map is not None:
|
|
1190
|
+
try:
|
|
1191
|
+
self._body_mass = fsum(body_masses)
|
|
1192
|
+
except OverflowError as exc:
|
|
1193
|
+
raise RepresentabilityError("total reference body mass is not representable") from exc
|
|
1194
|
+
if not isfinite(self._body_mass) or self._body_mass <= 0.:
|
|
1195
|
+
raise RepresentabilityError("total reference body mass is not representable")
|
|
1196
|
+
self._body_map.setflags(write=False)
|
|
1197
|
+
self._M = (sparse.coo_matrix((mass_values, (mass_rows, mass_columns)),
|
|
1198
|
+
shape=(total, total)).tocsr()
|
|
1199
|
+
if complete_mass else None)
|
|
1200
|
+
if self._M is not None and not np.isfinite(self._M.data).all():
|
|
1201
|
+
raise ValueError("physical panel mass assembly is not representable")
|
|
1202
|
+
self._C = C
|
|
1203
|
+
# Freeze a translation scale from the physical reference geometry;
|
|
1204
|
+
# rotation constraints remain in radians. Origin shifts do not alter it.
|
|
1205
|
+
self._compatibility_length = None
|
|
1206
|
+
if footprints:
|
|
1207
|
+
reference = np.asarray(tuple(positions.values())+tuple(beam_positions.values()))
|
|
1208
|
+
relative = reference-reference[0]
|
|
1209
|
+
radius = np.linalg.norm(relative-np.mean(relative, axis=0), axis=1)
|
|
1210
|
+
self._compatibility_length = 2.*float(np.max(radius))
|
|
1211
|
+
if not isfinite(self._compatibility_length) or self._compatibility_length <= 0.:
|
|
1212
|
+
raise ValueError("representable physical reference length required")
|
|
1213
|
+
self._T = T
|
|
1214
|
+
self._Tt = T.T.tocsr()
|
|
1215
|
+
self._projection_multi = np.flatnonzero(
|
|
1216
|
+
np.diff(self._Tt.indptr) > 1)
|
|
1217
|
+
self._projection_single = np.flatnonzero(
|
|
1218
|
+
np.diff(self._Tt.indptr) == 1)
|
|
1219
|
+
# Reuse the immutable CSR patterns; only the absolute values need
|
|
1220
|
+
# separate storage for repeated RHS validation.
|
|
1221
|
+
self._abs_K = sparse.csr_matrix(
|
|
1222
|
+
(np.abs(K.data), K.indices, K.indptr), shape=K.shape, copy=False)
|
|
1223
|
+
self._abs_C = sparse.csr_matrix(
|
|
1224
|
+
(np.abs(C.data), C.indices, C.indptr), shape=C.shape, copy=False)
|
|
1225
|
+
self._abs_Ct = abs(C.T.tocsr())
|
|
1226
|
+
self._abs_Tt = abs(T.T.tocsr())
|
|
1227
|
+
self._u0 = u0
|
|
1228
|
+
# One compilation-time binding covers the numerical model and physical
|
|
1229
|
+
# member data used by body action/recovery. Never hash the model per case.
|
|
1230
|
+
identity = sha256(b"B2_REFERENCE_BODY_LOAD_BINDING_V1_DEV")
|
|
1231
|
+
if self._modal_only:
|
|
1232
|
+
identity.update(reference_boundary_policy.encode("ascii"))
|
|
1233
|
+
identity.update(repr((shell_ids, beam_ids, tuple((i, tuple(positions[i])) for i in shell_ids), self._shell_formulations,
|
|
1234
|
+
tuple(point_laws), reference_inertia_policy)).encode("utf-8"))
|
|
1235
|
+
for matrix in (K, C, T):
|
|
1236
|
+
data = matrix.tocsr()
|
|
1237
|
+
identity.update(repr(data.shape).encode("ascii"))
|
|
1238
|
+
for array in (data.indptr, data.indices):
|
|
1239
|
+
identity.update(np.asarray(array, dtype="<i8").tobytes())
|
|
1240
|
+
identity.update(np.asarray(data.data, dtype="<f8").tobytes())
|
|
1241
|
+
identity.update(np.asarray(u0, dtype="<f8").tobytes())
|
|
1242
|
+
if body_map is not None:
|
|
1243
|
+
identity.update(np.asarray(body_map, dtype="<f8").tobytes())
|
|
1244
|
+
for member_id, (member, _) in self._members:
|
|
1245
|
+
identity.update(repr((member_id, member.definition.reference_points_m,
|
|
1246
|
+
member.definition.local_y_global, member.definition.section,
|
|
1247
|
+
member.definition.mass)).encode("utf-8"))
|
|
1248
|
+
for key, actions in sorted(self._section_point_actions.items()):
|
|
1249
|
+
identity.update(repr((key, tuple((i, s) for i, s, _ in actions))).encode("utf-8"))
|
|
1250
|
+
for _, _, pullback in actions:
|
|
1251
|
+
identity.update(np.asarray(pullback, dtype="<f8").tobytes())
|
|
1252
|
+
if point_trace_policy is not None:
|
|
1253
|
+
identity.update(point_trace_policy.encode("ascii"))
|
|
1254
|
+
for matrix in (H,):
|
|
1255
|
+
identity.update(np.asarray(matrix.indptr, dtype="<i8").tobytes())
|
|
1256
|
+
identity.update(np.asarray(matrix.indices, dtype="<i8").tobytes())
|
|
1257
|
+
identity.update(np.asarray(matrix.data, dtype="<f8").tobytes())
|
|
1258
|
+
for member_id, compiled_load in sorted(loaded_members.items()):
|
|
1259
|
+
identity.update(repr((member_id, compiled_load.point_indices,
|
|
1260
|
+
compiled_load.stations, compiled_load.offsets,
|
|
1261
|
+
compiled_load.quadrature_stations)).encode("utf-8"))
|
|
1262
|
+
for value in (compiled_load.maps.uniform_pose_map, compiled_load.maps.point_pose_map,
|
|
1263
|
+
compiled_load.maps.point_compliance, compiled_load.body_wrench_map,
|
|
1264
|
+
compiled_load.body_constraint_map, compiled_load.quadrature_weights,
|
|
1265
|
+
*compiled_load.port_constraints):
|
|
1266
|
+
identity.update(repr(value.shape).encode("ascii"))
|
|
1267
|
+
identity.update(np.asarray(value, dtype="<f8").tobytes())
|
|
1268
|
+
self._body_model_identity = identity.hexdigest()
|
|
1269
|
+
self._footprint_rigid_supported = (not footprints or
|
|
1270
|
+
(self._coordinate_modal_supports() and
|
|
1271
|
+
_supports_all_panel_rigid_fields(T, positions, beam_positions)))
|
|
1272
|
+
self._ku0 = np.asarray(K @ u0).reshape(-1)
|
|
1273
|
+
self._cu0 = np.asarray(C @ u0).reshape(-1)
|
|
1274
|
+
# Exact homogeneous singleton constraints prescribe a reduced
|
|
1275
|
+
# coordinate to zero. Store those substitutions once, rather than
|
|
1276
|
+
# allowing factor roundoff to turn a mathematically zero channel into
|
|
1277
|
+
# its own nonzero relative compatibility scale.
|
|
1278
|
+
self._physical_singletons = ()
|
|
1279
|
+
if self._physical_footprint_law and not self._modal_only:
|
|
1280
|
+
self._physical_singletons = self._structural_physical_singletons(C, T, u0)
|
|
1281
|
+
self._factor_scale = factor_scale
|
|
1282
|
+
self._handle = handle
|
|
1283
|
+
self._attachment_elimination = attachment_maps
|
|
1284
|
+
self._attachment_elimination_info = ({"status": "eligible", **attachment_maps.info}
|
|
1285
|
+
if attachment_maps is not None else
|
|
1286
|
+
{"status": "legacy_fallback", "reason": attachment_reason})
|
|
1287
|
+
# Retain the full original scaled operator on the physical route.
|
|
1288
|
+
# Eligible handles factorize its attachment reduction and recover full
|
|
1289
|
+
# answers; original checks/correction still use the same reused factors.
|
|
1290
|
+
self._physical_scaled_kkt = (scaled_kkt if self._physical_footprint_law
|
|
1291
|
+
and not self._modal_only else None)
|
|
1292
|
+
self._resolution_kkt = None
|
|
1293
|
+
self._affine_resolution_cache = None
|
|
1294
|
+
self._closed = False
|
|
1295
|
+
if diagnostic is not None:
|
|
1296
|
+
try: diagnostic.finish()
|
|
1297
|
+
except BaseException:
|
|
1298
|
+
try: self.close()
|
|
1299
|
+
except BaseException: pass
|
|
1300
|
+
raise
|
|
1301
|
+
|
|
1302
|
+
def _physical_equilibrium_ratio(self, answer, applied):
|
|
1303
|
+
"""Measure the existing per-row equilibrium check before recovery."""
|
|
1304
|
+
q, lam = answer[:-len(self._cu0)], answer[-len(self._cu0):]
|
|
1305
|
+
u = np.asarray(self._T @ q).reshape(-1)+self._u0
|
|
1306
|
+
force = np.asarray(self._K @ u).reshape(-1)
|
|
1307
|
+
constraint_force = np.asarray(self._C.T @ lam).reshape(-1)
|
|
1308
|
+
projected = self._project(force+constraint_force-applied)
|
|
1309
|
+
physical_force_scale = np.asarray(
|
|
1310
|
+
self._abs_K @ np.abs(u)+self._abs_Ct @ np.abs(lam)+
|
|
1311
|
+
np.abs(applied)).reshape(-1)
|
|
1312
|
+
equilibrium_scale = np.asarray(self._abs_Tt @ physical_force_scale).reshape(-1)
|
|
1313
|
+
by_node = physical_force_scale.reshape(-1, 6)
|
|
1314
|
+
physical_roundoff = np.tile(
|
|
1315
|
+
(float(np.max(by_node[:, :3])),)*3+
|
|
1316
|
+
(float(np.max(by_node[:, 3:])),)*3, len(by_node))
|
|
1317
|
+
projected_roundoff = np.asarray(self._abs_Tt @ physical_roundoff).reshape(-1)
|
|
1318
|
+
tolerance = (1e-9*equilibrium_scale+
|
|
1319
|
+
len(answer)*np.finfo(float).eps*projected_roundoff)
|
|
1320
|
+
if (not np.isfinite(projected).all() or
|
|
1321
|
+
not np.isfinite(tolerance).all() or np.any(tolerance < 0.)):
|
|
1322
|
+
raise _NumericalFailure("panel equilibrium correction is not representable")
|
|
1323
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
1324
|
+
ratios = np.divide(np.abs(projected), tolerance,
|
|
1325
|
+
out=np.full_like(projected, np.inf),
|
|
1326
|
+
where=tolerance > 0.)
|
|
1327
|
+
ratios[(tolerance == 0.) & (projected == 0.)] = 0.
|
|
1328
|
+
return float(np.max(ratios))
|
|
1329
|
+
|
|
1330
|
+
def _physical_work_ratio(self, answer, applied):
|
|
1331
|
+
"""Check one explicit physical load against homogeneous-constraint work."""
|
|
1332
|
+
if np.any(self._u0 != 0.):
|
|
1333
|
+
return 0.
|
|
1334
|
+
q = answer[:-len(self._cu0)]
|
|
1335
|
+
u = np.asarray(self._T @ q).reshape(-1)
|
|
1336
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1337
|
+
internal = float(u @ (self._K @ u))
|
|
1338
|
+
external = float(u @ applied)
|
|
1339
|
+
if not isfinite(internal) or not isfinite(external):
|
|
1340
|
+
raise _NumericalFailure("panel physical footprint work is not representable")
|
|
1341
|
+
defect = abs(internal-external)
|
|
1342
|
+
# The physical-footprint family admits no points, body-load driver or
|
|
1343
|
+
# nonzero constraint RHS. Keep the registered work absolute allowance.
|
|
1344
|
+
tolerance = 1e-12 + 1e-8*abs(external)
|
|
1345
|
+
return defect/tolerance
|
|
1346
|
+
|
|
1347
|
+
@staticmethod
|
|
1348
|
+
def _structural_physical_singletons(C, T, u0):
|
|
1349
|
+
"""Admit only a single product with individually zero offset terms.
|
|
1350
|
+
|
|
1351
|
+
Rounded cancellation in C@T or C@u0 never establishes eligibility.
|
|
1352
|
+
Coupled rows and even cancellation within one reduced column remain
|
|
1353
|
+
on the ordinary factor/guard route.
|
|
1354
|
+
"""
|
|
1355
|
+
constraints = C.tocsr(copy=True)
|
|
1356
|
+
transform = T.tocsr(copy=True)
|
|
1357
|
+
constraints.eliminate_zeros()
|
|
1358
|
+
transform.eliminate_zeros()
|
|
1359
|
+
singletons = []
|
|
1360
|
+
for row in range(constraints.shape[0]):
|
|
1361
|
+
start, end = constraints.indptr[row:row+2]
|
|
1362
|
+
physical = constraints.indices[start:end]
|
|
1363
|
+
if np.any(u0[physical] != 0.):
|
|
1364
|
+
continue
|
|
1365
|
+
products = []
|
|
1366
|
+
for index, coefficient in zip(physical, constraints.data[start:end]):
|
|
1367
|
+
first, last = transform.indptr[index:index+2]
|
|
1368
|
+
for column, value in zip(transform.indices[first:last], transform.data[first:last]):
|
|
1369
|
+
products.append((int(column), float(coefficient*value)))
|
|
1370
|
+
if len(products) > 1:
|
|
1371
|
+
break
|
|
1372
|
+
if len(products) > 1:
|
|
1373
|
+
break
|
|
1374
|
+
if len(products) == 1 and np.isfinite(products[0][1]) and products[0][1] != 0.:
|
|
1375
|
+
singletons.append((row, *products[0]))
|
|
1376
|
+
return tuple(singletons)
|
|
1377
|
+
|
|
1378
|
+
def _restore_physical_singletons(self, answer):
|
|
1379
|
+
"""Enforce exact zero coordinates only for roundoff-sized residues.
|
|
1380
|
+
|
|
1381
|
+
This changes the candidate, never a compatibility tolerance. Material
|
|
1382
|
+
errors remain for the existing equilibrium/correction/refusal checks.
|
|
1383
|
+
Translation and rotation amplitudes retain their separate units.
|
|
1384
|
+
"""
|
|
1385
|
+
if not self._physical_singletons:
|
|
1386
|
+
return answer
|
|
1387
|
+
q = answer[:-len(self._cu0)]
|
|
1388
|
+
u = np.asarray(self._T @ q).reshape(-1)+self._u0
|
|
1389
|
+
poses = np.abs(u).reshape(-1, 6)
|
|
1390
|
+
amplitude = np.tile((float(np.max(poses[:, :3])),)*3+
|
|
1391
|
+
(float(np.max(poses[:, 3:])),)*3, len(poses))
|
|
1392
|
+
bounds = len(answer)*np.finfo(float).eps*np.asarray(
|
|
1393
|
+
self._abs_C @ amplitude).reshape(-1)
|
|
1394
|
+
if not np.isfinite(bounds).all():
|
|
1395
|
+
raise _NumericalFailure("physical singleton substitution is not representable")
|
|
1396
|
+
restored = answer.copy()
|
|
1397
|
+
for row, column, coefficient in self._physical_singletons:
|
|
1398
|
+
if abs(coefficient*q[column]) <= bounds[row]:
|
|
1399
|
+
restored[column] = 0.
|
|
1400
|
+
return restored
|
|
1401
|
+
|
|
1402
|
+
def _physical_original_residual(self, answer, applied):
|
|
1403
|
+
"""Original affine/constraint covectors at the actual scored candidate.
|
|
1404
|
+
|
|
1405
|
+
Use the same original K/C/T and ordinary arithmetic as recovery; no
|
|
1406
|
+
rounded reduced/congruence matrix supplies this target. This private
|
|
1407
|
+
residual is used only by the physical footprint reference-static route.
|
|
1408
|
+
"""
|
|
1409
|
+
# Constructor admission excludes points and point_trace_policy, so H
|
|
1410
|
+
# and body/fixed-end constraint RHS d are absent on this route. Typed
|
|
1411
|
+
# body loads are refused before the sole solve_linear caller; batches
|
|
1412
|
+
# delegate to that caller. Refuse a corrupted/foreign private owner
|
|
1413
|
+
# rather than silently evaluating Cu=0 for [C,-H] or nonzero d.
|
|
1414
|
+
if (not self._physical_footprint_law or self.point_ids or
|
|
1415
|
+
self._point_trace_policy is not None or
|
|
1416
|
+
(self._H is not None and self._H.nnz != 0)):
|
|
1417
|
+
raise _NumericalFailure("original correction requires the separate physical footprint static family")
|
|
1418
|
+
if (answer.shape != self._factor_scale.shape or
|
|
1419
|
+
applied.shape != self._u0.shape or
|
|
1420
|
+
not np.isfinite(answer).all() or not np.isfinite(applied).all()):
|
|
1421
|
+
raise _NumericalFailure("finite original physical correction inputs required")
|
|
1422
|
+
nq = self._T.shape[1]
|
|
1423
|
+
q, lam = answer[:nq], answer[nq:]
|
|
1424
|
+
u = np.asarray(self._T @ q).reshape(-1)+self._u0
|
|
1425
|
+
reaction = np.asarray(self._K @ u).reshape(-1)+np.asarray(self._C.T @ lam).reshape(-1)-applied
|
|
1426
|
+
target = np.r_[self._project(reaction), np.asarray(self._C @ u).reshape(-1)]
|
|
1427
|
+
if not np.isfinite(u).all() or not np.isfinite(target).all():
|
|
1428
|
+
raise _NumericalFailure("original physical correction target is not representable")
|
|
1429
|
+
return target
|
|
1430
|
+
|
|
1431
|
+
def _correct_physical_equilibrium(self, scaled_answer, scaled_rhs, applied):
|
|
1432
|
+
"""One original-operator correction using the existing factorization.
|
|
1433
|
+
|
|
1434
|
+
``scaled_rhs`` remains in this private calling convention, but cannot
|
|
1435
|
+
define the original target. Restoration precedes both scoring and RHS
|
|
1436
|
+
formation; the correction is added to that same restored candidate.
|
|
1437
|
+
Physical equation/work/compatibility acceptance remains unchanged.
|
|
1438
|
+
"""
|
|
1439
|
+
answer = self._restore_physical_singletons(self._factor_scale*scaled_answer)
|
|
1440
|
+
original_ratio = self._physical_equilibrium_ratio(answer, applied)
|
|
1441
|
+
original_work_ratio = self._physical_work_ratio(answer, applied)
|
|
1442
|
+
original_score = max(original_ratio, original_work_ratio)
|
|
1443
|
+
if original_score <= 1.:
|
|
1444
|
+
return answer
|
|
1445
|
+
original_target = self._physical_original_residual(answer, applied)
|
|
1446
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1447
|
+
correction_rhs = -self._factor_scale*original_target
|
|
1448
|
+
if (not np.isfinite(correction_rhs).all() or
|
|
1449
|
+
np.any((original_target != 0.) & (correction_rhs == 0.))):
|
|
1450
|
+
raise _NumericalFailure("panel equilibrium correction is not representable")
|
|
1451
|
+
try:
|
|
1452
|
+
delta = np.asarray(self._handle.solve(correction_rhs), dtype=float)
|
|
1453
|
+
except RuntimeError as exc:
|
|
1454
|
+
raise _NumericalFailure("panel equilibrium correction failed: " + str(exc)) from exc
|
|
1455
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1456
|
+
candidate = answer+self._factor_scale*delta
|
|
1457
|
+
if candidate.shape != answer.shape or not np.isfinite(candidate).all():
|
|
1458
|
+
raise _NumericalFailure("finite panel equilibrium correction required")
|
|
1459
|
+
candidate = self._restore_physical_singletons(candidate)
|
|
1460
|
+
candidate_score = max(self._physical_equilibrium_ratio(candidate, applied),
|
|
1461
|
+
self._physical_work_ratio(candidate, applied))
|
|
1462
|
+
if candidate_score >= original_score:
|
|
1463
|
+
raise _NumericalFailure("panel equilibrium correction did not improve")
|
|
1464
|
+
return candidate
|
|
1465
|
+
|
|
1466
|
+
def _explicit_load_resolved(self, answer, rhs, explicit_rhs, *, applied=None, reconstructed=None) -> bool:
|
|
1467
|
+
"""Certify active stored KKT rows without a model-wide force floor.
|
|
1468
|
+
|
|
1469
|
+
Pure unit selectors retain the stored KKT certificate. Zero-offset
|
|
1470
|
+
footprint affine maps certify exact physical virtual equilibrium and
|
|
1471
|
+
the actual reconstruction error from original sparse operators/load.
|
|
1472
|
+
Nonzero offsets and loaded-point affine maps remain ineligible. This
|
|
1473
|
+
resolves net projected loads only, not cancelling individual actions.
|
|
1474
|
+
All existing physical equilibrium/compatibility checks still apply.
|
|
1475
|
+
"""
|
|
1476
|
+
if (np.any(self._u0 != 0.) or not np.isfinite(answer).all() or
|
|
1477
|
+
not np.isfinite(rhs).all()):
|
|
1478
|
+
return False
|
|
1479
|
+
selector = self._T.tocsc(copy=True)
|
|
1480
|
+
selector.eliminate_zeros()
|
|
1481
|
+
if (np.any(np.diff(selector.indptr) != 1) or
|
|
1482
|
+
np.any(selector.data != 1.) or
|
|
1483
|
+
len(np.unique(selector.indices)) != selector.shape[1]):
|
|
1484
|
+
if not self.footprints or self._point_trace_policy is not None or applied is None or reconstructed is None:
|
|
1485
|
+
return False
|
|
1486
|
+
if self._affine_resolution_cache is None:self._affine_resolution_cache = {}
|
|
1487
|
+
try:
|
|
1488
|
+
return certify_affine(self._K,self._C,self._T,answer,applied,reconstructed,self._affine_resolution_cache)
|
|
1489
|
+
except B2NumericalSolveFailure:
|
|
1490
|
+
return False
|
|
1491
|
+
active = np.flatnonzero(explicit_rhs > 0.)
|
|
1492
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1493
|
+
cap = np.nextafter(1e-9*explicit_rhs[active], 0.)
|
|
1494
|
+
if not len(active) or np.any(cap <= 0.) or not np.isfinite(cap).all():
|
|
1495
|
+
return False
|
|
1496
|
+
if self._resolution_kkt is None:
|
|
1497
|
+
columns = selector.indices
|
|
1498
|
+
rk = self._K.tocsr()[columns][:, columns]
|
|
1499
|
+
rc = self._C.tocsr()[:, columns]
|
|
1500
|
+
self._resolution_kkt = sparse.bmat(
|
|
1501
|
+
((rk, rc.T), (rc, None if self._point_trace_policy is None else -self._H)), format="csr")
|
|
1502
|
+
try:
|
|
1503
|
+
_, upper = _bounded_accurate_residual(
|
|
1504
|
+
self._resolution_kkt[active], answer[:, None], rhs[active, None])
|
|
1505
|
+
except B2NumericalSolveFailure:
|
|
1506
|
+
return False
|
|
1507
|
+
return bool(np.isfinite(upper).all() and np.all(upper[:, 0] <= cap))
|
|
1508
|
+
|
|
1509
|
+
def _project(self, values: np.ndarray) -> np.ndarray:
|
|
1510
|
+
return _checked_affine_projection(
|
|
1511
|
+
self._Tt, self._projection_multi, self._projection_single, values)
|
|
1512
|
+
|
|
1513
|
+
def _footprint_gap_tolerance(self, u, gap_scale, roundoff_fraction):
|
|
1514
|
+
"""Quiet-channel solve admission, bounded by reference compatibility.
|
|
1515
|
+
|
|
1516
|
+
Called only after explicit-load certification for unit selectors or
|
|
1517
|
+
proved general-affine footprint maps, with zero offsets. This is a
|
|
1518
|
+
solve/compatibility convention, not a bound on
|
|
1519
|
+
rounding in C@u. Translation and rotation amplitudes stay separate;
|
|
1520
|
+
actual row coefficients include eccentric lever arms. Historical point
|
|
1521
|
+
rows retain their original check. Every footprint row also stays below
|
|
1522
|
+
the independent reference-geometry 1e-9 compatibility cap.
|
|
1523
|
+
"""
|
|
1524
|
+
tolerance = 1e-9*gap_scale
|
|
1525
|
+
poses = np.abs(u).reshape(-1, 6)
|
|
1526
|
+
amplitude = np.tile((float(np.max(poses[:, :3])),)*3+
|
|
1527
|
+
(float(np.max(poses[:, 3:])),)*3, len(poses))
|
|
1528
|
+
allowance = roundoff_fraction*np.asarray(self._abs_C@amplitude).reshape(-1)
|
|
1529
|
+
if not np.isfinite(allowance).all():
|
|
1530
|
+
raise _NumericalFailure("footprint compatibility allowance is not representable")
|
|
1531
|
+
for item in self._footprint_incidence:
|
|
1532
|
+
width = getattr(item,"row_count",6)
|
|
1533
|
+
rows = slice(item.row_start, item.row_start+width)
|
|
1534
|
+
cap = 1e-9*np.array((self._compatibility_length,)*3+(1.,)*(width-3))
|
|
1535
|
+
tolerance[rows] = np.minimum(tolerance[rows]+allowance[rows], cap)
|
|
1536
|
+
return tolerance
|
|
1537
|
+
|
|
1538
|
+
@property
|
|
1539
|
+
def capabilities(self) -> ExperimentalB2Q4PanelCapabilities:
|
|
1540
|
+
if self._physical_footprint_law:
|
|
1541
|
+
return B2PhysicalRigidFootprintCapabilities(
|
|
1542
|
+
attachment="physical rigid-region translations and director tangents; five rows per selected node",
|
|
1543
|
+
operations=(("solve_linear","solve_linear_many","close") if self._has_s3 else ("solve_linear","solve_linear_many","buckling","close")),
|
|
1544
|
+
spectral_domain=("S3/mixed physical footprint modes and buckling unsupported" if self._has_s3 else "explicit physical-footprint conservative reference buckling request; modes unavailable without mass contract"),
|
|
1545
|
+
footprints=self.footprints)
|
|
1546
|
+
if self._modal_only:
|
|
1547
|
+
return ExperimentalB2Q4PanelCapabilities(
|
|
1548
|
+
operations=("modes", "close"),
|
|
1549
|
+
body_load_domain="unsupported by free-free modal-only policy",
|
|
1550
|
+
spectral_domain="experimental connected free-free reference descriptor; six verified physical rigid fields and selected finite flexible pairs; completeness unqualified")
|
|
1551
|
+
body_operations = (("body_acceleration_load",) if self._body_map is not None else ())
|
|
1552
|
+
body_domain = ("experimental reference-linear uniform global acceleration; typed loads give separate equilibrated section fields including interior point actions; raw vectors retain nodal constitutive recovery; loaded interior pose and finite gravity unsupported"
|
|
1553
|
+
if self._body_map is not None else "unavailable without complete physical density/mass")
|
|
1554
|
+
if self._point_trace_policy is not None:
|
|
1555
|
+
return ExperimentalB2LoadedPointCapabilities(
|
|
1556
|
+
attachment="explicit fixed-end loaded finite-vertex point trace",
|
|
1557
|
+
operations=("solve_linear", "solve_linear_many", "close") + body_operations,
|
|
1558
|
+
body_load_domain=("typed reference body acceleration and multiplier-dependent fixed-end bubbles; "
|
|
1559
|
+
"raw arrays are nodal loads; finite gravity unsupported" if self._body_map is not None
|
|
1560
|
+
else body_domain))
|
|
1561
|
+
if self.footprints:
|
|
1562
|
+
admitted = (self._M is not None and self._coordinate_modal_supports()
|
|
1563
|
+
and not np.any(self._u0 != 0.) and self._footprint_rigid_supported)
|
|
1564
|
+
return ExperimentalB2Q4FootprintCapabilities(
|
|
1565
|
+
attachment="explicit reference-linear selected-node rigid footprints and finite-vertex points",
|
|
1566
|
+
operations=(("solve_linear", "solve_linear_many", "modes", "close")
|
|
1567
|
+
if admitted else ("solve_linear", "solve_linear_many", "close")) + body_operations,
|
|
1568
|
+
body_load_domain=body_domain,
|
|
1569
|
+
spectral_domain=("experimental virgin supported reference modes; normal-spin locked; "
|
|
1570
|
+
"mechanical qualification incomplete; prestress unsupported"
|
|
1571
|
+
if admitted else
|
|
1572
|
+
"reference modes require complete physical mass, coordinate supports detecting all six rigid fields and zero offset; "
|
|
1573
|
+
"normal-spin locked; mechanical qualification incomplete; prestress unsupported"),
|
|
1574
|
+
footprints=self.footprints)
|
|
1575
|
+
if self._M is None:
|
|
1576
|
+
return ExperimentalB2Q4PanelCapabilities(
|
|
1577
|
+
operations=("solve_linear", "solve_linear_many", "close") + body_operations,
|
|
1578
|
+
body_load_domain=body_domain)
|
|
1579
|
+
return ExperimentalB2Q4PanelCapabilities(
|
|
1580
|
+
operations=("solve_linear", "solve_linear_many", "modes", "close") + body_operations,
|
|
1581
|
+
body_load_domain=body_domain,
|
|
1582
|
+
spectral_domain="experimental reference B2/Q4 finite pairs with coordinate supports; completeness unqualified")
|
|
1583
|
+
|
|
1584
|
+
def body_acceleration_load(self, acceleration_m_s2) -> B2ReferenceBodyLoad:
|
|
1585
|
+
"""Prepare an owned dead-load vector without assembling or factoring.
|
|
1586
|
+
|
|
1587
|
+
f = integral D.T rho a uses the compiled physical trace and mass origin.
|
|
1588
|
+
Pass ``result.load`` to the existing static or compatible batch route.
|
|
1589
|
+
"""
|
|
1590
|
+
if self._closed:
|
|
1591
|
+
raise ExperimentalB2Q4PanelError("experimental panel is closed")
|
|
1592
|
+
if self._modal_only:
|
|
1593
|
+
raise ValueError("free-free modal-only policy does not admit body loads")
|
|
1594
|
+
if self._body_map is None:
|
|
1595
|
+
raise ValueError("complete physical density/mass required for reference body load")
|
|
1596
|
+
raw = np.asarray(acceleration_m_s2, dtype=object)
|
|
1597
|
+
if (raw.shape != (3,) or any(isinstance(v, (bool, np.bool_)) or
|
|
1598
|
+
not isinstance(v, Real) for v in raw.flat)):
|
|
1599
|
+
raise ValueError("three finite real global acceleration components required")
|
|
1600
|
+
try:
|
|
1601
|
+
acceleration = np.array(raw, dtype=float, copy=True)
|
|
1602
|
+
except (ValueError, TypeError, OverflowError) as exc:
|
|
1603
|
+
raise ValueError("three finite real global acceleration components required") from exc
|
|
1604
|
+
if not np.isfinite(acceleration).all():
|
|
1605
|
+
raise ValueError("three finite real global acceleration components required")
|
|
1606
|
+
with np.errstate(over="ignore", invalid="ignore", under="ignore"):
|
|
1607
|
+
applied = self._body_map@acceleration
|
|
1608
|
+
if not np.isfinite(applied).all():
|
|
1609
|
+
raise RepresentabilityError("reference body acceleration load is not representable")
|
|
1610
|
+
# A finite vector can still lose a required force through underflow or
|
|
1611
|
+
# cancellation. Test rigid-translation work against integrated physical
|
|
1612
|
+
# mass without adding an absolute load floor or changing solver criteria.
|
|
1613
|
+
for axis, component in enumerate(acceleration):
|
|
1614
|
+
if component == 0.:
|
|
1615
|
+
continue
|
|
1616
|
+
expected = self._body_mass*float(component)
|
|
1617
|
+
try:
|
|
1618
|
+
actual = fsum(float(v) for v in applied[axis::6])
|
|
1619
|
+
except OverflowError as exc:
|
|
1620
|
+
raise RepresentabilityError("reference body force is not representable") from exc
|
|
1621
|
+
if (not isfinite(expected) or expected == 0. or not isfinite(actual)
|
|
1622
|
+
or abs(actual/expected-1.) > 1e-9):
|
|
1623
|
+
raise RepresentabilityError("required reference body force is not representable")
|
|
1624
|
+
return B2ReferenceBodyLoad(tuple(float(v) for v in acceleration),
|
|
1625
|
+
self.shell_node_ids, self.beam_node_ids, applied,
|
|
1626
|
+
self._body_model_identity)
|
|
1627
|
+
|
|
1628
|
+
def _body_load_snapshot(self, load):
|
|
1629
|
+
if isinstance(load, B2ReferenceBodyLoad):
|
|
1630
|
+
if (load.model_identity != self._body_model_identity or
|
|
1631
|
+
load.shell_node_ids != self.shell_node_ids or load.beam_node_ids != self.beam_node_ids):
|
|
1632
|
+
raise ValueError("body load belongs to a different compiled model")
|
|
1633
|
+
expected = self.body_acceleration_load(load.acceleration_m_s2)
|
|
1634
|
+
if not np.array_equal(load.load, expected.load):
|
|
1635
|
+
raise ValueError("body load values disagree with its physical acceleration")
|
|
1636
|
+
snapshot, fingerprint = _load_value_snapshot(load.load, self._u0.shape)
|
|
1637
|
+
metadata = sha256(fingerprint)
|
|
1638
|
+
metadata.update(load.model_identity.encode("utf-8"))
|
|
1639
|
+
metadata.update(repr((load.shell_node_ids, load.beam_node_ids)).encode("utf-8"))
|
|
1640
|
+
metadata.update(np.asarray(load.acceleration_m_s2, dtype="<f8").tobytes())
|
|
1641
|
+
return snapshot, metadata.digest()
|
|
1642
|
+
return _load_value_snapshot(load, self._u0.shape)
|
|
1643
|
+
|
|
1644
|
+
def _coordinate_modal_supports(self) -> bool:
|
|
1645
|
+
selector = self._T.copy()
|
|
1646
|
+
selector.eliminate_zeros()
|
|
1647
|
+
return not (np.any(np.diff(selector.tocsc().indptr) != 1) or
|
|
1648
|
+
np.any(np.diff(selector.indptr) > 1))
|
|
1649
|
+
|
|
1650
|
+
def _reference_modes_result(self, status, frequencies=(), physical=(),
|
|
1651
|
+
multipliers=(), equations=(), constraints=(),
|
|
1652
|
+
factorization_count=0, solve_count=0,
|
|
1653
|
+
failure_reason=None):
|
|
1654
|
+
args = (status, frequencies, physical, multipliers, equations, constraints,
|
|
1655
|
+
factorization_count, solve_count, failure_reason)
|
|
1656
|
+
if not self.footprints:
|
|
1657
|
+
return B2ReferenceModesResult(*args)
|
|
1658
|
+
components = ("ux", "uy", "uz", "rx", "ry", "rz")
|
|
1659
|
+
provenance = [B2ModalConstraintRow(
|
|
1660
|
+
6*i+j, component, point_id=point_id,
|
|
1661
|
+
coordinate_frame=("point_master_shell_reference" if j < 3
|
|
1662
|
+
else "point_beam_reference"),
|
|
1663
|
+
reference_frame_columns=self._point_reference_frames[i][0 if j < 3 else 1])
|
|
1664
|
+
for i, point_id in enumerate(self.point_ids)
|
|
1665
|
+
for j, component in enumerate(components)]
|
|
1666
|
+
for item in self._footprint_incidence:
|
|
1667
|
+
provenance.extend(B2ModalConstraintRow(
|
|
1668
|
+
item.row_start+j, component, footprint_id=item.footprint_id,
|
|
1669
|
+
shell_node_id=item.shell_node, beam_node_id=item.beam_node)
|
|
1670
|
+
for j, component in enumerate(components))
|
|
1671
|
+
point_modes = (tuple(mode[:6*len(self.point_ids)] for mode in multipliers)
|
|
1672
|
+
if self.point_ids else ())
|
|
1673
|
+
return B2FootprintReferenceModesResult(
|
|
1674
|
+
status, frequencies, physical, point_modes, equations, constraints,
|
|
1675
|
+
factorization_count, solve_count, failure_reason,
|
|
1676
|
+
footprint_ids=self.footprint_ids,
|
|
1677
|
+
constraint_multiplier_modes=multipliers, constraint_rows=tuple(provenance))
|
|
1678
|
+
|
|
1679
|
+
@property
|
|
1680
|
+
def physical_footprint_buckling_capabilities(self):
|
|
1681
|
+
from ._stiffened_shell_b2_reference_buckling import physical_footprint_capabilities
|
|
1682
|
+
return physical_footprint_capabilities(self)
|
|
1683
|
+
|
|
1684
|
+
@property
|
|
1685
|
+
def reference_buckling_capabilities(self):
|
|
1686
|
+
from ._stiffened_shell_b2_reference_buckling import capabilities
|
|
1687
|
+
return capabilities(self)
|
|
1688
|
+
|
|
1689
|
+
def buckling(self, request, *, cancellation=None):
|
|
1690
|
+
"""Explicit experimental reference-preload conservative spectral route."""
|
|
1691
|
+
from ._stiffened_shell_b2_reference_buckling import solve
|
|
1692
|
+
return solve(self, request, cancellation=cancellation)
|
|
1693
|
+
|
|
1694
|
+
def modes(self, request: B2ReferenceModesRequest, *,
|
|
1695
|
+
cancellation: CancellationToken | None = None
|
|
1696
|
+
) -> B2ReferenceModesResult | B2FreeReferenceModesResult:
|
|
1697
|
+
"""Sparse descriptor modes with physical inertia and massless multipliers.
|
|
1698
|
+
|
|
1699
|
+
Reuse compiled K/C/T and mass, never a global nullspace or dense inverse.
|
|
1700
|
+
A shifted general inverse-product is not Euclidean symmetric, so use
|
|
1701
|
+
eigs rather than eigsh. Explicit free-free owners project verified rigid
|
|
1702
|
+
fields in the physical mass metric; loaded/prestressed modes remain out.
|
|
1703
|
+
"""
|
|
1704
|
+
if self._physical_footprint_law:
|
|
1705
|
+
raise ValueError("physical footprint reference modes require a separately adopted mass contract")
|
|
1706
|
+
if self._closed:
|
|
1707
|
+
raise ExperimentalB2Q4PanelError("experimental panel is closed")
|
|
1708
|
+
if self._point_trace_policy is not None:
|
|
1709
|
+
raise ValueError("loaded point trace reference modes unsupported: shape inertia unverified")
|
|
1710
|
+
if self._has_s3 and self._reference_inertia_policy is None:
|
|
1711
|
+
raise ValueError("native S3 V2D physical mass and reference modes are deferred")
|
|
1712
|
+
if type(request) is not B2ReferenceModesRequest:
|
|
1713
|
+
raise ValueError("typed reference-mode request required")
|
|
1714
|
+
if self._M is None:
|
|
1715
|
+
raise ValueError("explicit shell density and beam physical mass required")
|
|
1716
|
+
if not self._coordinate_modal_supports():
|
|
1717
|
+
raise ValueError("reference modes currently require coordinate supports without MPCs")
|
|
1718
|
+
if self.footprints and np.any(self._u0 != 0.):
|
|
1719
|
+
raise ValueError("virgin nodal-footprint reference modes require zero prescribed offset")
|
|
1720
|
+
if self.footprints and not self._footprint_rigid_supported:
|
|
1721
|
+
raise ValueError("nodal-footprint reference modes require coordinate supports detecting all six physical rigid fields; free-free and under-supported modes unsupported")
|
|
1722
|
+
if cancellation is not None and not isinstance(cancellation, CancellationToken):
|
|
1723
|
+
raise ValueError("CancellationToken required")
|
|
1724
|
+
n = self._T.shape[1]
|
|
1725
|
+
rows = self._C.shape[0]
|
|
1726
|
+
if request.count >= min(n-rows-(6 if self._modal_only else 0), n+rows-1):
|
|
1727
|
+
raise ValueError("reference mode count exceeds supported sparse spectrum")
|
|
1728
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1729
|
+
sigma = -float(np.square(2.*np.pi*np.float64(request.shift_hz)))
|
|
1730
|
+
if not np.isfinite(sigma) or sigma == 0.:
|
|
1731
|
+
raise ValueError("reference mode shift is not representable")
|
|
1732
|
+
|
|
1733
|
+
def safe_point(stage):
|
|
1734
|
+
if cancellation is not None:
|
|
1735
|
+
cancellation.checkpoint(stage)
|
|
1736
|
+
|
|
1737
|
+
handle = None
|
|
1738
|
+
rigid = None
|
|
1739
|
+
overlap = 0.
|
|
1740
|
+
|
|
1741
|
+
def finish(*args, **kwargs):
|
|
1742
|
+
flexible = self._reference_modes_result(*args, **kwargs)
|
|
1743
|
+
if not self._modal_only:
|
|
1744
|
+
return flexible
|
|
1745
|
+
data = {}
|
|
1746
|
+
if flexible.status == "ok":
|
|
1747
|
+
data = dict(rigid_origin_m=rigid["origin"],
|
|
1748
|
+
canonical_rigid_modes=tuple(tuple(map(float, x)) for x in rigid["canonical"].T),
|
|
1749
|
+
rigid_modes=tuple(tuple(map(float, x)) for x in rigid["normalized"].T),
|
|
1750
|
+
rigid_multiplier_modes=tuple((0.,)*rows for _ in range(6)),
|
|
1751
|
+
rigid_kinetic_gram=tuple(tuple(map(float, x)) for x in rigid["gram"]),
|
|
1752
|
+
rigid_equation_residuals=rigid["equations"],
|
|
1753
|
+
rigid_constraint_residuals=rigid["gaps"],
|
|
1754
|
+
rigid_flexible_mass_overlap=overlap)
|
|
1755
|
+
return B2FreeReferenceModesResult(flexible.status, flexible,
|
|
1756
|
+
spectral_shift_rad2_s2=sigma, failure_reason=flexible.failure_reason, **data)
|
|
1757
|
+
|
|
1758
|
+
try:
|
|
1759
|
+
safe_point("reference modes before factorization")
|
|
1760
|
+
k = (self._T.T@self._K@self._T).tocsc()
|
|
1761
|
+
m = (self._T.T@self._M@self._T).tocsc()
|
|
1762
|
+
c = (self._C@self._T).tocsc()
|
|
1763
|
+
a = sparse.bmat(((k, c.T), (c, None)), format="csc")
|
|
1764
|
+
b = sparse.block_diag((m, sparse.csc_matrix((rows, rows))), format="csc")
|
|
1765
|
+
try:
|
|
1766
|
+
shifted, scale = _scaled_point_kkt(k-sigma*m, c)
|
|
1767
|
+
except ExperimentalB2Q4PanelError as exc:
|
|
1768
|
+
raise _NumericalFailure("reference modal scaling failed: "+str(exc)) from exc
|
|
1769
|
+
scaling = sparse.diags(scale, format="csc")
|
|
1770
|
+
bs = (scaling@b@scaling).tocsc()
|
|
1771
|
+
b.eliminate_zeros(); b.sort_indices()
|
|
1772
|
+
bs.eliminate_zeros(); bs.sort_indices()
|
|
1773
|
+
if (not np.isfinite(bs.data).all() or b.nnz != bs.nnz or
|
|
1774
|
+
not np.array_equal(b.indptr, bs.indptr) or
|
|
1775
|
+
not np.array_equal(b.indices, bs.indices) or
|
|
1776
|
+
np.any((b.data != 0.) & (bs.data == 0.))):
|
|
1777
|
+
raise _NumericalFailure("reference modal mass scaling is not representable")
|
|
1778
|
+
if self._modal_only:
|
|
1779
|
+
rigid = _free_b2_rigid_space(self, scale, bs, safe_point)
|
|
1780
|
+
|
|
1781
|
+
def project(vector):
|
|
1782
|
+
if not self._modal_only:
|
|
1783
|
+
return vector
|
|
1784
|
+
return vector-rigid["scaled"]@(rigid["scaled"].T@(bs@vector))
|
|
1785
|
+
|
|
1786
|
+
handle = factorize_cached(shifted, MatrixClass.GENERAL)
|
|
1787
|
+
safe_point("reference modes after factorization")
|
|
1788
|
+
if handle.status != "ok":
|
|
1789
|
+
raise _NumericalFailure("reference modal factorization failed: "+str(handle.failure_reason))
|
|
1790
|
+
|
|
1791
|
+
def action(vector):
|
|
1792
|
+
safe_point("reference modal iteration")
|
|
1793
|
+
try:
|
|
1794
|
+
answer = np.asarray(handle.solve(bs@project(vector)), dtype=float)
|
|
1795
|
+
except RuntimeError as exc:
|
|
1796
|
+
# These are explicit numerical failures of the shared
|
|
1797
|
+
# backend. Unclassified/programming errors still propagate.
|
|
1798
|
+
if (str(exc) == "Sparse solve produced NaN/Inf values" or
|
|
1799
|
+
str(exc).startswith("Cannot solve with failed factorization:")):
|
|
1800
|
+
raise _NumericalFailure("reference modal sparse solve failed: "+str(exc)) from exc
|
|
1801
|
+
raise
|
|
1802
|
+
if not np.isfinite(answer).all():
|
|
1803
|
+
raise _NumericalFailure("finite reference modal inverse action required")
|
|
1804
|
+
safe_point("reference modal iteration completed")
|
|
1805
|
+
return project(answer)
|
|
1806
|
+
|
|
1807
|
+
operator = LinearOperator(a.shape, matvec=action, dtype=float)
|
|
1808
|
+
values, vectors = eigs(operator, k=request.count, which="LM",
|
|
1809
|
+
v0=np.sin(np.arange(a.shape[0])+.23),
|
|
1810
|
+
tol=1e-11, maxiter=request.max_iterations)
|
|
1811
|
+
safe_point("reference modes after eigensolve")
|
|
1812
|
+
abs_a, abs_b, abs_c = abs(a), abs(b), abs(c)
|
|
1813
|
+
recovered = []
|
|
1814
|
+
for mu, y in zip(values, vectors.T):
|
|
1815
|
+
if (not np.isfinite(mu) or abs(mu.imag) > 1e-9*abs(mu.real) or
|
|
1816
|
+
mu.real <= 0. or not np.isfinite(y).all() or
|
|
1817
|
+
np.linalg.norm(y.imag) > 1e-9*np.linalg.norm(y.real)):
|
|
1818
|
+
raise _NumericalFailure("nonphysical or unresolved finite modal pair")
|
|
1819
|
+
lam = sigma+1./mu.real
|
|
1820
|
+
if self._modal_only:
|
|
1821
|
+
# Resolution screen, not a certified eigenvalue error bound.
|
|
1822
|
+
screen = 64*np.finfo(float).eps*a.shape[0]*(abs(sigma)+abs(1./mu.real))
|
|
1823
|
+
if not np.isfinite(screen) or lam <= screen:
|
|
1824
|
+
raise _NumericalFailure("extra mechanism or unresolved near-zero flexible pair")
|
|
1825
|
+
x = scale*y.real
|
|
1826
|
+
mass_norm = float(x@b@x)
|
|
1827
|
+
if not np.isfinite(lam) or lam <= 0. or not np.isfinite(mass_norm) or mass_norm <= 0.:
|
|
1828
|
+
raise _NumericalFailure("positive finite supported mode and physical inertia required")
|
|
1829
|
+
x /= np.sqrt(mass_norm)
|
|
1830
|
+
physical = np.asarray(self._T@x[:n])
|
|
1831
|
+
if not np.isfinite(x).all() or not np.isfinite(physical).all():
|
|
1832
|
+
raise _NumericalFailure("physical mode normalization is not representable")
|
|
1833
|
+
residual = a@x-lam*(b@x)
|
|
1834
|
+
bound = abs_a@abs(x)+abs(lam)*(abs_b@abs(x))
|
|
1835
|
+
equation = _modal_scaled_residual(residual, bound, scale)
|
|
1836
|
+
constraint = _modal_scaled_residual(c@x[:n], abs_c@abs(x[:n]), scale[n:])
|
|
1837
|
+
physical_gap = _modal_scaled_residual(
|
|
1838
|
+
self._C@physical, abs(self._C)@abs(physical), scale[n:])
|
|
1839
|
+
physical_balance = self._K@physical+self._C.T@x[n:]-lam*(self._M@physical)
|
|
1840
|
+
physical_bound = (abs(self._K)@abs(physical)+abs(self._C.T)@abs(x[n:])+
|
|
1841
|
+
abs(lam)*(abs(self._M)@abs(physical)))
|
|
1842
|
+
physical_equation = _modal_scaled_residual(
|
|
1843
|
+
self._project(physical_balance), abs(self._T.T)@physical_bound, scale[:n])
|
|
1844
|
+
equation = max(equation, physical_equation)
|
|
1845
|
+
constraint = max(constraint, physical_gap)
|
|
1846
|
+
if equation > 1e-8 or constraint > 1e-9:
|
|
1847
|
+
raise _NumericalFailure("reference modal equation or constraint residual failed")
|
|
1848
|
+
recovered.append((float(np.sqrt(lam)/(2.*np.pi)),
|
|
1849
|
+
tuple(physical), tuple(x[n:]),
|
|
1850
|
+
equation, constraint))
|
|
1851
|
+
recovered.sort(key=lambda mode: mode[0])
|
|
1852
|
+
primal = np.array([mode[1] for mode in recovered]).T
|
|
1853
|
+
modal_gram = primal.T@self._M@primal
|
|
1854
|
+
if (not np.isfinite(modal_gram).all() or
|
|
1855
|
+
np.max(abs(modal_gram-np.eye(request.count))) > 1e-7):
|
|
1856
|
+
raise _NumericalFailure("modal physical-mass orthogonality failed")
|
|
1857
|
+
if self._modal_only:
|
|
1858
|
+
overlap = float(np.max(abs(rigid["normalized"].T@self._M@primal)))
|
|
1859
|
+
if not np.isfinite(overlap) or overlap > 1e-7:
|
|
1860
|
+
raise _NumericalFailure("rigid/flexible physical-mass overlap failed")
|
|
1861
|
+
safe_point("reference modes before publication")
|
|
1862
|
+
return finish(
|
|
1863
|
+
"ok", tuple(mode[0] for mode in recovered),
|
|
1864
|
+
tuple(mode[1] for mode in recovered), tuple(mode[2] for mode in recovered),
|
|
1865
|
+
tuple(mode[3] for mode in recovered), tuple(mode[4] for mode in recovered),
|
|
1866
|
+
handle.factorization_count, handle.solve_count)
|
|
1867
|
+
except SolveCancelled as exc:
|
|
1868
|
+
return finish(
|
|
1869
|
+
"cancelled", factorization_count=handle.factorization_count if handle else 0,
|
|
1870
|
+
solve_count=handle.solve_count if handle else 0, failure_reason=str(exc))
|
|
1871
|
+
except (ArpackError, _NumericalFailure) as exc:
|
|
1872
|
+
return finish(
|
|
1873
|
+
"failed", factorization_count=handle.factorization_count if handle else 0,
|
|
1874
|
+
solve_count=handle.solve_count if handle else 0, failure_reason=str(exc))
|
|
1875
|
+
finally:
|
|
1876
|
+
# Do not retain a numerical factor through an exception traceback.
|
|
1877
|
+
handle = None
|
|
1878
|
+
|
|
1879
|
+
def _panel_result(self, *args, **kwargs):
|
|
1880
|
+
if self._physical_footprint_law:
|
|
1881
|
+
return B2PhysicalRigidFootprintResult(*args,footprint_ids=self.footprint_ids,**kwargs)
|
|
1882
|
+
if self._point_trace_policy is not None:
|
|
1883
|
+
return B2LoadedPointPanelResult(*args, **kwargs)
|
|
1884
|
+
if "body_model_identity" in kwargs:
|
|
1885
|
+
result_type = B2BodyLoadedFootprintResult if self.footprints else B2BodyLoadedPanelResult
|
|
1886
|
+
if self.footprints:
|
|
1887
|
+
kwargs["footprint_ids"] = self.footprint_ids
|
|
1888
|
+
return result_type(*args, **kwargs)
|
|
1889
|
+
if self.footprints:
|
|
1890
|
+
return ExperimentalB2Q4FootprintResult(
|
|
1891
|
+
*args, footprint_ids=self.footprint_ids, **kwargs)
|
|
1892
|
+
return ExperimentalB2Q4PanelResult(*args, **kwargs)
|
|
1893
|
+
|
|
1894
|
+
def solve_linear(self, load, *, recover_members=None,
|
|
1895
|
+
selected_footprints: B2NodalRigidFootprintSelection | B2PhysicalRigidFootprintSelection | None = None
|
|
1896
|
+
) -> ExperimentalB2Q4PanelResult:
|
|
1897
|
+
if self._closed:
|
|
1898
|
+
raise ExperimentalB2Q4PanelError("experimental panel is closed")
|
|
1899
|
+
if self._modal_only:
|
|
1900
|
+
raise ValueError("free-free modal-only policy does not admit static solves")
|
|
1901
|
+
selector = checked_physical_selection if self._physical_footprint_law else checked_selection
|
|
1902
|
+
chosen_footprints = selector(selected_footprints, self.footprints)
|
|
1903
|
+
if self._physical_footprint_law and isinstance(load,B2ReferenceBodyLoad):
|
|
1904
|
+
raise ValueError("physical footprint body-load driver not admitted")
|
|
1905
|
+
recovery = _checked_recovery(
|
|
1906
|
+
recover_members, {member_id for member_id, _ in self._members})
|
|
1907
|
+
try:
|
|
1908
|
+
applied, _ = self._body_load_snapshot(load)
|
|
1909
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
1910
|
+
if isinstance(load, B2ReferenceBodyLoad):
|
|
1911
|
+
raise
|
|
1912
|
+
raise ValueError("finite panel load vector required") from exc
|
|
1913
|
+
if applied.shape != self._u0.shape or not np.isfinite(applied).all():
|
|
1914
|
+
raise ValueError("finite panel load vector required")
|
|
1915
|
+
acceleration = load.acceleration_m_s2 if isinstance(load, B2ReferenceBodyLoad) else None
|
|
1916
|
+
body_gap = body_gap_scale = None
|
|
1917
|
+
try:
|
|
1918
|
+
rhs = np.r_[self._project(applied-self._ku0), -self._cu0]
|
|
1919
|
+
if self._point_trace_policy is not None:
|
|
1920
|
+
body_gap = np.zeros(len(self._cu0))
|
|
1921
|
+
body_gap_scale = np.zeros(len(self._cu0))
|
|
1922
|
+
for compiled_load in self._loaded_members.values():
|
|
1923
|
+
values, scales = compiled_load.body_constraint(acceleration)
|
|
1924
|
+
slots = [6*index+channel for index in compiled_load.point_indices for channel in range(6)]
|
|
1925
|
+
body_gap[slots] = values
|
|
1926
|
+
body_gap_scale[slots] = scales
|
|
1927
|
+
rhs[-len(self._cu0):] -= body_gap
|
|
1928
|
+
except (_NumericalFailure, RepresentabilityError) as exc:
|
|
1929
|
+
return self._panel_result(
|
|
1930
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
1931
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
1932
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
1933
|
+
scaled_rhs = self._factor_scale*rhs
|
|
1934
|
+
if (not np.isfinite(scaled_rhs).all() or
|
|
1935
|
+
np.any((rhs != 0.) & (scaled_rhs == 0.))):
|
|
1936
|
+
return self._panel_result(
|
|
1937
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
1938
|
+
solve_count=self._handle.solve_count,
|
|
1939
|
+
failure_reason="panel right-hand side scaling is not representable")
|
|
1940
|
+
try:
|
|
1941
|
+
scaled_answer = np.asarray(self._handle.solve(scaled_rhs), dtype=float)
|
|
1942
|
+
answer = self._factor_scale*scaled_answer
|
|
1943
|
+
if self._physical_footprint_law and answer.shape == rhs.shape and np.isfinite(answer).all():
|
|
1944
|
+
answer = self._correct_physical_equilibrium(
|
|
1945
|
+
scaled_answer, scaled_rhs, applied)
|
|
1946
|
+
if self._physical_work_ratio(answer, applied) > 1.:
|
|
1947
|
+
raise _NumericalFailure("panel physical footprint work consistency failed")
|
|
1948
|
+
except (RuntimeError, _NumericalFailure) as exc:
|
|
1949
|
+
return self._panel_result(
|
|
1950
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
1951
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
1952
|
+
try:
|
|
1953
|
+
if answer.shape != rhs.shape or not np.isfinite(answer).all():
|
|
1954
|
+
raise _NumericalFailure("finite panel solution required")
|
|
1955
|
+
q, lam = answer[:-len(self._cu0)], answer[-len(self._cu0):]
|
|
1956
|
+
u = np.asarray(self._T @ q).reshape(-1)+self._u0
|
|
1957
|
+
if not np.isfinite(u).all():
|
|
1958
|
+
raise _NumericalFailure("finite panel solution required")
|
|
1959
|
+
gap = np.asarray(self._C @ u).reshape(-1)
|
|
1960
|
+
compliance_gap_scale = None
|
|
1961
|
+
if self._point_trace_policy is not None:
|
|
1962
|
+
compliance_gap, compliance_gap_scale = checked_sparse_action(self._H, lam)
|
|
1963
|
+
gap = gap+body_gap-compliance_gap
|
|
1964
|
+
force = np.asarray(self._K @ u).reshape(-1)
|
|
1965
|
+
constraint_force = np.asarray(self._C.T @ lam).reshape(-1)
|
|
1966
|
+
reaction = force+constraint_force-applied
|
|
1967
|
+
projected = self._project(reaction)
|
|
1968
|
+
# Each row uses the absolute contribution sum in its own physical
|
|
1969
|
+
# work unit. A 1 N floor would mask a failed micro-/nano-load;
|
|
1970
|
+
# a single mixed force/moment norm would depend on chosen units.
|
|
1971
|
+
gap_scale = np.asarray(self._abs_C @ np.abs(u)).reshape(-1)
|
|
1972
|
+
if self._point_trace_policy is not None:
|
|
1973
|
+
gap_scale += body_gap_scale+compliance_gap_scale
|
|
1974
|
+
physical_force_scale = np.asarray(
|
|
1975
|
+
self._abs_K @ np.abs(u)+self._abs_Ct @ np.abs(lam)+
|
|
1976
|
+
np.abs(applied)).reshape(-1)
|
|
1977
|
+
equilibrium_scale = np.asarray(
|
|
1978
|
+
self._abs_Tt @ physical_force_scale).reshape(-1)
|
|
1979
|
+
# Sparse factorization can leave roundoff in a locally quiet DOF.
|
|
1980
|
+
# Its allowance follows the current force and moment magnitudes,
|
|
1981
|
+
# separately, then maps through the affine transpose; it has no
|
|
1982
|
+
# fixed N, Nm, m or rad floor.
|
|
1983
|
+
by_node = physical_force_scale.reshape(-1, 6)
|
|
1984
|
+
force_roundoff = float(np.max(by_node[:, :3]))
|
|
1985
|
+
moment_roundoff = float(np.max(by_node[:, 3:]))
|
|
1986
|
+
physical_roundoff = np.tile(
|
|
1987
|
+
(force_roundoff,)*3+(moment_roundoff,)*3,
|
|
1988
|
+
len(by_node))
|
|
1989
|
+
projected_roundoff = np.asarray(
|
|
1990
|
+
self._abs_Tt @ physical_roundoff).reshape(-1)
|
|
1991
|
+
roundoff_fraction = len(rhs)*np.finfo(float).eps
|
|
1992
|
+
if roundoff_fraction >= 1e-9:
|
|
1993
|
+
raise _NumericalFailure("panel equilibrium resolution insufficient")
|
|
1994
|
+
explicit_rhs = np.abs(self._project(applied))
|
|
1995
|
+
resolution_floor = roundoff_fraction*projected_roundoff
|
|
1996
|
+
# Prescribed offsets can induce reduced loads much smaller than
|
|
1997
|
+
# the model-wide reaction scale. Their physical effect is checked
|
|
1998
|
+
# by the full equilibrium/compatibility test below. This extra
|
|
1999
|
+
# small-load resolution guard applies to explicit applied work,
|
|
2000
|
+
# so a superposed tiny external load cannot disappear silently.
|
|
2001
|
+
active = explicit_rhs > 0.
|
|
2002
|
+
certified = False
|
|
2003
|
+
if np.any(resolution_floor[active] >
|
|
2004
|
+
1e-9*explicit_rhs[active]):
|
|
2005
|
+
certified = (bool(self.footprints) or self._point_trace_policy is not None) and self._explicit_load_resolved(
|
|
2006
|
+
answer, rhs, explicit_rhs, applied=applied, reconstructed=u)
|
|
2007
|
+
if not certified:
|
|
2008
|
+
raise _NumericalFailure("panel mixed-load resolution insufficient")
|
|
2009
|
+
equilibrium_tolerance = (1e-9*equilibrium_scale+
|
|
2010
|
+
roundoff_fraction*projected_roundoff)
|
|
2011
|
+
gap_tolerance = 1e-9*gap_scale
|
|
2012
|
+
if (self.footprints and np.any(np.abs(gap) > gap_tolerance) and
|
|
2013
|
+
(certified or self._explicit_load_resolved(answer, rhs, explicit_rhs, applied=applied, reconstructed=u))):
|
|
2014
|
+
gap_tolerance = self._footprint_gap_tolerance(u, gap_scale, roundoff_fraction)
|
|
2015
|
+
for item in self._footprint_incidence:
|
|
2016
|
+
width=getattr(item,"row_count",6)
|
|
2017
|
+
rows = slice(item.row_start, item.row_start+width)
|
|
2018
|
+
cap = 1e-9*np.array((self._compatibility_length,)*3+(1.,)*(width-3))
|
|
2019
|
+
gap_tolerance[rows] = np.minimum(gap_tolerance[rows], cap)
|
|
2020
|
+
if (not np.isfinite(reaction).all() or
|
|
2021
|
+
not np.isfinite(gap).all() or not np.isfinite(projected).all() or
|
|
2022
|
+
not np.isfinite(gap_scale).all() or
|
|
2023
|
+
not np.isfinite(equilibrium_tolerance).all() or
|
|
2024
|
+
not np.isfinite(gap_tolerance).all() or
|
|
2025
|
+
np.any(np.abs(gap) > gap_tolerance) or
|
|
2026
|
+
np.any(np.abs(projected) > equilibrium_tolerance)):
|
|
2027
|
+
raise _NumericalFailure("panel point gap or equilibrium failed")
|
|
2028
|
+
except (_NumericalFailure, RepresentabilityError) as exc:
|
|
2029
|
+
return self._panel_result(
|
|
2030
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
2031
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
2032
|
+
recovered = []
|
|
2033
|
+
equilibrated = []
|
|
2034
|
+
loaded_poses = []
|
|
2035
|
+
loaded_states = {}
|
|
2036
|
+
if self._point_trace_policy is not None:
|
|
2037
|
+
try:
|
|
2038
|
+
loaded_states = {member_id: compiled_load.bubble_state(acceleration, lam)
|
|
2039
|
+
for member_id, compiled_load in self._loaded_members.items()}
|
|
2040
|
+
except RepresentabilityError as exc:
|
|
2041
|
+
return self._panel_result("failed", factorization_count=self._handle.factorization_count,
|
|
2042
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
2043
|
+
for member_id, (member, slots) in self._members:
|
|
2044
|
+
if recovery is not None and member_id not in recovery:
|
|
2045
|
+
continue
|
|
2046
|
+
local = member.local_dof_map() @ u[slots]
|
|
2047
|
+
if not np.isfinite(local).all():
|
|
2048
|
+
return self._panel_result(
|
|
2049
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
2050
|
+
solve_count=self._handle.solve_count,
|
|
2051
|
+
failure_reason="finite physical B2 recovery coordinates required")
|
|
2052
|
+
result = member.operator.recover_sections(
|
|
2053
|
+
local, stations=(0., .5, 1.) if recovery is None
|
|
2054
|
+
else recovery[member_id])
|
|
2055
|
+
if (not np.isfinite(result.resultants).all() or
|
|
2056
|
+
not np.isfinite(result.strains).all()):
|
|
2057
|
+
return self._panel_result(
|
|
2058
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
2059
|
+
solve_count=self._handle.solve_count,
|
|
2060
|
+
failure_reason="finite physical B2 recovery required")
|
|
2061
|
+
recovered.append((member_id, tuple(
|
|
2062
|
+
tuple(map(float, row)) for row in result.resultants)))
|
|
2063
|
+
if isinstance(load, B2ReferenceBodyLoad) or self._point_trace_policy is not None:
|
|
2064
|
+
try:
|
|
2065
|
+
if self._point_trace_policy is not None:
|
|
2066
|
+
compiled_load = self._loaded_members[member_id]
|
|
2067
|
+
uniform, point_wrenches, _ = loaded_states[member_id]
|
|
2068
|
+
actions = compiled_load.section_actions(point_wrenches)
|
|
2069
|
+
poses = compiled_load.recover_pose(local, result.stations, uniform, point_wrenches)
|
|
2070
|
+
loaded_poses.append(B2LoadedMemberPoses(member_id, tuple(map(float, result.stations)),
|
|
2071
|
+
tuple(tuple(map(float, row)) for row in poses)))
|
|
2072
|
+
else:
|
|
2073
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
2074
|
+
uniform = (member.definition.mass.matrix_at_reference_axis()[:, :3]@
|
|
2075
|
+
member.definition.local_frame_rows()@np.asarray(load.acceleration_m_s2))
|
|
2076
|
+
actions = tuple((station, pullback@lam[6*i:6*i+6]) for i, station, pullback in
|
|
2077
|
+
self._section_point_actions.get(member_id, ()))
|
|
2078
|
+
if (not np.isfinite(uniform).all() or
|
|
2079
|
+
any(not np.isfinite(wrench).all() for _, wrench in actions)):
|
|
2080
|
+
raise RepresentabilityError("physical section actions are not representable")
|
|
2081
|
+
fields = member.operator.recover_equilibrated_sections(local,
|
|
2082
|
+
uniform_wrench=uniform, point_actions=actions, stations=result.stations)
|
|
2083
|
+
except RepresentabilityError as exc:
|
|
2084
|
+
return self._panel_result("failed", factorization_count=self._handle.factorization_count,
|
|
2085
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
2086
|
+
rows = lambda values: tuple(tuple(map(float, row)) for row in values)
|
|
2087
|
+
equilibrated.append(B2LoadedMemberSections(member_id, tuple(map(float, fields.stations)),
|
|
2088
|
+
rows(fields.resultants_left), rows(fields.resultants_right),
|
|
2089
|
+
rows(fields.strains_left), rows(fields.strains_right),
|
|
2090
|
+
tuple(map(float, fields.endpoint_tractions))))
|
|
2091
|
+
energy = .5*float(u @ force)
|
|
2092
|
+
if not isfinite(energy):
|
|
2093
|
+
return self._panel_result(
|
|
2094
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
2095
|
+
solve_count=self._handle.solve_count,
|
|
2096
|
+
failure_reason="finite panel strain energy required")
|
|
2097
|
+
extra = {}
|
|
2098
|
+
if self._physical_footprint_law:
|
|
2099
|
+
extra.update(constraint_multipliers=tuple(map(float,lam)),constraint_gaps=tuple(map(float,gap)),
|
|
2100
|
+
constraint_rows=tuple((item.row_start+j,item.footprint_id,item.shell_node,item.beam_node,component)
|
|
2101
|
+
for item in self._footprint_incidence for j,component in enumerate(("translation_x","translation_y","translation_z","director_1","director_2"))))
|
|
2102
|
+
if isinstance(load, B2ReferenceBodyLoad):
|
|
2103
|
+
extra.update(body_model_identity=load.model_identity,
|
|
2104
|
+
body_acceleration_m_s2=load.acceleration_m_s2,
|
|
2105
|
+
equilibrated_sections=tuple(equilibrated))
|
|
2106
|
+
if self._point_trace_policy is not None:
|
|
2107
|
+
try:
|
|
2108
|
+
loaded_energy = fsum((energy, *(state[2] for state in loaded_states.values())))
|
|
2109
|
+
except OverflowError:
|
|
2110
|
+
loaded_energy = float("inf")
|
|
2111
|
+
if not isfinite(loaded_energy) or loaded_energy < 0.:
|
|
2112
|
+
return self._panel_result("failed", factorization_count=self._handle.factorization_count,
|
|
2113
|
+
solve_count=self._handle.solve_count, failure_reason="loaded strain energy is not representable")
|
|
2114
|
+
extra.update(equilibrated_sections=tuple(equilibrated), loaded_member_poses=tuple(loaded_poses),
|
|
2115
|
+
loaded_strain_energy=loaded_energy)
|
|
2116
|
+
if chosen_footprints:
|
|
2117
|
+
try:
|
|
2118
|
+
wrench_recovery = physical_selected_wrenches if self._physical_footprint_law else selected_wrenches
|
|
2119
|
+
extra["footprint_wrenches"] = wrench_recovery(
|
|
2120
|
+
self._footprint_incidence, chosen_footprints, lam, gap)
|
|
2121
|
+
except (ValueError, OverflowError) as exc:
|
|
2122
|
+
return self._panel_result(
|
|
2123
|
+
"failed", factorization_count=self._handle.factorization_count,
|
|
2124
|
+
solve_count=self._handle.solve_count, failure_reason=str(exc))
|
|
2125
|
+
return self._panel_result(
|
|
2126
|
+
"completed", tuple(map(float, u)),
|
|
2127
|
+
tuple((point_id, tuple(map(float, lam[6*i:6*i+6])))
|
|
2128
|
+
for i, point_id in enumerate(self.point_ids)),
|
|
2129
|
+
tuple(map(float, reaction)),
|
|
2130
|
+
tuple((point_id, tuple(map(float, gap[6*i:6*i+6])))
|
|
2131
|
+
for i, point_id in enumerate(self.point_ids)),
|
|
2132
|
+
tuple(recovered), energy, self._handle.factorization_count,
|
|
2133
|
+
self._handle.solve_count, **extra)
|
|
2134
|
+
|
|
2135
|
+
def solve_linear_many(
|
|
2136
|
+
self, cases: Sequence[tuple[str, object]], *,
|
|
2137
|
+
selected_dofs: Sequence[int],
|
|
2138
|
+
selected_points: Sequence[str] = (),
|
|
2139
|
+
selected_footprints: B2NodalRigidFootprintSelection | B2PhysicalRigidFootprintSelection | None = None,
|
|
2140
|
+
recover_members: Mapping[str, Sequence[float]] | None = None,
|
|
2141
|
+
cancellation_token: CancellationToken | None = None,
|
|
2142
|
+
on_case: Callable[[str], None] | None = None,
|
|
2143
|
+
) -> ExperimentalB2Q4PanelBatchResult:
|
|
2144
|
+
"""Solve ordered cases with one factorization and bounded case storage.
|
|
2145
|
+
|
|
2146
|
+
Each result is complete before it joins the returned prefix. Numerical
|
|
2147
|
+
failure, cooperative cancellation or owner closure stops further cases.
|
|
2148
|
+
Closing from a completion callback preserves the complete prefix; a
|
|
2149
|
+
final-case closure still returns completed. Callback and programming
|
|
2150
|
+
errors propagate. This development route uses one RHS at a
|
|
2151
|
+
time, avoiding full displacement/reaction batches for large panels.
|
|
2152
|
+
Selected displacement, reaction and local member-resultant envelopes
|
|
2153
|
+
describe only complete cases in the returned prefix, with the first
|
|
2154
|
+
submitted case governing ties.
|
|
2155
|
+
"""
|
|
2156
|
+
if self._closed:
|
|
2157
|
+
raise ExperimentalB2Q4PanelError("experimental panel is closed")
|
|
2158
|
+
if self._modal_only:
|
|
2159
|
+
raise ValueError("free-free modal-only policy does not admit static solves")
|
|
2160
|
+
selector = checked_physical_selection if self._physical_footprint_law else checked_selection
|
|
2161
|
+
chosen_footprints = selector(selected_footprints, self.footprints)
|
|
2162
|
+
try:
|
|
2163
|
+
chosen = tuple(selected_dofs)
|
|
2164
|
+
chosen_points = tuple(selected_points)
|
|
2165
|
+
submitted = tuple(cases)
|
|
2166
|
+
except TypeError as exc:
|
|
2167
|
+
raise ValueError("ordered B2 panel cases and selection required") from exc
|
|
2168
|
+
if (not chosen or any(type(value) is not int or
|
|
2169
|
+
not 0 <= value < len(self._u0)
|
|
2170
|
+
for value in chosen) or
|
|
2171
|
+
len(set(chosen)) != len(chosen)):
|
|
2172
|
+
raise ValueError("unique selected panel coordinate indices required")
|
|
2173
|
+
if (any(type(point_id) is not str or
|
|
2174
|
+
point_id not in self.point_ids for point_id in chosen_points) or
|
|
2175
|
+
len(set(chosen_points)) != len(chosen_points)):
|
|
2176
|
+
raise ValueError("unique selected panel point IDs required")
|
|
2177
|
+
if (not submitted or
|
|
2178
|
+
any(type(item) not in (tuple, list) or len(item) != 2 or
|
|
2179
|
+
type(item[0]) is not str or not item[0].strip()
|
|
2180
|
+
for item in submitted) or
|
|
2181
|
+
len({item[0] for item in submitted}) != len(submitted)):
|
|
2182
|
+
raise ValueError("unique nonempty ordered B2 panel case IDs required")
|
|
2183
|
+
# Caller-owned list pairs may be changed by a completion callback.
|
|
2184
|
+
# Snapshot their inventory/references now; later load-value mutation
|
|
2185
|
+
# remains checked by the existing bounded fingerprints below.
|
|
2186
|
+
submitted = tuple((case_id, load) for case_id, load in submitted)
|
|
2187
|
+
if self._physical_footprint_law and any(isinstance(load,B2ReferenceBodyLoad) for _,load in submitted):
|
|
2188
|
+
raise ValueError("physical footprint body-load driver not admitted")
|
|
2189
|
+
selected_rows = tuple((item.row_start+j,item.footprint_id,item.shell_node,item.beam_node,component)
|
|
2190
|
+
for item in self._footprint_incidence if item.footprint_id in chosen_footprints
|
|
2191
|
+
for j,component in enumerate(("translation_x","translation_y","translation_z","director_1","director_2"))) if self._physical_footprint_law else ()
|
|
2192
|
+
if cancellation_token is not None and not isinstance(
|
|
2193
|
+
cancellation_token, CancellationToken):
|
|
2194
|
+
raise TypeError("B2 panel cancellation token required")
|
|
2195
|
+
if on_case is not None and not callable(on_case):
|
|
2196
|
+
raise TypeError("B2 panel case callback required")
|
|
2197
|
+
recovery = _checked_recovery(
|
|
2198
|
+
{} if recover_members is None else recover_members,
|
|
2199
|
+
{member_id for member_id, _ in self._members})
|
|
2200
|
+
# Counts outlive a callback closing the owner, without retaining its
|
|
2201
|
+
# factor in this operation or a propagated callback-error traceback.
|
|
2202
|
+
factorization_count = self._handle.factorization_count
|
|
2203
|
+
solve_count = self._handle.solve_count
|
|
2204
|
+
fingerprints = []
|
|
2205
|
+
for _case_id, load in submitted:
|
|
2206
|
+
if cancellation_token is not None and cancellation_token.is_cancelled:
|
|
2207
|
+
break
|
|
2208
|
+
snapshot, fingerprint = self._body_load_snapshot(load)
|
|
2209
|
+
fingerprints.append(fingerprint)
|
|
2210
|
+
del snapshot
|
|
2211
|
+
completed = []
|
|
2212
|
+
|
|
2213
|
+
def envelopes(attribute):
|
|
2214
|
+
if not completed:
|
|
2215
|
+
return ()
|
|
2216
|
+
extrema = []
|
|
2217
|
+
for index, dof in enumerate(chosen):
|
|
2218
|
+
# min/max preserve the first case on exact ties. No rounding,
|
|
2219
|
+
# absolute envelope or physical-unit conversion is introduced.
|
|
2220
|
+
minimum = min(completed, key=lambda case: getattr(case, attribute)[index])
|
|
2221
|
+
maximum = max(completed, key=lambda case: getattr(case, attribute)[index])
|
|
2222
|
+
extrema.append(ExperimentalB2Q4PanelResponseEnvelope(
|
|
2223
|
+
dof, getattr(minimum, attribute)[index], minimum.case_id,
|
|
2224
|
+
getattr(maximum, attribute)[index], maximum.case_id))
|
|
2225
|
+
return tuple(extrema)
|
|
2226
|
+
|
|
2227
|
+
def member_envelopes():
|
|
2228
|
+
if not completed or not recovery:
|
|
2229
|
+
return ()
|
|
2230
|
+
sections = tuple((case.case_id, dict(case.beam_resultants_local))
|
|
2231
|
+
for case in completed)
|
|
2232
|
+
extrema = []
|
|
2233
|
+
for member_id, stations in recovery.items():
|
|
2234
|
+
if any(member_id not in rows or len(rows[member_id]) != len(stations)
|
|
2235
|
+
for _, rows in sections):
|
|
2236
|
+
raise RuntimeError("incomplete selected B2 member recovery")
|
|
2237
|
+
for station_index, station in enumerate(stations):
|
|
2238
|
+
for component_index, component in enumerate(
|
|
2239
|
+
("N", "Qy", "Qz", "Mx", "My", "Mz")):
|
|
2240
|
+
values = tuple((case_id, rows[member_id][station_index][component_index])
|
|
2241
|
+
for case_id, rows in sections)
|
|
2242
|
+
minimum = min(values, key=lambda item: item[1])
|
|
2243
|
+
maximum = max(values, key=lambda item: item[1])
|
|
2244
|
+
extrema.append(ExperimentalB2Q4PanelMemberResultantEnvelope(
|
|
2245
|
+
member_id, station_index, station, component,
|
|
2246
|
+
minimum[1], minimum[0], maximum[1], maximum[0]))
|
|
2247
|
+
return tuple(extrema)
|
|
2248
|
+
|
|
2249
|
+
def finish(status, failure_reason=None):
|
|
2250
|
+
result_type = (ExperimentalB2Q4FootprintBatchResult if self.footprints
|
|
2251
|
+
else ExperimentalB2Q4PanelBatchResult)
|
|
2252
|
+
extra = {"selected_footprints": chosen_footprints} if self.footprints else {}
|
|
2253
|
+
if self._physical_footprint_law:
|
|
2254
|
+
result_type = B2PhysicalRigidFootprintBatchResult
|
|
2255
|
+
extra["constraint_rows"] = selected_rows
|
|
2256
|
+
return result_type(
|
|
2257
|
+
status, chosen, chosen_points, tuple(completed),
|
|
2258
|
+
factorization_count, solve_count,
|
|
2259
|
+
failure_reason=failure_reason,
|
|
2260
|
+
displacement_envelopes=envelopes("selected_displacements"),
|
|
2261
|
+
affine_reaction_envelopes=envelopes("selected_affine_reactions"),
|
|
2262
|
+
member_resultant_envelopes=member_envelopes(), **extra)
|
|
2263
|
+
|
|
2264
|
+
for index, (case_id, load) in enumerate(submitted):
|
|
2265
|
+
if self._closed or (cancellation_token is not None and
|
|
2266
|
+
cancellation_token.is_cancelled):
|
|
2267
|
+
break
|
|
2268
|
+
snapshot, fingerprint = self._body_load_snapshot(load)
|
|
2269
|
+
if fingerprint != fingerprints[index]:
|
|
2270
|
+
raise ValueError("B2 panel load case changed after submission")
|
|
2271
|
+
result = self.solve_linear(load if isinstance(load, B2ReferenceBodyLoad) else snapshot, recover_members=recovery,
|
|
2272
|
+
selected_footprints=selected_footprints)
|
|
2273
|
+
factorization_count = result.factorization_count
|
|
2274
|
+
solve_count = result.solve_count
|
|
2275
|
+
if result.status != "completed":
|
|
2276
|
+
return finish("failed", f"{case_id}: {result.failure_reason}")
|
|
2277
|
+
point_multipliers = dict(result.point_multipliers)
|
|
2278
|
+
point_gaps = dict(result.point_gaps)
|
|
2279
|
+
case_type = (ExperimentalB2Q4FootprintSelectedCase if self.footprints
|
|
2280
|
+
else ExperimentalB2Q4PanelSelectedCase)
|
|
2281
|
+
extra = {"footprint_wrenches": result.footprint_wrenches} if self.footprints else {}
|
|
2282
|
+
if self._physical_footprint_law:
|
|
2283
|
+
case_type = B2PhysicalRigidFootprintSelectedCase
|
|
2284
|
+
row_indices = tuple(row[0] for row in selected_rows)
|
|
2285
|
+
extra.update(constraint_multipliers=tuple(result.constraint_multipliers[i] for i in row_indices),
|
|
2286
|
+
constraint_gaps=tuple(result.constraint_gaps[i] for i in row_indices))
|
|
2287
|
+
if isinstance(load, B2ReferenceBodyLoad):
|
|
2288
|
+
case_type = B2BodyLoadedFootprintSelectedCase if self.footprints else B2BodyLoadedPanelSelectedCase
|
|
2289
|
+
extra.update(body_model_identity=result.body_model_identity,
|
|
2290
|
+
body_acceleration_m_s2=result.body_acceleration_m_s2,
|
|
2291
|
+
equilibrated_sections=result.equilibrated_sections)
|
|
2292
|
+
if self._point_trace_policy is not None:
|
|
2293
|
+
case_type = B2LoadedPointPanelSelectedCase
|
|
2294
|
+
extra.update(equilibrated_sections=result.equilibrated_sections,
|
|
2295
|
+
loaded_member_poses=result.loaded_member_poses,
|
|
2296
|
+
loaded_strain_energy=result.loaded_strain_energy)
|
|
2297
|
+
completed.append(case_type(
|
|
2298
|
+
case_id,
|
|
2299
|
+
tuple(result.displacements[index] for index in chosen),
|
|
2300
|
+
tuple(result.affine_reactions[index] for index in chosen),
|
|
2301
|
+
tuple((point_id, point_multipliers[point_id])
|
|
2302
|
+
for point_id in chosen_points),
|
|
2303
|
+
tuple((point_id, point_gaps[point_id])
|
|
2304
|
+
for point_id in chosen_points),
|
|
2305
|
+
result.beam_resultants_local,
|
|
2306
|
+
result.internal_strain_energy, **extra))
|
|
2307
|
+
if on_case is not None:
|
|
2308
|
+
on_case(case_id)
|
|
2309
|
+
return finish(
|
|
2310
|
+
"cancelled" if len(completed) != len(submitted) else "completed",
|
|
2311
|
+
failure_reason=("experimental panel closed" +
|
|
2312
|
+
(" after " + completed[-1].case_id if completed else "")
|
|
2313
|
+
if self._closed and len(completed) != len(submitted)
|
|
2314
|
+
else None))
|
|
2315
|
+
|
|
2316
|
+
def close(self) -> None:
|
|
2317
|
+
self._closed = True
|
|
2318
|
+
try:
|
|
2319
|
+
_retire_private_q4_mesh(self)
|
|
2320
|
+
finally:
|
|
2321
|
+
self._handle = None
|
|
2322
|
+
self._physical_scaled_kkt = None
|
|
2323
|
+
self._attachment_elimination = None
|
|
2324
|
+
if self._point_trace_policy is not None:
|
|
2325
|
+
for compiled_load in self._loaded_members.values():
|
|
2326
|
+
compiled_load._recovery_maps.clear()
|
|
2327
|
+
self._loaded_members.clear()
|
|
2328
|
+
self._H = self._abs_H = self._resolution_kkt = None
|
|
2329
|
+
self._resolution_kkt = None
|
|
2330
|
+
self._affine_resolution_cache = None
|
|
2331
|
+
self._buckling_model = None
|
|
2332
|
+
self._buckling_shells = ()
|
|
2333
|
+
|
|
2334
|
+
|
|
2335
|
+
def _release_coo_scratch(rows, columns, values, diagnostic, phase):
|
|
2336
|
+
# CSR owns its arrays; these compiler lists have no later mechanics use.
|
|
2337
|
+
counts = tuple(map(len, (rows, columns, values))) if diagnostic is not None else None
|
|
2338
|
+
rows.clear()
|
|
2339
|
+
columns.clear()
|
|
2340
|
+
values.clear()
|
|
2341
|
+
if diagnostic is not None:
|
|
2342
|
+
diagnostic.record_coo_release(phase, counts)
|
|
2343
|
+
|
|
2344
|
+
|
|
2345
|
+
def _scatter(rows, columns, values, slots, local):
|
|
2346
|
+
width = len(slots)
|
|
2347
|
+
rows.extend(np.repeat(slots, width))
|
|
2348
|
+
columns.extend(np.tile(slots, width))
|
|
2349
|
+
values.extend(np.asarray(local).ravel())
|
|
2350
|
+
|
|
2351
|
+
|
|
2352
|
+
def _modal_scaled_residual(residual, bound, row_scale):
|
|
2353
|
+
"""Infinity backward error in the fixed congruence row representation.
|
|
2354
|
+
|
|
2355
|
+
Near-zero components of an uncoupled mode are not separate relative-error
|
|
2356
|
+
denominators. Scaling the rows before the norm keeps their units consistent
|
|
2357
|
+
with the factorization coordinates without an arbitrary physical floor.
|
|
2358
|
+
"""
|
|
2359
|
+
residual, bound = np.asarray(residual), np.asarray(bound)
|
|
2360
|
+
if not np.isfinite(residual).all() or not np.isfinite(bound).all():
|
|
2361
|
+
return float("inf")
|
|
2362
|
+
numerator = np.max(abs(residual)*row_scale, initial=0.)
|
|
2363
|
+
denominator = np.max(bound*row_scale, initial=0.)
|
|
2364
|
+
if not np.isfinite(numerator) or not np.isfinite(denominator):
|
|
2365
|
+
return float("inf")
|
|
2366
|
+
if denominator == 0.:
|
|
2367
|
+
return 0. if numerator == 0. else float("inf")
|
|
2368
|
+
return float(numerator/denominator)
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
def _free_b2_rigid_space(owner, scale, scaled_mass, safe_point):
|
|
2372
|
+
"""Common physical velocities, with only a bounded six-by-six Gram solve."""
|
|
2373
|
+
origin = owner._modal_positions[:len(owner.shell_node_ids)].mean(axis=0)
|
|
2374
|
+
canonical = np.zeros((owner._K.shape[0], 6))
|
|
2375
|
+
for i, position in enumerate(owner._modal_positions):
|
|
2376
|
+
slots = slice(6*i, 6*i+3)
|
|
2377
|
+
canonical[slots, :3] = np.eye(3)
|
|
2378
|
+
for j in range(3):
|
|
2379
|
+
canonical[slots, 3+j] = np.cross(np.eye(3)[j], position-origin)
|
|
2380
|
+
canonical[6*i+3:6*i+6, 3:] = np.eye(3)
|
|
2381
|
+
n = owner._K.shape[0]
|
|
2382
|
+
equations, gaps = [], []
|
|
2383
|
+
for column in canonical.T:
|
|
2384
|
+
safe_point("reference modes rigid field")
|
|
2385
|
+
equation = _modal_scaled_residual(owner._K@column,
|
|
2386
|
+
abs(owner._K)@abs(column), scale[:n])
|
|
2387
|
+
gap = _modal_scaled_residual(owner._C@column,
|
|
2388
|
+
abs(owner._C)@abs(column), scale[n:])
|
|
2389
|
+
if equation > 1e-8 or gap > 1e-9:
|
|
2390
|
+
raise _NumericalFailure("canonical free-free rigid field violates physical K or point C")
|
|
2391
|
+
equations.append(equation); gaps.append(gap)
|
|
2392
|
+
gram = canonical.T@owner._M@canonical
|
|
2393
|
+
if (not np.isfinite(gram).all() or
|
|
2394
|
+
np.max(abs(gram-gram.T)) > 1e-10*np.max(abs(gram))):
|
|
2395
|
+
raise _NumericalFailure("canonical rigid kinetic Gram symmetry unresolved")
|
|
2396
|
+
try:
|
|
2397
|
+
lower = np.linalg.cholesky(gram)
|
|
2398
|
+
normalized = canonical@np.linalg.solve(lower.T, np.eye(6))
|
|
2399
|
+
except np.linalg.LinAlgError as exc:
|
|
2400
|
+
raise _NumericalFailure("six physical rigid fields require positive kinetic Gram") from exc
|
|
2401
|
+
if (not np.isfinite(normalized).all() or
|
|
2402
|
+
np.max(abs(normalized.T@owner._M@normalized-np.eye(6))) > 1e-7):
|
|
2403
|
+
raise _NumericalFailure("rigid kinetic normalization unresolved")
|
|
2404
|
+
scaled = np.vstack((normalized, np.zeros((owner._C.shape[0], 6))))/scale[:, None]
|
|
2405
|
+
if (not np.isfinite(scaled).all() or
|
|
2406
|
+
np.max(abs(scaled.T@scaled_mass@scaled-np.eye(6))) > 1e-7):
|
|
2407
|
+
raise _NumericalFailure("rigid congruence reconstruction unresolved")
|
|
2408
|
+
return dict(origin=tuple(map(float, origin)), canonical=canonical,
|
|
2409
|
+
normalized=normalized, scaled=scaled, gram=gram,
|
|
2410
|
+
equations=tuple(equations), gaps=tuple(gaps))
|
|
2411
|
+
|
|
2412
|
+
|
|
2413
|
+
class ExperimentalB2MixedPanelAnalysis(ExperimentalB2Q4PanelAnalysis):
|
|
2414
|
+
"""Explicit mixed-shell admission using the existing static sparse owner."""
|
|
2415
|
+
|
|
2416
|
+
_mixed_shells = True
|
|
2417
|
+
|
|
2418
|
+
@property
|
|
2419
|
+
def capabilities(self) -> ExperimentalB2MixedPanelCapabilities:
|
|
2420
|
+
existing = super().capabilities
|
|
2421
|
+
capability_type = (ExperimentalB2MixedReferenceModalCapabilities if
|
|
2422
|
+
self._reference_inertia_policy is not None else ExperimentalB2MixedPanelCapabilities)
|
|
2423
|
+
extra = ({"shell_mass_policies": tuple((shell_id,
|
|
2424
|
+
S3_V2D_TRANSLATIONAL_LUMPED_SECTION_AREAL_MASS_V1 if formulation ==
|
|
2425
|
+
S3_V2D_FORMULATION_ID else Q4_MASS_POLICY)
|
|
2426
|
+
for shell_id, formulation in self._shell_formulations)}
|
|
2427
|
+
if self._reference_inertia_policy is not None else {})
|
|
2428
|
+
if self._reference_inertia_policy is not None:
|
|
2429
|
+
extra["boundary_policy"] = self._reference_boundary_policy
|
|
2430
|
+
return capability_type(
|
|
2431
|
+
point_law_id=tuple(dict.fromkeys(law for _, law in self._point_laws)),
|
|
2432
|
+
attachment=existing.attachment,
|
|
2433
|
+
operations=existing.operations,
|
|
2434
|
+
body_load_domain=existing.body_load_domain,
|
|
2435
|
+
spectral_domain=(existing.spectral_domain if self._modal_only or self._physical_footprint_law else
|
|
2436
|
+
"experimental virgin supported finite descriptor pairs; heterogeneous Q4 director and native S3 lumped translation inertia; S3 rotary inertia omitted; completeness unqualified; prestress unsupported"
|
|
2437
|
+
if self._reference_inertia_policy is not None else
|
|
2438
|
+
"native S3 V2D physical mass and modes deferred" if self._has_s3
|
|
2439
|
+
else existing.spectral_domain),
|
|
2440
|
+
shell_formulations=self._shell_formulations,
|
|
2441
|
+
point_laws=self._point_laws,
|
|
2442
|
+
footprint_law_id=existing.footprint_law_id if self._physical_footprint_law else FOOTPRINT_LAW_ID if self.footprints else None,
|
|
2443
|
+
footprints=self.footprints, **extra)
|
|
2444
|
+
|
|
2445
|
+
|
|
2446
|
+
def create_experimental_b2_q4_panel_analysis(*args, **kwargs):
|
|
2447
|
+
return ExperimentalB2Q4PanelAnalysis(*args, **kwargs)
|