{ "cells": [ { "cell_type": "markdown", "id": "0d19cc7a", "metadata": {}, "source": [ "# NACA0012 Tutorial\n", "\n", "This tutorial is a walkthrough for the panel method. We will be using the ex_1_NACA0012.py example." ] }, { "cell_type": "markdown", "id": "65e170e4", "metadata": {}, "source": [ "We begin by importing relevant packages. We use numpy as the standard library for general arrays. We also import csdl to set up the graph.\n", "\n", "Next, we import the `PanelMethod` flow solver class from VortexAD. We also use one of the stored meshes, which can be accessed through the `SAMPLE_GEOMETRY_PATH` path. We use this path to access the mesh for this tutorial." ] }, { "cell_type": "code", "execution_count": 1, "id": "1eda54ab", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import csdl_alpha as csdl\n", "\n", "from VortexAD import PanelMethod\n", "from VortexAD import SAMPLE_GEOMETRY_PATH" ] }, { "cell_type": "markdown", "id": "203cca81", "metadata": {}, "source": [ "Next, we instantiate the graph recorder and set up inputs to the flow solver. The `Recorder` object is used to start the graph assembly. The `inline` argument dictates whether values will be stored with the graph assembly `(True)` or if only the graph structure will be formed `(False)`. \n", "\n", "From there, we set up our panel method inputs. First, we need to supply a path to the mesh file. We use the NACA0012 `stl` mesh made using OpenVSP, with clustering at the leading and trailing edges. In addition, we set up a variable for the pitch angle in degrees. We do this to easily control \n", "\n", "Next, we add the inputs we want to control into a dictionary to be read by the flow solver. In this case, we control two flow properties: the Mach number and pitch angle. We supply the mesh path, along with a reference area for our force and moment coefficients." ] }, { "cell_type": "code", "execution_count": 2, "id": "3f07c636", "metadata": {}, "outputs": [], "source": [ "# instantiate recorder to assemble the graph\n", "recorder = csdl.Recorder(inline=False)\n", "recorder.start()\n", "\n", "# set up input dictionary\n", "mesh_file_path = str(SAMPLE_GEOMETRY_PATH) + '/pm/naca0012_LE_TE_cluster.stl'\n", "pitch = csdl.Variable(value=np.array([5.]))\n", "\n", "# input dict\n", "input_dict = {\n", " 'Mach': 0.25,\n", " 'alpha': pitch,\n", " 'Cp cutoff': -5.,\n", " 'mesh_path': mesh_file_path, # can alternatively load mesh in with connectivity/TE data\n", " 'ref_area': 10., \n", "}" ] }, { "cell_type": "markdown", "id": "c929e2e8", "metadata": {}, "source": [ "Once our inputs are set up, we instantiate the flow solver and declare outputs.\n", "\n", "We use the `.declare_outputs()` method to specify which outputs of the panel method we are interested in. The available outputs can be found at the link [here]() or by calling `.print_output_options()`. For this tutorial, we are interested in the lift and induced drag coefficients, along with the pressure coefficient distribution.\n", "\n", "In addition, we manually call the `.setup_grid_properties()` method to verify the grid and trailing edge selection. Although this method is automatically called during instantiation of the`PanelMethod` class, we call it manually here to highlight this capability. Potential flow methods require specifying trailing edge locations (where wakes will shed from), so it is important to mark these elements accurately. The `threshold_angle` argument specifies the sharpness angle between adjacent panels, and the `plot` argument can be used to visualize the grid.\n", "\n", "Once our outputs have been set up, we can run through the panel method and assemble the graph by calling the `.evaluate()` method, which returns a dictionary of the outputs we previously declared. From there, we can extract our outputs of interested based on name. \n", "\n", "```{warning}\n", "Because we have set `inline=False` in the recorder, printing out these variables (CL, CDi, CP) will return nothing. We will need to use one of the backends to execute the graph to return its value.\n", "```\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "2fecb238", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(810, 3)\n", "(1, 810, 3)\n", "running pre-processing\n", "solving for doublet strengths\n", "yes batching; batch size = 1\n", "running post-processor\n", "(1,)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/luca/Packages/csdl_alpha/csdl_alpha/src/operations/tensor/expand.py:184: UserWarning: \"action\" will have no effect when expanding a scalar.\n", " warnings.warn('\"action\" will have no effect when expanding a scalar.')\n" ] } ], "source": [ "# instantiate PanelMethod class\n", "panel_method = PanelMethod(\n", " input_dict\n", ")\n", "# declare outputs of interest\n", "pm_outputs = [\n", " 'CL',\n", " 'CDi',\n", " 'Cp'\n", "]\n", "panel_method.declare_outputs(pm_outputs)\n", "\n", "panel_method.setup_grid_properties(threshold_angle=125, plot=True) # optional for debugging\n", "\n", "# run the panel method\n", "outputs = panel_method.evaluate()\n", "\n", "# read outputs\n", "CL = outputs['CL']\n", "CDi = outputs['CDi']\n", "CP = outputs['Cp']" ] }, { "cell_type": "markdown", "id": "00cd528c", "metadata": {}, "source": [ "the panel method will print some statements during graph assembly, such as different stages of the internal graph structure. In this case, we are also using partitioned vectorization, so the partition size is printed." ] }, { "cell_type": "markdown", "id": "255a8f80", "metadata": {}, "source": [ "Once the panel method graph has been assembled and CSDL variables have been assigned to python variables, we instantiate up a backend to compile and execute the graph. We use the `JaxSimulator`, which compiles the CSDL graph into `jax` code. We first need to set up lists for the graph inputs and outputs. These inputs and outputs are important because they identify what we have access to **once the graph is compiled**. To be more specific, once the graph is compiled, we can only change the variables in the input list to see only the variables in the output list. From there, we can execute the graph by calling `sim.run()`." ] }, { "cell_type": "code", "execution_count": 4, "id": "ed8efc49", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "compiling 'run' function ... (2168 nodes)\n" ] }, { "data": { "text/plain": [ "{: array([0.44523591]),\n", " : array([0.00544886]),\n", " : array([[ 0.27168939, 0.2194987 , 0.19842159, ..., -5. ,\n", " -5. , -5. ]])}" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# csdl-jax stuff\n", "inputs = [pitch]\n", "outputs = [CL, CDi, CP]\n", "\n", "sim = csdl.experimental.JaxSimulator(\n", " recorder=recorder,\n", " additional_inputs=inputs,\n", " additional_outputs=outputs,\n", " gpu=False\n", ")\n", "sim.run()" ] }, { "cell_type": "markdown", "id": "24c985c3", "metadata": {}, "source": [ "Once the simulator runs, we can see the values of our three outputs. In addition, we use the plotting functionality in the `PanelMethod` class to plot our pressure distribution over the geometry." ] }, { "cell_type": "code", "execution_count": 5, "id": "5441eb9c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CL: [0.44523591]\n", "CDi: [0.00544886]\n" ] } ], "source": [ "CL_val = sim[CL]\n", "CDi_val = sim[CDi]\n", "CP_val = sim[CP]\n", "\n", "print('CL:', CL_val)\n", "print('CDi:', CDi_val)\n", "\n", "panel_method.plot(CP_val, bounds=[-4,1])" ] }, { "cell_type": "markdown", "id": "7e410bee", "metadata": {}, "source": [ "![NACA0012 Cp](images/NACA0012_Cp.png \"NACA0012 Cp\")" ] } ], "metadata": { "kernelspec": { "display_name": "femo_env", "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.9.18" } }, "nbformat": 4, "nbformat_minor": 5 }