├── .gitattributes ├── .gitignore ├── .travis.yml ├── CACTUS.sln ├── CACTUS.vfproj ├── CMakeLists.txt ├── DAKOTA ├── Driver │ └── CACTUS_Driver.py ├── Example1 │ ├── OptTSR.dak │ ├── OptTSR_CL.out │ ├── Opt_Data_Out.dat │ ├── ParamTSR.dak │ ├── Param_Data_Out.dat │ └── TestVAWTNom.in └── Opt_SubLevel_Driver │ └── Opt_SubLevel_Driver.py ├── LICENSE ├── README.md ├── bin └── .gitignore ├── doc ├── DAKOTA │ ├── CACTUS_DAKOTA.docx │ └── CACTUS_DAKOTA.pdf ├── compile.md ├── paper │ ├── AIAA-2011-147-194.pdf │ ├── Michelen_etal_METS2014.pdf │ └── Wosnik et al CACTUS Model Eval HK Cross Flow Turbine SAND 2016 9787.pdf └── user-guide │ ├── User Guide.pdf │ └── tex │ ├── .gitignore │ ├── User Guide.pdf │ ├── User Guide.tex │ ├── content │ ├── appendix.tex │ ├── details.tex │ ├── input_description.tex │ ├── introduction.tex │ ├── normalization.tex │ ├── output_description.tex │ └── titlepage.tex │ ├── figures │ └── vortex_lattice.png │ └── refs.bib ├── media └── CACTUS.png ├── src ├── AeroCoeffs.f90 ├── BGeomSetup.f90 ├── BV_DynStall.f90 ├── BladeIndVel.f90 ├── BladeLoads.f90 ├── CACTUS.f90 ├── CalcBladeVel.f90 ├── CalcFreestream.f90 ├── CalcIndVel.f90 ├── EndRev.f90 ├── EndTS.f90 ├── InputGeom.f90 ├── LB_DynStall.f90 ├── RotateTurbine.f90 ├── SGeomSetup.f90 ├── SetBoundWake.f90 ├── UpdateBladeVel.f90 ├── UpdateStrutLoads.f90 ├── UpdateTowerVelocity.f90 ├── UpdateWakeVel.f90 ├── UpdateWall.f90 ├── VorIVel.f90 ├── WGeomSetup.f90 ├── WSolnSetup.f90 ├── WriteFieldData.f90 ├── WriteFinalOutput.f90 ├── WriteRegTOutput.f90 ├── WriteWakeElementData.f90 ├── WriteWallData.f90 ├── bsload.f90 ├── conlp.f90 ├── input.f90 ├── mod │ ├── airfoil.f90 │ ├── blade.f90 │ ├── compiler.f90 │ ├── configr.f90 │ ├── dystl.f90 │ ├── element.f90 │ ├── fielddata.f90 │ ├── fnames.f90 │ ├── iecgust.f90 │ ├── output.f90 │ ├── parameters.f90 │ ├── pathseparator.f90 │ ├── probesystem.f90 │ ├── quadsourcepanel.f90 │ ├── regtest.f90 │ ├── shear.f90 │ ├── strut.f90 │ ├── time.f90 │ ├── tower.f90 │ ├── util │ │ ├── geomutils.f90 │ │ ├── pidef.f90 │ │ ├── plot3d.f90 │ │ ├── util.f90 │ │ └── vecutils.f90 │ ├── varscale.f90 │ ├── vortex.f90 │ ├── wake.f90 │ ├── wakedata.f90 │ ├── wallgeom.f90 │ ├── wallsoln.f90 │ └── wallsystem.f90 └── shedvor.f90 └── test ├── Airfoil_Section_Data ├── N63_MCT_rR0p2.dat ├── N63_MCT_rR0p2_3DS.dat ├── N63_MCT_rR0p3.dat ├── N63_MCT_rR0p3_3DS.dat ├── N63_MCT_rR0p4.dat ├── N63_MCT_rR0p4_3DS.dat ├── N63_MCT_rR0p8.dat ├── N63_MCT_rR0p8_3DS.dat ├── NACA_0015.dat ├── NACA_0018.dat ├── NACA_0021.dat ├── Orig Section Data Files │ ├── alsectr.dat │ └── stall.dat ├── S809.dat ├── S809_UAE_rR0p3.dat ├── S809_UAE_rR0p45.dat ├── S809_UAE_rR0p6.dat ├── S809_UAE_rR0p8.dat └── Sandia_001850.dat ├── RegTest ├── .gitignore ├── RegTest1.in ├── RegTest1_RegData_Ex.out ├── RegTest2.in ├── RegTest2_RegData_Ex.out ├── RegTest3.in ├── RegTest3_RegData_Ex.out └── runreg.py ├── TestCase1 └── TestHAWT.in ├── TestCase2 ├── TestVAWT.in └── output_org │ ├── TestVAWT_ElementData.csv │ ├── TestVAWT_Param.csv │ ├── TestVAWT_RevData.csv │ └── TestVAWT_TimeData.csv └── TestGeom ├── TestHAWT.geom ├── TestHAWT.m ├── TestVAWT.geom └── TestVAWT.m /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.pdf binary 3 | *.doc binary 4 | *.docx binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CM/ 2 | *.o 3 | *.mod 4 | *~ 5 | *.u2d 6 | *.suo 7 | *.DS_Store 8 | Thumbs.db 9 | ~*.docx 10 | test/RegTest/__pycache__ 11 | build/ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | jobs: 4 | include: 5 | - os: linux 6 | dist: bionic 7 | - os: osx 8 | osx_image: xcode10.2 # nasty hack to get Python 3 (see https://blog.travis-ci.com/2019-08-07-extensive-python-testing-on-travis-ci) 9 | 10 | addons: 11 | apt: 12 | - gfortran 13 | - libblas-dev 14 | - libopenblas-dev 15 | homebrew: 16 | casks: 17 | - gfortran 18 | packages: 19 | - openblas 20 | - lapack 21 | 22 | install: 23 | - sudo pip install pytest 24 | 25 | script: 26 | - mkdir -p build 27 | - cd build 28 | - cmake -DOPENMP=OFF ../ 29 | - make 30 | - cd .. 31 | - bin/cactus 32 | - cd test/RegTest 33 | - PATH=$PATH:../../bin/ pytest runreg.py 34 | -------------------------------------------------------------------------------- /CACTUS.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{6989167D-11E4-40FE-8C1A-2192A86A7E90}") = "CACTUS", "CACTUS.vfproj", "{2CECD596-E387-4116-85A0-1C3EE0DC9F5A}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {2CECD596-E387-4116-85A0-1C3EE0DC9F5A}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {2CECD596-E387-4116-85A0-1C3EE0DC9F5A}.Debug|Win32.Build.0 = Debug|Win32 14 | {2CECD596-E387-4116-85A0-1C3EE0DC9F5A}.Release|Win32.ActiveCfg = Release|Win32 15 | {2CECD596-E387-4116-85A0-1C3EE0DC9F5A}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /CACTUS.vfproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | project(CACTUS 4 | VERSION 0.0 5 | DESCRIPTION "CACTUS" 6 | HOMEPAGE_URL http://www.github.com/SNL-WaterPower/CACTUS 7 | LANGUAGES Fortran) 8 | 9 | # set compiler-specific options 10 | if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") 11 | set(dialect "-O2 -fdefault-real-8 -ffree-line-length-0 -cpp") 12 | set(bounds "-fbounds-check") 13 | endif() 14 | if(CMAKE_Fortran_COMPILER_ID MATCHES "Intel") 15 | set(dialect "-O2 -r8 -mkl -fpp") 16 | set(bounds "-check bounds") 17 | set(BLA_VENDOR Intel10_64lp) # force use of MKL BLAS 18 | endif() 19 | if(CMAKE_Fortran_COMPILER_ID MATCHES "PGI") 20 | set(dialect "-O -r8 -Mpreprocess -Mvect=sse -Mcache_align -Mpre -Mnoframe -Mlre -Mflushz -Mautoinline") 21 | set(bounds "-C -Mbounds") 22 | endif() 23 | 24 | # find LAPACK, OpenMP 25 | enable_language(C) # For compatibility, some systems complain about this 26 | find_package(LAPACK) 27 | 28 | option(OPENMP "Compile with OpenMP enabled?" ON) 29 | if(OPENMP MATCHES ON) 30 | find_package(OpenMP) 31 | if (NOT OpenMP_Fortran_FOUND) 32 | message(FATAL_ERROR "OPENMP option was set to true (default), but no OpenMP was found. Install OpenMP, or try again with -DOPENMP=OFF. Exiting.") 33 | endif() 34 | string(APPEND dialect " ${OpenMP_Fortran_FLAGS}") 35 | endif() 36 | 37 | list(APPEND CMAKE_Fortran_FLAGS_DEBUG ${bounds}) 38 | list(APPEND CMAKE_Fortran_FLAGS ${dialect}) 39 | 40 | # add VERSION variable 41 | # see https://cmake.org/pipermail/cmake/2018-October/068389.html 42 | find_package(Git QUIET) 43 | 44 | if(GIT_EXECUTABLE) 45 | execute_process( 46 | COMMAND "${GIT_EXECUTABLE}" describe --abbrev=4 --dirty --always --tags 47 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 48 | RESULT_VARIABLE res 49 | OUTPUT_VARIABLE CACTUS_VERSION 50 | # ERROR_QUIET 51 | OUTPUT_STRIP_TRAILING_WHITESPACE) 52 | 53 | set_property(GLOBAL APPEND 54 | PROPERTY CMAKE_CONFIGURE_DEPENDS 55 | "${CMAKE_SOURCE_DIR}/.git/index") 56 | else(NOT GIT_EXECUTABLE) 57 | # no git found 58 | set(CACTUS_VERSION "no-git") 59 | endif() 60 | 61 | # get appropriate path separator 62 | if(MINGW OR WIN32 OR MSYS) 63 | set(SEP "\\") 64 | else() # e.g. Linux 65 | set(SEP "/") 66 | endif() 67 | 68 | # add definitions 69 | add_definitions(-DVERSION="${CACTUS_VERSION}") 70 | add_definitions(-DPATHSEP="${SEP}") 71 | 72 | # create source lists 73 | file(GLOB_RECURSE mod_sources ${PROJECT_SOURCE_DIR}/mod/**.f90) 74 | file(GLOB_RECURSE prog_sources ${PROJECT_SOURCE_DIR}/src/*.f90) 75 | 76 | add_executable(cactus ${prog_sources} ${mod_sources}) 77 | target_compile_options(cactus PUBLIC ${LAPACK_LINKER_FLAGS}) 78 | if(MINGW OR WIN32 OR MSYS) 79 | target_link_libraries(cactus ${LAPACK_LIBRARIES} -static) 80 | else() 81 | target_link_libraries(cactus ${LAPACK_LIBRARIES}) 82 | endif() 83 | 84 | # build to ./bin 85 | set_target_properties(cactus 86 | PROPERTIES 87 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin/") 88 | -------------------------------------------------------------------------------- /DAKOTA/Example1/OptTSR.dak: -------------------------------------------------------------------------------- 1 | # Runs local surrogate based optimization on CACTUS. The local surrogate is fit to the CACTUS cost function using "global" sampling within a trust 2 | # region defined for the surrogate function. This keeps the method from having to use the slightly noisy CACTUS results in gradient calculations. 3 | # This method is faster (especially when using async parallel CACTUS evalutations in the fitting of the surrogate function) and more robust than 4 | # direct gradient optimization on CACTUS output. 5 | # Note: Apparently, surrogate optimization methods can only minimize a cost function (their trust region method fails for maximization)... 6 | 7 | strategy 8 | single_method 9 | tabular_graphics_data 10 | tabular_graphics_file = 'Opt_Data_Out.dat' 11 | method_pointer = 'SBLO' 12 | 13 | method 14 | id_method = 'SBLO' 15 | surrogate_based_local 16 | max_iterations = 50 17 | convergence_tolerance = 1e-02 18 | soft_convergence_limit = 3 # terminate after this number of surrogate model iterations with improvement less than tolerance 19 | trust_region 20 | initial_size = 0.5 21 | minimum_size = 1.0e-2 22 | output silent 23 | # Surrogate model and minimization method pointers 24 | model_pointer = 'SURROGATE' 25 | approx_method_pointer = 'NLP' 26 | 27 | method 28 | id_method = 'NLP' 29 | output silent 30 | dot_bfgs 31 | max_iterations = 50 32 | convergence_tolerance = 1e-8 33 | 34 | model 35 | id_model = 'SURROGATE' 36 | surrogate 37 | global # Surrogate fit with "global" sampling within trust region 38 | # Surrogate function 39 | #polynomial quadratic # simple 40 | gaussian_process surfpack # kriging 41 | # Total samples to take in fitting the surrogate 42 | total_points = 5 # oversample (more than recommended point count) to smooth noisy cost function output 43 | #recommended_points 44 | reuse_samples region # reuse samples that are still in the current trust region 45 | # Sampling method and responses pointers 46 | responses_pointer = 'SURROGATE_RESP' 47 | dace_method_pointer = 'SAMPLING' 48 | # Surrogate model fit diagnostics 49 | #diagnostics 50 | 51 | variables 52 | continuous_design = 1 53 | initial_point = 6 54 | lower_bounds = 2 55 | upper_bounds = 10 56 | descriptors = 'Ut' 57 | continuous_state = 1 58 | initial_state = 52 59 | descriptors = 'RPM' 60 | 61 | responses 62 | id_responses = 'SURROGATE_RESP' 63 | num_objective_functions = 1 64 | # Must apply CACTUS descriptors to the surrogate responses as well as the true responses as these descriptors 65 | # somehow propagate to, and overwrite, the true response descriptors in the input files passed to the analysis driver (probably a bug)... 66 | descriptors = 'CostFunc_MaxCP' 67 | numerical_gradients 68 | method_source dakota 69 | interval_type central 70 | fd_gradient_step_size = 1.e-6 71 | no_hessians 72 | 73 | method 74 | id_method = 'SAMPLING' 75 | model_pointer = 'TRUTH' 76 | dace lhs 77 | seed = 1000 78 | # Set samples to minimum number of new truth model samples to be added to the 79 | # surrogate model basis on each surrogate model iteration. Do a couple per dimension 80 | # to hedge against directional bias potentially introduced by reusing samples... 81 | samples = 2 82 | 83 | model 84 | id_model = 'TRUTH' 85 | single 86 | interface_pointer = 'TRUE_FN' 87 | responses_pointer = 'TRUE_RESP' 88 | 89 | interface 90 | id_interface = 'TRUE_FN' 91 | analysis_drivers = '../Driver/CACTUS_Driver.py' 92 | analysis_components = '/home/jmurray/Project/CACTUS/stable/cactus' 'TestVAWTNom.in' 'N' 93 | fork 94 | parameters_file = 'Inputs.in' 95 | results_file = 'Outputs.out' 96 | # need to tag files for async calculation 97 | file_tag 98 | asynchronous 99 | evaluation_concurrency = 10 100 | 101 | responses 102 | id_responses = 'TRUE_RESP' 103 | num_objective_functions = 1 104 | descriptors = 'CostFunc_MaxCP' 105 | no_gradients 106 | no_hessians 107 | 108 | -------------------------------------------------------------------------------- /DAKOTA/Example1/Opt_Data_Out.dat: -------------------------------------------------------------------------------- 1 | %iter_no Ut CostFunc_MaxCP 2 | 1 6 -0.5131645 3 | 2 6.30782117 -0.5164985 4 | 3 6.30782117 -0.5164985 5 | 4 6.30782117 -0.5164985 6 | -------------------------------------------------------------------------------- /DAKOTA/Example1/ParamTSR.dak: -------------------------------------------------------------------------------- 1 | # DAKOTA parameter study on CACTUS 2 | strategy 3 | tabular_graphics_data 4 | tabular_graphics_file = 'Param_Data_Out.dat' 5 | single_method 6 | 7 | method 8 | multidim_parameter_study 9 | partitions = 16 0 0 10 | 11 | model 12 | single 13 | 14 | variables 15 | continuous_design = 1 16 | lower_bounds = 2 17 | upper_bounds = 10 18 | descriptors = 'Ut' 19 | continuous_state = 2 20 | lower_bounds = 16 0 21 | upper_bounds = 16 0 22 | descriptors = 'nti' 'iut' 23 | 24 | interface 25 | analysis_drivers = '../Driver/CACTUS_Driver.py' 26 | analysis_components = '/home/jmurray/Project/CACTUS/stable/cactus' 'TestVAWTNom.in' 'N' 27 | fork 28 | parameters_file = 'Inputs.in' 29 | results_file = 'Outputs.out' 30 | # need to tag files for async calculation 31 | file_tag 32 | asynchronous 33 | evaluation_concurrency = 10 34 | 35 | responses 36 | num_response_functions = 2 37 | descriptors = 'Cp' 'CostFunc_MaxCP' 38 | no_gradients 39 | no_hessians 40 | 41 | -------------------------------------------------------------------------------- /DAKOTA/Example1/Param_Data_Out.dat: -------------------------------------------------------------------------------- 1 | %eval_id Ut nti iut Cp CostFunc_MaxCP 2 | 1 2 16 0 0.09752095 -0.09752095 3 | 2 2.5 16 0 0.1868504 -0.1868504 4 | 3 3 16 0 0.2996178 -0.2996178 5 | 4 3.5 16 0 0.3673699 -0.3673699 6 | 5 4 16 0 0.4183855 -0.4183855 7 | 6 4.5 16 0 0.4504832 -0.4504832 8 | 7 5 16 0 0.4760848 -0.4760848 9 | 8 5.5 16 0 0.4938385 -0.4938385 10 | 9 6 16 0 0.5131645 -0.5131645 11 | 10 6.5 16 0 0.5157003 -0.5157003 12 | 11 7 16 0 0.5107957 -0.5107957 13 | 12 7.5 16 0 0.5040709 -0.5040709 14 | 13 8 16 0 0.486893 -0.486893 15 | 14 8.5 16 0 0.4607304 -0.4607304 16 | 15 9 16 0 0.4259402 -0.4259402 17 | 16 9.5 16 0 0.3863714 -0.3863714 18 | 17 10 16 0 0.3379339 -0.3379339 19 | -------------------------------------------------------------------------------- /DAKOTA/Example1/TestVAWTNom.in: -------------------------------------------------------------------------------- 1 | &ConfigInputs 2 | 3 | GPFlag = 0 4 | 5 | nr = 10 6 | nti = 16 7 | convrg = .0001 8 | iut = 0 9 | 10 | ifc = 0 11 | ixterm = 0 12 | 13 | ntif = 16 14 | iutf = 1 15 | nric = 9 16 | convrgf = .0001 17 | 18 | /End 19 | 20 | 21 | &CaseInputs 22 | 23 | jbtitle = 'Test VAWT' 24 | 25 | rho = .002378 26 | vis = .3739E-6 27 | tempr = 60.0 28 | hBLRef = 56.57 29 | slex = 0.0 30 | hAG = 15.0 31 | 32 | RPM = 52.0 33 | Ut = 5.0 34 | 35 | ! Turbine geometry 36 | GeomFilePath='../../Test/TestGeom/TestVAWT.geom' 37 | 38 | ! Airfoil section data 39 | nSect = 1 40 | AFDPath = '../../Airfoil_Section_Data/NACA_0015.dat' 41 | 42 | /End 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2013, Sandia Corporation, under the terms of Contract DE-AC04-94AL85000. The U.S. Government retains certain rights in this software. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](media/CACTUS.png) 2 | # CACTUS (Code for Axial and Cross-flow TUrbine Simulation) 3 | 4 | CACTUS (**C**ode for **A**xial and **C**ross-flow **TU**rbine **S**imulations), 5 | developed at Sandia National Laboratories, is a turbine simulation code based on a free wake vortex method. 6 | 7 | 8 | ### Compiling 9 | 10 | CACTUS can be compiled via CMake. For details, see [doc/compile.md](doc/compile.md) 11 | 12 | #### Tests 13 | Simple regression tests are included. After compiling, navigate to `test/RegTest/` and run: 14 | 15 | ``` 16 | PATH=$PATH:../../bin pytest runreg.py 17 | ``` 18 | 19 | ### Directory Structure 20 | 21 | - `bin`: Default target for compiled executables 22 | - `DAKOTA`: DAKOTA drivers (by Jon Murray) and examples 23 | - `doc`: Documentation -- user's manual, install instructions, DAKOTA drivers manual, relevant publications 24 | - `make`: Makefiles for various compilers and platforms 25 | - `src`: Source code 26 | - `test`: Test cases (regression tests, example HAWT/VAWT input files, airfoil files) 27 | 28 | ### Post-processing 29 | 30 | Tools for post-processing data from CACTUS simulations are available in the 31 | [CACTUS-tools](https://github.com/SNL-WaterPower/CACTUS-tools) repository. 32 | 33 | 34 | ### References 35 | 36 | For details about the development of CACTUS, please see 37 | 38 | - Murray, J., and Barone, M., “The Development of CACTUS, a Wind and Marine Turbine Performance Simulation Code,” _49th AIAA Aerospace Sciences Meeting including the New Horizons Forum and Aerospace Exposition_, Reston, Virginia: American Institute of Aeronautics and Astronautics, 2011, pp. 1–21. 39 | 40 | ### Disclaimer 41 | 42 | A CACTUS model V&V studies (Michelen et al. 2014, Wosnik et al. 2016) for cross-flow hydrokinetic turbines demonstrated it accurately predicts performance characteristics for axial-flow turbines, but it should not be used for cross-flow geometries. 43 | 44 | - Michelen, C., V.S. Neary, J. Murray, and M. Barone, M. (2014). CACTUS open-source code for hydrokinetic turbine design and analysis: Model performance evaluation and public dissemination as open-source tool. Proceedings of 2nd Marine Energy Technology Symposium 2014 (METS2014), at the 7th Annual Global Marine Renewable Energy Conference (GMREC 2014), Seattle, WA, April 15-18. 45 | 46 | - Wosnik M., Bachant P., Neary V.S., and A.W. Murphy (2016). Evaluation of Design & Analysis Code, CACTUS, for Predicting Cross-flow Hydrokinetic Turbine Performance. SAND2016-9787, September 2016. 34 pages. 47 | 48 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /doc/DAKOTA/CACTUS_DAKOTA.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/DAKOTA/CACTUS_DAKOTA.docx -------------------------------------------------------------------------------- /doc/DAKOTA/CACTUS_DAKOTA.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/DAKOTA/CACTUS_DAKOTA.pdf -------------------------------------------------------------------------------- /doc/compile.md: -------------------------------------------------------------------------------- 1 | # Compiling CACTUS 2 | 3 | ## General instructions 4 | Compilation requires CMake, which can be installed by your OS package manager, or manually (see https://cmake.org/install/) 5 | 6 | 1. Clone the repo, or download all source files to a folder. 7 | 8 | 2. In the root of the repo, create a `build/` directory, and run `cmake`/`make` 9 | ``` 10 | mkdir -p build 11 | cd build 12 | cmake ../ 13 | make 14 | ``` 15 | This will produce a `cactus` executable in the `bin/` folder. 16 | Support for OpenMP may be disabled by adding the `DOPENMP=OFF` flag to the `cmake` call, as below. 17 | ``` 18 | cmake -DOPENMP=OFF ../ 19 | ``` 20 | 21 | 3. The compilation can be tested with the bundled regression tests. 22 | Navigate into the `test/RegTest/` and run 23 | ``` 24 | PATH=$PATH:../../bin/ pytest ./runreg.py 25 | ``` 26 | This requires a Python 3 installation with the `pytest` package installed. 27 | 28 | ## Windows Tips 29 | For Windows, it is recommended to compile in MingW under MSYS2. 30 | 31 | 1. Install MSYS2 (https://www.msys2.org/) 32 | 2. Open MingW terminal (likely located at `C:\tools\msys64\mingw64.exe`) 33 | 3. Install the following packages using `pacman` 34 | ``` 35 | pacman -Sy git make cmake mingw-w64-x86_64-gcc-fortran mingw-w64-x86_64-lapack 36 | ``` 37 | 4. Follow the general install instructions above. 38 | 39 | For MSYS builds, the executables will be statically linked for portability. 40 | -------------------------------------------------------------------------------- /doc/paper/AIAA-2011-147-194.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/paper/AIAA-2011-147-194.pdf -------------------------------------------------------------------------------- /doc/paper/Michelen_etal_METS2014.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/paper/Michelen_etal_METS2014.pdf -------------------------------------------------------------------------------- /doc/paper/Wosnik et al CACTUS Model Eval HK Cross Flow Turbine SAND 2016 9787.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/paper/Wosnik et al CACTUS Model Eval HK Cross Flow Turbine SAND 2016 9787.pdf -------------------------------------------------------------------------------- /doc/user-guide/User Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/user-guide/User Guide.pdf -------------------------------------------------------------------------------- /doc/user-guide/tex/.gitignore: -------------------------------------------------------------------------------- 1 | ## Core latex/pdflatex auxiliary files: 2 | *.aux 3 | *.lof 4 | *.log 5 | *.lot 6 | *.fls 7 | *.out 8 | *.toc 9 | 10 | ## Intermediate documents: 11 | *.dvi 12 | *-converted-to.* 13 | # these rules might exclude image files for figures etc. 14 | # *.ps 15 | # *.eps 16 | # *.pdf 17 | 18 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 19 | *.bbl 20 | *.bcf 21 | *.blg 22 | *-blx.aux 23 | *-blx.bib 24 | *.brf 25 | *.run.xml 26 | 27 | ## Build tool auxiliary files: 28 | *.fdb_latexmk 29 | *.synctex 30 | *.synctex.gz 31 | *.synctex.gz(busy) 32 | *.pdfsync 33 | -------------------------------------------------------------------------------- /doc/user-guide/tex/User Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/user-guide/tex/User Guide.pdf -------------------------------------------------------------------------------- /doc/user-guide/tex/User Guide.tex: -------------------------------------------------------------------------------- 1 | % !TEX TS-program = pdflatex 2 | % !TEX encoding = UTF-8 Unicode 3 | 4 | % Example of the Memoir class, an alternative to the default LaTeX classes such as article and book, with many added features built into the class itself. 5 | 6 | \documentclass[12pt,a4paper,oneside,obeyspaces]{memoir} % for a long document 7 | % \documentclass[12pt,a4paper,article]{memoir} % for a short document 8 | \usepackage[utf8]{inputenc} % set input encoding to utf8 9 | \usepackage[hidelinks]{hyperref} 10 | \usepackage{listings} 11 | \usepackage{longtable} 12 | \usepackage{cleveref} 13 | \usepackage{graphicx} 14 | \usepackage{amsmath} 15 | \usepackage{color} 16 | \usepackage{booktabs} 17 | \usepackage[table]{xcolor} % to highlight table rows 18 | \usepackage{url} % for typesetting file paths 19 | \usepackage{siunitx} % for units 20 | 21 | % Don't forget to read the Memoir manual: memman.pdf 22 | 23 | \definecolor{highlightcolor}{rgb}{.95,.95,1.0} % highlight color for table rows 24 | \definecolor{lstcolor}{rgb}{.95,.95,0.95} % background color for listings 25 | 26 | % set the options for listing 27 | \sbox0{\small\ttfamily A} 28 | \edef\mybasewidth{\the\wd0 } 29 | \lstset{ 30 | basicstyle=\small\ttfamily,% print whole listing small 31 | columns=fixed, 32 | basewidth=\mybasewidth, 33 | breaklines=true, % break lines 34 | postbreak=\raisebox{0ex}[0ex][0ex]{\ensuremath{\color{red}\hookrightarrow\space}}, % add a red arrow at new broken line 35 | backgroundcolor=\color{lstcolor}, % background color of listing box 36 | framexleftmargin=6pt, % padding 37 | framextopmargin=6pt, % padding 38 | framexrightmargin=6pt, % padding 39 | framexbottommargin=6pt, % padding 40 | frame=tb, framerule=0pt, % padding 41 | } 42 | \newsavebox\lstbox 43 | 44 | % set up appendix 45 | \renewcommand*{\cftappendixname}{Appendix\space} 46 | 47 | % set up command to stretch table height 48 | \newcommand{\ra}[1]{\renewcommand{\arraystretch}{#1}} 49 | 50 | % set up command for degree fahrehnheit 51 | \DeclareSIUnit\Fahrenheit{\degree F} 52 | 53 | %%% Examples of Memoir customization 54 | %%% enable, disable or adjust these as desired 55 | 56 | %%% PAGE DIMENSIONS 57 | % Set up the paper to be as close as possible to both A4 & letter: 58 | \settrimmedsize{11in}{210mm}{*} % letter = 11in tall; a4 = 210mm wide 59 | \setlength{\trimtop}{0pt} 60 | \setlength{\trimedge}{\stockwidth} 61 | \addtolength{\trimedge}{-\paperwidth} 62 | \settypeblocksize{22cm}{16cm}{*} 63 | \setulmargins{*}{*}{*} % 50pt upper margins 64 | \setlrmargins{*}{*}{1} % set the margins to be equal 65 | \checkandfixthelayout 66 | 67 | % This is from memman.pdf 68 | 69 | %%% ToC (table of contents) APPEARANCE 70 | \maxtocdepth{subsection} % include subsections 71 | \renewcommand{\cftchapterpagefont}{} 72 | % \renewcommand{\cftchapterfont}{} % no bold! 73 | 74 | %%% HEADERS & FOOTERS 75 | \pagestyle{ruled} % try also: empty , plain , headings , ruled , Ruled , companion 76 | 77 | %%% CHAPTERS 78 | \chapterstyle{hangnum} % try also: default , section , hangnum , companion , article, demo 79 | 80 | \renewcommand{\chaptitlefont}{\Huge\sffamily\raggedright} % set sans serif chapter title font 81 | \renewcommand{\chapnumfont}{\Huge\sffamily\raggedright} % set sans serif chapter number font 82 | 83 | %%% SECTIONS 84 | \hangsecnum % hang the section numbers into the margin to match \chapterstyle{hangnum} 85 | \maxsecnumdepth{subsection} % number subsections 86 | 87 | \setsecheadstyle{\Large\sffamily\raggedright} % set sans serif section font 88 | \setsubsecheadstyle{\large\sffamily\raggedright} % set sans serif subsection font 89 | 90 | %% END Memoir customization 91 | 92 | %%% BEGIN DOCUMENT 93 | \begin{document} 94 | 95 | % set bib style 96 | \bibliographystyle{plain} 97 | 98 | % render titlepage 99 | \input{./content/titlepage.tex} 100 | 101 | \tableofcontents % the asterisk means that the contents itself isn't put into the ToC 102 | \listoffigures 103 | \listoftables 104 | 105 | \cleartorecto % new page before the first chapter 106 | \ra{1.3} % set the table row spacing for all chapters 107 | \input{./content/introduction.tex} 108 | \input{./content/normalization.tex} 109 | \input{./content/input_description.tex} 110 | \input{./content/output_description.tex} 111 | \input{./content/details.tex} 112 | 113 | \bibliography{refs} 114 | \appendix 115 | \input{./content/appendix.tex} 116 | 117 | \end{document} -------------------------------------------------------------------------------- /doc/user-guide/tex/content/appendix.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../User Guide.tex 2 | \chapter{Example geometry script} 3 | \label{sec:example_geometry_script} 4 | An example geometry creation MATLAB script for is provided here for a vertical axis wind turbine. This example can also be found in Test/TestGeom/TestVAWT.m in the CACTUS repository. 5 | 6 | \begin{lstlisting}[language=matlab] 7 | clear 8 | close all 9 | 10 | % Creates test VAWT geometry file 11 | 12 | % Add geom creation scripts to path 13 | path(path,'../../CreateGeom'); 14 | 15 | % Params 16 | R=31.5; % Center radius (ft) 17 | HR=2.64; % Height to radius ratio 18 | CRr=0.07408; % Root chord to radius 19 | eta=.42; % Blade mount point ratio (mount point behind leading edge as a fraction of chord) 20 | NBlade=2; 21 | NBElem=5; 22 | NStrut=2; % number of struts 23 | NSElem=5; 24 | CRs=CRr; % strut chord to radius 25 | TCs=.15; % strut thickness to chord 26 | 27 | % Output filename 28 | FN='TestVAWT.geom'; 29 | 30 | % Plot data? 31 | PlotTurbine=1; 32 | 33 | % Convert 34 | dToR=pi/180; 35 | 36 | % Create basic parabolic blade VAWT 37 | Type='VAWT'; 38 | BShape=1; 39 | T=CreateTurbine(NBlade,NBElem,NStrut,NSElem,R,[],[],[],Type,1,CRr,HR,eta,BShape,CRs,TCs); 40 | 41 | % Write geom file 42 | WriteTurbineGeom(FN,T); 43 | 44 | % Plot if desired 45 | if PlotTurbine 46 | 47 | % Plot animated turbine rotation 48 | XLim=[-4,4]; 49 | YLim=[-2,4]; 50 | ZLim=[-4,4]; 51 | 52 | % Plot controls 53 | PlotVec=1; 54 | SFVec=.5; 55 | Trans=.5; 56 | 57 | hf=figure(1); 58 | set(hf,'Position',[303 124 956 610]) 59 | set(gca,'Position',[5.2743e-002 5.1245e-002 8.9979e-001 8.8141e-001]) 60 | set(gca,'CameraPosition',[-52.1999 30.4749 62.2119]) 61 | set(gca,'CameraUpVector',[1.8643e-001 9.7433e-001 -1.2615e-001]) 62 | set(gca,'CameraViewAngle',6.3060e+000) 63 | grid on 64 | set(gcf,'Color','white'); 65 | hl=light('Position',[-1,0,0]); 66 | set(gca,'Color','white'); 67 | set(gca,'DataAspectRatio',[1,1,1]) 68 | set(gca,'XLim',XLim,'YLim',YLim,'ZLim',ZLim) 69 | 70 | HIn=[]; 71 | PhasePlot=linspace(0,2*pi,150); 72 | for i=1:length(PhasePlot) 73 | H=PlotTurbineGeom(T,hf,PhasePlot(i),HIn,Trans,PlotVec,SFVec); 74 | HIn=H; 75 | pause(.01); 76 | end 77 | 78 | end 79 | \end{lstlisting} -------------------------------------------------------------------------------- /doc/user-guide/tex/content/details.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../User Guide.tex 2 | \chapter{Details} 3 | \section{OpenMP acceleration} 4 | OpenMP acceleration is enabled in the main Biot-Savart calculation loop as well as in the calculation of field velocities as described in 5.5. OpenMP is enabled by default, provided the appropriate flags are selected during compilation and that the OpenMP libraries are properly linked. 5 | 6 | If OpenMP is enabled, CACTUS will write to standard out a few lines stating so. If CACTUS has not been compiled correctly with OpenMP flags and libraries, or if OpenMP is otherwise disabled, those lines about OpenMP will be omitted. 7 | 8 | If the number of threads displayed is less than the number of cores/threads available on the machine, check that the environment variable \path{OMP_NUM_THREADS} is correctly set. 9 | 10 | \begin{lstlisting}[language=bash] 11 | [phil@localhost CACTUS-CK]$ ./bin/cactus 12 | Starting CACTUS Execution. 13 | -------------------------- 14 | OpenMP is Enabled. 15 | Executing with 2 threads. 16 | 17 | 18 | Please call the program with the name of the input file on the command line. Ex. CACTUS INPUTFILE.in 19 | \end{lstlisting} -------------------------------------------------------------------------------- /doc/user-guide/tex/content/introduction.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../User Guide.tex 2 | \chapter{Introduction and overview} 3 | CACTUS (\textbf{C}ode for \textbf{A}xial and \textbf{C}rossflow \textbf{TU}rbine \textbf{S}imulation) is a turbine performance simulation code using a blade element discretization and a free vortex line description of the turbine wake. The code was originally based on \path{VDART3}, a free vortex wake simulation of the Darrieus wind turbine, developed by Strickland \cite{Strickland1979}. The codebase has been largely upgraded to the Fortran 9x standard, and a number of modifications have been made to the original \path{VDART3} methods including updates to the blade loads models, and new models to handle generic device geometry and marine turbine specific physics. 4 | 5 | This document serves as a user's manual for CACTUS. A brief overview of the code is provided below, however, more detailed information on the methods used can be found in \cite{Murray2011}. 6 | 7 | \section{Blade loads and wake models} 8 | CACTUS simulates a turbine device consisting of an arbitrary configuration of blade element sections. Each section can be assigned arbitrary load coefficient vs. angle of attack characteristics, which typically correspond to two-dimensional lift and drag coefficient data for a particular foil section. Since data from two-dimensional wind tunnel tests or foil performance calculations are used to represent element loads, it is generally assumed that these elements are in locally two-dimensional flow. 9 | 10 | A rotor blade consisting of an arbitrary planform shape and foil sections can be modeled by the synthesis of a number of blade elements. The blade loads and wake of the turbine rotor are evolved in time over a certain number of rotor revolutions, until the revolution-averaged rotor power is converged. The code output includes the blade aerodynamic forces, wake vortex trajectories, and performance metrics such as torque and power. 11 | 12 | CACTUS uses a potential flow model comprised of free vortex line elements to represent the turbine wake flow field. The vortex line structure attached to a single blade element is shown in \Cref{fig:vortex_lattice}. At each point in time, the bound vorticity ($\Gamma_B$) on each blade element is related to the element lift coefficient through the Kutta-Joukowski theorem, and the spanwise ($\Gamma_S$) and trailing vorticity ($\Gamma_T$) are recovered through the application of the Helmholz theorem of conservation of circulation along a vortex line \cite{katz2001low}. 13 | 14 | \begin{figure} 15 | \includegraphics{figures/vortex_lattice.png} 16 | \label{fig:vortex_lattice} 17 | \caption{Blade element with associated vortex lattice system.} 18 | \end{figure} 19 | 20 | \subsection{Dynamic blade loads} 21 | The operational cycle of some turbines, most notably cross-flow turbines or axial flow turbines in yawed flow conditions, cause the turbine blades to operate in dynamically variable flow conditions. The effects of blade rotation with respect to the surrounding fluid and the effects of dynamically variable flow angle of attack are captured with additional models. 22 | 23 | The effects of blade section pitch rate (rotation around an axis normal to the section plane) are captured by analogy to an analytical solution for a pitching flat plate. Improvements have been made to the original methodology used in \path{VDART3}, and modifications have been made to handle non-zero section pitching moment due to cambered foil sections. 24 | 25 | Under certain operational conditions, the turbine blades may operate at angles of attack beyond their steady-state stall limits for significant lengths of time. The transient behavior of the blade section loads during this ``dynamic stalling'' process must be modeled as it is not captured by the steady load coefficient data input for each foil section. The primary effect of dynamic stalling is a delay in the appearance of stalled flow effects on blade loads to higher angles of attack than would be expected in steady flow. 26 | 27 | Two models for dynamic stall effects on blade section loads are included in CACTUS. The modified Boeing-Vertol method of Gormont is the default. This algebraic method approximates dynamic stall effects with a ``lagged'' angle of attack, where the magnitude of the lag is empirically correlated to the angle of attack rate. The Leishman-Beddoes model incorporates more physical models and attempts to model the temporal evolution of dynamic stall flow phenomena and associated effects on blade loads. This model may provide more accurate results than the algebraic Boeing-Vertol method, but requires many more simulation time steps to be taken per turbine revolution to achieve converged results. 28 | 29 | \section{Solid and free-surface boundaries} 30 | CACTUS can simulate the effects of proximity to a ground plane or free (water) surface on turbine performance. The boundary conditions, either zero normal flow for a ground plane or constant surface pressure for a free surface, are applied using rectangular source panel elements. The free surface boundary condition is currently implemented as a quasi-static boundary, allowing it to respond only to the average flow created by the turbine and wake over a full revolution. 31 | 32 | The user is allowed to specify the time step interval between updates to the wall panel system. For the free surface model, the wall update interval specifies the number of time steps between updates to the revolution averaged quantities. It is often not necessary to update these quantities on every time step. If it's possible to reduce the frequency of wall panel system updates and still obtain convergence of the simulation output of interest, this can reduce simulation run time considerably. 33 | 34 | A recent addition to CACTUS is the ability to model \emph{general} solid boundaries, or walls, using generalized quadrilateral source panels. A 3-D mesh file describing the wall geometry must be generated externally and passed as an input to CACTUS. -------------------------------------------------------------------------------- /doc/user-guide/tex/content/normalization.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../User Guide.tex 2 | \chapter{Normalization parameters} 3 | Some definitions of parameters used in normalize CACTUS input and output parameters are given in \Cref{tbl:normalization_parameters}. 4 | 5 | \begin{table}[!htbp] 6 | \centering 7 | \caption{Parameters used for non-dimensionalization.} 8 | \label{tbl:normalization_parameters} 9 | \begin{tabular}{p{0.20\textwidth}p{0.70\textwidth}} 10 | \toprule 11 | Variable & Description \\ 12 | \midrule 13 | $\rho$ & Fluid density \\ 14 | $U_\infty$ & Freestream fluid flow speed \\ 15 | $A_T$ & Turbine reference area. Typically this reference area is chosen to be the projected frontal area of the volume swept by the rotor. \\ 16 | $R$ & Turbine reference radius \\ 17 | $\omega$ & Turbine rotation rate \\ 18 | $U_\textrm{local}$& Fluid flow speed local to an element. \\ 19 | $U_\textrm{tip}$ & Turbine tip speed. Defined as $U_\textrm{tip} = \omega R$. \\ 20 | $c$ & Element chord length \\ 21 | $A_E$ & Element planform area \\ 22 | \bottomrule 23 | \end{tabular} 24 | \end{table} 25 | 26 | In general, most parameters have been normalized at the machine scale. Unless otherwise noted in the input and output descriptions below, the force, torque, and power coefficients are normalized as: 27 | 28 | $$ C_F = \frac{F}{\frac{1}{2} \rho U_\infty^2 A_T} $$ 29 | $$ C_T = \frac{T}{\frac{1}{2} \rho U_\infty^2 A_T R} $$ 30 | $$ C_P = \frac{P}{\frac{1}{2} \rho U_\infty^3 A_T} $$ -------------------------------------------------------------------------------- /doc/user-guide/tex/content/titlepage.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../User Guide.tex 2 | \begin{titlingpage} 3 | \clearpage\thispagestyle{empty} 4 | 5 | \begin{center} 6 | {\sffamily\huge CACTUS User Guide\\} 7 | % ---------------------------------------------------------------- 8 | \vspace{1.5cm} 9 | {\Large Jonathan C. Murray \\ Aerosciences Department}\\[5pt] 10 | 11 | \vspace{1.5cm} 12 | {\Large Matthew Barone \\ Wind Energy Technologies}\\[5pt] 13 | 14 | \vspace{1.5cm} 15 | {\Large Phillip K. Chiu \\ Wind Energy Technologies}\\[5pt] 16 | % ---------------------------------------------------------------- 17 | \vspace{8cm} 18 | {\Large Sandia National Laboratories \\ 19 | P.O. Box 5800 \\ 20 | Albuquerque, NM 87185 \\} 21 | % ---------------------------------------------------------------- 22 | \end{center} 23 | \end{titlingpage} -------------------------------------------------------------------------------- /doc/user-guide/tex/figures/vortex_lattice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/doc/user-guide/tex/figures/vortex_lattice.png -------------------------------------------------------------------------------- /doc/user-guide/tex/refs.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{Murray2011, 2 | address = {Reston, Virginia}, 3 | author = {Murray, Jonathan and Barone, Matthew}, 4 | booktitle = {49th AIAA Aerospace Sciences Meeting including the New Horizons Forum and Aerospace Exposition}, 5 | doi = {10.2514/6.2011-147}, 6 | file = {:/snl/Home/pchiu/papers/AIAA-2011-147-194.pdf:pdf}, 7 | isbn = {978-1-60086-950-1}, 8 | mendeley-groups = {Sandia,Prospectus}, 9 | month = jan, 10 | number = {January}, 11 | pages = {1--21}, 12 | publisher = {American Institute of Aeronautics and Astronautics}, 13 | title = {{The Development of CACTUS, a Wind and Marine Turbine Performance Simulation Code}}, 14 | url = {http://arc.aiaa.org/doi/abs/10.2514/6.2011-147}, 15 | year = {2011} 16 | } 17 | @book{katz2001low, 18 | author = {Katz, Joseph and Plotkin, Allen}, 19 | publisher = {Cambridge University Press}, 20 | title = {{Low-speed aerodynamics}}, 21 | year = {2001} 22 | } 23 | @article{Strickland1979, 24 | author = {Strickland, J. H. and Webster, B. T. and Nguyen, T.}, 25 | doi = {10.1115/1.3449018}, 26 | issn = {00982202}, 27 | journal = {Journal of Fluids Engineering}, 28 | number = {4}, 29 | pages = {500}, 30 | title = {{A Vortex Model of the Darrieus Turbine: An Analytical and Experimental Study}}, 31 | url = {http://fluidsengineering.asmedigitalcollection.asme.org/article.aspx?articleid=1424807}, 32 | volume = {101}, 33 | year = {1979} 34 | } -------------------------------------------------------------------------------- /media/CACTUS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/CACTUS/4ae4fafdf1d598428d9df43f1951a80170dc7780/media/CACTUS.png -------------------------------------------------------------------------------- /src/AeroCoeffs.f90: -------------------------------------------------------------------------------- 1 | subroutine AeroCoeffs(nElem,alpha75,alpha5,Re,wPNorm,adotnorm,umach,SectInd,IsBE,CL,CD,CN,CT,CLCirc,CM25) 2 | 3 | use airfoil 4 | use dystl 5 | use pidef 6 | 7 | implicit none 8 | 9 | integer :: SectInd, IsBE, nElem 10 | real :: alpha75, alpha5, adotnorm, wPNorm, Re, umach, CL, CD, CN, CT, CLCirc 11 | real :: CLstat75, CLstat5, CDstat75, CLdyn5, CDdyn5, dCLAD, dCTAM, dCNAM, CL5, CD5, C, C1, CM25stat, CM25 12 | real :: alphaL, alphaD, aref, Fac 13 | 14 | 15 | ! Calc static characteristics 16 | CALL intp(Re,alpha75*condeg,CLstat75,CDstat75,CM25stat,SectInd) 17 | CALL intp(Re,alpha5*condeg,CLstat5,C,C1,SectInd) 18 | 19 | ! Apply pitch rate effects by analogy to pitching flat plate potential flow theory (SAND report) 20 | CL5=CLstat75 21 | CD5=CDstat75 22 | CM25=CM25stat+cos(alpha5)*(CLstat75-CLstat5)/4.0 23 | alphaL=alpha75 24 | CLCirc=CLstat75 25 | 26 | ! If no dynamic stall, use static values, else calc dynamic stall 27 | if (DSFlag/=0) then 28 | 29 | if (DSFlag==1) then 30 | ! Modified Boeing-Vertol approach 31 | Call BV_DynStall(nElem,CL5,CD5,alphaL,adotnorm,umach,Re,SectInd,CLdyn5,CDdyn5) 32 | else 33 | ! Leishman-Beddoes model 34 | Call LB_DynStall(nElem,CL5,CD5,alphaL,alpha5,umach,Re,SectInd,CLdyn5,CDdyn5) 35 | end if 36 | 37 | CL5=CLdyn5 38 | CD5=CDdyn5 39 | CLCirc=CLdyn5 40 | 41 | end if 42 | 43 | ! Tangential and normal coeffs 44 | CN=CL5*cos(alpha5)+CD5*sin(alpha5) 45 | CT=-CL5*sin(alpha5)+CD5*cos(alpha5) 46 | 47 | ! Calc tangential added mass increment by analogy to pitching flat plate potential flow theory (SAND report) 48 | dCTAM=2.0/cos(alpha5)*wPNorm*CM25stat-CLstat5/2.0*wPNorm 49 | ! Add in alphadot added mass effects (Theodorsen flat plate approx., Katz ch. 13) 50 | dCLAD=pi*adotnorm 51 | dCTAM=dCTAM-dCLAD*sin(alpha5) 52 | dCNAM=dCLAD*cos(alpha5) 53 | 54 | ! Add in added mass effects at low AOA (models not accurate at high AOA) 55 | Fac=1.0 56 | aref=abs(alpha5) 57 | if ((aref > pi/4.0) .AND. (aref < 3.0*pi/4.0)) then 58 | Fac = abs(1-4.0/pi*(aref-pi/4.0)) 59 | end if 60 | CT=CT+Fac*dCTAM 61 | CN=CN+Fac*dCNAM 62 | 63 | ! Calc total lift and drag coefficient based on flow direction at half-chord for reference 64 | CL=CN*cos(alpha5)-CT*sin(alpha5) 65 | CD=CN*sin(alpha5)+CT*cos(alpha5) 66 | 67 | return 68 | end subroutine AeroCoeffs 69 | -------------------------------------------------------------------------------- /src/BGeomSetup.f90: -------------------------------------------------------------------------------- 1 | subroutine BGeomSetup() 2 | 3 | use parameters 4 | 5 | use configr 6 | use element 7 | use pidef 8 | use util 9 | 10 | implicit none 11 | 12 | integer i, j, nei, nej 13 | real dx, dy, dz, vMag, ds 14 | 15 | ! Sets up blade geometry arrays for each blade, starting from the root of the first blade, and continuing for each blade. 16 | 17 | ! Set reference cr 18 | CrRef=0.0 19 | do i=1,nb 20 | do j=1,nbe 21 | CrRef=max(CrRef,Blades(i)%ECtoR(j)) 22 | end do 23 | end do 24 | 25 | ! Set representative geometry discretization level (for vortex core calculation) 26 | dSGeom=0.0 27 | do i=1,nb 28 | do j=1,nbe 29 | dx=Blades(i)%QCx(j+1)-Blades(i)%QCx(j) 30 | dy=Blades(i)%QCy(j+1)-Blades(i)%QCy(j) 31 | dz=Blades(i)%QCz(j+1)-Blades(i)%QCz(j) 32 | ds=sqrt(dx**2+dy**2+dz**2) 33 | dsGeom=max(dsGeom,ds) 34 | end do 35 | end do 36 | 37 | ! Set initial turbine geometry (zero theta) 38 | do i=1,nb 39 | 40 | nei=1+(i-1)*(nbe+1) 41 | 42 | ! Set blade end geometry 43 | do j=0,nbe 44 | nej=nei+j ! element index 45 | 46 | xBE(nej)=Blades(i)%QCx(j+1) 47 | yBE(nej)=Blades(i)%QCy(j+1) 48 | zBE(nej)=Blades(i)%QCz(j+1) 49 | 50 | txBE(nej)=Blades(i)%tx(j+1) 51 | tyBE(nej)=Blades(i)%ty(j+1) 52 | tzBE(nej)=Blades(i)%tz(j+1) 53 | 54 | CtoR(nej)=Blades(i)%CtoR(j+1) 55 | end do 56 | 57 | 58 | ! Calc element geometry 59 | 60 | ! JCM: currently, although these values are for each element, they are held in arrays sized for element ends, where the first value for each blade 61 | ! is simply ignored in bsload (where these values are used). 62 | 63 | ! JCM: These zeros are ignored... 64 | eArea(nei)=0.0 65 | eChord(nei)=0.0 66 | iSect(nei)=0.0 67 | xBC(nei)=0.0 68 | yBC(nei)=0.0 69 | zBC(nei)=0.0 70 | nxBC(nei)=0.0 71 | nyBC(nei)=0.0 72 | nzBC(nei)=0.0 73 | txBC(nei)=0.0 74 | tyBC(nei)=0.0 75 | tzBC(nei)=0.0 76 | sxBC(nei)=0.0 77 | syBC(nei)=0.0 78 | szBC(nei)=0.0 79 | Call CalcBEGeom(i) 80 | 81 | do j=1,nbe 82 | nej=nei+j 83 | iSect(nej)=Blades(i)%iSect(j) 84 | end do 85 | 86 | end do 87 | 88 | return 89 | end subroutine BGeomSetup 90 | -------------------------------------------------------------------------------- /src/BV_DynStall.f90: -------------------------------------------------------------------------------- 1 | subroutine BV_DynStall(nElem,CLstat,CDstat,alpha,adotnorm,umach,Re,SectInd,CL,CD) 2 | 3 | use airfoil 4 | use dystl 5 | use pidef 6 | 7 | implicit none 8 | 9 | real :: CLstat, CDstat, CL, CD, alpha, adotnorm, umach, Re 10 | integer :: SectInd, nElem 11 | 12 | integer :: isgn 13 | real :: dalphaRefMax, TransA, dalphaLRef, dalphaDRef, dalphaL, dalphaD, Fac 14 | real :: diff, smachl, hmachl, gammaxl, dgammal, smachm, hmachm, gammaxm, dgammam, gammal, gammam 15 | real :: alssn, alssp, alrefL, alLagD, alrefD, delN, delP, C, C1, AOA0 16 | 17 | ! Calculate the static stall envelope limits and other params 18 | CALL CalcBVStallAOALim(Re,SectInd,alssp,alssn) 19 | AOA0=alzer(SectInd) 20 | diff=0.06-tc(SectInd) 21 | smachl=0.4+5.0*diff 22 | hmachl=0.9+2.5*diff 23 | gammaxl=1.4-6.0*diff 24 | dgammal=gammaxl/(hmachl-smachl) 25 | smachm=0.2 26 | hmachm=0.7+2.5*diff 27 | gammaxm=1.0-2.5*diff 28 | dgammam=gammaxm/(hmachm-smachm) 29 | 30 | ! Limit reference dalpha to a maximum to keep sign of CL the same for 31 | ! alpha and lagged alpha (considered a reasonable lag...). Note: 32 | ! magnitude increasing and decreasing effect ratios are maintained. 33 | Fac=.9 ! Margin to ensure that dalphaRef is never large enough to make alrefL == AOA0 (blows up linear expansion model) 34 | dalphaRefMax=Fac*min(abs(alssp-AOA0),abs(alssn-AOA0))/max(k1pos,k1neg) 35 | TransA=.5*dalphaRefMax ! transition region for fairing lagged AOA in pure lag model 36 | 37 | isgn=sign(1.0,adotnorm) 38 | 39 | ! Modified Boeing-Vertol approach 40 | 41 | ! Lift 42 | gammal=gammaxl-(umach-smachl)*dgammal 43 | dalphaLRef=gammal*sqrt(abs(adotnorm)) 44 | dalphaLRef=min(dalphaLRef,dalphaRefMax) 45 | 46 | if ((adotnorm*(alpha-AOA0)) < 0.0) then 47 | ! Magnitude of CL decreasing 48 | dalphaL=k1neg*dalphaLRef 49 | alrefL=alpha-dalphaL*isgn 50 | 51 | ! Only switch DS off using lagged alpha 52 | if (BV_DynamicFlagL(nElem) == 1 .AND. (alrefL > alssn .AND. alrefL < alssp)) then 53 | BV_DynamicFlagL(nElem)=0 54 | end if 55 | 56 | else 57 | ! Magnitude of CL increasing 58 | dalphaL=dalphaLRef*k1pos 59 | alrefL=alpha-dalphaL*isgn 60 | 61 | ! switch DS on or off using alpha 62 | if (alpha <= alssn .OR. alpha >= alssp) then 63 | BV_DynamicFlagL(nElem)=1 64 | else 65 | BV_DynamicFlagL(nElem)=0 66 | end if 67 | end if 68 | 69 | ! Drag 70 | gammam=gammaxm-(umach-smachm)*dgammam 71 | if (umach < smachm) then 72 | gammam=gammaxm 73 | end if 74 | dalphaDRef=gammam*sqrt(abs(adotnorm)) 75 | dalphaDRef=min(dalphaDRef,dalphaRefMax) 76 | 77 | if ((adotnorm*(alpha-AOA0)) < 0.0) then 78 | ! Magnitude of CL decreasing 79 | dalphaD=k1neg*dalphaDRef 80 | alLagD=alpha-dalphaD*isgn 81 | 82 | ! Only switch DS off using lagged alpha 83 | if (BV_DynamicFlagD(nElem) == 1) then 84 | delN=alssn-alLagD 85 | delP=alLagD-alssp 86 | else 87 | delN=0.0 88 | delP=0.0 89 | end if 90 | else 91 | ! Magnitude of CL increasing 92 | dalphaD=dalphaDRef*k1pos 93 | alLagD=alpha-dalphaD*isgn 94 | 95 | ! switch DS on or off using alpha 96 | delN=alssn-alpha 97 | delP=alpha-alssp 98 | end if 99 | 100 | if (delN > TransA .OR. delP > TransA) then 101 | alrefD=alLagD 102 | BV_DynamicFlagD(nElem)=1 103 | elseif (delN > 0 .AND. delN < TransA) then 104 | ! Transition region (fairing effect...) 105 | alrefD=alpha+(alLagD-alpha)*delN/TransA 106 | BV_DynamicFlagD(nElem)=1 107 | elseif (delP > 0 .AND. delP < TransA) then 108 | ! Transition region (fairing effect...) 109 | alrefD=alpha+(alLagD-alpha)*delP/TransA 110 | BV_DynamicFlagD(nElem)=1 111 | else 112 | BV_DynamicFlagD(nElem)=0 113 | end if 114 | 115 | ! Static or dynamic model 116 | if (BV_DynamicFlagL(nElem) == 1) then 117 | ! Dynamic stall characteristics 118 | ! Linear expansion model for linear region coeffs 119 | CALL intp(Re,alrefL*condeg,CL,C,C1,SectInd) 120 | CL=CL/(alrefL-AOA0)*(alpha-AOA0) 121 | else 122 | ! Static characteristics 123 | CL=CLstat 124 | end if 125 | 126 | if (BV_DynamicFlagD(nElem) == 1) then 127 | ! Dynamic characteristics 128 | ! Pure lag model for drag 129 | CALL intp(Re,alrefD*condeg,C,CD,C1,SectInd) 130 | else 131 | ! Static characteristics 132 | CD=CDstat 133 | end if 134 | 135 | 136 | ! Diagnostic output 137 | BV_alpha=alpha*condeg 138 | BV_adotnorm=adotnorm 139 | BV_alrefL=alrefL*condeg 140 | BV_alrefD=alrefD*condeg 141 | 142 | return 143 | end subroutine BV_DynStall 144 | -------------------------------------------------------------------------------- /src/BladeIndVel.f90: -------------------------------------------------------------------------------- 1 | subroutine BladeIndVel(NT,ntTerm,NBE,NB,NE,XP,YP,ZP,UP,VP,WP,DUDX,Mode,CalcDer) 2 | 3 | use blade 4 | use wake 5 | 6 | integer :: nt, ntTerm, nbe, nb, ne, Mode, CalcDer 7 | real :: XP, YP, ZP, UP, VP, WP, DUDX 8 | integer :: i, j, k, nei, nej, nt1 9 | integer :: VFlag 10 | 11 | ! COMPUTE THE VORTEX INDUCED VELOCITY AT POINT XP,YP,ZP FROM THE BLADE SYSTEM 12 | ! Mode: 0 -> Calc whole system 13 | ! 1 -> Calc bound vorticity only 14 | ! 2 -> Calc whole system minus bound vorticity 15 | ! CalcDer: 1 to calc DUDX 16 | 17 | NT1=NT-1 18 | UP=0.0 19 | VP=0.0 20 | WP=0.0 21 | DUDX=0.0 22 | 23 | if (Mode == 0) then 24 | ! Calc induced velocity from the whole blade system (bound, spanwise, and trailing wake vorticity) 25 | 26 | ! CALCULATE THE VELOCITY CONTRIBUTIONS DUE TO TRAILING VORTICIES ( GT(1:NT-1,:) ) 27 | ! ntTerm represents the furthest away wake elements that are to be considered. (Calculated using user input xstop) 28 | VFlag=1 29 | do i=1,ne 30 | do j=ntTerm,NT1 31 | Call VorIVel(VFlag,CalcDer,GT(j,i),X(j,i),Y(j,i),Z(j,i),X(j+1,i),Y(j+1,i),Z(j+1,i),XP,YP,ZP,UP,VP,WP,DUDX) 32 | end do 33 | end do 34 | 35 | ! CALCULATE THE VELOCITY CONTRIBUTIONS DUE TO SPANWISE VORTICIES, including current bound vorticity ( GS(1:NT,:) ) 36 | do i=1,nb 37 | nei=(i-1)*(nbe+1) 38 | do j=1,nbe 39 | nej=nei+j 40 | do k=ntTerm,NT 41 | if (k==NT) then 42 | VFlag=0 43 | else 44 | VFlag=2 45 | end if 46 | Call VorIVel(VFlag,CalcDer,GS(k,nej),X(k,nej),Y(k,nej),Z(k,nej),X(k,nej+1),Y(k,nej+1),Z(k,nej+1),XP,YP,ZP,UP,VP,WP,DUDX) 47 | end do 48 | end do 49 | end do 50 | 51 | else if (Mode == 1) then 52 | ! Calc induced velocity from bound vorticity only 53 | 54 | VFlag=0 55 | do i=1,nb 56 | nei=(i-1)*(nbe+1) 57 | do j=1,nbe 58 | nej=nei+j 59 | k=NT 60 | Call VorIVel(VFlag,CalcDer,GS(k,nej),X(k,nej),Y(k,nej),Z(k,nej),X(k,nej+1),Y(k,nej+1),Z(k,nej+1),XP,YP,ZP,UP,VP,WP,DUDX) 61 | end do 62 | end do 63 | 64 | else 65 | ! Calc induced velocity from spanwise and trailing wake vorticity (exclude bound vorticity) 66 | 67 | ! CALCULATE THE VELOCITY CONTRIBUTIONS DUE TO TRAILING VORTICIES ( GT(1:NT-1,:) ) 68 | ! ntTerm represents the furthest away wake elements that are to be considered. (Calculated using user input xstop) 69 | VFlag=1 70 | do i=1,ne 71 | do j=ntTerm,NT1 72 | Call VorIVel(VFlag,CalcDer,GT(j,i),X(j,i),Y(j,i),Z(j,i),X(j+1,i),Y(j+1,i),Z(j+1,i),XP,YP,ZP,UP,VP,WP,DUDX) 73 | end do 74 | end do 75 | 76 | ! CALCULATE THE VELOCITY CONTRIBUTIONS DUE TO SPANWISE VORTICIES, NOT including current bound vorticity ( GS(1:NT-1,:) ) 77 | VFlag=2 78 | do i=1,nb 79 | nei=(i-1)*(nbe+1) 80 | do j=1,nbe 81 | nej=nei+j 82 | do k=ntTerm,NT1 83 | Call VorIVel(VFlag,CalcDer,GS(k,nej),X(k,nej),Y(k,nej),Z(k,nej),X(k,nej+1),Y(k,nej+1),Z(k,nej+1),XP,YP,ZP,UP,VP,WP,DUDX) 84 | end do 85 | end do 86 | end do 87 | 88 | end if 89 | 90 | return 91 | end subroutine BladeIndVel 92 | -------------------------------------------------------------------------------- /src/CalcBladeVel.f90: -------------------------------------------------------------------------------- 1 | subroutine CalcBladeVel(wx,wy,wz,rx,ry,rz,uBlade,vBlade,wBlade) 2 | 3 | use util 4 | 5 | real wx,wy,wz,rx,ry,rz,uBlade,vBlade,wBlade 6 | 7 | ! Blade rotation velocity (w x r) 8 | CALL cross(wx,wy,wz,rx,ry,rz,uBlade,vBlade,wBlade) 9 | 10 | return 11 | end subroutine CalcBladeVel 12 | -------------------------------------------------------------------------------- /src/CalcFreestream.f90: -------------------------------------------------------------------------------- 1 | subroutine CalcFreestream(xElem,yElem,zElem,u,v,w,ygcErr) 2 | 3 | use shear 4 | use configr 5 | use iecgust 6 | use tower 7 | 8 | real u, v, w 9 | real xElem,yElem,zElem 10 | integer ygcErr 11 | 12 | real IECGustVel, Vxtower 13 | 14 | ! Freestream velocity (with ground shear model) 15 | ! At y/R = 0, u/Uinf = 0. At y/R = yref, u/Uinf = 1 16 | ! slex = 0 : Constant freestream 17 | ! slex = 1/2 : Laminar shear layer (approx) 18 | ! slex = 1/7 : Turbulent shear layer (approx) 19 | 20 | if (Igust .EQ. 1) then 21 | u = IECGustVel((nt-1)*dt,xElem) 22 | v = 0.0 23 | w = 0.0 24 | !Write(*,'(F15.8)') u 25 | return 26 | end if 27 | 28 | if ((yElem+ygc) <= 0.0) then 29 | ! reflect across zero... 30 | ygcErr=1 31 | u=(-(yElem+ygc)/yref)**slex 32 | u=max(u,.01) ! limit to some non zero value... 33 | v=0.0 34 | w=0.0 35 | else 36 | u=((yElem+ygc)/yref)**slex 37 | u=max(u,.01) ! limit to some non zero value... 38 | v=0.0 39 | w=0.0 40 | end if 41 | 42 | if (Itower .EQ. 1) then 43 | Vxtower = wake_defect_velocity(xElem,yElem,zElem) 44 | !Write(20,'(4F20.12)') xElem,yElem,zElem,Vxtower 45 | u = u - Vxtower 46 | u = max(u,.01) 47 | !If (Vxtower .GT. .001) Write(20,'(F20.12)') u+Vxtower, u 48 | end if 49 | 50 | return 51 | end subroutine CalcFreestream 52 | 53 | 54 | REAL FUNCTION IECGustVel(time,x) 55 | 56 | Use iecgust 57 | Use pidef 58 | 59 | real time, x 60 | 61 | real tr 62 | 63 | tr = time - x - gustX0 64 | 65 | if ((tr .LE. gustT) .AND. (tr .GT. 0)) then 66 | IECGustVel = 1.0 - 0.37 * gustA * sin(3*pi*tr/gustT) & 67 | * (1.0 - cos(2*pi*tr/gustT)) 68 | !Write(*,'(2F20.12)') tr,IECGustVel 69 | else 70 | IECGustVel = 1.0 71 | end if 72 | 73 | return 74 | 75 | End FUNCTION IECGustVel 76 | -------------------------------------------------------------------------------- /src/CalcIndVel.f90: -------------------------------------------------------------------------------- 1 | subroutine CalcIndVel(NT,ntTerm,NBE,NB,NE,Px,Py,Pz,Vx,Vy,Vz) 2 | 3 | use wallsoln 4 | use blade 5 | 6 | ! Calculate wall and wake induced velocity (including bound vorticity component) 7 | 8 | real :: Px, Py, Pz, Vx, Vy, Vz, dUdX 9 | integer :: nt, ntTerm, nbe, nb, ne 10 | 11 | real :: Point(3), dVel(3), Vel(3) 12 | 13 | ! Calc wake induced velocity at wake locations 14 | CALL BladeIndVel(NT,ntTerm,NBE,NB,NE,Px,Py,Pz,Vx,Vy,Vz,dUdX,0,0) 15 | 16 | ! Calculate wall induced velocities at wake locations 17 | Point=[Px,Py,Pz] 18 | Call WallIndVel(Point,dVel) 19 | Vx=Vx+dVel(1) 20 | Vy=Vy+dVel(2) 21 | Vz=Vz+dVel(3) 22 | 23 | return 24 | end subroutine CalcIndVel 25 | 26 | -------------------------------------------------------------------------------- /src/EndRev.f90: -------------------------------------------------------------------------------- 1 | subroutine EndRev() 2 | 3 | use util 4 | use configr 5 | use output 6 | use time 7 | use fnames 8 | 9 | Call cpu_time(time2) 10 | !$ time2 = omp_get_wtime() 11 | 12 | dtime=time2-time1 13 | etime=time2-t0 14 | time1=etime 15 | !$ time1=omp_get_wtime() 16 | 17 | ! Calc average power over last revolution 18 | CPAve=CPSum/nti 19 | CTRAve=CTRSum/nti 20 | CFxAve=CFxSum/nti 21 | CFyAve=CFySum/nti 22 | CFzAve=CFzSum/nti 23 | ! Torque in ft-lbs 24 | TorqueAve=CTRAve*TorqueC 25 | ! Power coefficient based on tip speed 26 | KPAve=CPAve/ut**3 27 | ! Power in kW 28 | PowerAve=KPave*PowerC 29 | 30 | ! Set revolution average output 31 | RevOutData(1,1)=irev 32 | RevOutData(1,2)=CPAve 33 | RevOutData(1,3)=KPAve 34 | RevOutData(1,4)=CTRAve 35 | RevOutData(1,5)=CFxAve 36 | RevOutData(1,6)=CFyAve 37 | RevOutData(1,7)=CFzAve 38 | RevOutData(1,8)=PowerAve 39 | RevOutData(1,9)=TorqueAve 40 | 41 | ! Write to revolution average data csv file 42 | OPEN(9, FILE=RevOutputFN, POSITION='append') 43 | Call csvwrite(9,RevOutHead,RevOutData,0,1) 44 | CLOSE(9) 45 | 46 | ! Reset rev average sums 47 | CPSum=0.0 48 | CTRSum=0.0 49 | CFxSum=0.0 50 | CFySum=0.0 51 | CFzSum=0.0 52 | 53 | return 54 | end subroutine EndRev 55 | -------------------------------------------------------------------------------- /src/EndTS.f90: -------------------------------------------------------------------------------- 1 | subroutine EndTS() 2 | 3 | use util 4 | use configr 5 | use output 6 | use element 7 | use strut 8 | use regtest 9 | 10 | Implicit None 11 | 12 | real :: CP, CTR, CFx, CFy, CFz 13 | integer :: i, offset 14 | 15 | ! Collect timestep results and compile output 16 | 17 | ! Machine output 18 | CP=CP_B+CP_S 19 | CTR=CTR_B+CTR_S 20 | CFx=CFx_B+CFx_S 21 | CFy=CFy_B+CFy_S 22 | CFz=CFz_B+CFz_S 23 | 24 | ! Sums for rev averages 25 | CPSum=CPSum+CP 26 | CTRSum=CTRSum+CTR 27 | CFxSum=CFxSum+CFx 28 | CFySum=CFySum+CFy 29 | CFzSum=CFzSum+CFz 30 | 31 | TSOutData(1,1)=TimeN ! Normalized simulation time (t*Uinf/Rmax) 32 | TSOutData(1,2)=Theta ! Turbine phase angle (rad) 33 | TSOutData(1,3)=irev 34 | TSOutData(1,4)=CTR ! Torque coeff 35 | TSOutData(1,5)=CP ! Power coeff 36 | TSOutData(1,6)=CFx ! Fx coeff 37 | TSOutData(1,7)=CFy ! Fy coeff 38 | TSOutData(1,8)=CFz ! Fz coeff 39 | ! Blade output 40 | do i=1,nb 41 | offset=8+(i-1)*4 42 | TSOutData(1,offset+1)=Blades(i)%CFx ! Blade Fx coeff 43 | TSOutData(1,offset+2)=Blades(i)%CFy ! Blade Fy coeff 44 | TSOutData(1,offset+3)=Blades(i)%CFz ! Blade Fz coeff 45 | TSOutData(1,offset+4)=Blades(i)%CTR ! Blade torque coeff 46 | end do 47 | do i=1,NStrut 48 | offset=8+nb*4+(i-1)*4 49 | TSOutData(1,offset+1)=Struts(i)%CFx ! Strut Fx coeff 50 | TSOutData(1,offset+2)=Struts(i)%CFy ! Strut Fy coeff 51 | TSOutData(1,offset+3)=Struts(i)%CFz ! Strut Fz coeff 52 | TSOutData(1,offset+4)=Struts(i)%CTR ! Strut torque coeff 53 | end do 54 | 55 | ! Write to timestep data csv file 56 | Call csvwrite(10,TSOutHead,TSOutData,0,1) 57 | 58 | ! Write to element loads data csv file 59 | if (BladeElemOutFlag == 1) then 60 | Call csvwrite(11,BladeElemOutHead,BladeElemOutData,0,-1) 61 | end if 62 | 63 | ! Write to dynamic stall diagnostic data csv file 64 | if (DynStallOutFlag == 1) then 65 | if (DynStallOutType == 1) then 66 | Call csvwrite(16,DynStallOutBVHead,DynStallOutBVData,0,-1) 67 | else if (DynStallOutType == 2) then 68 | Call csvwrite(16,DynStallOutLBHead,DynStallOutLBData,0,-1) 69 | end if 70 | end if 71 | 72 | ! Reg test 73 | if (RegTFlag == 1) then 74 | Reg_CPOut=CP 75 | end if 76 | 77 | return 78 | end subroutine EndTS 79 | -------------------------------------------------------------------------------- /src/InputGeom.f90: -------------------------------------------------------------------------------- 1 | subroutine InputGeom(FN) 2 | 3 | use element 4 | use strut 5 | use varscale 6 | use configr 7 | 8 | implicit none 9 | 10 | integer, parameter :: MaxReadLine = 1000 11 | character(MaxReadLine) :: FN ! path to geometry input file 12 | 13 | integer :: NElem, i 14 | character(MaxReadLine) :: ReadLine 15 | 16 | ! Read geometry data file 17 | 18 | ! Format example: 19 | ! NBlade: 3 20 | ! NStrut: 3 21 | ! RotN: 0 0 1 22 | ! RotP: 0 0 0 23 | ! RefAR: 2.0 24 | ! RefR: 10.0 25 | ! Type: VAWT 26 | ! Blade 1: 27 | ! NElem: 5 28 | ! FlipN: 0 29 | ! QCx: 0 0 0 0 0 0 30 | ! QCy: 1 2 3 4 5 6 31 | ! QCz: 1 1 1 1 1 1 32 | ! tx: 1 1 1 1 1 1 33 | ! ty: 0 0 0 0 0 0 34 | ! tz: 0 0 0 0 0 0 35 | ! CtoR: .1 .1 .1 .1 .1 .1 36 | ! PEx: 0 0 0 0 0 37 | ! PEy: 1 2 3 4 5 38 | ! PEz: 1 1 1 1 1 39 | ! tEx: 0 0 0 0 0 40 | ! tEy: 1 2 3 4 5 41 | ! tEz: 1 1 1 1 1 42 | ! nEx: 0 0 0 0 0 43 | ! nEy: 1 2 3 4 5 44 | ! nEz: 1 1 1 1 1 45 | ! sEx: 1 1 1 1 1 46 | ! sEy: 0 0 0 0 0 47 | ! sEz: 1 2 3 4 5 48 | ! ECtoR: .1 .1 .1 .1 .1 49 | ! EAreaR: .1 .1 .1 .1 .1 50 | ! iSect: 1 1 1 1 1 51 | ! Blade 2: 52 | ! ... 53 | ! Strut 1: 54 | ! NElem: 5 55 | ! TtoC: .15 56 | ! MCx: 0 0 0 0 0 0 57 | ! MCy: 1 2 3 4 5 6 58 | ! MCz: 1 1 1 1 1 1 59 | ! CtoR: .1 .1 .1 .1 .1 .1 60 | ! PEx: 0 0 0 0 0 61 | ! PEy: 1 2 3 4 5 62 | ! PEz: 1 1 1 1 1 63 | ! sEx: 0 0 0 0 0 64 | ! sEy: 1 2 3 4 5 65 | ! sEz: 1 1 1 1 1 66 | ! ECtoR: .1 .1 .1 .1 .1 67 | ! EAreaR: .1 .1 .1 .1 .1 68 | ! BIndS: 0 69 | ! EIndS: 0 70 | ! BIndE: 1 71 | ! EIndE: 3 72 | ! Strut 2: 73 | ! ... 74 | 75 | 76 | ! Open input file for this section 77 | open(15, file=FN) 78 | 79 | ! Read header data 80 | read(15,'(A)') ReadLine 81 | read(ReadLine(index(ReadLine,':')+1:),*) nb 82 | 83 | read(15,'(A)') ReadLine 84 | read(ReadLine(index(ReadLine,':')+1:),*) NStrut 85 | 86 | read(15,'(A)') ReadLine 87 | read(ReadLine(index(ReadLine,':')+1:),*) RotX, RotY, RotZ 88 | 89 | read(15,'(A)') ReadLine 90 | read(ReadLine(index(ReadLine,':')+1:),*) RotPX, RotPY, RotPZ 91 | 92 | read(15,'(A)') ReadLine 93 | read(ReadLine(index(ReadLine,':')+1:),*) at 94 | 95 | read(15,'(A)') ReadLine 96 | read(ReadLine(index(ReadLine,':')+1:),*) Rmax 97 | 98 | read(15,'(A)') ReadLine 99 | 100 | ! Allocate blades struct 101 | allocate(Blades(nb)) 102 | 103 | ! Allocate struts struct 104 | if (NStrut>0) then 105 | allocate(Struts(NStrut)) 106 | end if 107 | 108 | ! Read blade data 109 | do i=1,nb 110 | 111 | read(15,'(A)') ReadLine 112 | 113 | read(15,'(A)') ReadLine 114 | read(ReadLine(index(ReadLine,':')+1:),*) NElem 115 | 116 | ! Allocate arrays in blade structure 117 | Call blade_geom_cns(i,NElem) 118 | 119 | ! JCM: Currently (until blade/wake geometry module is restructured), NElem must be the same for all blades. 120 | nbe=NElem 121 | 122 | read(15,'(A)') ReadLine 123 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%FlipN 124 | 125 | read(15,'(A)') ReadLine 126 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%QCx(1:NElem+1) 127 | 128 | read(15,'(A)') ReadLine 129 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%QCy(1:NElem+1) 130 | 131 | read(15,'(A)') ReadLine 132 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%QCz(1:NElem+1) 133 | 134 | read(15,'(A)') ReadLine 135 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%tx(1:NElem+1) 136 | 137 | read(15,'(A)') ReadLine 138 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%ty(1:NElem+1) 139 | 140 | read(15,'(A)') ReadLine 141 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%tz(1:NElem+1) 142 | 143 | read(15,'(A)') ReadLine 144 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%CtoR(1:NElem+1) 145 | 146 | read(15,'(A)') ReadLine 147 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%PEx(1:NElem) 148 | 149 | read(15,'(A)') ReadLine 150 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%PEy(1:NElem) 151 | 152 | read(15,'(A)') ReadLine 153 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%PEz(1:NElem) 154 | 155 | read(15,'(A)') ReadLine 156 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%tEx(1:NElem) 157 | 158 | read(15,'(A)') ReadLine 159 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%tEy(1:NElem) 160 | 161 | read(15,'(A)') ReadLine 162 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%tEz(1:NElem) 163 | 164 | read(15,'(A)') ReadLine 165 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%nEx(1:NElem) 166 | 167 | read(15,'(A)') ReadLine 168 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%nEy(1:NElem) 169 | 170 | read(15,'(A)') ReadLine 171 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%nEz(1:NElem) 172 | 173 | read(15,'(A)') ReadLine 174 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%sEx(1:NElem) 175 | 176 | read(15,'(A)') ReadLine 177 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%sEy(1:NElem) 178 | 179 | read(15,'(A)') ReadLine 180 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%sEz(1:NElem) 181 | 182 | read(15,'(A)') ReadLine 183 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%ECtoR(1:NElem) 184 | 185 | read(15,'(A)') ReadLine 186 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%EAreaR(1:NElem) 187 | 188 | read(15,'(A)') ReadLine 189 | read(ReadLine(index(ReadLine,':')+1:),*) Blades(i)%iSect(1:NElem) 190 | 191 | end do 192 | 193 | ! Read strut data 194 | do i=1,NStrut 195 | 196 | read(15,'(A)') ReadLine 197 | 198 | read(15,'(A)') ReadLine 199 | read(ReadLine(index(ReadLine,':')+1:),*) NElem 200 | 201 | read(15,'(A)') ReadLine 202 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%TtoC 203 | 204 | ! Allocate arrays in blade structure 205 | Call strut_comp_cns(i,NElem) 206 | 207 | read(15,'(A)') ReadLine 208 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%MCx(1:NElem+1) 209 | 210 | read(15,'(A)') ReadLine 211 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%MCy(1:NElem+1) 212 | 213 | read(15,'(A)') ReadLine 214 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%MCz(1:NElem+1) 215 | 216 | read(15,'(A)') ReadLine 217 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%CtoR(1:NElem+1) 218 | 219 | read(15,'(A)') ReadLine 220 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%PEx(1:NElem) 221 | 222 | read(15,'(A)') ReadLine 223 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%PEy(1:NElem) 224 | 225 | read(15,'(A)') ReadLine 226 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%PEz(1:NElem) 227 | 228 | read(15,'(A)') ReadLine 229 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%sEx(1:NElem) 230 | 231 | read(15,'(A)') ReadLine 232 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%sEy(1:NElem) 233 | 234 | read(15,'(A)') ReadLine 235 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%sEz(1:NElem) 236 | 237 | read(15,'(A)') ReadLine 238 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%ECtoR(1:NElem) 239 | 240 | read(15,'(A)') ReadLine 241 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%EAreaR(1:NElem) 242 | 243 | read(15,'(A)') ReadLine 244 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%BIndS 245 | 246 | read(15,'(A)') ReadLine 247 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%EIndS 248 | 249 | read(15,'(A)') ReadLine 250 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%BIndE 251 | 252 | read(15,'(A)') ReadLine 253 | read(ReadLine(index(ReadLine,':')+1:),*) Struts(i)%EIndE 254 | 255 | end do 256 | 257 | ! Close input file 258 | close(15) 259 | 260 | return 261 | end subroutine InputGeom 262 | -------------------------------------------------------------------------------- /src/LB_DynStall.f90: -------------------------------------------------------------------------------- 1 | subroutine LB_DynStall(nElem,CLstat,CDstat,alphaL,alpha5,umach,Re,SectInd,CL,CD) 2 | 3 | use airfoil 4 | use dystl 5 | use pidef 6 | 7 | implicit none 8 | 9 | real :: CLstat, CDstat, alphaL, alpha5, umach, Re, CL, CD 10 | integer :: SectInd, nElem 11 | 12 | real :: AOA0, CLID, Trans, dCLRefLE, dAOARefLE, AOARefLE, CLstatF, C, C1, CLIDF, CLRatio, CLsep, CLF, dCDF, KD, CLa, NOF, dCLv, dCDv, acut 13 | 14 | ! Leishman-Beddoes dynamic stall model (incompressible reduction). 15 | 16 | ! Airfoil data 17 | AOA0=alzer(SectInd) 18 | Call CalcLBStallAOALim(Re,SectInd,CLa,CLCritP(nElem),CLCritN(nElem)) 19 | 20 | ! Model constants 21 | KD=.1 ! TE separation drag factor 22 | 23 | ! Evaluate ideal CL curve at current AOA 24 | Call LB_EvalIdealCL(alphaL,AOA0,CLa,1,CLRef(nElem)) 25 | Call LB_EvalIdealCL(alphaL,AOA0,CLa,0,CLID) 26 | 27 | ! calc lagged ideal CL for comparison with critical LE separation CL 28 | Trans=(cos(alphaL-AOA0))**2 ! fair effect to zero at 90 deg. AOA... 29 | dCLRefLE=Trans*dp(nElem) ! dp is lagged CLRef change 30 | dAOARefLE=dCLRefLE/CLa 31 | 32 | ! define reference LE CL and AOA 33 | CLRefLE(nElem)=CLRef(nElem)-dCLRefLE 34 | if (CLRefLE(nElem)*(CLRefLE(nElem)-CLRefLE_Last(nElem)) > 0) then 35 | CLRateFlag(nElem)=1 36 | else 37 | CLRateFlag(nElem)=0 38 | end if 39 | AOARefLE=alphaL-dAOARefLE 40 | Call Force180(AOARefLE) 41 | 42 | ! calc effective static TE separation point using effective LE AOA 43 | Call intp(Re,AOARefLE*condeg,CLstatF,C,C1,SectInd) 44 | Call LB_EvalIdealCL(AOARefLE,AOA0,CLa,0,CLIDF) 45 | if (abs(CLIDF)<0.001) then 46 | CLRatio=999 47 | else 48 | CLRatio=CLstatF/CLIDF; 49 | end if 50 | 51 | if (CLRatio > 0.25) then 52 | Fstat(nElem)=min((sqrt(4.0*CLRatio)-1.0)**2,1.0) 53 | 54 | ! Test logic 55 | LB_LogicOutputs(nElem,1)=1 56 | else 57 | Fstat(nElem)=0 58 | 59 | ! Test logic 60 | LB_LogicOutputs(nElem,1)=2 61 | end if 62 | ! calc lagged Fstat to represent dynamic TE separation point 63 | F(nElem)=Fstat(nElem)-dF(nElem) 64 | ! force limits on lagged F (needed due to discretization error...) 65 | F(nElem)=min(max(F(nElem),0.0),1.0) 66 | 67 | ! Calc dynamic CL due to TE separation as fairing between fully attached and fully separated predictions from the Kirchoff approximation at current AOA 68 | if (abs(CLID)<0.001) then 69 | CLRatio=999 70 | else 71 | CLRatio=CLstat/CLID 72 | end if 73 | 74 | if (CLRatio > 1.0) then 75 | CLID=CLstat 76 | 77 | ! Test logic 78 | LB_LogicOutputs(nElem,2)=1 79 | end if 80 | 81 | if (CLRatio > 0.25) then 82 | CLsep=CLID/4.0 83 | 84 | ! Test logic 85 | LB_LogicOutputs(nElem,3)=1 86 | else 87 | CLsep=CLstat 88 | 89 | ! Test logic 90 | LB_LogicOutputs(nElem,3)=2 91 | end if 92 | CLF=CLsep+CLID*0.25*(F(nElem)+2.0*sqrt(F(nElem))) 93 | dCDF=KD*(CLstat-CLF)*sign(1.0,CLstat) 94 | 95 | ! LE vortex lift component, dCNv is a lagged change in the added normal force due 96 | ! to LE vortex shedding. Assumed to affect lift coeff as an added circulation... 97 | dCLv=dCNv(nElem)*cos(alpha5) 98 | dCDv=dCNv(nElem)*sin(alpha5) 99 | ! vortex feed is given by the rate at which lift (circulation) is being shed due to dynamic separation. Lift component due to separation is defined by the 100 | ! difference between the ideal lift and the lift including dynamic separation effects. 101 | cv(nElem)=CLID-CLF 102 | dcv(nElem)=cv(nElem)-cv_Last(nElem) 103 | ! If the sign of dcv is opposite the reference LE CL, set to zero to disallow negative vorticity from shedding from the leading edge. Also, limit the model 104 | ! at AOA>acut or if the magnitude of the reference CL is decreasing... 105 | acut=50.0*conrad 106 | if (sign(1.0,dcv(nElem)*CLRefLE(nElem))<0 .OR. abs(alphaL-AOA0)>acut .OR. CLRateFlag(nElem)<0) then 107 | dcv=0.0 108 | 109 | ! Test logic 110 | LB_LogicOutputs(nElem,4)=1 111 | end if 112 | 113 | ! Total lift and drag 114 | CL=CLF+dCLv 115 | CD=CDstat+dCDF+dCDv 116 | 117 | return 118 | end subroutine LB_DynStall 119 | -------------------------------------------------------------------------------- /src/RotateTurbine.f90: -------------------------------------------------------------------------------- 1 | subroutine RotateTurbine 2 | 3 | use configr 4 | use element 5 | use blade 6 | use strut 7 | 8 | ! Updates current turbine geometry 9 | 10 | ! Rotate turbine axis of rotation and origin if necessary... 11 | 12 | ! Rotate blades 13 | do i=1,nb 14 | Call RotateBlade(i,delt,RotX,RotY,RotZ,RotPX,RotPY,RotPZ) 15 | end do 16 | 17 | ! Rotate struts 18 | do i=1,NStrut 19 | Call RotateStrut(i,delt,RotX,RotY,RotZ,RotPX,RotPY,RotPZ) 20 | end do 21 | 22 | return 23 | end subroutine RotateTurbine 24 | -------------------------------------------------------------------------------- /src/SGeomSetup.f90: -------------------------------------------------------------------------------- 1 | subroutine SGeomSetup() 2 | 3 | use parameters 4 | 5 | use configr 6 | use element 7 | use strut 8 | use airfoil 9 | use pidef 10 | 11 | implicit none 12 | 13 | integer :: i, j, NElem, BIndS, EIndS, BIndE, EIndE, iSc 14 | real :: sx, sy, sz, VMag, LR 15 | 16 | ! Sets up strut geometry arrays. 17 | 18 | ! Set initial geometry (zero theta) 19 | do i=1,NStrut 20 | NElem=Struts(i)%NElem 21 | BIndS=Struts(i)%BIndS 22 | EIndS=Struts(i)%EIndS 23 | BIndE=Struts(i)%BIndE 24 | EIndE=Struts(i)%EIndE 25 | 26 | ! Get blade thickness to chord 27 | if (BIndS > 0) then 28 | iSc=Blades(BIndS)%iSect(EIndS) 29 | Struts(i)%tcS=tc(iSc) 30 | else 31 | Struts(i)%tcS=0.0 32 | end if 33 | if (BIndE > 0) then 34 | iSc=Blades(BIndE)%iSect(EIndE) 35 | Struts(i)%tcE=tc(iSc) 36 | else 37 | Struts(i)%tcE=0.0 38 | end if 39 | 40 | ! Init length sum 41 | LR=0.0 42 | do j=1,NElem 43 | VMag=sqrt(Struts(i)%sEx(j)**2+Struts(i)%sEy(j)**2+Struts(i)%sEz(j)**2) 44 | ! Sum length 45 | LR=LR+VMag 46 | end do 47 | Struts(i)%LR=LR 48 | 49 | ! Calc element geometry 50 | Call CalcSEGeom(i) 51 | 52 | end do 53 | 54 | return 55 | end subroutine SGeomSetup 56 | -------------------------------------------------------------------------------- /src/SetBoundWake.f90: -------------------------------------------------------------------------------- 1 | subroutine SetBoundWake() 2 | 3 | use configr 4 | use element 5 | use blade 6 | use wake 7 | 8 | ! Set new wake element positions 9 | do i=1,nb 10 | 11 | nei=1+(i-1)*(nbe+1) 12 | 13 | ! Blade element end locations (quarter chord). 14 | do j=0,nbe 15 | nej=nei+j ! element index 16 | x(nt,nej)=xBE(nej) 17 | y(nt,nej)=yBE(nej) 18 | z(nt,nej)=zBE(nej) 19 | end do 20 | end do 21 | 22 | 23 | return 24 | end subroutine SetBoundWake 25 | -------------------------------------------------------------------------------- /src/UpdateBladeVel.f90: -------------------------------------------------------------------------------- 1 | subroutine UpdateBladeVel(IFLG) 2 | 3 | use configr 4 | use blade 5 | use wake 6 | use wallsoln 7 | 8 | integer :: i,ygcErr 9 | real :: Point(3), dVel(3), dUdX 10 | 11 | ! Calculate the velocity induced on the blades by wake, wall, and freestream 12 | 13 | 14 | if (iflg .eq. 0) then 15 | ! re-initialize uiwake viwake wiwake as we are beginning a new time step 16 | uiwake(:)=0.0 17 | viwake(:)=0.0 18 | wiwake(:)=0.0 19 | end if 20 | 21 | 22 | do I=1,NE 23 | 24 | ! If flag is set, just recompute the velocity contiribution due to the bound vorticies on the blades. 25 | ! Otherwise, calculate all wake, wall and freestream induced velocity. 26 | if (IFLG .eq. 0) then 27 | 28 | ! Calculate freestream velocity at blade elements 29 | CALL CalcFreestream(X(NT,I),Y(NT,I),Z(NT,I),UFSB(I),VFSB(I),WFSB(I),ygcErr) 30 | 31 | ! Set freestream velocity of next shed wake elements to that calculated on the blade 32 | UFS(NT,I)=UFSB(I) 33 | VFS(NT,I)=VFSB(I) 34 | WFS(NT,I)=WFSB(I) 35 | 36 | 37 | USUM=0.0 38 | VSUM=0.0 39 | WSUM=0.0 40 | if (NT > 1) then 41 | 42 | ! Calculate wake velocity at blade elements (excluding bound vorticity component) 43 | Call BladeIndVel(NT,ntTerm,NBE,NB,NE,X(NT,I),Y(NT,I),Z(NT,I),USUM,VSUM,WSUM,dUdX,2,0) 44 | 45 | ! Calculate wall induced velocities at blade locations 46 | Point=[X(NT,I),Y(NT,I),Z(NT,I)] 47 | Call WallIndVel(Point,dVel) 48 | USUM=USUM+dVel(1) 49 | VSUM=VSUM+dVel(2) 50 | WSUM=WSUM+dVel(3) 51 | 52 | end if 53 | 54 | uiwake(I)=USUM 55 | viwake(I)=VSUM 56 | wiwake(I)=WSUM 57 | else 58 | USUM=uiwake(I) 59 | VSUM=viwake(I) 60 | WSUM=wiwake(I) 61 | end if 62 | 63 | ! CALCULATE THE VELOCITY CONTRIBUTIONS DUE TO JUST THE BOUND VORTICIES ON THE BLADES ( GS(NT,:) ) 64 | Call BladeIndVel(NT,ntTerm,NBE,NB,NE,X(NT,I),Y(NT,I),Z(NT,I),UP,VP,WP,dUdX,1,0) 65 | 66 | ! Set wake and wall velocities on blade 67 | UB(I)=USUM+UP 68 | VB(I)=VSUM+VP 69 | WB(I)=WSUM+WP 70 | 71 | ! Set induced velocity of next shed wake elements 72 | if (iut .eq. -2) then 73 | ! Fix wake velocities at freestream velocity 74 | U(NT,I)=0.0 75 | V(NT,I)=0.0 76 | W(NT,I)=0.0 77 | else 78 | U(NT,I)=UB(I) 79 | V(NT,I)=VB(I) 80 | W(NT,I)=WB(I) 81 | end if 82 | 83 | end do 84 | 85 | return 86 | end subroutine UpdateBladeVel 87 | -------------------------------------------------------------------------------- /src/UpdateStrutLoads.f90: -------------------------------------------------------------------------------- 1 | subroutine UpdateStrutLoads() 2 | 3 | use strut 4 | use configr 5 | 6 | Implicit None 7 | 8 | integer :: i, j, NElem 9 | integer :: ygcerr 10 | real :: xs, ys, zs, xj, yj, zj 11 | real :: uFSs, vFSs, wFSs, uBlade, vBlade, wBlade, us, vs, ws 12 | real :: uTot, vTot, wTot, ur, ReStrut 13 | real :: Delem 14 | real :: Fx, Fy, Fz, TRx, TRy, TRz, te 15 | real :: t_ave, carea, cdj, djunc 16 | 17 | ! Updates loads on struts 18 | 19 | ! Zero out current strut loads sum 20 | CP_S=0.0 21 | CTR_S=0.0 22 | CFx_S=0.0 23 | CFy_S=0.0 24 | CFz_S=0.0 25 | 26 | do i=1,NStrut 27 | 28 | ! Zero out current strut loads 29 | Struts(i)%CP=0.0 30 | Struts(i)%CTR=0.0 31 | Struts(i)%CFx=0.0 32 | Struts(i)%CFy=0.0 33 | Struts(i)%CFz=0.0 34 | 35 | NElem=Struts(i)%NElem 36 | do j=1,NElem 37 | 38 | xs=Struts(i)%PEx(j) 39 | ys=Struts(i)%PEy(j) 40 | zs=Struts(i)%PEz(j) 41 | 42 | ! Freestream velocity at strut location 43 | Call CalcFreestream(xs,ys,zs,uFSs,vFSs,wFSs,ygcerr) 44 | 45 | ! Blade velocity due to rotation 46 | CALL CalcBladeVel(wRotX,wRotY,wRotZ,xs,ys,zs,uBlade,vBlade,wBlade) 47 | 48 | ! Induced velocity at strut element 49 | Call CalcIndVel(NT,ntTerm,NBE,NB,NE,xs,ys,zs,us,vs,ws) 50 | 51 | ! Calculate relative velocity magnitude at strut element 52 | uTot = us+uFSs-uBlade 53 | vTot = vs+vFSs-vBlade 54 | wTot = ws+wFSs-wBlade 55 | ur = sqrt(uTot*uTot + vTot*vTot + wTot*wTot) 56 | ReStrut = ReM*ur*Struts(i)%ECtoR(j) ! Strut chord Reynolds number 57 | 58 | ! Fill current flow quantities at strut element 59 | Struts(i)%u(j)=uTot 60 | Struts(i)%v(j)=vTot 61 | Struts(i)%w(j)=wTot 62 | Struts(i)%ur(j)=ur 63 | Struts(i)%ReStrut(j)=ReStrut 64 | 65 | ! Calculate strut element coeffs 66 | Call StrutElemCoeffs(i,j) 67 | 68 | ! Drag coeff vector from this strut element, re-referenced to full turbine scale 69 | ! (D/(1/2*rho*Uinf^2*At) 70 | Delem = Struts(i)%Cd0(j) * Struts(i)%EAreaR(j) / at * ur**2 71 | Fx=Delem*uTot/ur 72 | Fy=Delem*vTot/ur 73 | Fz=Delem*wTot/ur 74 | ! Corresponding torque coeff. (T/(1/2*rho*Uinf^2*At*R)) 75 | CALL cross(xs-RotPX,ys-RotPY,zs-RotPZ,Fx,Fy,Fz,TRx,TRy,TRz) 76 | te=(TRx*RotX+TRy*RotY+TRz*RotZ) 77 | 78 | ! Add to strut output 79 | Struts(i)%CTR=Struts(i)%CTR + te 80 | Struts(i)%CP=Struts(i)%CP + te*ut 81 | Struts(i)%CFx=Struts(i)%CFx + Fx 82 | Struts(i)%CFy=Struts(i)%CFy + Fy 83 | Struts(i)%CFz=Struts(i)%CFz + Fz 84 | end do 85 | 86 | ! Blade/strut junction interference drag 87 | ! First strut element 88 | if (Struts(i)%BIndS > 0) then 89 | carea=Struts(i)%ECtoR(1)**2 90 | xj=Struts(i)%PEx(1) 91 | yj=Struts(i)%PEy(1) 92 | zj=Struts(i)%PEz(1) 93 | t_ave = 0.5 * (Struts(i)%TtoC + Struts(i)%tcS) 94 | uTot=Struts(i)%u(1) 95 | vTot=Struts(i)%v(1) 96 | wTot=Struts(i)%w(1) 97 | ur=Struts(i)%ur(1) 98 | Cdj = t_ave*t_ave * (17.0 * t_ave*t_ave - 0.05) 99 | !Cdj = 0.0112 ! t/c_avg = 0.165 100 | !Cdj = 0.0535 ! t/c_avg = 0.24 101 | Cdj = Cdj + Cdpar ! Additional user-specified parasitic drag 102 | ! Drag coeff vector, re-referenced to full turbine scale 103 | ! (D/(1/2*rho*Uinf^2*At) 104 | Djunc = Cdj * carea / at * ur**2 105 | Fx=Djunc*uTot/ur 106 | Fy=Djunc*vTot/ur 107 | Fz=Djunc*wTot/ur 108 | ! Corresponding torque coeff. (T/(1/2*rho*Uinf^2*At*R)) 109 | CALL cross(xj-RotPX,yj-RotPY,zj-RotPZ,Fx,Fy,Fz,TRx,TRy,TRz) 110 | te=(TRx*RotX+TRy*RotY+TRz*RotZ) 111 | ! Add to strut output 112 | Struts(i)%CTR=Struts(i)%CTR + te 113 | Struts(i)%CP=Struts(i)%CP + te*ut 114 | Struts(i)%CFx=Struts(i)%CFx + Fx 115 | Struts(i)%CFy=Struts(i)%CFy + Fy 116 | Struts(i)%CFz=Struts(i)%CFz + Fz 117 | end if 118 | ! Last strut element 119 | if (Struts(i)%BIndE > 0) then 120 | carea=Struts(i)%ECtoR(NElem)**2 121 | xj=Struts(i)%PEx(NElem) 122 | yj=Struts(i)%PEy(NElem) 123 | zj=Struts(i)%PEz(NElem) 124 | t_ave = 0.5 * (Struts(i)%TtoC + Struts(i)%tcE) 125 | uTot=Struts(i)%u(NElem) 126 | vTot=Struts(i)%v(NElem) 127 | wTot=Struts(i)%w(NElem) 128 | ur=Struts(i)%ur(NElem) 129 | Cdj = t_ave*t_ave * (17.0 * t_ave*t_ave - 0.05) 130 | !Cdj = 0.0112 ! t/c_avg = 0.165 131 | !Cdj = 0.0535 ! t/c_avg = 0.24 132 | Cdj = Cdj + Cdpar ! Additional user-specified parasitic drag 133 | ! Drag coeff vector, re-referenced to full turbine scale 134 | ! (D/(1/2*rho*Uinf^2*At) 135 | Djunc = Cdj * carea / at * ur**2 136 | Fx=Djunc*uTot/ur 137 | Fy=Djunc*vTot/ur 138 | Fz=Djunc*wTot/ur 139 | ! Corresponding torque coeff. (T/(1/2*rho*Uinf^2*At*R)) 140 | CALL cross(xj-RotPX,yj-RotPY,zj-RotPZ,Fx,Fy,Fz,TRx,TRy,TRz) 141 | te=(TRx*RotX+TRy*RotY+TRz*RotZ) 142 | ! Add to strut output 143 | Struts(i)%CTR=Struts(i)%CTR + te 144 | Struts(i)%CP=Struts(i)%CP + te*ut 145 | Struts(i)%CFx=Struts(i)%CFx + Fx 146 | Struts(i)%CFy=Struts(i)%CFy + Fy 147 | Struts(i)%CFz=Struts(i)%CFz + Fz 148 | end if 149 | 150 | ! Add to total struts output 151 | CTR_S=CTR_S + Struts(i)%CTR 152 | CP_S=CP_S + Struts(i)%CP 153 | CFx_S=CFx_S + Struts(i)%CFx 154 | CFy_S=CFy_S + Struts(i)%CFy 155 | CFz_S=CFz_S + Struts(i)%CFz 156 | 157 | end do 158 | 159 | return 160 | end subroutine UpdateStrutLoads 161 | -------------------------------------------------------------------------------- /src/UpdateTowerVelocity.f90: -------------------------------------------------------------------------------- 1 | subroutine UpdateTowerVelocity() 2 | 3 | use tower 4 | use configr 5 | 6 | implicit none 7 | 8 | real :: uFSt,vFSt,wFSt,utow,vtow,wtow 9 | integer :: i,ygcerr 10 | 11 | 12 | Do i = 1,tower_Npts 13 | 14 | ! Freestream velocity at tower location 15 | Call CalcFreestream(tower_x,tower_y(i),0.0,uFSt,vFSt,wFSt,ygcerr) 16 | 17 | ! Induced velocity at tower location 18 | Call CalcIndVel(NT,ntTerm,NBE,NB,NE,tower_x,tower_y(i),0.0,utow,vtow,wtow) 19 | 20 | tower_Vx(i) = uFSt + utow 21 | 22 | End Do 23 | 24 | 25 | return 26 | 27 | end subroutine UpdateTowerVelocity 28 | -------------------------------------------------------------------------------- /src/UpdateWakeVel.f90: -------------------------------------------------------------------------------- 1 | subroutine UpdateWakeVel() 2 | 3 | use configr 4 | use blade 5 | use wake 6 | use regtest 7 | 8 | integer :: ygcErr 9 | real :: x_t, y_t, z_t, u_t, v_t, w_t 10 | 11 | integer :: NTHREADS, TID, OMP_GET_NUM_THREADS, OMP_GET_THREAD_NUM, N, CHUNKSIZE, CHUNK 12 | 13 | ! Calculate the induced velocity at each lattice point in the wake from wake (including bound vorticity), wall, and freestream 14 | 15 | if (NT .ge. 1) then 16 | 17 | NT1=NT-1 18 | 19 | ! Update the old wake velocity values 20 | do I=1,NE 21 | do J=ntTerm,NT1 22 | UO(J,I)=U(J,I)+UFS(J,I) 23 | VO(J,I)=V(J,I)+VFS(J,I) 24 | WO(J,I)=W(J,I)+WFS(J,I) 25 | end do 26 | end do 27 | 28 | ! Calculate freestream velocity at wake locations 29 | ygcErr=0 30 | do I=1,NE 31 | do J=ntTerm,NT1 32 | CALL CalcFreestream(X(J,I),Y(J,I),Z(J,I),UFS(J,I),VFS(J,I),WFS(J,I),ygcErr) 33 | end do 34 | end do 35 | 36 | ! If this is proper time step, calculate system influence on wake velocities 37 | if (NT .eq. NSW) then 38 | !$omp parallel do private(j) 39 | do I=1,NE 40 | do J=ntTerm,NT1 41 | Call CalcIndVel(NT,ntTerm,NBE,NB,NE,X(J,I),Y(J,I),Z(J,I),U(J,I),V(J,I),W(J,I)) 42 | end do 43 | end do 44 | !$omp end parallel do 45 | 46 | ! Set the next update timestep 47 | nsw=nt+iut 48 | end if 49 | 50 | end if 51 | 52 | ! Regression test 53 | if (RegTFlag == 1) then 54 | Reg_MaxWVM=0.0 55 | ! Max wake velocity mag in first wake shed 56 | do I=1,NE 57 | Reg_MaxWVM=max(Reg_MaxWVM,sqrt(U(1,I)**2+V(1,I)**2+W(1,I)**2)) 58 | end do 59 | end if 60 | 61 | return 62 | end subroutine UpdateWakeVel 63 | -------------------------------------------------------------------------------- /src/UpdateWall.f90: -------------------------------------------------------------------------------- 1 | subroutine UpdateWall() 2 | 3 | use configr 4 | use blade 5 | use wallsoln 6 | use wallsystem 7 | use regtest 8 | use util 9 | 10 | integer :: ygcErr, i, IBCInd 11 | real :: Point(3), dVel(3), NVelSum, TVelSum, dUdX, dUdXSum 12 | 13 | ! If this is a wall update timestep... 14 | if (nt == nsWall) then 15 | 16 | if (GPFlag == 1 .or. WPFlag == 1) then 17 | ! Ground plane 18 | 19 | ! Calculate the velocities at wall panels from wake (including bound vorticity), and freestream. 20 | ! Update wall RHS and calc new panel source strengths 21 | 22 | !$omp parallel do private(i, dVel, NVelSum, dUdX) 23 | do i=1,NumWP_total 24 | 25 | ! Calculate freestream velocity at panel locations normal to panel 26 | CALL CalcFreestream(WCPoints(i,1),WCPoints(i,2)& 27 | &,WCPoints(i,3),dVel(1),dVel(2),dVel(3),ygcErr) 28 | NVelSum=sum(W3Vec(i,1:3)*dVel) 29 | 30 | ! Calc wake induced velocity at wall panel locations normal to panel 31 | CALL BladeIndVel(NT,ntTerm,NBE,NB,NE,WCPoints(i,1),WCPoints(i,2),WCPoints(i,3),dVel(1),dVel(2),dVel(3),dUdX,0,0) 32 | NVelSum=NVelSum+sum(W3Vec(i,1:3)*dVel) 33 | 34 | ! Calc FS induced velocity normal to panel 35 | if (FSFlag == 1) then 36 | Point=[WCPoints(i,1),WCPoints(i,2),WCPoints(i,3)] 37 | Call FSIndVel(Point,0,dVel,dUdX) 38 | NVelSum=NVelSum+sum(W3Vec(i,1:3)*dVel) 39 | end if 40 | 41 | ! Set RHS 42 | WRHS(i,1)=-NVelSum 43 | 44 | end do 45 | !$omp end parallel do 46 | 47 | ! Calc new wall panel source strengths 48 | WSource=matmul(WInfI,WRHS) 49 | 50 | end if 51 | 52 | 53 | if (FSFlag == 1) then 54 | ! Free Surface 55 | 56 | ! Calculate the velocities at wall panels from wake (including bound vorticity), and freestream. 57 | ! Update wall RHS and calc new panel source strengths 58 | if (UseFSWall) then 59 | ! Wall BC 60 | do i=1,NumFSCP 61 | 62 | ! Calculate freestream velocity at panel locations 63 | CALL CalcFreestream(FSCPPoints(i,1),FSCPPoints(i& 64 | &,2),FSCPPoints(i,3),dVel(1),dVel(2),dVel(3),ygcErr) 65 | NVelSum=sum(FSCZVec(i,1:3)*dVel) 66 | TVelSum=sum(FSCXVec(i,1:3)*dVel) 67 | 68 | ! Calc wake induced velocity at wall panel locations 69 | CALL BladeIndVel(NT,ntTerm,NBE,NB,NE,FSCPPoints(i,1),FSCPPoints(i,2),FSCPPoints(i,3),dVel(1),dVel(2),dVel(3),dUdX,0,0) 70 | NVelSum=NVelSum+sum(FSCZVec(i,1:3)*dVel) 71 | TVelSum=TVelSum+sum(FSCXVec(i,1:3)*dVel) 72 | 73 | ! Calc GP induced velocity 74 | if (GPFlag == 1) then 75 | Point=[FSCPPoints(i,1),FSCPPoints(i,2),FSCPPoints(i,3)] 76 | Call GPIndVel(Point,0,dVel,dUdX) 77 | 78 | NVelSum=NVelSum+sum(FSCZVec(i,1:3)*dVel) 79 | TVelSum=TVelSum+sum(FSCXVec(i,1:3)*dVel) 80 | end if 81 | 82 | ! Calc wall induced velocity 83 | if (WPFlag == 1) then 84 | Point=[FSCPPoints(i,1),FSCPPoints(i,2),FSCPPoints(i,3)] 85 | Call WPIndVel(Point,0,dVel,dUdX) 86 | 87 | NVelSum=NVelSum+sum(FSCZVec(i,1:3)*dVel) 88 | TVelSum=TVelSum+sum(FSCXVec(i,1:3)*dVel) 89 | end if 90 | 91 | ! Set RHS in list for running averages 92 | FSRHS(i,FSRHSInd)=-NVelSum 93 | ! Set new average RHS value. Note: Must build average over at least one revolution... 94 | FSRHSAve(i,1)=sum(FSRHS(i,1:NFSRHSAve))/real(NFSRHSAve) 95 | 96 | ! Set induced tangent velocity at colocation points in list for running averages (used for output) 97 | FSVT(i,FSRHSInd)=TVelSum 98 | ! Set new average VT value. Note: Must build average over at least one revolution... 99 | FSVTAve(i,1)=sum(FSVT(i,1:NFSRHSAve))/real(NFSRHSAve) 100 | 101 | end do 102 | 103 | else 104 | ! Free surface BC 105 | do i=1,NumFSCP 106 | 107 | ! Calculate freestream velocity at panel locations 108 | CALL CalcFreestream(FSCPPoints(i,1),FSCPPoints(i& 109 | &,2),FSCPPoints(i,3),dVel(1),dVel(2),dVel(3),ygcErr) 110 | NVelSum=sum(FSCZVec(i,1:3)*dVel) 111 | TVelSum=sum(FSCXVec(i,1:3)*dVel) 112 | dUdXSum=0.0 113 | 114 | ! Calc wake induced velocity at wall panel locations 115 | CALL BladeIndVel(NT,ntTerm,NBE,NB,NE,FSCPPoints(i,1),FSCPPoints(i,2),FSCPPoints(i,3),dVel(1),dVel(2),dVel(3),dUdX,0,1) 116 | NVelSum=NVelSum+sum(FSCZVec(i,1:3)*dVel) 117 | TVelSum=TVelSum+sum(FSCXVec(i,1:3)*dVel) 118 | dUdXSum=dUdXSum+dUdX 119 | 120 | ! Calc GP induced velocity 121 | if (GPFlag == 1) then 122 | Point=[FSCPPoints(i,1),FSCPPoints(i,2),FSCPPoints(i,3)] 123 | Call GPIndVel(Point,1,dVel,dUdX) 124 | NVelSum=NVelSum+sum(FSCZVec(i,1:3)*dVel) 125 | TVelSum=TVelSum+sum(FSCXVec(i,1:3)*dVel) 126 | dUdXSum=dUdXSum+dUdX 127 | end if 128 | 129 | ! Set RHS in list for running averages 130 | FSRHS(i,FSRHSInd)=(FnR**2)*dUdXSum-NVelSum 131 | ! Set new average RHS value. Note: Must build average over at least one revolution... 132 | FSRHSAve(i,1)=sum(FSRHS(i,1:NFSRHSAve))/real(NFSRHSAve) 133 | 134 | ! Set BC RHS if this is a BC colocation point 135 | if (FSBCRow(i)>0) then 136 | FSRHS(FSBCRow(i),FSRHSInd)=-dUdXSum 137 | FSRHSAve(FSBCRow(i),1)=sum(FSRHS(FSBCRow(i),1:NFSRHSAve))/real(NFSRHSAve) 138 | end if 139 | 140 | ! Set induced tangent velocity at colocation points in list for running averages (used for output) 141 | FSVT(i,FSRHSInd)=TVelSum 142 | ! Set new average VT value. Note: Must build average over at least one revolution... 143 | FSVTAve(i,1)=sum(FSVT(i,1:NFSRHSAve))/real(NFSRHSAve) 144 | 145 | end do 146 | 147 | end if 148 | 149 | ! Calc new free surface panel source strengths with running average (over approx 360 deg turbine rotation) 150 | ! RHS values. 151 | FSSource=matmul(FSSMatI,FSRHSAve) 152 | 153 | ! Reset running average index (once entire buffer is filled, overwrites old data at beginning of average buffer) 154 | if (FSRHSInd= vCutOffRad) then 53 | if (ivtxcor > 0 .and. CCAV < A2*VRAD2) then 54 | if (ivtxcor == 1) then 55 | ! Constant velocity (approx) core 56 | CCAV = A2*VRAD2 ! limit denominator to value at core radius 57 | VF = (ADBDB-ADCDC)*G/(12.56637*CCAV) 58 | if (CalcDer == 1) then 59 | DVFDX = G/12.56637*((AX/C-CX*ADC/C**3-AX/B+BX*ADB/B**3)/CCAV) 60 | end if 61 | else if (ivtxcor == 2) then 62 | ! Linear velocity (approx) core 63 | RRAT=sqrt(CCAV/(A2*VRAD2)) ! radius to core radius ratio 64 | CCAV = A2*VRAD2 ! limit denominator to value at core radius 65 | VF = RRAT*(ADBDB-ADCDC)*G/(12.56637*CCAV) 66 | if (CalcDer == 1) then 67 | DVFDX = G/12.56637*((AX/C-CX*ADC/C**3-AX/B+BX*ADB/B**3)/CCAV) 68 | end if 69 | end if 70 | else 71 | VF = (ADBDB-ADCDC)*G/(12.56637*CCAV) 72 | if (CalcDer == 1) then 73 | DVFDX = G/12.56637*((AX/C-CX*ADC/C**3-AX/B+BX*ADB/B**3)/CCAV-2*(ADBDB-ADCDC)/CCAV**2*(AZ*CCAY-AY*CCAZ)) 74 | end if 75 | end if 76 | else 77 | VF = 0.0 78 | DVFDX = 0.0 79 | end if 80 | 81 | UP = UP+CCAX*VF 82 | VP = VP+CCAY*VF 83 | WP = WP+CCAZ*VF 84 | if (CalcDer == 1) then 85 | DUDX = DUDX+CCAX*DVFDX 86 | end if 87 | 88 | return 89 | end subroutine VorIVel 90 | -------------------------------------------------------------------------------- /src/WSolnSetup.f90: -------------------------------------------------------------------------------- 1 | subroutine WSolnSetup() 2 | 3 | use util 4 | use wallsoln 5 | use wallsystem 6 | !$ use omp_lib 7 | 8 | integer :: i, j, Self, IBCInd, BCRow 9 | integer :: INFO 10 | integer, allocatable :: IPIV(:) 11 | real :: R(3,3), Point(3), dPG(3), dVel(3), dVelG(3), dudx 12 | 13 | real :: t0,t1 ! cpu time variables for matrix inversion 14 | 15 | ! Set up ground plane system 16 | if (GPFlag==1) then 17 | 18 | !! This assumes WGeomSetup() has already been called, and that the ground plane has been configured 19 | ! as a wall system of one wall. 20 | 21 | ! Setup wall self influence matrix 22 | write(*,*) 'Generating wall influence matrix...' 23 | call cpu_time(t0) 24 | !$ t0 = omp_get_wtime() 25 | call gen_influence_matrix() 26 | call cpu_time(t1) 27 | !$ t1 = omp_get_wtime() 28 | print '("Time to generate influence matrix = ",f15.3," seconds.")',t1-t0 29 | 30 | ! Store wall solution matrix and inverse 31 | write(*,*) 'Inverting wall influence matrix...' 32 | call cpu_time(t0) 33 | !$ t0 = omp_get_wtime() 34 | call invert_influence_matrix() 35 | call cpu_time(t1) 36 | !$ t1 = omp_get_wtime() 37 | print '("Time to invert influence matrix = ",f15.3," seconds.")',t1-t0 38 | 39 | end if 40 | 41 | 42 | ! Set up generic wall system 43 | if (WPFlag==1) then 44 | 45 | !! This assumes that the wall geometry has already been loaded as a wall system of one wall. 46 | 47 | ! Setup wall self influence matrix 48 | write(*,*) 'Generating wall influence matrix...' 49 | call cpu_time(t0) 50 | !$ t0 = omp_get_wtime() 51 | call gen_influence_matrix() 52 | call cpu_time(t1) 53 | !$ t1 = omp_get_wtime() 54 | print '("Time to generate influence matrix = ",f15.3," seconds.")',t1-t0 55 | 56 | ! Store wall solution matrix and inverse 57 | write(*,*) 'Inverting wall influence matrix...' 58 | call cpu_time(t0) 59 | !$ t0 = omp_get_wtime() 60 | call invert_influence_matrix() 61 | call cpu_time(t1) 62 | !$ t1 = omp_get_wtime() 63 | print '("Time to invert influence matrix = ",f15.3," seconds.")',t1-t0 64 | 65 | end if 66 | 67 | 68 | ! Set up free surface system 69 | if (FSFlag==1) then 70 | 71 | ! Setup free surface self influence matrix 72 | do i=1,NumFSP 73 | do j=1,NumFSCP 74 | ! Rotation from global to panel i 75 | R(1,1:3)=FSXVec(i,1:3) 76 | R(2,1:3)=FSYVec(i,1:3) 77 | R(3,1:3)=FSZVec(i,1:3) 78 | 79 | ! Calc influence in panel frame 80 | dPG=FSCPPoints(j,1:3)-FSCPoints(i,1:3) 81 | Call CalcRotation3(R,dPG,Point,0) 82 | Call RectSourceVel(Point,FSPL(i),FSPW(i),1.0,0,FSEdgeTol,1,dVel,dudx) 83 | 84 | ! Rotate to global frame 85 | Call CalcRotation3(R,dVel,dVelG,1) 86 | FSInCoeffN(j,i)=sum(dVelG*FSCZVec(j,1:3)) 87 | FSInCoeffT(j,i)=sum(dVelG*FSCXVec(j,1:3)) 88 | FSInCoeffdUdX(j,i)=dudx 89 | end do 90 | end do 91 | 92 | ! Create free surface solution matrix and inverse 93 | if (UseFSWall) then 94 | ! Wall solution matrix 95 | FSSMat=FSInCoeffN 96 | else 97 | ! Free surface solution matrix 98 | FSSMat(1:NumFSCP,1:NumFSP)=FSInCoeffN-(FnR**2)*FSInCoeffdUdX 99 | ! Add inflow BCs 100 | FSBCRow(:)=0 101 | do i=1,NumFSCPz 102 | IBCInd=1+(i-1)*NumFSCPx 103 | BCRow=NumFSCP+i 104 | FSSMat(BCRow,1:NumFSP)=FSInCoeffdUdX(IBCInd,1:NumFSP) 105 | ! Set BC row index 106 | FSBCRow(IBCInd)=BCRow 107 | end do 108 | end if 109 | 110 | ! LAPACK => DGESV: Linear equation solution A*X=B where A(N,N) X(N,NRHS) B(N,NRHS) 111 | ! Note that if NRHS = N, and B is the identity, X is the inverse of A... 112 | ! Initialize inverse to the identity 113 | FSSMatI(:,:)=0.0 114 | do i=1,NumFSP 115 | do j=1,NumFSP 116 | if (j==i) then 117 | FSSMatI(i,j)=1.0 118 | end if 119 | end do 120 | end do 121 | 122 | allocate(IPIV(NumFSP)) ! allocation storage for pivot array 123 | Call DGESV(NumFSP,NumFSP,FSSMat,NumFSP,IPIV,FSSMatI,NumFSP,INFO) 124 | if (INFO>0) then 125 | write(6,'(A)') 'Matrix inversion failed in WSolnSetup. Exiting...' 126 | stop 127 | end if 128 | 129 | ! Initialize source strengths and RHS to zero 130 | FSSource(:,:)=0.0 131 | FSRHS(:,:)=0.0 132 | FSRHSInd=1 133 | 134 | end if 135 | 136 | return 137 | end subroutine WSolnSetup 138 | 139 | -------------------------------------------------------------------------------- /src/WriteFieldData.f90: -------------------------------------------------------------------------------- 1 | subroutine WriteFieldData() 2 | 3 | ! Write field data 4 | 5 | use fielddata 6 | use blade 7 | use wake 8 | use wallsoln 9 | use configr 10 | use fnames 11 | 12 | implicit none 13 | 14 | real :: xnode, ynode, znode 15 | integer :: ygcErr 16 | character(len=10) :: nt_str 17 | 18 | ! Open file for writing - a new file at each timestep 19 | write(nt_str,'(I5.5)') nt 20 | FieldOutputFN=adjustl(trim(FieldOutputPath))//path_separator//trim(FNBase)//'_FieldData_'//trim(nt_str)//'.csv' 21 | OPEN(13, FILE=FieldOutputFN) 22 | write(13,'(A)') trim(FieldOutHead) 23 | 24 | !! Compute wake data on specified cartesian grid 25 | ! Compute blade, wake, and wall induced velocity 26 | do zcount=1,nzgrid 27 | !$omp parallel do private(xcount,xnode,ynode,znode) 28 | do ycount=1,nygrid 29 | do xcount=1,nxgrid 30 | ! Get the grid node location 31 | xnode = XGrid(xcount,ycount,zcount) 32 | ynode = YGrid(xcount,ycount,zcount) 33 | znode = ZGrid(xcount,ycount,zcount) 34 | 35 | ! Calculate wall and wake induced velocities at grid locations 36 | Call CalcIndVel(NT,ntTerm,NBE,NB,NE, & 37 | xnode,ynode,znode, & 38 | VXInd(xcount,ycount,zcount),VYInd(xcount,ycount,zcount),VZInd(xcount,ycount,zcount)) 39 | 40 | ! Calculate free stream velocities at grid locations 41 | Call CalcFreestream(xnode,ynode,znode, & 42 | UfsGrid(xcount,ycount,zcount),VfsGrid(xcount,ycount,zcount),WfsGrid(xcount,ycount,zcount), & 43 | ygcErr) 44 | end do 45 | end do 46 | !$omp end parallel do 47 | end do 48 | 49 | 50 | ! Output blade, wake, and wall induced velocity 51 | do zcount=1,nzgrid 52 | do ycount=1,nygrid 53 | do xcount=1,nxgrid 54 | ! Write to file 55 | write(13,'(E14.7,",",$)') TimeN ! Normalized simulation time (t*Uinf/Rmax) 56 | write(13,'(E14.7,",",$)') XGrid(xcount,ycount,zcount) 57 | write(13,'(E14.7,",",$)') YGrid(xcount,ycount,zcount) ! grid node locations 58 | write(13,'(E14.7,",",$)') ZGrid(xcount,ycount,zcount) 59 | write(13,'(E14.7,",",$)') VXInd(xcount,ycount,zcount) 60 | write(13,'(E14.7,",",$)') VYInd(xcount,ycount,zcount) ! induced velocities 61 | write(13,'(E14.7,",",$)') VZInd(xcount,ycount,zcount) 62 | write(13,'(E14.7,",",$)') UfsGrid(xcount,ycount,zcount) 63 | write(13,'(E14.7,",",$)') VfsGrid(xcount,ycount,zcount) ! freestream velocities 64 | write(13,'(E14.7)' ) WfsGrid(xcount,ycount,zcount) ! Dont suppress carriage return on last column 65 | end do 66 | end do 67 | end do 68 | 69 | ! close the output file 70 | CLOSE(13) 71 | 72 | return 73 | end subroutine WriteFieldData 74 | -------------------------------------------------------------------------------- /src/WriteFinalOutput.f90: -------------------------------------------------------------------------------- 1 | subroutine WriteFinalOutput() 2 | 3 | use util 4 | use airfoil, only : ilxtp, iuxtp 5 | 6 | implicit none 7 | 8 | ! Check error flags and write notifications to stdout 9 | if (ilxtp .gt. 0) write (6,615) 10 | if (iuxtp .gt. 0) write (6,618) 11 | 12 | return 13 | 618 FORMAT ('AT LEAST ONE BLADE REYNOLDS NUMBER WAS ABOVE TABLE LIMIT. UPPER LIMIT USED.') 14 | 615 FORMAT ('AT LEAST ONE BLADE REYNOLDS NUMBER WAS BELOW TABLE LIMIT. LOWER LIMIT USED.') 15 | end subroutine WriteFinalOutput 16 | -------------------------------------------------------------------------------- /src/WriteRegTOutput.f90: -------------------------------------------------------------------------------- 1 | subroutine WriteRegTOutput(Flag) 2 | 3 | use regtest 4 | 5 | integer :: Flag 6 | if (Flag == 0) then 7 | write(7,'(A)') 'Timestep NLIter ElemNum ElemAOA ElemCirc dElemCirc BVDynFlagL LBCheck' 8 | else if (Flag == 1) then 9 | ! Write data during non-linear iteration 10 | write(7,'(3I3,3E13.5,2I3)') Reg_TS, Reg_NLIter, Reg_ElemNum, Reg_ElemAOA, Reg_ElemCirc, Reg_dElemCirc, Reg_DFL, Reg_LBC 11 | else if (Flag == 2) then 12 | ! Write cp average over timesteps 13 | write(7,'(A)') 'CPave' 14 | write(7,'(E13.5)') Reg_CPOut 15 | ! Write max calculated wall source strength 16 | write(7,'(A)') 'MaxWS' 17 | write(7,'(E13.5)') Reg_MaxWS 18 | ! Write max calculated velocity in first wake shed 19 | write(7,'(A)') 'MaxWakeV' 20 | write(7,'(E13.5)') Reg_MaxWVM 21 | end if 22 | 23 | return 24 | end subroutine WriteRegTOutput 25 | -------------------------------------------------------------------------------- /src/WriteWakeElementData.f90: -------------------------------------------------------------------------------- 1 | subroutine WriteWakeElemData() 2 | 3 | ! Write wake element positions and velocity 4 | 5 | use wakedata 6 | use blade 7 | use wake 8 | use wallsoln 9 | use configr 10 | use fnames 11 | use pathseparator 12 | 13 | implicit none 14 | 15 | integer :: tCount, tCountMax, wcount, node_id 16 | character(len=10) :: nt_str 17 | 18 | ! Optional wake element data output 19 | write(nt_str,'(I5.5)') nt 20 | WakeOutputFN=adjustl(trim(WakeElemOutputPath))//path_separator//trim(FNBase)//'_WakeElemData_'//trim(nt_str)//'.csv' 21 | OPEN(12, FILE=WakeOutputFN) 22 | write(12,'(A)') trim(WakeOutHead) 23 | 24 | tCountMax=nt 25 | do wcount=1,NWakeInd+nb 26 | do tCount=1,tCountMax 27 | ! compute the unique ID number of the current wake filament 28 | ! (higher numbers correspond to newer elements) 29 | node_id = (NWakeInd+nb)*(tcount-1) + wcount 30 | 31 | write(12,'(E14.7,",",$)') TimeN ! Normalized simulation time (t*Uinf/Rmax) 32 | write(12,'(I0,",",$)') node_id ! Unique node ID 33 | write(12,'(I0,",",$)') wcount ! Node number that wake element originated from 34 | write(12,'(E14.7,",",$)') X(tCount,wcount) ! Wake element X position 35 | write(12,'(E14.7,",",$)') Y(tCount,wcount) ! Wake element Y position 36 | write(12,'(E14.7,",",$)') Z(tCount,wcount) ! Wake element Z position 37 | write(12,'(E14.7,",",$)') U(tCount,wcount) ! Wake element X velocity 38 | write(12,'(E14.7,",",$)') V(tCount,wcount) ! Wake element Y velocity 39 | ! Dont suppress carriage return on last column 40 | write(12,'(E14.7)') W(tCount,wcount) ! Wake element Z velocity 41 | end do 42 | end do 43 | 44 | ! close the output file 45 | CLOSE(12) 46 | 47 | return 48 | end subroutine WriteWakeElemData 49 | -------------------------------------------------------------------------------- /src/bsload.f90: -------------------------------------------------------------------------------- 1 | subroutine bsload(nElem,IsBE,alpha,alpha5,alpha75,adotnorm,Re,umach,ur,CL,CD,CM25,CLCirc,CN,CT,Fx,Fy,Fz,te) 2 | 3 | use element 4 | use blade 5 | use pidef 6 | use configr 7 | use airfoil 8 | use dystl 9 | use util 10 | 11 | implicit none 12 | 13 | integer nElem, IsBE 14 | real alpha, alpha5, alpha75, adotnorm, Re, umach, ur, CL, CD, CM25, CLCirc, CN, CT, Fx, Fy, Fz, te 15 | 16 | integer SectInd, nElem1 17 | real ElemAreaR, ElemChordR, xe, ye, ze, nxe, nye, nze, txe, tye, tze, sxe, sye, sze 18 | real dal, wP, wPNorm 19 | real uAve, vAve, wAve, uFSAve, vFSAve, wFSAve, uBlade, vBlade, wBlade, urdn, urdc 20 | real xe5, ye5, ze5, xe75, ye75, ze75, uBlade5, vBlade5, wBlade5, uBlade75, vBlade75, wBlade75 21 | real urdn5, ur5, urdn75, ur75 22 | real FN, FT, MS, TRx, TRy, TRz, CircDir 23 | 24 | 25 | ! Calculates aero loads on a blade element. Static and dynamic airfoil characteristics calculated here... 26 | 27 | nElem1=nElem-1 ! Blade element is referenced by its upper end location index nElem, with nElem-1 being the lower end location index. 28 | 29 | 30 | ! Retrieve the blade segment geometric information 31 | 32 | ! Element span 33 | ElemAreaR=eArea(nElem) 34 | ElemChordR=eChord(nElem) 35 | 36 | ! Quarter chord location 37 | xe=xBC(nElem) 38 | ye=yBC(nElem) 39 | ze=zBC(nElem) 40 | 41 | ! Element normal, tangential, and spanwise vectors 42 | nxe=nxBC(nElem) 43 | nye=nyBC(nElem) 44 | nze=nzBC(nElem) 45 | txe=txBC(nElem) 46 | tye=tyBC(nElem) 47 | tze=tzBC(nElem) 48 | sxe=sxBC(nElem) 49 | sye=syBC(nElem) 50 | sze=szBC(nElem) 51 | 52 | ! Direction of circulation in wake grid at positive lift 53 | CircDir=CircSign(nElem) 54 | 55 | ! Airfoil section 56 | SectInd=isect(nElem) 57 | 58 | 59 | ! Calculate the local blade segment angle of attack 60 | 61 | ! Wall and wake induced velocity 62 | uAve=(uB(nElem)+uB(nElem1))/2.0 63 | vAve=(vB(nElem)+vB(nElem1))/2.0 64 | wAve=(wB(nElem)+wB(nElem1))/2.0 65 | ! Freestream velocity 66 | uFSAve=(uFSB(nElem)+uFSB(nElem1))/2.0 67 | vFSAve=(vFSB(nElem)+vFSB(nElem1))/2.0 68 | wFSAve=(wFSB(nElem)+wFSB(nElem1))/2.0 69 | ! Blade velocity due to rotation 70 | CALL CalcBladeVel(wRotX,wRotY,wRotZ,xe,ye,ze,uBlade,vBlade,wBlade) 71 | 72 | ! Calc element normal and tangential velocity components. Calc element pitch rate. 73 | urdn = (nxe*(uAve+uFSAve-uBlade)+nye*(vAve+vFSAve-vBlade)+nze*(wAve+wFSAve-wBlade)) ! Normal 74 | urdc = (txe*(uAve+uFSAve-uBlade)+tye*(vAve+vFSAve-vBlade)+tze*(wAve+wFSAve-wBlade)) ! Tangential 75 | wP = sxe*wRotX+sye*wRotY+sze*wRotZ 76 | 77 | ur=sqrt(urdn**2+urdc**2) 78 | alpha=atan2(urdn,urdc) 79 | wPNorm=wP*ElemChordR/(2.0*max(ur,0.001)) ! wP*c/(2*U) 80 | 81 | Re=ReM*ElemChordR*ur 82 | umach=ur*Minf 83 | 84 | !--------- 85 | ! These .5c and .75c locations are used to calc pitch rate effects 86 | if (PRFlag/=0) then 87 | xe5=xe+0.25*ElemChordR*txe 88 | ye5=ye+0.25*ElemChordR*tye 89 | ze5=ze+0.25*ElemChordR*tze 90 | xe75=xe+0.5*ElemChordR*txe 91 | ye75=ye+0.5*ElemChordR*tye 92 | ze75=ze+0.5*ElemChordR*tze 93 | CALL CalcBladeVel(wRotX,wRotY,wRotZ,xe5,ye5,ze5,uBlade5,vBlade5,wBlade5) 94 | CALL CalcBladeVel(wRotX,wRotY,wRotZ,xe75,ye75,ze75,uBlade75,vBlade75,wBlade75) 95 | urdn5 = (nxe*(uAve+uFSAve-uBlade5)+nye*(vAve+vFSAve-vBlade5)+nze*(wAve+wFSAve-wBlade5)) 96 | ur5=sqrt(urdn5**2+urdc**2) 97 | alpha5=atan2(urdn5,urdc) 98 | urdn75 = (nxe*(uAve+uFSAve-uBlade75)+nye*(vAve+vFSAve-vBlade75)+nze*(wAve+wFSAve-wBlade75)) 99 | ur75=sqrt(urdn75**2+urdc**2) 100 | alpha75=atan2(urdn75,urdc) 101 | else 102 | alpha5=alpha 103 | alpha75=alpha 104 | wPNorm=0.0 105 | end if 106 | 107 | dal=alpha75-AOA_Last(nelem1) 108 | adotnorm=dal/DT*ElemChordR/(2.0*max(ur,0.001)) ! adot*c/(2*U) 109 | !-------- 110 | 111 | ! Evaluate aero coefficients and dynamic stall effects as appropriate 112 | Call AeroCoeffs(nElem,alpha75,alpha5,Re,wPNorm,adotnorm,umach,SectInd,IsBE,CL,CD,CN,CT,CLCirc,CM25) 113 | 114 | ! Bound vortex strength from CL via Kutta-Joukowski analogy. 115 | ! Save corresponding AOA as well 116 | GB_Raw(nElem1)=CircDir*(CLCirc*ElemChordR*ur/2.0) 117 | ! AOA(nElem1)=alpha 118 | AOA(nElem1)=alpha75 119 | ! normalized time step used to update states in the LB model 120 | ds(nElem)=2.0*ur*DT/ElemChordR 121 | 122 | ! Force and moment coeff. from this blade element, re-referenced to full turbine scale 123 | ! (F/(1/2*rho*Uinf^2*At) and M/(1/2*rho*Uinf^2*At*R) 124 | FN=CN*(ElemAreaR/at)*ur**2 125 | FT=CT*(ElemAreaR/at)*ur**2 126 | MS=CM25*ElemChordR*(ElemAreaR/at)*ur**2 127 | ! Corresponding torque coeff. (T/(1/2*rho*Uinf^2*At*R)) 128 | Fx=FN*nxe+FT*txe 129 | Fy=FN*nye+FT*tye 130 | Fz=FN*nze+FT*tze 131 | CALL cross(xe-RotPX,ye-RotPY,ze-RotPZ,Fx,Fy,Fz,TRx,TRy,TRz) 132 | te=(TRx*RotX+TRy*RotY+TRz*RotZ)+MS*(sxe*RotX+sye*RotY+sze*RotZ) 133 | 134 | return 135 | end subroutine bsload 136 | -------------------------------------------------------------------------------- /src/conlp.f90: -------------------------------------------------------------------------------- 1 | subroutine conlp() 2 | 3 | use configr 4 | use blade 5 | use wake 6 | 7 | logical NotDone 8 | 9 | ! Calculate the convection of shed vortex lattice points 10 | 11 | NT1=NT-1 12 | do I=1,NE 13 | if (NT > 1) then 14 | do J=ntTerm,NT1 15 | 16 | if (iut == 1) then 17 | ! Temporally "upwinded" calculation for the velocity to use in 18 | ! convecting the wake (use velocity extrapolated to t=t+.5*dt) 19 | X(J,I)=X(J,I)+(3.0*(U(J,I)+UFS(J,I))-UO(J,I))*DT/2.0 20 | Y(J,I)=Y(J,I)+(3.0*(V(J,I)+VFS(J,I))-VO(J,I))*DT/2.0 21 | Z(J,I)=Z(J,I)+(3.0*(W(J,I)+WFS(J,I))-WO(J,I))*DT/2.0 22 | else 23 | ! If wake velocities are not updated every timestep, simply 24 | ! assume constant velocity over a single timestep. 25 | X(J,I)=X(J,I)+(U(J,I)+UFS(J,I))*DT 26 | Y(J,I)=Y(J,I)+(V(J,I)+VFS(J,I))*DT 27 | Z(J,I)=Z(J,I)+(W(J,I)+WFS(J,I))*DT 28 | end if 29 | 30 | end do 31 | end if 32 | 33 | ! Use straight integration for newest wake points for which there is no old data 34 | X(NT,I)=X(NT,I)+(U(NT,I)+UFS(NT,I))*DT 35 | Y(NT,I)=Y(NT,I)+(V(NT,I)+VFS(NT,I))*DT 36 | Z(NT,I)=Z(NT,I)+(W(NT,I)+WFS(NT,I))*DT 37 | end do 38 | 39 | ! If ixterm is 1, update ntTerm as the farthest index in the wake that has at least one point below XSTOP 40 | if (IXTERM .eq. 1) then 41 | NotDone = .true. 42 | do while (NotDone .AND. ntTerm < NT1) 43 | ! Check 44 | do I=1,NE 45 | if (X(ntTerm,I) < XSTOP) then 46 | NotDone=.false. 47 | end if 48 | end do 49 | 50 | if (NotDone) then 51 | ntTerm=ntTerm+1 52 | end if 53 | end do 54 | end if 55 | 56 | return 57 | end subroutine conlp 58 | -------------------------------------------------------------------------------- /src/mod/blade.f90: -------------------------------------------------------------------------------- 1 | module blade 2 | 3 | ! Blade data 4 | 5 | ! Blade loading data 6 | real, allocatable :: GB(:) ! Bound vorticity 7 | real, allocatable :: OGB(:) ! Old bound vorticity (previous time step) 8 | real, allocatable :: GB_Raw(:) ! Raw (pre-filter) bound vorticity 9 | real, allocatable :: AOA(:) ! AOA on blade elements 10 | real, allocatable :: AOA_Last(:) ! Last value of AOA on blade elements 11 | 12 | real, allocatable :: UIWake(:) ! Velocity induced at blade from wake 13 | real, allocatable :: VIWake(:) ! Velocity induced at blade from wake 14 | real, allocatable :: WIWake(:) ! Velocity induced at blade from wake 15 | 16 | ! Induced velocity on blade lattice points 17 | real, allocatable :: UB(:) ! Lattice point x velocity for each element end lattice point 18 | real, allocatable :: VB(:) ! Lattice point y velocity for each element end lattice point 19 | real, allocatable :: WB(:) ! Lattice point z velocity for each element end lattice point 20 | 21 | ! Freestream velocity on blade lattice points 22 | real, allocatable :: UFSB(:) ! Lattice point x velocity for each element end lattice point 23 | real, allocatable :: VFSB(:) ! Lattice point y velocity for each element end lattice point 24 | real, allocatable :: WFSB(:) ! Lattice point z velocity for each element end lattice point 25 | 26 | ! Timestep filter 27 | integer :: TSFilFlag ! 1 to enable timestep filtering, 0 for no filtering (default) 28 | integer :: ntsf ! Number of timesteps over which the bound vorticity is filtered smooth (if TSFilFlag = 1) 29 | real :: KTF 30 | 31 | contains 32 | 33 | subroutine blade_cns(MaxSegEnds) 34 | 35 | ! Constructor - allocates memory for arrays containing element induced velocities on all blades 36 | 37 | integer :: MaxSegEnds 38 | 39 | allocate(GB(MaxSegEnds)) 40 | allocate(OGB(MaxSegEnds)) 41 | allocate(GB_Raw(MaxSegEnds)) 42 | allocate(AOA(MaxSegEnds)) 43 | allocate(AOA_Last(MaxSegEnds)) 44 | allocate(UIWake(MaxSegEnds)) 45 | allocate(VIWake(MaxSegEnds)) 46 | allocate(WIWake(MaxSegEnds)) 47 | allocate(UB(MaxSegEnds)) 48 | allocate(VB(MaxSegEnds)) 49 | allocate(WB(MaxSegEnds)) 50 | allocate(UFSB(MaxSegEnds)) 51 | allocate(VFSB(MaxSegEnds)) 52 | allocate(WFSB(MaxSegEnds)) 53 | 54 | 55 | end subroutine blade_cns 56 | 57 | subroutine UpdateAOALast(ne) 58 | 59 | integer :: ne 60 | integer :: k 61 | 62 | ! Save last AOA values for each element 63 | 64 | do k=1,ne 65 | AOA_Last(k)=AOA(k) 66 | end do 67 | 68 | end subroutine UpdateAOALast 69 | 70 | subroutine UpdateTSFilter(ne) 71 | 72 | integer :: ne 73 | integer :: k 74 | 75 | ! Update filtered bound vorticity (filtered smooth over approx. ntsf timesteps using a first order discrete filter) 76 | 77 | do k=1,ne 78 | GB(k)=KTF*GB_Raw(k) + (1.0-KTF)*GB(k) 79 | end do 80 | 81 | end subroutine UpdateTSFilter 82 | 83 | end module blade 84 | -------------------------------------------------------------------------------- /src/mod/compiler.f90: -------------------------------------------------------------------------------- 1 | #define xstr(s) str(s) 2 | #define str(s) #s 3 | 4 | module compiler 5 | 6 | ! compiler module 7 | 8 | implicit none 9 | 10 | ! Include mkl.fi include file if using Intel Fortran compiler 11 | #if defined(__INTEL_COMPILER) 12 | include "mkl.fi" 13 | #endif 14 | 15 | character(80) :: COMPILER_STRING, COMPILER_VER 16 | 17 | contains 18 | 19 | 20 | subroutine print_compiler_info() 21 | 22 | ! print_compiler_info() : print compiler info to stdout 23 | 24 | #if defined(__GFORTRAN__) 25 | COMPILER_VER = __VERSION__ 26 | write(*,'(A,A)') "Compiled with GNU Fortran version: ", COMPILER_VER 27 | #elif defined(__INTEL_COMPILER) 28 | COMPILER_VER = xstr(__INTEL_COMPILER) 29 | write(*,'(A,A,".",A)') "Compiled with Intel Fortran version: ", COMPILER_VER(1:2), COMPILER_VER(3:4) 30 | #else 31 | write(*,'(A)') "Compiled with unknown compiler." 32 | #endif 33 | 34 | ! Alert if MKL is enabled 35 | #if defined(__INTEL_COMPILER) 36 | !dec$if defined(__INTEL_MKL__) 37 | write(*,'(A)') "Intel MKL enabled." 38 | !dec$endif 39 | #endif 40 | 41 | ! print the Git version (VERSION must be defined via Makefile macro and defined 42 | ! with compiler flag -DVERSION='') 43 | #if defined(VERSION) 44 | write(*,'(A,A)') "Git version: ", VERSION 45 | #else 46 | write(*,'(A)') "Git version: unknown" 47 | #endif 48 | 49 | end subroutine print_compiler_info 50 | 51 | 52 | end module compiler 53 | -------------------------------------------------------------------------------- /src/mod/configr.f90: -------------------------------------------------------------------------------- 1 | module configr 2 | 3 | ! Configuration data 4 | 5 | ! Input flags 6 | character*80 :: jbtitle ! job title 7 | integer :: ifc ! Flag to use final convergence step 8 | integer :: iXTerm ! Flag to ignore wake points beyond xstop 9 | integer :: DiagOutFlag ! Set to 1 to print iteration info to stdout 10 | integer :: PRFlag ! 0 for no pitch rate aero effects, 1 to include these effects 11 | 12 | ! Params 13 | integer :: nb ! Number of blades 14 | integer :: nbe ! Number of blade segments/elements in a blade 15 | integer :: ne ! Number of blade segment ends (total) 16 | integer :: iut ! Number of time steps over which the wake velocities are left constant (Note: values of 0, -1, and -2 have defined meanings) 17 | integer :: nr ! Number of revolutions to perform 18 | integer :: iRev ! Revolution counter 19 | integer :: nt ! Time step counter 20 | integer :: nti ! Number of time steps per revolution 21 | integer :: nric ! Intermediate rev at which to switch to final convergence (if ifc = 1) 22 | integer :: ntif ! final number of time steps per revolution (used instead of nti during final convergence step if ifc = 1) 23 | integer :: iutf ! final number of time steps between updating the wake velocities (used instead of iut during final convergence step if ifc = 1) 24 | integer :: ntTerm ! Time step level beyond which wake is ignored (if iXTerm = 1) 25 | integer :: nSect ! Number of airfoil section data tables used 26 | integer :: nsw ! Next iteration at which wake velocities will be calculated 27 | integer :: nsWall ! Next iteration at which the wall models will be updated 28 | real :: convrg ! Convergence level (Note: for no convergence check, input -1) 29 | real :: convrgf ! Convergence level to be used for final convergence step (if ifc = 1) (Note: for no convergence check, input -1) 30 | real :: XStop ! If iXTerm = 1, ignore wake beyond x = xstop 31 | real :: CTExcrM ! Additional machine level excrescence torque based on tip speed and Rmax 32 | real :: VCRFB ! Vortex core radius factor (on max blade chord) for bound vortex 33 | real :: VCRFT ! Vortex core radius factor (on blade discretization level) for trailing wake vorticies 34 | real :: VCRFS ! Vortex core radius factor (on temporal discretization level) for spanwise wake vorticies 35 | 36 | integer :: WakeElemOutIntervalTimesteps ! Number of revolutions between writing wake data 37 | integer :: WakeElemOutStartTimestep ! Revolution number at which to start writing wake data 38 | integer :: WakeElemOutEndTimestep ! Revolution number at which to stop writing wake data 39 | 40 | integer :: FieldOutIntervalTimesteps ! Number of revolutions between writing wake data 41 | integer :: FieldOutStartTimestep ! Revolution number at which to start writing wake data 42 | integer :: FieldOutEndTimestep ! Revolution number at which to stop writing wake data 43 | 44 | integer :: WallOutIntervalTimesteps ! Number of revolutions between writing wall data 45 | integer :: WallOutStartTimestep ! Revolution number at which to start writing wall data 46 | integer :: WallOutEndTimestep ! Revolution number at which to stop writing wall data 47 | 48 | real :: ut ! Tip speed ratio 49 | real :: dt ! Normalized timestep 50 | real :: delt ! Phase angle step 51 | real :: wRotX ! Normalized machine angular velocity X 52 | real :: wRotY ! Normalized machine angular velocity Y 53 | real :: wRotZ ! Normalized machine angular velocity Z 54 | real :: RotX ! Machine rotation axis X 55 | real :: RotY ! Machine rotation axis Y 56 | real :: RotZ ! Machine rotation axis Z 57 | real :: RotPX ! Rotation origin X 58 | real :: RotPY ! Rotation origin Y 59 | real :: RotPZ ! Rotation origin Z 60 | real :: ReM ! Machine Reynolds number based on Rmax 61 | real :: MInf ! Freestream mach number 62 | 63 | real :: ForceC ! Output force normalization 64 | real :: TorqueC ! Output torque normalization 65 | real :: PowerC ! Output power normalization 66 | real :: AT ! Normalized frontal area (frontal area / (equitorial radius)^2) 67 | real :: AreaT ! Frontal area 68 | 69 | ! Current normalized time and phase angle (Theta) 70 | real :: Theta ! Current turbine rotation phase angle 71 | real :: TimeN ! Current normalized time 72 | 73 | ! Rev average quantities 74 | real :: CPAve ! Rev average power coeff 75 | real :: KPAve ! Rev average tip power coeff 76 | real :: CTRAve ! Rev average torque coeff 77 | real :: CFxAve ! Rev average Fx coeff 78 | real :: CFyAve ! Rev average Fy coeff 79 | real :: CFzAve ! Rev average Fz coeff 80 | real :: PowerAve ! Rev average power 81 | real :: TorqueAve ! Rev average torque 82 | real :: CPSum 83 | real :: CTRSum 84 | real :: CFxSum 85 | real :: CFySum 86 | real :: CFzSum 87 | 88 | end module configr 89 | -------------------------------------------------------------------------------- /src/mod/fielddata.f90: -------------------------------------------------------------------------------- 1 | module fielddata 2 | 3 | implicit none 4 | 5 | ! module to initialize and store field velocity data for output 6 | ! used by WriteFieldData.f95 7 | 8 | integer :: FieldOutFlag 9 | 10 | ! Velocity calculation on a grid 11 | character(1000) :: FieldOutHead = 'Normalized Time (-),X/R (-),Y/R (-),Z/R (-),U/Uinf (-),V/Uinf (-),W/Uinf (-),Ufs/Uinf (-),Vfs/Uinf (-),Wfs/Uinf (-)' 12 | 13 | ! number of grid elements in each direction 14 | integer :: nxgrid 15 | integer :: nygrid 16 | integer :: nzgrid 17 | 18 | ! grid extents 19 | real :: xgridL 20 | real :: xgridU 21 | real :: ygridL 22 | real :: ygridU 23 | real :: zgridL 24 | real :: zgridU 25 | 26 | ! grid spacing 27 | real :: dxgrid, dygrid, dzgrid 28 | 29 | ! arrays holding grid locations 30 | real, allocatable :: XGrid(:,:,:) 31 | real, allocatable :: YGrid(:,:,:) 32 | real, allocatable :: ZGrid(:,:,:) 33 | real, allocatable :: VXInd(:,:,:) 34 | real, allocatable :: VYInd(:,:,:) 35 | real, allocatable :: VZInd(:,:,:) 36 | real, allocatable :: UfsGrid(:,:,:) 37 | real, allocatable :: VfsGrid(:,:,:) 38 | real, allocatable :: WfsGrid(:,:,:) 39 | 40 | ! array index counters 41 | integer :: xcount, ycount, zcount 42 | 43 | contains 44 | 45 | subroutine fielddata_cns() 46 | 47 | ! allocate location and velocity arrays 48 | allocate(XGrid(nxgrid,nygrid,nzgrid)) 49 | allocate(YGrid(nxgrid,nygrid,nzgrid)) 50 | allocate(ZGrid(nxgrid,nygrid,nzgrid)) 51 | allocate(VXInd(nxgrid,nygrid,nzgrid)) 52 | allocate(VYInd(nxgrid,nygrid,nzgrid)) 53 | allocate(VZInd(nxgrid,nygrid,nzgrid)) 54 | allocate(UfsGrid(nxgrid,nygrid,nzgrid)) 55 | allocate(VfsGrid(nxgrid,nygrid,nzgrid)) 56 | allocate(WfsGrid(nxgrid,nygrid,nzgrid)) 57 | 58 | ! Compute the appropriate grid spacing 59 | ! (if number of grids is 1 in any direction, catch a divide-by-zero error) 60 | if (nxgrid==1) then 61 | dxgrid=0.0 62 | else 63 | dxgrid=(xgridU-xgridL)/(nxgrid-1) 64 | end if 65 | 66 | if (nygrid==1) then 67 | dygrid=0.0 68 | else 69 | dygrid=(ygridU-ygridL)/(nygrid-1) 70 | end if 71 | 72 | if (nzgrid==1) then 73 | dzgrid=0.0 74 | else 75 | dzgrid=(zgridU-zgridL)/(nzgrid-1) 76 | end if 77 | 78 | ! Set up grid node locations and initialize induced velocities 79 | do zcount=1,nzgrid 80 | do ycount=1,nygrid 81 | do xcount=1,nxgrid 82 | ! Setup grid node locations 83 | XGrid(xcount,ycount,zcount)=xgridL+(xcount-1)*dxgrid 84 | YGrid(xcount,ycount,zcount)=ygridL+(ycount-1)*dygrid 85 | ZGrid(xcount,ycount,zcount)=zgridL+(zcount-1)*dzgrid 86 | 87 | ! Initialize induced velocities to zero 88 | VXInd(xcount,ycount,zcount)=0.0 89 | VYInd(xcount,ycount,zcount)=0.0 90 | VZInd(xcount,ycount,zcount)=0.0 91 | 92 | ! Initialize freestream velocities to zero 93 | UfsGrid(xcount,ycount,zcount)=0.0 94 | VfsGrid(xcount,ycount,zcount)=0.0 95 | WfsGrid(xcount,ycount,zcount)=0.0 96 | end do 97 | end do 98 | end do 99 | end subroutine fielddata_cns 100 | 101 | end module fielddata -------------------------------------------------------------------------------- /src/mod/fnames.f90: -------------------------------------------------------------------------------- 1 | Module fnames 2 | 3 | use pathseparator 4 | 5 | implicit none 6 | 7 | integer :: nargin, FNLength, status, DecInd 8 | character(80) :: InputFN, SFOutputFN, RevOutputFN, TSOutputFN, ELOutputFN, RegOutputFN, WakeOutputFN, FieldOutputFN, GPOutputFN, FSOutputFN, DSOutputFN, FNBase 9 | character(255) :: OutputPath,WakeElemOutputPath,WallOutputPath,FieldOutputPath,ProbeOutputPath 10 | 11 | contains 12 | 13 | subroutine get_FNBase() 14 | logical :: back 15 | 16 | Call get_command_argument(1,InputFN,FNLength,status) 17 | 18 | back=.true. 19 | FNBase=InputFN((index(InputFN,'/',back)+1):len(InputFN)) 20 | DecInd=index(FNBase,'.',back) 21 | 22 | if (DecInd > 1) then 23 | FNBase=FNBase(1:(DecInd-1)) 24 | end if 25 | 26 | end subroutine get_FNBase 27 | 28 | End Module fnames 29 | -------------------------------------------------------------------------------- /src/mod/iecgust.f90: -------------------------------------------------------------------------------- 1 | module iecgust 2 | 3 | ! Parameters defining IEC Gust 4 | 5 | integer :: Igust ! Flag for IEC Gust 6 | real :: gustamp ! Gust Amplitude (m/s) 7 | real :: gusttime ! Gust Timescale (sec) 8 | real :: gustA ! Non-dimensional gust amplitude (U'/Uinf) 9 | real :: gustT ! Non-dimensional gust timescale (T*Uinf/Rmax) 10 | real :: gustX0 ! Starting position of the gust upstream (# rotor radii) 11 | 12 | end module iecgust 13 | -------------------------------------------------------------------------------- /src/mod/output.f90: -------------------------------------------------------------------------------- 1 | module output 2 | 3 | ! Arrays to be output to csv data files 4 | 5 | ! Output flags 6 | integer :: BladeElemOutFlag ! Set to 1 to output loads data at each timestep for each element, 0 to omit this output 7 | integer :: DynStallOutFlag ! Set to 1 to output dynamic stall diagnostic output 8 | 9 | ! Scale parameters and flow properties 10 | character(10000) :: ParamsOutHead = 'R (ft),Frontal Area (ft^2),RPM,U (ft/s),rho (slugs/ft^3),tempr (degF),vis (slugs/(ft*s)),q (lb/ft^2),TSR (-),ReM (-),FnR (-)' 11 | real :: ParamsOutData(1,11) ! Data 12 | 13 | ! Revolution average data 14 | character(10000) :: RevOutHead = 'Rev,Power Coeff. (-),Tip Power Coeff. (-),Torque Coeff. (-),Fx Coeff. (-),Fy Coeff. (-),Fz Coeff. (-),Power (kW),Torque (ft-lbs)' 15 | real :: RevOutData(1,9) ! Revolution average data for each revolution 16 | 17 | ! Timestep data 18 | character(10000) :: TSOutHead = 'Normalized Time (-),Theta (rad),Rev,Torque Coeff. (-),Power Coeff. (-),Fx Coeff. (-),Fy Coeff. (-),Fz Coeff. (-)' 19 | character(1000) :: TSOutHeadBlade = 'Blade Fx Coeff. (-),Blade Fy Coeff. (-),Blade Fz Coeff. (-),Blade Torque Coeff. (-)' 20 | character(1000) :: TSOutHeadStrut = 'Strut Fx Coeff. (-),Strut Fy Coeff. (-),Strut Fz Coeff. (-),Strut Torque Coeff. (-)' 21 | integer :: TSOutNCols ! Number of timestep outputs 22 | real, allocatable :: TSOutData(:,:) ! Timestep data for each timestep 23 | 24 | ! Element loads data 25 | character(10000) :: BladeElemOutHead = 'Normalized Time (-),Theta (rad),Blade,Element,Rev,x/R (-),y/R (-),z/R (-),AOA25 (deg),AOA50 (deg),AOA75 (deg),AdotNorm (-),Re (-),Mach (-),Ur (-),IndU (-),IndV (-),IndW (-),GB (?),CL (-),CD (-),CM25 (-),& 26 | &CLCirc (-),CN (-),CT (-),Fx (-),Fy (-),Fz (-),te (-)' 27 | integer :: BladeElemOutNCols = 29 ! Number of element loads outputs 28 | integer :: BladeElemOutRow ! Rows of element row output 29 | real, allocatable :: BladeElemOutData(:,:) ! Element loads data for each timestep 30 | 31 | ! Dynamic stall diagnostic output 32 | integer :: DynStallOutType 33 | character(10000) :: DynStallOutBVHead = 'Normalized Time (-),Theta (rad),Blade,Element,Rev,alpha (deg),adotnorm (-),alrefL (deg),alrefD (deg),DynamicFlagL,DynamicFlagD' 34 | integer :: DynStallOutBVNCols = 11 ! Number of BV diagnostic outputs 35 | integer :: DynStallOutBVRow ! Rows of element row output 36 | real, allocatable :: DynStallOutBVData(:,:) ! Diagnostic data for each timestep 37 | character(10000) :: DynStallOutLBHead = 'Normalized Time (-),Theta (rad),Blade,Element,Rev,L1,L2,L3,L4,L5,L6,L7,L8,L9,Check' 38 | integer :: DynStallOutLBNCols = 15 ! Number of LB diagnostic outputs 39 | integer :: DynStallOutLBRow ! Rows of element row output 40 | real, allocatable :: DynStallOutLBData(:,:) ! Diagnostic data for each timestep 41 | 42 | contains 43 | 44 | subroutine output_cns(MaxSeg, MaxBlades, MaxStruts, DSFlag) 45 | 46 | ! Constructor for the arrays in this module 47 | 48 | integer :: MaxSeg, MaxBlades, MaxStruts, DSFlag 49 | 50 | integer :: i 51 | 52 | ! Calculate number of timestep outputs 53 | TSOutNCols=8+4*MaxBlades+4*MaxStruts 54 | allocate(TSOutData(1,TSOutNCols)) 55 | do i=1,MaxBlades 56 | TSOutHead=trim(TSOutHead)//','//trim(TSOutHeadBlade) 57 | end do 58 | do i=1,MaxStruts 59 | TSOutHead=trim(TSOutHead)//','//trim(TSOutHeadStrut) 60 | end do 61 | 62 | if (BladeElemOutFlag == 1) then 63 | allocate(BladeElemOutData(MaxSeg,BladeElemOutNCols)) 64 | end if 65 | 66 | if (DynStallOutFlag == 1 .AND. DSFlag == 1) then 67 | DynStallOutType = 1 68 | allocate(DynStallOutBVData(MaxSeg,DynStallOutBVNCols)) 69 | else if (DynStallOutFlag == 1 .AND. DSFlag == 2) then 70 | DynStallOutType = 2 71 | allocate(DynStallOutLBData(MaxSeg,DynStallOutLBNCols)) 72 | end if 73 | 74 | 75 | end subroutine output_cns 76 | 77 | end module output 78 | -------------------------------------------------------------------------------- /src/mod/parameters.f90: -------------------------------------------------------------------------------- 1 | module parameters 2 | 3 | ! Sizes for arrays 4 | 5 | integer :: MaxBlades, MaxSegPerBlade, MaxSegEndPerBlade, MaxSegEnds, MaxSeg ! Maximums for blade geometry arrays 6 | integer :: MaxStruts ! Maximums for strut arrays 7 | integer :: MaxAirfoilSect, MaxReVals, MaxAOAVals ! Maximums for airfoil coeff data arrays 8 | integer :: MaxRevs ! Max revolutions 9 | integer :: MaxTimeStepPerRev ! Max time increments in a revolution 10 | integer :: MaxTimeSteps ! Max total time increments (MaxRevs * MaxTimeStepPerRev) 11 | integer :: MaxWakeNodes ! Max number of points in wake for an element (MaxRevs * MaxTimeStepPerRev) 12 | integer :: MaxNLIters ! Max iterations to perform in converging non linear component of blade element system 13 | 14 | end module parameters 15 | -------------------------------------------------------------------------------- /src/mod/pathseparator.f90: -------------------------------------------------------------------------------- 1 | module pathseparator 2 | 3 | implicit none 4 | 5 | character(1) :: path_separator 6 | 7 | contains 8 | 9 | subroutine get_path_separator() 10 | 11 | #if defined(PATHSEP) 12 | path_separator = PATHSEP 13 | #else 14 | ! fallback 15 | path_separator = '/' 16 | #endif 17 | end subroutine get_path_separator 18 | 19 | end module pathseparator -------------------------------------------------------------------------------- /src/mod/probesystem.f90: -------------------------------------------------------------------------------- 1 | module probesystem 2 | 3 | ! probe module for computing and writing velocities at a specified location 4 | 5 | use configr 6 | use pathseparator 7 | 8 | implicit none 9 | 10 | ! probe output options 11 | integer :: ProbeFlag ! flag for enabling probes (1 for probes, 0 for none) 12 | integer :: ProbeOutIntervalTimesteps ! interval between probe writes 13 | integer :: ProbeOutStartTimestep ! timestep at which to start writing probe data 14 | integer :: ProbeOutEndTimestep ! timestep at which to end writing probe data 15 | 16 | ! probe output header flag 17 | logical :: ProbeHeadersWritten = .False. 18 | 19 | type ProbeType 20 | 21 | real :: p(3) ! probe location 22 | real :: vel(3) ! probe velocity (at a single instant) 23 | real :: vel_fs(3) ! probe free-stream velocity (at a single instant) 24 | character(80) :: output_filename 25 | 26 | end type ProbeType 27 | 28 | type(ProbeType), allocatable :: probes(:) ! array to hold probes 29 | integer :: nprobes ! number of probes 30 | integer, parameter :: probe_iounit = 28 ! IO unit for probes 31 | 32 | 33 | contains 34 | 35 | subroutine compute_probe_vel(probe) 36 | 37 | ! compute_probe_vel() : computes the induced velocity and free-stream velocity 38 | ! at the probe location and stores it to the probe variables 39 | 40 | type(ProbeType), intent(inout) :: probe 41 | integer :: ygcErr ! error flag for CalcFreeStream 42 | 43 | ! Calculate wall and wake induced velocities at grid locations 44 | Call CalcIndVel(NT,ntTerm,NBE,NB,NE, & 45 | probe%p(1),probe%p(2),probe%p(3), & 46 | probe%vel(1),probe%vel(2),probe%vel(3)) 47 | 48 | ! Calculate free stream velocities at grid locations 49 | Call CalcFreestream(probe%p(1),probe%p(2),probe%p(3), & 50 | probe%vel_fs(1),probe%vel_fs(2),probe%vel_fs(3), & 51 | ygcErr) 52 | 53 | end subroutine compute_probe_vel 54 | 55 | 56 | subroutine read_probes(probespec_filename) 57 | ! read_probes() : reads in the coordinates of probes from a file 58 | ! Coordinates should be formatted as 59 | ! nprobes 60 | ! x1 y1 z1 61 | ! x2 y2 z2 62 | ! ... 63 | ! EOF 64 | 65 | integer i 66 | integer, parameter :: iounit = 20 67 | character(len=*), intent(in) :: probespec_filename ! Probe specification filename 68 | 69 | ! open file for reading 70 | open(unit=iounit, form='formatted', file=probespec_filename) 71 | 72 | ! read number of probes 73 | read(iounit,*) nprobes 74 | 75 | ! allocate for probes 76 | allocate(probes(nprobes)) 77 | 78 | ! read probe coordinates 79 | do i=1,nprobes 80 | read(iounit,*) probes(i)%p(1), probes(i)%p(2), probes(i)%p(3) 81 | end do 82 | 83 | ! close file 84 | close(unit=iounit) 85 | 86 | end subroutine read_probes 87 | 88 | 89 | subroutine write_probe_headers(probe_output_path) 90 | 91 | ! write_probe_headers() : writes the headers for the probe output files to the specified 92 | ! probe_output_path 93 | 94 | integer :: probe_num 95 | character(7198), parameter :: probe_file_header='X/R (-),Y/R (-),Z/R (-)' 96 | character(7198), parameter :: probe_data_header='Normalized Time (-),U/Uinf (-),V/Uinf (-),W/Uinf (-),Ufs/Uinf (-),Vfs/Uinf (-),Wfs/Uinf (-)' 97 | character(80) :: probe_num_str 98 | character(80) :: probe_output_path 99 | 100 | ! write header files 101 | do probe_num=1,nprobes 102 | 103 | ! assemble the probe filename in the pattern 104 | ! [run_name]_probe_[probe_num].csv 105 | write(probe_num_str,'(I5.5)') probe_num 106 | probes(probe_num)%output_filename = 'probe_'//trim(probe_num_str)//'.csv' 107 | 108 | ! open/write header/close file 109 | open(probe_iounit, file=trim(adjustl(probe_output_path))//path_separator//probes(probe_num)%output_filename) 110 | 111 | ! write probe location 112 | write(probe_iounit,'(A)') trim(probe_file_header) 113 | write(probe_iounit,'(E13.7,",",$)') probes(probe_num)%p(1) 114 | write(probe_iounit,'(E13.7,",",$)') probes(probe_num)%p(2) ! probe location 115 | write(probe_iounit,'(E13.7,"," )') probes(probe_num)%p(3) 116 | 117 | ! write probe data header 118 | write(probe_iounit,'(A)') trim(probe_data_header) 119 | 120 | ! close file 121 | close(probe_iounit) 122 | 123 | end do 124 | 125 | end subroutine write_probe_headers 126 | 127 | 128 | subroutine write_probes(probe_output_path) 129 | 130 | ! write_probes() : Computes the velocity for all probes and writes the data to the 131 | ! corresponding probe output file. 132 | 133 | type(ProbeType) :: probe 134 | integer :: probe_num 135 | character(80) :: probe_output_path 136 | 137 | ! Write the headers to the probe output files if they haven't been written already 138 | if (ProbeHeadersWritten .eqv. .False.) then 139 | call write_probe_headers(probe_output_path) 140 | ProbeHeadersWritten = .True. 141 | end if 142 | 143 | ! Write the probe data 144 | do probe_num=1,nprobes 145 | 146 | ! get the current probe 147 | probe = probes(probe_num) 148 | 149 | ! open file for writing 150 | open(probe_iounit, file=trim(adjustl(probe_output_path))//path_separator//probe%output_filename, POSITION='append') 151 | 152 | ! compute velocity at probe location 153 | call compute_probe_vel(probe) 154 | 155 | ! write the position, velocity, and free-stream velocity 156 | write(probe_iounit,'(E13.7,",",$)') TimeN ! Normalized simulation time (t*Uinf/Rmax) 157 | write(probe_iounit,'(E13.7,",",$)') probe%vel(1) 158 | write(probe_iounit,'(E13.7,",",$)') probe%vel(2) ! induced velocities 159 | write(probe_iounit,'(E13.7,",",$)') probe%vel(3) 160 | write(probe_iounit,'(E13.7,",",$)') probe%vel_fs(1) 161 | write(probe_iounit,'(E13.7,",",$)') probe%vel_fs(2) ! freestream velocities 162 | write(probe_iounit,'(E13.7)' ) probe%vel_fs(3) ! Dont suppress carriage return on last column 163 | 164 | ! close file 165 | close(probe_iounit) 166 | 167 | end do 168 | 169 | end subroutine write_probes 170 | 171 | end module probesystem -------------------------------------------------------------------------------- /src/mod/regtest.f90: -------------------------------------------------------------------------------- 1 | module regtest 2 | 3 | ! Regression test outputs 4 | 5 | integer :: RegTFlag ! Set to 1 to perform a single iteration regression test, 0 for normal operation 6 | integer :: Reg_TS ! timestep number 7 | integer :: Reg_NLIter ! non linear convergence loop iteration 8 | integer :: Reg_ElemNum ! element number 9 | integer :: Reg_DFL ! BV lift dynamic stall flag 10 | integer :: Reg_LBC ! LB dynamic stall model logic checksum 11 | 12 | real :: Reg_CPOut ! cp after one time step 13 | real :: Reg_ElemAOA ! AOA on each element 14 | real :: Reg_ElemCirc ! Circulation on each element 15 | real :: Reg_dElemCirc ! delta circulation between NL iterations 16 | real :: Reg_MaxWS ! max wall source value 17 | real :: Reg_MaxWVM ! max velocity mag in first wake shed 18 | 19 | end module regtest 20 | -------------------------------------------------------------------------------- /src/mod/shear.f90: -------------------------------------------------------------------------------- 1 | module shear 2 | 3 | ! Ground shear layer model data 4 | 5 | real :: yGC ! Ground clearance height to radius ratio 6 | real :: yRef ! Freestream reference height to radius ratio (99% location maybe) 7 | real :: slex ! Exponent for shear layer model (Ex. 1/2 for parabolic BL model, 0 for constant freestream...) 8 | real :: Tempr ! Temperature in degF 9 | 10 | end module shear 11 | -------------------------------------------------------------------------------- /src/mod/strut.f90: -------------------------------------------------------------------------------- 1 | module strut 2 | 3 | ! Strut geometry and loads outputs 4 | 5 | use util 6 | 7 | implicit none 8 | 9 | type StrutType 10 | integer :: NElem 11 | real :: TtoC ! Strut thickness to chord ratio 12 | 13 | ! Strut element end locations 14 | real, allocatable :: MCx(:) 15 | real, allocatable :: MCy(:) 16 | real, allocatable :: MCz(:) 17 | ! Strut chord to radius at element ends 18 | real, allocatable :: CtoR(:) 19 | ! Strut element center locations 20 | real, allocatable :: PEx(:) 21 | real, allocatable :: PEy(:) 22 | real, allocatable :: PEz(:) 23 | ! Strut element spanwise vector 24 | real, allocatable :: sEx(:) 25 | real, allocatable :: sEy(:) 26 | real, allocatable :: sEz(:) 27 | ! Strut element chord to radius and norm. area 28 | real, allocatable :: ECtoR(:) 29 | real, allocatable :: EAreaR(:) 30 | ! Blade and element indicies to which the strut connects (for interference drag calc) 31 | ! For struts that are attached to the rotor shaft at one end (not to another blade), set the appropriate BInd and EInd values to zero. 32 | ! BIndS, EIndS : first strut element 33 | ! BIndE, EIndE : last strut element 34 | integer :: BIndS 35 | integer :: EIndS 36 | integer :: BIndE 37 | integer :: EIndE 38 | 39 | real :: LR ! Strut length to radius ratio 40 | ! Interference drag calc parameters 41 | real :: tcS ! Thickness to chord of the blade element at the strut-blade junction (first strut element) 42 | real :: tcE ! Thickness to chord of the blade element at the strut-blade junction (last strut element) 43 | 44 | ! Current flow quantities at each element center 45 | real, allocatable :: ReStrut(:) 46 | real, allocatable :: u(:) ! u velocity over Uinf 47 | real, allocatable :: v(:) ! v velocity over Uinf 48 | real, allocatable :: w(:) ! w velocity over Uinf 49 | real, allocatable :: ur(:) ! velocity mag over Uinf 50 | 51 | ! Current strut element coeffs (normalized by strut element scale parameters) 52 | real, allocatable :: Cd0(:) 53 | 54 | ! Current total strut output (nomalized by machine scale parameters) 55 | real :: CP ! Power coefficient due to this strut 56 | real :: CTR ! Torque coefficient due to this strut 57 | real :: CFx ! Fx coefficient due to this strut 58 | real :: CFy ! Fy coefficient due to this strut 59 | real :: CFz ! Fz coefficient due to this strut 60 | 61 | end type StrutType 62 | 63 | type(StrutType), allocatable :: Struts(:) 64 | 65 | integer :: NStrut ! number of struts 66 | 67 | real, parameter :: ReCrit=3.0e5 68 | real :: Cdpar ! Additional strut parasitic interference drag coefficient based on "chord area" (chord squared) 69 | 70 | ! Current sum of output over all struts (nomalized by machine scale parameters) 71 | real :: CP_S ! Power coefficient due to all struts 72 | real :: CTR_S ! Torque coefficient due to all struts 73 | real :: CFx_S ! Fx coefficient due to all struts 74 | real :: CFy_S ! Fy coefficient due to all struts 75 | real :: CFz_S ! Fz coefficient due to all struts 76 | 77 | 78 | contains 79 | 80 | 81 | subroutine strut_comp_cns(SInd,NElem) 82 | 83 | implicit none 84 | 85 | ! Constructor for the arrays for a strut component 86 | 87 | integer :: SInd, NElem 88 | 89 | Struts(SInd)%NElem=NElem 90 | allocate(Struts(SInd)%MCx(NElem+1)) 91 | allocate(Struts(SInd)%MCy(NElem+1)) 92 | allocate(Struts(SInd)%MCz(NElem+1)) 93 | allocate(Struts(SInd)%CtoR(NElem+1)) 94 | allocate(Struts(SInd)%PEx(NElem)) 95 | allocate(Struts(SInd)%PEy(NElem)) 96 | allocate(Struts(SInd)%PEz(NElem)) 97 | allocate(Struts(SInd)%sEx(NElem)) 98 | allocate(Struts(SInd)%sEy(NElem)) 99 | allocate(Struts(SInd)%sEz(NElem)) 100 | allocate(Struts(SInd)%ECtoR(NElem)) 101 | allocate(Struts(SInd)%EAreaR(NElem)) 102 | allocate(Struts(SInd)%ReStrut(NElem)) 103 | allocate(Struts(SInd)%u(NElem)) 104 | allocate(Struts(SInd)%v(NElem)) 105 | allocate(Struts(SInd)%w(NElem)) 106 | allocate(Struts(SInd)%ur(NElem)) 107 | allocate(Struts(SInd)%Cd0(NElem)) 108 | 109 | end subroutine strut_comp_cns 110 | 111 | 112 | subroutine RotateStrut(SNum,delt,nrx,nry,nrz,px,py,pz) 113 | 114 | implicit none 115 | 116 | integer :: SNum, j, NElem 117 | real :: delt,nrx,nry,nrz,px,py,pz 118 | real :: vrx,vry,vrz 119 | 120 | ! Rotates data in strut arrays. Rotate element end geometry and recalculate element geometry. 121 | 122 | NElem=Struts(SNum)%NElem 123 | 124 | ! Strut end locations 125 | do j=1,NElem+1 126 | Call QuatRot(Struts(SNum)%MCx(j),Struts(SNum)%MCy(j),Struts(SNum)%MCz(j),delt,nrx,nry,nrz,px,py,pz,vrx,vry,vrz) 127 | Struts(SNum)%MCx(j)=vrx 128 | Struts(SNum)%MCy(j)=vry 129 | Struts(SNum)%MCz(j)=vrz 130 | end do 131 | 132 | ! Calc element geometry 133 | Call CalcSEGeom(SNum) 134 | 135 | 136 | end subroutine RotateStrut 137 | 138 | 139 | subroutine CalcSEGeom(SNum) 140 | 141 | implicit none 142 | 143 | integer :: SNum, NElem, j 144 | real :: sE(3) 145 | real :: sEM 146 | 147 | ! Calculates element geometry from element end geometry 148 | 149 | NElem=Struts(SNum)%NElem 150 | 151 | do j=1,NElem 152 | 153 | ! Element center locations 154 | Struts(SNum)%PEx(j)=0.5*(Struts(SNum)%MCx(j+1)+Struts(SNum)%MCx(j)) 155 | Struts(SNum)%PEy(j)=0.5*(Struts(SNum)%MCy(j+1)+Struts(SNum)%MCy(j)) 156 | Struts(SNum)%PEz(j)=0.5*(Struts(SNum)%MCz(j+1)+Struts(SNum)%MCz(j)) 157 | 158 | ! Set spanwise vectors 159 | sE=(/Struts(SNum)%MCx(j+1)-Struts(SNum)%MCx(j),Struts(SNum)%MCy(j+1)-Struts(SNum)%MCy(j),Struts(SNum)%MCz(j+1)-Struts(SNum)%MCz(j)/) 160 | sEM=sqrt(dot_product(sE,sE)) 161 | sE=sE/sEM 162 | Struts(SNum)%sEx(j)=sE(1) 163 | Struts(SNum)%sEy(j)=sE(2) 164 | Struts(SNum)%sEz(j)=sE(3) 165 | 166 | ! Calc element area and chord 167 | Struts(SNum)%ECtoR(j)=0.5*(Struts(SNum)%CtoR(j+1)+Struts(SNum)%CtoR(j)) 168 | Struts(SNum)%EAreaR(j)=sEM*Struts(SNum)%ECtoR(j) 169 | 170 | end do 171 | 172 | 173 | end subroutine CalcSEGeom 174 | 175 | 176 | subroutine StrutElemCoeffs(SInd,EInd) 177 | 178 | implicit none 179 | 180 | integer :: SInd, EInd 181 | real :: st, ReS, vs, AOS, pc 182 | real :: Cflam, Cdlam, Cfturb, Cdturb, Fblend 183 | 184 | ! Updates strut element coeffs for current flow states 185 | 186 | ! Calc sideslip angle 187 | vs=Struts(SInd)%u(EInd)*Struts(SInd)%sEx(EInd)+Struts(SInd)%v(EInd)*Struts(SInd)%sEy(EInd)+Struts(SInd)%w(EInd)*Struts(SInd)%sEz(EInd) 188 | AOS=asin(vs/Struts(SInd)%ur(EInd)) 189 | 190 | ! Effective section thickness and Re referenced to nominal flow path length rather than chord (p/c = 1/cos(AOS)) 191 | ! Allows estimation at high element sideslip (azimuthal struts). 192 | pc=1.0/abs(cos(AOS)) 193 | pc=min(pc,Struts(SInd)%LR/Struts(SInd)%ECtoR(EInd)) ! Limit p to strut length 194 | st=Struts(SInd)%TtoC/pc 195 | ReS=Struts(SInd)%ReStrut(EInd)*pc 196 | 197 | ! Calculate strut element profile drag 198 | Cflam = 2.66 / SQRT(ReS) ! Laminar friction drag coefficient 199 | Cdlam = 2.0 * Cflam * (1 + st) + st**2 ! Laminar drag coefficient 200 | Cfturb = 0.044 / ReS**(1.0/6.0) ! Turbulent friction drag coefficient 201 | Cdturb = 2.0 * Cfturb * (1.0 + 2.0*st + 60.0*st**4) ! Turbulent drag coefficient 202 | Fblend = 0.5 * (1.0 + TANH((LOG10(ReS)-LOG10(ReCrit))/0.2)) ! Blending function for transition between laminar and turbulent drag 203 | Struts(SInd)%Cd0(EInd) = (1.0-Fblend) * Cdlam + Fblend * Cdturb ! Profile drag coefficient 204 | 205 | 206 | end subroutine StrutElemCoeffs 207 | 208 | end module strut 209 | -------------------------------------------------------------------------------- /src/mod/time.f90: -------------------------------------------------------------------------------- 1 | module time 2 | 3 | ! Info about program real time usage 4 | 5 | real :: t0 ! Time in seconds marker for the beginning of the performance iteration 6 | real :: Time1 ! Time at end of last rev 7 | real :: Time2 ! Time at end of this rev 8 | real :: dtime ! Delta time over this rev 9 | real :: etime ! Elapsed time from begining at end of this rev 10 | 11 | end module time 12 | -------------------------------------------------------------------------------- /src/mod/tower.f90: -------------------------------------------------------------------------------- 1 | module tower 2 | 3 | implicit none 4 | 5 | ! user inputs 6 | real :: tower_D, tower_x, tower_ybot, tower_ytop 7 | integer :: Itower, tower_Npts 8 | 9 | real :: tower_CD, theta0 10 | real, parameter :: Delta_wake = 0.289, W0_wake = 1.75 11 | real, parameter :: S_wake = 0.083, alpha_wake = 0.693 12 | 13 | real, allocatable, dimension(:) :: tower_y, tower_Vx 14 | 15 | contains 16 | 17 | subroutine setup_tower 18 | 19 | implicit none 20 | 21 | integer :: i 22 | 23 | Allocate(tower_y(tower_Npts), tower_Vx(tower_Npts)) 24 | 25 | 26 | If (tower_Npts .LE. 1) Then 27 | Write(*,'(''Inadequate number of tower points.'')') 28 | End If 29 | 30 | Do i = 1,tower_Npts 31 | tower_y(i) = tower_ybot + (REAL(i)-1) / & 32 | (REAL(tower_Npts) - 1) * (tower_ytop-tower_ybot) 33 | tower_Vx(i) = 0.0 34 | End Do 35 | 36 | theta0 = 0.5 * tower_CD * tower_D 37 | 38 | end subroutine setup_tower 39 | 40 | 41 | FUNCTION interp_tower_Vx(y) 42 | 43 | implicit none 44 | 45 | real :: interp_tower_Vx, y 46 | integer :: i 47 | 48 | interp_tower_Vx = 0.0 49 | Do i = 1,tower_Npts-1 50 | If ((y .GE. tower_y(i)) .AND. (y .LE. tower_y(i+1))) Then 51 | interp_tower_Vx = tower_Vx(i) + (y - tower_y(i)) & 52 | / (tower_y(i+1) - tower_y(i)) * (tower_Vx(i+1) - & 53 | tower_Vx(i)) 54 | Exit 55 | End If 56 | End Do 57 | 58 | return 59 | 60 | END FUNCTION interp_tower_Vx 61 | 62 | 63 | FUNCTION wake_defect_velocity(x,y,z) 64 | 65 | implicit none 66 | 67 | real :: x, y, z, wake_defect_velocity 68 | real :: W, xi, Vx_tow, u0, f_wake 69 | 70 | If (x .LE. tower_x) Then 71 | wake_defect_velocity = 0.0 72 | return 73 | End If 74 | 75 | If ((y .LT. tower_ybot) .OR. (y .GT. tower_ytop)) Then 76 | wake_defect_velocity = 0.0 77 | return 78 | End If 79 | 80 | 81 | ! Calculate the wake half-width at this x location 82 | W = Delta_wake * sqrt((x-tower_x)*theta0) 83 | xi = z / W ! non-dimensional transverse coordinate 84 | Vx_tow = interp_tower_Vx(y) ! x-dir fluid velocity at tower 85 | !Write(20,'(F20.12)') Vx_tow 86 | u0 = W0_wake * sqrt(theta0/(x-tower_x )) * Vx_tow ! max velocity 87 | ! defect at this x location 88 | f_wake = exp(-alpha_wake * xi * xi) ! non-dimensional velocity defect 89 | wake_defect_velocity = u0 * f_wake ! dimensional velocity defect 90 | 91 | if (wake_defect_velocity .GT. 0.9 * Vx_tow) then 92 | wake_defect_velocity = 0.9 * Vx_tow 93 | end if 94 | 95 | return 96 | 97 | END FUNCTION wake_defect_velocity 98 | 99 | end module tower 100 | -------------------------------------------------------------------------------- /src/mod/util/geomutils.f90: -------------------------------------------------------------------------------- 1 | module geomutils 2 | 3 | ! module geomutils : functions for querying 2-D and 3-D geometries 4 | 5 | implicit none 6 | 7 | private 8 | public dist_point_to_segment_2d, is_point_in_quad 9 | 10 | contains 11 | 12 | function dist_point_to_segment_2d(x,y,x1,y1,x2,y2) 13 | 14 | ! dist_point_to_segment_2d() : returns the shortest distance from a point p to a line segment. 15 | ! Based on the method of http://paulbourke.net/geometry/pointlineplane/ 16 | ! 17 | ! Inputs: 18 | ! ======= 19 | ! x,y : the coordinates of the point 20 | ! 21 | ! Output: 22 | ! ======= 23 | ! x1,y1,x2,y2 : the start and end points of the line segment 24 | ! 25 | 26 | real, intent(in) :: x,y,x1,y1,x2,y2 27 | real :: dist_point_to_segment_2d 28 | 29 | real :: u, dx, dy 30 | real :: x_closest, y_closest 31 | 32 | ! check if points are coincident 33 | if (x1==x2 .and. y1==y2) then 34 | write(*,*) 'Error in dist_point_to_segment_2d : Points are coincident!' 35 | stop 36 | end if 37 | 38 | dx = x2-x1 39 | dy = y2-y1 40 | 41 | u = ((x-x1)*dx + (y-y1)*dy) / abs(dx**2 - dy**2) 42 | 43 | if (u < 0) then 44 | x_closest = x1 45 | y_closest = y1 46 | else if (u > 1) then 47 | x_closest = x2 48 | y_closest = y2 49 | else 50 | x_closest = x1+u*dx 51 | y_closest = y1+u*dy 52 | end if 53 | 54 | dist_point_to_segment_2d = ((x_closest-x)**2 + (y_closest-y)**2)**0.5 55 | 56 | end function dist_point_to_segment_2d 57 | 58 | 59 | function is_point_in_quad(p_list, p) 60 | 61 | ! is_point_in_quad() : For a quadrilateral whose corners are given in p_list, returns 62 | ! True if the point p lies within the polygon and False if the point p lies outside of it. 63 | ! Based on the method of http://paulbourke.net/geometry/polygonmesh/ 64 | ! 65 | ! Inputs: 66 | ! ======= 67 | ! p_list : a (4,3) array of points. expects that p(:,3) = 0 (the z-coordinate is zero) 68 | ! 69 | ! Outputs: 70 | ! ======== 71 | ! is_point_in_quad (logical) : .True. if point is inside the quadrilateral, .False. otherwise 72 | 73 | real, intent(in) :: p_list(4,3) 74 | real, intent(in) :: p(3) 75 | logical :: is_point_in_quad 76 | 77 | integer, parameter :: nsides = 4 78 | real :: p1(3), p2(3) 79 | real :: xinters 80 | 81 | integer :: i, counter 82 | 83 | counter = 0 84 | 85 | p1 = p_list(1,1:3) 86 | 87 | do i=1,nsides 88 | p2 = p_list(mod(i,nsides)+1,1:3) 89 | if (p(2) > min(p1(2),p2(2))) then 90 | if (p(2) <= max(p1(2),p2(2))) then 91 | if (p(1) <= max(p1(1),p2(1))) then 92 | if (p1(2) /= p2(2)) then 93 | xinters = (p(2)-p1(2))*(p2(1)-p1(1))/(p2(2)-p1(2)) + p1(1) 94 | if (p1(1) == p2(1) .or. p(1) <= xinters) then 95 | counter = counter + 1 96 | end if 97 | end if 98 | end if 99 | end if 100 | end if 101 | p1 = p2 102 | end do 103 | 104 | if (mod(counter,2) == 0) then 105 | is_point_in_quad = .False. 106 | else 107 | is_point_in_quad = .True. 108 | end if 109 | 110 | end function is_point_in_quad 111 | 112 | 113 | end module geomutils -------------------------------------------------------------------------------- /src/mod/util/pidef.f90: -------------------------------------------------------------------------------- 1 | module pidef 2 | 3 | ! Value of pi 4 | real, parameter :: pi = 4.0*atan(1.0) 5 | real, parameter :: conrad = pi/180.0 6 | real, parameter :: condeg = 180.0/pi 7 | 8 | end module pidef 9 | -------------------------------------------------------------------------------- /src/mod/util/plot3d.f90: -------------------------------------------------------------------------------- 1 | module plot3d 2 | 3 | ! plot3d : subroutines for reading plot3d mesh data 4 | 5 | implicit none 6 | 7 | private 8 | public read_p3d_multiblock 9 | 10 | integer, parameter :: IOunit = 20 11 | 12 | contains 13 | 14 | subroutine read_p3d_multiblock(xyz_filename,nblocks,ni,nj,nk,x,y,z) 15 | ! read_p3d_multiblock() : Read a formatted (ASCII) Plot3D multi-block mesh file. 16 | ! Coordinates must be specified one number per line. 17 | ! 18 | ! Inputs 19 | ! ====== 20 | ! xyz_filename : Plot3d mesh filename (*.xyz) 21 | ! 22 | ! Outputs 23 | ! ======= 24 | ! nblocks : number of blocks 25 | ! ni : mesh dimensions in first direction 26 | ! nj : mesh dimensions in second direciton 27 | ! nk : mesh dimensions in third direction 28 | 29 | character(len=*), intent(in) :: xyz_filename ! Plot3d mesh filename (*.xyz) 30 | 31 | integer, intent (out) :: nblocks ! number of blocks 32 | integer, allocatable, intent(out) :: ni(:), nj(:), nk(:) ! mesh dimensions 33 | real, allocatable, intent(out) :: x(:,:,:,:), y(:,:,:,:), z(:,:,:,:) ! mesh coordinates 34 | 35 | integer :: i,j,k 36 | integer :: m,n 37 | integer :: nimax, njmax, nkmax 38 | 39 | ! open file 40 | open(unit=IOunit, form='formatted', file=xyz_filename) 41 | 42 | ! read number of blocks 43 | read(IOunit,*) nblocks 44 | 45 | ! allocate for the dimensions of the mesh blocks 46 | allocate(ni(nblocks)) 47 | allocate(nj(nblocks)) 48 | allocate(nk(nblocks)) 49 | 50 | ! read dimensions of each block 51 | do m=1,nblocks 52 | read(IOunit,*) ni(m),nj(m),nk(m) 53 | end do 54 | 55 | ! compute maximum mesh size and allocate 56 | nimax = maxval(ni) 57 | njmax = maxval(nj) 58 | nkmax = maxval(nk) 59 | 60 | allocate(x(nimax,njmax,nkmax,nblocks)) 61 | allocate(y(nimax,njmax,nkmax,nblocks)) 62 | allocate(z(nimax,njmax,nkmax,nblocks)) 63 | 64 | ! read in data for each block 65 | do m = 1, nblocks 66 | read(IOunit,*) & 67 | ((( x(i,j,k,m), i=1,ni(m)), j=1,nj(m)), k=1,nk(m)), & 68 | ((( y(i,j,k,m), i=1,ni(m)), j=1,nj(m)), k=1,nk(m)), & 69 | ((( z(i,j,k,m), i=1,ni(m)), j=1,nj(m)), k=1,nk(m)) 70 | enddo 71 | 72 | ! close file 73 | close(IOunit) 74 | 75 | end subroutine read_p3d_multiblock 76 | 77 | 78 | end module plot3d -------------------------------------------------------------------------------- /src/mod/util/util.f90: -------------------------------------------------------------------------------- 1 | module util 2 | 3 | ! Utilities used in other modules and elsewhere in the code 4 | 5 | contains 6 | 7 | subroutine cross(ax,ay,az,bx,by,bz,cx,cy,cz) 8 | 9 | real ax,ay,az,bx,by,bz,cx,cy,cz 10 | 11 | cx = ay*bz - az*by 12 | cy = az*bx - ax*bz 13 | cz = ax*by - ay*bx 14 | 15 | end subroutine cross 16 | 17 | 18 | subroutine QuatRot(vx,vy,vz,Theta,nRx,nRy,nRz,Ox,Oy,Oz,vRx,vRy,vRz) 19 | 20 | ! % Perform rotation of vector v around normal vector nR using the 21 | ! % quaternion machinery. 22 | ! % v: input vector 23 | ! % Theta: rotation angle (rad) 24 | ! % nR: normal vector around which to rotate 25 | ! % Origin: origin point of rotation 26 | ! % 27 | ! % vR: Rotated vector 28 | 29 | real :: vx,vy,vz,Theta,nRx,nRy,nRz,Ox,Oy,Oz,vRx,vRy,vRz 30 | 31 | real :: p(4,1), pR(4,1), q(4), qbar(4), nRMag, vOx, vOy, vOz 32 | real :: QL(4,4), QbarR(4,4) 33 | 34 | ! Force normalize nR 35 | nRMag=sqrt(nRx**2+nRy**2+nRz**2) 36 | nRx=nRx/nRMag 37 | nRy=nRy/nRMag 38 | nRz=nRz/nRMag 39 | 40 | ! Quaternion form of v 41 | vOx=vx-Ox 42 | vOy=vy-Oy 43 | vOz=vz-Oz 44 | p=reshape((/0.0,vOx,vOy,vOz/),(/4,1/)) 45 | 46 | ! Rotation quaternion and conjugate 47 | q=(/cos(Theta/2),nRx*sin(Theta/2),nRy*sin(Theta/2),nRz*sin(Theta/2)/) 48 | qbar=(/q(1),-q(2),-q(3),-q(4)/) 49 | 50 | QL=transpose(reshape((/q(1), -q(2), -q(3), -q(4), & 51 | q(2), q(1), -q(4), q(3), & 52 | q(3), q(4), q(1), -q(2), & 53 | q(4), -q(3), q(2), q(1)/),(/4,4/))) 54 | 55 | QbarR=transpose(reshape((/qbar(1), -qbar(2), -qbar(3), -qbar(4), & 56 | qbar(2), qbar(1), qbar(4), -qbar(3), & 57 | qbar(3), -qbar(4), qbar(1), qbar(2), & 58 | qbar(4), qbar(3), -qbar(2), qbar(1)/),(/4,4/))) 59 | 60 | ! Rotate p 61 | pR=matmul(matmul(QbarR,QL),p) 62 | vRx=pR(2,1)+Ox 63 | vRy=pR(3,1)+Oy 64 | vRz=pR(4,1)+Oz 65 | 66 | end subroutine QuatRot 67 | 68 | 69 | subroutine csvwrite(FID,Header,Data,WriteHead,NRows) 70 | 71 | integer :: FID, WriteHead, NRows 72 | character(10000) :: Header 73 | real :: Data(:,:) 74 | 75 | integer :: nRow, nCol, i, j 76 | 77 | ! Writes comma separated data to file specified by FID (assumed already opened with the open command). 78 | ! If WriteHead is 1, will write the input header line, else will skip header line. 79 | ! NRows is the number of rows of Data to write. If NRows<0, all rows of Data will be written... 80 | 81 | ! Write header 82 | if (WriteHead>0) then 83 | write (FID,'(A)') trim(Header) 84 | end if 85 | 86 | ! Write data 87 | if (NRows>=0) then 88 | nRow=NRows 89 | else 90 | nRow=size(Data,1) 91 | end if 92 | nCol=size(Data,2) 93 | do i=1,nRow 94 | do j=1,nCol 95 | if (j