From 317d0625e8399e70bc2b15c6af0baef1d403ee31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 10 Jan 2024 14:33:03 +0100 Subject: [PATCH 01/19] Adding .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ba5476 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.pytest_cache/ +Panel_Aero.egg-info/ \ No newline at end of file From b5889a0ba1f5a90e384b97cc1732130e372000c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 11 Jan 2024 15:46:54 +0100 Subject: [PATCH 02/19] Make installation more granulat with core dependencies (only numpy) and a test section. Adjust installation instructions and unify wording. --- .github/workflows/regression-tests.yml | 2 +- .gitlab-ci.yml | 8 ++++---- README.md | 9 ++++----- setup.py | 14 ++++++-------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index e40a472..2ce7fca 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -26,7 +26,7 @@ jobs: run: | python -m pip install --upgrade pip # Install with -e (in editable mode) to allow the tracking of the test coverage - pip install -e . + pip install -e .[test] - name: Analyse the code with pytest run: | # Run the actual testing diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4147c85..e74b468 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,8 +7,8 @@ stages: - deploy .virtenv: &virtualenv - - python -m venv buildenv - - . buildenv/bin/activate + - python -m venv virtualenv + - source virtualenv/bin/activate build: # This stage only tests if the installation is possible. @@ -31,11 +31,11 @@ test: script: - *virtualenv # Install with -e (in editable mode) to allow the tracking of the test coverage - - pip install -e . + - pip install -e .[test] - which python - which pytest # Run the actual testing - - pytest -v --basetemp=./test_tmp --cov-report xml:coverage.xml --cov=panelaero --junitxml=testresult.xml + - pytest -v --basetemp=./tmp --cov-report xml:coverage.xml --cov=panelaero --junitxml=testresult.xml - coverage xml -o coverage.xml - coverage report - coverage html --directory ./coverage diff --git a/README.md b/README.md index 73ab25c..840c3c2 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,14 @@ If you use this software for your scientific work, we kindly ask you to include To install everything as a python package, including dependencies: ``` -pip install --user git+https://github.com/DLR-AE/PanelAero.git +pip install git+https://github.com/DLR-AE/PanelAero.git ``` ## How can I use it? In Python, you can import the VLM or the DLM as shown below. For further details, please see the example section. ``` -import panelaero.VLM as VLM -import panelaero.DLM as DLM +from panelaero import VLM, DLM ``` ## Developer installation @@ -29,8 +28,8 @@ As above, but with access to the code (keep the code where it is so that you can ``` git clone https://github.com/DLR-AE/PanelAero.git -cd ./panel-aero -pip install --user -e . +cd +pip install -e . ``` # License diff --git a/setup.py b/setup.py index 69dac7e..d8d160b 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,6 @@ """ -Setup file, currently supports: - -- installation via "pip install --user " -- installation via "python setup.py install --user" +Install Panel Aero with core dependencies via: +- pip install -e """ from setuptools import setup, find_packages @@ -18,10 +16,10 @@ def my_setup(): license='BSD 3-Clause License', packages=find_packages(), python_requires='>=3.7', - install_requires=[ - 'numpy', - 'pytest', - 'pytest-cov',], + install_requires=['numpy'], + extras_require={'test': ['pytest', + 'pytest-cov', + ]}, ) From 66c5347bddb719fb84646cfbe3d8a8114953b240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 22 Jan 2024 17:25:53 +0100 Subject: [PATCH 03/19] - Move rename examples to tutorials - Add first jupyter notebook - Adjust plotting function to allow embedding in jupyter notebook --- .../helper_functions}/build_aeromodel.py | 0 .../helper_functions}/plotting.py | 24 +- .../simplewing}/simplewing.CAERO1 | 0 .../simplewing}/simplewing_DLM_only.py | 4 +- tutorials/simplewing/simplewing_steady.ipynb | 244 ++++++++++++++++++ 5 files changed, 259 insertions(+), 13 deletions(-) rename {example => tutorials/helper_functions}/build_aeromodel.py (100%) rename {example => tutorials/helper_functions}/plotting.py (81%) rename {example => tutorials/simplewing}/simplewing.CAERO1 (100%) rename {example => tutorials/simplewing}/simplewing_DLM_only.py (89%) create mode 100644 tutorials/simplewing/simplewing_steady.ipynb diff --git a/example/build_aeromodel.py b/tutorials/helper_functions/build_aeromodel.py similarity index 100% rename from example/build_aeromodel.py rename to tutorials/helper_functions/build_aeromodel.py diff --git a/example/plotting.py b/tutorials/helper_functions/plotting.py similarity index 81% rename from example/plotting.py rename to tutorials/helper_functions/plotting.py index 7456913..a02b0b7 100755 --- a/example/plotting.py +++ b/tutorials/helper_functions/plotting.py @@ -12,7 +12,7 @@ class DetailedPlots(): def __init__(self, model): self.model = model - def plot_aerogrid(self, scalars=None, colormap='plasma', value_min=None, value_max=None): + def plot_aerogrid(self, scalars=None, colormap='plasma', embed_in_notebook=False): # create the unstructured grid points = self.model.aerogrid['cornerpoint_grids'][:, (1, 2, 3)] ug = tvtk.UnstructuredGrid(points=points) @@ -24,28 +24,25 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', value_min=None, value_m ug.cell_data.scalars = scalars # hand over unstructured grid to mayavi - mlab.figure(bgcolor=(1, 1, 1)) + if embed_in_notebook: + mlab.init_notebook('png') + mlab.figure(bgcolor=(1, 1, 1), size=(900,450)) src_aerogrid = mlab.pipeline.add_dataset(ug) # determine if suitable scalar data is given if scalars is not None: - # determine an upper and lower limit of the colorbar, if not given - if value_min is None: - value_min = scalars.min() - if value_max is None: - value_max = scalars.max() surface = mlab.pipeline.surface(src_aerogrid, opacity=1.0, line_width=0.5, - colormap=colormap, vmin=value_min, vmax=value_max) + colormap=colormap, vmin=scalars.min(), vmax=scalars.max()) surface.actor.mapper.scalar_visibility = True surface.module_manager.scalar_lut_manager.show_legend = True surface.module_manager.scalar_lut_manager.data_name = '' surface.module_manager.scalar_lut_manager.label_text_property.color = (0, 0, 0) - surface.module_manager.scalar_lut_manager.label_text_property.font_family = 'times' + surface.module_manager.scalar_lut_manager.label_text_property.font_family = 'arial' surface.module_manager.scalar_lut_manager.label_text_property.bold = False surface.module_manager.scalar_lut_manager.label_text_property.italic = False surface.module_manager.scalar_lut_manager.title_text_property.color = (0, 0, 0) - surface.module_manager.scalar_lut_manager.title_text_property.font_family = 'times' + surface.module_manager.scalar_lut_manager.title_text_property.font_family = 'arial' surface.module_manager.scalar_lut_manager.title_text_property.bold = False surface.module_manager.scalar_lut_manager.title_text_property.italic = False surface.module_manager.scalar_lut_manager.number_of_labels = 5 @@ -53,4 +50,9 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', value_min=None, value_m surface = mlab.pipeline.surface(src_aerogrid, opacity=1.0, line_width=0.5) surface.actor.mapper.scalar_visibility = False surface.actor.property.edge_visibility = True - mlab.show() + mlab.view(azimuth=60.0, elevation=-65.0, roll=55.0) + + if embed_in_notebook: + return surface + else: + mlab.show() diff --git a/example/simplewing.CAERO1 b/tutorials/simplewing/simplewing.CAERO1 similarity index 100% rename from example/simplewing.CAERO1 rename to tutorials/simplewing/simplewing.CAERO1 diff --git a/example/simplewing_DLM_only.py b/tutorials/simplewing/simplewing_DLM_only.py similarity index 89% rename from example/simplewing_DLM_only.py rename to tutorials/simplewing/simplewing_DLM_only.py index 16cb841..e3ce868 100644 --- a/example/simplewing_DLM_only.py +++ b/tutorials/simplewing/simplewing_DLM_only.py @@ -6,8 +6,8 @@ # Import from this repository from panelaero import DLM -from example.build_aeromodel import AeroModel -from example.plotting import DetailedPlots +from tutorials.helper_functions.build_aeromodel import AeroModel +from tutorials.helper_functions.plotting import DetailedPlots # build a model that includes the aerodynmic grid model = AeroModel('./simplewing.CAERO1') diff --git a/tutorials/simplewing/simplewing_steady.ipynb b/tutorials/simplewing/simplewing_steady.ipynb new file mode 100644 index 0000000..cbb610b --- /dev/null +++ b/tutorials/simplewing/simplewing_steady.ipynb @@ -0,0 +1,244 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3538aa5a-ffff-46db-a50e-2757151d302d", + "metadata": {}, + "source": [ + "# Simple Wing with Steady Aerodynamics\n", + "This is a short demonstration on how to use the Vortex Lattice Method (VLM) at the example of a simple wing.\n", + "\n", + "It is also assumed that you sucessfully installed the software as a Python package as described in the [README](https://github.com/DLR-AE/PanelAero?tab=readme-ov-file#installation--use).\n", + "\n", + "Let's see if we can import the software, which is a good indictor for a sucessful installation. In addition to the VLM inself, this tutorial also uses numpy and some helper functions provided along with the tutorials." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6690bb8-9b7f-4a41-a4b4-4e0ff06b2dfc", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "from panelaero import VLM\n", + "\n", + "from tutorials.helper_functions import build_aeromodel, plotting" + ] + }, + { + "cell_type": "markdown", + "id": "86ef8897-486e-4a68-a105-0b93db28f476", + "metadata": {}, + "source": [ + "## Set-up of Geometry\n", + "The geometry of the lifting surfaces is typically described using CAERO1 cards in Nastran eight-character-notation. Each field has exactly 8 characters and the format is defined in the following way:\n", + "```\n", + "$------><------><------><------><------><------><------><------><------>\n", + "CAERO1 EID CP NSPAN NCHORD + \n", + "+ X1 Y1 Z1 X12 X4 Y4 Z4 X34\n", + "```\n", + "With:\n", + "\n", + "EID = Element identification number \n", + "CP = Coordinate system \n", + "NSPAN = Number of spanwise panels \n", + "NCHORD = Number of chordwise panels \n", + "X1, Y1, Z1 = Location of wing root leading edge \n", + "X4, Y4, Z4 = Location of wing tip leading edge \n", + "X12, X34 = Chord length at wing root and tip \n", + "\n", + "Note that Nastran allows more fields / options on this card (see Nastran Quick Reference Guide), but not all of them are needed for this example or are not supported and skipped by the reader.\n", + "\n", + "\n", + "This is the CAERO1 input card for a simple, rectangular wing with a wing span of 1.104 m, measured from tip to tip, and a chord length of 0.1 m. The wing is discretized with 40x10 panels.\n", + "```\n", + "$------><------><------><------><------><------><------><------><------>\n", + "CAERO1 6401001 1001 0 40 10 1+ \n", + "+ 0.0 -0.552 0.0 0.1 0.0 +0.552 0.0 0.1\n", + "```\n", + "\n", + "A simple reader (build_aeromodel.py) is provied along with the examples, which is now used to parse the CAERO1 card." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "084da664-c344-4ec0-8b91-90ee90027821", + "metadata": {}, + "outputs": [], + "source": [ + "model = build_aeromodel.AeroModel('./simplewing.CAERO1')\n", + "model.build_aerogrid()" + ] + }, + { + "cell_type": "markdown", + "id": "333a4a40-221a-4cf8-949f-8db21e6711f7", + "metadata": {}, + "source": [ + "The result is a Python dictionary, which contains the geometrical information of all 400 aerodynamic panels. If you have Mayavi installed, the aerogrid can be visualized using the plotting routines (plotting.py) provided along with the examples." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "561965f7-4ca5-43e3-ac2f-46a265c27a01", + "metadata": {}, + "outputs": [], + "source": [ + "plots = plotting.DetailedPlots(model)\n", + "plots.plot_aerogrid(embed_in_notebook=True)" + ] + }, + { + "cell_type": "markdown", + "id": "52e947bc-7854-4389-a62f-4396ddaecee5", + "metadata": {}, + "source": [ + "## Calculate the AIC Matrix\n", + "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided in this software calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", + "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma) \\cdot \\mathbf{w_j}$$\n", + "The $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa69f70a-cef1-473e-b45e-a9112b37e2a0", + "metadata": {}, + "outputs": [], + "source": [ + "Qjj, _ = VLM.calc_Qjj(model.aerogrid, Ma=0.0)" + ] + }, + { + "cell_type": "markdown", + "id": "73ae40c7-6be5-4892-a83d-cc2c462cc1af", + "metadata": {}, + "source": [ + "Next, let's assume an onflow condition with Vtas = 25.0 m/s and an induced downwash of wj = 2.18 m/s on every panels, which corresponds to an angle of attack = 5.0 deg. Note that the downwash is scaled with Vtas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36e05a09-a8e8-41d6-a07f-2c346c85447c", + "metadata": {}, + "outputs": [], + "source": [ + "Vtas = 25.0\n", + "wj = np.ones(model.aerogrid['n']) * 2.18 / Vtas" + ] + }, + { + "cell_type": "markdown", + "id": "5ea5ba2d-58d2-4698-9aa6-539afcac4f6d", + "metadata": {}, + "source": [ + "Following the formula given above, multiplication of the downwash vector $\\mathbf{w_j}$ with the $\\mathbf{AIC}$ matrix yields the pressure coefficient distribution $\\mathbf{\\Delta c_p}$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c3aa2f6-603c-4e00-9c63-ce9536a6127b", + "metadata": {}, + "outputs": [], + "source": [ + "cp = Qjj.dot(wj)" + ] + }, + { + "cell_type": "markdown", + "id": "a7e5e789-1533-4dee-a25b-c82ed4aa1194", + "metadata": {}, + "source": [ + "## Vizualisation of Results\n", + "The pressure coefficient distribution may be visualized on the geometry using the helper function from above.\n", + "\n", + "Advanced option: To get an interactive 3D plot, use 'embed_in_notebook=False', which renders the plot in a new window." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee6da93c-8de5-48a4-a5c6-93edcb190d89", + "metadata": {}, + "outputs": [], + "source": [ + "plots.plot_aerogrid(cp, embed_in_notebook=True)" + ] + }, + { + "cell_type": "markdown", + "id": "a26c0090-c7a2-45a8-9cba-f7516050a6d0", + "metadata": {}, + "source": [ + "For many applications, including aeroelasticity, not nly the pressure distribution but the aerodynamic force vector is needed. From the pressure coefficient distribution cp, the aerodynamic forces Fxyz can be calculated by multiplication with area A and the normal vector of every panels as well as the dynamic pressure. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4b58a7c-a8a3-4856-97a4-4759c7f0faff", + "metadata": {}, + "outputs": [], + "source": [ + "q_dyn = 1.225 / 2.0 * Vtas ** 2\n", + "Fxyz = q_dyn * model.aerogrid['N'].T * model.aerogrid['A'] * cp" + ] + }, + { + "cell_type": "markdown", + "id": "7a7760ba-e24f-4f7b-bb96-99eebc8e9023", + "metadata": {}, + "source": [ + "The force vector Fxyz has the shape 3 x 400, reflecting the aerodynamic forces in x-, y- and z-direction per panel. Calculating the sums from all panels, the aerodynamic lift created by the wing should be Fz = 18.47 N. The componetns Fx and Fy should be zero in this case, because the wing has no dihedral and the VLM calculates no drag." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "893bb1f4-3253-4de8-b0b6-486b0a8748c6", + "metadata": {}, + "outputs": [], + "source": [ + "Fxyz.sum(axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "b02c64e3-8db5-442c-a046-3e9838276bfc", + "metadata": {}, + "source": [ + "## Summary\n", + "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. \n", + "\n", + "For more information on the theoretical background of the VLM, please consult the Technical Report DLR-IB-AE-GO-2020-137." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 64436c6eaaae888176c094297f8448261d9d3204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 22 Jan 2024 17:26:05 +0100 Subject: [PATCH 04/19] Add new dependencies --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index d8d160b..d487b45 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,9 @@ def my_setup(): install_requires=['numpy'], extras_require={'test': ['pytest', 'pytest-cov', + 'jupyter', + 'jupyter-book', + 'mayavi', ]}, ) From 46fc9c781da02ae4676adcba3db3804f2745d710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 11:31:15 +0100 Subject: [PATCH 05/19] Adding unsteady tutorial --- tutorials/simplewing/simplewing.CAERO1 | 2 +- tutorials/simplewing/simplewing_steady.ipynb | 88 ++++---- .../simplewing/simplewing_unsteady.ipynb | 198 ++++++++++++++++++ 3 files changed, 247 insertions(+), 41 deletions(-) create mode 100644 tutorials/simplewing/simplewing_unsteady.ipynb diff --git a/tutorials/simplewing/simplewing.CAERO1 b/tutorials/simplewing/simplewing.CAERO1 index cc21c82..e749175 100644 --- a/tutorials/simplewing/simplewing.CAERO1 +++ b/tutorials/simplewing/simplewing.CAERO1 @@ -1,5 +1,5 @@ $ This is the CAERO input card (see Nastran Quick Reference Guide) $ for a simple wing with 40x10 panels. $------><------><------><------><------><------><------><------><------> -CAERO1 6401001 1001 0 40 10 1+ +CAERO1 6401001 0 40 10 + + 0.0 -0.552 0.0 0.1 0.0 +0.552 0.0 0.1 \ No newline at end of file diff --git a/tutorials/simplewing/simplewing_steady.ipynb b/tutorials/simplewing/simplewing_steady.ipynb index cbb610b..50e14fd 100644 --- a/tutorials/simplewing/simplewing_steady.ipynb +++ b/tutorials/simplewing/simplewing_steady.ipynb @@ -6,9 +6,9 @@ "metadata": {}, "source": [ "# Simple Wing with Steady Aerodynamics\n", - "This is a short demonstration on how to use the Vortex Lattice Method (VLM) at the example of a simple wing.\n", + "This is a short tutorial on how to use the Vortex Lattice Method (VLM) at the example of a simple wing.\n", "\n", - "It is also assumed that you sucessfully installed the software as a Python package as described in the [README](https://github.com/DLR-AE/PanelAero?tab=readme-ov-file#installation--use).\n", + "It is assumed that you sucessfully installed PanelAero as a Python package as described in the [README](https://github.com/DLR-AE/PanelAero?tab=readme-ov-file#installation--use).\n", "\n", "Let's see if we can import the software, which is a good indictor for a sucessful installation. In addition to the VLM inself, this tutorial also uses numpy and some helper functions provided along with the tutorials." ] @@ -33,13 +33,13 @@ "metadata": {}, "source": [ "## Set-up of Geometry\n", - "The geometry of the lifting surfaces is typically described using CAERO1 cards in Nastran eight-character-notation. Each field has exactly 8 characters and the format is defined in the following way:\n", - "```\n", - "$------><------><------><------><------><------><------><------><------>\n", - "CAERO1 EID CP NSPAN NCHORD + \n", - "+ X1 Y1 Z1 X12 X4 Y4 Z4 X34\n", - "```\n", - "With:\n", + "The geometry of the lifting surfaces is described using flat panels (hence the name PanelAero), typically using CAERO1 cards in Nastran eight character notation. Each field has exactly eight characters and the format is defined in the following way (compare with Nastran Quick Reference Guide):\n", + "\n", + "``$------><------><------><------><------><------><------><------><------> `` \n", + "``CAERO1 EID CP NSPAN NCHORD +`` \n", + "``+ X1 Y1 Z1 X12 X4 Y4 Z4 X34 ``\n", + "\n", + "Note that Nastran allows more fields / options on this card, but not all of them are needed for this example and are not supported by the reader. To set-up the aerogrid, we only need the following information:\n", "\n", "EID = Element identification number \n", "CP = Coordinate system \n", @@ -49,15 +49,11 @@ "X4, Y4, Z4 = Location of wing tip leading edge \n", "X12, X34 = Chord length at wing root and tip \n", "\n", - "Note that Nastran allows more fields / options on this card (see Nastran Quick Reference Guide), but not all of them are needed for this example or are not supported and skipped by the reader.\n", + "The following input describes a simple, rectangular wing with a wing span of 1.104 m, measured from tip to tip, and a chord length of 0.1 m. The wing will be discretized with 40x10 panels.\n", "\n", - "\n", - "This is the CAERO1 input card for a simple, rectangular wing with a wing span of 1.104 m, measured from tip to tip, and a chord length of 0.1 m. The wing is discretized with 40x10 panels.\n", - "```\n", - "$------><------><------><------><------><------><------><------><------>\n", - "CAERO1 6401001 1001 0 40 10 1+ \n", - "+ 0.0 -0.552 0.0 0.1 0.0 +0.552 0.0 0.1\n", - "```\n", + "``$------><------><------><------><------><------><------><------><------> `` \n", + "``CAERO1 6401001 0 40 10 +`` \n", + "``+ 0.0 -0.552 0.0 0.1 0.0 +0.552 0.0 0.1 `` \n", "\n", "A simple reader (build_aeromodel.py) is provied along with the examples, which is now used to parse the CAERO1 card." ] @@ -78,7 +74,7 @@ "id": "333a4a40-221a-4cf8-949f-8db21e6711f7", "metadata": {}, "source": [ - "The result is a Python dictionary, which contains the geometrical information of all 400 aerodynamic panels. If you have Mayavi installed, the aerogrid can be visualized using the plotting routines (plotting.py) provided along with the examples." + "The result is a Python dictionary, which contains the geometrical information of all 400 aerodynamic panels. The panels of one CAERO card are all in the same plane and the edges are aligned in stream-wise direction. Multiple CAERO cards can be used to realize more complex wing geometries and vertical and horizontal tails may be modeled as well. Unlike Nastran, this implementation has no slender body elements. If you have Mayavi installed, the aerogrid of our simple wing can be visualized using the plotting routines (plotting.py) provided along with the examples." ] }, { @@ -92,15 +88,30 @@ "plots.plot_aerogrid(embed_in_notebook=True)" ] }, + { + "cell_type": "markdown", + "id": "060ffde8-2133-4480-874d-06fd14414dcf", + "metadata": {}, + "source": [ + "Advanced use: Assuming that you want to integrate the VLM in your own analysis code, you will probaby generate the aerodynamic grid with your own routines. This is definetly possible as long as you follow the definitions decribed in Table 2.1 in the Technical Report DLR-IB-AE-GO-2020-137 and is actually the intended use of the software." + ] + }, { "cell_type": "markdown", "id": "52e947bc-7854-4389-a62f-4396ddaecee5", "metadata": {}, "source": [ "## Calculate the AIC Matrix\n", - "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided in this software calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", + "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided by PanelAero calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma) \\cdot \\mathbf{w_j}$$\n", - "The $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained in the following way:" + "\n", + "The formulation of the VLM used and described herin follows closely the derivation given by Katz and Plotkin using horse shoe vortices as vizualized below. For more information on the theoretical background of the VLM, please consult Section 2.2 in the Technical Report DLR-IB-AE-GO-2020-137.\n", + "\n", + "
\n", + "\n", + "
\n", + "\n", + "Using the VLM, the $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained by calling VLM.calc_Qjj() along with the geometry and the desired Mach number. As the number of panels is rather small, the calculation of the $\\mathbf{AIC}$ matrix ist very fast. Note that the computation time increases with the square tot the number of panels." ] }, { @@ -150,15 +161,21 @@ "cp = Qjj.dot(wj)" ] }, + { + "cell_type": "markdown", + "id": "d221b5c4-8af0-44d1-9d25-191bfd672199", + "metadata": {}, + "source": [ + "Experimental use: The VLM also calculates a second matrix Bjj which includes only the two long edges of the horse shoe vortices and allows for the calculation of induced drag." + ] + }, { "cell_type": "markdown", "id": "a7e5e789-1533-4dee-a25b-c82ed4aa1194", "metadata": {}, "source": [ "## Vizualisation of Results\n", - "The pressure coefficient distribution may be visualized on the geometry using the helper function from above.\n", - "\n", - "Advanced option: To get an interactive 3D plot, use 'embed_in_notebook=False', which renders the plot in a new window." + "The pressure coefficient distribution may be visualized on the geometry using the helper function from above." ] }, { @@ -176,7 +193,9 @@ "id": "a26c0090-c7a2-45a8-9cba-f7516050a6d0", "metadata": {}, "source": [ - "For many applications, including aeroelasticity, not nly the pressure distribution but the aerodynamic force vector is needed. From the pressure coefficient distribution cp, the aerodynamic forces Fxyz can be calculated by multiplication with area A and the normal vector of every panels as well as the dynamic pressure. " + "In the plot above, a suction peak should be cleary visible along the leading edge and the pressure distribution should decrease from the center line towards the wing tips. Please pay attention the the sign convention: here, a positive downwash causes a positive pressure, which will lead to a positiv, upward force Fz.\n", + "\n", + "For many applications, including aeroelasticity, not only the pressure distribution but the aerodynamic force vector is needed. From the pressure coefficient distribution cp, the aerodynamic forces Fxyz can be calculated by multiplication with area A and the normal vector of every panels as well as the dynamic pressure. " ] }, { @@ -187,7 +206,8 @@ "outputs": [], "source": [ "q_dyn = 1.225 / 2.0 * Vtas ** 2\n", - "Fxyz = q_dyn * model.aerogrid['N'].T * model.aerogrid['A'] * cp" + "Fxyz = q_dyn * model.aerogrid['N'].T * model.aerogrid['A'] * cp\n", + "print(Fxyz.sum(axis=1))" ] }, { @@ -195,17 +215,7 @@ "id": "7a7760ba-e24f-4f7b-bb96-99eebc8e9023", "metadata": {}, "source": [ - "The force vector Fxyz has the shape 3 x 400, reflecting the aerodynamic forces in x-, y- and z-direction per panel. Calculating the sums from all panels, the aerodynamic lift created by the wing should be Fz = 18.47 N. The componetns Fx and Fy should be zero in this case, because the wing has no dihedral and the VLM calculates no drag." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "893bb1f4-3253-4de8-b0b6-486b0a8748c6", - "metadata": {}, - "outputs": [], - "source": [ - "Fxyz.sum(axis=1)" + "The force vector Fxyz has the shape 3 x 400, reflecting the aerodynamic forces in x-, y- and z-direction per panel. Calculating the sums from all panels, the aerodynamic lift created by the wing should be Fz = 18.47 N. The componetns Fx and Fy should be zero in this case, because the wing is flat (no dihedral) and the VLM calculates no drag." ] }, { @@ -213,10 +223,8 @@ "id": "b02c64e3-8db5-442c-a046-3e9838276bfc", "metadata": {}, "source": [ - "## Summary\n", - "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. \n", - "\n", - "For more information on the theoretical background of the VLM, please consult the Technical Report DLR-IB-AE-GO-2020-137." + "## Conclusion\n", + "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. " ] } ], diff --git a/tutorials/simplewing/simplewing_unsteady.ipynb b/tutorials/simplewing/simplewing_unsteady.ipynb new file mode 100644 index 0000000..cc6820b --- /dev/null +++ b/tutorials/simplewing/simplewing_unsteady.ipynb @@ -0,0 +1,198 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3538aa5a-ffff-46db-a50e-2757151d302d", + "metadata": {}, + "source": [ + "# Simple Wing with Unsteady Aerodynamics\n", + "After the steady aerodynamic case using the Vortec Lattice Method (VLM), this tutorial demonstrates on how to use the unsteady Doublet Lattice Method (DLM) at the example of the same, simple wing. Assuming that you already did the steady case, we will shorten/skip some of the explanations.\n", + "\n", + "It is also assumed that you sucessfully installed PanelAero as a Python package as described in the [README](https://github.com/DLR-AE/PanelAero?tab=readme-ov-file#installation--use).\n", + "\n", + "In addition to the DLM inself, this tutorial also uses numpy and some helper functions provided along with the tutorials." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6690bb8-9b7f-4a41-a4b4-4e0ff06b2dfc", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "from panelaero import DLM\n", + "\n", + "from tutorials.helper_functions import build_aeromodel, plotting" + ] + }, + { + "cell_type": "markdown", + "id": "86ef8897-486e-4a68-a105-0b93db28f476", + "metadata": {}, + "source": [ + "## Set-up of Geometry\n", + "As before, we parse the aerodynamic grid of our simple, rectangular wing defined by the CAERO1 card." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "084da664-c344-4ec0-8b91-90ee90027821", + "metadata": {}, + "outputs": [], + "source": [ + "model = build_aeromodel.AeroModel('./simplewing.CAERO1')\n", + "model.build_aerogrid()\n", + "plots = plotting.DetailedPlots(model)\n", + "plots.plot_aerogrid(embed_in_notebook=True)" + ] + }, + { + "cell_type": "markdown", + "id": "52e947bc-7854-4389-a62f-4396ddaecee5", + "metadata": {}, + "source": [ + "## Calculate the AIC Matrix\n", + "The Doublet Lattice Method (DLM) uses the same geoemtrical description of the lifting surface with aeroynamic panels as the VLM. However, the underlying modeling uses doublets insteady of vortices and works in the frequency domain. \n", + "\n", + "Like the VLM, the DLM provided in this software calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends on the geometry, the Mach number and the reduced frequency, defined by\n", + "$$k = \\frac{ \\omega }{V}$$\n", + "Note that the “Nastran definition” of the reduced frequency adds $c_\\textup{ref}/2$, leading to \n", + "$$k = {c_{ref} \\over 2V} \\cdot \\omega$$\n", + "\n", + "As before, the $\\mathbf{AIC}$ matrix then relates the induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to complex pressure coefficients $\\mathbf{\\Delta c_p}$.\n", + "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma, k) \\cdot \\mathbf{w_j}$$\n", + "For more information on the theoretical background of the DLM, please consult Section 2.3 in the Technical Report DLR-IB-AE-GO-2020-137.\n", + "\n", + "The $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa69f70a-cef1-473e-b45e-a9112b37e2a0", + "metadata": {}, + "outputs": [], + "source": [ + "Qjj = DLM.calc_Qjj(model.aerogrid, Ma=0.0, k=0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "73ae40c7-6be5-4892-a83d-cc2c462cc1af", + "metadata": {}, + "source": [ + "Next, let's assume the same onflow condition with Vtas = 25.0 m/s and an induced downwash of wj = 2.18 m/s on every panels, which corresponds to an angle of attack = 5.0 deg. Remember that the downwash is scaled with Vtas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36e05a09-a8e8-41d6-a07f-2c346c85447c", + "metadata": {}, + "outputs": [], + "source": [ + "Vtas = 25.0\n", + "wj = np.ones(model.aerogrid['n']) * 2.18 / Vtas" + ] + }, + { + "cell_type": "markdown", + "id": "5ea5ba2d-58d2-4698-9aa6-539afcac4f6d", + "metadata": {}, + "source": [ + "Following the formula given above, multiplication of the downwash vector $\\mathbf{w_j}$ with the $\\mathbf{AIC}$ matrix yields the pressure coefficient distribution $\\mathbf{\\Delta c_p}$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c3aa2f6-603c-4e00-9c63-ce9536a6127b", + "metadata": {}, + "outputs": [], + "source": [ + "cp = Qjj.dot(wj)" + ] + }, + { + "cell_type": "markdown", + "id": "a7e5e789-1533-4dee-a25b-c82ed4aa1194", + "metadata": {}, + "source": [ + "## Vizualisation of Results\n", + "The pressure coefficient distribution may be visualized on the geometry using the helper function provided along with this tutorial. Becasue we selected $k=0.1$ above, the pressure coefficients will be complex, so we vizualize the real and the imaginary part seperately. Compared to the steady aerodynamic approach using the VLM, the complex pressure coefficients obtained from the DLM change the magnitude and add a phase shift depending on the prescribed reduced frequency $k$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee6da93c-8de5-48a4-a5c6-93edcb190d89", + "metadata": {}, + "outputs": [], + "source": [ + "plots.plot_aerogrid(cp.real, embed_in_notebook=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccb2bb14-0671-4b4f-90be-4150d5e05c08", + "metadata": {}, + "outputs": [], + "source": [ + "plots.plot_aerogrid(cp.imag, embed_in_notebook=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b02c64e3-8db5-442c-a046-3e9838276bfc", + "metadata": {}, + "source": [ + "## Advanced Use\n", + "For most aeroelastic applications, typically the $\\mathbf{AIC}$ matrices are needed for different Mach numbers and for multiple reduced frequencies $k$. The function DLM.calc_Qjjs() accepts a lists as input and returns a four-dimensional array with the shape (n_mach, n_k, n_panels, n_panels] as shown in the example below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8828ac26-ae2b-4d9a-a2c3-79b1667f3ba8", + "metadata": {}, + "outputs": [], + "source": [ + "Qjjs = DLM.calc_Qjjs(model.aerogrid, Ma=[0.0, 0.5], k=[0.01, 0.1, 0.3, 0.5])\n", + "Qjjs.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c99af8f-6723-4c2d-bb85-7185e1ca71e4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3265edd7c6c8695e32e972960deda8f1c2d5afb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 11:31:30 +0100 Subject: [PATCH 06/19] Add sketch of aeropanels --- tutorials/simplewing/aeropanel_VLM.png | Bin 0 -> 55946 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 tutorials/simplewing/aeropanel_VLM.png diff --git a/tutorials/simplewing/aeropanel_VLM.png b/tutorials/simplewing/aeropanel_VLM.png new file mode 100755 index 0000000000000000000000000000000000000000..325543cb040ec30aead670d91e478f3842e26490 GIT binary patch literal 55946 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 Date: Tue, 23 Jan 2024 13:44:11 +0100 Subject: [PATCH 07/19] Updates to plotting and the text itself --- tutorials/helper_functions/plotting.py | 6 +----- tutorials/simplewing/simplewing_steady.ipynb | 11 +++++++++-- tutorials/simplewing/simplewing_unsteady.ipynb | 15 ++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tutorials/helper_functions/plotting.py b/tutorials/helper_functions/plotting.py index a02b0b7..253e79f 100755 --- a/tutorials/helper_functions/plotting.py +++ b/tutorials/helper_functions/plotting.py @@ -26,7 +26,7 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', embed_in_notebook=False # hand over unstructured grid to mayavi if embed_in_notebook: mlab.init_notebook('png') - mlab.figure(bgcolor=(1, 1, 1), size=(900,450)) + mlab.figure(bgcolor=(1, 1, 1), size=(600,450)) src_aerogrid = mlab.pipeline.add_dataset(ug) # determine if suitable scalar data is given @@ -41,10 +41,6 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', embed_in_notebook=False surface.module_manager.scalar_lut_manager.label_text_property.font_family = 'arial' surface.module_manager.scalar_lut_manager.label_text_property.bold = False surface.module_manager.scalar_lut_manager.label_text_property.italic = False - surface.module_manager.scalar_lut_manager.title_text_property.color = (0, 0, 0) - surface.module_manager.scalar_lut_manager.title_text_property.font_family = 'arial' - surface.module_manager.scalar_lut_manager.title_text_property.bold = False - surface.module_manager.scalar_lut_manager.title_text_property.italic = False surface.module_manager.scalar_lut_manager.number_of_labels = 5 else: surface = mlab.pipeline.surface(src_aerogrid, opacity=1.0, line_width=0.5) diff --git a/tutorials/simplewing/simplewing_steady.ipynb b/tutorials/simplewing/simplewing_steady.ipynb index 50e14fd..df4e346 100644 --- a/tutorials/simplewing/simplewing_steady.ipynb +++ b/tutorials/simplewing/simplewing_steady.ipynb @@ -103,13 +103,12 @@ "source": [ "## Calculate the AIC Matrix\n", "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided by PanelAero calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", + "\n", "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma) \\cdot \\mathbf{w_j}$$\n", "\n", "The formulation of the VLM used and described herin follows closely the derivation given by Katz and Plotkin using horse shoe vortices as vizualized below. For more information on the theoretical background of the VLM, please consult Section 2.2 in the Technical Report DLR-IB-AE-GO-2020-137.\n", "\n", - "
\n", "\n", - "
\n", "\n", "Using the VLM, the $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained by calling VLM.calc_Qjj() along with the geometry and the desired Mach number. As the number of panels is rather small, the calculation of the $\\mathbf{AIC}$ matrix ist very fast. Note that the computation time increases with the square tot the number of panels." ] @@ -226,6 +225,14 @@ "## Conclusion\n", "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. " ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cc1edf3-8476-4857-9fff-0106805a6f2c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/tutorials/simplewing/simplewing_unsteady.ipynb b/tutorials/simplewing/simplewing_unsteady.ipynb index cc6820b..ec3cd4b 100644 --- a/tutorials/simplewing/simplewing_unsteady.ipynb +++ b/tutorials/simplewing/simplewing_unsteady.ipynb @@ -58,12 +58,17 @@ "The Doublet Lattice Method (DLM) uses the same geoemtrical description of the lifting surface with aeroynamic panels as the VLM. However, the underlying modeling uses doublets insteady of vortices and works in the frequency domain. \n", "\n", "Like the VLM, the DLM provided in this software calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends on the geometry, the Mach number and the reduced frequency, defined by\n", + "\n", "$$k = \\frac{ \\omega }{V}$$\n", - "Note that the “Nastran definition” of the reduced frequency adds $c_\\textup{ref}/2$, leading to \n", + "\n", + "Note that the \"Nastran definition\" of the reduced frequency adds $c_\\textup{ref}/2$, leading to \n", + "\n", "$$k = {c_{ref} \\over 2V} \\cdot \\omega$$\n", "\n", "As before, the $\\mathbf{AIC}$ matrix then relates the induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to complex pressure coefficients $\\mathbf{\\Delta c_p}$.\n", + "\n", "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma, k) \\cdot \\mathbf{w_j}$$\n", + "\n", "For more information on the theoretical background of the DLM, please consult Section 2.3 in the Technical Report DLR-IB-AE-GO-2020-137.\n", "\n", "The $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained in the following way:" @@ -164,14 +169,6 @@ "Qjjs = DLM.calc_Qjjs(model.aerogrid, Ma=[0.0, 0.5], k=[0.01, 0.1, 0.3, 0.5])\n", "Qjjs.shape" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c99af8f-6723-4c2d-bb85-7185e1ca71e4", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 1fece3dd2c54c8cdb646c25f2826a1131dee63fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 13:45:29 +0100 Subject: [PATCH 08/19] Add files to build the book --- tutorials/_config.yml | 53 ++++++++++++++++++++++++++++++++++++++++ tutorials/_toc.yml | 8 ++++++ tutorials/intro.md | 11 +++++++++ tutorials/references.bib | 12 +++++++++ 4 files changed, 84 insertions(+) create mode 100644 tutorials/_config.yml create mode 100644 tutorials/_toc.yml create mode 100644 tutorials/intro.md create mode 100644 tutorials/references.bib diff --git a/tutorials/_config.yml b/tutorials/_config.yml new file mode 100644 index 0000000..88b69a1 --- /dev/null +++ b/tutorials/_config.yml @@ -0,0 +1,53 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: Panel Aero Tutorials +author: Arne Voß + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +latex: + latex_documents: + targetname: tutorials.tex + +# Add a bibtex file so that we can create citations +bibtex_bibfiles: + - references.bib + +# Information about where the book exists on the web +repository: + url: https://github.com/DLR-AE/PanelAero # Online location of your book + path_to_book: ./tutorials # Optional path to your book, relative to the repository root + branch: master # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true + +# Set parameter to prevent that each line of the console output is displayed as a new cell. +# From what I understood, this is because the log messages are a stream, and streams are rendered +# differently by Jupyter. +sphinx: + config: + nb_merge_streams: true + +# Parse and render settings +parse: + myst_enable_extensions: # default extensions to enable in the myst parser. See https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html + # - amsmath + - colon_fence + # - deflist + - dollarmath + # - html_admonition + - html_image + - linkify + # - replacements + # - smartquotes + - substitution + - tasklist \ No newline at end of file diff --git a/tutorials/_toc.yml b/tutorials/_toc.yml new file mode 100644 index 0000000..019f0a4 --- /dev/null +++ b/tutorials/_toc.yml @@ -0,0 +1,8 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: simplewing/simplewing_steady +- file: simplewing/simplewing_unsteady \ No newline at end of file diff --git a/tutorials/intro.md b/tutorials/intro.md new file mode 100644 index 0000000..4e586e3 --- /dev/null +++ b/tutorials/intro.md @@ -0,0 +1,11 @@ +# Panel Aero Tutorials + +While the Technical Report {cite}`vos_implementation_2020` mainly describes the theoretical background, this collection of tutorials focuses on how to use the software. + +```{tableofcontents} +``` + + +```{bibliography} +:style: unsrt +``` \ No newline at end of file diff --git a/tutorials/references.bib b/tutorials/references.bib new file mode 100644 index 0000000..880943c --- /dev/null +++ b/tutorials/references.bib @@ -0,0 +1,12 @@ + +@techreport{vos_implementation_2020, + address = {Göttingen, Germany}, + type = {Technical {Report}}, + title = {An {Implementation} of the {Vortex} {Lattice} and the {Doublet} {Lattice} {Method}}, + url = {https://elib.dlr.de/136536/}, + number = {DLR-IB-AE-GO-2020-137}, + institution = {Institut für Aeroelastik, Deutsches Zentrum für Luft- und Raumfahrt}, + author = {Voß, Arne}, + year = {2020}, + file = {Voß - 2020 - An Implementation of the Vortex Lattice and the Do.pdf:/work/voss_ar/Literatur/Paper/Voß - 2020 - An Implementation of the Vortex Lattice and the Do.pdf:application/pdf}, +} From 1c821d68bcafa72b6654b6ff31b929bc5a1d5700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 13:45:43 +0100 Subject: [PATCH 09/19] Remove old Python example --- tutorials/simplewing/simplewing_DLM_only.py | 37 --------------------- 1 file changed, 37 deletions(-) delete mode 100644 tutorials/simplewing/simplewing_DLM_only.py diff --git a/tutorials/simplewing/simplewing_DLM_only.py b/tutorials/simplewing/simplewing_DLM_only.py deleted file mode 100644 index e3ce868..0000000 --- a/tutorials/simplewing/simplewing_DLM_only.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -This is a short demonstration on how to use the DLM at the example of a simple wing. -""" -# Imports from python -import numpy as np - -# Import from this repository -from panelaero import DLM -from tutorials.helper_functions.build_aeromodel import AeroModel -from tutorials.helper_functions.plotting import DetailedPlots - -# build a model that includes the aerodynmic grid -model = AeroModel('./simplewing.CAERO1') -model.build_aerogrid() - -# run the DLM -# with k = omega/U, the "classical" definition, not Nastran definition! -Qjj = DLM.calc_Qjj(model.aerogrid, Ma=0.0, k=0.1) - -# set-up some generic onflow -Vtas = 25.0 -q_dyn = 1.225 / 2.0 * Vtas ** 2 -# downwash of 1.0 m/s on every panel, scaled with Vtas -wj = np.ones(model.aerogrid['n']) * 1.0 / Vtas -# calculate the pressure coefficent distribution -cp = Qjj.dot(wj) - -# Plot of real and imaginary parts, note that these plots use mayavi and tvtk -# (possibly not installed by default and to keep the list of dependencies small). -plots = DetailedPlots(model=model) -plots.plot_aerogrid(cp.real) -plots.plot_aerogrid(cp.imag) - -# a force vector is calculated like this -Fxyz = q_dyn * model.aerogrid['N'].T * model.aerogrid['A'] * Qjj.dot(wj) - -print('Done.') From 0e13597a607fa3127be4bee9e89bfdb266c71401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 13:57:35 +0100 Subject: [PATCH 10/19] Moving Tutorials into ./doc/tutorials --- {tutorials => doc/tutorials}/_config.yml | 0 {tutorials => doc/tutorials}/_toc.yml | 0 .../tutorials}/helper_functions/build_aeromodel.py | 0 .../tutorials}/helper_functions/plotting.py | 0 {tutorials => doc/tutorials}/intro.md | 0 {tutorials => doc/tutorials}/references.bib | 0 .../tutorials}/simplewing/aeropanel_VLM.png | Bin .../tutorials}/simplewing/simplewing.CAERO1 | 0 .../tutorials}/simplewing/simplewing_steady.ipynb | 0 .../tutorials}/simplewing/simplewing_unsteady.ipynb | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename {tutorials => doc/tutorials}/_config.yml (100%) rename {tutorials => doc/tutorials}/_toc.yml (100%) rename {tutorials => doc/tutorials}/helper_functions/build_aeromodel.py (100%) rename {tutorials => doc/tutorials}/helper_functions/plotting.py (100%) rename {tutorials => doc/tutorials}/intro.md (100%) rename {tutorials => doc/tutorials}/references.bib (100%) rename {tutorials => doc/tutorials}/simplewing/aeropanel_VLM.png (100%) rename {tutorials => doc/tutorials}/simplewing/simplewing.CAERO1 (100%) rename {tutorials => doc/tutorials}/simplewing/simplewing_steady.ipynb (100%) rename {tutorials => doc/tutorials}/simplewing/simplewing_unsteady.ipynb (100%) diff --git a/tutorials/_config.yml b/doc/tutorials/_config.yml similarity index 100% rename from tutorials/_config.yml rename to doc/tutorials/_config.yml diff --git a/tutorials/_toc.yml b/doc/tutorials/_toc.yml similarity index 100% rename from tutorials/_toc.yml rename to doc/tutorials/_toc.yml diff --git a/tutorials/helper_functions/build_aeromodel.py b/doc/tutorials/helper_functions/build_aeromodel.py similarity index 100% rename from tutorials/helper_functions/build_aeromodel.py rename to doc/tutorials/helper_functions/build_aeromodel.py diff --git a/tutorials/helper_functions/plotting.py b/doc/tutorials/helper_functions/plotting.py similarity index 100% rename from tutorials/helper_functions/plotting.py rename to doc/tutorials/helper_functions/plotting.py diff --git a/tutorials/intro.md b/doc/tutorials/intro.md similarity index 100% rename from tutorials/intro.md rename to doc/tutorials/intro.md diff --git a/tutorials/references.bib b/doc/tutorials/references.bib similarity index 100% rename from tutorials/references.bib rename to doc/tutorials/references.bib diff --git a/tutorials/simplewing/aeropanel_VLM.png b/doc/tutorials/simplewing/aeropanel_VLM.png similarity index 100% rename from tutorials/simplewing/aeropanel_VLM.png rename to doc/tutorials/simplewing/aeropanel_VLM.png diff --git a/tutorials/simplewing/simplewing.CAERO1 b/doc/tutorials/simplewing/simplewing.CAERO1 similarity index 100% rename from tutorials/simplewing/simplewing.CAERO1 rename to doc/tutorials/simplewing/simplewing.CAERO1 diff --git a/tutorials/simplewing/simplewing_steady.ipynb b/doc/tutorials/simplewing/simplewing_steady.ipynb similarity index 100% rename from tutorials/simplewing/simplewing_steady.ipynb rename to doc/tutorials/simplewing/simplewing_steady.ipynb diff --git a/tutorials/simplewing/simplewing_unsteady.ipynb b/doc/tutorials/simplewing/simplewing_unsteady.ipynb similarity index 100% rename from tutorials/simplewing/simplewing_unsteady.ipynb rename to doc/tutorials/simplewing/simplewing_unsteady.ipynb From f6404b5e45be517d4a978240c8d84901302ad65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 14:05:40 +0100 Subject: [PATCH 11/19] - Add Jupyter-book to regression testing - Add dependencies for [tutorials] to setup.py --- .github/workflows/regression-tests.yml | 80 ++++++++++++++++++++------ setup.py | 10 ++-- 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 2ce7fca..fad088f 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -1,6 +1,4 @@ -# This workflow will install and then lint the code with Flake8 and Pylint. -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - +# This workflow will run some regression tests. name: Regression Tests on: @@ -10,11 +8,12 @@ on: branches: '*' jobs: + Pytest: runs-on: ubuntu-latest strategy: matrix: - # Add multiple Python versions here to run tests on new(er) versions. + # Add multiple Python versions here to run tests on new(er) versions. python-version: ["3.10"] steps: - uses: actions/checkout@v3 @@ -30,30 +29,79 @@ jobs: - name: Analyse the code with pytest run: | # Run the actual testing - pytest -v --cov=panelaero --junitxml=testresult.xml + pytest -v --basetemp=./tmp --cov=panelaero --junitxml=testresult.xml # Create some reports coverage report coverage xml -o coverage.xml # Put the html into a 2nd-level sub-folder and use 1st-level subfolder for uploading to maintain folder - coverage html --directory ./coverage/coverage - - name: Upload HTML coverage report as an artifact - uses: actions/upload-artifact@v3 + coverage html --directory ./coverage + - name: Upload test restults and coverage as an artifact + uses: actions/upload-artifact@v4 + with: + name: test results and coverage + path: | + testresult.xml + coverage.xml + coverage + if-no-files-found: ignore + + Jupyter: + # Building the Jupyter book is not really a regression test. However, it has to be in this workflow due to the handling of + # the artifacts. + runs-on: ubuntu-latest + strategy: + matrix: + # Select Python version to be used for compiling here. + python-version: ["3.10"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 with: - name: coverage - path: ./coverage + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # Install the package itself to make sure that all imports work. + pip install .[tutorials] + - name: Assemble the tutorials to a jupyter book and build htlm pages + run: | + jupyter-book build ./doc/tutorials + # Put the html into a 2nd-level sub-folder and use 1st-level subfolder for uploading + mkdir ./doc/html + mv ./doc/tutorials/_build/html ./doc/html/tutorials + - name: Upload Jupyter book as an artifact + uses: actions/upload-artifact@v4 + with: + name: tutorials + path: ./doc/html if-no-files-found: ignore - - name: Upload HTML coverage report for pages + + combine-pages: + runs-on: ubuntu-latest + # Add a dependency to the build job + needs: [Jupyter, Pytest] + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + - name: See what we've got and merge artifacts + run: | + ls -la + mkdir pages + mv ./tutorials ./pages/tutorials + mv ./coverage ./pages/coverage + - name: Upload artifact for pages # This is not a normal artifact but one that can be deployed to the GitHub pages in the next step uses: actions/upload-pages-artifact@v3 with: - name: github-pages # This name may not be changed according to the documentation - path: ./coverage + name: github-pages # This name may not be changed according to the documentation + path: ./pages # There must be only one path if-no-files-found: ignore - + deploy-pages: # Add a dependency to the build job - needs: Pytest - + needs: combine-pages # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: pages: write # to deploy to Pages diff --git a/setup.py b/setup.py index d487b45..1c73880 100644 --- a/setup.py +++ b/setup.py @@ -19,10 +19,12 @@ def my_setup(): install_requires=['numpy'], extras_require={'test': ['pytest', 'pytest-cov', - 'jupyter', - 'jupyter-book', - 'mayavi', - ]}, + ], + 'tutorials': ['jupyter', + 'jupyter-book', + 'mayavi', + ] + }, ) From fe376ab5faeac11732cf040a92f018c8f740a716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 14:54:38 +0100 Subject: [PATCH 12/19] Apply formatting --- doc/tutorials/helper_functions/plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/helper_functions/plotting.py b/doc/tutorials/helper_functions/plotting.py index 253e79f..ea221b5 100755 --- a/doc/tutorials/helper_functions/plotting.py +++ b/doc/tutorials/helper_functions/plotting.py @@ -26,7 +26,7 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', embed_in_notebook=False # hand over unstructured grid to mayavi if embed_in_notebook: mlab.init_notebook('png') - mlab.figure(bgcolor=(1, 1, 1), size=(600,450)) + mlab.figure(bgcolor=(1, 1, 1), size=(600, 450)) src_aerogrid = mlab.pipeline.add_dataset(ug) # determine if suitable scalar data is given @@ -47,7 +47,7 @@ def plot_aerogrid(self, scalars=None, colormap='plasma', embed_in_notebook=False surface.actor.mapper.scalar_visibility = False surface.actor.property.edge_visibility = True mlab.view(azimuth=60.0, elevation=-65.0, roll=55.0) - + if embed_in_notebook: return surface else: From 355422bb1e3ede91b3df9884a143b234861e1ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 14:54:52 +0100 Subject: [PATCH 13/19] Fix import --- doc/tutorials/simplewing/simplewing_steady.ipynb | 2 +- doc/tutorials/simplewing/simplewing_unsteady.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/simplewing/simplewing_steady.ipynb b/doc/tutorials/simplewing/simplewing_steady.ipynb index df4e346..0d3a1ca 100644 --- a/doc/tutorials/simplewing/simplewing_steady.ipynb +++ b/doc/tutorials/simplewing/simplewing_steady.ipynb @@ -24,7 +24,7 @@ "\n", "from panelaero import VLM\n", "\n", - "from tutorials.helper_functions import build_aeromodel, plotting" + "from doc.tutorials.helper_functions import build_aeromodel, plotting" ] }, { diff --git a/doc/tutorials/simplewing/simplewing_unsteady.ipynb b/doc/tutorials/simplewing/simplewing_unsteady.ipynb index ec3cd4b..4df8ace 100644 --- a/doc/tutorials/simplewing/simplewing_unsteady.ipynb +++ b/doc/tutorials/simplewing/simplewing_unsteady.ipynb @@ -24,7 +24,7 @@ "\n", "from panelaero import DLM\n", "\n", - "from tutorials.helper_functions import build_aeromodel, plotting" + "from doc.tutorials.helper_functions import build_aeromodel, plotting" ] }, { From 86038703fcccc22514adddd4dd9069fb637e614c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 15:29:35 +0100 Subject: [PATCH 14/19] Add path of helper_functions --- doc/tutorials/simplewing/simplewing_steady.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/tutorials/simplewing/simplewing_steady.ipynb b/doc/tutorials/simplewing/simplewing_steady.ipynb index 0d3a1ca..1a1462e 100644 --- a/doc/tutorials/simplewing/simplewing_steady.ipynb +++ b/doc/tutorials/simplewing/simplewing_steady.ipynb @@ -24,7 +24,9 @@ "\n", "from panelaero import VLM\n", "\n", - "from doc.tutorials.helper_functions import build_aeromodel, plotting" + "import sys\n", + "sys.path.append('../')\n", + "from helper_functions import build_aeromodel, plotting" ] }, { From cbc96e12af30d4dc1feb23daca5ff69c7933cd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 16:08:59 +0100 Subject: [PATCH 15/19] Make GUIs work --- .github/workflows/regression-tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index fad088f..4ecab5f 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -53,14 +53,21 @@ jobs: matrix: # Select Python version to be used for compiling here. python-version: ["3.10"] + # Step 1 to make GUIs work, see https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html + env: + DISPLAY: ':99.0' steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} + # Step 2 to make GUIs work + - uses: tlambert03/setup-qt-libs@v1 - name: Install dependencies run: | + # Step 3 to make GUIs work + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX python -m pip install --upgrade pip # Install the package itself to make sure that all imports work. pip install .[tutorials] From 682133e2d1b9a838c8fc38f549014df6fa80728e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 16:19:34 +0100 Subject: [PATCH 16/19] Adapt import style --- doc/tutorials/simplewing/simplewing_steady.ipynb | 12 ++---------- doc/tutorials/simplewing/simplewing_unsteady.ipynb | 6 ++++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/doc/tutorials/simplewing/simplewing_steady.ipynb b/doc/tutorials/simplewing/simplewing_steady.ipynb index 1a1462e..5eed62e 100644 --- a/doc/tutorials/simplewing/simplewing_steady.ipynb +++ b/doc/tutorials/simplewing/simplewing_steady.ipynb @@ -20,11 +20,11 @@ "metadata": {}, "outputs": [], "source": [ + "import sys\n", "import numpy as np\n", - "\n", "from panelaero import VLM\n", "\n", - "import sys\n", + "# Here we add the parent path so that some helper functions are found.\n", "sys.path.append('../')\n", "from helper_functions import build_aeromodel, plotting" ] @@ -227,14 +227,6 @@ "## Conclusion\n", "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. " ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6cc1edf3-8476-4857-9fff-0106805a6f2c", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/doc/tutorials/simplewing/simplewing_unsteady.ipynb b/doc/tutorials/simplewing/simplewing_unsteady.ipynb index 4df8ace..f39dc8b 100644 --- a/doc/tutorials/simplewing/simplewing_unsteady.ipynb +++ b/doc/tutorials/simplewing/simplewing_unsteady.ipynb @@ -20,11 +20,13 @@ "metadata": {}, "outputs": [], "source": [ + "import sys\n", "import numpy as np\n", - "\n", "from panelaero import DLM\n", "\n", - "from doc.tutorials.helper_functions import build_aeromodel, plotting" + "# Here we add the parent path so that some helper functions are found.\n", + "sys.path.append('../')\n", + "from helper_functions import build_aeromodel, plotting" ] }, { From 8d19c977a0825e3bf3b99a7f4ead66199c57df14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 23 Jan 2024 16:28:39 +0100 Subject: [PATCH 17/19] Adapt the Tutorials section in the Readme --- README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 840c3c2..81deff4 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,20 @@ Voß, A., “An Implementation of the Vortex Lattice and the Doublet Lattice Met If you use this software for your scientific work, we kindly ask you to include a reference to this report in your publications. Thank you! # Installation & Use -## User installation -To install everything as a python package, including dependencies: +## Basic Installation +Install Panel Aero as a python package with core dependencies via: ``` pip install git+https://github.com/DLR-AE/PanelAero.git ``` ## How can I use it? - -In Python, you can import the VLM or the DLM as shown below. For further details, please see the example section. +In Python, you can import the VLM or the DLM as shown below. For further details, please see the Tutorials section. ``` from panelaero import VLM, DLM ``` -## Developer installation +## Advanced Installation As above, but with access to the code (keep the code where it is so that you can explore and modify): ``` @@ -32,6 +31,23 @@ cd pip install -e . ``` +## Tutorials & Examples +There are is a growing number of tutorials based on Jupyter notebooks. You can either have a look at the static html tutorials or use the Jupyter notebooks interactively. For the latter, start a jupyter notebook server, which will open a dashboard in your web browser. Then open one of the *.ipynb notebooks from ./doc/tutorials and walk through the tutorials step-by-step. + +[View static html tutorials](https://dlr-ae.github.io/PanelAero/tutorials/) + +or + +``` +jupyter notebook +``` +Any missing dependencies (probably jupyter and mayavi) can be installed with: + +``` +pip install -e .[tutorials] +``` + + # License This software is developed for scientific applications and is delivered as open source without any liability (BSD 3-Clause, please see the [license](LICENSE) for details). For every new aircraft, a validation against test data and/or other simulation tools is highly recommended and in the responsibility of the user. From 017d81b3ef9a71a13d162a5576b321fa3f295348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 26 Jan 2024 15:09:01 +0100 Subject: [PATCH 18/19] Spell correction and wording --- .../simplewing/simplewing_steady.ipynb | 22 +++++++++---------- .../simplewing/simplewing_unsteady.ipynb | 16 +++++++++++--- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/doc/tutorials/simplewing/simplewing_steady.ipynb b/doc/tutorials/simplewing/simplewing_steady.ipynb index 5eed62e..eff4fc1 100644 --- a/doc/tutorials/simplewing/simplewing_steady.ipynb +++ b/doc/tutorials/simplewing/simplewing_steady.ipynb @@ -10,7 +10,7 @@ "\n", "It is assumed that you sucessfully installed PanelAero as a Python package as described in the [README](https://github.com/DLR-AE/PanelAero?tab=readme-ov-file#installation--use).\n", "\n", - "Let's see if we can import the software, which is a good indictor for a sucessful installation. In addition to the VLM inself, this tutorial also uses numpy and some helper functions provided along with the tutorials." + "Let's see if we can import the software, which is a good indictor for a sucessful installation. In addition to the VLM inself, this tutorial also uses numpy and some helper functions provided along with the tutorials. Next to Python's built-in modules, Panel Aero only requires Numpy for its core functionality. In addition to that, Mayavi is used for the visualizations in this and the following tutorials." ] }, { @@ -35,13 +35,13 @@ "metadata": {}, "source": [ "## Set-up of Geometry\n", - "The geometry of the lifting surfaces is described using flat panels (hence the name PanelAero), typically using CAERO1 cards in Nastran eight character notation. Each field has exactly eight characters and the format is defined in the following way (compare with Nastran Quick Reference Guide):\n", + "The geometry of the lifting surfaces is described using flat panels (hence the name Panel Aero), typically using CAERO1 cards in Nastran eight character notation. Each field has exactly eight characters and the format is defined in the following way (compare with Nastran Quick Reference Guide):\n", "\n", "``$------><------><------><------><------><------><------><------><------> `` \n", "``CAERO1 EID CP NSPAN NCHORD +`` \n", "``+ X1 Y1 Z1 X12 X4 Y4 Z4 X34 ``\n", "\n", - "Note that Nastran allows more fields / options on this card, but not all of them are needed for this example and are not supported by the reader. To set-up the aerogrid, we only need the following information:\n", + "Note that Nastran allows more fields / options on this card, but not all of them are needed for this example and are not supported by the reader provided along with this tutorial. To set-up the aerogrid, we only need the following information:\n", "\n", "EID = Element identification number \n", "CP = Coordinate system \n", @@ -57,7 +57,7 @@ "``CAERO1 6401001 0 40 10 +`` \n", "``+ 0.0 -0.552 0.0 0.1 0.0 +0.552 0.0 0.1 `` \n", "\n", - "A simple reader (build_aeromodel.py) is provied along with the examples, which is now used to parse the CAERO1 card." + "A simple reader (build_aeromodel.py) is provied along with the tutorials, which is now used to parse the CAERO1 card." ] }, { @@ -104,7 +104,7 @@ "metadata": {}, "source": [ "## Calculate the AIC Matrix\n", - "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided by PanelAero calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", + "Based on a given aerodynamic grid, the Vortex Lattice Method (VLM) provided by Panel Aero calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends only on the geometry and the Mach number. The $\\mathbf{AIC}$ matrix then relates an induced downwash $\\mathbf{w_j}$ on each aerodynamic panel to a circulation strength $\\Gamma_j$, which is translated to a pressure coefficient $c_p$.\n", "\n", "$$\\mathbf{\\Delta c_p} = \\mathbf{AIC}(Ma) \\cdot \\mathbf{w_j}$$\n", "\n", @@ -112,7 +112,7 @@ "\n", "\n", "\n", - "Using the VLM, the $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained by calling VLM.calc_Qjj() along with the geometry and the desired Mach number. As the number of panels is rather small, the calculation of the $\\mathbf{AIC}$ matrix ist very fast. Note that the computation time increases with the square tot the number of panels." + "Using the VLM, the $\\mathbf{AIC}$ matrix (called Qjj in the following) is obtained by calling VLM.calc_Qjj() along with the geometry and the desired Mach number. As the number of panels is rather small, the calculation of the $\\mathbf{AIC}$ matrix ist very fast. Note that the computation time increases with the square of the number of panels." ] }, { @@ -122,7 +122,7 @@ "metadata": {}, "outputs": [], "source": [ - "Qjj, _ = VLM.calc_Qjj(model.aerogrid, Ma=0.0)" + "Qjj, Bjj = VLM.calc_Qjj(model.aerogrid, Ma=0.0)" ] }, { @@ -167,7 +167,7 @@ "id": "d221b5c4-8af0-44d1-9d25-191bfd672199", "metadata": {}, "source": [ - "Experimental use: The VLM also calculates a second matrix Bjj which includes only the two long edges of the horse shoe vortices and allows for the calculation of induced drag." + "Experimental use: The VLM also calculates a second matrix Bjj which includes only the two long edges of the horse shoe vortices and allows for the calculation of induced drag, which is not discussed further in this tutorial." ] }, { @@ -196,7 +196,7 @@ "source": [ "In the plot above, a suction peak should be cleary visible along the leading edge and the pressure distribution should decrease from the center line towards the wing tips. Please pay attention the the sign convention: here, a positive downwash causes a positive pressure, which will lead to a positiv, upward force Fz.\n", "\n", - "For many applications, including aeroelasticity, not only the pressure distribution but the aerodynamic force vector is needed. From the pressure coefficient distribution cp, the aerodynamic forces Fxyz can be calculated by multiplication with area A and the normal vector of every panels as well as the dynamic pressure. " + "For many applications, including aeroelasticity, not only the pressure distribution but the aerodynamic force vector is needed. From the pressure coefficient distribution cp, the aerodynamic forces Fxyz can be calculated by multiplication with the area A and the normal vector of every panels as well as the dynamic pressure q. " ] }, { @@ -216,7 +216,7 @@ "id": "7a7760ba-e24f-4f7b-bb96-99eebc8e9023", "metadata": {}, "source": [ - "The force vector Fxyz has the shape 3 x 400, reflecting the aerodynamic forces in x-, y- and z-direction per panel. Calculating the sums from all panels, the aerodynamic lift created by the wing should be Fz = 18.47 N. The componetns Fx and Fy should be zero in this case, because the wing is flat (no dihedral) and the VLM calculates no drag." + "The force vector Fxyz has the shape 3 x 400, reflecting the aerodynamic forces in x-, y- and z-direction per panel. Calculating the sums from all panels, the aerodynamic lift created by the wing should be Fz = 18.47 N. Because the wing is flat (no dihedral) and the VLM calculates no drag, the components Fx and Fy should be zero in this case." ] }, { @@ -225,7 +225,7 @@ "metadata": {}, "source": [ "## Conclusion\n", - "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. " + "This tutorial demonstrated how to discretize a simple wing with aerodynamic panels and how to calculate an $\\mathbf{AIC}$ matrix for aerodynamic steady applications using the VLM. After that, we vizualized the resulting pressure distribution and explored how to calculate a force vector from the pressures. " ] } ], diff --git a/doc/tutorials/simplewing/simplewing_unsteady.ipynb b/doc/tutorials/simplewing/simplewing_unsteady.ipynb index f39dc8b..bcfa143 100644 --- a/doc/tutorials/simplewing/simplewing_unsteady.ipynb +++ b/doc/tutorials/simplewing/simplewing_unsteady.ipynb @@ -57,7 +57,7 @@ "metadata": {}, "source": [ "## Calculate the AIC Matrix\n", - "The Doublet Lattice Method (DLM) uses the same geoemtrical description of the lifting surface with aeroynamic panels as the VLM. However, the underlying modeling uses doublets insteady of vortices and works in the frequency domain. \n", + "The Doublet Lattice Method (DLM) uses the same geoemtrical description of the lifting surface using aeroynamic panels as the VLM. However, the underlying aerodynamic theroy uses doublets insteady of vortices and works in the frequency domain. \n", "\n", "Like the VLM, the DLM provided in this software calculates a matrix of so-called aerodynamic influence coefficients (AIC), which depends on the geometry, the Mach number and the reduced frequency, defined by\n", "\n", @@ -129,7 +129,9 @@ "metadata": {}, "source": [ "## Vizualisation of Results\n", - "The pressure coefficient distribution may be visualized on the geometry using the helper function provided along with this tutorial. Becasue we selected $k=0.1$ above, the pressure coefficients will be complex, so we vizualize the real and the imaginary part seperately. Compared to the steady aerodynamic approach using the VLM, the complex pressure coefficients obtained from the DLM change the magnitude and add a phase shift depending on the prescribed reduced frequency $k$." + "The pressure coefficient distribution may be visualized on the geometry using the helper function provided along with this tutorial. Becasue we selected $k=0.1$ above, the pressure coefficients will be complex, so we vizualize the real and the imaginary part seperately. Compared to the steady aerodynamic approach using the VLM, the complex pressure coefficients obtained from the DLM typically reduce the magnitude and add a phase shift, which depends on the prescribed reduced frequency $k$.\n", + "\n", + "Plot of the real part of $\\mathbf{\\Delta c_p}$:" ] }, { @@ -142,6 +144,14 @@ "plots.plot_aerogrid(cp.real, embed_in_notebook=True)" ] }, + { + "cell_type": "markdown", + "id": "d9dc0266-f352-443a-9299-3f6720d2a47d", + "metadata": {}, + "source": [ + "And now the imaginary part of $\\mathbf{\\Delta c_p}$:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -158,7 +168,7 @@ "metadata": {}, "source": [ "## Advanced Use\n", - "For most aeroelastic applications, typically the $\\mathbf{AIC}$ matrices are needed for different Mach numbers and for multiple reduced frequencies $k$. The function DLM.calc_Qjjs() accepts a lists as input and returns a four-dimensional array with the shape (n_mach, n_k, n_panels, n_panels] as shown in the example below." + "For most aeroelastic applications, typically the $\\mathbf{AIC}$ matrices are needed for different Mach numbers and for multiple reduced frequencies $k$. The function DLM.calc_Qjjs() accepts a lists as input and returns a four-dimensional array with the shape (n_mach, n_k, n_panels, n_panels) as shown in the example below." ] }, { From fb94a1a1d502e585a5589b09a4351c5205c28387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 26 Jan 2024 16:15:15 +0100 Subject: [PATCH 19/19] Adjusted Readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 81deff4..0e7d79a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Install Panel Aero as a python package with core dependencies via: pip install git+https://github.com/DLR-AE/PanelAero.git ``` ## How can I use it? -In Python, you can import the VLM or the DLM as shown below. For further details, please see the Tutorials section. +In Python, you can import the VLM or the DLM as shown below. For further details, please see the Tutorials section. This is no stand-alone aerodynamic software but is intended to be integrated in other software, for example for loads and aeroelastic analyses. ``` from panelaero import VLM, DLM @@ -32,9 +32,9 @@ pip install -e . ``` ## Tutorials & Examples -There are is a growing number of tutorials based on Jupyter notebooks. You can either have a look at the static html tutorials or use the Jupyter notebooks interactively. For the latter, start a jupyter notebook server, which will open a dashboard in your web browser. Then open one of the *.ipynb notebooks from ./doc/tutorials and walk through the tutorials step-by-step. +There is a growing number of tutorials based on Jupyter notebooks. You can either have a look at the static html tutorials or use the Jupyter notebooks interactively. For the latter, start a jupyter notebook server, which will open a dashboard in your web browser. Then open one of the *.ipynb notebooks from ./doc/tutorials and walk through the tutorials step-by-step. -[View static html tutorials](https://dlr-ae.github.io/PanelAero/tutorials/) +[View html tutorials](https://dlr-ae.github.io/PanelAero/tutorials/) or @@ -47,7 +47,6 @@ Any missing dependencies (probably jupyter and mayavi) can be installed with: pip install -e .[tutorials] ``` - # License This software is developed for scientific applications and is delivered as open source without any liability (BSD 3-Clause, please see the [license](LICENSE) for details). For every new aircraft, a validation against test data and/or other simulation tools is highly recommended and in the responsibility of the user.