├── .gitignore ├── AUTHORS.txt ├── doc ├── .gitignore └── CMakeLists.txt ├── examples ├── hawt │ ├── README │ ├── CMakeLists.txt │ ├── hawt-blade.dat │ ├── hawt-airfoil.dat │ └── hawt.cpp ├── clarky-section │ ├── README │ ├── CMakeLists.txt │ ├── clarky.dat │ └── clarky-section.cpp ├── vawt │ ├── CMakeLists.txt │ └── vawt.cpp ├── naca0012-section-oscillation │ ├── CMakeLists.txt │ └── naca0012-section-oscillation.cpp ├── CMakeLists.txt └── gmsh-lifting-surface │ ├── CMakeLists.txt │ └── gmsh-lifting-surface.cpp ├── vortexje ├── surface-loaders │ ├── rply │ │ └── CMakeLists.txt │ ├── CMakeLists.txt │ ├── gmsh-surface-loader.hpp │ ├── ply-surface-loader.hpp │ ├── ply-surface-loader.cpp │ └── gmsh-surface-loader.cpp ├── field-writers │ ├── CMakeLists.txt │ ├── vtk-field-writer.hpp │ └── vtk-field-writer.cpp ├── boundary-layers │ ├── CMakeLists.txt │ ├── dummy-boundary-layer.hpp │ └── dummy-boundary-layer.cpp ├── empirical-wakes │ ├── CMakeLists.txt │ ├── ramasamy-leishman-wake.hpp │ └── ramasamy-leishman-wake.cpp ├── shape-generators │ ├── airfoils │ │ ├── CMakeLists.txt │ │ ├── naca4-airfoil-generator.hpp │ │ └── naca4-airfoil-generator.cpp │ ├── CMakeLists.txt │ ├── ellipse-generator.hpp │ └── ellipse-generator.cpp ├── surface-writers │ ├── CMakeLists.txt │ ├── vtk-surface-writer.hpp │ ├── gmsh-surface-writer.hpp │ ├── gmsh-surface-writer.cpp │ └── vtk-surface-writer.cpp ├── CMakeLists.txt ├── surface-loader.hpp ├── lifting-surface-builder.hpp ├── surface-builder.hpp ├── parameters.cpp ├── wake.hpp ├── surface-writer.cpp ├── surface-writer.hpp ├── boundary-layer.hpp ├── lifting-surface.hpp ├── field-writer.hpp ├── parameters.hpp ├── surface.hpp ├── wake.cpp ├── solver.hpp ├── lifting-surface.cpp ├── body.hpp ├── lifting-surface-builder.cpp ├── surface-builder.cpp └── body.cpp ├── tests ├── vortex-core │ ├── CMakeLists.txt │ └── test-vortex-core.cpp ├── CMakeLists.txt ├── elliptic-planform │ ├── CMakeLists.txt │ └── test-elliptic-planform.cpp ├── interpolation-layer │ └── CMakeLists.txt ├── sphere │ ├── CMakeLists.txt │ └── test-sphere.cpp └── naca0012-airfoil │ ├── naca0012-reference-data.txt │ ├── CMakeLists.txt │ └── test-naca0012-airfoil.cpp ├── vortexje.pc.in ├── README.md ├── INSTALL.txt ├── cmake ├── set_cxx_norm.cmake └── FindEigen3.cmake └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Jorn Baayen 2 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | html 2 | latex 3 | -------------------------------------------------------------------------------- /examples/hawt/README: -------------------------------------------------------------------------------- 1 | The data is for the NREL Phase VI rotor. 2 | -------------------------------------------------------------------------------- /examples/clarky-section/README: -------------------------------------------------------------------------------- 1 | From http://aerospace.illinois.edu/m-selig/ads/coord_database.html. 2 | -------------------------------------------------------------------------------- /examples/vawt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(vawt vawt.cpp) 2 | target_link_libraries(vawt vortexje) 3 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/rply/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | rply.c) 3 | 4 | add_library(rply OBJECT ${SRCS}) 5 | -------------------------------------------------------------------------------- /tests/vortex-core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test-vortex-core test-vortex-core.cpp) 2 | target_link_libraries(test-vortex-core vortexje) 3 | 4 | add_test(vortex-core test-vortex-core) 5 | -------------------------------------------------------------------------------- /examples/naca0012-section-oscillation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(naca0012-section-oscillation naca0012-section-oscillation.cpp) 2 | target_link_libraries(naca0012-section-oscillation vortexje) 3 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(clarky-section) 2 | add_subdirectory(gmsh-lifting-surface) 3 | add_subdirectory(hawt) 4 | add_subdirectory(naca0012-section-oscillation) 5 | add_subdirectory(vawt) 6 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(naca0012-airfoil) 2 | add_subdirectory(sphere) 3 | add_subdirectory(vortex-core) 4 | add_subdirectory(interpolation-layer) 5 | add_subdirectory(elliptic-planform) 6 | -------------------------------------------------------------------------------- /examples/clarky-section/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(clarky-section clarky-section.cpp) 2 | target_link_libraries(clarky-section vortexje) 3 | 4 | configure_file(clarky.dat ${CMAKE_CURRENT_BINARY_DIR}/clarky.dat COPYONLY) 5 | -------------------------------------------------------------------------------- /tests/elliptic-planform/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test-elliptic-planform test-elliptic-planform.cpp) 2 | target_link_libraries(test-elliptic-planform vortexje) 3 | 4 | add_test(elliptic-planform test-elliptic-planform) 5 | 6 | -------------------------------------------------------------------------------- /tests/interpolation-layer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test-interpolation-layer test-interpolation-layer.cpp) 2 | target_link_libraries(test-interpolation-layer vortexje) 3 | 4 | add_test(interpolation-layer test-interpolation-layer) 5 | -------------------------------------------------------------------------------- /tests/sphere/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(sphere.msh ${CMAKE_CURRENT_BINARY_DIR}/sphere.msh COPYONLY) 2 | 3 | add_executable(test-sphere test-sphere.cpp) 4 | target_link_libraries(test-sphere vortexje) 5 | 6 | add_test(sphere test-sphere) 7 | -------------------------------------------------------------------------------- /vortexje/field-writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | vtk-field-writer.cpp) 3 | 4 | set(HDRS 5 | vtk-field-writer.hpp) 6 | 7 | add_library(field-writers OBJECT ${SRCS}) 8 | 9 | install(FILES ${HDRS} DESTINATION include/vortexje/field-writers) 10 | -------------------------------------------------------------------------------- /examples/gmsh-lifting-surface/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(gmsh-lifting-surface gmsh-lifting-surface.cpp) 2 | target_link_libraries(gmsh-lifting-surface vortexje) 3 | 4 | configure_file(gmsh-lifting-surface.msh ${CMAKE_CURRENT_BINARY_DIR}/gmsh-lifting-surface.msh COPYONLY) 5 | -------------------------------------------------------------------------------- /vortexje/boundary-layers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | dummy-boundary-layer.cpp) 3 | 4 | set(HDRS 5 | dummy-boundary-layer.hpp) 6 | 7 | add_library(boundary-layers OBJECT ${SRCS}) 8 | 9 | install(FILES ${HDRS} DESTINATION include/vortexje/boundary-layers) 10 | -------------------------------------------------------------------------------- /vortexje/empirical-wakes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | ramasamy-leishman-wake.cpp) 3 | 4 | set(HDRS 5 | ramasamy-leishman-wake.hpp) 6 | 7 | add_library(empirical-wakes OBJECT ${SRCS}) 8 | 9 | install(FILES ${HDRS} DESTINATION include/vortexje/empirical-wakes) 10 | -------------------------------------------------------------------------------- /examples/hawt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(hawt hawt.cpp) 2 | target_link_libraries(hawt vortexje) 3 | 4 | configure_file(hawt-airfoil.dat ${CMAKE_CURRENT_BINARY_DIR}/hawt-airfoil.dat COPYONLY) 5 | configure_file(hawt-blade.dat ${CMAKE_CURRENT_BINARY_DIR}/hawt-blade.dat COPYONLY) 6 | -------------------------------------------------------------------------------- /tests/naca0012-airfoil/naca0012-reference-data.txt: -------------------------------------------------------------------------------- 1 | -10 -0.7517 0.0376 2 | -8 -0.6015 0.0261 3 | -6 -0.4503 0.0174 4 | -4 -0.2993 0.0116 5 | -2 -0.1494 0.0082 6 | 0 0.0000 0.0071 7 | 2 0.1494 0.0082 8 | 4 0.2993 0.0116 9 | 6 0.4503 0.0174 10 | 8 0.6015 0.0261 11 | 10 0.7517 0.0376 12 | -------------------------------------------------------------------------------- /vortexje/shape-generators/airfoils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | naca4-airfoil-generator.cpp) 3 | 4 | set(HDRS 5 | naca4-airfoil-generator.hpp) 6 | 7 | add_library(airfoils OBJECT ${SRCS}) 8 | 9 | install(FILES ${HDRS} DESTINATION include/vortexje/shape-generators/airfoils) 10 | -------------------------------------------------------------------------------- /vortexje/shape-generators/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(airfoils) 2 | 3 | set(SRCS 4 | ellipse-generator.cpp) 5 | 6 | set(HDRS 7 | ellipse-generator.hpp) 8 | 9 | add_library(shape-generators OBJECT ${SRCS}) 10 | 11 | install(FILES ${HDRS} DESTINATION include/vortexje/shape-generators) 12 | -------------------------------------------------------------------------------- /tests/naca0012-airfoil/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(naca0012-reference-data.txt ${CMAKE_CURRENT_BINARY_DIR}/naca0012-reference-data.txt COPYONLY) 2 | 3 | add_executable(test-naca0012-airfoil test-naca0012-airfoil.cpp) 4 | target_link_libraries(test-naca0012-airfoil vortexje) 5 | 6 | add_test(naca0012 test-naca0012-airfoil) 7 | 8 | -------------------------------------------------------------------------------- /vortexje/surface-writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | gmsh-surface-writer.cpp 3 | vtk-surface-writer.cpp) 4 | 5 | set(HDRS 6 | gmsh-surface-writer.hpp 7 | vtk-surface-writer.hpp) 8 | 9 | add_library(surface-writers OBJECT ${SRCS}) 10 | 11 | install(FILES ${HDRS} DESTINATION include/vortexje/surface-writers) 12 | -------------------------------------------------------------------------------- /vortexje.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/lib 4 | includedir=${prefix}/include 5 | 6 | Name: Vortexje 7 | Description: Vortexje CFD 8 | Version: @Vortexje_VERSION_MAJOR@.@Vortexje_VERSION_MINOR@ 9 | Requires: eigen3 10 | Libs: -L${libdir} -lm -lvortexje 11 | Cflags: -I${includedir}/vortexje 12 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(rply) 2 | 3 | set(SRCS 4 | gmsh-surface-loader.cpp 5 | ply-surface-loader.cpp) 6 | 7 | set(HDRS 8 | gmsh-surface-loader.hpp 9 | ply-surface-loader.hpp) 10 | 11 | add_library(surface-loaders OBJECT ${SRCS}) 12 | 13 | install(FILES ${HDRS} DESTINATION include/vortexje/surface-loaders) 14 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # add a target to generate API documentation with Doxygen 2 | find_package(Doxygen) 3 | if(DOXYGEN_FOUND) 4 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) 5 | add_custom_target(doc 6 | ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 7 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 8 | COMMENT "Generating API documentation with Doxygen" VERBATIM 9 | ) 10 | endif(DOXYGEN_FOUND) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vortexje is an open-source implementation of the source-doublet panel method. Its features include: 2 | 3 | * Modern C++ API, easily integrated with other simulation environments; 4 | * Supports Gmsh and PLY mesh formats; 5 | * Output in Gmsh or in VTK format for analysis with Paraview; 6 | * Optional wake convection; 7 | * Wake-body and wake-wake interaction; 8 | * Transparent integration with boundary layer models. 9 | 10 | For a coupling with the MBDyn multi-body dynamics software, see [vortexje-mbdyn](http://github.com/tuhh-sn/vortexje-mbdyn). 11 | -------------------------------------------------------------------------------- /vortexje/shape-generators/ellipse-generator.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Ellipse generator. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __ELLIPSE_GENERATOR_HPP__ 10 | #define __ELLIPSE_GENERATOR_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | namespace Vortexje 16 | { 17 | 18 | /** 19 | Class for generation of ellipses. 20 | 21 | @brief Ellipse generation. 22 | */ 23 | class EllipseGenerator 24 | { 25 | public: 26 | static std::vector > generate(double a, double b, int n_points); 27 | }; 28 | 29 | }; 30 | 31 | #endif // __ELLIPSE_GENERATOR_HPP__ 32 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/gmsh-surface-loader.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Gmsh surface loader. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __GMSH_SURFACE_LOADER_HPP__ 10 | #define __GMSH_SURFACE_LOADER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Gmsh MSH file loader. 21 | 22 | @brief Gmsh surface loader. 23 | */ 24 | class GmshSurfaceLoader : public SurfaceLoader 25 | { 26 | public: 27 | const char *file_extension() const; 28 | 29 | bool load(std::shared_ptr surface, const std::string &filename); 30 | }; 31 | 32 | }; 33 | 34 | #endif // __GMSH_SURFACE_LOADER_HPP__ 35 | -------------------------------------------------------------------------------- /examples/hawt/hawt-blade.dat: -------------------------------------------------------------------------------- 1 | dr chord twist thick 2 | 0.8835 0.1830 0.0000 0.1830 3 | 0.9460 0.2660 3.3500 0.1730 4 | 1.0085 0.3490 6.7000 0.1630 5 | 1.0675 0.4410 9.9000 0.1540 6 | 1.1335 0.5440 13.4000 0.1540 7 | 1.1955 0.6405 16.7200 0.1540 8 | 1.2575 0.7370 20.0400 0.1540 9 | 1.3430 0.7280 18.0740 0.1525 10 | 1.5100 0.7110 14.2920 0.1490 11 | 1.6480 0.6970 11.9090 0.1460 12 | 1.9520 0.6660 7.9790 0.1395 13 | 2.3430 0.6270 4.7150 0.1314 14 | 2.5620 0.6050 3.4250 0.1267 15 | 2.8670 0.5740 2.0830 0.1203 16 | 3.1850 0.5420 1.1150 0.1135 17 | 3.4760 0.5120 0.4940 0.1073 18 | 3.7810 0.4820 -0.0150 0.1010 19 | 4.0860 0.4510 -0.4750 0.0945 20 | 4.3910 0.4200 -0.9200 0.0880 21 | 4.6960 0.3890 -1.3520 0.0815 22 | 5.0000 0.3580 -1.7750 0.0750 23 | 5.3050 0.3280 -2.1910 0.0687 24 | 5.4185 0.3165 -2.2205 0.0663 25 | 5.5320 0.3050 -2.5000 0.0639 26 | -------------------------------------------------------------------------------- /vortexje/shape-generators/airfoils/naca4-airfoil-generator.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- NACA 4-series airfoil generator. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __NACA4_AIRFOIL_GENERATOR_HPP__ 10 | #define __NACA4_AIRFOIL_GENERATOR_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | namespace Vortexje 16 | { 17 | 18 | /** 19 | Class for generation of NACA 4-digit series airfoils. 20 | 21 | @brief NACA 4-digit series airfoil generation. 22 | */ 23 | class NACA4AirfoilGenerator 24 | { 25 | public: 26 | static std::vector > generate(double max_camber, double max_camber_dist, double max_thickness, bool finite_te_thickness, double chord, int n_points, int &trailing_edge_point_id); 27 | }; 28 | 29 | }; 30 | 31 | #endif // __NACA4_AIRFOIL_GENERATOR_HPP__ 32 | -------------------------------------------------------------------------------- /vortexje/surface-writers/vtk-surface-writer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- VTK surface writer. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __VTK_SURFACE_WRITER_HPP__ 10 | #define __VTK_SURFACE_WRITER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | VTK surface file writer. 21 | 22 | @brief VTK surface writer. 23 | */ 24 | class VTKSurfaceWriter : public SurfaceWriter 25 | { 26 | public: 27 | const char *file_extension() const; 28 | 29 | bool write(const std::shared_ptr &surface, const std::string &filename, 30 | int node_offset, int panel_offset, 31 | const std::vector &view_names, const std::vector > &view_data); 32 | }; 33 | 34 | }; 35 | 36 | #endif // __VTK_SURFACE_WRITER_HPP__ 37 | -------------------------------------------------------------------------------- /vortexje/surface-writers/gmsh-surface-writer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Gmsh surface writer. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __GMSH_SURFACE_WRITER_HPP__ 10 | #define __GMSH_SURFACE_WRITER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Gmsh MSH file writer. 21 | 22 | @brief Gmsh surface writer. 23 | */ 24 | class GmshSurfaceWriter : public SurfaceWriter 25 | { 26 | public: 27 | const char *file_extension() const; 28 | 29 | bool write(const std::shared_ptr &surface, const std::string &filename, 30 | int node_offset, int panel_offset, 31 | const std::vector &view_names, const std::vector > &view_data); 32 | }; 33 | 34 | }; 35 | 36 | #endif // __GMSH_SURFACE_WRITER_HPP__ 37 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | Dependencies: 2 | ------------- 3 | 4 | * Eigen3, a C++ template library for linear algebra: 5 | 6 | http://eigen.tuxfamily.org/ 7 | 8 | Debian/Ubuntu package: libeigen3-dev 9 | Red Hat/Fedora package: eigen3-devel 10 | 11 | * CMake version 2.8.9 or newer, to generate the build files: 12 | 13 | http://cmake.org/ 14 | 15 | Debian/Ubuntu package: cmake 16 | Red Hat/Fedora package: cmake 17 | 18 | * Optionally Doxygen, to generate the documentation: 19 | 20 | http://www.stack.nl/~dimitri/doxygen/ 21 | 22 | Debian/Ubuntu package: doxygen 23 | Red Hat/Fedora package: doxygen 24 | 25 | 26 | Installation: 27 | ------------- 28 | 29 | * Run CMake to generate the build files. 30 | 31 | UNIX: 32 | 33 | Inside the Vortexje source folder, run: 34 | 35 | mkdir build && cd build && cmake .. 36 | 37 | * Build Vortexje using the chosen build system. 38 | 39 | UNIX: 40 | 41 | make 42 | 43 | See the following website for details on the CMake build process: 44 | 45 | http://www.cmake.org/cmake/help/runningcmake.html 46 | -------------------------------------------------------------------------------- /vortexje/boundary-layers/dummy-boundary-layer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Dummy boundary layer class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __DUMMY_BOUNDARY_LAYER_HPP__ 10 | #define __DUMMY_BOUNDARY_LAYER_HPP__ 11 | 12 | #include 13 | 14 | namespace Vortexje 15 | { 16 | 17 | /** 18 | Dummy boundary layer class. 19 | 20 | @brief Dummy boundary layer. 21 | */ 22 | class DummyBoundaryLayer : public BoundaryLayer 23 | { 24 | public: 25 | bool recalculate(const Eigen::Vector3d &freestream_velocity, const Eigen::MatrixXd &surface_velocities); 26 | 27 | double thickness(const std::shared_ptr &surface, int panel) const; 28 | 29 | Eigen::Vector3d velocity(const std::shared_ptr &surface, int panel, double y) const; 30 | 31 | double blowing_velocity(const std::shared_ptr &surface, int panel) const; 32 | 33 | Eigen::Vector3d friction(const std::shared_ptr &surface, int panel) const; 34 | }; 35 | 36 | }; 37 | 38 | #endif // __DUMMY_BOUNDARY_LAYER_HPP__ 39 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/ply-surface-loader.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- PLY surface loader. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __PLY_SURFACE_LOADER_HPP__ 10 | #define __PLY_SURFACE_LOADER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Polygon File Format/Stanford Triangle Format loader. 21 | 22 | @brief PLY surface loader. 23 | */ 24 | class PLYSurfaceLoader : public SurfaceLoader 25 | { 26 | public: 27 | const char *file_extension() const; 28 | 29 | bool load(std::shared_ptr surface, const std::string &filename); 30 | 31 | // rply callbacks. 32 | void read_vertex_coordinate(int index, double value); 33 | void read_panel_node(int index, int length, int node); 34 | 35 | private: 36 | // This class is not re-entrant. 37 | std::shared_ptr surface; 38 | 39 | int current_panel; 40 | 41 | Eigen::Vector3d current_point; 42 | std::vector current_panel_nodes; 43 | }; 44 | 45 | }; 46 | 47 | #endif // __PLY_SURFACE_LOADER_HPP__ 48 | -------------------------------------------------------------------------------- /vortexje/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(boundary-layers) 2 | add_subdirectory(empirical-wakes) 3 | add_subdirectory(surface-loaders) 4 | add_subdirectory(surface-writers) 5 | add_subdirectory(field-writers) 6 | add_subdirectory(shape-generators) 7 | 8 | set(SRCS 9 | surface.cpp 10 | solver.cpp 11 | parameters.cpp 12 | lifting-surface.cpp 13 | wake.cpp 14 | body.cpp 15 | surface-builder.cpp 16 | lifting-surface-builder.cpp 17 | surface-writer.cpp) 18 | 19 | set(HDRS 20 | surface.hpp 21 | solver.hpp 22 | parameters.hpp 23 | lifting-surface.hpp 24 | wake.hpp 25 | boundary-layer.hpp 26 | body.hpp 27 | surface-builder.hpp 28 | lifting-surface-builder.hpp 29 | surface-loader.hpp 30 | surface-writer.hpp 31 | field-writer.hpp) 32 | 33 | add_library(vortexje SHARED ${SRCS} 34 | $ 35 | $ 36 | $ 37 | $ 38 | $ 39 | $ 40 | $ 41 | $) 42 | 43 | install (TARGETS vortexje DESTINATION lib) 44 | install (FILES ${HDRS} DESTINATION include/vortexje) 45 | -------------------------------------------------------------------------------- /vortexje/surface-loader.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface loader base class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __SURFACE_LOADER_HPP__ 10 | #define __SURFACE_LOADER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Surface loader base class. 21 | 22 | @brief Surface loader base class. 23 | */ 24 | class SurfaceLoader 25 | { 26 | public: 27 | /** 28 | Destructor. 29 | */ 30 | virtual ~SurfaceLoader() {}; 31 | 32 | /** 33 | Returns the appropriate file extension for this FieldWriter. 34 | 35 | @returns The file extension. 36 | */ 37 | virtual const char *file_extension() const = 0; 38 | 39 | /** 40 | Loads and parses the contents of a file into a Surface. 41 | 42 | @param[in] surface Surface to load to. 43 | @param[in] filename Filename pointing to the file to load. 44 | 45 | @returns true on success. 46 | */ 47 | virtual bool load(std::shared_ptr surface, const std::string &filename) = 0; 48 | }; 49 | 50 | }; 51 | 52 | #endif // __SURFACE_LOADER_HPP__ 53 | -------------------------------------------------------------------------------- /vortexje/lifting-surface-builder.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Lifting surface builder. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __LIFTING_SURFACE_BUILDER_HPP__ 10 | #define __LIFTING_SURFACE_BUILDER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace Vortexje 18 | { 19 | 20 | /** 21 | Class for construction of lifting surfaces by joining 2-D airfoils together. 22 | 23 | @brief Lifting surface construction helper. 24 | */ 25 | class LiftingSurfaceBuilder : public SurfaceBuilder 26 | { 27 | public: 28 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 29 | 30 | LiftingSurfaceBuilder(LiftingSurface &lifting_surface); 31 | 32 | /** 33 | LiftingSurface under construction. 34 | */ 35 | LiftingSurface &lifting_surface; 36 | 37 | std::vector create_panels_inside_airfoil(const std::vector &airfoil_nodes, int trailing_edge_point_id, int z_sign); 38 | 39 | void finish(const std::vector > &node_strips, const std::vector > &panel_strips, int trailing_edge_point_id); 40 | }; 41 | 42 | }; 43 | 44 | #endif // __LIFTING_SURFACE_BUILDER_HPP__ 45 | -------------------------------------------------------------------------------- /examples/hawt/hawt-airfoil.dat: -------------------------------------------------------------------------------- 1 | S809 AIRFOIL 2 | 31.0 30.0 3 | 4 | 0.00000 0.00000 5 | 0.00037 0.00275 6 | 0.00575 0.01166 7 | 0.01626 0.02133 8 | 0.03158 0.03136 9 | 0.05147 0.04143 10 | 0.07568 0.05132 11 | 0.1039 0.06082 12 | 0.1358 0.06972 13 | 0.17103 0.07786 14 | 0.2092 0.08505 15 | 0.24987 0.09113 16 | 0.29259 0.09594 17 | 0.33689 0.09933 18 | 0.38223 0.10109 19 | 0.42809 0.10101 20 | 0.47384 0.09843 21 | 0.52005 0.09237 22 | 0.56801 0.08356 23 | 0.61747 0.07379 24 | 0.66718 0.06403 25 | 0.71606 0.05462 26 | 0.76314 0.04578 27 | 0.80756 0.03761 28 | 0.84854 0.03017 29 | 0.88537 0.02335 30 | 0.91763 0.01694 31 | 0.94523 0.01101 32 | 0.96799 0.006 33 | 0.98528 0.00245 34 | 0.99623 0.00054 35 | 36 | 0.00000 0.00000 37 | 0.00140 -0.00498 38 | 0.00933 -0.01272 39 | 0.02321 -0.02162 40 | 0.04223 -0.03144 41 | 0.06579 -0.04199 42 | 0.09325 -0.05301 43 | 0.12397 -0.06408 44 | 0.15752 -0.07467 45 | 0.19362 -0.08447 46 | 0.23175 -0.09326 47 | 0.27129 -0.10060 48 | 0.31188 -0.10589 49 | 0.35328 -0.10866 50 | 0.39541 -0.10842 51 | 0.43832 -0.10484 52 | 0.48234 -0.09756 53 | 0.52837 -0.08697 54 | 0.57663 -0.07442 55 | 0.62649 -0.06112 56 | 0.67710 -0.04792 57 | 0.72752 -0.03558 58 | 0.77668 -0.02466 59 | 0.82348 -0.01559 60 | 0.86677 -0.00859 61 | 0.90545 -0.00370 62 | 0.93852 -0.00075 63 | 0.96509 0.00054 64 | 0.98446 0.00065 65 | 0.99612 0.00024 66 | 67 | -------------------------------------------------------------------------------- /vortexje/surface-builder.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface builder. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __SURFACE_BUILDER_HPP__ 10 | #define __SURFACE_BUILDER_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace Vortexje 18 | { 19 | 20 | /** 21 | Class for construction of surfaces by joining 2-D shapes together. 22 | 23 | @brief surface construction helper. 24 | */ 25 | class SurfaceBuilder 26 | { 27 | public: 28 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 29 | 30 | SurfaceBuilder(Surface &surface); 31 | 32 | /** 33 | Surface under construction. 34 | */ 35 | Surface &surface; 36 | 37 | std::vector create_nodes_for_points(const std::vector > &points); 38 | 39 | std::vector create_panels_between_shapes(const std::vector &first_nodes, const std::vector &second_nodes, bool cyclic = true); 40 | 41 | std::vector create_panels_inside_shape(const std::vector &nodes, const Eigen::Vector3d &tip_point, int z_sign); 42 | 43 | void finish(); 44 | }; 45 | 46 | }; 47 | 48 | #endif // __SURFACE_BUILDER_HPP__ 49 | -------------------------------------------------------------------------------- /vortexje/shape-generators/ellipse-generator.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Ellipse generator. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace std; 14 | using namespace Eigen; 15 | using namespace Vortexje; 16 | 17 | static const double pi = 3.141592653589793238462643383279502884; 18 | 19 | /** 20 | Generates points tracing an ellipse. 21 | 22 | @param[in] a Semi-major axis (x-coordinate). 23 | @param[in] b Semi-minor axis (y-coordinate). 24 | @param[in] n_points Number of points to return. 25 | 26 | @returns List of points. 27 | */ 28 | vector > 29 | EllipseGenerator::generate(double a, double b, int n_points) 30 | { 31 | vector > points; 32 | 33 | // Go in the clockwise (negative) direction, for consistency with the airfoil generators. 34 | double dt = -2 * pi / (double) n_points; 35 | for (int i = 0; i < n_points; i++) { 36 | double t = i * dt; 37 | 38 | Vector3d point(a * cos(t), b * sin(t), 0.0); 39 | 40 | points.push_back(point); 41 | } 42 | 43 | return points; 44 | } 45 | -------------------------------------------------------------------------------- /vortexje/parameters.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Default parameters 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | using namespace Vortexje; 15 | using namespace std; 16 | 17 | // Default values: 18 | int Parameters::linear_solver_max_iterations = 20000; 19 | 20 | double Parameters::linear_solver_tolerance = numeric_limits::epsilon(); 21 | 22 | bool Parameters::unsteady_bernoulli = true; 23 | 24 | bool Parameters::convect_wake = true; 25 | 26 | bool Parameters::wake_emission_follow_bisector = true; 27 | 28 | double Parameters::wake_emission_distance_factor = 0.25; 29 | 30 | double Parameters::wake_vortex_core_radius = 0.0; 31 | 32 | double Parameters::static_wake_length = 100.0; 33 | 34 | double Parameters::zero_threshold = numeric_limits::epsilon(); 35 | 36 | double Parameters::collocation_point_delta = 1e-12; 37 | 38 | double Parameters::interpolation_layer_thickness = 0.0; 39 | 40 | int Parameters::max_boundary_layer_iterations = 100; 41 | 42 | double Parameters::boundary_layer_iteration_tolerance = numeric_limits::epsilon(); 43 | -------------------------------------------------------------------------------- /vortexje/wake.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Wake. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __WAKE_HPP__ 10 | #define __WAKE_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace Vortexje 19 | { 20 | 21 | /** 22 | Representation of a wake, i.e., a vortex sheet. 23 | 24 | @brief Wake representation. 25 | */ 26 | class Wake : public Surface 27 | { 28 | public: 29 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 30 | 31 | Wake(std::shared_ptr lifting_surface); 32 | 33 | /** 34 | Associated lifting surface. 35 | */ 36 | std::shared_ptr lifting_surface; 37 | 38 | virtual void add_layer(); 39 | 40 | void translate_trailing_edge(const Eigen::Vector3d &translation); 41 | void transform_trailing_edge(const Eigen::Transform &transformation); 42 | 43 | virtual void update_properties(double dt); 44 | 45 | virtual Eigen::Vector3d vortex_ring_unit_velocity(const Eigen::Vector3d &x, int this_panel) const; 46 | 47 | /** 48 | Strengths of the doublet, or vortex ring, panels. 49 | */ 50 | std::vector doublet_coefficients; 51 | }; 52 | 53 | }; 54 | 55 | #endif // __WAKE_HPP__ 56 | -------------------------------------------------------------------------------- /vortexje/surface-writer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface writer base class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | using namespace std; 12 | using namespace Eigen; 13 | using namespace Vortexje; 14 | 15 | /** 16 | Saves the given surface. 17 | 18 | @param[in] surface Surface to write. 19 | @param[in] filename Destination filename. 20 | 21 | @returns true on success. 22 | */ 23 | bool 24 | SurfaceWriter::write(const std::shared_ptr &surface, const std::string &filename) 25 | { 26 | return write(surface, filename, 0, 0); 27 | } 28 | 29 | /** 30 | Saves the given surface. 31 | 32 | @param[in] surface Surface to write. 33 | @param[in] filename Destination filename. 34 | @param[in] node_offset Node numbering offset in output file. 35 | @param[in] panel_offset Panel numbering offset in output file. 36 | 37 | @returns true on success. 38 | */ 39 | bool 40 | SurfaceWriter::write(const std::shared_ptr &surface, const std::string &filename, 41 | int node_offset, int panel_offset) 42 | { 43 | vector empty_names; 44 | vector > empty_data; 45 | 46 | return write(surface, filename, 0, 0, empty_names, empty_data); 47 | } 48 | -------------------------------------------------------------------------------- /cmake/set_cxx_norm.cmake: -------------------------------------------------------------------------------- 1 | # Taken from POLDER, licensed under LGPL v3. 2 | # https://github.com/Morwenn/POLDER 3 | 4 | # Version 5 | cmake_minimum_required(VERSION 2.6.3) 6 | 7 | set(CXX_NORM_CXX98 1) # C++98 8 | set(CXX_NORM_CXX03 2) # C++03 9 | set(CXX_NORM_CXX11 3) # C++11 10 | 11 | # - Set the wanted C++ norm 12 | # Adds the good argument to the command line in function of the compiler 13 | # Currently only works with g++ and clang++ 14 | macro(set_cxx_norm NORM) 15 | 16 | # Extract c++ compiler --version output 17 | exec_program( 18 | ${CMAKE_CXX_COMPILER} 19 | ARGS --version 20 | OUTPUT_VARIABLE _compiler_output 21 | ) 22 | # Keep only the first line 23 | string(REGEX REPLACE 24 | "(\n.*$)" 25 | "" 26 | cxx_compiler_version "${_compiler_output}" 27 | ) 28 | # Extract the version number 29 | string(REGEX REPLACE 30 | "([^0-9.])|([0-9.][^0-9.])" 31 | "" 32 | cxx_compiler_version "${cxx_compiler_version}" 33 | ) 34 | 35 | if(CMAKE_COMPILER_IS_GNUCXX) 36 | 37 | if(${NORM} EQUAL ${CXX_NORM_CXX98}) 38 | add_definitions("-std=c++98") 39 | elseif(${NORM} EQUAL ${CXX_NORM_CXX03}) 40 | add_definitions("-std=c++03") 41 | elseif(${NORM} EQUAL ${CXX_NORM_CXX11}) 42 | if(${cxx_compiler_version} VERSION_LESS "4.7.0") 43 | add_definitions("-std=c++0x") 44 | else() 45 | add_definitions("-std=c++11") 46 | endif() 47 | endif() 48 | 49 | elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 50 | 51 | if(${NORM} EQUAL ${CXX_NORM_CXX11}) 52 | add_definitions("-std=c++0x") 53 | endif() 54 | 55 | endif() 56 | 57 | endmacro() 58 | -------------------------------------------------------------------------------- /vortexje/field-writers/vtk-field-writer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- VTK field writer. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __VTK_FIELD_WRITER_HPP__ 10 | #define __VTK_FIELD_WRITER_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace Vortexje 18 | { 19 | 20 | /** 21 | VTK field file writer. 22 | 23 | @brief VTK field writer. 24 | */ 25 | class VTKFieldWriter : public FieldWriter 26 | { 27 | public: 28 | const char *file_extension() const; 29 | 30 | bool write_velocity_field(const Solver &solver, 31 | const std::string &filename, 32 | double x_min, double x_max, 33 | double y_min, double y_max, 34 | double z_min, double z_max, 35 | double dx, double dy, double dz); 36 | 37 | bool write_velocity_potential_field(const Solver &solver, 38 | const std::string &filename, 39 | double x_min, double x_max, 40 | double y_min, double y_max, 41 | double z_min, double z_max, 42 | double dx, double dy, double dz); 43 | 44 | private: 45 | void write_preamble(std::ofstream &f, 46 | double x_min, double y_min, double z_min, 47 | double dx, double dy, double dz, 48 | int nx, int ny, int nz) const; 49 | }; 50 | 51 | }; 52 | 53 | #endif // __VTK_FIELD_WRITER_HPP__ 54 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | project(Vortexje) 3 | 4 | # The version number. 5 | set(Vortexje_VERSION_MAJOR 0) 6 | set(Vortexje_VERSION_MINOR 2) 7 | 8 | # Use C++11. 9 | include("cmake/set_cxx_norm.cmake") 10 | set_cxx_norm(CXX_NORM_CXX11) 11 | 12 | # Use Eigen3. 13 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 14 | find_package(Eigen3 3.2.2 REQUIRED) 15 | 16 | # Base compiler flags 17 | if(CMAKE_COMPILER_IS_GNUCXX) 18 | set(CMAKE_CXX_FLAGS "-O3 -march=native -Wall") # Optimize for this machine, and turn on compiler warnings. 19 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") # Optimize for this architecture. 20 | endif() 21 | 22 | # Enable PIC for shared library generation. 23 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 24 | 25 | # Use OpenMP. 26 | find_package(OpenMP) 27 | if(OPENMP_FOUND) 28 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 29 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 30 | endif() 31 | 32 | # Include directories. 33 | include_directories(${EIGEN3_INCLUDE_DIR}) 34 | include_directories(.) 35 | 36 | # Enable testing. 37 | enable_testing() 38 | 39 | # Subdirectories. 40 | add_subdirectory(vortexje) 41 | add_subdirectory(tests) 42 | add_subdirectory(examples) 43 | add_subdirectory(doc) 44 | 45 | # Install pkg-config file. 46 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/vortexje.pc.in ${CMAKE_CURRENT_BINARY_DIR}/vortexje.pc @ONLY) 47 | 48 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/vortexje.pc DESTINATION lib/pkgconfig) 49 | 50 | # Build a CPack driven installer package 51 | include(InstallRequiredSystemLibraries) 52 | set(CPACK_RESOURCE_FILE_LICENSE 53 | "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") 54 | set(CPACK_PACKAGE_VERSION_MAJOR "${Vortexje_VERSION_MAJOR}") 55 | set(CPACK_PACKAGE_VERSION_MINOR "${Vortexje_VERSION_MINOR}") 56 | include(CPack) 57 | -------------------------------------------------------------------------------- /vortexje/surface-writer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface writer base class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __SURFACE_WRITER_HPP__ 10 | #define __SURFACE_WRITER_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace Vortexje 18 | { 19 | 20 | /** 21 | Surface writer base class. 22 | 23 | @brief Surface writer base class. 24 | */ 25 | class SurfaceWriter 26 | { 27 | public: 28 | /** 29 | Destructor. 30 | */ 31 | virtual ~SurfaceWriter() {}; 32 | 33 | /** 34 | Returns the appropriate file extension for this SurfaceWriter. 35 | 36 | @returns The file extension. 37 | */ 38 | virtual const char *file_extension() const = 0; 39 | 40 | bool write(const std::shared_ptr &surface, const std::string &filename); 41 | 42 | bool write(const std::shared_ptr &surface, const std::string &filename, 43 | int node_offset, int panel_offset); 44 | 45 | /** 46 | Saves the given surface to a file, including data vectors associating numerical values to each panel. 47 | 48 | @param[in] surface Surface to write. 49 | @param[in] filename Destination filename. 50 | @param[in] node_offset Node numbering offset in output file. 51 | @param[in] panel_offset Panel numbering offset in output file. 52 | @param[in] view_names List of names of data vectors to be stored. 53 | @param[in] view_data List of data vectors to be stored. 54 | 55 | @returns true on success. 56 | */ 57 | virtual bool write(const std::shared_ptr &surface, const std::string &filename, 58 | int node_offset, int panel_offset, 59 | const std::vector &view_names, const std::vector > &view_data) = 0; 60 | }; 61 | 62 | }; 63 | 64 | #endif // __SURFACE_WRITER_HPP__ 65 | -------------------------------------------------------------------------------- /vortexje/boundary-layers/dummy-boundary-layer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Dummy boundary layer class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | using namespace Eigen; 12 | using namespace Vortexje; 13 | using namespace std; 14 | 15 | /** 16 | Normally, this function solves the relevant boundary layer equations. Here, it does nothing. 17 | 18 | @param[in] freestream_velocity Freestream velocity vector. 19 | @param[in] surface_velocities (n x 3)-matrix of surface velocities. 20 | 21 | @returns Always true. 22 | */ 23 | bool 24 | DummyBoundaryLayer::recalculate(const Vector3d &freestream_velocity, const MatrixXd &surface_velocities) 25 | { 26 | return true; 27 | } 28 | 29 | /** 30 | Returns the thickness of the boundary layer at the given panel. 31 | 32 | @param[in] surface Reference surface. 33 | @param[in] panel Reference panel. 34 | 35 | @returns Zero. 36 | */ 37 | double 38 | DummyBoundaryLayer::thickness(const shared_ptr &surface, int panel) const 39 | { 40 | return 0.0; 41 | } 42 | 43 | /** 44 | Returns the velocity in the boundary layer at the given panel, at the given wall distance. 45 | 46 | @param[in] surface Reference surface. 47 | @param[in] panel Reference panel. 48 | @param[in] y Wall distance. 49 | 50 | @returns Zero vector. 51 | */ 52 | Vector3d 53 | DummyBoundaryLayer::velocity(const shared_ptr &surface, int panel, double y) const 54 | { 55 | return Vector3d(0, 0, 0); 56 | } 57 | 58 | /** 59 | Returns the blowing velocity for the given panel. 60 | 61 | @param[in] surface Reference surface. 62 | @param[in] panel Reference panel. 63 | 64 | @returns Blowing velocity for the given panel. 65 | */ 66 | double 67 | DummyBoundaryLayer::blowing_velocity(const shared_ptr &surface, int panel) const 68 | { 69 | return 0.0; 70 | } 71 | 72 | /** 73 | Returns the friction force acting on the given panel. 74 | 75 | @param[in] surface Reference surface. 76 | @param[in] panel Reference panel. 77 | 78 | @returns Friction force acting on the given panel. 79 | */ 80 | Vector3d 81 | DummyBoundaryLayer::friction(const shared_ptr &surface, int panel) const 82 | { 83 | return Vector3d(0, 0, 0); 84 | } 85 | -------------------------------------------------------------------------------- /vortexje/boundary-layer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Boundary layer base class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __BOUNDARY_LAYER_HPP__ 10 | #define __BOUNDARY_LAYER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace Vortexje 19 | { 20 | 21 | /** 22 | Per-surface boundary layer base class. 23 | 24 | @brief Boundary layer base class. 25 | */ 26 | class BoundaryLayer 27 | { 28 | public: 29 | /** 30 | Destructor. 31 | */ 32 | virtual ~BoundaryLayer() {}; 33 | 34 | /** 35 | Normally, this function solves the relevant boundary layer equations. Here, it does nothing. 36 | 37 | @param[in] freestream_velocity Freestream velocity vector. 38 | @param[in] surface_velocities (n x 3)-matrix of surface velocities. 39 | 40 | @returns true on success. 41 | */ 42 | virtual bool recalculate(const Eigen::Vector3d &freestream_velocity, const Eigen::MatrixXd &surface_velocities) = 0; 43 | 44 | /** 45 | Returns the thickness of the boundary layer at the given panel. 46 | 47 | @param[in] surface Reference surface. 48 | @param[in] panel Reference panel. 49 | 50 | @returns Boundary layer thickness of the given panel. 51 | */ 52 | virtual double thickness(const std::shared_ptr &surface, int panel) const = 0; 53 | 54 | /** 55 | Returns the velocity in the boundary layer at the given panel, at the given wall distance. 56 | 57 | @param[in] surface Reference surface. 58 | @param[in] panel Reference panel. 59 | @param[in] y Wall distance. 60 | 61 | @returns Boundary layer velocity of the given panel, at the given wall distance. 62 | */ 63 | virtual Eigen::Vector3d velocity(const std::shared_ptr &surface, int panel, double y) const = 0; 64 | 65 | /** 66 | Returns the blowing velocity for the given panel. 67 | 68 | @param[in] surface Reference surface. 69 | @param[in] panel Reference panel. 70 | 71 | @returns Blowing velocity for the given panel. 72 | */ 73 | virtual double blowing_velocity(const std::shared_ptr &surface, int panel) const = 0; 74 | 75 | /** 76 | Returns the friction force acting on the given panel. 77 | 78 | @param[in] surface Reference surface. 79 | @param[in] panel Reference panel. 80 | 81 | @returns Friction force acting on the given panel. 82 | */ 83 | virtual Eigen::Vector3d friction(const std::shared_ptr &surface, int panel) const = 0; 84 | }; 85 | 86 | }; 87 | 88 | #endif // __BOUNDARY_LAYER_HPP__ 89 | -------------------------------------------------------------------------------- /vortexje/lifting-surface.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Lifting surface. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __LIFTING_SURFACE_HPP__ 10 | #define __LIFTING_SURFACE_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Representation of lifting surface. 21 | 22 | Note: The labels "upper" and "lower" must be consistent within the same surface, but are otherwise arbitrary. 23 | 24 | @brief Lifting surface representation. 25 | */ 26 | class LiftingSurface : public Surface 27 | { 28 | public: 29 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 30 | 31 | LiftingSurface(const std::string &id); 32 | 33 | /** 34 | Nodes on the upper side of the surface. The first dimension is the chordwise direction; the second the spanwise direction. 35 | 36 | Note: This matrix must at least cover the nodes belonging to the upper side panels adjacent to the trailing edge. 37 | */ 38 | Eigen::MatrixXi upper_nodes; 39 | 40 | /** 41 | Nodes on the lower side of the surface. The first dimension is the chordwise direction; the second the spanwise direction. 42 | 43 | Note: This matrix must at least cover the nodes belonging to the lower side panels adjacent to the trailing edge. 44 | */ 45 | Eigen::MatrixXi lower_nodes; 46 | 47 | /** 48 | Panels on the upper side of the surface. The first dimension is the chordwise direction; the second the spanwise direction. 49 | 50 | Note: This matrix must at least cover the upper side panels adjacent to the trailing edge. 51 | */ 52 | Eigen::MatrixXi upper_panels; 53 | 54 | /** 55 | Panels on the lower side of the surface. The first dimension is the chordwise direction; the second the spanwise direction. 56 | 57 | Note: This matrix must at least cover the lower side panels adjacent to the trailing edge. 58 | */ 59 | Eigen::MatrixXi lower_panels; 60 | 61 | int n_chordwise_nodes() const; 62 | int n_chordwise_panels() const; 63 | 64 | int n_spanwise_nodes() const; 65 | int n_spanwise_panels() const; 66 | 67 | int trailing_edge_node(int index) const; 68 | int trailing_edge_upper_panel(int index) const; 69 | int trailing_edge_lower_panel(int index) const; 70 | 71 | void finish_trailing_edge(); 72 | 73 | virtual void transform(const Eigen::Transform &transformation); 74 | 75 | virtual Eigen::Vector3d wake_emission_velocity(const Eigen::Vector3d &apparent_velocity, int node_index) const; 76 | 77 | private: 78 | /** 79 | Cached list of trailing edge bisector vectors. 80 | */ 81 | Eigen::MatrixXd trailing_edge_bisectors; 82 | 83 | /** 84 | Cached list of vectors normal to the initial wake strip surface. 85 | */ 86 | Eigen::MatrixXd wake_normals; 87 | }; 88 | 89 | }; 90 | 91 | #endif // __LIFTING_SURFACE_HPP__ 92 | -------------------------------------------------------------------------------- /vortexje/empirical-wakes/ramasamy-leishman-wake.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Ramasamy-Leishman wake model. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __RAMASAMY_LEISHMAN_WAKE_HPP__ 10 | #define __RAMASAMY_LEISHMAN_WAKE_HPP__ 11 | 12 | #include 13 | 14 | namespace Vortexje 15 | { 16 | 17 | /** 18 | Representation of a wake, i.e., a vortex sheet. 19 | 20 | @brief Wake representation. 21 | 22 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 23 | */ 24 | class RamasamyLeishmanWake : public Wake 25 | { 26 | public: 27 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 28 | 29 | RamasamyLeishmanWake(std::shared_ptr lifting_surface); 30 | 31 | void add_layer(); 32 | 33 | void update_properties(double dt); 34 | 35 | Eigen::Vector3d vortex_ring_unit_velocity(const Eigen::Vector3d &x, int this_panel) const; 36 | 37 | /** 38 | Radii of the vortex filaments forming the vortex rings. 39 | */ 40 | std::vector > vortex_core_radii; 41 | 42 | /** 43 | Ramasamy-Leishman wake model parameters. 44 | 45 | @brief Wake model parameters. 46 | */ 47 | class Parameters { 48 | public: 49 | /** 50 | Kinematic viscosity of the fluid. 51 | */ 52 | static double fluid_kinematic_viscosity; 53 | 54 | /** 55 | Initial vortex filament radius, when using the Ramasamy-Leishman vortex sheet model. 56 | 57 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 58 | */ 59 | static double initial_vortex_core_radius; 60 | 61 | /** 62 | Minimum vortex filament radius, when using the Ramasamy-Leishman vortex sheet model. 63 | 64 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 65 | */ 66 | static double min_vortex_core_radius; 67 | 68 | /** 69 | Lamb's constant from the Ramasamy-Leishman vortex sheet model. 70 | 71 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 72 | */ 73 | static double lambs_constant; 74 | 75 | /** 76 | a' constant from the Ramasamy-Leishman vortex sheet model. 77 | 78 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 79 | */ 80 | static double a_prime; 81 | }; 82 | 83 | private: 84 | /** 85 | Initial lengths of the vortex filaments forming the vortex rings. 86 | */ 87 | std::vector > base_edge_lengths; 88 | 89 | void update_vortex_ring_radii(int panel, double dt); 90 | }; 91 | 92 | }; 93 | 94 | #endif // __RAMASAMY_LEISHMAN_WAKE_HPP__ 95 | -------------------------------------------------------------------------------- /tests/sphere/test-sphere.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Simple sphere example. 3 | // 4 | // Copyright (C) 2013 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | #define TEST_TOLERANCE 5e-2 20 | 21 | int 22 | main (int argc, char **argv) 23 | { 24 | // Load sphere surface: 25 | GmshSurfaceLoader surface_loader; 26 | 27 | shared_ptr sphere(new Surface("main")); 28 | surface_loader.load(sphere, string("sphere.msh")); 29 | 30 | // Create surface body: 31 | shared_ptr body(new Body(string("sphere"))); 32 | body->add_non_lifting_surface(sphere); 33 | 34 | // Set up solver: 35 | Solver solver("test-sphere-log"); 36 | solver.add_body(body); 37 | 38 | Vector3d freestream_velocity(30.0, 0, 0); 39 | solver.set_freestream_velocity(freestream_velocity); 40 | 41 | double fluid_density = 1.2; 42 | solver.set_fluid_density(fluid_density); 43 | 44 | // Run simulation: 45 | solver.solve(); 46 | 47 | // Check pressure coefficients and surface potential values: 48 | for (int i = 0; i < sphere->n_panels(); i++) { 49 | Vector3d x = sphere->panel_collocation_point(i, false); 50 | 51 | // Angle between flow and point on sphere: 52 | double theta = acos(x.dot(freestream_velocity) / (x.norm() * freestream_velocity.norm())); 53 | 54 | // Analytical solution for pressure coefficient and surface potential. 55 | // See J. Katz and A. Plotkin, Low-Speed Aerodynamics, Cambridge University Press, 2001. 56 | double C_p_ref = 1.0 - 9.0 / 4.0 * pow(sin(theta), 2); 57 | 58 | double R = x.norm(); 59 | double phi_ref = freestream_velocity.norm() * cos(theta) * (R + pow(R, 3) / (2 * pow(R, 2))); 60 | 61 | // Computed pressure coefficient: 62 | double C_p = solver.pressure_coefficient(sphere, i); 63 | 64 | double phi = solver.surface_velocity_potential(sphere, i); 65 | 66 | // Compare: 67 | if (fabs(C_p - C_p_ref) > TEST_TOLERANCE) { 68 | cerr << " *** TEST FAILED *** " << endl; 69 | cerr << " theta = " << theta << " rad" << endl; 70 | cerr << " C_p(ref) = " << C_p_ref << endl; 71 | cerr << " C_p = " << C_p << endl; 72 | cerr << " ******************* " << endl; 73 | 74 | exit(1); 75 | } 76 | 77 | if (fabs(phi - phi_ref) / (R * freestream_velocity.norm()) > TEST_TOLERANCE) { 78 | cerr << " *** TEST FAILED *** " << endl; 79 | cerr << " theta = " << theta << " rad" << endl; 80 | cerr << " phi(ref) = " << phi_ref << endl; 81 | cerr << " phi = " << phi << endl; 82 | cerr << " ******************* " << endl; 83 | 84 | exit(1); 85 | } 86 | } 87 | 88 | // Done: 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /cmake/FindEigen3.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Eigen3 lib 2 | # 3 | # This module supports requiring a minimum version, e.g. you can do 4 | # find_package(Eigen3 3.1.2) 5 | # to require version 3.1.2 or newer of Eigen3. 6 | # 7 | # Once done this will define 8 | # 9 | # EIGEN3_FOUND - system has eigen lib with correct version 10 | # EIGEN3_INCLUDE_DIR - the eigen include directory 11 | # EIGEN3_VERSION - eigen version 12 | 13 | # Copyright (c) 2006, 2007 Montel Laurent, 14 | # Copyright (c) 2008, 2009 Gael Guennebaud, 15 | # Copyright (c) 2009 Benoit Jacob 16 | # Redistribution and use is allowed according to the terms of the 2-clause BSD license. 17 | 18 | if(NOT Eigen3_FIND_VERSION) 19 | if(NOT Eigen3_FIND_VERSION_MAJOR) 20 | set(Eigen3_FIND_VERSION_MAJOR 2) 21 | endif(NOT Eigen3_FIND_VERSION_MAJOR) 22 | if(NOT Eigen3_FIND_VERSION_MINOR) 23 | set(Eigen3_FIND_VERSION_MINOR 91) 24 | endif(NOT Eigen3_FIND_VERSION_MINOR) 25 | if(NOT Eigen3_FIND_VERSION_PATCH) 26 | set(Eigen3_FIND_VERSION_PATCH 0) 27 | endif(NOT Eigen3_FIND_VERSION_PATCH) 28 | 29 | set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") 30 | endif(NOT Eigen3_FIND_VERSION) 31 | 32 | macro(_eigen3_check_version) 33 | file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) 34 | 35 | string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") 36 | set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") 37 | string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") 38 | set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") 39 | string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") 40 | set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") 41 | 42 | set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) 43 | if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 44 | set(EIGEN3_VERSION_OK FALSE) 45 | else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 46 | set(EIGEN3_VERSION_OK TRUE) 47 | endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 48 | 49 | if(NOT EIGEN3_VERSION_OK) 50 | 51 | message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " 52 | "but at least version ${Eigen3_FIND_VERSION} is required") 53 | endif(NOT EIGEN3_VERSION_OK) 54 | endmacro(_eigen3_check_version) 55 | 56 | if (EIGEN3_INCLUDE_DIR) 57 | 58 | # in cache already 59 | _eigen3_check_version() 60 | set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) 61 | 62 | else (EIGEN3_INCLUDE_DIR) 63 | 64 | find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library 65 | PATHS 66 | ${CMAKE_INSTALL_PREFIX}/include 67 | ${KDE4_INCLUDE_DIR} 68 | PATH_SUFFIXES eigen3 eigen 69 | ) 70 | 71 | if(EIGEN3_INCLUDE_DIR) 72 | _eigen3_check_version() 73 | endif(EIGEN3_INCLUDE_DIR) 74 | 75 | include(FindPackageHandleStandardArgs) 76 | find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) 77 | 78 | mark_as_advanced(EIGEN3_INCLUDE_DIR) 79 | 80 | endif(EIGEN3_INCLUDE_DIR) 81 | 82 | -------------------------------------------------------------------------------- /examples/clarky-section/clarky.dat: -------------------------------------------------------------------------------- 1 | CLARK Y AIRFOIL 2 | 61.0 61.0 3 | 4 | 0.0000000 0.0000000 5 | 0.0005000 0.0023390 6 | 0.0010000 0.0037271 7 | 0.0020000 0.0058025 8 | 0.0040000 0.0089238 9 | 0.0080000 0.0137350 10 | 0.0120000 0.0178581 11 | 0.0200000 0.0253735 12 | 0.0300000 0.0330215 13 | 0.0400000 0.0391283 14 | 0.0500000 0.0442753 15 | 0.0600000 0.0487571 16 | 0.0800000 0.0564308 17 | 0.1000000 0.0629981 18 | 0.1200000 0.0686204 19 | 0.1400000 0.0734360 20 | 0.1600000 0.0775707 21 | 0.1800000 0.0810687 22 | 0.2000000 0.0839202 23 | 0.2200000 0.0861433 24 | 0.2400000 0.0878308 25 | 0.2600000 0.0890840 26 | 0.2800000 0.0900016 27 | 0.3000000 0.0906804 28 | 0.3200000 0.0911857 29 | 0.3400000 0.0915079 30 | 0.3600000 0.0916266 31 | 0.3800000 0.0915212 32 | 0.4000000 0.0911712 33 | 0.4200000 0.0905657 34 | 0.4400000 0.0897175 35 | 0.4600000 0.0886427 36 | 0.4800000 0.0873572 37 | 0.5000000 0.0858772 38 | 0.5200000 0.0842145 39 | 0.5400000 0.0823712 40 | 0.5600000 0.0803480 41 | 0.5800000 0.0781451 42 | 0.6000000 0.0757633 43 | 0.6200000 0.0732055 44 | 0.6400000 0.0704822 45 | 0.6600000 0.0676046 46 | 0.6800000 0.0645843 47 | 0.7000000 0.0614329 48 | 0.7200000 0.0581599 49 | 0.7400000 0.0547675 50 | 0.7600000 0.0512565 51 | 0.7800000 0.0476281 52 | 0.8000000 0.0438836 53 | 0.8200000 0.0400245 54 | 0.8400000 0.0360536 55 | 0.8600000 0.0319740 56 | 0.8800000 0.0277891 57 | 0.9000000 0.0235025 58 | 0.9200000 0.0191156 59 | 0.9400000 0.0146239 60 | 0.9600000 0.0100232 61 | 0.9700000 0.0076868 62 | 0.9800000 0.0053335 63 | 0.9900000 0.0029690 64 | 1.0000000 0.0005993 65 | 66 | 0.0000000 0.0000000 67 | 0.0005000 -.0046700 68 | 0.0010000 -.0059418 69 | 0.0020000 -.0078113 70 | 0.0040000 -.0105126 71 | 0.0080000 -.0142862 72 | 0.0120000 -.0169733 73 | 0.0200000 -.0202723 74 | 0.0300000 -.0226056 75 | 0.0400000 -.0245211 76 | 0.0500000 -.0260452 77 | 0.0600000 -.0271277 78 | 0.0800000 -.0284595 79 | 0.1000000 -.0293786 80 | 0.1200000 -.0299633 81 | 0.1400000 -.0302404 82 | 0.1600000 -.0302546 83 | 0.1800000 -.0300490 84 | 0.2000000 -.0296656 85 | 0.2200000 -.0291445 86 | 0.2400000 -.0285181 87 | 0.2600000 -.0278164 88 | 0.2800000 -.0270696 89 | 0.3000000 -.0263079 90 | 0.3200000 -.0255565 91 | 0.3400000 -.0248176 92 | 0.3600000 -.0240870 93 | 0.3800000 -.0233606 94 | 0.4000000 -.0226341 95 | 0.4200000 -.0219042 96 | 0.4400000 -.0211708 97 | 0.4600000 -.0204353 98 | 0.4800000 -.0196986 99 | 0.5000000 -.0189619 100 | 0.5200000 -.0182262 101 | 0.5400000 -.0174914 102 | 0.5600000 -.0167572 103 | 0.5800000 -.0160232 104 | 0.6000000 -.0152893 105 | 0.6200000 -.0145551 106 | 0.6400000 -.0138207 107 | 0.6600000 -.0130862 108 | 0.6800000 -.0123515 109 | 0.7000000 -.0116169 110 | 0.7200000 -.0108823 111 | 0.7400000 -.0101478 112 | 0.7600000 -.0094133 113 | 0.7800000 -.0086788 114 | 0.8000000 -.0079443 115 | 0.8200000 -.0072098 116 | 0.8400000 -.0064753 117 | 0.8600000 -.0057408 118 | 0.8800000 -.0050063 119 | 0.9000000 -.0042718 120 | 0.9200000 -.0035373 121 | 0.9400000 -.0028028 122 | 0.9600000 -.0020683 123 | 0.9700000 -.0017011 124 | 0.9800000 -.0013339 125 | 0.9900000 -.0009666 126 | 1.0000000 -.0005993 127 | -------------------------------------------------------------------------------- /vortexje/field-writer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface writer base class. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __FIELD_WRITER_HPP__ 10 | #define __FIELD_WRITER_HPP__ 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace Vortexje 17 | { 18 | 19 | /** 20 | Field writer base class. 21 | 22 | @brief Field writer base class. 23 | */ 24 | class FieldWriter 25 | { 26 | public: 27 | /** 28 | Destructor. 29 | */ 30 | virtual ~FieldWriter() {}; 31 | 32 | /** 33 | Returns the appropriate file extension for this FieldWriter. 34 | 35 | @returns The file extension. 36 | */ 37 | virtual const char *file_extension() const = 0; 38 | 39 | /** 40 | Logs the velocity vector field for a specified grid. 41 | 42 | @param[in] solver Solver whose state to output. 43 | @param[in] filename Destination filename. 44 | @param[in] x_min Minimum X coordinate of grid. 45 | @param[in] x_max Maximum X coordinate of grid. 46 | @param[in] y_min Minimum Y coordinate of grid. 47 | @param[in] y_max Maximum Y coordinate of grid. 48 | @param[in] z_min Minimum Z coordinate of grid. 49 | @param[in] z_max Maximum Z coordinate of grid. 50 | @param[in] dx Grid step size in X-direction. 51 | @param[in] dy Grid step size in Y-direction. 52 | @param[in] dz Grid step size in Z-direction. 53 | 54 | @returns true on success. 55 | */ 56 | virtual bool write_velocity_field(const Solver &solver, 57 | const std::string &filename, 58 | double x_min, double x_max, 59 | double y_min, double y_max, 60 | double z_min, double z_max, 61 | double dx, double dy, double dz) = 0; 62 | 63 | /** 64 | Logs the velocity potential scalar field for a specified grid. 65 | 66 | @param[in] solver Solver whose state to output. 67 | @param[in] filename Destination filename. 68 | @param[in] x_min Minimum X coordinate of grid. 69 | @param[in] x_max Maximum X coordinate of grid. 70 | @param[in] y_min Minimum Y coordinate of grid. 71 | @param[in] y_max Maximum Y coordinate of grid. 72 | @param[in] z_min Minimum Z coordinate of grid. 73 | @param[in] z_max Maximum Z coordinate of grid. 74 | @param[in] dx Grid step size in X-direction. 75 | @param[in] dy Grid step size in Y-direction. 76 | @param[in] dz Grid step size in Z-direction. 77 | 78 | @returns true on success. 79 | */ 80 | virtual bool write_velocity_potential_field(const Solver &solver, 81 | const std::string &filename, 82 | double x_min, double x_max, 83 | double y_min, double y_max, 84 | double z_min, double z_max, 85 | double dx, double dy, double dz) = 0; 86 | }; 87 | 88 | }; 89 | 90 | #endif // __FIELD_WRITER_HPP__ 91 | -------------------------------------------------------------------------------- /vortexje/parameters.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Numerical simulation parameters. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __PARAMETERS_HPP__ 10 | #define __PARAMETERS_HPP__ 11 | 12 | namespace Vortexje 13 | { 14 | 15 | /** 16 | Parameter settings. 17 | 18 | The static parameters of this class control the behavior of the solver. 19 | 20 | @brief Parameter settings. 21 | */ 22 | class Parameters 23 | { 24 | public: 25 | /** 26 | Maximum number of iterations of BiCGSTAB linear solver. 27 | */ 28 | static int linear_solver_max_iterations; 29 | 30 | /** 31 | Tolerance of BiCGSTAB linear solver. 32 | */ 33 | static double linear_solver_tolerance; 34 | 35 | /** 36 | Whether or not to apply the unsteady Bernoulli equation. 37 | */ 38 | static bool unsteady_bernoulli; 39 | 40 | /** 41 | Whether or not to enable the convection of wake nodes. 42 | */ 43 | static bool convect_wake; 44 | 45 | /** 46 | Whether to emit new wake panels into the direction of the trailing edge bisector, rather than following the 47 | apparent velocity. 48 | */ 49 | static bool wake_emission_follow_bisector; 50 | 51 | /** 52 | Multiplied with the trailing edge velocity to obtain the distance by which the first wake vortex is placed away 53 | from the trailing edge. 54 | 55 | @note See J. Katz and A, Plotkin, Low-Speed Aerodynamics, 2nd Edition, Cambridge University Press, 2001. 56 | */ 57 | static double wake_emission_distance_factor; 58 | 59 | /** 60 | Wake vortex filament core radius (Rankine model). Within the core, the velocity decreases linearly to zero. 61 | 62 | For wake-wake interaction problems, set this to an suitably small number greater than zero. Care must be taken, however, that 63 | the radius is less than the distance between two subsequent layers of wake panels. I.e., it must hold that 64 | 65 | \f[ 66 | \mbox{wake\_vortex\_core\_radius} < \mbox{wake\_emission\_distance\_factor} \cdot v_{\infty} \cdot dt 67 | \f] 68 | */ 69 | static double wake_vortex_core_radius; 70 | 71 | /** 72 | Length of the wake, in case of no wake convection. 73 | */ 74 | static double static_wake_length; 75 | 76 | /** 77 | Quantities below this threshold will be treated as zero. 78 | */ 79 | static double zero_threshold; 80 | 81 | /** 82 | Distance to below-surface collocation points (along normal). 83 | */ 84 | static double collocation_point_delta; 85 | 86 | /** 87 | Thickness of the velocity interpolation layer around the body and its boundary layer. 88 | 89 | Inside the interpolation layer, velocities will be interpolated linearly between surface (or boundary layer) velocities, and the 90 | velocities a layer thickness away from the body and its boundary layer. 91 | 92 | For wake-body interaction problems, set this to an suitably small number greater than zero. Care must be taken, however, that 93 | the outer edge of the first row of wake panels lies outside of the interpolation layer. I.e., it must hold that 94 | 95 | \f[ 96 | \mbox{interpolation\_layer\_thickness} < \mbox{wake\_emission\_distance\_factor} \cdot v_{\infty} \cdot dt 97 | \f] 98 | */ 99 | static double interpolation_layer_thickness; 100 | 101 | /** 102 | Maximum number of potential and boundary layer computation iterations. 103 | */ 104 | static int max_boundary_layer_iterations; 105 | 106 | /** 107 | Boundary layer iteration tolerance. 108 | */ 109 | static double boundary_layer_iteration_tolerance; 110 | }; 111 | 112 | }; 113 | 114 | #endif // __PARAMETERS_HPP__ 115 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/ply-surface-loader.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- PLY surface loader. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include "rply/rply.h" 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | /** 20 | Returns the PLY file extension (".ply"). 21 | 22 | @returns The PLY file extension (".ply"). 23 | */ 24 | const char * 25 | PLYSurfaceLoader::file_extension() const 26 | { 27 | return ".ply"; 28 | } 29 | 30 | // rply parser callbacks. 31 | static int 32 | vertex_cb(p_ply_argument argument) 33 | { 34 | void *ptr; 35 | long index; 36 | ply_get_argument_user_data(argument, &ptr, &index); 37 | 38 | PLYSurfaceLoader *loader = static_cast(ptr); 39 | 40 | loader->read_vertex_coordinate(index, ply_get_argument_value(argument)); 41 | 42 | return 1; 43 | } 44 | 45 | static int 46 | face_cb(p_ply_argument argument) 47 | { 48 | void *ptr; 49 | ply_get_argument_user_data(argument, &ptr, NULL); 50 | 51 | PLYSurfaceLoader *loader = static_cast(ptr); 52 | 53 | long length, index; 54 | ply_get_argument_property(argument, NULL, &length, &index); 55 | 56 | if (index >= 0) 57 | loader->read_panel_node(index, length, (int) ply_get_argument_value(argument)); 58 | 59 | return 1; 60 | } 61 | 62 | /** 63 | Loads and parses the contents of a PLY file into a surface-> 64 | 65 | @param[in] surface Surface to load to. 66 | @param[in] filename Filename pointing to the PLY file to load. 67 | 68 | @returns true on success. 69 | */ 70 | bool 71 | PLYSurfaceLoader::load(shared_ptr surface, const string &filename) 72 | { 73 | cout << "Surface " << surface->id << ": Loading from " << filename << "." << endl; 74 | 75 | this->surface = surface; 76 | 77 | current_panel = 0; 78 | 79 | // Load surface from PLY file. 80 | // We use "rply" from http://w3.impa.br/~diego/software/rply/, available under the MIT license. 81 | p_ply ply = ply_open(filename.c_str(), NULL, 0, NULL); 82 | if (!ply) 83 | return false; 84 | 85 | if (!ply_read_header(ply)) 86 | return false; 87 | 88 | ply_set_read_cb(ply, "vertex", "x", vertex_cb, this, 0); 89 | ply_set_read_cb(ply, "vertex", "y", vertex_cb, this, 1); 90 | ply_set_read_cb(ply, "vertex", "z", vertex_cb, this, 2); 91 | 92 | ply_set_read_cb(ply, "face", "vertex_indices", face_cb, this, 0); 93 | 94 | if (!ply_read(ply)) 95 | return false; 96 | 97 | ply_close(ply); 98 | 99 | // Compute surface topology: 100 | surface->compute_topology(); 101 | 102 | // Compute panel geometry: 103 | surface->compute_geometry(); 104 | 105 | // Done: 106 | return true; 107 | } 108 | 109 | /** 110 | Internal interface method to rply library. 111 | */ 112 | void 113 | PLYSurfaceLoader::read_vertex_coordinate(int index, double value) 114 | { 115 | current_point(index) = value; 116 | 117 | if (index == 2) { 118 | // This is the last coordinate. Process vertex. 119 | surface->nodes.push_back(current_point); 120 | 121 | shared_ptr > neighbor_list = make_shared >(); 122 | surface->node_panel_neighbors.push_back(neighbor_list); 123 | } 124 | } 125 | 126 | /** 127 | Internal interface method to rply library. 128 | */ 129 | void 130 | PLYSurfaceLoader::read_panel_node(int index, int length, int node) 131 | { 132 | current_panel_nodes.push_back(node); 133 | 134 | surface->node_panel_neighbors[node]->push_back(current_panel); 135 | 136 | if (index == length - 1) { 137 | // This is the last node. Process panel. 138 | surface->panel_nodes.push_back(current_panel_nodes); 139 | 140 | current_panel_nodes.clear(); 141 | 142 | current_panel++; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/gmsh-lifting-surface/gmsh-lifting-surface.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Gmsh wing section construction example. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace Eigen; 20 | using namespace Vortexje; 21 | 22 | static const double pi = 3.141592653589793238462643383279502884; 23 | 24 | // Main: 25 | int 26 | main (int argc, char **argv) 27 | { 28 | // Enable wake convection: 29 | Parameters::convect_wake = true; 30 | 31 | // Create lifting surface object: 32 | shared_ptr wing(new LiftingSurface("main")); 33 | 34 | // Load Gmsh mesh file: 35 | GmshSurfaceLoader loader; 36 | loader.load(wing, "gmsh-lifting-surface.msh"); 37 | 38 | // Set lifting surface metadata: 39 | // N.B. The node and panel numbers must match those in the Gmsh file, 40 | // *except* that Gmsh counts from 1, whereas Vortexje counts from 0. 41 | // The Gmsh node and panel numbers are one higher than the Vortexje numbers. 42 | int n_nodes_per_airfoil = 32; 43 | int n_airfoils = 21; 44 | 45 | wing->upper_nodes.resize(n_nodes_per_airfoil / 2 + 1, n_airfoils); 46 | wing->lower_nodes.resize(n_nodes_per_airfoil / 2 + 1, n_airfoils); 47 | for (int j = 0; j < n_airfoils; j++) { 48 | for (int i = 0; i < n_nodes_per_airfoil / 2 + 1; i++) { 49 | wing->upper_nodes(i, j) = j * n_nodes_per_airfoil + i; 50 | 51 | if (i == 0) 52 | wing->lower_nodes(i, j) = j * n_nodes_per_airfoil; 53 | else 54 | wing->lower_nodes(i, j) = j * n_nodes_per_airfoil + (n_nodes_per_airfoil - i); 55 | } 56 | } 57 | 58 | wing->upper_panels.resize(n_nodes_per_airfoil / 2, n_airfoils - 1); 59 | wing->lower_panels.resize(n_nodes_per_airfoil / 2, n_airfoils - 1); 60 | for (int j = 0; j < n_airfoils - 1; j++) { 61 | for (int i = 0; i < n_nodes_per_airfoil / 2; i++) { 62 | wing->upper_panels(i, j) = j * n_nodes_per_airfoil + i; 63 | 64 | wing->lower_panels(i, j) = j * n_nodes_per_airfoil + (n_nodes_per_airfoil - 1 - i); 65 | } 66 | } 67 | 68 | // Finish trailing edge setup: 69 | wing->finish_trailing_edge(); 70 | 71 | // Prescribe angle of attack: 72 | double alpha = 5.0 / 180.0 * pi; 73 | wing->rotate(Vector3d::UnitZ(), -alpha); 74 | 75 | // Create surface body: 76 | shared_ptr body(new Body(string("wing-section"))); 77 | body->add_lifting_surface(wing); 78 | 79 | // Set up solver: 80 | Solver solver("gmsh-lifting-surface-log"); 81 | solver.add_body(body); 82 | 83 | Vector3d freestream_velocity(30, 0, 0); 84 | solver.set_freestream_velocity(freestream_velocity); 85 | 86 | double fluid_density = 1.2; 87 | solver.set_fluid_density(fluid_density); 88 | 89 | // Set up surface writer: 90 | VTKSurfaceWriter surface_writer; 91 | 92 | // Set up field writer: 93 | VTKFieldWriter field_writer; 94 | 95 | // Run simulation: 96 | double t = 0.0; 97 | double dt = 0.01; 98 | int step_number = 0; 99 | 100 | solver.initialize_wakes(dt); 101 | while (t < 60) { 102 | // Solve: 103 | solver.solve(dt); 104 | 105 | // Log coefficients: 106 | solver.log(step_number, surface_writer); 107 | 108 | // Enable below to log the velocity field: 109 | // field_writer.write_velocity_field(solver, "velocity-field.vtk", 0.1, 0.1, 0.1, 0.2, 0.2, 0.2); 110 | 111 | // Update wake: 112 | solver.update_wakes(dt); 113 | 114 | // Step time: 115 | t += dt; 116 | step_number++; 117 | } 118 | 119 | // Done: 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /vortexje/surface-writers/gmsh-surface-writer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Gmsh surface writer. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | /** 20 | Returns the Gmsh MSH surface file extension (".msh"). 21 | 22 | @returns The Gmsh MSH surface file extension (".msh"). 23 | */ 24 | const char * 25 | GmshSurfaceWriter::file_extension() const 26 | { 27 | return ".msh"; 28 | } 29 | 30 | /** 31 | Saves the given surface to a Gmsh MSH file, including data vectors associating numerical values to each panel. 32 | 33 | @param[in] surface Surface to write. 34 | @param[in] filename Destination filename. 35 | @param[in] node_offset Node numbering offset in output file. 36 | @param[in] panel_offset Panel numbering offset in output file. 37 | @param[in] view_names List of names of data vectors to be stored. 38 | @param[in] view_data List of data vectors to be stored. 39 | 40 | @returns true on success. 41 | */ 42 | bool 43 | GmshSurfaceWriter::write(const std::shared_ptr &surface, const string &filename, 44 | int node_offset, int panel_offset, 45 | const std::vector &view_names, const vector > &view_data) 46 | { 47 | cout << "Surface " << surface->id << ": Saving to " << filename << "." << endl; 48 | 49 | // Save surface to gmsh file: 50 | ofstream f; 51 | f.open(filename.c_str()); 52 | 53 | f << "$MeshFormat" << endl; 54 | f << "2.2 0 8" << endl; 55 | f << "$EndMeshFormat" << endl; 56 | 57 | f << "$Nodes" << endl; 58 | f << surface->n_nodes() << endl; 59 | 60 | for (int i = 0; i < surface->n_nodes(); i++) { 61 | f << i + node_offset + 1; 62 | 63 | for (int j = 0; j < 3; j++) { 64 | f << ' '; 65 | f << surface->nodes[i](j); 66 | } 67 | 68 | f << endl; 69 | } 70 | 71 | f << "$EndNodes" << endl; 72 | f << "$Elements" << endl; 73 | f << surface->n_panels() << endl; 74 | 75 | for (int i = 0; i < surface->n_panels(); i++) { 76 | int element_type; 77 | switch (surface->panel_nodes[i].size()) { 78 | case 3: 79 | element_type = 2; 80 | break; 81 | case 4: 82 | element_type = 3; 83 | break; 84 | default: 85 | cerr << "Surface " << surface->id << ": Unknown polygon at panel " << i << "." << endl; 86 | continue; 87 | } 88 | 89 | f << i + panel_offset + 1; 90 | 91 | f << ' '; 92 | 93 | f << element_type; 94 | 95 | f << ' '; 96 | 97 | f << 0; 98 | 99 | for (int j = 0; j < (int) surface->panel_nodes[i].size(); j++) { 100 | f << ' '; 101 | f << surface->panel_nodes[i][j] + node_offset + 1; 102 | } 103 | 104 | f << endl; 105 | } 106 | 107 | f << "$EndElements" << endl; 108 | 109 | for (int k = 0; k < (int) view_names.size(); k++) { 110 | f << "$ElementData" << endl; 111 | f << "1" << endl; 112 | f << '"' << view_names[k] << '"' << endl; 113 | f << "1" << endl; 114 | f << 0.0 << endl; 115 | f << "3" << endl; 116 | f << 0 << endl; 117 | f << view_data[k].cols() << endl; 118 | f << surface->n_panels() << endl; 119 | 120 | for (int i = 0; i < surface->n_panels(); i++) { 121 | f << i + panel_offset + 1; 122 | 123 | for (int j = 0; j < view_data[k].cols(); j++) { 124 | f << ' '; 125 | 126 | f << view_data[k](i, j); 127 | } 128 | 129 | f << endl; 130 | } 131 | 132 | f << "$EndElementData" << endl; 133 | } 134 | 135 | f.close(); 136 | 137 | // Done: 138 | return true; 139 | } 140 | -------------------------------------------------------------------------------- /vortexje/surface-writers/vtk-surface-writer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- VTK surface writer. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | /** 20 | Returns the VTK surface file extension (".vtk"). 21 | 22 | @returns The VTK surface file extension (".vtk"). 23 | */ 24 | const char * 25 | VTKSurfaceWriter::file_extension() const 26 | { 27 | return ".vtk"; 28 | } 29 | 30 | /** 31 | Saves the given surface to a VTK file, including data vectors associating numerical values to each panel. 32 | 33 | @param[in] surface Surface to write. 34 | @param[in] filename Destination filename. 35 | @param[in] node_offset Node numbering offset in output file. 36 | @param[in] panel_offset Panel numbering offset in output file. 37 | @param[in] view_names List of names of data vectors to be stored. 38 | @param[in] view_data List of data vectors to be stored. 39 | 40 | @returns true on success. 41 | */ 42 | bool 43 | VTKSurfaceWriter::write(const std::shared_ptr &surface, const string &filename, 44 | int node_offset, int panel_offset, 45 | const std::vector &view_names, const vector > &view_data) 46 | { 47 | cout << "Surface " << surface->id << ": Saving to " << filename << "." << endl; 48 | 49 | // Save surface to VTK file: 50 | ofstream f; 51 | f.open(filename.c_str()); 52 | 53 | f << "# vtk DataFile Version 2.0" << endl; 54 | f << "FieldData" << endl; 55 | f << "ASCII" << endl; 56 | f << "DATASET UNSTRUCTURED_GRID" << endl; 57 | f << "POINTS " << surface->n_nodes() << " double" << endl; 58 | 59 | for (int i = 0; i < surface->n_nodes(); i++) { 60 | for (int j = 0; j < 3; j++) { 61 | if (j > 0) 62 | f << ' '; 63 | f << surface->nodes[i](j); 64 | } 65 | f << endl; 66 | } 67 | 68 | f << endl; 69 | 70 | int size = 0; 71 | for (int i = 0; i < surface->n_panels(); i++) 72 | size += surface->panel_nodes[i].size() + 1; 73 | 74 | f << "CELLS " << surface->n_panels() << " " << size << endl; 75 | 76 | for (int i = 0; i < surface->n_panels(); i++) { 77 | f << surface->panel_nodes[i].size(); 78 | 79 | for (int j = 0; j < (int) surface->panel_nodes[i].size(); j++) { 80 | f << ' '; 81 | f << surface->panel_nodes[i][j]; 82 | } 83 | 84 | f << endl; 85 | } 86 | 87 | f << endl; 88 | 89 | f << "CELL_TYPES " << surface->n_panels() << endl; 90 | 91 | for (int i = 0; i < surface->n_panels(); i++) { 92 | int cell_type; 93 | switch (surface->panel_nodes[i].size()) { 94 | case 3: 95 | cell_type = 5; 96 | break; 97 | case 4: 98 | cell_type = 9; 99 | break; 100 | default: 101 | cerr << "Surface " << surface->id << ": Unknown polygon at panel " << i << "." << endl; 102 | continue; 103 | } 104 | 105 | f << cell_type << endl; 106 | } 107 | 108 | f << endl; 109 | 110 | f << "CELL_DATA " << surface->n_panels() << endl; 111 | 112 | for (int k = 0; k < (int) view_names.size(); k++) { 113 | if (view_data[k].cols() == 1) { 114 | f << "SCALARS " << view_names[k] << " double 1" << endl; 115 | f << "LOOKUP_TABLE default" << endl; 116 | } else 117 | f << "VECTORS " << view_names[k] << " double" << endl; 118 | 119 | for (int i = 0; i < surface->n_panels(); i++) { 120 | for (int j = 0; j < view_data[k].cols(); j++) { 121 | if (j > 0) 122 | f << ' '; 123 | 124 | f << view_data[k](i, j); 125 | } 126 | 127 | f << endl; 128 | } 129 | 130 | f << endl; 131 | } 132 | 133 | f.close(); 134 | 135 | // Done: 136 | return true; 137 | } 138 | -------------------------------------------------------------------------------- /examples/naca0012-section-oscillation/naca0012-section-oscillation.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- NACA0012 airfoil section in harmonic oscillation. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace Eigen; 20 | using namespace Vortexje; 21 | 22 | static const double pi = 3.141592653589793238462643383279502884; 23 | 24 | int 25 | main (int argc, char **argv) 26 | { 27 | // Set up parameters for unsteady simulation: 28 | Parameters::unsteady_bernoulli = true; 29 | Parameters::convect_wake = true; 30 | 31 | // Create wing: 32 | shared_ptr wing(new LiftingSurface("main")); 33 | 34 | LiftingSurfaceBuilder surface_builder(*wing); 35 | 36 | const double chord = 0.5; 37 | const double span = 1.0; 38 | 39 | const int n_points_per_airfoil = 32; 40 | const int n_airfoils = 21; 41 | 42 | int trailing_edge_point_id; 43 | vector prev_airfoil_nodes; 44 | 45 | vector > node_strips; 46 | vector > panel_strips; 47 | 48 | for (int i = 0; i < n_airfoils; i++) { 49 | vector > airfoil_points = 50 | NACA4AirfoilGenerator::generate(0, 0, 0.12, true, chord, n_points_per_airfoil, trailing_edge_point_id); 51 | for (int j = 0; j < (int) airfoil_points.size(); j++) 52 | airfoil_points[j](2) += i * span / (double) (n_airfoils - 1); 53 | 54 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 55 | node_strips.push_back(airfoil_nodes); 56 | 57 | if (i > 0) { 58 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 59 | panel_strips.push_back(airfoil_panels); 60 | } 61 | 62 | prev_airfoil_nodes = airfoil_nodes; 63 | } 64 | 65 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 66 | 67 | // Create body: 68 | shared_ptr body(new Body(string("wing-section"))); 69 | body->add_lifting_surface(wing); 70 | 71 | // Set up oscillation: 72 | double alpha_max = 10.0 / 180.0 * pi; 73 | 74 | double omega = 2 * pi / 1.0; 75 | 76 | // Set up solver: 77 | Solver solver("naca0012-section-oscillation-log"); 78 | solver.add_body(body); 79 | 80 | Vector3d freestream_velocity(30, 0, 0); 81 | solver.set_freestream_velocity(freestream_velocity); 82 | 83 | double fluid_density = 1.2; 84 | solver.set_fluid_density(fluid_density); 85 | 86 | double dt = 0.01; 87 | 88 | // Set up logging: 89 | VTKSurfaceWriter writer; 90 | 91 | ofstream f; 92 | f.open("naca0012-section-oscillation-log/forces.txt"); 93 | 94 | // Compute: 95 | double alpha = 0.0; 96 | double t = 0.0; 97 | int i = 0; 98 | 99 | solver.initialize_wakes(dt); 100 | 101 | while (t < 30.0) { 102 | // Solve: 103 | solver.solve(dt); 104 | 105 | // Log source, doublet, and pressure distributions: 106 | solver.log(i, writer); 107 | 108 | // Log lift and drag coefficients: 109 | Vector3d F_a = solver.force(body); 110 | 111 | double q = 0.5 * fluid_density * freestream_velocity.squaredNorm(); 112 | 113 | double S = chord * span; 114 | 115 | double C_L = F_a(1) / (q * S); 116 | double C_D = F_a(0) / (q * S); 117 | 118 | f << alpha << ' ' << C_L << ' ' << C_D << endl; 119 | 120 | // Rotate wing: 121 | alpha = alpha_max * sin(omega * t); 122 | Quaterniond attitude = AngleAxis(alpha, Vector3d::UnitZ()) * Quaterniond(1, 0, 0, 0); 123 | body->set_attitude(attitude); 124 | 125 | // Update rotational velocity: 126 | double dalphadt = alpha_max * omega * cos(omega * t); 127 | body->set_rotational_velocity(Vector3d(0, 0, dalphadt)); 128 | 129 | // Update wake: 130 | solver.update_wakes(dt); 131 | 132 | // Step time: 133 | t += dt; 134 | i++; 135 | } 136 | 137 | f.close(); 138 | 139 | // Done. 140 | return 0; 141 | } 142 | -------------------------------------------------------------------------------- /vortexje/surface-loaders/gmsh-surface-loader.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Gmsh surface loader. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | /** 20 | Returns the Gmsh MSH surface file extension (".msh"). 21 | 22 | @returns The Gmsh MSH surface file extension (".msh"). 23 | */ 24 | const char * 25 | GmshSurfaceLoader::file_extension() const 26 | { 27 | return ".msh"; 28 | } 29 | 30 | /** 31 | Loads and parses the contents of a Gmsh MSH file into a surface-> 32 | 33 | @param[in] surface Surface to load to. 34 | @param[in] filename Filename pointing to the Gmsh MSH file to load. 35 | 36 | @returns true on success. 37 | */ 38 | bool 39 | GmshSurfaceLoader::load(shared_ptr surface, const string &filename) 40 | { 41 | cout << "Surface " << surface->id << ": Loading from " << filename << "." << endl; 42 | 43 | // Load surface from gmsh MSH file: 44 | ifstream f; 45 | f.open(filename.c_str()); 46 | 47 | bool in_nodes = false, in_elements = false; 48 | int current_panel = 0; 49 | while (f.good()) { 50 | string line; 51 | getline(f, line); 52 | 53 | if (line[0] == '$') { 54 | if (line == "$MeshFormat" ) { 55 | getline(f, line); 56 | 57 | istringstream tokens(line); 58 | 59 | string version; 60 | int file_type, data_size; 61 | tokens >> version >> file_type >> data_size; 62 | 63 | if (version != "2.2" || file_type != 0 || data_size != 8) { 64 | cerr << "Surface " << surface->id << ": Unknown data format in " << filename << "." << endl; 65 | 66 | f.close(); 67 | 68 | return false; 69 | } 70 | 71 | getline(f, line); 72 | 73 | } else if (line == "$Nodes" ) { 74 | getline(f, line); 75 | 76 | in_nodes = true; 77 | 78 | } else if (line == "$EndNodes" ) { 79 | in_nodes = false; 80 | 81 | } else if (line == "$Elements" ) { 82 | getline(f, line); 83 | 84 | in_elements = true; 85 | 86 | } else if (line == "$EndElements" ) { 87 | in_elements = false; 88 | 89 | } 90 | 91 | } else if (in_nodes) { 92 | istringstream tokens(line); 93 | 94 | int node_number; 95 | double x, y, z; 96 | tokens >> node_number >> x >> y >> z; 97 | 98 | surface->nodes.push_back(Vector3d(x, y, z)); 99 | 100 | shared_ptr > neighbor_list = make_shared >(); 101 | surface->node_panel_neighbors.push_back(neighbor_list); 102 | 103 | } else if (in_elements) { 104 | istringstream tokens(line); 105 | 106 | int single_number, single_type, number_of_tags, number_of_nodes; 107 | tokens >> single_number >> single_type >> number_of_tags; 108 | 109 | switch (single_type) { 110 | case 2: 111 | number_of_nodes = 3; 112 | break; 113 | case 3: 114 | number_of_nodes = 4; 115 | break; 116 | default: 117 | continue; // Only read triangles and quatrangles. 118 | } 119 | 120 | for (int i = 0; i < number_of_tags; i++) { 121 | int tag; 122 | tokens >> tag; 123 | } 124 | 125 | vector single_panel_nodes; 126 | for (int i = 0; i < number_of_nodes; i++) { 127 | int node; 128 | tokens >> node; 129 | 130 | single_panel_nodes.push_back(node - 1); 131 | 132 | surface->node_panel_neighbors[node - 1]->push_back(current_panel); 133 | } 134 | 135 | surface->panel_nodes.push_back(single_panel_nodes); 136 | 137 | current_panel++; 138 | } 139 | } 140 | 141 | f.close(); 142 | 143 | // Compute surface topology: 144 | surface->compute_topology(); 145 | 146 | // Compute panel geometry: 147 | surface->compute_geometry(); 148 | 149 | // Done: 150 | return true; 151 | } 152 | -------------------------------------------------------------------------------- /vortexje/shape-generators/airfoils/naca4-airfoil-generator.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- NACA 4-digit series airfoil generator. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | using namespace std; 15 | using namespace Eigen; 16 | using namespace Vortexje; 17 | 18 | static const double pi = 3.141592653589793238462643383279502884; 19 | 20 | // Cosine rule: 21 | static double 22 | cosine_rule(int n_points, int i) 23 | { 24 | return 0.5 * (1 - cos(pi * i / (double) n_points)); 25 | } 26 | 27 | // NACA airfoil generation: 28 | static double 29 | compute_y_c(double x, double max_camber, double max_camber_dist, double max_thickness, double chord) 30 | { 31 | if (x < max_camber_dist * chord) 32 | return max_camber * x / pow(max_camber_dist, 2) * (2 * max_camber_dist - x / chord); 33 | else 34 | return max_camber * (chord - x) / pow(1 - max_camber_dist, 2) * (1 + x / chord - 2 * max_camber_dist); 35 | } 36 | 37 | static double 38 | compute_theta(double x, double max_camber, double max_camber_dist, double max_thickness, double chord) 39 | { 40 | if (x < max_camber_dist * chord) 41 | return atan(max_camber / pow(max_camber_dist, 2) * (2 * max_camber_dist - x / chord) 42 | - max_camber * x / pow(max_camber_dist, 2) / chord); 43 | else 44 | return atan(-max_camber / pow(1 - max_camber_dist, 2) * (1 + x / chord - 2 * max_camber_dist) 45 | + max_camber * (chord - x) / pow(1 - max_camber_dist, 2) / chord); 46 | } 47 | 48 | static double 49 | compute_y_t(double x, double max_camber, double max_camber_dist, double max_thickness, bool finite_te_thickness, double chord) 50 | { 51 | double k5; 52 | if (finite_te_thickness) { 53 | if (x == chord) 54 | return 0.0; // Ensure a symmetric airfoil. 55 | 56 | k5 = -0.1015; 57 | } else 58 | k5 = -0.1036; 59 | 60 | return max_thickness / 0.2 * chord * (0.2969 * sqrt(x / chord) - 0.1260 * (x / chord) 61 | - 0.3516 * pow(x / chord, 2) + 0.2843 * pow(x / chord, 3) + k5 * pow(x / chord, 4)); 62 | } 63 | 64 | /** 65 | Generates points tracing a 4-digit series NACA airfoil. 66 | 67 | @param[in] max_camber Maximum camber as a percentage of the chord (the first digit, divided by 100). 68 | @param[in] max_camber_dist Distance of maximum camber from the leading edge (the second digit, dividid by 10). 69 | @param[in] max_thickness Maximum thickness of the airfoil (the last two digits, divided by 100). 70 | @param[in] finite_te_thickness True to use a trailing edge with finite thickness. 71 | @param[in] chord Chord length. 72 | @param[in] n_points Number of points to return. 73 | @param[out] trailing_edge_point_id Index of the trailing edge node in the returned list. 74 | 75 | @returns List of points. 76 | 77 | @note See S. Yon, J. Katz, and A. Plotkin, Effect of Airfoil (Trailing-Edge) Thickness on the Numerical Solution of Panel Methods Based on the Dirichlet Boundary Condition, AIAA Journal, Vol. 30, No. 3, March 1992, for the issues that may arise when using an infinitely thin trailing edge. 78 | */ 79 | vector > 80 | NACA4AirfoilGenerator::generate(double max_camber, double max_camber_dist, double max_thickness, bool finite_te_thickness, double chord, int n_points, int &trailing_edge_point_id) 81 | { 82 | if (n_points % 2 == 1) { 83 | cerr << "NACA4::generate(): n_nodes must be even." << endl; 84 | exit(1); 85 | } 86 | 87 | vector > airfoil_points; 88 | 89 | // Add upper nodes: 90 | for (int i = 0; i < n_points / 2; i++) { 91 | double x = chord * cosine_rule(n_points / 2, i); 92 | 93 | double y_c = compute_y_c (x, max_camber, max_camber_dist, max_thickness, chord); 94 | double theta = compute_theta(x, max_camber, max_camber_dist, max_thickness, chord); 95 | double y_t = compute_y_t (x, max_camber, max_camber_dist, max_thickness, finite_te_thickness, chord); 96 | 97 | Vector3d upper_point(x - y_t * sin(theta), y_c + y_t * cos(theta), 0.0); 98 | airfoil_points.push_back(upper_point); 99 | } 100 | 101 | // Add lower nodes: 102 | for (int i = 0; i < n_points / 2; i++) { 103 | double x = chord * (1 - cosine_rule(n_points / 2, i)); 104 | 105 | double y_c = compute_y_c (x, max_camber, max_camber_dist, max_thickness, chord); 106 | double theta = compute_theta(x, max_camber, max_camber_dist, max_thickness, chord); 107 | double y_t = compute_y_t (x, max_camber, max_camber_dist, max_thickness, finite_te_thickness, chord); 108 | 109 | Vector3d lower_point(x + y_t * sin(theta), y_c - y_t * cos(theta), 0.0); 110 | airfoil_points.push_back(lower_point); 111 | } 112 | 113 | // Done. 114 | trailing_edge_point_id = n_points / 2; 115 | 116 | return airfoil_points; 117 | } 118 | -------------------------------------------------------------------------------- /tests/naca0012-airfoil/test-naca0012-airfoil.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Test NACA0012 lift- and drag coefficients against a reference. 3 | // 4 | // Copyright (C) 2013 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace Eigen; 18 | using namespace Vortexje; 19 | 20 | static const double pi = 3.141592653589793238462643383279502884; 21 | 22 | #define TEST_TOLERANCE 2e-2 23 | 24 | Vector2d 25 | run_test(double alpha) 26 | { 27 | // Set up parameters for simplest possible simulation: 28 | Parameters::unsteady_bernoulli = false; 29 | Parameters::convect_wake = false; 30 | 31 | // Create wing: 32 | shared_ptr wing(new LiftingSurface("main")); 33 | 34 | LiftingSurfaceBuilder surface_builder(*wing); 35 | 36 | const double chord = 0.75; 37 | const double span = 4.5; 38 | 39 | const int n_points_per_airfoil = 32; 40 | const int n_airfoils = 21; 41 | 42 | int trailing_edge_point_id; 43 | vector prev_airfoil_nodes; 44 | 45 | vector > node_strips; 46 | vector > panel_strips; 47 | 48 | for (int i = 0; i < n_airfoils; i++) { 49 | vector > airfoil_points = 50 | NACA4AirfoilGenerator::generate(0, 0, 0.12, true, chord, n_points_per_airfoil, trailing_edge_point_id); 51 | for (int j = 0; j < (int) airfoil_points.size(); j++) 52 | airfoil_points[j](2) += i * span / (double) (n_airfoils - 1); 53 | 54 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 55 | node_strips.push_back(airfoil_nodes); 56 | 57 | if (i > 0) { 58 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 59 | panel_strips.push_back(airfoil_panels); 60 | } 61 | 62 | prev_airfoil_nodes = airfoil_nodes; 63 | } 64 | 65 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 66 | 67 | // Rotate by angle of attack: 68 | wing->rotate(Vector3d::UnitZ(), -alpha); 69 | 70 | // Create surface body: 71 | shared_ptr body(new Body(string("wing-section"))); 72 | body->add_lifting_surface(wing); 73 | 74 | // Set up solver: 75 | Solver solver("test-naca0012-log"); 76 | solver.add_body(body); 77 | 78 | Vector3d freestream_velocity(30, 0, 0); 79 | solver.set_freestream_velocity(freestream_velocity); 80 | 81 | double fluid_density = 1.2; 82 | solver.set_fluid_density(fluid_density); 83 | 84 | // Compute: 85 | solver.initialize_wakes(); 86 | solver.solve(); 87 | 88 | // Output lift and drag coefficients: 89 | Vector3d F_a = solver.force(body); 90 | 91 | double q = 0.5 * fluid_density * freestream_velocity.squaredNorm(); 92 | 93 | double S = chord * span; 94 | 95 | double C_L = F_a(1) / (q * S); 96 | double C_D = F_a(0) / (q * S); 97 | 98 | return Vector2d(C_L, C_D); 99 | } 100 | 101 | int 102 | main (int argc, char **argv) 103 | { 104 | // Load reference values. 105 | std::vector > reference_results; 106 | 107 | ifstream f; 108 | f.open("naca0012-reference-data.txt"); 109 | 110 | while (f.good()) { 111 | string line; 112 | getline(f, line); 113 | 114 | if (line.length() == 0) 115 | break; 116 | 117 | istringstream tokens(line); 118 | 119 | Vector3d reference_result; 120 | tokens >> reference_result(0) >> reference_result(1) >> reference_result(2); 121 | 122 | reference_results.push_back(reference_result); 123 | } 124 | 125 | f.close(); 126 | 127 | // Compute new values, and compare. 128 | for (unsigned int i = 0; i < reference_results.size(); i++) { 129 | Vector3d &reference_result = reference_results[i]; 130 | 131 | Vector2d res = run_test(reference_result(0) / 180.0 * pi); 132 | 133 | if (fabs(res[0] - reference_result(1)) > TEST_TOLERANCE) { 134 | cerr << " *** TEST FAILED *** " << endl; 135 | cerr << " alpha = " << reference_result(0) << " deg" << endl; 136 | cerr << " C_L(ref) = " << reference_result(1) << endl; 137 | cerr << " C_L = " << res(0) << endl; 138 | cerr << " ******************* " << endl; 139 | 140 | exit(1); 141 | } 142 | 143 | if (fabs(res[1] - reference_result(2)) > TEST_TOLERANCE) { 144 | cerr << " *** TEST FAILED *** " << endl; 145 | cerr << " alpha = " << reference_result(0) << " deg" << endl; 146 | cerr << " C_D(ref) = " << reference_result(2) << endl; 147 | cerr << " C_D = " << res(1) << endl; 148 | cerr << " ******************* " << endl; 149 | 150 | exit(1); 151 | } 152 | } 153 | 154 | // Done. 155 | return 0; 156 | } 157 | -------------------------------------------------------------------------------- /vortexje/surface.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __SURFACE_HPP__ 10 | #define __SURFACE_HPP__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | namespace Vortexje 24 | { 25 | 26 | /** 27 | Surface representation using node-panel, panel-node, and panel-panel data structures. 28 | Implements geometrical and singularity panel influence operations. 29 | 30 | @brief Surface representation. 31 | */ 32 | class Surface 33 | { 34 | public: 35 | /** 36 | Automatically generated surface identification number. 37 | */ 38 | std::string id; 39 | 40 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 41 | 42 | Surface(const std::string &id); 43 | 44 | virtual ~Surface(); 45 | 46 | int add_triangle(int node_a, int node_b, int node_c); 47 | int add_quadrangle(int node_a, int node_b, int node_c, int node_d); 48 | 49 | void compute_topology(); 50 | 51 | void compute_geometry(int panel); 52 | void compute_geometry(); 53 | 54 | void cut_panels(int panel_a, int panel_b); 55 | 56 | int n_nodes() const; 57 | int n_panels() const; 58 | 59 | /** 60 | Node number to point map. 61 | */ 62 | std::vector > nodes; 63 | 64 | /** 65 | Node number to neigboring panel numbers map. 66 | 67 | A shared pointer is used to encapsulate the list of neighboring panel IDs. This is to allow 68 | physically different nodes to share and maintain an identical list of panel neighbours, as 69 | required by the panel flattening procedure. 70 | */ 71 | std::vector > > node_panel_neighbors; 72 | 73 | /** 74 | Panel number to comprising vertex numbers map. 75 | */ 76 | std::vector > panel_nodes; 77 | 78 | /** 79 | Panel number to (edge number to (neighboring panel number, edge number)-list) map. 80 | 81 | Edges are numbered locally in the positive direction, starting from 0. 82 | 83 | By including a vector of (neighboring panel number, edge number) pairs for every (panel, edge) pair, 84 | an edge of a large panel may border several edges of smaller panels. 85 | */ 86 | std::vector > > > panel_neighbors; 87 | 88 | /** 89 | Panel number to comprising vertex points (in the panel coordinate system) map. 90 | */ 91 | std::vector > > panel_transformed_points; 92 | 93 | void rotate(const Eigen::Vector3d &axis, double angle); 94 | virtual void transform(const Eigen::Matrix3d &transformation); 95 | virtual void transform(const Eigen::Transform &transformation); 96 | virtual void translate(const Eigen::Vector3d &translation); 97 | 98 | const Eigen::Vector3d &panel_collocation_point(int panel, bool below_surface) const; 99 | 100 | const Eigen::Vector3d &panel_normal(int panel) const; 101 | 102 | const Eigen::Transform &panel_coordinate_transformation(int panel) const; 103 | 104 | double panel_surface_area(int panel) const; 105 | 106 | virtual void source_and_doublet_influence(const Eigen::Vector3d &x, int this_panel, double &source_influence, double &doublet_influence) const; 107 | 108 | double source_influence(const Eigen::Vector3d &x, int this_panel) const; 109 | double doublet_influence(const Eigen::Vector3d &x, int this_panel) const; 110 | 111 | virtual Eigen::Vector3d source_unit_velocity(const Eigen::Vector3d &x, int this_panel) const; 112 | virtual Eigen::Vector3d vortex_ring_unit_velocity(const Eigen::Vector3d &x, int this_panel) const; 113 | 114 | double doublet_influence(const std::shared_ptr &other, int other_panel, int this_panel) const; 115 | double source_influence(const std::shared_ptr &other, int other_panel, int this_panel) const; 116 | 117 | void source_and_doublet_influence(const std::shared_ptr &other, int other_panel, int this_panel, double &source_influence, double &doublet_influence) const; 118 | 119 | Eigen::Vector3d source_unit_velocity(const std::shared_ptr &other, int other_panel, int this_panel) const; 120 | Eigen::Vector3d vortex_ring_unit_velocity(const std::shared_ptr &other, int other_panel, int this_panel) const; 121 | 122 | protected: 123 | /** 124 | Panel number to collocation point map. 125 | */ 126 | std::vector > panel_collocation_points[2]; 127 | 128 | /** 129 | Panel number to normal map. 130 | */ 131 | std::vector > panel_normals; 132 | 133 | /** 134 | Panel number to panel coordinate transformation map. 135 | */ 136 | std::vector, Eigen::aligned_allocator > > panel_coordinate_transformations; 137 | 138 | /** 139 | Panel number to surface area map. 140 | */ 141 | std::vector panel_surface_areas; 142 | }; 143 | 144 | }; 145 | 146 | #endif // __SURFACE_HPP__ 147 | -------------------------------------------------------------------------------- /tests/vortex-core/test-vortex-core.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Test Rankine wake vortex model. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace Eigen; 18 | using namespace Vortexje; 19 | 20 | static const double pi = 3.141592653589793238462643383279502884; 21 | 22 | #define TEST_TOLERANCE 1e-3 23 | 24 | int 25 | main (int argc, char **argv) 26 | { 27 | // Set parameters: 28 | Parameters::wake_vortex_core_radius = 1e1; 29 | Parameters::convect_wake = false; 30 | Parameters::static_wake_length = 1e5; 31 | 32 | // Create wing: 33 | shared_ptr wing(new LiftingSurface("main")); 34 | 35 | LiftingSurfaceBuilder surface_builder(*wing); 36 | 37 | const double chord = 1.0; 38 | const double span = 1e4; 39 | 40 | const int n_points_per_airfoil = 10; 41 | const int n_airfoils = 2; 42 | 43 | int trailing_edge_point_id; 44 | vector prev_airfoil_nodes; 45 | 46 | vector > node_strips; 47 | vector > panel_strips; 48 | 49 | for (int i = 0; i < n_airfoils; i++) { 50 | vector > airfoil_points = 51 | NACA4AirfoilGenerator::generate(0, 0, 0.12, true, chord, n_points_per_airfoil, trailing_edge_point_id); 52 | for (int j = 0; j < (int) airfoil_points.size(); j++) 53 | airfoil_points[j](2) += i * span / (double) (n_airfoils - 1); 54 | 55 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 56 | node_strips.push_back(airfoil_nodes); 57 | 58 | if (i > 0) { 59 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 60 | panel_strips.push_back(airfoil_panels); 61 | } 62 | 63 | prev_airfoil_nodes = airfoil_nodes; 64 | } 65 | 66 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 67 | 68 | // Rotate by angle of attack: 69 | double alpha = pi * 5.0 / 180.0; 70 | wing->rotate(Vector3d::UnitZ(), -alpha); 71 | 72 | // Create wake: 73 | shared_ptr wake(new Wake(wing)); 74 | 75 | // Create surface body: 76 | shared_ptr body(new Body(string("wing-section"))); 77 | body->add_lifting_surface(wing, wake); 78 | 79 | // Set up solver: 80 | Solver solver("test-vortex-core-log"); 81 | solver.add_body(body); 82 | 83 | Vector3d freestream_velocity(30.0, 0, 0); 84 | solver.set_freestream_velocity(freestream_velocity); 85 | 86 | double fluid_density = 1.2; 87 | solver.set_fluid_density(fluid_density); 88 | 89 | // Run simulation: 90 | solver.initialize_wakes(); 91 | solver.solve(); 92 | 93 | // Check wake-induced velocities: 94 | Vector3d te_middle = 0.5 * wing->nodes[wing->trailing_edge_node(0)] + 0.5 * wing->nodes[wing->trailing_edge_node(1)]; 95 | double r; 96 | 97 | r = 2.0 * Parameters::wake_vortex_core_radius; 98 | Vector3d far_point(te_middle + (Parameters::static_wake_length + r) * Vector3d(1, 0, 0)); 99 | 100 | Vector3d reference_far_point_velocity(0, -wake->doublet_coefficients[0] / (2 * pi * r), 0); 101 | 102 | Vector3d far_point_velocity = solver.velocity(far_point) - freestream_velocity; 103 | if ((far_point_velocity - reference_far_point_velocity).norm() > TEST_TOLERANCE) { 104 | cerr << " *** FAR POINT TEST FAILED *** " << endl; 105 | cerr << " |V_ref| = " << reference_far_point_velocity.norm() << endl; 106 | cerr << " |V| = " << far_point_velocity.norm() << endl; 107 | cerr << " ******************* " << endl; 108 | 109 | exit(1); 110 | } 111 | 112 | r = 0.5 * Parameters::wake_vortex_core_radius; 113 | Vector3d close_point(te_middle + (Parameters::static_wake_length + r) * Vector3d(1, 0, 0)); 114 | 115 | Vector3d reference_close_point_velocity(0, -wake->doublet_coefficients[0] * r / (2 * pi * pow(Parameters::wake_vortex_core_radius, 2)), 0); 116 | //Vector3d reference_close_point_velocity(0, -wake->doublet_coefficients[0] / (2 * pi * r), 0); 117 | 118 | Vector3d close_point_velocity = solver.velocity(close_point) - freestream_velocity; 119 | if ((close_point_velocity - reference_close_point_velocity).norm() > TEST_TOLERANCE) { 120 | cerr << " *** CLOSE POINT TEST FAILED *** " << endl; 121 | cerr << " |V_ref| = " << reference_close_point_velocity.norm() << endl; 122 | cerr << " |V| = " << close_point_velocity.norm() << endl; 123 | cerr << " ******************* " << endl; 124 | 125 | exit(1); 126 | } 127 | 128 | r = 0.0; 129 | Vector3d locus_point(te_middle + (Parameters::static_wake_length + r) * Vector3d(1, 0, 0)); 130 | 131 | Vector3d reference_locus_point_velocity(0, -wake->doublet_coefficients[0] * r / (2 * pi * pow(Parameters::wake_vortex_core_radius, 2)), 0); 132 | //Vector3d reference_locus_point_velocity(0, -wake->doublet_coefficients[0] / (2 * pi * r), 0); 133 | 134 | Vector3d locus_point_velocity = solver.velocity(locus_point) - freestream_velocity; 135 | if ((locus_point_velocity - reference_locus_point_velocity).norm() > TEST_TOLERANCE) { 136 | cerr << " *** LOCUS POINT TEST FAILED *** " << endl; 137 | cerr << " |V_ref| = " << reference_locus_point_velocity.norm() << endl; 138 | cerr << " |V| = " << locus_point_velocity.norm() << endl; 139 | cerr << " ******************* " << endl; 140 | 141 | exit(1); 142 | } 143 | 144 | // Done: 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /examples/clarky-section/clarky-section.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Clark-Y wing section construction example. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace Eigen; 20 | using namespace Vortexje; 21 | 22 | static const double pi = 3.141592653589793238462643383279502884; 23 | 24 | // Load airfoil data from file: 25 | static vector > 26 | read_airfoil(const std::string &filename, int &trailing_edge_point_id) 27 | { 28 | vector > upper_points; 29 | vector > lower_points; 30 | 31 | // Parse file: 32 | ifstream f; 33 | f.open(filename.c_str()); 34 | 35 | int side = -1; 36 | 37 | while (f.good()) { 38 | string line; 39 | getline(f, line); 40 | 41 | // Empty line signifies new section: 42 | bool empty_line = false; 43 | if (line.find_first_not_of("\r\n\t ") == string::npos) 44 | empty_line = true; 45 | 46 | if (empty_line) { 47 | side++; 48 | continue; 49 | } 50 | 51 | // Read x, y coordinates: 52 | if (side >= 0) { 53 | istringstream tokens(line); 54 | 55 | double x, y; 56 | tokens >> x >> y; 57 | 58 | Vector3d point(x, y, 0.0); 59 | if (side == 0) 60 | upper_points.push_back(point); 61 | else 62 | lower_points.push_back(point); 63 | } 64 | } 65 | 66 | // Close file: 67 | f.close(); 68 | 69 | // Assemble entire airfoil: 70 | vector > points; 71 | 72 | for (int i = 0; i < (int) upper_points.size() - 1; i++) 73 | points.push_back(upper_points[i]); 74 | 75 | // Use a thin trailing edge: 76 | Vector3d trailing_edge_point = 0.5 * (upper_points[(int) upper_points.size() - 1] + lower_points[(int) lower_points.size() - 1]); 77 | points.push_back(trailing_edge_point); 78 | trailing_edge_point_id = (int) points.size() - 1; 79 | 80 | for (int i = (int) lower_points.size() - 2; i > 0; i--) 81 | points.push_back(lower_points[i]); 82 | 83 | // Done: 84 | return points; 85 | } 86 | 87 | // Main: 88 | int 89 | main(int argc, char **argv) 90 | { 91 | // Enable wake convection: 92 | Parameters::convect_wake = true; 93 | 94 | // Load airfoil data: 95 | int trailing_edge_point_id; 96 | vector > clarky_airfoil = read_airfoil("clarky.dat", trailing_edge_point_id); 97 | 98 | // Create lifting surface object: 99 | shared_ptr wing(new LiftingSurface("main")); 100 | 101 | // Construct wing section: 102 | LiftingSurfaceBuilder surface_builder(*wing); 103 | 104 | const int n_airfoils = 21; 105 | 106 | const double chord = 1.0; 107 | const double span = 5.0; 108 | 109 | vector prev_airfoil_nodes; 110 | 111 | vector > node_strips; 112 | vector > panel_strips; 113 | 114 | for (int i = 0; i < n_airfoils; i++) { 115 | vector > airfoil_points; 116 | for (int j = 0; j < (int) clarky_airfoil.size(); j++) 117 | airfoil_points.push_back(Vector3d(chord * clarky_airfoil[j](0), chord * clarky_airfoil[j](1), i * span / (double) (n_airfoils - 1))); 118 | 119 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 120 | node_strips.push_back(airfoil_nodes); 121 | 122 | if (i > 0) { 123 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 124 | panel_strips.push_back(airfoil_panels); 125 | } 126 | 127 | prev_airfoil_nodes = airfoil_nodes; 128 | } 129 | 130 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 131 | 132 | // Translate into the canonical coordinate system: 133 | Vector3d translation(-chord / 3.0, 0.0, -span / 2.0); 134 | wing->translate(translation); 135 | 136 | // Prescribe angle of attack: 137 | double alpha = 5.0 / 180.0 * pi; 138 | wing->rotate(Vector3d::UnitZ(), -alpha); 139 | 140 | // Create surface body: 141 | shared_ptr body(new Body(string("wing-section"))); 142 | body->add_lifting_surface(wing); 143 | 144 | // Set up solver: 145 | Solver solver("clarky-section-log"); 146 | solver.add_body(body); 147 | 148 | Vector3d freestream_velocity(30, 0, 0); 149 | solver.set_freestream_velocity(freestream_velocity); 150 | 151 | double fluid_density = 1.2; 152 | solver.set_fluid_density(fluid_density); 153 | 154 | // Set up surface writer: 155 | VTKSurfaceWriter surface_writer; 156 | 157 | // Set up field writer: 158 | VTKFieldWriter field_writer; 159 | 160 | // Run simulation: 161 | double t = 0.0; 162 | double dt = 0.01; 163 | int step_number = 0; 164 | 165 | solver.initialize_wakes(dt); 166 | while (t < 60) { 167 | // Solve: 168 | solver.solve(dt); 169 | 170 | // Log coefficients: 171 | solver.log(step_number, surface_writer); 172 | 173 | // Enable below to log the velocity field: 174 | //field_writer.write_velocity_field(solver, "velocity-field.vtk", -0.5, 2.0, -3.0, 3.0, -1.0, 1.0, 0.2, 0.2, 0.2); 175 | 176 | // Update wake: 177 | solver.update_wakes(dt); 178 | 179 | // Step time: 180 | t += dt; 181 | step_number++; 182 | } 183 | 184 | // Done: 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /tests/elliptic-planform/test-elliptic-planform.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Elliptic wing with NACA0012 airfoil. Checks induced drag. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace Eigen; 20 | using namespace Vortexje; 21 | 22 | static const double pi = 3.141592653589793238462643383279502884; 23 | 24 | #define DELTA_CONVERGENCE 1e-1 25 | #define DELTA_T 1e-2 26 | 27 | #define C_D_TEST_TOLERANCE 1e-3 28 | 29 | //#define OUTPUT_RESULTS 30 | 31 | #ifdef OUTPUT_RESULTS 32 | static ofstream f; 33 | #endif 34 | 35 | // Cosine rule: 36 | static double 37 | cosine_rule(int n_points, int i) 38 | { 39 | return 0.5 * (1 - cos(pi * i / (double) n_points)); 40 | } 41 | 42 | // Run a test for a single angle of attack: 43 | bool 44 | run_test(double alpha) 45 | { 46 | // Set up parameters for unsteady simulation: 47 | Parameters::unsteady_bernoulli = true; 48 | Parameters::convect_wake = true; 49 | 50 | // Create wing: 51 | shared_ptr wing(new LiftingSurface("main")); 52 | 53 | LiftingSurfaceBuilder surface_builder(*wing); 54 | 55 | const double AR = 6.0; 56 | const double chord = 0.5; 57 | const double span = AR * chord; 58 | 59 | const int n_points_per_airfoil = 40; 60 | const int n_airfoils = AR * n_points_per_airfoil / 2; 61 | 62 | int trailing_edge_point_id; 63 | vector prev_airfoil_nodes; 64 | 65 | vector > node_strips; 66 | vector > panel_strips; 67 | 68 | for (int i = 0; i < n_airfoils; i++) { 69 | double y = -span / 2.0 + span * cosine_rule(n_airfoils - 1, i); 70 | double chord_y = chord * sqrt(1.0 - pow(2.0 * y / span, 2)); 71 | double offset_x = (chord - chord_y) / 2.0; 72 | 73 | vector > airfoil_points; 74 | if (i == 0 || i == n_airfoils - 1) { 75 | Vector3d tip_point(0.0, 0.0, 0.0); 76 | for (int j = 0; j < n_points_per_airfoil; j++) 77 | airfoil_points.push_back(tip_point); 78 | 79 | } else 80 | airfoil_points = NACA4AirfoilGenerator::generate(0, 0, 0.12, true, chord_y, n_points_per_airfoil, trailing_edge_point_id); 81 | 82 | for (int j = 0; j < (int) airfoil_points.size(); j++) { 83 | airfoil_points[j](0) += offset_x; 84 | airfoil_points[j](2) += y; 85 | } 86 | 87 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 88 | node_strips.push_back(airfoil_nodes); 89 | 90 | if (i > 0) { 91 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 92 | panel_strips.push_back(airfoil_panels); 93 | } 94 | 95 | prev_airfoil_nodes = airfoil_nodes; 96 | } 97 | 98 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 99 | 100 | // Rotate by angle of attack: 101 | wing->rotate(Vector3d::UnitZ(), -alpha); 102 | 103 | // Create body: 104 | shared_ptr body(new Body(string("elliptic-wing"))); 105 | body->add_lifting_surface(wing); 106 | 107 | // Set up solver: 108 | Solver solver("test-elliptic-planform-log"); 109 | solver.add_body(body); 110 | 111 | Vector3d freestream_velocity(30, 0, 0); 112 | solver.set_freestream_velocity(freestream_velocity); 113 | 114 | double fluid_density = 1.2; 115 | solver.set_fluid_density(fluid_density); 116 | 117 | // Run simulation: 118 | double t = 0.0; 119 | double dt = DELTA_T; 120 | int step_number = 0; 121 | 122 | solver.initialize_wakes(dt); 123 | 124 | // Iterate until converence: 125 | Vector3d F_prev(0, 0, 0); 126 | while (true) { 127 | // Solve: 128 | solver.solve(dt); 129 | 130 | // Compute force: 131 | Vector3d F = solver.force(body); 132 | 133 | // Check convergence: 134 | double delta = (F - F_prev).norm(); 135 | cout << "Force delta = " << delta << " N" << endl; 136 | if (delta < DELTA_CONVERGENCE) 137 | break; 138 | F_prev = F; 139 | 140 | // Update wakes: 141 | solver.update_wakes(dt); 142 | 143 | // Step time: 144 | t += dt; 145 | step_number++; 146 | } 147 | 148 | // Compute lift and drag coefficients: 149 | Vector3d F_a = solver.force(body); 150 | 151 | double q = 0.5 * fluid_density * freestream_velocity.squaredNorm(); 152 | 153 | double S = pi * chord * span / 4.0; 154 | 155 | double C_L = F_a(1) / (q * S); 156 | double C_D = F_a(0) / (q * S); 157 | 158 | double C_D_ref = 1.0 / pi * S / pow(span, 2) * pow(C_L, 2); 159 | 160 | // Log lift and drag coefficients: 161 | #ifdef OUTPUT_RESULTS 162 | f << C_L << ' ' << C_D << ' ' << C_D_ref << endl; 163 | #endif 164 | 165 | // Compare induced drag coefficients: 166 | if (fabs(C_D - C_D_ref) > C_D_TEST_TOLERANCE) { 167 | cerr << " *** TEST FAILED *** " << endl; 168 | cerr << " C_D(ref) = " << C_D_ref << endl; 169 | cerr << " C_D = " << C_D << endl; 170 | cerr << " ******************* " << endl; 171 | 172 | return false; 173 | } 174 | 175 | // Done. 176 | return true; 177 | } 178 | 179 | int 180 | main (int argc, char **argv) 181 | { 182 | double alpha_min = 0.0; 183 | double alpha_max = 8.0; 184 | double dalpha = 1.0; 185 | 186 | #ifdef OUTPUT_RESULTS 187 | f.open("test-elliptic-planform.txt"); 188 | #endif 189 | 190 | double alpha = alpha_min; 191 | while (alpha <= alpha_max) { 192 | if (!run_test(alpha / 180.0 * pi)) 193 | exit(1); 194 | 195 | alpha += dalpha; 196 | } 197 | 198 | #ifdef OUTPUT_RESULTS 199 | f.close(); 200 | #endif 201 | 202 | return 0; 203 | } 204 | -------------------------------------------------------------------------------- /vortexje/wake.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Wake 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace std; 14 | using namespace Eigen; 15 | using namespace Vortexje; 16 | 17 | static const double pi = 3.141592653589793238462643383279502884; 18 | 19 | // Avoid having to divide by 4 pi all the time: 20 | static const double one_over_4pi = 1.0 / (4 * pi); 21 | 22 | /** 23 | Constructs an empty wake. 24 | 25 | @param[in] lifting_surface Associated lifting surface. 26 | */ 27 | Wake::Wake(shared_ptr lifting_surface) 28 | : Surface(lifting_surface->id + string("_wake")), lifting_surface(lifting_surface) 29 | { 30 | } 31 | 32 | /** 33 | Adds new layer of wake panels. 34 | */ 35 | void 36 | Wake::add_layer() 37 | { 38 | // Is this the first layer? 39 | bool first_layer; 40 | if (n_nodes() < lifting_surface->n_spanwise_nodes()) 41 | first_layer = true; 42 | else 43 | first_layer = false; 44 | 45 | // Add layer of nodes at trailing edge, and add panels if necessary: 46 | for (int k = 0; k < lifting_surface->n_spanwise_nodes(); k++) { 47 | Vector3d new_point = lifting_surface->nodes[lifting_surface->trailing_edge_node(k)]; 48 | 49 | int node = n_nodes(); 50 | nodes.push_back(new_point); 51 | 52 | if (k > 0 && !first_layer) { 53 | vector vertices; 54 | vertices.push_back(node - 1); 55 | vertices.push_back(node - 1 - lifting_surface->n_spanwise_nodes()); 56 | vertices.push_back(node - lifting_surface->n_spanwise_nodes()); 57 | vertices.push_back(node); 58 | 59 | int panel = n_panels(); 60 | panel_nodes.push_back(vertices); 61 | 62 | vector > > local_panel_neighbors; 63 | local_panel_neighbors.resize(vertices.size()); 64 | panel_neighbors.push_back(local_panel_neighbors); 65 | 66 | shared_ptr > empty = make_shared >(); 67 | node_panel_neighbors.push_back(empty); 68 | 69 | doublet_coefficients.push_back(0); 70 | 71 | compute_geometry(panel); 72 | 73 | } else { 74 | shared_ptr > empty = make_shared >(); 75 | node_panel_neighbors.push_back(empty); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | Translates the nodes of the trailing edge. 82 | 83 | @param[in] translation Translation vector. 84 | */ 85 | void 86 | Wake::translate_trailing_edge(const Eigen::Vector3d &translation) 87 | { 88 | if (n_nodes() < lifting_surface->n_spanwise_nodes()) 89 | return; 90 | 91 | int k0; 92 | 93 | if (Parameters::convect_wake) 94 | k0 = n_nodes() - lifting_surface->n_spanwise_nodes(); 95 | else 96 | k0 = 0; 97 | 98 | for (int k = k0; k < n_nodes(); k++) 99 | nodes[k] += translation; 100 | 101 | if (Parameters::convect_wake) 102 | k0 = n_panels() - lifting_surface->n_spanwise_panels(); 103 | else 104 | k0 = 0; 105 | 106 | for (int k = k0; k < n_panels(); k++) 107 | compute_geometry(k); 108 | } 109 | 110 | /** 111 | Transforms the nodes of the trailing edge. 112 | 113 | @param[in] transformation Affine transformation. 114 | */ 115 | void 116 | Wake::transform_trailing_edge(const Eigen::Transform &transformation) 117 | { 118 | if (n_nodes() < lifting_surface->n_spanwise_nodes()) 119 | return; 120 | 121 | int k0; 122 | 123 | if (Parameters::convect_wake) 124 | k0 = n_nodes() - lifting_surface->n_spanwise_nodes(); 125 | else 126 | k0 = 0; 127 | 128 | for (int k = k0; k < n_nodes(); k++) 129 | nodes[k] = transformation * nodes[k]; 130 | 131 | if (Parameters::convect_wake) 132 | k0 = n_panels() - lifting_surface->n_spanwise_panels(); 133 | else 134 | k0 = 0; 135 | 136 | for (int k = k0; k < n_panels(); k++) 137 | compute_geometry(k); 138 | } 139 | 140 | /** 141 | Updates any non-geometrical wake properties. This method does nothing by default. 142 | 143 | @param[in] dt Time step size. 144 | */ 145 | void 146 | Wake::update_properties(double dt) 147 | { 148 | } 149 | 150 | /** 151 | Computes the velocity induced by a vortex ring of unit strength. 152 | 153 | @param[in] x Point at which the velocity is evaluated. 154 | @param[in] this_panel Panel on which the vortex ring is located. 155 | 156 | @returns Velocity induced by the vortex ring. 157 | */ 158 | Vector3d 159 | Wake::vortex_ring_unit_velocity(const Eigen::Vector3d &x, int this_panel) const 160 | { 161 | Vector3d velocity(0, 0, 0); 162 | 163 | for (int i = 0; i < (int) panel_nodes[this_panel].size(); i++) { 164 | int previous_idx; 165 | if (i == 0) 166 | previous_idx = panel_nodes[this_panel].size() - 1; 167 | else 168 | previous_idx = i - 1; 169 | 170 | const Vector3d &node_a = nodes[panel_nodes[this_panel][previous_idx]]; 171 | const Vector3d &node_b = nodes[panel_nodes[this_panel][i]]; 172 | 173 | Vector3d r_0 = node_b - node_a; 174 | Vector3d r_1 = node_a - x; 175 | Vector3d r_2 = node_b - x; 176 | 177 | double r_0_norm = r_0.norm(); 178 | double r_1_norm = r_1.norm(); 179 | double r_2_norm = r_2.norm(); 180 | 181 | Vector3d r_1xr_2 = r_1.cross(r_2); 182 | double r_1xr_2_sqnorm = r_1xr_2.squaredNorm(); 183 | 184 | if (r_0_norm < Parameters::zero_threshold || 185 | r_1_norm < Parameters::zero_threshold || 186 | r_2_norm < Parameters::zero_threshold || 187 | r_1xr_2_sqnorm < Parameters::zero_threshold) 188 | continue; 189 | 190 | double r = sqrt(r_1xr_2_sqnorm) / r_0_norm; 191 | if (r < Parameters::wake_vortex_core_radius) { 192 | // Rankine vortex core segment: 193 | velocity += r_1xr_2 / (r_0_norm * pow(Parameters::wake_vortex_core_radius, 2)) 194 | * (r_0 / r_0_norm).dot(r_1 / r_1_norm - r_2 / r_2_norm); 195 | 196 | } else { 197 | // Free vortex segment: 198 | velocity += r_1xr_2 / r_1xr_2_sqnorm * r_0.dot(r_1 / r_1_norm - r_2 / r_2_norm); 199 | 200 | } 201 | } 202 | 203 | return one_over_4pi * velocity; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /vortexje/solver.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Solver. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __SOLVER_HPP__ 10 | #define __SOLVER_HPP__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace Vortexje 25 | { 26 | 27 | /** 28 | Class for solution of the panel method equations, and their propagation in time. 29 | 30 | @brief Panel method solver. 31 | */ 32 | class Solver 33 | { 34 | public: 35 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 36 | 37 | Solver(const std::string &log_folder); 38 | 39 | ~Solver(); 40 | 41 | /** 42 | Data structure containing a body and its boundary layer. 43 | 44 | @brief Body data. 45 | */ 46 | class BodyData { 47 | public: 48 | /** 49 | Constructs a BodyData object. 50 | 51 | @param[in] body Body. 52 | @param[in] boundary_layer Boundary layer model. 53 | */ 54 | BodyData(std::shared_ptr body, std::shared_ptr boundary_layer) : 55 | body(body), boundary_layer(boundary_layer) {} 56 | 57 | /** 58 | Associated body object. 59 | */ 60 | std::shared_ptr body; 61 | 62 | /** 63 | Associated boundary layer model. 64 | */ 65 | std::shared_ptr boundary_layer; 66 | }; 67 | 68 | /** 69 | List of surface bodies. 70 | */ 71 | std::vector > bodies; 72 | 73 | void add_body(std::shared_ptr body); 74 | 75 | void add_body(std::shared_ptr body, std::shared_ptr boundary_layer); 76 | 77 | /** 78 | Freestream velocity. 79 | */ 80 | Eigen::Vector3d freestream_velocity; 81 | 82 | void set_freestream_velocity(const Eigen::Vector3d &value); 83 | 84 | /** 85 | Density of the fluid. 86 | */ 87 | double fluid_density; 88 | 89 | void set_fluid_density(double value); 90 | 91 | void initialize_wakes(double dt = 0.0); 92 | 93 | void update_wakes(double dt = 0.0); 94 | 95 | bool solve(double dt = 0.0, bool propagate = true); 96 | 97 | void propagate(); 98 | 99 | double velocity_potential(const Eigen::Vector3d &x) const; 100 | 101 | Eigen::Vector3d velocity(const Eigen::Vector3d &x) const; 102 | 103 | double surface_velocity_potential(const std::shared_ptr &surface, int panel) const; 104 | 105 | Eigen::Vector3d surface_velocity(const std::shared_ptr &surface, int panel) const; 106 | 107 | double pressure_coefficient(const std::shared_ptr &surface, int panel) const; 108 | 109 | Eigen::Vector3d force(const std::shared_ptr &body) const; 110 | Eigen::Vector3d force(const std::shared_ptr &surface) const; 111 | 112 | Eigen::Vector3d moment(const std::shared_ptr &body, const Eigen::Vector3d &x) const; 113 | Eigen::Vector3d moment(const std::shared_ptr &surface, const Eigen::Vector3d &x) const; 114 | 115 | /** 116 | Data structure bundling a Surface, a panel ID, and a point on the panel. 117 | 118 | @brief Surface, panel ID, and point bundle. 119 | */ 120 | class SurfacePanelPoint { 121 | public: 122 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 123 | 124 | /** 125 | Constructor. 126 | 127 | @param[in] surface Associated Surface object. 128 | @param[in] panel Panel ID. 129 | @param[in] point Point. 130 | */ 131 | SurfacePanelPoint(std::shared_ptr surface, int panel, const Eigen::Vector3d &point) 132 | : surface(surface), panel(panel), point(point) {}; 133 | 134 | /** 135 | Associated Surface object. 136 | */ 137 | std::shared_ptr surface; 138 | 139 | /** 140 | Panel ID. 141 | */ 142 | int panel; 143 | 144 | /** 145 | Point. 146 | */ 147 | Eigen::Vector3d point; 148 | }; 149 | 150 | std::vector > trace_streamline(const SurfacePanelPoint &start) const; 151 | 152 | void log(int step_number, SurfaceWriter &writer) const; 153 | 154 | private: 155 | std::string log_folder; 156 | 157 | std::vector > non_wake_surfaces; 158 | int n_non_wake_panels; 159 | 160 | std::map, std::shared_ptr > surface_to_body; 161 | 162 | Eigen::VectorXd source_coefficients; 163 | Eigen::VectorXd doublet_coefficients; 164 | 165 | Eigen::VectorXd surface_velocity_potentials; 166 | Eigen::MatrixXd surface_velocities; 167 | Eigen::VectorXd pressure_coefficients; 168 | 169 | Eigen::VectorXd previous_surface_velocity_potentials; 170 | 171 | double compute_source_coefficient(const std::shared_ptr &body, const std::shared_ptr &surface, int panel, 172 | const std::shared_ptr &boundary_layer, bool include_wake_influence) const; 173 | 174 | double compute_surface_velocity_potential(const std::shared_ptr &surface, int offset, int panel) const; 175 | 176 | double compute_surface_velocity_potential_time_derivative(int offset, int panel, double dt) const; 177 | 178 | Eigen::Vector3d compute_surface_velocity(const std::shared_ptr &body, const std::shared_ptr &surface, int panel) const; 179 | 180 | double compute_reference_velocity_squared(const std::shared_ptr &body) const; 181 | 182 | double compute_pressure_coefficient(const Eigen::Vector3d &surface_velocity, double dphidt, double v_ref) const; 183 | 184 | Eigen::Vector3d compute_velocity_interpolated(const Eigen::Vector3d &x, std::set &ignore_set) const; 185 | 186 | Eigen::Vector3d compute_velocity(const Eigen::Vector3d &x) const; 187 | 188 | double compute_velocity_potential(const Eigen::Vector3d &x) const; 189 | 190 | Eigen::Vector3d compute_trailing_edge_vortex_displacement(const std::shared_ptr &body, const std::shared_ptr &lifting_surface, int index, double dt) const; 191 | 192 | Eigen::Vector3d compute_scalar_field_gradient(const Eigen::VectorXd &scalar_field, const std::shared_ptr &body, const std::shared_ptr &surface, int panel) const; 193 | 194 | int compute_index(const std::shared_ptr &surface, int panel) const; 195 | }; 196 | 197 | }; 198 | 199 | #endif // __SOLVER_HPP__ 200 | -------------------------------------------------------------------------------- /vortexje/lifting-surface.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Lifting surface. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using namespace Eigen; 14 | using namespace Vortexje; 15 | 16 | /** 17 | Constructs an empty LiftingSurface. 18 | */ 19 | LiftingSurface::LiftingSurface(const string &id) : Surface(id) 20 | { 21 | // Do nothing. 22 | } 23 | 24 | /** 25 | Returns the number of chordwise nodes. 26 | 27 | @returns The number of chordwise nodes. 28 | */ 29 | int 30 | LiftingSurface::n_chordwise_nodes() const 31 | { 32 | return (int) upper_nodes.rows(); 33 | } 34 | 35 | /** 36 | Returns the number of chordwise panels. 37 | 38 | @returns The number of chordwise panels. 39 | */ 40 | int 41 | LiftingSurface::n_chordwise_panels() const 42 | { 43 | return (int) upper_panels.rows(); 44 | } 45 | 46 | /** 47 | Returns the number of spanwise nodes. 48 | 49 | @returns The number of spanwise nodes. 50 | */ 51 | int 52 | LiftingSurface::n_spanwise_nodes() const 53 | { 54 | return (int) upper_nodes.cols(); 55 | } 56 | 57 | /** 58 | Returns the number of spanwise panels. 59 | 60 | @returns The number of spanwise panels. 61 | */ 62 | int 63 | LiftingSurface::n_spanwise_panels() const 64 | { 65 | return (int) upper_panels.cols(); 66 | } 67 | 68 | /** 69 | Returns the index'th trailing edge node. 70 | 71 | @param[in] index Trailing edge node index. 72 | 73 | @returns The node number of the index'th trailing edge node. 74 | */ 75 | int 76 | LiftingSurface::trailing_edge_node(int index) const 77 | { 78 | return upper_nodes(upper_nodes.rows() - 1, index); 79 | } 80 | 81 | /** 82 | Returns the index'th upper trailing edge panel. 83 | 84 | @param[in] index Trailing edge panel index. 85 | 86 | @returns The panel number of the index'th upper trailing edge panel. 87 | */ 88 | int 89 | LiftingSurface::trailing_edge_upper_panel(int index) const 90 | { 91 | return upper_panels(upper_panels.rows() - 1, index); 92 | } 93 | 94 | /** 95 | Returns the index'th lower trailing edge panel. 96 | 97 | @param[in] index Trailing edge panel index. 98 | 99 | @returns The panel number of the index'th lower trailing edge panel. 100 | */ 101 | int 102 | LiftingSurface::trailing_edge_lower_panel(int index) const 103 | { 104 | return lower_panels(lower_panels.rows() - 1, index); 105 | } 106 | 107 | /** 108 | Finishes the set up of the trailing edge. 109 | 110 | This function computes the trailing edge bisectors, the initial wake strip normals, and terminates the neigbor relationships 111 | along the trailing edge. 112 | */ 113 | void 114 | LiftingSurface::finish_trailing_edge() 115 | { 116 | // Compute trailing edge bisectors and normals to the initial wake strip surface: 117 | trailing_edge_bisectors.resize(n_spanwise_nodes(), 3); 118 | wake_normals.resize(n_spanwise_nodes(), 3); 119 | 120 | if (n_chordwise_nodes() > 1) { 121 | for (int i = 0; i < n_spanwise_nodes(); i++) { 122 | // Compute bisector: 123 | Vector3d upper = nodes[upper_nodes(upper_nodes.rows() - 1, i)] - nodes[upper_nodes(upper_nodes.rows() - 2, i)]; 124 | Vector3d lower = nodes[lower_nodes(lower_nodes.rows() - 1, i)] - nodes[lower_nodes(lower_nodes.rows() - 2, i)]; 125 | 126 | upper.normalize(); 127 | lower.normalize(); 128 | 129 | Vector3d trailing_edge_bisector = upper + lower; 130 | trailing_edge_bisector.normalize(); 131 | 132 | trailing_edge_bisectors.row(i) = trailing_edge_bisector; 133 | 134 | // Compute normal to the initial wake strip surface, spanned by the bisector and by the span direction: 135 | int prev_node, next_node; 136 | 137 | if (i > 0) 138 | prev_node = trailing_edge_node(i - 1); 139 | else 140 | prev_node = trailing_edge_node(i); 141 | 142 | if (i < n_spanwise_nodes() - 1) 143 | next_node = trailing_edge_node(i + 1); 144 | else 145 | next_node = trailing_edge_node(i); 146 | 147 | Vector3d wake_normal(0, 0, 0); 148 | 149 | if (prev_node != next_node) { 150 | Vector3d span_direction = nodes[next_node] - nodes[prev_node]; 151 | 152 | wake_normal = span_direction.cross(trailing_edge_bisector); 153 | wake_normal.normalize(); 154 | } 155 | 156 | wake_normals.row(i) = wake_normal; 157 | } 158 | 159 | } else { 160 | // No bisector information available: 161 | trailing_edge_bisectors.setZero(); 162 | wake_normals.setZero(); 163 | } 164 | 165 | // Terminate neighbor relationships across trailing edge. 166 | for (int i = 0; i < n_spanwise_panels(); i++) 167 | cut_panels(trailing_edge_upper_panel(i), trailing_edge_lower_panel(i)); 168 | } 169 | 170 | /** 171 | Transforms this lifting surface. 172 | 173 | @param[in] transformation Affine transformation. 174 | */ 175 | void 176 | LiftingSurface::transform(const Eigen::Transform &transformation) 177 | { 178 | // Call super: 179 | this->Surface::transform(transformation); 180 | 181 | // Transform bisectors and wake normals: 182 | for (int i = 0; i < n_spanwise_nodes(); i++) { 183 | Vector3d trailing_edge_bisector = trailing_edge_bisectors.row(i); 184 | trailing_edge_bisectors.row(i) = transformation.linear() * trailing_edge_bisector; 185 | 186 | Vector3d wake_normal = wake_normals.row(i); 187 | wake_normals.row(i) = transformation.linear() * wake_normal; 188 | } 189 | } 190 | 191 | /** 192 | Returns the wake emission velocity, at the node_index'th trailing edge node. 193 | 194 | @param[in] apparent_velocity Apparent velocity. 195 | @param[in] node_index Trailing edge node index. 196 | 197 | @returns The wake emission velocity, at the node_index'th trailinge edge node. 198 | */ 199 | Eigen::Vector3d 200 | LiftingSurface::wake_emission_velocity(const Eigen::Vector3d &apparent_velocity, int node_index) const 201 | { 202 | Vector3d wake_emission_velocity; 203 | 204 | if (Parameters::wake_emission_follow_bisector) { 205 | Vector3d wake_normal = wake_normals.row(node_index); 206 | 207 | // Project apparent velocity onto wake emission plane: 208 | wake_emission_velocity = -(apparent_velocity - apparent_velocity.dot(wake_normal) * wake_normal); 209 | 210 | } else { 211 | // Emit wake in direction of apparent velocity: 212 | wake_emission_velocity = -apparent_velocity; 213 | 214 | } 215 | 216 | // Done: 217 | return wake_emission_velocity; 218 | } 219 | -------------------------------------------------------------------------------- /vortexje/body.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Body. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #ifndef __BODY_HPP__ 10 | #define __BODY_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace Vortexje 24 | { 25 | 26 | /** 27 | Container for surfaces. 28 | 29 | This class is designed for the application of kinematic operations to a body consisting of multiple surfaces. 30 | A typical application would be an airplane consisting of a fuselage and lifting surfaces. 31 | 32 | @brief Surface container. 33 | */ 34 | class Body 35 | { 36 | public: 37 | /** 38 | Body name. 39 | */ 40 | std::string id; 41 | 42 | /** 43 | Data structure containing a non-lifting surface. 44 | 45 | @brief Surface data. 46 | */ 47 | class SurfaceData { 48 | public: 49 | /** 50 | Constructs a SurfaceData object. 51 | 52 | @param[in] surface Surface object. 53 | */ 54 | SurfaceData(std::shared_ptr surface) : 55 | surface(surface) {} 56 | 57 | /** 58 | Associated surface object. 59 | */ 60 | std::shared_ptr surface; 61 | }; 62 | 63 | /** 64 | Data structure grouping a lifting surface with its wake. 65 | 66 | @brief Lifting surface data. 67 | */ 68 | class LiftingSurfaceData : public SurfaceData { 69 | public: 70 | /** 71 | Constructs a LiftingSurfaceData object. 72 | 73 | @param[in] lifting_surface Lifting surface object. 74 | @param[in] wake Wake object for this surface. 75 | */ 76 | LiftingSurfaceData(std::shared_ptr lifting_surface, std::shared_ptr wake) : 77 | SurfaceData(lifting_surface), lifting_surface(lifting_surface), wake(wake) { } 78 | 79 | /** 80 | Associated lifting surface object. 81 | */ 82 | std::shared_ptr lifting_surface; 83 | 84 | /** 85 | Associated wake object. 86 | */ 87 | std::shared_ptr wake; 88 | }; 89 | 90 | /** 91 | List of non-lifting surfaces. 92 | */ 93 | std::vector > non_lifting_surfaces; 94 | 95 | /** 96 | List of lifting surfaces. 97 | */ 98 | std::vector > lifting_surfaces; 99 | 100 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 101 | 102 | Body(const std::string &id); 103 | 104 | ~Body(); 105 | 106 | void add_non_lifting_surface(std::shared_ptr surface); 107 | 108 | void add_lifting_surface(std::shared_ptr lifting_surface); 109 | void add_lifting_surface(std::shared_ptr lifting_surface, std::shared_ptr wake); 110 | 111 | /** 112 | Stitching of neighbour relationships between surfaces. 113 | */ 114 | 115 | /** 116 | Data structure bundling a Surface, a panel ID, and an edge number. 117 | 118 | @brief Surface, panel ID, and edge bundle. 119 | */ 120 | class SurfacePanelEdge { 121 | public: 122 | /** 123 | Constructor. 124 | */ 125 | SurfacePanelEdge() {} 126 | 127 | /** 128 | Constructor. 129 | 130 | @param[in] surface Associated Surface object. 131 | @param[in] panel Panel ID. 132 | @param[in] edge Edge number. 133 | */ 134 | SurfacePanelEdge(std::shared_ptr surface, int panel, int edge) : surface(surface), panel(panel), edge(edge) { }; 135 | 136 | /** 137 | Associated Surface object. 138 | */ 139 | std::shared_ptr surface; 140 | 141 | /** 142 | Panel ID. 143 | */ 144 | int panel; 145 | 146 | /** 147 | Edge ID. 148 | */ 149 | int edge; 150 | 151 | /** 152 | Equality operator. 153 | */ 154 | bool operator==(const SurfacePanelEdge &other) const 155 | { 156 | return (surface.get() == other.surface.get()) && (panel == other.panel) && (edge == other.edge); 157 | } 158 | }; 159 | 160 | void stitch_panels(std::shared_ptr surface_a, int panel_a, int edge_a, std::shared_ptr surface_b, int panel_b, int edge_b); 161 | 162 | std::vector panel_neighbors(const std::shared_ptr &surface, int panel) const; 163 | 164 | std::vector panel_neighbors(const std::shared_ptr &surface, int panel, int edge) const; 165 | 166 | /** 167 | Linear position of the entire body. 168 | */ 169 | Eigen::Vector3d position; 170 | 171 | /** 172 | Linear velocity of the entire body. 173 | */ 174 | Eigen::Vector3d velocity; 175 | 176 | /** 177 | Attitude (orientation) of the entire body. 178 | */ 179 | Eigen::Quaterniond attitude; 180 | 181 | /** 182 | Rotational velocity of the entire body. 183 | */ 184 | Eigen::Vector3d rotational_velocity; 185 | 186 | void set_position(const Eigen::Vector3d &position); 187 | void set_attitude(const Eigen::Quaterniond &attitude); 188 | 189 | void set_velocity(const Eigen::Vector3d &velocity); 190 | void set_rotational_velocity(const Eigen::Vector3d &rotational_velocity); 191 | 192 | Eigen::Vector3d panel_kinematic_velocity(const std::shared_ptr &surface, int panel) const; 193 | 194 | Eigen::Vector3d node_kinematic_velocity(const std::shared_ptr &surface, int node) const; 195 | 196 | protected: 197 | /** 198 | Helper class to compare two SurfacePanelEdge objects: first by surface, then by panel. 199 | The edge numbers are not compared. 200 | 201 | @brief Helper class to compare two SurfacePanelEdge objects. 202 | */ 203 | class CompareSurfacePanelEdge { 204 | public: 205 | /** 206 | Compare two SurfacePanelEdge objects: first by surface, then by panel. 207 | 208 | @param[in] a First SurfacePanelEdge. 209 | @param[in] b Second SurfacePanelEdge. 210 | */ 211 | bool operator() (const SurfacePanelEdge a, const SurfacePanelEdge b) const { 212 | if (a.surface->id == b.surface->id) 213 | if (a.panel == b.panel) 214 | return (a.edge < b.edge); 215 | else 216 | return (a.panel < b.panel); 217 | else 218 | return (a.surface->id < b.surface->id); 219 | } 220 | }; 221 | 222 | /** 223 | List of stitches. 224 | */ 225 | std::map, CompareSurfacePanelEdge> stitches; 226 | }; 227 | 228 | }; 229 | 230 | #endif // __BODY_HPP__ 231 | -------------------------------------------------------------------------------- /vortexje/lifting-surface-builder.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Lifting surface builder. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | using namespace std; 12 | using namespace Eigen; 13 | using namespace Vortexje; 14 | 15 | /** 16 | Constructs a new LiftingSurfaceBuilder object for the given LiftingSurface. 17 | 18 | @param[in] lifting_surface LiftingSurface object to construct. 19 | */ 20 | LiftingSurfaceBuilder::LiftingSurfaceBuilder(LiftingSurface &lifting_surface) : SurfaceBuilder(lifting_surface), lifting_surface(lifting_surface) 21 | { 22 | } 23 | 24 | /** 25 | Fills an airfoil with panels. 26 | 27 | @param[in] airfoil_nodes Node numbers tracing an airfoil, starting with the leading edge. 28 | @param[in] trailing_edge_point_id Index of trailing edge node in list of airfoil nodes. 29 | @param[in] z_sign Handedness of the created panels. 30 | 31 | @returns List of new panel numbers. 32 | */ 33 | vector 34 | LiftingSurfaceBuilder::create_panels_inside_airfoil(const vector &airfoil_nodes, int trailing_edge_point_id, int z_sign) 35 | { 36 | vector new_panels; 37 | 38 | // Add middle nodes: 39 | vector upper_nodes; 40 | vector lower_nodes; 41 | vector middle_nodes; 42 | for (int i = 1; i < trailing_edge_point_id; i++) { 43 | int upper_node_id = airfoil_nodes[i]; 44 | int lower_node_id = airfoil_nodes[airfoil_nodes.size() - i]; 45 | 46 | Vector3d upper_point = lifting_surface.nodes[upper_node_id]; 47 | Vector3d lower_point = lifting_surface.nodes[lower_node_id]; 48 | 49 | Vector3d middle_point = 0.5 * (upper_point + lower_point); 50 | 51 | int middle_node_id = lifting_surface.nodes.size(); 52 | 53 | lifting_surface.nodes.push_back(middle_point); 54 | 55 | shared_ptr > empty_vector = make_shared >(); 56 | lifting_surface.node_panel_neighbors.push_back(empty_vector); 57 | 58 | upper_nodes.push_back(upper_node_id); 59 | lower_nodes.push_back(lower_node_id); 60 | middle_nodes.push_back(middle_node_id); 61 | } 62 | 63 | // Close middle part with panels: 64 | vector new_between_panels; 65 | 66 | if (z_sign == 1) { 67 | new_between_panels = create_panels_between_shapes(middle_nodes, upper_nodes, false); 68 | new_panels.insert(new_panels.end(), new_between_panels.begin(), new_between_panels.end()); 69 | 70 | new_between_panels = create_panels_between_shapes(lower_nodes, middle_nodes, false); 71 | new_panels.insert(new_panels.end(), new_between_panels.begin(), new_between_panels.end()); 72 | 73 | } else { 74 | new_between_panels = create_panels_between_shapes(upper_nodes, middle_nodes, false); 75 | new_panels.insert(new_panels.end(), new_between_panels.begin(), new_between_panels.end()); 76 | 77 | new_between_panels = create_panels_between_shapes(middle_nodes, lower_nodes, false); 78 | new_panels.insert(new_panels.end(), new_between_panels.begin(), new_between_panels.end()); 79 | 80 | } 81 | 82 | // Create triangle for leading and trailing edges: 83 | int new_panel; 84 | 85 | if (z_sign == 1) { 86 | new_panel = lifting_surface.add_triangle(airfoil_nodes[0], airfoil_nodes[1], middle_nodes[0]); 87 | new_panels.push_back(new_panel); 88 | 89 | new_panel = lifting_surface.add_triangle(airfoil_nodes[0], middle_nodes[0], airfoil_nodes[airfoil_nodes.size() - 1]); 90 | new_panels.push_back(new_panel); 91 | 92 | new_panel = lifting_surface.add_triangle(middle_nodes[trailing_edge_point_id - 2], airfoil_nodes[trailing_edge_point_id - 1], airfoil_nodes[trailing_edge_point_id]); 93 | new_panels.push_back(new_panel); 94 | 95 | new_panel = lifting_surface.add_triangle(middle_nodes[trailing_edge_point_id - 2], airfoil_nodes[trailing_edge_point_id], airfoil_nodes[trailing_edge_point_id + 1]); 96 | new_panels.push_back(new_panel); 97 | 98 | } else { 99 | new_panel = lifting_surface.add_triangle(airfoil_nodes[0], middle_nodes[0], airfoil_nodes[1]); 100 | new_panels.push_back(new_panel); 101 | 102 | new_panel = lifting_surface.add_triangle(airfoil_nodes[0], airfoil_nodes[airfoil_nodes.size() - 1], middle_nodes[0]); 103 | new_panels.push_back(new_panel); 104 | 105 | new_panel = lifting_surface.add_triangle(middle_nodes[trailing_edge_point_id - 2], airfoil_nodes[trailing_edge_point_id], airfoil_nodes[trailing_edge_point_id - 1]); 106 | new_panels.push_back(new_panel); 107 | 108 | new_panel = lifting_surface.add_triangle(middle_nodes[trailing_edge_point_id - 2], airfoil_nodes[trailing_edge_point_id + 1], airfoil_nodes[trailing_edge_point_id]); 109 | new_panels.push_back(new_panel); 110 | 111 | } 112 | 113 | // Done: 114 | return new_panels; 115 | } 116 | 117 | /** 118 | Finishes the surface construction process by computing the topology as well as various geometrical properties. 119 | 120 | @param[in] node_strips Chordwise strips of nodes, each strip starting with the leading edge. 121 | @param[in] panel_strips Chordwise strips of panels, each strip starting with the leading edge. 122 | @param[in] trailing_edge_point_id Index of trailing edge node in list of airfoil nodes. 123 | */ 124 | void 125 | LiftingSurfaceBuilder::finish(const std::vector > &node_strips, const std::vector > &panel_strips, int trailing_edge_point_id) 126 | { 127 | // Perform basic surface finishing. 128 | this->SurfaceBuilder::finish(); 129 | 130 | // Put together the node matrices. 131 | // We need to apply some trickery to make sure that the leading and trailing edge nodes appear in both matrices. 132 | lifting_surface.upper_nodes.resize(trailing_edge_point_id + 1, node_strips.size()); 133 | lifting_surface.lower_nodes.resize(node_strips[0].size() - trailing_edge_point_id + 1, node_strips.size()); 134 | 135 | for (int i = 0; i <= trailing_edge_point_id; i++) { 136 | for (int j = 0; j < (int) node_strips.size(); j++) { 137 | lifting_surface.upper_nodes(i, j) = node_strips[j][i]; 138 | 139 | if (i == 0) 140 | lifting_surface.lower_nodes(i, j) = node_strips[j][i]; 141 | } 142 | } 143 | 144 | for (int i = trailing_edge_point_id; i < (int) node_strips[0].size(); i++) 145 | for (int j = 0; j < (int) node_strips.size(); j++) 146 | lifting_surface.lower_nodes(node_strips[0].size() - i, j) = node_strips[j][i]; 147 | 148 | // Put together the panel matrices. 149 | lifting_surface.upper_panels.resize(lifting_surface.upper_nodes.rows() - 1, lifting_surface.upper_nodes.cols() - 1); 150 | lifting_surface.lower_panels.resize(lifting_surface.lower_nodes.rows() - 1, lifting_surface.lower_nodes.cols() - 1); 151 | 152 | for (int i = 0; i < (int) lifting_surface.upper_panels.rows(); i++) 153 | for (int j = 0; j < (int) lifting_surface.upper_panels.cols(); j++) 154 | lifting_surface.upper_panels(i, j) = panel_strips[j][i]; 155 | 156 | for (int i = 0; i < (int) lifting_surface.lower_panels.rows(); i++) 157 | for (int j = 0; j < (int) lifting_surface.lower_panels.cols(); j++) 158 | lifting_surface.lower_panels(lifting_surface.lower_panels.rows() - 1 - i, j) = panel_strips[j][lifting_surface.upper_panels.rows() + i]; 159 | 160 | // Finish trailing edge setup. 161 | lifting_surface.finish_trailing_edge(); 162 | } 163 | -------------------------------------------------------------------------------- /vortexje/field-writers/vtk-field-writer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- VTK field writer. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace std; 16 | using namespace Eigen; 17 | using namespace Vortexje; 18 | 19 | /** 20 | Returns the VTK file extension (".vtk"). 21 | 22 | @returns The VTK file extension (".vtk"). 23 | */ 24 | const char * 25 | VTKFieldWriter::file_extension() const 26 | { 27 | return ".vtk"; 28 | } 29 | 30 | /** 31 | Logs the velocity vector field into a VTK file. The grid is the smallest 32 | box encompassing all nodes of all surfaces, expanded in the X, Y, and Z 33 | directions by the given margins. 34 | 35 | @param[in] solver Solver whose state to output. 36 | @param[in] filename Destination filename. 37 | @param[in] x_min Minimum X coordinate of grid. 38 | @param[in] x_max Maximum X coordinate of grid. 39 | @param[in] y_min Minimum Y coordinate of grid. 40 | @param[in] y_max Maximum Y coordinate of grid. 41 | @param[in] z_min Minimum Z coordinate of grid. 42 | @param[in] z_max Maximum Z coordinate of grid. 43 | @param[in] dx Grid step size in X-direction. 44 | @param[in] dy Grid step size in Y-direction. 45 | @param[in] dz Grid step size in Z-direction. 46 | 47 | @returns true on success. 48 | */ 49 | bool 50 | VTKFieldWriter::write_velocity_field(const Solver &solver, const std::string &filename, 51 | double x_min, double x_max, 52 | double y_min, double y_max, 53 | double z_min, double z_max, 54 | double dx, double dy, double dz) 55 | { 56 | int nx = round((x_max - x_min) / dx) + 1; 57 | int ny = round((y_max - y_min) / dy) + 1; 58 | int nz = round((z_max - z_min) / dz) + 1; 59 | 60 | // Compute velocity vector field: 61 | cout << "VTKFieldWriter: Computing velocity vector field." << endl; 62 | 63 | vector > velocities; 64 | velocities.resize(nx * ny * nz); 65 | 66 | int i; 67 | 68 | #pragma omp parallel 69 | { 70 | #pragma omp for schedule(dynamic, 1) 71 | for (i = 0; i < nx * ny * nz; i++) { 72 | double x, y, z; 73 | x = x_min + (i % (nx * ny)) % nx * dx; 74 | y = y_min + (i % (nx * ny)) / nx * dy; 75 | z = z_min + (i / (nx * ny)) * dz; 76 | velocities[i] = solver.velocity(Vector3d(x, y, z)); 77 | } 78 | } 79 | 80 | // Write output in VTK format: 81 | cout << "VTKFieldWriter: Saving velocity vector field to " << filename << "." << endl; 82 | 83 | ofstream f; 84 | f.open(filename.c_str()); 85 | 86 | write_preamble(f, x_min, y_min, z_min, dx, dy, dz, nx, ny, nz); 87 | 88 | // Velocity vector field; 89 | f << "VECTORS Velocity double" << endl; 90 | 91 | vector >::const_iterator it; 92 | for (it = velocities.begin(); it != velocities.end(); it++) { 93 | Vector3d v = *it; 94 | f << v(0) << " " << v(1) << " " << v(2) << endl; 95 | } 96 | 97 | // Close file: 98 | f.close(); 99 | 100 | // Done: 101 | return true; 102 | } 103 | 104 | /** 105 | Logs the velocity potential scalar field into a VTK file. The grid is the 106 | smallest box encompassing all nodes of all surfaces, expanded in the X, Y, 107 | and Z directions by the given margins. 108 | 109 | @param[in] solver Solver whose state to output. 110 | @param[in] filename Destination filename. 111 | @param[in] x_min Minimum X coordinate of grid. 112 | @param[in] x_max Maximum X coordinate of grid. 113 | @param[in] y_min Minimum Y coordinate of grid. 114 | @param[in] y_max Maximum Y coordinate of grid. 115 | @param[in] z_min Minimum Z coordinate of grid. 116 | @param[in] z_max Maximum Z coordinate of grid. 117 | @param[in] dx Grid step size in X-direction. 118 | @param[in] dy Grid step size in Y-direction. 119 | @param[in] dz Grid step size in Z-direction. 120 | 121 | @returns true on success. 122 | */ 123 | bool 124 | VTKFieldWriter::write_velocity_potential_field(const Solver &solver, const std::string &filename, 125 | double x_min, double x_max, 126 | double y_min, double y_max, 127 | double z_min, double z_max, 128 | double dx, double dy, double dz) 129 | { 130 | int nx = round((x_max - x_min) / dx) + 1; 131 | int ny = round((y_max - y_min) / dy) + 1; 132 | int nz = round((z_max - z_min) / dz) + 1; 133 | 134 | // Compute velocity vector field: 135 | cout << "VTKFieldWriter: Computing velocity potential field." << endl; 136 | 137 | vector velocity_potentials; 138 | velocity_potentials.resize(nx * ny * nz); 139 | 140 | int i; 141 | 142 | #pragma omp parallel 143 | { 144 | #pragma omp for schedule(dynamic, 1) 145 | for (i = 0; i < nx * ny * nz; i++) { 146 | double x, y, z; 147 | x = x_min + (i % (nx * ny)) % nx * dx; 148 | y = y_min + (i % (nx * ny)) / nx * dy; 149 | z = z_min + (i / (nx * ny)) * dz; 150 | velocity_potentials[i] = solver.velocity_potential(Vector3d(x, y, z)); 151 | } 152 | } 153 | 154 | // Write output in VTK format: 155 | cout << "VTKFieldWriter: Saving velocity potential field to " << filename << "." << endl; 156 | 157 | ofstream f; 158 | f.open(filename.c_str()); 159 | 160 | write_preamble(f, x_min, y_min, z_min, dx, dy, dz, nx, ny, nz); 161 | 162 | // Velocity potential field: 163 | f << "SCALARS VelocityPotential double 1" << endl; 164 | f << "LOOKUP_TABLE default" << endl; 165 | 166 | vector::const_iterator it; 167 | for (it = velocity_potentials.begin(); it != velocity_potentials.end(); it++) { 168 | double p = *it; 169 | 170 | f << p << endl; 171 | } 172 | 173 | // Close file: 174 | f.close(); 175 | 176 | // Done: 177 | return true; 178 | } 179 | 180 | /** 181 | Write preamble for VTK output file. 182 | */ 183 | void 184 | VTKFieldWriter::write_preamble(ofstream &f, 185 | double x_min, double y_min, double z_min, 186 | double dx, double dy, double dz, 187 | int nx, int ny, int nz) const 188 | { 189 | f << "# vtk DataFile Version 2.0" << endl; 190 | f << "FieldData" << endl; 191 | f << "ASCII" << endl; 192 | f << "DATASET RECTILINEAR_GRID" << endl; 193 | f << "DIMENSIONS " << nx << " " << ny << " " << nz << endl; 194 | f << "X_COORDINATES " << nx << " double" << endl; 195 | for (int i = 0; i < nx; i++) { 196 | if (i > 0) 197 | f << ' '; 198 | f << x_min + i * dx; 199 | } 200 | f << endl; 201 | f << "Y_COORDINATES " << ny << " double" << endl; 202 | for (int i = 0; i < ny; i++) { 203 | if (i > 0) 204 | f << ' '; 205 | f << y_min + i * dy; 206 | } 207 | f << endl; 208 | f << "Z_COORDINATES " << nz << " double" << endl; 209 | for (int i = 0; i < nz; i++) { 210 | if (i > 0) 211 | f << ' '; 212 | f << z_min + i * dz; 213 | } 214 | f << endl; 215 | 216 | f << endl; 217 | 218 | f << "POINT_DATA " << nx * ny * nz << endl; 219 | } 220 | -------------------------------------------------------------------------------- /examples/vawt/vawt.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Simple VAWT example. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | using namespace Eigen; 23 | using namespace Vortexje; 24 | 25 | static const double pi = 3.141592653589793238462643383279502884; 26 | 27 | #define N_BLADES 2 28 | #define MILL_RADIUS 2.5 29 | #define TIP_SPEED_RATIO 5 30 | #define WIND_VELOCITY 6 31 | //#define INCLUDE_TOWER 32 | 33 | class Blade : public LiftingSurface 34 | { 35 | public: 36 | // Constructor: 37 | Blade(const string &id) 38 | : LiftingSurface(id) 39 | { 40 | // Create blade: 41 | LiftingSurfaceBuilder surface_builder(*this); 42 | 43 | const int n_points_per_airfoil = 32; 44 | const int n_airfoils = 21; 45 | 46 | const double chord = 0.75; 47 | const double span = 4.5; 48 | 49 | int trailing_edge_point_id; 50 | vector prev_airfoil_nodes; 51 | 52 | vector > node_strips; 53 | vector > panel_strips; 54 | 55 | for (int i = 0; i < n_airfoils; i++) { 56 | vector > airfoil_points = 57 | NACA4AirfoilGenerator::generate(0, 0, 0.12, true, chord, n_points_per_airfoil, trailing_edge_point_id); 58 | for (int j = 0; j < (int) airfoil_points.size(); j++) 59 | airfoil_points[j](2) += i * span / (double) (n_airfoils - 1); 60 | 61 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 62 | node_strips.push_back(airfoil_nodes); 63 | 64 | if (i > 0) { 65 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 66 | panel_strips.push_back(airfoil_panels); 67 | } 68 | 69 | prev_airfoil_nodes = airfoil_nodes; 70 | } 71 | 72 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 73 | 74 | // Translate and rotate into the canonical coordinate system: 75 | Vector3d translation(-chord / 3.0, 0.0, -span / 2.0); 76 | translate(translation); 77 | 78 | rotate(Vector3d::UnitZ(), -pi / 2.0); 79 | } 80 | }; 81 | 82 | class Tower : public Surface 83 | { 84 | public: 85 | // Constructor: 86 | Tower() 87 | : Surface("tower") 88 | { 89 | // Create cylinder: 90 | SurfaceBuilder surface_builder(*this); 91 | 92 | const double r = 0.1; 93 | const double h = 4.5; 94 | 95 | const int n_points = 32; 96 | const int n_layers = 21; 97 | 98 | vector prev_nodes; 99 | 100 | for (int i = 0; i < n_layers; i++) { 101 | vector > points = 102 | EllipseGenerator::generate(r, r, n_points); 103 | for (int j = 0; j < (int) points.size(); j++) 104 | points[j](2) += i * h / (double) (n_layers - 1); 105 | 106 | vector nodes = surface_builder.create_nodes_for_points(points); 107 | 108 | if (i > 0) 109 | vector airfoil_panels = surface_builder.create_panels_between_shapes(nodes, prev_nodes); 110 | 111 | prev_nodes = nodes; 112 | } 113 | 114 | surface_builder.finish(); 115 | 116 | // Translate into the canonical coordinate system: 117 | Vector3d translation(0.0, 0.0, -h / 2.0); 118 | translate(translation); 119 | } 120 | }; 121 | 122 | class VAWT : public Body 123 | { 124 | public: 125 | double rotor_radius; 126 | 127 | // Constructor: 128 | VAWT(string id, 129 | double rotor_radius, 130 | int n_blades, 131 | Vector3d position, 132 | double theta_0, 133 | double dthetadt) : 134 | Body(id), rotor_radius(rotor_radius) 135 | { 136 | // Initialize kinematics: 137 | this->position = position; 138 | this->velocity = Vector3d(0, 0, 0); 139 | this->attitude = AngleAxis(theta_0, Vector3d::UnitZ()); 140 | this->rotational_velocity = Vector3d(0, 0, dthetadt); 141 | 142 | #ifdef INCLUDE_TOWER 143 | // Initialize tower: 144 | shared_ptr tower(new Tower()); 145 | add_non_lifting_surface(tower); 146 | #endif 147 | 148 | // Initialize blades: 149 | for (int i = 0; i < n_blades; i++) { 150 | stringstream ss; 151 | ss << "blade_" << i; 152 | shared_ptr blade(new Blade(ss.str())); 153 | 154 | Vector3d translation(rotor_radius, 0, 0); 155 | blade->translate(translation); 156 | 157 | double theta_blade = theta_0 + 2 * pi / n_blades * i; 158 | blade->rotate(Vector3d::UnitZ(), theta_blade); 159 | 160 | blade->translate(position); 161 | 162 | shared_ptr wake(new RamasamyLeishmanWake(blade)); 163 | add_lifting_surface(blade, wake); 164 | } 165 | } 166 | 167 | // Rotate: 168 | void 169 | rotate(double dt) 170 | { 171 | // Compute new kinematic state: 172 | Quaterniond new_attitude = AngleAxis(rotational_velocity(2) * dt, Vector3d::UnitZ()) * attitude; 173 | set_attitude(new_attitude); 174 | } 175 | }; 176 | 177 | int 178 | main (int argc, char **argv) 179 | { 180 | // Set simulation parameters: 181 | Parameters::convect_wake = true; 182 | Parameters::interpolation_layer_thickness = 1e-1; 183 | Parameters::wake_vortex_core_radius = 1e-3; 184 | 185 | // Set up VAWT: 186 | Vector3d position(0, 0, 0); 187 | 188 | shared_ptr vawt(new VAWT(string("vawt"), 189 | MILL_RADIUS, 190 | N_BLADES, 191 | position, 192 | pi / 6.0, 193 | TIP_SPEED_RATIO * WIND_VELOCITY / MILL_RADIUS)); 194 | 195 | // Set up solver: 196 | Solver solver("vawt-log"); 197 | solver.add_body(vawt); 198 | 199 | Vector3d freestream_velocity(WIND_VELOCITY, 0, 0); 200 | solver.set_freestream_velocity(freestream_velocity); 201 | 202 | double fluid_density = 1.2; 203 | solver.set_fluid_density(fluid_density); 204 | 205 | // Set up file format for logging: 206 | VTKSurfaceWriter surface_writer; 207 | 208 | // Log shaft moments: 209 | ofstream f; 210 | f.open("vawt-log/shaft_moment.txt"); 211 | 212 | // Run simulation: 213 | double t = 0.0; 214 | double dt = 0.0033; 215 | int step_number = 0; 216 | 217 | solver.initialize_wakes(dt); 218 | while (t < 60) { 219 | // Solve: 220 | solver.solve(dt); 221 | 222 | // Log coefficients: 223 | solver.log(step_number, surface_writer); 224 | 225 | // Log shaft moment: 226 | Vector3d M = solver.moment(vawt, position); 227 | f << M(2) << endl; 228 | 229 | // Rotate blades: 230 | vawt->rotate(dt); 231 | 232 | // Update wakes: 233 | solver.update_wakes(dt); 234 | 235 | // Step time: 236 | t += dt; 237 | step_number++; 238 | } 239 | 240 | // Close shaft log file: 241 | f.close(); 242 | 243 | // Done: 244 | return 0; 245 | } 246 | -------------------------------------------------------------------------------- /vortexje/surface-builder.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Surface builder. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | using namespace std; 18 | using namespace Eigen; 19 | using namespace Vortexje; 20 | 21 | /** 22 | Constructs a new SurfaceBuilder object for the given surface. 23 | 24 | @param[in] surface Surface object to construct. 25 | */ 26 | SurfaceBuilder::SurfaceBuilder(Surface &surface) : surface(surface) 27 | { 28 | } 29 | 30 | /** 31 | Creates new nodes for the given list of points. 32 | 33 | @param[in] points List of points. 34 | 35 | @returns A list of new node numbers. 36 | */ 37 | vector 38 | SurfaceBuilder::create_nodes_for_points(const vector > &points) 39 | { 40 | vector new_nodes; 41 | 42 | for (int i = 0; i < (int) points.size(); i++) { 43 | int node_id = surface.nodes.size(); 44 | 45 | surface.nodes.push_back(points[i]); 46 | 47 | shared_ptr > empty_vector = make_shared >(); 48 | surface.node_panel_neighbors.push_back(empty_vector); 49 | 50 | new_nodes.push_back(node_id); 51 | } 52 | 53 | return new_nodes; 54 | } 55 | 56 | /** 57 | Connects two lists of nodes with new panels. 58 | 59 | @param[in] first_nodes First list of node numbers. 60 | @param[in] second_nodes Second list of node numbers. 61 | @param[in] cyclic True if the last nodes in the lists are adjacent to the first nodes in the lists. 62 | 63 | @returns A list of new panel numbers. 64 | */ 65 | vector 66 | SurfaceBuilder::create_panels_between_shapes(const vector &first_nodes, const vector &second_nodes, bool cyclic) 67 | { 68 | vector new_panels; 69 | 70 | for (int i = 0; i < (int) first_nodes.size(); i++) { 71 | // Bundle panel nodes in appropriate order: 72 | int next_i; 73 | if (i == (int) first_nodes.size() - 1) { 74 | if (cyclic) 75 | next_i = 0; 76 | else 77 | break; 78 | } else 79 | next_i = i + 1; 80 | 81 | vector original_nodes; 82 | original_nodes.push_back(first_nodes[i]); 83 | original_nodes.push_back(second_nodes[i]); 84 | original_nodes.push_back(second_nodes[next_i]); 85 | original_nodes.push_back(first_nodes[next_i]); 86 | 87 | // Filter out duplicate nodes while preserving order: 88 | vector unique_nodes; 89 | 90 | for (int j = 0; j < 4; j++) { 91 | bool duplicate = false; 92 | for (int k = j + 1; k < 4; k++) { 93 | if (original_nodes[j] == original_nodes[k]) { 94 | duplicate = true; 95 | 96 | break; 97 | } 98 | } 99 | 100 | if (!duplicate) 101 | unique_nodes.push_back(original_nodes[j]); 102 | } 103 | 104 | // Add panel appropriate for number of nodes: 105 | int panel_id; 106 | switch (unique_nodes.size()) { 107 | case 3: 108 | { 109 | // Add triangle: 110 | panel_id = surface.add_triangle(unique_nodes[0], unique_nodes[1], unique_nodes[2]); 111 | 112 | break; 113 | } 114 | 115 | case 4: 116 | { 117 | // Construct a planar quadrangle: 118 | 119 | // Center points around their mean: 120 | Vector3d mean(0, 0, 0); 121 | for (int i = 0; i < 4; i++) 122 | mean += surface.nodes[unique_nodes[i]]; 123 | mean /= 4.0; 124 | 125 | MatrixXd X(4, 3); 126 | for (int i = 0; i < 4; i++) 127 | X.row(i) = surface.nodes[unique_nodes[i]] - mean; 128 | 129 | // Perform PCA to find dominant directions: 130 | SelfAdjointEigenSolver solver(X.transpose() * X); 131 | 132 | Vector3d eigenvalues = solver.eigenvalues(); 133 | Matrix3d eigenvectors = solver.eigenvectors(); 134 | 135 | double min_eigenvalue = numeric_limits::max(); 136 | int min_eigenvalue_index = -1; 137 | for (int i = 0; i < 3; i++) { 138 | if (eigenvalues(i) < min_eigenvalue) { 139 | min_eigenvalue = eigenvalues(i); 140 | min_eigenvalue_index = i; 141 | } 142 | } 143 | 144 | Vector3d normal = eigenvectors.col(min_eigenvalue_index); 145 | 146 | // Create new points by projecting onto surface spanned by dominant directions: 147 | Vector3d vertices[4]; 148 | for (int i = 0; i < 4; i++) 149 | vertices[i] = surface.nodes[unique_nodes[i]] - (normal * X.row(i)) * normal; 150 | 151 | // Add points to surface: 152 | int new_nodes[4]; 153 | for (int j = 0; j < 4; j++) { 154 | // If the new points don't match the original ones, create new nodes: 155 | if ((vertices[j] - surface.nodes[unique_nodes[j]]).norm() < Parameters::zero_threshold) { 156 | new_nodes[j] = unique_nodes[j]; 157 | } else { 158 | new_nodes[j] = surface.nodes.size(); 159 | 160 | surface.nodes.push_back(vertices[j]); 161 | 162 | surface.node_panel_neighbors.push_back(surface.node_panel_neighbors[unique_nodes[j]]); 163 | } 164 | } 165 | 166 | // Add planar quadrangle: 167 | panel_id = surface.add_quadrangle(new_nodes[0], new_nodes[1], new_nodes[2], new_nodes[3]); 168 | 169 | break; 170 | } 171 | 172 | default: 173 | // Unknown panel type: 174 | cerr << "SurfaceBuilder::create_panels_between_shapes: Cannot create panel with " << unique_nodes.size() << " vertices." << endl; 175 | exit(1); 176 | } 177 | 178 | new_panels.push_back(panel_id); 179 | } 180 | 181 | return new_panels; 182 | } 183 | 184 | /** 185 | Fills a shape with triangular panels, all meeting on the specified tip point. 186 | 187 | @param[in] nodes Node numbers tracing a shape. 188 | @param[in] tip_point Point where all triangles meet. 189 | @param[in] z_sign Handedness of the created panels. 190 | 191 | @returns List of new panel numbers. 192 | */ 193 | vector 194 | SurfaceBuilder::create_panels_inside_shape(const vector &nodes, const Vector3d &tip_point, int z_sign) 195 | { 196 | vector new_panels; 197 | 198 | // Add tip node: 199 | int tip_node = surface.nodes.size(); 200 | 201 | surface.nodes.push_back(tip_point); 202 | 203 | shared_ptr > empty_vector = make_shared >(); 204 | surface.node_panel_neighbors.push_back(empty_vector); 205 | 206 | // Create triangle for leading and trailing edges: 207 | for (int i = 0; i < (int) nodes.size(); i++) { 208 | int triangle[3]; 209 | 210 | if (z_sign == 1) { 211 | triangle[0] = nodes[i]; 212 | if (i == (int) nodes.size() - 1) 213 | triangle[1] = nodes[0]; 214 | else 215 | triangle[1] = nodes[i + 1]; 216 | triangle[2] = tip_node; 217 | 218 | } else { 219 | triangle[0] = nodes[i]; 220 | triangle[1] = tip_node; 221 | if (i == (int) nodes.size() - 1) 222 | triangle[2] = nodes[0]; 223 | else 224 | triangle[2] = nodes[i + 1]; 225 | 226 | } 227 | 228 | int new_panel = surface.add_triangle(triangle[0], triangle[1], triangle[2]); 229 | new_panels.push_back(new_panel); 230 | } 231 | 232 | // Done: 233 | return new_panels; 234 | } 235 | 236 | /** 237 | Finishes the surface construction process by computing the topology as well as various geometrical properties. 238 | */ 239 | void 240 | SurfaceBuilder::finish() 241 | { 242 | surface.compute_topology(); 243 | surface.compute_geometry(); 244 | } 245 | -------------------------------------------------------------------------------- /vortexje/body.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Body. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace Eigen; 16 | using namespace Vortexje; 17 | 18 | /** 19 | Constructs a new Body. 20 | 21 | @param[in] id Name for this body. 22 | */ 23 | Body::Body(const string &id) : id(id) 24 | { 25 | // Initialize kinematics: 26 | position = Vector3d(0, 0, 0); 27 | velocity = Vector3d(0, 0, 0); 28 | 29 | attitude = Quaterniond(1, 0, 0, 0); 30 | rotational_velocity = Vector3d(0, 0, 0); 31 | } 32 | 33 | /** 34 | Body destructor. 35 | */ 36 | Body::~Body() 37 | { 38 | } 39 | 40 | /** 41 | Adds a non-lifting surface to this body. 42 | 43 | @param[in] non_lifting_surface Non-lifting surface. 44 | */ 45 | void 46 | Body::add_non_lifting_surface(std::shared_ptr non_lifting_surface) 47 | { 48 | non_lifting_surfaces.push_back(shared_ptr(new SurfaceData(non_lifting_surface))); 49 | } 50 | 51 | /** 52 | Adds a lifting surface to this body. 53 | 54 | @param[in] lifting_surface Lifting surface. 55 | */ 56 | void 57 | Body::add_lifting_surface(std::shared_ptr lifting_surface) 58 | { 59 | shared_ptr wake(new Wake(lifting_surface)); 60 | 61 | add_lifting_surface(lifting_surface, wake); 62 | } 63 | 64 | /** 65 | Adds a lifting surface and its wake to this body. 66 | 67 | @param[in] lifting_surface Lifting surface. 68 | @param[in] wake Wake. 69 | */ 70 | void 71 | Body::add_lifting_surface(std::shared_ptr lifting_surface, std::shared_ptr wake) 72 | { 73 | lifting_surfaces.push_back(shared_ptr(new LiftingSurfaceData(lifting_surface, wake))); 74 | } 75 | 76 | /** 77 | Creates an across-surface neighborhood relationship between two panels. This operation 78 | is also known as stitching. 79 | 80 | @param[in] surface_a First reference surface. 81 | @param[in] panel_a First reference panel. 82 | @param[in] edge_a First reference edge. 83 | @param[in] surface_b Second reference surface. 84 | @param[in] panel_b Second reference panel. 85 | @param[in] edge_b Second reference edge. 86 | */ 87 | void 88 | Body::stitch_panels(std::shared_ptr surface_a, int panel_a, int edge_a, std::shared_ptr surface_b, int panel_b, int edge_b) 89 | { 90 | // Add stitch from A to B: 91 | stitches[SurfacePanelEdge(surface_a, panel_a, edge_a)].push_back(SurfacePanelEdge(surface_b, panel_b, edge_b)); 92 | 93 | // Add stitch from B to A: 94 | stitches[SurfacePanelEdge(surface_b, panel_b, edge_b)].push_back(SurfacePanelEdge(surface_a, panel_a, edge_a)); 95 | } 96 | 97 | /** 98 | Lists both in-surface and across-surface (stitched) neighbors of the given panel. 99 | 100 | @param[in] surface Reference surface. 101 | @param[in] panel Reference panel. 102 | 103 | @returns List of in-surface and across-surface panel neighbors. 104 | */ 105 | vector 106 | Body::panel_neighbors(const std::shared_ptr &surface, int panel) const 107 | { 108 | vector neighbors; 109 | 110 | // List in-surface neighbors: 111 | for (int i = 0; i < (int) surface->panel_nodes[panel].size(); i++) { 112 | vector > &edge_neighbors = surface->panel_neighbors[panel][i]; 113 | vector >::const_iterator it; 114 | for (it = edge_neighbors.begin(); it != edge_neighbors.end(); it++) 115 | neighbors.push_back(SurfacePanelEdge(surface, it->first, it->second)); 116 | } 117 | 118 | // List stitches: 119 | for (int i = 0; i < (int) surface->panel_nodes[panel].size(); i++) { 120 | map, CompareSurfacePanelEdge>::const_iterator it = 121 | stitches.find(SurfacePanelEdge(surface, panel, i)); 122 | if (it != stitches.end()) { 123 | vector edge_stitches = it->second; 124 | vector::const_iterator sit; 125 | for (sit = edge_stitches.begin(); sit != edge_stitches.end(); sit++) 126 | neighbors.push_back(*sit); 127 | } 128 | } 129 | 130 | // Done: 131 | return neighbors; 132 | } 133 | 134 | /** 135 | Lists both in-surface and across-surface (stitched) neighbors of the given panel and edge. 136 | 137 | @param[in] surface Reference surface. 138 | @param[in] panel Reference panel. 139 | @param[in] edge Reference edge. 140 | 141 | @returns List of in-surface and across-surface panel neighbors for the given edge. 142 | */ 143 | vector 144 | Body::panel_neighbors(const std::shared_ptr &surface, int panel, int edge) const 145 | { 146 | vector neighbors; 147 | 148 | // List in-surface neighbors: 149 | { 150 | vector > &edge_neighbors = surface->panel_neighbors[panel][edge]; 151 | vector >::const_iterator it; 152 | for (it = edge_neighbors.begin(); it != edge_neighbors.end(); it++) 153 | neighbors.push_back(SurfacePanelEdge(surface, it->first, it->second)); 154 | } 155 | 156 | // List stitches: 157 | { 158 | map, CompareSurfacePanelEdge>::const_iterator it = 159 | stitches.find(SurfacePanelEdge(surface, panel, edge)); 160 | if (it != stitches.end()) { 161 | vector edge_stitches = it->second; 162 | vector::const_iterator sit; 163 | for (sit = edge_stitches.begin(); sit != edge_stitches.end(); sit++) 164 | neighbors.push_back(*sit); 165 | } 166 | } 167 | 168 | // Done: 169 | return neighbors; 170 | } 171 | 172 | /** 173 | Sets the linear position of this body. 174 | 175 | @param[in] position Linear position. 176 | */ 177 | void 178 | Body::set_position(const Vector3d &position) 179 | { 180 | // Compute differential translation: 181 | Vector3d translation = position - this->position; 182 | 183 | // Apply: 184 | vector >::iterator si; 185 | for (si = non_lifting_surfaces.begin(); si != non_lifting_surfaces.end(); si++) { 186 | shared_ptr d = *si; 187 | 188 | d->surface->translate(translation); 189 | } 190 | 191 | vector >::iterator lsi; 192 | for (lsi = lifting_surfaces.begin(); lsi != lifting_surfaces.end(); lsi++) { 193 | shared_ptr d = *lsi; 194 | 195 | d->surface->translate(translation); 196 | 197 | d->wake->translate_trailing_edge(translation); 198 | } 199 | 200 | // Update state: 201 | this->position = position; 202 | } 203 | 204 | /** 205 | Sets the attitude (orientation) of this body. 206 | 207 | @param[in] attitude Attitude (orientation) of this body, as normalized quaternion. 208 | */ 209 | void 210 | Body::set_attitude(const Quaterniond &attitude) 211 | { 212 | // Compute differential transformation: 213 | Transform transformation = Translation(position) * attitude * this->attitude.inverse() * Translation(-position); 214 | 215 | // Apply: 216 | vector >::iterator si; 217 | for (si = non_lifting_surfaces.begin(); si != non_lifting_surfaces.end(); si++) { 218 | shared_ptr d = *si; 219 | 220 | d->surface->transform(transformation); 221 | } 222 | 223 | vector >::iterator lsi; 224 | for (lsi = lifting_surfaces.begin(); lsi != lifting_surfaces.end(); lsi++) { 225 | shared_ptr d = *lsi; 226 | 227 | d->surface->transform(transformation); 228 | 229 | d->wake->transform_trailing_edge(transformation); 230 | } 231 | 232 | // Update state: 233 | this->attitude = attitude; 234 | } 235 | 236 | /** 237 | Sets the linear velocity of this body. 238 | 239 | @param[in] velocity Linear velocity. 240 | */ 241 | void 242 | Body::set_velocity(const Vector3d &velocity) 243 | { 244 | this->velocity = velocity; 245 | } 246 | 247 | /** 248 | Sets the rotational velocity of this body. 249 | 250 | @param[in] rotational_velocity Rotational velocity. 251 | */ 252 | void 253 | Body::set_rotational_velocity(const Vector3d &rotational_velocity) 254 | { 255 | this->rotational_velocity = rotational_velocity; 256 | } 257 | 258 | /** 259 | Computes the kinematic velocity of the given panel. 260 | 261 | @param[in] surface Surface, belonging to this body. 262 | @param[in] panel Panel, belonging to this surface. 263 | 264 | @return The kinematic velocity. 265 | */ 266 | Vector3d 267 | Body::panel_kinematic_velocity(const std::shared_ptr &surface, int panel) const 268 | { 269 | const Vector3d &panel_position = surface->panel_collocation_point(panel, false); 270 | Vector3d r = panel_position - position; 271 | return velocity + rotational_velocity.cross(r); 272 | } 273 | 274 | /** 275 | Computes the kinematic velocity of the given node. 276 | 277 | @param[in] surface Surface, belonging to this body. 278 | @param[in] node Node, belonging to this surface. 279 | 280 | @return The kinematic velocity. 281 | */ 282 | Vector3d 283 | Body::node_kinematic_velocity(const std::shared_ptr &surface, int node) const 284 | { 285 | Vector3d r = surface->nodes[node] - position; 286 | return velocity + rotational_velocity.cross(r); 287 | } 288 | -------------------------------------------------------------------------------- /examples/hawt/hawt.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Simple HAWT example. 3 | // 4 | // Copyright (C) 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | using namespace Eigen; 23 | using namespace Vortexje; 24 | 25 | #define N_BLADES 3 26 | #define ROTOR_RADIUS 5.5320 27 | #define TIP_SPEED_RATIO 3 28 | #define WIND_VELOCITY 6.0 29 | 30 | class Blade : public LiftingSurface 31 | { 32 | public: 33 | // Constructor: 34 | Blade(const string &id) 35 | : LiftingSurface(id) 36 | { 37 | // Read data: 38 | read_airfoil("hawt-airfoil.dat"); 39 | read_blade_geometry("hawt-blade.dat"); 40 | 41 | // Create blade: 42 | LiftingSurfaceBuilder surface_builder(*this); 43 | 44 | vector prev_airfoil_nodes; 45 | 46 | vector > node_strips; 47 | vector > panel_strips; 48 | 49 | for (int i = 0; i < (int) blade_dr.size(); i++) { 50 | vector > airfoil_points; 51 | for (int j = 0; j < (int) unscaled_airfoil_points.size(); j++) { 52 | Vector3d airfoil_point(blade_chord[i] * (unscaled_airfoil_points[j](0) - 0.25), 53 | blade_thickness[i] / unscaled_airfoil_thickness * unscaled_airfoil_points[j](1), 54 | blade_dr[i]); 55 | 56 | airfoil_point = AngleAxis(blade_twist[i], Vector3d::UnitZ()) * airfoil_point; 57 | 58 | airfoil_points.push_back(airfoil_point); 59 | } 60 | 61 | vector airfoil_nodes = surface_builder.create_nodes_for_points(airfoil_points); 62 | node_strips.push_back(airfoil_nodes); 63 | 64 | if (i > 0) { 65 | vector airfoil_panels = surface_builder.create_panels_between_shapes(airfoil_nodes, prev_airfoil_nodes, trailing_edge_point_id); 66 | panel_strips.push_back(airfoil_panels); 67 | } 68 | 69 | prev_airfoil_nodes = airfoil_nodes; 70 | } 71 | 72 | surface_builder.finish(node_strips, panel_strips, trailing_edge_point_id); 73 | 74 | rotate(Vector3d::UnitZ(), -M_PI / 2.0); 75 | } 76 | 77 | private: 78 | vector > unscaled_airfoil_points; 79 | double unscaled_airfoil_thickness; 80 | 81 | int trailing_edge_point_id; 82 | 83 | vector blade_dr; 84 | vector blade_chord; 85 | vector blade_twist; 86 | vector blade_thickness; 87 | 88 | // Load airfoil data from file: 89 | void 90 | read_airfoil(const std::string &filename) 91 | { 92 | vector > upper_points; 93 | vector > lower_points; 94 | 95 | // Parse file: 96 | ifstream f; 97 | f.open(filename.c_str()); 98 | 99 | int side = -1; 100 | 101 | while (f.good()) { 102 | string line; 103 | getline(f, line); 104 | 105 | // Empty line signifies new section: 106 | bool empty_line = false; 107 | if (line.find_first_not_of("\r\n\t ") == string::npos) 108 | empty_line = true; 109 | 110 | if (empty_line) { 111 | side++; 112 | continue; 113 | } 114 | 115 | // Read x, y coordinates: 116 | if (side >= 0) { 117 | istringstream tokens(line); 118 | 119 | double x, y; 120 | tokens >> x >> y; 121 | 122 | Vector3d point(x, y, 0.0); 123 | if (side == 0) 124 | upper_points.push_back(point); 125 | else 126 | lower_points.push_back(point); 127 | } 128 | } 129 | 130 | // Close file: 131 | f.close(); 132 | 133 | // Assemble entire airfoil: 134 | for (int i = 0; i < (int) upper_points.size() - 1; i++) 135 | unscaled_airfoil_points.push_back(upper_points[i]); 136 | 137 | // Use a thin trailing edge: 138 | Vector3d trailing_edge_point = 0.5 * (upper_points[(int) upper_points.size() - 1] + lower_points[(int) lower_points.size() - 1]); 139 | unscaled_airfoil_points.push_back(trailing_edge_point); 140 | trailing_edge_point_id = (int) unscaled_airfoil_points.size() - 1; 141 | 142 | for (int i = (int) lower_points.size() - 2; i > 0; i--) 143 | unscaled_airfoil_points.push_back(lower_points[i]); 144 | 145 | // Compute thickness: 146 | double max_y = 0.0; 147 | for (int i = 0; i < (int) upper_points.size(); i++) { 148 | if (upper_points[i](1) > max_y) 149 | max_y = upper_points[i](1); 150 | } 151 | 152 | double min_y = 0.0; 153 | for (int i = 0; i < (int) lower_points.size(); i++) { 154 | if (lower_points[i](1) < min_y) 155 | min_y = lower_points[i](1); 156 | } 157 | 158 | unscaled_airfoil_thickness = max_y - min_y; 159 | } 160 | 161 | // Load blade geometry from file: 162 | void 163 | read_blade_geometry(const std::string &filename) 164 | { 165 | // Parse file: 166 | ifstream f; 167 | f.open(filename.c_str()); 168 | 169 | while (f.good()) { 170 | string line; 171 | getline(f, line); 172 | 173 | // Read section geometry: 174 | if (line.substr(0, 2) != "dr" && line.length() > 0) { 175 | istringstream tokens(line); 176 | 177 | double dr, chord, twist, thick; 178 | tokens >> dr >> chord >> twist >> thick; 179 | 180 | blade_dr.push_back(dr); 181 | blade_chord.push_back(chord); 182 | blade_twist.push_back(M_PI * twist / 180.0); 183 | blade_thickness.push_back(thick); 184 | } 185 | } 186 | 187 | // Close file: 188 | f.close(); 189 | } 190 | }; 191 | 192 | class HAWT : public Body 193 | { 194 | public: 195 | // Constructor: 196 | HAWT(string id, 197 | int n_blades, 198 | Vector3d position, 199 | double theta_0, 200 | double dthetadt) : 201 | Body(id) 202 | { 203 | // Initialize kinematics: 204 | this->position = position; 205 | this->velocity = Vector3d(0, 0, 0); 206 | this->attitude = AngleAxis(theta_0, Vector3d::UnitX()); 207 | this->rotational_velocity = Vector3d(-dthetadt, 0, 0); 208 | 209 | // Initialize blades: 210 | for (int i = 0; i < n_blades; i++) { 211 | stringstream ss; 212 | ss << "blade_" << i; 213 | shared_ptr blade(new Blade(ss.str())); 214 | 215 | double theta_blade = theta_0 + 2 * M_PI / n_blades * i; 216 | blade->rotate(Vector3d::UnitX(), theta_blade); 217 | 218 | blade->translate(position); 219 | 220 | shared_ptr wake(new RamasamyLeishmanWake(blade)); 221 | add_lifting_surface(blade, wake); 222 | } 223 | } 224 | 225 | // Rotate: 226 | void 227 | rotate(double dt) 228 | { 229 | // Compute new kinematic state: 230 | Quaterniond new_attitude = AngleAxis(rotational_velocity(0) * dt, Vector3d::UnitX()) * attitude; 231 | set_attitude(new_attitude); 232 | } 233 | }; 234 | 235 | int 236 | main (int argc, char **argv) 237 | { 238 | // Set simulation parameters: 239 | Parameters::unsteady_bernoulli = true; 240 | Parameters::convect_wake = true; 241 | Parameters::interpolation_layer_thickness = 1.5e-3; 242 | 243 | RamasamyLeishmanWake::Parameters::initial_vortex_core_radius = 1.5e-3; 244 | RamasamyLeishmanWake::Parameters::min_vortex_core_radius = 1.5e-3; 245 | 246 | // Set up HAWT: 247 | Vector3d position(0, 0, 0); 248 | 249 | shared_ptr hawt(new HAWT(string("hawt"), 250 | N_BLADES, 251 | position, 252 | 0.0, 253 | TIP_SPEED_RATIO * WIND_VELOCITY / ROTOR_RADIUS)); 254 | 255 | // Set up solver: 256 | Solver solver("hawt-log"); 257 | solver.add_body(hawt); 258 | 259 | Vector3d freestream_velocity(WIND_VELOCITY, 0, 0); 260 | solver.set_freestream_velocity(freestream_velocity); 261 | 262 | double fluid_density = 1.2; 263 | solver.set_fluid_density(fluid_density); 264 | 265 | // Set up file format for logging: 266 | VTKSurfaceWriter surface_writer; 267 | 268 | // Log shaft moments: 269 | ofstream f; 270 | f.open("hawt-log/shaft_moment.txt"); 271 | 272 | // Run simulation: 273 | double t = 0.0; 274 | double dt = 0.0033; 275 | int step_number = 0; 276 | 277 | solver.initialize_wakes(dt); 278 | while (t < 60) { 279 | // Solve: 280 | solver.solve(dt); 281 | 282 | // Log coefficients: 283 | solver.log(step_number, surface_writer); 284 | 285 | // Log shaft moment: 286 | Vector3d M = solver.moment(hawt, position); 287 | f << M(0) << endl; 288 | 289 | // Rotate blades: 290 | hawt->rotate(dt); 291 | 292 | // Update wakes: 293 | solver.update_wakes(dt); 294 | 295 | // Step time: 296 | t += dt; 297 | step_number++; 298 | } 299 | 300 | // Close shaft log file: 301 | f.close(); 302 | 303 | // Done: 304 | return 0; 305 | } 306 | -------------------------------------------------------------------------------- /vortexje/empirical-wakes/ramasamy-leishman-wake.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Vortexje -- Ramasamy-Leishman wake model. 3 | // 4 | // Copyright (C) 2012 - 2014 Baayen & Heinz GmbH. 5 | // 6 | // Authors: Jorn Baayen 7 | // 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace Eigen; 16 | using namespace Vortexje; 17 | 18 | static const double pi = 3.141592653589793238462643383279502884; 19 | 20 | // Default parameter values: 21 | double RamasamyLeishmanWake::Parameters::fluid_kinematic_viscosity = 15.68e-6; 22 | 23 | double RamasamyLeishmanWake::Parameters::initial_vortex_core_radius = 0.07; 24 | 25 | double RamasamyLeishmanWake::Parameters::min_vortex_core_radius = 0.07; 26 | 27 | double RamasamyLeishmanWake::Parameters::lambs_constant = 1.25643; 28 | 29 | double RamasamyLeishmanWake::Parameters::a_prime = 6.5e-5; 30 | 31 | // Ramasamy-Leishman series data: 32 | typedef struct { 33 | double vortex_reynolds_number; 34 | double a_1; 35 | double b_1; 36 | double a_2; 37 | double b_2; 38 | double b_3; 39 | } ramasamy_leishman_data_row; 40 | 41 | static ramasamy_leishman_data_row ramasamy_leishman_data[12] = { { 1, 1.0000, 1.2560, 0.0000, 0.00000, 0.0000}, 42 | { 100, 1.0000, 1.2515, 0.0000, 0.00000, 0.0000}, 43 | { 1000, 1.0000, 1.2328, 0.0000, 0.00000, 0.0000}, 44 | {10000, 0.8247, 1.2073, 0.1753, 0.02630, 0.0000}, 45 | {2.5e4, 0.5933, 1.3480, 0.2678, 0.01870, 0.2070}, 46 | {4.8e4, 0.4602, 1.3660, 0.3800, 0.01380, 0.1674}, 47 | {7.5e4, 0.3574, 1.3995, 0.4840, 0.01300, 0.1636}, 48 | { 1e5, 0.3021, 1.4219, 0.5448, 0.01220, 0.1624}, 49 | {2.5e5, 0.1838, 1.4563, 0.6854, 0.00830, 0.1412}, 50 | { 5e5, 0.1386, 1.4285, 0.7432, 0.00580, 0.1144}, 51 | {7.5e5, 0.1011, 1.4462, 0.7995, 0.00480, 0.1078}, 52 | { 1e6, 0.0792, 1.4716, 0.8352, 0.00420, 0.1077} }; 53 | 54 | // Avoid having to divide by 4 pi all the time: 55 | static const double one_over_4pi = 1.0 / (4 * pi); 56 | 57 | /** 58 | Constructs an empty Ramasamy-Leishman wake. 59 | 60 | @param[in] lifting_surface Associated lifting surface. 61 | */ 62 | RamasamyLeishmanWake::RamasamyLeishmanWake(std::shared_ptr lifting_surface): Wake(lifting_surface) 63 | { 64 | } 65 | 66 | /** 67 | Adds new layer of wake panels. 68 | */ 69 | void 70 | RamasamyLeishmanWake::add_layer() 71 | { 72 | // Add layer: 73 | this->Wake::add_layer(); 74 | 75 | // Add R-L data: 76 | if (n_panels() >= lifting_surface->n_spanwise_panels()) { 77 | for (int k = 0; k < lifting_surface->n_spanwise_panels(); k++) { 78 | int panel = n_panels() - lifting_surface->n_spanwise_panels() + k; 79 | 80 | // Add initial vortex core radii. 81 | vector panel_vortex_core_radii; 82 | for (int i = 0; i < 4; i++) 83 | panel_vortex_core_radii.push_back(RamasamyLeishmanWake::Parameters::initial_vortex_core_radius); 84 | vortex_core_radii.push_back(panel_vortex_core_radii); 85 | 86 | // Store base edge lengths. 87 | vector edge_lengths; 88 | for (int i = 0; i < 4; i++) { 89 | int prev_idx; 90 | if (i == 0) 91 | prev_idx = 3; 92 | else 93 | prev_idx = i - 1; 94 | 95 | const Vector3d &node_a = nodes[panel_nodes[panel][prev_idx]]; 96 | const Vector3d &node_b = nodes[panel_nodes[panel][i]]; 97 | 98 | Vector3d edge = node_b - node_a; 99 | edge_lengths.push_back(edge.norm()); 100 | } 101 | base_edge_lengths.push_back(edge_lengths); 102 | } 103 | } 104 | } 105 | 106 | /** 107 | Computes the unit velocity induced by a Ramasamy-Leishman vortex ring. 108 | 109 | @param[in] x Point at which the velocity is evaluated. 110 | @param[in] this_panel Panel on which the vortex ring is located. 111 | 112 | @returns Unit velocity induced by the Ramasamy-Leishman vortex ring. 113 | 114 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 115 | */ 116 | Vector3d 117 | RamasamyLeishmanWake::vortex_ring_unit_velocity(const Eigen::Vector3d &x, int this_panel) const 118 | { 119 | if (this_panel >= n_panels() - lifting_surface->n_spanwise_panels()) { 120 | // This panel is contained in the latest row of wake panels. To satisfy the Kutta condition 121 | // exactly, we use the unmodified vortex ring unit velocity here. 122 | return this->Surface::vortex_ring_unit_velocity(x, this_panel); 123 | } 124 | 125 | // Compute vortex Reynolds number: 126 | double vortex_reynolds_number = doublet_coefficients[this_panel] / RamasamyLeishmanWake::Parameters::fluid_kinematic_viscosity; 127 | 128 | // Interpolate Ramasamy-Leishman series values piecewise-linearly: 129 | int less_than_idx; 130 | for (less_than_idx = 0; less_than_idx < 12; less_than_idx++) { 131 | ramasamy_leishman_data_row &row = ramasamy_leishman_data[less_than_idx]; 132 | 133 | if (vortex_reynolds_number < row.vortex_reynolds_number) 134 | break; 135 | } 136 | 137 | double a[3]; 138 | double b[3]; 139 | if (less_than_idx == 0) { 140 | a[0] = ramasamy_leishman_data[0].a_1; 141 | a[1] = ramasamy_leishman_data[0].a_2; 142 | 143 | b[0] = ramasamy_leishman_data[0].b_1; 144 | b[1] = ramasamy_leishman_data[0].b_2; 145 | b[2] = ramasamy_leishman_data[0].b_3; 146 | } else if (less_than_idx == 12) { 147 | a[0] = ramasamy_leishman_data[11].a_1; 148 | a[1] = ramasamy_leishman_data[11].a_2; 149 | 150 | b[0] = ramasamy_leishman_data[11].b_1; 151 | b[1] = ramasamy_leishman_data[11].b_2; 152 | b[2] = ramasamy_leishman_data[11].b_3; 153 | } else { 154 | double one_over_delta_vortex_reynolds_number = 155 | 1.0 / (ramasamy_leishman_data[less_than_idx].vortex_reynolds_number - ramasamy_leishman_data[less_than_idx - 1].vortex_reynolds_number); 156 | double x = vortex_reynolds_number - ramasamy_leishman_data[less_than_idx - 1].vortex_reynolds_number; 157 | double slope; 158 | 159 | slope = (ramasamy_leishman_data[less_than_idx].a_1 - ramasamy_leishman_data[less_than_idx - 1].a_1) * one_over_delta_vortex_reynolds_number; 160 | a[0] = ramasamy_leishman_data[less_than_idx - 1].a_1 + slope * x; 161 | 162 | slope = (ramasamy_leishman_data[less_than_idx].a_2 - ramasamy_leishman_data[less_than_idx - 1].a_2) * one_over_delta_vortex_reynolds_number; 163 | a[1] = ramasamy_leishman_data[less_than_idx - 1].a_2 + slope * x; 164 | 165 | slope = (ramasamy_leishman_data[less_than_idx].b_1 - ramasamy_leishman_data[less_than_idx - 1].b_1) * one_over_delta_vortex_reynolds_number; 166 | b[0] = ramasamy_leishman_data[less_than_idx - 1].b_1 + slope * x; 167 | 168 | slope = (ramasamy_leishman_data[less_than_idx].b_2 - ramasamy_leishman_data[less_than_idx - 1].b_2) * one_over_delta_vortex_reynolds_number; 169 | b[1] = ramasamy_leishman_data[less_than_idx - 1].b_2 + slope * x; 170 | 171 | slope = (ramasamy_leishman_data[less_than_idx].b_3 - ramasamy_leishman_data[less_than_idx - 1].b_3) * one_over_delta_vortex_reynolds_number; 172 | b[2] = ramasamy_leishman_data[less_than_idx - 1].b_3 + slope * x; 173 | } 174 | 175 | a[2] = 1 - a[0] - a[1]; 176 | 177 | // Compute velocity: 178 | Vector3d velocity(0, 0, 0); 179 | 180 | for (int i = 0; i < (int) panel_nodes[this_panel].size(); i++) { 181 | int previous_idx; 182 | if (i == 0) 183 | previous_idx = panel_nodes[this_panel].size() - 1; 184 | else 185 | previous_idx = i - 1; 186 | 187 | const Vector3d &node_a = nodes[panel_nodes[this_panel][previous_idx]]; 188 | const Vector3d &node_b = nodes[panel_nodes[this_panel][i]]; 189 | 190 | Vector3d r_0 = node_b - node_a; 191 | Vector3d r_1 = node_a - x; 192 | Vector3d r_2 = node_b - x; 193 | 194 | double r_0_norm = r_0.norm(); 195 | double r_1_norm = r_1.norm(); 196 | double r_2_norm = r_2.norm(); 197 | 198 | Vector3d r_1xr_2 = r_1.cross(r_2); 199 | double r_1xr_2_sqnorm = r_1xr_2.squaredNorm(); 200 | double r_1xr_2_norm = sqrt(r_1xr_2_sqnorm); 201 | 202 | if (r_0_norm < Vortexje::Parameters::zero_threshold || 203 | r_1_norm < Vortexje::Parameters::zero_threshold || 204 | r_2_norm < Vortexje::Parameters::zero_threshold || 205 | r_1xr_2_sqnorm < Vortexje::Parameters::zero_threshold) 206 | continue; 207 | 208 | double d = r_1xr_2_norm / r_0_norm; 209 | 210 | double dr = pow(d / vortex_core_radii[this_panel][i], 2); 211 | 212 | double sum = 0; 213 | for (int j = 0; j < 3; j++) 214 | sum += a[j] * exp(-b[j] * dr); 215 | 216 | velocity += (1 - sum) * r_1xr_2 / r_1xr_2_sqnorm * r_0.dot(r_1 / r_1_norm - r_2 / r_2_norm); 217 | } 218 | 219 | return one_over_4pi * velocity; 220 | } 221 | 222 | /** 223 | Updates the Ramasamy-Leishman vortex ring core radii. 224 | 225 | @param[in] panel Panel number. 226 | @param[in] dt Time step size. 227 | 228 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 229 | */ 230 | void 231 | RamasamyLeishmanWake::update_vortex_ring_radii(int panel, double dt) 232 | { 233 | for (int i = 0; i < 4; i++) { 234 | int prev_idx; 235 | if (i == 0) 236 | prev_idx = 3; 237 | else 238 | prev_idx = i - 1; 239 | 240 | double vortex_reynolds_number = fabs(doublet_coefficients[panel]) / RamasamyLeishmanWake::Parameters::fluid_kinematic_viscosity; 241 | 242 | double t_multiplier = 4 * RamasamyLeishmanWake::Parameters::lambs_constant * 243 | (1 + vortex_reynolds_number * RamasamyLeishmanWake::Parameters::a_prime) * 244 | RamasamyLeishmanWake::Parameters::fluid_kinematic_viscosity; 245 | 246 | double t = (pow(vortex_core_radii[panel][i], 2) - pow(RamasamyLeishmanWake::Parameters::initial_vortex_core_radius, 2)) / t_multiplier; 247 | 248 | double vortex_core_size_0 = sqrt(pow(RamasamyLeishmanWake::Parameters::initial_vortex_core_radius, 2) + t_multiplier * (t + dt)); 249 | 250 | Vector3d node_a = nodes[panel_nodes[panel][prev_idx]]; 251 | Vector3d node_b = nodes[panel_nodes[panel][i]]; 252 | 253 | Vector3d edge = node_b - node_a; 254 | 255 | double strain = (edge.norm() - base_edge_lengths[panel][i]) / base_edge_lengths[panel][i]; 256 | 257 | vortex_core_radii[panel][i] = fmax(RamasamyLeishmanWake::Parameters::min_vortex_core_radius, vortex_core_size_0 / sqrt(1 + strain)); 258 | } 259 | } 260 | 261 | /** 262 | Updates the Ramasamy-Leishman vortex ring core radii. 263 | 264 | @param[in] dt Time step size. 265 | 266 | @note See M. Ramasamy and J. G. Leishman, Reynolds Number Based Blade Tip Vortex Model, University of Maryland, 2005. 267 | */ 268 | void 269 | RamasamyLeishmanWake::update_properties(double dt) 270 | { 271 | int i; 272 | 273 | #pragma omp parallel 274 | { 275 | #pragma omp for schedule(dynamic, 1) 276 | for (i = 0; i < n_panels(); i++) 277 | update_vortex_ring_radii(i, dt); 278 | } 279 | } 280 | --------------------------------------------------------------------------------