qf-solver 0.2.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. mitc4/__init__.py +19 -0
  2. mitc4/benchmarks.py +183 -0
  3. mitc4/cli.py +115 -0
  4. mitc4/constants.py +11 -0
  5. mitc4/convergence.py +282 -0
  6. mitc4/element.py +312 -0
  7. mitc4/geometry.py +43 -0
  8. mitc4/locking.py +274 -0
  9. mitc4/material.py +63 -0
  10. mitc4/mesh.py +71 -0
  11. mitc4/model.py +179 -0
  12. mitc4/verification.py +287 -0
  13. mitc4/visualization.py +80 -0
  14. qf_solver-0.2.0a0.data/data/examples/beam2_cantilever.json +31 -0
  15. qf_solver-0.2.0a0.data/data/examples/frictional_contact_plane.json +32 -0
  16. qf_solver-0.2.0a0.data/data/examples/frictionless_contact_plane.json +17 -0
  17. qf_solver-0.2.0a0.data/data/examples/frictionless_contact_surface.json +16 -0
  18. qf_solver-0.2.0a0.data/data/examples/invalid_inverted_tet4.json +23 -0
  19. qf_solver-0.2.0a0.data/data/examples/mitc3_laminate_harmonic.json +30 -0
  20. qf_solver-0.2.0a0.data/data/examples/mitc3_modal_cantilever.json +24 -0
  21. qf_solver-0.2.0a0.data/data/examples/mitc3_newmark_cantilever.json +34 -0
  22. qf_solver-0.2.0a0.data/data/examples/mitc3_shell_static.json +28 -0
  23. qf_solver-0.2.0a0.data/data/examples/mitc4_dynamic_multicomponent.json +48 -0
  24. qf_solver-0.2.0a0.data/data/examples/mitc4_harmonic_cantilever.json +34 -0
  25. qf_solver-0.2.0a0.data/data/examples/mitc4_laminate_static.json +46 -0
  26. qf_solver-0.2.0a0.data/data/examples/mitc4_modal_cantilever.json +30 -0
  27. qf_solver-0.2.0a0.data/data/examples/mitc4_newmark_cantilever.json +39 -0
  28. qf_solver-0.2.0a0.data/data/examples/mitc4_shell_static.json +26 -0
  29. qf_solver-0.2.0a0.data/data/examples/rbe2_rigid_arm.json +18 -0
  30. qf_solver-0.2.0a0.data/data/examples/spring_mass_oscillator.json +40 -0
  31. qf_solver-0.2.0a0.data/data/examples/tet10_orthotropic_newmark.json +57 -0
  32. qf_solver-0.2.0a0.data/data/examples/tet10_orthotropic_static.json +37 -0
  33. qf_solver-0.2.0a0.data/data/examples/tet10_static.json +35 -0
  34. qf_solver-0.2.0a0.data/data/examples/tet4_body_force.json +33 -0
  35. qf_solver-0.2.0a0.data/data/examples/tet4_compression.json +33 -0
  36. qf_solver-0.2.0a0.data/data/examples/tet4_dynamic_free_vibration.json +29 -0
  37. qf_solver-0.2.0a0.data/data/examples/tet4_dynamic_sdof_free_vibration.json +30 -0
  38. qf_solver-0.2.0a0.data/data/examples/tet4_dynamic_tabulated_load.json +32 -0
  39. qf_solver-0.2.0a0.data/data/examples/tet4_elastoplastic_static.json +35 -0
  40. qf_solver-0.2.0a0.data/data/examples/tet4_geometric_nonlinear_static.json +19 -0
  41. qf_solver-0.2.0a0.data/data/examples/tet4_harmonic_response.json +24 -0
  42. qf_solver-0.2.0a0.data/data/examples/tet4_harmonic_sdof_response.json +25 -0
  43. qf_solver-0.2.0a0.data/data/examples/tet4_modal_unit.json +24 -0
  44. qf_solver-0.2.0a0.data/data/examples/tet4_nonlinear_static.json +29 -0
  45. qf_solver-0.2.0a0.data/data/examples/tet4_orthotropic_modal.json +32 -0
  46. qf_solver-0.2.0a0.data/data/examples/tet4_orthotropic_static.json +31 -0
  47. qf_solver-0.2.0a0.data/data/examples/tet4_pressure.json +30 -0
  48. qf_solver-0.2.0a0.data/data/examples/tet4_static.json +26 -0
  49. qf_solver-0.2.0a0.data/data/examples/tet4_transient_dynamic.json +28 -0
  50. qf_solver-0.2.0a0.data/data/qualification/benchmarks.json +178 -0
  51. qf_solver-0.2.0a0.data/data/qualification/campaign.json +423 -0
  52. qf_solver-0.2.0a0.data/data/qualification/demonstrations.json +34 -0
  53. qf_solver-0.2.0a0.data/data/qualification/formulas.json +738 -0
  54. qf_solver-0.2.0a0.data/data/qualification/requirements.json +708 -0
  55. qf_solver-0.2.0a0.data/data/requirements/baseline-large-linux.txt +5 -0
  56. qf_solver-0.2.0a0.data/data/requirements/baseline-standard.txt +33 -0
  57. qf_solver-0.2.0a0.dist-info/METADATA +327 -0
  58. qf_solver-0.2.0a0.dist-info/RECORD +338 -0
  59. qf_solver-0.2.0a0.dist-info/WHEEL +5 -0
  60. qf_solver-0.2.0a0.dist-info/entry_points.txt +4 -0
  61. qf_solver-0.2.0a0.dist-info/licenses/LICENSE +199 -0
  62. qf_solver-0.2.0a0.dist-info/licenses/NOTICE +8 -0
  63. qf_solver-0.2.0a0.dist-info/top_level.txt +2 -0
  64. solveur/__init__.py +94 -0
  65. solveur/api/__init__.py +131 -0
  66. solveur/api/public.py +538 -0
  67. solveur/benchmarks/__init__.py +16 -0
  68. solveur/benchmarks/beam.py +169 -0
  69. solveur/benchmarks/catalog.py +106 -0
  70. solveur/benchmarks/demonstrations.py +322 -0
  71. solveur/benchmarks/dynamic.py +158 -0
  72. solveur/benchmarks/gmsh_factory.py +273 -0
  73. solveur/benchmarks/nonlinear.py +149 -0
  74. solveur/benchmarks/runner.py +70 -0
  75. solveur/benchmarks/shell.py +287 -0
  76. solveur/benchmarks/solid.py +406 -0
  77. solveur/benchmarks/solid_extended.py +358 -0
  78. solveur/benchmarks/support.py +175 -0
  79. solveur/benchmarks/types.py +48 -0
  80. solveur/cli/__init__.py +1 -0
  81. solveur/cli/benchmark.py +35 -0
  82. solveur/cli/large.py +348 -0
  83. solveur/cli/main.py +348 -0
  84. solveur/cli/mesh.py +33 -0
  85. solveur/cli/standard.py +182 -0
  86. solveur/cli/verification.py +219 -0
  87. solveur/cli/vnv.py +39 -0
  88. solveur/contact/__init__.py +5 -0
  89. solveur/contact/entities.py +102 -0
  90. solveur/contact/slip_root.py +338 -0
  91. solveur/contact/solver.py +700 -0
  92. solveur/core/__init__.py +1 -0
  93. solveur/core/analysis.py +89 -0
  94. solveur/core/assembler.py +306 -0
  95. solveur/core/audit.py +682 -0
  96. solveur/core/audit_checks.py +418 -0
  97. solveur/core/constraints.py +325 -0
  98. solveur/core/dofs.py +61 -0
  99. solveur/core/dynamic.py +641 -0
  100. solveur/core/dynamic_checkpoint.py +101 -0
  101. solveur/core/dynamic_controls.py +153 -0
  102. solveur/core/dynamic_history.py +144 -0
  103. solveur/core/dynamic_reduction.py +361 -0
  104. solveur/core/errors.py +57 -0
  105. solveur/core/geometric_nonlinear.py +200 -0
  106. solveur/core/geometric_nonlinear_controls.py +25 -0
  107. solveur/core/harmonic.py +210 -0
  108. solveur/core/linear_methods.py +256 -0
  109. solveur/core/linear_policy.py +212 -0
  110. solveur/core/material_state.py +61 -0
  111. solveur/core/modal.py +395 -0
  112. solveur/core/model.py +287 -0
  113. solveur/core/nonlinear.py +700 -0
  114. solveur/core/nonlinear_checkpoint.py +206 -0
  115. solveur/core/nonlinear_controls.py +174 -0
  116. solveur/core/qualification.py +321 -0
  117. solveur/core/rbe.py +173 -0
  118. solveur/core/result_serialization.py +51 -0
  119. solveur/core/results.py +256 -0
  120. solveur/core/router.py +39 -0
  121. solveur/core/solver.py +216 -0
  122. solveur/documentation/__init__.py +2 -0
  123. solveur/documentation/server.py +112 -0
  124. solveur/elements/__init__.py +1 -0
  125. solveur/elements/beam/__init__.py +6 -0
  126. solveur/elements/beam/beam2.py +181 -0
  127. solveur/elements/discrete.py +122 -0
  128. solveur/elements/registry.py +72 -0
  129. solveur/elements/shell/__init__.py +1 -0
  130. solveur/elements/shell/frames.py +36 -0
  131. solveur/elements/shell/mitc3.py +342 -0
  132. solveur/elements/shell/mitc3_condensation.py +43 -0
  133. solveur/elements/shell/mitc4.py +21 -0
  134. solveur/elements/solid/__init__.py +1 -0
  135. solveur/elements/solid/common.py +44 -0
  136. solveur/elements/solid/quadrature.py +93 -0
  137. solveur/elements/solid/tet10.py +275 -0
  138. solveur/elements/solid/tet4.py +106 -0
  139. solveur/elements/solid/tet4_total_lagrangian.py +114 -0
  140. solveur/elements/solid/tet4_total_lagrangian_batch.py +162 -0
  141. solveur/io/__init__.py +9 -0
  142. solveur/io/audit_markdown.py +373 -0
  143. solveur/io/checkpoint_common.py +17 -0
  144. solveur/io/constraint_schema.py +151 -0
  145. solveur/io/contact_schema.py +92 -0
  146. solveur/io/csv_writer.py +118 -0
  147. solveur/io/discrete_schema.py +153 -0
  148. solveur/io/dynamic_checkpoint.py +75 -0
  149. solveur/io/evidence_verifier.py +194 -0
  150. solveur/io/evidence_writer.py +138 -0
  151. solveur/io/json_reader.py +48 -0
  152. solveur/io/json_writer.py +15 -0
  153. solveur/io/laminate_schema.py +96 -0
  154. solveur/io/load_schema.py +151 -0
  155. solveur/io/manifest.py +193 -0
  156. solveur/io/model_writer.py +158 -0
  157. solveur/io/nonlinear_checkpoint.py +76 -0
  158. solveur/io/schema.py +700 -0
  159. solveur/io/schema_values.py +55 -0
  160. solveur/io/vtu_writer.py +179 -0
  161. solveur/large/__init__.py +46 -0
  162. solveur/large/assembler.py +194 -0
  163. solveur/large/audit.py +204 -0
  164. solveur/large/benchmark.py +294 -0
  165. solveur/large/campaign.py +277 -0
  166. solveur/large/distributed_model.py +426 -0
  167. solveur/large/dofs.py +25 -0
  168. solveur/large/evidence.py +55 -0
  169. solveur/large/generator.py +143 -0
  170. solveur/large/io.py +199 -0
  171. solveur/large/materials.py +26 -0
  172. solveur/large/matrix_free.py +156 -0
  173. solveur/large/memory.py +78 -0
  174. solveur/large/model.py +82 -0
  175. solveur/large/mpi_diagnostics.py +93 -0
  176. solveur/large/optimization.py +283 -0
  177. solveur/large/partitioning.py +277 -0
  178. solveur/large/postprocess.py +328 -0
  179. solveur/large/profiling.py +240 -0
  180. solveur/large/qualification.py +262 -0
  181. solveur/large/readiness.py +179 -0
  182. solveur/large/runtime.py +108 -0
  183. solveur/large/solver.py +524 -0
  184. solveur/large/tet4_batch.py +141 -0
  185. solveur/large/tuning.py +199 -0
  186. solveur/large/verification.py +273 -0
  187. solveur/loads/__init__.py +5 -0
  188. solveur/loads/entities.py +132 -0
  189. solveur/loads/integration.py +376 -0
  190. solveur/materials/__init__.py +27 -0
  191. solveur/materials/beam.py +42 -0
  192. solveur/materials/composite.py +128 -0
  193. solveur/materials/factory.py +173 -0
  194. solveur/materials/failure.py +190 -0
  195. solveur/materials/laminate.py +280 -0
  196. solveur/materials/orthotropic.py +221 -0
  197. solveur/materials/shell.py +5 -0
  198. solveur/materials/solid.py +224 -0
  199. solveur/mesh/__init__.py +1 -0
  200. solveur/mesh/constraint_validation.py +32 -0
  201. solveur/mesh/contact_validation.py +74 -0
  202. solveur/mesh/entities.py +13 -0
  203. solveur/mesh/gmsh_importer.py +578 -0
  204. solveur/mesh/gmsh_reader.py +148 -0
  205. solveur/mesh/gmsh_types.py +81 -0
  206. solveur/mesh/quality.py +225 -0
  207. solveur/mesh/topology.py +11 -0
  208. solveur/mesh/validation.py +686 -0
  209. solveur/mesh/validation_helpers.py +111 -0
  210. solveur/paths.py +34 -0
  211. solveur/post/__init__.py +6 -0
  212. solveur/post/audit.py +88 -0
  213. solveur/post/harmonic_shell.py +325 -0
  214. solveur/post/shell_results.py +226 -0
  215. solveur/post/stress.py +609 -0
  216. solveur/verification/__init__.py +1 -0
  217. solveur/verification/analytical_references.py +56 -0
  218. solveur/verification/calculix_composite.py +309 -0
  219. solveur/verification/calculix_composite_conical_cutout.py +341 -0
  220. solveur/verification/calculix_composite_conical_ply_stress.py +220 -0
  221. solveur/verification/calculix_curved_composite.py +452 -0
  222. solveur/verification/calculix_curved_orientation.py +465 -0
  223. solveur/verification/calculix_j2.py +287 -0
  224. solveur/verification/calculix_mitc3.py +206 -0
  225. solveur/verification/calculix_mitc3_curved_composite.py +472 -0
  226. solveur/verification/calculix_mitc3_laminate_ply_stress.py +299 -0
  227. solveur/verification/calculix_mitc4_conical_cutout.py +281 -0
  228. solveur/verification/calculix_nafems.py +213 -0
  229. solveur/verification/calculix_tet10.py +216 -0
  230. solveur/verification/calculix_tl_structural.py +396 -0
  231. solveur/verification/calculix_total_lagrangian.py +254 -0
  232. solveur/verification/campaign.py +632 -0
  233. solveur/verification/code_aster_beam2.py +118 -0
  234. solveur/verification/code_aster_beam2_modal.py +128 -0
  235. solveur/verification/code_aster_beam2_newmark.py +265 -0
  236. solveur/verification/code_aster_beam2_transverse.py +272 -0
  237. solveur/verification/code_aster_contact.py +247 -0
  238. solveur/verification/code_aster_contact_additional.py +662 -0
  239. solveur/verification/code_aster_contact_folded.py +118 -0
  240. solveur/verification/code_aster_contact_folded_search.py +107 -0
  241. solveur/verification/code_aster_contact_tet4.py +155 -0
  242. solveur/verification/code_aster_discrete.py +299 -0
  243. solveur/verification/code_aster_friction_contact.py +282 -0
  244. solveur/verification/code_aster_j2.py +299 -0
  245. solveur/verification/code_aster_mitc3.py +335 -0
  246. solveur/verification/code_aster_mitc3_dynamic.py +270 -0
  247. solveur/verification/code_aster_mitc3_hemisphere.py +438 -0
  248. solveur/verification/code_aster_mitc3_laminate_dynamic.py +346 -0
  249. solveur/verification/code_aster_mitc4_conical_cutout.py +272 -0
  250. solveur/verification/code_aster_mitc4_laminate_dynamic.py +444 -0
  251. solveur/verification/code_aster_nafems.py +130 -0
  252. solveur/verification/code_aster_rbe.py +142 -0
  253. solveur/verification/code_aster_tet10_dynamic.py +481 -0
  254. solveur/verification/code_aster_tet10_j2_complex.py +376 -0
  255. solveur/verification/code_aster_tet10_j2_structural.py +353 -0
  256. solveur/verification/code_aster_tet4_dynamic.py +28 -0
  257. solveur/verification/code_aster_tl_structural.py +510 -0
  258. solveur/verification/composite_analytic.py +286 -0
  259. solveur/verification/composite_conical_cutout.py +170 -0
  260. solveur/verification/composite_curved_assembly.py +397 -0
  261. solveur/verification/composite_nafems.py +391 -0
  262. solveur/verification/composite_ply_stress.py +301 -0
  263. solveur/verification/composite_structural.py +361 -0
  264. solveur/verification/contact_additional_models.py +266 -0
  265. solveur/verification/contact_additional_oracle.py +123 -0
  266. solveur/verification/contact_campaign.py +81 -0
  267. solveur/verification/contact_deformable_master.py +145 -0
  268. solveur/verification/contact_master_surface.py +314 -0
  269. solveur/verification/contact_tet4_master.py +196 -0
  270. solveur/verification/dynamic_family_campaign.py +324 -0
  271. solveur/verification/elastica.py +61 -0
  272. solveur/verification/external_correlation.py +248 -0
  273. solveur/verification/frictional_contact.py +253 -0
  274. solveur/verification/frictional_contact_structural.py +163 -0
  275. solveur/verification/frictionless_contact_structural.py +294 -0
  276. solveur/verification/j2_material.py +491 -0
  277. solveur/verification/j2_methods.py +158 -0
  278. solveur/verification/j2_step_sensitivity.py +172 -0
  279. solveur/verification/j2_structural.py +233 -0
  280. solveur/verification/large_petsc_scipy.py +173 -0
  281. solveur/verification/linear_solver_comparison.py +145 -0
  282. solveur/verification/mechanical.py +5 -0
  283. solveur/verification/mesh_checks.py +5 -0
  284. solveur/verification/mitc3_campaign.py +416 -0
  285. solveur/verification/mitc3_dynamic_extended.py +471 -0
  286. solveur/verification/mitc3_laminate_dynamic.py +376 -0
  287. solveur/verification/mitc3_models.py +369 -0
  288. solveur/verification/mitc3_refined.py +347 -0
  289. solveur/verification/mitc4_campaign.py +605 -0
  290. solveur/verification/mitc4_conical_cutout.py +271 -0
  291. solveur/verification/mitc4_harmonic.py +294 -0
  292. solveur/verification/mitc4_harmonic_broadband.py +383 -0
  293. solveur/verification/mitc4_harmonic_condensation.py +301 -0
  294. solveur/verification/mitc4_harmonic_nafems.py +513 -0
  295. solveur/verification/mitc4_laminate_dynamic.py +372 -0
  296. solveur/verification/mitc4_laminate_layups.py +148 -0
  297. solveur/verification/mitc4_modal.py +254 -0
  298. solveur/verification/mitc4_modal_extended.py +692 -0
  299. solveur/verification/mitc4_modal_external.py +262 -0
  300. solveur/verification/mitc4_modal_plate.py +310 -0
  301. solveur/verification/mitc4_newmark.py +270 -0
  302. solveur/verification/mitc4_newmark_broadband.py +377 -0
  303. solveur/verification/mitc4_newmark_extended.py +356 -0
  304. solveur/verification/mitc4_newmark_operational.py +508 -0
  305. solveur/verification/orthotropic_additional_stress.py +243 -0
  306. solveur/verification/orthotropic_complex_mesh.py +297 -0
  307. solveur/verification/orthotropic_convergence.py +230 -0
  308. solveur/verification/orthotropic_external.py +378 -0
  309. solveur/verification/orthotropic_modal_dynamic.py +363 -0
  310. solveur/verification/orthotropic_performance.py +193 -0
  311. solveur/verification/orthotropic_singularity_vnv.py +591 -0
  312. solveur/verification/orthotropic_solid.py +219 -0
  313. solveur/verification/singularity_stress.py +133 -0
  314. solveur/verification/tet10.py +176 -0
  315. solveur/verification/tet10_geometry_quadrature.py +248 -0
  316. solveur/verification/tet10_mass_modal_loads.py +299 -0
  317. solveur/verification/tet10_near_incompressible.py +270 -0
  318. solveur/verification/tet10_structural_convergence.py +552 -0
  319. solveur/verification/tet4_tl_buckling_refinement.py +179 -0
  320. solveur/verification/tet4_total_lagrangian.py +149 -0
  321. solveur/verification/tet4_total_lagrangian_assembly.py +335 -0
  322. solveur/verification/tet4_total_lagrangian_buckling.py +325 -0
  323. solveur/verification/tet4_total_lagrangian_postbuckling.py +295 -0
  324. solveur/verification/tet4_total_lagrangian_steps.py +141 -0
  325. solveur/verification/tet4_total_lagrangian_stress.py +236 -0
  326. solveur/verification/torsion_stress_probe.py +441 -0
  327. solveur/verification/total_lagrangian_structural.py +237 -0
  328. solveur/verification/traceability.py +398 -0
  329. solveur/verification/transient_modal_oracle.py +115 -0
  330. solveur/verification/vnv_benchmark_import.py +397 -0
  331. solveur/verification/vnv_manifest.py +55 -0
  332. solveur/verification/vnv_report.py +241 -0
  333. solveur/verification/vnv_runner.py +391 -0
  334. solveur/verification/vnv_schema.py +407 -0
  335. solveur/verification/vnv_torsion_import.py +516 -0
  336. solveur/verification/vnv_types.py +124 -0
  337. solveur/verification/vnv_visualization.py +231 -0
  338. solveur/version.py +17 -0
