├── .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 |
--------------------------------------------------------------------------------