├── .gitignore ├── AUTHORS.rst ├── CITATION.cff ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── README.md ├── README.rst ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── img │ ├── Folder_selection.png │ ├── square-4_elements-horizontal_disp.png │ ├── square-4_elements-vertical_disp.png │ ├── square-4_elements.png │ ├── square-4_elements.svg │ ├── template_schematic.pdf │ ├── template_schematic.png │ ├── template_schematic.svg │ └── wrench.png ├── index.rst ├── installation.rst ├── make.bat ├── modules.rst ├── readme.rst ├── tutorial.rst ├── tutorials │ ├── brazilian_test.rst │ ├── geometry_gmsh.rst │ ├── img │ │ ├── Agregar_arcos.png │ │ ├── Agregar_linea_fisica.png │ │ ├── Agregar_lineas.png │ │ ├── Agregar_puntos.png │ │ ├── Agregar_superficie.png │ │ ├── Guardar_malla.png │ │ ├── Mallar_2D.png │ │ ├── Motor_geometrico.png │ │ ├── Nuevo_archivo.png │ │ ├── Prueba_brasilera.pdf │ │ ├── Prueba_brasilera.svg │ │ ├── Prueba_brasilera_ux.svg │ │ ├── Prueba_brasilera_uy.svg │ │ ├── Ver_superficie_malla.png │ │ └── solids_GUI-ventana.png │ ├── old_format.rst │ ├── square_example.rst │ ├── template.geo │ ├── template.msh │ └── template_input.py └── usage.rst ├── examples ├── beam_convergence │ ├── beam_convergence.ipynb │ ├── error_vs_h.txt │ └── img │ │ ├── Beam_convergence.pdf │ │ ├── Beam_mesh-12.pdf │ │ ├── Beam_mesh-12288.pdf │ │ ├── Beam_mesh-192.pdf │ │ ├── Beam_mesh-3.pdf │ │ ├── Beam_mesh-3072.pdf │ │ ├── Beam_mesh-48.pdf │ │ ├── Beam_mesh-768.pdf │ │ ├── cantilever_beam.svg │ │ └── meshes.pdf ├── custom_barba.css ├── simple_truss │ ├── img │ │ ├── simple_truss.png │ │ └── simple_truss.svg │ └── simple_truss.ipynb └── square-4_elements │ ├── eles.txt │ ├── loads.txt │ ├── mater.txt │ └── nodes.txt ├── paper ├── paper.bib ├── paper.md └── wrench.png ├── requirements-dev.txt ├── requirements-docs.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── solidspy ├── __init__.py ├── __main__.py ├── assemutil.py ├── blochutil.py ├── femutil.py ├── gaussutil.py ├── postprocesor.py ├── preprocesor.py ├── solids_GUI.py ├── solutil.py └── uelutil.py └── tests ├── test_assemutil.py ├── test_bloch.py ├── test_femutil.py ├── test_gaussutil.py ├── test_integration.py ├── test_postprocesor.py ├── test_preprocesor.py ├── test_solutil.py └── test_uelutil.py /.gitignore: -------------------------------------------------------------------------------- 1 | ### FEM Python files ### 2 | 3 | # Output files 4 | out.txt 5 | KELEMS.txt 6 | KLOADS.txt 7 | KMATES.txt 8 | KNODES.txt 9 | MESHUTIL/contour 10 | MESHUTIL/mesher 11 | 12 | ### Fortran ### 13 | # Prerequisites 14 | *.d 15 | 16 | # Compiled Object files 17 | *.slo 18 | *.lo 19 | *.o 20 | *.obj 21 | 22 | # Precompiled Headers 23 | *.gch 24 | *.pch 25 | 26 | # Compiled Dynamic libraries 27 | *.so 28 | *.dylib 29 | *.dll 30 | 31 | # Fortran module files 32 | *.mod 33 | *.smod 34 | 35 | # Compiled Static libraries 36 | *.lai 37 | *.la 38 | *.a 39 | *.lib 40 | 41 | # Executables 42 | *.exe 43 | *.out 44 | *.app 45 | 46 | ### LaTeX ### 47 | ## Core latex/pdflatex auxiliary files: 48 | *.aux 49 | *.lof 50 | *.log 51 | *.lot 52 | *.fls 53 | *.toc 54 | *.fmt 55 | *.fot 56 | *.cb 57 | *.cb2 58 | 59 | ## Intermediate documents: 60 | *.dvi 61 | *-converted-to.* 62 | # these rules might exclude image files for figures etc. 63 | # *.ps 64 | # *.eps 65 | # *.pdf 66 | 67 | ## Generated if empty string is given at "Please type another file name for output:" 68 | .pdf 69 | 70 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 71 | *.bbl 72 | *.bcf 73 | *.blg 74 | *-blx.aux 75 | *-blx.bib 76 | *.brf 77 | *.run.xml 78 | 79 | ## Build tool auxiliary files: 80 | *.fdb_latexmk 81 | *.synctex 82 | *.synctex(busy) 83 | *.synctex.gz 84 | *.synctex.gz(busy) 85 | *.pdfsync 86 | 87 | ## Auxiliary and intermediate files from other packages: 88 | # algorithms 89 | *.alg 90 | *.loa 91 | 92 | # achemso 93 | acs-*.bib 94 | 95 | # amsthm 96 | *.thm 97 | 98 | # beamer 99 | *.nav 100 | *.pre 101 | *.snm 102 | *.vrb 103 | 104 | # changes 105 | *.soc 106 | 107 | # cprotect 108 | *.cpt 109 | 110 | # elsarticle (documentclass of Elsevier journals) 111 | *.spl 112 | 113 | # endnotes 114 | *.ent 115 | 116 | # fixme 117 | *.lox 118 | 119 | # feynmf/feynmp 120 | *.mf 121 | *.mp 122 | *.t[1-9] 123 | *.t[1-9][0-9] 124 | *.tfm 125 | *.[1-9] 126 | *.[1-9][0-9] 127 | 128 | #(r)(e)ledmac/(r)(e)ledpar 129 | *.end 130 | *.?end 131 | *.[1-9][0-9][0-9] 132 | *.[1-9]R 133 | *.[1-9][0-9]R 134 | *.[1-9][0-9][0-9]R 135 | *.eledsec[1-9] 136 | *.eledsec[1-9]R 137 | *.eledsec[1-9][0-9] 138 | *.eledsec[1-9][0-9]R 139 | *.eledsec[1-9][0-9][0-9] 140 | *.eledsec[1-9][0-9][0-9]R 141 | 142 | # glossaries 143 | *.acn 144 | *.acr 145 | *.glg 146 | *.glo 147 | *.gls 148 | *.glsdefs 149 | 150 | # gnuplottex 151 | *-gnuplottex-* 152 | 153 | # gregoriotex 154 | *.gaux 155 | *.gtex 156 | 157 | # hyperref 158 | 159 | # knitr 160 | *-concordance.tex 161 | # TODO Comment the next line if you want to keep your tikz graphics files 162 | *.tikz 163 | *-tikzDictionary 164 | 165 | # listings 166 | *.lol 167 | 168 | # makeidx 169 | *.idx 170 | *.ilg 171 | *.ind 172 | *.ist 173 | 174 | # minitoc 175 | *.maf 176 | *.mlf 177 | *.mlt 178 | *.mtc[0-9]* 179 | 180 | # minted 181 | _minted* 182 | *.pyg 183 | 184 | # morewrites 185 | *.mw 186 | 187 | # mylatexformat 188 | 189 | # nomencl 190 | *.nlo 191 | 192 | # pax 193 | *.pax 194 | 195 | # sagetex 196 | *.sagetex.sage 197 | *.sagetex.py 198 | *.sagetex.scmd 199 | 200 | # scrwfile 201 | *.wrt 202 | 203 | # sympy 204 | *.sout 205 | *.sympy 206 | sympy-plots-for-*.tex/ 207 | 208 | # pdfcomment 209 | *.upa 210 | *.upb 211 | 212 | # pythontex 213 | *.pytxcode 214 | pythontex-files-*/ 215 | 216 | # thmtools 217 | *.loe 218 | 219 | # TikZ & PGF 220 | *.dpth 221 | *.md5 222 | *.auxlock 223 | 224 | # todonotes 225 | *.tdo 226 | 227 | # easy-todo 228 | *.lod 229 | 230 | # xindy 231 | *.xdy 232 | 233 | # xypic precompiled matrices 234 | *.xyc 235 | 236 | # endfloat 237 | *.ttt 238 | *.fff 239 | 240 | # Latexian 241 | TSWLatexianTemp* 242 | 243 | ## Editors: 244 | # WinEdt 245 | *.bak 246 | *.sav 247 | 248 | # Texpad 249 | .texpadtmp 250 | 251 | # Kile 252 | *.backup 253 | 254 | # KBibTeX 255 | *~[0-9]* 256 | 257 | # auto folder when using emacs and auctex 258 | /auto/* 259 | 260 | # expex forward references with \gathertags 261 | *-tags.tex 262 | 263 | # VSCode 264 | .vscode/ 265 | 266 | ### Python ### 267 | # Byte-compiled / optimized / DLL files 268 | __pycache__/ 269 | *.py[cod] 270 | *$py.class 271 | 272 | # C extensions 273 | 274 | # Distribution / packaging 275 | .Python 276 | env/ 277 | build/ 278 | develop-eggs/ 279 | dist/ 280 | downloads/ 281 | eggs/ 282 | .eggs/ 283 | lib/ 284 | lib64/ 285 | parts/ 286 | sdist/ 287 | var/ 288 | wheels/ 289 | *.egg-info/ 290 | .installed.cfg 291 | *.egg 292 | 293 | # PyInstaller 294 | # Usually these files are written by a python script from a template 295 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 296 | *.manifest 297 | *.spec 298 | 299 | # Installer logs 300 | pip-log.txt 301 | pip-delete-this-directory.txt 302 | 303 | # Unit test / coverage reports 304 | htmlcov/ 305 | .tox/ 306 | .coverage 307 | .coverage.* 308 | .cache 309 | nosetests.xml 310 | coverage.xml 311 | *,cover 312 | .hypothesis/ 313 | 314 | # Translations 315 | *.mo 316 | *.pot 317 | 318 | # Django stuff: 319 | local_settings.py 320 | 321 | # Flask stuff: 322 | instance/ 323 | .webassets-cache 324 | 325 | # Scrapy stuff: 326 | .scrapy 327 | 328 | # Sphinx documentation 329 | docs/_build/ 330 | 331 | # PyBuilder 332 | target/ 333 | 334 | # Jupyter Notebook 335 | .ipynb_checkpoints 336 | 337 | # pyenv 338 | .python-version 339 | 340 | # celery beat schedule file 341 | celerybeat-schedule 342 | 343 | # dotenv 344 | .env 345 | 346 | # virtualenv 347 | .venv 348 | venv/ 349 | ENV/ 350 | 351 | # Spyder project settings 352 | .spyderproject 353 | 354 | # Rope project settings 355 | .ropeproject 356 | 357 | # Pytest 358 | .cache/ 359 | .pytest_cache/ 360 | 361 | 362 | ### Windows ### 363 | # Windows thumbnail cache files 364 | Thumbs.db 365 | ehthumbs.db 366 | ehthumbs_vista.db 367 | 368 | # Folder config file 369 | Desktop.ini 370 | 371 | # Recycle Bin used on file shares 372 | $RECYCLE.BIN/ 373 | 374 | # Windows Installer files 375 | *.cab 376 | *.msi 377 | *.msm 378 | *.msp 379 | 380 | # Windows shortcuts 381 | *.lnk 382 | 383 | ### Linux ### 384 | *~ 385 | 386 | # temporary files which can be created if a process still has a handle open of a deleted file 387 | .fuse_hidden* 388 | 389 | # KDE directory preferences 390 | .directory 391 | 392 | # Linux trash folder which might appear on any partition or disk 393 | .Trash-* 394 | 395 | # .nfs files are created when an open file is removed but is still being accessed 396 | .nfs* 397 | 398 | # nohup.out 399 | nohup.out 400 | 401 | ### macOS ### 402 | *.DS_Store 403 | .AppleDouble 404 | .LSOverride 405 | 406 | # Icon must end with two \r 407 | Icon 408 | 409 | 410 | # Thumbnails 411 | ._* 412 | 413 | # Files that might appear in the root of a volume 414 | .DocumentRevisions-V100 415 | .fseventsd 416 | .Spotlight-V100 417 | .TemporaryItems 418 | .Trashes 419 | .VolumeIcon.icns 420 | .com.apple.timemachine.donotpresent 421 | 422 | # Directories potentially created on remote AFP share 423 | .AppleDB 424 | .AppleDesktop 425 | Network Trash Folder 426 | Temporary Items 427 | .apdisk 428 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Principal developers 6 | -------------------- 7 | 8 | * Nicolas Guarin-Zapata (`@nicoguaro`) 9 | * Juan Gómez (`@jgomezc1`) 10 | 11 | 12 | Contributions 13 | ------------- 14 | 15 | * Edward Villegas Pulgarin (`@cosmoscalibur`) 16 | * Guillaume Huet (`@guillaumehuet`) 17 | * Marc Gehring (`mg494`) 18 | 19 | 20 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.1.0 5 | title: 'SolidsPy: 2D-Finite Element Analysis with Python' 6 | message: >- 7 | If you use this software, please cite it using the 8 | metadata from this file. 9 | type: software 10 | authors: 11 | - given-names: Nicolás 12 | family-names: Guarín-Zapata 13 | email: nguarinz@eafit.edu.co 14 | affiliation: Universidad EAFIT 15 | orcid: 'https://orcid.org/0000-0002-9435-1914' 16 | - given-names: Juan David 17 | family-names: Gómez 18 | email: jgomezc1@eafit.edu.co 19 | affiliation: Universidad EAFIT 20 | identifiers: 21 | - type: doi 22 | value: 10.5281/zenodo.4029270 23 | url: 'https://github.com/AppliedMechanics-EAFIT/SolidsPy' 24 | abstract: >- 25 | SolidsPy is a simple finite element analysis code 26 | for 2D elasticity problems. The code uses as input 27 | simple-to-create text files defining a model in 28 | terms of nodal, element, material, and load data. 29 | keywords: 30 | - finite element method 31 | - scientific computing 32 | - computational mechanics 33 | - elasticity 34 | - python 35 | license: MIT 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | 9 | Types of Contributions 10 | ---------------------- 11 | 12 | You can contribute in many ways: 13 | 14 | Create FEM Analysis 15 | ~~~~~~~~~~~~~~~~~~~ 16 | 17 | If you run a Finite Element Analysis using SolidsPy, and want to share it 18 | with the community, submit a pull request to our sibling project 19 | `SolidsPy-meshes `__. 20 | 21 | 22 | Report Bugs 23 | ~~~~~~~~~~~ 24 | 25 | Report bugs at https://github.com/AppliedMechanics-EAFIT/SolidsPy/issues. 26 | 27 | If you are reporting a bug, please include: 28 | 29 | * Your operating system name and version. 30 | * Any details about your local setup that might be helpful in troubleshooting. 31 | * If you can, provide detailed steps to reproduce the bug. 32 | * If you don't have steps to reproduce the bug, just note your observations in 33 | as much detail as you can. Questions to start a discussion about the issue 34 | are welcome. 35 | 36 | Fix Bugs 37 | ~~~~~~~~ 38 | 39 | Look through the GitHub issues for bugs. Anything tagged with "bug" 40 | is open to whoever wants to implement it. 41 | 42 | Implement Features 43 | ~~~~~~~~~~~~~~~~~~ 44 | 45 | Look through the GitHub issues for features. Anything tagged with "enhancement" 46 | and "please-help" is open to whoever wants to implement it. 47 | 48 | Please do not combine multiple feature enhancements into a single pull request. 49 | 50 | 51 | Write Documentation 52 | ~~~~~~~~~~~~~~~~~~~ 53 | 54 | SolidsPy could always use more documentation, whether as part of the 55 | official SolidsPy docs, in docstrings, or even on the web in blog posts, 56 | articles, and such. 57 | 58 | Submit Feedback 59 | ~~~~~~~~~~~~~~~ 60 | 61 | The best way to send feedback is to file an issue at 62 | https://github.com/AppliedMechanics-EAFIT/SolidsPy/issues. 63 | 64 | If you are proposing a feature: 65 | 66 | * Explain in detail how it would work. 67 | * Keep the scope as narrow as possible, to make it easier to implement. 68 | * Remember that this is a volunteer-driven project, and that contributions 69 | are welcome :) 70 | 71 | 72 | Contributor Guidelines 73 | ---------------------- 74 | 75 | Pull Request Guidelines 76 | ~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | Before you submit a pull request, check that it meets these guidelines: 79 | 80 | 1. The pull request should include tests. 81 | 2. If the pull request adds functionality, the docs should be updated. Put 82 | your new functionality into a function with a docstring, and add the 83 | feature to the list in README.rst. 84 | 3. The pull request should work for Python 2.7, 3.3, 3.4, 3.5, 3.6. 85 | 86 | Coding Standards 87 | ~~~~~~~~~~~~~~~~ 88 | 89 | * PEP8 90 | * Functions over classes except in tests 91 | * Quotes via http://stackoverflow.com/a/56190/5549 92 | 93 | * Use double quotes around strings that are used for interpolation or that are natural language messages 94 | * Use single quotes for small symbol-like strings (but break the rules if the strings contain quotes) 95 | * Use triple double quotes for docstrings and raw string literals for regular expressions even if they aren't needed. 96 | * Example: 97 | 98 | .. code-block:: python 99 | 100 | LIGHT_MESSAGES = { 101 | 'English': "There are %(number_of_lights)s lights.", 102 | 'Pirate': "Arr! Thar be %(number_of_lights)s lights." 103 | } 104 | 105 | def lights_message(language, number_of_lights): 106 | """Return a language-appropriate string reporting the light count.""" 107 | return LIGHT_MESSAGES[language] % locals() 108 | 109 | def is_pirate(message): 110 | """Return True if the given message sounds piratical.""" 111 | return re.search(r"(?i)(arr|avast|yohoho)!", message) is not None 112 | 113 | * Write new code in Python 3. 114 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ------- 3 | 4 | 1.1.0 (2023-11-03) 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | * Remove NumPy types in the code that were removed since NumPy 1.24 8 | 9 | 1.0.15 (2018-05-09) 10 | ~~~~~~~~~~~~~~~~~~ 11 | 12 | * Fix element ordering in `rectgrid` and doctests. 13 | 14 | 1.0.14 (2018-05-08) 15 | ~~~~~~~~~~~~~~~~~~ 16 | 17 | * Add Jacobian checks. 18 | * Pytest catch exceptions. 19 | 20 | 1.0.13 (2018-05-07) 21 | ~~~~~~~~~~~~~~~~~~ 22 | 23 | * Update meshio syntax for physical groups. 24 | * Add citation information to package. 25 | 26 | 1.0.12 (2018-04-16) 27 | ~~~~~~~~~~~~~~~~~~ 28 | 29 | * Documentation built with Sphinx. 30 | * Docs in ReadTheDocs. 31 | 32 | 1.0.0 (2017-07-17) 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | * First release on PyPI. 36 | 37 | Roadmap 38 | ------- 39 | 40 | Pending ... 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 - 2018 Juan Gomez and Nicolás Guarín-Zapata 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolidsPy: 2D-Finite Element Analysis with Python 2 | 3 | ![](https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/master/docs/img/wrench.png) 4 | [![PyPI download](https://img.shields.io/pypi/v/solidspy.svg)](https://pypi.python.org/pypi/continuum_mechanics) 5 | [![Documentation Status](https://readthedocs.org/projects/solidspy/badge/?version=latest)](https://solidspy.readthedocs.io/en/latest/) 6 | [![Downloads frequency](https://img.shields.io/pypi/dm/solidspy)](https://pypistats.org/packages/solidspy) 7 | [![image](https://zenodo.org/badge/48294591.svg)](https://zenodo.org/badge/latestdoi/48294591) 8 | 9 | A simple finite element analysis code for 2D elasticity problems. The code uses 10 | as input simple-to-create text files defining a model in terms of nodal, 11 | element, material and load data. 12 | 13 | - Documentation: 14 | - GitHub: 15 | - PyPI: 16 | - Free and open source software: [MIT license](http://en.wikipedia.org/wiki/MIT_License) 17 | 18 | ## Features 19 | 20 | - It is based on an open-source environment. 21 | - It is easy to use. 22 | - The code allows to find displacement, strain and stress solutions 23 | for arbitrary two-dimensional domains discretized into finite 24 | elements and subject to point loads. 25 | - The code is organized in independent modules for pre-processing, 26 | assembly and post-processing allowing the user to easily modify it 27 | or add features like new elements or analyses pipelines. 28 | - It was created with academic and research purposes. 29 | - It has been used to tech the following courses: 30 | - Introduction to Solid Mechanics. 31 | - Computational Modeling. 32 | - Introduction to the Finite Element Methods. 33 | - Introduction to Soil Mechanics. 34 | 35 | ## Installation 36 | 37 | The code is written in Python and it depends on `numpy`, and `scipy` and. It 38 | has been tested under Windows, Mac, Linux and Android. 39 | 40 | To install *SolidsPy* open a terminal and type: 41 | 42 | pip install solidspy 43 | 44 | To specify through a GUI the folder where the input files are stored you will 45 | need to install [easygui](http://easygui.readthedocs.org/en/master/). 46 | 47 | To easily generate the required SolidsPy text files out of a 48 | [Gmsh](http://gmsh.info/) model you will need [meshio](https://github.com/nschloe/meshio). 49 | 50 | These two can be installed with: 51 | 52 | pip install easygui 53 | pip install meshio 54 | 55 | ## How to run a simple model 56 | 57 | For further explanation check the [docs](http://solidspy.readthedocs.io/en/latest/). 58 | 59 | Let's suppose that we have a simple model represented by the following files 60 | (see [tutorials/square example](http://solidspy.readthedocs.io/en/latest/tutorials/square_example.html) 61 | for further explanation). 62 | 63 | - `nodes.txt` 64 | 65 | ``` 66 | 0 0.00 0.00 0 -1 67 | 1 2.00 0.00 0 -1 68 | 2 2.00 2.00 0 0 69 | 3 0.00 2.00 0 0 70 | 4 1.00 0.00 -1 -1 71 | 5 2.00 1.00 0 0 72 | 6 1.00 2.00 0 0 73 | 7 0.00 1.00 0 0 74 | 8 1.00 1.00 0 0 75 | ``` 76 | 77 | 78 | - `eles.txt` 79 | 80 | ``` 81 | 0 1 0 0 4 8 7 82 | 1 1 0 4 1 5 8 83 | 2 1 0 7 8 6 3 84 | 3 1 0 8 5 2 6 85 | ``` 86 | 87 | 88 | - `mater.txt` 89 | 90 | ``` 91 | 1.0 0.3 92 | ``` 93 | 94 | 95 | - `loads.txt` 96 | 97 | ``` 98 | 3 0.0 1.0 99 | 6 0.0 2.0 100 | 2 0.0 1.0 101 | ``` 102 | 103 | 104 | Run it in Python as follows: 105 | 106 | ``` python 107 | import matplotlib.pyplot as plt # load matplotlib 108 | from solidspy import solids_GUI # import our package 109 | disp = solids_GUI() # run the Finite Element Analysis 110 | plt.show() # plot contours 111 | ``` 112 | 113 | For Mac users it is suggested to use an IPython console to run the example. 114 | 115 | ## License 116 | 117 | This project is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License). 118 | The documents are licensed under [Creative Commons Attribution License](http://creativecommons.org/licenses/by/4.0/). 119 | 120 | ## Citation 121 | 122 | To cite SolidsPy in publications use 123 | 124 | > Nicolás Guarín-Zapata, Juan Gomez (2023). SolidsPy: Version 1.1.0 125 | > (Version v1.1.0). Zenodo. 126 | 127 | A BibTeX entry for LaTeX users is 128 | 129 | ```bibtex 130 | @software{solidspy, 131 | title = {SolidsPy: 2D-Finite Element Analysis with Python}, 132 | version = {1.1.0}, 133 | author = {Guarín-Zapata, Nicolás and Gómez, Juan}, 134 | year = 2023, 135 | keywords = {Python, Finite elements, Scientific computing, Computational mechanics}, 136 | abstract = {SolidsPy is a simple finite element analysis code for 2D elasticity 137 | problems. The code uses as input simple-to-create text files defining a model 138 | in terms of nodal, element, material and load data.}, 139 | url = {https://github.com/AppliedMechanics-EAFIT/SolidsPy}, 140 | doi = {https://doi.org/10.5281/zenodo.7694030} 141 | } 142 | ``` 143 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | SolidsPy: 2D-Finite Element Analysis with Python 2 | ================================================ 3 | 4 | .. figure:: https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/master/docs/img/wrench.png 5 | :alt: Wrench under bending 6 | 7 | .. image:: https://img.shields.io/pypi/v/solidspy.svg 8 | :target: https://pypi.python.org/pypi/continuum_mechanics 9 | :alt: PyPI download 10 | 11 | .. image:: https://readthedocs.org/projects/solidspy/badge/?version=latest 12 | :target: https://solidspy.readthedocs.io/en/latest/ 13 | :alt: Documentation Status 14 | 15 | .. image:: https://img.shields.io/pypi/dm/solidspy 16 | :target: https://pypistats.org/packages/solidspy 17 | :alt: Downloads frequency 18 | 19 | .. image:: https://zenodo.org/badge/48294591.svg 20 | :target: https://zenodo.org/badge/latestdoi/48294591 21 | 22 | 23 | A simple finite element analysis code for 2D elasticity problems. 24 | The code uses as input simple-to-create text files 25 | defining a model in terms of nodal, element, material and load data. 26 | 27 | - Documentation: http://solidspy.readthedocs.io 28 | - GitHub: https://github.com/AppliedMechanics-EAFIT/SolidsPy 29 | - PyPI: https://pypi.org/project/solidspy/ 30 | - Free and open source software: `MIT license `__ 31 | 32 | 33 | 34 | Features 35 | -------- 36 | 37 | * It is based on an open-source environment. 38 | 39 | * It is easy to use. 40 | 41 | * The code allows to find displacement, strain and stress solutions for 42 | arbitrary two-dimensional domains discretized into finite elements and 43 | subject to point loads. 44 | 45 | * The code is organized in independent modules for pre-processing, assembly and 46 | post-processing allowing the user to easily modify it or add features like 47 | new elements or analyses pipelines. 48 | 49 | * It was created with academic and research purposes. 50 | 51 | * It has been used to tech the following courses: 52 | 53 | - Computational Modeling. 54 | - Introduction to the Finite Element Methods. 55 | 56 | 57 | Installation 58 | ------------ 59 | 60 | The code is written in Python and it depends on ``numpy``, and ``scipy`` 61 | and. It has been tested under Windows, Mac, Linux and Android. 62 | 63 | To install *SolidsPy* open a terminal and type: 64 | 65 | :: 66 | 67 | pip install solidspy 68 | 69 | To specify through a GUI the folder where the input 70 | files are stored you will need to install `easygui `__. 71 | 72 | To easily generate the required SolidsPy text files out of a 73 | `Gmsh `__ model you will need 74 | `meshio `__. 75 | 76 | These two can be installed with: 77 | 78 | :: 79 | 80 | pip install easygui 81 | pip install meshio 82 | 83 | 84 | How to run a simple model 85 | ------------------------- 86 | 87 | For further explanation check the `docs `__. 88 | 89 | Let's suppose that we have a simple model represented by the following 90 | files (see `tutorials/square example `__ 91 | for further explanation). 92 | 93 | 94 | - ``nodes.txt`` 95 | 96 | :: 97 | 98 | 0 0.00 0.00 0 -1 99 | 1 2.00 0.00 0 -1 100 | 2 2.00 2.00 0 0 101 | 3 0.00 2.00 0 0 102 | 4 1.00 0.00 -1 -1 103 | 5 2.00 1.00 0 0 104 | 6 1.00 2.00 0 0 105 | 7 0.00 1.00 0 0 106 | 8 1.00 1.00 0 0 107 | 108 | - ``eles.txt`` 109 | 110 | :: 111 | 112 | 0 1 0 0 4 8 7 113 | 1 1 0 4 1 5 8 114 | 2 1 0 7 8 6 3 115 | 3 1 0 8 5 2 6 116 | 117 | - ``mater.txt`` 118 | 119 | :: 120 | 121 | 1.0 0.3 122 | 123 | - ``loads.txt`` 124 | 125 | :: 126 | 127 | 3 0.0 1.0 128 | 6 0.0 2.0 129 | 2 0.0 1.0 130 | 131 | Run it in Python as follows: 132 | 133 | .. code:: python 134 | 135 | import matplotlib.pyplot as plt # load matplotlib 136 | from solidspy import solids_GUI # import our package 137 | disp = solids_GUI() # run the Finite Element Analysis 138 | plt.show() # plot contours 139 | 140 | For Mac users it is suggested to use an IPython console to run the example. 141 | 142 | 143 | License 144 | ------- 145 | 146 | This project is licensed under the `MIT 147 | license `__. The documents are 148 | licensed under `Creative Commons Attribution 149 | License `__. 150 | 151 | Citation 152 | -------- 153 | 154 | To cite SolidsPy in publications use 155 | 156 | Nicolás Guarín-Zapata, Juan Gomez (2020). SolidsPy: Version 1.0.16 157 | (Version v1.0.16). Zenodo. http://doi.org/10.5281/zenodo.4029270 158 | 159 | A BibTeX entry for LaTeX users is 160 | 161 | .. code:: bibtex 162 | 163 | @software{solidspy, 164 | title = {SolidsPy: 2D-Finite Element Analysis with Python}, 165 | version = {1.0.16}, 166 | author = {Guarín-Zapata, Nicolás and Gómez, Juan}, 167 | year = 2020, 168 | keywords = {Python, Finite elements, Scientific computing, Computational mechanics}, 169 | abstract = {SolidsPy is a simple finite element analysis code for 170 | 2D elasticity problems. The code uses as input simple-to-create text 171 | files defining a model in terms of nodal, element, material and 172 | load data.}, 173 | url = {https://github.com/AppliedMechanics-EAFIT/SolidsPy}, 174 | doi = {http://doi.org/10.5281/zenodo.4029270} 175 | } 176 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = SolidsPy 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # SolidsPy documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Apr 16 18:07:33 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('../solidspy')) 23 | 24 | import solidspy 25 | 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.doctest', 39 | 'sphinx.ext.mathjax', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.napoleon', 42 | 'sphinx.ext.todo'] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix(es) of source filenames. 48 | # You can specify multiple suffix as a list of string: 49 | # 50 | # source_suffix = ['.rst', '.md'] 51 | source_suffix = '.rst' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = 'SolidsPy' 58 | copyright = u'2023, Nicolás Guarín-Zapata & Juan Gómez' 59 | author = u'Nicolás Guarín-Zapata & Juan Gómez' 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = '1.1' 67 | # The full version, including alpha/beta/rc tags. 68 | release = '1.1.0' 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | # This patterns also effect to html_static_path and html_extra_path 80 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 81 | 82 | # The name of the Pygments (syntax highlighting) style to use. 83 | pygments_style = 'sphinx' 84 | 85 | # If true, `todo` and `todoList` produce output, else they produce nothing. 86 | todo_include_todos = False 87 | 88 | 89 | # -- Options for HTML output ---------------------------------------------- 90 | 91 | # The theme to use for HTML and HTML Help pages. See the documentation for 92 | # a list of builtin themes. 93 | # 94 | html_theme = 'alabaster' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | # 100 | html_theme_options = { 101 | 'show_related': True 102 | } 103 | 104 | # Add any paths that contain custom static files (such as style sheets) here, 105 | # relative to this directory. They are copied after the builtin static files, 106 | # so a file named "default.css" will overwrite the builtin "default.css". 107 | html_static_path = ['_static'] 108 | 109 | # Custom sidebar templates, must be a dictionary that maps document names 110 | # to template names. 111 | # 112 | # This is required for the alabaster theme 113 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 114 | html_sidebars = { 115 | '**': [ 116 | # 'about.html', 117 | # 'navigation.html', 118 | 'relations.html', # needs 'show_related': True theme option to display 119 | 'searchbox.html', 120 | ] 121 | } 122 | 123 | 124 | # -- Options for HTMLHelp output ------------------------------------------ 125 | 126 | # Output file base name for HTML help builder. 127 | htmlhelp_basename = 'SolidsPydoc' 128 | 129 | 130 | # -- Options for LaTeX output --------------------------------------------- 131 | 132 | latex_elements = { 133 | # The paper size ('letterpaper' or 'a4paper'). 134 | # 135 | # 'papersize': 'letterpaper', 136 | 137 | # The font size ('10pt', '11pt' or '12pt'). 138 | # 139 | # 'pointsize': '10pt', 140 | 141 | # Additional stuff for the LaTeX preamble. 142 | # 143 | # 'preamble': '', 144 | 145 | # Latex figure (float) alignment 146 | # 147 | # 'figure_align': 'htbp', 148 | } 149 | 150 | # Grouping the document tree into LaTeX files. List of tuples 151 | # (source start file, target name, title, 152 | # author, documentclass [howto, manual, or own class]). 153 | latex_documents = [ 154 | (master_doc, 'SolidsPy.tex', 'SolidsPy Documentation', 155 | u'Nicolás Guarín-Zapata \\& Juan Gómez', 'manual'), 156 | ] 157 | 158 | 159 | # -- Options for manual page output --------------------------------------- 160 | 161 | # One entry per manual page. List of tuples 162 | # (source start file, name, description, authors, manual section). 163 | man_pages = [ 164 | (master_doc, 'solidspy', 'SolidsPy Documentation', 165 | [author], 1) 166 | ] 167 | 168 | 169 | # -- Options for Texinfo output ------------------------------------------- 170 | 171 | # Grouping the document tree into Texinfo files. List of tuples 172 | # (source start file, target name, title, author, 173 | # dir menu entry, description, category) 174 | texinfo_documents = [ 175 | (master_doc, 'SolidsPy', 'SolidsPy Documentation', 176 | author, 'SolidsPy', 'One line description of project.', 177 | 'Miscellaneous'), 178 | ] 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/img/Folder_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/Folder_selection.png -------------------------------------------------------------------------------- /docs/img/square-4_elements-horizontal_disp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/square-4_elements-horizontal_disp.png -------------------------------------------------------------------------------- /docs/img/square-4_elements-vertical_disp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/square-4_elements-vertical_disp.png -------------------------------------------------------------------------------- /docs/img/square-4_elements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/square-4_elements.png -------------------------------------------------------------------------------- /docs/img/template_schematic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/template_schematic.pdf -------------------------------------------------------------------------------- /docs/img/template_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/template_schematic.png -------------------------------------------------------------------------------- /docs/img/wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/img/wrench.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. SolidsPy documentation master file, created by 2 | sphinx-quickstart on Mon Apr 16 18:07:33 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to SolidsPy's documentation! 7 | ==================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | readme 14 | tutorial 15 | installation 16 | usage 17 | contributing 18 | authors 19 | modules 20 | history 21 | 22 | 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The code is written in Python and it depends on ``numpy``, ``scipy`` and 5 | ``sympy``. It has been tested under Windows, Mac, Linux and Android. 6 | 7 | To install *SolidsPy* open a terminal and type: 8 | 9 | :: 10 | 11 | pip install solidspy 12 | 13 | To specify through a GUI the folder where the input 14 | files are stored you will need to install `easygui `__. 15 | 16 | To easily generate the required SolidsPy text files out of a 17 | `Gmsh `__ model you will need 18 | `meshio `__. 19 | 20 | These two can be installed with: 21 | 22 | :: 23 | 24 | pip install easygui 25 | pip install meshio 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=SolidsPy 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | SolidsPy reference 2 | ================== 3 | 4 | SolidsPy have the following modules: 5 | 6 | - ``solids_GUI.py``: The main program; 7 | - ``preprocesor.py``: Pre-processing subroutines including 8 | `Gmsh `__ convertion functions using 9 | `meshio `__ 10 | - ``assemutil.py``: Assembly of elemental stiffness matrices ; 11 | - ``femutil.py``: Shape functions, its derivatives and general 12 | finite element method subroutines; 13 | - ``uelutil.py``: Elemental or local matrix subroutines for 14 | different elements; and 15 | - ``postprocesor.py``: Several results handling subroutines. 16 | 17 | 18 | .. automodule:: solids_GUI 19 | :members: 20 | 21 | .. automodule:: assemutil 22 | :members: 23 | 24 | .. automodule:: femutil 25 | :members: 26 | 27 | .. automodule:: gaussutil 28 | :members: 29 | 30 | .. automodule:: preprocesor 31 | :members: 32 | 33 | .. automodule:: postprocesor 34 | :members: 35 | 36 | .. automodule:: solutil 37 | :members: 38 | 39 | .. automodule:: uelutil 40 | :members: 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | SolidsPy: 2D-Finite Element Analysis with Python 2 | ================================================ 3 | 4 | .. figure:: https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/master/docs/img/wrench.png 5 | :alt: Wrench under bending. 6 | 7 | .. image:: https://img.shields.io/pypi/v/solidspy.svg 8 | :target: https://pypi.python.org/pypi/continuum_mechanics 9 | :alt: PyPI download 10 | 11 | .. image:: https://readthedocs.org/projects/solidspy/badge/?version=latest 12 | :target: https://solidspy.readthedocs.io/en/latest/ 13 | :alt: Documentation Status 14 | 15 | .. image:: https://img.shields.io/pypi/dm/solidspy 16 | :target: https://pypistats.org/packages/solidspy 17 | :alt: Downloads frequency 18 | 19 | .. image:: https://zenodo.org/badge/48294591.svg 20 | :target: https://zenodo.org/badge/latestdoi/48294591 21 | 22 | 23 | A simple finite element analysis code for 2D elasticity problems. 24 | The code uses as input simple-to-create text files 25 | defining a model in terms of nodal, element, material and load data. 26 | 27 | - Documentation: http://solidspy.readthedocs.io 28 | - GitHub: https://github.com/AppliedMechanics-EAFIT/SolidsPy 29 | - PyPI: https://pypi.org/project/solidspy/ 30 | - Free and open source software: `MIT license `__ 31 | 32 | 33 | 34 | Features 35 | -------- 36 | 37 | * It is based on an open-source environment. 38 | 39 | * It is easy to use. 40 | 41 | * The code allows to find displacement, strain and stress solutions for 42 | arbitrary two-dimensional domains discretized into finite elements and 43 | subject to point loads. 44 | 45 | * The code is organized in independent modules for pre-processing, assembly and 46 | post-processing allowing the user to easily modify it or add features like 47 | new elements or analyses pipelines. 48 | 49 | * It was created with academic and research purposes. 50 | 51 | * It has been used to tech the following courses: 52 | 53 | - Computational Modeling. 54 | - Introduction to the Finite Element Methods. 55 | 56 | 57 | Installation 58 | ------------ 59 | 60 | The code is written in Python and it depends on ``numpy``, and ``scipy`` 61 | and. It has been tested under Windows, Mac, Linux and Android. 62 | 63 | To install *SolidsPy* open a terminal and type: 64 | 65 | :: 66 | 67 | pip install solidspy 68 | 69 | To specify through a GUI the folder where the input 70 | files are stored you will need to install `easygui `__. 71 | 72 | To easily generate the required SolidsPy text files out of a 73 | `Gmsh `__ model you will need 74 | `meshio `__. 75 | 76 | These two can be installed with: 77 | 78 | :: 79 | 80 | pip install easygui 81 | pip install meshio 82 | 83 | 84 | How to run a simple model 85 | ------------------------- 86 | 87 | For further explanation check the `docs `__. 88 | 89 | Let's suppose that we have a simple model represented by the following 90 | files (see `tutorials/square example `__ 91 | for further explanation). 92 | 93 | 94 | - ``nodes.txt`` 95 | 96 | :: 97 | 98 | 0 0.00 0.00 0 -1 99 | 1 2.00 0.00 0 -1 100 | 2 2.00 2.00 0 0 101 | 3 0.00 2.00 0 0 102 | 4 1.00 0.00 -1 -1 103 | 5 2.00 1.00 0 0 104 | 6 1.00 2.00 0 0 105 | 7 0.00 1.00 0 0 106 | 8 1.00 1.00 0 0 107 | 108 | - ``eles.txt`` 109 | 110 | :: 111 | 112 | 0 1 0 0 4 8 7 113 | 1 1 0 4 1 5 8 114 | 2 1 0 7 8 6 3 115 | 3 1 0 8 5 2 6 116 | 117 | - ``mater.txt`` 118 | 119 | :: 120 | 121 | 1.0 0.3 122 | 123 | - ``loads.txt`` 124 | 125 | :: 126 | 127 | 3 0.0 1.0 128 | 6 0.0 2.0 129 | 2 0.0 1.0 130 | 131 | Run it in Python as follows: 132 | 133 | .. code:: python 134 | 135 | import matplotlib.pyplot as plt # load matplotlib 136 | from solidspy import solids_GUI # import our package 137 | disp = solids_GUI() # run the Finite Element Analysis 138 | plt.show() # plot contours 139 | 140 | For Mac users it is suggested to use an IPython console to run the example. 141 | 142 | 143 | License 144 | ------- 145 | 146 | This project is licensed under the `MIT 147 | license `__. The documents are 148 | licensed under `Creative Commons Attribution 149 | License `__. 150 | 151 | Citation 152 | -------- 153 | 154 | To cite SolidsPy in publications use 155 | 156 | Nicolás Guarín-Zapata, Juan Gomez (2023). SolidsPy: Version 1.1.0 157 | (Version v1.1.0). Zenodo. 158 | 159 | A BibTeX entry for LaTeX users is 160 | 161 | .. code:: bibtex 162 | 163 | @software{solidspy, 164 | title = {SolidsPy: 2D-Finite Element Analysis with Python}, 165 | version = {1.1.0}, 166 | author = {Guarín-Zapata, Nicolás and Gómez, Juan}, 167 | year = 2023, 168 | keywords = {Python, Finite elements, Scientific computing, Computational mechanics}, 169 | abstract = {SolidsPy is a simple finite element analysis code for 170 | 2D elasticity problems. The code uses as input simple-to-create text 171 | files defining a model in terms of nodal, element, material and 172 | load data.}, 173 | url = {https://github.com/AppliedMechanics-EAFIT/SolidsPy}, 174 | doi = {http://doi.org/10.5281/zenodo.4029270} 175 | } 176 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | SolidsPy's tutorials 2 | ==================== 3 | 4 | Here we show through simple examples how to conduct analyses using 5 | SolidsPy. 6 | 7 | First we describe the structure of the text files for the 8 | problem of small 2×2 square plate under axial loading. In the second part 9 | of the documents we describe the creation of a 10 | SolidsPy model with the aid of `Gmsh `__. This is 11 | necessary when conducting analysis in large and complex geometries. In 12 | this case the Gmsh files need to be converted into text files using 13 | subroutines based upon `meshio `__. 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :caption: Contents: 18 | 19 | tutorials/square_example 20 | tutorials/geometry_gmsh 21 | tutorials/brazilian_test 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/tutorials/brazilian_test.rst: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | Geometry in Gmsh and solution with SolidsPy 3 | ========================================================================== 4 | 5 | :Author: Nicolás Guarín-Zapata 6 | :Date: September, 2018 7 | 8 | This document is a tutorial on how to generate a (specific) geometry 9 | using Gmsh [Gmsh2009]_ and its subsequent processing for the generation 10 | of input files for a Finite Element program in Python. This document 11 | does not pretend to be an introduction to the management of Gmsh, 12 | for this we suggest the official tutorial [Gmsh_tut]_ 13 | (for text mode) or the official screencasts [Gmsh_scr]_ 14 | (for the graphical interface). 15 | 16 | Model 17 | ===== 18 | 19 | The example to be solved corresponds to the determination of 20 | Efforts in a cylinder in the *Brazilian Test*. The Brazilian Test 21 | is a technique that is used for the indirect measurement of the resistance of 22 | rocks It is a simple and effective technique, and therefore it is commonly used 23 | for rock measurements. Sometimes this test is also used 24 | for concrete [D3967-16]_. 25 | 26 | The following figure presents a scheme of the model to solve. Since the 27 | original model may present rigid body movements, it 28 | decides to use the symmetry of the problem. Then, the problem to 29 | solve is a quarter of the original problem and the surfaces lower e 30 | left present restrictions of *roller*. 31 | 32 | .. figure:: img/Prueba_brasilera.svg 33 | :alt: Schematic of the problem to be solved. 34 | :width: 400 px 35 | 36 | Schematic of the problem to be solved. 37 | 38 | Creation of the geometry and mesh in Gmsh 39 | ========================================= 40 | 41 | As a first step, it is suggested to create a new file in Gmsh, as 42 | It shows in the following figure. 43 | 44 | .. figure:: img/Nuevo_archivo.png 45 | :alt: Creation of a new file in Gmsh. 46 | :width: 800 px 47 | 48 | Creation of a new file in Gmsh. 49 | 50 | When creating a new document it is possible [1]_ for Gmsh to ask about which 51 | geometry kernel to use. We will not dwell on what the differences are 52 | and we will use ``built-in``. 53 | 54 | .. figure:: img/Motor_geometrico.png 55 | :alt: Pop-up window asking for the geometry kernel. 56 | :width: 400 px 57 | 58 | Pop-up window asking for the geometry kernel. 59 | 60 | To create a model, we initially create the points. For that, let's go 61 | to the option: ``Geometry> Elementary Entities> Add> Point``, as 62 | shown in the following figure. Then, the coordinates of the 63 | points in the pop-up window and "Add". Finally we can close the 64 | pop-up window and press ``e``. 65 | 66 | .. figure:: img/Agregar_puntos.png 67 | :alt: Agregar puntos al modelo. 68 | :width: 800 px 69 | 70 | Agregar puntos al modelo. 71 | 72 | Later we create lines. For this, we go to the option: 73 | `` Geometry> Elementary Entities> Add> Straight line``, as 74 | shown in the following figure, and we select the initial points and 75 | endings for each line. At the end, we can press ``e``. 76 | 77 | .. figure:: img/Agregar_lineas.png 78 | :alt: Add straight lines to the model. 79 | :width: 800 px 80 | 81 | Add straight lines to the model. 82 | 83 | We also create the circle arcs. For this, we go to the 84 | option: ``Geometry> Elementary Entities> Add> Circle Arc``, as 85 | shown in the following figure, and we select the initial points, 86 | central and final for each arc (in that order). At the end, we can 87 | press ``e``. 88 | 89 | .. figure:: img/Agregar_arcos.png 90 | :alt: Add arcs to the model. 91 | :width: 800 px 92 | 93 | Add arcs to the model. 94 | 95 | Since we already have a closed contour, we can define a surface. 96 | For this, we go to the option: 97 | ``Geometry> Elementary Entities> Add> Plane Surface``, as 98 | shown in the following figure, and we select the contours in order. 99 | At the end, we can press `` e``. 100 | 101 | .. figure:: img/Agregar_superficie.png 102 | :alt: Add surfaces. 103 | :width: 800 px 104 | 105 | Add surfaces. 106 | 107 | Now, we need to define *physical groups*. Physical groups allow us to 108 | associate names to different parts of the model such as lines and 109 | surfaces. This will allow us to define the region in which we will resolve 110 | the model (and we will associate a material), the regions that have 111 | restricted movements (boundary conditions) and the regions 112 | on which we will apply the load. In our case we will have 4 groups 113 | physical: 114 | 115 | - Region of the model, where we will define a material; 116 | 117 | - Bottom edge, where we will restrict the displacement in :math:`y`; 118 | 119 | - Left edge, where we will restrict the displacement in :math:`x`; and 120 | 121 | - Top point, where we will apply the point load. 122 | 123 | To define the physical groups we are going to 124 | ``Geometry> Physical groups> Add> Plane Surface``, as shown in the 125 | next figure. In this case, we can leave the field of `` Name`` empty 126 | and allow Gmsh to name the groups for us, which will be 127 | numbers that we can then consult in the text file 128 | 129 | .. figure:: img/Agregar_linea_fisica.png 130 | :alt: Add physical groups. 131 | :width: 800 px 132 | 133 | Add physical groups. 134 | 135 | After (slightly) editing the text file (``.geo``) this looks like this 136 | 137 | .. code:: C 138 | 139 | L = 0.1; 140 | 141 | // Points 142 | Point(1) = {0, 0, 0, L}; 143 | Point(2) = {1, 0, 0, L}; 144 | Point(3) = {0, 1, 0, L}; 145 | 146 | // Lines 147 | Line(1) = {3, 1}; 148 | Line(2) = {1, 2}; 149 | Circle(3) = {2, 1, 3}; 150 | 151 | // Surfaces 152 | Line Loop(1) = {2, 3, 1}; 153 | Plane Surface(1) = {1}; 154 | 155 | // Physical groups 156 | Physical Line(1) = {1}; 157 | Physical Line(2) = {2}; 158 | Physical Point(3) = {3}; 159 | Physical Surface(4) = {1}; 160 | 161 | We added a parameter ``L``, which we can vary to 162 | to change the size of the elements when creating the 163 | mesh. 164 | 165 | Now, we proceed to create the mesh. To do this, we go to ``Mesh> 2D``. 166 | As we see in the figure below. 167 | 168 | .. figure:: img/Mallar_2D.png 169 | :alt: Create the mesh. 170 | :width: 800 px 171 | 172 | Create the mesh. 173 | 174 | Additionally, we can change the configuration so that it shows the elements 175 | of the mesh in colors. For this, we are going to 176 | ``Tools> Options> Mesh`` and mark the box that indicates 177 | ``Surface faces``. 178 | 179 | .. figure:: img/Ver_superficie_malla.png 180 | :alt: Create the mesh. 181 | :width: 800 px 182 | 183 | Create the mesh. 184 | 185 | We can then refine the mesh going to 186 | ``Mesh> Refine by Splitting``, or by modifying the ``L`` parameter in the 187 | input file (.geo). As a last step, we want to save the mesh. 188 | To do this, go to ``Mesh> Save``, or ``File> Save Mesh``, as 189 | shows below. 190 | 191 | .. figure:: img/Guardar_malla.png 192 | :alt: Save the ``.msh`` file. 193 | :width: 800 px 194 | 195 | Save the ``.msh`` file. 196 | 197 | Python script to generate input files 198 | ===================================== 199 | 200 | We need to create files with the information of the nodes (``nodes.txt``), 201 | elements (``eles.txt``), loads (``loads.txt``) and materials 202 | (``mater.txt``). 203 | 204 | The following code generates the necessary input files for 205 | Run the finite element program in Python. 206 | 207 | .. code-block:: python 208 | 209 | import meshio 210 | import numpy as np 211 | 212 | mesh = meshio.read("Prueba_brasilera.msh") 213 | points = mesh.points 214 | cells = mesh.cells 215 | point_data = mesh.point_data 216 | cell_data = mesh.cell_data 217 | 218 | # Element data 219 | eles = cells["triangle"] 220 | els_array = np.zeros([eles.shape[0], 6], dtype=int) 221 | els_array[:, 0] = range(eles.shape[0]) 222 | els_array[:, 1] = 3 223 | els_array[:, 3::] = eles 224 | 225 | # Nodes 226 | nodes_array = np.zeros([points.shape[0], 5]) 227 | nodes_array[:, 0] = range(points.shape[0]) 228 | nodes_array[:, 1:3] = points[:, :2] 229 | 230 | # Boundaries 231 | lines = cells["line"] 232 | bounds = cell_data["line"]["gmsh:physical"] 233 | nbounds = len(bounds) 234 | 235 | # Loads 236 | id_cargas = cells["vertex"] 237 | nloads = len(id_cargas) 238 | load = -10e8 # N/m 239 | loads_array = np.zeros((nloads, 3)) 240 | loads_array[:, 0] = id_cargas 241 | loads_array[:, 1] = 0 242 | loads_array[:, 2] = load 243 | 244 | # Boundary conditions 245 | id_izq = [cont for cont in range(nbounds) if bounds[cont] == 1] 246 | id_inf = [cont for cont in range(nbounds) if bounds[cont] == 2] 247 | nodes_izq = lines[id_izq] 248 | nodes_izq = nodes_izq.flatten() 249 | nodes_inf = lines[id_inf] 250 | nodes_inf = nodes_inf.flatten() 251 | nodes_array[nodes_izq, 3] = -1 252 | nodes_array[nodes_inf, 4] = -1 253 | 254 | # Materials 255 | mater_array = np.array([[70e9, 0.35], 256 | [70e9, 0.35]]) 257 | maters = cell_data["triangle"]["gmsh:physical"] 258 | els_array[:, 2] = [1 for mater in maters if mater == 4] 259 | 260 | # Create files 261 | np.savetxt("eles.txt", els_array, fmt="%d") 262 | np.savetxt("nodes.txt", nodes_array, 263 | fmt=("%d", "%.4f", "%.4f", "%d", "%d")) 264 | np.savetxt("loads.txt", loads_array, fmt=("%d", "%.6f", "%.6f")) 265 | np.savetxt("mater.txt", mater_array, fmt="%.6f") 266 | 267 | 268 | Now, let's discuss the different parts of the code to see what it does 269 | each. 270 | 271 | Header and reading the ``.msh`` file 272 | ------------------------------------ 273 | 274 | The first part loads the necessary Python modules and reads the file 275 | of mesh that in this case is called ``Prueba_brasilera.msh`` (line 6 and 276 | 7). In order for Python to be able to read the file, it must be in the 277 | same directory as the Python file that will process it. 278 | 279 | .. code:: python 280 | 281 | import meshio 282 | import numpy as np 283 | 284 | 285 | mesh = meshio.read("Prueba_brasilera.msh") 286 | points = mesh.points 287 | cells = mesh.cells 288 | point_data = mesh.point_data 289 | cell_data = mesh.cell_data 290 | 291 | Element data 292 | ------------ 293 | 294 | The next section of the code creates the data for elements. The line 295 | 18 creates a variable `` eles`` with the information of the nodes that 296 | make up each triangle. Line 11 creates an array (filled with zeros) 297 | with the number of rows equal to the number of elements 298 | (``eles.shape[0]``) and 6 columns [2]_. Then we assign a number to 299 | each element, what we do on line 12 with ``range(eles.shape[0])`` 300 | and this we assign to column 0. All 301 | elements are triangles, that's why we should put 3 in column 1. Last, 302 | in this section, we assign the nodes of each element to the array 303 | with (line 19), and this assignment is made from column 3 to 304 | final with ``els_array[:, 3::]``. 305 | 306 | .. code:: python 307 | 308 | # Element data 309 | eles = cells["triangle"] 310 | els_array = np.zeros([eles.shape[0], 6], dtype=int) 311 | els_array[:, 0] = range(eles.shape[0]) 312 | els_array[:, 1] = 3 313 | els_array[:, 3::] = eles 314 | 315 | Nodes data 316 | ---------- 317 | 318 | In the next section we create the information related to the 319 | nodes. To do this, on line 17 we created an array ``nodes_array`` 320 | with 5 columns and as many rows as there are points in the model 321 | (``points.shape[0]``). Then, we assign the 322 | element type on line 18. And finally, we assign the 323 | information on the coordinates of the nodes on line 19 with 324 | ``nodes_array[:, 1:3] = points[:, :2]``, where we are adding the 325 | information in columns 1 and 2. 326 | 327 | .. code:: python 328 | 329 | # Nodes 330 | nodes_array = np.zeros([points.shape[0], 5]) 331 | nodes_array[:, 0] = range(points.shape[0]) 332 | nodes_array[:, 1:3] = points[:, :2] 333 | 334 | Boundary data 335 | ------------- 336 | 337 | In the next section we find the line information. For this, 338 | we read the ``cells`` information in position ``line`` [3]_ 339 | (line 22). The array ``lines`` 340 | will then have the information of the nodes that form each 341 | line that is on the border of the model. Then, we read the information 342 | of the physical lines (line 23), and we calculate how many lines belong 343 | to the physical lines (line 24). 344 | 345 | .. code:: python 346 | 347 | # Boundaries 348 | lines = cells["line"] 349 | bounds = cell_data["line"]["gmsh:physical"] 350 | nbounds = len(bounds) 351 | 352 | Load data 353 | --------- 354 | 355 | In the next section we must define the information of loads. 356 | In this case, the loads are assigned in a single point that we define as a 357 | physical group. On line 27 we read the nodes (in this case, one). 358 | Then, we create an array that has as many rows as loads (``nloads``) and 3 359 | columns Assign the number of the node to which each load belongs 360 | (line 31), the charges in :math: `x` (line 32) and the loads in :math:`y` and 361 | (line 33) 362 | 363 | .. code:: python 364 | 365 | # Loads 366 | id_cargas = cells["vertex"] 367 | nloads = len(id_cargas) 368 | load = -10e8 # N/m 369 | loads_array = np.zeros((nloads, 3)) 370 | loads_array[:, 0] = id_cargas 371 | loads_array[:, 1] = 0 372 | loads_array[:, 2] = load 373 | 374 | Boundary conditions 375 | ------------------- 376 | 377 | Now, we will proceed to apply the boundary conditions, that is, the 378 | model regions that have restrictions on displacements. 379 | Initially, we identify which lines have an identifier 1 380 | (which would be the left side) with 381 | 382 | .. code:: python 383 | 384 | id_izq = [cont for cont in range(nbounds) if bounds[cont] == 1] 385 | 386 | This creates a list with the numbers (``cont``) for which the 387 | condition (``bounds[cont] == 1``). On line 46 we get the nodes that belong to 388 | these lines, however, this array has as many rows as lines 389 | on the left side and two columns. First we return this array as 390 | a one-dimensional array with ``nodes_izq.flatten()``. Later, on line 42, 391 | we assign the value of -1 in the third column of the array for 392 | nodes that belong to the left side. In the same way, this process 393 | is repeated for the nodes at the bottom line. 394 | 395 | .. code:: python 396 | 397 | # Boundary conditions 398 | id_izq = [cont for cont in range(nbounds) if bounds[cont] == 1] 399 | id_inf = [cont for cont in range(nbounds) if bounds[cont] == 2] 400 | nodes_izq = lines[id_izq] 401 | nodes_izq = nodes_izq.flatten() 402 | nodes_inf = lines[id_inf] 403 | nodes_inf = nodes_inf.flatten() 404 | nodes_array[nodes_izq, 3] = -1 405 | nodes_array[nodes_inf, 4] = -1 406 | 407 | Materials 408 | --------- 409 | 410 | In the next section we assign the corresponding materials to each 411 | element. In this case, we only have one material. However, it 412 | present the example as if there were two different ones. First, we created a 413 | array with the material information where the first column 414 | represents the Young's module and the second the Poisson's relation (line 415 | 46). Then, we read the information of the physical groups of surfaces 416 | on line 48. Finally, we assign the value of 0 to the materials that 417 | have as physical group 4 (see file ``.geo`` above) and 1 to the 418 | others, which in this case will be zero (line 49). This information goes in the 419 | column 2 of the arrangement. 420 | 421 | .. code:: python 422 | 423 | # Materials 424 | mater_array = np.array([[70e9, 0.35], 425 | [70e9, 0.35]]) 426 | maters = cell_data["triangle"]["gmsh:physical"] 427 | els_array[:, 2] = [1 for mater in maters if mater == 4] 428 | 429 | Export files 430 | ------------ 431 | 432 | The last section uses the ``numpy`` function to export the 433 | files. 434 | 435 | .. code:: python 436 | 437 | # Create files 438 | np.savetxt("eles.txt", els_array, fmt="%d") 439 | np.savetxt("nodes.txt", nodes_array, 440 | fmt=("%d", "%.4f", "%.4f", "%d", "%d")) 441 | np.savetxt("loads.txt", loads_array, fmt=("%d", "%.6f", "%.6f")) 442 | np.savetxt("mater.txt", mater_array, fmt="%.6f") 443 | 444 | Solution using SolidsPy 445 | ======================= 446 | 447 | To solve the model, we can type [4]_ 448 | 449 | .. code:: python 450 | 451 | from solidspy import solids_GUI 452 | disp = solids_GUI() 453 | 454 | After running this program it will appear 455 | a pop-up window as shown below. In this window 456 | the directory we should locate the folder with the input files 457 | generated previously. Keep in mind that the appearance of 458 | this window may vary between operating systems. Also, we have 459 | notef that sometimes the pop-up window may be hidden 460 | by other windows on your desktop. 461 | 462 | .. figure:: img/solids_GUI-ventana.png 463 | :alt: Pop-up window to locate folder with input files. 464 | :width: 600 px 465 | 466 | Pop-up window to locate folder with input files. 467 | 468 | 469 | At this point, the program must solve the model. If the 470 | input files are used without modifications the program should print 471 | a message similar to the following. 472 | 473 | :: 474 | 475 | Number of nodes: 123 476 | Number of elements: 208 477 | Number of equations: 224 478 | Duration for system solution: 0:00:00.086983 479 | Duration for post processing: 0:00:00 480 | Analysis terminated successfully! 481 | 482 | the times taken to solve the system can change a bit 483 | from one computer to another. 484 | 485 | As a last step, the program generates graphics with the fields of 486 | displacements, deformations and stresses, as shown in the 487 | next figures. 488 | 489 | .. figure:: img/Prueba_brasilera_ux.svg 490 | :alt: Horizontal displacement. 491 | :width: 400 px 492 | 493 | Horizontal displacement. 494 | 495 | .. figure:: img/Prueba_brasilera_uy.svg 496 | :alt: Vertical displacement. 497 | :width: 400 px 498 | 499 | Vertical displacement. 500 | 501 | References 502 | ========== 503 | 504 | .. [D3967-16] 505 | ASTM D3967–16 (2016), Standard Test Method for Splitting Tensile 506 | Strength of Intact Rock Core Specimens, ASTM 507 | International, `www.astm.org `__. 508 | 509 | .. [Gmsh2009] 510 | Geuzaine, Christophe, y Jean-François Remacle (2009), *Gmsh: A 3-D 511 | finite element mesh generator with built-in pre-and post-processing 512 | facilities*. International Journal for Numerical Methods in Engineering, 513 | 79.11. 514 | 515 | .. [Gmsh_tut] 516 | Geuzaine, Christophe, y Jean-François Remacle (2017), Gmsh Official 517 | Tutorial. Accessed: April 18, 2018 518 | http://gmsh.info/doc/texinfo/gmsh.html#Tutorial. 519 | 520 | .. [Gmsh_scr] 521 | Geuzaine, Christophe, y Jean-François Remacle (2017), Gmsh Official 522 | Screencasts. Accessed: April 18, 2018de 523 | http://gmsh.info/screencasts/. 524 | 525 | 526 | .. [1] 527 | If the version is 3.0 or higher, this pop-up window will appear. 528 | 529 | .. [2] 530 | For quadrilateral elements, 7 columns would be used, since each 531 | Element is defined by 4 nodes. 532 | 533 | .. [3] 534 | ``cells`` is a dictionary and allows to store information associated 535 | with some keywords, in this case it is ``lines``. 536 | 537 | .. [4] 538 | To make use of the graphical interface it must be installed 539 | ``easygui``. 540 | -------------------------------------------------------------------------------- /docs/tutorials/geometry_gmsh.rst: -------------------------------------------------------------------------------- 1 | Creation of a simple geometry using Gmsh 2 | ======================================== 3 | 4 | :Author: Juan Gómez 5 | :Date: October 2017 6 | 7 | We want to create a mesh for the following geometry 8 | 9 | .. figure:: ../img/template_schematic.png 10 | :alt: Bilayer model. 11 | 12 | Bilayer model. 13 | 14 | The ``.geo`` file for this model is the following 15 | 16 | .. code:: c 17 | 18 | // Parameters 19 | L = 2.0; 20 | H = 1.0; 21 | h_1 = H/2; 22 | h_2 = H/2; 23 | lado_elem = 0.1; 24 | 25 | 26 | // Points 27 | Point(1) = {0.0, 0.0, 0, lado_elem}; 28 | Point(2) = {L, 0.0, 0, lado_elem}; 29 | Point(3) = {L, h_2, 0, lado_elem}; 30 | Point(4) = {L, H, 0, lado_elem}; 31 | Point(5) = {0, H, 0, lado_elem}; 32 | Point(6) = {0, h_2, 0, lado_elem}; 33 | 34 | // Lines 35 | Line(1) = {1, 2}; 36 | Line(2) = {2, 3}; 37 | Line(3) = {3, 4}; 38 | Line(4) = {4, 5}; 39 | Line(5) = {5, 6}; 40 | Line(6) = {6, 1}; 41 | Line(7) = {3, 6}; 42 | 43 | // Surfaces 44 | Line Loop(8) = {1, 2, 7, 6}; 45 | Plane Surface(9) = {8}; 46 | Line Loop(10) = {-7, 5, 4, 3}; 47 | Plane Surface(11) = {10}; 48 | 49 | // Physical groups 50 | Physical Surface(100) = {9}; // Material superior 51 | Physical Surface(200) = {11}; // Material inferior 52 | Physical Line(300) = {5, 6, 3, 2}; // Lineas laterales 53 | Physical Line(400) = {1}; // Linea inferior 54 | Physical Line(500) = {4}; // Linea superior (cargas) 55 | -------------------------------------------------------------------------------- /docs/tutorials/img/Agregar_arcos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Agregar_arcos.png -------------------------------------------------------------------------------- /docs/tutorials/img/Agregar_linea_fisica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Agregar_linea_fisica.png -------------------------------------------------------------------------------- /docs/tutorials/img/Agregar_lineas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Agregar_lineas.png -------------------------------------------------------------------------------- /docs/tutorials/img/Agregar_puntos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Agregar_puntos.png -------------------------------------------------------------------------------- /docs/tutorials/img/Agregar_superficie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Agregar_superficie.png -------------------------------------------------------------------------------- /docs/tutorials/img/Guardar_malla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Guardar_malla.png -------------------------------------------------------------------------------- /docs/tutorials/img/Mallar_2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Mallar_2D.png -------------------------------------------------------------------------------- /docs/tutorials/img/Motor_geometrico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Motor_geometrico.png -------------------------------------------------------------------------------- /docs/tutorials/img/Nuevo_archivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Nuevo_archivo.png -------------------------------------------------------------------------------- /docs/tutorials/img/Prueba_brasilera.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Prueba_brasilera.pdf -------------------------------------------------------------------------------- /docs/tutorials/img/Prueba_brasilera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 34 | 35 | 43 | 49 | 50 | 58 | 64 | 65 | 73 | 79 | 80 | 81 | 100 | 102 | 103 | 105 | image/svg+xml 106 | 108 | 109 | 110 | 111 | 112 | 117 | 120 | 126 | 132 | 138 | 139 | 151 | 157 | 160 | 166 | 172 | 178 | 184 | 190 | 191 | 194 | 200 | 206 | 212 | 218 | 224 | 225 | Q 236 | Q 247 | Q 258 | 259 | 260 | -------------------------------------------------------------------------------- /docs/tutorials/img/Ver_superficie_malla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/Ver_superficie_malla.png -------------------------------------------------------------------------------- /docs/tutorials/img/solids_GUI-ventana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/docs/tutorials/img/solids_GUI-ventana.png -------------------------------------------------------------------------------- /docs/tutorials/old_format.rst: -------------------------------------------------------------------------------- 1 | Using the old format format for SolidsPy 2 | ======================================== 3 | 4 | :Author: Nicolás Guarín-Zapata 5 | :Date: September 2019 6 | 7 | Until version 1.0.16 the data for a simulation was stored in 4 files 8 | 9 | - ``nodes.txt`` 10 | 11 | - ``eles.txt`` 12 | 13 | - ``mater.txt`` 14 | 15 | - ``loads.txt`` 16 | 17 | As shown below. 18 | 19 | - ``nodes.txt`` 20 | 21 | :: 22 | 23 | 0 0.00 0.00 0 -1 24 | 1 2.00 0.00 0 -1 25 | 2 2.00 2.00 0 0 26 | 3 0.00 2.00 0 0 27 | 4 1.00 0.00 -1 -1 28 | 5 2.00 1.00 0 0 29 | 6 1.00 2.00 0 0 30 | 7 0.00 1.00 0 0 31 | 8 1.00 1.00 0 0 32 | 33 | - ``eles.txt`` 34 | 35 | :: 36 | 37 | 0 1 0 0 4 8 7 38 | 1 1 0 4 1 5 8 39 | 2 1 0 7 8 6 3 40 | 3 1 0 8 5 2 6 41 | 42 | - ``mater.txt`` 43 | 44 | :: 45 | 46 | 1.0 0.3 47 | 48 | - ``loads.txt`` 49 | 50 | :: 51 | 52 | 3 0.0 1.0 53 | 6 0.0 2.0 54 | 2 0.0 1.0 55 | 56 | Run it in Python as follows: 57 | 58 | .. code:: python 59 | 60 | import matplotlib.pyplot as plt # load matplotlib 61 | from solidspy import solids_GUI # import our package 62 | disp = solids_GUI() # run the Finite Element Analysis 63 | plt.show() # plot contours -------------------------------------------------------------------------------- /docs/tutorials/square_example.rst: -------------------------------------------------------------------------------- 1 | Start here: 2×2 square with axial load 2 | ====================================== 3 | 4 | :Author: Nicolás Guarín-Zapata 5 | :Date: May, 2017 6 | 7 | In this document we briefly describe the use of SolidsPy, through a 8 | simple example corresponding to a square plate under point loads. 9 | 10 | Input files 11 | ----------- 12 | 13 | The code requires the domain to be input in the form of text files 14 | containing the nodes, elements, loads and material information. These 15 | files must reside in the same directory and must have the names 16 | ``eles.txt``, ``nodes.txt``, ``mater.txt`` and ``loads.txt``. Assume 17 | that we want to find the response of the 2×2 square under unitary 18 | vertical point loads shown in the following figure. Where one corner is 19 | located at (0,0) and the opposite one at (2,2). 20 | 21 | .. figure:: ../img/square-4_elements.png 22 | :alt: 4-element solid under point loads. 23 | 24 | 4-element solid under point loads. 25 | 26 | The file ``nodes.txt`` is composed of the following fields: 27 | 28 | - Column 0: Nodal identifier (integer). 29 | - Column 1: x-coordinate (float). 30 | - Column 2: y-coordinate (float). 31 | - Column 3: Boundary condition flag along the x-direction (0 free, -1 32 | restrained). 33 | - Column 4: Boundary condition flag along the y-direction (0 free, -1 34 | restrained). 35 | 36 | The corresponding file has the following data 37 | 38 | :: 39 | 40 | 0 0.00 0.00 0 -1 41 | 1 2.00 0.00 0 -1 42 | 2 2.00 2.00 0 0 43 | 3 0.00 2.00 0 0 44 | 4 1.00 0.00 -1 -1 45 | 5 2.00 1.00 0 0 46 | 6 1.00 2.00 0 0 47 | 7 0.00 1.00 0 0 48 | 8 1.00 1.00 0 0 49 | 50 | The file ``eles.txt`` contain the element information. Each line in the 51 | file defines the information for a single element and is composed of the 52 | following fields: 53 | 54 | - Column 0: Element identifier (integer). 55 | - Column 1: Element type (integer): 56 | 57 | - 1 for a 4-noded quadrilateral. 58 | - 2 for a 6-noded triangle. 59 | - 3 for a 3-noded triangle. 60 | 61 | - Column 2: Material profile for the current element (integer). 62 | - Column 3 to end: Element connectivity, this is a list of the nodes 63 | conforming each element. The nodes should be listed in 64 | counterclockwise orientation. 65 | 66 | The corresponding file has the following data 67 | 68 | :: 69 | 70 | 0 1 0 0 4 8 7 71 | 1 1 0 4 1 5 8 72 | 2 1 0 7 8 6 3 73 | 3 1 0 8 5 2 6 74 | 75 | The file ``mater.txt`` contain the material information. Each line in 76 | the file corresponds to a material profile to be assigned to the 77 | different elements in the elements file. In this example, there is one 78 | material profile. Each line in the file is composed of the following 79 | fields: 80 | 81 | - Column 0: Young's modulus for the current profile (float). 82 | - Column 1: Poisson's ratio for the current profile (float). 83 | 84 | The corresponding file has the following data 85 | 86 | :: 87 | 88 | 1.0 0.3 89 | 90 | The file ``loads.txt`` contains the point loads information. Each line 91 | in the file defines the load information for a single node and is 92 | composed of the following fields 93 | 94 | - Column 0: Nodal identifier (integer). 95 | - Column 1: Load magnitude for the current node along the x-direction 96 | (float). 97 | - Column 2: Load magnitude for the current node along the y-direction 98 | (float). 99 | 100 | The corresponding file has the following data 101 | 102 | :: 103 | 104 | 3 0.0 1.0 105 | 6 0.0 2.0 106 | 2 0.0 1.0 107 | 108 | Executing the program 109 | --------------------- 110 | 111 | After installing the package, you can run the program in a Python 112 | terminal using 113 | 114 | :: 115 | 116 | >>> from solidspy import solids_GUI 117 | >>> solids_GUI() 118 | 119 | In Linux and you can also run the program from the terminal using 120 | 121 | :: 122 | 123 | $ python -m solidspy 124 | 125 | If you have ``easygui`` installed a pop-up window will appear for you to 126 | select the folder with the input files 127 | 128 | .. figure:: ../img/Folder_selection.png 129 | :width: 400px 130 | :alt: Folder selection window. 131 | 132 | Folder selection window. 133 | 134 | select the folder and click ok. 135 | 136 | If you don't have ``easygui`` installed the software will ask you for 137 | the path to your folder. The path can be absolute or relative. 138 | 139 | :: 140 | 141 | Enter folder (empty for the current one): 142 | 143 | 144 | Then, you will see some information regarding your analysis 145 | 146 | :: 147 | 148 | Number of nodes: 9 149 | Number of elements: 4 150 | Number of equations: 14 151 | Duration for system solution: 0:00:00.006895 152 | Duration for post processing: 0:00:01.466066 153 | Analysis terminated successfully! 154 | 155 | And, once the solution is achieved you will see displacements and stress 156 | solutions as contour plots, like the following 157 | 158 | .. image:: ../img/square-4_elements-horizontal_disp.png 159 | :width: 400px 160 | 161 | .. image:: ../img/square-4_elements-vertical_disp.png 162 | :width: 400px 163 | 164 | 165 | Interactive execution 166 | ~~~~~~~~~~~~~~~~~~~~~ 167 | 168 | You can also run the program interactively using a Python terminal, a 169 | good option is `IPython `__. 170 | 171 | In IPython you can run the program with 172 | 173 | .. code:: python 174 | 175 | In [1]: from solidspy import solids_GUI 176 | 177 | In [2]: UC = solids_GUI() 178 | 179 | After running the code we have the nodal variables for post-processing. 180 | For example, we can print the displacement vector 181 | 182 | .. code:: python 183 | 184 | In [3]: np.set_printoptions(threshold=np.nan) 185 | 186 | In [4]: print(np.round(UC, 3)) 187 | [ 0.6 -0.6 -0.6 4. 0.6 4. -0.6 2. -0. 4. 0.6 2. -0. 2. ] 188 | 189 | where we first setup the printing option for IPython to show the full 190 | array and then rounded the array to 3 decimal places. 191 | 192 | .. code:: python 193 | 194 | In [5]: U_mag = np.sqrt(UC[0::2]**2 + UG[1::2]**2) 195 | 196 | In [6]: print(np.round(U_mag, 3)) 197 | [ 0.849 4.045 4.045 2.088 4. 2.088 2. ] 198 | -------------------------------------------------------------------------------- /docs/tutorials/template.geo: -------------------------------------------------------------------------------- 1 | // Parameters 2 | L = 2.0; 3 | H = 1.0; 4 | h_1 = H/2; 5 | h_2 = H/2; 6 | lado_elem = 0.1; 7 | 8 | 9 | // Points 10 | Point(1) = {0.0, 0.0, 0, lado_elem}; 11 | Point(2) = {L, 0.0, 0, lado_elem}; 12 | Point(3) = {L, h_2, 0, lado_elem}; 13 | Point(4) = {L, H, 0, lado_elem}; 14 | Point(5) = {0, H, 0, lado_elem}; 15 | Point(6) = {0, h_2, 0, lado_elem}; 16 | 17 | // Lines 18 | Line(1) = {1, 2}; 19 | Line(2) = {2, 3}; 20 | Line(3) = {3, 4}; 21 | Line(4) = {4, 5}; 22 | Line(5) = {5, 6}; 23 | Line(6) = {6, 1}; 24 | Line(7) = {3, 6}; 25 | 26 | // Surfaces 27 | Line Loop(8) = {1, 2, 7, 6}; 28 | Plane Surface(9) = {8}; 29 | Line Loop(10) = {-7, 5, 4, 3}; 30 | Plane Surface(11) = {10}; 31 | 32 | // Physical groups 33 | Physical Surface(100) = {9}; // Material superior 34 | Physical Surface(200) = {11}; // Material inferior 35 | Physical Line(300) = {5, 6, 3, 2}; // Lineas laterales 36 | Physical Line(400) = {1}; // Linea inferior 37 | Physical Line(500) = {4}; // Linea superior (cargas) 38 | 39 | -------------------------------------------------------------------------------- /docs/tutorials/template_input.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Template to generate the input files for the FEM code SolidsPy. 4 | The script uses meshio to read a GMSH mesh and produce 5 | text files nodes.txt, eles.txt , mater.txt and loads.txt 6 | 7 | @authors: Juan Gomez 8 | Nicolas Guarin-Zapata 9 | """ 10 | import meshio 11 | import numpy as np 12 | # 13 | # Read the GMSH file using meshio 14 | # 15 | mesh = meshio.read("template.msh") 16 | points = mesh.points 17 | cells = mesh.cells 18 | point_data = mesh.point_data 19 | cell_data = mesh.cell_data 20 | 21 | 22 | # Process element data. In this case we have used 3-noded triangles. 23 | # (In SolidsPy 1 stands for quad; 2 for triangle6; 3 for triangle) 24 | eles = cells["triangle"] 25 | els_array = np.zeros([eles.shape[0], 6], dtype=int) 26 | # Asigns element id 27 | els_array[:, 0] = range(eles.shape[0]) 28 | # Assigns element type according to SolidsPy 29 | els_array[:, 1] = 3 30 | # Assign element connectivities 31 | els_array[:, 3::] = eles 32 | # Creates the materials array and assigns a profile (either 0 or 1) to 33 | # each element according to the physical surface 34 | mater_array = np.array([[1.0, 0.30], 35 | [5.0, 0.30]]) 36 | maters = cell_data["triangle"]["gmsh:physical"] 37 | els_array[:, 2] = [0 if mater == 100 else 1 for mater in maters] 38 | 39 | # Process nodal data 40 | nodes_array = np.zeros([points.shape[0], 5]) 41 | nodes_array[:, 0] = range(points.shape[0]) # Assigns nodal id 42 | nodes_array[:, 1:3] = points[:, :2] # Assigns space coordinates 43 | 44 | # Process physical lines to assign displacement boundary conditions 45 | # and nodal point loads 46 | # 47 | # Displacement boundary conditions 48 | lines = cells["line"] 49 | # Bounds contains data corresponding to the physical line. 50 | bounds = cell_data["line"]["gmsh:physical"] 51 | # (-1 means a restraint degree of freedom) 52 | id_frontera_lat = [cont for cont in range(len(bounds)) if bounds[cont] == 300] 53 | nodes_frontera_lat = lines[id_frontera_lat] 54 | nodes_frontera_lat = nodes_frontera_lat.flatten() 55 | nodes_frontera_lat = list(set(nodes_frontera_lat)) 56 | nodes_array[nodes_frontera_lat, 3] = -1 57 | id_frontera_abajo = [cont for cont in range(len(bounds)) if bounds[cont] == 400] 58 | nodes_frontera_abajo = lines[id_frontera_abajo] 59 | nodes_frontera_abajo = nodes_frontera_abajo.flatten() 60 | nodes_frontera_abajo = list(set(nodes_frontera_abajo)) 61 | nodes_array[nodes_frontera_abajo, 4] = -1 62 | # Nodal point loads 63 | id_carga = [cont for cont in range(len(bounds)) if bounds[cont] == 500] 64 | nodes_carga = lines[id_carga] 65 | nodes_carga = nodes_carga.flatten() 66 | nodes_carga = list(set(nodes_carga)) 67 | ncargas = len(nodes_carga) 68 | carga_total = -2.0 69 | cargas = np.zeros((ncargas, 3)) 70 | cargas[:, 0] = nodes_carga 71 | cargas[:, 2] = carga_total/ncargas 72 | 73 | # Write the model .txt files 74 | np.savetxt("eles.txt" , els_array , fmt="%d") 75 | np.savetxt("loads.txt", cargas, fmt=("%d", "%.6f", "%.6f")) 76 | np.savetxt("nodes.txt", nodes_array , fmt=("%d", "%.4f", "%.4f", "%d", "%d")) 77 | np.savetxt("mater.txt", mater_array , fmt="%.6f") 78 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | 4 | .. todo:: 5 | 6 | Write comprehensive usage docs. 7 | -------------------------------------------------------------------------------- /examples/beam_convergence/error_vs_h.txt: -------------------------------------------------------------------------------- 1 | Iteration, Elements, h, error 2 | 2, 12, 4, 0.0489864 3 | 3, 48, 2, 0.107604 4 | 4, 192, 1, 0.0427641 5 | 5, 768, 0.5, 0.0277539 6 | 6, 3072, 0.25, 0.01599 7 | 7, 12288, 0.125, 0.00840804 8 | 8, 49152, 0.0625, 0.00458369 9 | -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_convergence.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_convergence.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-12.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-12288.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-12288.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-192.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-192.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-3.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-3072.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-3072.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-48.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-48.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/Beam_mesh-768.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/Beam_mesh-768.pdf -------------------------------------------------------------------------------- /examples/beam_convergence/img/meshes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/beam_convergence/img/meshes.pdf -------------------------------------------------------------------------------- /examples/custom_barba.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 70 | 85 | 86 | -------------------------------------------------------------------------------- /examples/simple_truss/img/simple_truss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/examples/simple_truss/img/simple_truss.png -------------------------------------------------------------------------------- /examples/square-4_elements/eles.txt: -------------------------------------------------------------------------------- 1 | 0 1 0 0 4 8 7 2 | 1 1 0 4 1 5 8 3 | 2 1 0 7 8 6 3 4 | 3 1 0 8 5 2 6 5 | -------------------------------------------------------------------------------- /examples/square-4_elements/loads.txt: -------------------------------------------------------------------------------- 1 | 3 0.0 1.0 2 | 6 0.0 2.0 3 | 2 0.0 1.0 4 | -------------------------------------------------------------------------------- /examples/square-4_elements/mater.txt: -------------------------------------------------------------------------------- 1 | 1.0 0.3 2 | 3 | -------------------------------------------------------------------------------- /examples/square-4_elements/nodes.txt: -------------------------------------------------------------------------------- 1 | 0 0.00 0.00 0 -1 2 | 1 2.00 0.00 0 -1 3 | 2 2.00 2.00 0 0 4 | 3 0.00 2.00 0 0 5 | 4 1.00 0.00 -1 -1 6 | 5 2.00 1.00 0 0 7 | 6 1.00 2.00 0 0 8 | 7 0.00 1.00 0 0 9 | 8 1.00 1.00 0 0 10 | -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @book{bathe, 2 | title={Finite element procedures}, 3 | author={Bathe, Klaus-J{\"u}rgen}, 4 | year={2006}, 5 | publisher={Klaus-Jurgen Bathe} 6 | } 7 | 8 | @article{gmsh, 9 | title={Gmsh: A 3-D finite element mesh generator with built-in pre-and post-processing facilities}, 10 | author={Geuzaine, Christophe and Remacle, Jean-Fran{\c{c}}ois}, 11 | journal={International journal for numerical methods in engineering}, 12 | volume={79}, 13 | number={11}, 14 | pages={1309--1331}, 15 | year={2009}, 16 | publisher={Wiley Online Library} 17 | } 18 | 19 | @Misc{scipy, 20 | author = {Eric Jones and Travis Oliphant and Pearu Peterson and others}, 21 | title = {{SciPy}: Open source scientific tools for {Python}}, 22 | year = {2001--}, 23 | url = "http://www.scipy.org/", 24 | note = {[Online; accessed June 12, 2018]} 25 | } 26 | 27 | @Misc{meshio, 28 | author = {Nico Schlömer et al.}, 29 | title = {{meshio v1.11.7. Zenodo. http://doi.org/10.5281/zenodo.1173116}}, 30 | year = {2016--}, 31 | url = "https://github.com/nschloe/meshio", 32 | note = {[Online; accessed June 12, 2018]} 33 | } 34 | -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'SolidsPy: 2D-Finite Element Analysis with Python' 3 | tags: 4 | - Python 5 | - finite elements 6 | - computational mechanics 7 | - scientific computing 8 | authors: 9 | - name: Nicolás Guarín-Zapata 10 | orcid: 0000-0002-9435-1914 11 | affiliation: 1 12 | - name: Juan D. Gómez 13 | orcid: 0000-0000-0000-0000 14 | affiliation: 1 15 | affiliations: 16 | - name: Departamento de Ingeniería Civil, Universidad EAFIT, Medellín-Colombia 17 | index: 1 18 | date: 12 June 2018 19 | bibliography: paper.bib 20 | --- 21 | 22 | # Summary 23 | 24 | 25 | JOSS welcomes submissions from broadly diverse research areas. For this reason, we request that authors include in the paper some sentences that would explain the software functionality and domain of use to a non-specialist reader. Your submission should probably be somewhere between 250-1000 words. 26 | 27 | A list of the authors of the software and their affiliations 28 | A summary describing the high-level functionality and purpose of the software for a diverse, non-specialist audience 29 | A clear statement of need that illustrates the purpose of the software 30 | A list of key references including a link to the software archive 31 | Mentions (if applicable) of any ongoing research projects using the software or recent scholarly publications enabled by it 32 | 33 | 34 | ![Displacement and von Mises stress for a wrench under bending.](wrench.png) 35 | 36 | The Finite Element Method is a numerical method for the solution of problems in engineering and physics [@bathe]. These problems are commonly written as boundary value problems and involve partial differential equations. 37 | 38 | ``SolidsPy`` is a simple finite element analysis code for 2D elasticity problems and was designed to be used by researchers in computational mechanics and by 39 | students in courses on Computational modeling. It has also been used in graduate 40 | courses on Introduction to the Finite Element Method. It uses as input simple-to-create text files defining a model in terms of nodal, element, material and load data. Some feature of ``SolidsPy`` are: 41 | - It is based on an open-source environment. 42 | - It is easy to use. 43 | - The code allows to find displacement, strain and stress solutions for arbitrary two-dimensional domains discretized into finite elements and subject to point loads. 44 | - The code is organized in independent modules for pre-processing, assembly and post-processing allowing the user to easily modify it or add features like new elements or analyses pipelines. 45 | 46 | ``SolidsPy`` uses SciPy [@scipy] for matrix (sparse/dense) storage and solution of systems of equations. For the creation of complex meshes it is suggested to utilize ``Gmsh`` [@gmsh] and then take advantage of ``meshio`` [@meshio] as interface to Gmsh. 47 | 48 | 49 | # Acknowledgements 50 | 51 | We acknowledge contributions from Edward Villegas. 52 | 53 | 54 | # References 55 | -------------------------------------------------------------------------------- /paper/wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppliedMechanics-EAFIT/SolidsPy/7319fba18d57aa47198ae3776d06b69a1a23f98a/paper/wrench.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | pytest 3 | coverage 4 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | matplotlib 4 | easygui 5 | meshio==3.0 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal =1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """A simple Finite Element program 4 | 5 | https://github.com/AppliedMechanics-EAFIT/SolidsPy 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | 11 | # Get the long description from the README file 12 | with open('README.md', 'r', encoding='utf-8') as f: 13 | long_description = f.read() 14 | 15 | 16 | 17 | requirements = ['numpy', 18 | 'scipy', 19 | 'matplotlib', 20 | 'easygui', 21 | 'meshio==3.0'] 22 | 23 | setup( 24 | name='solidspy', 25 | 26 | version='1.1.0.post1', 27 | 28 | description='A simple Finite Element program', 29 | long_description=long_description, 30 | long_description_content_type="text/markdown", 31 | 32 | # The project's main homepage. 33 | url='https://github.com/AppliedMechanics-EAFIT/SolidsPy', 34 | 35 | 36 | # Author details 37 | author='Nicolas Guarin-Zapata , Juan Gomez ', 38 | author_email='nguarinz@eafit.edu.co', 39 | 40 | license='MIT', 41 | 42 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 43 | classifiers=[ 44 | # How mature is this project? Common values are 45 | # 3 - Alpha 46 | # 4 - Beta 47 | # 5 - Production/Stable 48 | 'Development Status :: 4 - Beta', 49 | 50 | # Indicate who your project is intended for 51 | 'Intended Audience :: Education', 52 | 'Topic :: Scientific/Engineering', 53 | 54 | # Pick your license as you wish (should match "license" above) 55 | 'License :: OSI Approved :: MIT License', 56 | 57 | # Specify the Python versions you support here. In particular, ensure 58 | # that you indicate whether you support Python 2, Python 3 or both. 59 | 'Programming Language :: Python :: 3', 60 | ], 61 | 62 | keywords='finite-elements fem scientific-computing', 63 | 64 | 65 | # You can just specify the packages manually here if your project is 66 | # simple. Or you can use find_packages(). 67 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 68 | 69 | # List run-time dependencies here. These will be installed by pip when 70 | # your project is installed. For an analysis of "install_requires" vs pip's 71 | # requirements files see: 72 | # https://packaging.python.org/en/latest/requirements.html 73 | install_requires=requirements, 74 | 75 | 76 | # To provide executable scripts, use entry points in preference to the 77 | # "scripts" keyword. Entry points provide cross-platform support and allow 78 | # pip to create the appropriate form of executable for the target platform. 79 | entry_points={ 80 | 'console_scripts': [ 81 | 'sample=sample:main', 82 | ], 83 | }, 84 | ) 85 | -------------------------------------------------------------------------------- /solidspy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from solidspy.solids_GUI import solids_GUI 4 | 5 | __all__ = ["assemutil", 6 | "femutil", 7 | "gaussutil", 8 | "postprocesor", 9 | "preprocesor", 10 | "solutil", 11 | "uelutil", 12 | "solids_GUI"] 13 | 14 | __version__ = "1.1.0" 15 | 16 | 17 | __citation__ = """@software{solidspy, 18 | title = {SolidsPy: 2D-Finite Element Analysis with Python}, 19 | author = {Guarín-Zapata, Nicolás and Gómez, Juan}, 20 | year = 2023, 21 | keywords = {Python, Finite elements, Scientific computing, Computational mechanics}, 22 | abstract = {SolidsPy is a simple finite element analysis code for 23 | 2D elasticity problems. The code uses as input simple-to-create text 24 | files defining a model in terms of nodal, element, material and 25 | load data.}, 26 | url = {https://github.com/AppliedMechanics-EAFIT/SolidsPy} 27 | }""" 28 | -------------------------------------------------------------------------------- /solidspy/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from solidspy.solids_GUI import solids_GUI 4 | 5 | solids_GUI() -------------------------------------------------------------------------------- /solidspy/assemutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Assembly routines 4 | ----------------- 5 | 6 | Functions to assemble the system of equations for a finite element 7 | analysis. 8 | 9 | """ 10 | import numpy as np 11 | from scipy.sparse import coo_matrix 12 | import solidspy.uelutil as ue 13 | import solidspy.femutil as fem 14 | 15 | 16 | def eqcounter(cons, ndof_node=2): 17 | """Count active equations 18 | 19 | Creates boundary conditions array bc_array 20 | 21 | Parameters 22 | ---------- 23 | cons : ndarray. 24 | Array with constraints for each node. 25 | 26 | Returns 27 | ------- 28 | neq : int 29 | Number of equations in the system after removing the nodes 30 | with imposed displacements. 31 | bc_array : ndarray (int) 32 | Array that maps the nodes with number of equations. 33 | 34 | """ 35 | nnodes = cons.shape[0] 36 | bc_array = cons.copy().astype(int) 37 | neq = 0 38 | for i in range(nnodes): 39 | for j in range(ndof_node): 40 | if bc_array[i, j] == 0: 41 | bc_array[i, j] = neq 42 | neq += 1 43 | 44 | return neq, bc_array 45 | 46 | 47 | def DME(cons, elements, ndof_node=2, ndof_el_max=18, ndof_el=None): 48 | """Create assembly array operator 49 | 50 | Count active equations, create boundary conditions array ``bc_array`` 51 | and the assembly operator ``assem_op``. 52 | 53 | Parameters 54 | ---------- 55 | cons : ndarray. 56 | Array with constraints for each degree of freedom in each node. 57 | elements : ndarray 58 | Array with the number for the nodes in each element. 59 | ndof_node : int, optional 60 | Number of degrees of freedom per node. By default it is 2. 61 | ndof_el_max : int, optional 62 | Number of maximum degrees of freedom per element. By default it is 63 | 18. 64 | ndof_el : callable, optional 65 | Function that return number of degrees of freedom for elements. It 66 | is needed for user elements. 67 | 68 | Returns 69 | ------- 70 | assem_op : ndarray (int) 71 | Assembly operator. 72 | bc_array : ndarray (int) 73 | Boundary conditions array. 74 | neq : int 75 | Number of active equations in the system. 76 | 77 | """ 78 | nels = elements.shape[0] 79 | assem_op = np.zeros([nels, ndof_el_max], dtype=int) 80 | neq, bc_array = eqcounter(cons, ndof_node=ndof_node) 81 | for ele in range(nels): 82 | iet = elements[ele, 1] 83 | if ndof_el is None: 84 | ndof, _, _ = fem.eletype(iet) 85 | else: 86 | ndof = ndof_el(iet) 87 | assem_op[ele, :ndof] = bc_array[elements[ele, 3:]].flatten() 88 | return assem_op, bc_array, neq 89 | 90 | 91 | def ele_fun(eletype): 92 | """Return the function for the element type given 93 | 94 | Parameters 95 | ---------- 96 | eletype : int 97 | Type of element. 98 | 99 | Returns 100 | ------- 101 | ele_fun : callable 102 | Function for the element type given. 103 | """ 104 | elem_id = { 105 | 1: ue.elast_quad4, 106 | 2: ue.elast_tri6, 107 | 3: ue.elast_tri3, 108 | 4: ue.elast_quad9, 109 | 5: ue.spring, 110 | 6: ue.truss2D, 111 | 7: ue.beam2DU, 112 | 8: ue.beam2D} 113 | try: 114 | return elem_id[eletype] 115 | except: 116 | raise ValueError("You entered an invalid type of element.") 117 | 118 | 119 | def retriever(elements, mats, nodes, ele, uel=None): 120 | """Computes the elemental stiffness matrix of element ``ele`` 121 | 122 | Parameters 123 | ---------- 124 | elements : ndarray 125 | Array with the number for the nodes in each element. 126 | mats : ndarray. 127 | Array with the material profiles. 128 | nodes : ndarray. 129 | Array with the nodal numbers and coordinates. 130 | ele : int. 131 | Identifier of the element to be assembled. 132 | 133 | Returns 134 | ------- 135 | kloc : ndarray (float) 136 | Array with the local stiffness matrix. 137 | mloc : ndarray (float) 138 | Array with the local mass matrix. 139 | """ 140 | elem_type = elements[ele, 1] 141 | params = mats[elements[ele, 2], :] 142 | elcoor = nodes[elements[ele, 3:], 1:] 143 | if uel is None: 144 | uel = ele_fun(elem_type) 145 | kloc, mloc = uel(elcoor, params) 146 | return kloc, mloc 147 | 148 | 149 | def assembler(elements, mats, nodes, neq, assem_op, sparse=True, uel=None): 150 | """Assembles the global stiffness matrix 151 | 152 | Parameters 153 | ---------- 154 | elements : ndarray (int) 155 | Array with the number for the nodes in each element. 156 | mats : ndarray (float) 157 | Array with the material profiles. 158 | nodes : ndarray (float) 159 | Array with the nodal numbers and coordinates. 160 | assem_op : ndarray (int) 161 | Assembly operator. 162 | neq : int 163 | Number of active equations in the system. 164 | sparse : boolean (optional) 165 | Boolean variable to pick sparse assembler. It is True 166 | by default. 167 | uel : callable function (optional) 168 | Python function that returns the local stiffness matrix. 169 | 170 | Returns 171 | ------- 172 | kglob : ndarray (float) 173 | Array with the global stiffness matrix. It might be 174 | dense or sparse, depending on the value of _sparse_ 175 | mglob : ndarray (float) 176 | Array with the global mass matrix. It might be 177 | dense or sparse, depending on the value of _sparse_ 178 | 179 | """ 180 | if sparse: 181 | kglob, mglob = sparse_assem(elements, mats, nodes, neq, assem_op, 182 | uel=uel) 183 | else: 184 | kglob, mglob = dense_assem(elements, mats, nodes, neq, assem_op, 185 | uel=uel) 186 | 187 | return kglob, mglob 188 | 189 | 190 | def dense_assem(elements, mats, nodes, neq, assem_op, uel=None): 191 | """ 192 | Assembles the global stiffness matrix 193 | using a dense storing scheme 194 | 195 | Parameters 196 | ---------- 197 | elements : ndarray (int) 198 | Array with the number for the nodes in each element. 199 | mats : ndarray (float) 200 | Array with the material profiles. 201 | nodes : ndarray (float) 202 | Array with the nodal numbers and coordinates. 203 | assem_op : ndarray (int) 204 | Assembly operator. 205 | neq : int 206 | Number of active equations in the system. 207 | uel : callable function (optional) 208 | Python function that returns the local stiffness matrix. 209 | 210 | Returns 211 | ------- 212 | kglob : ndarray (float) 213 | Array with the global stiffness matrix in a dense numpy 214 | array. 215 | 216 | """ 217 | kglob = np.zeros((neq, neq)) 218 | mglob = np.zeros((neq, neq)) 219 | nels = elements.shape[0] 220 | for ele in range(nels): 221 | kloc, mloc = retriever(elements, mats, nodes, ele, uel=uel) 222 | ndof = kloc.shape[0] 223 | dme = assem_op[ele, :ndof] 224 | for row in range(ndof): 225 | glob_row = dme[row] 226 | if glob_row != -1: 227 | for col in range(ndof): 228 | glob_col = dme[col] 229 | if glob_col != -1: 230 | kglob[glob_row, glob_col] += kloc[row, col] 231 | mglob[glob_row, glob_col] += mloc[row, col] 232 | 233 | return kglob, mglob 234 | 235 | 236 | def sparse_assem(elements, mats, nodes, neq, assem_op, uel=None): 237 | """ 238 | Assembles the global stiffness matrix 239 | using a sparse storing scheme 240 | 241 | The scheme used to assemble is COOrdinate list (COO), and 242 | it converted to Compressed Sparse Row (CSR) afterward 243 | for the solution phase [1]_. 244 | 245 | Parameters 246 | ---------- 247 | elements : ndarray (int) 248 | Array with the number for the nodes in each element. 249 | mats : ndarray (float) 250 | Array with the material profiles. 251 | nodes : ndarray (float) 252 | Array with the nodal numbers and coordinates. 253 | assem_op : ndarray (int) 254 | Assembly operator. 255 | neq : int 256 | Number of active equations in the system. 257 | uel : callable function (optional) 258 | Python function that returns the local stiffness matrix. 259 | 260 | Returns 261 | ------- 262 | kglob : sparse matrix (float) 263 | Array with the global stiffness matrix in a sparse 264 | Compressed Sparse Row (CSR) format. 265 | 266 | References 267 | ---------- 268 | .. [1] Sparse matrix. (2017, March 8). In Wikipedia, 269 | The Free Encyclopedia. 270 | https://en.wikipedia.org/wiki/Sparse_matrix 271 | 272 | """ 273 | rows = [] 274 | cols = [] 275 | stiff_vals = [] 276 | mass_vals = [] 277 | nels = elements.shape[0] 278 | for ele in range(nels): 279 | kloc, mloc = retriever(elements, mats, nodes, ele, uel=uel) 280 | ndof = kloc.shape[0] 281 | dme = assem_op[ele, :ndof] 282 | for row in range(ndof): 283 | glob_row = dme[row] 284 | if glob_row != -1: 285 | for col in range(ndof): 286 | glob_col = dme[col] 287 | if glob_col != -1: 288 | rows.append(glob_row) 289 | cols.append(glob_col) 290 | stiff_vals.append(kloc[row, col]) 291 | mass_vals.append(mloc[row, col]) 292 | 293 | stiff = coo_matrix((stiff_vals, (rows, cols)), shape=(neq, neq)).tocsr() 294 | mass = coo_matrix((mass_vals, (rows, cols)), shape=(neq, neq)).tocsr() 295 | return stiff, mass 296 | 297 | 298 | def loadasem(loads, bc_array, neq, ndof_node=2): 299 | """Assembles the global Right Hand Side Vector 300 | 301 | Parameters 302 | ---------- 303 | loads : ndarray 304 | Array with the loads imposed in the system. 305 | bc_array : ndarray (int) 306 | Array that maps the nodes with number of equations. 307 | neq : int 308 | Number of equations in the system after removing the nodes 309 | with imposed displacements. 310 | 311 | Returns 312 | ------- 313 | rhs_vec : ndarray 314 | Array with the right hand side vector. 315 | 316 | """ 317 | nloads = loads.shape[0] 318 | rhs_vec = np.zeros([neq]) 319 | for cont in range(nloads): 320 | node = int(loads[cont, 0]) 321 | for dof in range(ndof_node): 322 | dof_id = bc_array[node, dof] 323 | if dof_id != -1: 324 | rhs_vec[dof_id] = loads[cont, dof + 1] 325 | return rhs_vec 326 | 327 | 328 | if __name__ == "__main__": 329 | import doctest 330 | doctest.testmod() 331 | -------------------------------------------------------------------------------- /solidspy/blochutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Bloch analysis utilities 5 | ------------------------ 6 | Routines used in Bloch Analisis. 7 | 8 | """ 9 | import numpy as np 10 | from scipy.sparse import coo_matrix 11 | 12 | 13 | #%% Auxiliar 14 | def reassign_dof_bloch(ndofs, dofs_ima, dofs_ref): 15 | """Find dof number after applying boundary conditions 16 | 17 | Parameters 18 | ---------- 19 | ndofs : int 20 | Number of degrees of freedom. 21 | dofs_ima : list, int 22 | List with image degrees of freedom. 23 | dofs_ref : list, int 24 | List with reference degrees of freedom. 25 | 26 | 27 | Examples 28 | -------- 29 | 30 | Let us consider an example for a square made with spring and masses. 31 | In that case we have the following dofs mappings. 32 | 33 | >>> dofs_ref = [0, 1, 0, 1, 0, 1] 34 | >>> dofs_ima = [2, 3, 4, 5, 6, 7] 35 | 36 | And the new numbering would be 37 | 38 | >>> new_num = reassign_dof_bloch(8, dofs_ima, dofs_ref) 39 | >>> new_num 40 | [0, 1, 0, 1, 0, 1, 0, 1] 41 | 42 | Returns 43 | ------- 44 | new_num : list, int 45 | List with new numbering for the dof after applying Bloch analysis. 46 | 47 | """ 48 | new_num = [] 49 | cont = 0 50 | for cont_dof in range(ndofs): 51 | if cont_dof in dofs_ima: 52 | cont += 1 53 | ref_dof = dofs_ref[dofs_ima.index(cont_dof)] 54 | if ref_dof < cont_dof: 55 | new_num.append(ref_dof) 56 | else: 57 | new_num.append(ref_dof - cont) 58 | else: 59 | new_num.append(cont_dof - cont) 60 | return new_num 61 | 62 | 63 | #%% Dense matrices 64 | 65 | 66 | #%% Sparse matrices 67 | def bloch_transform_mat(wavevector, coords, ndofs, nodes_ref, nodes_ima, 68 | dofs_ref, dofs_ima, nodes_ref_dof, nodes_ima_dof, 69 | new_num): 70 | """Form transformation matrices for Bloch conditions 71 | 72 | Parameters 73 | ---------- 74 | wavevector: array like 75 | Wavevector, it should have as many components as coordinates. 76 | coords : array like 77 | Coordinates array. 78 | ndofs : int 79 | Number of degrees of freedom. 80 | nodes_ref : list, int 81 | List with reference nodes. 82 | nodes_ima : list, int 83 | List with image nodes. 84 | dofs_ref : list, int 85 | List with reference degrees of freedom. 86 | dofs_ima : list, int 87 | List with image degrees of freedom. 88 | nodes_ref_dof : dictionary 89 | Dictionary that maps nodes to dof for reference nodes. 90 | nodes_ima_dof : dictionary 91 | Dictionary that maps nodes to dof for image nodes. 92 | 93 | 94 | Examples 95 | -------- 96 | 97 | Let us consider an example for a square made with spring and masses. 98 | In that case we have the following dofs mappings. 99 | 100 | >>> wavevector = np.array([0, 0]) 101 | >>> coords = np.array([ 102 | ... [0, 0], 103 | ... [1, 0], 104 | ... [0, 1], 105 | ... [1, 1]]) 106 | >>> nodes_ref = [0, 0, 0] 107 | >>> nodes_ima = [1, 2, 3] 108 | >>> dofs_ref = [0, 1, 0, 1, 0, 1] 109 | >>> dofs_ima = [2, 3, 4, 5, 6, 7] 110 | >>> nodes_ref_dof = {0: [0, 1]} 111 | >>> nodes_ima_dof = {1: [2, 3], 2: [4, 5], 3: [6, 7]} 112 | >>> new_num = [0, 1, 0, 1, 0, 1, 0, 1] 113 | >>> T = bloch_transform_mat(wavevector, coords, 8, nodes_ref, 114 | ... nodes_ima, dofs_ref, dofs_ima, 115 | ... nodes_ref_dof, nodes_ima_dof, new_num) 116 | >>> T_exact = np.array([ 117 | ... [1., 0.], 118 | ... [0., 1.], 119 | ... [1., 0.], 120 | ... [0., 1.], 121 | ... [1., 0.], 122 | ... [0., 1.], 123 | ... [1., 0.], 124 | ... [0., 1.]]) 125 | >>> np.allclose(T.toarray(), T_exact) 126 | True 127 | 128 | Now, let us consider an example with a mesh with 2x2 elements with 129 | one dof per node. 130 | 131 | >>> wavevector = 0.5*np.pi*np.array([1, 1]) 132 | >>> coords = np.array([ 133 | ... [0, 0], 134 | ... [1, 0], 135 | ... [2, 0], 136 | ... [0, 1], 137 | ... [1, 1], 138 | ... [2, 1], 139 | ... [0, 2], 140 | ... [1, 2], 141 | ... [2, 2]]) 142 | >>> nodes_ref = [0, 0, 0, 1, 3] 143 | >>> nodes_ima = [2, 6, 8, 7, 5] 144 | >>> dofs_ref = [0, 0, 0, 1, 3] 145 | >>> dofs_ima = [2, 6, 8, 7, 5] 146 | >>> nodes_ref_dof = {0: [0], 1: [1], 3: [3]} 147 | >>> nodes_ima_dof = {2: [2], 5: [5], 6: [6], 7: [7], 8: [8]} 148 | >>> new_num = [0, 1, 0, 2, 3, 2, 0, 1, 0] 149 | >>> T = bloch_transform_mat(wavevector, coords, 9, nodes_ref, 150 | ... nodes_ima, dofs_ref, dofs_ima, 151 | ... nodes_ref_dof, nodes_ima_dof, new_num) 152 | >>> T_exact = np.array([ 153 | ... [ 1., 0., 0., 0.], 154 | ... [ 0., 1., 0., 0.], 155 | ... [-1., 0., 0., 0.], 156 | ... [ 0., 0., 1., 0.], 157 | ... [ 0., 0., 0., 1.], 158 | ... [ 0., 0., -1., 0.], 159 | ... [-1., 0., 0., 0.], 160 | ... [ 0., -1., 0., 0.], 161 | ... [ 1., 0., 0., 0.]]) 162 | >>> np.allclose(T.toarray(), T_exact) 163 | True 164 | 165 | 166 | Returns 167 | ------- 168 | Tmat : sparse matrix 169 | Transformation matrix to apply Bloch boundary conditions. 170 | 171 | """ 172 | rows = [] 173 | cols = [] 174 | vals = [] 175 | 176 | # Inner nodes 177 | for cont_dof in range(ndofs): 178 | if cont_dof not in dofs_ima: 179 | rows.append(cont_dof) 180 | cols.append(new_num[cont_dof]) 181 | vals.append(1) 182 | 183 | # Bloch nodes 184 | for node_ima, node_ref in zip(nodes_ima, nodes_ref): 185 | pos_diff = coords[node_ima] - coords[node_ref] 186 | phase_shift = np.exp(1.0j*np.dot(wavevector, pos_diff)) 187 | dof_ref = nodes_ref_dof[node_ref] 188 | dof_ima = nodes_ima_dof[node_ima] 189 | for cont_ref, cont_ima in zip(dof_ref, dof_ima): 190 | rows.append(cont_ima) 191 | cols.append(new_num[cont_ref]) 192 | vals.append(phase_shift) 193 | 194 | nconds = len(dofs_ref) 195 | return coo_matrix((vals, (rows, cols)), 196 | shape=(ndofs, ndofs - nconds)).tocsr() 197 | 198 | 199 | #%% Doctests 200 | if __name__ == "__main__": 201 | import doctest 202 | doctest.testmod() -------------------------------------------------------------------------------- /solidspy/femutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | FEM routines 4 | ------------ 5 | 6 | Functions to compute kinematics variables for the Finite 7 | Element Analysis. 8 | 9 | The elements included are: 10 | 1. 4 node bilinear quadrilateral. 11 | 2. 6 node quadratic triangle. 12 | 3. 3 node linear triangle. 13 | 14 | The notation used is similar to the one used by Bathe [1]_. 15 | 16 | 17 | References 18 | ---------- 19 | .. [1] Bathe, Klaus-Jürgen. Finite element procedures. Prentice Hall, 20 | Pearson Education, 2006. 21 | 22 | """ 23 | import numpy as np 24 | import solidspy.gaussutil as gau 25 | 26 | 27 | def eletype(eletype): 28 | """Assigns number to degrees of freedom 29 | 30 | According to iet assigns number of degrees of freedom, number of 31 | nodes and minimum required number of integration points. 32 | 33 | Parameters 34 | ---------- 35 | eletype : int 36 | Type of element. These are: 37 | 1. 4 node bilinear quadrilateral. 38 | 2. 6 node quadratic triangle. 39 | 3. 3 node linear triangle. 40 | 5. 2 node spring. 41 | 6. 2 node truss element. 42 | 7. 2 node beam (3 DOF per node). 43 | 8. 2 node beam with axial force (3 DOF per node). 44 | 45 | Returns 46 | ------- 47 | ndof : int 48 | Number of degrees of freedom for the selected element. 49 | nnodes : int 50 | Number of nodes for the selected element. 51 | ngpts : int 52 | Number of Gauss points for the selected element. 53 | 54 | """ 55 | elem_id = { 56 | 1: (8, 4, 4), 57 | 2: (12, 6, 7), 58 | 3: (6, 3, 3), 59 | 4: (18, 9, 9), 60 | 5: (4, 2, 3), 61 | 6: (4, 2, 3), 62 | 7: (6, 2, 3), 63 | 8: (6, 2, 3)} 64 | try: 65 | return elem_id[eletype] 66 | except: 67 | raise ValueError("You entered an invalid type of element.") 68 | 69 | 70 | #%% Shape functions and derivatives 71 | 72 | # Triangles 73 | def shape_tri3(r, s): 74 | """ 75 | Shape functions and derivatives for a linear element 76 | 77 | Parameters 78 | ---------- 79 | r : float 80 | Horizontal coordinate of the evaluation point. 81 | s : float 82 | Vertical coordinate of the evaluation point. 83 | 84 | Returns 85 | ------- 86 | N : ndarray (float) 87 | Array with the shape functions evaluated at the point (r, s). 88 | dNdr : ndarray (float) 89 | Array with the derivative of the shape functions evaluated at 90 | the point (r, s). 91 | 92 | Examples 93 | -------- 94 | We can check evaluating at two different points, namely (0, 0) and 95 | (0, 0.5). Thus 96 | 97 | >>> N, _ = shape_tri3(0, 0) 98 | >>> N_ex = np.array([ 99 | ... [1, 0, 0, 0, 0, 0], 100 | ... [0, 1, 0, 0, 0, 0]]) 101 | >>> np.allclose(N, N_ex) 102 | True 103 | 104 | and 105 | 106 | >>> N, _ = shape_tri3(1/2, 1/2) 107 | >>> N_ex = np.array([ 108 | ... [0, 0, 1/2, 0, 1/2, 0], 109 | ... [0, 0, 0, 1/2, 0, 1/2]]) 110 | >>> np.allclose(N, N_ex) 111 | True 112 | 113 | """ 114 | N = np.array([1 - r - s, r, s]) 115 | dNdr = np.array([ 116 | [-1, 1, 0], 117 | [-1, 0, 1]]) 118 | return N, dNdr 119 | 120 | 121 | def shape_tri6(r, s): 122 | """ 123 | Shape functions and derivatives for a quadratic element 124 | 125 | Parameters 126 | ---------- 127 | r : float 128 | Horizontal coordinate of the evaluation point. 129 | s : float 130 | Vertical coordinate of the evaluation point. 131 | 132 | Returns 133 | ------- 134 | N : ndarray (float) 135 | Array with the shape functions evaluated at the point (r, s). 136 | dNdr : ndarray (float) 137 | Array with the derivative of the shape functions evaluated at 138 | the point (r, s). 139 | """ 140 | N = np.array( 141 | [(1 - r - s) - 2*r*(1 - r - s) - 2*s*(1 - r - s), 142 | r - 2*r*(1 - r - s) - 2*r*s, 143 | s - 2*r*s - 2*s*(1-r-s), 144 | 4*r*(1 - r - s), 145 | 4*r*s, 146 | 4*s*(1 - r - s)]) 147 | dNdr = np.array([ 148 | [4*r + 4*s - 3, 4*r - 1, 0, -8*r - 4*s + 4, 4*s, -4*s], 149 | [4*r + 4*s - 3, 0, 4*s - 1, -4*r, 4*r, -4*r - 8*s + 4]]) 150 | return N, dNdr 151 | 152 | # Quadrilaterals 153 | def shape_quad4(r, s): 154 | """ 155 | Shape functions and derivatives for a bilinear element 156 | 157 | Parameters 158 | ---------- 159 | r : float 160 | Horizontal coordinate of the evaluation point. 161 | s : float 162 | Vertical coordinate of the evaluation point. 163 | 164 | Returns 165 | ------- 166 | N : ndarray (float) 167 | Array with the shape functions evaluated at the point (r, s). 168 | dNdr : ndarray (float) 169 | Array with the derivative of the shape functions evaluated at 170 | the point (r, s). 171 | 172 | Examples 173 | -------- 174 | We can check evaluating at two different points, namely (0, 0) and 175 | (1, 1). Thus 176 | 177 | >>> N, _ = shape_quad4(0, 0) 178 | >>> N_ex = np.array([ 179 | ... [1/4, 0, 1/4, 0, 1/4, 0, 1/4, 0], 180 | ... [0, 1/4, 0, 1/4, 0, 1/4, 0, 1/4]]) 181 | >>> np.allclose(N, N_ex) 182 | True 183 | 184 | and 185 | 186 | >>> N, _ = shape_quad4(1, 1) 187 | >>> N_ex = np.array([ 188 | ... [0, 0, 0, 0, 1, 0, 0, 0], 189 | ... [0, 0, 0, 0, 0, 1, 0, 0]]) 190 | >>> np.allclose(N, N_ex) 191 | True 192 | 193 | """ 194 | N = 0.25*np.array( 195 | [(1 - r)*(1 - s), 196 | (1 + r)*(1 - s), 197 | (1 + r)*(1 + s), 198 | (1 - r)*(1 + s)]) 199 | dNdr = 0.25*np.array([ 200 | [s - 1, -s + 1, s + 1, -s - 1], 201 | [r - 1, -r - 1, r + 1, -r + 1]]) 202 | return N, dNdr 203 | 204 | 205 | def shape_quad9(r, s): 206 | """ 207 | Shape functions and derivatives for a biquadratic element 208 | 209 | Parameters 210 | ---------- 211 | r : float 212 | Horizontal coordinate of the evaluation point. 213 | s : float 214 | Vertical coordinate of the evaluation point. 215 | 216 | Returns 217 | ------- 218 | N : ndarray (float) 219 | Array with the shape functions evaluated at the point (r, s). 220 | dNdr : ndarray (float) 221 | Array with the derivative of the shape functions evaluated at 222 | the point (r, s). 223 | """ 224 | N = np.array( 225 | [0.25*r*s*(r - 1.0)*(s - 1.0), 226 | 0.25*r*s*(r + 1.0)*(s - 1.0), 227 | 0.25*r*s*(r + 1.0)*(s + 1.0), 228 | 0.25*r*s*(r - 1.0)*(s + 1.0), 229 | 0.5*s*(-r**2 + 1.0)*(s - 1.0), 230 | 0.5*r*(r + 1.0)*(-s**2 + 1.0), 231 | 0.5*s*(-r**2 + 1.0)*(s + 1.0), 232 | 0.5*r*(r - 1.0)*(-s**2 + 1.0), 233 | (-r**2 + 1.0)*(-s**2 + 1.0)]) 234 | dNdr = np.array([ 235 | [0.25*s*(2.0*r - 1.0)*(s - 1.0), 236 | 0.25*s*(2.0*r + 1.0)*(s - 1.0), 237 | 0.25*s*(2.0*r + 1.0)*(s + 1.0), 238 | 0.25*s*(2.0*r - 1.0)*(s + 1.0), 239 | r*s*(-s + 1.0), 240 | -0.5*(2.0*r + 1.0)*(s**2 - 1.0), 241 | -r*s*(s + 1.0), 242 | 0.5*(-2.0*r + 1.0)*(s**2 - 1.0), 243 | 2.0*r*(s**2 - 1.0)], 244 | [0.25*r*(r - 1.0)*(2.0*s - 1.0), 245 | 0.25*r*(r + 1.0)*(2.0*s - 1.0), 246 | 0.25*r*(r + 1.0)*(2.0*s + 1.0), 247 | 0.25*r*(r - 1.0)*(2.0*s + 1.0), 248 | 0.5*(r**2 - 1.0)*(-2.0*s + 1.0), 249 | -r*s*(r + 1.0), 250 | -0.5*(r**2 - 1.0)*(2.0*s + 1.0), 251 | r*s*(-r + 1.0), 252 | 2.0*s*(r**2 - 1.0)]]) 253 | return N, dNdr 254 | 255 | 256 | 257 | def shape_quad8(r, s): 258 | """ 259 | Shape functions and derivatives for a 8-noded serendipity element 260 | 261 | Parameters 262 | ---------- 263 | r : float 264 | Horizontal coordinate of the evaluation point. 265 | s : float 266 | Vertical coordinate of the evaluation point. 267 | 268 | Returns 269 | ------- 270 | N : ndarray (float) 271 | Array with the shape functions evaluated at the point (r, s). 272 | dNdr : ndarray (float) 273 | Array with the derivative of the shape functions evaluated at 274 | the point (r, s). 275 | """ 276 | N = 0.25*np.array( 277 | [(1.0 - r)*(1.0 - s) - (1.0 - r)*(1.0 - s**2) - (1.0 - s)*(1.0 - r**2), 278 | (1.0 + r)*(1.0 - s) - (1.0 + r)*(1.0 - s**2) - (1.0 - s)*(1.0 - r**2), 279 | (1.0 + r)*(1.0 + s) - (1.0 + r)*(1.0 - s**2) - (1.0 + s)*(1.0 - r**2), 280 | (1.0 - r)*(1.0 + s) - (1.0 - r)*(1.0 - s**2) - (1.0 + s)*(1.0 - r**2), 281 | 2.0*(1.0 - s)*(1.0 - r**2), 282 | 2.0*(1.0 + r)*(1.0 - s**2), 283 | 2.0*(1.0 + s)*(1.0 - r**2), 284 | 2.0*(1.0 - r)*(1.0 - s**2)]) 285 | dNdr = 0.25*np.array([ 286 | [-2.0*r*(s - 1.0) - s**2 + s, 287 | -2.0*r*(s - 1.0) + s**2 - s, 288 | -2.0*r*(-s - 1.0) + s**2 + s, 289 | -2.0*r*(-s - 1.0) - s**2 - s, 290 | -2.0*r*(2.0 - 2.0*s), 291 | 2.0 - 2.0*s**2, 292 | -2.0*r*(2.0*s + 2.0), 293 | 2.0*s**2 - 2.0], 294 | [-r**2 + r - 2.0*s*(r - 1.0), 295 | -r**2 - r - 2.0*s*(-r - 1.0), 296 | r**2 + r - 2.0*s*(-r - 1.0), 297 | r**2 - r - 2.0*s*(r - 1.0), 298 | 2.0*r**2 - 2.0, 299 | -2.0*s*(2.0*r + 2.0), 300 | 2.0 - 2.0*r**2, 301 | -2.0*s*(2.0 - 2.0*r)]]) 302 | return N, dNdr 303 | 304 | 305 | ## 3D elements 306 | def shape_tet4(r, s, t): 307 | """ 308 | Shape functions and derivatives for a linear tetrahedron 309 | 310 | Parameters 311 | ---------- 312 | r : float 313 | Horizontal coordinate of the evaluation point. 314 | s : float 315 | Horizontal coordinate of the evaluation point. 316 | t : float 317 | Vertical coordinate of the evaluation point. 318 | 319 | Returns 320 | ------- 321 | N : ndarray (float) 322 | Array with the shape functions evaluated at the point (r, s, t). 323 | dNdr : ndarray (float) 324 | Array with the derivative of the shape functions evaluated at 325 | the point (r, s, t). 326 | """ 327 | N = np.array([1 - r - s - t, r, s, t]) 328 | dNdr = np.array([ 329 | [-1, 1, 0, 0], 330 | [-1, 0, 1, 0], 331 | [-1, 0, 0, 1]]) 332 | return N, dNdr 333 | 334 | 335 | def shape_hex8(r, s, t): 336 | """ 337 | Shape functions and derivatives for a trilinear element 338 | 339 | Parameters 340 | ---------- 341 | r : float 342 | Horizontal coordinate of the evaluation point. 343 | s : float 344 | Horizontal coordinate of the evaluation point. 345 | t : float 346 | Vertical coordinate of the evaluation point. 347 | 348 | Returns 349 | ------- 350 | N : ndarray (float) 351 | Array with the shape functions evaluated at the point (r, s, t). 352 | dNdr : ndarray (float) 353 | Array with the derivative of the shape functions evaluated at 354 | the point (r, s, t). 355 | """ 356 | N = np.array([ 357 | (1 - r)*(1 - s)*(1 - t), (1 - s)*(1 - t)*(r + 1), 358 | (1 - t)*(r + 1)*(s + 1), (1 - r)*(1 - t)*(s + 1), 359 | (1 - r)*(1 - s)*(t + 1), (1 - s)*(r + 1)*(t + 1), 360 | (r + 1)*(s + 1)*(t + 1), (1 - r)*(s + 1)*(t + 1)]) 361 | dNdr = np.array([ 362 | [(1 - t)*(s - 1), (1 - s)*(1 - t), 363 | (1 - t)*(s + 1), (1 - t)*(-s - 1), 364 | (1 - s)*(-t - 1), (1 - s)*(t + 1), 365 | (s + 1)*(t + 1), -(s + 1)*(t + 1)], 366 | [(1 - t)*(r - 1), (1 - t)*(-r - 1), 367 | (1 - t)*(r + 1), (1 - r)*(1 - t), 368 | -(1 - r)*(t + 1), -(r + 1)*(t + 1), 369 | (r + 1)*(t + 1), (1 - r)*(t + 1)], 370 | [-(1 - r)*(1 - s), -(1 - s)*(r + 1), 371 | -(r + 1)*(s + 1), -(1 - r)*(s + 1), 372 | (1 - r)*(1 - s), (1 - s)*(r + 1), 373 | (r + 1)*(s + 1), (1 - r)*(s + 1)]]) 374 | return 0.125*N, 0.125*dNdr 375 | 376 | 377 | #%% Derivative matrices 378 | def elast_diff_2d(r, s, coord, element): 379 | """ 380 | Interpolation matrices for elements for plane elasticity 381 | 382 | Parameters 383 | ---------- 384 | r : float 385 | Horizontal coordinate of the evaluation point. 386 | s : float 387 | Vertical coordinate of the evaluation point. 388 | coord : ndarray (float) 389 | Coordinates of the element. 390 | 391 | Returns 392 | ------- 393 | H : ndarray (float) 394 | Array with the shape functions evaluated at the point (r, s) 395 | for each degree of freedom. 396 | B : ndarray (float) 397 | Array with the displacement to strain matrix evaluated 398 | at the point (r, s). 399 | det : float 400 | Determinant of the Jacobian. 401 | """ 402 | N, dNdr = element(r, s) 403 | det, jaco_inv = jacoper(dNdr, coord) 404 | dNdx = jaco_inv @ dNdr 405 | H = np.zeros((2, 2*N.shape[0])) 406 | B = np.zeros((3, 2*N.shape[0])) 407 | H[0, 0::2] = N 408 | H[1, 1::2] = N 409 | B[0, 0::2] = dNdx[0, :] 410 | B[1, 1::2] = dNdx[1, :] 411 | B[2, 0::2] = dNdx[1, :] 412 | B[2, 1::2] = dNdx[0, :] 413 | return H, B, det 414 | 415 | 416 | def elast_diff_axi(r, s, coord, element): 417 | """ 418 | Interpolation matrices for elements for axisymetric elasticity 419 | 420 | Parameters 421 | ---------- 422 | r : float 423 | Horizontal coordinate of the evaluation point. 424 | s : float 425 | Vertical coordinate of the evaluation point. 426 | coord : ndarray (float) 427 | Coordinates of the element. 428 | 429 | Returns 430 | ------- 431 | H : ndarray (float) 432 | Array with the shape functions evaluated at the point (r, s) 433 | for each degree of freedom. 434 | B : ndarray (float) 435 | Array with the displacement to strain matrix evaluated 436 | at the point (r, s). 437 | det : float 438 | Determinant of the Jacobian. 439 | """ 440 | N, dNdr = element(r, s) 441 | x = N.dot(coord[:, 0]) 442 | if x < 0: 443 | raise ValueError("Horizontal coordinates should be non-negative.") 444 | det, jaco_inv = jacoper(dNdr, coord) 445 | dNdx = jaco_inv @ dNdr 446 | H = np.zeros((2, 2*N.shape[0])) 447 | B = np.zeros((4, 2*N.shape[0])) 448 | H[0, 0::2] = N 449 | H[1, 1::2] = N 450 | B[0, 0::2] = dNdx[0, :] 451 | B[1, 1::2] = dNdx[1, :] 452 | B[2, 0::2] = dNdx[1, :] 453 | B[2, 1::2] = dNdx[0, :] 454 | B[2, 1::2] = dNdx[0, :] 455 | B[3, 0::2] = N/x 456 | return H, B, det 457 | 458 | 459 | ## 3D elements 460 | def elast_diff_3d(r, s, t, coord, interp=shape_hex8): 461 | """ 462 | Interpolation matrices for a trilinear element for 463 | elasticity 464 | 465 | Parameters 466 | ---------- 467 | r : float 468 | Horizontal coordinate of the evaluation point. 469 | s : float 470 | Horizontal coordinate of the evaluation point. 471 | t : float 472 | Vertical coordinate of the evaluation point. 473 | coord : ndarray (float) 474 | Coordinates of the element. 475 | 476 | Returns 477 | ------- 478 | H : ndarray (float) 479 | Array with the shape functions evaluated at the point (r, s, t) 480 | for each degree of freedom. 481 | B : ndarray (float) 482 | Array with the displacement to strain matrix evaluated 483 | at the point (r, s, t). 484 | det : float 485 | Determinant of the Jacobian. 486 | """ 487 | N, dNdr = interp(r, s, t) 488 | det, jaco_inv = jacoper(dNdr, coord) 489 | dNdx = jaco_inv @ dNdr 490 | H = np.zeros((3, 3*N.shape[0])) 491 | B = np.zeros((6, 3*N.shape[0])) 492 | H[0, 0::3] = N 493 | H[1, 1::3] = N 494 | H[2, 2::3] = N 495 | B[0, 0::3] = dNdx[0, :] 496 | B[1, 1::3] = dNdx[1, :] 497 | B[2, 2::3] = dNdx[2, :] 498 | B[3, 1::3] = dNdx[2, :] 499 | B[3, 2::3] = dNdx[1, :] 500 | B[4, 0::3] = dNdx[2, :] 501 | B[4, 2::3] = dNdx[0, :] 502 | B[5, 0::3] = dNdx[1, :] 503 | B[5, 1::3] = dNdx[0, :] 504 | return H, B, det 505 | 506 | 507 | #%% 508 | def jacoper(dNdr, coord): 509 | """ 510 | Compute the Jacobian of the transformation evaluated at 511 | the Gauss point 512 | 513 | Parameters 514 | ---------- 515 | dNdr : ndarray 516 | Derivatives of the interpolation function with respect to the 517 | natural coordinates. 518 | coord : ndarray 519 | Coordinates of the nodes of the element (nnodes, ndim). 520 | 521 | Returns 522 | ------- 523 | jaco_inv : ndarray (ndim, ndim) 524 | Jacobian of the transformation evaluated at a point. 525 | 526 | """ 527 | jaco = dNdr @ coord 528 | det = np.linalg.det(jaco) 529 | if np.isclose(np.abs(det), 0.0): 530 | msg = "Jacobian close to zero. Check the shape of your elements!" 531 | raise ValueError(msg) 532 | jaco_inv = np.linalg.inv(jaco) 533 | if det < 0.0: 534 | msg = "Jacobian is negative. Check your elements orientation!" 535 | raise ValueError(msg) 536 | return det, jaco_inv 537 | 538 | 539 | #%% Material routines 540 | def umat(params): 541 | """2D Elasticity consitutive matrix in plane stress 542 | 543 | For plane strain use effective properties. 544 | 545 | Parameters 546 | ---------- 547 | params : tuple 548 | Material parameters in the following order: 549 | 550 | E : float 551 | Young modulus (>0). 552 | nu : float 553 | Poisson coefficient (-1, 0.5). 554 | 555 | Returns 556 | ------- 557 | C : ndarray 558 | Constitutive tensor in Voigt notation. 559 | 560 | Examples 561 | -------- 562 | 563 | >>> params = 8/3, 1/3 564 | >>> C = umat(params) 565 | >>> C_ex = np.array([ 566 | ... [3, 1, 0], 567 | ... [1, 3, 0], 568 | ... [0, 0, 1]]) 569 | >>> np.allclose(C, C_ex) 570 | True 571 | 572 | """ 573 | E, nu = params 574 | C = np.zeros((3, 3)) 575 | enu = E/(1 - nu**2) 576 | mnu = (1 - nu)/2 577 | C[0, 0] = enu 578 | C[0, 1] = nu*enu 579 | C[1, 0] = C[0, 1] 580 | C[1, 1] = enu 581 | C[2, 2] = enu*mnu 582 | 583 | return C 584 | 585 | 586 | def elast_mat_2d(params): 587 | """2D Elasticity consitutive matrix in plain stress 588 | 589 | For plane stress use effective properties. 590 | 591 | Parameters 592 | ---------- 593 | params : tuple 594 | Material parameters in the following order: 595 | 596 | E : float 597 | Young modulus (>0). 598 | nu : float 599 | Poisson coefficient (-1, 0.5). 600 | 601 | Returns 602 | ------- 603 | C : ndarray 604 | Constitutive tensor in Voigt notation. 605 | 606 | """ 607 | E, nu = params 608 | lamda = E*nu/(1 + nu)/(1 - 2*nu) 609 | mu = 0.5*E/(1 + nu) 610 | C = np.array([ 611 | [2*mu + lamda, lamda, 0, 0], 612 | [lamda, 2*mu + lamda, 0, 0], 613 | [0, 0, mu, 0], 614 | [0, 0, 0, 2*mu + lamda]]) 615 | return C 616 | 617 | 618 | def elast_mat_axi(params): 619 | """Elasticity consitutive matrix for axisymmetric problems. 620 | 621 | Parameters 622 | ---------- 623 | params : tuple 624 | Material parameters in the following order: 625 | 626 | E : float 627 | Young modulus (>0). 628 | nu : float 629 | Poisson coefficient (-1, 0.5). 630 | 631 | Returns 632 | ------- 633 | C : ndarray 634 | Constitutive tensor in Voigt notation. 635 | 636 | """ 637 | E, nu = params 638 | lamda = E*nu/(1 + nu)/(1 - 2*nu) 639 | mu = 0.5*E/(1 + nu) 640 | C = np.array([ 641 | [2*mu + lamda, lamda, 0, 0], 642 | [lamda, 2*mu + lamda, 0, 0], 643 | [0, 0, mu, 0], 644 | [0, 0, 0, 2*mu + lamda]]) 645 | return C 646 | 647 | 648 | def elast_mat(params): 649 | """3D Elasticity consitutive matrix. 650 | 651 | Parameters 652 | ---------- 653 | params : tuple 654 | Material parameters in the following order: 655 | 656 | E : float 657 | Young modulus (>0). 658 | nu : float 659 | Poisson coefficient (-1, 0.5). 660 | 661 | Returns 662 | ------- 663 | C : ndarray 664 | Constitutive tensor in Voigt notation. 665 | 666 | """ 667 | E, nu = params 668 | lamda = E*nu/(1 + nu)/(1 - 2*nu) 669 | mu = 0.5*E/(1 + nu) 670 | C = np.array([ 671 | [2*mu + lamda, lamda, lamda, 0, 0, 0], 672 | [lamda, 2*mu + lamda, lamda, 0, 0, 0], 673 | [lamda, lamda, 2*mu + lamda, 0, 0, 0], 674 | [0, 0, 0, mu, 0, 0], 675 | [0, 0, 0, 0, mu, 0], 676 | [0, 0, 0, 0, 0, mu]]) 677 | return C 678 | 679 | 680 | 681 | #%% Elemental strains 682 | def str_el3(coord, ul): 683 | """Compute the strains at each element integration point 684 | 685 | This one is used for 3-noded triangular elements. 686 | 687 | Parameters 688 | ---------- 689 | coord : ndarray 690 | Coordinates of the nodes of the element (nn, 2). 691 | ul : ndarray 692 | Array with displacements for the element. 693 | 694 | Returns 695 | ------- 696 | epsGT : ndarray 697 | Strain components for the Gauss points. 698 | xl : ndarray 699 | Configuration of the Gauss points after deformation. 700 | 701 | """ 702 | epsG = np.zeros([3, 3]) 703 | xl = np.zeros([3, 2]) 704 | gpts, _ = gau.gauss_tri(order=1) 705 | for i in range(gpts.shape[0]): 706 | ri, si = gpts[i, :] 707 | H, B, _ = elast_diff_2d(ri, si, coord, shape_tri3) 708 | epsG[:, i] = B @ ul 709 | xl[i, 0] = np.dot(H[0, ::2], coord[:, 0]) 710 | xl[i, 1] = np.dot(H[0, ::2], coord[:, 0]) 711 | return epsG.T, xl 712 | 713 | 714 | def str_el6(coord, ul): 715 | """Compute the strains at each element integration point 716 | 717 | This one is used for 6-noded triangular elements. 718 | 719 | Parameters 720 | ---------- 721 | coord : ndarray 722 | Coordinates of the nodes of the element (6, 2). 723 | ul : ndarray 724 | Array with displacements for the element. 725 | 726 | Returns 727 | ------- 728 | epsGT : ndarray 729 | Strain components for the Gauss points. 730 | xl : ndarray 731 | Configuration of the Gauss points after deformation. 732 | 733 | """ 734 | epsG = np.zeros([3, 7]) 735 | xl = np.zeros([7, 2]) 736 | gpts, _ = gau.gauss_tri(order=3) 737 | for i in range(7): 738 | ri, si = gpts[i, :] 739 | H, B, _ = elast_diff_2d(ri, si, coord, shape_tri6) 740 | epsG[:, i] = B @ ul 741 | xl[i, 0] = np.dot(H[0, ::2], coord[:, 0]) 742 | xl[i, 1] = np.dot(H[0, ::2], coord[:, 0]) 743 | return epsG.T, xl 744 | 745 | 746 | def str_el4(coord, ul): 747 | """Compute the strains at each element integration point 748 | 749 | This one is used for 4-noded quadrilateral elements. 750 | 751 | Parameters 752 | ---------- 753 | coord : ndarray 754 | Coordinates of the nodes of the element (4, 2). 755 | ul : ndarray 756 | Array with displacements for the element. 757 | 758 | Returns 759 | ------- 760 | epsGT : ndarray 761 | Strain components for the Gauss points. 762 | xl : ndarray 763 | Configuration of the Gauss points after deformation. 764 | 765 | """ 766 | epsG = np.zeros([3, 4]) 767 | xl = np.zeros([4, 2]) 768 | gpts, _ = gau.gauss_nd(2) 769 | for i in range(gpts.shape[0]): 770 | ri, si = gpts[i, :] 771 | H, B, _= elast_diff_2d(ri, si, coord, shape_quad4) 772 | epsG[:, i] = B @ ul 773 | xl[i, 0] = np.dot(H[0, ::2], coord[:, 0]) 774 | xl[i, 1] = np.dot(H[0, ::2], coord[:, 0]) 775 | return epsG.T, xl 776 | 777 | 778 | if __name__ == "__main__": 779 | import doctest 780 | doctest.testmod() 781 | -------------------------------------------------------------------------------- /solidspy/preprocesor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Preprocessor subroutines 4 | ------------------------- 5 | 6 | This module contains functions to preprocess the input files to compute 7 | a Finite Element Analysis. 8 | 9 | """ 10 | import sys 11 | import numpy as np 12 | 13 | 14 | def readin(folder=""): 15 | """Read the input files""" 16 | nodes = np.loadtxt(folder + 'nodes.txt', ndmin=2) 17 | mats = np.loadtxt(folder + 'mater.txt', ndmin=2) 18 | elements = np.loadtxt(folder + 'eles.txt', ndmin=2, dtype=int) 19 | loads = np.loadtxt(folder + 'loads.txt', ndmin=2) 20 | 21 | return nodes, mats, elements, loads 22 | 23 | 24 | def echomod(nodes, mats, elements, loads, folder=""): 25 | """Create echoes of the model input files""" 26 | np.savetxt(folder + "KNODES.txt", nodes, fmt='%5.2f', delimiter=' ') 27 | np.savetxt(folder + "KMATES.txt", mats, fmt='%5.2f', delimiter=' ') 28 | np.savetxt(folder + "KELEMS.txt", elements, fmt='%d', delimiter=' ') 29 | np.savetxt(folder + "KLOADS.txt", loads, fmt='%5.2f', delimiter=' ') 30 | 31 | 32 | def initial_params(): 33 | """Read initial parameters for the simulation 34 | 35 | The parameters to be read are: 36 | 37 | - folder: location of the input files. 38 | - name: name for the output files (if echo is True). 39 | - echo: echo output files. 40 | """ 41 | # Check Python version 42 | version = sys.version_info.major 43 | if version == 3: 44 | global raw_input 45 | raw_input = input 46 | elif version == 2: 47 | pass 48 | else: 49 | raise ValueError("You should use Python 2.x at least!") 50 | 51 | # Try to run with easygui 52 | try: 53 | import easygui 54 | folder = easygui.diropenbox(title="Folder for the job") + "/" 55 | except: 56 | folder = raw_input('Enter folder (empty for the current one): ') 57 | 58 | return folder 59 | 60 | 61 | def ele_writer(cells, cell_data, ele_tag, phy_sur, ele_type, mat_tag, nini): 62 | """ 63 | Extracts a subset of elements from a complete mesh according to the 64 | physical surface phy_sur and writes down the proper fields into an 65 | elements array. 66 | 67 | Parameters 68 | ---------- 69 | cell : dictionary 70 | Dictionary created by meshio with cells information. 71 | cell_data: dictionary 72 | Dictionary created by meshio with cells data information. 73 | ele_tag : string 74 | Element type according to meshio convention, 75 | e.g., quad9 or line3. 76 | phy_sur : int 77 | Physical surface for the subset. 78 | ele_type: int 79 | Element type. 80 | mat_tag : int 81 | Material profile for the subset. 82 | ndof : int 83 | Number of degrees of freedom for the elements. 84 | nnode : int 85 | Number of nodes for the element. 86 | nini : int 87 | Element id for the first element in the set. 88 | 89 | Returns 90 | ------- 91 | nf : int 92 | Element id for the last element in the set 93 | els_array : int 94 | Elemental data. 95 | 96 | """ 97 | eles = cells[ele_tag] 98 | dict_nnode = {'triangle': 3, 99 | 'triangle6': 6, 100 | 'quad': 4} 101 | nnode = dict_nnode[ele_tag] 102 | phy_surface = cell_data[ele_tag]['gmsh:physical'] 103 | ele_id = [cont for cont, _ in enumerate(phy_surface[:]) 104 | if phy_surface[cont] == phy_sur] 105 | els_array = np.zeros([len(ele_id) , 3 + nnode], dtype=int) 106 | els_array[: , 0] = range(nini , len(ele_id) + nini ) 107 | els_array[: , 1] = ele_type 108 | els_array[: , 2] = mat_tag 109 | els_array[: , 3::] = eles[ele_id, :] 110 | nf = nini + len(ele_id) 111 | return nf , els_array 112 | 113 | 114 | def node_writer(points , point_data): 115 | """Write nodal data as required by SolidsPy 116 | 117 | Parameters 118 | ---------- 119 | points : dictionary 120 | Nodal points 121 | point_data : dictionary 122 | Physical data associatted to the nodes. 123 | 124 | Returns 125 | ------- 126 | nodes_array : ndarray (int) 127 | Array with the nodal data according to SolidsPy. 128 | 129 | """ 130 | nodes_array = np.zeros([points.shape[0], 5]) 131 | nodes_array[:, 0] = range(points.shape[0]) 132 | nodes_array[:, 1:3] = points[:, :2] 133 | return nodes_array 134 | 135 | 136 | def boundary_conditions(cells, cell_data, phy_lin, nodes_array, bc_x, bc_y): 137 | """Impose nodal point boundary conditions as required by SolidsPy 138 | 139 | Parameters 140 | ---------- 141 | cell : dictionary 142 | Dictionary created by meshio with cells information. 143 | cell_data: dictionary 144 | Dictionary created by meshio with cells data information. 145 | phy_lin : int 146 | Physical line where BCs are to be imposed. 147 | nodes_array : int 148 | Array with the nodal data and to be modified by BCs. 149 | bc_x, bc_y : int 150 | Boundary condition flag along the x and y direction: 151 | * -1: restrained 152 | * 0: free 153 | 154 | Returns 155 | ------- 156 | nodes_array : int 157 | Array with the nodal data after imposing BCs according 158 | to SolidsPy. 159 | 160 | """ 161 | lines = cells["line"] 162 | # Bounds contains data corresponding to the physical line. 163 | phy_line = cell_data["line"]["gmsh:physical"] 164 | id_frontera = [cont for cont in range(len(phy_line)) 165 | if phy_line[cont] == phy_lin] 166 | nodes_frontera = lines[id_frontera] 167 | nodes_frontera = nodes_frontera.flatten() 168 | nodes_frontera = list(set(nodes_frontera)) 169 | nodes_array[nodes_frontera, 3] = bc_x 170 | nodes_array[nodes_frontera, 4] = bc_y 171 | return nodes_array 172 | 173 | 174 | def loading(cells, cell_data, phy_lin, P_x, P_y): 175 | """Impose nodal boundary conditions as required by SolidsPy 176 | 177 | Parameters 178 | ---------- 179 | cell : dictionary 180 | Dictionary created by meshio with cells information. 181 | cell_data: dictionary 182 | Dictionary created by meshio with cells data information. 183 | phy_lin : int 184 | Physical line where BCs are to be imposed. 185 | nodes_array : int 186 | Array with the nodal data and to be modified by BCs. 187 | P_x, P_y : float 188 | Load components in x and y directions. 189 | 190 | Returns 191 | ------- 192 | nodes_array : int 193 | Array with the nodal data after imposing BCs according 194 | to SolidsPy. 195 | 196 | """ 197 | lines = cells["line"] 198 | # Bounds contains data corresponding to the physical line. 199 | phy_line = cell_data["line"]["gmsh:physical"] 200 | id_carga = [cont for cont in range(len(phy_line)) 201 | if phy_line[cont] == phy_lin] 202 | nodes_carga = lines[id_carga] 203 | nodes_carga = nodes_carga.flatten() 204 | nodes_carga = list(set(nodes_carga)) 205 | ncargas = len(nodes_carga) 206 | cargas = np.zeros((ncargas, 3)) 207 | cargas[:, 0] = nodes_carga 208 | cargas[:, 1] = P_x/ncargas 209 | cargas[:, 2] = P_y/ncargas 210 | return cargas 211 | 212 | 213 | def rect_grid(length, height, nx, ny, eletype=None): 214 | """Generate a structured mesh for a rectangle 215 | 216 | The coordinates of the nodes will be defined in the 217 | domain [-length/2, length/2] x [-height/2, height/2]. 218 | 219 | Parameters 220 | ---------- 221 | length : float 222 | Length of the domain. 223 | height : gloat 224 | Height of the domain. 225 | nx : int 226 | Number of elements in the x direction. 227 | ny : int 228 | Number of elements in the y direction. 229 | eletype : None 230 | It does nothing right now. 231 | 232 | Returns 233 | ------- 234 | x : ndarray (float) 235 | x-coordinates for the nodes. 236 | y : ndarray (float) 237 | y-coordinates for the nodes. 238 | els : ndarray 239 | Array with element data. 240 | 241 | Examples 242 | -------- 243 | 244 | >>> x, y, els = rect_grid(2, 2, 2, 2) 245 | >>> x 246 | array([-1., 0., 1., -1., 0., 1., -1., 0., 1.]) 247 | >>> y 248 | array([-1., -1., -1., 0., 0., 0., 1., 1., 1.]) 249 | >>> els 250 | array([[0, 1, 0, 0, 1, 4, 3], 251 | [1, 1, 0, 1, 2, 5, 4], 252 | [2, 1, 0, 3, 4, 7, 6], 253 | [3, 1, 0, 4, 5, 8, 7]]) 254 | 255 | """ 256 | y, x = np.mgrid[-height/2:height/2:(ny + 1)*1j, 257 | -length/2:length/2:(nx + 1)*1j] 258 | els = np.zeros((nx*ny, 7), dtype=int) 259 | els[:, 1] = 1 260 | for row in range(ny): 261 | for col in range(nx): 262 | cont = row*nx + col 263 | els[cont, 0] = cont 264 | els[cont, 3:7] = [cont + row, cont + row + 1, 265 | cont + row + nx + 2, cont + row + nx + 1] 266 | return x.flatten(), y.flatten(), els 267 | 268 | 269 | if __name__ == "__main__": 270 | import doctest 271 | doctest.testmod() 272 | -------------------------------------------------------------------------------- /solidspy/solids_GUI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | solids_GUI: simple interface 4 | ---------------------------- 5 | 6 | Computes the displacement solution for a finite element assembly 7 | of 2D solids under point loads using as input easy-to-create 8 | text files containing element, nodal, materials and loads data. 9 | The input files are created out of a Gmsh (.msh) generated file 10 | using the Python module ``meshio``. 11 | 12 | Created by Juan Gomez and Nicolas Guarin-Zapata. 13 | 14 | """ 15 | from datetime import datetime 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | import solidspy.preprocesor as pre 19 | import solidspy.postprocesor as pos 20 | import solidspy.assemutil as ass 21 | import solidspy.solutil as sol 22 | 23 | 24 | def solids_GUI(plot_contours=True, compute_strains=False, folder=None): 25 | """ 26 | Run a complete workflow for a Finite Element Analysis 27 | 28 | Parameters 29 | ---------- 30 | plot_contours : Bool (optional) 31 | Boolean variable to plot contours of the computed variables. 32 | By default it is True. 33 | compute_strains : Bool (optional) 34 | Boolean variable to compute Strains and Stresses at nodes. 35 | By default it is False. 36 | folder : string (optional) 37 | String with the path to the input files. If not provided 38 | it would ask for it in a pop-up window. 39 | 40 | Returns 41 | ------- 42 | UC : ndarray (nnodes, 2) 43 | Displacements at nodes. 44 | E_nodes : ndarray (nnodes, 3), optional 45 | Strains at nodes. It is returned when `compute_strains` is True. 46 | S_nodes : ndarray (nnodes, 3), optional 47 | Stresses at nodes. It is returned when `compute_strains` is True. 48 | 49 | """ 50 | if folder is None: 51 | folder = pre.initial_params() 52 | start_time = datetime.now() 53 | echo = False 54 | 55 | # Pre-processing 56 | nodes, mats, elements, loads = pre.readin(folder=folder) 57 | if echo: 58 | pre.echomod(nodes, mats, elements, loads, folder=folder) 59 | assem_op, bc_array, neq = ass.DME(nodes[:, -2:], elements) 60 | print("Number of nodes: {}".format(nodes.shape[0])) 61 | print("Number of elements: {}".format(elements.shape[0])) 62 | print("Number of equations: {}".format(neq)) 63 | 64 | # System assembly 65 | stiff_mat, _ = ass.assembler(elements, mats, nodes[:, :3], neq, assem_op) 66 | rhs_vec = ass.loadasem(loads, bc_array, neq) 67 | 68 | # System solution 69 | disp = sol.static_sol(stiff_mat, rhs_vec) 70 | if not np.allclose(stiff_mat.dot(disp)/stiff_mat.max(), 71 | rhs_vec/stiff_mat.max()): 72 | print("The system is not in equilibrium!") 73 | end_time = datetime.now() 74 | print('Duration for system solution: {}'.format(end_time - start_time)) 75 | 76 | # Post-processing 77 | start_time = datetime.now() 78 | disp_complete = pos.complete_disp(bc_array, nodes, disp) 79 | strain_nodes, stress_nodes = None, None 80 | if compute_strains: 81 | strain_nodes, stress_nodes = pos.strain_nodes(nodes, elements, mats, 82 | disp_complete) 83 | if plot_contours: 84 | pos.fields_plot(elements, nodes, disp_complete, E_nodes=strain_nodes, 85 | S_nodes=stress_nodes) 86 | end_time = datetime.now() 87 | print('Duration for post processing: {}'.format(end_time - start_time)) 88 | print('Analysis terminated successfully!') 89 | if compute_strains: 90 | return (disp_complete, strain_nodes, stress_nodes) 91 | else: 92 | return disp_complete 93 | 94 | 95 | 96 | def solids_auto(data, plot_contours=True, compute_strains=False): 97 | """ 98 | Run a complete workflow for a Finite Element Analysis 99 | 100 | Parameters 101 | ---------- 102 | data : dict 103 | Simulation data composed of nodes, constrains, elements, 104 | materials and loads. 105 | plot_contours : Bool (optional) 106 | Boolean variable to plot contours of the computed variables. 107 | By default it is True. 108 | compute_strains : Bool (optional) 109 | Boolean variable to compute Strains and Stresses at nodes. 110 | By default it is False. 111 | 112 | Returns 113 | ------- 114 | UC : ndarray (nnodes, 2) 115 | Displacements at nodes. 116 | E_nodes : ndarray (nnodes, 3), optional 117 | Strains at nodes. It is returned when `compute_strains` is True. 118 | S_nodes : ndarray (nnodes, 3), optional 119 | Stresses at nodes. It is returned when `compute_strains` is True. 120 | 121 | """ 122 | # Retrieving data 123 | nodes = data["nodes"] 124 | cons = data["cons"] 125 | elements = data["elements"] 126 | mats = data["mats"] 127 | loads = data["loads"] 128 | 129 | 130 | # Pre-processing 131 | assem_op, bc_array, neq = ass.DME(cons, elements) 132 | print("Number of nodes: {}".format(nodes.shape[0])) 133 | print("Number of elements: {}".format(elements.shape[0])) 134 | print("Number of equations: {}".format(neq)) 135 | 136 | # System assembly 137 | stiff_mat, _ = ass.assembler(elements, mats, nodes, neq, assem_op) 138 | rhs_vec = ass.loadasem(loads, bc_array, neq) 139 | 140 | # System solution 141 | start_time = datetime.now() 142 | disp = sol.static_sol(stiff_mat, rhs_vec) 143 | if not np.allclose(stiff_mat.dot(disp)/stiff_mat.max(), 144 | rhs_vec/stiff_mat.max()): 145 | print("The system is not in equilibrium!") 146 | end_time = datetime.now() 147 | print('Duration for system solution: {}'.format(end_time - start_time)) 148 | 149 | # Post-processing 150 | start_time = datetime.now() 151 | disp_complete = pos.complete_disp(bc_array, nodes, disp) 152 | strain_nodes, stress_nodes = None, None 153 | if compute_strains: 154 | strain_nodes, stress_nodes = pos.strain_nodes(nodes, elements, mats, 155 | disp_complete) 156 | if plot_contours: 157 | pos.fields_plot(elements, nodes, disp_complete, E_nodes=strain_nodes, 158 | S_nodes=stress_nodes) 159 | end_time = datetime.now() 160 | print('Duration for post processing: {}'.format(end_time - start_time)) 161 | print('Analysis terminated successfully!') 162 | if compute_strains: 163 | return (disp_complete, strain_nodes, stress_nodes) 164 | else: 165 | return disp_complete 166 | 167 | 168 | if __name__ == '__main__': 169 | displacement = solids_GUI() 170 | plt.show() 171 | -------------------------------------------------------------------------------- /solidspy/solutil.py: -------------------------------------------------------------------------------- 1 | """ 2 | Solver routines 3 | --------------- 4 | 5 | Utilities for solution of FEM systems 6 | 7 | """ 8 | from numpy import ndarray 9 | from numpy.linalg import solve 10 | from scipy.sparse import csr_matrix 11 | from scipy.sparse.linalg import spsolve 12 | 13 | 14 | def static_sol(mat, rhs): 15 | """Solve a static problem [mat]{u_sol} = {rhs} 16 | 17 | Parameters 18 | ---------- 19 | mat : array 20 | Array with the system of equations. It can be stored in 21 | dense or sparse scheme. 22 | rhs : array 23 | Array with right-hand-side of the system of equations. 24 | 25 | Returns 26 | ------- 27 | u_sol : array 28 | Solution of the system of equations. 29 | 30 | Raises 31 | ------ 32 | 33 | """ 34 | if type(mat) is csr_matrix: 35 | u_sol = spsolve(mat, rhs) 36 | elif type(mat) is ndarray: 37 | u_sol = solve(mat, rhs) 38 | else: 39 | raise TypeError("Matrix should be numpy array or csr_matrix.") 40 | 41 | return u_sol 42 | -------------------------------------------------------------------------------- /solidspy/uelutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Element subroutines 4 | ------------------- 5 | Each UEL subroutine computes the local stiffness matrix for a given 6 | finite element. 7 | 8 | New elements can be added by including additional subroutines. 9 | 10 | """ 11 | import numpy as np 12 | import solidspy.femutil as fem 13 | import solidspy.gaussutil as gau 14 | 15 | 16 | #%% Continuum elements 17 | 18 | # Triangles 19 | def elast_tri3(coord, params): 20 | """Triangular element with 3 nodes 21 | 22 | Parameters 23 | ---------- 24 | coord : ndarray 25 | Coordinates for the nodes of the element (3, 2). 26 | params : tuple 27 | Material parameters in the following order: 28 | 29 | young : float 30 | Young modulus (>0). 31 | poisson : float 32 | Poisson coefficient (-1, 0.5). 33 | dens : float, optional 34 | Density (>0). 35 | 36 | Returns 37 | ------- 38 | stiff_mat : ndarray 39 | Local stiffness matrix for the element (6, 6). 40 | mass_mat : ndarray 41 | Local mass matrix for the element (6, 6). 42 | 43 | Examples 44 | -------- 45 | 46 | >>> coord = np.array([ 47 | ... [0, 0], 48 | ... [1, 0], 49 | ... [0, 1]]) 50 | >>> params = [8/3, 1/3] 51 | >>> stiff, mass = uel3ntrian(coord, params) 52 | >>> stiff_ex = 1/2 * np.array([ 53 | ... [4, 2, -3, -1, -1, -1], 54 | ... [2, 4, -1, -1, -1, -3], 55 | ... [-3, -1, 3, 0, 0, 1], 56 | ... [-1, -1, 0, 1, 1, 0], 57 | ... [-1, -1, 0, 1, 1, 0], 58 | ... [-1, -3, 1, 0, 0, 3]]) 59 | >>> np.allclose(stiff, stiff_ex) 60 | True 61 | 62 | """ 63 | stiff_mat = np.zeros([6, 6]) 64 | mass_mat = np.zeros([6, 6]) 65 | C = fem.umat(params[:2]) 66 | if len(params) == 2: 67 | dens = 1 68 | else: 69 | dens = params[-1] 70 | gpts, gwts = gau.gauss_tri(order=2) 71 | for cont in range(gpts.shape[0]): 72 | r, s = gpts[cont, :] 73 | H, B, det = fem.elast_diff_2d(r, s, coord, fem.shape_tri3) 74 | factor = det * gwts[cont] 75 | stiff_mat += 0.5 * factor * (B.T @ C @ B) 76 | mass_mat += 0.5 * dens * factor * (H.T @ H) 77 | return stiff_mat, mass_mat 78 | 79 | 80 | def elast_tri6(coord, params): 81 | """Triangular element with 6 nodes 82 | 83 | Parameters 84 | ---------- 85 | coord : ndarray 86 | Coordinates for the nodes of the element (6, 2). 87 | params : tuple 88 | Material parameters in the following order: 89 | 90 | young : float 91 | Young modulus (>0). 92 | poisson : float 93 | Poisson coefficient (-1, 0.5). 94 | dens : float, optional 95 | Density (>0). 96 | 97 | Returns 98 | ------- 99 | stiff_mat : ndarray 100 | Local stiffness matrix for the element (12, 12). 101 | mass_mat : ndarray 102 | Local mass matrix for the element (12, 12). 103 | 104 | Examples 105 | -------- 106 | 107 | >>> coord = np.array([ 108 | ... [0, 0], 109 | ... [1, 0], 110 | ... [0, 1], 111 | ... [0.5, 0], 112 | ... [0.5, 0.5], 113 | ... [0, 0.5]]) 114 | >>> params = [8/3, 1/3] 115 | >>> stiff, mass = uel6ntrian(coord, params) 116 | >>> stiff_ex = 1/6 * np.array([ 117 | ... [12, 6, 3, 1, 1, 1, -12, -4, 0, 0, -4, -4], 118 | ... [6, 12, 1, 1, 1, 3, -4, -4, 0, 0, -4, -12], 119 | ... [3, 1, 9, 0, 0, -1, -12, -4, 0, 4, 0, 0], 120 | ... [1, 1, 0, 3, -1, 0, -4, -4, 4, 0, 0, 0], 121 | ... [1, 1, 0, -1, 3, 0, 0, 0, 0, 4, -4, -4], 122 | ... [1, 3, -1, 0, 0, 9, 0, 0, 4, 0, -4, -12], 123 | ... [-12, -4, -12, -4, 0, 0, 32, 8, -8, -8, 0, 8], 124 | ... [-4, -4, -4, -4, 0, 0, 8, 32, -8, -24, 8, 0], 125 | ... [0, 0, 0, 4, 0, 4, -8, -8, 32, 8, -24, -8], 126 | ... [0, 0, 4, 0, 4, 0, -8, -24, 8, 32, -8, -8], 127 | ... [-4, -4, 0, 0, -4, -4, 0, 8, -24, -8, 32, 8], 128 | ... [-4, -12, 0, 0, -4, -12, 8, 0, -8, -8, 8, 32]]) 129 | >>> np.allclose(stiff, stiff_ex) 130 | True 131 | 132 | """ 133 | stiff_mat = np.zeros([12, 12]) 134 | mass_mat = np.zeros([12, 12]) 135 | C = fem.umat(params[:2]) 136 | if len(params) == 2: 137 | dens = 1 138 | else: 139 | dens = params[-1] 140 | gpts, gwts = gau.gauss_tri(order=3) 141 | for cont in range(gpts.shape[0]): 142 | r, s = gpts[cont, :] 143 | H, B, det = fem.elast_diff_2d(r, s, coord, fem.shape_tri6) 144 | factor = gwts[cont] * det 145 | stiff_mat += 0.5 * factor * (B.T @ C @ B) 146 | mass_mat += 0.5 * dens * factor * (H.T @ H) 147 | return stiff_mat, mass_mat 148 | 149 | 150 | # Quadrilaterals 151 | def elast_quad4(coord, params): 152 | """Quadrilateral element with 4 nodes 153 | 154 | Parameters 155 | ---------- 156 | coord : ndarray 157 | Coordinates for the nodes of the element (4, 2). 158 | params : tuple 159 | Material parameters in the following order: 160 | 161 | young : float 162 | Young modulus (>0). 163 | poisson : float 164 | Poisson coefficient (-1, 0.5). 165 | dens : float, optional 166 | Density (>0). 167 | 168 | Returns 169 | ------- 170 | stiff_mat : ndarray 171 | Local stiffness matrix for the element (8, 8). 172 | mass_mat : ndarray 173 | Local mass matrix for the element (8, 8). 174 | 175 | Examples 176 | -------- 177 | 178 | >>> coord = np.array([[-1, -1], [1, -1], [1, 1], [-1, 1]]) 179 | >>> params = [8/3, 1/3, 1] 180 | >>> stiff, mass = uel4nquad(coord, params) 181 | >>> stiff_ex = 1/6 * np.array([ 182 | ... [ 8, 3, -5, 0, -4, -3, 1, 0], 183 | ... [ 3, 8, 0, 1, -3, -4, 0, -5], 184 | ... [-5, 0, 8, -3, 1, 0, -4, 3], 185 | ... [ 0, 1, -3, 8, 0, -5, 3, -4], 186 | ... [-4, -3, 1, 0, 8, 3, -5, 0], 187 | ... [-3, -4, 0, -5, 3, 8, 0, 1], 188 | ... [ 1, 0, -4, 3, -5, 0, 8, -3], 189 | ... [ 0, -5, 3, -4, 0, 1, -3, 8]]) 190 | >>> mass_ex = 1/9 * np.array([ 191 | ... [4, 0, 2, 0, 1, 0, 2, 0], 192 | ... [0, 4, 0, 2, 0, 1, 0, 2], 193 | ... [2, 0, 4, 0, 2, 0, 1, 0], 194 | ... [0, 2, 0, 4, 0, 2, 0, 1], 195 | ... [1, 0, 2, 0, 4, 0, 2, 0], 196 | ... [0, 1, 0, 2, 0, 4, 0, 2], 197 | ... [2, 0, 1, 0, 2, 0, 4, 0], 198 | ... [0, 2, 0, 1, 0, 2, 0, 4]]) 199 | >>> np.allclose(stiff, stiff_ex) 200 | True 201 | >>> np.allclose(mass, mass_ex) 202 | True 203 | 204 | """ 205 | stiff_mat = np.zeros([8, 8]) 206 | mass_mat = np.zeros([8, 8]) 207 | C = fem.umat(params[:2]) 208 | if len(params) == 2: 209 | dens = 1 210 | else: 211 | dens = params[-1] 212 | gpts, gwts = gau.gauss_nd(2) 213 | for cont in range(gpts.shape[0]): # pylint: disable=E1136 # pylint/issues/3139 214 | r, s = gpts[cont, :] 215 | H, B, det = fem.elast_diff_2d(r, s, coord, fem.shape_quad4) 216 | factor = det * gwts[cont] 217 | stiff_mat += factor * (B.T @ C @ B) 218 | mass_mat += dens*factor* (H.T @ H) 219 | return stiff_mat, mass_mat 220 | 221 | 222 | def elast_quad9(coord, params): 223 | """ 224 | Quadrilateral element with 9 nodes for classic elasticity 225 | under plane-strain 226 | 227 | Parameters 228 | ---------- 229 | coord : coord 230 | Coordinates of the element. 231 | params : list 232 | List with material parameters in the following order: 233 | [Young modulus, Poisson coefficient, density]. 234 | 235 | Returns 236 | ------- 237 | stiff_mat : ndarray (float) 238 | Local stifness matrix. 239 | mass_mat : ndarray (float) 240 | Local mass matrix. 241 | """ 242 | stiff_mat = np.zeros((18, 18)) 243 | mass_mat = np.zeros((18, 18)) 244 | C = fem.umat(params[:2]) 245 | if len(params) == 2: 246 | dens = 1 247 | else: 248 | dens = params[-1] 249 | gpts, gwts = gau.gauss_nd(3, ndim=2) 250 | for cont in range(gpts.shape[0]): # pylint: disable=E1136 # pylint/issues/3139 251 | r, s = gpts[cont, :] 252 | H, B, det = fem.elast_diff_2d(r, s, coord, fem.shape_quad9) 253 | factor = det * gwts[cont] 254 | stiff_mat += factor * (B.T @ C @ B) 255 | mass_mat += dens * factor * (H.T @ H) 256 | return stiff_mat, mass_mat 257 | 258 | 259 | def elast_quad8(coord, params): 260 | """ 261 | Quadrilateral element with 9 nodes for classic elasticity 262 | under plane-strain 263 | 264 | Parameters 265 | ---------- 266 | coord : coord 267 | Coordinates of the element. 268 | params : list 269 | List with material parameters in the following order: 270 | [Young modulus, Poisson coefficient, density]. 271 | 272 | Returns 273 | ------- 274 | stiff_mat : ndarray (float) 275 | Local stifness matrix. 276 | mass_mat : ndarray (float) 277 | Local mass matrix. 278 | """ 279 | E, nu, rho = params 280 | C = fem.umat((E, nu)) 281 | stiff_mat = np.zeros((16, 16)) 282 | mass_mat = np.zeros((16, 16)) 283 | gpts, gwts = gau.gauss_nd(3, ndim=2) 284 | for cont in range(gpts.shape[0]): 285 | r = gpts[cont, 0] 286 | s = gpts[cont, 1] 287 | H, B, det = fem.elast_diff_2d(r, s, coord, fem.shape_quad8) 288 | factor = det * gwts[cont] 289 | stiff_mat += factor * (B.T @ C @ B) 290 | mass_mat += rho*factor * (H.T @ H) 291 | return stiff_mat, mass_mat 292 | 293 | 294 | ## Axisymmetric 295 | def elast_axi_quad9(coord, params): 296 | """ 297 | Quadrilateral element with 9 nodes for classic elasticity 298 | under plane-strain 299 | 300 | Parameters 301 | ---------- 302 | coord : coord 303 | Coordinates of the element. 304 | params : list 305 | List with material parameters in the following order: 306 | [Young modulus, Poisson coefficient, density]. 307 | 308 | Returns 309 | ------- 310 | stiff_mat : ndarray (float) 311 | Local stifness matrix. 312 | mass_mat : ndarray (float) 313 | Local mass matrix. 314 | """ 315 | 316 | E, nu, rho = params 317 | C = fem.elast_mat_axi((E, nu)) 318 | stiff_mat = np.zeros((18, 18)) 319 | mass_mat = np.zeros((18, 18)) 320 | gpts, gwts = gau.gauss_nd(3, ndim=2) 321 | for cont in range(gpts.shape[0]): 322 | r = gpts[cont, 0] 323 | s = gpts[cont, 1] 324 | H, B, det = fem.elast_diff_axi(r, s, coord, fem.shape_quad9) 325 | factor = det * gwts[cont] 326 | stiff_mat += factor * (B.T @ C @ B) 327 | mass_mat += rho*factor * (H.T @ H) 328 | return stiff_mat, mass_mat 329 | 330 | ## 3D elements 331 | def elast_tet4(coord, params): 332 | """Tetraedral element with 4 nodes for classic elasticity 333 | 334 | Parameters 335 | ---------- 336 | coord : coord 337 | Coordinates of the element. 338 | params : list 339 | List with material parameters in the following order: 340 | [Young modulus, Poisson coefficient, density]. 341 | 342 | Returns 343 | ------- 344 | stiff_mat : ndarray (float) 345 | Local stifness matrix. 346 | mass_mat : ndarray (float) 347 | Local mass matrix. 348 | """ 349 | E, nu, rho = params 350 | C = fem.elast_mat((E, nu)) 351 | stiff_mat = np.zeros((12, 12)) 352 | mass_mat = np.zeros((12, 12)) 353 | gpts, gwts = gau.gauss_tet(3) 354 | for cont in range(gpts.shape[0]): 355 | r = gpts[cont, 0] 356 | s = gpts[cont, 1] 357 | t = gpts[cont, 2] 358 | H, B, det = fem.elast_diff_3d(r, s, t, coord, fem.shape_tet4) 359 | factor = det * gwts[cont] / 6 360 | stiff_mat += factor * (B.T @ C @ B) 361 | mass_mat += rho*factor * (H.T @ H) 362 | return stiff_mat, mass_mat 363 | 364 | 365 | def elast_hex8(coord, params): 366 | """Hexaedral element with 8 nodes for classic elasticity 367 | 368 | Parameters 369 | ---------- 370 | coord : coord 371 | Coordinates of the element. 372 | params : list 373 | List with material parameters in the following order: 374 | [Young modulus, Poisson coefficient, density]. 375 | 376 | Returns 377 | ------- 378 | stiff_mat : ndarray (float) 379 | Local stifness matrix. 380 | mass_mat : ndarray (float) 381 | Local mass matrix. 382 | """ 383 | E, nu, rho = params 384 | C = fem.elast_mat((E, nu)) 385 | stiff_mat = np.zeros((24, 24)) 386 | mass_mat = np.zeros((24, 24)) 387 | gpts, gwts = gau.gauss_nd(2, ndim=3) 388 | for cont in range(gpts.shape[0]): 389 | r = gpts[cont, 0] 390 | s = gpts[cont, 1] 391 | t = gpts[cont, 2] 392 | H, B, det = fem.elast_diff_3d(r, s, t, coord) 393 | factor = det * gwts[cont] 394 | stiff_mat += factor * (B.T @ C @ B) 395 | mass_mat += rho*factor * (H.T @ H) 396 | return stiff_mat, mass_mat 397 | 398 | 399 | #%% Structural elements 400 | def spring(coord, stiff): 401 | """1D 2-noded Spring element 402 | 403 | Parameters 404 | ---------- 405 | coord : ndarray 406 | Coordinates for the nodes of the element (2, 2). 407 | stiff : float 408 | Spring stiffness (>0). 409 | 410 | Returns 411 | ------- 412 | stiff_mat : ndarray 413 | Local stiffness matrix for the element (4, 4). 414 | mass_mat : ndarray 415 | Local mass matrix for the element (4, 4). For now it 416 | returns a zero matrix and it is used for compatibility 417 | reasons. 418 | 419 | Examples 420 | -------- 421 | 422 | >>> coord = np.array([ 423 | ... [0, 0], 424 | ... [1, 0]]) 425 | >>> stiff, mass = uelspring(coord, 8/3) 426 | >>> stiff_ex = 8/3 * np.array([ 427 | ... [1, 0, -1, 0], 428 | ... [0, 0, 0, 0], 429 | ... [-1, 0, 1, 0], 430 | ... [0, 0, 0, 0]]) 431 | >>> np.allclose(stiff, stiff_ex) 432 | True 433 | 434 | """ 435 | vec = coord[1, :] - coord[0, :] 436 | nx = vec[0]/np.linalg.norm(vec) 437 | ny = vec[1]/np.linalg.norm(vec) 438 | Q = np.array([ 439 | [nx, ny, 0, 0], 440 | [0, 0, nx, ny]]) 441 | stiff_mat = stiff * np.array([ 442 | [1, -1], 443 | [-1, 1]]) 444 | stiff_mat = Q.T @ stiff_mat @ Q 445 | return stiff_mat, np.zeros(4) 446 | 447 | 448 | def truss2D(coord, params): 449 | """2D 2-noded truss element 450 | 451 | Parameters 452 | ---------- 453 | coord : ndarray 454 | Coordinates for the nodes of the element (2, 2). 455 | params : tuple 456 | Element parameters in the following order: 457 | 458 | young : float 459 | Young modulus (>0). 460 | area : float 461 | Cross-sectional area (>0). 462 | dens : float, optional 463 | Density (>0). 464 | 465 | Returns 466 | ------- 467 | stiff_mat : ndarray 468 | Local stiffness matrix for the element (4, 4). 469 | mass_mat : ndarray 470 | Local mass matrix for the element (4, 4). 471 | 472 | Examples 473 | -------- 474 | 475 | >>> coord = np.array([ 476 | ... [0, 0], 477 | ... [1, 0]]) 478 | >>> params = [1.0 , 1.0] 479 | >>> stiff, mass = ueltruss2D(coord, params) 480 | >>> stiff_ex = np.array([ 481 | ... [1, 0, -1, 0], 482 | ... [0, 0, 0, 0], 483 | ... [-1, 0, 1, 0], 484 | ... [0, 0, 0, 0]]) 485 | >>> np.allclose(stiff, stiff_ex) 486 | True 487 | 488 | """ 489 | vec = coord[1, :] - coord[0, :] 490 | length = np.linalg.norm(vec) 491 | nx = vec[0]/length 492 | ny = vec[1]/length 493 | Q = np.array([ 494 | [nx, ny, 0, 0], 495 | [0, 0, nx, ny]]) 496 | young, area = params[:2] 497 | stiff = area*young/length 498 | stiff_mat = stiff * np.array([ 499 | [1, -1], 500 | [-1, 1]]) 501 | stiff_mat = Q.T @ stiff_mat @ Q 502 | if len(params) == 2: 503 | dens = 1.0 504 | else: 505 | dens = params[-1] 506 | mass = area * length * dens 507 | mass_mat = mass/6*np.array([ 508 | [2, 0, 1, 0], 509 | [0, 2, 0, 1], 510 | [1, 0, 2, 0], 511 | [0, 1, 0, 2]]) 512 | return stiff_mat, mass_mat 513 | 514 | 515 | def beam2DU(coord, params): 516 | """2D 2-noded beam element without axial deformation 517 | 518 | Parameters 519 | ---------- 520 | coord : ndarray 521 | Coordinates for the nodes of the element (2, 2). 522 | params : tuple 523 | Element parameters in the following order: 524 | 525 | young : float 526 | Young modulus (>0). 527 | area_moment : float 528 | Second moment of area (>0). 529 | dens : float 530 | Density (>0). 531 | area : float, optional 532 | Cross-sectional area (>0). 533 | 534 | Returns 535 | ------- 536 | stiff_mat : ndarray 537 | Local stiffness matrix for the element (6, 6). 538 | mass_mat : ndarray 539 | Local mass matrix for the element (6, 6). 540 | """ 541 | vec = coord[1, :] - coord[0, :] 542 | nx = vec[0]/np.linalg.norm(vec) 543 | ny = vec[1]/np.linalg.norm(vec) 544 | L = np.linalg.norm(vec) 545 | Q = np.array([ 546 | [ny, nx, 0, 0, 0, 0], 547 | [0, 0, 1, 0, 0, 0], 548 | [0, 0, 0, ny, nx, 0], 549 | [0, 0, 0, 0, 0, 1]]) 550 | young, area_moment = params[:2] 551 | bending_stiff = area_moment*young/L**3 552 | stiff_mat = bending_stiff * np.array([ 553 | [12, 6*L, -12, 6*L], 554 | [6*L, 4*L**2, -6*L, 2*L**2], 555 | [-12, -6*L, 12, -6*L], 556 | [6*L, 2*L**2, -6*L, 4*L**2]]) 557 | if len(params) == 2: 558 | dens = 1.0 559 | area = 1.0 560 | else: 561 | dens, area = params[2:] 562 | mass = area * L * dens 563 | mass_mat = mass/420*np.array([ 564 | [156, 22*L, 54, -13*L], 565 | [22*L, 4*L**2, 13*L, -3*L**2], 566 | [54, 13*L, 156, -22*L], 567 | [-13*L, -3*L**2, -22*L, 4*L**2]]) 568 | stiff_mat = Q.T @ stiff_mat @ Q 569 | mass_mat = Q.T @ mass_mat @ Q 570 | return stiff_mat, mass_mat 571 | 572 | 573 | def beam2D(coord, params): 574 | """2D 2-noded beam element with axial deformation 575 | 576 | Parameters 577 | ---------- 578 | coord : ndarray 579 | Coordinates for the nodes of the element (2, 2). 580 | params : tuple 581 | Element parameters in the following order: 582 | 583 | young : float 584 | Young modulus (>0). 585 | area_moment : float 586 | Second moment of area (>0). 587 | area : float, optional 588 | Cross-sectional area (>0). 589 | dens : float 590 | Density (>0). 591 | 592 | Returns 593 | ------- 594 | stiff_mat : ndarray 595 | Local stiffness matrix for the element (6, 6). 596 | mass_mat : ndarray 597 | Local mass matrix for the element (6, 6). 598 | """ 599 | vec = coord[1, :] - coord[0, :] 600 | nx = vec[0]/np.linalg.norm(vec) 601 | ny = vec[1]/np.linalg.norm(vec) 602 | L = np.linalg.norm(vec) 603 | Q = np.array([ 604 | [nx, -ny, 0, 0, 0, 0], 605 | [ny, nx, 0, 0, 0, 0], 606 | [0, 0, 1, 0, 0, 0], 607 | [0, 0, 0, nx, -ny, 0], 608 | [0, 0, 0, ny, nx, 0], 609 | [0, 0, 0, 0, 0, 1]]) 610 | young, area_moment, area = params[:3] 611 | bending_stiff = area_moment * young 612 | ratio = area/area_moment 613 | stiff_mat = bending_stiff/L**3 * np.array([ 614 | [ratio*L**2, 0, 0, -ratio*L**2, 0, 0], 615 | [0, 12, 6*L, 0, -12, 6*L], 616 | [0, 6*L, 4*L**2, 0, -6*L, 2*L**2], 617 | [-ratio*L**2, 0, 0, ratio*L**2, 0, 0], 618 | [0, -12, -6*L, 0, 12, -6*L], 619 | [0, 6*L, 2*L**2, 0, -6*L, 4*L**2]]) 620 | 621 | if len(params) == 3: 622 | dens = 1.0 623 | else: 624 | dens = params[3:] 625 | mass = area * L * dens 626 | mass_mat = mass/420*np.array([ 627 | [140, 0, 0, 70, 0, 0], 628 | [0, 156, 22*L, 0, 54, -13*L], 629 | [0, 22*L, 4*L**2, 0, 13*L, -3*L**2], 630 | [70, 0, 0, 140, 0, 0], 631 | [0, 54, 13*L, 0, 156, -22*L], 632 | [0, -13*L, -3*L**2, 0, -22*L, 4*L**2]]) 633 | stiff_mat = Q.T @ stiff_mat @ Q 634 | mass_mat = Q.T @ mass_mat @ Q 635 | return stiff_mat, mass_mat 636 | 637 | 638 | if __name__ == "__main__": 639 | import doctest 640 | doctest.testmod() 641 | -------------------------------------------------------------------------------- /tests/test_assemutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``assemutil`` module 4 | 5 | """ 6 | import numpy as np 7 | import solidspy.assemutil as ass 8 | 9 | 10 | def test_sparse_assem(): 11 | """Tests for sparse assembler""" 12 | # 2 x 2 mesh 13 | mats = np.array([[16, 1/3]]) 14 | nodes = np.array([ 15 | [0, -1, -1], 16 | [1, 0, -1], 17 | [2, 1, -1], 18 | [3, -1, 0], 19 | [4, 0, 0], 20 | [5, 1, 0], 21 | [6, -1, 1], 22 | [7, 0, 1], 23 | [8, 1, 1]]) 24 | elements = np.array([ 25 | [0, 1, 0, 0, 1, 4, 3], 26 | [1, 1, 0, 1, 2, 5, 4], 27 | [2, 1, 0, 3, 4, 7, 6], 28 | [3, 1, 0, 4, 5, 8, 7]]) 29 | assem_op = np.array([ 30 | [0, 1, 2, 3, 8, 9, 6, 7], 31 | [2, 3, 4, 5, 10, 11, 8, 9], 32 | [6, 7, 8, 9, 14, 15, 12, 13], 33 | [8, 9, 10, 11, 16, 17, 14, 15]], dtype=int) 34 | 35 | neq = 18 36 | K_ass, _ = ass.sparse_assem(elements, mats, nodes, neq, assem_op) 37 | K_exact = np.array([ 38 | [8, 3, -5, 0, 0, 0, 1, 0, -4, -3, 0, 0, 0, 0, 0, 0, 0, 0], 39 | [3, 8, 0, 1, 0, 0, 0, -5, -3, -4, 0, 0, 0, 0, 0, 0, 0, 0], 40 | [-5, 0, 16, 0, -5, 0, -4, 3, 2, 0, -4, -3, 0, 0, 0, 0, 0, 0], 41 | [0, 1, 0, 16, 0, 1, 3, -4, 0, -10, -3, -4, 0, 0, 0, 0, 0, 0], 42 | [0, 0, -5, 0, 8, -3, 0, 0, -4, 3, 1, 0, 0, 0, 0, 0, 0, 0], 43 | [0, 0, 0, 1, -3, 8, 0, 0, 3, -4, 0, -5, 0, 0, 0, 0, 0, 0], 44 | [1, 0, -4, 3, 0, 0, 16, 0, -10, 0, 0, 0, 1, 0, -4, -3, 0, 0], 45 | [0, -5, 3, -4, 0, 0, 0, 16, 0, 2, 0, 0, 0, -5, -3, -4, 0, 0], 46 | [-4, -3, 2, 0, -4, 3, -10, 0, 32, 0, -10, 0, -4, 3, 2, 0, -4, -3], 47 | [-3, -4, 0, -10, 3, -4, 0, 2, 0, 32, 0, 2, 3, -4, 0, -10, -3, -4], 48 | [0, 0, -4, -3, 1, 0, 0, 0, -10, 0, 16, 0, 0, 0, -4, 3, 1, 0], 49 | [0, 0, -3, -4, 0, -5, 0, 0, 0, 2, 0, 16, 0, 0, 3, -4, 0, -5], 50 | [0, 0, 0, 0, 0, 0, 1, 0, -4, 3, 0, 0, 8, -3, -5, 0, 0, 0], 51 | [0, 0, 0, 0, 0, 0, 0, -5, 3, -4, 0, 0, -3, 8, 0, 1, 0, 0], 52 | [0, 0, 0, 0, 0, 0, -4, -3, 2, 0, -4, 3, -5, 0, 16, 0, -5, 0], 53 | [0, 0, 0, 0, 0, 0, -3, -4, 0, -10, 3, -4, 0, 1, 0, 16, 0, 1], 54 | [0, 0, 0, 0, 0, 0, 0, 0, -4, -3, 1, 0, 0, 0, -5, 0, 8, 3], 55 | [0, 0, 0, 0, 0, 0, 0, 0, -3, -4, 0, -5, 0, 0, 0, 1, 3, 8]]) 56 | assert np.allclose(K_ass.toarray(), K_exact) 57 | 58 | 59 | def test_dense_assem(): 60 | """Tests for dense assembler""" 61 | 62 | # 2 x 2 mesh 63 | mats = np.array([[16, 1/3]]) 64 | nodes = np.array([ 65 | [0, -1, -1], 66 | [1, 0, -1], 67 | [2, 1, -1], 68 | [3, -1, 0], 69 | [4, 0, 0], 70 | [5, 1, 0], 71 | [6, -1, 1], 72 | [7, 0, 1], 73 | [8, 1, 1]]) 74 | elements = np.array([ 75 | [0, 1, 0, 0, 1, 4, 3], 76 | [1, 1, 0, 1, 2, 5, 4], 77 | [2, 1, 0, 3, 4, 7, 6], 78 | [3, 1, 0, 4, 5, 8, 7]]) 79 | assem_op = np.array([ 80 | [0, 1, 2, 3, 8, 9, 6, 7], 81 | [2, 3, 4, 5, 10, 11, 8, 9], 82 | [6, 7, 8, 9, 14, 15, 12, 13], 83 | [8, 9, 10, 11, 16, 17, 14, 15]], dtype=int) 84 | 85 | neq = 18 86 | K_ass, _ = ass.dense_assem(elements, mats, nodes, neq, assem_op) 87 | K_exact = np.array([ 88 | [8, 3, -5, 0, 0, 0, 1, 0, -4, -3, 0, 0, 0, 0, 0, 0, 0, 0], 89 | [3, 8, 0, 1, 0, 0, 0, -5, -3, -4, 0, 0, 0, 0, 0, 0, 0, 0], 90 | [-5, 0, 16, 0, -5, 0, -4, 3, 2, 0, -4, -3, 0, 0, 0, 0, 0, 0], 91 | [0, 1, 0, 16, 0, 1, 3, -4, 0, -10, -3, -4, 0, 0, 0, 0, 0, 0], 92 | [0, 0, -5, 0, 8, -3, 0, 0, -4, 3, 1, 0, 0, 0, 0, 0, 0, 0], 93 | [0, 0, 0, 1, -3, 8, 0, 0, 3, -4, 0, -5, 0, 0, 0, 0, 0, 0], 94 | [1, 0, -4, 3, 0, 0, 16, 0, -10, 0, 0, 0, 1, 0, -4, -3, 0, 0], 95 | [0, -5, 3, -4, 0, 0, 0, 16, 0, 2, 0, 0, 0, -5, -3, -4, 0, 0], 96 | [-4, -3, 2, 0, -4, 3, -10, 0, 32, 0, -10, 0, -4, 3, 2, 0, -4, -3], 97 | [-3, -4, 0, -10, 3, -4, 0, 2, 0, 32, 0, 2, 3, -4, 0, -10, -3, -4], 98 | [0, 0, -4, -3, 1, 0, 0, 0, -10, 0, 16, 0, 0, 0, -4, 3, 1, 0], 99 | [0, 0, -3, -4, 0, -5, 0, 0, 0, 2, 0, 16, 0, 0, 3, -4, 0, -5], 100 | [0, 0, 0, 0, 0, 0, 1, 0, -4, 3, 0, 0, 8, -3, -5, 0, 0, 0], 101 | [0, 0, 0, 0, 0, 0, 0, -5, 3, -4, 0, 0, -3, 8, 0, 1, 0, 0], 102 | [0, 0, 0, 0, 0, 0, -4, -3, 2, 0, -4, 3, -5, 0, 16, 0, -5, 0], 103 | [0, 0, 0, 0, 0, 0, -3, -4, 0, -10, 3, -4, 0, 1, 0, 16, 0, 1], 104 | [0, 0, 0, 0, 0, 0, 0, 0, -4, -3, 1, 0, 0, 0, -5, 0, 8, 3], 105 | [0, 0, 0, 0, 0, 0, 0, 0, -3, -4, 0, -5, 0, 0, 0, 1, 3, 8]]) 106 | assert np.allclose(K_ass, K_exact) 107 | 108 | 109 | # Test for uel with all ones 110 | def uel_ones(elcoord, params): 111 | """Dummy UEL with all ones""" 112 | return np.ones((8, 8)), np.ones((8, 8)) 113 | 114 | nodes = np.zeros((9, 3)) 115 | nodes[:, 0] = range(0, 9) 116 | nodes[:, 1:3] = np.array([ 117 | [0, 0], 118 | [1, 0], 119 | [2, 0], 120 | [0, 1], 121 | [1, 1], 122 | [2, 1], 123 | [0, 2], 124 | [1, 2], 125 | [2, 2]]) 126 | cons = np.zeros((9, 2)) 127 | elements = np.ones((4, 7), dtype=int) 128 | elements[:, 0] = range(0, 4) 129 | elements[:, 2] = 0 130 | elements[:, 3:] = np.array([ 131 | [0, 1, 4, 3], 132 | [1, 2, 5, 4], 133 | [3, 4, 7, 6], 134 | [4, 5, 8, 7]]) 135 | mats = np.array([[1, 0.3]]) 136 | assemp_op, bc_array, neq = ass.DME(cons, elements) 137 | stiff, _ = ass.assembler(elements, mats, nodes, neq, assem_op, 138 | sparse=False, uel=uel_ones) 139 | stiff_exact = np.array([ 140 | [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], 141 | [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], 142 | [1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0], 143 | [1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0], 144 | [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], 145 | [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], 146 | [1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 0, 0, 1, 1, 1, 1, 0, 0], 147 | [1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 0, 0, 1, 1, 1, 1, 0, 0], 148 | [1, 1, 2, 2, 1, 1, 2, 2, 4, 4, 2, 2, 1, 1, 2, 2, 1, 1], 149 | [1, 1, 2, 2, 1, 1, 2, 2, 4, 4, 2, 2, 1, 1, 2, 2, 1, 1], 150 | [0, 0, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 0, 0, 1, 1, 1, 1], 151 | [0, 0, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 0, 0, 1, 1, 1, 1], 152 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0], 153 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0], 154 | [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1], 155 | [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1], 156 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], 157 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1]]) 158 | assert np.allclose(stiff, stiff_exact) 159 | 160 | 161 | # Test assembly of a truss 162 | length = 10*np.cos(np.pi/6) 163 | nodes = np.array([ 164 | [0.0, length, 0.0], 165 | [1.0, 0.0, 5.0], 166 | [2.0, 0.0, 0.0]]) 167 | mats = np.array([[1e6, 0.01]]) 168 | elements = np.array([ 169 | [0, 6, 0, 2, 0], 170 | [1, 6, 0, 1, 0], 171 | [2, 6, 0, 1, 2]]) 172 | neq = 3 173 | assem_op = np.array([ 174 | [-1, -1, 0, 1], 175 | [ 0, 1, -1, 2], 176 | [-1, 2, -1, -1]]) 177 | stiff, _ = ass.assembler(elements, mats, nodes, neq, assem_op, 178 | sparse=False) 179 | stiff_exact = 250*np.array([ 180 | [8/np.sqrt(3) + 3, -np.sqrt(3), np.sqrt(3)], 181 | [-np.sqrt(3), 1, -1], 182 | [np.sqrt(3), -1, 9]]) 183 | assert np.allclose(stiff, stiff_exact) 184 | 185 | 186 | -------------------------------------------------------------------------------- /tests/test_bloch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test for functions in ``blochutil`` module 4 | """ 5 | import numpy as np 6 | from numpy import array, exp, pi 7 | from solidspy.blochutil import bloch_transform_mat 8 | 9 | 10 | def test_bloch_transform_mat(): 11 | 12 | # Square made with springs and mass in the center 13 | coords = np.array([ 14 | [0, 0], 15 | [2, 0], 16 | [2, 2], 17 | [0, 2], 18 | [1, 1]]) 19 | ndofs = 10 20 | nodes_ref = [0, 0, 0] 21 | nodes_ima = [1, 2, 3] 22 | dofs_ima = [2, 3, 4, 5, 6, 7] 23 | dofs_ref = [0, 1, 0, 1, 0, 1] 24 | nodes_ref_dof = {0: [0, 1]} 25 | nodes_ima_dof = {1: [2, 3], 2: [4, 5], 3: [6, 7]} 26 | new_num = [0, 1, 0, 1, 0, 1, 0, 1, 2, 3] 27 | nk = 51 28 | wavenumbers = np.random.uniform(-pi/2, pi/2, (nk, 2)) 29 | for wavenumber in wavenumbers: 30 | kx, ky = wavenumber 31 | T = bloch_transform_mat((kx, ky), coords, ndofs, nodes_ref, nodes_ima, 32 | dofs_ref, dofs_ima, nodes_ref_dof, nodes_ima_dof, 33 | new_num) 34 | TH = array([ 35 | [1, 0, exp(-2j*kx), 0, exp(-2j*(kx + ky)), 0, exp(-2j*ky), 0, 0, 0], 36 | [0, 1, 0, exp(-2j*kx), 0, exp(-2j*(kx + ky)), 0, exp(-2j*ky), 0, 0], 37 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], 38 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]]) 39 | assert np.allclose(T.toarray(), TH.T.conj()) 40 | -------------------------------------------------------------------------------- /tests/test_femutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``femutil`` module 4 | 5 | """ 6 | import numpy as np 7 | import pytest 8 | import solidspy.femutil as fem 9 | 10 | 11 | #%% Tests for Shape functions and derivatives 12 | result = np.eye(3) 13 | @pytest.mark.parametrize("r, s, res",[ 14 | [0.0, 0.0,result[0]], 15 | [1.0, 0.0,result[1]], 16 | [0.0, 1.0,result[2]] 17 | ]) 18 | def test_shape_tri3(r,s,res): 19 | # Interpolation condition check 20 | N, _ = fem.shape_tri3(r,s) 21 | assert np.allclose(N,res) 22 | 23 | result = np.eye(6) 24 | @pytest.mark.parametrize("r, s, res",[ 25 | [0.0, 0.0,result[0]], 26 | [1.0, 0.0,result[1]], 27 | [0.0, 1.0,result[2]], 28 | [0.5, 0.0,result[3]], 29 | [0.5, 0.5,result[4]], 30 | [0.0, 0.5,result[5]]]) 31 | def test_shape_tri6_N(r,s,res): 32 | # Interpolation condition check 33 | N, _ = fem.shape_tri6(r,s) 34 | assert np.allclose(N, res) 35 | 36 | def test_shape_tri6_dNdr(): 37 | # Evaluation at (1/3, 1/3) 38 | N, dNdr = fem.shape_tri6(1/3, 1/3) 39 | N_exp = np.array([-1., -1., -1., 4., 4., 4.])/9 40 | dNdr_exp = np.array([ 41 | [-1., 1., 0., 0., 4., -4.], 42 | [-1., 0., 1., -4., 4., 0.]])/3 43 | assert np.allclose(N, N_exp) 44 | assert np.allclose(dNdr, dNdr_exp) 45 | 46 | result = np.eye(4) 47 | @pytest.mark.parametrize("r, s, res",[ 48 | [-1.0, -1.0,result[0]], 49 | [1.0, -1.0,result[1]], 50 | [1.0, 1.0,result[2]], 51 | [-1.0, 1.0,result[3]]]) 52 | def test_shape_quad4_N(r,s,res): 53 | # Interpolation condition check 54 | N, _ = fem.shape_quad4(r,s) 55 | assert np.allclose(N, res) 56 | 57 | def test_shape_quad4_dNdr(): 58 | # For point (0, 0) 59 | N, _ = fem.shape_quad4(0, 0) 60 | N_ex = 0.25 * np.array([[1, 1, 1, 1]]) 61 | assert np.allclose(N, N_ex) 62 | 63 | 64 | result = np.eye(9) 65 | @pytest.mark.parametrize("r, s, res",[ 66 | [-1.0, -1.0,result[0]], 67 | [ 1.0, -1.0,result[1]], 68 | [ 1.0, 1.0,result[2]], 69 | [-1.0, 1.0,result[3]], 70 | [ 0.0, -1.0,result[4]], 71 | [ 1.0, 0.0,result[5]], 72 | [ 0.0, 1.0,result[6]], 73 | [-1.0, 0.0,result[7]], 74 | [ 0.0, 0.0,result[8]]]) 75 | def test_shape_quad9_N(r,s,res): 76 | # Interpolation condition check 77 | N, _ = fem.shape_quad9(r, s) 78 | assert np.allclose(N, res) 79 | 80 | def test_shape_quad9_dNdr(): 81 | # Evaluation at (1/4, 1/4) 82 | N, dNdr = fem.shape_quad9(0.25, 0.25) 83 | N_exp = np.array( 84 | [0.00878906, -0.01464844, 0.02441406, -0.01464844, 85 | -0.08789062, 0.14648438, 0.14648438, -0.08789062, 86 | 0.87890625]) 87 | 88 | dNdr_exp = np.array([ 89 | [0.0234375, -0.0703125, 0.1171875, -0.0390625, 0.046875, 90 | 0.703125, -0.078125, -0.234375, -0.46875], 91 | [0.0234375, -0.0390625, 0.1171875, -0.0703125, -0.234375, 92 | -0.078125, 0.703125, 0.046875, -0.46875]]) 93 | assert np.allclose(N, N_exp) 94 | assert np.allclose(dNdr, dNdr_exp) 95 | 96 | result = np.eye(8) 97 | @pytest.mark.parametrize("r, s, res",[ 98 | [-1.0, -1.0,result[0]], 99 | [ 1.0, -1.0,result[1]], 100 | [ 1.0, 1.0,result[2]], 101 | [-1.0, 1.0,result[3]], 102 | [ 0.0, -1.0,result[4]], 103 | [ 1.0, 0.0,result[5]], 104 | [ 0.0, 1.0,result[6]], 105 | [-1.0, 0.0,result[7]]]) 106 | def test_shape_quad8(r,s,res): 107 | # Interpolation condition check 108 | N, _ = fem.shape_quad8(r,s) 109 | assert np.allclose(N, res) 110 | 111 | 112 | # 3D elements 113 | result = np.eye(4) 114 | @pytest.mark.parametrize("r, s, t, res",[ 115 | [0.0, 0.0, 0.0,result[0]], 116 | [1.0, 0.0, 0.0,result[1]], 117 | [0.0, 1.0, 0.0,result[2]], 118 | [0.0, 0.0, 1.0,result[3]] 119 | ]) 120 | def test_shape_tet4(r,s,t,res): 121 | # Interpolation condition check 122 | N, _ = fem.shape_tet4(r, s, t) 123 | assert np.allclose(N, res) 124 | 125 | result = np.eye(8) 126 | @pytest.mark.parametrize("r, s, t, res",[ 127 | [-1.0, -1.0, -1.0,result[0]], 128 | [ 1.0, -1.0, -1.0,result[1]], 129 | [ 1.0, 1.0, -1.0,result[2]], 130 | [-1.0, 1.0, -1.0,result[3]], 131 | [-1.0, -1.0, 1.0,result[4]], 132 | [ 1.0, -1.0, 1.0,result[5]], 133 | [ 1.0, 1.0, 1.0,result[6]], 134 | [-1.0, 1.0, 1.0,result[7]]]) 135 | def test_shape_hex(r,s,t,res): 136 | # Interpolation condition check 137 | N, _ = fem.shape_hex8(r, s, t) 138 | assert np.allclose(N, res) 139 | 140 | 141 | #%% Jacobian 142 | def test_jacoper(): 143 | """Tests for jacobian of the elemental transformation""" 144 | 145 | # Perfect element at (0, 0) 146 | dhdx = 0.25*np.array([ 147 | [-1, 1, 1, -1], 148 | [-1, -1, 1, 1]]) 149 | coord = np.array([ 150 | [-1, -1], 151 | [1, -1], 152 | [1, 1], 153 | [-1, 1]]) 154 | det, jaco_inv = fem.jacoper(dhdx, coord) 155 | jaco_inv_ex = np.eye(2) 156 | assert np.isclose(det, 1) 157 | assert np.allclose(jaco_inv, jaco_inv_ex) 158 | 159 | # Shear element at (0, 0) 160 | dhdx = 0.25*np.array([ 161 | [-1, 1, 1, -1], 162 | [-1, -1, 1, 1]]) 163 | coord = np.array([ 164 | [-1.5, -1], 165 | [0.5, -1], 166 | [1.5, 1], 167 | [-0.5, 1]]) 168 | det, jaco_inv = fem.jacoper(dhdx, coord) 169 | jaco_inv_ex = np.eye(2) 170 | jaco_inv_ex[1, 0] = -0.5 171 | assert np.isclose(det, 1) 172 | assert np.allclose(jaco_inv, jaco_inv_ex) 173 | 174 | # Wrong triangles 175 | 176 | # Repeated node 177 | dhdx = np.array([ 178 | [-1, 1, 0], 179 | [-1, 0, 1]]) 180 | coord = np.array([ 181 | [0, 0], 182 | [0, 0], 183 | [0, 1]]) 184 | 185 | with pytest.raises(ValueError): 186 | det, jaco_inv = fem.jacoper(dhdx, coord) 187 | 188 | # Opposite orientation 189 | coord = np.array([ 190 | [0, 0], 191 | [0, 1], 192 | [1, 0]]) 193 | 194 | with pytest.raises(ValueError): 195 | det, jaco_inv = fem.jacoper(dhdx, coord) 196 | 197 | 198 | # Wrong quads 199 | 200 | # Opposite orientation 201 | dhdx = 0.25*np.array([ 202 | [-1, 1, 1, -1], 203 | [-1, -1, 1, 1]]) 204 | coord = np.array([ 205 | [-1, 1], 206 | [1, 1], 207 | [1, -1], 208 | [-1, -1]]) 209 | with pytest.raises(ValueError): 210 | det, jaco_inv = fem.jacoper(dhdx, coord) 211 | 212 | # Repeated nodes 213 | dhdx = 0.25*np.array([ 214 | [-1, 1, 1, -1], 215 | [-1, -1, 1, 1]]) 216 | coord = np.array([ 217 | [1, -1], 218 | [1, -1], 219 | [1, -1], 220 | [-1, 1]]) 221 | with pytest.raises(ValueError): 222 | det, jaco_inv = fem.jacoper(dhdx, coord) 223 | -------------------------------------------------------------------------------- /tests/test_gaussutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``gaussutil`` module 4 | 5 | """ 6 | import numpy as np 7 | import solidspy.gaussutil as gauss 8 | 9 | 10 | def test_gauss_nd(): 11 | """Test for ND Gauss integration""" 12 | 13 | npts = 4 14 | ndim = 2 15 | pts, wts = gauss.gauss_nd(npts, ndim=ndim) 16 | fun = lambda x, y: 3*x*y**2 - x**3 17 | inte = 0.0 18 | for cont, pt in enumerate(pts): 19 | x, y = pt 20 | inte += wts[cont] * fun(x, y) 21 | 22 | assert np.isclose(inte, 0) 23 | 24 | npts = 10 25 | ndim = 2 26 | pts, wts = gauss.gauss_nd(npts, ndim=ndim) 27 | fun = lambda x, y: np.exp(-x**2 - y**2) 28 | inte = 0.0 29 | for cont, pt in enumerate(pts): 30 | x, y = pt 31 | inte += wts[cont] * fun(x, y) 32 | 33 | assert np.isclose(inte, 2.23098514140413) 34 | 35 | 36 | def test_gauss_tri(): 37 | # Zero order 38 | pts, wts = gauss.gauss_tri(order=1) 39 | fun = lambda x, y: 1 40 | inte = 0.0 41 | for cont, pt in enumerate(pts): 42 | x, y = pt 43 | inte += 0.5 * wts[cont] * fun(x, y) 44 | assert np.isclose(inte, 0.5) 45 | 46 | # First order 47 | pts, wts = gauss.gauss_tri(order=1) 48 | a, b = np.random.uniform(0, 1, 2) 49 | fun = lambda x, y: a*x + b*y 50 | inte = 0.0 51 | for cont, pt in enumerate(pts): 52 | x, y = pt 53 | inte += 0.5 * wts[cont] * fun(x, y) 54 | assert np.isclose(inte, (a + b)/6) 55 | 56 | # Second order 57 | pts, wts = gauss.gauss_tri(order=2) 58 | a, b, c, d, e, f = np.random.uniform(0, 1, 6) 59 | fun = lambda x, y: a*x**2 + b*y**2 + c*x*y + d*x + e*y + f 60 | inte = 0.0 61 | for cont, pt in enumerate(pts): 62 | x, y = pt 63 | inte += 0.5 * wts[cont] * fun(x, y) 64 | assert np.isclose(inte, (2*a + 2*b + c + 4*d + 4*e + 12*f)/24) 65 | 66 | # Third order 67 | pts, wts = gauss.gauss_tri(order=3) 68 | a, b, c, d, e, f = np.random.uniform(0, 1, 6) 69 | fun = lambda x, y: a*x**2 + b*y**2 + c*x*y + d*x + e*y + f 70 | inte = 0.0 71 | for cont, pt in enumerate(pts): 72 | x, y = pt 73 | inte += 0.5 * wts[cont] * fun(x, y) 74 | assert np.isclose(inte, (2*a + 2*b + c + 4*d + 4*e + 12*f)/24) 75 | 76 | # Seventh order 77 | pts, wts = gauss.gauss_tri(order=7) 78 | a, b, c, d, e, f, g, h = np.random.uniform(0, 1, 8) 79 | fun = lambda x, y: a*x**7 + b*x**6*y + c*x**5*y**2 + d*x**4*y**3\ 80 | + e*x**3*y**4 + f*x**2*y**5 + g*x*y**6 + h*y**7 81 | inte = 0.0 82 | for cont, pt in enumerate(pts): 83 | x, y = pt 84 | inte += 0.5 * wts[cont] * fun(x, y) 85 | assert np.isclose(inte, (105*a + 15*b + 5*c + 3*d + 3*e + 5*f + 15*g 86 | + 105*h)/7560) 87 | 88 | 89 | def test_gauss_tet(): 90 | # Zero order 91 | pts, wts = gauss.gauss_tet(order=1) 92 | fun = lambda x, y, z: 1 93 | inte = 0.0 94 | for cont, pt in enumerate(pts): 95 | x, y, z = pt 96 | inte += wts[cont] * fun(x, y, z)/6 97 | assert np.isclose(inte, 1/6) 98 | 99 | # First order 100 | pts, wts = gauss.gauss_tet(order=1) 101 | a, b, c = np.random.uniform(0, 1, 3) 102 | fun = lambda x, y, z : a*x + b*y + c*z 103 | inte = 0.0 104 | for cont, pt in enumerate(pts): 105 | x, y, z = pt 106 | inte += wts[cont] * fun(x, y, z)/6 107 | assert np.isclose(inte, (a + b + c)/24) 108 | 109 | # Second order 110 | pts, wts = gauss.gauss_tet(order=2) 111 | a, b, c, d, e, f = np.random.uniform(0, 1, 6) 112 | fun = lambda x, y, z: a*x**2 + b*y**2 + c*z**2 + d*x*y + e*y*z + f*z*x 113 | inte = 0.0 114 | for cont, pt in enumerate(pts): 115 | x, y, z = pt 116 | inte += wts[cont] * fun(x, y, z) / 6 117 | assert np.isclose(inte, (2*a + 2*b + 2*c + d + e + f)/120) 118 | 119 | # Third order 120 | pts, wts = gauss.gauss_tet(order=3) 121 | a, b, c, d = np.random.uniform(0, 1, 4) 122 | fun = lambda x, y, z: a*x**3 + b*y**3 + c*z**3 + d*x*y*z 123 | inte = 0.0 124 | for cont, pt in enumerate(pts): 125 | x, y, z = pt 126 | inte += wts[cont] * fun(x, y, z) / 6 127 | assert np.isclose(inte, (6*a + 6*b + 6*c + d)/720) 128 | 129 | # Seventh order 130 | pts, wts = gauss.gauss_tet(order=7) 131 | a, b, c, d, e, f, g, h = np.random.uniform(0, 1, 8) 132 | fun = lambda x, y, z: a*x**7 + b*y**7 + c*z**7 + d*x**6 + e*y**6\ 133 | + f*z**6 + g*x*y*z + h 134 | inte = 0.0 135 | for cont, pt in enumerate(pts): 136 | x, y, z= pt 137 | inte += wts[cont] * fun(x, y, z) / 6 138 | assert np.isclose(inte, (7*a + 7*b + 7*c + 10*d + 10*e + 10*f + 7*g\ 139 | + 840*h)/5040) -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Integration tests for solidspy 4 | 5 | """ 6 | import numpy as np 7 | from scipy.sparse.linalg import eigsh 8 | import solidspy.postprocesor as pos 9 | import solidspy.assemutil as ass 10 | import solidspy.solutil as sol 11 | 12 | 13 | def test_4_elements(): 14 | """2×2 mesh with uniaxial load""" 15 | nodes = np.array([ 16 | [0, 0, 0], 17 | [1, 2, 0], 18 | [2, 2, 2], 19 | [3, 0, 2], 20 | [4, 1, 0], 21 | [5, 2, 1], 22 | [6, 1, 2], 23 | [7, 0, 1], 24 | [8, 1, 1]]) 25 | cons = np.array([ 26 | [0, -1], 27 | [0, -1], 28 | [0, 0], 29 | [0, 0], 30 | [-1, -1], 31 | [0, 0], 32 | [0, 0], 33 | [0, 0], 34 | [0, 0]]) 35 | eles = np.array([ 36 | [0, 1, 0, 0, 4, 8, 7], 37 | [1, 1, 0, 4, 1, 5, 8], 38 | [2, 1, 0, 7, 8, 6, 3], 39 | [3, 1, 0, 8, 5, 2, 6]]) 40 | loads = np.array([ 41 | [3, 0, 1], 42 | [6, 0, 2], 43 | [2, 0, 1]]) 44 | mater = np.array([[1.0, 0.3]]) 45 | assem_op, bc_array, neq = ass.DME(cons, eles) 46 | stiff, _ = ass.assembler(eles, mater, nodes, neq, assem_op) 47 | load_vec = ass.loadasem(loads, bc_array, neq) 48 | disp = sol.static_sol(stiff, load_vec) 49 | disp_complete = pos.complete_disp(bc_array, nodes, disp) 50 | disp_analytic = np.array([ 51 | [ 0.6, 0.0], 52 | [-0.6, 0.0], 53 | [-0.6, 4.0], 54 | [0.6, 4.0], 55 | [0.0, 0.0], 56 | [-0.6, 2.0], 57 | [0.0, 4.0], 58 | [0.6, 2.0], 59 | [0.0, 2.0]]) 60 | assert np.allclose(disp_complete, disp_analytic) 61 | 62 | 63 | def test_2_elements(): 64 | """2x1 mesh cantilever beam""" 65 | nodes = np.array([ 66 | [0, 0, 0], 67 | [1, 1, 0], 68 | [2, 2, 0], 69 | [3, 0, 1], 70 | [4, 1, 1], 71 | [5, 2, 1]]) 72 | cons = np.array([ 73 | [-1, -1], 74 | [0, 0], 75 | [0, 0], 76 | [-1, -1], 77 | [0, 0], 78 | [0, 0]]) 79 | eles = np.array([ 80 | [0, 1, 0, 0, 1, 4, 3], 81 | [1, 1, 0, 1, 2, 5, 4]]) 82 | loads = np.array([ 83 | [2, 0, -0.5], 84 | [5, 0, -0.5]]) 85 | mater = np.array([[1.0, 0.3]]) 86 | assem_op, bc_array, neq = ass.DME(cons, eles) 87 | stiff, _ = ass.assembler(eles, mater, nodes, neq, assem_op) 88 | load_vec = ass.loadasem(loads, bc_array, neq) 89 | disp = sol.static_sol(stiff, load_vec) 90 | disp_complete = pos.complete_disp(bc_array, nodes, disp) 91 | disp_analytic = 1/45 * np.array([ 92 | [0, 0], 93 | [-273, -390], 94 | [-364, -1144], 95 | [0, 0], 96 | [273, -390], 97 | [364, -1144]]) 98 | 99 | assert np.allclose(disp_complete, disp_analytic) 100 | 101 | 102 | def test_beams(): 103 | """Beams with axial force""" 104 | 105 | # Analytic problem 106 | nodes = np.array([ 107 | [0, 0.0, 0.0], 108 | [1, 0.0, 6.0], 109 | [2, 4.0, 6.0]]) 110 | cons = np.array([ 111 | [-1, -1, -1], 112 | [0, 0, 0], 113 | [-1, -1, -1]]) 114 | mats = np.array([[200e9, 1.33e-4, 0.04]]) 115 | elements = np.array([ 116 | [0, 8, 0, 0, 1], 117 | [1, 8, 0, 1, 2]]) 118 | loads = np.array([ 119 | [1, -12000, -24000, -6000]]) 120 | assem_op, bc_array, neq = ass.DME(cons, elements, ndof_node=3) 121 | stiff, _ = ass.assembler(elements, mats, nodes, neq, assem_op, 122 | sparse=False) 123 | load_vec = ass.loadasem(loads, bc_array, neq, ndof_node=3) 124 | solution = sol.static_sol(stiff, load_vec) 125 | solution_analytic = np.array([-6.29e-6, -1.695e-5, -0.13e-3]) 126 | assert np.allclose(solution, solution_analytic, rtol=1e-1) 127 | 128 | 129 | def test_eigs_truss(): 130 | """Eigenvalues of a bar""" 131 | nnodes = 513 132 | 133 | x = np.linspace(0, np.pi, nnodes) 134 | nodes = np.zeros((nnodes, 3)) 135 | nodes[:, 0] = range(nnodes) 136 | nodes[:, 1] = x 137 | cons = np.zeros((nnodes, 2)) 138 | cons[:, 1] = -1 139 | cons[0, 0] = -1 140 | cons[-1, 0] = -1 141 | mats = np.array([[1.0, 1.0, 1.0]]) 142 | elements = np.zeros((nnodes - 1, 5 ), dtype=int) 143 | elements[:, 0] = range(nnodes - 1) 144 | elements[:, 1] = 6 145 | elements[:, 3] = range(nnodes - 1) 146 | elements[:, 4] = range(1, nnodes) 147 | 148 | assem_op, bc_array, neq = ass.DME(cons, elements) 149 | stiff, mass = ass.assembler(elements, mats, nodes, neq, assem_op) 150 | 151 | vals, _ = eigsh(stiff, M=mass, which="SM") 152 | assert np.allclose(vals, np.linspace(1, 6, 6)**2, rtol=1e-2) 153 | 154 | 155 | def test_eigs_beam(): 156 | """Eigenvalues of a cantilever beam""" 157 | 158 | nnodes = 10 159 | 160 | x = np.linspace(0, np.pi, nnodes) 161 | nodes = np.zeros((nnodes, 3)) 162 | nodes[:, 0] = range(nnodes) 163 | nodes[:, 1] = x 164 | cons = np.zeros((nnodes, 3)) 165 | cons[0, :] = -1 166 | cons[:, 0] = -1 167 | mats = np.array([[1.0, 1.0, 1.0, 1.0]]) 168 | elements = np.zeros((nnodes - 1, 5 ), dtype=int) 169 | elements[:, 0] = range(nnodes - 1) 170 | elements[:, 1] = 7 171 | elements[:, 3] = range(nnodes - 1) 172 | elements[:, 4] = range(1, nnodes) 173 | 174 | assem_op, bc_array, neq = ass.DME(cons, elements, ndof_node=3) 175 | stiff, mass = ass.assembler(elements, mats, nodes, neq, assem_op) 176 | 177 | vals, _ = eigsh(stiff, M=mass, which="SM") 178 | vals_analytic = np.array([0.596864162694467, 1.49417561427335, 179 | 2.50024694616670, 3.49998931984744, 180 | 4.50000046151508, 5.49999998005609]) 181 | assert np.allclose(vals**0.25, vals_analytic, rtol=1e-2) 182 | 183 | -------------------------------------------------------------------------------- /tests/test_postprocesor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``postprocesor`` module 4 | 5 | """ 6 | import numpy as np 7 | import solidspy.postprocesor as pos 8 | 9 | 10 | def test_strain_nodes(): 11 | """Tests for strain/stress calculation at nodes""" 12 | 13 | # 2 x 2 mesh with axial load and rollers on the sides 14 | mats = np.array([[8/3, 1/3]]) 15 | nodes = np.array([ 16 | [0, -1, -1], 17 | [1, 0, -1], 18 | [2, 1, -1], 19 | [3, -1, 0], 20 | [4, 0, 0], 21 | [5, 1, 0], 22 | [6,-1, 1], 23 | [7, 0, 1], 24 | [8, 1, 1]]) 25 | elements =np.array([ 26 | [0, 1, 0, 0, 1, 4, 3], 27 | [1, 1, 0, 1, 2, 5, 4], 28 | [2, 1, 0, 3, 4, 7, 6], 29 | [3, 1, 0, 4, 5, 8, 7]]) 30 | UC = np.array([ 31 | [0, 0], 32 | [0, 0], 33 | [0, 0], 34 | [0, 1], 35 | [0, 1], 36 | [0, 1], 37 | [0, 2], 38 | [0, 2], 39 | [0, 2]]) 40 | E_nodes, S_nodes = pos.strain_nodes(nodes , elements, mats, UC) 41 | E_exact = np.zeros((9, 3)) 42 | E_exact[:, 1] = 1 43 | S_exact = np.zeros((9, 3)) 44 | S_exact[:, 0] = 1 45 | S_exact[:, 1] = 3 46 | assert np.allclose(E_exact, E_nodes) 47 | assert np.allclose(S_exact, S_nodes) 48 | -------------------------------------------------------------------------------- /tests/test_preprocesor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``preprocesor`` module 4 | 5 | """ 6 | import numpy as np 7 | import solidspy.preprocesor as pre 8 | 9 | 10 | def test_rect_grid(): 11 | """Tests for structured meshes generation""" 12 | 13 | # 2 x 2 mesh 14 | x, y, els = pre.rect_grid(2, 2, 2, 2) 15 | assert np.allclose(x, np.array([-1., 0., 1., 16 | -1., 0., 1., 17 | -1., 0., 1.])) 18 | assert np.allclose(y, np.array([-1., -1., -1., 19 | 0., 0., 0., 20 | 1., 1., 1.])) 21 | assert np.allclose(els, np.array([ 22 | [0, 1, 0, 0, 1, 4, 3], 23 | [1, 1, 0, 1, 2, 5, 4], 24 | [2, 1, 0, 3, 4, 7, 6], 25 | [3, 1, 0, 4, 5, 8, 7]])) 26 | -------------------------------------------------------------------------------- /tests/test_solutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``solutil`` module 4 | 5 | """ 6 | import numpy as np 7 | from scipy.sparse import csr_matrix 8 | from scipy.sparse import rand 9 | import solidspy.solutil as sol 10 | 11 | 12 | def test_static_solve(): 13 | """Tests for static solver""" 14 | 15 | # Identity matrix and rhs = {1, 2, 3} 16 | mat = np.eye(3) 17 | rhs = np.array([1, 2, 3]) 18 | u_sol = sol.static_sol(mat, rhs) 19 | 20 | row = np.array([0, 1, 2]) 21 | col = np.array([0, 1, 2]) 22 | data = np.array([1, 1, 1]) 23 | mat_sparse = csr_matrix((data, (row, col)), shape=(3, 3)) 24 | u_sol2 = sol.static_sol(mat_sparse, rhs) 25 | assert np.allclose(u_sol, u_sol2) 26 | assert np.allclose(u_sol, [1, 2, 3]) 27 | 28 | # Random matrices and right hand side 29 | np.random.seed(1) 30 | ntest = 10 31 | for cont in range(ntest): 32 | rhs = np.random.rand(100) 33 | mat = rand(100, 100, density=0.3) 34 | mat = 0.5*(mat + mat.transpose()) 35 | u_sol = sol.static_sol(mat.toarray(), rhs) 36 | u_sol2 = sol.static_sol(mat.tocsr(), rhs) 37 | print(mat.toarray()) 38 | assert np.allclose(u_sol, u_sol2) 39 | -------------------------------------------------------------------------------- /tests/test_uelutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test cases for functions on ``uelutil`` module 4 | 5 | """ 6 | import numpy as np 7 | import solidspy.uelutil as uel 8 | 9 | 10 | #%% Continuum elements 11 | def test_elast_tri3(): 12 | """Tests for 3-noded tri uel""" 13 | coord = np.array([ 14 | [0, 0], 15 | [1, 0], 16 | [0, 1]]) 17 | params = 8/3, 1/3, 1 18 | stiff, mass = uel.elast_tri3(coord, params) 19 | stiff_ex = 1/2 * np.array([ 20 | [4, 2, -3, -1, -1, -1], 21 | [2, 4, -1, -1, -1, -3], 22 | [-3, -1, 3, 0, 0, 1], 23 | [-1, -1, 0, 1, 1, 0], 24 | [-1, -1, 0, 1, 1, 0], 25 | [-1, -3, 1, 0, 0, 3]]) 26 | mass_ex = 1/24 * np.array([ 27 | [2, 0, 1, 0, 1, 0], 28 | [0, 2, 0, 1, 0, 1], 29 | [1, 0, 2, 0, 1, 0], 30 | [0, 1, 0, 2, 0, 1], 31 | [1, 0, 1, 0, 2, 0], 32 | [0, 1, 0, 1, 0, 2]]) 33 | assert np.allclose(stiff, stiff_ex) 34 | assert np.allclose(mass, mass_ex) 35 | 36 | 37 | def test_elast_tri6(): 38 | """Tests for 6-noded tri uel""" 39 | coord = np.array([ 40 | [0, 0], 41 | [1, 0], 42 | [0, 1], 43 | [0.5, 0], 44 | [0.5, 0.5], 45 | [0, 0.5]]) 46 | params = 8/3, 1/3, 1 47 | stiff, mass = uel.elast_tri6(coord, params) 48 | stiff_ex = 1/6 * np.array([ 49 | [12, 6, 3, 1, 1, 1, -12, -4, 0, 0, -4, -4], 50 | [6, 12, 1, 1, 1, 3, -4, -4, 0, 0, -4, -12], 51 | [3, 1, 9, 0, 0, -1, -12, -4, 0, 4, 0, 0], 52 | [1, 1, 0, 3, -1, 0, -4, -4, 4, 0, 0, 0], 53 | [1, 1, 0, -1, 3, 0, 0, 0, 0, 4, -4, -4], 54 | [1, 3, -1, 0, 0, 9, 0, 0, 4, 0, -4, -12], 55 | [-12, -4, -12, -4, 0, 0, 32, 8, -8, -8, 0, 8], 56 | [-4, -4, -4, -4, 0, 0, 8, 32, -8, -24, 8, 0], 57 | [0, 0, 0, 4, 0, 4, -8, -8, 32, 8, -24, -8], 58 | [0, 0, 4, 0, 4, 0, -8, -24, 8, 32, -8, -8], 59 | [-4, -4, 0, 0, -4, -4, 0, 8, -24, -8, 32, 8], 60 | [-4, -12, 0, 0, -4, -12, 8, 0, -8, -8, 8, 32]]) 61 | mass_ex = 1/360 * np.array([ 62 | [6, 0, -1, 0, -1, 0, 0, 0, -4, 0, 0, 0], 63 | [0, 6, 0, -1, 0, -1, 0, 0, 0, -4, 0, 0], 64 | [-1, 0, 6, 0, -1, 0, 0, 0, 0, 0, -4, 0], 65 | [0, -1, 0, 6, 0, -1, 0, 0, 0, 0, 0, -4], 66 | [-1, 0, -1, 0, 6, 0, -4, 0, 0, 0, 0, 0], 67 | [0, -1, 0, -1, 0, 6, 0, -4, 0, 0, 0, 0], 68 | [0, 0, 0, 0, -4, 0, 32, 0, 16, 0, 16, 0], 69 | [0, 0, 0, 0, 0, -4, 0, 32, 0, 16, 0, 16], 70 | [-4, 0, 0, 0, 0, 0, 16, 0, 32, 0, 16, 0], 71 | [0, -4, 0, 0, 0, 0, 0, 16, 0, 32, 0, 16], 72 | [0, 0, -4, 0, 0, 0, 16, 0, 16, 0, 32, 0], 73 | [0, 0, 0, -4, 0, 0, 0, 16, 0, 16, 0, 32]]) 74 | assert np.allclose(stiff, stiff_ex) 75 | assert np.allclose(mass, mass_ex) 76 | 77 | 78 | # Quadrilaterals 79 | def test_elast_quad4(): 80 | """Tests for 4-noded quad uel""" 81 | coord = np.array([[-1, -1], [1, -1], [1, 1], [-1, 1]]) 82 | params = 8/3, 1/3 83 | stiff, mass = uel.elast_quad4(coord, params) 84 | stiff_ex = 1/6 * np.array([ 85 | [ 8, 3, -5, 0, -4, -3, 1, 0], 86 | [ 3, 8, 0, 1, -3, -4, 0, -5], 87 | [-5, 0, 8, -3, 1, 0, -4, 3], 88 | [ 0, 1, -3, 8, 0, -5, 3, -4], 89 | [-4, -3, 1, 0, 8, 3, -5, 0], 90 | [-3, -4, 0, -5, 3, 8, 0, 1], 91 | [ 1, 0, -4, 3, -5, 0, 8, -3], 92 | [ 0, -5, 3, -4, 0, 1, -3, 8]]) 93 | mass_ex = 1/9 * np.array([ 94 | [4, 0, 2, 0, 1, 0, 2, 0], 95 | [0, 4, 0, 2, 0, 1, 0, 2], 96 | [2, 0, 4, 0, 2, 0, 1, 0], 97 | [0, 2, 0, 4, 0, 2, 0, 1], 98 | [1, 0, 2, 0, 4, 0, 2, 0], 99 | [0, 1, 0, 2, 0, 4, 0, 2], 100 | [2, 0, 1, 0, 2, 0, 4, 0], 101 | [0, 2, 0, 1, 0, 2, 0, 4]]) 102 | assert np.allclose(stiff, stiff_ex) 103 | assert np.allclose(mass, mass_ex) 104 | 105 | 106 | #%% Structural elements 107 | def test_spring(): 108 | """Tests for 2-noded springs uel""" 109 | coord = np.array([ 110 | [0, 0], 111 | [1, 0]]) 112 | params = 1 113 | stiff, _ = uel.spring(coord, params) 114 | stiff_ex = np.array([ 115 | [1, 0, -1, 0], 116 | [0, 0, 0, 0], 117 | [-1, 0, 1, 0], 118 | [0, 0, 0, 0]]) 119 | assert np.allclose(stiff, stiff_ex) 120 | 121 | 122 | def test_truss2D(): 123 | """Tests for 2-noded 2D truss uel""" 124 | coord = np.array([ 125 | [0, 0], 126 | [1, 0]]) 127 | params = 1.0, 1.0, 1.0 128 | stiff, mass = uel.truss2D(coord, params) 129 | stiff_ex = np.array([ 130 | [1, 0, -1, 0], 131 | [0, 0, 0, 0], 132 | [-1, 0, 1, 0], 133 | [0, 0, 0, 0]]) 134 | mass_ex = 1/6*np.array([ 135 | [2, 0, 1, 0], 136 | [0, 2, 0, 1], 137 | [1, 0, 2, 0], 138 | [0, 1, 0, 2]]) 139 | assert np.allclose(stiff, stiff_ex) 140 | assert np.allclose(mass, mass_ex) 141 | 142 | 143 | def test_beam2DU(): 144 | """Tests for 2-noded 2D beam uel""" 145 | coord = np.array([ 146 | [0, 0], 147 | [1, 0]]) 148 | params = 1.0, 1.0, 1.0, 1.0 149 | stiff, mass = uel.beam2DU(coord, params) 150 | stiff_ex = np.array([ 151 | [0, 0, 0, 0, 0, 0], 152 | [0, 12, 6, 0, -12, 6], 153 | [0, 6, 4, 0, -6, 2], 154 | [0, 0, 0, 0, 0, 0], 155 | [0, -12, -6, 0, 12, -6], 156 | [0, 6, 2, 0, -6, 4]]) 157 | mass_ex = 1/420*np.array([ 158 | [0, 0, 0, 0, 0, 0], 159 | [0, 156, 22, 0, 54, -13], 160 | [0, 22, 4, 0, 13, -3], 161 | [0, 0, 0, 0, 0, 0], 162 | [0, 54, 13, 0, 156, -22], 163 | [0, -13, -3, 0, -22, 4]]) 164 | assert np.allclose(stiff, stiff_ex) 165 | assert np.allclose(mass, mass_ex) 166 | --------------------------------------------------------------------------------