mitc4/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """MITC4 flat-shell finite element package."""
2
+
3
+ from mitc4.benchmarks import CantileverPlateBenchmark, ScordelisLoBenchmark, ShearLockingStudy
4
+ from mitc4.element import MITC4Element, Q4FullShearElement
5
+ from mitc4.material import ShellMaterial
6
+ from mitc4.model import ShellModel
7
+ from mitc4.verification import MechanicalVerifier, VerificationResult
8
+
9
+ __all__ = [
10
+ "CantileverPlateBenchmark",
11
+ "MITC4Element",
12
+ "MechanicalVerifier",
13
+ "Q4FullShearElement",
14
+ "ScordelisLoBenchmark",
15
+ "ShearLockingStudy",
16
+ "ShellMaterial",
17
+ "ShellModel",
18
+ "VerificationResult",
19
+ ]
mitc4/benchmarks.py ADDED
@@ -0,0 +1,183 @@
1
+ """Benchmark problems and shear-locking studies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+
11
+ from mitc4.constants import DOF_PER_NODE, UY, UZ
12
+ from mitc4.element import MITC4Element, Q4FullShearElement
13
+ from mitc4.material import ShellMaterial
14
+ from mitc4.mesh import MeshFactory
15
+ from mitc4.model import ShellModel
16
+ from mitc4.visualization import DeformationPlotter
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class BenchmarkResult:
21
+ name: str
22
+ values: dict[str, float]
23
+
24
+
25
+ class ScordelisLoBenchmark:
26
+ reference_w = -0.3024
27
+
28
+ def __init__(self, nx: int = 24, ny: int = 24):
29
+ self.nx = nx
30
+ self.ny = ny
31
+
32
+ def run(self, *, show: bool = False, png: Path | None = None, scale: float = 20.0) -> BenchmarkResult:
33
+ E = 4.32e8
34
+ nu = 0.0
35
+ t = 0.25
36
+ R = 25.0
37
+ L = 50.0
38
+ angle = math.radians(80.0)
39
+ qz = -90.0
40
+
41
+ mesh = MeshFactory.scordelis_lo(self.nx, self.ny)
42
+ model = ShellModel(mesh.nodes, mesh.quads, ShellMaterial(E=E, nu=nu, t=t))
43
+ area_element = (L / self.nx) * (R * angle / self.ny)
44
+ for conn in mesh.quads:
45
+ for node in conn:
46
+ model.add_nodal_load(int(node), UZ, qz * area_element / 4.0)
47
+
48
+ for i, p in enumerate(mesh.nodes):
49
+ if abs(p[0]) < 1.0e-10 or abs(p[0] - L) < 1.0e-10:
50
+ model.add_fixed_dof(i, UY)
51
+ model.add_fixed_dof(i, UZ)
52
+
53
+ model.add_fixed_dof(0, 0)
54
+ model.add_fixed_dof(0, 5)
55
+ U = model.solve()
56
+
57
+ mid_x = self.nx // 2
58
+ node_a = mid_x * (self.ny + 1) + 0
59
+ node_b = mid_x * (self.ny + 1) + self.ny
60
+ w_a = float(U[node_a * DOF_PER_NODE + UZ])
61
+ w_b = float(U[node_b * DOF_PER_NODE + UZ])
62
+ error = abs((w_a - self.reference_w) / self.reference_w) * 100.0
63
+ symmetry_error = abs(w_a - w_b) / abs(self.reference_w) * 100.0
64
+
65
+ if show or png is not None:
66
+ DeformationPlotter(scale=scale).plot(
67
+ mesh.nodes,
68
+ mesh.quads,
69
+ U,
70
+ title=f"Scordelis-Lo roof {self.nx}x{self.ny} - error {error:.2f}%",
71
+ png=png,
72
+ show=show,
73
+ )
74
+
75
+ return BenchmarkResult(
76
+ "Scordelis-Lo",
77
+ {
78
+ "w_edge_center": w_a,
79
+ "w_opposite_edge_center": w_b,
80
+ "reference": self.reference_w,
81
+ "error_percent": error,
82
+ "symmetry_error_percent": symmetry_error,
83
+ "num_nodes": float(mesh.nodes.shape[0]),
84
+ "num_elements": float(mesh.quads.shape[0]),
85
+ },
86
+ )
87
+
88
+
89
+ class CantileverPlateBenchmark:
90
+ def __init__(
91
+ self,
92
+ nx: int = 16,
93
+ ny: int = 4,
94
+ *,
95
+ length: float = 1.0,
96
+ width: float = 0.2,
97
+ thickness: float = 0.01,
98
+ force: float = -1000.0,
99
+ E: float = 210.0e9,
100
+ nu: float = 0.3,
101
+ element_type: type[MITC4Element] = MITC4Element,
102
+ ):
103
+ self.nx = nx
104
+ self.ny = ny
105
+ self.length = length
106
+ self.width = width
107
+ self.thickness = thickness
108
+ self.force = force
109
+ self.E = E
110
+ self.nu = nu
111
+ self.element_type = element_type
112
+
113
+ def run(self, *, show: bool = False, png: Path | None = None, scale: float | None = None) -> BenchmarkResult:
114
+ mesh = MeshFactory.rectangular_plate(self.nx, self.ny, self.length, self.width)
115
+ material = ShellMaterial(E=self.E, nu=self.nu, t=self.thickness)
116
+ model = ShellModel(mesh.nodes, mesh.quads, material, element_type=self.element_type)
117
+
118
+ tip_nodes = np.where(np.isclose(mesh.nodes[:, 0], self.length))[0]
119
+ for node in tip_nodes:
120
+ model.add_nodal_load(int(node), UZ, self.force / len(tip_nodes))
121
+
122
+ root_nodes = np.where(np.isclose(mesh.nodes[:, 0], 0.0))[0]
123
+ for node in root_nodes:
124
+ model.fix_node(int(node))
125
+
126
+ U = model.solve()
127
+ tip_w = float(np.mean(U[tip_nodes * DOF_PER_NODE + UZ]))
128
+ D = self.E * self.thickness**3 / (12.0 * (1.0 - self.nu**2))
129
+ reference = self.force * self.length**3 / (3.0 * D * self.width)
130
+ error = abs((tip_w - reference) / reference) * 100.0
131
+
132
+ if scale is None:
133
+ scale = 0.15 * self.length / max(abs(tip_w), 1.0e-30)
134
+ if show or png is not None:
135
+ DeformationPlotter(scale=scale).plot(
136
+ mesh.nodes,
137
+ mesh.quads,
138
+ U,
139
+ title=f"Cantilever plate {self.nx}x{self.ny} - {self.element_type.name}",
140
+ png=png,
141
+ show=show,
142
+ )
143
+
144
+ return BenchmarkResult(
145
+ "Cantilever plate",
146
+ {
147
+ "tip_w": tip_w,
148
+ "reference": reference,
149
+ "error_percent": error,
150
+ "ratio_to_reference": tip_w / reference,
151
+ "num_nodes": float(mesh.nodes.shape[0]),
152
+ "num_elements": float(mesh.quads.shape[0]),
153
+ },
154
+ )
155
+
156
+
157
+ class ShearLockingStudy:
158
+ """Compare MITC shear interpolation with full Q4 shear interpolation."""
159
+
160
+ def __init__(self, nx: int = 8, ny: int = 2, thicknesses: tuple[float, ...] = (1.0e-2, 1.0e-3, 1.0e-4)):
161
+ self.nx = nx
162
+ self.ny = ny
163
+ self.thicknesses = thicknesses
164
+
165
+ def run(self) -> BenchmarkResult:
166
+ values: dict[str, float] = {}
167
+ mitc_ratios = []
168
+ full_ratios = []
169
+ for t in self.thicknesses:
170
+ mitc = CantileverPlateBenchmark(self.nx, self.ny, thickness=t, element_type=MITC4Element).run()
171
+ full = CantileverPlateBenchmark(self.nx, self.ny, thickness=t, element_type=Q4FullShearElement).run()
172
+ mitc_ratio = mitc.values["ratio_to_reference"]
173
+ full_ratio = full.values["ratio_to_reference"]
174
+ mitc_ratios.append(mitc_ratio)
175
+ full_ratios.append(full_ratio)
176
+ values[f"mitc_ratio_t_{t:.0e}"] = mitc_ratio
177
+ values[f"full_ratio_t_{t:.0e}"] = full_ratio
178
+
179
+ values["mitc_ratio_spread"] = max(mitc_ratios) - min(mitc_ratios)
180
+ values["full_thin_ratio"] = full_ratios[-1]
181
+ values["locking_contrast"] = mitc_ratios[-1] / max(full_ratios[-1], 1.0e-30)
182
+ return BenchmarkResult("Shear locking study", values)
183
+
mitc4/cli.py ADDED
@@ -0,0 +1,115 @@
1
+ """Command line interface for the MITC4 package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Sequence
8
+
9
+ from mitc4.benchmarks import CantileverPlateBenchmark, ScordelisLoBenchmark, ShearLockingStudy
10
+ from mitc4.verification import MechanicalVerifier, print_results_table
11
+ from solveur.version import __version__
12
+
13
+
14
+ def command_verify(args: argparse.Namespace) -> int:
15
+ results = MechanicalVerifier().run(include_benchmark=not args.quick, png=args.png)
16
+ print_results_table(results)
17
+ return 0 if all(result.passed for result in results) else 1
18
+
19
+
20
+ def command_scordelis(args: argparse.Namespace) -> int:
21
+ result = ScordelisLoBenchmark(args.nx, args.ny).run(show=args.show, png=args.png, scale=args.scale)
22
+ values = result.values
23
+ print("\nScordelis-Lo roof")
24
+ print(f"mesh : {args.nx} x {args.ny}")
25
+ print(f"edge center Uz : {values['w_edge_center']:.8e} m")
26
+ print(f"opposite edge Uz : {values['w_opposite_edge_center']:.8e} m")
27
+ print(f"reference Uz : {values['reference']:.8e} m")
28
+ print(f"relative error : {values['error_percent']:.4f} %")
29
+ print(f"symmetry error : {values['symmetry_error_percent']:.4f} %")
30
+ if args.png is not None:
31
+ print(f"figure saved : {args.png}")
32
+ return 0 if values["error_percent"] < args.max_error else 1
33
+
34
+
35
+ def command_cantilever(args: argparse.Namespace) -> int:
36
+ result = CantileverPlateBenchmark(
37
+ args.nx,
38
+ args.ny,
39
+ length=args.length,
40
+ width=args.width,
41
+ thickness=args.thickness,
42
+ force=args.force,
43
+ E=args.E,
44
+ nu=args.nu,
45
+ ).run(show=args.show, png=args.png, scale=args.scale)
46
+ values = result.values
47
+ print("\nCantilever plate")
48
+ print(f"mesh : {args.nx} x {args.ny}")
49
+ print(f"tip Uz : {values['tip_w']:.8e} m")
50
+ print(f"EB/Kirchhoff check : {values['reference']:.8e} m")
51
+ print(f"relative difference : {values['error_percent']:.4f} %")
52
+ if args.png is not None:
53
+ print(f"figure saved : {args.png}")
54
+ return 0
55
+
56
+
57
+ def command_shear_study(args: argparse.Namespace) -> int:
58
+ result = ShearLockingStudy(args.nx, args.ny).run()
59
+ print("\nTransverse shear / shear-locking study")
60
+ for key, value in result.values.items():
61
+ print(f"{key:24s}: {value:.8e}")
62
+ return 0
63
+
64
+
65
+ def build_parser() -> argparse.ArgumentParser:
66
+ parser = argparse.ArgumentParser(description="MITC4 flat-shell solver and verification suite.")
67
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
68
+ sub = parser.add_subparsers(dest="command")
69
+
70
+ verify = sub.add_parser("verify", help="run mechanical verification tests")
71
+ verify.add_argument("--quick", action="store_true", help="skip Scordelis-Lo benchmark")
72
+ verify.add_argument("--png", type=Path, default=Path("results/scordelis_verification.png"), help="benchmark figure path")
73
+ verify.set_defaults(func=command_verify)
74
+
75
+ scordelis = sub.add_parser("scordelis", help="solve the Scordelis-Lo roof benchmark")
76
+ scordelis.add_argument("--nx", type=int, default=24)
77
+ scordelis.add_argument("--ny", type=int, default=24)
78
+ scordelis.add_argument("--scale", type=float, default=20.0)
79
+ scordelis.add_argument("--show", action="store_true")
80
+ scordelis.add_argument("--png", type=Path, default=Path("results/scordelis.png"))
81
+ scordelis.add_argument("--max-error", type=float, default=1.5)
82
+ scordelis.set_defaults(func=command_scordelis)
83
+
84
+ cantilever = sub.add_parser("cantilever", help="solve a clamped rectangular plate")
85
+ cantilever.add_argument("--nx", type=int, default=16)
86
+ cantilever.add_argument("--ny", type=int, default=4)
87
+ cantilever.add_argument("--length", type=float, default=1.0)
88
+ cantilever.add_argument("--width", type=float, default=0.2)
89
+ cantilever.add_argument("--thickness", type=float, default=0.01)
90
+ cantilever.add_argument("--force", type=float, default=-1000.0)
91
+ cantilever.add_argument("--E", type=float, default=210.0e9)
92
+ cantilever.add_argument("--nu", type=float, default=0.3)
93
+ cantilever.add_argument("--scale", type=float, default=None)
94
+ cantilever.add_argument("--show", action="store_true")
95
+ cantilever.add_argument("--png", type=Path, default=Path("results/cantilever.png"))
96
+ cantilever.set_defaults(func=command_cantilever)
97
+
98
+ shear = sub.add_parser("shear-study", help="compare MITC and full Q4 transverse shear behavior")
99
+ shear.add_argument("--nx", type=int, default=8)
100
+ shear.add_argument("--ny", type=int, default=2)
101
+ shear.set_defaults(func=command_shear_study)
102
+
103
+ return parser
104
+
105
+
106
+ def main(argv: Sequence[str] | None = None) -> int:
107
+ parser = build_parser()
108
+ args = parser.parse_args(argv)
109
+ if args.command is None:
110
+ args = parser.parse_args(["verify"])
111
+ return args.func(args)
112
+
113
+
114
+ if __name__ == "__main__":
115
+ raise SystemExit(main())
mitc4/constants.py ADDED
@@ -0,0 +1,11 @@
1
+ """Shared constants for the MITC4 package."""
2
+
3
+ DOF_PER_NODE = 6
4
+
5
+ UX = 0
6
+ UY = 1
7
+ UZ = 2
8
+ RX = 3
9
+ RY = 4
10
+ RZ = 5
11
+
mitc4/convergence.py ADDED
@@ -0,0 +1,282 @@
1
+ """Five-level structural convergence studies for the MITC4 element."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import asdict, dataclass
7
+
8
+ import numpy as np
9
+
10
+ from mitc4.benchmarks import ScordelisLoBenchmark
11
+ from mitc4.constants import DOF_PER_NODE, RX, RZ, UX, UY, UZ
12
+ from mitc4.material import ShellMaterial
13
+ from mitc4.model import ShellModel
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ConvergencePoint:
18
+ mesh: tuple[int, int]
19
+ element_count: int
20
+ value: float
21
+ reference: float
22
+ relative_error: float
23
+ solver_method: str = "direct"
24
+ solver_iterations: int | None = None
25
+ solver_relative_residual: float | None = None
26
+
27
+ def to_dict(self) -> dict[str, object]:
28
+ return asdict(self)
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class StructuralConvergence:
33
+ identifier: str
34
+ points: tuple[ConvergencePoint, ...]
35
+ error_limit: float
36
+ status: str
37
+ final_increment: float
38
+ minimum_reference_error: float
39
+ review_status: str
40
+ recommendation: str
41
+
42
+ def to_dict(self) -> dict[str, object]:
43
+ return {
44
+ "study_id": self.identifier,
45
+ "status": self.status,
46
+ "error_limit": self.error_limit,
47
+ "points": [point.to_dict() for point in self.points],
48
+ "final_increment": self.final_increment,
49
+ "minimum_reference_error": self.minimum_reference_error,
50
+ "review_status": self.review_status,
51
+ "recommendation": self.recommendation,
52
+ }
53
+
54
+
55
+ class Mitc4StructuralConvergence:
56
+ """Run Cook, Scordelis-Lo and pinched-cylinder h studies."""
57
+
58
+ def run(self, *, quick: bool = False) -> dict[str, StructuralConvergence]:
59
+ if quick:
60
+ return {}
61
+ return {
62
+ "cook": self._study(
63
+ "VNV-MITC4-COOK-001",
64
+ ((4, 4), (8, 8), (16, 16), (24, 24), (32, 32), (64, 64)),
65
+ 0.05,
66
+ _cook_point,
67
+ ),
68
+ "scordelis": self._study(
69
+ "VNV-MITC4-SCORDELIS-001",
70
+ ((8, 8), (12, 12), (16, 16), (24, 24), (32, 32)),
71
+ 0.02,
72
+ _scordelis_point,
73
+ ),
74
+ "pinched": self._study(
75
+ "VNV-MITC4-PINCHED-001",
76
+ ((8, 16), (12, 24), (16, 32), (24, 48), (32, 64)),
77
+ 0.10,
78
+ _pinched_point,
79
+ ),
80
+ }
81
+
82
+ @staticmethod
83
+ def drilling_sensitivity(
84
+ scales: tuple[float, ...] = (1.0e-6, 1.0e-5, 1.0e-4, 1.0e-3, 1.0e-2),
85
+ ) -> dict[str, object]:
86
+ points = [
87
+ {"drilling_scale": scale, "tip_displacement": _cook_point(16, 16, scale).value}
88
+ for scale in scales
89
+ ]
90
+ baseline = next(point["tip_displacement"] for point in points if point["drilling_scale"] == 1.0e-4)
91
+ for point in points:
92
+ point["relative_change"] = abs(point["tip_displacement"] - baseline) / max(abs(baseline), 1.0e-30)
93
+ plateau = max(
94
+ point["relative_change"]
95
+ for point in points
96
+ if 1.0e-5 <= point["drilling_scale"] <= 1.0e-3
97
+ )
98
+ return {
99
+ "study_id": "VNV-MITC4-DRILLING-001",
100
+ "status": "PASS" if plateau <= 0.01 else "FAIL",
101
+ "plateau_relative_change": plateau,
102
+ "plateau_limit": 0.01,
103
+ "selected_scale": 1.0e-4,
104
+ "points": points,
105
+ }
106
+
107
+ @staticmethod
108
+ def _study(
109
+ identifier: str,
110
+ meshes: tuple[tuple[int, int], ...],
111
+ limit: float,
112
+ runner: object,
113
+ ) -> StructuralConvergence:
114
+ points = tuple(runner(*mesh) for mesh in meshes)
115
+ final_error = points[-1].relative_error
116
+ minimum_error = min(point.relative_error for point in points)
117
+ final_increment = abs(points[-1].value - points[-2].value) / max(abs(points[-1].value), 1.0e-30)
118
+ status = "PASS" if final_error <= limit and final_increment <= limit else "FAIL"
119
+ review_status = "WARNING" if final_error > minimum_error + 0.01 else "PASS"
120
+ recommendation = (
121
+ "The reference error rises after an earlier minimum; audit the Cook reference and boundary conditions "
122
+ "before claiming full static acceptance."
123
+ if review_status == "WARNING"
124
+ else "Reference-error trend is compatible with the current acceptance criterion."
125
+ )
126
+ return StructuralConvergence(
127
+ identifier,
128
+ points,
129
+ limit,
130
+ status,
131
+ final_increment,
132
+ minimum_error,
133
+ review_status,
134
+ recommendation,
135
+ )
136
+
137
+
138
+ def cook_large_point(nx: int = 200, ny: int = 200) -> ConvergencePoint:
139
+ """Run an opt-in, memory-bounded Cook refinement point."""
140
+ return _cook_point(nx, ny, iterative=True)
141
+
142
+
143
+ def _cook_point(
144
+ nx: int,
145
+ ny: int,
146
+ drilling_scale: float = 1.0e-4,
147
+ *,
148
+ iterative: bool = False,
149
+ ) -> ConvergencePoint:
150
+ scale = 0.01
151
+ corners = scale * np.asarray(
152
+ [[0.0, 0.0, 0.0], [48.0, 44.0, 0.0], [48.0, 60.0, 0.0], [0.0, 44.0, 0.0]]
153
+ )
154
+ nodes, quads, node = _bilinear_mesh(corners, nx, ny)
155
+ young, thickness, total_force = 1.0e6, 0.01, 100.0
156
+ model = ShellModel(
157
+ nodes,
158
+ quads,
159
+ ShellMaterial(E=young, nu=1.0 / 3.0, t=thickness, drilling_scale=drilling_scale),
160
+ )
161
+ edge_length = float(np.linalg.norm(corners[2] - corners[1]))
162
+ traction = total_force / edge_length
163
+ for j in range(ny):
164
+ first, second = node(nx, j), node(nx, j + 1)
165
+ segment = float(np.linalg.norm(nodes[second] - nodes[first]))
166
+ model.add_nodal_load(first, UY, 0.5 * traction * segment)
167
+ model.add_nodal_load(second, UY, 0.5 * traction * segment)
168
+ for j in range(ny + 1):
169
+ model.fix_node(node(0, j))
170
+ diagnostics: dict[str, float | int | str] = {}
171
+ if iterative:
172
+ displacement, diagnostics = model.solve_iterative()
173
+ else:
174
+ displacement = model.solve()
175
+ tip = node(nx, ny)
176
+ value = float(displacement[tip * DOF_PER_NODE + UY])
177
+ reference = 23.96 * total_force / (young * thickness)
178
+ return _point(
179
+ nx,
180
+ ny,
181
+ value,
182
+ reference,
183
+ solver_method=str(diagnostics.get("method", "direct")),
184
+ solver_iterations=int(diagnostics["iterations"]) if "iterations" in diagnostics else None,
185
+ solver_relative_residual=(
186
+ float(diagnostics["relative_residual"]) if "relative_residual" in diagnostics else None
187
+ ),
188
+ )
189
+
190
+
191
+ def _scordelis_point(nx: int, ny: int) -> ConvergencePoint:
192
+ result = ScordelisLoBenchmark(nx, ny).run().values
193
+ value = 0.5 * (result["w_edge_center"] + result["w_opposite_edge_center"])
194
+ return _point(nx, ny, value, result["reference"])
195
+
196
+
197
+ def _pinched_point(nx: int, ntheta: int) -> ConvergencePoint:
198
+ length, radius = 600.0, 300.0
199
+ nodes, quads, node = _periodic_cylinder(length, radius, nx, ntheta)
200
+ model = ShellModel(nodes, quads, ShellMaterial(E=3.0e6, nu=0.3, t=3.0))
201
+ for j in range(ntheta):
202
+ for i in (0, nx):
203
+ model.add_fixed_dof(node(i, j), UY)
204
+ model.add_fixed_dof(node(i, j), UZ)
205
+ model.add_fixed_dof(node(i, j), RX)
206
+ model.add_fixed_dof(node(0, 0), UX)
207
+ model.add_fixed_dof(node(0, 0), RZ)
208
+ load_a = node(nx // 2, 0)
209
+ load_b = node(nx // 2, ntheta // 2)
210
+ model.add_nodal_load(load_a, UY, -1.0)
211
+ model.add_nodal_load(load_b, UY, 1.0)
212
+ displacement = model.solve()
213
+ value = abs(float(displacement[load_a * DOF_PER_NODE + UY]))
214
+ return _point(nx, ntheta, value, 1.8248e-5)
215
+
216
+
217
+ def _point(
218
+ nx: int,
219
+ ny: int,
220
+ value: float,
221
+ reference: float,
222
+ *,
223
+ solver_method: str = "direct",
224
+ solver_iterations: int | None = None,
225
+ solver_relative_residual: float | None = None,
226
+ ) -> ConvergencePoint:
227
+ return ConvergencePoint(
228
+ mesh=(nx, ny),
229
+ element_count=nx * ny,
230
+ value=value,
231
+ reference=reference,
232
+ relative_error=abs((value - reference) / reference),
233
+ solver_method=solver_method,
234
+ solver_iterations=solver_iterations,
235
+ solver_relative_residual=solver_relative_residual,
236
+ )
237
+
238
+
239
+ def _bilinear_mesh(
240
+ corners: np.ndarray, nx: int, ny: int
241
+ ) -> tuple[np.ndarray, np.ndarray, object]:
242
+ nodes = []
243
+ for i in range(nx + 1):
244
+ xi = i / nx
245
+ for j in range(ny + 1):
246
+ eta = j / ny
247
+ nodes.append(
248
+ (1.0 - xi) * (1.0 - eta) * corners[0]
249
+ + xi * (1.0 - eta) * corners[1]
250
+ + xi * eta * corners[2]
251
+ + (1.0 - xi) * eta * corners[3]
252
+ )
253
+
254
+ def node(i: int, j: int) -> int:
255
+ return i * (ny + 1) + j
256
+
257
+ quads = [
258
+ [node(i, j), node(i + 1, j), node(i + 1, j + 1), node(i, j + 1)]
259
+ for i in range(nx)
260
+ for j in range(ny)
261
+ ]
262
+ return np.asarray(nodes), np.asarray(quads, dtype=int), node
263
+
264
+
265
+ def _periodic_cylinder(
266
+ length: float, radius: float, nx: int, ntheta: int
267
+ ) -> tuple[np.ndarray, np.ndarray, object]:
268
+ nodes = [
269
+ [-0.5 * length + length * i / nx, radius * math.cos(2.0 * math.pi * j / ntheta), radius * math.sin(2.0 * math.pi * j / ntheta)]
270
+ for i in range(nx + 1)
271
+ for j in range(ntheta)
272
+ ]
273
+
274
+ def node(i: int, j: int) -> int:
275
+ return i * ntheta + (j % ntheta)
276
+
277
+ quads = [
278
+ [node(i, j), node(i + 1, j), node(i + 1, j + 1), node(i, j + 1)]
279
+ for i in range(nx)
280
+ for j in range(ntheta)
281
+ ]
282
+ return np.asarray(nodes), np.asarray(quads, dtype=int), node