├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── pyproject.toml ├── setup.py ├── src ├── api.f90 ├── i_common.f90 ├── m_spline.f90 ├── m_userio.f90 ├── m_vortex.f90 ├── m_xaero.f90 ├── m_xbend.f90 ├── m_xio.f90 ├── m_xnoise.f90 ├── m_xoper.f90 ├── m_xrotor.f90 ├── m_xutils.f90 ├── p_test.f90 ├── p_xbend.f90 ├── p_xnoise.f90 ├── p_xoper.f90 ├── p_xrotor.f90 └── s_xrotor.f90 └── xrotor ├── __init__.py ├── model.py ├── test.py └── xrotor.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.egg-info/ 4 | venv/ 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(xrotor Fortran) 3 | #project(xrotor_exec Fortran) 4 | #project(xrotor_test Fortran) 5 | 6 | enable_language(Fortran) 7 | add_library(xrotor SHARED 8 | src/i_common.f90 9 | src/m_spline.f90 10 | src/m_userio.f90 11 | src/m_vortex.f90 12 | src/m_xaero.f90 13 | src/m_xbend.f90 14 | src/m_xio.f90 15 | src/m_xnoise.f90 16 | src/m_xoper.f90 17 | src/m_xrotor.f90 18 | src/m_xutils.f90 19 | src/s_xrotor.f90 20 | src/api.f90) 21 | 22 | #add_executable(xrotor_exec 23 | # src/i_common.f90 24 | # src/m_spline.f90 25 | # src/m_userio.f90 26 | # src/m_vortex.f90 27 | # src/m_xaero.f90 28 | # src/m_xbend.f90 29 | # src/m_xio.f90 30 | # src/m_xnoise.f90 31 | # src/m_xoper.f90 32 | # src/m_xrotor.f90 33 | # src/m_xutils.f90 34 | # src/s_xrotor.f90 35 | # src/p_xbend.f90 36 | # src/p_xnoise.f90 37 | # src/p_xoper.f90 38 | # src/p_xrotor.f90) 39 | # 40 | # 41 | #add_executable(xrotor_test 42 | # src/i_common.f90 43 | # src/m_spline.f90 44 | # src/m_userio.f90 45 | # src/m_vortex.f90 46 | # src/m_xaero.f90 47 | # src/m_xbend.f90 48 | # src/m_xio.f90 49 | # src/m_xnoise.f90 50 | # src/m_xoper.f90 51 | # src/m_xrotor.f90 52 | # src/m_xutils.f90 53 | # src/s_xrotor.f90 54 | # src/api.f90 55 | # src/p_test.f90) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | General 3 | ------- 4 | This is a stripped down version of XROTOR. All the modification, design, and graphical functionality has 5 | been removed. The only main menu options that are available in this stripped down version are: 6 | * OPER, which allows for the calculation of performance characteristics at given operating conditions; 7 | * BEND, which allows for the calculation of structural loads and deformations; 8 | * NOIS, which allows for the calculation of the acoustic signature; 9 | * LOAD, which loads a propeller definition file from the disk; 10 | * SAVE, which saves a propeller definition file to the disk; and 11 | * DISP, which displays the current propeller characteristics data onscreen. 12 | 13 | Building and Installing the Python Module 14 | ----------------------------------------- 15 | To successfully build and install the Python module a few prerequisites have to be present on your system. First of all, 16 | a working installation of Python is required, of course. The module targets Python 3, and does NOT support Python 2. 17 | Furthermore, working compilers for C and Fortran have to be installed and on the PATH. On Windows, the build and 18 | installation have ONLY been tested with MinGW, using gcc and gfortran. 19 | 20 | Then, installing XRotor should be as simple as running: 21 | 22 | ```bash 23 | pip install xrotor 24 | ``` 25 | 26 | Or, from the root of the downloaded repository: 27 | 28 | ```bash 29 | pip install . 30 | ``` 31 | 32 | On Windows, you may have to force the system to use MinGW. To do so, create a file named `distutils.cfg` in 33 | `PYTHONPATH\Lib\distutils` with the following contents: 34 | 35 | ```INI 36 | [build] 37 | compiler=mingw32 38 | ``` 39 | 40 | If you are not able to create this file for your Python environment, it is also possible to force the use of MinGW 41 | directly when invoking `pip` by calling: 42 | 43 | ```bash 44 | pip install --global-option build_ext --global-option --compiler=mingw32 xrotor 45 | ``` 46 | 47 | Using the Module 48 | ---------------- 49 | All XRotor operations are performed using the `XRotor` class. So the first step when using this module is to create an 50 | instance of this class: 51 | 52 | ```pycon 53 | >>> from xrotor import XRotor 54 | >>> xr = XRotor() 55 | ``` 56 | 57 | If this does not produce any errors, the installtion should be functioning properly. 58 | A test case is installed along with the module. To run it in XRotor, execute the following commands in the same python 59 | console: 60 | 61 | ```pycon 62 | >>> from xrotor.model import Case 63 | >>> from xrotor.test import case 64 | >>> xr.case = Case.from_dict(case) 65 | >>> xr.operate(1, 2000) 66 | 67 | Iter dGmax @Imax gGrms Av Aw Be rlx 68 | 1 0.397E-01 1 0.167E-02 0.1553 0.1750 9.966 0.2000 69 | 2 0.225E-01 1 0.103E-02 0.1553 0.1764 9.966 0.2000 70 | 3 0.154E-01 1 0.733E-03 0.1553 0.1776 9.966 0.2000 71 | 4 0.116E-01 1 0.559E-03 0.1553 0.1824 9.966 1.0000 72 | 5 0.514E-03 29 0.224E-04 0.1553 0.1825 9.966 0.2000 73 | 6 0.412E-03 29 0.179E-04 0.1553 0.1825 9.966 1.0000 74 | 7 0.227E-05 29 0.742E-07 0.1553 0.1825 9.966 0.2000 75 | 76 | ``` 77 | 78 | These commands initialize a sample propeller definition in XRotor and operate it at a fixed RPM of 2000 rev/min. The 79 | output from the last function should be familiar to anyone who has used the original XRotor console application before: 80 | it is the convergence history of the OPER command. The familiar solution results can also be printed to the screen with 81 | the following command: 82 | 83 | ```pycon 84 | >>> xr.print_case() 85 | 86 | =========================================================================== 87 | Free Tip Potential Formulation Solution: 88 | Wake adv. ratio: 0.18252 89 | no. blades : 2 radius(m) : 0.8300 adv. ratio: 0.15532 90 | thrust(n) : 481. power(w) : 0.219E+05 torque(n-m): 105. 91 | Efficiency : 0.5929 speed(m/s) : 27.000 rpm : 2000.000 92 | Eff induced: 0.8510 Eff ideal : 0.8993 Tcoef : 0.4981 93 | Tnacel(n) : 0.0132 hub rad.(m): 0.0600 disp. rad. : 0.0000 94 | Tvisc(n) : -15.5982 Pvisc(w) : 0.615E+04 95 | rho(kg/m3) : 1.22500 Vsound(m/s): 340.000 mu(kg/m-s) : 0.1789E-04 96 | --------------------------------------------------------------------------- 97 | Sigma: NaN 98 | Ct: 0.04658 Cp: 0.03833 j: 0.48795 99 | Tc: 0.49815 Pc: 0.84023 adv: 0.15532 100 | 101 | i r/r c/r beta(deg) cl Cd rEx10^6 Mach effi effp na.u/u 102 | 1 0.081 0.1458 59.81 0.433 0.0934 0.25 0.090 3.944 0.555 0.000 103 | 2 0.108 0.1475 56.04 0.501 0.0799 0.28 0.097 1.343 0.691 0.000 104 | 3 0.149 0.1500 50.09 0.602 0.0720 0.32 0.110 1.036 0.782 0.000 105 | 4 0.196 0.1527 43.42 0.642 0.0687 0.38 0.127 0.953 0.808 0.000 106 | 5 0.244 0.1558 36.98 0.624 0.0620 0.44 0.147 0.933 0.816 0.000 107 | 6 0.292 0.1594 31.45 0.581 0.0559 0.52 0.169 0.933 0.811 0.000 108 | 7 0.341 0.1634 27.32 0.544 0.0521 0.60 0.191 0.928 0.800 0.000 109 | 8 0.388 0.1672 24.38 0.521 0.0495 0.69 0.214 0.914 0.789 0.000 110 | 9 0.435 0.1697 22.20 0.506 0.0474 0.77 0.236 0.896 0.781 0.000 111 | 10 0.481 0.1699 20.38 0.494 0.0459 0.85 0.258 0.882 0.772 0.000 112 | 11 0.526 0.1679 18.76 0.484 0.0450 0.91 0.280 0.875 0.761 0.000 113 | 12 0.569 0.1639 17.37 0.476 0.0444 0.95 0.301 0.871 0.749 0.000 114 | 13 0.611 0.1583 16.20 0.471 0.0440 0.99 0.322 0.867 0.738 0.000 115 | 14 0.652 0.1515 15.24 0.470 0.0438 1.00 0.342 0.864 0.729 0.000 116 | 15 0.690 0.1438 14.45 0.471 0.0436 1.00 0.361 0.860 0.722 0.000 117 | 16 0.727 0.1356 13.78 0.475 0.0436 0.99 0.380 0.856 0.715 0.000 118 | 17 0.762 0.1271 13.22 0.479 0.0438 0.98 0.397 0.853 0.709 0.000 119 | 18 0.794 0.1188 12.73 0.483 0.0441 0.95 0.414 0.849 0.702 0.000 120 | 19 0.825 0.1106 12.29 0.486 0.0447 0.92 0.429 0.846 0.694 0.000 121 | 20 0.853 0.1029 11.91 0.487 0.0455 0.88 0.443 0.843 0.685 0.000 122 | 21 0.879 0.0958 11.57 0.485 0.0466 0.84 0.456 0.838 0.673 0.000 123 | 22 0.903 0.0892 11.26 0.479 0.0481 0.81 0.468 0.832 0.660 0.000 124 | 23 0.924 0.0834 10.99 0.468 0.0501 0.77 0.479 0.824 0.642 0.000 125 | 24 0.943 0.0782 10.74 0.450 0.0529 0.74 0.488 0.813 0.618 0.000 126 | 25 0.959 0.0738 10.53 0.423 0.0570 0.71 0.496 0.796 0.585 0.000 127 | 26 0.972 0.0701 10.35 0.385 0.0635 0.68 0.503 0.775 0.537 0.000 128 | 27 0.983 0.0672 10.20 0.335 0.0740 0.66 0.509 0.747 0.467 0.000 129 | 28 0.991 0.0651 10.08 0.270 0.0913 0.65 0.513 0.713 0.365 0.000 130 | 29 0.997 0.0636 10.01 0.193 0.1177 0.63 0.516 0.675 0.236 0.000 131 | 30 0.999 0.0629 9.97 0.125 0.1467 0.63 0.518 0.642 0.123 0.000 132 | ``` 133 | 134 | If the module is working as it should, the output should match the output shown above. 135 | 136 | At the time of writing, the only the two operating modes available are fixed RPM and fixed thrust with fixed blade pitch. 137 | Both can be invoked by calling the `operate` member function on an instance of the `XRotor` class. The first argument 138 | to this function specifies which mode is used: 1 for fixed RPM, as was demonstrated above; 2 for fixed thrust at fixed 139 | blade pitch. The second argument to the function specifies the value for the RPM/thrust. 140 | 141 | See the documentation for more detailed explanation of how to use the API. 142 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build", "cmake"] 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2018 D. de Vries 3 | # 4 | # This file is part of XRotor. 5 | # 6 | # XRotor is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # XRotor is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with XRotor. If not, see . 18 | import os 19 | import platform 20 | import re 21 | import subprocess 22 | import sys 23 | 24 | from setuptools import setup 25 | from setuptools.extension import Extension 26 | from setuptools.command.build_ext import build_ext 27 | 28 | __version__ = re.findall( 29 | r"""__version__ = ["']+([0-9\.]*)["']+""", 30 | open('xrotor/__init__.py').read(), 31 | )[0] 32 | 33 | options = {k: 'OFF' for k in ['--opt', '--debug', '--cuda']} 34 | for flag in options.keys(): 35 | if flag in sys.argv: 36 | options[flag] = 'ON' 37 | sys.argv.remove(flag) 38 | 39 | # Command line flags forwarded to CMake 40 | cmake_cmd_args = [] 41 | for f in sys.argv: 42 | if f.startswith('-D'): 43 | cmake_cmd_args.append(f) 44 | sys.argv.remove(f) 45 | 46 | 47 | class CMakeExtension(Extension): 48 | 49 | def __init__(self, name, cmake_target=None, cmake_list_dir='.', **kwargs): 50 | super().__init__(name, sources=[], **kwargs) 51 | self.cmake_lists_dir = os.path.abspath(cmake_list_dir) 52 | self.cmake_target = cmake_target 53 | 54 | 55 | class CMakeBuild(build_ext): 56 | 57 | def build_extensions(self): 58 | # Ensure that CMake is present and working 59 | try: 60 | out = subprocess.check_output(['cmake', '--version']) 61 | except OSError: 62 | raise RuntimeError('Cannot find CMake executable') 63 | 64 | for ext in self.extensions: 65 | extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) 66 | cfg = 'Debug' if options['--debug'] == 'ON' else 'Release' 67 | 68 | cmake_args = [ 69 | '-DCMAKE_BUILD_TYPE=%s' % cfg, 70 | # Ask CMake to place the resulting library in the directory 71 | # containing the extension 72 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir), 73 | # Other intermediate static libraries are placed in a 74 | # temporary build directory instead 75 | '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), self.build_temp), 76 | # Hint CMake to use the same Python executable that 77 | # is launching the build, prevents possible mismatching if 78 | # multiple versions of Python are installed 79 | '-DPYTHON_EXECUTABLE={}'.format(sys.executable), 80 | # Add other project-specific CMake arguments if needed 81 | # ... 82 | ] 83 | 84 | # We can handle some platform-specific settings at our discretion 85 | if platform.system() == 'Windows': 86 | plat = ('x64' if platform.architecture()[0] == '64bit' else 'Win32') 87 | cmake_args += [ 88 | # These options are likely to be needed under Windows 89 | '-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE', 90 | '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir), 91 | ] 92 | # Assuming that Visual Studio and MinGW are supported compilers 93 | if self.compiler.compiler_type == 'msvc': 94 | cmake_args += [ 95 | '-DCMAKE_GENERATOR_PLATFORM=%s' % plat, 96 | ] 97 | else: 98 | cmake_args += [ 99 | '-G', 'MinGW Makefiles', 100 | ] 101 | 102 | cmake_args += cmake_cmd_args 103 | 104 | if ext.cmake_target is not None: 105 | cmake_args += ['--target', ext.cmake_target] 106 | 107 | print(cmake_args) 108 | 109 | if not os.path.exists(self.build_temp): 110 | os.makedirs(self.build_temp) 111 | 112 | # Config and build the extension 113 | subprocess.check_call(['cmake', ext.cmake_lists_dir] + cmake_args, 114 | cwd=self.build_temp) 115 | subprocess.check_call(['cmake', '--build', '.', '--config', cfg], 116 | cwd=self.build_temp) 117 | 118 | 119 | def readme(): 120 | with open('README.md') as f: 121 | return f.read() 122 | 123 | 124 | setup( 125 | name='xrotor', 126 | version=__version__, 127 | description='Stripped down version of XROTOR as compiled python module ', 128 | long_description=readme(), 129 | long_description_content_type='text/markdown', 130 | classifiers=[ 131 | 'Development Status :: 3 - Alpha', 132 | 'Intended Audience :: Science/Research', 133 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 134 | 'Natural Language :: English', 135 | 'Operating System :: MacOS :: MacOS X', 136 | 'Operating System :: POSIX :: Linux', 137 | 'Operating System :: Microsoft :: Windows', 138 | 'Programming Language :: Fortran', 139 | 'Programming Language :: Python :: 3 :: Only', 140 | 'Topic :: Scientific/Engineering', 141 | ], 142 | keywords='xrotor propeller performance analysis', 143 | url='https://github.com/daniel-de-vries/xrotor-python', 144 | download_url='https://github.com/daniel-de-vries/xrotor-python/tarball/' + __version__, 145 | author='Daniël de Vries', 146 | author_email='contact@daniel-de-vries.com', 147 | license='GNU General Public License v3 or later (GPLv3+)', 148 | packages=['xrotor'], 149 | # package_dir={'': 'src'}, 150 | ext_modules=[CMakeExtension('xrotor.xrotor')], 151 | cmdclass={'build_ext': CMakeBuild}, 152 | install_requires=['numpy', 'scipy'], 153 | zip_save=False 154 | ) 155 | -------------------------------------------------------------------------------- /src/api.f90: -------------------------------------------------------------------------------- 1 | !*==API.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! 5 | ! This file is part of XRotor. 6 | ! 7 | ! XRotor is free software: you can redistribute it and/or modify 8 | ! it under the terms of the GNU General Public License as published by 9 | ! the Free Software Foundation, either version 3 of the License, or 10 | ! (at your option) any later version. 11 | ! 12 | ! XRotor is distributed in the hope that it will be useful, 13 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ! GNU General Public License for more details. 16 | ! 17 | ! You should have received a copy of the GNU General Public License 18 | ! along with XRotor. If not, see . 19 | !*********************************************************************** 20 | 21 | module api 22 | use, intrinsic :: iso_c_binding, only : c_float, c_double, c_int, c_bool, c_char 23 | use :: i_common, only : Common 24 | implicit none 25 | private 26 | public set_print, get_print, & 27 | set_max_iter, get_max_iter, & 28 | set_use_compr_corr, get_use_compr_corr, & 29 | set_vrtx, get_vrtx, & 30 | set_fast, get_fast, & 31 | init, set_case, operate, dp, show, save_prop, & 32 | get_rms, get_performance, get_blade_angle_change, & 33 | get_number_of_stations, get_station_conditions, load_prop 34 | 35 | integer, parameter :: dp = kind(0.D0) 36 | 37 | type (Common), private :: ctxt 38 | 39 | contains 40 | 41 | subroutine set_print(setting) bind(c, name = 'set_print') 42 | use i_common, only : show_output 43 | logical(c_bool), intent(in) :: setting 44 | show_output = setting 45 | end 46 | 47 | function get_print() bind(c, name = 'get_print') 48 | use i_common, only : show_output 49 | logical(c_bool) :: get_print 50 | get_print = show_output 51 | end 52 | 53 | subroutine set_max_iter(setting) bind(c, name = 'set_max_iter') 54 | integer(c_int), intent(in) :: setting 55 | ctxt%nitera = ctxt%nitera 56 | end 57 | 58 | function get_max_iter() bind(c, name = 'get_max_iter') 59 | integer(c_int) :: get_max_iter 60 | get_max_iter = ctxt%nitera 61 | end 62 | 63 | subroutine set_use_compr_corr(use_compr_corr) bind(c, name = 'set_use_compr_corr') 64 | logical(c_bool) :: use_compr_corr 65 | ctxt%use_compr_corr = use_compr_corr 66 | end 67 | 68 | function get_use_compr_corr() bind(c, name = 'get_use_compr_corr') 69 | logical(c_bool) :: get_use_compr_corr 70 | get_use_compr_corr = ctxt%use_compr_corr 71 | end 72 | 73 | subroutine set_vrtx(vrtx) bind(c, name = 'set_vrtx' ) 74 | logical(c_bool) :: vrtx 75 | ctxt%vrtx = vrtx 76 | end 77 | 78 | function get_vrtx() bind(c, name = 'get_vrtx') 79 | logical(c_bool) :: get_vrtx 80 | get_vrtx = ctxt%vrtx 81 | end 82 | 83 | subroutine set_fast(fast) bind(c, name = 'set_fast' ) 84 | logical(c_bool) :: fast 85 | ctxt%fast = fast 86 | end 87 | 88 | function get_fast() bind(c, name = 'get_fast') 89 | logical(c_bool) :: get_fast 90 | get_fast = ctxt%fast 91 | end 92 | 93 | subroutine init() bind(c, name = 'init') 94 | use m_xrotor, only : init_ 95 | !ctxt = Common() 96 | call init_(ctxt) 97 | end 98 | 99 | subroutine set_case(& 100 | rho, vso, rmu, alt, vel, adv, & 101 | r_hub, r_tip, r_wake, rake, & 102 | n_blds, & 103 | n_geom, geomdata, & 104 | n_polars, n_polar_points, xi_polars, polardata, & 105 | free, duct, wind) bind(c, name = 'set_case') 106 | use i_common, only : pi 107 | use m_xio, only : initcase 108 | use m_xaero, only : putpolars 109 | 110 | real(c_float), intent(in) :: rho, vso, rmu, alt, vel, adv 111 | real(c_float), intent(in) :: r_hub, r_tip, r_wake, rake 112 | integer(c_int), intent(in) :: n_blds, n_geom 113 | real(c_float), intent(in) :: geomdata(4, n_geom) 114 | integer(c_int), intent(in) :: n_polars, n_polar_points(n_polars) 115 | real(c_float), intent(in) :: xi_polars(n_polars), polardata(sum(n_polar_points), 4) 116 | logical(c_bool), intent(in) :: free, duct, wind 117 | 118 | real :: my_polardata(sum(n_polar_points), 4) 119 | 120 | integer :: i 121 | 122 | ctxt%rho = rho 123 | ctxt%vso = vso 124 | ctxt%rmu = rmu 125 | ctxt%alt = alt 126 | ctxt%vel = vel 127 | ctxt%adv = adv 128 | 129 | ctxt%rad = r_tip 130 | ctxt%xi0 = r_hub / r_tip 131 | ctxt%xw0 = r_wake / r_tip 132 | ctxt%rake = rake 133 | 134 | ctxt%nblds = n_blds 135 | 136 | do i = 1, n_geom 137 | ctxt%xi(i) = geomdata(1, i) 138 | ctxt%ch(i) = geomdata(2, i) 139 | ctxt%beta(i) = geomdata(3, i) * pi / 180. 140 | ctxt%beta0(i) = ctxt%beta(i) 141 | ctxt%ubody(i) = geomdata(4, i) 142 | enddo 143 | 144 | my_polardata = polardata 145 | my_polardata(:, 1) = polardata(:, 1) * pi / 180. 146 | call putpolars(ctxt, n_polars, n_polar_points, xi_polars, my_polardata) 147 | 148 | call initcase(ctxt, n_geom, .false.) 149 | end 150 | 151 | function operate(spec, value, fix, fixed) bind(c, name = 'operate') 152 | use m_xoper, only : aper 153 | use i_common, only : show_output, pi 154 | real(c_float) :: operate 155 | integer(c_int), intent(in) :: spec 156 | real(c_float), intent(in) :: value 157 | integer(c_int), optional, intent(in) :: fix 158 | real(c_float), optional, intent(in) :: fixed 159 | integer :: ifix, i 160 | 161 | operate = 1.0 162 | 163 | if (present(fix)) then 164 | if (fix==1.and..not.present(fixed)) then 165 | print *, "If 'fix' is given, 'fixed' must be given too." 166 | return 167 | endif 168 | ifix = fix 169 | else 170 | ifix = 2 171 | endif 172 | 173 | select case (spec) 174 | case (1) 175 | ctxt%tspec = value 176 | case (2) 177 | ctxt%qspec = value 178 | case (3) 179 | ctxt%pspec = value 180 | case (4) 181 | ctxt%adv = ctxt%vel / (ctxt%rad * value * pi / 30.) 182 | case default 183 | print *, "Unknown value for 'spec'. Should be 1, 2, 3, or 4." 184 | return 185 | endselect 186 | 187 | if (ifix==1) then 188 | ctxt%adv = ctxt%vel / (ctxt%rad * fixed * pi / 30.) 189 | elseif (ifix/=2) then 190 | print *, "Unknown value for 'fix'. Should be 1 or 2." 191 | return 192 | endif 193 | 194 | ctxt%conv = .false. 195 | call aper(ctxt, spec, ifix, .true.) 196 | 197 | if (ifix==1.and.spec/=4) then 198 | if (ctxt%conv) then 199 | !----- convergence was achieved: show blade angle change incurred 200 | if (show_output) write (*, 99001) ctxt%dbeta * 180.0 / pi 201 | 99001 format (' Blade angle changed', f7.3, ' degrees') 202 | else 203 | !----- convergence failed: restore clobbered blade angles 204 | do i = 1, ctxt%ii 205 | ctxt%beta(i) = ctxt%beta(i) - ctxt%dbeta 206 | ctxt%beta0(i) = ctxt%beta0(i) - ctxt%dbeta 207 | enddo 208 | endif 209 | endif 210 | 211 | operate = ctxt%rms 212 | end 213 | 214 | subroutine show() bind(c, name = 'show') 215 | use m_xrotor, only : output 216 | call output(ctxt, 6) 217 | end 218 | 219 | subroutine save_prop() bind(c, name = 'save_prop') 220 | use m_xio, only : save 221 | call save(ctxt, 'output.json') 222 | end 223 | 224 | subroutine load_prop(fname) 225 | use m_xio, only : load 226 | character*(*) fname 227 | call load(ctxt, fname) 228 | end 229 | 230 | function get_rms() bind(c, name = 'get_rms') 231 | real(c_float) :: get_rms 232 | get_rms = ctxt%rms 233 | end 234 | 235 | subroutine get_performance(rpm, thrust, torque, power, efficiency) bind(c, name = 'get_performance') 236 | use i_common, only : pi 237 | real(c_float), intent(out) :: rpm, thrust, torque, power, efficiency 238 | 239 | thrust = ctxt%ttot * ctxt%rho * ctxt%vel**2 * ctxt%rad**2 240 | torque = ctxt%qtot * ctxt%rho * ctxt%vel**2 * ctxt%rad**3 241 | power = ctxt%ptot * ctxt%rho * ctxt%vel**3 * ctxt%rad**2 242 | 243 | efficiency = ctxt%ttot / ctxt%ptot 244 | rpm = ctxt%vel / (ctxt%rad * ctxt%adv * pi / 30.) 245 | end 246 | 247 | function get_blade_angle_change() bind(c, name = 'get_blade_angle_change') 248 | use i_common, only : pi 249 | real(c_float) :: get_blade_angle_change 250 | get_blade_angle_change = ctxt%dbeta * 180.0 / pi 251 | end 252 | 253 | subroutine set_number_of_stations(setting) bind(c, name = 'set_number_of_stations') 254 | use m_spline, only : spline, seval 255 | use m_xrotor, only : setx 256 | integer(c_int), intent(in) :: setting 257 | integer :: i, iisav 258 | 259 | if (ctxt%lrotor) then 260 | iisav = ctxt%ii 261 | do i = 1, iisav 262 | ctxt%w1(i) = ctxt%xi(i) 263 | ctxt%w2(i) = ctxt%ch(i) 264 | ctxt%w4(i) = ctxt%beta(i) 265 | ctxt%w6(i) = ctxt%ubody(i) 266 | ctxt%w8(i) = ctxt%cldes(i) 267 | enddo 268 | ctxt%w3(1:ctxt%ii) = spline(ctxt%w1(1:ctxt%ii), ctxt% & 269 | & w2(1:ctxt%ii)) 270 | ctxt%w5(1:ctxt%ii) = spline(ctxt%w1(1:ctxt%ii), ctxt% & 271 | & w4(1:ctxt%ii)) 272 | ctxt%w7(1:ctxt%ii) = spline(ctxt%w1(1:ctxt%ii), ctxt% & 273 | & w6(1:ctxt%ii)) 274 | ctxt%w9(1:ctxt%ii) = spline(ctxt%w1(1:ctxt%ii), ctxt% & 275 | & w8(1:ctxt%ii)) 276 | endif 277 | 278 | ctxt%ii = setting 279 | ctxt%iinf = ctxt%ii + ctxt%ii / 2 280 | call setx(ctxt) 281 | if (ctxt%lrotor) then 282 | do i = 1, ctxt%ii 283 | ctxt%ch(i) = seval(ctxt%xi(i), ctxt%w2, ctxt%w3& 284 | &, ctxt%w1) 285 | ctxt%beta(i) = seval(ctxt%xi(i), ctxt%w4, ctxt%& 286 | & w5, ctxt%w1) 287 | ctxt%ubody(i) = seval(ctxt%xi(i), ctxt%w6, ctxt& 288 | & %w7, ctxt%w1) 289 | ctxt%cldes(i) = seval(ctxt%xi(i), ctxt%w8, ctxt& 290 | & %w9, ctxt%w1) 291 | ctxt%beta0(i) = ctxt%beta(i) 292 | enddo 293 | endif 294 | end 295 | 296 | function get_number_of_stations() bind(c, name = 'get_number_of_stations') 297 | integer(c_int) :: get_number_of_stations 298 | get_number_of_stations = ctxt%ii 299 | end 300 | 301 | subroutine get_station_conditions(n, xi, Re, M, Cl, Cd, Cm) bind(c, name = 'get_station_conditions') 302 | use m_xoper, only : calcw 303 | integer(c_int), intent(in) :: n 304 | real(c_float), intent(out) :: xi(n), Re(n), M(n), Cl(n), Cd(n), Cm(n) 305 | real :: w 306 | integer :: i 307 | 308 | xi = ctxt%xi(1:n) 309 | Re = ctxt%re(1:n) 310 | Cl = ctxt%cl(1:n) 311 | Cd = ctxt%cd(1:n) 312 | Cm = ctxt%cm(1:n) 313 | 314 | do i = 1, n 315 | call calcw(ctxt, i, w) 316 | M(i) = w * ctxt%vel / ctxt%vso 317 | enddo 318 | end 319 | 320 | end 321 | -------------------------------------------------------------------------------- /src/i_common.f90: -------------------------------------------------------------------------------- 1 | !*==I_COMMON.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | 22 | module i_common 23 | use, intrinsic :: iso_c_binding, only : c_f_pointer, c_float, c_int, c_ptr 24 | implicit none 25 | 26 | public 27 | !--- ix - max number of radial prop stations 28 | !--- icasx - max number of stored cases 29 | !--- nparx - number of case parameters stored 30 | !--- iwx - dimension of work arrays 31 | integer, parameter :: ix = 100, ixp = ix + 1, nparx = 12, icasx = 100, iwx = 200 32 | 33 | !--- nax - max number of aerodynamic sections defined 34 | !--- ndx - number of aerodynamic parameter defined for each section 35 | integer, parameter :: nax = 20, ndx = 14 36 | 37 | integer, parameter :: iq = ix + 5, jx = (iq * 3) / 2 + 1 38 | real, parameter :: pi = 4.0 * atan(1.0) 39 | ! real, parameter :: pi = 3.141592654 40 | 41 | logical :: show_output = .true. 42 | 43 | logical, private, parameter :: f = .false. 44 | 45 | type, public :: Common 46 | real :: q(iq, iq) = 0. 47 | 48 | character(len = 80) :: savfil = '', fname = '' 49 | character(len = 32) :: name = '' 50 | 51 | integer :: luread = 0, luwrit = 0, lutemp = 0, lusave = 0 52 | 53 | logical :: conv = f, greek = f, terse = f, vrtx = f, fast = f, free = f, duct = f, lstruc = f, & 54 | ldesini = f, loprini = f, lrotor = f, lvnorm = f, lpwrvar = f, & 55 | wind = f, dest = f, desp = f, stall(ix) = f, legend = f 56 | logical :: use_compr_corr = .false. 57 | 58 | real :: rho = 0., rmu = 0., vso = 0., vel = 0., rad = 0., gee = 0., alt = 0. 59 | 60 | integer :: ii = 0, iinf = 0, incr = 0, nn = 0, nblds = 0, ixspac = 0, & 61 | niterd = 0, nitera = 0 62 | real :: version = 0., dt = 0. 63 | 64 | integer :: iaero(ix) = 0 65 | 66 | real :: ch(ix) = 0., beta(ix) = 0., beta0(ix) = 0., t(ix) = 0., dbeta = 0., & 67 | xi(ix) = 0., dxi(ix) = 0., xi0 = 0., xitip = 0., xinf = 0., & 68 | xpitch = 0., xv(ix) = 0., rake = 0. 69 | 70 | integer :: nadd = 0 71 | real :: radd(ix) = 0., & 72 | uadd(ix) = 0., vadd(ix) = 0., & 73 | uaddr(ix) = 0., vaddr(ix) = 0., & 74 | ubody(ix) = 0., vbody(ix) = 0., urduct = 0. 75 | 76 | real :: cl(ix) = 0., cd(ix) = 0., cm(ix) = 0., & 77 | re(ix) = 0., effp(ix) = 0., gam(ix) = 0., & 78 | dtii(ix) = 0., dpii(ix) = 0., & 79 | dtvi(ix) = 0., dpvi(ix) = 0., & 80 | dtwi(ix) = 0., dpwi(ix) = 0. 81 | 82 | integer :: naero = 0 83 | real :: xiaero(nax) = 0., aerodata(ndx, nax) = 0. 84 | 85 | integer :: n_polars = 0 86 | integer, allocatable :: n_polar_points(:), i_polars(:) 87 | real, allocatable :: xi_polars(:), polardata(:, :) 88 | 89 | real :: px(ix) = 0., py(ix) = 0., pz(ix) = 0., & 90 | mx(ix) = 0., my(ix) = 0., mz(ix) = 0., & 91 | px_ty(ix) = 0., px_tz(ix) = 0., pz_tx(ix) = 0., pz_ty(ix) = 0., pz_wz(ix) = 0., & 92 | my_ty(ix) = 0., mz_ty(ix) = 0., mz_tx(ix) = 0., mz_wz(ix) = 0., & 93 | tx(ixp) = 0., ty(ixp) = 0., tz(ixp) = 0., & 94 | wx(ixp) = 0., wy(ixp) = 0., wz(ixp) = 0., & 95 | shrx(ixp) = 0., shry(ixp) = 0., shrz(ixp) = 0., & 96 | momx(ixp) = 0., momy(ixp) = 0., momz(ixp) = 0., & 97 | eixxb(ix) = 0., eiyyb(ix) = 0., & 98 | eab(ix) = 0., gjb(ix) = 0., ekb(ix) = 0., & 99 | mb(ix) = 0., mxxb(ix) = 0., & 100 | xocg(ix) = 0., xosc(ix) = 0., & 101 | rstb(ix) = 0. 102 | 103 | real :: caspar(0:nparx, icasx) = 0. 104 | integer :: ncase = 0, kcase = 0, iwtyp = 0 105 | 106 | real :: adv = 0., adw = 0., adwfctr = 0., & 107 | rms = 0., rlx = 0., effinv = 0., & 108 | tspec = 0., pspec = 0., qspec = 0., & 109 | ttot = 0., ptot = 0., qtot = 0., & 110 | tinv = 0., pinv = 0., twak = 0., & 111 | pwak = 0., tvis = 0., pvis = 0., & 112 | gresmx = 0., fresmx = 0., aresmx = 0. 113 | 114 | real :: ti_adv = 0., pi_adv = 0., ti_adw = 0., pi_adw = 0., & 115 | tw_adv = 0., pw_adv = 0., tw_adw = 0., pw_adw = 0., & 116 | tv_adv = 0., pv_adv = 0., tv_adw = 0., pv_adw = 0., tv_dbe = 0., pv_dbe = 0., & 117 | ti_gam(ix) = 0., pi_gam(ix) = 0., & 118 | tw_gam(ix) = 0., pw_gam(ix) = 0., & 119 | tv_gam(ix) = 0., pv_gam(ix) = 0. 120 | 121 | real :: w0(iwx) = 0., w1(iwx) = 0., w2(iwx) = 0., w3(iwx) = 0., w4(iwx) = 0., & 122 | w5(iwx) = 0., w6(iwx) = 0., w7(iwx) = 0., w8(iwx) = 0., w9(iwx) = 0., & 123 | t0(iwx) = 0., t1(iwx) = 0., t2(iwx) = 0., t3(iwx) = 0., t4(iwx) = 0., & 124 | t5(iwx) = 0., t6(iwx) = 0., t7(iwx) = 0., t8(iwx) = 0., t9(iwx) = 0. 125 | 126 | real :: raddes = 0., veldes = 0., advdes = 0., rpmdes = 0., r0des = 0., rwdes = 0., & 127 | tddes = 0., pddes = 0., des = 0., pdes = 0., cldes0 = 0., cldes(ix) = 0. 128 | 129 | integer :: npwrvar = 0 130 | real :: rpmvar(ix) = 0., pwrvar(ix) = 0., xpwrvar(ix) = 0. 131 | 132 | real :: vwak(ix) = 0., vw_gam(ix, ix) = 0., vw_adw(ix) = 0., vw_adv(ix) = 0., & 133 | vind(3, ix) = 0., vind_gam(3, ix, ix) = 0., vind_adw(3, ix) = 0. 134 | 135 | real :: xw0 = 0., xwtip = 0., & 136 | xw(ix) = 0., xw_gam(ix, ix) = 0., xw_adw(ix) = 0., xw_adv(ix) = 0., & 137 | dxw(ix) = 0., dxw_gam(ix, ix) = 0., dxw_adw(ix) = 0., dxw_adv(ix) = 0. 138 | 139 | real :: dgam(ix) = 0., res(iq) = 0., dadv = 0., dadw = 0., dbet = 0., deff = 0., dq(iq) = 0., dgamold(ix) = 0. 140 | 141 | logical :: always_overwrite = .true. 142 | end type Common 143 | ! 144 | !cc EQUIVALENCE (A_GAMJ(0,1),Q(1,1)) 145 | ! 146 | 147 | ! 148 | ! LUREAD Terminal input unit number (normally 5) 149 | ! LUWRIT Terminal output unit number (normally 6) 150 | ! LUTEMP Disk file unit number 151 | ! LUSAVE Disk save file output unit number 152 | ! 153 | ! RHO Fluid density (dimensioned) 154 | ! RMU Fluid dynamic viscosity (dimensioned) 155 | ! VSO Fluid speed of sound (dimensioned) 156 | ! VEL Flight speed (dimensioned) 157 | ! RAD Rotor tip radius (dimensioned) 158 | ! GEE Earth's acceleration (dimensioned) 159 | ! ALT Altitude for fluid properties (km), 999.0 if not defined 160 | ! 161 | ! II Number of radial stations on blade 162 | ! IINF Number of radial stations on blade + outer domain 163 | ! INCR Radial station increment for terminal output 164 | ! NN Number of perturbation potential harmonic terms 165 | ! NBLDS Number of blades 166 | ! 167 | ! IXSPAC 1 = cosine r/R array stretching 168 | ! 2 = sine stretching (less spacing near root) 169 | ! 170 | ! CH(.) Chord array 171 | ! BETA(.) Twist angle array 172 | ! BETA0(.) Static twist angle array (with zero structural loads) 173 | ! T(.) Dummy radial coordinate array 174 | ! DBETA Accumulated change in twist angle 175 | ! XI(.) Radial coordinate array (r/R) 176 | ! DXI(.) Radial coordinate increment at each station 177 | ! XI0 Blade root radial coordinate value 178 | ! XITIP Blade tip radial coordinate value (always = 1) 179 | ! XINF Outer radial coordinate value where farfield BC is applied 180 | ! XPITCH x/c location of pitch axis for loads calculations and plots 181 | ! IAERO Index of inboard aero section for aero characteristics 182 | ! 183 | ! CL(.) Local lift coefficient array 184 | ! CD(.) Local drag coefficient array 185 | ! CM(.) Local blade airfoil Cm 186 | ! GAM(.) Local circulation array 187 | ! STALL(.) Local profile stall flag array 188 | ! RE(.) Local Reynolds number array 189 | ! EFFP(.) Local profile efficiency array 190 | ! 191 | !-- aero data quantities for each defined radial aerodynamic section 192 | ! NAERO Number of aerodynamic datasets defined (NAERO>=1) 193 | ! XIAERO Radial station r/R where aero dataset is defined 194 | ! AERODATA Aerodynamic definition of the blade section at XIAERO 195 | ! AERODATA( 1,x) = A0 (angle of zero lift) 196 | ! AERODATA( 2,x) = CLMAX (Max CL) 197 | ! AERODATA( 3,x) = CLMIN (Min CL) 198 | ! AERODATA( 4,x) = DCLDA (Incompressible 2-D lift curve slope) 199 | ! AERODATA( 5,x) = DCLDA_STALL (2-D lift curve slope at stall) 200 | ! AERODATA( 6,x) = DCL_STALL (CL increment, onset to full stall) 201 | ! AERODATA( 7,x) = CDMIN (Minimum drag coefficient value) 202 | ! AERODATA( 8,x) = CLDMIN (Lift at minimum drag value) 203 | ! AERODATA( 9,x) = DCDCL2 (Parabolic drag param d(Cd)/dCL^2) 204 | ! AERODATA(10,x) = CMCON (Incompressible 2-D pitching moment) 205 | ! AERODATA(11,x) = REREF (reference Reynold's number) 206 | ! AERODATA(12,x) = REXP (Reynold's number exponent Cd~Re^REXP) 207 | ! AERODATA(13,x) = MCRIT (critical Mach #) 208 | ! AERODATA(14,x) = TOC (thickness/chord) 209 | ! 210 | !-- structural quantities below are referred to the following axes: 211 | !- X-axis points backward along rotation axis 212 | !- Y-axis points radially outward 213 | !- Z-axis points sideways at right angles to blade (Z = X x Y) 214 | ! 215 | ! PX PY PZ(.) load/length in X, Y, Z directions (aero + centrifugal) 216 | ! MX MY MZ(.) moment/length around X, Y, Z axes (aero + centrifugal) 217 | ! 218 | ! PX_TY(.) sensitivities of loadings to deflections 219 | ! PX_TZ(.) 220 | ! PZ_TX(.) (A_B denotes dA/dB) 221 | ! PZ_TY(.) 222 | ! PZ_WZ(.) 223 | ! MY_TY(.) 224 | ! MZ_TY(.) 225 | ! MZ_TX(.) 226 | ! MZ_WZ(.) 227 | ! 228 | ! TX TY TZ(.) deflection angles around X, Y, Z axes 229 | ! WX WY WZ(.) deflections in X, Y, Z directions 230 | ! SHRX SHRY SHRZ(.) resultant loads (shear,tension) in X Y Z directions 231 | ! MOMX MOMY MOMZ(.) resultant moments (b.m., torsion) around X Y Z axes 232 | ! 233 | ! EIXXB(.) bending stiffness in the blade airfoil plane (dimensioned) 234 | ! EIYYB(.) bending stiffness out of blade airfoil plane (dimensioned) 235 | ! EAB(.) extensional stiffness (dimensioned) 236 | ! GJB(.) torsional stiffness (dimensioned) 237 | ! EKB(.) torsion moment/extension strain stiffness (dimensioned) 238 | ! MB(.) mass/length of blade (dimensioned) 239 | ! MXXB(.) pitch-axis moment of inertia/length of blade (dimensioned) 240 | ! XOCG(.) x/c of blade section CG 241 | ! XOSC(.) x/c of blade section shear center 242 | ! RSTB(.) radius used for post-processing strain display (dimensioned) 243 | ! 244 | ! CASPAR(..) case-parameter array 245 | ! 0 case run flags 246 | ! 1 advance ratio 247 | ! 2 velocity 248 | ! 3 tip angle 249 | ! 4 altitude 250 | ! 5 density 251 | ! 6 dynamic viscosity 252 | ! 7 speed of sound 253 | ! 8 power 254 | ! 9 thrust 255 | ! 10 torque 256 | ! 11 efficiency 257 | ! 258 | ! NCASE current number of saved operating cases 259 | ! KCASE indicator for independent parameter of case sweep 260 | ! 0 = none 261 | ! 1 = advance ratio 262 | ! 2 = rpm 263 | ! 3 = velocity 264 | ! 4 = blade angle 265 | ! 266 | ! ADV Advance ratio 267 | ! ADW Wake advance ratio 268 | ! TPSPEC Specified thrust, torque, or power 269 | ! RMS, RLX Rms residual and under-relaxation factor 270 | ! EFFINV 1 / Inviscid efficiency 271 | ! IWTYP Type of induced velocity model emplyed currently 272 | ! 1 = Graded Momentum, 2 = Potential Formulation 273 | ! 274 | ! TTOT Rotor inviscid + viscous + nacelle thrust 275 | ! PTOT Rotor inviscid + viscous + nacelle power 276 | ! QTOT Rotor inviscid + viscous + nacelle torque = PTOT*ADV 277 | ! TINV, PINV Inviscid thrust, power 278 | ! TWAK, PWAK Inviscid + nacelle thrust, power 279 | ! TVIS, PVIS Viscous thrust, power 280 | ! 281 | ! TTOT = TVIS + TWAK 282 | ! = TVIS + (TINV + Tnacelle) 283 | ! 284 | ! 285 | ! TI_ADV Sensitivities of TINV,PINV to advance ratio 286 | ! PI_ADV 287 | ! TI_ADW Sensitivities of TINV,PINV to wake advance ratio 288 | ! PI_ADW 289 | ! TW_ADV Sensitivities of TWAK,PWAK to advance ratio 290 | ! PW_ADV 291 | ! TW_ADW Sensitivities of TWAK,PWAK to wake advance ratio 292 | ! PW_ADW 293 | ! TV_ADV Sensitivities of TVIS,PVIS to advance ratio 294 | ! PV_ADV 295 | ! TV_ADW Sensitivities of TVIS,PVIS to wake advance ratio 296 | ! PV_ADW 297 | ! TV_DBE Sensitivities of TVIS,PVIS to blade angle change 298 | ! PV_DBE 299 | ! TI_GAM(.) Sensitivity arrays to radial circulation distribution 300 | ! PI_GAM(.) " 301 | ! TW_GAM(.) " 302 | ! PW_GAM(.) " 303 | ! TV_GAM(.) " 304 | ! PV_GAM(.) " 305 | ! 306 | ! CONV T if Converged solution exists 307 | ! GREEK T if Unrecognized command 308 | ! TERSE T if Terse output (no radial distributions) 309 | ! FAST T if Graded Momentum, Potential otherwise 310 | ! FREE T if Free wake 311 | ! DUCT T if duct is present 312 | ! LDESINI T if rotor is to be initialized for design each time 313 | ! LOPRINI T if rotor is to be initialized for analysis each time 314 | ! LROTOR T if rotor exists 315 | ! LVNORM T if flight speed V is used for normalization, wR otherwise 316 | ! WIND T if windmill-mode plotting is to be used 317 | ! 318 | ! DEST Design-to-thrust option flag 319 | ! DESP Design-to-power option flag 320 | ! 321 | ! SAVFIL Disk output save filename 322 | ! FNAME Generic filename 323 | ! NAME Case name 324 | ! 325 | ! W0-9(.) Temporary work arrays 326 | ! T0-9(.) Temporary work arrays 327 | ! 328 | ! RADDES Design rotor radius (dimensioned) 329 | ! VELDES Design speed (dimensioned) 330 | ! ADVDES Design advance ratio (dimensioned) 331 | ! RPMDES Design advance ratio (dimensioned) 332 | ! R0DES Design root radius (dimensioned) 333 | ! RWDES Design disp. body root radius (dimensioned) 334 | ! TDDES Design thrust (dimensioned) 335 | ! PDDES Design power (dimensioned) 336 | ! TDES Design thrust 337 | ! PDES Design power 338 | ! CLDES0 Constant design CL 339 | ! CLDES(.) Radial design-CL array 340 | ! 341 | ! VTAN(.) Tangential induced velocity array 342 | ! VWAK(.) Equivalent-rotor tangential induced velocity array 343 | ! VT_GAM(..) VTAN-blade bound circulation sensitivity matrix 344 | ! VT_ADW(.) VTAN-wake advance ratio sensitivity matrix 345 | ! VW_GAM(..) VWAK-blade bound circulation sensitivity matrix 346 | ! VW_ADW(.) VWAK-wake advance ratio sensitivity matrix 347 | ! VW_ADV(.) VWAK-advance ratio sensitivity matrix 348 | ! XW0 Root radius of equivalent rotor (= far wake disp. body radius) 349 | ! XWTIP Tip radius of equivalent rotor 350 | ! XW(.) Equivalent-rotor radial coordinate array 351 | ! XW_GAM(..) XW-blade bound circulation sensitivity matrix 352 | ! XW_ADW(.) XW-wake advance ratio sensitivity matrix 353 | ! XW_ADV(.) XW-advance ratio sensitivity matrix 354 | ! DXW(.) Equivalent-rotor radial coordinate increment array 355 | ! DXW_GAM(..) DXW-blade bound circulation sensitivity matrix 356 | ! DXW_ADW(.) DXW-wake advance ratio sensitivity matrix 357 | ! DXW_ADV(.) DXW-advance ratio sensitivity matrix 358 | ! 359 | ! DGAM(.) Newton update delta array for bound circulation 360 | ! RES(.) Newton residual array for bound circulation 361 | ! DADV Newton update delta for advance ratio 362 | ! DADW Newton update delta for wake advance ratio 363 | ! DBET Newton update delta for blade angle 364 | ! DEFF Newton update delta for 1 / inviscid efficiency 365 | ! DQ(.) Generic solution vector 366 | ! 367 | ! UBODY(.) Nacelle perturbation axial velocity 368 | ! VBODY(.) Nacelle perturbation radial velocity 369 | ! ABODY(.) Nacelle cross-sectional area array 370 | ! ZBODY(.) Nacelle streamwise coordinate 371 | ! NZ Number of nacelle streamwise stations 372 | end 373 | -------------------------------------------------------------------------------- /src/m_spline.f90: -------------------------------------------------------------------------------- 1 | !*==M_SPLINE.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | 22 | module m_spline 23 | implicit none 24 | public 25 | contains 26 | function spline(s, x) result(xs) 27 | real, intent(in) :: s(:), x(:) 28 | real, allocatable :: xs(:), a(:), b(:), c(:) 29 | 30 | integer :: i, n 31 | real :: dsm, dsp 32 | !------------------------------------------------------- 33 | ! Calculates spline coefficients for x(s). | 34 | ! Zero 2nd derivative end conditions are used. | 35 | ! To evaluate the spline at some value of s, | 36 | ! use seval and/or deval. | 37 | ! | 38 | ! s independent variable array (input) | 39 | ! x dependent variable array (input) | 40 | ! xs dx/ds array (calculated) | 41 | ! n number of points (input) | 42 | ! | 43 | !------------------------------------------------------- 44 | n = min(size(s), size(x)) 45 | allocate (xs(n)) 46 | 47 | if (n==1) then 48 | xs(1) = 0. 49 | return 50 | endif 51 | 52 | allocate (a(n), b(n), c(n)) 53 | 54 | b(1) = 0. 55 | c(n) = 0. 56 | 57 | do i = 2, n - 1 58 | dsm = s(i) - s(i - 1) 59 | dsp = s(i + 1) - s(i) 60 | b(i) = dsp 61 | a(i) = 2.0 * (dsm + dsp) 62 | c(i) = dsm 63 | xs(i) = 3.0 * ((x(i + 1) - x(i)) * dsm / dsp + (x(i) - x(i - 1)) * dsp / dsm) 64 | enddo 65 | 66 | !---- set zero second derivative end conditions 67 | a(1) = 2.0 68 | c(1) = 1.0 69 | xs(1) = 3.0 * (x(2) - x(1)) / (s(2) - s(1)) 70 | b(n) = 1.0 71 | a(n) = 2.0 72 | xs(n) = 3.0 * (x(n) - x(n - 1)) / (s(n) - s(n - 1)) 73 | 74 | !---- solve for derivative array xs 75 | call trisol(a, b, c, xs) 76 | end 77 | ! spline 78 | 79 | function splind(s, x, xs1, xs2) result(xs) 80 | real, intent(in) :: s(:), x(:), xs1, xs2 81 | real, allocatable :: xs(:), a(:), b(:), c(:) 82 | 83 | integer :: i, n 84 | real :: dsm, dsp 85 | !------------------------------------------------------- 86 | ! Calculates spline coefficients for x(s). | 87 | ! Specified 1st derivative and/or zero 2nd | 88 | ! or 3rd derivative end conditions can be used. | 89 | ! To evaluate the spline at some value of s, | 90 | ! use seval and/or deval. | 91 | ! | 92 | ! s independent variable array (input) | 93 | ! x dependent variable array (input) | 94 | ! xs dx/ds array (calculated) | 95 | ! n number of points (input) | 96 | ! xs1,xs2 endpoint derivatives (input) | 97 | ! If = 999.0, use zero 2nd derivative | 98 | ! If = -999.0, use zero 3rd derivative | 99 | ! | 100 | !------------------------------------------------------- 101 | n = min(size(s), size(x)) 102 | allocate (xs(n)) 103 | 104 | if (n==1) then 105 | xs(1) = 0. 106 | return 107 | endif 108 | 109 | allocate (a(n), b(n), c(n)) 110 | 111 | do i = 2, n - 1 112 | dsm = s(i) - s(i - 1) 113 | dsp = s(i + 1) - s(i) 114 | b(i) = dsp 115 | a(i) = 2.0 * (dsm + dsp) 116 | c(i) = dsm 117 | xs(i) = 3.0 * ((x(i + 1) - x(i)) * dsm / dsp + (x(i) - x(i - 1)) * dsp / dsm) 118 | enddo 119 | 120 | !---- set left end condition 121 | if (xs1==999.0) then 122 | !----- zero 2nd derivative 123 | a(1) = 2.0 124 | c(1) = 1.0 125 | xs(1) = 3.0 * (x(2) - x(1)) / (s(2) - s(1)) 126 | elseif (xs1==-999.0) then 127 | !----- set zero 3rd derivative 128 | a(1) = 1.0 129 | c(1) = 1.0 130 | xs(1) = 2.0 * (x(2) - x(1)) / (s(2) - s(1)) 131 | else 132 | !----- specified 1st derivative 133 | a(1) = 1.0 134 | c(1) = 0. 135 | xs(1) = xs1 136 | endif 137 | 138 | !---- set right end condition 139 | if (xs2==999.0) then 140 | !----- zero 2nd derivative 141 | b(n) = 1.0 142 | a(n) = 2.0 143 | xs(n) = 3.0 * (x(n) - x(n - 1)) / (s(n) - s(n - 1)) 144 | elseif (xs2==-999.0) then 145 | !----- zero 3rd derivative 146 | b(n) = 1.0 147 | a(n) = 1.0 148 | xs(n) = 2.0 * (x(n) - x(n - 1)) / (s(n) - s(n - 1)) 149 | else 150 | !----- specified 1st derivative 151 | a(n) = 1.0 152 | b(n) = 0. 153 | xs(n) = xs2 154 | endif 155 | 156 | !---- if only two points, cannot have zero third derivatives at both ends 157 | if (n==2.and.xs1==-999.0.and.xs2==-999.0) then 158 | !----- set zero 2nd derivative at right end (left end will also be zero) 159 | b(n) = 1.0 160 | a(n) = 2.0 161 | xs(n) = 3.0 * (x(n) - x(n - 1)) / (s(n) - s(n - 1)) 162 | endif 163 | 164 | !---- solve for derivative array xs 165 | call trisol(a, b, c, xs) 166 | end 167 | ! splind 168 | 169 | function splina(s, x) result(xs) 170 | real, intent(in) :: s(:), x(:) 171 | real, allocatable :: xs(:) 172 | 173 | logical :: lend 174 | integer :: i, n 175 | real :: ds, dx, xs1, xs2 176 | !------------------------------------------------------- 177 | ! Calculates spline coefficients for x(s). | 178 | ! a simple averaging of adjacent segment slopes | 179 | ! is used to achieve non-oscillatory curve | 180 | ! End conditions are set by end segment slope | 181 | ! To evaluate the spline at some value of s, | 182 | ! use seval and/or deval. | 183 | ! | 184 | ! s independent variable array (input) | 185 | ! x dependent variable array (input) | 186 | ! xs dx/ds array (calculated) | 187 | ! n number of points (input) | 188 | ! | 189 | !------------------------------------------------------- 190 | n = min(size(s), size(x)) 191 | allocate (xs(n)) 192 | 193 | if (n==1) then 194 | xs(1) = 0. 195 | return 196 | endif 197 | 198 | lend = .true. 199 | do i = 1, n - 1 200 | ds = s(i + 1) - s(i) 201 | if (ds==0.) then 202 | xs(i) = xs1 203 | lend = .true. 204 | else 205 | dx = x(i + 1) - x(i) 206 | xs2 = dx / ds 207 | if (lend) then 208 | xs(i) = xs2 209 | lend = .false. 210 | else 211 | xs(i) = 0.5 * (xs1 + xs2) 212 | endif 213 | endif 214 | xs1 = xs2 215 | enddo 216 | xs(n) = xs1 217 | end 218 | ! splina 219 | 220 | subroutine trisol(a, b, c, d) 221 | real, intent(inout) :: a(:), b(:), c(:), d(:) 222 | integer :: k, kk, km 223 | !----------------------------------------- 224 | ! Solves kk long, tri-diagonal system | 225 | ! | 226 | ! a c d | 227 | ! b a c d | 228 | ! b a . . | 229 | ! . . c . | 230 | ! b a d | 231 | ! | 232 | ! The righthand side d is replaced by | 233 | ! the solution. a, c are destroyed. | 234 | !----------------------------------------- 235 | kk = min(size(a), size(b), size(c), size(d)) 236 | 237 | do k = 2, kk 238 | km = k - 1 239 | c(km) = c(km) / a(km) 240 | d(km) = d(km) / a(km) 241 | a(k) = a(k) - b(k) * c(km) 242 | d(k) = d(k) - b(k) * d(km) 243 | enddo 244 | 245 | d(kk) = d(kk) / a(kk) 246 | 247 | do k = kk - 1, 1, -1 248 | d(k) = d(k) - c(k) * d(k + 1) 249 | enddo 250 | end 251 | ! trisol 252 | 253 | real function seval(ss, x, xs, s)result(val) 254 | real, intent(in) :: ss, x(:), xs(:), s(:) 255 | integer :: i, ilow, imid, n 256 | real :: ds, t, cx1, cx2 257 | !-------------------------------------------------- 258 | ! Calculates x(ss) | 259 | ! xs array must have been calculated by spline | 260 | !-------------------------------------------------- 261 | n = min(size(x), size(xs), size(s)) 262 | if (n==1) then 263 | val = x(1) 264 | return 265 | endif 266 | 267 | ilow = 1 268 | i = n 269 | 270 | do while (i - ilow>1) 271 | imid = (i + ilow) / 2 272 | if (ss1) 305 | imid = (i + ilow) / 2 306 | if (ss. 20 | !*********************************************************************** 21 | ! 22 | ! 23 | !==== user input routines with prompting and error trapping 24 | ! 25 | ! 26 | 27 | module m_userio 28 | implicit none 29 | contains 30 | subroutine aski(prompt, iinput) 31 | use i_common, only : show_output 32 | !*** Start of declarations inserted by SPAG 33 | integer NP 34 | !*** End of declarations inserted by SPAG 35 | ! 36 | !---- integer input 37 | ! 38 | character*(*) prompt, line*80 39 | integer iinput 40 | ! 41 | np = index(prompt, '^') - 1 42 | if (np==0) np = len(prompt) 43 | ! 44 | if (iinput/=999.and.show_output) write (*, 99001) iinput 45 | 99001 format (/'Current value : ', i5) 46 | 100 if (show_output) write (*, 99002) prompt(1:np) 47 | ! 48 | 99002 format (a, ' i> ', $) 49 | read (*, 99003, err = 100) line 50 | 99003 format (a) 51 | if (line/=' ') read (line, *, err = 100) iinput 52 | return 53 | end 54 | ! aski 55 | 56 | 57 | subroutine askr(prompt, rinput) 58 | use i_common, only : show_output 59 | !*** Start of declarations inserted by SPAG 60 | integer NP 61 | !*** End of declarations inserted by SPAG 62 | ! 63 | !---- real input 64 | ! 65 | character*(*) prompt, line*80 66 | real rinput 67 | ! 68 | np = index(prompt, '^') - 1 69 | if (np==0) np = len(prompt) 70 | ! 71 | if (rinput/=999..and.show_output) write (*, 99001) rinput 72 | 99001 format (/'Current value : ', g12.6) 73 | 100 if (show_output) write (*, 99002) prompt(1:np) 74 | ! 75 | 99002 format (a, ' r> ', $) 76 | read (*, 99003, err = 100) line 77 | 99003 format (a) 78 | if (line/=' ') read (line, *, err = 100) rinput 79 | return 80 | end 81 | ! askr 82 | 83 | 84 | subroutine askl(prompt, linput) 85 | use i_common, only : show_output 86 | !*** Start of declarations inserted by SPAG 87 | integer NP 88 | !*** End of declarations inserted by SPAG 89 | ! 90 | !---- logical input 91 | ! 92 | character*(*) prompt 93 | logical linput 94 | character*1 char 95 | ! 96 | np = index(prompt, '^') - 1 97 | if (np==0) np = len(prompt) 98 | do 99 | ! 100 | if (show_output) write (*, 99001) prompt(1:np) 101 | ! 102 | 99001 format (/a, ' y/n> ', $) 103 | read (*, 99002) char 104 | 99002 format (a) 105 | if (char=='y') char = 'y' 106 | if (char=='n') char = 'n' 107 | if (char=='y'.or.char=='n') then 108 | ! 109 | linput = char=='y' 110 | return 111 | endif 112 | enddo 113 | end 114 | ! askl 115 | 116 | 117 | subroutine asks(prompt, input) 118 | use i_common, only : show_output 119 | !*** Start of declarations inserted by SPAG 120 | integer NP 121 | !*** End of declarations inserted by SPAG 122 | ! 123 | !---- string of arbitrary length input 124 | ! 125 | character*(*) prompt 126 | character*(*) input 127 | ! 128 | np = index(prompt, '^') - 1 129 | if (np==0) np = len(prompt) 130 | ! 131 | if (show_output) write (*, 99001) prompt(1:np) 132 | ! 133 | 99001 format (/a, ' s> ', $) 134 | read (*, 99002) input 135 | 99002 format (a) 136 | ! 137 | return 138 | end 139 | ! asks 140 | 141 | 142 | 143 | subroutine askc(prompt, comand, cargs) 144 | use i_common, only : show_output 145 | !*** Start of declarations inserted by SPAG 146 | integer I, IZERO, K, KI, NCARGS, NP 147 | !*** End of declarations inserted by SPAG 148 | ! 149 | !---- returns 4-byte character string input converted to uppercase 150 | !---- also returns rest of input characters in cargs string 151 | ! 152 | character*(*) prompt 153 | character*(*) comand, cargs 154 | ! 155 | character*128 line 156 | ! 157 | izero = ichar('0') 158 | ! 159 | np = index(prompt, '^') - 1 160 | if (np==0) np = len(prompt) 161 | ! 162 | if (show_output) write (*, 99001) prompt(1:np) 163 | ! 164 | 99001 format (/a, ' c> ', $) 165 | read (*, 99002) line 166 | 99002 format (a) 167 | ! 168 | !---- strip off leading blanks 169 | do k = 1, 128 170 | if (line(1:1)/=' ') exit 171 | line = line(2:128) 172 | enddo 173 | ! 174 | !---- find position of first blank, "+", "-", ".", ",", or numeral 175 | k = index(line, ' ') 176 | ki = index(line, '-') 177 | if (ki/=0) k = min(k, ki) 178 | ki = index(line, '+') 179 | if (ki/=0) k = min(k, ki) 180 | ki = index(line, '.') 181 | if (ki/=0) k = min(k, ki) 182 | ki = index(line, ',') 183 | if (ki/=0) k = min(k, ki) 184 | do i = 0, 9 185 | ki = index(line, char(izero + i)) 186 | if (ki/=0) k = min(k, ki) 187 | enddo 188 | ! 189 | ! if(k == 1) then 190 | !c------ the "command" is a number... set entire comand string with it 191 | ! comand = line 192 | ! else 193 | !c------ the "command" is some string... just use the part up to the argument 194 | ! comand = line(1:k-1) 195 | ! endif 196 | ! 197 | if (k<=1) k = 5 198 | !---- set 4-byte alphabetic command string and convert it to uppercase 199 | comand = line(1:k - 1) 200 | call lc2uc(comand) 201 | ! 202 | cargs = line(k:128) 203 | call strip(cargs, ncargs) 204 | return 205 | end 206 | ! askc 207 | 208 | 209 | subroutine lc2uc(input) 210 | !*** Start of declarations inserted by SPAG 211 | integer I, K, N 212 | !*** End of declarations inserted by SPAG 213 | character*(*) input 214 | ! 215 | character*26 lcase, ucase 216 | data lcase/'abcdefghijklmnopqrstuvwxyz'/ 217 | data ucase/'abcdefghijklmnopqrstuvwxyz'/ 218 | ! 219 | n = len(input) 220 | ! 221 | do i = 1, n 222 | k = index(lcase, input(i:i)) 223 | if (k>0) input(i:i) = ucase(k:k) 224 | enddo 225 | ! 226 | end 227 | ! lc2uc 228 | 229 | 230 | 231 | 232 | subroutine readi(n, ivar, error) 233 | !*** Start of declarations inserted by SPAG 234 | integer I, IVAR, IVTMP, N, NTMP 235 | !*** End of declarations inserted by SPAG 236 | dimension ivar(n) 237 | logical error 238 | !-------------------------------------------------- 239 | ! Reads n integer variables, leaving unchanged 240 | ! if only is entered. 241 | !-------------------------------------------------- 242 | dimension ivtmp(40) 243 | character*80 line 244 | ! 245 | read (*, 99001) line 246 | 99001 format (a80) 247 | ! 248 | do i = 1, n 249 | ivtmp(i) = ivar(i) 250 | enddo 251 | ! 252 | ntmp = 40 253 | call getint(line, ivtmp, ntmp, error) 254 | ! 255 | if (error) return 256 | ! 257 | do i = 1, n 258 | ivar(i) = ivtmp(i) 259 | enddo 260 | ! 261 | end 262 | ! readi 263 | 264 | 265 | subroutine readr(n, var, error) 266 | !*** Start of declarations inserted by SPAG 267 | integer I, N, NTMP 268 | real VAR, VTMP 269 | !*** End of declarations inserted by SPAG 270 | dimension var(n) 271 | logical error 272 | !------------------------------------------------- 273 | ! Reads n real variables, leaving unchanged 274 | ! if only is entered. 275 | !------------------------------------------------- 276 | dimension vtmp(40) 277 | character*80 line 278 | ! 279 | read (*, 99001) line 280 | 99001 format (a80) 281 | ! 282 | do i = 1, n 283 | vtmp(i) = var(i) 284 | enddo 285 | ! 286 | ntmp = 40 287 | call getflt(line, vtmp, ntmp, error) 288 | ! 289 | if (error) return 290 | ! 291 | do i = 1, n 292 | var(i) = vtmp(i) 293 | enddo 294 | ! 295 | end 296 | ! readr 297 | 298 | 299 | subroutine getint(input, a, n, error) 300 | !*** Start of declarations inserted by SPAG 301 | integer I, ILEN, ILENP, IPASS, K, KCOMMA, KSPACE, N, NINP 302 | !*** End of declarations inserted by SPAG 303 | character*(*) input 304 | integer a(*) 305 | logical error 306 | !---------------------------------------------------------- 307 | ! Parses character string input into an array 308 | ! of integer numbers returned in a(1...n) 309 | ! 310 | ! Will attempt to extract no more than n numbers, 311 | ! unless n = 0, in which case all numbers present 312 | ! in input will be extracted. 313 | ! 314 | ! n returns how many numbers were actually extracted. 315 | !---------------------------------------------------------- 316 | character*130 rec 317 | ! 318 | !---- only first 128 characters in input will be parsed 319 | ilen = min(len(input), 128) 320 | ilenp = ilen + 2 321 | ! 322 | !---- put input into local work string (which will be munched) 323 | rec(1:ilenp) = input(1:ilen) // ' ,' 324 | ! 325 | !---- ignore everything after a "!" character 326 | k = index(rec, '!') 327 | if (k>0) rec(1:ilen) = rec(1:k - 1) 328 | ! 329 | ninp = n 330 | ! 331 | !---- count up how many numbers are to be extracted 332 | n = 0 333 | k = 1 334 | do ipass = 1, ilen 335 | !------ search for next space or comma starting with current index k 336 | kspace = index(rec(k:ilenp), ' ') + k - 1 337 | kcomma = index(rec(k:ilenp), ',') + k - 1 338 | ! 339 | if (k==kspace) then 340 | !------- just skip this space 341 | k = k + 1 342 | goto 50 343 | endif 344 | ! 345 | if (k==kcomma) then 346 | !------- comma found.. increment number count and keep looking 347 | n = n + 1 348 | k = k + 1 349 | goto 50 350 | endif 351 | ! 352 | !------ neither space nor comma found, so we ran into a number... 353 | !- ...increment number counter and keep looking after next space or comma 354 | n = n + 1 355 | k = min(kspace, kcomma) + 1 356 | ! 357 | 50 if (k>=ilen) exit 358 | enddo 359 | ! 360 | !---- decide on how many numbers to read, and go ahead and read them 361 | if (ninp>0) n = min(n, ninp) 362 | read (rec(1:ilen), *, err = 100) (a(i), i = 1, n) 363 | error = .false. 364 | return 365 | ! 366 | !---- bzzzt !!! 367 | !cc write(*,*) 'getint: String-to-integer conversion error.' 368 | 100 n = 0 369 | error = .true. 370 | end 371 | 372 | 373 | subroutine getflt(input, a, n, error) 374 | !*** Start of declarations inserted by SPAG 375 | integer I, ILEN, ILENP, IPASS, K, KCOMMA, KSPACE, N, NINP 376 | !*** End of declarations inserted by SPAG 377 | character*(*) input 378 | real a(*) 379 | logical error 380 | !---------------------------------------------------------- 381 | ! Parses character string input into an array 382 | ! of real numbers returned in a(1...n) 383 | ! 384 | ! Will attempt to extract no more than n numbers, 385 | ! unless n = 0, in which case all numbers present 386 | ! in input will be extracted. 387 | ! 388 | ! n returns how many numbers were actually extracted. 389 | !---------------------------------------------------------- 390 | character*130 rec 391 | ! 392 | !---- only first 128 characters in input will be parsed 393 | ilen = min(len(input), 128) 394 | ilenp = ilen + 2 395 | ! 396 | !---- put input into local work string (which will be munched) 397 | rec(1:ilenp) = input(1:ilen) // ' ,' 398 | ! 399 | !---- ignore everything after a "!" character 400 | k = index(rec, '!') 401 | if (k>0) rec(1:ilen) = rec(1:k - 1) 402 | ! 403 | ninp = n 404 | ! 405 | !---- count up how many numbers are to be extracted 406 | n = 0 407 | k = 1 408 | do ipass = 1, ilen 409 | !------ search for next space or comma starting with current index k 410 | kspace = index(rec(k:ilenp), ' ') + k - 1 411 | kcomma = index(rec(k:ilenp), ',') + k - 1 412 | ! 413 | if (k==kspace) then 414 | !------- just skip this space 415 | k = k + 1 416 | goto 50 417 | endif 418 | ! 419 | if (k==kcomma) then 420 | !------- comma found.. increment number count and keep looking 421 | n = n + 1 422 | k = k + 1 423 | goto 50 424 | endif 425 | ! 426 | !------ neither space nor comma found, so we ran into a number... 427 | !- ...increment number counter and keep looking after next space or comma 428 | n = n + 1 429 | k = min(kspace, kcomma) + 1 430 | ! 431 | 50 if (k>=ilen) exit 432 | enddo 433 | ! 434 | !---- decide on how many numbers to read, and go ahead and read them 435 | if (ninp>0) n = min(n, ninp) 436 | read (rec(1:ilen), *, err = 100) (a(i), i = 1, n) 437 | error = .false. 438 | return 439 | ! 440 | !---- bzzzt !!! 441 | !cc write(*,*) 'getflt: String-to-integer conversion error.' 442 | 100 n = 0 443 | error = .true. 444 | end 445 | 446 | 447 | subroutine strip(string, ns) 448 | !*** Start of declarations inserted by SPAG 449 | integer K, K1, K2, N, NS 450 | !*** End of declarations inserted by SPAG 451 | character*(*) string 452 | !------------------------------------------- 453 | ! Strips leading blanks off string 454 | ! and returns length of non-blank part. 455 | !------------------------------------------- 456 | n = len(string) 457 | ! 458 | !---- find last non-blank character 459 | do k2 = n, 1, -1 460 | if (string(k2:k2)/=' ') goto 100 461 | enddo 462 | k2 = 0 463 | ! 464 | !---- find first non-blank character 465 | 100 do k1 = 1, k2 466 | if (string(k1:k1)/=' ') exit 467 | enddo 468 | ! 469 | !---- number of non-blank characters 470 | ns = k2 - k1 + 1 471 | if (ns==0) return 472 | ! 473 | !---- shift string so first character is non-blank 474 | string(1:ns) = string(k1:k2) 475 | ! 476 | !---- pad tail of string with blanks 477 | do k = ns + 1, n 478 | string(k:k) = ' ' 479 | enddo 480 | ! 481 | end 482 | end 483 | -------------------------------------------------------------------------------- /src/m_vortex.f90: -------------------------------------------------------------------------------- 1 | !*==M_VORTEX.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | 22 | module m_vortex 23 | implicit none 24 | contains 25 | subroutine vrtxco(imax, ii, nblds, lduct, rake, & 26 | xi, xv, gam, adw, vind_gam, vind_adw) 27 | use i_common, only : show_output, pi 28 | !*** Start of declarations inserted by SPAG 29 | real A, ADW, B, BLDS, DDTH1, DDTH2, DTBLD, DTH, DTH1, DTH2, DTH3, GAM, & 30 | & R0X, R0Y, R0Z, R1X, R1Y, R1Z, R1_ADW, R2X 31 | real R2Y, R2Z, R2_ADW, RAD1, RAD2, RAD3, RAKE, RV, TANRAK, THET, THET1, & 32 | & THET2, THET3, THETOFF, THETSPC, UVW, UVW_A, UVW_B, VADW, VIND_ADW 33 | real VIND_GAM, VSUM, XI, XI0, XITIP, XV, XXV 34 | integer I, II, IMAX, J, L, N, N1, N2, NBLDS, NTDIM, NTHET 35 | !*** End of declarations inserted by SPAG 36 | ! 37 | parameter (ntdim = 5000) 38 | dimension xi(imax), xv(imax), gam(imax) 39 | dimension vind_adw(3, imax), vind_gam(3, imax, imax) 40 | ! 41 | dimension a(3), b(3), uvw(3), uvw_a(3, 3), uvw_b(3, 3) 42 | dimension vsum(3), vadw(3) 43 | dimension thetspc(5000) 44 | ! 45 | logical lduct 46 | !----------------------------------------- 47 | ! Calculates "Vortex Momentum" 48 | ! Gamma-swirl influence coefficients 49 | ! 50 | ! Input: 51 | ! imax array dimension 52 | ! ii number of radial points on blade (circulation stations) 53 | ! nn number of Fourier harmonics 54 | ! nblds number of blades 55 | ! lduct t for duct outer bc 56 | ! rake blade rake angle from y axis in xy plane 57 | ! xi(i) r/r control point coordinate array 58 | ! xv(i) r/r vortex leg coordinate array 59 | ! gam(i) circulation array 60 | ! adw wake advance ratio v/wr 61 | ! 62 | ! Output: 63 | ! vt_gam(i,j) sensitivity of swirl velocity to circulation 64 | ! vt_adw(i) sensitivity of swirl velocity to wake advance ratio 65 | !----------------------------------------- 66 | blds = float(nblds) 67 | ! 68 | !pi = 4.0 * atan(1.0) 69 | ! 70 | xi0 = xv(1) 71 | xitip = xv(ii + 1) 72 | tanrak = tan(rake) 73 | 74 | do i = 1, ii 75 | do j = 1, ii 76 | vind_gam(1, i, j) = 0. 77 | vind_gam(2, i, j) = 0. 78 | vind_gam(3, i, j) = 0. 79 | enddo 80 | vind_adw(1, i) = 0. 81 | vind_adw(2, i) = 0. 82 | vind_adw(3, i) = 0. 83 | enddo 84 | ! 85 | !--- Set up variable theta spacing for near, intermediate and far field 86 | dth1 = pi / 60. 87 | rad1 = 2.0 88 | thet1 = rad1 / adw 89 | dth2 = pi / 20. 90 | rad2 = 4.0 91 | thet2 = rad2 / adw 92 | dth3 = pi / 8. 93 | rad3 = 50.0 94 | thet3 = rad3 / adw 95 | ! 96 | n1 = ifix(2.0 * (thet1) / (dth2 + dth1)) 97 | n2 = ifix(2.0 * (thet2 - thet1) / (dth3 + dth2)) 98 | ddth1 = (dth2 - dth1) / float(n1 - 1) 99 | ddth2 = (dth3 - dth2) / float(n2 - 1) 100 | ! 101 | thet = 0.0 102 | dth = dth1 103 | do i = 1, ntdim 104 | if (thet1) then 242 | vind_gam(1, i, j - 1) = vind_gam(1, i, j - 1) + vsum(1) 243 | vind_gam(2, i, j - 1) = vind_gam(2, i, j - 1) + vsum(2) 244 | vind_gam(3, i, j - 1) = vind_gam(3, i, j - 1) + vsum(3) 245 | vind_adw(1, i) = vind_adw(1, i) + vadw(1) * gam(j - 1) 246 | vind_adw(2, i) = vind_adw(2, i) + vadw(2) * gam(j - 1) 247 | vind_adw(3, i) = vind_adw(3, i) + vadw(3) * gam(j - 1) 248 | endif 249 | ! 250 | enddo ! j loop 251 | ! 252 | enddo ! i loop 253 | ! 254 | endif 255 | ! 256 | end 257 | ! vrtxco 258 | 259 | 260 | subroutine vorsegvel(a, b, uvw, uvw_a, uvw_b) 261 | !*** Start of declarations inserted by SPAG 262 | real A, ADB, AMAG, ASQ, AXB, AXB_A, AXB_B, B, BMAG, BSQ, DEN, DEN_ASQ, & 263 | & DEN_BSQ, PI4, T, T_ADB, T_ASQ, T_BSQ, UVW, UVW_A 264 | real UVW_B 265 | integer K, L 266 | !*** End of declarations inserted by SPAG 267 | !------------------------------------------------------------------- 268 | ! Calculates the velocity induced by a vortex segment 269 | ! of unit strength, with no core radius. 270 | ! 271 | ! The point where the velocity is calculated is at 0,0,0. 272 | ! 273 | ! Positive circulation is by righthand rule from a to b. 274 | ! 275 | ! Input: 276 | ! a(3) coordinates of vertex #1 of the vortex 277 | ! b(3) coordinates of vertex #2 of the vortex 278 | ! 279 | ! Output: 280 | ! uvw(3) induced velocity 281 | ! uvw_a(3,3) duvw/da sensitivity 282 | ! uvw_b(3,3) duvw/db sensitivity 283 | ! 284 | !------------------------------------------------------------------- 285 | dimension a(3), b(3), uvw(3), uvw_a(3, 3), uvw_b(3, 3) 286 | ! 287 | ! 288 | dimension axb(3), axb_a(3, 3), axb_b(3, 3) 289 | ! 290 | data pi4/12.56637062/ 291 | ! 292 | asq = a(1)**2 + a(2)**2 + a(3)**2 293 | bsq = b(1)**2 + b(2)**2 + b(3)**2 294 | ! 295 | amag = sqrt(asq) 296 | bmag = sqrt(bsq) 297 | ! 298 | do k = 1, 3 299 | uvw(k) = 0. 300 | do l = 1, 3 301 | uvw_a(k, l) = 0. 302 | uvw_b(k, l) = 0. 303 | enddo 304 | enddo 305 | ! 306 | !---- contribution from the vortex leg 307 | if (amag * bmag/=0.0) then 308 | axb(1) = a(2) * b(3) - a(3) * b(2) 309 | axb(2) = a(3) * b(1) - a(1) * b(3) 310 | axb(3) = a(1) * b(2) - a(2) * b(1) 311 | ! 312 | axb_a(1, 1) = 0.0 313 | axb_a(1, 2) = b(3) 314 | axb_a(1, 3) = -b(2) 315 | axb_a(2, 1) = -b(3) 316 | axb_a(2, 2) = 0.0 317 | axb_a(2, 3) = b(1) 318 | axb_a(3, 1) = b(2) 319 | axb_a(3, 2) = -b(1) 320 | axb_a(3, 3) = 0.0 321 | ! 322 | axb_b(1, 1) = 0.0 323 | axb_b(1, 2) = -a(3) 324 | axb_b(1, 3) = a(2) 325 | axb_b(2, 1) = a(3) 326 | axb_b(2, 2) = 0.0 327 | axb_b(2, 3) = -a(1) 328 | axb_b(3, 1) = -a(2) 329 | axb_b(3, 2) = a(1) 330 | axb_b(3, 3) = 0.0 331 | ! 332 | adb = a(1) * b(1) + a(2) * b(2) + a(3) * b(3) 333 | ! 334 | den = amag * bmag + adb 335 | den_asq = 0.5 * bmag / amag 336 | den_bsq = 0.5 * amag / bmag 337 | ! 338 | t = (1.0 / amag + 1.0 / bmag) / den 339 | ! 340 | t_adb = -t / den 341 | t_asq = -t / den * den_asq - 0.5 / (den * amag * asq) 342 | t_bsq = -t / den * den_bsq - 0.5 / (den * bmag * bsq) 343 | ! 344 | do k = 1, 3 345 | uvw(k) = axb(k) * t 346 | ! 347 | do l = 1, 3 348 | uvw_a(k, l) = axb(k) * t_asq * a(l) * 2.0& 349 | + axb(k) * t_adb * b(l)& 350 | + axb_a(k, l) * t 351 | uvw_b(k, l) = axb(k) * t_bsq * b(l) * 2.0& 352 | + axb(k) * t_adb * a(l)& 353 | + axb_b(k, l) * t 354 | enddo 355 | enddo 356 | endif 357 | ! 358 | do k = 1, 3 359 | uvw(k) = uvw(k) / pi4 360 | do l = 1, 3 361 | uvw_a(k, l) = uvw_a(k, l) / pi4 362 | uvw_b(k, l) = uvw_b(k, l) / pi4 363 | enddo 364 | enddo 365 | ! 366 | end 367 | ! vorvel 368 | 369 | end 370 | -------------------------------------------------------------------------------- /src/m_xaero.f90: -------------------------------------------------------------------------------- 1 | !*==M_XAERO.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | 22 | 23 | !--- Aero data stored for one or more radial aerodynamic sections 24 | ! 25 | !-- aero data quantities for each defined radial aerodynamic section 26 | ! naero Number of aerodynamic datasets defined (naero>=1) 27 | ! xiaero Radial station r/r where aero dataset is defined 28 | ! aerodata Aerodynamic definition of the blade section at xiaero 29 | ! aerodata( 1,x) = a0 (angle of zero lift) 30 | ! aerodata( 2,x) = clmax (Max cl) 31 | ! aerodata( 3,x) = clmin (Min cl) 32 | ! aerodata( 4,x) = dclda (Incompressible 2-d lift curve slope) 33 | ! aerodata( 5,x) = dclda_stall (2-d lift curve slope at stall) 34 | ! aerodata( 6,x) = dcl_stall (cl increment, onset to full stall) 35 | ! aerodata( 7,x) = cdmin (Minimum drag coefficient value) 36 | ! aerodata( 8,x) = cldmin (Lift at minimum drag value) 37 | ! aerodata( 9,x) = dcdcl2 (Parabolic drag param d(Cd)/dcl^2) 38 | ! aerodata(10,x) = cmcon (Incompressible 2-d pitching moment) 39 | ! aerodata(11,x) = reref (reference Reynold's number) 40 | ! aerodata(12,x) = rexp (Reynold's number exponent Cd~Re^rexp) 41 | ! aerodata(13,x) = mcrit (critical Mach #) 42 | ! aerodata(14,x) = toc (thickness/chord) 43 | 44 | 45 | module m_xaero 46 | implicit none 47 | contains 48 | subroutine setiaero(ctxt) 49 | !-------------------------------------------------- 50 | ! Sets up indices referring to aero section for 51 | ! each radial station 52 | !-------------------------------------------------- 53 | use i_common, only : Common 54 | implicit real(M) 55 | !*** Start of declarations inserted by SPAG 56 | integer I, N 57 | !*** End of declarations inserted by SPAG 58 | type (Common), intent(inout) :: ctxt 59 | ! 60 | !--- Find lower index of aero data sections xiaero(n) bounding xi(is) 61 | do i = 1, ctxt%ii 62 | ctxt%iaero(i) = 1 63 | do n = 1, ctxt%n_polars 64 | if (ctxt%xi_polars(n)<=ctxt%xi(i)) ctxt%iaero(i) = n 65 | enddo 66 | enddo 67 | end 68 | 69 | 70 | subroutine getpolar(ctxt, n, polar) 71 | use i_common, only : Common 72 | type(Common), intent(inout) :: ctxt 73 | integer, intent(in) :: n 74 | real, allocatable, intent(out) :: polar(:, :) ! ctxt%n_polar_points(n), 4) 75 | 76 | polar = ctxt%polardata(ctxt%i_polars(n):ctxt%i_polars(n+1)-1, :) 77 | end 78 | 79 | 80 | subroutine putpolars(ctxt, n_polars, n_polar_points, xi_polars, polardata) 81 | use i_common, only : Common 82 | type(Common), intent(inout) :: ctxt 83 | integer, intent(in) :: n_polars, n_polar_points(n_polars) 84 | real, intent(in) :: xi_polars(n_polars), polardata(sum(n_polar_points), 4) 85 | 86 | integer :: i, i_start, i_end 87 | integer, allocatable :: temp_indices(:), temp_n_polar_points(:) 88 | real, allocatable :: temp_xi_polars(:), temp_polardata(:, :) 89 | 90 | ctxt%n_polars = n_polars 91 | 92 | temp_indices = (/1, (1 + sum(n_polar_points(:i)), i=1, n_polars)/) 93 | call move_alloc(temp_indices, ctxt%i_polars) 94 | 95 | temp_n_polar_points = n_polar_points 96 | call move_alloc(temp_n_polar_points, ctxt%n_polar_points) 97 | 98 | temp_xi_polars = xi_polars 99 | call move_alloc(temp_xi_polars, ctxt%xi_polars) 100 | 101 | temp_polardata = polardata 102 | call move_alloc(temp_polardata, ctxt%polardata) 103 | end 104 | 105 | !************************************************************************* 106 | ! Interpolated aero section properties functions 107 | ! These routines implement a functional representation of the 108 | ! blade aero properties (cl,cd,cm) vs alfa 109 | !************************************************************************* 110 | 111 | 112 | subroutine getclcdcm(ctxt, is, alf, w, rey, & 113 | clift, cl_alf, cl_w, & 114 | clmax, clmin, dcl_stall, stallf, & 115 | cdrag, cd_alf, cd_w, cd_rey, & 116 | cmom, cm_al, cm_w) 117 | !------------------------------------------------------------- 118 | ! cl(alpha), 119 | ! cd(alpha), 120 | ! cm(alpha) interpolation function for blade at station is 121 | !------------------------------------------------------------- 122 | use i_common, only : Common, show_output 123 | implicit real(M) 124 | !*** Start of declarations inserted by SPAG 125 | real A0, ALF, CDMIN, CDRAG, CDRAG2, CD_ALF, CD_ALF2, CD_REY, CD_REY2, & 126 | & CD_W, CD_W2, CLDMIN, CLIFT, CLIFT2, CLMAX, CLMAX2, CLMIN, CLMIN2, & 127 | & CL_ALF, CL_ALF2 128 | real CL_W, CL_W2, CMCON, CMOM, CMOM2, CM_AL, CM_AL2, CM_W, CM_W2, DCDCL2, & 129 | & DCLDA, DCLDA_STALL, DCL_STALL, DCL_STALL2, FRAC, MCRIT, REREF, REXP, & 130 | & REY, W 131 | integer IS, N 132 | !*** End of declarations inserted by SPAG 133 | type (Common), intent(inout) :: ctxt 134 | logical stallf, stallf2 135 | real :: xi_polar1, xi_polar2 136 | 137 | real, allocatable :: polar(:, :) 138 | ! 139 | !--- Check for installed aero data section index 140 | n = ctxt%iaero(is) 141 | if (n<1.or.n>ctxt%n_polars) then 142 | ! 143 | if (ctxt%n_polars>1) then 144 | !--- Find lower index of aero data sections xiaero(n) bounding xi(is) 145 | do n = 1, ctxt%n_polars 146 | if (ctxt%xi_polars(n)>ctxt%xi(is)) goto 100 147 | !c write(*,*) 'getcl iaero= ',n,' is= ',is,xiaero(n),xi(is) 148 | ctxt%iaero(is) = n 149 | enddo 150 | if (show_output) write (*, *) & 151 | &'aero section not found for station '& 152 | &, ctxt%xi(is) 153 | endif 154 | ! 155 | n = 1 156 | ctxt%iaero(is) = n 157 | endif 158 | ! 159 | !--- Get section aero data from stored section array 160 | 100 call getpolar(ctxt, n, polar) 161 | clmax = maxval(polar(:, 2)) 162 | clmin = minval(polar(:, 2)) 163 | xi_polar1 = ctxt%xi_polars(n) 164 | !--- Get data for inner bounding aero section 165 | call clcdcm(ctxt, alf, w, rey, & 166 | clift, cl_alf, cl_w, stallf, & 167 | cdrag, cd_alf, cd_w, cd_rey, & 168 | cmom, cm_al, cm_w, & 169 | polar, ctxt%use_compr_corr) 170 | ! 171 | !--- Check for another bounding section, if not we are done, 172 | ! if we have another section linearly interpolate data to station is 173 | if (n1.0) then 177 | !c write(*,*) 'cl n,is,xi,frac = ',n,is,xi(is),frac 178 | endif 179 | ! 180 | call getpolar(ctxt, n + 1, polar) 181 | clmax2 = maxval(polar(:, 2)) 182 | clmin2 = minval(polar(:, 2)) 183 | !--- Get data for outer bounding aero section 184 | call clcdcm(ctxt, alf, w, rey, & 185 | clift2, cl_alf2, cl_w2, stallf2, & 186 | cdrag2, cd_alf2, cd_w2, cd_rey2, & 187 | cmom2, cm_al2, cm_w2, & 188 | polar, ctxt%use_compr_corr) 189 | !--- Interpolate aero data to blade station 190 | stallf = stallf .or. stallf2 191 | clift = (1.0 - frac) * clift + frac * clift2 192 | cl_alf = (1.0 - frac) * cl_alf + frac * cl_alf2 193 | cl_w = (1.0 - frac) * cl_w + frac * cl_w2 194 | clmax = (1.0 - frac) * clmax + frac * clmax2 195 | clmin = (1.0 - frac) * clmin + frac * clmin2 196 | dcl_stall = (1.0 - frac) * dcl_stall + frac * dcl_stall2 197 | ! 198 | cmom = (1.0 - frac) * cmom + frac * cmom2 199 | cm_al = (1.0 - frac) * cm_al + frac * cm_al2 200 | cm_w = (1.0 - frac) * cm_w + frac * cm_w2 201 | ! 202 | cdrag = (1.0 - frac) * cdrag + frac * cdrag2 203 | cd_alf = (1.0 - frac) * cd_alf + frac * cd_alf2 204 | cd_w = (1.0 - frac) * cd_w + frac * cd_w2 205 | cd_rey = (1.0 - frac) * cd_rey + frac * cd_rey2 206 | endif 207 | ! 208 | end 209 | 210 | 211 | subroutine getalf(ctxt, is, clift, w, alf, alf_cl, alf_w, stallf) 212 | !------------------------------------------------------------ 213 | ! Inverse alpha(cl) function 214 | ! Uses Newton-Raphson iteration to get alf from cl function 215 | !------------------------------------------------------------ 216 | use i_common, only : Common, show_output 217 | implicit real(M) 218 | !*** Start of declarations inserted by SPAG 219 | real A0, ALF, ALF_CL, ALF_W, CDRAG, CD_ALF, CD_REY, CD_W, CLIFT, CLMAX, & 220 | & CLMIN, CLTEMP, CL_ALF, CL_W, CMOM, CM_AL, CM_W, DALF, DCL_STALL, EPS 221 | integer IS, ITER, NITER 222 | real REY, W 223 | !*** End of declarations inserted by SPAG 224 | logical stallf 225 | data niter/10/ 226 | data eps/1.0E-5/ 227 | type (Common), intent(inout) :: ctxt 228 | ! 229 | stallf = .false. 230 | ! 231 | !---hhy had to set a0 to first aero section as a0 is now section property 232 | a0 = ctxt%aerodata(1, 1) 233 | rey = 0.0 234 | ! 235 | alf = a0 236 | do iter = 1, niter 237 | call getclcdcm(ctxt, is, alf, w, rey, & 238 | cltemp, cl_alf, cl_w, & 239 | clmax, clmin, dcl_stall, stallf, & 240 | cdrag, cd_alf, cd_w, cd_rey, & 241 | cmom, cm_al, cm_w) 242 | !c if(stallf) go to 20 243 | dalf = -(cltemp - clift) / cl_alf 244 | alf = alf + dalf 245 | alf_cl = 1.0 / cl_alf 246 | alf_w = -cl_w / cl_alf 247 | if (abs(dalf) pi) then 295 | alf = alf - 2.*pi 296 | end if 297 | 298 | a_max = maxval(polar(:, 1)) 299 | a_min = minval(polar(:, 1)) 300 | if (a_min < alf .and. alf < a_max) then 301 | ! Find the indices of the angles of attack in the polar just below and just above the specified one 302 | i_below = maxloc(polar(:, 1), 1, polar(:, 1) <= alf) 303 | i_above = minloc(polar(:, 1), 1, polar(:, 1) >= alf) 304 | 305 | if (i_below == i_above) then 306 | i_above = i_below + 1 307 | end if 308 | 309 | ! Compute delta values from alpha(i_below) to alpha(i_above) 310 | deltas = pack(polar(i_above, :), .true.) - pack(polar(i_below, :), .true.) 311 | 312 | ! Interpolation factor 313 | f = (alf - polar(i_below, 1)) / deltas(1) 314 | else 315 | ! Treat cases where request alf falls outside the known range 316 | ! This is done by assuming cl, cd, and cm are periodic in alpha with period 2*pi 317 | ! When this assumption is made, alphas outside of the known range can simply be interpolated using the 318 | ! edge values. 319 | i_below = size(polar, 1) 320 | deltas = pack(polar(1, :), .true.) - pack(polar(i_below, :), .true.) 321 | deltas(1) = deltas(1) + 2.*pi 322 | f = (alf - polar(i_below, 1)) / deltas(1) 323 | if (alf <= a_min) then 324 | f = f + 2.*pi/deltas(1) 325 | end if 326 | end if 327 | 328 | ! Compute interpolate data and local slopes 329 | new_data = pack(polar(i_below, 2:), .true.) + f * deltas(2:) 330 | deriv = deltas(2:) / deltas(1) 331 | 332 | ! Store results in proper places 333 | clift = new_data(1) 334 | cdrag = new_data(2) 335 | cmom = new_data(3) 336 | 337 | cl_alf = deriv(1) 338 | cd_alf = deriv(2) 339 | cm_al = deriv(3) 340 | 341 | cd_rey = 0. 342 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 343 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Optional Compressibility Corrections !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 344 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 345 | if (.not. present(use_corrections) .or. .not. use_corrections) then 346 | cl_w = 0. 347 | cd_w = 0. 348 | cm_w = 0. 349 | 350 | else 351 | if (.not. present(m_crit)) then 352 | mcrit = 0.6 353 | else 354 | mcrit = m_crit 355 | end if 356 | 357 | ! Compute cl at cdmin 358 | cdmin = minval(polar(:, 3)) 359 | i_above = maxloc(polar(:, 2), 1, polar(:, 3) == cdmin) 360 | cldmin = polar(i_below, 2) 361 | 362 | cdmfactor = 10.0 363 | clmfactor = 0.25 364 | mexp = 3.0 365 | cdmdd = 0.0020 366 | cdmstall = 0.1000 367 | ! 368 | !---- Prandtl-Glauert compressibility factor 369 | msq = w * w * ctxt%vel**2 / ctxt%vso**2 370 | msq_w = 2.0 * w * ctxt%vel**2 / ctxt%vso**2 371 | if(msq >= 1.0) then 372 | if (show_output) write(*, *) 'clfunc: Local Mach number limited to 0.99, was ', msq 373 | msq = 0.99 374 | msq_w = 0. 375 | endif 376 | pg = 1.0 / sqrt(1.0 - msq) 377 | pg_w = 0.5 * msq_w * pg**3 378 | ! 379 | !---- Mach number and dependence on velocity 380 | mach = sqrt(msq) 381 | mach_w = 0.0 382 | if (mach/=0.0) mach_w = 0.5 * msq_w / mach 383 | ! 384 | ! 385 | !------------------------------------------------------------ 386 | !--- Generate cl from dcl/dAlpha and Prandtl-Glauert scaling 387 | cla = clift * pg 388 | cla_alf = cl_alf * pg 389 | cla_w = clift * pg_w 390 | ! 391 | !--- Effective cLmax is limited by Mach effects 392 | ! reduces cLmax to match the cl of onset of serious compressible drag 393 | clmax = maxval(polar(:, 2)) 394 | clmin = minval(polar(:, 2)) 395 | dmstall = (cdmstall / cdmfactor)**(1.0 / mexp) 396 | clmaxm = max(0.0, (mcrit + dmstall - mach) / clmfactor) + cldmin 397 | clmax = min(clmax, clmaxm) 398 | clminm = min(0.0, -(mcrit + dmstall - mach) / clmfactor) + cldmin 399 | clmin = max(clmin, clminm) 400 | 401 | !------------------------------------------------------------ 402 | !--- cm from cmcon and Prandtl-Glauert scaling 403 | cmom = pg * cmom 404 | cm_al = 0.0 405 | cm_w = pg_w * cmom 406 | 407 | !--- Compressibility drag (accounts for drag rise above Mcrit with cl effects 408 | ! cdc is a function of a scaling factor*(m-Mcrit(cl))**mexp 409 | ! dmdd is the Mach difference corresponding to cd rise of cdmdd at mcrit 410 | dmdd = (cdmdd / cdmfactor)**(1.0 / mexp) 411 | critmach = mcrit - clmfactor * abs(clift - cldmin) - dmdd 412 | critmach_alf = -clmfactor * abs(cl_alf) 413 | critmach_w = -clmfactor * abs(cl_w) 414 | if (mach. 20 | !*********************************************************************** 21 | 22 | module m_xio 23 | implicit none 24 | contains 25 | subroutine save(ctxt, fname1) 26 | use m_userio, only : asks 27 | use m_xaero, only : getpolar 28 | use i_common, only : Common, show_output, pi 29 | implicit real(M) 30 | !*** Start of declarations inserted by SPAG 31 | real A0, A0DEG, BETA0DEG, CDMIN, CLDMIN, CLMAX, CLMIN, CMCON, DCDCL2, & 32 | & DCLDA, DCLDA_STALL, DCL_STALL, MCRIT, REREF, REXP, XISECT 33 | integer I, LU, N, j 34 | !*** End of declarations inserted by SPAG 35 | type (Common), intent(inout) :: ctxt 36 | character*(*) fname1 37 | !-------------------------------------------------------------------------- 38 | ! Save rotor and operating state in new xrotor_Version 6.9 format 39 | ! This format saves additional parameters including the aero data 40 | ! sections. 41 | !-------------------------------------------------------------------------- 42 | logical lvduct 43 | ! 44 | character*1 ans 45 | real, allocatable :: polar(:, :) 46 | character (47) :: settings 47 | ! 48 | ctxt%greek = .false. 49 | !c if(.not.conv) then 50 | !c write(*,1050) 51 | !c return 52 | !c endif 53 | ! 54 | lvduct = abs(ctxt%adw - ctxt%adv * ctxt%urduct)>=5.E-5 55 | lu = ctxt%lutemp 56 | ctxt%fname = fname1 57 | ! 58 | if (ctxt%fname(1:1)==' ') call asks('enter filename^', ctxt%fname) 59 | open (lu, file = ctxt%fname, status = 'old', err = 100) 60 | if (show_output) write (*, *) 61 | if (show_output) write (*, *) 'Output file exists. Overwrite? y' 62 | if (.not. ctxt%always_overwrite) then 63 | read (*, 99001) ans 64 | end if 65 | ! 66 | !................................................................... 67 | 99001 format (a) 68 | if (index('nn', ans)==0) goto 200 69 | ! 70 | close (lu) 71 | if (show_output) write (*, *) 'Current rotor not saved.' 72 | return 73 | ! 74 | 100 open (lu, file = ctxt%fname, status = 'new', err = 300) 75 | 200 rewind (lu) 76 | 77 | settings = ' "free": ' 78 | if (ctxt%free) then 79 | settings = trim(settings) // ' true ' 80 | else 81 | settings = trim(settings) // ' false' 82 | end if 83 | settings = trim(settings) // ', "duct": ' 84 | if (ctxt%duct) then 85 | settings = trim(settings) // ' true ' 86 | else 87 | settings = trim(settings) // ' false' 88 | end if 89 | settings = trim(settings) // ', "wind": ' 90 | if (ctxt%wind) then 91 | settings = trim(settings) // ' true ' 92 | else 93 | settings = trim(settings) // ' false' 94 | end if 95 | 96 | 99100 format (A) 97 | 99101 format (A, g12.4, A) 98 | 99102 format (A, i3, A) 99 | 99103 format (' ['3(f8.4, ', '), f8.4']', A) 100 | 99104 format (' "'f5.3'": [') 101 | 99105 format (' ['3(f8.4, ', '), f8.4']', A) 102 | write (lu, 99100) '{' 103 | write (lu, 99100) ' "conditions": {' 104 | write (lu, 99101) ' "rho": ', ctxt%rho, ',' 105 | write (lu, 99101) ' "vso": ', ctxt%vso, ',' 106 | write (lu, 99101) ' "rmu": ', ctxt%rmu, ',' 107 | write (lu, 99101) ' "alt": ', ctxt%alt, ',' 108 | write (lu, 99101) ' "vel": ', ctxt%vel, ',' 109 | write (lu, 99101) ' "adv": ', ctxt%adv 110 | write (lu, 99100) ' },' 111 | write (lu, 99100) ' "disk": {' 112 | write (lu, 99102) ' "n_blds": ', ctxt%nblds, ',' 113 | write (lu, 99100) ' "dimensions": {' 114 | write (lu, 99101) ' "r_hub" : ', ctxt%xi0 * ctxt%rad, ',' 115 | write (lu, 99101) ' "r_tip" : ', ctxt%rad, ',' 116 | write (lu, 99101) ' "r_wake": ', ctxt%xw0 * ctxt%rad, ',' 117 | write (lu, 99101) ' "rake" : ', ctxt%rake 118 | write (lu, 99100) ' },' 119 | write (lu, 99100) ' "geometry": [' 120 | do i = 1, ctxt%ii-1 121 | if (i < ctxt%ii-1) then 122 | write (lu, 99103) ctxt%xi(i), ctxt%ch(i), ctxt%beta(i) * 180. / pi, ctxt%ubody(i), ',' 123 | else 124 | write (lu, 99103) ctxt%xi(i), ctxt%ch(i), ctxt%beta(i) * 180. / pi, ctxt%ubody(i) 125 | end if 126 | end do 127 | write (lu, 99100) ' ],' 128 | write (lu, 99100) ' "polars": {' 129 | do i = 1, ctxt%n_polars 130 | write (lu, 99104) ctxt%xi_polars(i) 131 | call getpolar(ctxt, i, polar) 132 | do n = 1, ctxt%n_polar_points(i) 133 | if (n < ctxt%n_polar_points(i)) then 134 | write (lu, 99105) polar(n, 1) * 180. / pi, polar(n, 2:), ',' 135 | else 136 | write (lu, 99105) polar(n, 1) * 180. / pi, polar(n, 2:) 137 | end if 138 | end do 139 | 140 | if (i < ctxt%n_polars - 1) then 141 | write (lu, 99100) ' ],' 142 | else 143 | write (lu, 99100) ' ]' 144 | end if 145 | end do 146 | write (lu, 99100) ' }' 147 | write (lu, 99100) ' },' 148 | write (lu, 99100) ' "settings": {' 149 | write (lu, 99100) settings 150 | write (lu, 99100) ' },' 151 | write (lu, 99101) ' "u_r_duct": ', ctxt%urduct 152 | write (lu, 99100) '}' 153 | 154 | ! !--- Save added velocity components 155 | ! if (ctxt%nadd>1) then 156 | ! write (lu, 99015) ctxt%nadd 157 | ! 99015 format ('!Nadd'/1(1x, i5), /'! Radd Uadd Vadd') 158 | ! do i = 1, ctxt%nadd 159 | ! write (lu, 99017) ctxt%radd(i), ctxt%uadd(i), ctxt%vadd(i) 160 | ! enddo 161 | ! if (show_output) write (*, *) & 162 | ! &'External slipstream included in save file' 163 | ! endif 164 | 165 | ! 166 | close (lu) 167 | return 168 | ! 169 | 300 if (show_output) write (*, *) 'Bad filename.' 170 | if (show_output) write (*, *) 'Current rotor not saved.' 171 | return 172 | 99016 format (/' *** Converged operating solution does not exist ***') 173 | 99017 format (5(f16.4, 1x)) 174 | ! 175 | !x123456789012x123456789012x123456789012x123456789012x123456789012 176 | !! Rho Vso Rmu Alt') 177 | ! 178 | end 179 | ! sav 180 | 181 | subroutine load(ctxt, fname1) 182 | !------------------------------------------------------------------------ 183 | ! Reads in previously saved rotor in new xrotor_Version >= 6.9 format 184 | ! This format saves more information and can have optional comment 185 | ! lines beginning with a ! character. 186 | !------------------------------------------------------------------------ 187 | ! use m_xaero, only : putaero 188 | use m_userio, only : asks 189 | use i_common, only : Common, show_output, ix, pi 190 | use m_xutils, only : strip 191 | use m_xaero, only: putpolars 192 | implicit real(M) 193 | !*** Start of declarations inserted by SPAG 194 | real A0, A0DEG, BETADEG, CDMIN, CLDMIN, CLMAX, CLMIN, CMCON, DCDCL2, & 195 | & DCLDA, DCLDA_STALL, DCL_STALL, FILEVERS, MCRIT, REREF, REXP, XISECT 196 | integer I, IIX, LU, N 197 | !*** End of declarations inserted by SPAG 198 | type (Common), intent(inout) :: ctxt 199 | character*(*) fname1 200 | character*128 line 201 | character*4 dump 202 | real :: xi_tmp, point(4) 203 | integer :: n_geom, n_polars 204 | real, allocatable :: xi_polars(:), polardata(:, :), tmp_polardata(:, :) 205 | integer, allocatable :: n_polar_points(:), indices(:), tmp_indices(:) 206 | ctxt%greek = .false. 207 | ! 208 | lu = ctxt%lutemp 209 | ! 210 | ctxt%fname = fname1 211 | if (ctxt%fname(1:1)==' ') call asks('enter filename^', ctxt%fname) 212 | open (lu, file = ctxt%fname, status = 'old', err = 400) 213 | 214 | call rdline(lu, line) !{ 215 | call rdline(lu, line) ! "conditions": { 216 | call rdline(lu, line) 217 | read (line(11:23), *, err = 500) ctxt%rho 218 | call rdline(lu, line) 219 | read (line(11:23), *, err = 500) ctxt%vso 220 | call rdline(lu, line) 221 | read (line(11:23), *, err = 500) ctxt%rmu 222 | call rdline(lu, line) 223 | read (line(11:23), *, err = 500) ctxt%alt 224 | call rdline(lu, line) 225 | read (line(11:23), *, err = 500) ctxt%vel 226 | call rdline(lu, line) 227 | read (line(11:23), *, err = 500) ctxt%adv 228 | call rdline(lu, line) ! }, 229 | call rdline(lu, line) ! "disk": { 230 | call rdline(lu, line) 231 | read (line(14:17), *, err = 500) ctxt%nblds 232 | call rdline(lu, line) ! "dimensions": { 233 | call rdline(lu, line) 234 | read (line(16:28), *, err = 500) ctxt%xi0 235 | call rdline(lu, line) 236 | read (line(16:28), *, err = 500) ctxt%rad 237 | call rdline(lu, line) 238 | read (line(16:28), *, err = 500) ctxt%xw0 239 | call rdline(lu, line) 240 | read (line(16:28), *, err = 500) ctxt%rake 241 | ctxt%xi0 = ctxt%xi0 / ctxt%rad 242 | ctxt%xw0 = ctxt%xw0 / ctxt%rad 243 | call rdline(lu, line) ! }, 244 | call rdline(lu, line) ! "geometry": [ 245 | do i=1, ctxt%ii 246 | call rdline(lu, line) 247 | line = trim(adjustl(line)) 248 | 249 | if (line(1:1) == '[') then 250 | call strip(line, '[],') 251 | read (line, *, err = 100) ctxt%xi(i), ctxt%ch(i), ctxt%beta(i), ctxt%ubody(i) 252 | elseif (line(1:1) == ']') then 253 | goto 100 254 | else 255 | goto 500 256 | end if 257 | end do 258 | if (show_output) write (*, 99001) ctxt%fname(1:32) 259 | 99001 format (' File ', a, ' contains too many radial stations.'/' Loading not completed'/) 260 | return 261 | 262 | 100 ctxt%beta = ctxt%beta * pi / 180. 263 | ctxt%beta0 = ctxt%beta 264 | n_geom = i 265 | 266 | ctxt%n_polars = 0 267 | allocate(xi_polars(0)) 268 | allocate(polardata(0, 4)) 269 | allocate(n_polar_points(0)) 270 | 271 | call rdline(lu, line) ! "polars": { 272 | do i=1, 1000 273 | call rdline(lu, line) 274 | line = adjustl(line) 275 | if (line(1:1) == '"') then 276 | call strip(line, '"":[') 277 | read (line(1:5), *, err = 200) xi_tmp 278 | xi_polars = [xi_polars, xi_tmp] 279 | n_polar_points = [n_polar_points, 0] 280 | n_polars = n_polars + 1 281 | elseif (line(1:1) == '[') then 282 | call strip(line, '[],') 283 | read (line, *, err = 500) point 284 | n_polar_points(n_polars) = n_polar_points(n_polars) + 1 285 | allocate(tmp_polardata(sum(n_polar_points), 4)) 286 | tmp_polardata(1:sum(n_polar_points)-1, :) = polardata 287 | tmp_polardata(sum(n_polar_points), :) = point 288 | call move_alloc(tmp_polardata, polardata) 289 | elseif (line(1:1) == ']') then 290 | if (line(2:2) /= ',') then 291 | goto 200 292 | end if 293 | else 294 | goto 500 295 | end if 296 | end do 297 | 200 polardata(:, 1) = polardata(:, 1) * pi / 180. 298 | call putpolars(ctxt, n_polars, n_polar_points, xi_polars, polardata) 299 | 300 | call rdline(lu, line) ! } 301 | call rdline(lu, line) ! }, 302 | call rdline(lu, line) ! "settings": { 303 | call rdline(lu, line) 304 | if (line(13:16) == 'true') then 305 | ctxt%free = .true. 306 | elseif (line(13:16) == 'false') then 307 | ctxt%free = .false. 308 | else 309 | ctxt%free = .true. 310 | end if 311 | if (line(28:31) == 'true ') then 312 | ctxt%duct = .true. 313 | elseif (line(28:32) == 'false') then 314 | ctxt%duct = .false. 315 | else 316 | ctxt%duct = .false. 317 | end if 318 | if (line(43:46) == 'true') then 319 | ctxt%wind = .true. 320 | elseif (line(43:47) == 'false') then 321 | ctxt%wind = .false. 322 | else 323 | ctxt%wind = .false. 324 | end if 325 | 326 | ! !--- Optional duct velocity 327 | ! ctxt%urduct = 1.0 328 | ! call rdline(lu, line) 329 | ! if (line/='end'.and.line/='err') read (line, *, end = 100) ctxt%urduct 330 | ! ! 331 | ! !---- Optional slipstream velocities 332 | ! 100 ctxt%nadd = 0 333 | ! call rdline(lu, line) 334 | ! if (line/='end'.and.line/='err') then 335 | ! read (line, *, end = 300) ctxt%nadd 336 | ! if (ctxt%nadd>ix) then 337 | ! ctxt%nadd = ix 338 | ! if (show_output) write (*, *) & 339 | ! &'Warning, slipstream data terminated at '& 340 | ! &, ix 341 | ! endif 342 | ! do i = 1, ctxt%nadd 343 | ! call rdline(lu, line) 344 | ! if (line=='end'.or.line=='err') goto 200 345 | ! read (line, *, err = 200, end = 200) ctxt%radd(i), ctxt%uadd(i), & 346 | ! & ctxt%vadd(i) 347 | ! enddo 348 | ! if (i2) ctxt%nadd = i - 1 358 | ! ! 359 | ! 300 close (lu) 360 | ! if (ctxt%nadd>1) then 361 | ! if (show_output) write (*, *) 362 | ! if (show_output) write (*, *) & 363 | ! &'slipstream profiles read with #points '& 364 | ! &, ctxt%nadd 365 | ! endif 366 | ! ! 367 | ! ctxt%conv = .false. 368 | ! ! 369 | ! !--- Check for number of analysis stations to use 370 | ! if (iix/=ctxt%ii) then 371 | ! 350 if (show_output) write (*, 99002) iix, ctxt%ii, ctxt%ii 372 | ! 99002 format (/'Read # input stations = ', i3, /'Using # blade stations = ', & 373 | ! & i3, /'Enter # stations or for ', i3, ' ', $) 374 | ! read (*, 99003) line 375 | ! 99003 format (a) 376 | ! if (line/=' ') read (line, *, err = 350) ctxt%ii 377 | ! endif 378 | 379 | 300 close (lu) 380 | call initcase(ctxt, n_geom, .false.) 381 | ctxt%lrotor = .true. 382 | return 383 | 384 | 400 if (show_output) write (*, 99004) ctxt%fname(1:32) 385 | 99004 format (' File ', a, ' not found'/) 386 | return 387 | 388 | 500 if (show_output) write (*, 99005) ctxt%fname(1:32) 389 | 99005 format (' File ', a, ' has incompatible format'/' Loading not completed'/) 390 | close (lu) 391 | ctxt%conv = .false. 392 | return 393 | end 394 | 395 | subroutine initcase(ctxt, iix, losolve) 396 | use m_xrotor, only : output, setx 397 | use m_xoper, only : aper, xwinit 398 | use m_xaero, only : setiaero 399 | use i_common, only : Common, pi 400 | use m_spline, only : spline, seval 401 | implicit real(M) 402 | !*** Start of declarations inserted by SPAG 403 | integer I, IIX 404 | !*** End of declarations inserted by SPAG 405 | type (Common), intent(inout) :: ctxt 406 | logical losolve 407 | !---- spline blade geometry to "old" radial locations 408 | do i = 1, iix 409 | ctxt%w1(i) = ctxt%xi(i) 410 | ctxt%w2(i) = ctxt%ch(i) 411 | ctxt%w4(i) = ctxt%beta(i) 412 | ctxt%w6(i) = ctxt%ubody(i) 413 | enddo 414 | ctxt%w3(1:iix) = spline(ctxt%w1(1:iix), ctxt%w2(1:iix)) 415 | ctxt%w5(1:iix) = spline(ctxt%w1(1:iix), ctxt%w4(1:iix)) 416 | ctxt%w7(1:iix) = spline(ctxt%w1(1:iix), ctxt%w6(1:iix)) 417 | ! 418 | !---- set radial stations for built-in distribution scheme 419 | call setx(ctxt) 420 | call xwinit(ctxt) 421 | ! 422 | !---- interpolate read-in geometry to generated radial stations 423 | do i = 1, ctxt%ii 424 | ctxt%ch(i) = seval(ctxt%xi(i), ctxt%w2(1:iix), ctxt%w3(1:iix), ctxt%w1(1:iix)) 425 | ctxt%beta(i) = seval(ctxt%xi(i), ctxt%w4(1:iix), ctxt%w5(1:iix), ctxt%w1(1:iix)) 426 | ctxt%ubody(i) = seval(ctxt%xi(i), ctxt%w6(1:iix), ctxt%w7(1:iix), ctxt%w1(1:iix)) 427 | ctxt%beta0(i) = ctxt%beta(i) 428 | !c write(*,*) 'load trp i,ch,beta ',i,ch(i),beta(i) 429 | enddo 430 | ctxt%iinf = ctxt%ii + ctxt%ii / 2 431 | ! 432 | call setiaero(ctxt) 433 | !---- calculate current operating point 434 | if (losolve) then 435 | call aper(ctxt, 4, 2, .true.) 436 | if (ctxt%conv) call output(ctxt, ctxt%luwrit) 437 | endif 438 | ! 439 | !---- define design quantities for design of mil prop with same parameters 440 | ctxt%raddes = ctxt%rad 441 | ctxt%veldes = ctxt%vel 442 | ctxt%advdes = 0. 443 | ctxt%rpmdes = ctxt%vel / (ctxt%rad * ctxt%adv) * 30.0 / pi 444 | ctxt%r0des = ctxt%xi0 * ctxt%rad 445 | ctxt%rwdes = ctxt%xw0 * ctxt%rad 446 | ctxt%tddes = ctxt%ttot * ctxt%rho * ctxt%vel**2 * ctxt%rad**2 447 | ctxt%pddes = ctxt%ptot * ctxt%rho * ctxt%vel**3 * ctxt%rad**2 448 | ctxt%dest = .false. 449 | ctxt%desp = .true. 450 | do i = 1, ctxt%ii 451 | ctxt%cldes(i) = ctxt%cl(i) 452 | enddo 453 | ctxt%cldes0 = 0. 454 | end 455 | ! load 456 | 457 | 458 | 459 | subroutine rdline(lun, line) 460 | !*** Start of declarations inserted by SPAG 461 | integer LUN 462 | !*** End of declarations inserted by SPAG 463 | !...Purpose Read a non-comment line from the input file 464 | !...Input Data read from unit lun 465 | !...Output line Character string with input line 466 | ! line is set to 'end' for end or errors 467 | ! 468 | character*(*) line 469 | do 470 | read (lun, 99001, end = 100, err = 200) line 471 | ! 472 | 99001 format (a) 473 | ! 474 | !---- skip comment line 475 | if (index('!#', line(1:1))==0) then 476 | ! 477 | !---- skip blank line 478 | ! 479 | !---- normal return after significant line 480 | if (line/=' ') return 481 | endif 482 | enddo 483 | ! 484 | 100 line = 'end ' 485 | return 486 | ! 487 | 200 line = 'err ' 488 | end 489 | 490 | 491 | end 492 | -------------------------------------------------------------------------------- /src/m_xutils.f90: -------------------------------------------------------------------------------- 1 | !*==M_XUTILS.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | 22 | module m_xutils 23 | implicit none 24 | contains 25 | elemental subroutine strip(string,set) 26 | character(len=*), intent(inout) :: string 27 | character(len=*), intent(in) :: set 28 | integer :: old, new, stride 29 | old = 1; new = 1 30 | do 31 | stride = scan( string( old : ), set ) 32 | if ( stride > 0 ) then 33 | string( new : new+stride-2 ) = string( old : old+stride-2 ) 34 | old = old+stride 35 | new = new+stride-1 36 | else 37 | string( new : ) = string( old : ) 38 | return 39 | end if 40 | end do 41 | end subroutine strip 42 | 43 | subroutine gauss(nsiz, nn, z, r, nrhs) 44 | !*** Start of declarations inserted by SPAG 45 | integer K, L, N, NN, NP, NP1, NRHS, NSIZ, NX 46 | real PIVOT, R, TEMP, Z, ZTMP 47 | !*** End of declarations inserted by SPAG 48 | ! ******************************************************* 49 | ! * * 50 | ! * Solves general Nxn system in n unknowns * 51 | ! * with arbitrary number (nrhs) of righthand sides. * 52 | ! * Assumes system is invertible... * 53 | ! * ...if it isn't, a divide by zero will result. * 54 | ! * * 55 | ! * z is the coefficient matrix... * 56 | ! * ...destroyed during solution process. * 57 | ! * r is the righthand side(s)... * 58 | ! * ...replaced by the solution vector(s). * 59 | ! * * 60 | ! * Mark Drela 1984 * 61 | ! ******************************************************* 62 | ! 63 | dimension z(nsiz, nsiz), r(nsiz, nrhs) 64 | ! 65 | do np = 1, nn - 1 66 | np1 = np + 1 67 | ! 68 | !------ find max pivot index nx 69 | nx = np 70 | do n = np1, nn 71 | if (abs(z(n, np))>abs(z(nx, np))) nx = n 72 | enddo 73 | ! 74 | pivot = 1.0 / z(nx, np) 75 | ! 76 | !------ switch pivots 77 | z(nx, np) = z(np, np) 78 | ! 79 | !------ switch rows & normalize pivot row 80 | do l = np1, nn 81 | temp = z(nx, l) * pivot 82 | z(nx, l) = z(np, l) 83 | z(np, l) = temp 84 | enddo 85 | ! 86 | do l = 1, nrhs 87 | temp = r(nx, l) * pivot 88 | r(nx, l) = r(np, l) 89 | r(np, l) = temp 90 | enddo 91 | ! 92 | !------ forward eliminate everything 93 | do k = np1, nn 94 | ztmp = z(k, np) 95 | ! 96 | ! if(ztmp == 0.0) go to 15 97 | ! 98 | do l = np1, nn 99 | z(k, l) = z(k, l) - ztmp * z(np, l) 100 | enddo 101 | do l = 1, nrhs 102 | r(k, l) = r(k, l) - ztmp * r(np, l) 103 | enddo 104 | enddo 105 | ! 106 | enddo 107 | ! 108 | !---- solve for last row 109 | do l = 1, nrhs 110 | r(nn, l) = r(nn, l) / z(nn, nn) 111 | enddo 112 | ! 113 | !---- back substitute everything 114 | do np = nn - 1, 1, -1 115 | np1 = np + 1 116 | do l = 1, nrhs 117 | do k = np1, nn 118 | r(np, l) = r(np, l) - z(np, k) * r(k, l) 119 | enddo 120 | enddo 121 | enddo 122 | ! 123 | end 124 | ! gauss 125 | end 126 | -------------------------------------------------------------------------------- /src/p_test.f90: -------------------------------------------------------------------------------- 1 | program test_xrotor 2 | use, intrinsic :: iso_c_binding, only : c_int, c_bool, c_float 3 | use api, only : init, set_case, operate, show, get_number_of_stations, get_station_conditions, set_use_compr_corr, & 4 | save_prop, load_prop 5 | real :: rho, vso, rmu, alt, vel, adv, r_hub, r_tip, r_wake, rake 6 | integer, parameter :: n_geom = 6, n_polars = 1, n_polar_points(n_polars) = (/105/) 7 | real :: geomdata(4, n_geom), xi_polars(n_polars), polardata(sum(n_polar_points), 4) 8 | logical(c_bool) :: free, duct, wind, use_compr_corr 9 | real, allocatable :: xi(:), Re(:), M(:) 10 | real :: res 11 | integer :: n_blds 12 | 13 | rho = 1.225 14 | vso = 340. 15 | rmu = 1.789e-5 16 | alt = 1. 17 | vel = 27. 18 | adv = .15 19 | 20 | r_hub = .06 21 | r_tip = .83 22 | r_wake = 0. 23 | rake = 0. 24 | 25 | n_blds = 2 26 | 27 | geomdata = reshape((/& ! 28 | !r/R c/R beta ubody 29 | 0.15, 0.15, 50.0, 0.00, & 30 | 0.30, 0.16, 30.7, 0.00, & 31 | 0.45, 0.17, 21.6, 0.00, & 32 | 0.60, 0.16, 16.5, 0.00, & 33 | 0.75, 0.13, 13.4, 0.00, & 34 | 0.90, 0.09, 11.3, 0.00/), (/4, n_geom/)) 35 | 36 | xi_polars = 0. 37 | ! NACA 6412 at Re = 500,000 and M = 0.0 38 | polardata = reshape((/& 39 | ! apha [deg] 40 | -9.5000, -9.2500, -9.0000, -8.7500, -8.5000, -8.2500, -8.0000, & 41 | -7.7500, -7.5000, -7.2500, -7.0000, -6.7500, -6.5000, -6.0000, & 42 | -5.7500, -5.5000, -5.2500, -5.0000, -4.7500, -4.5000, -4.2500, & 43 | -4.0000, -3.7500, -3.5000, -3.2500, -3.0000, -2.7500, -2.5000, & 44 | -2.2500, -2.0000, -1.7500, -1.5000, -1.2500, -1.0000, -0.7500, & 45 | -0.5000, -0.2500, 0.0000, 0.2500, 0.5000, 0.7500, 1.0000, & 46 | 1.2500, 1.5000, 1.7500, 2.0000, 2.2500, 2.5000, 2.7500, & 47 | 3.0000, 3.2500, 3.5000, 3.7500, 4.0000, 4.2500, 4.5000, & 48 | 4.7500, 5.0000, 5.2500, 5.5000, 5.7500, 6.0000, 6.2500, & 49 | 6.5000, 6.7500, 7.0000, 7.2500, 7.5000, 7.7500, 8.0000, & 50 | 8.2500, 8.5000, 8.7500, 9.0000, 9.2500, 9.5000, 9.7500, & 51 | 10.0000, 10.2500, 10.5000, 10.7500, 11.0000, 11.2500, 11.5000, & 52 | 11.7500, 12.0000, 12.2500, 12.5000, 12.7500, 13.0000, 13.2500, & 53 | 13.5000, 13.7500, 14.0000, 14.2500, 14.5000, 14.7500, 15.0000, & 54 | 15.2500, 15.5000, 15.7500, 16.0000, 16.2500, 16.5000, 16.7500, & 55 | ! cl [-] 56 | -0.1344, -0.2938, -0.2807, -0.2627, -0.2407, -0.2191, -0.1937, & 57 | -0.1673, -0.1435, -0.1184, -0.0929, -0.0682, -0.0419, 0.0114, & 58 | 0.0372, 0.0643, 0.0908, 0.1179, 0.1441, 0.1714, 0.1976, & 59 | 0.2254, 0.2513, 0.2790, 0.3050, 0.3323, 0.3586, 0.3853, & 60 | 0.4119, 0.4382, 0.4645, 0.4907, 0.5169, 0.5429, 0.5688, & 61 | 0.5946, 0.6201, 0.6458, 0.6712, 0.6963, 0.7212, 0.7419, & 62 | 0.8011, 0.8268, 0.8523, 0.8782, 0.9038, 0.9297, 0.9556, & 63 | 0.9810, 1.0072, 1.0328, 1.0584, 1.0843, 1.1098, 1.1353, & 64 | 1.1609, 1.1861, 1.2116, 1.2367, 1.2611, 1.2856, 1.3093, & 65 | 1.3320, 1.3544, 1.3764, 1.3985, 1.4203, 1.4404, 1.4601, & 66 | 1.4785, 1.4960, 1.5139, 1.5288, 1.5427, 1.5530, 1.5615, & 67 | 1.5684, 1.5732, 1.5761, 1.5778, 1.5792, 1.5797, 1.5818, & 68 | 1.5841, 1.5861, 1.5874, 1.5904, 1.5928, 1.5928, 1.5952, & 69 | 1.5959, 1.5928, 1.5954, 1.5936, 1.5871, 1.5890, 1.5882, & 70 | 1.5833, 1.5733, 1.5732, 1.5708, 1.5677, 1.5633, 1.5568, & 71 | ! cd [-] 72 | 0.0894, 0.0289, 0.0245, 0.0224, 0.0201, 0.0192, 0.0186, & 73 | 0.0178, 0.0170, 0.0162, 0.0151, 0.0145, 0.0140, 0.0132, & 74 | 0.0125, 0.0122, 0.0119, 0.0114, 0.0112, 0.0109, 0.0106, & 75 | 0.0105, 0.0102, 0.0101, 0.0098, 0.0097, 0.0096, 0.0095, & 76 | 0.0094, 0.0093, 0.0092, 0.0091, 0.0091, 0.0091, 0.0091, & 77 | 0.0091, 0.0091, 0.0091, 0.0091, 0.0091, 0.0090, 0.0086, & 78 | 0.0080, 0.0081, 0.0083, 0.0085, 0.0086, 0.0088, 0.0089, & 79 | 0.0091, 0.0093, 0.0094, 0.0096, 0.0098, 0.0100, 0.0102, & 80 | 0.0103, 0.0105, 0.0107, 0.0109, 0.0111, 0.0113, 0.0115, & 81 | 0.0117, 0.0119, 0.0121, 0.0123, 0.0126, 0.0128, 0.0131, & 82 | 0.0134, 0.0138, 0.0142, 0.0147, 0.0153, 0.0161, 0.0171, & 83 | 0.0182, 0.0196, 0.0210, 0.0227, 0.0244, 0.0263, 0.0281, & 84 | 0.0300, 0.0320, 0.0341, 0.0362, 0.0383, 0.0408, 0.0432, & 85 | 0.0458, 0.0489, 0.0515, 0.0546, 0.0583, 0.0611, 0.0643, & 86 | 0.0681, 0.0726, 0.0758, 0.0794, 0.0831, 0.0870, 0.0912, & 87 | ! cm [-] 88 | -0.0883, -0.1484, -0.1502, -0.1500, -0.1503, -0.1496, -0.1494, & 89 | -0.1494, -0.1489, -0.1484, -0.1482, -0.1477, -0.1474, -0.1467, & 90 | -0.1464, -0.1461, -0.1457, -0.1455, -0.1450, -0.1448, -0.1444, & 91 | -0.1441, -0.1437, -0.1435, -0.1430, -0.1427, -0.1423, -0.1420, & 92 | -0.1416, -0.1411, -0.1407, -0.1403, -0.1398, -0.1393, -0.1389, & 93 | -0.1384, -0.1378, -0.1373, -0.1368, -0.1362, -0.1356, -0.1343, & 94 | -0.1414, -0.1409, -0.1404, -0.1399, -0.1395, -0.1391, -0.1387, & 95 | -0.1382, -0.1379, -0.1375, -0.1371, -0.1367, -0.1363, -0.1359, & 96 | -0.1355, -0.1351, -0.1347, -0.1342, -0.1337, -0.1331, -0.1324, & 97 | -0.1315, -0.1305, -0.1295, -0.1286, -0.1276, -0.1262, -0.1248, & 98 | -0.1232, -0.1215, -0.1199, -0.1178, -0.1157, -0.1131, -0.1104, & 99 | -0.1076, -0.1046, -0.1016, -0.0986, -0.0957, -0.0930, -0.0906, & 100 | -0.0884, -0.0864, -0.0845, -0.0829, -0.0814, -0.0800, -0.0788, & 101 | -0.0778, -0.0767, -0.0760, -0.0753, -0.0746, -0.0742, -0.0739, & 102 | -0.0737, -0.0735, -0.0735, -0.0735, -0.0736, -0.0739, -0.0743/), (/105, 4/)) 103 | 104 | free = .true. 105 | duct = .false. 106 | wind = .false. 107 | 108 | use_compr_corr = .false. 109 | 110 | call init() 111 | ! call set_case(& 112 | ! rho, vso, rmu, alt, vel, adv, & 113 | ! r_hub, r_tip, r_wake, rake, & 114 | ! n_blds, & 115 | ! n_geom, geomdata, & 116 | ! n_polars, n_polar_points, xi_polars, polardata, & 117 | ! free, duct, wind) 118 | ! call set_use_compr_corr(use_compr_corr) 119 | ! res = operate(4, 2000.) 120 | ! call show() 121 | ! 122 | ! call save_prop() 123 | 124 | call init() 125 | call load_prop('output.json') 126 | call set_use_compr_corr(use_compr_corr) 127 | res = operate(4, 2000.) 128 | call show() 129 | 130 | n_stations = get_number_of_stations() 131 | allocate(xi(n_stations)) 132 | allocate(Re(n_stations)) 133 | allocate(M(n_stations)) 134 | call get_station_conditions(n_stations, xi, Re, M) 135 | end program test_xrotor -------------------------------------------------------------------------------- /src/p_xbend.f90: -------------------------------------------------------------------------------- 1 | !*==P_XBEND.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | module p_xbend 3 | implicit none 4 | contains 5 | subroutine bend(ctxt) 6 | use m_xbend, only : mclr, stset, eiload, stadd, stcalc, stclr, stwrit 7 | use m_xrotor, only : opfile 8 | use m_userio, only : getflt, askc, getint 9 | use i_common, only : Common, show_output 10 | implicit real(M) 11 | !*** Start of declarations inserted by SPAG 12 | integer I, IINPUT, NINPUT 13 | real RINPUT 14 | !*** End of declarations inserted by SPAG 15 | type (Common), intent(inout) :: ctxt 16 | character*4 comand 17 | character*132 comarg 18 | ! 19 | dimension iinput(20) 20 | dimension rinput(20) 21 | logical error 22 | ! 23 | !--------------------------------------------- 24 | ! Run rotor at arbitrary operating points 25 | !--------------------------------------------- 26 | ! 27 | ctxt%greek = .false. 28 | ! 29 | if (ctxt%lstruc) then 30 | if (show_output) write (*, *) 31 | if (show_output) write (*, *) 'Structural properties available' 32 | else 33 | if (show_output) write (*, *) 34 | if (show_output) write (*, *) 'Structural properties not available' 35 | endif 36 | do 37 | ! 38 | call askc('.bend^', comand, comarg) 39 | ! 40 | do i = 1, 20 41 | iinput(i) = 0 42 | rinput(i) = 0.0 43 | enddo 44 | ninput = 0 45 | call getint(comarg, iinput, ninput, error) 46 | ninput = 0 47 | call getflt(comarg, rinput, ninput, error) 48 | ! 49 | if (comand==' ') return 50 | if (comand=='? '.and.show_output) write (*, 99001) 51 | 99001 format (/' read f Read in blade structural properties'/ & 52 | &' eval Evaluate structural loads and deflections'/ & 53 | &' clr Clear all structural deflections'// & 54 | &' defl Set new twist = static + structural twist'/ & 55 | &' rest Set new twist = static twist'/ & 56 | &' sets Set static twist = current - structural twist'// & 57 | &' writ f Write structural solution to disk file'// & 58 | &' help Display help on structural calculation') 59 | if (comand/='? ') then 60 | if (comand=='read') then 61 | ! 62 | !------------------------------------------------------------- 63 | call eiload(ctxt, comarg) 64 | elseif (comand=='clr ') then 65 | ! 66 | !------------------------------------------------------------- 67 | call stclr(ctxt) 68 | elseif (comand=='eval') then 69 | ! 70 | !------------------------------------------------------------- 71 | if (.not.ctxt%lstruc) then 72 | if (show_output) write (*, *) & 73 | &'Structural properties not available' 74 | if (show_output) write (*, *) & 75 | &'Assuming zero mass, infinite stiffness...' 76 | endif 77 | !cc call stload(ctxt) 78 | call stcalc(ctxt) 79 | call stwrit(ctxt, ctxt%luwrit) 80 | elseif (comand=='defl') then 81 | ! 82 | !------------------------------------------------------------- 83 | call stadd(ctxt) 84 | elseif (comand=='rest') then 85 | ! 86 | !------------------------------------------------------------- 87 | do i = 1, ctxt%ii 88 | ctxt%beta(i) = ctxt%beta0(i) 89 | enddo 90 | ctxt%conv = .false. 91 | elseif (comand=='writ') then 92 | ! 93 | !------------------------------------------------------------- 94 | if (comarg(1:1)/=' ') ctxt%savfil = comarg 95 | call opfile(ctxt%lusave, ctxt%savfil) 96 | call stwrit(ctxt, ctxt%lusave) 97 | close (ctxt%lusave) 98 | elseif (comand=='sets') then 99 | ! 100 | !------------------------------------------------------------- 101 | call stset(ctxt) 102 | elseif (comand=='mclr') then 103 | ! 104 | !------------------------------------------------------------- 105 | call mclr(ctxt) 106 | elseif (comand=='help') then 107 | ! 108 | !------------------------------------------------------------- 109 | if (show_output) write (*, 99002) 110 | ! 111 | ! 112 | 99002 format (/'The axis definitions are:'// & 113 | &' x aft along prop rotation axis'/ & 114 | &' y radial alng blade'/ & 115 | &' z perpendicular to blade: x x y = z'// & 116 | &'The structural solution contains two groups of data, ''the first group ha& 117 | &s:'//' u/r deflections in the x direction'/ & 118 | &' w/r deflections in the z direction'/ & 119 | &' t torsional twist (positive in the increasing ''incidence directio& 120 | &n)'/' Mz bending moment about the z axis'/ & 121 | &' Mx bending moment about the x axis'/ & 122 | &' t moment about the radial y axis (i.e. torsion)'/ & 123 | &' p tensile load (shear in y direction)'/ & 124 | &' Sx shear in x direction'/' Sz shear in z direction'// & 125 | &'The second group of structural data contains:'// & 126 | &' Ex strain due to bending in the x direction'/ & 127 | &' Ey strain due to extension in the y direction'/ & 128 | &' Ez strain due to bending in the z direction'/ & 129 | &' Emax maximum strain calculated by ''Emax = sqrt(Ex^2 + Ez^2) + Ey'/ & 130 | &' g shear strain due to twist t'// & 131 | &' Note that Ex, Ez, and g are evaluated at the ''local radius rst from'/& 132 | &' the structural axis (rst is an input quantity, ''normally set to the '& 133 | & / & 134 | &' distance of the highest or lowest profile point ''from the structural & 135 | &axis)'/) 136 | else 137 | ! 138 | !------------------------------------------------------------- 139 | if (show_output) write (*, 99003) comand 140 | ! 141 | !....................................................................... 142 | ! 143 | 99003 format (1x, a4, ' command not recognized. Type a "?" for list'& 144 | &) 145 | endif 146 | endif 147 | enddo 148 | ! 149 | end 150 | ! bend 151 | end 152 | -------------------------------------------------------------------------------- /src/p_xnoise.f90: -------------------------------------------------------------------------------- 1 | !*==P_XNOISE.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | module p_xnoise 3 | implicit none 4 | contains 5 | subroutine noise(ctxt) 6 | !--------------------------------------- 7 | ! Calculates the sound pressure 8 | ! time history of the propeller 9 | ! at specified observer positions. 10 | !--------------------------------------- 11 | use m_xnoise, only : dbfoot, sft, ptrace 12 | use m_userio, only : askr, readi, getflt, asks, readr, getint, askc 13 | use i_common, only : Common, ix, show_output 14 | use m_spline, only : spline, seval 15 | implicit real(M) 16 | !*** Start of declarations inserted by SPAG 17 | real ADB, AOC, AOC0, AOCI, AOCX, DCLIMB, DECIB, DELT, DHARM, FAMPL, GALT, & 18 | & PAVG, PCOMP, PHASE, PRES, PRMS, RINPUT, THX 19 | real THX1, THX2, THY, THY1, THY2, TIME, UNITL, XA, XDB, XLIM, XOBS, & 20 | & XYZOBS, YDB, YLIM, YOBS, ZOBS 21 | integer I, IA, IH, IINPUT, IT, J, K, LU, NA, NDBSIZ, NHARM, NHARX, & 22 | & NINPUT, NT, NT1, NTX, NXDIM, NYDIM 23 | !*** End of declarations inserted by SPAG 24 | ! 25 | parameter (ntx = 160) 26 | parameter (nharx = ntx / 2) 27 | dimension aoci(ix), aoc(ix), aocx(ix), xa(ix) 28 | dimension pcomp(0:ntx, 3), pres(0:ntx), time(0:ntx) 29 | dimension decib(0:nharx), fampl(nharx), phase(nharx) 30 | ! 31 | parameter (nxdim = 81, nydim = 81) 32 | type (Common), intent(inout) :: ctxt 33 | dimension adb(nxdim, nydim) 34 | dimension xdb(nxdim, nydim) 35 | dimension ydb(nxdim, nydim) 36 | dimension xlim(2), ylim(2) 37 | dimension ndbsiz(2) 38 | ! 39 | character*80 prompt 40 | character*4 comand 41 | character*132 comarg 42 | ! 43 | dimension iinput(20) 44 | dimension rinput(20) 45 | logical error, ldbcon, lptrac 46 | ! 47 | dimension xyzobs(3) 48 | character*2 ulnam 49 | 50 | integer nt_temp(1) 51 | ! 52 | save nt 53 | save aoc0, xyzobs 54 | save ulnam, unitl 55 | ! 56 | !---- number of rotation steps for one full rotation 57 | data nt/80/ 58 | ! 59 | !---- default airfoil area/c^2 , observer location 60 | data aoc0, xyzobs/ - 1.0, 0.0, 0.0, -100.0/ 61 | ! 62 | !---- start by using foot input 63 | !cc data ulnam, unitl / 'm ', 1.0 / 64 | data ulnam, unitl/'ft', 3.28084/ 65 | ! 66 | ! 67 | ctxt%greek = .false. 68 | ! 69 | ldbcon = .false. 70 | lptrac = .false. 71 | ! 72 | !---- i,j size of grid for db footprint contour plot 73 | ndbsiz(1) = 21 74 | ndbsiz(2) = 11 75 | ! 76 | !---- number of blade-passing harmonics to be calculated, and annotation delta 77 | nharm = nt / 2 78 | dharm = 5.0 79 | ! 80 | if (aoc0/=0.0) then 81 | if (aoc0<0.0) aoc0 = 0.0 82 | do i = 1, ctxt%ii 83 | aoci(i) = aoc0 84 | enddo 85 | endif 86 | ! 87 | if (ulnam=='(m) ') then 88 | if (show_output) write (*, *) & 89 | &'Coordinates currently specified in meters' 90 | else 91 | if (show_output) write (*, *) & 92 | &'Coordinates currently specified in feet' 93 | endif 94 | ! 95 | ! 96 | if (show_output) write (*, 99008) 97 | 100 do 98 | ! 99 | call askc('.nois^', comand, comarg) 100 | ! 101 | do i = 1, 20 102 | iinput(i) = 0 103 | rinput(i) = 0.0 104 | enddo 105 | ninput = 0 106 | call getint(comarg, iinput, ninput, error) 107 | ninput = 0 108 | call getflt(comarg, rinput, ninput, error) 109 | ! 110 | if (comand==' ') return 111 | if (comand=='? '.and.show_output) write (*, 99008) 112 | if (comand/='? ') then 113 | if (comand=='p ') then 114 | ! 115 | !=========================================================================== 116 | if (ninput>=3) then 117 | xyzobs(1) = rinput(1) 118 | xyzobs(2) = rinput(2) 119 | xyzobs(3) = rinput(3) 120 | else 121 | if (show_output) write (*, 99001) 122 | 99001 format (/' Cartesian system fixed to airplane.'/ & 123 | &' (x forward, y left, z up): '/' '/ & 124 | &' z '/ & 125 | &' '/ & 126 | &' . '/ & 127 | &' x '/ & 128 | &' . . '/ & 129 | &' . '/ & 130 | &' . . '/ & 131 | &' . '/ & 132 | &' y . . . _______\\________ '/ & 133 | &' \\ '/ & 134 | &' __\\__ '/ & 135 | &' ') 136 | do 137 | ! 138 | !cc 123456789012345678901234567890123 4567 890 139 | prompt = 'Enter observer x,y,z coordinates (' // & 140 | & ulnam // '): ' 141 | if (show_output) write (*, 99002) prompt(1:40), & 142 | & (xyzobs(k), k = 1, 3) 143 | 99002 format (1x, a, 3F12.2) 144 | call readr(3, xyzobs, error) 145 | if (.not.(error)) exit 146 | enddo 147 | endif 148 | goto 500 149 | else 150 | if (comand=='foot') then 151 | !cc go to 900 152 | ! 153 | !====================================================================== 154 | if (ninput>=1) then 155 | galt = rinput(1) 156 | else 157 | prompt = 'Enter flight altitude above ground (' // & 158 | & ulnam // ')^' 159 | call askr(prompt, galt) 160 | endif 161 | if (ninput>=2) then 162 | dclimb = rinput(2) 163 | else 164 | call askr('Enter climb angle (deg)^', dclimb) 165 | endif 166 | ! 167 | !---- set default ground-grid limits 168 | xlim(1) = -2.0 * galt 169 | xlim(2) = 2.0 * galt 170 | ylim(1) = -1.0 * galt 171 | ylim(2) = 1.0 * galt 172 | ! 173 | if (show_output) write (*, *) 174 | do 175 | !cc 1234567890123456789012345 6789 012 176 | prompt = 'Enter footprint x limits (' // ulnam // '): ' 177 | if (show_output) write (*, 99009) prompt(1:32), & 178 | & xlim(1), xlim(2) 179 | call readr(2, xlim, error) 180 | if (.not.(error)) then 181 | do 182 | ! 183 | prompt = 'Enter footprint y limits (' // & 184 | & ulnam // '): ' 185 | if (show_output) write (*, 99009) & 186 | & prompt(1:32), ylim(1), ylim(2) 187 | call readr(2, ylim, error) 188 | if (.not.(error)) then 189 | do 190 | ! 191 | if (show_output) write (*, 99003) & 192 | & 'Enter footprint grid size: ', & 193 | & ndbsiz(1), ndbsiz(2) 194 | 99003 format (1x, a, 2I6) 195 | call readi(2, ndbsiz, error) 196 | if (.not.(error)) then 197 | if (ndbsiz(1)>nxdim.or.ndbsiz(2)& 198 | & >nydim) then 199 | if (show_output) & 200 | & write (*, *) & 201 | &'Array dimension limits are:'& 202 | &, nxdim, nydim 203 | ndbsiz(1) & 204 | & = min(ndbsiz(1), nxdim) 205 | ndbsiz(2) & 206 | & = min(ndbsiz(2), nydim) 207 | cycle 208 | endif 209 | ! 210 | ! 211 | thx1 = atan2(xlim(1), galt) 212 | thx2 = atan2(xlim(2), galt) 213 | thy1 = atan2(ylim(1), galt) 214 | thy2 = atan2(ylim(2), galt) 215 | do i = 1, ndbsiz(1) 216 | do j = 1, ndbsiz(2) 217 | thx = thx1 + (thx2 - thx1) & 218 | & * float(i - 1) & 219 | & / float(ndbsiz(1) - 1) 220 | thy = thy1 + (thy2 - thy1) & 221 | & * float(j - 1) & 222 | & / float(ndbsiz(2) - 1) 223 | xdb(i, j) = galt * tan(thx) 224 | ydb(i, j) = galt * tan(thy) 225 | enddo 226 | enddo 227 | ! 228 | if (show_output) write (*, *) 229 | if (show_output) write (*, *) & 230 | &'Calculating db footprint...' 231 | nt1 = nt 232 | call dbfoot(ctxt%nblds, ctxt%ii, & 233 | & ctxt%xi(1), ctxt%dxi, aoci, & 234 | & ctxt%ch, ctxt%gam, ctxt%adv, & 235 | & ctxt%rad, ctxt%vel, ctxt%vso, & 236 | & ctxt%rho, galt, dclimb, unitl, & 237 | & nt1, nxdim, nydim, ndbsiz(1), & 238 | & ndbsiz(2), xdb, ydb, adb) 239 | ldbcon = .true. 240 | goto 110 241 | endif 242 | enddo 243 | endif 244 | enddo 245 | endif 246 | enddo 247 | elseif (comand/='ntim') then 248 | if (comand=='unit') then 249 | ! 250 | !====================================================================== 251 | if (ulnam=='ft') then 252 | ulnam = 'm ' 253 | unitl = 1.0 254 | if (show_output) write (*, *) & 255 | &'Coordinates now specified in meters' 256 | else 257 | ulnam = 'ft' 258 | unitl = 3.28084 259 | if (show_output) write (*, *) & 260 | &'Coordinates now specified in feet' 261 | endif 262 | cycle 263 | elseif (comand=='aoc ') then 264 | ! 265 | !=========================================================================== 266 | ! 2 267 | !---- set local blade airfoil area / c 268 | ! (this version assumes that it's constant) 269 | if (ninput>=1) then 270 | aoc0 = rinput(1) 271 | else 272 | call askr(& 273 | &'Enter blade airfoil (cross-sectional area)/chord**2^'& 274 | &, aoc0) 275 | endif 276 | ! 277 | do i = 1, ctxt%ii 278 | aoci(i) = aoc0 279 | enddo 280 | ! 281 | !---- recalculate pressure signature if observer position has been chosen 282 | if (.not.(lptrac)) cycle 283 | goto 500 284 | elseif (comand=='afil') then 285 | ! 286 | !=========================================================================== 287 | !---- this version reads in an area distribution list with increasing r/r: 288 | ! a/c^2 r/r 289 | ! a/c^2 r/r 290 | ! a/c^2 r/r 291 | ! . . 292 | ! . . 293 | ! 294 | ! These are splined to the computational radial stations. 295 | ! 296 | ctxt%fname = comarg 297 | if (ctxt%fname(1:1)==' ') call asks(& 298 | &'enter blade airfoil area/c**2 distribution filename^'& 299 | &, ctxt%fname) 300 | ! 301 | lu = ctxt%lutemp 302 | open (lu, file = ctxt%fname, status = 'old', err = 400) 303 | do ia = 1, ix 304 | read (lu, *, end = 200, err = 300) xa(ia), aoc(ia) 305 | enddo 306 | if (show_output) write (*, *) & 307 | &'Array size limited. Not all points read in.' 308 | ia = ix + 1 309 | exit 310 | else 311 | 312 | if (show_output) write (*, 99004) comand 313 | 99004 format (1x, a4, ' command not recognized.'// & 314 | &' Type "?" for list, to exit menu.'& 315 | &) 316 | cycle 317 | endif 318 | endif 319 | ! 320 | !=========================================================================== 321 | 110 if (ninput>=1) then 322 | nt = iinput(1) 323 | else 324 | do 325 | if (show_output) write (*, 99005) nt 326 | 99005 format (/1x, & 327 | &' Enter number of p(t) samples/revolution:', & 328 | & i7) 329 | call readi(1, nt_temp, error) 330 | nt = nt_temp(1) 331 | if (.not.(error)) exit 332 | enddo 333 | endif 334 | ! 335 | if (nt>ntx) then 336 | nt = ntx 337 | if (show_output) write (*, *) & 338 | &'Number of samples limited to array limit:'& 339 | &, ntx 340 | endif 341 | ! 342 | nharm = nt / 2 343 | endif 344 | endif 345 | enddo 346 | 200 na = ia - 1 347 | close (lu) 348 | ! 349 | aocx(1:na) = spline(xa(1:na), aoc(1:na)) 350 | do i = 1, ctxt%ii 351 | ! todo: test this 352 | aoci(i) = seval(ctxt%xi(i), aoc, aocx, xa) 353 | enddo 354 | aoc0 = 0.0 355 | ! 356 | !---- recalculate pressure signature if observer position has been chosen 357 | if (.not.(lptrac)) goto 100 358 | goto 500 359 | ! 360 | 300 if (show_output) write (*, *) 'File read error' 361 | close (lu) 362 | goto 100 363 | ! 364 | 400 if (show_output) write (*, *) 'File open error' 365 | goto 100 366 | ! 367 | !=========================================================================== 368 | !=========================================================================== 369 | !---- p(t) signature calculation 370 | 500 xobs = xyzobs(1) / unitl 371 | yobs = xyzobs(2) / unitl 372 | zobs = xyzobs(3) / unitl 373 | call ptrace(xobs, yobs, zobs, ctxt%nblds, ctxt%ii, ctxt%xi(1), ctxt%dxi, aoci, & 374 | & ctxt%ch, ctxt%gam, ctxt%adv, ctxt%rad, ctxt%vel, ctxt%vso, & 375 | & ctxt%rho, ntx, nt, pcomp, time) 376 | ! 377 | !---- set total p(t) signal 378 | do it = 0, nt 379 | pres(it) = pcomp(it, 1) + pcomp(it, 2) + pcomp(it, 3) 380 | enddo 381 | lptrac = .true. 382 | ! 383 | !---- integrate p(t) for rms pressure 384 | prms = 0. 385 | do it = 1, nt 386 | delt = time(it) - time(it - 1) 387 | pavg = (pres(it) + pres(it - 1)) * 0.5 388 | prms = prms + pavg**2 * delt 389 | enddo 390 | prms = sqrt(prms / (time(nt) - time(0))) 391 | ! 392 | !---- get amplitude of each blade-passing harmonic component 393 | call sft(pres, time, nt, fampl, phase, nharm) 394 | ! 395 | !---- set Decibels relative to 20 microPa for each harmonic component 396 | do ih = 1, nharm 397 | decib(ih) = 20.0 * alog10(sqrt(0.5) * fampl(ih) / 20.0E-6) 398 | enddo 399 | ! 400 | !---- set total db level from r.m.s. pressure 401 | decib(0) = 20.0 * alog10(prms / 20.0E-6) 402 | ! 403 | !---- print out decibel spectrum 404 | if (show_output) write (*, 99006) 0, decib(0) 405 | 99006 format (/' Sound level for each multiple of blade-passing frequency'// & 406 | &' n db', /1x, i6, f9.2, ' (total)') 407 | if (show_output) write (*, 99007) (k, decib(k), k = 1, nharm) 408 | 99007 format (1x, i6, f9.2) 409 | 99008 format (/' p rrr Calculate acoustic p(t) at observer x,y,z'/ & 410 | &' foot rr Calculate db ground noise footprint'/ & 411 | &' ntim i Change number of time samples'/ & 412 | &' unit Toggle coordinate unit m,ft'// & 413 | &' aoc r Set constant blade cross-sectional area/c**2'/ & 414 | &' afil f Set blade cross-sectional area/c**2 from file') 415 | 99009 format (1x, a, 2F10.0) 416 | !cc 12 95.02 417 | ! 418 | !cc go to 900 419 | !..................................................................... 420 | end 421 | end 422 | -------------------------------------------------------------------------------- /src/p_xrotor.f90: -------------------------------------------------------------------------------- 1 | !*==P_XROTOR.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | !*********************************************************************** 3 | ! Copyright (c) 2018 D. de Vries 4 | ! Original Copyright (c) 2011 Mark Drela 5 | ! 6 | ! This file is part of XRotor. 7 | ! 8 | ! XRotor is free software: you can redistribute it and/or modify 9 | ! it under the terms of the GNU General Public License as published by 10 | ! the Free Software Foundation, either version 3 of the License, or 11 | ! (at your option) any later version. 12 | ! 13 | ! XRotor is distributed in the hope that it will be useful, 14 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ! GNU General Public License for more details. 17 | ! 18 | ! You should have received a copy of the GNU General Public License 19 | ! along with XRotor. If not, see . 20 | !*********************************************************************** 21 | module p_xrotor 22 | implicit none 23 | contains 24 | subroutine rotor() ! 25 | 26 | !--- module statement for Windoze dvFortran 27 | !cc use dflib 28 | ! 29 | use m_xio, only : save, load 30 | use p_xnoise, only : noise 31 | use m_xoper, only : shocas, getcas 32 | use m_xrotor, only : output, init_ 33 | use p_xoper, only : oper 34 | use p_xbend, only : bend 35 | use m_userio, only : getflt, askc, getint 36 | use i_common, only : Common, show_output, nparx 37 | implicit real(M) 38 | !*** Start of declarations inserted by SPAG 39 | integer I, IARGC, IINPUT, KF, NARG, NINPUT 40 | real RINPUT 41 | !*** End of declarations inserted by SPAG 42 | type (Common) :: ctxt 43 | character*7 comand 44 | character*128 comarg 45 | ! 46 | dimension iinput(20) 47 | dimension rinput(20) 48 | logical error 49 | ! 50 | !==================================================== 51 | ! 52 | ! Interactive Design and Analysis Program 53 | ! for Free-tip and Ducted Rotors 54 | ! 55 | ! October 1992 56 | ! Copyright Mark Drela 57 | ! Versions 6.7-7.x 58 | ! Copyright Mark Drela, Harold Youngren 59 | ! 60 | !==================================================== 61 | ! 62 | !ctxt = Common() 63 | 64 | ctxt%version = 7.55 65 | ! 66 | !---- logical unit numbers 67 | ctxt%luread = 5 ! terminal read 68 | ctxt%luwrit = 6 ! terminal write 69 | ctxt%lutemp = 3 ! general-use disk i/o unit (usually available) 70 | ctxt%lusave = 4 ! save file (usually open) 71 | ! 72 | ! 73 | if (show_output) write (*, 99001) ctxt%version 74 | ! 75 | !..................................................................... 76 | ! 77 | 99001 format (/' ========================='/' xrotor Version', & 78 | &f5.2/' =========================') 79 | ! 80 | call init_(ctxt) 81 | ! 82 | ctxt%fname = ' ' 83 | !--- Get command line args (if present) 84 | narg = iargc() 85 | ! 86 | if (narg>0) call getarg(1, ctxt%fname) 87 | if (ctxt%fname(1:1)/=' ') call load(ctxt, ctxt%fname) 88 | ! 89 | ctxt%fname = ' ' 90 | if (narg>1) call getarg(2, ctxt%fname) 91 | if (ctxt%fname(1:1)/=' ') then 92 | ctxt%ncase = 0 93 | open (ctxt%lutemp, file = ctxt%fname, status = 'old', err = 100) 94 | call getcas(ctxt%lutemp, nparx, ctxt%ncase, ctxt%caspar) 95 | close (ctxt%lutemp) 96 | if (ctxt%ncase>0) then 97 | kf = index(ctxt%fname, ' ') - 1 98 | if (show_output) write (*, *) 'Operating cases read from file '& 99 | &, ctxt%fname(1:kf), ' ...' 100 | call shocas(ctxt%luwrit, nparx, ctxt%ncase, ctxt%caspar, ctxt%rad, & 101 | & ctxt%name) 102 | endif 103 | endif 104 | ! 105 | 100 if (show_output) write (*, 99003) 106 | do 107 | ! 108 | call askc(' xrotor^', comand, comarg) 109 | ! 110 | do i = 1, 20 111 | iinput(i) = 0 112 | rinput(i) = 0.0 113 | enddo 114 | ninput = 0 115 | call getint(comarg, iinput, ninput, error) 116 | ninput = 0 117 | call getflt(comarg, rinput, ninput, error) 118 | ! 119 | ctxt%greek = .true. 120 | if (comand/=' ') then 121 | if (comand=='? '.and.show_output) write (*, 99003) 122 | if (comand/='? ') then 123 | if (comand=='quit') stop 124 | ! 125 | if (comand=='oper') call oper(ctxt) 126 | if (comand=='bend') call bend(ctxt) 127 | if (comand=='save') call save(ctxt, comarg) 128 | if (comand=='load') call load(ctxt, comarg) 129 | if (comand=='nois') call noise(ctxt) 130 | if (comand=='disp') then 131 | ! 132 | !--------------------------------------------------------------------- 133 | call output(ctxt, ctxt%luwrit) 134 | else 135 | if (ctxt%greek.and.show_output) write (*, 99002) comand 136 | 99002 format (1x, a4, & 137 | &' command not recognized. Type a "?" for list') 138 | endif 139 | endif 140 | endif 141 | enddo 142 | 99003 format (/' quit Exit program'/ & 143 | &' .oper Calculate off-design operating points'/ & 144 | &' .bend Calculate structural loads and deflections'/ & 145 | &' .nois Calculate and plot acoustic signature'/ & 146 | &' save f Save rotor to restart file'/ & 147 | &' load f Read rotor from restart file'/ & 148 | &' disp Display current design point') 149 | end 150 | ! rotor 151 | end 152 | !*==XROTOR.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 153 | 154 | program xrotor 155 | use p_xrotor, only : rotor 156 | call rotor 157 | end 158 | -------------------------------------------------------------------------------- /src/s_xrotor.f90: -------------------------------------------------------------------------------- 1 | !*==S_XROTOR.f90 processed by SPAG 7.25DB at 09:24 on 2 Aug 2019 2 | module s_xrotor 3 | implicit none 4 | contains 5 | subroutine uvadd(ctxt, xiw, wa, wt) 6 | 7 | use i_common, only : Common 8 | use m_spline, only : seval 9 | implicit real(M) 10 | !*** Start of declarations inserted by SPAG 11 | real RDIM, WA, WT, XIW 12 | !*** End of declarations inserted by SPAG 13 | type (Common), intent(inout) :: ctxt 14 | ! 15 | wa = 0.0 16 | wt = 0.0 17 | ! 18 | if (ctxt%nadd<=1) return 19 | ! 20 | rdim = xiw * ctxt%rad 21 | if (rdim>=ctxt%radd(1).and.rdim<=ctxt%radd(ctxt%nadd)) then 22 | wa = seval(rdim, ctxt%uadd, ctxt%uaddr, ctxt%radd) / ctxt%vel 23 | wt = seval(rdim, ctxt%vadd, ctxt%vaddr, ctxt%radd) / ctxt%vel 24 | endif 25 | ! 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /xrotor/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2018 D. de Vries 3 | # 4 | # This file is part of XRotor. 5 | # 6 | # XRotor is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # XRotor is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with XRotor. If not, see . 18 | __version__ = "1.3.1" 19 | 20 | from .xrotor import XRotor 21 | from .model import Case 22 | -------------------------------------------------------------------------------- /xrotor/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2018 D. de Vries 3 | # 4 | # This file is part of XRotor. 5 | # 6 | # XRotor is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # XRotor is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with XRotor. If not, see . 18 | import numpy as np 19 | 20 | from ctypes import c_float, c_int 21 | from scipy.optimize import minimize, newton, minimize_scalar 22 | from typing import Dict, Union, Iterable, Optional, List 23 | 24 | array_like = Union[float, Iterable[float], np.ndarray] 25 | 26 | 27 | class Conditions(object): 28 | """Propeller operating conditions 29 | 30 | Attributes 31 | ---------- 32 | rho : float 33 | Air density in kg/m^3 34 | vso : float 35 | Speed of sound in m/s 36 | rmu : float 37 | Dynamic viscosity in 38 | vel : float 39 | Inflow velocity in m/s 40 | adv : float 41 | Advance ratio 42 | It is defined as J = V/(ωR), where J is the advance ratio, V is the inflow velocity, ω is the rotational rate in 43 | rad/s, and R is the blade tip radius in m. 44 | """ 45 | 46 | def __init__(self, 47 | rho= 0, vso=0, rmu=0, 48 | alt=0, vel=0, adv=0): 49 | super().__init__() 50 | self.rho = rho 51 | self.vso = vso 52 | self.rmu = rmu 53 | self.alt = alt 54 | self.vel = vel 55 | self.adv = adv 56 | 57 | 58 | class Geometry(object): 59 | """Propeller blade geometry definition 60 | 61 | Attributes 62 | ---------- 63 | r_hub : float 64 | Hub radius in m 65 | r_tip : float 66 | Blade tip radius in m 67 | r_wake : float 68 | Wake blockage radius in m 69 | rake : float 70 | Rake angle 71 | geomdata : np.ndarray 72 | Array with the geometric data 73 | n_geom 74 | radii 75 | chord 76 | twist 77 | ubody 78 | """ 79 | 80 | def __init__(self): 81 | super().__init__() 82 | self.r_hub = 0 83 | self.r_tip = 1 84 | self.r_wake = 0 85 | self.rake = 0 86 | self.geomdata = np.zeros((4, 0), dtype=c_float) 87 | 88 | @property 89 | def n_geom(self): 90 | """int: Number of points at which the geometric data is recorded.""" 91 | return self.geomdata.shape[1] 92 | 93 | @n_geom.setter 94 | def n_geom(self, n_geom: int): 95 | self.geomdata = np.resize(self.geomdata, (4, n_geom)) 96 | 97 | @property 98 | def radii(self) -> np.ndarray: 99 | """np.ndarray: List of normalized radial station locations.""" 100 | return self.geomdata[0, :] 101 | 102 | @radii.setter 103 | def radii(self, radii: np.ndarray): 104 | if radii.size != self.geomdata.shape[1]: 105 | self.geomdata = np.resize(self.geomdata, (4, radii.size)) 106 | self.geomdata[0, :] = radii[:] 107 | 108 | @property 109 | def chord(self) -> np.ndarray: 110 | """np.ndarray: List of normalized chord lengths for each radial station.""" 111 | return self.geomdata[1, :] 112 | 113 | @chord.setter 114 | def chord(self, chord: np.ndarray): 115 | if chord.size != self.geomdata.shape[1]: 116 | self.geomdata = np.resize(self.geomdata, (4, chord.size)) 117 | self.geomdata[1, :] = chord[:] 118 | 119 | @property 120 | def twist(self) -> np.ndarray: 121 | """np.ndarray: List of twist angles for each radial station in degrees.""" 122 | return self.geomdata[2, :] 123 | 124 | @twist.setter 125 | def twist(self, twist: np.ndarray): 126 | if twist.size != self.geomdata.shape[1]: 127 | self.geomdata = np.resize(self.geomdata, (4, twist.size)) 128 | self.geomdata[2, :] = twist[:] 129 | 130 | @property 131 | def ubody(self) -> np.ndarray: 132 | """np.ndarray: Nacelle perturbation axial velocities at each radial station in m/s.""" 133 | return self.geomdata[3, :] 134 | 135 | @ubody.setter 136 | def ubody(self, ubody: np.ndarray): 137 | if ubody.size != self.geomdata.shape[1]: 138 | self.geomdata = np.resize(self.geomdata, (4, ubody.size)) 139 | self.geomdata[3, :] = ubody[:] 140 | 141 | 142 | class Section(object): 143 | """Aerodynamic section definition 144 | 145 | Attributes 146 | ---------- 147 | a_0 : float 148 | Zero-lift angle of attack in degrees. 149 | dClda : float 150 | Lift curve slope, d(C_l)/d(alpha), in the linear regime in 1/rad. 151 | Cl_min, Cl_min : float 152 | Lift coefficients at the positive and negative stall points. 153 | dClda_stall : float 154 | Lift curve slope in the stalled part of the lift curve in 1/rad. 155 | dCl_stall : float 156 | Lift increment from the end of the linear range to stall. 157 | Cd_min : float 158 | Minimum drag coefficient. 159 | Cl_Cd_min : float 160 | Lift coefficient when the drag coefficient is at its minimum. 161 | dCddCl2 : float 162 | Quadratic drag polar coefficient, d(C_d)/d(C_l^2). 163 | Cm_const : float 164 | Moment coefficient in the linear regime, which is assumed to be constant. 165 | M_crit : float 166 | Critical Mach number. 167 | Re : float 168 | Reference Reynolds number. 169 | Re_exp : float 170 | Exponential correction coefficient to correct for Reynolds number increments due to induced velocities. 171 | """ 172 | 173 | def __init__(self, *args, **kwargs): 174 | super().__init__() 175 | self.a_0 = 0. if len(args) < 1 else args[0] 176 | self.dClda = 0. if len(args) < 2 else args[1] 177 | self.Cl_max = 0. if len(args) < 3 else args[2] 178 | self.Cl_min = 0. if len(args) < 4 else args[3] 179 | self.dClda_stall = 0. if len(args) < 5 else args[4] 180 | self.dCl_stall = 0. if len(args) < 6 else args[5] 181 | self.Cd_min = 0. if len(args) < 7 else args[6] 182 | self.Cl_Cd_min = 0. if len(args) < 8 else args[7] 183 | self.dCddCl2 = 0. if len(args) < 9 else args[8] 184 | 185 | self.Cm_const = 0. if 'Cm_const' not in kwargs else kwargs['Cm_const'] 186 | self.M_crit = 0. if 'M_crit' not in kwargs else kwargs['M_crit'] 187 | self.Re = 0. if 'Re' not in kwargs else kwargs['Re'] 188 | self.Re_exp = 0. if 'Re_exp' not in kwargs else kwargs['Re_exp'] 189 | 190 | @staticmethod 191 | def from_dict(d): 192 | """Create an instance of the Section class from a dictionary. 193 | 194 | Parameters 195 | ---------- 196 | d : dict 197 | Dictionary containing relevant aerodynamic properties 198 | 199 | Returns 200 | ------- 201 | Section 202 | Instance of this class with the data provided through the dictionary 203 | """ 204 | sec = Section() 205 | for key, value in d.items(): 206 | if hasattr(sec, key): 207 | setattr(sec, key, value) 208 | return sec 209 | 210 | @staticmethod 211 | def fit_polar(a: np.ndarray, cl: np.ndarray, cd: np.ndarray, cm: np.ndarray, cp: Optional[np.ndarray]=None): 212 | """Fit the aerodynamic Section properties to a given polar. 213 | 214 | Parameters 215 | ---------- 216 | a, cl, cd, cm : np.ndarray 217 | Polar generated by an appropriate 2D airfoil analysis program or experimental results. 218 | cp : np.ndarray 219 | List of minimal pressure coefficients for each alpha in a. Optional. 220 | 221 | Returns 222 | ------- 223 | Section 224 | An instance of the Section class with all model parameters specified to provide the best possible 225 | fit to the polar data. 226 | """ 227 | # If any of the polar arrays are all nans return a dummy instance of Section 228 | if np.all(np.isnan(a)) or np.all(np.isnan(cl)) or np.all(np.isnan(cd)) or np.all(np.isnan(cm)) or \ 229 | (cp is not None and np.all(np.isnan(cp))): 230 | return Section(*(9*(0.,)), Cm_const=0., M_crit=0.) 231 | 232 | # Remove any polar points with nans in any of its values 233 | i_valid = np.isnan(a) + np.isnan(cl) + np.isnan(cd) + np.isnan(cm) 234 | if cp is not None: 235 | i_valid += np.isnan(cp) 236 | i_valid = np.logical_not(i_valid) 237 | 238 | a = a[i_valid] 239 | cl = cl[i_valid] 240 | cd = cd[i_valid] 241 | cm = cm[i_valid] 242 | if cp is not None: 243 | cp = cp[i_valid] 244 | 245 | # Sort polar points by angle of attack 246 | i_sorted = np.argsort(a) 247 | a = a[i_sorted] 248 | cl = cl[i_sorted] 249 | cd = cd[i_sorted] 250 | cm = cm[i_sorted] 251 | if cp is not None: 252 | cp = cp[i_sorted] 253 | 254 | # If minimum cps are given, compute the critical Mach number 255 | a_unique, indices = np.unique(a, return_index=True) 256 | cp = np.interp(0., a_unique, cp[indices]) 257 | 258 | gamma = 1.4 259 | 260 | def f(M): 261 | return ((2. / (gamma * M ** 2)) * 262 | (((gamma + 1.) / (2 * (1 + .5 * (gamma - 1) * M ** 2))) ** (gamma / (1 - gamma)) - 1)) - cp 263 | 264 | def df(M): 265 | a = ((-4. / (gamma * M ** 3)) * 266 | (((gamma + 1.) / (2 * (1 + .5 * (gamma - 1) * M ** 2))) ** (gamma / (1 - gamma)) - 1)) 267 | b = -(2. / (gamma * M ** 2)) * (gamma / (1 - gamma)) * \ 268 | ((gamma + 1.) / (2 * (1 + .5 * (gamma - 1) * M ** 2))) ** (gamma / (1 - gamma) - 1) * \ 269 | ((gamma + 1) / (2 * (1 + .5 * (gamma - 1) * M ** 2)) ** 2) * 2 * (gamma - 1) * M 270 | return a + b 271 | 272 | M_crit = newton(f, 1., df) 273 | else: 274 | M_crit = 0.6 275 | 276 | def gaussian(x, mu, sigma): 277 | """Unweighted gaussian function.""" 278 | return np.exp(-(x - mu) ** 2 / (2 * sigma ** 2)) 279 | 280 | # Weigh the importance of/confidence in points of the lift curve for fitting 281 | sigma_a = (np.max(a) - np.min(a)) / 2 282 | mu_a = np.min(a) + sigma_a 283 | weights = gaussian(a, mu_a, sigma_a/2) 284 | 285 | def e_cl(x): 286 | """Weighted root-mean-squared error of fitted cl and given cl.""" 287 | return np.sqrt(np.mean(weights * (Section(*x).cl(a) - cl) ** 2)) 288 | 289 | # Fit lift curve 290 | res_cl = minimize(e_cl, np.ones(6), bounds=[(-10., 10.), (1., 20.), (0., 3.), (-3., 0.), (-2., 2.), (.01, .5)]) 291 | 292 | # Cut off polar at Cl_min and Cl_max to improve drag polar fit 293 | inter_model = Section(*res_cl.x) 294 | bounds = (np.min(a), np.max(a)) 295 | res_a_cl_max = minimize_scalar(lambda a_cl_max: (inter_model.cl(a_cl_max) - res_cl.x[2])**2, bounds=bounds) 296 | res_a_cl_min = minimize_scalar(lambda a_cl_min: (inter_model.cl(a_cl_min) - res_cl.x[3])**2, bounds=bounds) 297 | i = np.logical_and(a > res_a_cl_min.x, a < res_a_cl_max.x) 298 | a = a[i] 299 | cd = cd[i] 300 | 301 | # Compute a new confidence interval for fitting the drag polar 302 | sigma_a = (np.max(a) - np.min(a)) / 2 303 | mu_a = np.min(a) + sigma_a 304 | weights = gaussian(a, mu_a, sigma_a / 2) 305 | 306 | def e_cd(x): 307 | """Weighted root-mean-squared error of fitted cd and given cd.""" 308 | return np.sqrt(np.mean(weights * (Section(*res_cl.x, *x).cd(a) - cd) ** 2)) 309 | 310 | # Fit drag polar 311 | res_cd = minimize(e_cd, np.ones(3), bounds=[(0, 0.5), (-1., 1.), (0., 1.)]) 312 | 313 | return Section(*res_cl.x, *res_cd.x, Cm_const=np.average(cm), M_crit=M_crit) 314 | 315 | def cl_lin(self, alpha: array_like) -> array_like: 316 | """Calculate the lift coefficient in the linear range for a given angle of attack. 317 | 318 | Parameters 319 | ---------- 320 | alpha : array-like 321 | One or more angles of attack in degrees. 322 | 323 | Returns 324 | ------- 325 | Linear lift coefficients for each specified angle of attack. 326 | """ 327 | return (alpha - self.a_0) * np.pi / 180. * self.dClda 328 | 329 | def delta_cl_nl(self, alpha: array_like) -> array_like: 330 | """Calculate the offset of the lift coefficient in the non-linear range for a given angle of attack. 331 | 332 | Parameters 333 | ---------- 334 | alpha : array-like 335 | One or more angles of attack in degrees. 336 | 337 | Returns 338 | ------- 339 | Offset of the lift coefficients for each specified angle of attack. 340 | """ 341 | cl_lin = self.cl_lin(alpha) 342 | return self.dCl_stall * np.log((1 + np.exp((cl_lin - self.Cl_max) / self.dCl_stall)) / 343 | (1 + np.exp((self.Cl_min - cl_lin) / self.dCl_stall))) 344 | 345 | def cl(self, alpha: Union[float, Iterable[float], np.ndarray]) -> Union[float, Iterable[float], np.ndarray]: 346 | """Calculate the lift coefficient for a given angle of attack. 347 | 348 | Parameters 349 | ---------- 350 | alpha : float or iterable of floats or np.ndarray 351 | One or more angles of attack in degrees. 352 | 353 | Returns 354 | ------- 355 | Lift coefficients for each specified angle of attack. 356 | """ 357 | return self.cl_lin(alpha) - (1 - self.dClda_stall / self.dClda) * self.delta_cl_nl(alpha) 358 | 359 | def cd_lin(self, alpha: array_like) -> array_like: 360 | """Calculate the drag coefficient in the linear range for a given angle of attack. 361 | 362 | Parameters 363 | ---------- 364 | alpha : array-like 365 | One or more angles of attack in degrees. 366 | 367 | Returns 368 | ------- 369 | Linear drag coefficients for each specified angle of attack. 370 | """ 371 | return self.Cd_min + self.dCddCl2 * (self.cl(alpha) - self.Cl_Cd_min) ** 2 372 | 373 | def delta_cd_nl(self, alpha: array_like) -> array_like: 374 | """Calculate the offset of the drag coefficient in the non-linear range for a given angle of attack. 375 | 376 | Parameters 377 | ---------- 378 | alpha : array-like 379 | One or more angles of attack in degrees. 380 | 381 | Returns 382 | ------- 383 | Offset of the drag coefficients for each specified angle of attack. 384 | """ 385 | return 2 * ((1 - self.dClda_stall / self.dClda) * self.delta_cl_nl(alpha) / self.dClda) ** 2 386 | 387 | def cd(self, alpha: array_like) -> array_like: 388 | """Calculate the drag coefficient for a given angle of attack. 389 | 390 | Parameters 391 | ---------- 392 | alpha : array-like 393 | One or more angles of attack in degrees. 394 | 395 | Returns 396 | ------- 397 | Drag coefficients for each specified angle of attack. 398 | """ 399 | return self.cd_lin(alpha) + self.delta_cd_nl(alpha) 400 | 401 | 402 | class Blade(object): 403 | """Propeller blade definition 404 | 405 | Attributes 406 | ---------- 407 | geometry : Geometry 408 | Instance of the Geometry class representing the geometry of the blade 409 | polars : dict of np.ndarray instances by float 410 | Dictionary of aerodynamic polars, indexed by their normalized radial positions along the blade 411 | geomdata 412 | polardata 413 | """ 414 | 415 | def __init__(self, geometry: Geometry = Geometry(), polars: Dict[float, np.ndarray] = None): 416 | super().__init__() 417 | self.geometry: Geometry = geometry 418 | self.polars: Dict[float, polars] = dict() if polars is None else polars 419 | 420 | @property 421 | def geomdata(self) -> np.ndarray: 422 | """np.ndarray: Array of the blade's geometric data""" 423 | return self.geometry.geomdata 424 | 425 | @property 426 | def n_polar_points(self) -> np.ndarray: 427 | """np.ndarray: Number of points for each specified polar""" 428 | return np.array([polar.shape[0] for polar in self.polars.values()], dtype=c_int, order='F') 429 | 430 | @property 431 | def polardata(self) -> np.ndarray: 432 | """np.ndarray: Array with all Sections' polar data""" 433 | return np.asarray(np.vstack(list(self.polars.values())), dtype=c_float, order='F') 434 | 435 | @property 436 | def xi_polars(self) -> np.ndarray: 437 | """np.ndarray: List of normalized radial positions of the polars""" 438 | return np.asarray(list(self.polars.keys()), dtype=c_float, order='F') 439 | 440 | 441 | class Disk(object): 442 | """Propeller disk definition 443 | 444 | Attributes 445 | ---------- 446 | n_blds : int 447 | Number of blades 448 | blade : Blade 449 | Definition of the blade 450 | """ 451 | 452 | def __init__(self, n_blds: int = 0, blade: Blade = Blade()): 453 | super().__init__() 454 | self.n_blds: int = n_blds 455 | self.blade: Blade = blade 456 | 457 | 458 | class Settings(object): 459 | """XRotor run case settings 460 | 461 | Attributes 462 | ---------- 463 | free : bool 464 | True for free wake formulation 465 | duct : bool 466 | True if a duct is present 467 | wind : bool 468 | True if windmill-mode plotting is to be used 469 | """ 470 | 471 | def __init__(self, free=True, duct=False, wind=False): 472 | super().__init__() 473 | self.free = free 474 | self.duct = duct 475 | self.wind = wind 476 | 477 | 478 | class Case(object): 479 | """XRotor run case definition 480 | 481 | Attributes 482 | ---------- 483 | conditions : Conditions 484 | Specification of the operating conditions 485 | disk : Disk 486 | Definition of the propeller disk 487 | settings : Settings 488 | Specification of the run case settings 489 | """ 490 | 491 | def __init__(self, 492 | conditions: Conditions = Conditions(), 493 | disk: Disk = Disk(), settings: Settings = Settings()): 494 | super().__init__() 495 | self.conditions: Conditions = conditions 496 | self.disk: Disk = disk 497 | self.settings: Settings = settings 498 | 499 | @staticmethod 500 | def from_dict(d): 501 | """Construct an instance of this class from a dictionary. 502 | 503 | Parameters 504 | ---------- 505 | d : dict 506 | Dictionary representing an XRotor run case 507 | 508 | Returns 509 | ------- 510 | Case 511 | Instance of this class corresponding to the data passed through the dictionary 512 | """ 513 | case = Case() 514 | 515 | def recurse(obj, sub_d): 516 | if isinstance(obj, dict): 517 | for key, value in sub_d.items(): 518 | obj.update({key: np.asarray(value)}) 519 | else: 520 | for key, value in sub_d.items(): 521 | if hasattr(obj, key): 522 | if isinstance(value, dict): 523 | recurse(getattr(obj, key), value) 524 | else: 525 | setattr(obj, key, value) 526 | recurse(case, d) 527 | return case 528 | 529 | 530 | class Performance(object): 531 | """Blade performance specification 532 | 533 | Attributes 534 | ---------- 535 | rpm 536 | thrust 537 | torque 538 | power 539 | efficiency 540 | """ 541 | 542 | def __init__(self, rpm: float = 0, thrust: float = 0, torque: float = 0, power: float = 0, efficiency: float = 0): 543 | super().__init__() 544 | self._rpm: c_float = c_float(rpm) 545 | self._thrust: c_float = c_float(thrust) 546 | self._torque: c_float = c_float(torque) 547 | self._power: c_float = c_float(power) 548 | self._efficiency: c_float = c_float(efficiency) 549 | 550 | @property 551 | def rpm(self): 552 | """float: Rotational speed in rev/min""" 553 | return self._rpm.value 554 | 555 | @property 556 | def thrust(self): 557 | """float: Thrust in N""" 558 | return self._thrust.value 559 | 560 | @property 561 | def torque(self): 562 | """float: Torque in Nm""" 563 | return self._torque.value 564 | 565 | @property 566 | def power(self): 567 | """float: Power in W""" 568 | return self._power.value 569 | 570 | @property 571 | def efficiency(self): 572 | """float: Overall efficiency""" 573 | return self._efficiency.value 574 | 575 | -------------------------------------------------------------------------------- /xrotor/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2018 D. de Vries 3 | # 4 | # This file is part of XRotor. 5 | # 6 | # XRotor is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # XRotor is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with XRotor. If not, see . 18 | import multiprocessing 19 | import numpy as np 20 | import unittest 21 | 22 | from .xrotor import XRotor 23 | from .model import Case 24 | 25 | geomdata = np.array([ 26 | # r/R c/R t/c beta 27 | [0.2000, 0.0759, 0.7977, 45.6410], 28 | [0.2500, 0.0904, 0.5492, 43.9615], 29 | [0.3000, 0.1062, 0.3635, 41.8157], 30 | [0.3500, 0.1226, 0.2405, 39.4593], 31 | [0.4000, 0.1382, 0.1748, 36.8433], 32 | [0.4500, 0.1480, 0.1366, 34.1065], 33 | [0.5000, 0.1520, 0.1182, 31.8362], 34 | [0.5500, 0.1503, 0.1078, 29.9828], 35 | [0.6000, 0.1461, 0.1015, 28.3983], 36 | [0.6500, 0.1393, 0.0955, 27.0601], 37 | [0.7000, 0.1313, 0.0915, 25.9288], 38 | [0.7500, 0.1224, 0.0884, 24.9254], 39 | [0.8000, 0.1121, 0.0855, 24.0828], 40 | [0.8500, 0.1010, 0.0839, 23.3286], 41 | [0.9000, 0.0886, 0.0818, 22.6380], 42 | [0.9500, 0.0752, 0.0812, 22.1021], 43 | ]) 44 | 45 | case = { 46 | 'conditions': { 47 | # Standard atmosphere at sea level 48 | 'rho': 1.225, 49 | 'vso': 340, 50 | 'rmu': 1.789e-5, 51 | 'alt': 1, 52 | # Operating conditions in accordance with NASA Report No. 640 53 | # adv = V / (Omega * R) = V / (RPM * pi/30 * R) 54 | 'vel': 35.56, 55 | 'adv': 0.70 56 | }, 57 | 'disk': { 58 | 'n_blds': 2, 59 | 'blade': { 60 | 'geometry': { 61 | # Geometry of the NASA Report No. 640, 5868-9, 2 blades, blade angle 25 deg at 0.75R propeller 62 | 'r_hub' : 0.305, 63 | 'r_tip' : 1.524, 64 | 'r_wake': 0.0, 65 | 'rake' : 0.0, 66 | 'radii' : geomdata[:, 0], 67 | 'chord' : geomdata[:, 1], 68 | 'twist' : geomdata[:, 3], 69 | 'ubody' : np.zeros_like(geomdata[:, 0]) 70 | }, 71 | 'polars': { 72 | 0.0: np.array([ 73 | # Clark-y at Re = 1,000,000, M = 0.3 (ncrit = 0.01) 74 | # alpha cl cd cm 75 | [-10.00, -0.6912, 0.0334, -0.0951], 76 | [ -9.75, -0.7003, 0.0277, -0.0954], 77 | [ -9.50, -0.6899, 0.0246, -0.0947], 78 | [ -9.25, -0.6723, 0.0226, -0.0939], 79 | [ -9.00, -0.6511, 0.0210, -0.0930], 80 | [ -8.75, -0.6276, 0.0198, -0.0921], 81 | [ -8.50, -0.6027, 0.0188, -0.0913], 82 | [ -8.25, -0.5767, 0.0179, -0.0905], 83 | [ -8.00, -0.5500, 0.0172, -0.0898], 84 | [ -7.75, -0.5227, 0.0166, -0.0891], 85 | [ -7.50, -0.4950, 0.0160, -0.0885], 86 | [ -7.25, -0.4670, 0.0156, -0.0878], 87 | [ -7.00, -0.4387, 0.0151, -0.0873], 88 | [ -6.75, -0.4102, 0.0147, -0.0867], 89 | [ -6.50, -0.3816, 0.0144, -0.0862], 90 | [ -6.25, -0.3528, 0.0141, -0.0857], 91 | [ -6.00, -0.3240, 0.0138, -0.0852], 92 | [ -5.75, -0.2950, 0.0135, -0.0848], 93 | [ -5.50, -0.2660, 0.0133, -0.0844], 94 | [ -5.25, -0.2370, 0.0130, -0.0840], 95 | [ -5.00, -0.2079, 0.0128, -0.0836], 96 | [ -4.75, -0.1788, 0.0127, -0.0833], 97 | [ -4.50, -0.1497, 0.0125, -0.0830], 98 | [ -4.25, -0.1205, 0.0124, -0.0827], 99 | [ -4.00, -0.0914, 0.0122, -0.0824], 100 | [ -3.75, -0.0623, 0.0121, -0.0821], 101 | [ -3.50, -0.0331, 0.0120, -0.0818], 102 | [ -3.25, -0.0040, 0.0119, -0.0816], 103 | [ -3.00, 0.0251, 0.0119, -0.0813], 104 | [ -2.75, 0.0542, 0.0118, -0.0811], 105 | [ -2.50, 0.0832, 0.0117, -0.0809], 106 | [ -2.25, 0.1123, 0.0117, -0.0807], 107 | [ -2.00, 0.1413, 0.0117, -0.0804], 108 | [ -1.75, 0.1703, 0.0116, -0.0802], 109 | [ -1.50, 0.1992, 0.0116, -0.0800], 110 | [ -1.25, 0.2282, 0.0116, -0.0798], 111 | [ -1.00, 0.2570, 0.0116, -0.0796], 112 | [ -0.75, 0.2859, 0.0116, -0.0794], 113 | [ -0.50, 0.3147, 0.0116, -0.0792], 114 | [ -0.25, 0.3434, 0.0117, -0.0790], 115 | [ 0.00, 0.3721, 0.0117, -0.0788], 116 | [ 0.25, 0.4007, 0.0117, -0.0786], 117 | [ 0.50, 0.4293, 0.0118, -0.0784], 118 | [ 0.75, 0.4578, 0.0119, -0.0782], 119 | [ 1.00, 0.4863, 0.0119, -0.0779], 120 | [ 1.25, 0.5147, 0.0120, -0.0777], 121 | [ 1.50, 0.5430, 0.0121, -0.0774], 122 | [ 1.75, 0.5712, 0.0122, -0.0772], 123 | [ 2.00, 0.5994, 0.0123, -0.0769], 124 | [ 2.25, 0.6274, 0.0124, -0.0766], 125 | [ 2.50, 0.6554, 0.0125, -0.0763], 126 | [ 2.75, 0.6833, 0.0126, -0.0759], 127 | [ 3.00, 0.7111, 0.0127, -0.0756], 128 | [ 3.25, 0.7387, 0.0129, -0.0752], 129 | [ 3.50, 0.7662, 0.0130, -0.0748], 130 | [ 3.75, 0.7936, 0.0132, -0.0744], 131 | [ 4.00, 0.8208, 0.0133, -0.0739], 132 | [ 4.25, 0.8479, 0.0135, -0.0734], 133 | [ 4.50, 0.8748, 0.0137, -0.0729], 134 | [ 4.75, 0.9015, 0.0139, -0.0723], 135 | [ 5.00, 0.9280, 0.0141, -0.0717], 136 | [ 5.25, 0.9542, 0.0143, -0.0711], 137 | [ 5.50, 0.9802, 0.0145, -0.0704], 138 | [ 5.75, 1.0060, 0.0148, -0.0696], 139 | [ 6.00, 1.0314, 0.0150, -0.0688], 140 | [ 6.25, 1.0564, 0.0153, -0.0679], 141 | [ 6.50, 1.0811, 0.0156, -0.0670], 142 | [ 6.75, 1.1053, 0.0159, -0.066], 143 | [ 7.00, 1.1290, 0.0162, -0.0648], 144 | [ 7.25, 1.1514, 0.0165, -0.0635], 145 | [ 7.50, 1.1719, 0.0168, -0.0618], 146 | [ 7.75, 1.1921, 0.0172, -0.0600], 147 | [ 8.00, 1.2133, 0.0176, -0.0585], 148 | [ 8.25, 1.2342, 0.0181, -0.0571], 149 | [ 8.50, 1.2546, 0.0185, -0.0555], 150 | [ 8.75, 1.2743, 0.0191, -0.0539], 151 | [ 9.00, 1.2934, 0.0196, -0.0523], 152 | [ 9.25, 1.3118, 0.0202, -0.0506], 153 | [ 9.50, 1.3295, 0.0209, -0.0489], 154 | [ 9.75, 1.3465, 0.0216, -0.0472], 155 | [ 10.00, 1.3628, 0.0224, -0.0454], 156 | [ 10.25, 1.3783, 0.0232, -0.0437], 157 | [ 10.50, 1.3932, 0.0241, -0.0420], 158 | [ 10.75, 1.4073, 0.0251, -0.0403], 159 | [ 11.00, 1.4205, 0.0262, -0.0386], 160 | [ 11.25, 1.4330, 0.0274, -0.0370], 161 | [ 11.50, 1.4445, 0.0287, -0.0354], 162 | [ 11.75, 1.4551, 0.0302, -0.0339], 163 | [ 12.00, 1.4647, 0.0317, -0.0325], 164 | [ 12.25, 1.4733, 0.0334, -0.0312], 165 | [ 12.50, 1.4807, 0.0353, -0.0299], 166 | [ 12.75, 1.4869, 0.0373, -0.0288], 167 | [ 13.00, 1.4919, 0.0395, -0.0278], 168 | [ 13.25, 1.4955, 0.0420, -0.0270], 169 | [ 13.50, 1.4979, 0.0446, -0.0263], 170 | [ 13.75, 1.4989, 0.0474, -0.0258], 171 | [ 14.00, 1.4985, 0.0505, -0.0255], 172 | [ 14.25, 1.4967, 0.0539, -0.0254], 173 | [ 14.50, 1.4934, 0.0576, -0.0256], 174 | [ 14.75, 1.4885, 0.0615, -0.0260], 175 | [ 15.00, 1.4822, 0.0657, -0.0266], 176 | [ 15.25, 1.4742, 0.0703, -0.0275], 177 | [ 15.50, 1.4647, 0.0751, -0.0286], 178 | [ 15.75, 1.4540, 0.0802, -0.0299], 179 | [ 16.00, 1.4421, 0.0856, -0.0315], 180 | [ 16.25, 1.4294, 0.0912, -0.0333], 181 | [ 16.50, 1.4162, 0.0969, -0.0353], 182 | [ 16.75, 1.4029, 0.1028, -0.0374], 183 | [ 17.00, 1.3895, 0.1087, -0.0398], 184 | [ 17.25, 1.3764, 0.1147, -0.0423], 185 | [ 17.50, 1.3638, 0.1207, -0.0450], 186 | [ 17.75, 1.3516, 0.1267, -0.0478], 187 | [ 18.00, 1.3402, 0.1327, -0.0509], 188 | [ 18.25, 1.3295, 0.1387, -0.0541], 189 | [ 18.50, 1.3194, 0.1447, -0.0574], 190 | [ 18.75, 1.3100, 0.1507, -0.0609], 191 | [ 19.00, 1.3012, 0.1567, -0.0646], 192 | [ 19.25, 1.2931, 0.1627, -0.0683], 193 | [ 19.50, 1.2855, 0.1686, -0.0722], 194 | [ 19.75, 1.2787, 0.1746, -0.0761], 195 | [ 20.00, 1.2727, 0.1805, -0.0801] 196 | ]) 197 | } 198 | } 199 | }, 200 | 'settings': { 201 | 'free': True, 'duct': False, 'wind': False 202 | } 203 | } 204 | 205 | 206 | def print_perf(perf): 207 | print('\nrpm: {:10.3G}, thrust(N): {:10.8G}, torque(Nm): {:10.8G}, power(W): {:10.8G}, eff: {:10.9G}' 208 | .format(perf.rpm, perf.thrust, perf.torque, perf.power, perf.efficiency)) 209 | 210 | 211 | class TestXRotor(unittest.TestCase): 212 | """Test whether the XROTOR module functions properly.""" 213 | 214 | def test_solve_for_thrust(self): 215 | """Run the test case at a thrust of 500 N and make sure the performance results match the expected values. 216 | 217 | Expected performance for a thrust of 500 N: 218 | - Torque : Q ≈ 107 Nm 219 | - Power : P ≈ 22.7 kW 220 | - RPM : Ω ≈ 2019 rev/min 221 | - Efficiency : η ≈ 0.596 222 | """ 223 | xr = XRotor() 224 | xr.case = Case.from_dict(case) 225 | rms = xr.operate(thrust=500) 226 | perf = xr.performance 227 | 228 | print_perf(perf) 229 | 230 | self.assertTrue(abs(rms) < 1.0e-7) 231 | self.assertAlmostEqual(perf.thrust, 500, 0) 232 | self.assertAlmostEqual(perf.torque, 107, 0) 233 | self.assertAlmostEqual(perf.power/1000, 22.7, 1) 234 | self.assertAlmostEqual(perf.rpm, 2019, 0) 235 | self.assertAlmostEqual(perf.efficiency, 0.596, 3) 236 | 237 | def test_solve_for_torque(self): 238 | """Run the test case at a torque of 100 Nm and make sure the performance results match the expected values. 239 | 240 | Expected performance for a torque of 100 Nm: 241 | - Thrust : T ≈ 446 N 242 | - Power : P ≈ 20.6 kW 243 | - RPM : Ω ≈ 1963 rev/min 244 | - Efficiency : η ≈ 0.586 245 | """ 246 | xr = XRotor() 247 | xr.case = Case.from_dict(case) 248 | rms = xr.operate(torque=100) 249 | perf = xr.performance 250 | 251 | print_perf(perf) 252 | 253 | self.assertTrue(abs(rms) < 1.0e-7) 254 | self.assertAlmostEqual(perf.thrust, 446, 0) 255 | self.assertAlmostEqual(perf.torque, 100, 0) 256 | self.assertAlmostEqual(perf.power/1000, 20.6, 1) 257 | self.assertAlmostEqual(perf.rpm, 1963, 0) 258 | self.assertAlmostEqual(perf.efficiency, 0.586, 3) 259 | 260 | def test_solve_for_power(self): 261 | """Run the test case at a power of 20 kW and make sure the performance results match the expected values. 262 | 263 | Expected performance for a torque of 100 Nm: 264 | - Thrust : T ≈ 431 N 265 | - Torque : Q ≈ 98 Nm 266 | - RPM : Ω ≈ 1947 rev/min 267 | - Efficiency : η ≈ 0.582 268 | """ 269 | xr = XRotor() 270 | xr.case = Case.from_dict(case) 271 | rms = xr.operate(power=20e3) 272 | perf = xr.performance 273 | 274 | print_perf(perf) 275 | 276 | self.assertTrue(abs(rms) < 1.0e-7) 277 | self.assertAlmostEqual(perf.thrust, 431, 0) 278 | self.assertAlmostEqual(perf.torque, 98, 0) 279 | self.assertAlmostEqual(perf.power/1000, 20.0, 1) 280 | self.assertAlmostEqual(perf.rpm, 1947, 0) 281 | self.assertAlmostEqual(perf.efficiency, 0.582, 3) 282 | 283 | def test_solve_for_rpm(self): 284 | """Run the test case at an RPM of 2000 and make sure the performance results match the expected values. 285 | 286 | Expected performance at 2000 rev/min: 287 | - Thrust : T ≈ 481 N 288 | - Torque : Q ≈ 105 Nm 289 | - Power : P ≈ 21.9 kW 290 | - Efficiency : η ≈ 0.593 291 | """ 292 | xr = XRotor() 293 | xr.case = Case.from_dict(case) 294 | rms = xr.operate(rpm=2000) 295 | perf = xr.performance 296 | 297 | print_perf(perf) 298 | 299 | self.assertTrue(abs(rms) < 1.0e-7) 300 | self.assertAlmostEqual(perf.thrust, 481, 0) 301 | self.assertAlmostEqual(perf.torque, 105, 0) 302 | self.assertAlmostEqual(perf.power/1000, 21.9, 1) 303 | self.assertAlmostEqual(perf.rpm, 2000, 0) 304 | self.assertAlmostEqual(perf.efficiency, 0.593, 3) 305 | 306 | 307 | class TestXRotorConcurrently(unittest.TestCase): 308 | """Test whether the tests can be run properly in parallel.""" 309 | 310 | def test_concurrently(self): 311 | """Run all test cases contained in the TestXRotor test case in parallel.""" 312 | test_xrotor = TestXRotor() 313 | 314 | ps = [multiprocessing.Process(target=test_xrotor.test_solve_for_thrust), 315 | multiprocessing.Process(target=test_xrotor.test_solve_for_torque), 316 | multiprocessing.Process(target=test_xrotor.test_solve_for_power), 317 | multiprocessing.Process(target=test_xrotor.test_solve_for_rpm)] 318 | [p.start() for p in ps] 319 | [p.join() for p in ps] 320 | 321 | 322 | if __name__ == '__main__': 323 | unittest.main() 324 | -------------------------------------------------------------------------------- /xrotor/xrotor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2018 D. de Vries 3 | # 4 | # This file is part of XRotor. 5 | # 6 | # XRotor is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # XRotor is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with XRotor. If not, see . 18 | import numpy as np 19 | import os 20 | import glob 21 | import ctypes 22 | 23 | from ctypes import c_bool, c_int, byref, POINTER, c_float, cdll 24 | from shutil import copy2 25 | from tempfile import NamedTemporaryFile 26 | 27 | from .model import Case, Performance 28 | 29 | here = os.path.abspath(os.path.dirname(__file__)) 30 | lib_path = glob.glob(os.path.join(here, 'libxrotor.*'))[0] 31 | lib_ext = lib_path[lib_path.rfind('.'):] 32 | fptr = POINTER(c_float) 33 | iptr = POINTER(c_int) 34 | 35 | 36 | class XRotor(object): 37 | """Interface to the XRotor Fortran routines. 38 | 39 | Attributes 40 | ---------- 41 | print 42 | max_iter 43 | case 44 | performance 45 | station_conditions 46 | rms 47 | """ 48 | 49 | def __init__(self): 50 | super().__init__() 51 | tmp = NamedTemporaryFile(mode='wb', delete=False, suffix=lib_ext) 52 | tmp.close() 53 | self._lib_path = tmp.name 54 | copy2(lib_path, self._lib_path) 55 | self._lib = cdll.LoadLibrary(self._lib_path) 56 | 57 | self._lib.get_print.restype = c_bool 58 | self._lib.get_use_compr_corr.restype = c_bool 59 | self._lib.get_vrtx.restype = c_bool 60 | self._lib.get_fast.restype = c_bool 61 | self._lib.operate.restype = c_float 62 | self._lib.get_rms.restype = c_float 63 | self._lib.get_blade_angle_change.restype = c_float 64 | 65 | self._lib.init() 66 | self._case: Case = None 67 | 68 | def __del__(self): 69 | handle = self._lib._handle 70 | del self._lib 71 | try: 72 | ctypes.windll.kernel32.FreeLibrary(handle) 73 | except AttributeError: 74 | pass 75 | finally: 76 | os.remove(self._lib_path) 77 | 78 | @property 79 | def print(self): 80 | """bool: True if console output should be shown.""" 81 | return self._lib.get_print() 82 | 83 | @print.setter 84 | def print(self, value): 85 | self._lib.set_print(byref(c_bool(value))) 86 | 87 | @property 88 | def max_iter(self): 89 | """integer: Maximum number of iterations.""" 90 | return self._lib.get_max_iter() 91 | 92 | @max_iter.setter 93 | def max_iter(self, value): 94 | self._lib.set_max_iter(byref(c_int(value))) 95 | 96 | @property 97 | def use_compr_corr(self): 98 | """bool: True if compressibility correction should be used.""" 99 | return self._lib.get_use_compr_corr() 100 | 101 | @use_compr_corr.setter 102 | def use_compr_corr(self, value): 103 | self._lib.set_use_compr_corr(byref(c_bool(value))) 104 | 105 | @property 106 | def vrtx(self): 107 | """bool: True if Vortex Wake should be used, False if Graded Momentum should be used.""" 108 | return self._lib.get_vrtx() 109 | 110 | @vrtx.setter 111 | def vrtx(self, value): 112 | self._lib.set_vrtx(byref(c_bool(value))) 113 | 114 | @property 115 | def fast(self): 116 | """bool: True for Graded Momentum, False for Potential Formulation. Does not matter if vrtx == True.""" 117 | return self._lib.get_fast() 118 | 119 | @fast.setter 120 | def fast(self, value): 121 | self._lib.set_fast(byref(c_bool(value))) 122 | 123 | @property 124 | def n_stations(self): 125 | """int: Number of radial stations.""" 126 | return self._lib.get_number_of_stations() 127 | 128 | @n_stations.setter 129 | def n_stations(self, value): 130 | self._lib.set_number_of_stations(byref(c_int(value))) 131 | 132 | @property 133 | def case(self) -> Case: 134 | """Case: XRotor run case specification""" 135 | return self._case 136 | 137 | @case.setter 138 | def case(self, case: Case): 139 | self._case = case 140 | self._lib.set_case( 141 | byref(c_float(case.conditions.rho)), 142 | byref(c_float(case.conditions.vso)), 143 | byref(c_float(case.conditions.rmu)), 144 | byref(c_float(case.conditions.alt)), 145 | byref(c_float(case.conditions.vel)), 146 | byref(c_float(case.conditions.adv)), 147 | byref(c_float(case.disk.blade.geometry.r_hub)), 148 | byref(c_float(case.disk.blade.geometry.r_tip)), 149 | byref(c_float(case.disk.blade.geometry.r_wake)), 150 | byref(c_float(case.disk.blade.geometry.rake)), 151 | byref(c_int(case.disk.n_blds)), 152 | byref(c_int(case.disk.blade.geometry.n_geom)), 153 | np.asfortranarray(case.disk.blade.geomdata).ctypes.data_as(fptr), 154 | byref(c_int(case.disk.blade.n_polar_points.size)), 155 | np.asfortranarray(case.disk.blade.n_polar_points).ctypes.data_as(iptr), 156 | np.asfortranarray(case.disk.blade.xi_polars).ctypes.data_as(fptr), 157 | np.asfortranarray(case.disk.blade.polardata).ctypes.data_as(fptr), 158 | byref(c_bool(case.settings.free)), 159 | byref(c_bool(case.settings.duct)), 160 | byref(c_bool(case.settings.wind)) 161 | ) 162 | 163 | @property 164 | def performance(self) -> Performance: 165 | """Performance: Propeller performance specification""" 166 | perf = Performance() 167 | self._lib.get_performance( 168 | byref(perf._rpm), byref(perf._thrust), byref(perf._torque), byref(perf._power), byref(perf._efficiency) 169 | ) 170 | return perf 171 | 172 | @property 173 | def station_conditions(self): 174 | """(np.ndarray, np.ndarray): Normalized radial coordinates and corresponding local Reynolds and Mach numbers.""" 175 | n = self.n_stations 176 | xi = np.zeros(n, dtype=c_float, order='F') 177 | re = np.zeros(n, dtype=c_float, order='F') 178 | ma = np.zeros(n, dtype=c_float, order='F') 179 | cl = np.zeros(n, dtype=c_float, order='F') 180 | cd = np.zeros(n, dtype=c_float, order='F') 181 | cm = np.zeros(n, dtype=c_float, order='F') 182 | self._lib.get_station_conditions(byref(c_int(n)), 183 | xi.ctypes.data_as(fptr), re.ctypes.data_as(fptr), ma.ctypes.data_as(fptr), 184 | cl.ctypes.data_as(fptr), cd.ctypes.data_as(fptr), cm.ctypes.data_as(fptr)) 185 | return xi, re, ma, cl, cd, cm 186 | 187 | @property 188 | def rms(self): 189 | """float: The root-mean-squared error of the last XRotor analysis.""" 190 | return float(self._lib.get_rms()) 191 | 192 | def operate(self, thrust=None, torque=None, power=None, rpm=None): 193 | """Operate the propeller at a specified thrust, torque, power, or rpm. 194 | 195 | When only only one parameter is specified, the blade pitch will be left unchanged. 196 | If thrust, torque, or power is given along with rpm, the blade pitch will be varied and the rpm constrained. 197 | 198 | Parameters 199 | ---------- 200 | thrust : float 201 | Thrust in N 202 | torque : float 203 | Torque in Nm 204 | power : float 205 | Power in W 206 | rpm : float 207 | Rotational speed in rev/s 208 | 209 | Returns 210 | ------- 211 | rms : float 212 | Root-mean-squared error of after last XRotor iteration. 213 | blade_angle_change : float, optional 214 | Blade angle change in degrees, if the rpm is constrained for given thrust, torque, or power. 215 | """ 216 | if thrust is not None and torque is not None and power is not None: 217 | raise ValueError('Thrust, torque, and power cannot be specified simultaneously.') 218 | if thrust is not None and torque is not None: 219 | raise ValueError('Thrust and torque cannot be specified simultaneously.') 220 | if thrust is not None and power is not None: 221 | raise ValueError('Thrust and power cannot be specified simultaneously.') 222 | 223 | if thrust is not None: 224 | if rpm is not None: 225 | rms = self._lib.operate(byref(c_int(1)), byref(c_float(thrust)), byref(c_int(1)), byref(c_float(rpm))) 226 | blade_angle_change = self._lib.get_blade_angle_change() 227 | return rms, blade_angle_change 228 | else: 229 | return self._lib.operate(byref(c_int(1)), byref(c_float(thrust)), byref(c_int(2)), None) 230 | elif torque is not None: 231 | if rpm is not None: 232 | rms = self._lib.operate(byref(c_int(2)), byref(c_float(torque)), byref(c_int(1)), byref(c_float(rpm))) 233 | blade_angle_change = self._lib.get_blade_angle_change() 234 | return rms, blade_angle_change 235 | else: 236 | return self._lib.operate(byref(c_int(2)), byref(c_float(torque)), byref(c_int(2)), None) 237 | elif power is not None: 238 | if rpm is not None: 239 | rms = self._lib.operate(byref(c_int(3)), byref(c_float(power)), byref(c_int(1)), byref(c_float(rpm))) 240 | blade_angle_change = self._lib.get_blade_angle_change() 241 | return rms, blade_angle_change 242 | else: 243 | return self._lib.operate(byref(c_int(3)), byref(c_float(power)), byref(c_int(2)), None) 244 | elif rpm is not None: 245 | return self._lib.operate(byref(c_int(4)), byref(c_float(rpm)), None, None) 246 | else: 247 | raise ValueError('Neither thrust, torque, power, or rpm was provided.') 248 | 249 | def print_case(self): 250 | """Print the characteristics of the run case at the last operating point to the terminal.""" 251 | self._lib.show() 252 | 253 | --------------------------------------------------------------------------------