├── .DS_Store ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── docs.yml │ ├── publish.yml │ ├── pypi_publish.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── NOTICE ├── README.md ├── docs ├── .DS_Store ├── Makefile ├── _static │ ├── .DS_Store │ ├── css │ │ └── my_style.css │ └── images │ │ ├── .DS_Store │ │ ├── FASTvsNuMADcsys.eps │ │ ├── FASTvsNuMADcsys.png │ │ ├── FASTvsNuMADcsys.pptx │ │ ├── FullLengthBlade.jpg │ │ ├── TEoverlap.png │ │ ├── bladeKeyPoints.eps │ │ ├── bladeKeyPoints.pptx │ │ ├── blade_attribute_tree.png │ │ ├── cubit │ │ ├── sampleCross-section.png │ │ ├── sampleCross-section.svg │ │ ├── sampleLE.png │ │ ├── sampleTE.png │ │ ├── sampleWeb_adhesive.png │ │ ├── sampleWeb_adhesive.svg │ │ └── sample_solid_mesh_cubit.png │ │ ├── fatigueDamageFractionExample.bmp │ │ ├── keypoints.png │ │ ├── loadDirections.eps │ │ ├── loadDirections.png │ │ ├── loadDirections.pptx │ │ ├── localFieldsDataExplanationFigure-eps-converted-to.pdf │ │ ├── localFieldsDataExplanationFigure.eps │ │ ├── localFieldsDataExplanationFigure.png │ │ ├── localFieldsDataExplanationFigure.pptx │ │ ├── localFieldsPlotExample.png │ │ ├── momentsToForces.eps │ │ ├── momentsToForces.png │ │ ├── momentsToForces.pptx │ │ ├── numadFASTcsys.eps │ │ ├── numadFASTcsys.png │ │ ├── numadFASTcsys.pptx │ │ ├── pyNuMAD_overview.png │ │ ├── shellMesh_inHouse.jpg │ │ └── solidMesh_inHouse.jpg ├── apidoc │ ├── pynumad.analysis.ansys.rst │ ├── pynumad.analysis.cubit.rst │ ├── pynumad.graphics.rst │ ├── pynumad.io.rst │ ├── pynumad.objects.airfoil.rst │ ├── pynumad.objects.blade.rst │ ├── pynumad.objects.bom.rst │ ├── pynumad.objects.component.rst │ ├── pynumad.objects.definition.rst │ ├── pynumad.objects.geometry.rst │ ├── pynumad.objects.keypoints.rst │ ├── pynumad.objects.material.rst │ ├── pynumad.objects.materialdb.rst │ ├── pynumad.objects.rst │ ├── pynumad.objects.stackdb.rst │ ├── pynumad.objects.station.rst │ ├── pynumad.rst │ └── pynumad.utils.rst ├── compare_in_house_vs_cubit.csv ├── conf.py ├── contributing.rst ├── cs_params_dict.csv ├── index.rst ├── license.rst ├── publications.rst ├── reference.rst ├── refs │ ├── conclusion.bib │ └── publications.bib ├── release-notes.rst └── user-guide │ ├── beam_models.rst │ ├── blade_definition.rst │ ├── getting-started.rst │ ├── index.rst │ ├── installation.rst │ ├── meshing.rst │ ├── overview.rst │ ├── settings_dict.csv │ ├── shell_models.rst │ └── solid_models.rst ├── examples ├── ansys_example.py ├── buildIEA15InsertsRoot.py ├── buildIEA15TubeRoot.py ├── cubit_beam.py ├── cubit_solid.py ├── example_data │ ├── IEA-15-240-RWT.yaml │ ├── V27_fromScan.yaml │ ├── blade.xlsx │ ├── blade.yaml │ └── myBlade_Modified.yaml ├── get_damping_factors.py ├── gettingstarted.ipynb ├── myBlade_loadsTable.json ├── readyaml_buildmesh.py ├── write_abaqus_shell_model.py └── write_abaqus_solid_model.py ├── noxfile.py ├── pyproject.toml └── src ├── .DS_Store └── pynumad ├── .DS_Store ├── __init__.py ├── analysis ├── __init__.py ├── abaqus │ ├── read.py │ └── write.py ├── ansys │ ├── main_ansys_analysis.py │ ├── read.py │ ├── run.py │ ├── utility.py │ └── write.py ├── beam_utils.py ├── cubit │ ├── __init__.py │ ├── connect_cross_sections.py │ ├── make_blade.py │ └── make_cross_sections.py └── make_models.py ├── data ├── __init__.py ├── airfoils │ ├── DU91-W-250.txt │ ├── DU93-W-210.txt │ ├── DU97-W-300.txt │ ├── DU99-W-350.txt │ ├── DU99-W-405.txt │ ├── NACA-63-214.txt │ ├── NACA-64-618.txt │ └── circular.txt ├── blade_yamls │ ├── BAR0_NREL_1_4_2021.yaml │ ├── BAR1_SNL_1_18_2021.yaml │ ├── BAR2_HeavyTow_SNL_1_25_2021.yaml │ ├── BAR2_SNL_1_18_2021.yaml │ ├── BAR3_HeavyTow_SNL_1_25_2021.yaml │ ├── BAR3_SNL_1_18_2021.yaml │ ├── BAR4_HeavyTow_SNL_1_25_2021.yaml │ ├── BAR4_SNL_1_18_2021.yaml │ └── myBlade.yaml ├── blades │ ├── blade.xlsx │ └── blade.yaml └── templates │ ├── mat_ori.py.template │ ├── sd.i.template │ └── sm.i.template ├── graphics ├── __init__.py └── graphics.py ├── io ├── __init__.py ├── airfoil_to_xml.py ├── excel_to_blade.py ├── mesh_to_yaml.py ├── xml_to_airfoil.py └── yaml_to_blade.py ├── mesh_gen ├── __init__.py ├── boundary2d.py ├── element_utils.py ├── mesh2d.py ├── mesh3d.py ├── mesh_gen.py ├── mesh_tools.py ├── segment2d.py ├── shell_region.py ├── spatial_grid_list2d.py ├── spatial_grid_list3d.py └── surface.py ├── objects ├── __init__.py ├── airfoil.py ├── blade.py ├── bom.py ├── component.py ├── definition.py ├── geometry.py ├── keypoints.py ├── material.py ├── materialdb.py ├── settings.py ├── stackdb.py └── station.py ├── paths.py ├── software_paths.json ├── tests ├── __init__.py ├── test_affinetrans.py ├── test_airfoil.py ├── test_blade_io.py ├── test_data │ ├── airfoils │ │ ├── DU91-W-250.txt │ │ ├── DU93-W-210.txt │ │ ├── DU97-W-300.txt │ │ ├── DU99-W-350.txt │ │ ├── DU99-W-405.txt │ │ ├── NACA-63-214.txt │ │ ├── NACA-64-618.txt │ │ └── circular.txt │ └── blades │ │ ├── blade.xlsx │ │ ├── blade.yaml │ │ ├── excel_blade.pkl │ │ └── yaml_blade.pkl ├── test_interpolator.py ├── test_mesh.py └── test_misc.py └── utils ├── __init__.py ├── affinetrans.py ├── damping.py ├── distributed_loading.py ├── fatigue.py ├── interpolation.py ├── misc_utils.py └── orientations.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Provide a summary of the proposed changes, describe tests and documentation, and review the acknowledgement below. 2 | 3 | ## Summary 4 | Include issues that are resolved by this pull request. 5 | 6 | ## Tests and documentation 7 | New features require tests and documentation. 8 | 9 | ## Acknowledgement 10 | By contributing to this software project, I acknowledge that I have reviewed the [software quality assurance guidelines](XXX) and that my contributions are submitted under the [Revised BSD License](XXX). 11 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | # Allow only one concurrent deployment 9 | concurrency: 10 | group: "pages" 11 | cancel-in-progress: false 12 | 13 | jobs: 14 | build: 15 | name: Build the documentation with Sphinx 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | - name: Setup nox 21 | uses: excitedleigh/setup-nox@v2.0.0 22 | - name: Build docs 23 | run: nox -s docs 24 | - name: Upload artifact 25 | uses: actions/upload-pages-artifact@v1 26 | with: 27 | path: 'docs/_build/html' 28 | deploy: 29 | name: Deploy documentation to GitHub Pages 30 | needs: build 31 | # Sets permissions of the GITHUB_TOKEN to allow deployment 32 | permissions: 33 | contents: read 34 | pages: write 35 | id-token: write 36 | runs-on: ubuntu-latest 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | steps: 41 | - name: Setup pages 42 | uses: actions/configure-pages@v3 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v1 46 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload to PyPi 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: '3.x' 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install setuptools wheel twine 19 | - name: Build and publish 20 | env: 21 | TWINE_USERNAME: __token__ 22 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 23 | run: | 24 | python -m build 25 | twine upload dist/* 26 | -------------------------------------------------------------------------------- /.github/workflows/pypi_publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload to PyPi 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: '3.x' 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install setuptools wheel twine build 19 | - name: Build and publish 20 | env: 21 | TWINE_USERNAME: __token__ 22 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 23 | run: | 24 | python -m build --sdist 25 | twine upload dist/* 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: run tests 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [windows-latest, macOS-13, ubuntu-latest] 11 | python-version: ['3.8', '3.9', '3.10'] 12 | fail-fast: false 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install pynumad 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install -e . 23 | - name: Test with pytest 24 | run: | 25 | pip install --upgrade pytest coverage 26 | coverage run --source=pynumad --omit="*/tests/*" -m pytest 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Ansys 163 | *.mac 164 | *.db 165 | *.DSP 166 | *.emat 167 | *.err 168 | *.esav 169 | *.full 170 | *.mntr 171 | *.stat 172 | *.src 173 | *.out 174 | *.dbb 175 | *.bat 176 | 177 | # vscode 178 | .vscode 179 | *.code-workspace 180 | 181 | # etc 182 | *.log 183 | 184 | # pynumad 185 | src/pynumad/software_paths.json 186 | 187 | #VABS 188 | *.in 189 | *.in.* 190 | 191 | #Cubit 192 | *.cub 193 | *.g 194 | *.jou 195 | 196 | #BeamDyn 197 | *BeamDyn*dat 198 | 199 | #Sierra 200 | sd.i 201 | sm.i 202 | euler 203 | mat_ori.py 204 | 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Sandia National Laboratories 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/pynumad/software_paths.json 2 | recursive-include src/pynumad/data * 3 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2021 2 | National Technology & Engineering Solutions of Sandia, LLC (NTESS). 3 | Under the terms of Contract DE-NA0003525 with NTESS, 4 | the U.S. Government retains certain rights in this software. 5 | 6 | Licensed under the BSD 3-Clause License; 7 | you may not use this file except in compliance with the License. 8 | 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyNuMAD 2 | [pyNuMAD (Python Numerical Manufacturing And Design)](https://github.com/sandialabs/pyNuMAD) is an object-oriented, open-source software program written in Python which simplifies the process of creating a three-dimensional model of a wind turbine blade. The tool organizes all blade information including aerodynamic and material properties as well as material placement into an 3 | intuitive API for use with other softwares. The purpose of pyNuMAD is to provide an intermediary between raw blade data in the form of yaml, excel, xml files and analytical platforms 4 | (ANSYS, Cubit, openFAST, etc). 5 | 6 | For any questions or support [create a new issue](https://github.com/sandialabs/pyNuMAD/issues/new) on GitHub. 7 | 8 | ## Documentation 9 | Documentation for pynumad is accessible at https://sandialabs.github.io/pyNuMAD/. 10 | 11 | ![](docs/_static/images/pyNuMAD_overview.png) 12 | 13 | ## Examples 14 | 15 | Step-by-step examples are located in the [examples](https://github.com/sandialabs/pyNuMAD/tree/main/examples) folder. Follow allong in the documentation. 16 | 17 | ## License 18 | 19 | pyNuMAD is licensed under BSD 3-clause license. Please see the 20 | [LICENSE](https://github.com/sandialabs/pyNuMAD/blob/main/LICENSE) included in 21 | the source code repository for more details. 22 | 23 | ## Acknowledgements 24 | 25 | pyNuMAD is currently being developed with funding from Department of Energy's 26 | (DOE) Energy Efficiency and Renewable Energy (EERE) Wind Energy Technology Office (WETO). 27 | -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/.DS_Store -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS = 2 | SPHINXBUILD = sphinx-build 3 | SPHINXPROJ = ReadtheDocsSphinxTheme 4 | SOURCEDIR = . 5 | BUILDDIR = _build 6 | 7 | # Put it first so that "make" without argument is like "make help". 8 | help: 9 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 10 | 11 | .PHONY: help Makefile 12 | 13 | # Catch-all target: route all unknown targets to Sphinx using the new 14 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 15 | %: Makefile 16 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 17 | -------------------------------------------------------------------------------- /docs/_static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/.DS_Store -------------------------------------------------------------------------------- /docs/_static/css/my_style.css: -------------------------------------------------------------------------------- 1 | @import url("theme.css"); 2 | 3 | .wy-nav-content { 4 | max-width: 1000px !important; 5 | } -------------------------------------------------------------------------------- /docs/_static/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/.DS_Store -------------------------------------------------------------------------------- /docs/_static/images/FASTvsNuMADcsys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/FASTvsNuMADcsys.png -------------------------------------------------------------------------------- /docs/_static/images/FASTvsNuMADcsys.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/FASTvsNuMADcsys.pptx -------------------------------------------------------------------------------- /docs/_static/images/FullLengthBlade.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/FullLengthBlade.jpg -------------------------------------------------------------------------------- /docs/_static/images/TEoverlap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/TEoverlap.png -------------------------------------------------------------------------------- /docs/_static/images/bladeKeyPoints.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/bladeKeyPoints.pptx -------------------------------------------------------------------------------- /docs/_static/images/blade_attribute_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/blade_attribute_tree.png -------------------------------------------------------------------------------- /docs/_static/images/cubit/sampleCross-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/cubit/sampleCross-section.png -------------------------------------------------------------------------------- /docs/_static/images/cubit/sampleLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/cubit/sampleLE.png -------------------------------------------------------------------------------- /docs/_static/images/cubit/sampleTE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/cubit/sampleTE.png -------------------------------------------------------------------------------- /docs/_static/images/cubit/sampleWeb_adhesive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/cubit/sampleWeb_adhesive.png -------------------------------------------------------------------------------- /docs/_static/images/cubit/sample_solid_mesh_cubit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/cubit/sample_solid_mesh_cubit.png -------------------------------------------------------------------------------- /docs/_static/images/fatigueDamageFractionExample.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/fatigueDamageFractionExample.bmp -------------------------------------------------------------------------------- /docs/_static/images/keypoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/keypoints.png -------------------------------------------------------------------------------- /docs/_static/images/loadDirections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/loadDirections.png -------------------------------------------------------------------------------- /docs/_static/images/loadDirections.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/loadDirections.pptx -------------------------------------------------------------------------------- /docs/_static/images/localFieldsDataExplanationFigure-eps-converted-to.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/localFieldsDataExplanationFigure-eps-converted-to.pdf -------------------------------------------------------------------------------- /docs/_static/images/localFieldsDataExplanationFigure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/localFieldsDataExplanationFigure.png -------------------------------------------------------------------------------- /docs/_static/images/localFieldsDataExplanationFigure.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/localFieldsDataExplanationFigure.pptx -------------------------------------------------------------------------------- /docs/_static/images/localFieldsPlotExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/localFieldsPlotExample.png -------------------------------------------------------------------------------- /docs/_static/images/momentsToForces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/momentsToForces.png -------------------------------------------------------------------------------- /docs/_static/images/momentsToForces.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/momentsToForces.pptx -------------------------------------------------------------------------------- /docs/_static/images/numadFASTcsys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/numadFASTcsys.png -------------------------------------------------------------------------------- /docs/_static/images/numadFASTcsys.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/numadFASTcsys.pptx -------------------------------------------------------------------------------- /docs/_static/images/pyNuMAD_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/pyNuMAD_overview.png -------------------------------------------------------------------------------- /docs/_static/images/shellMesh_inHouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/shellMesh_inHouse.jpg -------------------------------------------------------------------------------- /docs/_static/images/solidMesh_inHouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/docs/_static/images/solidMesh_inHouse.jpg -------------------------------------------------------------------------------- /docs/apidoc/pynumad.analysis.ansys.rst: -------------------------------------------------------------------------------- 1 | Ansys Analysis 2 | ============== 3 | 4 | main_ansys_analysis 5 | ------------------- 6 | 7 | .. automodule:: pynumad.analysis.ansys.main_ansys_analysis 8 | :members: 9 | :no-undoc-members: 10 | :show-inheritance: 11 | 12 | read 13 | ---- 14 | 15 | .. automodule:: pynumad.analysis.ansys.read 16 | :members: 17 | :no-undoc-members: 18 | :show-inheritance: 19 | 20 | run 21 | --- 22 | 23 | .. automodule:: pynumad.analysis.ansys.run 24 | :members: 25 | :no-undoc-members: 26 | :show-inheritance: 27 | 28 | utility 29 | ------- 30 | 31 | .. automodule:: pynumad.analysis.ansys.utility 32 | :members: 33 | :no-undoc-members: 34 | :show-inheritance: 35 | 36 | write 37 | ------- 38 | 39 | .. automodule:: pynumad.analysis.ansys.write 40 | :members: 41 | :no-undoc-members: 42 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.analysis.cubit.rst: -------------------------------------------------------------------------------- 1 | Cubit Meshing 2 | ============== 3 | 4 | make_blade 5 | ---------- 6 | 7 | .. automodule:: pynumad.analysis.cubit.make_blade 8 | :members: 9 | :no-undoc-members: 10 | :show-inheritance: 11 | 12 | connect_cross_sections 13 | ---------------------- 14 | 15 | .. automodule:: pynumad.analysis.cubit.connect_cross_sections 16 | :members: 17 | :no-undoc-members: 18 | :show-inheritance: 19 | 20 | make_cross_sections 21 | ------------------- 22 | 23 | .. automodule:: pynumad.analysis.cubit.make_cross_sections 24 | :members: 25 | :no-undoc-members: 26 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.graphics.rst: -------------------------------------------------------------------------------- 1 | Graphics module 2 | ===================== 3 | 4 | .. automodule:: pynumad.graphics 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/apidoc/pynumad.io.rst: -------------------------------------------------------------------------------- 1 | IO module 2 | ===================== 3 | 4 | .. automodule:: pynumad.io 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. automodule:: pynumad.io.excel_to_blade 13 | :members: 14 | :no-undoc-members: 15 | :show-inheritance: 16 | 17 | .. automodule:: pynumad.io.yaml_to_blade 18 | :members: 19 | :no-undoc-members: 20 | :show-inheritance: 21 | 22 | .. automodule:: pynumad.io.xml_to_airfoil 23 | :members: 24 | :no-undoc-members: 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.airfoil.rst: -------------------------------------------------------------------------------- 1 | Airfoil 2 | ============== 3 | 4 | .. automodule:: pynumad.objects.airfoil 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.blade.rst: -------------------------------------------------------------------------------- 1 | Blade 2 | ============= 3 | 4 | .. automodule:: pynumad.objects.blade 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.bom.rst: -------------------------------------------------------------------------------- 1 | BillOfMaterials 2 | ================= 3 | 4 | .. automodule:: pynumad.objects.bom 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.component.rst: -------------------------------------------------------------------------------- 1 | Component 2 | ================ 3 | 4 | .. automodule:: pynumad.objects.component 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.definition.rst: -------------------------------------------------------------------------------- 1 | Definition 2 | ============== 3 | 4 | .. automodule:: pynumad.objects.definition 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.geometry.rst: -------------------------------------------------------------------------------- 1 | Geometry 2 | ============== 3 | 4 | .. automodule:: pynumad.objects.geometry 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.keypoints.rst: -------------------------------------------------------------------------------- 1 | Keypoints 2 | ============== 3 | 4 | .. automodule:: pynumad.objects.keypoints 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.material.rst: -------------------------------------------------------------------------------- 1 | Material 2 | =============== 3 | 4 | .. automodule:: pynumad.objects.material 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.materialdb.rst: -------------------------------------------------------------------------------- 1 | MaterialDatabase 2 | ======================= 3 | 4 | .. automodule:: pynumad.objects.materialdb 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.rst: -------------------------------------------------------------------------------- 1 | Objects module 2 | ========================= 3 | 4 | .. automodule:: pynumad.objects 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | pynumad.objects.airfoil 16 | pynumad.objects.blade 17 | pynumad.objects.bom 18 | pynumad.objects.component 19 | pynumad.objects.definition 20 | pynumad.objects.geometry 21 | pynumad.objects.keypoints 22 | pynumad.objects.material 23 | pynumad.objects.materialdb 24 | pynumad.objects.stackdb 25 | pynumad.objects.station 26 | -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.stackdb.rst: -------------------------------------------------------------------------------- 1 | StackDatabase 2 | ============= 3 | 4 | .. automodule:: pynumad.objects.stackdb 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.objects.station.rst: -------------------------------------------------------------------------------- 1 | Station 2 | ============== 3 | 4 | .. automodule:: pynumad.objects.station 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/apidoc/pynumad.rst: -------------------------------------------------------------------------------- 1 | .. _api_documentation: 2 | 3 | API documentation 4 | =================== 5 | 6 | .. automodule:: pynumad 7 | :members: 8 | :no-undoc-members: 9 | :show-inheritance: 10 | 11 | Subpackages 12 | ----------- 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | 17 | pynumad.objects 18 | pynumad.utils 19 | pynumad.io 20 | pynumad.graphics 21 | pynumad.analysis.cubit 22 | pynumad.analysis.ansys 23 | -------------------------------------------------------------------------------- /docs/apidoc/pynumad.utils.rst: -------------------------------------------------------------------------------- 1 | Utils Module 2 | ===================== 3 | 4 | .. automodule:: pynumad.utils.affinetrans 5 | :members: 6 | :no-undoc-members: 7 | :show-inheritance: 8 | 9 | .. automodule:: pynumad.utils.interpolation 10 | :members: 11 | :no-undoc-members: 12 | :show-inheritance: 13 | 14 | .. automodule:: pynumad.utils.misc_utils 15 | :members: 16 | :no-undoc-members: 17 | :show-inheritance: -------------------------------------------------------------------------------- /docs/compare_in_house_vs_cubit.csv: -------------------------------------------------------------------------------- 1 | ,In-House Mesher,Cubit Mesher 2 | Pure shell model, Yes, No 3 | Shell model with solid TE adhesive, Yes, No 4 | Pure solid model, Yes, Yes 5 | Beam model, No, Yes 6 | Meshing scheme, Grows solid meshes from OML, Grows solid meshes from cross sections 7 | Availability,Open to anyone,.gov email or contract with SNL 8 | FEA Solver interface,Unlimited but users must code everything,Best used with Sierra but can partially export to many commercial FEA 9 | Feature flexibility,Only limited by funding for new flexibility,"Flexible regarding geometric complexity, element type (quad, tri, etc...)" 10 | Export to CAD,No,Yes 11 | Solid model mesh continuity,Discontinuous mesh,Continuous mesh 12 | TE adhesive, Yes, Yes 13 | Web adhesive, No, Yes 14 | Imperfect webs, No, Yes 15 | Bird's Mouth, No, Yes -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from pynumad import __version__ 2 | 3 | # -- Project information ----------------------------------------------------- 4 | project = u'pyNuMAD' 5 | copyright = u'2023 National Technology & Engineering Solutions of Sandia, LLC (NTESS)' 6 | author = u'pyNuMAD Developers' 7 | 8 | version = __version__ 9 | release = __version__ 10 | 11 | # -- General configuration --------------------------------------------------- 12 | 13 | extensions = [ 14 | 'sphinx.ext.autodoc', 15 | 'sphinx.ext.doctest', 16 | 'sphinx.ext.todo', 17 | 'sphinx.ext.coverage', 18 | # 'sphinx.ext.viewcode', # commenting out for now b/c bad render width 19 | 'sphinx.ext.napoleon', 20 | 'sphinxcontrib.bibtex', 21 | ] 22 | napoleon_use_rtype = False 23 | viewcode_import = True 24 | numpydoc_show_class_members = True 25 | numpydoc_show_inherited_class_members = False 26 | numpydoc_class_members_toctree = False 27 | autodoc_member_order = 'bysource' 28 | autoclass_content = 'both' 29 | bibtex_bibfiles = ['refs/publications.bib','refs/conclusion.bib'] 30 | templates_path = ['_templates'] 31 | source_suffix = '.rst' 32 | master_doc = 'index' 33 | language = "en" 34 | numfig = True 35 | 36 | # List of patterns, relative to source directory, that match files and 37 | # directories to ignore when looking for source files. 38 | # This pattern also affects html_static_path and html_extra_path . 39 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_user'] 40 | 41 | # The name of the Pygments (syntax highlighting) style to use. 42 | pygments_style = 'sphinx' 43 | 44 | # -- Options for HTML output ------------------------------------------------- 45 | 46 | # The theme to use for HTML and HTML Help pages. See the documentation for 47 | # a list of builtin themes. 48 | 49 | html_theme = 'pydata_sphinx_theme' 50 | 51 | # Add any paths that contain custom static files (such as style sheets) here, 52 | # relative to this directory. They are copied after the builtin static files, 53 | # so a file named "default.css" will overwrite the builtin "default.css". 54 | html_static_path = ["_static"] 55 | html_style = 'css/my_style.css' 56 | 57 | # -- Options for HTMLHelp output --------------------------------------------- 58 | 59 | # Output file base name for HTML help builder. 60 | htmlhelp_basename = 'pyNuMADdoc' -------------------------------------------------------------------------------- /docs/cs_params_dict.csv: -------------------------------------------------------------------------------- 1 | Key,Description 2 | ``nel_per_layer``,"Type: Int. Number of intervals assigned to Cubit curves named ""layer_thickness""" 3 | ``element_size``,"Type: Float or Int. Approximate element aspect ratio. Zero will generate maximally coarse mesh " 4 | ``element_shape``,"Type: str. Could be either ""quad"" or ""tri""" 5 | ``layer_transition_angle``,Type: float. Angle in degrees to connect adjacent componets with different thicknesses 6 | ``birds_mouth_amplitude_fraction``,Type: float. Total brid's mouth depth as a fraction web height. 7 | ``minimum_layer_thickness``,Type: float. The smallest allowable layer thickness. Recommend 0.001 8 | ``adhesive_mat_name``,Type: str. Name of adhesive material. Must match yaml file exacly. 9 | ``web_fore_adhesive_thickness``,Type: array. Adhesive thicknesses in meters. Array size must be the same size as blade.ispan. 10 | ``web_aft_adhesive_thickness``,Type: array. Adhesive thicknesses in meters. Array size must be the same size as blade.ispan. 11 | ``le_adhesive_thickness``,Type: array. Adhesive thicknesses in meters. Array size must be the same size as blade.ispan. 12 | ``te_adhesive_thickness``,Type: array. Adhesive thicknesses in meters. Array size must be the same size as blade.ispan. 13 | ``web_adhesive_width``,Type: array. Adhesive width in meters. Array size must be the same size as blade.ispan. 14 | ``te_adhesive_width``,Type: array. Adhesive width in meters. Array size must be the same size as blade.ispan. 15 | ``max_web_imperfection_distance``,Type: array. Web imperfection amplitude in meters. Array size must be the same size as blade.ispan. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _home: 2 | 3 | pyNuMAD 4 | ======== 5 | 6 | The structural design and optimization of wind turbine blades is a 7 | complex task. In many cases it is difficult to find the optimal design 8 | of a turbine blade by hand, or by trial and error, and the software 9 | tools used for such designs are most effective when integrated into 10 | automated optimization and analysis algorithms. A new version of the 11 | software tool `pyNuMAD (Python Numerical Manufacturing And Design) `_ for the design 12 | and modeling of wind turbine blades is developed and described. 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | :hidden: 17 | 18 | user-guide/index 19 | contributing 20 | apidoc/pynumad 21 | reference 22 | release-notes 23 | publications 24 | license 25 | 26 | .. _intro-citation: 27 | 28 | Citing NuMAD 29 | =============== 30 | 31 | To cite pyNuMAD, please utilize the reference below. 32 | 33 | [1] Camarena, E., Anderson, E., Bonney, K. L., Clarke, R. J., & Paquette, J. (2023). pyNuMAD 1.0.0. Zenodo. https://doi.org/10.5281/zenodo.10023189 34 | 35 | 36 | 37 | .. _developers: 38 | 39 | pyNuMAD Developers 40 | ===================== 41 | 42 | pyNuMAD has been developed by `Sandia National Laboratories 43 | (Sandia) `_, 44 | funded by the U.S. Department of Energy’s Wind Energy Technologies Technologies Office. 45 | 46 | 47 | Current members of the development team include: 48 | 49 | - Joshua Paquette (“Josh”) (Sandia - PI) 50 | - Evan Anderson (Sandia) 51 | - Ernesto Camarena (Sandia) 52 | - Kirk Bonney (Sandia) 53 | - Ryan James Clarke (Sandia) 54 | 55 | 56 | Funding 57 | ======= 58 | 59 | Development and maintenance of the NuMAD code is funded by the U.S. Department of Energy’s Wind Energy Technologies Office. 60 | Sandia National Laboratories is a multi-mission laboratory managed and operated by National Technology and Engineering Solutions 61 | of Sandia, LLC., a wholly owned subsidiary of Honeywell International, Inc., for the U.S. Department of Energy’s 62 | National Nuclear Security Administration under contract DE-NA0003525. 63 | 64 | Indices and tables 65 | ================== 66 | 67 | * :ref:`genindex` 68 | * :ref:`modindex` 69 | * :ref:`search` 70 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | License 4 | ======= 5 | pyNuMAD is copyright through the National Technology & Engineering Solutions of Sandia, LLC (NTESS). 6 | The software is distributed under the BSD 3-Clause License. 7 | 8 | Copyright 9 | ------------ 10 | 11 | .. literalinclude:: ../NOTICE 12 | :language: text 13 | 14 | 15 | BSD 3-Clause License 16 | ------------------------- 17 | 18 | .. literalinclude:: ../LICENSE 19 | :language: text 20 | 21 | -------------------------------------------------------------------------------- /docs/publications.rst: -------------------------------------------------------------------------------- 1 | .. _publications: 2 | 3 | Publications 4 | ============ 5 | 6 | 7 | Here is a list of publications regarding the legacy NuMAD. pyNuMAD publications forthcoming. The list includes publications written by the NuMAD team about the development and application of NuMAD as well as publications written by users of the NuMAD code outside of the NuMAD development team. 8 | 9 | .. bibliography:: refs/publications.bib 10 | :all: -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | 4 | Reference 5 | ============ 6 | 7 | This page contains definitions for various terminology and abbreviations 8 | used throughout pyNuMAD documentation and code. 9 | 10 | Terminology 11 | ----------- 12 | 13 | 14 | Airfoil: A unitless (0-1) 2D outline of the desired aerodynamic shape (i.e., NACA-63-214, DU99-W-405, etc). 15 | These are defined at every span location (different from ispan). 16 | 17 | Profiles: Usually there are not enough airfoils defined along the span to create a smooth 3D blade geometry. 18 | Profiles are unitless airfoil geometries at each blade interpolated span (ispan) location. 19 | 20 | Station: A station is an airfoil object at a specified span location. 21 | 22 | Stack: A stack of material plys specified by types and thicknesses. 23 | 24 | Ply: A layer of material with defined properties, layup angle, and thickness. 25 | 26 | Camber: A 2D collection of points midway between the HP curve and LP curve of an airfoil or profile. 27 | 28 | Keypoint: Specified locations along the profile circumference used to define where there is a change in stack definition. 29 | 30 | Component: TODO 31 | 32 | Shearweb: TODO 33 | 34 | Span: TODO 35 | 36 | Chord: TODO 37 | 38 | Twist: TODO 39 | 40 | Prebend: TODO 41 | 42 | Sweep: TODO 43 | 44 | TE Type: TODO 45 | 46 | 47 | Abbreviations 48 | ------------- 49 | 50 | BOM: Bill of Materials 51 | 52 | OML: Outer Mold Line 53 | 54 | TE: Trailing Edge 55 | 56 | LE: Leading Edge 57 | 58 | PS: Pressure Side (high pressure) 59 | 60 | SS: Suction Side (low pressure) -------------------------------------------------------------------------------- /docs/refs/conclusion.bib: -------------------------------------------------------------------------------- 1 | @article{Fagerberg2005, 2 | author = {Fagerberg, Linus and Zenkert, Dan}, 3 | title = {Effects of anisotropy and multiaxial loading on the wrinkling of sandwich panels}, 4 | journal = {Journal of Sandwich Structures and Materials}, 5 | volume = {7}, 6 | number = {3}, 7 | pages = {177-194}, 8 | ISSN = {10996362 (ISSN) 9 | 15307972 (EISSN)}, 10 | DOI = {10.1177/109963205048525}, 11 | year = {2005}, 12 | type = {Journal Article} 13 | } 14 | 15 | @inbook{Berg2011, 16 | author = {Berg, Jonathan and Paquette, Joshua and Resor, Brian}, 17 | title = {Mapping of 1D Beam Loads to the 3D Wind Blade for Buckling Analysis}, 18 | booktitle = {52nd AIAA/ASME/ASCE/AHS/ASC Structures, Structural Dynamics and Materials Conference}, 19 | series = {Structures, Structural Dynamics, and Materials and Co-located Conferences}, 20 | publisher = {American Institute of Aeronautics and Astronautics}, 21 | DOI = {doi:10.2514/6.2011-1880 22 | 10.2514/6.2011-1880}, 23 | year = {2011}, 24 | type = {Book Section} 25 | } 26 | 27 | -------------------------------------------------------------------------------- /docs/refs/publications.bib: -------------------------------------------------------------------------------- 1 | @article{camarena2022land, 2 | title={Land-based wind turbines with flexible rail-transportable blades--Part 2: 3D finite element design optimization of the rotor blades}, 3 | author={Camarena, Ernesto and Anderson, Evan and Paquette, Josh and Bortolotti, Pietro and Feil, Roland and Johnson, Nick}, 4 | journal={Wind Energy Science}, 5 | volume={7}, 6 | number={1}, 7 | pages={19--35}, 8 | year={2022}, 9 | publisher={Copernicus GmbH} 10 | } 11 | 12 | @article{bortolotti2021land, 13 | title={Land-based wind turbines with flexible rail-transportable blades--Part 1: Conceptual design and aeroservoelastic performance}, 14 | author={Bortolotti, Pietro and Johnson, Nick and Abbas, Nikhar J and Anderson, Evan and Camarena, Ernesto and Paquette, Joshua}, 15 | journal={Wind Energy Science}, 16 | volume={6}, 17 | number={5}, 18 | pages={1277--1290}, 19 | year={2021}, 20 | publisher={Copernicus GmbH} 21 | } 22 | 23 | @inproceedings{kelley2020investigation, 24 | title={Investigation of flutter for large, highly flexible wind turbine blades}, 25 | author={Kelley, Christopher Lee and Paquette, Joshua}, 26 | booktitle={Journal of Physics: Conference Series}, 27 | volume={1618}, 28 | number={5}, 29 | pages={052078}, 30 | year={2020}, 31 | organization={IOP Publishing} 32 | } 33 | 34 | @techreport{resor2014aeroelastic, 35 | title={An aeroelastic reference model for the SWIFT turbines}, 36 | author={Resor, Brian Ray and LeBlanc, Bruce Philip}, 37 | year={2014}, 38 | institution={Sandia National Lab.(SNL-NM), Albuquerque, NM (United States)} 39 | } 40 | 41 | @inproceedings{van2011model, 42 | title={Model Form Error of Alternate Modeling Strategies: Shell Type Wind Turbine Blades}, 43 | author={Van Buren, Kendra Lu and Atamturktur, Sez}, 44 | booktitle={Rotating Machinery, Structural Health Monitoring, Shock and Vibration, Volume 5: Proceedings of the 29th IMAC, A Conference on Structural Dynamics, 2011}, 45 | pages={53--64}, 46 | year={2011}, 47 | organization={Springer} 48 | } 49 | 50 | @phdthesis{yossri2022multi, 51 | title={Multi-Fidelity Modeling, Stability, and Performance of Small and Mid-Scale Wind Turbines Blades}, 52 | author={Yossri, Widad}, 53 | year={2022}, 54 | school={New Mexico State University} 55 | } 56 | 57 | @inproceedings{resor2010evaluation, 58 | title={An evaluation of wind turbine blade cross section analysis techniques}, 59 | author={Resor, Brian and Paquette, Joshua and Laird, Daniel and Griffith, D}, 60 | booktitle={51st AIAA/ASME/ASCE/AHS/ASC Structures, Structural Dynamics, and Materials Conference 18th AIAA/ASME/AHS Adaptive Structures Conference 12th}, 61 | pages={2575}, 62 | year={2010} 63 | } 64 | 65 | @article{malcolm2007extraction, 66 | title={Extraction of equivalent beam properties from blade models}, 67 | author={Malcolm, David J and Laird, Daniel L}, 68 | journal={Wind Energy: An International Journal for Progress and Applications in Wind Power Conversion Technology}, 69 | volume={10}, 70 | number={2}, 71 | pages={135--157}, 72 | year={2007}, 73 | publisher={Wiley Online Library} 74 | } 75 | 76 | @techreport{paquette2007increased, 77 | title={Increased Strength in Wind Turbine Blades through Innovative Structural Design.}, 78 | author={Paquette, Joshua A and Veers, Paul S}, 79 | year={2007}, 80 | institution={Sandia National Lab.(SNL-NM), Albuquerque, NM (United States)} 81 | } 82 | 83 | @inproceedings{ashwill2007concepts, 84 | title={Concepts to facilitate very large blades}, 85 | author={Ashwill, Tom and Laird, Daniel}, 86 | booktitle={45th AIAA Aerospace Sciences Meeting and Exhibit}, 87 | pages={817}, 88 | year={2007} 89 | } 90 | 91 | @inproceedings{paquette2006modeling, 92 | title={Modeling and testing of 9m research blades}, 93 | author={Paquette, Joshua and Laird, Daniel and Griffith, Daniel and Rip, Laura}, 94 | booktitle={44th AIAA Aerospace Sciences Meeting and Exhibit}, 95 | pages={1199}, 96 | year={2006} 97 | } 98 | 99 | @inproceedings{malcolm2005identification, 100 | title={Identification and use of blade physical properties}, 101 | author={Malcolm, David and Laird, Daniel}, 102 | booktitle={43rd AIAA Aerospace Sciences Meeting and Exhibit}, 103 | pages={971}, 104 | year={2005} 105 | } 106 | 107 | @inproceedings{laird2005finite, 108 | title={Finite element modeling of wind turbine blades}, 109 | author={Laird, Daniel and Montoya, Felicia and Malcolm, David}, 110 | booktitle={43rd AIAA Aerospace Sciences Meeting and Exhibit}, 111 | pages={195}, 112 | year={2005} 113 | } -------------------------------------------------------------------------------- /docs/release-notes.rst: -------------------------------------------------------------------------------- 1 | .. _release-notes: 2 | 3 | Release Notes 4 | ============= 5 | 6 | .. _pyNuMADv0x: 7 | 8 | pyNuMAD 1.0.0 9 | ----------- 10 | 11 | Core functionality from previous versions of the NuMAD software 12 | framework have been translated to the Python programming language 13 | under the name pyNuMAD. Following the changes made in NuMAD 3.0, 14 | pyNuMAD focuses on an object-oriented approach to blade design. 15 | 16 | Below is a summary of key features currently available: 17 | 18 | - Reading in blade definitions from the `windIO yaml format `__. 19 | Legacy NuMAD Excel input files are also supported. 20 | - Creation of wind blades made from shell and/or solid elements. 21 | - Optional details such as adhesive bonds. 22 | - 2D cross sectional meshes for tools like VABS/ANBA/OpenSG 23 | 24 | The GUI and `.nmd` legacy input files are discontinued for now. 25 | 26 | .. _NuMADv3: 27 | 28 | NuMAD v3.0 29 | ---------------- 30 | This previous MATLAB release incorporates structural optimization, associated 31 | structural analyses, and the move to object-oriented data structures. 32 | The exclusive use of the GUI in prior versions prevented automation in 33 | optimizations. Thus, moving to these data structures enabled 34 | optimization. Since the GUI functionality is advantageous in certain 35 | situations, its functionality is still retained. Current documentation primarily 36 | describes usage of the structural optimization, associated structural 37 | analyses, and the object-oriented data structures. 38 | 39 | - A new capability also includes the ability to accept input from the 40 | International Energy Agency (IEA) Wind Task 37 blade ontology. This 41 | is known to have enhanced collaboration. 42 | 43 | - 3D FEA shell analyses have been partially detached from the GUI and can now be 44 | parameterized for various parameter studies and optimization. Data I/O for 45 | ANSYS has been automated for mesh generation as well as various analyses, 46 | such as tip-deflection, buckling, material rupture, total mass, and 47 | frequencies. 48 | 49 | - A technique was developed to determine the design loads. The 50 | thousands of section forces and moments that occurred during the 51 | dynamic structural analyses required in the system-level optimization 52 | were reduced to nine critical load cases. 53 | 54 | - The other newly developed analysis procedure allowed to evaluate 55 | fatigue damage for every material layer at various cross-sections of 56 | a blade. Further details on both new analyses are provided in the 57 | journal article “Part II: 3D FEM design optimization of the rotor 58 | blades”. 59 | 60 | - Click `here `_ for NuMAD v3.0 61 | 62 | .. Kelley: add DOI 63 | 64 | .. _FutureDev: 65 | 66 | Possible Future Development 67 | --------------------------- 68 | 69 | - Allow for the option to make the blade entirely of solid elements 70 | 71 | - Incorporate adhesive modeling 72 | 73 | - Allow for FEA without the need of commercial FE licenses 74 | 75 | - Probabilistic flaw distributions 76 | 77 | - Incorporate Progressive damage 78 | 79 | - In addition to the ANSYS interface, add capability for a user to use 80 | other commercial FEA codes such as Abaqus and/or Nastran 81 | 82 | 83 | Prior Releases 84 | ---------------- 85 | 86 | .. _NuMADv2: 87 | 88 | NuMAD v2.0 89 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 90 | .. TODO: add DOI 91 | 92 | 93 | NuMAD first became available in the early 2000’s. It was originally written in Tcl, chosen for its ability to easily manipulate graphics and port information in and out of the main code. NuMAD has always been designed to output files for ANSYS. The latest release of the previous NuMAD code is dated 31 March 2010. 94 | 95 | Beginning with the last release of the previous NuMAD, its authors realized that blade design was quickly heading in a direction where a computationally intense, and platform independent programming environment was needed for NuMAD. Mathworks MATLAB® was chosen as the environment because of its widespread use, its computational power, its flexibility in operating systems, its graphical capabilities, its popularity with researchers and students, and its ability to compile as well as to run from raw source code. The current release of NuMAD also includes some advanced capabilities which go beyond the basic connection to creation of the finite element model: tabularized input format, swept blades, blades with pre-bend, output for CFD mesh creation and output of blade cross section properties. 96 | 97 | NuMAD v2.0 is available in two forms: 1) compiled MCR executable and 2) raw MATLAB source files. The MCR executables are in binaries.zip. Note that the MCR executables were too large to be placed in the "bin" folder of the source code. They as well as the rest of the "bin" files are in the binaries.zip asset. Thus, you can ignore the files in "bin" folder of the source code. Users are encouraged to study the raw source files to understand how NuMAD works. When there are capabilities that presently do not exist, the authors encourage users to work together with the authors to write modules that accomplish the tasks. It is the hope of the authors that the accessibility of the MATLAB source code will, in the long run, enable an extremely capable tool that will continue to enable DOE’s goals and benefit the entire wind industry. 98 | 99 | * Initial release of NuMAD in MATLAB: `Click here `_ 100 | 101 | * Refer to the former user’s manual in PDF form (`SAND2012-7028 `__). 102 | 103 | -------------------------------------------------------------------------------- /docs/user-guide/beam_models.rst: -------------------------------------------------------------------------------- 1 | .. _meshing: 2 | 3 | Making Beam Models 4 | ================================== 5 | 6 | Currently, only BeamDyn powered by VABS beam properties is supported. After meshing 7 | cross sections with :py:func:`~pynumad.analysis.cubit.make_blade.cubit_make_cross_sections`, call on 8 | :py:func:`~pynumad.analysis.make_models.write_beam_model`. Be sure to issue the following 9 | import statement 10 | 11 | .. code-block:: python 12 | 13 | from pynumad.analysis.make_models import write_beam_model 14 | 15 | 16 | An example called `cubit_beam.py` exists in the examples folder. -------------------------------------------------------------------------------- /docs/user-guide/blade_definition.rst: -------------------------------------------------------------------------------- 1 | .. _blade-overview: 2 | 3 | Blade Overview 4 | ============== 5 | 6 | The fundamental class of pyNuMAD is the Blade class. 7 | Each blade object houses a collection of subobjects as attributes 8 | which organize the various parameters and data of the blade 9 | in a logical fashion. In what follows, each of these primary 10 | attributes of the blade are explained at a high-level. For 11 | more information, please refer to the API documentation. 12 | The below figure illustrates the dependencies between various 13 | blade subobjects. For example, a definition object is required 14 | to generate geometry, keypoints, bill of material, and material database. 15 | 16 | .. _blade-tree: 17 | .. figure:: /_static/images/blade_attribute_tree.png 18 | 19 | Dependency tree for blade subobjects 20 | 21 | 22 | Definition (blade.definition) 23 | ----------------------------- 24 | 25 | A :py:class:`~pynumad.objects.definition.Definition` 26 | object provides attributes for the basic design 27 | of a wind turbine blade - i.e. where the blade is *defined*. 28 | Typically this is populated by 29 | a yaml file or an excel file, however it is possible to build a blade 30 | from scratch by manually assigning all of the necessary attributes. 31 | Once a definition object has been loaded in either manually or from 32 | a file, a user can make additional modifications to the blade, such as 33 | adding additional station locations or changing material assignments, before 34 | generating downstream data. 35 | 36 | Many of the attributes in Definition are parameterized by spanwise location. 37 | For example, *stations* are airfoils at specified span locations. 38 | Other airfoil properties and external blade shape data are 39 | defined with the :py:class:`~pynumad.objects.airfoil.Airfoil` 40 | class and the :py:class:`~pynumad.objects.station.Station` object respectively, and are stored in ``definition.stations``. 41 | Material properties, layup information, and thicknesses and widths are 42 | defined in the :py:class:`~pynumad.objects.material.Material` and :py:class:`~pynumad.objects.component.Component` classes 43 | and stored in ``blade.materials`` and ``blade.components``. 44 | 45 | 46 | Geometry (blade.geometry) 47 | --------------------------- 48 | 49 | Typically, the blade definition does not contain 50 | high enough fidelity data for creating a mesh, so 51 | pyNuMAD performs additional interpolation to 52 | create a more detailed geometry. The :py:class:`~pynumad.objects.geometry.Geometry` class generates 53 | and stores the interpoloated geometry. Many of the attributes are named as `iattribute` 54 | where attribute is the name of some attribute in the definition which was interpolated. Additionally 55 | the ``coordinates`` attribute holds the 3D geometry of the interpolated blade. 56 | 57 | 58 | Keypoints (blade.keypoints) 59 | --------------------------- 60 | 61 | The Keypoints class generates and organizes data related 62 | to keypoints. 63 | Airfoils are partitioned by keypoints, 64 | as shown in :numref:`keypoints-fig`. Various definition properties such as 65 | ``definition.leband``, 66 | ``definition.teband``, ``definition.sparcapwidth``, 67 | and ``definition.sparcapoffset`` help to 68 | position the keypoints precisely. For example, ``definition.leband`` is the 69 | arclength from the *le* keypoint to the keypoint *a*. *Regions* are 70 | defined between the keypoints as listed in :numref:`define-regions`. 71 | Adjacent stations help to define these regions as areas. Spanwise lines emanating 72 | from each keypoint are connected to the corresponding keypoints on an 73 | adjacent station; thus bounding the region with four curves. A suffix of 74 | either HP or LP is added to each region name to distinguish regions on 75 | the high pressure surface verses the low pressure surface. 76 | 77 | 78 | .. _keypoints-fig: 79 | .. figure:: /_static/images/keypoints.png 80 | 81 | Keypoint locations 82 | 83 | 84 | .. _define-regions: 85 | .. table:: Region definition by keypoints (TE-Trailing edge, LE-leading edge) 86 | 87 | +----------------------------------+-----------------------------------+ 88 | | Region Name | Bounding Keypoints | 89 | +==================================+===================================+ 90 | | LE | le & a | 91 | +----------------------------------+-----------------------------------+ 92 | | LE Panel | a & b | 93 | +----------------------------------+-----------------------------------+ 94 | | Spar | b & c | 95 | +----------------------------------+-----------------------------------+ 96 | | TE Panel | c & d | 97 | +----------------------------------+-----------------------------------+ 98 | | TE REINF | d & e | 99 | +----------------------------------+-----------------------------------+ 100 | | TE Flatback | e & te | 101 | +----------------------------------+-----------------------------------+ 102 | 103 | 104 | StackDB (blade.stackdb) 105 | ----------------------- 106 | The :py:class:`~pynumad.objects.stackdb.StackDatabase` class serves to assign material 107 | stack information across the blade. The ``stacks`` attribute stores stack information 108 | for the outer blade shape and takes the form of a 2-dimensional array. The first 109 | dimension represents segment (as determined by keypoints) 110 | and the second dimension represents station (index along interpolated 111 | blade span). The ``swstacks`` attribute stores stack information for the shearwebs 112 | and also takes the form of a 2D array, where the first dimension is the shearweb 113 | number, and the second dimension is also the station. 114 | 115 | 116 | BillOfMaterials (blade.bom) 117 | --------------------------- 118 | The BillOfMaterials class organizes material types and quantities in 119 | a blade. However, this class is not currently 120 | used in pyNuMAD analyses and only exists for legacy reasons. 121 | 122 | 123 | MaterialDB (blade.materialdb) 124 | ----------------------------- 125 | The MaterialDB class is another class for organizing materials. 126 | However, this class also is not currently 127 | used in pyNuMAD analyses and only exists for legacy reasons. 128 | -------------------------------------------------------------------------------- /docs/user-guide/getting-started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | Getting Started 4 | =============== 5 | 6 | Initializing a blade object 7 | --------------------------- 8 | 9 | After a successful installation, pyNuMAD can be imported in a python 10 | session in the usual way:: 11 | 12 | import pynumad 13 | 14 | Next, to initialize an empty blade object run:: 15 | 16 | blade = pynumad.Blade() 17 | 18 | To populate the blade with a yaml or Excel file you can run:: 19 | 20 | blade.read_yaml("path/to/yaml") 21 | blade.read_excel("path/to/excel") 22 | 23 | or initialize the blade with the path:: 24 | 25 | blade = pynumad.Blade("path/to/yaml") 26 | blade = pynumad.Blade("path/to/excel") 27 | 28 | 29 | Interfacing with other software 30 | -------------------------------- 31 | 32 | Depending on your needs, you might need to add installation paths for third-party software such as ANSYS, Cubit, etc... 33 | To do so, locate the file named `src/pynumad/software_paths.json` and modify it accordingly. Note that these paths 34 | are optional if you only need to read in a blade as described above and/or using the in-house mesher. 35 | 36 | **To Configure Cubit** 37 | After downloading the main Cubit folder and placing it anywhere on your machine: 38 | 39 | * Locate `cubit.py` in the main Cubit folder and add the full path to the "cubit" entry in `src/pynumad/software_paths.json`. 40 | On Linux this may look something like: "/some/place/Cubit-16.10/bin/". If Mac it may be: "/Applications/Cubit.app/Contents/MacOS/". 41 | 42 | * Locate `PyCubed_Main.py` in the main Cubit folder and add the full path to the "cubitEnhancements" entry in `src/pynumad/software_paths.json`. 43 | On Linux this may look something like: "/some/place/Cubit-16.10/bin/python3/lib/python3.7/site-packages/". On Mac it may be: "/Applications/Cubit.app/Contents/MacOS/python3/lib/site-packages/". 44 | 45 | **To Configure ANSYS** 46 | Locate the `mapdl` executable in your ANSYS installation folder. Then place the name of the executable along with its path in the "ansys_path" 47 | entry of `src/pynumad/software_paths.json`. For example, it may look like: "/some/where/ansys_inc/v222/ansys/bin/mapdl" 48 | -------------------------------------------------------------------------------- /docs/user-guide/index.rst: -------------------------------------------------------------------------------- 1 | .. _user-guide: 2 | 3 | User’s Guide 4 | ============ 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Get Started 9 | 10 | overview 11 | installation 12 | getting-started 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | :caption: User Interface 17 | 18 | blade_definition 19 | meshing 20 | beam_models 21 | shell_models 22 | solid_models 23 | -------------------------------------------------------------------------------- /docs/user-guide/installation.rst: -------------------------------------------------------------------------------- 1 | .. _intallation: 2 | 3 | Installation 4 | ============ 5 | 6 | Download pyNuMAD 7 | ---------------- 8 | 9 | The pyNuMAD source code is hosted on the `pyNuMAD GitHub repository `_. 10 | pyNuMAD users are recommended to clone the Github repository. 11 | Cloning the repository allows users to easily pull the latest updates to the pyNuMAD source code. 12 | These updates may improve the code's speed, accuracy and add additional functionality or advanced features. 13 | 14 | .. TODO: this section doesn't exist 15 | .. Developers who wish to contribute to pyNuMAD should see the corresponding Developer :ref:`dev-getting-started` section. 16 | 17 | To download pyNuMAD using `git `_, type the following in a git interface:: 18 | 19 | git clone https://github.com/sandialabs/pyNuMAD 20 | 21 | Installation 22 | ------------ 23 | 24 | After downloading the source, pyNuMAD can be installed by running 25 | the following command in the root of the repository:: 26 | 27 | pip install -e . 28 | 29 | Currently, it is necessary to use `-e` for a local installation so that certain data files are not lost in the installation process. 30 | 31 | Developers are recommended to install using the instructions on 32 | :ref:`contributing` page. 33 | -------------------------------------------------------------------------------- /docs/user-guide/overview.rst: -------------------------------------------------------------------------------- 1 | .. _overview: 2 | 3 | Overview 4 | ======================= 5 | 6 | 7 | `pyNuMAD (Python Numerical Manufacturing And Design) `_ simplifies the process of creating 8 | structural models of wind turbine blades at various fidelities. The tool manages information such as blade geometry, 9 | material properties, layups, and layup placement. By interfacing with the following codes: 10 | - `ANSYS Mechanical® `__ 11 | - `Abaqus `__ 12 | - `Cubit `__ 13 | - `VABS `__ 14 | - `BeamDyn `__ 15 | 16 | pyNuMAD can create beam, shell, or solid models. 17 | 18 | The code was created by converting select functionalities from the well-known NuMAD tool. The move to Python was motivated 19 | by increasing code accessibility as well as to aid with integration with other WETO tools. Sandia developers have transitioned 20 | the NuMAD repository to a static code. The graphical user interphase is currently unsupported. However new features have been implemented. 21 | Namely, 22 | 23 | - In-house mesher for pure shell, pure solid, or shell + solid adhesive 24 | - Cubit mesher for contiguous solid meshes and cross-sectional analysis 25 | 26 | Supported input files: 27 | 28 | - yaml files from the `IEA Ontology `__ 29 | - Excel files from the legacy version of NuMAD 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/user-guide/settings_dict.csv: -------------------------------------------------------------------------------- 1 | Key,Description 2 | ``make_input_for``,"Type: str. ""SM"", ""SD"", ""VABS"", ""ANBA"", or None. It should all be in one string such as ""vabssm"". Letter case agnostic." 3 | ``export``,"Type: str. ""cub"", ""g"", or None. It should all be in one string such as ""cubg""." -------------------------------------------------------------------------------- /docs/user-guide/shell_models.rst: -------------------------------------------------------------------------------- 1 | Making Shell Models 2 | =================== 3 | 4 | 5 | Currently, shell models can be made using ANSYS or Abaqus. Examples can be found in pyNuMAD/examples: 6 | 7 | `ansys_analysis.py` runs an analysis using ANSYS with a model generated by the in-house mesher. 8 | 9 | `write_abaqus_shell_model.py` generates an abaqus input file using the in-house mesher, which can be imported and analyzed using Abaqus CAE. 10 | 11 | For shell models, the in-house mesher takes an option for whether to include the trailing edge adhesive, meshed with solid elements. If included, the output gives constraint equations to tie the motion of the adhesive together with the blade. write_abaqus_shell_model.py demonstrates the access and usage of this. -------------------------------------------------------------------------------- /docs/user-guide/solid_models.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Making Solid Models 3 | =================== 4 | 5 | 6 | Pure solid model (explicitly descritezed sandwich panels) 7 | ========================================================= 8 | 9 | Pure solid models can be made in Sierra SM, Sierra SD or Abaqus. 10 | Currenly only SD is partially supported since it does not allow for 11 | spatially varying material orientations. 12 | 13 | Continuous meshes in Sierra 14 | ---------------------------- 15 | 16 | #. The first step is to make a Genesis mesh file and the associated files with 17 | :py:func:`~pynumad.analysis.cubit.make_blade.cubit_make_solid_blade`. This 18 | will create the following file 19 | 20 | * {wt_name}.g. Genesis mesh file. 21 | 22 | #. The next step is to Compute material orientation with 23 | :py:func:`~pynumad.analysis.cubit.make_blade.compute_material_orientations` 24 | 25 | #. Next, the orientation data needs to be assigned with 26 | :py:func:`~pynumad.analysis.cubit.make_blade.assign_material_orientations` 27 | 28 | #. Finally, the mesh needs to be exported in Genisis format 29 | .. code-block:: python 30 | cubit.cmd(f'export mesh "{wt_name}.g" overwrite') 31 | 32 | 33 | #. Make the Sierra SM input files with :py:func:`~pynumad.analysis.make_models.write_sierra_sm_model`. 34 | 35 | This creates the following file: 36 | 37 | * sm.i : Sierra SM input file 38 | 39 | Be sure to have ran the following import statement 40 | 41 | .. code-block:: python 42 | 43 | from pynumad.analysis.make_models import write_sierra_sm_model 44 | 45 | 46 | #. AND/OR Make the Sierra SD input files with :py:func:`~pynumad.analysis.make_models.write_sierra_sd_model`. 47 | 48 | This creates the following files: 49 | 50 | * sd.i: Sierra SD input file 51 | 52 | Be sure to have ran the following import statement 53 | 54 | .. code-block:: python 55 | 56 | from pynumad.analysis.make_models import write_sierra_sd_model 57 | 58 | 59 | #. Finally run Sierra SM with: launch -n 10 adagio -i sm.i, where n is the 60 | number of CPUs. 61 | 62 | #. AND/OR run Sierra SD with: launch -n 10 salinas -i sd.i, where n is the 63 | number of CPUs. 64 | 65 | An example called `cubit_solid.py` exists in the examples folder. 66 | 67 | Discontinuous meshes in Abaqus 68 | ------------------------------ 69 | 70 | The example pyNuMAD/examples/write_abaqus_solid_model.py generates a fully solid element model input file in Abaqus format, which can be imported and analyzed using Abaqus CAE. With the ih-house mesher, the shear webs and adhesive are tied to the blade outer shell with nodal constraint equations. These are included in the model input file generated by this example. 71 | 72 | Layered solid model (homogenized sandwich panels) 73 | ================================================== 74 | 75 | This capability does not yet exist. 76 | 77 | 78 | -------------------------------------------------------------------------------- /examples/ansys_example.py: -------------------------------------------------------------------------------- 1 | import pynumad 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | import json 6 | from pynumad.utils.misc_utils import setup_logging 7 | from pynumad.mesh_gen.mesh_gen import shell_mesh_general 8 | from pynumad.analysis.ansys.write import write_ansys_shell_model 9 | from pynumad.analysis.ansys.run import call_ansys 10 | from pynumad.utils.distributed_loading import loads_table_coordinate_trans 11 | from pynumad.analysis.ansys.main_ansys_analysis import main_ansys_analysis 12 | 13 | 14 | 15 | 16 | 17 | 18 | blade=pynumad.Blade() 19 | yamlName='myBlade_Modified' 20 | blade.read_yaml('example_data/'+yamlName+'.yaml') 21 | 22 | log=setup_logging(yamlName+'_ansys') 23 | 24 | 25 | # create a shell model in ansys w/o adhesive 26 | elementSize=0.45 27 | mesh_data=shell_mesh_general(blade, forSolid=False, includeAdhesive=False, elementSize=elementSize) 28 | 29 | config = dict() 30 | config["elementType"] = '181' 31 | config['blade_name'] = yamlName 32 | filename=write_ansys_shell_model(blade, mesh_data, config) 33 | 34 | call_ansys(filename,log) 35 | 36 | 37 | # Load a previously built loads_table 38 | with open('myBlade_loadsTable.json','r') as fid: 39 | loads_table = [json.load(fid)] 40 | 41 | loads_table=loads_table_coordinate_trans(loads_table) 42 | 43 | # Set up configuration for deflection run 44 | 45 | analysis_config = dict() 46 | analysis_config['meshFile'] = 'master.db' 47 | analysis_config['analysisFileName'] = 'bladeAnalysis' 48 | analysis_config['np'] = 2 49 | analysis_config['analysisFlags'] = dict() 50 | analysis_config['analysisFlags']['mass'] = True 51 | analysis_config['analysisFlags']['deflection'] = True 52 | analysis_config['analysisFlags']['failure'] = 'smax' 53 | analysis_config['analysisFlags']['fatigue'] = 'all' 54 | analysis_config['analysisFlags']['resultants'] = True 55 | analysis_config['analysisFlags']['local_fields'] = 'all' 56 | analysis_config['analysisFlags']['globalBuckling'] = 3 57 | 58 | ansys_result = main_ansys_analysis(blade,mesh_data,loads_table,analysis_config,elementSize,log) 59 | 60 | # Plot the flapwise deflection 61 | y=ansys_result['deflection'][0][1] 62 | 63 | plt.figure() 64 | plt.plot(blade.ispan,y) 65 | 66 | 67 | print('') -------------------------------------------------------------------------------- /examples/buildIEA15InsertsRoot.py: -------------------------------------------------------------------------------- 1 | import pynumad as pynu 2 | import numpy as np 3 | import os 4 | from os.path import join 5 | 6 | from pynumad.mesh_gen.mesh_gen import get_root_mesh 7 | from pynumad.io.mesh_to_yaml import * 8 | from pynumad.analysis.abaqus.write import * 9 | 10 | ## This example builds a solid element model of the root of the IEA-15MW reference offshore wind turbine 11 | ## using a conventional bolt insert design. 12 | 13 | ## Define input yaml file and name of abaqus input file to be generated 14 | bladeYaml = 'example_data/IEA-15-240-RWT.yaml' 15 | abaqusFile = 'IEA_15_insert_root.inp' 16 | 17 | ## Read the model into a blade object 18 | blade = pynu.Blade() 19 | blade.read_yaml(bladeYaml) 20 | 21 | ## Define blade root specifications 22 | rootDiameter = blade.definition.chord[0] 23 | rtRad = 0.5*rootDiameter 24 | 25 | axisPts = [0.,1.0] ## Range of points along the spanwise axis in meters 26 | radius = [rtRad,rtRad] ## Radius corresponding to spanwise axis points 27 | boltRad = 0.024 ## Radius of a single bolt 28 | insThk = 0.01 ## Thickness of bolt insert layer 29 | adhThk = 0.01 ## Thickness of bolt adhesive layer 30 | rtThk = 3.0*(boltRad + adhThk + insThk) ## Total thickness of wall at root 31 | thickness = [rtThk,rtThk] ## Axis point thickness 32 | nIns = int(np.pi*rootDiameter/rtThk) ## Number of inserts 33 | elementSize = adhThk ## Nominal element size for the model 34 | elLayers = 10 ## Number of element layers in the spanwise direction 35 | 36 | ## Generate the root mesh 37 | insertMesh = get_root_mesh(axisPts,radius,thickness,elementSize,elLayers,config='inserts',boltRad=boltRad,insertThk=insThk,adhesiveThk=adhThk,numIns=nIns) 38 | 39 | ## Define material properties specific to root design 40 | materials = list() 41 | 42 | mat = dict() 43 | mat['name'] = 'boltMat' 44 | mat['density'] = 7800.0 45 | mat['elastic'] = {'E': [200.0e+9,200.0e+9,200.0e+9], 46 | 'nu': [0.3,0.3,0.3], 47 | 'G': [79.3e+9,79.3e+9,79.3e+9]} 48 | materials.append(mat) 49 | 50 | mat = dict() 51 | mat['name'] = 'insertMat' 52 | mat['density'] = 7800.0 53 | mat['elastic'] = {'E': [200.0e+9,200.0e+9,200.0e+9], 54 | 'nu': [0.3,0.3,0.3], 55 | 'G': [79.3e+9,79.3e+9,79.3e+9]} 56 | materials.append(mat) 57 | 58 | mat = dict() 59 | mat['name'] = 'adhesiveMat' 60 | mat['density'] = 1100.0 61 | mat['elastic'] = {'E': [4.56e+6,4.56e+6,4.56e+6], 62 | 'nu': [0.49,0.49,0.49], 63 | 'G': [1.52e+6,1.52e+6,1.52e+6]} 64 | materials.append(mat) 65 | 66 | mat = dict() 67 | mat['name'] = 'fillMat' 68 | mat['density'] = 1940.0 69 | mat['elastic'] = {'E': [28.7e+9,16.6e+9,16.7e+9], 70 | 'nu': [0.5,0.0,0.17], 71 | 'G': [8.4e+9,3.49e+9,3.49e+9]} 72 | materials.append(mat) 73 | 74 | insertMesh['materials'] = materials 75 | 76 | ## Write the Abaqus input file 77 | 78 | write_solid_general(abaqusFile,insertMesh) -------------------------------------------------------------------------------- /examples/buildIEA15TubeRoot.py: -------------------------------------------------------------------------------- 1 | import pynumad as pynu 2 | import numpy as np 3 | import os 4 | from os.path import join 5 | 6 | from pynumad.mesh_gen.mesh_gen import get_root_mesh 7 | from pynumad.io.mesh_to_yaml import * 8 | from pynumad.analysis.abaqus.write import * 9 | 10 | ## This example builds a solid element model of the root of the IEA-15MW reference offshore wind turbine 11 | ## using an imbedded solid tube insert. 12 | 13 | ## Define input yaml file and name of Abaqus input file to be generated 14 | bladeYaml = 'example_data/IEA-15-240-RWT.yaml' 15 | abaqusFile = 'IEA_15_tube_root.inp' 16 | 17 | ## Read the model into a blade object 18 | blade = pynu.Blade() 19 | blade.read_yaml(bladeYaml) 20 | 21 | ## Define blade root specifications 22 | rootDiameter = blade.definition.chord[0] 23 | rtRad = 0.5*rootDiameter 24 | 25 | tubeThk = 0.048 ## Thickness of tube insert 26 | adhThk = 0.01 ## Thickness of adhesive 27 | axisPts = [0.,1.] ## Range of points along the spanwise axis in meters 28 | radius = [rtRad,rtRad] ## Radius corresponding to spanwise axis points 29 | rtThk = 1.5*(tubeThk + 2.0*adhThk) ## Thickness of root 30 | thickness = [rtThk,rtThk] ## Axis point thickness 31 | elementSize = adhThk ## Nominal element size for the model 32 | elLayers = 10 ## Number of element layers in the spanwise direction 33 | tubeExt = 1.0 ## Length of tube extension 34 | extNE = 10 ## Number of element layer in tube extension 35 | 36 | ## Generate the root mesh 37 | tubeMesh = get_root_mesh(axisPts,radius,thickness,elementSize,elLayers,config='tube',adhesiveThk=adhThk,tubeThk=tubeThk,tubeExtend=tubeExt,extNumEls=extNE) 38 | 39 | ## Define material properties specific to root design 40 | materials = list() 41 | 42 | mat = dict() 43 | mat['name'] = 'adhesiveMat' 44 | mat['density'] = 1100.0 45 | mat['elastic'] = {'E': [4.56e+6,4.56e+6,4.56e+6], 46 | 'nu': [0.49,0.49,0.49], 47 | 'G': [1.52e+6,1.52e+6,1.52e+6]} 48 | materials.append(mat) 49 | 50 | mat = dict() 51 | mat['name'] = 'fillMat' 52 | mat['density'] = 1940.0 53 | mat['elastic'] = {'E': [28.7e+9,16.6e+9,16.7e+9], 54 | 'nu': [0.5,0.0,0.17], 55 | 'G': [8.4e+9,3.49e+9,3.49e+9]} 56 | materials.append(mat) 57 | 58 | mat = dict() 59 | mat['name'] = 'tubeMat' 60 | mat['density'] = 7800.0 61 | mat['elastic'] = {'E': [200.0e+9,200.0e+9,200.0e+9], 62 | 'nu': [0.3,0.3,0.3], 63 | 'G': [79.3e+9,79.3e+9,79.3e+9]} 64 | materials.append(mat) 65 | 66 | tubeMesh['materials'] = materials 67 | 68 | ## Write the Abaqus input file 69 | 70 | write_solid_general(abaqusFile,tubeMesh) -------------------------------------------------------------------------------- /examples/cubit_beam.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pynumad 3 | 4 | sys.path.append(pynumad.SOFTWARE_PATHS['cubit']) 5 | sys.path.append(pynumad.SOFTWARE_PATHS['cubit_enhancements']) 6 | 7 | import cubit 8 | from pynumad.analysis.cubit.make_blade import * 9 | import numpy as np 10 | 11 | from pynumad.analysis.make_models import write_beam_model 12 | import logging 13 | 14 | 15 | def get_cs_params(): 16 | cs_params = {} 17 | cs_params['nel_per_layer'] = 3 18 | cs_params['element_ar'] = 5 19 | cs_params['element_shape'] = 'quad' 20 | cs_params['element_thickness_ar'] =5 21 | 22 | cs_params['layer_transition_angle'] = 30 23 | cs_params['birds_mouth_amplitude_fraction']=1.0 24 | cs_params['minimum_layer_thickness'] = 0.001 25 | 26 | 27 | 28 | cs_params['adhesive_mat_name'] = 'Adhesive' 29 | 30 | 31 | totalStations = np.asarray(blade.ispan).size 32 | 33 | 34 | root_adhesive_thickness=0.001 35 | tip_adhesive_thickness=0.001 36 | te_adhesive_width_percent_chord=5 37 | adhesive_array=np.linspace(root_adhesive_thickness, tip_adhesive_thickness, num=totalStations) 38 | 39 | cs_params['web_fore_adhesive_thickness'] = adhesive_array 40 | cs_params['web_aft_adhesive_thickness'] = adhesive_array 41 | cs_params['le_adhesive_thickness'] = adhesive_array 42 | cs_params['te_adhesive_thickness'] = adhesive_array 43 | cs_params['web_adhesive_width']=np.zeros((totalStations,)) 44 | cs_params['te_adhesive_width']=np.zeros((totalStations,)) 45 | cs_params['max_web_imperfection_distance']=np.zeros((totalStations,)) 46 | 47 | #Spanwise varying parameters 48 | thickness_scaling=0.001 49 | geometry_scaling=thickness_scaling*1000 50 | for i_station in range(totalStations): 51 | 52 | if blade.definition.leband[i_station]!=0.0 and cs_params['le_adhesive_thickness'][i_station] > blade.definition.leband[i_station]*0.85/1000: #Adhesive thickness can't be greater than 85% of the LE band 53 | raise ValueError(f'LE adhesive thickness of {cs_params["le_adhesive_thickness"][i_station]} for station {i_station} is too large for the specified LE band width of {blade.definition.leband[i_station]/1000}.') 54 | 55 | 56 | #Adhesive width parameters 57 | cs_params['web_adhesive_width'][i_station] = 0.03 * blade.geometry.ichord[i_station] 58 | cs_params['te_adhesive_width'][i_station] = te_adhesive_width_percent_chord/100.0 * blade.geometry.ichord[i_station] * geometry_scaling 59 | 60 | cs_params['max_web_imperfection_distance'][i_station] = 0.0001 * blade.geometry.ichord[i_station] * geometry_scaling 61 | 62 | return cs_params 63 | 64 | blade=pynumad.Blade() 65 | 66 | yamlName='myBlade_Modified' 67 | blade.read_yaml('example_data/'+yamlName+'.yaml') 68 | 69 | 70 | 71 | 72 | wt_name=yamlName 73 | dir_name='.' 74 | 75 | 76 | cs_params=get_cs_params() 77 | settings={} 78 | settings['make_input_for']='VABSbeamdyn' #SM, VABS, ANBA, or None 79 | settings['export']='cubg' #cub, g, or None 80 | 81 | station_list = [] 82 | cubit_make_cross_sections(blade,wt_name,settings,cs_params,'2D',stationList=station_list,directory=dir_name) #Note an empty list for stationList will make all cross sections. 83 | 84 | #Proportional damping values for BeamDyn file. 85 | mu=[0.00257593, 0.0017469, 0.0017469, 0.0017469, 0.00257593, 0.0017469] 86 | 87 | 88 | #Set up logging 89 | log =logging.getLogger(__name__) 90 | log.setLevel(logging.DEBUG) 91 | fh=logging.FileHandler(wt_name+'driver.log',mode='w') 92 | log.addHandler(fh) 93 | 94 | #Read in a fresh new blade 95 | blade=pynumad.Blade() 96 | blade.read_yaml('example_data/'+yamlName+'.yaml') 97 | file_names=write_beam_model(wt_name,station_list,settings,blade,mu,log,directory=dir_name) -------------------------------------------------------------------------------- /examples/cubit_solid.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pynumad 3 | 4 | sys.path.append(pynumad.SOFTWARE_PATHS['cubit']) 5 | sys.path.append(pynumad.SOFTWARE_PATHS['cubit_enhancements']) 6 | 7 | import cubit 8 | from pynumad.analysis.cubit.make_blade import * 9 | import numpy as np 10 | 11 | from pynumad.analysis.make_models import write_sierra_sm_model 12 | from pynumad.analysis.make_models import write_sierra_sd_model 13 | 14 | 15 | def get_cs_params(): 16 | cs_params = {} 17 | cs_params['nel_per_layer'] = 1 18 | cs_params['element_ar'] = 5 19 | cs_params['element_shape'] = 'hex' 20 | cs_params['element_thickness_ar'] =5 21 | 22 | 23 | cs_params['layer_transition_angle'] = 30 24 | cs_params['birds_mouth_amplitude_fraction']=1.0 25 | cs_params['minimum_layer_thickness'] = 0.001 26 | 27 | 28 | 29 | cs_params['adhesive_mat_name'] = 'Adhesive' 30 | 31 | 32 | totalStations = np.asarray(blade.ispan).size 33 | 34 | 35 | root_adhesive_thickness=0.001 36 | tip_adhesive_thickness=0.001 37 | te_adhesive_width_percent_chord=5 38 | adhesive_array=np.linspace(root_adhesive_thickness, tip_adhesive_thickness, num=totalStations) 39 | 40 | cs_params['web_fore_adhesive_thickness'] = adhesive_array 41 | cs_params['web_aft_adhesive_thickness'] = adhesive_array 42 | cs_params['le_adhesive_thickness'] = adhesive_array 43 | cs_params['te_adhesive_thickness'] = adhesive_array 44 | cs_params['web_adhesive_width']=np.zeros((totalStations,)) 45 | cs_params['te_adhesive_width']=np.zeros((totalStations,)) 46 | cs_params['max_web_imperfection_distance']=np.zeros((totalStations,)) 47 | 48 | #Spanwise varying parameters 49 | thickness_scaling=0.001 50 | geometry_scaling=thickness_scaling*1000 51 | for i_station in range(totalStations): 52 | 53 | if blade.definition.leband[i_station]!=0.0 and cs_params['le_adhesive_thickness'][i_station] > blade.definition.leband[i_station]*0.85/1000: #Adhesive thickness can't be greater than 85% of the LE band 54 | raise ValueError(f'LE adhesive thickness of {cs_params["le_adhesive_thickness"][i_station]} for station {i_station} is too large for the specified LE band width of {blade.definition.leband[i_station]/1000}.') 55 | 56 | 57 | #Adhesive width parameters 58 | cs_params['web_adhesive_width'][i_station] = 0.03 * blade.geometry.ichord[i_station] 59 | cs_params['te_adhesive_width'][i_station] = te_adhesive_width_percent_chord/100.0 * blade.geometry.ichord[i_station] * geometry_scaling 60 | 61 | cs_params['max_web_imperfection_distance'][i_station] = 0.0001 * blade.geometry.ichord[i_station] * geometry_scaling 62 | 63 | return cs_params 64 | 65 | blade=pynumad.Blade() 66 | 67 | yamlName='myBlade_Modified' 68 | blade.read_yaml('example_data/'+yamlName+'.yaml') 69 | 70 | 71 | wt_name=yamlName 72 | dirName='.' 73 | 74 | 75 | cs_params=get_cs_params() 76 | settings={} 77 | settings['make_input_for']='SmSd' #SM, VABS, ANBA, or None 78 | settings['export']='cubg' #cub, g, or None 79 | 80 | 81 | #Make Cubit Geometry 82 | station_list = [2,3] 83 | materials_used=cubit_make_solid_blade(blade, wt_name, settings, cs_params, stationList=station_list) 84 | 85 | #Compute material orientation 86 | orientation_data=compute_material_orientations(cs_params['element_shape'],ncpus = 1) 87 | 88 | #assign material orientation in Cubit 89 | assign_material_orientations(orientation_data) 90 | 91 | #Export mesh in Genisis format 92 | cubit.cmd(f'export mesh "{wt_name}.g" overwrite') 93 | 94 | 95 | #Write Sierra input file 96 | from pynumad.paths import SOFTWARE_PATHS 97 | template_path=SOFTWARE_PATHS['pynumad']+'src/data/templates/' 98 | 99 | write_sierra_sm_model(template_path+'sm.i.template',wt_name,station_list,blade,materials_used,'.') 100 | 101 | write_sierra_sd_model(template_path+'sd.i.template',wt_name,station_list,blade,materials_used,'.') -------------------------------------------------------------------------------- /examples/example_data/blade.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/examples/example_data/blade.xlsx -------------------------------------------------------------------------------- /examples/get_damping_factors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: evaande 4 | """ 5 | 6 | ## Note: 7 | ## This script is meant to be run after the accompanying example, 'write_abaqus_shell_model.py', 8 | ## and after running the resulting modal analysis execution script in Abaqus FEA software. 9 | ## The blade model/mesh yaml file 'BAR0.yaml' and the Abaqus results yaml 'BAR0DampingInp.yaml' 10 | ## produced by those steps are needed inputs for the stuctural damping loss factor calculations 11 | ## performed below. 12 | 13 | import pynumad as pynu 14 | import yaml 15 | from yaml import CLoader as Loader 16 | from pynumad.analysis.abaqus.read import * 17 | from pynumad.utils.damping import * 18 | from pynumad.io.mesh_to_yaml import * 19 | 20 | modelYaml = 'BAR0.yaml' 21 | abqResYaml = 'BAR0DampingInp.yaml' 22 | 23 | ## Define material loss factors for every relevant material in the blade 24 | mat_lf = {'Gelcoat': [1.,1.,1.,1.,1.,1.], 25 | 'Adhesive': [0.3,0.3,0.3,0.3,0.3,0.3], 26 | 'glass_biax': [0.8,0.8,0.5,0.3,0.3,0.3], 27 | 'glass_triax': [0.85,0.85,0.5,0.4,0.4,0.4], 28 | 'glass_uni': [0.9,0.5,0.5,0.3,0.3,0.3], 29 | 'medium_demsity_foam': [0.3,0.3,0.3,0.3,0.3,0.3]} 30 | 31 | print('reading model yaml...') 32 | ## Read in model/mesh data 33 | 34 | model_data = yaml_to_mesh(modelYaml) 35 | 36 | print('constructing damping input...') 37 | 38 | ## Extract results from abaqus output 39 | damp_inp = build_damping_input(abqResYaml, mat_lf, model_data) 40 | 41 | print('getting structural damping factors...') 42 | 43 | ## Get the structural loss factors 44 | 45 | struct_lf = get_modal_loss_factors(damp_inp) 46 | 47 | print('structural damping factors:') 48 | print(struct_lf) -------------------------------------------------------------------------------- /examples/gettingstarted.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 14, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from os.path import join\n", 10 | "from pynumad import Blade" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 15, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "excel_path = join(\"example_data\",\"blade.xlsx\")\n", 20 | "yaml_path = join(\"example_data\",\"blade.yaml\")" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 16, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "data": { 30 | "text/plain": [ 31 | "" 32 | ] 33 | }, 34 | "execution_count": 16, 35 | "metadata": {}, 36 | "output_type": "execute_result" 37 | } 38 | ], 39 | "source": [ 40 | "excel_blade = Blade()\n", 41 | "excel_blade.read_excel(excel_path)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 17, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "data": { 51 | "text/plain": [ 52 | "array([3.386 , 3.386 , 3.386 , 4.557 , 4.652 ,\n", 53 | " 4.50603872, 4.249 , 4.007 , 3.748 , 3.502 ,\n", 54 | " 3.256 , 3.01 , 2.764 , 2.518 , 2.313 ,\n", 55 | " 2.086 , 1.419 , 1.0855 ])" 56 | ] 57 | }, 58 | "execution_count": 17, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | } 62 | ], 63 | "source": [ 64 | "excel_blade.definition.chord" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 18, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "yaml_blade = Blade(yaml_path)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 19, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/plain": [ 84 | "array([4.5 , 4.51653455, 4.59255455, 4.72026865, 4.87845037,\n", 85 | " 5.04235972, 5.18880397, 5.29555399, 5.33889615, 5.27996547,\n", 86 | " 5.13049713, 4.93201208, 4.72694825, 4.52910314, 4.30815493,\n", 87 | " 4.07109567, 3.83078655, 3.59377787, 3.3535724 , 3.10972681,\n", 88 | " 2.86394905, 2.61780457, 2.37255549, 2.12536608, 1.87360817,\n", 89 | " 1.6133977 , 1.34551447, 1.07050448, 0.78813172, 0.5 ])" 90 | ] 91 | }, 92 | "execution_count": 19, 93 | "metadata": {}, 94 | "output_type": "execute_result" 95 | } 96 | ], 97 | "source": [ 98 | "yaml_blade.definition.chord" 99 | ] 100 | } 101 | ], 102 | "metadata": { 103 | "kernelspec": { 104 | "display_name": "pynumad_dev", 105 | "language": "python", 106 | "name": "python3" 107 | }, 108 | "language_info": { 109 | "codemirror_mode": { 110 | "name": "ipython", 111 | "version": 3 112 | }, 113 | "file_extension": ".py", 114 | "mimetype": "text/x-python", 115 | "name": "python", 116 | "nbconvert_exporter": "python", 117 | "pygments_lexer": "ipython3", 118 | "version": "3.11.4" 119 | }, 120 | "orig_nbformat": 4, 121 | "vscode": { 122 | "interpreter": { 123 | "hash": "ba674c09f1f0848b2fbf056d887b4fc7c439e19ef790e5a5121ff7e500d5b97d" 124 | } 125 | } 126 | }, 127 | "nbformat": 4, 128 | "nbformat_minor": 2 129 | } 130 | -------------------------------------------------------------------------------- /examples/myBlade_loadsTable.json: -------------------------------------------------------------------------------- 1 | {"rBlade": [2.5, 7.5, 12.5, 17.5, 22.5, 27.5, 32.5, 37.5, 42.5, 47.5, 52.5, 57.5, 62.5, 67.5, 72.5, 77.5, 82.5, 87.5, 92.5, 97.5], "Fxb": [136110.0, -129290.0, 138140.0, -97060.0, 134640.0, -57130.0, 126980.0, -61170.0, 116710.0, -33800.0, 102780.0, -1900.0, 83630.0, 14820.0, 57140.0, 38850.0, 22370.0, 62310.0, 670.0, 700.0], "Fyb": [333820.0, -61550.00000000001, 235639.99999999997, 21210.0, 118839.99999999999, -200139.99999999997, 98070.0, 65730.0, 37950.0, 52759.99999999999, -11560.0, 40690.0, -29500.0, 33200.0, -7720.000000000001, 15870.0, -11560.0, -6350.0, -14940.0, 8110.000000000001], "Fzb": [1008300.0, 985600.0, 946300.0, 895900.0, 837100.0, 770800.0, 699500.0, 624500.0, 549600.0, 474800.0, 401800.0, 331600.0, 267000.0, 207400.0, 154400.0, 107400.0, 69300.0, 37900.0, 17900.0, 3800.0], "Mxb": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Myb": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Mzb": [391100.0, 248970.0, 199030.0, 177620.0, 149070.0, 114450.00000000001, 74350.0, 27250.000000000004, -12750.0, -48040.0, -64720.0, -72200.0, -73190.0, -59870.0, -43390.0, -24650.0, -15320.0, -14680.000000000002, -13890.0, -8950.0], "Alpha": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "prebend": [0.0112, 0.063, 0.1495, 0.2447, 0.3227, 0.3582, 0.3333, 0.2726, 0.188, 0.0609, -0.1235, -0.3422, -0.6135, -0.9416, -1.2977, -1.705, -2.1473, -2.6275, -3.1457, -3.708], "presweep": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} -------------------------------------------------------------------------------- /examples/readyaml_buildmesh.py: -------------------------------------------------------------------------------- 1 | import pynumad as pynu 2 | import numpy as np 3 | import pickle 4 | from pprint import pprint 5 | from os.path import join 6 | 7 | from pynumad.mesh_gen.mesh_gen import get_shell_mesh 8 | 9 | blade = pynu.Blade() 10 | file_name = join("example_data","blade.yaml") 11 | blade.read_yaml(file_name) 12 | 13 | elementSize = 0.2 14 | adhes = 1 15 | 16 | meshData = get_shell_mesh(blade, adhes, elementSize) 17 | 18 | # Print first 10 nodes coordinates 19 | pprint(meshData['nodes'][:10,:]) 20 | 21 | # Print first 10 element connectivities 22 | pprint(meshData['elements'][:10,:]) 23 | -------------------------------------------------------------------------------- /examples/write_abaqus_shell_model.py: -------------------------------------------------------------------------------- 1 | import pynumad as pynu 2 | import numpy as np 3 | import os 4 | from os.path import join 5 | 6 | from pynumad.mesh_gen.mesh_gen import get_shell_mesh 7 | from pynumad.io.mesh_to_yaml import * 8 | from pynumad.analysis.abaqus.write import * 9 | 10 | ## Define inputs 11 | bladeYaml = join("example_data","blade.yaml") 12 | meshYaml = "BAR0.yaml" 13 | abqFileName = "BAR0.inp" 14 | abqScriptName = "runModalAnalysis.py" 15 | dampFileName = "BAR0DampingInp.yaml" 16 | adhesiveMat = "Adhesive" 17 | 18 | ## Read blade data from yaml file 19 | blade = pynu.Blade() 20 | blade.read_yaml(bladeYaml) 21 | 22 | ## Set the airfoil point resolution 23 | for stat in blade.definition.stations: 24 | stat.airfoil.resample(n_samples=300) 25 | 26 | #blade.generate_geometry() 27 | blade.update_blade() 28 | nStations = blade.geometry.coordinates.shape[2] 29 | minTELengths = 0.001*np.ones(nStations) 30 | blade.expand_blade_geometry_te(minTELengths) 31 | 32 | ## Set the target element size for the mesh 33 | elementSize = 0.2 34 | 35 | ## Generate mesh 36 | adhes = 1 37 | bladeMesh = get_shell_mesh(blade, adhes, elementSize) 38 | 39 | ## Add a section to define the adhesive line 40 | sec = dict() 41 | sec['type'] = 'solid' 42 | sec['elementSet'] = 'adhesiveElSet' 43 | sec['material'] = adhesiveMat 44 | bladeMesh['adhesiveSection'] = sec 45 | 46 | mesh_to_yaml(bladeMesh,meshYaml) 47 | 48 | ## Write Abaqus input 49 | 50 | numModes = 3 51 | write_shell_modal_input(abqFileName, blade, bladeMesh, adhesiveMat, numModes) 52 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import nox 2 | 3 | @nox.session 4 | def tests(session): 5 | """Run tests.""" 6 | session.install(".") 7 | session.install("pytest") 8 | session.run("pytest") 9 | 10 | @nox.session 11 | def lint(session): 12 | """Lint.""" 13 | session.install("flake8") 14 | session.run("flake8", "--import-order-style", "google") 15 | 16 | @nox.session 17 | def docs(session): 18 | """Generate documentation.""" 19 | session.run("pip", "install", "-e", ".") 20 | session.install("sphinx") 21 | session.install("pydata-sphinx-theme") 22 | session.install("sphinxcontrib-bibtex") 23 | session.cd("docs/") 24 | session.run("make", "html") 25 | 26 | @nox.session 27 | def serve(session): 28 | """Serve documentation. Port can be specified as a positional argument.""" 29 | try: 30 | port = session.posargs[0] 31 | except IndexError: 32 | port = "8085" 33 | session.run("python", "-m", "http.server", "-b", "localhost", "-d", "docs/_build/html", port) 34 | 35 | @nox.session 36 | def check_style(session): 37 | """Check if code follows black style.""" 38 | session.install("black") 39 | session.run("black", "--check", "src") 40 | 41 | @nox.session 42 | def enforce_style(session): 43 | """Apply black style to code base.""" 44 | session.install("black") 45 | session.run("black", "src") 46 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pyNuMAD" 7 | description = "Numerical Manufacturing and Design Tool" 8 | readme = "README.md" 9 | version = "1.0.0" 10 | authors = [ 11 | {name = "Kirk Bonney", email = "klbonne@sandia.gov"}, 12 | {name = "Ernesto Camarena", email = "ecamare@sandia.gov"}, 13 | {name = "Evan Anderson", email = "evaande@sandia.gov"}, 14 | ] 15 | maintainers = [ 16 | {name = "Kirk Bonney", email = "klbonne@sandia.gov"}, 17 | {name = "Ernesto Camarena", email = "ecamare@sandia.gov"}, 18 | {name = "Evan Anderson", email = "evaande@sandia.gov"}, 19 | ] 20 | requires-python = ">=3.8" 21 | 22 | dependencies = [ 23 | "numpy", 24 | "scipy", 25 | "pandas", 26 | "matplotlib", 27 | "pyyaml", 28 | "plotly", 29 | "openpyxl", 30 | ] 31 | 32 | # [project.optional-dependencies] 33 | # test = [ 34 | # "pytest >=6.0", 35 | # ] 36 | 37 | classifiers = [ 38 | "License :: OSI Approved :: BSD License", 39 | "Programming Language :: Python :: 3 :: Only", 40 | "Programming Language :: Python :: 3.8", 41 | "Programming Language :: Python :: 3.9", 42 | "Programming Language :: Python :: 3.10", 43 | "Programming Language :: Python :: 3.11", 44 | "Programming Language :: Python :: 3.12", 45 | ] 46 | 47 | [project.urls] 48 | Homepage = "https://github.com/sandialabs/pynumad" 49 | Documentation = "https://sandialabs.github.io/pyNuMAD/" 50 | "Bug Tracker" = "https://github.com/sandialabs/pyNuMAD/issues" 51 | Changelog = "https://sandialabs.github.io/pyNuMAD/release-notes.html" 52 | 53 | [tool.black] 54 | line-length = 140 55 | 56 | [tool.setuptools] 57 | include-package-data = true 58 | 59 | [tool.setuptools.packages.find] 60 | where = ["src"] -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/.DS_Store -------------------------------------------------------------------------------- /src/pynumad/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/.DS_Store -------------------------------------------------------------------------------- /src/pynumad/__init__.py: -------------------------------------------------------------------------------- 1 | from pynumad.objects.blade import Blade 2 | from pynumad.objects.airfoil import Airfoil 3 | from pynumad.objects.component import Component 4 | from pynumad.objects.material import Material 5 | from pynumad.objects.station import Station 6 | from pynumad.io.mesh_to_yaml import mesh_to_yaml 7 | 8 | from pynumad import mesh_gen 9 | from pynumad import utils 10 | from pynumad import analysis 11 | from pynumad import graphics 12 | 13 | from pynumad.paths import SOFTWARE_PATHS, DATA_PATH, set_path 14 | 15 | 16 | __version__ = "1.0.0" 17 | 18 | __copyright__ = """Copyright 2023 National Technology & Engineering 19 | Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 20 | with NTESS, the U.S. Government retains certain rights in this software.""" 21 | 22 | __license__ = "Revised BSD License" 23 | -------------------------------------------------------------------------------- /src/pynumad/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from pynumad.analysis import abaqus 2 | #from pynumad.analysis import ansys 3 | #from pynumad.analysis import cubit 4 | #from pynumad.analysis.cubit.make_blade import cubit_make_cross_sections,cubit_make_solid_blade -------------------------------------------------------------------------------- /src/pynumad/analysis/abaqus/read.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Dec 11 15:00:28 2023 4 | 5 | @author: evaande 6 | """ 7 | import csv 8 | import yaml 9 | from yaml import CLoader as Loader 10 | from pynumad.mesh_gen.mesh_tools import get_element_volumes 11 | 12 | ## read_csv_model_output will likely be phased out, but currently remains for reference 13 | def read_csv_modal_output(fileName): 14 | outData = dict() 15 | outData['modes'] = dict() 16 | inFile = open(fileName,'r') 17 | reader = csv.reader(inFile) 18 | colHd = ['Frame', 'Instance Name', 'Element Label', 'IntPt', 'Section Point', 19 | 'E-E11', 'E-E22', 'E-E33', 'E-E12', 'E-E13', 'E-E23', 20 | 'S-S11', 'S-S22', 'S-S33', 'S-S12', 'S-S13', 'S-S23'] 21 | colInd = dict() 22 | for hd in colHd: 23 | colInd[hd] = -1 24 | Est = 5 25 | Sst = 11 26 | for row in reader: 27 | if(colInd['Element Label'] == -1): 28 | for j, hd in enumerate(row): 29 | sthd = hd.strip() 30 | if(sthd in colInd): 31 | colInd[sthd] = j 32 | else: 33 | newEnt = dict() 34 | newEnt['label'] = int(row[colInd['Element Label']]) 35 | j = colInd['Instance Name'] 36 | if(j > -1): 37 | newEnt['part'] = row[j] 38 | j = colInd['IntPt'] 39 | if(j > -1): 40 | newEnt['intPt'] = int(row[j]) 41 | spStr = row[colInd['Section Point']] 42 | if('Layer =' in spStr): 43 | sLst = spStr.split('Layer =') 44 | newEnt['layer'] = int(sLst[1]) 45 | stress = [0.,0.,0.,0.,0.,0.] 46 | strain = [0.,0.,0.,0.,0.,0.] 47 | for i in range(0,6): 48 | j = colInd[colHd[Est+i]] 49 | if(j > -1): 50 | strain[i] = float(row[j]) 51 | j = colInd[colHd[Sst+i]] 52 | if(j > -1): 53 | strain[i] = float(row[j]) 54 | newEnt['stress'] = stress 55 | newEnt['strain'] = strain 56 | mdStr = 'mode_unkn' 57 | j = colInd['Frame'] 58 | if(j > -1): 59 | frmStr = row[j] 60 | if('Mode' in frmStr): 61 | lst1 = frmStr.split('Mode') 62 | lst2 = lst1[1].split(':') 63 | mdStr = 'mode_' + lst2[0].strip() 64 | try: 65 | outData['modes'][mdStr].append(newEnt) 66 | except: 67 | outData['modes'][mdStr] = [newEnt] 68 | inFile.close() 69 | return outData 70 | 71 | def build_damping_input(abq_results,material_loss_factors,meshData): 72 | inFile = open(abq_results,'r') 73 | dampInp = yaml.load(inFile, Loader=Loader) 74 | inFile.close() 75 | modes = dampInp['modes'] 76 | bladeEV = get_element_volumes(meshData) 77 | try: 78 | adMeshData = dict() 79 | adMeshData['nodes'] = meshData['adhesiveNds'] 80 | adMeshData['elements'] = meshData['adhesiveEls'] 81 | es = dict() 82 | es['name'] = 'adhesiveEls' 83 | numAdEl = len(meshData['adhesiveEls']) 84 | es['labels'] = list(range(0,numAdEl)) 85 | elSets = [es] 86 | sets = dict() 87 | sets['element'] = elSets 88 | adMeshData['sets'] = sets 89 | adMeshData['sections'] = [meshData['adhesiveSection']] 90 | adEV = get_element_volumes(adMeshData) 91 | except: 92 | pass 93 | for md in modes: 94 | thisMd = modes[md] 95 | volume = list() 96 | material = list() 97 | for i, lab in enumerate(thisMd['label']): 98 | labSt = str(lab) 99 | part = thisMd['part'][i] 100 | if('adhesive' in part.lower()): 101 | volume.append(adEV['elVols'][labSt]) 102 | material.append(adEV['elMats'][labSt]) 103 | else: 104 | lay = thisMd['layer'][i] - 1 105 | if(lay >= 0): 106 | volume.append(bladeEV['elVols'][labSt][lay]) 107 | material.append(bladeEV['elMats'][labSt][lay]) 108 | else: 109 | volume.append(bladeEV['elVols'][labSt]) 110 | material.append(bladeEV['elMats'][labSt]) 111 | modes[md]['volume'] = volume 112 | modes[md]['material'] = material 113 | dampInp['mat_loss_factors'] = material_loss_factors 114 | return dampInp -------------------------------------------------------------------------------- /src/pynumad/analysis/ansys/run.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | from pynumad.paths import SOFTWARE_PATHS 4 | import glob,os 5 | def call_ansys(script_name,log=False,script_out='ansys.out',ncpus=1): 6 | for f in glob.glob("*.lock"): 7 | os.remove(f) 8 | ansys_path=SOFTWARE_PATHS['ansys'] 9 | MAXnLicenceTries=100 10 | try: 11 | 12 | this_cmd = f'export KMP_STACKSIZE=2048k & {ansys_path} -b -p ANSYS -I {script_name} -o {script_out} -np {str(ncpus)}' 13 | if log: 14 | log.info(f' running: {this_cmd}') 15 | 16 | licenseAvailable=False 17 | nLicenceTries=0 18 | while not licenseAvailable and nLicenceTries <=MAXnLicenceTries-1: 19 | subprocess.run(this_cmd, shell=True, check=True, capture_output=True) 20 | 21 | with open(script_out, 'r') as f: 22 | lines = f.readlines() 23 | if 'lapsed time' in ''.join(lines): 24 | licenseAvailable=True 25 | if log: 26 | log.info(f' Complete: {this_cmd}') 27 | # #log the last line of .ech file: 28 | 29 | # if 'Congratulations! No errors' in lines[-1]: 30 | # log.info(f'****************************\n{lines[-1]}\n******************************') 31 | # licenseAvailable=True 32 | # nLicenceTries=0 33 | # elif 'license' in lines[-1].lower(): 34 | # nLicenceTries+=1 35 | # log.info(f'****************************\nnLicenceTries: {nLicenceTries}, {lines[-1]}\n******************************') 36 | 37 | # else: 38 | # log.error(f'****************************\n{lines[-1]}\n******************************') 39 | # raise Exception(f'Cross-sectional homogenization for file {filePath} failed due to: \n {lines[-1]} \n Beam model creation failed.') 40 | if nLicenceTries ==MAXnLicenceTries: 41 | string=f'License failed to be obtained after {MAXnLicenceTries} tries. ANSYS model creation failed.' 42 | if log: 43 | log.error(string) 44 | raise Exception(string) 45 | 46 | except subprocess.CalledProcessError as e: 47 | with open(script_out, 'r') as f: 48 | lines = f.readlines() 49 | if log: 50 | log.error(''.join(lines)) 51 | log.error(f'Error running {this_cmd}: {e}') 52 | 53 | -------------------------------------------------------------------------------- /src/pynumad/analysis/cubit/__init__.py: -------------------------------------------------------------------------------- 1 | #from pynumad.analysis import ansys 2 | #from pynumad.analysis import cubit 3 | #from .make_blade import cubit_make_cross_sections,cubit_make_solid_blade -------------------------------------------------------------------------------- /src/pynumad/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/data/__init__.py -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/DU91-W-250.txt: -------------------------------------------------------------------------------- 1 | Points generated by BRR for NuMAD, 6/2/2011 2 | 3 | 1.000000 0.000000 4 | 0.996600 -0.001489 5 | 0.982350 0.001083 6 | 0.967060 0.003666 7 | 0.950720 0.006059 8 | 0.933330 0.008039 9 | 0.914900 0.009364 10 | 0.895420 0.009853 11 | 0.874900 0.009399 12 | 0.853330 0.007912 13 | 0.830720 0.005286 14 | 0.807060 0.001409 15 | 0.782350 -0.003807 16 | 0.756600 -0.010378 17 | 0.730000 -0.018173 18 | 0.703330 -0.026764 19 | 0.676670 -0.035883 20 | 0.650000 -0.045300 21 | 0.623330 -0.054782 22 | 0.596670 -0.064101 23 | 0.570000 -0.073089 24 | 0.543330 -0.081615 25 | 0.516670 -0.089564 26 | 0.490000 -0.096818 27 | 0.463330 -0.103264 28 | 0.436670 -0.108814 29 | 0.410001 -0.113421 30 | 0.383330 -0.117082 31 | 0.356670 -0.119809 32 | 0.330000 -0.121574 33 | 0.303330 -0.122305 34 | 0.276670 -0.121948 35 | 0.250000 -0.120452 36 | 0.224210 -0.117887 37 | 0.199820 -0.114407 38 | 0.176840 -0.110114 39 | 0.155259 -0.105105 40 | 0.135080 -0.099506 41 | 0.116310 -0.093415 42 | 0.098930 -0.086924 43 | 0.082970 -0.080148 44 | 0.068400 -0.073160 45 | 0.055240 -0.065996 46 | 0.043480 -0.058651 47 | 0.033120 -0.051119 48 | 0.024170 -0.043449 49 | 0.016620 -0.035805 50 | 0.010470 -0.028351 51 | 0.005720 -0.021038 52 | 0.002380 -0.013586 53 | 0.000440 -0.005719 54 | 0.000180 -0.003603 55 | 0.000000 0.000000 56 | 0.001240 0.009445 57 | 0.003880 0.015569 58 | 0.007920 0.021609 59 | 0.013370 0.027973 60 | 0.020220 0.034622 61 | 0.028470 0.041508 62 | 0.038120 0.048561 63 | 0.049180 0.055726 64 | 0.061640 0.062932 65 | 0.075510 0.070134 66 | 0.090770 0.077252 67 | 0.107450 0.084235 68 | 0.125520 0.091008 69 | 0.144990 0.097509 70 | 0.165870 0.103672 71 | 0.188160 0.109419 72 | 0.211840 0.114648 73 | 0.236930 0.119261 74 | 0.263330 0.123120 75 | 0.290000 0.126000 76 | 0.316670 0.127850 77 | 0.343330 0.128618 78 | 0.370000 0.128276 79 | 0.396670 0.126985 80 | 0.423330 0.124971 81 | 0.450000 0.122304 82 | 0.476670 0.119044 83 | 0.503330 0.115302 84 | 0.530000 0.111124 85 | 0.556670 0.106549 86 | 0.583330 0.101632 87 | 0.610000 0.096418 88 | 0.636670 0.090934 89 | 0.663330 0.085217 90 | 0.690000 0.079299 91 | 0.716670 0.073216 92 | 0.743330 0.067014 93 | 0.769610 0.060782 94 | 0.794840 0.054682 95 | 0.819020 0.048765 96 | 0.842160 0.043060 97 | 0.864250 0.037558 98 | 0.885290 0.032274 99 | 0.905290 0.027224 100 | 0.924250 0.022410 101 | 0.942160 0.017829 102 | 0.959020 0.013465 103 | 0.974840 0.009259 104 | 0.989610 0.005151 105 | 0.993140 0.004139 106 | 0.996600 0.003137 107 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/DU93-W-210.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.001433 5 | 0.98235 0.000525 6 | 0.96706 0.002326 7 | 0.95072 0.003923 8 | 0.93333 0.005233 9 | 0.9149 0.006132 10 | 0.89542 0.006488 11 | 0.8749 0.006195 12 | 0.85333 0.005168 13 | 0.83072 0.003326 14 | 0.80706 0.000596 15 | 0.78235 -0.003084 16 | 0.7566 -0.007721 17 | 0.73 -0.013225 18 | 0.70333 -0.019296 19 | 0.67667 -0.025743 20 | 0.65 -0.032405 21 | 0.62333 -0.039113 22 | 0.596669 -0.045706 23 | 0.57 -0.052067 24 | 0.54333 -0.058103 25 | 0.51667 -0.063728 26 | 0.49 -0.068863 27 | 0.46333 -0.07343 28 | 0.43667 -0.077361 29 | 0.41 -0.080623 30 | 0.38333 -0.083216 31 | 0.35667 -0.08515 32 | 0.33 -0.0864 33 | 0.30333 -0.086922 34 | 0.27667 -0.086667 35 | 0.25 -0.085613 36 | 0.22421 -0.0838 37 | 0.19982 -0.081337 38 | 0.17684 -0.078299 39 | 0.15526 -0.074756 40 | 0.13508 -0.070793 41 | 0.11631 -0.066483 42 | 0.09893 -0.061888 43 | 0.08297 -0.057094 44 | 0.0684 -0.052148 45 | 0.05524 -0.047078 46 | 0.04348 -0.041878 47 | 0.03312 -0.036549 48 | 0.02417 -0.031124 49 | 0.01662 -0.02571 50 | 0.01047 -0.02044 51 | 0.00572 -0.015262 52 | 0.00238 -0.009983 53 | 0.00044 -0.004434 54 | 0.00018 -0.002918 55 | 0 0 56 | 0.00124 0.009227 57 | 0.00388 0.01562 58 | 0.00792 0.021692 59 | 0.01337 0.02785 60 | 0.02022 0.034039 61 | 0.02847 0.040333 62 | 0.03812 0.046832 63 | 0.04918 0.053476 64 | 0.06164 0.060173 65 | 0.07551 0.066874 66 | 0.09077 0.073511 67 | 0.10745 0.080044 68 | 0.12552 0.086405 69 | 0.14499 0.092538 70 | 0.16587 0.098389 71 | 0.18816 0.103889 72 | 0.21184 0.108956 73 | 0.23693 0.113512 74 | 0.26333 0.11744 75 | 0.29 0.120531 76 | 0.31667 0.122749 77 | 0.34333 0.124106 78 | 0.37 0.124586 79 | 0.39667 0.124175 80 | 0.42333 0.122924 81 | 0.45 0.120889 82 | 0.47667 0.118133 83 | 0.50333 0.114704 84 | 0.529999 0.11071 85 | 0.55667 0.106266 86 | 0.58333 0.10143 87 | 0.61 0.096264 88 | 0.63667 0.090804 89 | 0.66333 0.085113 90 | 0.69 0.079235 91 | 0.71667 0.0732 92 | 0.74333 0.067056 93 | 0.76961 0.060882 94 | 0.79484 0.05484 95 | 0.81902 0.048959 96 | 0.84216 0.043257 97 | 0.86425 0.037763 98 | 0.88529 0.032498 99 | 0.90529 0.027444 100 | 0.92425 0.022599 101 | 0.94216 0.01797 102 | 0.95902 0.013536 103 | 0.97484 0.009271 104 | 0.98961 0.005156 105 | 0.99314 0.004112 106 | 0.9966 0.003036 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/DU97-W-300.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.00158 5 | 0.98235 0.001634 6 | 0.96706 0.004046 7 | 0.95072 0.005582 8 | 0.93333 0.006226 9 | 0.9149 0.005999 10 | 0.89542 0.004872 11 | 0.8749 0.002779 12 | 0.85333 -0.000368 13 | 0.83072 -0.004624 14 | 0.80706 -0.009984 15 | 0.78235 -0.016471 16 | 0.7566 -0.024047 17 | 0.73 -0.032557 18 | 0.70333 -0.041612 19 | 0.67667 -0.05106 20 | 0.65 -0.060788 21 | 0.62333 -0.070695 22 | 0.59667 -0.080696 23 | 0.57 -0.090714 24 | 0.54333 -0.100649 25 | 0.51667 -0.110402 26 | 0.49 -0.11992 27 | 0.46333 -0.129084 28 | 0.43667 -0.137718 29 | 0.41 -0.145655 30 | 0.38333 -0.152704 31 | 0.35667 -0.158553 32 | 0.33 -0.162913 33 | 0.30333 -0.165482 34 | 0.27667 -0.165916 35 | 0.25 -0.163991 36 | 0.22421 -0.159761 37 | 0.19982 -0.153713 38 | 0.17684 -0.146332 39 | 0.15526 -0.137971 40 | 0.13508 -0.128881 41 | 0.11631 -0.119233 42 | 0.09893 -0.10919 43 | 0.08297 -0.098953 44 | 0.0684 -0.08862 45 | 0.05524 -0.078307 46 | 0.04348 -0.068106 47 | 0.03312 -0.058132 48 | 0.02417 -0.048515 49 | 0.01662 -0.039286 50 | 0.01047 -0.030528 51 | 0.00572 -0.022168 52 | 0.00238 -0.014116 53 | 0.00044 -0.006006 54 | 0.00018 -0.00382 55 | 0 0 56 | 0.00124 0.010102 57 | 0.00388 0.017251 58 | 0.00792 0.024503 59 | 0.01337 0.032107 60 | 0.02022 0.039992 61 | 0.02847 0.048081 62 | 0.03812 0.056286 63 | 0.04918 0.064535 64 | 0.06164 0.072737 65 | 0.07551 0.080817 66 | 0.09077 0.088685 67 | 0.10745 0.096271 68 | 0.12552 0.103484 69 | 0.14499 0.11024 70 | 0.16587 0.11644 71 | 0.18816 0.121974 72 | 0.21184 0.126684 73 | 0.23693 0.130356 74 | 0.26333 0.132796 75 | 0.29 0.134011 76 | 0.31667 0.1342 77 | 0.34333 0.133522 78 | 0.37 0.132098 79 | 0.39667 0.130013 80 | 0.42333 0.127342 81 | 0.45 0.124153 82 | 0.47667 0.120514 83 | 0.50333 0.116476 84 | 0.529999 0.112072 85 | 0.55667 0.107342 86 | 0.58333 0.102317 87 | 0.61 0.097025 88 | 0.63667 0.091493 89 | 0.66333 0.085748 90 | 0.69 0.079812 91 | 0.71667 0.073711 92 | 0.74333 0.067472 93 | 0.76961 0.061198 94 | 0.79484 0.055075 95 | 0.81902 0.049125 96 | 0.84216 0.043373 97 | 0.86425 0.037841 98 | 0.88529 0.032546 99 | 0.90529 0.027489 100 | 0.92425 0.022675 101 | 0.94216 0.018095 102 | 0.95902 0.01371 103 | 0.97484 0.009465 104 | 0.98961 0.005364 105 | 0.99314 0.004371 106 | 0.9966 0.003402 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/DU99-W-350.txt: -------------------------------------------------------------------------------- 1 | Points generated by BRR for NuMAD, 6/2/2011 2 | 3 | 1.000000 0.000000 4 | 0.996600 -0.001900 5 | 0.982350 0.001410 6 | 0.967060 0.003580 7 | 0.950720 0.004510 8 | 0.933330 0.004250 9 | 0.914900 0.002930 10 | 0.895420 0.000610 11 | 0.874900 -0.002760 12 | 0.853330 -0.007270 13 | 0.830720 -0.012940 14 | 0.807060 -0.019740 15 | 0.782350 -0.027650 16 | 0.756600 -0.036580 17 | 0.730000 -0.046370 18 | 0.703330 -0.056610 19 | 0.676670 -0.067190 20 | 0.650000 -0.078010 21 | 0.623330 -0.089010 22 | 0.596670 -0.100140 23 | 0.570000 -0.111330 24 | 0.543330 -0.122470 25 | 0.516670 -0.133420 26 | 0.490000 -0.144100 27 | 0.463330 -0.154360 28 | 0.436670 -0.164020 29 | 0.410000 -0.172880 30 | 0.383330 -0.180740 31 | 0.356670 -0.187250 32 | 0.330000 -0.192110 33 | 0.303330 -0.194980 34 | 0.276670 -0.195520 35 | 0.250000 -0.193430 36 | 0.224210 -0.188770 37 | 0.199820 -0.182070 38 | 0.176840 -0.173870 39 | 0.155260 -0.164540 40 | 0.135080 -0.154350 41 | 0.116310 -0.143450 42 | 0.098930 -0.132050 43 | 0.082970 -0.120280 44 | 0.068400 -0.108290 45 | 0.055240 -0.096220 46 | 0.043480 -0.084220 47 | 0.033120 -0.072440 48 | 0.024170 -0.061050 49 | 0.016620 -0.049950 50 | 0.010470 -0.039110 51 | 0.005720 -0.028480 52 | 0.002380 -0.017760 53 | 0.000440 -0.007260 54 | 0.000180 -0.004610 55 | 0.000000 0.000000 56 | 0.001240 0.012400 57 | 0.003880 0.023290 58 | 0.007920 0.033430 59 | 0.013370 0.043390 60 | 0.020220 0.053440 61 | 0.028470 0.063480 62 | 0.038120 0.073450 63 | 0.049180 0.083270 64 | 0.061640 0.092840 65 | 0.075510 0.102070 66 | 0.090770 0.110870 67 | 0.107450 0.119170 68 | 0.125520 0.126890 69 | 0.144990 0.133960 70 | 0.165870 0.140250 71 | 0.188160 0.145660 72 | 0.211840 0.150020 73 | 0.236930 0.153160 74 | 0.263330 0.154860 75 | 0.290000 0.155240 76 | 0.316670 0.154570 77 | 0.343330 0.153060 78 | 0.370000 0.150910 79 | 0.396670 0.148140 80 | 0.423330 0.144770 81 | 0.450000 0.140860 82 | 0.476670 0.136480 83 | 0.503330 0.131650 84 | 0.530000 0.126390 85 | 0.556670 0.120770 86 | 0.583330 0.114830 87 | 0.610000 0.108590 88 | 0.636670 0.102100 89 | 0.663330 0.095390 90 | 0.690000 0.088490 91 | 0.716670 0.081440 92 | 0.743330 0.074260 93 | 0.769610 0.067080 94 | 0.794840 0.060130 95 | 0.819020 0.053400 96 | 0.842160 0.046940 97 | 0.864250 0.040790 98 | 0.885290 0.034950 99 | 0.905290 0.029410 100 | 0.924250 0.024190 101 | 0.942160 0.019260 102 | 0.959020 0.014560 103 | 0.974840 0.010060 104 | 0.989610 0.005780 105 | 0.993140 0.004770 106 | 0.996600 0.003780 107 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/DU99-W-405.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.00261 5 | 0.98235 0.0004 6 | 0.96706 0.00232 7 | 0.95072 0.00303 8 | 0.93333 0.00258 9 | 0.9149 0.00103 10 | 0.89542 -0.0016 11 | 0.8749 -0.00539 12 | 0.85333 -0.01045 13 | 0.83072 -0.01681 14 | 0.80706 -0.02452 15 | 0.78235 -0.03354 16 | 0.7566 -0.04382 17 | 0.73 -0.05519 18 | 0.70333 -0.06717 19 | 0.67667 -0.07958 20 | 0.65 -0.09228 21 | 0.62333 -0.10515 22 | 0.59667 -0.11804 23 | 0.57 -0.13083 24 | 0.54333 -0.14333 25 | 0.51667 -0.15537 26 | 0.49 -0.16673 27 | 0.46333 -0.17727 28 | 0.43667 -0.18683 29 | 0.41 -0.19527 30 | 0.38333 -0.20248 31 | 0.35667 -0.20827 32 | 0.33 -0.21243 33 | 0.30333 -0.21476 34 | 0.27667 -0.21501 35 | 0.25 -0.21297 36 | 0.22421 -0.2087 37 | 0.19982 -0.20257 38 | 0.17684 -0.19496 39 | 0.15526 -0.18616 40 | 0.13508 -0.17635 41 | 0.11631 -0.16567 42 | 0.09893 -0.15429 43 | 0.08297 -0.1423 44 | 0.0684 -0.12982 45 | 0.05524 -0.11698 46 | 0.04348 -0.10392 47 | 0.03312 -0.09076 48 | 0.02417 -0.07766 49 | 0.01662 -0.06446 50 | 0.01047 -0.05117 51 | 0.00572 -0.03783 52 | 0.00238 -0.02333 53 | 0.00044 -0.00905 54 | 0.00018 -0.00567 55 | 0 0 56 | 0.00124 0.01552 57 | 0.00388 0.03133 58 | 0.00792 0.04505 59 | 0.01337 0.0579 60 | 0.02022 0.0707 61 | 0.02847 0.08324 62 | 0.03812 0.09549 63 | 0.04918 0.1074 64 | 0.06164 0.11884 65 | 0.07551 0.12972 66 | 0.09077 0.13998 67 | 0.10745 0.14953 68 | 0.12552 0.15831 69 | 0.14499 0.16625 70 | 0.16587 0.17323 71 | 0.18816 0.17916 72 | 0.21184 0.18388 73 | 0.23693 0.18729 74 | 0.26333 0.18924 75 | 0.29 0.18979 76 | 0.31667 0.18914 77 | 0.34333 0.18746 78 | 0.37 0.18491 79 | 0.39667 0.18154 80 | 0.42333 0.17739 81 | 0.45 0.17253 82 | 0.47667 0.16701 83 | 0.50333 0.16088 84 | 0.53 0.15417 85 | 0.55667 0.14698 86 | 0.58333 0.13938 87 | 0.61 0.13142 88 | 0.63667 0.12318 89 | 0.66333 0.11471 90 | 0.69 0.10605 91 | 0.71667 0.09724 92 | 0.74333 0.08832 93 | 0.76961 0.07947 94 | 0.79484 0.07094 95 | 0.81902 0.06275 96 | 0.84216 0.05493 97 | 0.86425 0.04754 98 | 0.88529 0.04056 99 | 0.90529 0.034 100 | 0.92425 0.02786 101 | 0.94216 0.02212 102 | 0.95902 0.0167 103 | 0.97484 0.01158 104 | 0.98961 0.00678 105 | 0.99314 0.00565 106 | 0.9966 0.00455 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/NACA-63-214.txt: -------------------------------------------------------------------------------- 1 | Coords generated by DESIGNFOIL R6.44 on 27 Feb 2012. NACA 63-214 a=1.0. 2 | 3 | 1.000000 0.000000 4 | 0.997982 0.000177 5 | 0.991946 0.000495 6 | 0.981938 0.000759 7 | 0.968037 0.000774 8 | 0.950353 0.000344 9 | 0.929029 -0.000704 10 | 0.904240 -0.002492 11 | 0.876194 -0.005015 12 | 0.845121 -0.008307 13 | 0.811279 -0.012321 14 | 0.774946 -0.016983 15 | 0.736420 -0.022170 16 | 0.696017 -0.027715 17 | 0.654064 -0.033406 18 | 0.610903 -0.039008 19 | 0.566879 -0.044323 20 | 0.522346 -0.049153 21 | 0.477659 -0.053289 22 | 0.433173 -0.056531 23 | 0.389239 -0.058721 24 | 0.346203 -0.059741 25 | 0.304404 -0.059574 26 | 0.264173 -0.058383 27 | 0.225829 -0.056292 28 | 0.189677 -0.053416 29 | 0.156003 -0.049861 30 | 0.125078 -0.045773 31 | 0.097144 -0.041155 32 | 0.072423 -0.036124 33 | 0.051109 -0.030751 34 | 0.033375 -0.025233 35 | 0.019354 -0.019545 36 | 0.009122 -0.013431 37 | 0.002626 -0.007024 38 | 0.000000 0.000000 39 | 0.001400 0.007523 40 | 0.006949 0.014919 41 | 0.016683 0.022417 42 | 0.030390 0.029728 43 | 0.047922 0.037025 44 | 0.069129 0.044261 45 | 0.093839 0.051183 46 | 0.121851 0.057671 47 | 0.152934 0.063560 48 | 0.186834 0.068813 49 | 0.223274 0.073246 50 | 0.261958 0.076725 51 | 0.302571 0.079111 52 | 0.344780 0.080259 53 | 0.388240 0.079990 54 | 0.432594 0.078307 55 | 0.477476 0.075320 56 | 0.522518 0.071185 57 | 0.567354 0.066098 58 | 0.611618 0.060277 59 | 0.654952 0.053924 60 | 0.697008 0.047253 61 | 0.737448 0.040513 62 | 0.775951 0.033936 63 | 0.812211 0.027717 64 | 0.845941 0.022006 65 | 0.876878 0.016913 66 | 0.904777 0.012520 67 | 0.929420 0.008841 68 | 0.950616 0.005929 69 | 0.968197 0.003722 70 | 0.982025 0.002113 71 | 0.991984 0.000993 72 | 0.997993 0.000285 73 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/NACA-64-618.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 96 pts. 2 | 3 | 1.000000 0.000000 4 | 0.950000 0.005509 5 | 0.900000 0.004658 6 | 0.850000 0.000919 7 | 0.800000 -0.004648 8 | 0.750000 -0.011413 9 | 0.700000 -0.018863 10 | 0.650000 -0.026561 11 | 0.600000 -0.034208 12 | 0.550000 -0.041463 13 | 0.500000 -0.048014 14 | 0.450000 -0.053500 15 | 0.400000 -0.057379 16 | 0.350000 -0.058949 17 | 0.300000 -0.058574 18 | 0.250000 -0.056741 19 | 0.200000 -0.053388 20 | 0.150000 -0.048358 21 | 0.100000 -0.041191 22 | 0.081048 -0.037704 23 | 0.075000 -0.036468 24 | 0.055828 -0.031979 25 | 0.050000 -0.030399 26 | 0.030174 -0.023996 27 | 0.025000 -0.021994 28 | 0.016968 -0.018206 29 | 0.012500 -0.015498 30 | 0.011428 -0.014731 31 | 0.008504 -0.012363 32 | 0.007500 -0.011441 33 | 0.005000 -0.008807 34 | 0.000000 0.000000 35 | 0.001496 0.015369 36 | 0.003572 0.018949 37 | 0.005000 0.020971 38 | 0.007500 0.024033 39 | 0.008032 0.024623 40 | 0.012500 0.029082 41 | 0.019826 0.035159 42 | 0.025000 0.038957 43 | 0.050000 0.054081 44 | 0.075000 0.065750 45 | 0.100000 0.075457 46 | 0.150000 0.090959 47 | 0.200000 0.102641 48 | 0.250000 0.111307 49 | 0.300000 0.117333 50 | 0.350000 0.120868 51 | 0.400000 0.121520 52 | 0.450000 0.119070 53 | 0.500000 0.114205 54 | 0.550000 0.107377 55 | 0.600000 0.098901 56 | 0.650000 0.089020 57 | 0.700000 0.077949 58 | 0.750000 0.065906 59 | 0.800000 0.053161 60 | 0.850000 0.040021 61 | 0.900000 0.026756 62 | 0.950555 0.013428 63 | 64 | -------------------------------------------------------------------------------- /src/pynumad/data/airfoils/circular.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 0 4 | 0.999013364 -0.03139526 5 | 0.996057351 -0.062666617 6 | 0.991143625 -0.093690657 7 | 0.984291581 -0.124344944 8 | 0.975528258 -0.154508497 9 | 0.964888243 -0.184062276 10 | 0.952413526 -0.212889646 11 | 0.93815334 -0.240876837 12 | 0.922163963 -0.267913397 13 | 0.904508497 -0.293892626 14 | 0.885256621 -0.318711995 15 | 0.864484314 -0.342273553 16 | 0.842273553 -0.364484314 17 | 0.818711995 -0.385256621 18 | 0.793892626 -0.404508497 19 | 0.767913397 -0.422163963 20 | 0.740876837 -0.43815334 21 | 0.712889646 -0.452413526 22 | 0.684062276 -0.464888243 23 | 0.654508497 -0.475528258 24 | 0.624344944 -0.484291581 25 | 0.593690657 -0.491143625 26 | 0.562666617 -0.496057351 27 | 0.53139526 -0.499013364 28 | 0.5 -0.5 29 | 0.46860474 -0.499013364 30 | 0.437333383 -0.496057351 31 | 0.406309343 -0.491143625 32 | 0.375655056 -0.484291581 33 | 0.345491503 -0.475528258 34 | 0.315937724 -0.464888243 35 | 0.287110354 -0.452413526 36 | 0.259123163 -0.43815334 37 | 0.232086603 -0.422163963 38 | 0.206107374 -0.404508497 39 | 0.181288005 -0.385256621 40 | 0.157726447 -0.364484314 41 | 0.135515686 -0.342273553 42 | 0.114743379 -0.318711995 43 | 0.095491503 -0.293892626 44 | 0.077836037 -0.267913397 45 | 0.06184666 -0.240876837 46 | 0.047586474 -0.212889646 47 | 0.035111757 -0.184062276 48 | 0.024471742 -0.154508497 49 | 0.015708419 -0.124344944 50 | 0.008856375 -0.093690657 51 | 0.003942649 -0.062666617 52 | 0.000986636 -0.03139526 53 | 0.0 0.0 54 | 0.000986636 0.03139526 55 | 0.003942649 0.062666617 56 | 0.008856375 0.093690657 57 | 0.015708419 0.124344944 58 | 0.024471742 0.154508497 59 | 0.035111757 0.184062276 60 | 0.047586474 0.212889646 61 | 0.06184666 0.240876837 62 | 0.077836037 0.267913397 63 | 0.095491503 0.293892626 64 | 0.114743379 0.318711995 65 | 0.135515686 0.342273553 66 | 0.157726447 0.364484314 67 | 0.181288005 0.385256621 68 | 0.206107374 0.404508497 69 | 0.232086603 0.422163963 70 | 0.259123163 0.43815334 71 | 0.287110354 0.452413526 72 | 0.315937724 0.464888243 73 | 0.345491503 0.475528258 74 | 0.375655056 0.484291581 75 | 0.406309343 0.491143625 76 | 0.437333383 0.496057351 77 | 0.46860474 0.499013364 78 | 0.5 0.5 79 | 0.53139526 0.499013364 80 | 0.562666617 0.496057351 81 | 0.593690657 0.491143625 82 | 0.624344944 0.484291581 83 | 0.654508497 0.475528258 84 | 0.684062276 0.464888243 85 | 0.712889646 0.452413526 86 | 0.740876837 0.43815334 87 | 0.767913397 0.422163963 88 | 0.793892626 0.404508497 89 | 0.818711995 0.385256621 90 | 0.842273553 0.364484314 91 | 0.864484314 0.342273553 92 | 0.885256621 0.318711995 93 | 0.904508497 0.293892626 94 | 0.922163963 0.267913397 95 | 0.93815334 0.240876837 96 | 0.952413526 0.212889646 97 | 0.964888243 0.184062276 98 | 0.975528258 0.154508497 99 | 0.984291581 0.124344944 100 | 0.991143625 0.093690657 101 | 0.996057351 0.062666617 102 | 0.999013364 0.03139526 103 | 104 | -------------------------------------------------------------------------------- /src/pynumad/data/blades/blade.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/data/blades/blade.xlsx -------------------------------------------------------------------------------- /src/pynumad/data/templates/mat_ori.py.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | 5 | import sys 6 | # add Cubit libraries to your path 7 | 8 | import math 9 | import numpy as np 10 | 11 | import pickle 12 | 13 | import exodus 14 | 15 | e = exodus.exodus('IN_MESH', mode = 'a', array_type = 'numpy') 16 | e.summarize() 17 | 18 | with open('euler','rb') as file: 19 | eulerAngles = pickle.load(file) 20 | 21 | 22 | 23 | axis_1=1 24 | axis_2=2 25 | axis_3=3 26 | 27 | e.set_element_variable_number(6) 28 | e.put_element_variable_name('rotation_axis_one',1) 29 | e.put_element_variable_name('rotation_angle_one',2) 30 | 31 | e.put_element_variable_name('rotation_axis_two',3) 32 | e.put_element_variable_name('rotation_angle_two',4) 33 | 34 | e.put_element_variable_name('rotation_axis_three',5) 35 | e.put_element_variable_name('rotation_angle_three',6) 36 | 37 | 38 | 39 | blockNames=e.get_elem_blk_names() 40 | blockIDs=e.get_elem_blk_ids() 41 | 42 | 43 | 44 | for iBlock in blockIDs: 45 | theta1=[] 46 | theta2=[] 47 | theta3=[] 48 | 49 | 50 | _,nelem_in_block,_,_=e.elem_blk_info(iBlock) 51 | 52 | #print(e.elem_blk_info(iBlock)) 53 | for iHex in e.get_block_id_map("EX_ELEM_BLOCK",iBlock): 54 | #print(f'HexID: {iHex}, theta2: {eulerAngles[0][iHex]} theta1: {eulerAngles[1][iHex]} theta3: {eulerAngles[2][iHex]}') 55 | theta1.append(eulerAngles[0][iHex]) 56 | 57 | theta2.append(eulerAngles[1][iHex]) 58 | 59 | theta3.append(eulerAngles[2][iHex]) 60 | 61 | 62 | e.put_element_variable_values(iBlock,'rotation_axis_one',1,axis_1*np.ones(nelem_in_block)) 63 | e.put_element_variable_values(iBlock,'rotation_angle_one',1,theta1) 64 | 65 | e.put_element_variable_values(iBlock,'rotation_axis_two',1,axis_2*np.ones(nelem_in_block)) 66 | e.put_element_variable_values(iBlock,'rotation_angle_two',1,theta2) 67 | 68 | e.put_element_variable_values(iBlock,'rotation_axis_three',1,axis_3*np.ones(nelem_in_block)) 69 | e.put_element_variable_values(iBlock,'rotation_angle_three',1,theta3) 70 | 71 | e.close() 72 | 73 | -------------------------------------------------------------------------------- /src/pynumad/data/templates/sd.i.template: -------------------------------------------------------------------------------- 1 | // Sierra/SD blank input 2 | 3 | SOLUTION 4 | case 'myEigen' 5 | eigen 6 | nmodes = 10 7 | END 8 | 9 | file 10 | geometry_file 'IN_MESH' 11 | end 12 | 13 | BLADE_BLOCKS 14 | 15 | 16 | 17 | outputs 18 | disp 19 | stress 20 | eorient 21 | material_direction_1 22 | material_direction_2 23 | material_direction_3 24 | end 25 | 26 | BLADE_MATERIALS 27 | 28 | boundary 29 | nodeset root 30 | fixed 31 | nodeset tip 32 | z=-10.5 33 | end 34 | -------------------------------------------------------------------------------- /src/pynumad/graphics/__init__.py: -------------------------------------------------------------------------------- 1 | from .graphics import plot_airfoil, plot_regions, plot_geometry, plot_profile 2 | -------------------------------------------------------------------------------- /src/pynumad/graphics/graphics.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from pynumad.utils.interpolation import interpolator_wrap 4 | 5 | 6 | def plot_airfoil(airfoil): 7 | """Plot airfoil""" 8 | fig, ax = plt.subplots() 9 | # ax[0].plot(self.x,self.y,'.-') 10 | ax.plot(airfoil.coordinates[:, 0], airfoil.coordinates[:, 1], ".-", color="black") 11 | ax.plot(airfoil.c, airfoil.camber, color="red") 12 | # mtx = self.maxthick * np.array([1,1]) 13 | # kn = find(self.c >= self.maxthick,1) 14 | # mty = self.camber(kn) + self.thickness(kn) * np.array([0.5,- 0.5]) 15 | # line(mtx,mty,'LineStyle',':','Color','k') 16 | # else: 17 | fig.show() 18 | return ax 19 | 20 | 21 | def plot_regions(blade): 22 | fig, ax = plt.subplots() 23 | 24 | n = blade.keypoints.shape[0] 25 | for kn in range(n): 26 | blade.hgKeypoints[kn] = ax.plot( 27 | blade.keypoints[kn, 2, :], 28 | blade.keypoints[kn, 0, :], 29 | blade.keypoints[kn, 1, :], 30 | ) 31 | fig.show() 32 | return ax 33 | 34 | 35 | def plot_geometry(self): 36 | fig, ax = plt.subplots() 37 | n = self.geometry.shape[2] 38 | for k in range(n): 39 | self.hgGeometry[k] = ax.plot( 40 | self.geometry[:, 2, k], self.geometry[:, 0, k], self.geometry[:, 1, k] 41 | ) 42 | fig.show() 43 | return ax 44 | 45 | 46 | def plot_profile(blade, k): 47 | fig, ax = plt.subplots() 48 | ax.plot(blade.profiles[:, 0, k], blade.profiles[:, 1, k], ".-") 49 | fig.show() 50 | return ax 51 | 52 | 53 | def plot_component(component): 54 | """ 55 | TODO docstring 56 | """ 57 | fig, ax = plt.subplots() 58 | cpx, cpy = component.getcp() 59 | ax.plot(cpx, cpy) 60 | x = np.linspace(0, 1, 100) 61 | y = np.round(interpolator_wrap(cpx, cpy, x, "pchip", 0)) 62 | ax.plot(x, y) 63 | plt.title(component.name) 64 | fig.show() 65 | return ax 66 | -------------------------------------------------------------------------------- /src/pynumad/io/__init__.py: -------------------------------------------------------------------------------- 1 | from .airfoil_to_xml import airfoil_to_xml 2 | from .excel_to_blade import excel_to_blade 3 | from .mesh_to_yaml import mesh_to_yaml 4 | from .xml_to_airfoil import xml_to_airfoil 5 | from .yaml_to_blade import yaml_to_blade 6 | -------------------------------------------------------------------------------- /src/pynumad/io/airfoil_to_xml.py: -------------------------------------------------------------------------------- 1 | def airfoil_to_xml(coords, reftext, fname): 2 | """WriteNuMADAirfoil Write NuMAD airfoil files 3 | 4 | Parameters 5 | ---------- 6 | coords : array 7 | Nx2 array of airfoil coordinate data. First column contains 8 | x-values, second column contains y-values. Airfoil coordinates are in 9 | order as specified by NuMAD (i.e. trailing edge = (1,0) and leading 10 | edge = (0,0) 11 | reftext : string 12 | string representing reference text 13 | fname : string 14 | full filename, incl extension, of NuMAD airfoil file to write 15 | 16 | Returns 17 | ------- 18 | None 19 | """ 20 | with open(fname, "wt") as fid: 21 | fid.write("\n%s\n" % (reftext)) 22 | fid.write("\n" % ()) 23 | for i in range(coords.shape[0]): 24 | fid.write("%8.12f\t%8.12f\n" % tuple(coords[i, :])) 25 | fid.write("" % ()) 26 | -------------------------------------------------------------------------------- /src/pynumad/io/mesh_to_yaml.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from yaml import CLoader as Loader 3 | import numpy as np 4 | import os 5 | 6 | 7 | def mesh_to_yaml(meshData, file_name): 8 | """ 9 | TODO docstring 10 | """ 11 | mDataOut = dict() 12 | nodes = list() 13 | for nd in meshData["nodes"]: 14 | ndstr = str(list(nd)) 15 | nodes.append(ndstr) 16 | elements = list() 17 | for el in meshData["elements"]: 18 | elstr = str(list(el)) 19 | elements.append(elstr) 20 | esList = list() 21 | for es in meshData["sets"]["element"]: 22 | newSet = dict() 23 | newSet["name"] = es["name"] 24 | labels = list() 25 | for el in es["labels"]: 26 | labels.append(int(el)) 27 | newSet["labels"] = labels 28 | esList.append(newSet) 29 | nsList = list() 30 | try: 31 | for ns in meshData["sets"]["node"]: 32 | newSet = dict() 33 | newSet["name"] = ns["name"] 34 | labels = list() 35 | for nd in ns["labels"]: 36 | labels.append(int(nd)) 37 | newSet["labels"] = labels 38 | nsList.append(newSet) 39 | except: 40 | pass 41 | sections = list() 42 | for sec in meshData["sections"]: 43 | newSec = dict() 44 | newSec["type"] = sec["type"] 45 | newSec["elementSet"] = sec["elementSet"] 46 | if sec["type"] == "shell": 47 | newLayup = list() 48 | for lay in sec["layup"]: 49 | laystr = str(lay) 50 | newLayup.append(laystr) 51 | newSec["layup"] = newLayup 52 | newSec["xDir"] = str(list(sec["xDir"])) 53 | newSec["xyDir"] = str(list(sec["xyDir"])) 54 | else: 55 | newSec["material"] = sec["material"] 56 | sections.append(newSec) 57 | elOri = list() 58 | for ori in meshData["elementOrientations"]: 59 | elOri.append(str(list(ori))) 60 | 61 | mDataOut["nodes"] = nodes 62 | mDataOut["elements"] = elements 63 | mDataOut["sets"] = dict() 64 | mDataOut["sets"]["element"] = esList 65 | mDataOut["sections"] = sections 66 | mDataOut["elementOrientations"] = elOri 67 | try: 68 | adNds = list() 69 | for nd in meshData["adhesiveNds"]: 70 | ndstr = str(list(nd)) 71 | adNds.append(ndstr) 72 | adEls = list() 73 | for el in meshData["adhesiveEls"]: 74 | elstr = str(list(el)) 75 | adEls.append(elstr) 76 | mDataOut["adhesiveNds"] = adNds 77 | mDataOut["adhesiveEls"] = adEls 78 | mDataOut["adhesiveElSet"] = meshData["adhesiveElSet"] 79 | mDataOut["adhesiveSection"] = meshData["adhesiveSection"] 80 | constraints = list() 81 | for c in meshData["constraints"]: 82 | terms = list() 83 | for t in c["terms"]: 84 | newTerm = dict() 85 | newTerm["nodeSet"] = t["nodeSet"] 86 | newTerm["node"] = int(t["node"]) 87 | newTerm["coef"] = float(t["coef"]) 88 | terms.append(newTerm) 89 | newConst = dict() 90 | newConst["terms"] = terms 91 | newConst["rhs"] = c["rhs"] 92 | constraints.append(newConst) 93 | mDataOut["constraints"] = constraints 94 | except: 95 | pass 96 | 97 | try: 98 | mDataOut["materials"] = meshData["materials"] 99 | except: 100 | pass 101 | 102 | fileStr = yaml.dump(mDataOut,sort_keys=False) 103 | 104 | fileStr = fileStr.replace("'","") 105 | fileStr = fileStr.replace('"','') 106 | 107 | outStream = open(file_name,'w') 108 | outStream.write(fileStr) 109 | outStream.close() 110 | 111 | def yaml_to_mesh(fileName): 112 | inFile = open(fileName,'r') 113 | meshData = yaml.load(inFile,Loader=Loader) 114 | inFile.close() 115 | meshData['nodes'] = np.array(meshData['nodes']) 116 | meshData['elements'] = np.array(meshData['elements']) 117 | try: 118 | meshData['adhesiveNds'] = np.array(meshData['adhesiveNds']) 119 | meshData['adhesiveEls'] = np.array(meshData['adhesiveEls']) 120 | except: 121 | pass 122 | return meshData -------------------------------------------------------------------------------- /src/pynumad/io/xml_to_airfoil.py: -------------------------------------------------------------------------------- 1 | import re 2 | import numpy as np 3 | 4 | 5 | def xml_to_airfoil(airfoil, filecontents=None): 6 | """TODO docstring 7 | 8 | Parameters 9 | ---------- 10 | 11 | Returns 12 | ------- 13 | """ 14 | # DEVNOTE: this works for the kinds of af files 15 | # found in the BAR reference blades 16 | # but not for general xmls 17 | # this should be extended to be more general at some point 18 | # [coords, reference] = readAirfoilXML(filecontents) 19 | 20 | # The following regular expression pattern matches any number of characters 21 | # found between the opening and closing "reference" tags 22 | fulltext = "".join(filecontents) 23 | 24 | pattern = "(.*)" 25 | t = re.search(pattern, fulltext) 26 | reference = t.group(1) 27 | 28 | for line in filecontents: 29 | # check if there is a tag 30 | if re.search("<", line): 31 | continue 32 | # otherwise, assume coordinate data 33 | else: 34 | if re.search("\t", line): 35 | line = line.replace("\t", " ") 36 | x, y = line.split(" ") 37 | x = float(x) 38 | y = float(y) 39 | try: 40 | coords = np.append(coords, [[x, y]], axis=0) 41 | except UnboundLocalError: 42 | coords = np.array([[x, y]]) 43 | airfoil.reference = reference 44 | airfoil.coordinates = coords 45 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/__init__.py: -------------------------------------------------------------------------------- 1 | from .mesh_gen import get_shell_mesh, get_solid_mesh 2 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/boundary2d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pynumad.mesh_gen.mesh_tools as mt 3 | from pynumad.mesh_gen.segment2d import * 4 | 5 | 6 | class Boundary2D: 7 | def __init__(self, segList=[]): 8 | self.segList = list() 9 | self.segList.extend(segList) 10 | 11 | def addSegment(self, segType, keyPts, numEls): 12 | self.segList.append(Segment2D(segType, keyPts, numEls)) 13 | 14 | def getBoundaryMesh(self): 15 | allNds = list() 16 | allEds = list() 17 | totNds = 0 18 | for seg in self.segList: 19 | segMesh = seg.getNodesEdges() 20 | allNds.extend(segMesh["nodes"]) 21 | allEds.extend(segMesh["edges"] + totNds) 22 | totNds = len(allNds) 23 | allNds = np.array(allNds) 24 | allEds = np.array(allEds) 25 | 26 | meshData = dict() 27 | meshData["nodes"] = allNds 28 | meshData["elements"] = allEds 29 | 30 | output = mt.mergeDuplicateNodes(meshData) 31 | 32 | return output 33 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/segment2d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import interpolate 3 | 4 | 5 | class Segment2D: 6 | def __init__(self, segType, keyPts, numEls): 7 | self.segType = segType ## line, curve, arc 8 | self.keyPts = keyPts 9 | self.numEls = numEls 10 | 11 | def getNodesEdges(self): 12 | nNds = self.numEls + 1 13 | if self.segType == "line": 14 | pt1 = np.array(self.keyPts[0]) 15 | pt2 = np.array(self.keyPts[1]) 16 | proj = pt2 - pt1 17 | steps = (1.0 / self.numEls) * np.array(range(0, nNds)) 18 | nds = list() 19 | for st in steps: 20 | nd = pt1 + st * proj 21 | nds.append(nd) 22 | nodes = np.array(nds) 23 | eN1 = np.array(range(0, self.numEls), dtype=int) 24 | eN2 = np.array(range(1, nNds), dtype=int) 25 | edges = np.transpose(np.array([eN1, eN2])) 26 | output = dict() 27 | output["nodes"] = nodes 28 | output["edges"] = edges 29 | return output 30 | elif self.segType == "curve": 31 | kPTp = np.transpose(np.array(self.keyPts)) 32 | numKp = len(self.keyPts) 33 | pKp = (1.0 / (numKp - 1)) * np.array(range(0, numKp)) 34 | pNds = (1.0 / self.numEls) * np.array(range(0, nNds)) 35 | if numKp == 2: 36 | order = "linear" 37 | elif numKp == 3: 38 | order = "quadratic" 39 | else: 40 | order = "cubic" 41 | iFun = interpolate.interp1d( 42 | pKp, 43 | kPTp[0], 44 | order, 45 | axis=0, 46 | bounds_error=False, 47 | fill_value="extrapolate", 48 | ) 49 | xNds = iFun(pNds) 50 | iFun = interpolate.interp1d( 51 | pKp, 52 | kPTp[1], 53 | order, 54 | axis=0, 55 | bounds_error=False, 56 | fill_value="extrapolate", 57 | ) 58 | yNds = iFun(pNds) 59 | eN1 = np.array(range(0, self.numEls), dtype=int) 60 | eN2 = np.array(range(1, nNds), dtype=int) 61 | output = dict() 62 | output["nodes"] = np.transpose(np.array([xNds, yNds])) 63 | output["edges"] = np.transpose(np.array([eN1, eN2])) 64 | return output 65 | elif self.segType == "arc": 66 | kPar = np.array(self.keyPts) 67 | dist13 = np.linalg.norm(kPar[0] - kPar[2]) 68 | if dist13 < 1.0e-12: 69 | center = 0.5 * (kPar[0] + kPar[1]) 70 | rad = np.linalg.norm(kPar[0] - center) 71 | else: 72 | center = 0.3333 * (kPar[0] + kPar[1] + kPar[2]) 73 | ndc = 1.0 74 | i = 0 75 | Rvec = np.zeros(2) 76 | dRdC = np.zeros((2, 2)) 77 | while ndc > 1.0e-12 and i < 50: 78 | v1 = kPar[0] - center 79 | v2 = kPar[1] - center 80 | v3 = kPar[2] - center 81 | Rvec[0] = np.dot(v1, v1) - np.dot(v2, v2) 82 | Rvec[1] = np.dot(v1, v1) - np.dot(v3, v3) 83 | dRdC[0] = 2.0 * (v2 - v1) 84 | dRdC[1] = 2.0 * (v3 - v1) 85 | dc = np.linalg.solve(dRdC, -Rvec) 86 | center = center + dc 87 | ndc = np.linalg.norm(dc) 88 | i = i + 1 89 | rad = np.linalg.norm(kPar[0] - center) 90 | theta = np.zeros(3) 91 | for i in range(0, 3): 92 | xRel = kPar[i, 0] - center[0] 93 | yRel = kPar[i, 1] - center[1] 94 | if abs(xRel) < 1.0e-12: 95 | xRel = 1.0e-12 96 | if xRel > 0.0: 97 | theta[i] = np.arctan(yRel / xRel) 98 | else: 99 | theta[i] = np.arctan(yRel / xRel) + np.pi 100 | pi2 = 2.0*np.pi 101 | t1Rng = [theta[0]-pi2,theta[0],theta[0]+pi2] 102 | t2Rng = [theta[1]-pi2,theta[1],theta[1]+pi2] 103 | t3Rng = [theta[2]-pi2,theta[2],theta[2]+pi2] 104 | for t1 in t1Rng: 105 | for t2 in t2Rng: 106 | for t3 in t3Rng: 107 | prod = (t2-t1)*(t3-t2) 108 | arc = abs(t3-t1) 109 | if(prod > 0.0 and arc <= pi2): 110 | theta = [t1,t2,t3] 111 | thetaNds = np.linspace(theta[0],theta[2],nNds) 112 | nodes = np.zeros((nNds, 2)) 113 | for thi in range(0, nNds): 114 | nodes[thi, 0] = rad * np.cos(thetaNds[thi]) + center[0] 115 | nodes[thi, 1] = rad * np.sin(thetaNds[thi]) + center[1] 116 | eN1 = np.array(range(0, self.numEls), dtype=int) 117 | eN2 = np.array(range(1, nNds), dtype=int) 118 | output = dict() 119 | output["nodes"] = nodes 120 | output["edges"] = np.transpose(np.array([eN1, eN2])) 121 | return output 122 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/spatial_grid_list2d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class spatial_grid_list2d: 5 | def __init__(self, minimumX, maximumX, minimumY, maximumY, xGridSize, yGridSize): 6 | self.xMin = minimumX 7 | self.yMin = minimumY 8 | self.xGSz = xGridSize 9 | self.yGSz = yGridSize 10 | xLen = maximumX - minimumX 11 | yLen = maximumY - minimumY 12 | self.xRows = int(np.ceil(xLen / xGridSize)) 13 | self.yRows = int(np.ceil(yLen / yGridSize)) 14 | self.fullList = list() 15 | for i in range(0, self.xRows): 16 | xList = list() 17 | for j in range(0, self.yRows): 18 | xList.append(list()) 19 | self.fullList.append(xList) 20 | 21 | def getDim(self): 22 | return [self.xGSz*self.xRows, self.yGSz*self.yRows] 23 | 24 | def addEntry(self, val, coord): 25 | xRow = int(np.floor((coord[0] - self.xMin) / self.xGSz)) 26 | yRow = int(np.floor((coord[1] - self.yMin) / self.yGSz)) 27 | self.fullList[xRow][yRow].append(val) 28 | 29 | def findInXYMargin(self, point, Xmargin, Ymargin): 30 | if Xmargin == -1: 31 | iMax = self.xRows 32 | iMin = 0 33 | else: 34 | # outStr = 'point[0] ' + str(point[0]) + ' Xmargin ' + str(Xmargin) + ' xMin ' + str(self.xMin) + ' xGSz ' + str(self.xGSz) 35 | # print(outStr) 36 | iMax = int(np.ceil((point[0] + Xmargin - self.xMin) / self.xGSz)) 37 | if iMax > self.xRows: 38 | iMax = self.xRows 39 | iMin = int(np.floor((point[0] - Xmargin - self.xMin) / self.xGSz)) 40 | if iMin < 0: 41 | iMin = 0 42 | 43 | if Ymargin == -1: 44 | jMax = self.yRows 45 | jMin = 0 46 | else: 47 | jMax = int(np.ceil((point[1] + Ymargin - self.yMin) / self.yGSz)) 48 | if jMax > self.yRows: 49 | jMax = self.yRows 50 | jMin = int(np.floor((point[1] - Ymargin - self.yMin) / self.yGSz)) 51 | if jMin < 0: 52 | jMin = 0 53 | 54 | labelList = list() 55 | for i in range(iMin, iMax): 56 | for j in range(jMin, jMax): 57 | labelList.extend(self.fullList[i][j]) 58 | 59 | return labelList 60 | 61 | def findInRadius(self, point, radius): 62 | labelList = self.findInXYMargin(point, radius, radius) 63 | return labelList 64 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/spatial_grid_list3d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class spatial_grid_list3d: 5 | def __init__( 6 | self, 7 | minimumX, 8 | maximumX, 9 | minimumY, 10 | maximumY, 11 | minimumZ, 12 | maximumZ, 13 | xGridSize, 14 | yGridSize, 15 | zGridSize, 16 | ): 17 | self.xMin = minimumX 18 | self.yMin = minimumY 19 | self.zMin = minimumZ 20 | self.xGSz = xGridSize 21 | self.yGSz = yGridSize 22 | self.zGSz = zGridSize 23 | xLen = maximumX - minimumX 24 | yLen = maximumY - minimumY 25 | zLen = maximumZ - minimumZ 26 | self.xRows = int(np.ceil(xLen / xGridSize)) 27 | self.yRows = int(np.ceil(yLen / yGridSize)) 28 | self.zRows = int(np.ceil(zLen / zGridSize)) 29 | self.fullList = list() 30 | for i in range(0, self.xRows): 31 | xList = list() 32 | for j in range(0, self.yRows): 33 | yList = list() 34 | for k in range(0, self.zRows): 35 | yList.append(list()) 36 | xList.append(yList) 37 | self.fullList.append(xList) 38 | 39 | def getDim(self): 40 | return [self.xGSz*self.xRows, self.yGSz*self.yRows, self.zGSz*self.zRows] 41 | 42 | def addEntry(self, val, coord): 43 | xRow = int(np.floor((coord[0] - self.xMin) / self.xGSz)) 44 | yRow = int(np.floor((coord[1] - self.yMin) / self.yGSz)) 45 | zRow = int(np.floor((coord[2] - self.zMin) / self.zGSz)) 46 | self.fullList[xRow][yRow][zRow].append(val) 47 | 48 | def findInXYZMargin(self, point, Xmargin, Ymargin, Zmargin): 49 | if Xmargin == -1: 50 | iMax = self.xRows 51 | iMin = 0 52 | else: 53 | iMax = int(np.ceil((point[0] + Xmargin - self.xMin) / self.xGSz)) 54 | if iMax > self.xRows: 55 | iMax = self.xRows 56 | iMin = int(np.floor((point[0] - Xmargin - self.xMin) / self.xGSz)) 57 | if iMin < 0: 58 | iMin = 0 59 | 60 | if Ymargin == -1: 61 | jMax = self.yRows 62 | jMin = 0 63 | else: 64 | jMax = int(np.ceil((point[1] + Ymargin - self.yMin) / self.yGSz)) 65 | if jMax > self.yRows: 66 | jMax = self.yRows 67 | jMin = int(np.floor((point[1] - Ymargin - self.yMin) / self.yGSz)) 68 | if jMin < 0: 69 | jMin = 0 70 | 71 | if Zmargin == -1: 72 | kMax = self.zRows 73 | kMin = 0 74 | else: 75 | kMax = int(np.ceil((point[2] + Zmargin - self.zMin) / self.zGSz)) 76 | if kMax > self.zRows: 77 | kMax = self.zRows 78 | kMin = int(np.floor((point[2] - Zmargin - self.zMin) / self.zGSz)) 79 | if kMin < 0: 80 | kMin = 0 81 | 82 | labelList = list() 83 | for i in range(iMin, iMax): 84 | for j in range(jMin, jMax): 85 | for k in range(kMin, kMax): 86 | labelList.extend(self.fullList[i][j][k]) 87 | 88 | return labelList 89 | 90 | def findInRadius(self, point, radius): 91 | labelList = self.findInXYZMargin(point, radius, radius, radius) 92 | return labelList 93 | -------------------------------------------------------------------------------- /src/pynumad/mesh_gen/surface.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pynumad.mesh_gen.mesh_tools as mt 3 | from pynumad.mesh_gen.shell_region import * 4 | 5 | 6 | class Surface: 7 | def __init__(self, regionList=[], regionNames=[], meshList=[], meshNames=[]): 8 | self.shellRegions = list() 9 | self.shellRegions.extend(regionList) 10 | self.regionNames = list() 11 | self.regionNames.extend(regionNames) 12 | self.meshes = list() 13 | self.meshes.extend(meshList) 14 | self.meshNames = list() 15 | self.meshNames.extend(meshNames) 16 | 17 | def addShellRegion( 18 | self, 19 | regType, 20 | keyPts, 21 | numEls, 22 | name=None, 23 | natSpaceCrds=[], 24 | elType="quad", 25 | meshMethod="free", 26 | ): 27 | self.shellRegions.append( 28 | ShellRegion(regType, keyPts, numEls, natSpaceCrds, elType, meshMethod) 29 | ) 30 | if name == None: 31 | numReg = len(self.shellRegions) 32 | regName = "Sub-Region_" + str(numReg) 33 | self.regionNames.append(regName) 34 | else: 35 | self.regionNames.append(name) 36 | 37 | def addMesh(self, meshData, name=None): 38 | self.meshes.append(meshData) 39 | if name == None: 40 | numMsh = len(self.meshes) 41 | meshName = "Sub-Mesh_" + str(numMsh) 42 | self.meshNames.append(meshName) 43 | else: 44 | self.meshNames.append(name) 45 | 46 | def getSurfaceMesh(self): 47 | allNds = list() 48 | allEls = list() 49 | elSetList = list() 50 | numNds = 0 51 | numEls = 0 52 | regi = 0 53 | for reg in self.shellRegions: 54 | regMesh = reg.createShellMesh() 55 | allNds.extend(regMesh["nodes"]) 56 | setList = list() 57 | eli = 0 58 | for el in regMesh["elements"]: 59 | for i in range(0, 4): 60 | if el[i] != -1: 61 | el[i] = el[i] + numNds 62 | allEls.append(el) 63 | setList.append((eli + numEls)) 64 | eli = eli + 1 65 | thisSet = dict() 66 | thisSet["name"] = self.regionNames[regi] 67 | thisSet["labels"] = setList 68 | elSetList.append(thisSet) 69 | numNds = len(allNds) 70 | numEls = len(allEls) 71 | regi = regi + 1 72 | mshi = 0 73 | for msh in self.meshes: 74 | setList = list() 75 | eli = 0 76 | for el in msh["elements"]: 77 | newEl = -1*np.ones(4,dtype=int) 78 | for i in range(0, 4): 79 | if el[i] != -1: 80 | newEl[i] = el[i] + numNds 81 | allEls.append(newEl) 82 | setList.append((eli + numEls)) 83 | eli = eli + 1 84 | thisSet = dict() 85 | thisSet["name"] = self.meshNames[mshi] 86 | thisSet["labels"] = setList 87 | elSetList.append(thisSet) 88 | allNds.extend(msh["nodes"]) 89 | numNds = len(allNds) 90 | numEls = len(allEls) 91 | mshi = mshi + 1 92 | mData = dict() 93 | mData["nodes"] = np.array(allNds) 94 | mData["elements"] = np.array(allEls) 95 | mData = mt.mergeDuplicateNodes(mData) 96 | mData["sets"] = dict() 97 | mData["sets"]["element"] = elSetList 98 | return mData 99 | -------------------------------------------------------------------------------- /src/pynumad/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/objects/__init__.py -------------------------------------------------------------------------------- /src/pynumad/objects/blade.py: -------------------------------------------------------------------------------- 1 | # for type hints 2 | from numpy import ndarray 3 | 4 | import numpy as np 5 | 6 | from pynumad.io.yaml_to_blade import yaml_to_blade 7 | from pynumad.io.excel_to_blade import excel_to_blade 8 | from pynumad.utils.interpolation import interpolator_wrap 9 | from pynumad.objects.geometry import Geometry 10 | from pynumad.objects.settings import BladeSettings 11 | from pynumad.objects.keypoints import KeyPoints 12 | from pynumad.objects.definition import Definition 13 | from pynumad.objects.bom import BillOfMaterials 14 | from pynumad.objects.materialdb import MaterialDatabase 15 | from pynumad.objects.stackdb import StackDatabase 16 | 17 | 18 | class Blade: 19 | """Blade class 20 | 21 | Parameters 22 | ---------- 23 | filename : str, optional 24 | Directory and filename of blade input file to load into the 25 | Blade object. 26 | 27 | Attributes 28 | ---------- 29 | name : str 30 | Name of the blade 31 | definition : Definition 32 | Object containing the definition of the blade. 33 | ispan : ndarray 34 | Array defining the interpolated stations from 0 to 1 35 | geometry : Geometry 36 | Object containing the interpolated geometry of the blade 37 | keypoints : KeyPoints 38 | Object containing information about keypoint locations and areas 39 | bill_of_materials : BillOfMaterials 40 | stackdb : StackDatabase 41 | materialdb : MaterialDatabase 42 | settings : BladeSettings 43 | 44 | Example 45 | ------- 46 | 47 | blade = Blade("path/to/blade.yaml") 48 | """ 49 | 50 | def __init__(self, filename: str = None): 51 | self.name: str = None 52 | self.definition: Definition = Definition() 53 | self.ispan: ndarray = None 54 | self.geometry: Geometry = Geometry() 55 | self.keypoints: KeyPoints = KeyPoints() 56 | self.bill_of_materials: BillOfMaterials = BillOfMaterials() 57 | self.stackdb: StackDatabase = StackDatabase() 58 | self.materialdb: MaterialDatabase = MaterialDatabase() 59 | self.settings: BladeSettings = BladeSettings() 60 | 61 | # read input file 62 | if filename: 63 | if "yaml" in filename or "yml" in filename: 64 | self.read_yaml(filename) 65 | elif "xls" in filename or "xlsx" in filename: 66 | self.read_excel(filename) 67 | else: 68 | raise Exception( 69 | "Unknown filetype. Currently supported inputs are excel and yaml files." 70 | ) 71 | self.name = filename.split(".")[0] 72 | else: 73 | self.name = "blade" 74 | return 75 | 76 | def __str__(self): 77 | attributes = "" 78 | for attr_name, attr_value in vars(self).items(): 79 | if isinstance(attr_value, list): 80 | attributes += f"{attr_name}={len(attr_value)}, " 81 | elif isinstance(attr_value, np.ndarray): 82 | attributes += f"{attr_name}={attr_value.shape}, " 83 | else: 84 | attributes += f"{attr_name}={attr_value}, " 85 | return f"Blade with {attributes[:-2]}" 86 | 87 | def read_yaml(self, filename: str): 88 | """Populate blade attributes with yaml file data 89 | 90 | Parameters 91 | ---------- 92 | filename: str 93 | name of yaml file to be read 94 | 95 | Returns 96 | ------- 97 | self 98 | 99 | """ 100 | yaml_to_blade(self, filename) 101 | return self 102 | 103 | def read_excel(self, filename: str): 104 | """Populate blade attributes with excel file data 105 | 106 | Parameters 107 | ---------- 108 | filename: str 109 | name of excel file to be read 110 | 111 | Returns 112 | ------- 113 | self 114 | 115 | """ 116 | excel_to_blade(self, filename) 117 | return self 118 | 119 | def update_blade(self): 120 | """Generates geometry, keypoints, bill of materials, 121 | stack database, and material database based on the 122 | blade definition. 123 | """ 124 | self.geometry.generate(self.definition) 125 | self.keypoints.generate(self.definition, self.geometry) 126 | self.bill_of_materials.generate(self.definition, self.keypoints) 127 | self.stackdb.generate(self.keypoints, self.bill_of_materials) 128 | self.materialdb.generate(self.definition.materials, self.stackdb) 129 | return self 130 | 131 | def expand_blade_geometry_te(self, min_edge_lengths): 132 | """ 133 | TODO: docstring 134 | """ 135 | self.geometry.expand_blade_geometry_te(min_edge_lengths) 136 | self.keypoints.generate(self.definition, self.geometry) 137 | return 138 | 139 | def add_interpolated_station(self, span_location: float): 140 | """Adds an interpolated station to blade geometry 141 | 142 | Parameters 143 | ---------- 144 | span_location : float 145 | location along span between 0 and 1. 146 | 147 | Returns 148 | ------- 149 | int 150 | integer index where the new span was inserted 151 | """ 152 | x0 = self.ispan 153 | 154 | if span_location < self.ispan[-1] and span_location > 0: 155 | for i_span, spanLocation in enumerate(self.ispan[1:]): 156 | if span_location < spanLocation: 157 | insertIndex = i_span + 1 158 | break 159 | else: 160 | raise ValueError( 161 | f"A new span location with value {span_location} is not possible." 162 | ) 163 | 164 | self.ispan = np.insert(self.ispan, insertIndex, np.array([span_location])) 165 | self.definition.ispan = np.insert(self.definition.ispan, insertIndex, np.array([span_location])) 166 | 167 | self.definition.leband = interpolator_wrap(x0, self.definition.leband, self.ispan) 168 | self.definition.teband = interpolator_wrap(x0, self.definition.teband, self.ispan) 169 | self.definition.sparcapwidth_hp = interpolator_wrap(x0, self.definition.sparcapwidth_hp, self.ispan) 170 | self.definition.sparcapwidth_lp = interpolator_wrap(x0, self.definition.sparcapwidth_lp, self.ispan) 171 | self.definition.sparcapoffset_hp = interpolator_wrap(x0, self.definition.sparcapoffset_hp, self.ispan) 172 | self.definition.sparcapoffset_lp = interpolator_wrap(x0, self.definition.sparcapoffset_lp, self.ispan) 173 | 174 | self.update_blade() 175 | return insertIndex 176 | -------------------------------------------------------------------------------- /src/pynumad/objects/component.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pynumad.utils.interpolation import interpolator_wrap 4 | 5 | 6 | class Component: 7 | """ Component class 8 | 9 | Attributes 10 | ---------- 11 | group : int 12 | 0 = blade, 1 = first shear web, 2 = second shear web, etc. 13 | name : str 14 | Name, such as 'spar' 15 | materialid : str 16 | Material id number from blade.materials 17 | fabricangle : float 18 | Fiber angle 19 | hpextents : list 20 | Array of keypoints such as ['b','c'] 21 | lpextents : list 22 | String Array: Array of keypoints such as ['b','c'] 23 | control_points : np 24 | control points defining layer distribution 25 | imethod: str 26 | imethod 27 | pinnedends 28 | hCtrl 29 | hLine 30 | """ 31 | 32 | def __init__(self): 33 | self.name: str = None 34 | self.group: int = None 35 | self.materialid: str = None 36 | self.fabricangle: float = None 37 | self.hpextents: list = None 38 | self.lpextents: list = None 39 | self.control_points: np.ndarray = None 40 | self.imethod: str = "linear" 41 | self.pinnedends: bool = None 42 | self._keylabels = [ 43 | "te", 44 | "e", 45 | "d", 46 | "c", 47 | "b", 48 | "a", 49 | "le", 50 | "a", 51 | "b", 52 | "c", 53 | "d", 54 | "e", 55 | "te", 56 | ] 57 | 58 | 59 | def __eq__(self, other): 60 | attrs = vars(self).keys() 61 | 62 | for attr in attrs: 63 | self_attr = getattr(self, attr) 64 | other_attr = getattr(other, attr) 65 | if isinstance(self_attr, (int, float, str, list, bool)): 66 | if self_attr != other_attr: 67 | return False 68 | elif isinstance(self_attr, np.ndarray): 69 | if (self_attr != other_attr).any(): 70 | return False 71 | return True 72 | 73 | def _compare(self, other): 74 | """ 75 | Parameters 76 | ---------- 77 | other : Component 78 | 79 | Returns 80 | ------- 81 | bool 82 | """ 83 | return self == other 84 | 85 | def get_control_points(self): 86 | if self.pinnedends: 87 | if np.any(self.control_points[:, 0] < 0) or np.any(self.control_points[:, 0] > 1): 88 | raise Exception( 89 | 'ComponentDef: first coordinate of control points must be in range [0,1] when using "pinned" ends' 90 | ) 91 | cpx = np.concatenate(([-0.01], self.control_points[:, 0], [1.01])) 92 | cpy = np.concatenate(([0], self.control_points[:, 1], [0])) 93 | else: 94 | cpx = self.control_points[:, 0] 95 | cpy = self.control_points[:, 1] 96 | 97 | return cpx, cpy 98 | 99 | def get_num_layers(self, span): 100 | cpx, cpy = self.get_control_points() 101 | return interpolator_wrap(cpx, cpy, span, self.imethod, 0) 102 | 103 | def find_region_extents(self): 104 | """ 105 | TODO docstring 106 | """ 107 | le = self._keylabels.index("le") 108 | # "_keylabels" is expected to wrap from te on hp side around to te on lp side 109 | try: 110 | if len(self.hpextents) == 2: 111 | try: 112 | hp1 = self._keylabels[: le + 1].index(self.hpextents[0]) 113 | except KeyError: 114 | print(f'HP extent label "{self.hpextents[0]}" not defined.') 115 | try: 116 | hp2 = self._keylabels[: le + 1].index(self.hpextents[1]) 117 | except KeyError: 118 | print(f'HP extent label "{self.hpextents[1]}" not defined.') 119 | hpRegion = [hp1, hp2] 120 | hpRegion.sort() 121 | else: 122 | hpRegion = [] 123 | except TypeError: 124 | hpRegion = [] 125 | 126 | try: 127 | if len(self.lpextents) == 2: 128 | try: 129 | lp1 = self._keylabels[le:].index(self.lpextents[0]) + le 130 | except KeyError: 131 | print(f'HP extent label "{self.hpextents[0]}" not defined.') 132 | try: 133 | lp2 = self._keylabels[le:].index(self.lpextents[1]) + le 134 | except KeyError: 135 | print(f'HP extent label "{self.hpextents[1]}" not defined.') 136 | lpRegion = [lp1, lp2] 137 | lpRegion.sort() 138 | else: 139 | lpRegion = [] 140 | except TypeError: 141 | lpRegion = [] 142 | 143 | # if length(comp['hpextents'])==1 && length(comp['lpextents'])==1 144 | # sw1 = find(1==strcmpi(comp['hpextents']{1},_keylabels(1:le))); 145 | # assert(~isempty(sw1),'HP extent label "#s" not defined.',comp['hpextents']{1}); 146 | # w2 = find(1==strcmpi(comp['lpextents']{1},_keylabels(le:end))) + le-1; 147 | # assert(~isempty(sw2),'LP extent label "#s" not defined.',comp['lpextents']{1}); 148 | # swRegion = [sw1 sw2]; 149 | # else 150 | swRegion = [] 151 | return hpRegion, lpRegion # ,swRegion 152 | -------------------------------------------------------------------------------- /src/pynumad/objects/definition.py: -------------------------------------------------------------------------------- 1 | # for type hints 2 | from numpy import ndarray 3 | 4 | from pynumad.objects.airfoil import Airfoil 5 | from pynumad.objects.station import Station 6 | from pynumad.objects.component import Component 7 | from pynumad.objects.material import Material 8 | 9 | 10 | class Definition: 11 | """Definition Class 12 | 13 | A Definition object is designed to contain the 14 | basic defining features of a blade, which can 15 | be populated manually or by reading a blade file. 16 | 17 | Attributes 18 | ---------- 19 | components : dict 20 | Dictionary of components indexed by component name 21 | shearweb : list 22 | List of shearwebs. len(shearweb) == number of shearwebs. 23 | shearweb[i] gives an array defining the ith shearweb. 24 | materials : dict 25 | Dictionary of material objects indexed by material name. 26 | stacks : ndarray 27 | swstacks : array 28 | ispan : ndarray 29 | Spanwise locations of interpolated output 30 | aerocenter : ndarray 31 | Aerodynamic center of airfoil (used only by NuMAD->FAST) 32 | chord : ndarray 33 | Chord distribution [m] 34 | chordoffset : ndarray 35 | Chordwise offset (in addition to natural offset) 36 | degreestwist : ndarray 37 | Twist distribution [degrees] 38 | percentthick : ndarray 39 | Percent thickness of airfoil [%] 40 | prebend : ndarray 41 | Blade prebend, reference axis location along x2 [m] 42 | span : ndarray 43 | Spanwise location of distributed properties [m] 44 | sparcapoffset : ndarray 45 | sparcapwidth : ndarray 46 | Locations of keypoints b & c, defines distance 47 | between keypoints b & c [mm]. First entry is the HP spar cap. 48 | Second entry is the LP spar cap 49 | stations : list 50 | List of station objects 51 | sweep : ndarray 52 | Blade sweep, reference axis location along x1 [m] 53 | teband : float 54 | Location of keypoint e 55 | leband : float 56 | Location of keypoint a 57 | te_type : list 58 | """ 59 | def __init__(self): 60 | self.components: dict[str,Component] = None 61 | self.shearweb: list = None 62 | self.materials: dict[str,Material] = None 63 | self.stations: list[Station] = None 64 | self.te_type: list[str] = None 65 | self.stacks: ndarray = None 66 | self.swstacks: ndarray = None 67 | self.ispan: ndarray = None 68 | self.aerocenter: ndarray = None 69 | self.chord: ndarray = None 70 | self.chordoffset: ndarray = None 71 | self.degreestwist: ndarray = None 72 | self.percentthick: ndarray = None 73 | self.prebend: ndarray = None 74 | self.span: ndarray = None 75 | self.sparcapoffset: ndarray = None 76 | self.sparcapwidth: ndarray = None 77 | self.sweep: ndarray = None 78 | self.teband: float = None 79 | self.leband: float = None 80 | 81 | # init properties 82 | self._natural_offset: int = 1 83 | self._rotorspin: int = 1 84 | self._swtwisted: int = 0 85 | 86 | def __eq__(self, other): 87 | attrs = vars(self).keys() 88 | for attr in attrs: 89 | self_attr = getattr(self, attr) 90 | other_attr = getattr(other, attr) 91 | if isinstance(self_attr, (int, float, str, list, dict)): 92 | if self_attr != other_attr: 93 | return False 94 | elif isinstance(self_attr, ndarray): 95 | if (self_attr != other_attr).any(): 96 | return False 97 | return True 98 | 99 | @property 100 | def natural_offset(self): 101 | """ 102 | 1 = offset by max thickness location, 103 | 0 = do not offset to max thickness 104 | """ 105 | return self._natural_offset 106 | 107 | @natural_offset.setter 108 | def natural_offset(self, new_natural_offset): 109 | if not (new_natural_offset == 0 or new_natural_offset == 1): 110 | raise Exception("natural_offset must be 0 or 1") 111 | else: 112 | self._natural_offset = new_natural_offset 113 | 114 | @property 115 | def rotorspin(self): 116 | """ 117 | Rotor Spin, 118 | 1 = CW rotation looking downwind, 119 | -1 = CCW rotation 120 | """ 121 | return self._rotorspin 122 | 123 | @rotorspin.setter 124 | def rotorspin(self, new_rotorspin): 125 | if not (new_rotorspin == 1 or new_rotorspin == -1): 126 | raise Exception("rotorspin must be 1 (cw) or -1 (ccw)") 127 | else: 128 | self._rotorspin = new_rotorspin 129 | 130 | @property 131 | def swtwisted(self): 132 | """ 133 | Shear Web, 134 | 0 = planar shear webs, 135 | 1 = shear webs twisted by blade twist 136 | """ 137 | return self._swtwisted 138 | 139 | @swtwisted.setter 140 | def swtwisted(self, new_swtwisted): 141 | if not (new_swtwisted == 0 or new_swtwisted == 1): 142 | raise Exception("swtwisted must be 0 or 1") 143 | else: 144 | self._swtwisted = new_swtwisted 145 | 146 | def add_station(self, af: Airfoil, spanlocation: float): 147 | """This method adds a station 148 | 149 | Specifically, a station object is created 150 | and appended to self.stations. 151 | 152 | Parameters 153 | ---------- 154 | af : airfoil 155 | spanlocation : float 156 | 157 | Returns 158 | ------- 159 | None 160 | 161 | Example 162 | ------- 163 | ``blade.add_station(af,spanlocation)`` where ``af`` = airfoil filename 164 | or ``Airfoil`` object 165 | """ 166 | new_station = Station(af) 167 | new_station.spanlocation = spanlocation 168 | self.stations.append(new_station) 169 | 170 | return self 171 | -------------------------------------------------------------------------------- /src/pynumad/objects/material.py: -------------------------------------------------------------------------------- 1 | from numpy import ndarray 2 | 3 | 4 | class Material: 5 | """Material class 6 | 7 | Attributes 8 | ---------- 9 | name : str 10 | User selected name of the material 11 | type : str 12 | Two options: isotropic or orthotropic 13 | layerthickness : float 14 | Layer thickness [mm] 15 | ex : float 16 | Longitudinal elastic modulus [Pa] 17 | ey : float 18 | Transverse elastic modulus [Pa] 19 | ez : float 20 | Through-the-thickness elastic modulus in the 21 | principal material coordinates [Pa] 22 | gxy : float 23 | In-plane shear modulus [Pa] 24 | gyz : float 25 | Transverse shear modulus [Pa] 26 | gxz : float 27 | Transverse shear modulus [Pa] 28 | prxy : float 29 | In-plane Poisson ratio [ ] 30 | pryz : float 31 | Transverse Poisson ratio [ ] 32 | prxz : float 33 | Transverse Poisson ratio [ ] 34 | density : float 35 | Cured mass density [kg/m2] 36 | drydensity : float 37 | Density of fabric 38 | uts : float 39 | 1 x 3 array of ultimate tensile strength design values. 40 | Sequence: SL , ST, Sz, 1 x 1 for isotropic. 41 | ucs : float 42 | 1 x 3 array of ultimate compressive strength design values. 43 | Sequence: SL , ST, Sz, 1 x 1 for isotropic. 44 | uss : float 45 | 1 x 3 array of ultimate shear strength design values. 46 | Sequence: SLT , STz, SLz, 1 x 1 for isotropic. 47 | xzit : float 48 | Lz tensile inclination parameter for Puck failure index 49 | xzic : float 50 | Lz compressive inclination parameter for Puck failure index 51 | yzit : float 52 | Tz tensile inclination parameter for Puck failure index 53 | yzic : float 54 | Tz compressive inclination parameter for Puck failure index 55 | g1g2 : float 56 | Fracture toughness ratio between GI (mode I) and GII (mode II) [ ] 57 | alp0 : float 58 | Fracture angle under pure transverse compression [degrees] 59 | etat : float 60 | Transverse friction coefficient for Larc [ ] 61 | etal : float 62 | Longitudinal friction coefficient for Larc [ ] 63 | m : list 64 | Fatigue slope exponent [ ] 65 | gamma_mf : list 66 | from DNL-GL standard, fatigue strength reduction factor 67 | gamma_ms : list 68 | from DNV-GL standard, short term strength reduction factor 69 | reference : str = None 70 | 71 | """ 72 | 73 | def __init__(self): 74 | self.name: str = None 75 | self.type: str = None 76 | self.reference: str = None 77 | self.layerthickness: float = None 78 | self.ex: float = None 79 | self.ey: float = None 80 | self.ez: float = None 81 | self.gxy: float = None 82 | self.gyz: float = None 83 | self.gxz: float = None 84 | self.prxy: float = None 85 | self.pryz: float = None 86 | self.prxz: float = None 87 | self.density: float = None 88 | self.drydensity: float = None 89 | self.uts: float = None 90 | self.ucs: float = None 91 | self.uss: float = None 92 | self.xzit: float = None 93 | self.xzic: float = None 94 | self.yzit: float = None 95 | self.yzic: float = None 96 | self.g1g2: float = None 97 | self.alp0: float = None 98 | self.etat: float = None 99 | self.etal: float = None 100 | self.m: list[float] = None 101 | self.gamma_mf: list[float] = None 102 | self.gamma_ms: list[float] = None 103 | 104 | 105 | def __eq__(self, other): 106 | attrs = vars(self).keys() 107 | 108 | for attr in attrs: 109 | self_attr = getattr(self, attr) 110 | other_attr = getattr(other, attr) 111 | try: 112 | if (self_attr != self_attr) & (other_attr != other_attr): 113 | continue 114 | except ValueError: 115 | pass 116 | if isinstance(self_attr, (int,float,str,list)): 117 | if self_attr != other_attr: 118 | return False 119 | elif isinstance(self_attr, ndarray): 120 | if (self_attr != other_attr).any(): 121 | return False 122 | return True 123 | -------------------------------------------------------------------------------- /src/pynumad/objects/settings.py: -------------------------------------------------------------------------------- 1 | class BladeSettings: 2 | """This object houses various settings for the Blade object""" 3 | 4 | def __init__(self): 5 | # mesh settings 6 | self.mesh: float = 0.45 7 | -------------------------------------------------------------------------------- /src/pynumad/objects/station.py: -------------------------------------------------------------------------------- 1 | from pynumad.objects.airfoil import Airfoil 2 | 3 | 4 | class Station: 5 | """ 6 | Attributes 7 | ---------- 8 | airfoil : Airfoil 9 | Airfoil corresponding to the station 10 | spanlocation 11 | Spanwise location where station is defined [m] 12 | """ 13 | 14 | def __init__(self, af=None): 15 | """ 16 | Parameters 17 | ---------- 18 | af : Airfoil, string 19 | airfoil object or filename to airfoil coords 20 | """ 21 | self.airfoil = None 22 | self.spanlocation = None 23 | 24 | if isinstance(af, str): 25 | self.airfoil = Airfoil(filename=af) 26 | elif isinstance(af, Airfoil): 27 | self.airfoil = af 28 | else: 29 | self.airfoil = Airfoil() 30 | 31 | def __eq__(self, other): 32 | attrs = vars(self).keys() 33 | for attr in attrs: 34 | if getattr(self, attr) != getattr(other, attr): 35 | return False 36 | return True 37 | 38 | 39 | # unsure if these are needed in pynumad -kb 40 | # @property 41 | # def degreestwist(self): 42 | # """ 43 | # TODO docstring 44 | # """ 45 | # _degreestwist = interpolator_wrap( 46 | # self.parent.span, self.parent.degreestwist, self.spanlocation 47 | # ) 48 | # return _degreestwist 49 | 50 | # @property 51 | # def chord(self): 52 | # """ 53 | # TODO docstring 54 | # """ 55 | # _chord = interpolator_wrap( 56 | # self.parent.span, self.parent.chord, self.spanlocation 57 | # ) 58 | # return _chord 59 | 60 | # @property 61 | # def percentthick(self): 62 | # """ 63 | # TODO docstring 64 | # """ 65 | # _percentthick = interpolator_wrap( 66 | # self.parent.span, self.parent.percentthick, self.spanlocation 67 | # ) 68 | # return _percentthick 69 | 70 | # @property 71 | # def coffset(self): 72 | # """ 73 | # TODO docstring 74 | # """ 75 | # # coffset = interp1(self.parent.span,self.parent.coffset,self.spanlocation); 76 | # _coffset = self.airfoil.maxthick 77 | # return _coffset 78 | 79 | # @property 80 | # def xyz(self): 81 | # """ 82 | # TODO docstring 83 | # """ 84 | # twistFlag = -1 85 | # tratio = self.percentthick / self.airfoil.percentthick 86 | # thick = self.airfoil.thickness * tratio 87 | # hp = self.airfoil.camber - 0.5 * thick 88 | # lp = self.airfoil.camber + 0.5 * thick 89 | # c = self.airfoil.c 90 | # x = np.concatenate((c[-1], np.flipud(c), c[1:], c[-1]), axis=0) 91 | # y = np.concatenate((hp[-1], np.flipud(hp), lp[1:], lp[-1]), axis=0) 92 | # x = (x - self.coffset) * self.chord * twistFlag 93 | # y = (y) * self.chord 94 | # twist = twistFlag * self.degreestwist * np.pi / 180 95 | # xyz = np.zeros((len(x), 3)) 96 | # xyz[:, 1] = np.cos(twist) * x - np.sin(twist) * y 97 | # xyz[:, 2] = np.sin(twist) * x + np.cos(twist) * y 98 | # xyz[:, 3] = self.spanlocation 99 | # return xyz 100 | 101 | # NOTE: Not finished. Not sure what this is used for 102 | # def updateProfile(self): 103 | # xyz = self.xyz 104 | # if len(self.hgProfile) == 0: 105 | # self.hgProfile = line(0, 0, 0) 106 | # set(self.hgProfile, "XData", xyz[:, 0], "YData", xyz[:, 1], "ZData", xyz[:, 2]) 107 | # return 108 | -------------------------------------------------------------------------------- /src/pynumad/paths.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname, join 2 | import json 3 | # DATA_PATH is intended to be used by pynumad internals to find airfoil files 4 | # as needed by the excel_to_blade function 5 | package_dir = dirname(abspath(str(__file__))) 6 | DATA_PATH = join(package_dir, "data") 7 | 8 | with open(join(package_dir, "software_paths.json"), "rt") as f: 9 | SOFTWARE_PATHS = json.load(f) 10 | 11 | def set_path(path_label, path): 12 | """_summary_ 13 | 14 | Parameters 15 | ---------- 16 | path_label : str 17 | Label of the path to be set. 18 | - "cubit" 19 | - "cubit_enhancements" 20 | - "ansys" 21 | path : str 22 | Value to set label as 23 | """ 24 | SOFTWARE_PATHS[path_label] = path 25 | with open(join(package_dir, "software_paths.json"), "wt") as f: 26 | json.dump(SOFTWARE_PATHS, f, indent=4) -------------------------------------------------------------------------------- /src/pynumad/software_paths.json: -------------------------------------------------------------------------------- 1 | {"pynumad" : "/Users/ecamare/repos/pynumad/", 2 | "ansys" : "mapdl", 3 | "cubit" : "/Applications/Cubit-16.16/Cubit.app/Contents/MacOS/", 4 | "cubit_enhancements" : "/Applications/Cubit-16.16/Cubit.app/Contents/MacOS/python3/lib/site-packages/" 5 | } -------------------------------------------------------------------------------- /src/pynumad/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/tests/__init__.py -------------------------------------------------------------------------------- /src/pynumad/tests/test_affinetrans.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pynumad.utils.affinetrans import rotation, translation 4 | 5 | 6 | class TestAffinetrans(unittest.TestCase): 7 | def test_rotation(self): 8 | pass 9 | 10 | def test_translation(self): 11 | pass 12 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_airfoil.py: -------------------------------------------------------------------------------- 1 | import unittest, os 2 | 3 | from pynumad.objects.airfoil import Airfoil 4 | 5 | test_data_dir = os.path.join(os.path.dirname(__file__), "test_data") 6 | 7 | 8 | class TestAirfoil(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(cls): 11 | cls.xmlfile = os.path.join(test_data_dir, "airfoils", "DU91-W-250.txt") 12 | 13 | def test_load_xml(self): 14 | x = Airfoil(filename=self.xmlfile) 15 | 16 | # check reference 17 | self.assertEqual(x.reference, "Points generated by BRR for NuMAD, 6/2/2011") 18 | 19 | # TODO: check coords 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_blade_io.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import pickle 4 | 5 | from pynumad.objects.blade import Blade 6 | 7 | test_data_dir = os.path.join(os.path.dirname(__file__), "test_data") 8 | 9 | 10 | class TestBladeIO(unittest.TestCase): 11 | @classmethod 12 | def setUpClass(cls): 13 | cls.xlsxfile = os.path.join(test_data_dir, "blades", "blade.xlsx") 14 | cls.yamlfile = os.path.join(test_data_dir, "blades", "blade.yaml") 15 | 16 | with open(os.path.join(test_data_dir,"blades","yaml_blade.pkl"), "rb") as f: 17 | cls.yaml_blade_pkl = pickle.load(f) 18 | 19 | with open(os.path.join(test_data_dir,"blades","excel_blade.pkl"), "rb") as f: 20 | cls.excel_blade_pkl = pickle.load(f) 21 | 22 | def test_xlsx_blade(self): 23 | xlsxblade = Blade(self.xlsxfile) 24 | # assert xlsxblade.definition == self.excel_blade_pkl.definition 25 | 26 | 27 | def test_yaml_blade(self): 28 | yamlblade = Blade(self.yamlfile) 29 | # assert yamlblade.definition == self.yaml_blade_pkl.definition 30 | 31 | 32 | if __name__ == "__main__": 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/DU91-W-250.txt: -------------------------------------------------------------------------------- 1 | Points generated by BRR for NuMAD, 6/2/2011 2 | 3 | 1.000000 0.000000 4 | 0.996600 -0.001489 5 | 0.982350 0.001083 6 | 0.967060 0.003666 7 | 0.950720 0.006059 8 | 0.933330 0.008039 9 | 0.914900 0.009364 10 | 0.895420 0.009853 11 | 0.874900 0.009399 12 | 0.853330 0.007912 13 | 0.830720 0.005286 14 | 0.807060 0.001409 15 | 0.782350 -0.003807 16 | 0.756600 -0.010378 17 | 0.730000 -0.018173 18 | 0.703330 -0.026764 19 | 0.676670 -0.035883 20 | 0.650000 -0.045300 21 | 0.623330 -0.054782 22 | 0.596670 -0.064101 23 | 0.570000 -0.073089 24 | 0.543330 -0.081615 25 | 0.516670 -0.089564 26 | 0.490000 -0.096818 27 | 0.463330 -0.103264 28 | 0.436670 -0.108814 29 | 0.410001 -0.113421 30 | 0.383330 -0.117082 31 | 0.356670 -0.119809 32 | 0.330000 -0.121574 33 | 0.303330 -0.122305 34 | 0.276670 -0.121948 35 | 0.250000 -0.120452 36 | 0.224210 -0.117887 37 | 0.199820 -0.114407 38 | 0.176840 -0.110114 39 | 0.155259 -0.105105 40 | 0.135080 -0.099506 41 | 0.116310 -0.093415 42 | 0.098930 -0.086924 43 | 0.082970 -0.080148 44 | 0.068400 -0.073160 45 | 0.055240 -0.065996 46 | 0.043480 -0.058651 47 | 0.033120 -0.051119 48 | 0.024170 -0.043449 49 | 0.016620 -0.035805 50 | 0.010470 -0.028351 51 | 0.005720 -0.021038 52 | 0.002380 -0.013586 53 | 0.000440 -0.005719 54 | 0.000180 -0.003603 55 | 0.000000 0.000000 56 | 0.001240 0.009445 57 | 0.003880 0.015569 58 | 0.007920 0.021609 59 | 0.013370 0.027973 60 | 0.020220 0.034622 61 | 0.028470 0.041508 62 | 0.038120 0.048561 63 | 0.049180 0.055726 64 | 0.061640 0.062932 65 | 0.075510 0.070134 66 | 0.090770 0.077252 67 | 0.107450 0.084235 68 | 0.125520 0.091008 69 | 0.144990 0.097509 70 | 0.165870 0.103672 71 | 0.188160 0.109419 72 | 0.211840 0.114648 73 | 0.236930 0.119261 74 | 0.263330 0.123120 75 | 0.290000 0.126000 76 | 0.316670 0.127850 77 | 0.343330 0.128618 78 | 0.370000 0.128276 79 | 0.396670 0.126985 80 | 0.423330 0.124971 81 | 0.450000 0.122304 82 | 0.476670 0.119044 83 | 0.503330 0.115302 84 | 0.530000 0.111124 85 | 0.556670 0.106549 86 | 0.583330 0.101632 87 | 0.610000 0.096418 88 | 0.636670 0.090934 89 | 0.663330 0.085217 90 | 0.690000 0.079299 91 | 0.716670 0.073216 92 | 0.743330 0.067014 93 | 0.769610 0.060782 94 | 0.794840 0.054682 95 | 0.819020 0.048765 96 | 0.842160 0.043060 97 | 0.864250 0.037558 98 | 0.885290 0.032274 99 | 0.905290 0.027224 100 | 0.924250 0.022410 101 | 0.942160 0.017829 102 | 0.959020 0.013465 103 | 0.974840 0.009259 104 | 0.989610 0.005151 105 | 0.993140 0.004139 106 | 0.996600 0.003137 107 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/DU93-W-210.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.001433 5 | 0.98235 0.000525 6 | 0.96706 0.002326 7 | 0.95072 0.003923 8 | 0.93333 0.005233 9 | 0.9149 0.006132 10 | 0.89542 0.006488 11 | 0.8749 0.006195 12 | 0.85333 0.005168 13 | 0.83072 0.003326 14 | 0.80706 0.000596 15 | 0.78235 -0.003084 16 | 0.7566 -0.007721 17 | 0.73 -0.013225 18 | 0.70333 -0.019296 19 | 0.67667 -0.025743 20 | 0.65 -0.032405 21 | 0.62333 -0.039113 22 | 0.596669 -0.045706 23 | 0.57 -0.052067 24 | 0.54333 -0.058103 25 | 0.51667 -0.063728 26 | 0.49 -0.068863 27 | 0.46333 -0.07343 28 | 0.43667 -0.077361 29 | 0.41 -0.080623 30 | 0.38333 -0.083216 31 | 0.35667 -0.08515 32 | 0.33 -0.0864 33 | 0.30333 -0.086922 34 | 0.27667 -0.086667 35 | 0.25 -0.085613 36 | 0.22421 -0.0838 37 | 0.19982 -0.081337 38 | 0.17684 -0.078299 39 | 0.15526 -0.074756 40 | 0.13508 -0.070793 41 | 0.11631 -0.066483 42 | 0.09893 -0.061888 43 | 0.08297 -0.057094 44 | 0.0684 -0.052148 45 | 0.05524 -0.047078 46 | 0.04348 -0.041878 47 | 0.03312 -0.036549 48 | 0.02417 -0.031124 49 | 0.01662 -0.02571 50 | 0.01047 -0.02044 51 | 0.00572 -0.015262 52 | 0.00238 -0.009983 53 | 0.00044 -0.004434 54 | 0.00018 -0.002918 55 | 0 0 56 | 0.00124 0.009227 57 | 0.00388 0.01562 58 | 0.00792 0.021692 59 | 0.01337 0.02785 60 | 0.02022 0.034039 61 | 0.02847 0.040333 62 | 0.03812 0.046832 63 | 0.04918 0.053476 64 | 0.06164 0.060173 65 | 0.07551 0.066874 66 | 0.09077 0.073511 67 | 0.10745 0.080044 68 | 0.12552 0.086405 69 | 0.14499 0.092538 70 | 0.16587 0.098389 71 | 0.18816 0.103889 72 | 0.21184 0.108956 73 | 0.23693 0.113512 74 | 0.26333 0.11744 75 | 0.29 0.120531 76 | 0.31667 0.122749 77 | 0.34333 0.124106 78 | 0.37 0.124586 79 | 0.39667 0.124175 80 | 0.42333 0.122924 81 | 0.45 0.120889 82 | 0.47667 0.118133 83 | 0.50333 0.114704 84 | 0.529999 0.11071 85 | 0.55667 0.106266 86 | 0.58333 0.10143 87 | 0.61 0.096264 88 | 0.63667 0.090804 89 | 0.66333 0.085113 90 | 0.69 0.079235 91 | 0.71667 0.0732 92 | 0.74333 0.067056 93 | 0.76961 0.060882 94 | 0.79484 0.05484 95 | 0.81902 0.048959 96 | 0.84216 0.043257 97 | 0.86425 0.037763 98 | 0.88529 0.032498 99 | 0.90529 0.027444 100 | 0.92425 0.022599 101 | 0.94216 0.01797 102 | 0.95902 0.013536 103 | 0.97484 0.009271 104 | 0.98961 0.005156 105 | 0.99314 0.004112 106 | 0.9966 0.003036 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/DU97-W-300.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.00158 5 | 0.98235 0.001634 6 | 0.96706 0.004046 7 | 0.95072 0.005582 8 | 0.93333 0.006226 9 | 0.9149 0.005999 10 | 0.89542 0.004872 11 | 0.8749 0.002779 12 | 0.85333 -0.000368 13 | 0.83072 -0.004624 14 | 0.80706 -0.009984 15 | 0.78235 -0.016471 16 | 0.7566 -0.024047 17 | 0.73 -0.032557 18 | 0.70333 -0.041612 19 | 0.67667 -0.05106 20 | 0.65 -0.060788 21 | 0.62333 -0.070695 22 | 0.59667 -0.080696 23 | 0.57 -0.090714 24 | 0.54333 -0.100649 25 | 0.51667 -0.110402 26 | 0.49 -0.11992 27 | 0.46333 -0.129084 28 | 0.43667 -0.137718 29 | 0.41 -0.145655 30 | 0.38333 -0.152704 31 | 0.35667 -0.158553 32 | 0.33 -0.162913 33 | 0.30333 -0.165482 34 | 0.27667 -0.165916 35 | 0.25 -0.163991 36 | 0.22421 -0.159761 37 | 0.19982 -0.153713 38 | 0.17684 -0.146332 39 | 0.15526 -0.137971 40 | 0.13508 -0.128881 41 | 0.11631 -0.119233 42 | 0.09893 -0.10919 43 | 0.08297 -0.098953 44 | 0.0684 -0.08862 45 | 0.05524 -0.078307 46 | 0.04348 -0.068106 47 | 0.03312 -0.058132 48 | 0.02417 -0.048515 49 | 0.01662 -0.039286 50 | 0.01047 -0.030528 51 | 0.00572 -0.022168 52 | 0.00238 -0.014116 53 | 0.00044 -0.006006 54 | 0.00018 -0.00382 55 | 0 0 56 | 0.00124 0.010102 57 | 0.00388 0.017251 58 | 0.00792 0.024503 59 | 0.01337 0.032107 60 | 0.02022 0.039992 61 | 0.02847 0.048081 62 | 0.03812 0.056286 63 | 0.04918 0.064535 64 | 0.06164 0.072737 65 | 0.07551 0.080817 66 | 0.09077 0.088685 67 | 0.10745 0.096271 68 | 0.12552 0.103484 69 | 0.14499 0.11024 70 | 0.16587 0.11644 71 | 0.18816 0.121974 72 | 0.21184 0.126684 73 | 0.23693 0.130356 74 | 0.26333 0.132796 75 | 0.29 0.134011 76 | 0.31667 0.1342 77 | 0.34333 0.133522 78 | 0.37 0.132098 79 | 0.39667 0.130013 80 | 0.42333 0.127342 81 | 0.45 0.124153 82 | 0.47667 0.120514 83 | 0.50333 0.116476 84 | 0.529999 0.112072 85 | 0.55667 0.107342 86 | 0.58333 0.102317 87 | 0.61 0.097025 88 | 0.63667 0.091493 89 | 0.66333 0.085748 90 | 0.69 0.079812 91 | 0.71667 0.073711 92 | 0.74333 0.067472 93 | 0.76961 0.061198 94 | 0.79484 0.055075 95 | 0.81902 0.049125 96 | 0.84216 0.043373 97 | 0.86425 0.037841 98 | 0.88529 0.032546 99 | 0.90529 0.027489 100 | 0.92425 0.022675 101 | 0.94216 0.018095 102 | 0.95902 0.01371 103 | 0.97484 0.009465 104 | 0.98961 0.005364 105 | 0.99314 0.004371 106 | 0.9966 0.003402 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/DU99-W-350.txt: -------------------------------------------------------------------------------- 1 | Points generated by BRR for NuMAD, 6/2/2011 2 | 3 | 1.000000 0.000000 4 | 0.996600 -0.001900 5 | 0.982350 0.001410 6 | 0.967060 0.003580 7 | 0.950720 0.004510 8 | 0.933330 0.004250 9 | 0.914900 0.002930 10 | 0.895420 0.000610 11 | 0.874900 -0.002760 12 | 0.853330 -0.007270 13 | 0.830720 -0.012940 14 | 0.807060 -0.019740 15 | 0.782350 -0.027650 16 | 0.756600 -0.036580 17 | 0.730000 -0.046370 18 | 0.703330 -0.056610 19 | 0.676670 -0.067190 20 | 0.650000 -0.078010 21 | 0.623330 -0.089010 22 | 0.596670 -0.100140 23 | 0.570000 -0.111330 24 | 0.543330 -0.122470 25 | 0.516670 -0.133420 26 | 0.490000 -0.144100 27 | 0.463330 -0.154360 28 | 0.436670 -0.164020 29 | 0.410000 -0.172880 30 | 0.383330 -0.180740 31 | 0.356670 -0.187250 32 | 0.330000 -0.192110 33 | 0.303330 -0.194980 34 | 0.276670 -0.195520 35 | 0.250000 -0.193430 36 | 0.224210 -0.188770 37 | 0.199820 -0.182070 38 | 0.176840 -0.173870 39 | 0.155260 -0.164540 40 | 0.135080 -0.154350 41 | 0.116310 -0.143450 42 | 0.098930 -0.132050 43 | 0.082970 -0.120280 44 | 0.068400 -0.108290 45 | 0.055240 -0.096220 46 | 0.043480 -0.084220 47 | 0.033120 -0.072440 48 | 0.024170 -0.061050 49 | 0.016620 -0.049950 50 | 0.010470 -0.039110 51 | 0.005720 -0.028480 52 | 0.002380 -0.017760 53 | 0.000440 -0.007260 54 | 0.000180 -0.004610 55 | 0.000000 0.000000 56 | 0.001240 0.012400 57 | 0.003880 0.023290 58 | 0.007920 0.033430 59 | 0.013370 0.043390 60 | 0.020220 0.053440 61 | 0.028470 0.063480 62 | 0.038120 0.073450 63 | 0.049180 0.083270 64 | 0.061640 0.092840 65 | 0.075510 0.102070 66 | 0.090770 0.110870 67 | 0.107450 0.119170 68 | 0.125520 0.126890 69 | 0.144990 0.133960 70 | 0.165870 0.140250 71 | 0.188160 0.145660 72 | 0.211840 0.150020 73 | 0.236930 0.153160 74 | 0.263330 0.154860 75 | 0.290000 0.155240 76 | 0.316670 0.154570 77 | 0.343330 0.153060 78 | 0.370000 0.150910 79 | 0.396670 0.148140 80 | 0.423330 0.144770 81 | 0.450000 0.140860 82 | 0.476670 0.136480 83 | 0.503330 0.131650 84 | 0.530000 0.126390 85 | 0.556670 0.120770 86 | 0.583330 0.114830 87 | 0.610000 0.108590 88 | 0.636670 0.102100 89 | 0.663330 0.095390 90 | 0.690000 0.088490 91 | 0.716670 0.081440 92 | 0.743330 0.074260 93 | 0.769610 0.067080 94 | 0.794840 0.060130 95 | 0.819020 0.053400 96 | 0.842160 0.046940 97 | 0.864250 0.040790 98 | 0.885290 0.034950 99 | 0.905290 0.029410 100 | 0.924250 0.024190 101 | 0.942160 0.019260 102 | 0.959020 0.014560 103 | 0.974840 0.010060 104 | 0.989610 0.005780 105 | 0.993140 0.004770 106 | 0.996600 0.003780 107 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/DU99-W-405.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 106 pts. 2 | 3 | 1 0 4 | 0.9966 -0.00261 5 | 0.98235 0.0004 6 | 0.96706 0.00232 7 | 0.95072 0.00303 8 | 0.93333 0.00258 9 | 0.9149 0.00103 10 | 0.89542 -0.0016 11 | 0.8749 -0.00539 12 | 0.85333 -0.01045 13 | 0.83072 -0.01681 14 | 0.80706 -0.02452 15 | 0.78235 -0.03354 16 | 0.7566 -0.04382 17 | 0.73 -0.05519 18 | 0.70333 -0.06717 19 | 0.67667 -0.07958 20 | 0.65 -0.09228 21 | 0.62333 -0.10515 22 | 0.59667 -0.11804 23 | 0.57 -0.13083 24 | 0.54333 -0.14333 25 | 0.51667 -0.15537 26 | 0.49 -0.16673 27 | 0.46333 -0.17727 28 | 0.43667 -0.18683 29 | 0.41 -0.19527 30 | 0.38333 -0.20248 31 | 0.35667 -0.20827 32 | 0.33 -0.21243 33 | 0.30333 -0.21476 34 | 0.27667 -0.21501 35 | 0.25 -0.21297 36 | 0.22421 -0.2087 37 | 0.19982 -0.20257 38 | 0.17684 -0.19496 39 | 0.15526 -0.18616 40 | 0.13508 -0.17635 41 | 0.11631 -0.16567 42 | 0.09893 -0.15429 43 | 0.08297 -0.1423 44 | 0.0684 -0.12982 45 | 0.05524 -0.11698 46 | 0.04348 -0.10392 47 | 0.03312 -0.09076 48 | 0.02417 -0.07766 49 | 0.01662 -0.06446 50 | 0.01047 -0.05117 51 | 0.00572 -0.03783 52 | 0.00238 -0.02333 53 | 0.00044 -0.00905 54 | 0.00018 -0.00567 55 | 0 0 56 | 0.00124 0.01552 57 | 0.00388 0.03133 58 | 0.00792 0.04505 59 | 0.01337 0.0579 60 | 0.02022 0.0707 61 | 0.02847 0.08324 62 | 0.03812 0.09549 63 | 0.04918 0.1074 64 | 0.06164 0.11884 65 | 0.07551 0.12972 66 | 0.09077 0.13998 67 | 0.10745 0.14953 68 | 0.12552 0.15831 69 | 0.14499 0.16625 70 | 0.16587 0.17323 71 | 0.18816 0.17916 72 | 0.21184 0.18388 73 | 0.23693 0.18729 74 | 0.26333 0.18924 75 | 0.29 0.18979 76 | 0.31667 0.18914 77 | 0.34333 0.18746 78 | 0.37 0.18491 79 | 0.39667 0.18154 80 | 0.42333 0.17739 81 | 0.45 0.17253 82 | 0.47667 0.16701 83 | 0.50333 0.16088 84 | 0.53 0.15417 85 | 0.55667 0.14698 86 | 0.58333 0.13938 87 | 0.61 0.13142 88 | 0.63667 0.12318 89 | 0.66333 0.11471 90 | 0.69 0.10605 91 | 0.71667 0.09724 92 | 0.74333 0.08832 93 | 0.76961 0.07947 94 | 0.79484 0.07094 95 | 0.81902 0.06275 96 | 0.84216 0.05493 97 | 0.86425 0.04754 98 | 0.88529 0.04056 99 | 0.90529 0.034 100 | 0.92425 0.02786 101 | 0.94216 0.02212 102 | 0.95902 0.0167 103 | 0.97484 0.01158 104 | 0.98961 0.00678 105 | 0.99314 0.00565 106 | 0.9966 0.00455 107 | 108 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/NACA-63-214.txt: -------------------------------------------------------------------------------- 1 | Coords generated by DESIGNFOIL R6.44 on 27 Feb 2012. NACA 63-214 a=1.0. 2 | 3 | 1.000000 0.000000 4 | 0.997982 0.000177 5 | 0.991946 0.000495 6 | 0.981938 0.000759 7 | 0.968037 0.000774 8 | 0.950353 0.000344 9 | 0.929029 -0.000704 10 | 0.904240 -0.002492 11 | 0.876194 -0.005015 12 | 0.845121 -0.008307 13 | 0.811279 -0.012321 14 | 0.774946 -0.016983 15 | 0.736420 -0.022170 16 | 0.696017 -0.027715 17 | 0.654064 -0.033406 18 | 0.610903 -0.039008 19 | 0.566879 -0.044323 20 | 0.522346 -0.049153 21 | 0.477659 -0.053289 22 | 0.433173 -0.056531 23 | 0.389239 -0.058721 24 | 0.346203 -0.059741 25 | 0.304404 -0.059574 26 | 0.264173 -0.058383 27 | 0.225829 -0.056292 28 | 0.189677 -0.053416 29 | 0.156003 -0.049861 30 | 0.125078 -0.045773 31 | 0.097144 -0.041155 32 | 0.072423 -0.036124 33 | 0.051109 -0.030751 34 | 0.033375 -0.025233 35 | 0.019354 -0.019545 36 | 0.009122 -0.013431 37 | 0.002626 -0.007024 38 | 0.000000 0.000000 39 | 0.001400 0.007523 40 | 0.006949 0.014919 41 | 0.016683 0.022417 42 | 0.030390 0.029728 43 | 0.047922 0.037025 44 | 0.069129 0.044261 45 | 0.093839 0.051183 46 | 0.121851 0.057671 47 | 0.152934 0.063560 48 | 0.186834 0.068813 49 | 0.223274 0.073246 50 | 0.261958 0.076725 51 | 0.302571 0.079111 52 | 0.344780 0.080259 53 | 0.388240 0.079990 54 | 0.432594 0.078307 55 | 0.477476 0.075320 56 | 0.522518 0.071185 57 | 0.567354 0.066098 58 | 0.611618 0.060277 59 | 0.654952 0.053924 60 | 0.697008 0.047253 61 | 0.737448 0.040513 62 | 0.775951 0.033936 63 | 0.812211 0.027717 64 | 0.845941 0.022006 65 | 0.876878 0.016913 66 | 0.904777 0.012520 67 | 0.929420 0.008841 68 | 0.950616 0.005929 69 | 0.968197 0.003722 70 | 0.982025 0.002113 71 | 0.991984 0.000993 72 | 0.997993 0.000285 73 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/NACA-64-618.txt: -------------------------------------------------------------------------------- 1 | NuMad format, 96 pts. 2 | 3 | 1.000000 0.000000 4 | 0.950000 0.005509 5 | 0.900000 0.004658 6 | 0.850000 0.000919 7 | 0.800000 -0.004648 8 | 0.750000 -0.011413 9 | 0.700000 -0.018863 10 | 0.650000 -0.026561 11 | 0.600000 -0.034208 12 | 0.550000 -0.041463 13 | 0.500000 -0.048014 14 | 0.450000 -0.053500 15 | 0.400000 -0.057379 16 | 0.350000 -0.058949 17 | 0.300000 -0.058574 18 | 0.250000 -0.056741 19 | 0.200000 -0.053388 20 | 0.150000 -0.048358 21 | 0.100000 -0.041191 22 | 0.081048 -0.037704 23 | 0.075000 -0.036468 24 | 0.055828 -0.031979 25 | 0.050000 -0.030399 26 | 0.030174 -0.023996 27 | 0.025000 -0.021994 28 | 0.016968 -0.018206 29 | 0.012500 -0.015498 30 | 0.011428 -0.014731 31 | 0.008504 -0.012363 32 | 0.007500 -0.011441 33 | 0.005000 -0.008807 34 | 0.000000 0.000000 35 | 0.001496 0.015369 36 | 0.003572 0.018949 37 | 0.005000 0.020971 38 | 0.007500 0.024033 39 | 0.008032 0.024623 40 | 0.012500 0.029082 41 | 0.019826 0.035159 42 | 0.025000 0.038957 43 | 0.050000 0.054081 44 | 0.075000 0.065750 45 | 0.100000 0.075457 46 | 0.150000 0.090959 47 | 0.200000 0.102641 48 | 0.250000 0.111307 49 | 0.300000 0.117333 50 | 0.350000 0.120868 51 | 0.400000 0.121520 52 | 0.450000 0.119070 53 | 0.500000 0.114205 54 | 0.550000 0.107377 55 | 0.600000 0.098901 56 | 0.650000 0.089020 57 | 0.700000 0.077949 58 | 0.750000 0.065906 59 | 0.800000 0.053161 60 | 0.850000 0.040021 61 | 0.900000 0.026756 62 | 0.950555 0.013428 63 | 64 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/airfoils/circular.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 0 4 | 0.999013364 -0.03139526 5 | 0.996057351 -0.062666617 6 | 0.991143625 -0.093690657 7 | 0.984291581 -0.124344944 8 | 0.975528258 -0.154508497 9 | 0.964888243 -0.184062276 10 | 0.952413526 -0.212889646 11 | 0.93815334 -0.240876837 12 | 0.922163963 -0.267913397 13 | 0.904508497 -0.293892626 14 | 0.885256621 -0.318711995 15 | 0.864484314 -0.342273553 16 | 0.842273553 -0.364484314 17 | 0.818711995 -0.385256621 18 | 0.793892626 -0.404508497 19 | 0.767913397 -0.422163963 20 | 0.740876837 -0.43815334 21 | 0.712889646 -0.452413526 22 | 0.684062276 -0.464888243 23 | 0.654508497 -0.475528258 24 | 0.624344944 -0.484291581 25 | 0.593690657 -0.491143625 26 | 0.562666617 -0.496057351 27 | 0.53139526 -0.499013364 28 | 0.5 -0.5 29 | 0.46860474 -0.499013364 30 | 0.437333383 -0.496057351 31 | 0.406309343 -0.491143625 32 | 0.375655056 -0.484291581 33 | 0.345491503 -0.475528258 34 | 0.315937724 -0.464888243 35 | 0.287110354 -0.452413526 36 | 0.259123163 -0.43815334 37 | 0.232086603 -0.422163963 38 | 0.206107374 -0.404508497 39 | 0.181288005 -0.385256621 40 | 0.157726447 -0.364484314 41 | 0.135515686 -0.342273553 42 | 0.114743379 -0.318711995 43 | 0.095491503 -0.293892626 44 | 0.077836037 -0.267913397 45 | 0.06184666 -0.240876837 46 | 0.047586474 -0.212889646 47 | 0.035111757 -0.184062276 48 | 0.024471742 -0.154508497 49 | 0.015708419 -0.124344944 50 | 0.008856375 -0.093690657 51 | 0.003942649 -0.062666617 52 | 0.000986636 -0.03139526 53 | 0.0 0.0 54 | 0.000986636 0.03139526 55 | 0.003942649 0.062666617 56 | 0.008856375 0.093690657 57 | 0.015708419 0.124344944 58 | 0.024471742 0.154508497 59 | 0.035111757 0.184062276 60 | 0.047586474 0.212889646 61 | 0.06184666 0.240876837 62 | 0.077836037 0.267913397 63 | 0.095491503 0.293892626 64 | 0.114743379 0.318711995 65 | 0.135515686 0.342273553 66 | 0.157726447 0.364484314 67 | 0.181288005 0.385256621 68 | 0.206107374 0.404508497 69 | 0.232086603 0.422163963 70 | 0.259123163 0.43815334 71 | 0.287110354 0.452413526 72 | 0.315937724 0.464888243 73 | 0.345491503 0.475528258 74 | 0.375655056 0.484291581 75 | 0.406309343 0.491143625 76 | 0.437333383 0.496057351 77 | 0.46860474 0.499013364 78 | 0.5 0.5 79 | 0.53139526 0.499013364 80 | 0.562666617 0.496057351 81 | 0.593690657 0.491143625 82 | 0.624344944 0.484291581 83 | 0.654508497 0.475528258 84 | 0.684062276 0.464888243 85 | 0.712889646 0.452413526 86 | 0.740876837 0.43815334 87 | 0.767913397 0.422163963 88 | 0.793892626 0.404508497 89 | 0.818711995 0.385256621 90 | 0.842273553 0.364484314 91 | 0.864484314 0.342273553 92 | 0.885256621 0.318711995 93 | 0.904508497 0.293892626 94 | 0.922163963 0.267913397 95 | 0.93815334 0.240876837 96 | 0.952413526 0.212889646 97 | 0.964888243 0.184062276 98 | 0.975528258 0.154508497 99 | 0.984291581 0.124344944 100 | 0.991143625 0.093690657 101 | 0.996057351 0.062666617 102 | 0.999013364 0.03139526 103 | 104 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/blades/blade.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/tests/test_data/blades/blade.xlsx -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/blades/excel_blade.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/tests/test_data/blades/excel_blade.pkl -------------------------------------------------------------------------------- /src/pynumad/tests/test_data/blades/yaml_blade.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/tests/test_data/blades/yaml_blade.pkl -------------------------------------------------------------------------------- /src/pynumad/tests/test_interpolator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from pynumad.utils.interpolation import interpolator_wrap 6 | 7 | 8 | class TestInterpolator(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(self): 11 | self.x = np.linspace(0, 10, 5) 12 | v1 = np.sin(self.x) 13 | v2 = self.x**2 14 | self.v = np.array((v1, v2)).transpose() 15 | self.xq = np.linspace(0, 10, 10) 16 | 17 | def test_linear(self): 18 | vq = interpolator_wrap(self.x, self.v, self.xq, method="linear") 19 | correct_vq = np.array( 20 | [ 21 | [0.00000000e00, 0.00000000e00], 22 | [2.65987620e-01, 2.77777778e00], 23 | [5.31975239e-01, 5.55555556e00], 24 | [7.93400045e-02, 1.25000000e01], 25 | [-6.12836182e-01, 2.08333333e01], 26 | [-5.37385552e-01, 3.19444444e01], 27 | [3.05691893e-01, 4.58333333e01], 28 | [7.73330967e-01, 6.11111111e01], 29 | [1.14654928e-01, 8.05555556e01], 30 | [-5.44021111e-01, 1.00000000e02], 31 | ] 32 | ) 33 | array_equal = np.isclose(vq, correct_vq).all() 34 | self.assertTrue(array_equal) 35 | 36 | def test_pchip(self): 37 | vq = interpolator_wrap(self.x, self.v, self.xq, method="pchip") 38 | correct_vq = np.array( 39 | [ 40 | [0.0, 0.0], 41 | [0.47952836, 1.57750343], 42 | [0.59634519, 5.21262003], 43 | [0.1947027, 10.76388889], 44 | [-0.76238042, 19.843107], 45 | [-0.71953191, 30.69415866], 46 | [0.4462048, 44.48302469], 47 | [0.92197998, 60.40237769], 48 | [0.50904307, 78.89803384], 49 | [-0.54402111, 100.0], 50 | ] 51 | ) 52 | array_equal = np.isclose(vq, correct_vq).all() 53 | self.assertTrue(array_equal) 54 | pass 55 | 56 | def test_spline(self): 57 | vq = interpolator_wrap(self.x, self.v, self.xq, method="spline") 58 | correct_vq = np.array( 59 | [ 60 | [0.0, 0.0], 61 | [1.09041923, 1.2345679], 62 | [0.79794503, 4.9382716], 63 | [-0.11178833, 11.11111111], 64 | [-0.87314655, 19.75308642], 65 | [-0.75463378, 30.86419753], 66 | [0.19006212, 44.44444444], 67 | [1.12206955, 60.49382716], 68 | [1.16837848, 79.01234568], 69 | [-0.54402111, 100.0], 70 | ] 71 | ) 72 | array_equal = np.isclose(vq, correct_vq).all() 73 | self.assertTrue(array_equal) 74 | pass 75 | 76 | 77 | if __name__ == "__main__": 78 | unittest.main() 79 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_mesh.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from pynumad.mesh_gen.mesh_gen import get_shell_mesh 4 | from pynumad.objects.blade import Blade 5 | 6 | test_data_dir = os.path.join(os.path.dirname(__file__), "test_data") 7 | 8 | 9 | class TestMesh(unittest.TestCase): 10 | @classmethod 11 | def setUpClass(cls): 12 | cls.yamlfile = os.path.join(test_data_dir, "blades", "blade.yaml") 13 | 14 | def test_mesh(self): 15 | blade = Blade(self.yamlfile) 16 | elementSize = 0.2 17 | adhes = 1 18 | meshData = get_shell_mesh(blade, includeAdhesive=adhes, elementSize=elementSize) 19 | -------------------------------------------------------------------------------- /src/pynumad/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | from pynumad.utils.misc_utils import LARCetaL, LARCetaT, _parse_data 5 | 6 | 7 | class TestMisc(unittest.TestCase): 8 | def test_larcetat(self): 9 | alp0 = 53.0 10 | etat = LARCetaT(alp0) 11 | self.assertAlmostEqual(etat, 0.2867453857588078) 12 | 13 | def test_larcetal(self): 14 | SL = 10000000000.0 15 | YC = -10000000000.0 16 | alp0 = 53.0 17 | etal = LARCetaL(SL, YC, alp0) 18 | self.assertAlmostEqual(etal, -0.7610479585895458) 19 | 20 | def test_parse(self): 21 | cases = [50.0, "1e10", "6.4023E8"] 22 | self.assertEqual(_parse_data(cases[0]), 50.0) 23 | self.assertEqual(_parse_data(cases[1]), 10000000000.0) 24 | truths = np.isclose( 25 | _parse_data(cases), np.array([50.0, 10000000000.0, 640230000.0]) 26 | ) 27 | self.assertTrue(truths.all()) 28 | -------------------------------------------------------------------------------- /src/pynumad/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/pyNuMAD/2ad75d98247c7086069f7b8b959fdbd9ca3e0bb6/src/pynumad/utils/__init__.py -------------------------------------------------------------------------------- /src/pynumad/utils/affinetrans.py: -------------------------------------------------------------------------------- 1 | from scipy.spatial.transform import Rotation 2 | import numpy as np 3 | 4 | 5 | def rotation(axis, angle): 6 | """ 7 | Designed to replace matlab's makehgtform 8 | """ 9 | r = Rotation.from_euler(axis, angle) 10 | rmatrix = np.eye(4) 11 | rmatrix[0:3, 0:3] = r.as_matrix() 12 | return rmatrix 13 | 14 | 15 | def translation(xtrans, ytrans, ztrans): 16 | tmatrix = np.eye(4) 17 | tmatrix[0:3, 3] = [xtrans, ytrans, ztrans] 18 | return tmatrix 19 | 20 | 21 | if __name__ == "__main__": 22 | print(translation(2, 3, 4)) 23 | print(rotation("z", 1)) 24 | -------------------------------------------------------------------------------- /src/pynumad/utils/damping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Nov 29 11:40:17 2023 4 | 5 | @author: evaande 6 | """ 7 | 8 | def get_modal_loss_factors(modal_data): 9 | loss_factors = dict() 10 | mat_lf = modal_data['mat_loss_factors'] 11 | modes = modal_data['modes'] 12 | for mk in modes: 13 | tot_loss = 0.0 14 | tot_U = 0.0 15 | md = modes[mk] 16 | for i, s in enumerate(md['stress']): 17 | mlf = mat_lf[md['material'][i]] 18 | e = md['strain'][i] 19 | vol = md['volume'][i] 20 | U_el = 0.0 21 | del_U = 0.0 22 | for j in range(0,6): 23 | try: 24 | p = s[j]*e[j] 25 | except: 26 | p = float(s[j])*float(e[j]) 27 | U_el = U_el + p 28 | del_U = del_U + p*mlf[j] 29 | p = 0.5*vol 30 | U_el = p*U_el 31 | del_U = p*del_U 32 | tot_U = tot_U + U_el 33 | tot_loss = tot_loss + del_U 34 | loss_factors[mk] = tot_loss/tot_U 35 | return loss_factors -------------------------------------------------------------------------------- /src/pynumad/utils/interpolation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from scipy.interpolate import interp1d, PchipInterpolator, PPoly, CubicSpline 4 | 5 | 6 | def interpolator_wrap(x, v, xq, method="linear", axis=0, extrapolation=None): 7 | """This function is designed to emulate the arg structure and output 8 | of matlabs interp1d function. 9 | 10 | Parameters 11 | ---------- 12 | x : array 13 | v : array 14 | xq : array 15 | method : str 16 | Defaults to 'linear'. 17 | axis : int 18 | Defaults to 0. 19 | extrapolation :bool 20 | Defaults to None. 21 | 22 | Returns: 23 | array : 24 | """ 25 | if method == "linear": 26 | interpolator = interp1d( 27 | x, v, "linear", axis, bounds_error=False, fill_value="extrapolate" 28 | ) 29 | vq = interpolator(xq) 30 | elif method == "pchip": 31 | interpolator = PchipInterpolator(x, v, axis, extrapolate=True) 32 | vq = interpolator(xq) 33 | elif method == "spline": 34 | interpolator = interp1d( 35 | x, v, "cubic", axis, bounds_error=False, fill_value="extrapolate" 36 | ) 37 | vq = interpolator(xq) 38 | return vq 39 | # if method == 'pp': 40 | # pass 41 | # if method == 'v5cubic': 42 | # raise Exception("Method error for interpolator_wrap. 'v5cubic' not implemented") 43 | # if method == 'makima': 44 | # raise Exception("Method error for interpolator_wrap. 'makima' not implemented") 45 | # if method == 'nearest': 46 | # raise Exception("Method error for interpolator_wrap. 'nearest' not implemented") 47 | # if method == 'next': 48 | # raise Exception("Method error for interpolator_wrap. 'next' not implemented") 49 | # if method == 'previous': 50 | # raise Exception("Method error for interpolator_wrap. 'previous' not implemented") 51 | 52 | 53 | def calcGenLinePP(blade_struct: dict): 54 | # TODO: docstring 55 | # Calculate blade reference line piecewise polynomials 56 | # blade_struct = calcGenLinePP(blade_struct) updates the piecewise 57 | # polynomial representation of the blade's Presweep and Precurve 58 | # reference lines. This function is called by NuMAD_genline. 59 | 60 | # The fields PresweepRef and PrecurveRef are required in blade_struct. 61 | # Each of these fields has the following data structure: 62 | # method: 'normal' | 'shear' 63 | # This field is not used by calcGenLinePP. 64 | # table: N-by-3 matrix with columns span,offset,slope 65 | # This table provides the offset and slope constraints of the 66 | # reference line at specific spanwise locations along the 67 | # blade. NaN may be used wherever a constraint is not 68 | # desired. 69 | # pptype: 'poly' | 'spline' | 'pchip' | 'linear' | 'disabled' 70 | # This field selects the interpolation method to use to 71 | # create the piecewise polynomial 72 | # poly = minimum order polynomial which satisfies all constraints 73 | # spline = cubic spline (offset constraints only) 74 | # pchip = shape-preserving cubic spline (offset constraints only) 75 | # linear = linear interpolation (offset constraints only) 76 | # disabled = returns straight line 77 | # pp: piecewise polynomial data created by this function 78 | # dpp: piecewise polynomial data of reference line's derivative 79 | 80 | # See also NuMAD_genline, PPoly, interp1. 81 | 82 | # PresweepRef 83 | spline_type = blade_struct["PresweepRef"]["pptype"] 84 | PresweepRef = blade_struct["PresweepRef"]["table"] 85 | if spline_type in ["linear", "spline", "pchip"]: 86 | if PresweepRef.shape[0] > 1: 87 | pp = CubicSpline(PresweepRef[:, 0], PresweepRef[:, 1]) 88 | else: 89 | pp = CubicSpline([0, 1], [0, 0]) 90 | 91 | blade_struct["PresweepRef"]["pp"] = pp 92 | # dc = np.diag(np.arange(pp.order - 1,1+- 1,- 1),1) 93 | 94 | # blade_struct["PresweepRef"]["dpp"] = PPoly(pp.breaks,pp.coefs * dc) 95 | 96 | # PrecurveRef 97 | spline_type = blade_struct["PrecurveRef"]["pptype"] 98 | PrecurveRef = blade_struct["PrecurveRef"]["table"] 99 | if spline_type in ["linear", "spline", "pchip"]: 100 | if PrecurveRef.shape[0] > 1: 101 | pp = CubicSpline(PrecurveRef[0, :], PrecurveRef[1, :]) 102 | else: 103 | pp = CubicSpline([0, 1], [0, 0]) 104 | 105 | blade_struct["PrecurveRef"]["pp"] = pp 106 | # dc = np.diag(np.arange(pp.order - 1,1+- 1,- 1),1) 107 | 108 | # blade_struct["PrecurveRef"]["dpp"] = PPoly(pp.breaks,pp.coefs * dc) 109 | 110 | return blade_struct 111 | -------------------------------------------------------------------------------- /src/pynumad/utils/misc_utils.py: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Part of the SNL NuMAD Toolbox # 3 | # Developed by Sandia National Laboratories Wind Energy Technologies # 4 | # See license.txt for disclaimer information # 5 | ######################################################################## 6 | 7 | import numpy as np 8 | 9 | def setup_logging(file_name): 10 | import logging 11 | 12 | log = logging.getLogger(__name__) 13 | log.setLevel(logging.DEBUG) 14 | fh = logging.FileHandler(file_name+'.log', mode='w') 15 | log.addHandler(fh) 16 | return log 17 | 18 | def full_keys_from_substrings(key_list, subtring_list,ignore_case=True): 19 | """ 20 | Example Usage: 21 | subString = ['B1N3TDx'] 22 | res=full_keys_from_substrings(df.keys(),subString) 23 | 24 | output: 25 | ['B1N3TDxr_[m]'] 26 | 27 | Example Usage: 28 | subString = ['B2','TDx'] 29 | res=full_keys_from_substrings(df.keys(),subString) 30 | 31 | output: 32 | ['B2N1TDxr_[m]', 'B2N2TDxr_[m]', 'B2N3TDxr_[m]', 'B2N4TDxr_[m]', 'B2N5TDxr_[m]', 'B2N6TDxr_[m]', 'B2N7TDxr_[m]', 'B2N8TDxr_[m]', 'B2N9TDxr_[m]', 'B2TipTDxr_[m]'] 33 | 34 | 35 | """ 36 | matched_keys = [] 37 | if ignore_case: 38 | 39 | for key in key_list: 40 | if all(substring.lower() in key.lower() for substring in subtring_list): 41 | matched_keys.append(key) 42 | else: 43 | for key in key_list: 44 | if all(substring in key for substring in subtring_list): 45 | matched_keys.append(key) 46 | return matched_keys 47 | 48 | 49 | # SED-like substitution 50 | def copy_and_replace(fin, fout, replacements): 51 | inf = open(fin, "r") 52 | outf = open(fout, "w") 53 | for line in inf: 54 | for src, target in replacements.items(): 55 | line = line.replace(src, target) 56 | outf.write(line) 57 | inf.close() 58 | outf.close() 59 | 60 | 61 | def LARCetaT(alp0): 62 | # TODO complete docstring 63 | """Compute the coefficient of transverse influence required for Larc failure criteria. 64 | 65 | "In the absence of biaxial test data, ?L can be estimated from the longitudinal 66 | and transverse shear strengths." Failure Criteria for FRP Laminates in Plane Stress 67 | Carlos G. Dávila, Pedro P. Camanho 68 | 69 | Parameters 70 | ---------- 71 | alp0 72 | Material fracture angle, degrees 73 | 74 | Returns 75 | ------- 76 | etaT 77 | """ 78 | num = -1 79 | denom = np.tan(np.deg2rad(2 * alp0)) 80 | with np.errstate(divide="ignore", invalid="ignore"): 81 | etaT = num / denom 82 | return etaT 83 | 84 | 85 | def LARCetaL(SL, YC, alp0): 86 | # TODO complete docstring 87 | """Compute the coefficient of longitudinal influence required for Larc failure criteria. 88 | 89 | "In the absence of biaxial test data, ?L can be estimated from the longitudinal 90 | and transverse shear strengths." Failure Criteria for FRP Laminates in Plane Stress 91 | Carlos G. Dávila, Pedro P. Camanho 92 | 93 | Parameters 94 | ---------- 95 | SL 96 | Lateral shear strength 97 | YC 98 | Transverse compressive strength 99 | alp0 100 | Material fracture angle, degrees 101 | Returns 102 | ------- 103 | etaL 104 | """ 105 | if alp0: 106 | num = -SL * np.cos(np.deg2rad(2 * alp0)) 107 | denom = YC * np.cos(np.deg2rad(alp0)) ** 2 108 | with np.errstate(divide="ignore", invalid="ignore"): 109 | etaL = num / denom 110 | else: 111 | etaL = None 112 | return etaL 113 | 114 | 115 | def _parse_data(data): 116 | """Helper function for parsing data from blade yaml files. 117 | 118 | Parameters 119 | ---------- 120 | data 121 | a number or list of numbers where numbers can be floats or strings 122 | e.g. 3.0 or '1.2e2' 123 | 124 | Returns 125 | ------- 126 | parsed_data 127 | single float or array of floats 128 | """ 129 | try: 130 | # detect whether data is list 131 | data + [] 132 | except TypeError: # case for single data point 133 | parsed_data = float(data) 134 | else: 135 | parsed_data = np.array( 136 | [float(val) for val in data] 137 | ) # case for list of data points 138 | finally: 139 | return parsed_data 140 | -------------------------------------------------------------------------------- /src/pynumad/utils/orientations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | 5 | def getDCM(globalAxisBasisVectors,newCoordinateSystemVectors): 6 | dcm=np.zeros([3,3]) 7 | for iRow in range(3): 8 | for iColumn in range(3): 9 | dcm[iRow,iColumn]=np.dot(newCoordinateSystemVectors[iRow],globalAxisBasisVectors[iColumn]) 10 | 11 | # check if matrix is orthogonal 12 | dot_product = np.dot(dcm, dcm.T) 13 | identity_matrix = np.identity(len(dcm)) 14 | 15 | matrixIsOrthogonal=np.allclose(dot_product, identity_matrix,atol=0.01) 16 | 17 | if matrixIsOrthogonal: 18 | return dcm 19 | else: 20 | raise ValueError(f'Direction cosine matrix is not orthogonal {dot_product}') 21 | 22 | 23 | def dcmToEulerAngles(dcm): 24 | 25 | ''' 26 | Formulas from: 27 | Euler Angle Formulas 28 | David Eberly, Geometric Tools, Redmond WA 98052 29 | https://www.geometrictools.com/ 30 | ''' 31 | #RzRyRx 32 | if round(dcm[2,0],3)<1: 33 | if round(dcm[2,0],3)>-1: 34 | theta2=math.asin(-dcm[2,0]) 35 | theta3=math.atan2(dcm[1,0],dcm[0,0]) 36 | theta1=math.atan2(dcm[2,1],dcm[2,2]) 37 | else: #r20 =-1 38 | theta2=math.pi/2 39 | theta3=-math.atan2(-dcm[1,2],dcm[1,1]) 40 | theta1=0 41 | else:#r20 =1 42 | theta2=-math.pi/2 43 | theta3=math.atan2(-dcm[1,2],dcm[1,1]) 44 | theta1=0 45 | return theta1*180/math.pi,theta2*180/math.pi,theta3*180/math.pi --------------------------------------------------------------------------------