From c5786dc39c3131a9f730d86aca50b807a31ea1fc Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 09:30:27 +0100 Subject: [PATCH 01/53] add pyproject.toml to download packages prior to build --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c2948fb7c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools", + #"scikit-build>=0.13", + "cmake==3.14.3" + ] \ No newline at end of file From fb40764456ad25649d272ec3a7d5e23ee5b6f305 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 09:31:52 +0100 Subject: [PATCH 02/53] minimal env (only for people without low level libraries in system) --- environment.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 environment.yml diff --git a/environment.yml b/environment.yml new file mode 100644 index 000000000..d731b72c9 --- /dev/null +++ b/environment.yml @@ -0,0 +1,10 @@ +name: sharpy +channels: + - conda-forge + - defaults +dependencies: + - python=3.10 + - blas + - lapack + - libgcc + - libgfortran \ No newline at end of file From 70b8b183da7ebd0719c47600540a1b621ac48a50 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 09:34:11 +0100 Subject: [PATCH 03/53] less restricted cmake in the setup (>= added) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c2948fb7c..3f6f8a088 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,5 +2,5 @@ requires = [ "setuptools", #"scikit-build>=0.13", - "cmake==3.14.3" + "cmake>=3.14.3" ] \ No newline at end of file From 330ef4ea6314dc4eba82792d174e92d505f13ac9 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 10:45:39 +0100 Subject: [PATCH 04/53] add functions to setup to build submodules --- setup.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 06207ffc3..ce8ea96b6 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,79 @@ -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Extension, Command +#from skbuild import setup +from setuptools.command.build_ext import build_ext +import subprocess + import re import os -this_directory = os.path.abspath(os.path.dirname(__file__)) +class CMakeBuildExt(build_ext): + """Custom command to build Submodules packages during installation.""" + + def run(self): + + package_dir = os.path.dirname(os.path.abspath(__file__)) + build_dir = package_dir + "/build" + config_settings = getattr(self, 'config_settings', {}) + cmake_args = [] + if 'build_subm' in config_settings.keys() and config_settings['build_subm']=='no': + pass + else: + if not os.path.isdir(build_dir): + os.makedirs(build_dir) + subprocess.check_call( + ["cmake", ".."] + cmake_args, cwd=build_dir + ) + subprocess.check_call( + ["make", "install", "-j4"], cwd=build_dir + ) + + super().run() + +class BuildCommand(Command): + """Custom command to build Submodules packages without installation.""" + + description = 'Build Submodules in lib packages' + user_options = [ + ('cmake-args=', None, 'Additional CMake arguments'), + ] + + def initialize_options(self): + self.cmake_args = None + + def finalize_options(self): + pass + + def run(self): + # Run the CMake build step with additional cmake_args + package_dir = os.path.dirname(os.path.abspath(__file__)) + build_dir = package_dir + "/build" + if not os.path.isdir(build_dir): + os.makedirs(build_dir) + if self.cmake_args is not None: + subprocess.check_call( + ["cmake", f"{self.cmake_args}", ".."], cwd=build_dir + ) + else: + subprocess.check_call( + ["cmake", ".."], cwd=build_dir + ) + + subprocess.check_call( + ["make", "install", "-j4"], cwd=build_dir + ) + +ext_modules = [ + Extension('lib', []), + # Add more Extension instances for additional extension modules +] + + __version__ = re.findall( r"""__version__ = ["']+([0-9\.]*)["']+""", open(os.path.join(this_directory, "sharpy/version.py")).read(), )[0] +this_directory = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: long_description = f.read() @@ -25,6 +91,9 @@ author_email="", url="https://github.com/ImperialCollegeLondon/sharpy", license="BSD 3-Clause License", + ext_modules=ext_modules, + cmdclass={"build_ext": CMakeBuildExt, + "build_subm": BuildCommand}, packages=find_packages( where='./', include=['sharpy*'], From 216bd5ba4be9b73a170bc625608638d2f83d90e8 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 10:48:05 +0100 Subject: [PATCH 05/53] add required packages to pip install --- setup.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/setup.py b/setup.py index ce8ea96b6..f0032feac 100644 --- a/setup.py +++ b/setup.py @@ -99,8 +99,44 @@ def run(self): include=['sharpy*'], exclude=['tests'] ), + # data_files=[ + # ("./lib/UVLM/lib", ["libuvlm.so"]), + # ("./lib/xbeam/lib", ["libxbeam.so"]) + # ], + python_requires=">=3.8", install_requires=[ + "numpy", + "configobj", + "h5py", + "scipy", + "sympy", + "matplotlib", + "colorama", + "dill", + "jupyterlab", + "mayavi", #tvtk + "pandas", + "control", + # For pandas excel reader. + "openpyxl>=3.0.10", + "lxml>=4.4.1", + "PySocks", + "PyYAML" ], + extras_require={ + "docs": [ + "sphinx", + "recommonmark>=0.6.0", + "sphinx_rtd_theme>=0.4.3", + "nbsphinx>=0.4.3" + ], + "all": [ + "sphinx", + "recommonmark>=0.6.0", + "sphinx_rtd_theme>=0.4.3", + "nbsphinx>=0.4.3" + ], + }, classifiers=[ "Operating System :: Linux, Mac OS", "Programming Language :: Python, C++", From a9c319439c7f2ace8d7e888cc6b7d713bb3fa5bf Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Wed, 31 May 2023 14:41:08 +0100 Subject: [PATCH 06/53] remove unnecessary environments (leaving newer for reference) --- utils/environment_linux.yml | 170 ---------------------------------- utils/environment_macos.yml | 103 -------------------- utils/environment_minimal.yml | 104 --------------------- 3 files changed, 377 deletions(-) delete mode 100644 utils/environment_linux.yml delete mode 100644 utils/environment_macos.yml delete mode 100644 utils/environment_minimal.yml diff --git a/utils/environment_linux.yml b/utils/environment_linux.yml deleted file mode 100644 index 0834869ce..000000000 --- a/utils/environment_linux.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: sharpy_env -channels: - - conda-forge - - defaults -dependencies: - - _libgcc_mutex=0.1=main - - alabaster=0.7.12=py37_0 - - apptools=4.4.0=py37_1 - - asn1crypto=1.2.0=py37_0 - - attrs=19.3.0=py_0 - - babel=2.7.0=py_0 - - backcall=0.1.0=py37_0 - - bleach=3.1.0=py_0 - - bzip2=1.0.8=h7b6447c_0 - - ca-certificates=2020.12.5=ha878542_0 - - certifi=2020.12.5=py37h89c1867_1 - - cffi=1.13.1=py37h2e261b9_0 - - chardet=3.0.4=py37_1003 - - cmake=3.14.0=h52cb24c_0 - - colorama=0.4.1=py37_0 - - commonmark=0.9.0=py_0 - - configobj=5.0.6=py37_1 - - control=0.8.4=py37h89c1867_0 - - cryptography=2.8=py37h1ba5d50_0 - - curl=7.67.0=hbc83047_0 - - cycler=0.10.0=py37_0 - - dbus=1.13.12=h746ee38_0 - - decorator=4.4.1=py_0 - - defusedxml=0.6.0=py_0 - - dill=0.3.1.1=py37_0 - - docutils=0.15.2=py37_0 - - eigen=3.3.7=hfd86e86_0 - - entrypoints=0.3=py37_1000 - - envisage=4.8.0=py_0 - - expat=2.2.6=he6710b0_0 - - fontconfig=2.13.0=h9420a91_0 - - freetype=2.9.1=h8a8886c_1 - - future=0.18.2=py37_0 - - glib=2.63.1=h5a9c865_0 - - gst-plugins-base=1.14.0=hbbd80ab_1 - - gstreamer=1.14.0=hb453b48_1 - - h5py=2.9.0=nompi_py37hcafd542_1103 - - hdf4=4.2.13=h3ca952b_2 - - hdf5=1.10.4=hb1b8bf9_0 - - icu=58.2=h9c2bf20_1 - - idna=2.8=py37_0 - - imagesize=1.1.0=py37_0 - - importlib_metadata=0.23=py37_0 - - intel-openmp=2019.4=243 - - ipykernel=5.1.3=py37h39e3cac_0 - - ipython=7.10.1=py37h39e3cac_0 - - ipython_genutils=0.2.0=py_1 - - jedi=0.15.1=py37_0 - - jinja2=2.10.3=py_0 - - jpeg=9b=h024ee3a_2 - - json5=0.8.5=py_0 - - jsoncpp=1.8.4=hfd86e86_0 - - jsonschema=3.1.1=py37_0 - - jupyter_client=5.3.3=py37_1 - - jupyter_core=4.5.0=py_0 - - jupyterlab=1.2.3=py_0 - - jupyterlab_server=1.0.6=py_0 - - kiwisolver=1.1.0=py37he6710b0_0 - - krb5=1.16.1=h173b8e3_7 - - lapack=3.6.1=ha44fe06_2 - - libblas=3.8.0=14_openblas - - libcblas=3.8.0=14_openblas - - libcurl=7.67.0=h20c2e04_0 - - libedit=3.1.20181209=hc058e9b_0 - - libffi=3.2.1=hd88cf55_4 - - libgcc-ng=9.1.0=hdf63c60_0 - - libgfortran=3.0.0=1 - - libgfortran-ng=7.3.0=hdf63c60_0 - - liblapack=3.8.0=14_openblas - - libnetcdf=4.6.1=h11d0813_2 - - libogg=1.3.2=h7b6447c_0 - - libopenblas=0.3.7=h5ec1e0e_4 - - libpng=1.6.37=hbc83047_0 - - libsodium=1.0.16=h1bed415_0 - - libssh2=1.8.2=h1ba5d50_0 - - libstdcxx-ng=9.1.0=hdf63c60_0 - - libtheora=1.1.1=h5ab3b9f_1 - - libtiff=4.1.0=h2733197_0 - - libuuid=1.0.3=h1bed415_2 - - libvorbis=1.3.6=h7b6447c_0 - - libxcb=1.13=h1bed415_1 - - libxml2=2.9.9=hea5a465_1 - - libxslt=1.1.33=h7d1a2b0_0 - - lxml=4.4.1=py37hefd8a0e_0 - - lz4-c=1.8.1.2=h14c3975_0 - - markupsafe=1.1.1=py37h7b6447c_0 - - matplotlib=3.1.1=py37h5429711_0 - - matplotlib-base=3.1.3=py37hef1b27d_0 - - mayavi=4.7.1=py37h94891b3_2 - - mistune=0.8.4=py37h516909a_1000 - - more-itertools=7.2.0=py_0 - - nbconvert=5.6.1=py37_0 - - nbformat=4.4.0=py_1 - - nbsphinx=0.4.3=py_0 - - ncurses=6.1=he6710b0_1 - - notebook=6.0.1=py37_0 - - numpy=1.17.3=py37h95a1406_0 - - openssl=1.1.1i=h27cfd23_0 - - packaging=19.2=py_0 - - pandas=0.25.3=py37hb3f55d8_0 - - pandoc=2.7.3=0 - - pandocfilters=1.4.2=py_1 - - parso=0.5.1=py_0 - - pcre=8.43=he6710b0_0 - - pexpect=4.7.0=py37_0 - - pickleshare=0.7.5=py37_0 - - pip=19.3.1=py37_0 - - prometheus_client=0.7.1=py_0 - - prompt_toolkit=3.0.2=py_0 - - ptyprocess=0.6.0=py37_0 - - pycparser=2.19=py37_0 - - pyface=6.1.2=py37_0 - - pygments=2.4.2=py_0 - - pyopenssl=19.0.0=py37_0 - - pyparsing=2.4.4=py_0 - - pyqt=5.9.2=py37h05f1152_2 - - pyrsistent=0.15.5=py37h516909a_0 - - pysocks=1.7.1=py37_0 - - python=3.7.5=h0371630_0 - - python-dateutil=2.8.1=py_0 - - python_abi=3.7=1_cp37m - - pytz=2019.3=py_0 - - pyyaml=5.1.2=py37h7b6447c_0 - - pyzmq=18.1.0=py37he6710b0_0 - - qt=5.9.7=h5867ecd_1 - - readline=7.0=h7b6447c_5 - - recommonmark=0.6.0=py_0 - - requests=2.22.0=py37_0 - - rhash=1.3.8=h1ba5d50_0 - - scipy=1.3.2=py37h921218d_0 - - send2trash=1.5.0=py37_0 - - setuptools=41.6.0=py37_0 - - sip=4.19.8=py37hf484d3e_0 - - six=1.13.0=py37_0 - - snowballstemmer=2.0.0=py_0 - - sphinx=3.0.3=py_0 - - sphinx_rtd_theme=0.5.0=pyh9f0ad1d_0 - - sphinxcontrib-applehelp=1.0.1=py_0 - - sphinxcontrib-devhelp=1.0.1=py_0 - - sphinxcontrib-htmlhelp=1.0.2=py_0 - - sphinxcontrib-jsmath=1.0.1=py_0 - - sphinxcontrib-qthelp=1.0.2=py_0 - - sphinxcontrib-serializinghtml=1.1.3=py_0 - - sqlite=3.30.1=h7b6447c_0 - - tbb=2019.8=hfd86e86_0 - - terminado=0.8.3=py37_0 - - testpath=0.4.4=py_0 - - tk=8.6.8=hbc83047_0 - - tornado=6.0.3=py37h7b6447c_0 - - traitlets=4.3.3=py37_0 - - traits=5.2.0=py37h7b6447c_0 - - traitsui=6.1.3=py_0 - - urllib3=1.24.2=py37_0 - - vtk=8.2.0=py37haa4764d_200 - - wcwidth=0.1.7=py37_0 - - webencodings=0.5.1=py_1 - - wheel=0.33.6=py37_0 - - xlrd=1.2.0=py37_0 - - xz=5.2.4=h14c3975_4 - - yaml=0.1.7=had09818_2 - - zeromq=4.3.1=he6710b0_3 - - zipp=0.6.0=py_0 - - zlib=1.2.11=h7b6447c_3 - - zstd=1.3.7=h0b5b093_0 - - slycot=0.4.0.0=py37h27181d0_1 diff --git a/utils/environment_macos.yml b/utils/environment_macos.yml deleted file mode 100644 index 740bc301b..000000000 --- a/utils/environment_macos.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: sharpy_env -channels: - - conda-forge - - defaults -dependencies: - - _libgcc_mutex=0.1=main - - alabaster=0.7.12=py37_0 - - apptools=4.4.0=py37_1 - - blas=1.0=mkl - - bzip2=1.0.8=h01d97ff_1 - - ca-certificates=2020.10.14=0 - - certifi=2020.6.20=py37_0 - - cmake=3.14.0=haff7e42_0 - - colorama=0.4.1=py37_0 - - configobj=5.0.6=py37_1 - - control=0.8.4=py37hf985489_0 - - coverage=4.5.4=py37h1de35cc_0 - - curl=7.67.0=ha441bb4_0 - - cycler=0.10.0=py37_0 - - dbus=1.13.12=h90a0687_0 - - dill=0.3.1.1=py37_0 - - eigen=3.3.7=h04f5b5a_1000 - - envisage=4.8.0=py_0 - - expat=2.2.6=h0a44026_0 - - freetype=2.9.1=hb4e5f40_0 - - future=0.18.2=py37_0 - - gettext=0.19.8.1=h15daf44_3 - - glib=2.63.1=hd977a24_0 - - h5py=2.9.0=py37h3134771_0 - - hdf4=4.2.13=h39711bb_2 - - hdf5=1.10.4=hfa1e0ec_0 - - icu=58.2=h4b95b61_1 - - intel-openmp=2019.4=233 - - jpeg=9b=he5867d9_2 - - jsoncpp=1.8.4=h04f5b5a_0 - - kiwisolver=1.1.0=py37h0a44026_0 - - krb5=1.16.1=hddcf347_7 - - libcurl=7.67.0=h051b688_0 - - libcxx=4.0.1=hcfea43d_1 - - libcxxabi=4.0.1=hcfea43d_1 - - libedit=3.1.20181209=hb402a30_0 - - libffi=3.2.1=h475c297_4 - - libgfortran=3.0.1=h93005f0_2 - - libiconv=1.15=hdd342a3_7 - - libnetcdf=4.6.1=hd5207e6_2 - - libogg=1.3.2=h1de35cc_0 - - libopenblas=0.3.7=hd44dcd8_1 - - libpng=1.6.37=ha441bb4_0 - - libssh2=1.8.2=hcdc9a53_2 - - libtheora=1.1.1=hb4e5f40_1 - - libtiff=4.1.0=hcb84e12_0 - - libvorbis=1.3.6=h1de35cc_0 - - libxml2=2.9.9=hf6e021a_1 - - libxslt=1.1.33=h33a18ac_0 - - lxml=4.4.2=py37hef8c89e_0 - - lz4-c=1.8.1.2=h1de35cc_0 - - matplotlib=3.1.1=py37h54f8f79_0 - - matplotlib-base=3.1.1=py37h3a684a6_1 - - mayavi=4.6.2=py37hdde6e19_4 - - mkl=2019.4=233 - - mkl-include=2019.4=233 - - mkl-service=2.3.0=py37hfbe908c_0 - - mkl_fft=1.0.15=py37h5e564d8_0 - - mkl_random=1.1.0=py37ha771720_0 - - ncurses=6.1=h0a44026_1 - - numpy=1.17.4=py37h890c691_0 - - numpy-base=1.17.4=py37h6575580_0 - - openblas=0.3.7=hd44dcd8_1 - - openssl=1.1.1h=haf1e3a3_0 - - pandas=0.25.3=py37h0a44026_0 - - pcre=8.43=h0a44026_0 - - pip=19.3.1=py37_0 - - pyface=6.1.2=py37_0 - - pygments=2.5.2=py_0 - - pyparsing=2.4.5=py_0 - - pyqt=5.9.2=py37h655552a_2 - - python=3.7.5=h359304d_0 - - python-dateutil=2.8.1=py_0 - - python_abi=3.7=1_cp37m - - pytz=2019.3=py_0 - - pyyaml=5.3.1=py37haf1e3a3_1 - - qt=5.9.7=h468cd18_1 - - readline=7.0=h1de35cc_5 - - rhash=1.3.8=ha12b0ac_0 - - scipy=1.3.1=py37h1410ff5_0 - - setuptools=42.0.2=py37_0 - - sip=4.19.8=py37h0a44026_0 - - six=1.13.0=py37_0 - - slycot=0.3.5.0=py37h82d0005_0 - - sqlite=3.30.1=ha441bb4_0 - - tbb=2019.8=h04f5b5a_0 - - tk=8.6.8=ha441bb4_0 - - tornado=6.0.3=py37h1de35cc_0 - - traits=5.2.0=py37h1de35cc_0 - - traitsui=6.1.3=py_0 - - vtk=8.2.0=py37h9bafd54_200 - - wheel=0.33.6=py37_0 - - xlrd=1.2.0=py_0 - - xz=5.2.4=h1de35cc_4 - - yaml=0.2.5=haf1e3a3_0 - - zlib=1.2.11=h1de35cc_3 - - zstd=1.3.7=h5bba6e5_0 - diff --git a/utils/environment_minimal.yml b/utils/environment_minimal.yml deleted file mode 100644 index ea5841579..000000000 --- a/utils/environment_minimal.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: sharpy_minimal -channels: - - conda-forge - - defaults -dependencies: - - _libgcc_mutex=0.1=main - - apptools=4.5.0=py_0 - - bzip2=1.0.8=h7b6447c_0 - - ca-certificates=2020.12.5=ha878542_0 - - certifi=2020.12.5=py37h89c1867_1 - - cmake=3.14.0=h52cb24c_0 - - colorama=0.4.1=py37_0 - - configobj=5.0.6=py_0 - - control=0.8.4=py37h89c1867_0 - - curl=7.67.0=hbc83047_0 - - cycler=0.10.0=py_2 - - dbus=1.13.6=he372182_0 - - dill=0.3.1.1=py37_0 - - eigen=3.3.7=hc9558a2_1001 - - envisage=4.8.0=py_0 - - expat=2.2.6=he6710b0_0 - - fontconfig=2.13.1=he4413a7_1000 - - freetype=2.10.0=he983fc9_1 - - future=0.18.2=py37_0 - - gettext=0.19.8.1=hc5be6a0_1002 - - glib=2.58.3=py37h6f030ca_1002 - - gst-plugins-base=1.14.5=h0935bb2_0 - - gstreamer=1.14.5=h36ae1b5_0 - - h5py=2.9.0=py37h7918eee_0 - - hdf4=4.2.13=h3ca952b_2 - - hdf5=1.10.4=hb1b8bf9_0 - - icu=58.2=hf484d3e_1000 - - jpeg=9c=h14c3975_1001 - - jsoncpp=1.8.4=hfd86e86_0 - - kiwisolver=1.1.0=py37hc9558a2_0 - - krb5=1.16.1=h173b8e3_7 - - lapack=3.6.1=ha44fe06_2 - - libblas=3.8.0=14_openblas - - libcblas=3.8.0=14_openblas - - libcurl=7.67.0=h20c2e04_0 - - libedit=3.1.20181209=hc058e9b_0 - - libffi=3.2.1=hd88cf55_4 - - libgcc-ng=9.1.0=hdf63c60_0 - - libgfortran=3.0.0=1 - - libgfortran-ng=7.3.0=hdf63c60_2 - - libiconv=1.15=h516909a_1005 - - liblapack=3.8.0=14_openblas - - libnetcdf=4.6.1=h11d0813_2 - - libogg=1.3.2=h7b6447c_0 - - libopenblas=0.3.7=h5ec1e0e_4 - - libpng=1.6.37=hed695b0_0 - - libssh2=1.8.2=h1ba5d50_0 - - libstdcxx-ng=9.1.0=hdf63c60_0 - - libtheora=1.1.1=h5ab3b9f_1 - - libtiff=4.1.0=h2733197_0 - - libuuid=2.32.1=h14c3975_1000 - - libvorbis=1.3.6=h7b6447c_0 - - libxcb=1.13=h14c3975_1002 - - libxml2=2.9.9=hea5a465_1 - - libxslt=1.1.33=h7d1a2b0_0 - - lxml=4.4.2=py37hefd8a0e_0 - - lz4-c=1.8.1.2=h14c3975_0 - - matplotlib=3.1.1=py37h5429711_0 - - matplotlib-base=3.1.3=py37hef1b27d_0 - - mayavi=4.7.1=py37h94891b3_2 - - ncurses=6.1=he6710b0_1 - - numpy=1.17.3=py37h95a1406_0 - - openssl=1.1.1i=h27cfd23_0 - - pandas=0.25.3=py37hb3f55d8_0 - - pcre=8.43=he1b5a44_0 - - pip=19.3.1=py37_0 - - pthread-stubs=0.4=h14c3975_1001 - - pyface=6.1.2=py37_0 - - pygments=2.5.2=py_0 - - pyparsing=2.4.5=py_0 - - pyqt=5.9.2=py37hcca6a23_4 - - python=3.7.5=h0371630_0 - - python-dateutil=2.8.1=py_0 - - python_abi=3.7=1_cp37m - - pytz=2019.3=py_0 - - pyyaml=5.1.2=py37h7b6447c_0 - - qt=5.9.7=h52cfd70_2 - - readline=7.0=h7b6447c_5 - - rhash=1.3.8=h1ba5d50_0 - - scipy=1.3.2=py37h921218d_0 - - setuptools=42.0.2=py37_0 - - sip=4.19.8=py37hf484d3e_0 - - six=1.13.0=py37_0 - - slycot=0.4.0.0=py37h27181d0_1 - - sqlite=3.30.1=h7b6447c_0 - - tbb=2019.8=hfd86e86_0 - - tk=8.6.8=hbc83047_0 - - tornado=6.0.3=py37h516909a_0 - - traits=5.2.0=py37h7b6447c_0 - - traitsui=6.1.3=py_0 - - vtk=8.2.0=py37haa4764d_200 - - wheel=0.33.6=py37_0 - - xlrd=1.2.0=py37_0 - - xorg-libxau=1.0.9=h14c3975_0 - - xorg-libxdmcp=1.1.3=h516909a_0 - - xz=5.2.4=h14c3975_4 - - yaml=0.1.7=had09818_2 - - zlib=1.2.11=h7b6447c_3 - - zstd=1.3.7=h0b5b093_0 From 85a4475b424c4b7ccf15e03ab6f3954429f34b25 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Tue, 30 May 2023 12:16:41 +0100 Subject: [PATCH 07/53] add option, via environment variable, to not build submodules --- setup.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index f0032feac..4fa3fef23 100644 --- a/setup.py +++ b/setup.py @@ -8,14 +8,22 @@ class CMakeBuildExt(build_ext): """Custom command to build Submodules packages during installation.""" + + # def copy_extensions_to_source(self): + # "Override the method to prevent copying package files" + # pass + def finalize_options(self): + super().finalize_options() + # Process and use os.environ['CUSTOM_CONFIG_SETTINGS'] as needed + self.pip_nobuild = os.environ.get('PIP_NOBUILD') + def run(self): - + package_dir = os.path.dirname(os.path.abspath(__file__)) build_dir = package_dir + "/build" - config_settings = getattr(self, 'config_settings', {}) cmake_args = [] - if 'build_subm' in config_settings.keys() and config_settings['build_subm']=='no': + if self.pip_nobuild=="yes": pass else: if not os.path.isdir(build_dir): From d0ee1195f77540e10355f7e928c21f4d20e32a62 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Wed, 31 May 2023 07:36:56 +0100 Subject: [PATCH 08/53] fix to env for Ubuntu --- environment.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/environment.yml b/environment.yml index d731b72c9..70ee5a035 100644 --- a/environment.yml +++ b/environment.yml @@ -3,8 +3,13 @@ channels: - conda-forge - defaults dependencies: + - eigen + - libopenblas + - libblas + - libcblas + - liblapack + - libgomp + - libgfortran + - libgcc-ng + - libgfortran-ng - python=3.10 - - blas - - lapack - - libgcc - - libgfortran \ No newline at end of file From 780714b76c4f067ae51da8a1a930bcc162c4ea59 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Wed, 31 May 2023 14:55:00 +0100 Subject: [PATCH 09/53] fix wrong location of variable this_directory in merging conflicts --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4fa3fef23..43c371cf6 100644 --- a/setup.py +++ b/setup.py @@ -75,13 +75,12 @@ def run(self): # Add more Extension instances for additional extension modules ] - +this_directory = os.path.abspath(os.path.dirname(__file__)) __version__ = re.findall( r"""__version__ = ["']+([0-9\.]*)["']+""", open(os.path.join(this_directory, "sharpy/version.py")).read(), )[0] -this_directory = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: long_description = f.read() From a8d588471d8dbe4516bcb51beef67dbd2ea86ac3 Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Wed, 21 Jun 2023 07:53:52 +0100 Subject: [PATCH 10/53] simple compilation from run() method --- setup.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 43c371cf6..d8e2d5763 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,24 @@ def run(self): super().run() +def run(): + + pip_nobuild = os.environ.get('PIP_NOBUILD') + package_dir = os.path.dirname(os.path.abspath(__file__)) + build_dir = package_dir + "/build" + cmake_args = [] + if pip_nobuild=="yes": + pass + else: + if not os.path.isdir(build_dir): + os.makedirs(build_dir) + subprocess.check_call( + ["cmake", ".."] + cmake_args, cwd=build_dir + ) + subprocess.check_call( + ["make", "install", "-j4"], cwd=build_dir + ) + class BuildCommand(Command): """Custom command to build Submodules packages without installation.""" @@ -83,7 +101,7 @@ def run(self): with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: long_description = f.read() - +run() setup( name="sharpy", version=__version__, @@ -98,8 +116,8 @@ def run(self): author_email="", url="https://github.com/ImperialCollegeLondon/sharpy", license="BSD 3-Clause License", - ext_modules=ext_modules, - cmdclass={"build_ext": CMakeBuildExt, + #ext_modules=ext_modules, + cmdclass={#"build_ext": CMakeBuildExt, "build_subm": BuildCommand}, packages=find_packages( where='./', From 1b0f8afd50a0f90d0c4926b9e2985f3d19afaf5f Mon Sep 17 00:00:00 2001 From: Alvaro Cea Date: Wed, 28 Jun 2023 10:03:06 +0100 Subject: [PATCH 11/53] minor environment compatibility for macos --- environment.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index 70ee5a035..97bcf0db6 100644 --- a/environment.yml +++ b/environment.yml @@ -8,8 +8,7 @@ dependencies: - libblas - libcblas - liblapack - - libgomp - libgfortran - - libgcc-ng + - libgcc - libgfortran-ng - python=3.10 From 4c4fb486a6e1409a0050babf27dc6ae9e653ca31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Fri, 20 Oct 2023 22:09:41 +0200 Subject: [PATCH 12/53] fix [savedata] save of linear system related hdf5 file saves - fix intend otherwise these linear/rom systems would only be saved if no data.h5 file exist from the start - remove all files before it is written as overwriting them causes an Assertation error --- sharpy/postproc/savedata.py | 58 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/sharpy/postproc/savedata.py b/sharpy/postproc/savedata.py index 87ccd2df5..6c7e7e6e3 100644 --- a/sharpy/postproc/savedata.py +++ b/sharpy/postproc/savedata.py @@ -155,9 +155,7 @@ def initialise(self, data, custom_settings=None, caller=None, restart=False): self.filename_linear = self.folder + self.data.settings['SHARPy']['case'] + '.linss.h5' # remove old file if it exists - for file_path in [self.filename, self.filename_linear]: - if os.path.isfile(file_path): - os.remove(file_path) + self.remove_file_if_exist(self.filename) # check that there is a linear system - else return setting to false if self.settings['save_linear'] or self.settings['save_linear_uvlm']: @@ -204,6 +202,10 @@ def initialise(self, data, custom_settings=None, caller=None, restart=False): self.ClassesToSave += (sharpy.solvers.linearassembler.Linear, sharpy.linear.src.libss.ss_block) self.caller = caller + def remove_file_if_exist(self, filepath): + if os.path.isfile(filepath): + os.remove(filepath) + def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) @@ -247,33 +249,37 @@ def run(self, **kwargs): hdfile.close() - if self.settings['save_linear_uvlm']: - linhdffile = h5py.File(self.filename.replace('.data.h5', '.uvlmss.h5'), 'a') - h5utils.add_as_grp(self.data.linear.linear_system.uvlm.ss, linhdffile, grpname='ss', + if self.settings['save_linear_uvlm']: + linhdffile = h5py.File(self.filename.replace('.data.h5', '.uvlmss.h5'), 'a') + self.remove_file_if_exist(linhdffile) + h5utils.add_as_grp(self.data.linear.linear_system.uvlm.ss, linhdffile, grpname='ss', + ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], + compress_float=self.settings['compress_float']) + h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linhdffile, + grpname='linearisation_vectors', + ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], + compress_float=self.settings['compress_float']) + linhdffile.close() + + if self.settings['save_linear']: + self.remove_file_if_exist(self.filename_linear) + with h5py.File(self.filename_linear, 'a') as linfile: + h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linfile, + grpname='linearisation_vectors', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) - h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linhdffile, - grpname='linearisation_vectors', + h5utils.add_as_grp(self.data.linear.ss, linfile, grpname='ss', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) - linhdffile.close() - - if self.settings['save_linear']: - with h5py.File(self.filename_linear, 'a') as linfile: - h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linfile, - grpname='linearisation_vectors', - ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], - compress_float=self.settings['compress_float']) - h5utils.add_as_grp(self.data.linear.ss, linfile, grpname='ss', - ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], - compress_float=self.settings['compress_float']) - - if self.settings['save_rom']: - try: - for k, rom in self.data.linear.linear_system.uvlm.rom.items(): - rom.save(self.filename.replace('.data.h5', '_{:s}.rom.h5'.format(k.lower()))) - except AttributeError: - cout.cout_wrap('Could not locate a reduced order model to save') + + if self.settings['save_rom']: + try: + for k, rom in self.data.linear.linear_system.uvlm.rom.items(): + romhdffile = self.filename.replace('.data.h5', '_{:s}.rom.h5'.format(k.lower())) + self.remove_file_if_exist(romhdffile) + rom.save(romhdffile) + except AttributeError: + cout.cout_wrap('Could not locate a reduced order model to save') elif self.settings['format'] == 'mat': from scipy.io import savemat From 3bdabedeccf2fe6064bc04ff7f307fbee9c0456e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Tue, 7 Nov 2023 13:49:37 +0000 Subject: [PATCH 13/53] fix [gust] type and handling of gust component definitions --- sharpy/generators/gustvelocityfield.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sharpy/generators/gustvelocityfield.py b/sharpy/generators/gustvelocityfield.py index e13157a53..812f3c51e 100644 --- a/sharpy/generators/gustvelocityfield.py +++ b/sharpy/generators/gustvelocityfield.py @@ -89,8 +89,8 @@ class one_minus_cos(BaseGust): settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust :math:`u_{de}`.' - settings_types['gust_component'] = 'int' - settings_default['gust_component'] = 2 + settings_types['gust_component'] = 'list(int)' + settings_default['gust_component'] = [2] settings_description['gust_component'] = 'Component of the gust velocity in the G-frame (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() @@ -258,7 +258,7 @@ def initialise_interpolation_functions(self): def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) - for counter, idim in enumerate(self.settings['gust_component']): + for counter, idim in enumerate(list(self.settings['gust_component'])): vel[idim] = self.list_interpolated_velocity_field_functions[counter](time) return vel @@ -283,7 +283,7 @@ def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) d = np.dot(np.array([x, y, z]), self.u_inf_direction) if d <= 0.0: - for counter, idim in enumerate(self.settings['gust_component']): + for counter, idim in enumerate(list(self.settings['gust_component'])): vel[idim] = self.list_interpolated_velocity_field_functions[counter](d) return vel From bf3e2959facf22bd4b34ee7853d62243ba4190b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Tue, 7 Nov 2023 17:15:51 +0000 Subject: [PATCH 14/53] Revert "fix [gust] type and handling of gust component definitions" This reverts commit 3bdabedeccf2fe6064bc04ff7f307fbee9c0456e. --- sharpy/generators/gustvelocityfield.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sharpy/generators/gustvelocityfield.py b/sharpy/generators/gustvelocityfield.py index 812f3c51e..e13157a53 100644 --- a/sharpy/generators/gustvelocityfield.py +++ b/sharpy/generators/gustvelocityfield.py @@ -89,8 +89,8 @@ class one_minus_cos(BaseGust): settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust :math:`u_{de}`.' - settings_types['gust_component'] = 'list(int)' - settings_default['gust_component'] = [2] + settings_types['gust_component'] = 'int' + settings_default['gust_component'] = 2 settings_description['gust_component'] = 'Component of the gust velocity in the G-frame (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() @@ -258,7 +258,7 @@ def initialise_interpolation_functions(self): def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) - for counter, idim in enumerate(list(self.settings['gust_component'])): + for counter, idim in enumerate(self.settings['gust_component']): vel[idim] = self.list_interpolated_velocity_field_functions[counter](time) return vel @@ -283,7 +283,7 @@ def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) d = np.dot(np.array([x, y, z]), self.u_inf_direction) if d <= 0.0: - for counter, idim in enumerate(list(self.settings['gust_component'])): + for counter, idim in enumerate(self.settings['gust_component']): vel[idim] = self.list_interpolated_velocity_field_functions[counter](d) return vel From db36db53660f24cd4e3c6cd2eb78d8d5faea462b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Wed, 6 Dec 2023 16:51:33 +0000 Subject: [PATCH 15/53] fix [modalutils] number of modes to be exported as vtk In the previous version, less modes are extracted for free flying aircraft because number of rigid body DoFs are wrongly substracted from the total number of modes although the first modes (which are the rigid body modes) are already skipped. Thus if 30 modes are to be exported only 10 mode shapes are created. --- sharpy/structure/utils/modalutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharpy/structure/utils/modalutils.py b/sharpy/structure/utils/modalutils.py index f16353c26..b7698cb86 100644 --- a/sharpy/structure/utils/modalutils.py +++ b/sharpy/structure/utils/modalutils.py @@ -283,7 +283,7 @@ def write_modes_vtk(data, eigenvectors, NumLambda, filename_root, else: num_rigid_body = 0 - for mode in range(num_rigid_body, NumLambda - num_rigid_body): + for mode in range(num_rigid_body, NumLambda): # scale eigenvector eigvec = eigenvectors[:num_dof, mode] fact = scale_mode(data, eigvec, rot_max_deg, perc_max) From ecf6d60258e3d984a806cd07b86dc7ca6fbb94ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Fri, 20 Oct 2023 16:44:37 +0200 Subject: [PATCH 16/53] fix [udp tutorial] jupyter notebook meta data - caused error in opening the jupyter notebook and for the tutorial not to be displayed in the readthedocs documentation --- .../example_notebooks/UDP_control/tutorial_udp_control.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/source/content/example_notebooks/UDP_control/tutorial_udp_control.ipynb b/docs/source/content/example_notebooks/UDP_control/tutorial_udp_control.ipynb index b0205753d..28948292f 100644 --- a/docs/source/content/example_notebooks/UDP_control/tutorial_udp_control.ipynb +++ b/docs/source/content/example_notebooks/UDP_control/tutorial_udp_control.ipynb @@ -2368,9 +2368,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Run SHARPy Simulation" - "execution_count": 22, - ] + "### Run SHARPy Simulation"] }, { "cell_type": "code", From 261204aa011b57dca1a0de94b79ae3e82ac63c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Mon, 18 Dec 2023 13:03:32 +0000 Subject: [PATCH 17/53] remove [postproc] left over prints from debugging --- sharpy/postproc/aeroforcescalculator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sharpy/postproc/aeroforcescalculator.py b/sharpy/postproc/aeroforcescalculator.py index 219b0f783..c13ac60b3 100644 --- a/sharpy/postproc/aeroforcescalculator.py +++ b/sharpy/postproc/aeroforcescalculator.py @@ -132,11 +132,8 @@ def calculate_forces(self, ts): self.data.aero.timestep_info[ts].body_unsteady_forces[i_surf, 0:3] ) = self.calculate_forces_for_isurf_in_g_frame(force[i_surf], unsteady_force=unsteady_force[i_surf]) - print(self.settings["nonlifting_body"]) if self.settings["nonlifting_body"]: - print(self.data.nonlifting_body.n_surf) for i_surf in range(self.data.nonlifting_body.n_surf): - print(i_surf) ( self.data.nonlifting_body.timestep_info[ts].inertial_steady_forces[i_surf, 0:3], self.data.nonlifting_body.timestep_info[ts].body_steady_forces[i_surf, 0:3], From 501f73e816d49007cbf33505ada99598ead95419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefanie=20D=C3=BCssler?= Date: Mon, 18 Dec 2023 13:43:08 +0000 Subject: [PATCH 18/53] remove [postproc] unused columns from exported force and moment matrices - before it creates an output file with extra columns without any values --- sharpy/postproc/aeroforcescalculator.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sharpy/postproc/aeroforcescalculator.py b/sharpy/postproc/aeroforcescalculator.py index c13ac60b3..cb6833ff3 100644 --- a/sharpy/postproc/aeroforcescalculator.py +++ b/sharpy/postproc/aeroforcescalculator.py @@ -233,8 +233,8 @@ def screen_output(self, ts): def file_output(self, filename): # assemble forces/moments matrix # (1 timestep) + (3+3 inertial steady+unsteady) + (3+3 body steady+unsteady) - force_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3 + 3 + 3)) - moment_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3 + 3 + 3)) + force_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3 )) + moment_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3)) for ts in range(self.ts_max): aero_tstep = self.data.aero.timestep_info[ts] i = 0 @@ -268,12 +268,10 @@ def file_output(self, filename): header += 'fx_unsteady_G, fy_unsteady_G, fz_unsteady_G, ' header += 'fx_steady_a, fy_steady_a, fz_steady_a, ' header += 'fx_unsteady_a, fy_unsteady_a, fz_unsteady_a' - header += 'mx_total_G, my_total_G, mz_total_G' - header += 'mx_total_a, my_total_a, mz_total_a' np.savetxt(self.folder + 'forces_' + filename, force_matrix, - fmt='%i' + ', %10e'*18, + fmt='%i' + ', %10e' * (np.shape(force_matrix)[1] - 1), delimiter=',', header=header, comments='#') @@ -287,7 +285,7 @@ def file_output(self, filename): np.savetxt(self.folder + 'moments_' + filename, moment_matrix, - fmt='%i' + ', %10e'*18, + fmt='%i' + ', %10e' * (np.shape(moment_matrix)[1] - 1), delimiter=',', header=header, comments='#') From d03a99bafd2de7f457bb1f885cb34716c5d75054 Mon Sep 17 00:00:00 2001 From: kccwing <60852830+kccwing@users.noreply.github.com> Date: Sat, 20 Jan 2024 01:18:39 +0800 Subject: [PATCH 19/53] Update noaero.py for consistency in function input NoAero errors out because of missing optional input - fixed --- sharpy/solvers/noaero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharpy/solvers/noaero.py b/sharpy/solvers/noaero.py index 5a18a68d9..562bbe3a4 100644 --- a/sharpy/solvers/noaero.py +++ b/sharpy/solvers/noaero.py @@ -78,7 +78,7 @@ def update_grid(self, beam): -1, beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep): + def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep = None): # called by DynamicCoupled if self.settings['update_grid']: self.data.aero.generate_zeta_timestep_info(structure_tstep, From fe0c5d7e0a8c3fb88f4eefbcca6fef3d260235ee Mon Sep 17 00:00:00 2001 From: Sam Lane Date: Thu, 25 Jan 2024 10:16:00 +0000 Subject: [PATCH 20/53] Add scipy version info to env yml --- utils/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/environment.yml b/utils/environment.yml index f6c7afba7..3f5def727 100644 --- a/utils/environment.yml +++ b/utils/environment.yml @@ -17,6 +17,7 @@ dependencies: - PySocks>=1.7.1 - PyYAML>=5.1.2 - recommonmark>=0.6.0 + - scipy>=1.11.4,<1.12 - slycot>=0.4.0 - sphinx_rtd_theme>=0.4.3 - wheel>=0.33.6 From 6529a2659887cf8b1abee5823d8e47806d0f295b Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:07:07 +0000 Subject: [PATCH 21/53] Added documentation for pip install Also fixed an issue with SHARPy not running due to a package import error. --- docs/source/content/installation.md | 90 +++++++++++++++++++++-------- sharpy/generators/floatingforces.py | 3 +- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index 72912462c..0128ac81b 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -1,7 +1,7 @@ -# SHARPy v2.0 Installation Guide -__Last revision 9 October 2023__ +# SHARPy v2.2 Installation Guide +__Last revision 26 January 2024__ -The following step by step tutorial will guide you through the installation process of SHARPy. This is the updated process valid from v2.0. +The following step by step tutorial will guide you through the installation process of SHARPy. This is the updated process valid from v2.2. ## Requirements @@ -54,9 +54,58 @@ once you initialise SHARPy you will also automatically clone the relevant versio 2. We will now set up the SHARPy environment that will install other required distributions. -### Setting up the Python Environment +### Setting up the Python Environment (Standalone) -SHARPy uses the Anaconda package manager to provide the necessary Python packages. +SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). + +1. Check that your Python version is 3.10. Higher versions may be incompatible with the required modules. + ```bash + python --version + ``` + +### Quick install +The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. +1. Move into the cloned repository: + ```bash + cd sharpy + ``` + +2. Create a directory `build` that will be used during CMake's building process and `cd` into it. + Ensure it is located in the main ./sharpy folder otherwise the following steps won't work: + ```bash + mkdir build + cd build + ``` + +3. Prepare UVLM and xbeam for compilation using `gfortran` and `g++` in their release builds running. If you'd like to + change compilers see the Custom Installation. + ```bash + cmake .. + ``` + +4. Compile the libraries. + ```bash + make install -j 4 + ``` + where the number after the `-j` flag will specify how many cores to use during installation. + This should take approximately 2 minutes (Tested on MacOS Sonoma). + +7. Finally, leave the build directory and install SHARPy: + ```bash + cd .. + pip install . + ``` + +8. You can check the version of SHARPy you are running with: + ```bash + sharpy --version + ``` + +__You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running-sharpy) section. + +### Setting up the Python Environment (Anaconda) + +SHARPy can use the Anaconda package manager to provide the necessary Python packages. These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries or running any SHARPy cases. @@ -90,8 +139,7 @@ or running any SHARPy cases. ### Quick install -The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. If -you would like to install a develop build or modify the compilation settings of the libraries skip to the next section. +The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. 1. Move into the cloned repository: ```bash cd sharpy @@ -125,7 +173,7 @@ you would like to install a develop build or modify the compilation settings of make install -j 4 ``` where the number after the `-j` flag will specify how many cores to use during installation. - This should take approximately 5 minutes (Tested on Ubuntu 22.04.1). + This should take approximately 2 minutes (Tested on MacOS Sonoma). 7. Finally, leave the build directory and install SHARPy: ```bash @@ -138,12 +186,17 @@ you would like to install a develop build or modify the compilation settings of sharpy --version ``` +If running SHARPy from Anaconda, you need to load the conda environment. Therefore, __before you run any SHARPy case or test__, activate the SHARPy conda environment: +```bash +conda activate sharpy +``` + __You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running-sharpy) section. ### Custom installation These steps will show you how to compile the xbeam and UVLM libraries such that you can modify the compilation settings -to your taste. +to your taste. This is compatible with both standalone and Anaconda installations. 1. If you want to use SHARPy's latest release, skip this step. If you would like to use the latest development work, you will need to checkout the `develop` branch. For more info on how we structure our development and what branches @@ -154,14 +207,13 @@ to your taste. ``` This command will check out the `develop` branch and set it to track the remote origin. It will also set the submodules (xbeam and UVLM) to the right commit. -2. Create the conda environment that SHARPy will use: +2. If using Anaconda, create the conda environment that SHARPy will use and activate the environment: ```bash cd sharpy/utils conda env create -f environment.yml cd ../.. ``` -3. Activate the `sharpy` conda environment: ```bash conda activate sharpy ``` @@ -276,18 +328,8 @@ python -m unittest **Enjoy!** - ## Running SHARPy -In order to run SHARPy, you need to load the conda environment. Therefore, __before you run any SHARPy case__: - -1. Activate the SHARPy conda environment - ```bash - conda activate sharpy - ``` - -You are now ready to run SHARPy cases from the terminal. - ### Automated tests SHARPy uses unittests to verify the integrity of the code. @@ -341,12 +383,12 @@ SHARPy cases are therefore usually ran in the following way: 2. Run it to produce the `.h5` files and the `.sharpy` files. ```bash - (sharpy_env) python generate_case.py + python generate_case.py ``` 3. Run SHARPy (ensure the environment is activated). ```bash - (sharpy_env) sharpy case.sharpy + sharpy case.sharpy ``` #### Output @@ -381,7 +423,6 @@ is stored in [HDF5](https://support.hdfgroup.org/HDF5/) format, which is compres The `sharpy` call is: ```bash - # Make sure that the sharpy_env conda environment is active sharpy ``` @@ -409,7 +450,6 @@ is stored in [HDF5](https://support.hdfgroup.org/HDF5/) format, which is compres You are now ready to run the case again: ```bash - # Make sure that the sharpy_env conda environment is active sharpy ``` diff --git a/sharpy/generators/floatingforces.py b/sharpy/generators/floatingforces.py index c296d97f7..fa3a6735b 100644 --- a/sharpy/generators/floatingforces.py +++ b/sharpy/generators/floatingforces.py @@ -2,7 +2,8 @@ import h5py as h5 import ctypes as ct import os -from scipy import fft, ifft +from scipy import fft +from scipy.fftpack import ifft from scipy.interpolate import interp1d from control import forced_response, TransferFunction From f390ea04dcf8f0bb54f6b82c2c857653fead45a6 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:18:30 +0000 Subject: [PATCH 22/53] Remove conda/mambda from Github workflow --- .github/workflows/sharpy_tests.yaml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/sharpy_tests.yaml b/.github/workflows/sharpy_tests.yaml index 5b8b1d546..543f39f48 100644 --- a/.github/workflows/sharpy_tests.yaml +++ b/.github/workflows/sharpy_tests.yaml @@ -31,26 +31,11 @@ jobs: with: version: 10 platform: x64 - #Swapped from Conda to Mamba due to Github runner memory constraint - - name: Setup Mamba - uses: mamba-org/setup-micromamba@v1 - name: Pre-Install dependencies run: | gfortran --version - wget --no-check-certificate https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -O mamba.sh - bash mamba.sh -b -p $HOME/mamba - export PATH="$HOME/mamba/bin:$PATH" - hash -r - mamba config --set always_yes yes --set changeps1 no - mamba update --name base mamba - mamba list --name base mamba - mamba init bash - hash -r export QT_QPA_PLATFORM='offscreen' sudo apt install libeigen3-dev - mamba env create -f utils/environment.yml - mamba init bash - source activate sharpy git submodule init git submodule update git fetch --tags -f From 4514deaa91015d34d0357f21d79fe3fc611d8673 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:28:07 +0000 Subject: [PATCH 23/53] Update dockerfile to remove mamba/conda dependency --- Dockerfile | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index e6ca1d9df..23982c96c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,6 @@ FROM centos:8 ENV PYTHONDONTWRITEBYTECODE=true ENV BASH_ENV ~/.bashrc SHELL ["/bin/bash", "-c"] -ENV PATH=${PATH}:/mamba/bin # CENTOS 8 has reached end of life - Not yet an updated Docker base for CentOS stream # Point to the CentOS 8 vault in order to download dependencies @@ -17,32 +16,12 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all -# Install Mamba -# Swapped from Conda to Mamba due to Github runner memory constraint -RUN wget --no-check-certificate https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -O /mamba.sh && \ - chmod +x /mamba.sh && \ - /mamba.sh -b -p /mamba/ && \ - rm /mamba.sh && hash -r - ADD / /sharpy_dir/ -# Update mamba and make it run with no user interaction -# Cleanup mamba installation -RUN mamba init bash -RUN mamba config --set always_yes yes --set changeps1 no -RUN mamba update -q conda -RUN mamba config --set auto_activate_base false -RUN mamba env create -f /sharpy_dir/utils/environment.yml && mamba clean -afy && \ - - find /mamba/ -follow -type f -name '*.a' -delete && \ - find /mamba/ -follow -type f -name '*.pyc' -delete && \ - find /mamba/ -follow -type f -name '*.js.map' -delete - #COPY /utils/docker/* /root/ RUN ln -s /sharpy_dir/utils/docker/* /root/ RUN cd sharpy_dir && \ - mamba activate sharpy && \ git submodule update --init --recursive && \ mkdir build && \ cd build && \ From 52a282e7be5492a4abf628e2e42cffca1609d0c7 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:34:13 +0000 Subject: [PATCH 24/53] Added CMake install to Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 23982c96c..872503f05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN cd /etc/yum.repos.d/ && \ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all +RUN apt-get update && apt-get -y install cmake ADD / /sharpy_dir/ From fccaf87b37f5f76d9a951c1b8892468851c0415b Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:37:31 +0000 Subject: [PATCH 25/53] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 872503f05..7178de850 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN cd /etc/yum.repos.d/ && \ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all -RUN apt-get update && apt-get -y install cmake +RUN yum update && yum -y install cmake ADD / /sharpy_dir/ From de8c9ec712989cf41b1161eef0e2ef476c4acedd Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:40:26 +0000 Subject: [PATCH 26/53] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7178de850..e52ab8993 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN cd /etc/yum.repos.d/ && \ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all -RUN yum update && yum -y install cmake +RUN yum -y update && yum -y install cmake ADD / /sharpy_dir/ From 4b853cf03b3f6c9e23c92ff09d7c3f9ffb422c4b Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:43:15 +0000 Subject: [PATCH 27/53] Update Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index e52ab8993..b0cd4bbf2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all RUN yum -y update && yum -y install cmake +RUN yum install lapack ADD / /sharpy_dir/ From fa2814a417b23b3488f28ea93bcf5ad007b92821 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:46:29 +0000 Subject: [PATCH 28/53] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b0cd4bbf2..f11b096a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all RUN yum -y update && yum -y install cmake -RUN yum install lapack +RUN yum install libblas-dev libblas3 ADD / /sharpy_dir/ From 75e3060dacb791bd6bae9464e6b85589f455a2b2 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Fri, 26 Jan 2024 10:50:21 +0000 Subject: [PATCH 29/53] Update Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f11b096a0..496207299 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,9 @@ RUN cd /etc/yum.repos.d/ && \ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all + RUN yum -y update && yum -y install cmake -RUN yum install libblas-dev libblas3 +RUN yum install blas ADD / /sharpy_dir/ From 64e1e68e6d7c1fffd70d0e37a428b1ec95bfecf1 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:31:06 +0000 Subject: [PATCH 30/53] Update Dockerfile --- Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 496207299..3ce372ed8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,6 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum clean all RUN yum -y update && yum -y install cmake -RUN yum install blas ADD / /sharpy_dir/ @@ -33,5 +32,4 @@ RUN cd sharpy_dir && \ pip install . && \ rm -rf build -ENTRYPOINT ["/bin/bash", "--init-file", "/root/bashrc"] - +ENTRYPOINT ["/bin/bash", "--init-file", "/root/bashrc"] \ No newline at end of file From c986f46b9b835788478132ebfaf97ae546f748ce Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:34:24 +0000 Subject: [PATCH 31/53] Update Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 3ce372ed8..145074270 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all +RUN yum install libopenblas RUN yum -y update && yum -y install cmake ADD / /sharpy_dir/ From b32b3a48b23e74647376b7eb29be025db870bec8 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:38:12 +0000 Subject: [PATCH 32/53] Update Dockerfile --- Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 145074270..d951fcee0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,12 +13,9 @@ RUN cd /etc/yum.repos.d/ && \ # Development tools including compilers RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ - yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ + yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack blas vim tmux cmake && \ yum clean all -RUN yum install libopenblas -RUN yum -y update && yum -y install cmake - ADD / /sharpy_dir/ #COPY /utils/docker/* /root/ From c1bbc79f17f5d3d3102d1bc900ef946de644185e Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:43:04 +0000 Subject: [PATCH 33/53] Update Dockerfile --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d951fcee0..550a6d657 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,12 @@ RUN cd /etc/yum.repos.d/ && \ # Development tools including compilers RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ - yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack blas vim tmux cmake && \ + yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all +RUN yum localinstall 'https://centos.pkgs.org/7/centos-x86_64/blas-3.4.2-8.el7.x86_64.rpm' + + ADD / /sharpy_dir/ #COPY /utils/docker/* /root/ From 00ceec2a8c7e2aa459a159429cc0b1a2be0d7ca1 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:46:42 +0000 Subject: [PATCH 34/53] Update Dockerfile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 550a6d657..95e889998 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all -RUN yum localinstall 'https://centos.pkgs.org/7/centos-x86_64/blas-3.4.2-8.el7.x86_64.rpm' - +RUN yum install openblas-devel.x86_64 -y ADD / /sharpy_dir/ From 3f922bc57087834995d44c4b577d4136a0ed0e72 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:49:59 +0000 Subject: [PATCH 35/53] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 95e889998..9d3c876f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all -RUN yum install openblas-devel.x86_64 -y +RUN yum install openblas.x86_64 -y ADD / /sharpy_dir/ From 47cd9798c002e7fdda617a1815555c85abd3c869 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 16:56:58 +0000 Subject: [PATCH 36/53] Update Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9d3c876f5..4f21337d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,8 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all -RUN yum install openblas.x86_64 -y +RUN yum-config-manager --add-repo https://yum.repos.intel.com/mkl/setup/intel-mkl.repo && \ + yum install intel-mkl-2020.0-088 ADD / /sharpy_dir/ From da725cec8460e3594b5bb322fdea4caea945cca3 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 17:01:53 +0000 Subject: [PATCH 37/53] Update Dockerfile --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4f21337d3..5d5c0963c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,10 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all -RUN yum-config-manager --add-repo https://yum.repos.intel.com/mkl/setup/intel-mkl.repo && \ - yum install intel-mkl-2020.0-088 +RUN yum install yum-utils + +RUN yum config-manager --add-repo https://yum.repos.intel.com/mkl/setup/intel-mkl.repo +RUN yum install intel-mkl-2020.0-088 ADD / /sharpy_dir/ From 03a4d0f1c7505c3b718679842c315e5345052d8a Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 30 Jan 2024 17:03:43 +0000 Subject: [PATCH 38/53] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5d5c0963c..ae5be5e42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,10 +16,10 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ yum clean all -RUN yum install yum-utils +RUN yum install -y yum-utils RUN yum config-manager --add-repo https://yum.repos.intel.com/mkl/setup/intel-mkl.repo -RUN yum install intel-mkl-2020.0-088 +RUN yum install -y intel-mkl-2020.0-088 ADD / /sharpy_dir/ From 226a6b730576b623d549250c49503a5efb796d72 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Thu, 8 Feb 2024 13:59:33 +0000 Subject: [PATCH 39/53] Return Dockerfile to using conda/mamba --- Dockerfile | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index ae5be5e42..2b65297bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM centos:8 ENV PYTHONDONTWRITEBYTECODE=true ENV BASH_ENV ~/.bashrc SHELL ["/bin/bash", "-c"] +ENV PATH=${PATH}:/mamba/bin # CENTOS 8 has reached end of life - Not yet an updated Docker base for CentOS stream # Point to the CentOS 8 vault in order to download dependencies @@ -13,20 +14,35 @@ RUN cd /etc/yum.repos.d/ && \ # Development tools including compilers RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ - yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux cmake && \ + yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all -RUN yum install -y yum-utils - -RUN yum config-manager --add-repo https://yum.repos.intel.com/mkl/setup/intel-mkl.repo -RUN yum install -y intel-mkl-2020.0-088 +# Install Mamba +# Swapped from Conda to Mamba due to Github runner memory constraint +RUN wget --no-check-certificate https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -O /mamba.sh && \ + chmod +x /mamba.sh && \ + /mamba.sh -b -p /mamba/ && \ + rm /mamba.sh && hash -r ADD / /sharpy_dir/ +# Update mamba and make it run with no user interaction +# Cleanup mamba installation +RUN mamba init bash +RUN mamba config --set always_yes yes --set changeps1 no +RUN mamba update -q conda +RUN mamba config --set auto_activate_base false +RUN mamba env create -f /sharpy_dir/utils/environment.yml && mamba clean -afy && \ + + find /mamba/ -follow -type f -name '*.a' -delete && \ + find /mamba/ -follow -type f -name '*.pyc' -delete && \ + find /mamba/ -follow -type f -name '*.js.map' -delete + #COPY /utils/docker/* /root/ RUN ln -s /sharpy_dir/utils/docker/* /root/ RUN cd sharpy_dir && \ + mamba activate sharpy && \ git submodule update --init --recursive && \ mkdir build && \ cd build && \ From eb32df50a0d378277b0e3c13e25b84ae4c2bda67 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Thu, 8 Feb 2024 14:10:19 +0000 Subject: [PATCH 40/53] Update installation.md --- docs/source/content/installation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index 0128ac81b..053b2f39e 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -18,7 +18,7 @@ Windows users can also run it by first installing the Windows Subsystem for Linu __Required Distributions__ -* Anaconda Python 3.10 +* Python 3.10 (compatility with other versions is not guaranteed) * GCC 6.0 or higher (recommended). C++ and Fortran. __Recommended Software__ @@ -63,7 +63,7 @@ SHARPy can be installed as a standalone package, without the use of a package ma python --version ``` -### Quick install +### Quick install (Standalone) The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. 1. Move into the cloned repository: ```bash @@ -138,7 +138,7 @@ or running any SHARPy cases. some dependencies are included in the conda environment. You should now see ```(sharpy)``` on your command line. -### Quick install +### Quick install (Anaconda) The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. 1. Move into the cloned repository: ```bash From ce51ac74cd98783ae24c60aa902a3011f1d0a75f Mon Sep 17 00:00:00 2001 From: Ben Preston <144227999+ben-l-p@users.noreply.github.com> Date: Sun, 11 Feb 2024 12:53:45 +0000 Subject: [PATCH 41/53] Added note about different pip install options --- docs/source/content/installation.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index 053b2f39e..7abef6626 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -54,49 +54,48 @@ once you initialise SHARPy you will also automatically clone the relevant versio 2. We will now set up the SHARPy environment that will install other required distributions. -### Setting up the Python Environment (Standalone) +### Quick install (Standalone) -SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). +SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. -1. Check that your Python version is 3.10. Higher versions may be incompatible with the required modules. +1. Check that your Python version is 3.10. Other versions may be incompatible with the required modules. ```bash python --version ``` - -### Quick install (Standalone) -The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. -1. Move into the cloned repository: + +2. Move into the cloned repository: ```bash cd sharpy ``` -2. Create a directory `build` that will be used during CMake's building process and `cd` into it. +3. Create a directory `build` that will be used during CMake's building process and `cd` into it. Ensure it is located in the main ./sharpy folder otherwise the following steps won't work: ```bash mkdir build cd build ``` -3. Prepare UVLM and xbeam for compilation using `gfortran` and `g++` in their release builds running. If you'd like to +4. Prepare UVLM and xbeam for compilation using `gfortran` and `g++` in their release builds running. If you'd like to change compilers see the Custom Installation. ```bash cmake .. ``` -4. Compile the libraries. +5. Compile the libraries. ```bash make install -j 4 ``` where the number after the `-j` flag will specify how many cores to use during installation. This should take approximately 2 minutes (Tested on MacOS Sonoma). -7. Finally, leave the build directory and install SHARPy: +6. Finally, leave the build directory and install SHARPy: ```bash cd .. pip install . ``` + There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. -8. You can check the version of SHARPy you are running with: +7. You can check the version of SHARPy you are running with: ```bash sharpy --version ``` From e33d2cc4029a13c8f19d64dad18737e2216d5cd1 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 20 Feb 2024 19:44:05 +0000 Subject: [PATCH 42/53] Use latest mayavi from Github No longer using "pip install mayavi" as this had build errors depending on python version (known bug) --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d8e2d5763..2dd39ab92 100644 --- a/setup.py +++ b/setup.py @@ -139,10 +139,9 @@ def run(self): "colorama", "dill", "jupyterlab", - "mayavi", #tvtk + "mayavi @ git+https://github.com/enthought/mayavi.git", #Used for TVTK. Bug in pip install, hence git clone "pandas", "control", - # For pandas excel reader. "openpyxl>=3.0.10", "lxml>=4.4.1", "PySocks", From 654017409bd3923a2c95b62d6c640ff97e4d69f7 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Tue, 20 Feb 2024 21:44:45 +0000 Subject: [PATCH 43/53] Changed docs to include auto built, added ARM64 Conda environment --- docs/source/content/installation.md | 52 +++++++++++++---------------- environment_arm64.yml | 12 +++++++ 2 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 environment_arm64.yml diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index 7abef6626..f5411f2da 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -1,5 +1,5 @@ # SHARPy v2.2 Installation Guide -__Last revision 26 January 2024__ +__Last revision 20 February 2024__ The following step by step tutorial will guide you through the installation process of SHARPy. This is the updated process valid from v2.2. @@ -9,7 +9,7 @@ __Operating System Requirements__ SHARPy is being developed and tested on the following operating systems: * CentOS 7 and CentOS 8 -* Ubuntu 18.04 LTS +* Ubuntu 22.04 LTS * Debian 10 * MacOS Mojave and Catalina (Intel) * MacOS Sonoma (Apple Silicon M2) @@ -18,8 +18,9 @@ Windows users can also run it by first installing the Windows Subsystem for Linu __Required Distributions__ -* Python 3.10 (compatility with other versions is not guaranteed) +* Python 3.10 or higher * GCC 6.0 or higher (recommended). C++ and Fortran. +* Eigen3, BLAS, MKL/LAPACK (all included in Anaconda) __Recommended Software__ @@ -58,44 +59,36 @@ once you initialise SHARPy you will also automatically clone the relevant versio SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. -1. Check that your Python version is 3.10. Other versions may be incompatible with the required modules. +1. If the prerequisite algebra packages are not installed, they can be installed as following (with a Homebrew equivelent for Mac installs): + ```bash + sudo apt install -y libblas-dev liblapack-dev libeigen3-dev + ``` + +2. Check that your Python version is 3.10 or higher. Other versions may be incompatible with the required modules. ```bash python --version ``` -2. Move into the cloned repository: +3. Move into the cloned repository: ```bash cd sharpy ``` -3. Create a directory `build` that will be used during CMake's building process and `cd` into it. - Ensure it is located in the main ./sharpy folder otherwise the following steps won't work: +4. Install SHARPy: ```bash - mkdir build - cd build - ``` - -4. Prepare UVLM and xbeam for compilation using `gfortran` and `g++` in their release builds running. If you'd like to - change compilers see the Custom Installation. - ```bash - cmake .. + pip install . ``` + This will install any required Python packages as well as building the xbeam and UVLM libraries. This may take a few minutes. + + There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. -5. Compile the libraries. - ```bash - make install -j 4 - ``` - where the number after the `-j` flag will specify how many cores to use during installation. - This should take approximately 2 minutes (Tested on MacOS Sonoma). + In systems without root access, you may not have permission to install using this method. In that case, to complete a local install: -6. Finally, leave the build directory and install SHARPy: ```bash - cd .. - pip install . + python setup.py --user ``` - There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. -7. You can check the version of SHARPy you are running with: +4. You can check the version of SHARPy you are running with: ```bash sharpy --version ``` @@ -105,8 +98,7 @@ __You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running- ### Setting up the Python Environment (Anaconda) SHARPy can use the Anaconda package manager to provide the necessary Python packages. -These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries -or running any SHARPy cases. +These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries or running any SHARPy cases. Please note that this install method does 1. If you still do not have it in your system, install the [Anaconda](https://conda.io/docs/) Python 3 distribution. @@ -127,7 +119,9 @@ or running any SHARPy cases. conda env create -f environment.yml cd ../.. ``` - This should take approximately 15 minutes to complete (Tested on Ubuntu 22.04.1). + This should take approximately 15 minutes to complete (Tested on Ubuntu 22.04.1). + + For installation on Apple Silicon, use ```environment_arm64.yml```. This requires GCC and GFortran to be installed prior. 5. Activate the `sharpy` conda environment: ```bash diff --git a/environment_arm64.yml b/environment_arm64.yml new file mode 100644 index 000000000..2b5246d0c --- /dev/null +++ b/environment_arm64.yml @@ -0,0 +1,12 @@ +name: sharpy +channels: + - conda-forge + - defaults +dependencies: + - eigen + - libopenblas + - libblas + - libcblas + - liblapack + - libgfortran + - python=3.10 \ No newline at end of file From 03928aa13fd76cf992ea77dbee906cb381795a5f Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Wed, 21 Feb 2024 08:55:59 +0000 Subject: [PATCH 44/53] Small documentation update --- docs/source/content/installation.md | 39 ++++++++--------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index f5411f2da..acad2a7ab 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -59,7 +59,7 @@ once you initialise SHARPy you will also automatically clone the relevant versio SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. -1. If the prerequisite algebra packages are not installed, they can be installed as following (with a Homebrew equivelent for Mac installs): +1. If the prerequisite algebra packages are not installed, they can be installed as following (with a Homebrew equivelent available for Mac installs): ```bash sudo apt install -y libblas-dev liblapack-dev libeigen3-dev ``` @@ -98,7 +98,7 @@ __You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running- ### Setting up the Python Environment (Anaconda) SHARPy can use the Anaconda package manager to provide the necessary Python packages. -These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries or running any SHARPy cases. Please note that this install method does +These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries or running any SHARPy cases. 1. If you still do not have it in your system, install the [Anaconda](https://conda.io/docs/) Python 3 distribution. @@ -127,7 +127,7 @@ These are specified in an Anaconda environment that shall be activated prior to ```bash conda activate sharpy ``` - you need to do this before you compile the `xbeam` and `uvlm` libraries, as + This must be done before you compile the `xbeam` and `uvlm` libraries, as some dependencies are included in the conda environment. You should now see ```(sharpy)``` on your command line. @@ -143,38 +143,21 @@ The quick install is geared towards getting the release build of SHARPy running (sharpy) [usr@host] $ ``` - If this is not the case, activate the environment otherwise xbeam and UVLM will not compile. +3. Install SHARPy: ```bash - conda activate sharpy - ``` - -3. Create a directory `build` that will be used during CMake's building process and `cd` into it. - Ensure it is located in the main ./sharpy folder otherwise the following steps won't work: - ```bash - mkdir build - cd build - ``` - -4. Prepare UVLM and xbeam for compilation using `gfortran` and `g++` in their release builds running. If you'd like to - change compilers see the Custom Installation. - ```bash - cmake .. + pip install . ``` + This will install any required Python packages as well as building the xbeam and UVLM libraries. This may take a few minutes. + + There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. -5. Compile the libraries. - ```bash - make install -j 4 - ``` - where the number after the `-j` flag will specify how many cores to use during installation. - This should take approximately 2 minutes (Tested on MacOS Sonoma). + In systems without root access, you may not have permission to install using this method. In that case, to complete a local install: -7. Finally, leave the build directory and install SHARPy: ```bash - cd .. - pip install . + python setup.py --user ``` -8. You can check the version of SHARPy you are running with: +4. You can check the version of SHARPy you are running with: ```bash sharpy --version ``` From 4e1e22a288f204f1a1d4c3785c2d28ffb5c8ad4c Mon Sep 17 00:00:00 2001 From: Ben Preston <144227999+ben-l-p@users.noreply.github.com> Date: Mon, 26 Feb 2024 09:27:59 +0000 Subject: [PATCH 45/53] Changed docs to use --user flag as default for installation --- docs/source/content/installation.md | 32 +++++++++-------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index acad2a7ab..848936b06 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -74,19 +74,13 @@ SHARPy can be installed as a standalone package, without the use of a package ma cd sharpy ``` -4. Install SHARPy: +4. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. ```bash - pip install . - ``` - This will install any required Python packages as well as building the xbeam and UVLM libraries. This may take a few minutes. - - There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. - - In systems without root access, you may not have permission to install using this method. In that case, to complete a local install: - - ```bash - python setup.py --user + pip install --user . ``` + The ```--user``` flag is included for systems without root access (often Linux) as the install will fail otherwise. This flag can be removed when a global install is required, and your machine allows it (works on Mac). + + There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install --user .[docs]```. For the whole lot, ```bash pip install --user .[all]```. 4. You can check the version of SHARPy you are running with: ```bash @@ -143,19 +137,13 @@ The quick install is geared towards getting the release build of SHARPy running (sharpy) [usr@host] $ ``` -3. Install SHARPy: +3. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. ```bash - pip install . - ``` - This will install any required Python packages as well as building the xbeam and UVLM libraries. This may take a few minutes. - - There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install .[docs]```. For the whole lot, ```bash pip install .[all]```. - - In systems without root access, you may not have permission to install using this method. In that case, to complete a local install: - - ```bash - python setup.py --user + pip install --user . ``` + The ```--user``` flag is included for systems without root access (often Linux) as the install will fail otherwise. This flag can be removed when a global install is required, and your machine allows it (works on Mac). + + There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install --user .[docs]```. For the whole lot, ```bash pip install --user .[all]```. 4. You can check the version of SHARPy you are running with: ```bash From 2441ee197cbf3bace5fcce5287260cbc0b192272 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Mon, 26 Feb 2024 11:45:34 +0000 Subject: [PATCH 46/53] Added a few more install dependencies to the docs --- docs/source/content/installation.md | 47 ++++++++++++----------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index 848936b06..baa97c2f3 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -1,5 +1,5 @@ # SHARPy v2.2 Installation Guide -__Last revision 20 February 2024__ +__Last revision 26 February 2024__ The following step by step tutorial will guide you through the installation process of SHARPy. This is the updated process valid from v2.2. @@ -19,9 +19,15 @@ Windows users can also run it by first installing the Windows Subsystem for Linu __Required Distributions__ * Python 3.10 or higher -* GCC 6.0 or higher (recommended). C++ and Fortran. +* CMake +* GCC 6.0 or higher, G++, GFortran (all included in Anaconda) * Eigen3, BLAS, MKL/LAPACK (all included in Anaconda) +If the prerequisite packages are not installed and you are not using Anaconda, they can be installed as following on Linux (with a Homebrew equivelent available for Mac installs): +```bash +sudo apt install -y cmake g++ gfortran libblas-dev liblapack-dev libeigen3-dev +``` + __Recommended Software__ You may find the applications below useful, we recommend you use them but cannot provide any direct support. @@ -46,35 +52,29 @@ once you initialise SHARPy you will also automatically clone the relevant versio ### Set up the folder structure -1. Clone `sharpy` in your desired location, if you agree with the license in `license.txt`. - ```bash - git clone --recursive http://github.com/ImperialCollegeLondon/sharpy - ``` - The `--recursive` flag will also initialise and update the submodules SHARPy depends on, - [xbeam](http://github.com/imperialcollegelondon/xbeam) and [UVLM](http://github.com/imperialcollegelondon/UVLM). +Clone `sharpy` in your desired location, if you agree with the license in `license.txt`. +```bash +git clone --recursive http://github.com/ImperialCollegeLondon/sharpy +``` +The `--recursive` flag will also initialise and update the submodules SHARPy depends on, +[xbeam](http://github.com/imperialcollegelondon/xbeam) and [UVLM](http://github.com/imperialcollegelondon/UVLM). -2. We will now set up the SHARPy environment that will install other required distributions. ### Quick install (Standalone) SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. -1. If the prerequisite algebra packages are not installed, they can be installed as following (with a Homebrew equivelent available for Mac installs): - ```bash - sudo apt install -y libblas-dev liblapack-dev libeigen3-dev - ``` - -2. Check that your Python version is 3.10 or higher. Other versions may be incompatible with the required modules. +1. Check that your Python version is 3.10 or higher. Other versions may be incompatible with the required modules. ```bash python --version ``` -3. Move into the cloned repository: +2. Move into the cloned repository: ```bash cd sharpy ``` -4. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. +3. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. ```bash pip install --user . ``` @@ -100,7 +100,7 @@ These are specified in an Anaconda environment that shall be activated prior to ```bash python --version ``` -3. If python 3.10 is needed, use: +3. If a specific python version is required, for example 3.10, use: ```bash conda install python=3.10 @@ -109,20 +109,15 @@ These are specified in an Anaconda environment that shall be activated prior to 4. Create the conda environment that SHARPy will use: ```bash - cd sharpy/utils conda env create -f environment.yml - cd ../.. ``` - This should take approximately 15 minutes to complete (Tested on Ubuntu 22.04.1). - - For installation on Apple Silicon, use ```environment_arm64.yml```. This requires GCC and GFortran to be installed prior. + This should take approximately 5 minutes to complete (Tested on Ubuntu 22.04.1). For installation on Apple Silicon, use ```environment_arm64.yml```; this requires GCC and GFortran to be installed prior. 5. Activate the `sharpy` conda environment: ```bash conda activate sharpy ``` - This must be done before you compile the `xbeam` and `uvlm` libraries, as - some dependencies are included in the conda environment. You should now see ```(sharpy)``` on your command line. + You should now see ```(sharpy)``` on your command line. ### Quick install (Anaconda) @@ -173,9 +168,7 @@ to your taste. This is compatible with both standalone and Anaconda installation 2. If using Anaconda, create the conda environment that SHARPy will use and activate the environment: ```bash - cd sharpy/utils conda env create -f environment.yml - cd ../.. ``` ```bash From 719dcfa946a8f836dcdb7137832b8a3fff786195 Mon Sep 17 00:00:00 2001 From: Rafa Palacios Date: Mon, 26 Feb 2024 16:53:53 +0000 Subject: [PATCH 47/53] Update installation.md Working like a charm on Debian 12, so I've updated the version in the docs. --- docs/source/content/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/content/installation.md b/docs/source/content/installation.md index baa97c2f3..0d47ed47c 100644 --- a/docs/source/content/installation.md +++ b/docs/source/content/installation.md @@ -10,7 +10,7 @@ __Operating System Requirements__ SHARPy is being developed and tested on the following operating systems: * CentOS 7 and CentOS 8 * Ubuntu 22.04 LTS -* Debian 10 +* Debian 12 * MacOS Mojave and Catalina (Intel) * MacOS Sonoma (Apple Silicon M2) From 646e5f09292b87efc5a208071aac2e10b9f2233d Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Mon, 26 Feb 2024 18:38:15 +0000 Subject: [PATCH 48/53] Removed SciPy FFT fix - will add to another pull request --- sharpy/generators/floatingforces.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sharpy/generators/floatingforces.py b/sharpy/generators/floatingforces.py index fa3a6735b..c296d97f7 100644 --- a/sharpy/generators/floatingforces.py +++ b/sharpy/generators/floatingforces.py @@ -2,8 +2,7 @@ import h5py as h5 import ctypes as ct import os -from scipy import fft -from scipy.fftpack import ifft +from scipy import fft, ifft from scipy.interpolate import interp1d from control import forced_response, TransferFunction From 906a56613cf1880ee6018d67d26b27ca19959c30 Mon Sep 17 00:00:00 2001 From: Ben Preston Date: Mon, 26 Feb 2024 18:49:33 +0000 Subject: [PATCH 49/53] Readded SciPy FFT changes --- sharpy/generators/floatingforces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharpy/generators/floatingforces.py b/sharpy/generators/floatingforces.py index c296d97f7..832833321 100644 --- a/sharpy/generators/floatingforces.py +++ b/sharpy/generators/floatingforces.py @@ -2,7 +2,7 @@ import h5py as h5 import ctypes as ct import os -from scipy import fft, ifft +from scipy.fft import fft, ifft from scipy.interpolate import interp1d from control import forced_response, TransferFunction From 8bd0eba028c0b54317ca8a4dcd34f203d30ede6f Mon Sep 17 00:00:00 2001 From: Ben Preston <144227999+ben-l-p@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:54:28 +0100 Subject: [PATCH 50/53] Fix conflict in environment.yml --- utils/environment.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/environment.yml b/utils/environment.yml index b1d94bb36..b479f91f0 100644 --- a/utils/environment.yml +++ b/utils/environment.yml @@ -15,7 +15,8 @@ dependencies: - pandas>=0.25.3 - pyOpenSSL>=19.0.0 - PySocks>=1.7.1 - - PyYAML>=5.1.2 + - PyYAML>=5.1.2 + - recommonmark>=0.6.0 - scipy>=1.11.4,<1.12 - slycot>=0.4.0 - sphinx_rtd_theme>=0.4.3 From 282cb31e04c13c5315fb8aa1de7fd7830c5db192 Mon Sep 17 00:00:00 2001 From: kccwing <60852830+kccwing@users.noreply.github.com> Date: Tue, 9 Apr 2024 00:37:28 +0100 Subject: [PATCH 51/53] fixes for initialisation --- sharpy/solvers/aerogridloader.py | 48 +- sharpy/solvers/dynamiccoupled.py | 391 ++++++++-- sharpy/solvers/dynamictrim.py | 732 ++++++++++++++++++ sharpy/solvers/noaero.py | 2 +- sharpy/solvers/nonlineardynamiccoupledstep.py | 3 +- sharpy/solvers/nonlineardynamicmultibody.py | 430 +++++++++- sharpy/solvers/staticcoupled.py | 50 +- sharpy/solvers/staticuvlm.py | 143 ++-- sharpy/solvers/stepuvlm.py | 69 +- sharpy/solvers/trim.py | 2 +- 10 files changed, 1572 insertions(+), 298 deletions(-) create mode 100644 sharpy/solvers/dynamictrim.py diff --git a/sharpy/solvers/aerogridloader.py b/sharpy/solvers/aerogridloader.py index af8098676..bc8f9a8df 100644 --- a/sharpy/solvers/aerogridloader.py +++ b/sharpy/solvers/aerogridloader.py @@ -6,13 +6,12 @@ import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils import sharpy.utils.generator_interface as gen_interface -from sharpy.solvers.gridloader import GridLoader @solver -class AerogridLoader(GridLoader): +class AerogridLoader(BaseSolver): """ - ``AerogridLoader`` class, inherited from ``GridLoader`` + ``AerogridLoader`` class, inherited from ``BaseSolver`` Generates aerodynamic grid based on the input data @@ -37,9 +36,9 @@ class AerogridLoader(GridLoader): settings_types (dict): Acceptable types for the values in ``settings`` settings_default (dict): Name-value pair of default values for the aerodynamic settings data (ProblemData): class structure - file_name (str): name of the ``.aero.h5`` HDF5 file + aero_file_name (str): name of the ``.aero.h5`` HDF5 file aero: empty attribute - data_dict (dict): key-value pairs of aerodynamic data + aero_data_dict (dict): key-value pairs of aerodynamic data wake_shape_generator (class): Wake shape generator """ @@ -90,13 +89,28 @@ class AerogridLoader(GridLoader): settings_options=settings_options) def __init__(self): - super().__init__ - self.file_name = '.aero.h5' + self.data = None + self.settings = None + self.aero_file_name = '' + # storage of file contents + self.aero_data_dict = dict() + + # aero storage self.aero = None + self.wake_shape_generator = None def initialise(self, data, restart=False): - super().initialise(data) + self.data = data + self.settings = data.settings[self.solver_id] + + # init settings + settings_utils.to_custom_types(self.settings, + self.settings_types, + self.settings_default, options=self.settings_options) + + # read input file (aero) + self.read_files() wake_shape_generator_type = gen_interface.generator_from_string( self.settings['wake_shape_generator']) @@ -105,9 +119,25 @@ def initialise(self, data, restart=False): self.settings['wake_shape_generator_input'], restart=restart) + def read_files(self): + # open aero file + # first, file names + self.aero_file_name = (self.data.case_route + + '/' + + self.data.case_name + + '.aero.h5') + + # then check that the file exists + h5utils.check_file_exists(self.aero_file_name) + + # read and store the hdf5 file + with h5.File(self.aero_file_name, 'r') as aero_file_handle: + # store files in dictionary + self.aero_data_dict = h5utils.load_h5_in_dict(aero_file_handle) + def run(self, **kwargs): self.data.aero = aerogrid.Aerogrid() - self.data.aero.generate(self.data_dict, + self.data.aero.generate(self.aero_data_dict, self.data.structure, self.settings, self.data.ts) diff --git a/sharpy/solvers/dynamiccoupled.py b/sharpy/solvers/dynamiccoupled.py index 719a58380..0e5950d22 100644 --- a/sharpy/solvers/dynamiccoupled.py +++ b/sharpy/solvers/dynamiccoupled.py @@ -178,10 +178,6 @@ class DynamicCoupled(BaseSolver): 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' - settings_types['nonlifting_body_interactions'] = 'bool' - settings_default['nonlifting_body_interactions'] = False - settings_description['nonlifting_body_interactions'] = 'Effect of Nonlifting Bodies on Lifting bodies are considered' - settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -369,20 +365,17 @@ def initialise(self, data, custom_settings=None, restart=False): def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: - self.remove_old_timestep_info(self.data.structure.timestep_info) - self.remove_old_timestep_info(self.data.aero.timestep_info) - if self.settings['nonlifting_body_interactions']: - self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) + # copy last info to first + self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1] + self.data.structure.timestep_info[0] = self.data.structure.timestep_info[-1] + # delete all the rest + while len(self.data.aero.timestep_info) - 1: + del self.data.aero.timestep_info[-1] + while len(self.data.structure.timestep_info) - 1: + del self.data.structure.timestep_info[-1] self.data.ts = 0 - def remove_old_timestep_info(self, tstep_info): - # copy last info to first - tstep_info[0] = tstep_info[-1].copy() - # delete all the rest - while len(tstep_info) - 1: - del tstep_info[-1] - def process_controller_output(self, controlled_state): """ This function modified the solver properties and parameters as @@ -515,6 +508,8 @@ def network_loop(self, in_queue, out_queue, finish_event): out_network.close() def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=None): + # import pdb + # pdb.set_trace() self.logger.debug('Inside time loop') # dynamic simulations start at tstep == 1, 0 is reserved for the initial state for self.data.ts in range( @@ -532,10 +527,6 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No structural_kstep = self.data.structure.timestep_info[-1].copy() aero_kstep = self.data.aero.timestep_info[-1].copy() - if self.settings['nonlifting_body_interactions']: - nl_body_kstep = self.data.nonlifting_body.timestep_info[-1].copy() - else: - nl_body_kstep = None self.logger.debug('Time step {}'.format(self.data.ts)) # Add the controller here @@ -543,10 +534,13 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No state = {'structural': structural_kstep, 'aero': aero_kstep} for k, v in self.controllers.items(): - state = v.control(self.data, state) + state, control = v.control(self.data, state) # this takes care of the changes in options for the solver structural_kstep, aero_kstep = self.process_controller_output( state) + self.aero_solver.update_custom_grid(state['structural'], state['aero']) + # import pdb + # pdb.set_trace() # Add external forces if self.with_runtime_generators: @@ -576,17 +570,16 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No cout.cout_wrap(("The FSI solver did not converge!!! residuals: %f %f" % (print_res, print_res_dqdt))) self.aero_solver.update_custom_grid( structural_kstep, - aero_kstep, - nl_body_kstep) + aero_kstep) break # generate new grid (already rotated) aero_kstep = controlled_aero_kstep.copy() - + # import pdb + # pdb.set_trace() self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep, - nl_body_kstep) + structural_kstep, + aero_kstep) # compute unsteady contribution force_coeff = 0.0 @@ -618,8 +611,7 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No self.data = self.aero_solver.run(aero_step=aero_kstep, structural_step=structural_kstep, convect_wake=True, - unsteady_contribution=unsteady_contribution, - nl_body_tstep = nl_body_kstep) + unsteady_contribution=unsteady_contribution) self.time_aero += time.perf_counter() - ini_time_aero previous_kstep = structural_kstep.copy() @@ -630,15 +622,12 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No previous_kstep.runtime_unsteady_forces = previous_runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep, - nl_body_kstep) - + self.aero_solver.update_custom_grid(structural_kstep, + aero_kstep) + self.map_forces(aero_kstep, - structural_kstep, - nl_body_kstep = nl_body_kstep, - unsteady_forces_coeff = force_coeff) + structural_kstep, + force_coeff) # relaxation relax_factor = self.relaxation_factor(k) @@ -682,20 +671,16 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No self.aero_solver, self.with_runtime_generators): # move the aerodynamic surface according to the structural one - self.aero_solver.update_custom_grid(structural_kstep, - aero_kstep, - nl_body_tstep = nl_body_kstep) + self.aero_solver.update_custom_grid( + structural_kstep, + aero_kstep) break # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid(structural_kstep, - aero_kstep, - nl_body_tstep = nl_body_kstep) + self.aero_solver.update_custom_grid(structural_kstep, aero_kstep) self.aero_solver.add_step() self.data.aero.timestep_info[-1] = aero_kstep.copy() - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.timestep_info[-1] = nl_body_kstep.copy() self.structural_solver.add_step() self.data.structure.timestep_info[-1] = structural_kstep.copy() @@ -713,9 +698,11 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No structural_kstep.for_vel[2], np.sum(structural_kstep.steady_applied_forces[:, 0]), np.sum(structural_kstep.steady_applied_forces[:, 2])]) + # import pdb + # pdb.set_trace() (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( - self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) + self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) # run postprocessors if self.with_postprocessors: for postproc in self.postprocessors: @@ -770,9 +757,7 @@ def convergence(self, k, tstep, previous_tstep, return False # Check the special case of no aero and no runtime generators - if (aero_solver.solver_id.lower() == "noaero"\ - or struct_solver.solver_id.lower() == "nostructural")\ - and not with_runtime_generators: + if aero_solver.solver_id.lower() == "noaero" and not with_runtime_generators: return True # relative residuals @@ -811,7 +796,7 @@ def convergence(self, k, tstep, previous_tstep, return True - def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unsteady_forces_coeff=1.0): + def map_forces(self, aero_kstep, structural_kstep, unsteady_forces_coeff=1.0): # set all forces to 0 structural_kstep.steady_applied_forces.fill(0.0) structural_kstep.unsteady_applied_forces.fill(0.0) @@ -826,8 +811,8 @@ def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unstead self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), - self.data.aero.data_dict) - dynamic_struct_forces = unsteady_forces_coeff*mapping.aero2struct_force_mapping( + self.data.aero.aero_dict) + dynamic_struct_forces = mapping.aero2struct_force_mapping( aero_kstep.dynamic_forces, self.data.aero.struct2aero_mapping, aero_kstep.zeta, @@ -836,7 +821,7 @@ def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unstead self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), - self.data.aero.data_dict) + self.data.aero.aero_dict) if self.correct_forces: struct_forces = \ @@ -849,18 +834,6 @@ def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unstead structural_kstep.postproc_node['aero_steady_forces'] = struct_forces structural_kstep.postproc_node['aero_unsteady_forces'] = dynamic_struct_forces - # if self.settings['nonlifting_body_interactions']: - # struct_forces += mapping.aero2struct_force_mapping( - # nl_body_kstep.forces, - # self.data.nonlifting_body.struct2aero_mapping, - # nl_body_kstep.zeta, - # structural_kstep.pos, - # structural_kstep.psi, - # self.data.structure.node_master_elem, - # self.data.structure.connectivities, - # structural_kstep.cag(), - # self.data.nonlifting_body.data_dict) - # prescribed forces + aero forces # prescribed forces + aero forces + runtime generated structural_kstep.steady_applied_forces += struct_forces structural_kstep.steady_applied_forces += self.data.structure.ini_info.steady_applied_forces @@ -886,6 +859,296 @@ def relaxation_factor(self, k): value = initial + (final - initial)/self.settings['relaxation_steps']*k return value + def change_trim(self, thrust, thrust_nodes, tail_deflection, tail_cs_index): + # self.cleanup_timestep_info() + # self.data.structure.timestep_info = [] + # self.data.structure.timestep_info.append(self.data.structure.ini_info.copy()) + # aero_copy = self.data.aero.timestep_info[-1] + # self.data.aero.timestep_info = [] + # self.data.aero.timestep_info.append(aero_copy) + # self.data.ts = 0 + + + try: + self.force_orientation + except AttributeError: + self.force_orientation = np.zeros((len(thrust_nodes), 3)) + for i_node, node in enumerate(thrust_nodes): + self.force_orientation[i_node, :] = ( + algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3])) + # print(self.force_orientation) + + # thrust + # thrust is scaled so that the direction of the forces is conserved + # in all nodes. + # the `thrust` parameter is the force PER node. + # if there are two or more nodes in thrust_nodes, the total forces + # is n_nodes_in_thrust_nodes*thrust + # thrust forces have to be indicated in structure.ini_info + # print(algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[0, 0:3])*thrust) + for i_node, node in enumerate(thrust_nodes): + # self.data.structure.ini_info.steady_applied_forces[i_node, 0:3] = ( + # algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[i_node, 0:3])*thrust) + self.data.structure.ini_info.steady_applied_forces[node, 0:3] = ( + self.force_orientation[i_node, :]*thrust) + self.data.structure.timestep_info[0].steady_applied_forces[node, 0:3] = ( + self.force_orientation[i_node, :]*thrust) + + # tail deflection + try: + self.data.aero.aero_dict['control_surface_deflection'][tail_cs_index] = tail_deflection + except KeyError: + raise Exception('This model has no control surfaces') + except IndexError: + raise Exception('The tail control surface index > number of surfaces') + + # update grid + self.aero_solver.update_grid(self.data.structure) + + def time_step(self, in_queue=None, out_queue=None, finish_event=None, solvers=None): + # import pdb + # pdb.set_trace() + self.logger.debug('Inside time step') + # dynamic simulations start at tstep == 1, 0 is reserved for the initial state + self.data.ts = len(self.data.structure.timestep_info) + initial_time = time.perf_counter() + + # network only + # get input from the other thread + if in_queue: + self.logger.info('Time Loop - Waiting for input') + values = in_queue.get() # should be list of tuples + self.logger.debug('Time loop - received {}'.format(values)) + self.set_of_variables.update_timestep(self.data, values) + + structural_kstep = self.data.structure.timestep_info[-1].copy() + aero_kstep = self.data.aero.timestep_info[-1].copy() + self.logger.debug('Time step {}'.format(self.data.ts)) + + # Add the controller here + if self.with_controllers: + control = [] + state = {'structural': structural_kstep, + 'aero': aero_kstep} + for k, v in self.controllers.items(): + state, control_command = v.control(self.data, state) + # this takes care of the changes in options for the solver + structural_kstep, aero_kstep = self.process_controller_output(state) + control.append(control_command) + self.aero_solver.update_custom_grid(state['structural'], state['aero']) + # import pdb + # pdb.set_trace() + + # Add external forces + if self.with_runtime_generators: + structural_kstep.runtime_steady_forces.fill(0.) + structural_kstep.runtime_unsteady_forces.fill(0.) + params = dict() + params['data'] = self.data + params['struct_tstep'] = structural_kstep + params['aero_tstep'] = aero_kstep + params['fsi_substep'] = -1 + for id, runtime_generator in self.runtime_generators.items(): + runtime_generator.generate(params) + + self.time_aero = 0.0 + self.time_struc = 0.0 + + # Copy the controlled states so that the interpolation does not + # destroy the previous information + controlled_structural_kstep = structural_kstep.copy() + controlled_aero_kstep = aero_kstep.copy() + + for k in range(self.settings['fsi_substeps'] + 1): + if (k == self.settings['fsi_substeps'] and + self.settings['fsi_substeps']): + print_res = 0 if self.res == 0. else np.log10(self.res) + print_res_dqdt = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) + cout.cout_wrap(("The FSI solver did not converge!!! residuals: %f %f" % (print_res, print_res_dqdt))) + self.aero_solver.update_custom_grid( + structural_kstep, + aero_kstep) + break + + # generate new grid (already rotated) + aero_kstep = controlled_aero_kstep.copy() + self.aero_solver.update_custom_grid( + structural_kstep, + aero_kstep) + + # compute unsteady contribution + force_coeff = 0.0 + unsteady_contribution = False + if self.settings['include_unsteady_force_contribution']: + if self.data.ts > self.settings['steps_without_unsteady_force']: + unsteady_contribution = True + if k < self.settings['pseudosteps_ramp_unsteady_force']: + force_coeff = k/self.settings['pseudosteps_ramp_unsteady_force'] + else: + force_coeff = 1. + + previous_runtime_steady_forces = structural_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) + previous_runtime_unsteady_forces = structural_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) + # Add external forces + if self.with_runtime_generators: + structural_kstep.runtime_steady_forces.fill(0.) + structural_kstep.runtime_unsteady_forces.fill(0.) + params = dict() + params['data'] = self.data + params['struct_tstep'] = structural_kstep + params['aero_tstep'] = aero_kstep + params['fsi_substep'] = k + for id, runtime_generator in self.runtime_generators.items(): + runtime_generator.generate(params) + + # run the solver + ini_time_aero = time.perf_counter() + self.data = self.aero_solver.run(aero_step=aero_kstep, + structural_step=structural_kstep, + convect_wake=True, + unsteady_contribution=unsteady_contribution) + self.time_aero += time.perf_counter() - ini_time_aero + + previous_kstep = structural_kstep.copy() + structural_kstep = controlled_structural_kstep.copy() + structural_kstep.runtime_steady_forces = previous_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) + structural_kstep.runtime_unsteady_forces = previous_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) + previous_kstep.runtime_steady_forces = previous_runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) + previous_kstep.runtime_unsteady_forces = previous_runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) + + # move the aerodynamic surface according the the structural one + self.aero_solver.update_custom_grid(structural_kstep, + aero_kstep) + + self.map_forces(aero_kstep, + structural_kstep, + force_coeff) + + # relaxation + relax_factor = self.relaxation_factor(k) + relax(self.data.structure, + structural_kstep, + previous_kstep, + relax_factor) + + # check if nan anywhere. + # if yes, raise exception + if np.isnan(structural_kstep.steady_applied_forces).any(): + raise exc.NotConvergedSolver('NaN found in steady_applied_forces!') + if np.isnan(structural_kstep.unsteady_applied_forces).any(): + raise exc.NotConvergedSolver('NaN found in unsteady_applied_forces!') + + copy_structural_kstep = structural_kstep.copy() + ini_time_struc = time.perf_counter() + for i_substep in range( + self.settings['structural_substeps'] + 1): + # run structural solver + coeff = ((i_substep + 1)/ + (self.settings['structural_substeps'] + 1)) + + structural_kstep = self.interpolate_timesteps( + step0=self.data.structure.timestep_info[-1], + step1=copy_structural_kstep, + out_step=structural_kstep, + coeff=coeff) + + self.data = self.structural_solver.run( + structural_step=structural_kstep, + dt=self.substep_dt) + + # self.aero_solver.update_custom_grid( + # structural_kstep, + # aero_kstep) + self.time_struc += time.perf_counter() - ini_time_struc + + # check convergence + if self.convergence(k, + structural_kstep, + previous_kstep, + self.structural_solver, + self.aero_solver, + self.with_runtime_generators): + # move the aerodynamic surface according to the structural one + self.aero_solver.update_custom_grid( + structural_kstep, + aero_kstep) + break + + # move the aerodynamic surface according the the structural one + self.aero_solver.update_custom_grid(structural_kstep, aero_kstep) + + self.aero_solver.add_step() + self.data.aero.timestep_info[-1] = aero_kstep.copy() + self.structural_solver.add_step() + self.data.structure.timestep_info[-1] = structural_kstep.copy() + + final_time = time.perf_counter() + + if self.print_info: + print_res = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) + self.residual_table.print_line([self.data.ts, + self.data.ts*self.dt, + k, + self.time_struc/(self.time_aero + self.time_struc), + final_time - initial_time, + print_res, + structural_kstep.for_vel[0], + structural_kstep.for_vel[2], + np.sum(structural_kstep.steady_applied_forces[:, 0]), + np.sum(structural_kstep.steady_applied_forces[:, 2])]) + (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], + self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( + self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) + # run postprocessors + if self.with_postprocessors: + for postproc in self.postprocessors: + self.data = self.postprocessors[postproc].run(online=True, solvers=solvers) + + # network only + # put result back in queue + if out_queue: + self.logger.debug('Time loop - about to get out variables from data') + self.set_of_variables.get_value(self.data) + if out_queue.full(): + # clear the queue such that it always contains the latest time step + out_queue.get() # clear item from queue + self.logger.debug('Data output Queue is full - clearing output') + out_queue.put(self.set_of_variables) + + if finish_event: + finish_event.set() + self.logger.info('Time loop - Complete') + + return control + + def extract_resultants(self, tstep=None): + return self.structural_solver.extract_resultants(tstep) + + # def extract_controlcommand(self, tstep=None): + # if self.with_controllers: + # structural_kstep = self.data.structure.timestep_info[-1].copy() + # aero_kstep = self.data.aero.timestep_info[-1].copy() + # state = {'structural': structural_kstep, + # 'aero': aero_kstep} + # control = [] + # for k, v in self.controllers.items(): + # state, control_command = v.control(self.data, state) + # control.append(control_command) + # return control + + def get_direction(self, thrust_nodes, tstep=None): + self.force_orientation_G = np.zeros((len(thrust_nodes), 3)) + for i_node, node in enumerate(thrust_nodes): + # import pdb + # pdb.set_trace() + elem_thrust_node = np.where(self.data.structure.connectivities == 0)[0][0] + node_thrust_node = np.where(self.data.structure.connectivities == 0)[1][0] + self.force_orientation_G[i_node, :] = np.dot(algebra.quat2rotation(self.data.structure.ini_info.quat), + np.dot(algebra.crv2rotation(self.data.structure.ini_info.psi[elem_thrust_node, node_thrust_node, :]), + algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3]))) + return self.force_orientation_G + + @staticmethod def interpolate_timesteps(step0, step1, out_step, coeff): """ diff --git a/sharpy/solvers/dynamictrim.py b/sharpy/solvers/dynamictrim.py new file mode 100644 index 000000000..0f85c3b28 --- /dev/null +++ b/sharpy/solvers/dynamictrim.py @@ -0,0 +1,732 @@ +import numpy as np + +import sharpy.utils.cout_utils as cout +import sharpy.utils.solver_interface as solver_interface +from sharpy.utils.solver_interface import solver, BaseSolver +import sharpy.utils.settings as settings_utils +import os + + +@solver +class DynamicTrim(BaseSolver): + """ + The ``StaticTrim`` solver determines the longitudinal state of trim (equilibrium) for an aeroelastic system in + static conditions. It wraps around the desired solver to yield the state of trim of the system, in most cases + the :class:`~sharpy.solvers.staticcoupled.StaticCoupled` solver. + + It calculates the required angle of attack, elevator deflection and thrust required to achieve longitudinal + equilibrium. The output angles are shown in degrees. + + The results from the trimming iteration can be saved to a text file by using the `save_info` option. + """ + solver_id = 'DynamicTrim' + solver_classification = 'Flight Dynamics' + + settings_types = dict() + settings_default = dict() + settings_description = dict() + + settings_types['print_info'] = 'bool' + settings_default['print_info'] = True + settings_description['print_info'] = 'Print info to screen' + + settings_types['solver'] = 'str' + settings_default['solver'] = '' + settings_description['solver'] = 'Solver to run in trim routine' + + settings_types['solver_settings'] = 'dict' + settings_default['solver_settings'] = dict() + settings_description['solver_settings'] = 'Solver settings dictionary' + + settings_types['max_iter'] = 'int' + settings_default['max_iter'] = 40000 + settings_description['max_iter'] = 'Maximum number of iterations of trim routine' + + settings_types['fz_tolerance'] = 'float' + settings_default['fz_tolerance'] = 0.01 + settings_description['fz_tolerance'] = 'Tolerance in vertical force' + + settings_types['fx_tolerance'] = 'float' + settings_default['fx_tolerance'] = 0.01 + settings_description['fx_tolerance'] = 'Tolerance in horizontal force' + + settings_types['m_tolerance'] = 'float' + settings_default['m_tolerance'] = 0.01 + settings_description['m_tolerance'] = 'Tolerance in pitching moment' + + settings_types['tail_cs_index'] = ['int', 'list(int)'] + settings_default['tail_cs_index'] = 0 + settings_description['tail_cs_index'] = 'Index of control surfaces that move to achieve trim' + + settings_types['thrust_nodes'] = 'list(int)' + settings_default['thrust_nodes'] = [0] + settings_description['thrust_nodes'] = 'Nodes at which thrust is applied' + + settings_types['initial_alpha'] = 'float' + settings_default['initial_alpha'] = 0. + settings_description['initial_alpha'] = 'Initial angle of attack' + + settings_types['initial_deflection'] = 'float' + settings_default['initial_deflection'] = 0. + settings_description['initial_deflection'] = 'Initial control surface deflection' + + settings_types['initial_thrust'] = 'float' + settings_default['initial_thrust'] = 0.0 + settings_description['initial_thrust'] = 'Initial thrust setting' + + settings_types['initial_angle_eps'] = 'float' + settings_default['initial_angle_eps'] = 0.05 + settings_description['initial_angle_eps'] = 'Initial change of control surface deflection' + + settings_types['initial_thrust_eps'] = 'float' + settings_default['initial_thrust_eps'] = 2. + settings_description['initial_thrust_eps'] = 'Initial thrust setting change' + + settings_types['relaxation_factor'] = 'float' + settings_default['relaxation_factor'] = 0.2 + settings_description['relaxation_factor'] = 'Relaxation factor' + + settings_types['notrim_relax'] = 'bool' + settings_default['notrim_relax'] = False + settings_description['notrim_relax'] = 'Disable gains for trim - releases internal loads at initial values' + + settings_types['notrim_relax_iter'] = 'int' + settings_default['notrim_relax_iter'] = 10000000 + settings_description['notrim_relax_iter'] = 'Terminate notrim_relax at defined number of steps' + + + settings_types['speed_up_factor'] = 'float' + settings_default['speed_up_factor'] = 1.0 + settings_description['speed_up_factor'] = 'increase dt in trim iterations' + + settings_types['save_info'] = 'bool' + settings_default['save_info'] = False + settings_description['save_info'] = 'Save trim results to text file' + + settings_table = settings_utils.SettingsTable() + __doc__ += settings_table.generate(settings_types, settings_default, settings_description) + + def __init__(self): + self.data = None + self.settings = None + self.solver = None + + # The order is + # [0]: alpha/fz + # [1]: alpha + delta (gamma)/moment + # [2]: thrust/fx + + self.n_input = 3 + self.i_iter = 0 + + self.input_history = [] + self.output_history = [] + self.gradient_history = [] + self.trimmed_values = np.zeros((3,)) + + self.table = None + self.folder = None + + def initialise(self, data, restart=False): + self.data = data + self.settings = data.settings[self.solver_id] + settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) + + self.solver = solver_interface.initialise_solver(self.settings['solver']) + + if self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicCoupledStep": + #replace free flying with clamped + oldsettings = self.settings['solver_settings'] + self.settings['solver_settings']['structural_solver'] = 'NonLinearDynamicPrescribedStep' + # self.settings['solver_settings']['structural_solver_settings'] = {'print_info': 'off', + # 'max_iterations': 950, + # 'delta_curved': 1e-1, + # 'min_delta': tolerance, + # 'newmark_damp': 5e-3, + # 'gravity_on': gravity, + # 'gravity': 9.81, + # 'num_steps': n_tstep, + # 'dt': dt, + # 'initial_velocity': u_inf * int(free_flight)} commented since both solvers take (almost) same inputs + u_inf = self.settings['solver_settings']['structural_solver_settings']['initial_velocity'] + self.settings['solver_settings']['structural_solver_settings'].pop('initial_velocity') + self.settings['solver_settings']['structural_solver_settings']['newmark_damp'] = 1.0 + # settings['StepUvlm'] = {'print_info': 'off', + # 'num_cores': num_cores, + # 'convection_scheme': 2, + # 'gamma_dot_filtering': 6, + # 'velocity_field_generator': 'GustVelocityField', + # 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, + # 'u_inf_direction': [1., 0, 0], + # 'gust_shape': '1-cos', + # 'gust_parameters': {'gust_length': gust_length, + # 'gust_intensity': gust_intensity * u_inf}, + # 'offset': gust_offset, + # 'relative_motion': relative_motion}, + # 'rho': rho, + # 'n_time_steps': n_tstep, + # 'dt': dt} + self.settings['solver_settings']['aero_solver_settings']['velocity_field_generator'] = 'SteadyVelocityField' + u_inf_direction = self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input'].clear() + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf'] = u_inf + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] = u_inf_direction + + self.settings['solver_settings'] + # TODO: plus dynamic coupled to add controller + + # import pdb + # pdb.set_trace() + + gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians + gains_alpha = np.array([0.00015, 0.00015, 0.0]) + gains_thrust = -np.array([0.1, 0.05, 0.0]) #we can pick them too! this is 1-1 almost + route = data.settings['SHARPy']['route'] + n_tstep = int(self.settings['solver_settings']['n_time_steps']) + dt = float(self.settings['solver_settings']['dt']) + elev_file = route + '/elev.csv' + alpha_file = route + '/alpha.csv' + thrust_file = route + '/thrust.csv' + + elev_hist = np.linspace(0, n_tstep*dt, n_tstep) + elev_hist = 0.0/180.0*np.pi*elev_hist + + alpha_hist = np.linspace(1, 1, n_tstep) + alpha_hist = 0.0/180.0*np.pi*alpha_hist + + thrust_hist = np.linspace(1, 1, n_tstep) + thrust_hist = 0.0/180.0*np.pi*thrust_hist + + np.savetxt(elev_file, elev_hist) + np.savetxt(alpha_file, alpha_hist) + np.savetxt(thrust_file, thrust_hist) + + + try: + self.settings['solver_settings']['controller_id'].clear() + self.settings['solver_settings']['controller_settings'].clear() + except: + print("original no controller") + + self.settings['solver_settings']['controller_id']= {'controller_elevator': 'ControlSurfacePidController', + 'controller_alpha': 'AlphaController', + 'controller_thrust': 'ThrustController'} + self.settings['solver_settings']['controller_settings']= {'controller_elevator': {'P': gains_elev[0], + 'I': gains_elev[1], + 'D': gains_elev[2], + 'dt': dt, + 'input_type': 'm_y', + 'write_controller_log': True, + 'controlled_surfaces': [0], + 'time_history_input_file': route + '/elev.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_deflection'] + } + , + 'controller_alpha': {'P': gains_alpha[0], + 'I': gains_alpha[1], + 'D': gains_alpha[2], + 'dt': dt, + 'input_type': 'f_z', + 'write_controller_log': True, + 'time_history_input_file': route + '/alpha.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_alpha']} + , + 'controller_thrust': {'thrust_nodes': self.settings['thrust_nodes'], + 'P': gains_thrust[0], + 'I': gains_thrust[1], + 'D': gains_thrust[2], + 'dt': dt, + 'input_type': 'f_x', + 'write_controller_log': True, + 'time_history_input_file': route + '/thrust.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_thrust']} + } + # import pdb + # pdb.set_trace() + # #end + + self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) + + self.folder = data.output_folder + '/statictrim/' + if not os.path.exists(self.folder): + os.makedirs(self.folder) + + self.table = cout.TablePrinter(10, 8, ['g', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'], + filename=self.folder+'trim_iterations.txt') + self.table.print_header(['iter', 'alpha[deg]', 'elev[deg]', 'thrust', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) + + + elif self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicMultibody": + # import pdb + # pdb.set_trace() + self.data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + self.data.structure.timestep_info[0].mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + self.data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + # self.data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + # self.data.structure.timestep_info[0].mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + # self.data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' + #replace free flying with clamped + oldsettings = self.settings['solver_settings'] + self.settings['solver_settings']['structural_solver'] = 'NonLinearDynamicMultibody' + # self.settings['solver_settings']['structural_solver_settings'] = {'print_info': 'off', + # 'max_iterations': 950, + # 'delta_curved': 1e-1, + # 'min_delta': tolerance, + # 'newmark_damp': 5e-3, + # 'gravity_on': gravity, + # 'gravity': 9.81, + # 'num_steps': n_tstep, + # 'dt': dt, + # 'initial_velocity': u_inf * int(free_flight)} commented since both solvers take (almost) same inputs + self.settings['solver_settings']['structural_solver_settings'].pop('dyn_trim') + u_inf = self.settings['solver_settings']['structural_solver_settings']['initial_velocity'] + self.settings['solver_settings']['structural_solver_settings'].pop('initial_velocity') + # import pdb + # pdb.set_trace() + dt = self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] + # self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] = float(dt)/5. + self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['newmark_damp'] = 0.1 + # settings['StepUvlm'] = {'print_info': 'off', + # 'num_cores': num_cores, + # 'convection_scheme': 2, + # 'gamma_dot_filtering': 6, + # 'velocity_field_generator': 'GustVelocityField', + # 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, + # 'u_inf_direction': [1., 0, 0], + # 'gust_shape': '1-cos', + # 'gust_parameters': {'gust_length': gust_length, + # 'gust_intensity': gust_intensity * u_inf}, + # 'offset': gust_offset, + # 'relative_motion': relative_motion}, + # 'rho': rho, + # 'n_time_steps': n_tstep, + # 'dt': dt} + # self.settings['solver_settings']['aero_solver_settings']['convection_scheme'] = 2 + + self.settings['solver_settings']['aero_solver_settings']['velocity_field_generator'] = 'SteadyVelocityField' + u_inf_direction = self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input'].clear() + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf'] = u_inf + self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] = u_inf_direction + + self.settings['solver_settings'] + # TODO: plus dynamic coupled to add controller + + # import pdb + # pdb.set_trace() + + # gains_elev = -np.array([0.000015, 0.000015, 0.0]) #we can pick them! dimensional force/moment versus radians + # gains_alpha = np.array([0.000010, 0.000010, 0.0]) + # # gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians + # # gains_alpha = np.array([0.00010, 0.00010, 0.0]) + # gains_thrust = -np.array([0.1, 0.05, 0.0]) #we can pick them too! this is 1-1 almost + gains_elev = (not self.settings['notrim_relax'])*-np.array([0.000015, 0.000010, 0.0]) #we can pick them! dimensional force/moment versus radians + gains_alpha = (not self.settings['notrim_relax'])*np.array([0.000015, 0.000010, 0.0]) + # gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians + # gains_alpha = np.array([0.00010, 0.00010, 0.0]) + gains_thrust = (not self.settings['notrim_relax'])*-np.array([0.1, 0.05, 0.0]) + + + + #we can pick them too! this is 1-1 almost + route = data.settings['SHARPy']['route'] + n_tstep = int(self.settings['solver_settings']['n_time_steps']) + n_tstep *=40 + self.settings['solver_settings']['n_time_steps'] = n_tstep + self.settings['solver_settings']['structural_solver_settings']['num_steps'] = n_tstep + self.settings['solver_settings']['aero_solver_settings']['n_time_steps'] = n_tstep + # import pdb + # pdb.set_trace() + + dt = float(self.settings['solver_settings']['dt'])*self.settings['speed_up_factor'] + # import pdb + # pdb.set_trace() + self.settings['solver_settings']['dt'] = dt + self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] = dt + self.settings['solver_settings']['aero_solver_settings']['dt'] = dt + elev_file = route + '/elev.csv' + alpha_file = route + '/alpha.csv' + thrust_file = route + '/thrust.csv' + + elev_hist = np.linspace(0, n_tstep*dt, n_tstep) + elev_hist = 0.0/180.0*np.pi*elev_hist + + alpha_hist = np.linspace(1, 1, n_tstep) + alpha_hist = 0.0/180.0*np.pi*alpha_hist + + thrust_hist = np.linspace(1, 1, n_tstep) + thrust_hist = 0.0/180.0*np.pi*thrust_hist + + np.savetxt(elev_file, elev_hist) + np.savetxt(alpha_file, alpha_hist) + np.savetxt(thrust_file, thrust_hist) + + try: + self.settings['solver_settings']['controller_id'].clear() + self.settings['solver_settings']['controller_settings'].clear() + except: + print("original no controller") + + self.settings['solver_settings']['controller_id']= {'controller_elevator': 'ControlSurfacePidController' + , + 'controller_alpha': 'AlphaController' + , + 'controller_thrust': 'ThrustController' + } + self.settings['solver_settings']['controller_settings']= {'controller_elevator': {'P': gains_elev[0], + 'I': gains_elev[1], + 'D': gains_elev[2], + 'dt': dt, + 'input_type': 'm_y', + 'write_controller_log': True, + 'controlled_surfaces': [0], + 'time_history_input_file': route + '/elev.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_deflection'] + } + , + 'controller_alpha': {'P': gains_alpha[0], + 'I': gains_alpha[1], + 'D': gains_alpha[2], + 'dt': dt, + 'input_type': 'f_z', + 'write_controller_log': True, + 'time_history_input_file': route + '/alpha.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_alpha']} + , + 'controller_thrust': {'thrust_nodes': self.settings['thrust_nodes'], + 'P': gains_thrust[0], + 'I': gains_thrust[1], + 'D': gains_thrust[2], + 'dt': dt, + 'input_type': 'f_x', + 'write_controller_log': True, + 'time_history_input_file': route + '/thrust.csv', + 'use_initial_value': True, + 'initial_value': self.settings['initial_thrust']} + } + # import pdb + # pdb.set_trace() + # #end + + self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) + + self.folder = data.output_folder + '/statictrim/' + if not os.path.exists(self.folder): + os.makedirs(self.folder) + + self.table = cout.TablePrinter(10, 8, ['g', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'], + filename=self.folder+'trim_iterations.txt') + self.table.print_header(['iter', 'alpha[deg]', 'elev[deg]', 'thrust', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) + else: + raise NotImplementedError('Dynamic trim is only working with nonlinearcoupled or multibody!') + + + def undo_changes(self, data): + + # import pdb + # pdb.set_trace() + if self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicMultibody": + data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'free' + data.structure.timestep_info[-1].mb_dict['body_00']['FoR_movement'] = 'free' + data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'free' + print("HARDCODED!! 16723") + + # data.structure.ini_info.pos_dot *= 0. + # data.structure.ini_info.pos_ddot *= 0. + # data.structure.ini_info.psi_dot *= 0. + # data.structure.ini_info.psi_dot_local *= 0. + # data.structure.ini_info.psi_ddot *= 0. + # data.structure.timestep_info[-1].pos_dot *= 0. + # data.structure.timestep_info[-1].pos_ddot *= 0. + # data.structure.timestep_info[-1].psi_dot *= 0. + # data.structure.timestep_info[-1].psi_dot_local *= 0. + # data.structure.timestep_info[-1].psi_ddot *= 0. + # data.structure.timestep_info[-1].mb_FoR_vel *= 0. + # data.structure.timestep_info[-1].mb_FoR_acc *= 0. + # data.structure.timestep_info[-1].mb_dquatdt *= 0. + # data.structure.timestep_info[-1].dqddt *= 0. + # data.structure.timestep_info[-1].forces_constraints_FoR *= 0. + # data.structure.timestep_info[-1].forces_constraints_nodes *= 0. + + # data.structure.timestep_info[-1].dqdt *= 0. + # data.structure.timestep_info[-1].q *= 0. + # data.structure.timestep_info[-1].steady_applied_forces *= 0. + # data.structure.timestep_info[-1].unsteady_applied_forces *= 0. + + # import pdb + # pdb.set_trace() + # data.structure.timestep_info[0].q = np.append(data.structure.timestep_info[0].q, np.zeros(10)) + # data.structure.timestep_info[0].dqdt = np.append(data.structure.timestep_info[0].dqdt, np.zeros(10)) + # data.structure.timestep_info[0].dqddt = np.append(data.structure.timestep_info[0].dqddt, np.zeros(10)) + + + def increase_ts(self): + self.data.ts += 1 + self.structural_solver.next_step() + self.aero_solver.next_step() + + def run(self, **kwargs): + + # In the event the modal solver has been run prior to StaticCoupled (i.e. to get undeformed modes), copy + # results and then attach to the resulting timestep + try: + modal = self.data.structure.timestep_info[-1].modal.copy() + modal_exists = True + except AttributeError: + modal_exists = False + + self.trim_algorithm() + + if modal_exists: + self.data.structure.timestep_info[-1].modal = modal + + if self.settings['save_info']: + np.savetxt(self.folder + '/trim_values.txt', self.trimmed_values) + + # save trimmed values for dynamic coupled multibody access if needed + self.data.trimmed_values = self.trimmed_values + self.undo_changes(self.data) + + return self.data + + def convergence(self, fz, m, fx, thrust): + return_value = np.array([False, False, False]) + + if np.abs(fz) < self.settings['fz_tolerance']: + return_value[0] = True + + if np.abs(m) < self.settings['m_tolerance']: + return_value[1] = True + + # print(fx) + # print(thrust) + if np.abs(fx) < self.settings['fx_tolerance']: + return_value[2] = True + + return return_value + + def trim_algorithm(self): + """ + Trim algorithm method + + The trim condition is found iteratively. + + Returns: + np.array: array of trim values for angle of attack, control surface deflection and thrust. + """ + for self.i_iter in range(self.settings['max_iter'] + 1): + if self.i_iter == self.settings['max_iter']: + raise Exception('The Trim routine reached max iterations without convergence!') + + self.input_history.append([]) + self.output_history.append([]) + # self.gradient_history.append([]) + for i in range(self.n_input): + self.input_history[self.i_iter].append(0) + self.output_history[self.i_iter].append(0) + # self.gradient_history[self.i_iter].append(0) + + # the first iteration requires computing gradients + if not self.i_iter: + # add to input history the initial estimation + self.input_history[self.i_iter][0] = self.settings['initial_alpha'] + self.input_history[self.i_iter][1] = (self.settings['initial_deflection'] + + self.settings['initial_alpha']) + self.input_history[self.i_iter][2] = self.settings['initial_thrust'] + + # compute output + (self.output_history[self.i_iter][0], + self.output_history[self.i_iter][1], + self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], + self.input_history[self.i_iter][1], + self.input_history[self.i_iter][2]) + + # # do not check for convergence in first step to let transient take effect! + # # check for convergence (in case initial values are ok) + # if all(self.convergence(self.output_history[self.i_iter][0], + # self.output_history[self.i_iter][1], + # self.output_history[self.i_iter][2], + # self.input_history[self.i_iter][2])): + # self.trimmed_values = self.input_history[self.i_iter] + # return + + # # compute gradients + # # dfz/dalpha + # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0] + self.settings['initial_angle_eps'], + # self.input_history[self.i_iter][1], + # self.input_history[self.i_iter][2]) + + # self.gradient_history[self.i_iter][0] = ((l - self.output_history[self.i_iter][0]) / + # self.settings['initial_angle_eps']) + + # # dm/dgamma + # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], + # self.input_history[self.i_iter][1] + self.settings['initial_angle_eps'], + # self.input_history[self.i_iter][2]) + + # self.gradient_history[self.i_iter][1] = ((m - self.output_history[self.i_iter][1]) / + # self.settings['initial_angle_eps']) + + # # dfx/dthrust + # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], + # self.input_history[self.i_iter][1], + # self.input_history[self.i_iter][2] + + # self.settings['initial_thrust_eps']) + + # self.gradient_history[self.i_iter][2] = ((d - self.output_history[self.i_iter][2]) / + # self.settings['initial_thrust_eps']) + + continue + + # if not all(np.isfinite(self.gradient_history[self.i_iter - 1])) + # now back to normal evaluation (not only the i_iter == 0 case) + # compute next alpha with the previous gradient + # convergence = self.convergence(self.output_history[self.i_iter - 1][0], + # self.output_history[self.i_iter - 1][1], + # self.output_history[self.i_iter - 1][2]) + convergence = np.full((3, ), False) + if convergence[0]: + # fz is converged, don't change it + self.input_history[self.i_iter][0] = self.input_history[self.i_iter - 1][0] + # self.gradient_history[self.i_iter][0] = self.gradient_history[self.i_iter - 1][0] + else: + self.input_history[self.i_iter][0] = self.input_history[self.i_iter - 1][0] + + if convergence[1]: + # m is converged, don't change it + self.input_history[self.i_iter][1] = self.input_history[self.i_iter - 1][1] + # self.gradient_history[self.i_iter][1] = self.gradient_history[self.i_iter - 1][1] + else: + # compute next gamma with the previous gradient + self.input_history[self.i_iter][1] = self.input_history[self.i_iter - 1][1] + + if convergence[2]: + # fx is converged, don't change it + self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] + # self.gradient_history[self.i_iter][2] = self.gradient_history[self.i_iter - 1][2] + else: + # compute next gamma with the previous gradient + self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] + # else: + # if convergence[0] and convergence[1]: + + # # compute next gamma with the previous gradient + # self.input_history[self.i_iter][2] = self.balance_thrust(self.output_history[self.i_iter - 1][2]) + # else: + # self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] + + if self.settings['relaxation_factor']: + for i_dim in range(3): + self.input_history[self.i_iter][i_dim] = (self.input_history[self.i_iter][i_dim]*(1 - self.settings['relaxation_factor']) + + self.input_history[self.i_iter][i_dim]*self.settings['relaxation_factor']) + + # evaluate + (self.output_history[self.i_iter][0], + self.output_history[self.i_iter][1], + self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], + self.input_history[self.i_iter][1], + self.input_history[self.i_iter][2]) + + if not convergence[0]: + # self.gradient_history[self.i_iter][0] = ((self.output_history[self.i_iter][0] - + # self.output_history[self.i_iter - 1][0]) / + # (self.input_history[self.i_iter][0] - + # self.input_history[self.i_iter - 1][0])) + pass + + if not convergence[1]: + # self.gradient_history[self.i_iter][1] = ((self.output_history[self.i_iter][1] - + # self.output_history[self.i_iter - 1][1]) / + # (self.input_history[self.i_iter][1] - + # self.input_history[self.i_iter - 1][1])) + pass + + if not convergence[2]: + # self.gradient_history[self.i_iter][2] = ((self.output_history[self.i_iter][2] - + # self.output_history[self.i_iter - 1][2]) / + # (self.input_history[self.i_iter][2] - + # self.input_history[self.i_iter - 1][2])) + pass + + # check convergence + convergence = self.convergence(self.output_history[self.i_iter][0], + self.output_history[self.i_iter][1], + self.output_history[self.i_iter][2], + self.input_history[self.i_iter][2]) + # print(convergence) + if all(convergence) or ((self.settings['notrim_relax']) or (self.i_iter > self.settings['notrim_relax_iter'])): + self.trimmed_values = self.input_history[self.i_iter] + self.table.close_file() + return + + # def balance_thrust(self, drag): + # thrust_nodes = self.settings['thrust_nodes'] + # thrust_nodes_num = len(thrust_nodes) + # orientation = self.solver.get_direction(thrust_nodes) + # thrust = -drag/np.sum(orientation, axis=0)[0] + # return thrust + + + def evaluate(self, alpha, deflection_gamma, thrust): + if not np.isfinite(alpha): + import pdb; pdb.set_trace() + if not np.isfinite(deflection_gamma): + import pdb; pdb.set_trace() + if not np.isfinite(thrust): + import pdb; pdb.set_trace() + + # cout.cout_wrap('--', 2) + # cout.cout_wrap('Trying trim: ', 2) + # cout.cout_wrap('Alpha: ' + str(alpha*180/np.pi), 2) + # cout.cout_wrap('CS deflection: ' + str((deflection_gamma - alpha)*180/np.pi), 2) + # cout.cout_wrap('Thrust: ' + str(thrust), 2) + # modify the trim in the static_coupled solver + # self.solver.change_trim(thrust, + # self.settings['thrust_nodes'], + # deflection_gamma - alpha, + # self.settings['tail_cs_index']) + # run the solver + # import pdb + # pdb.set_trace() + control = self.solver.time_step() + # extract resultants + forces, moments = self.solver.extract_resultants() + # extract controller inputs + # control = self.solver.extract_controlcommand() + alpha = control[1] + self.input_history[self.i_iter][0] = alpha + + deflection_gamma = control[0] + self.input_history[self.i_iter][1] = deflection_gamma + + thrust = control[2] + self.input_history[self.i_iter][2] = thrust + + # deflection_gamma = control[0] + # thrust = control[1] + + forcez = forces[2] + forcex = forces[0] + moment = moments[1] + # cout.cout_wrap('Forces and moments:', 2) + # cout.cout_wrap('fx = ' + str(forces[0]) + ' mx = ' + str(moments[0]), 2) + # cout.cout_wrap('fy = ' + str(forces[1]) + ' my = ' + str(moments[1]), 2) + # cout.cout_wrap('fz = ' + str(forces[2]) + ' mz = ' + str(moments[2]), 2) + + self.table.print_line([self.i_iter, + alpha*180/np.pi, + (deflection_gamma)*180/np.pi, + thrust, + forces[0], + forces[1], + forces[2], + moments[0], + moments[1], + moments[2]]) + + return forcez, moment, forcex diff --git a/sharpy/solvers/noaero.py b/sharpy/solvers/noaero.py index 562bbe3a4..5a18a68d9 100644 --- a/sharpy/solvers/noaero.py +++ b/sharpy/solvers/noaero.py @@ -78,7 +78,7 @@ def update_grid(self, beam): -1, beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep = None): + def update_custom_grid(self, structure_tstep, aero_tstep): # called by DynamicCoupled if self.settings['update_grid']: self.data.aero.generate_zeta_timestep_info(structure_tstep, diff --git a/sharpy/solvers/nonlineardynamiccoupledstep.py b/sharpy/solvers/nonlineardynamiccoupledstep.py index c6a665b83..621686ba5 100644 --- a/sharpy/solvers/nonlineardynamiccoupledstep.py +++ b/sharpy/solvers/nonlineardynamiccoupledstep.py @@ -77,7 +77,8 @@ def initialise(self, data, custom_settings=None, restart=False): def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) - + # TODO: previous_structural_step never used + previous_structural_step = settings_utils.set_value_or_default(kwargs, 'previous_structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) xbeamlib.xbeam_step_couplednlndyn(self.data.structure, diff --git a/sharpy/solvers/nonlineardynamicmultibody.py b/sharpy/solvers/nonlineardynamicmultibody.py index bcbd4be7d..ba4046491 100644 --- a/sharpy/solvers/nonlineardynamicmultibody.py +++ b/sharpy/solvers/nonlineardynamicmultibody.py @@ -62,6 +62,25 @@ class NonLinearDynamicMultibody(_BaseStructural): settings_default['zero_ini_dot_ddot'] = False settings_description['zero_ini_dot_ddot'] = 'Set to zero the position and crv derivatives at the first time step' + settings_types['fix_prescribed_quat_ini'] = 'bool' + settings_default['fix_prescribed_quat_ini'] = False + settings_description['fix_prescribed_quat_ini'] = 'Set to initial the quaternion for prescibed bodies' + + # initial speed direction is given in inertial FOR!!! also in a lot of cases coincident with global A frame + settings_types['initial_velocity_direction'] = 'list(float)' + settings_default['initial_velocity_direction'] = [-1.0, 0.0, 0.0] + settings_description['initial_velocity_direction'] = 'Initial velocity of the reference node given in the inertial FOR' + + settings_types['initial_velocity'] = 'float' + settings_default['initial_velocity'] = 0 + settings_description['initial_velocity'] = 'Initial velocity magnitude of the reference node' + + # restart sim after dynamictrim + settings_types['dyn_trim'] = 'bool' + settings_default['dyn_trim'] = False + settings_description['dyn_trim'] = 'flag for dyntrim prior to dyncoup' + + settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) @@ -103,42 +122,302 @@ def initialise(self, data, custom_settings=None, restart=False): self.data.structure.add_unsteady_information( self.data.structure.dyn_dict, self.settings['num_steps']) - # Define the number of equations - self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) - self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) + # import pdb + # pdb.set_trace() + if self.settings['dyn_trim']: + # import pdb + # pdb.set_trace() + + self.data = self.data.previousndm.data + + self.Lambda = self.data.Lambda + self.Lambda_dot = self.data.Lambda_dot + self.Lambda_ddot = np.zeros_like(self.data.Lambda) + + num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + + new_quat = np.zeros([num_body,4]) + ini_quat = np.zeros([num_body,4]) + new_direction = np.zeros([num_body,3]) + + # import pdb + # pdb.set_trace() + # self.settings['initial_velocity_direction'] = [-0.8, 0, 0.6] + + # if self.settings['initial_velocity']: + # for ibody in range(num_body): + # new_quat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] + # ini_quat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] + # b = algebra.multiply_matrices(algebra.quat2rotation(new_quat[0]),self.settings['initial_velocity_direction']) + # new_direction[ibody] = algebra.multiply_matrices(algebra.quat2rotation(new_quat[ibody]).T,b) + # # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), + # # self.settings['initial_velocity_direction']) + # self.data.structure.timestep_info[-1].for_vel[0:3] += new_direction[0]*self.settings['initial_velocity'] + # # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + # # # self.data.structure.num_bodies + # for ibody in range(num_body): + # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,0:3] += new_direction[ibody]*self.settings['initial_velocity'] + # print(self.data.structure.timestep_info[-1].mb_FoR_vel) + + if self.settings['initial_velocity']: + for ibody in range(num_body): + new_quat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] + ini_quat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] + new_direction[ibody] = algebra.multiply_matrices(algebra.quat2rotation(new_quat[ibody]).T,self.settings['initial_velocity_direction']) + # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), + # self.settings['initial_velocity_direction']) + self.data.structure.timestep_info[-1].for_vel[0:3] += new_direction[0]*self.settings['initial_velocity'] + # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + # # self.data.structure.num_bodies + for ibody in range(num_body): + self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,0:3] += new_direction[ibody]*self.settings['initial_velocity'] + print(self.data.structure.timestep_info[-1].mb_FoR_vel) + + # import pdb + # pdb.set_trace() + # if self.settings['initial_velocity']: + # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), + # self.settings['initial_velocity_direction']) + # self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] + # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + # # self.data.structure.num_bodies + # for ibody in range(num_body): + # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel + + + # nowquat = np.zeros([num_body,4]) + # iniquat = np.zeros([num_body,4]) + # # reset the a2 rot axis for hinge axes onlyyyyyyy!!!!!!! TODO: + # for ibody in range(num_body): + # nowquat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] + # iniquat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] + + + # # hardcodeeeeeee + # for iconstraint in range(2): + # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2'] = algebra.multiply_matrices(algebra.quat2rotation(self.data.structure.timestep_info[-1].mb_quat[iconstraint+1]).T, + # algebra.quat2rotation(self.data.structure.ini_mb_dict['body_%02d' % (iconstraint+1)]['quat']), + # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2']) + + # # reset quat, pos, vel, acc + # for ibody in range(num_body): + # self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] = self.data.structure.timestep_info[-1].mb_quat[ibody] + # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_acceleration'] = self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] + # self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] = np.zeros([1,6]) + + # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_velocity'] = self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] + # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_position'] = self.data.structure.timestep_info[-1].mb_FoR_pos[ibody,:] + + # self.data.structure.timestep_info[-1].mb_dict = self.data.structure.ini_mb_dict + # self.data.structure.ini_info.mb_dict = self.data.structure.ini_mb_dict + + # # Define the number of equations + self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) + + # import pdb + # pdb.set_trace() + # Define the number of dofs + self.define_sys_size() #check + self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) + num_LM_eq = self.num_LM_eq + + + self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) + + self.settings['time_integrator_settings']['sys_size'] = self.sys_size + self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq + + # Initialise time integrator + self.time_integrator = solver_interface.initialise_solver( + self.settings['time_integrator']) + self.time_integrator.initialise( + self.data, self.settings['time_integrator_settings']) + + if self.settings['write_lm']: + dire = self.data.output_folder + '/NonLinearDynamicMultibody/' + if not os.path.isdir(dire): + os.makedirs(dire) - self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + self.out_files = {'lambda': dire + 'lambda.dat', + 'lambda_dot': dire + 'lambda_dot.dat', + 'lambda_ddot': dire + 'lambda_ddot.dat', + 'cond_number': dire + 'cond_num.dat'} - if self.settings['write_lm']: - dire = self.data.output_folder + '/NonLinearDynamicMultibody/' - if not os.path.isdir(dire): - os.makedirs(dire) - self.out_files = {'lambda': dire + 'lambda.dat', - 'lambda_dot': dire + 'lambda_dot.dat', - 'lambda_ddot': dire + 'lambda_ddot.dat', - 'cond_number': dire + 'cond_num.dat'} - # clean up files - for file in self.out_files.values(): - if os.path.isfile(file): - os.remove(file) - # Define the number of dofs - self.define_sys_size() + # # add initial speed to RBM + # if self.settings['initial_velocity']: + # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), + # self.settings['initial_velocity_direction']) + # self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] + # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + # # self.data.structure.num_bodies + # for ibody in range(num_body): + # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel + # # import pdb + # # pdb.set_trace() - self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) + # # nowquat = np.zeros([num_body,4]) + # # iniquat = np.zeros([num_body,4]) + # # # reset the a2 rot axis for hinge axes onlyyyyyyy!!!!!!! TODO: + # # for ibody in range(num_body): + # # nowquat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] + # # iniquat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] + - self.settings['time_integrator_settings']['sys_size'] = self.sys_size - self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq + # # # hardcodeeeeeee + # # for iconstraint in range(2): + # # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2'] = algebra.multiply_matrices(algebra.quat2rotation(self.data.structure.timestep_info[-1].mb_quat[iconstraint+1]).T, + # # algebra.quat2rotation(self.data.structure.ini_mb_dict['body_%02d' % (iconstraint+1)]['quat']), + # # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2']) - # Initialise time integrator - if not restart: - self.time_integrator = solver_interface.initialise_solver( - self.settings['time_integrator']) - self.time_integrator.initialise( - self.data, self.settings['time_integrator_settings'], restart=restart) + # # # reset quat, pos, vel, acc + # # for ibody in range(num_body): + # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] = self.data.structure.timestep_info[-1].mb_quat[ibody] + # # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_acceleration'] = self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] + # # self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] = np.zeros([1,6]) + + # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_velocity'] = self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] + # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_position'] = self.data.structure.timestep_info[-1].mb_FoR_pos[ibody,:] + + + + + # # Define the number of equations + # self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) + # print(self.data.structure.ini_mb_dict) + # # import pdb + # # pdb.set_trace() + # self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) + + # # self.num_LM_eq = 10 + + # structural_step = self.data.structure.timestep_info[-1] + # # dt= self.settings['dt'] + # # import pdb + # # pdb.set_trace() + + + # MBdict = structural_step.mb_dict + + + # # import pdb + # # pdb.set_trace() + + # MB_beam, MB_tstep = mb.split_multibody( + # self.data.structure, + # structural_step, + # MBdict, + # -1) + + # # import pdb + # # pdb.set_trace() + + # q = [] + # dqdt = [] + # dqddt = [] + + # for ibody in range(num_body): + # q = np.append(q, MB_tstep[ibody].q) + # dqdt = np.append(dqdt, MB_tstep[ibody].dqdt) + # dqddt = np.append(dqddt, MB_tstep[ibody].dqddt) + + # q = np.append(q, self.data.Lambda) + # dqdt = np.append(dqdt, self.data.Lambda_dot) + # dqddt = np.append(dqddt, np.zeros([10])) + + # # q = np.insert(q, 336, np.zeros([10])) + # # dqdt = np.insert(dqdt, 336, np.zeros([10])) + # # dqddt = np.insert(dqddt, 336, np.zeros([10])) + + + + # self.Lambda, self.Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, self.num_LM_eq) + + # self.Lambda_ddot = np.zeros_like(self.Lambda) + + # # self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + # # self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + # # self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + + # if self.settings['write_lm']: + # dire = self.data.output_folder + '/NonLinearDynamicMultibody/' + # if not os.path.isdir(dire): + # os.makedirs(dire) + + # self.out_files = {'lambda': dire + 'lambda.dat', + # 'lambda_dot': dire + 'lambda_dot.dat', + # 'lambda_ddot': dire + 'lambda_ddot.dat', + # 'cond_number': dire + 'cond_num.dat'} + # # clean up files + # for file in self.out_files.values(): + # if os.path.isfile(file): + # os.remove(file) + + # # Define the number of dofs + # self.define_sys_size() + + # self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) + + # self.settings['time_integrator_settings']['sys_size'] = self.sys_size + # self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq + + # # Initialise time integrator + # if not restart: + # self.time_integrator = solver_interface.initialise_solver( + # self.settings['time_integrator']) + # self.time_integrator.initialise( + # self.data, self.settings['time_integrator_settings'], restart=restart) + + else: + # add initial speed to RBM + if self.settings['initial_velocity']: + new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), + self.settings['initial_velocity_direction']) + self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] + num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] + # self.data.structure.num_bodies + for ibody in range(num_body): + self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel + # import pdb + # pdb.set_trace() + + # Define the number of equations + self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) + self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) + + self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + + if self.settings['write_lm']: + dire = self.data.output_folder + '/NonLinearDynamicMultibody/' + if not os.path.isdir(dire): + os.makedirs(dire) + + self.out_files = {'lambda': dire + 'lambda.dat', + 'lambda_dot': dire + 'lambda_dot.dat', + 'lambda_ddot': dire + 'lambda_ddot.dat', + 'cond_number': dire + 'cond_num.dat'} + # clean up files + for file in self.out_files.values(): + if os.path.isfile(file): + os.remove(file) + + # Define the number of dofs + self.define_sys_size() + + self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) + + self.settings['time_integrator_settings']['sys_size'] = self.sys_size + self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq + + # Initialise time integrator + if not restart: + self.time_integrator = solver_interface.initialise_solver( + self.settings['time_integrator']) + self.time_integrator.initialise( + self.data, self.settings['time_integrator_settings'], restart=restart) def add_step(self): self.data.structure.next_step() @@ -204,6 +483,8 @@ def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, M first_dof = 0 last_dof = 0 + # import pdb + # pdb.set_trace() # Loop through the different bodies for ibody in range(len(MB_beam)): @@ -213,10 +494,22 @@ def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, M K = None Q = None + + # import pdb + # pdb.set_trace() # Generate the matrices for each body if MB_beam[ibody].FoR_movement == 'prescribed': last_dof = first_dof + MB_beam[ibody].num_dof.value + # old_quat = MB_tstep[ibody].quat.copy() + M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) + # MB_tstep[ibody].quat = old_quat + + elif MB_beam[ibody].FoR_movement == 'prescribed_trim': + last_dof = first_dof + MB_beam[ibody].num_dof.value + # old_quat = MB_tstep[ibody].quat.copy() M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) + # MB_tstep[ibody].quat = old_quat + elif MB_beam[ibody].FoR_movement == 'free': last_dof = first_dof + MB_beam[ibody].num_dof.value + 10 @@ -283,8 +576,36 @@ def integrate_position(self, MB_beam, MB_tstep, dt): MB_tstep[ibody].for_pos[0:3] += dt*np.dot(MB_tstep[ibody].cga(),MB_tstep[ibody].for_vel[0:3]) def extract_resultants(self, tstep): + # import pdb + # pdb.set_trace() + # TODO: code + if tstep is None: + tstep = self.data.structure.timestep_info[self.data.ts] + steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) + totals = steady + unsteady + grav + return totals[0:3], totals[3:6] + + def extract_resultants_not_required(self, tstep): + # as ibody = None returns for entire structure! How convenient! + import pdb + pdb.set_trace() # TODO: code - return np.zeros((3)), np.zeros((3)) + if tstep is None: + tstep = self.data.structure.timestep_info[self.data.ts] + steady_running = 0 + unsteady_running = 0 + grav_running = 0 + for body in range (0, self.data.structure.ini_mb_dict['num_bodies']): + steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav'], ibody = body) + steady_running += steady + unsteady_running += unsteady + grav_running += grav + totals = steady_running + unsteady_running + grav_running + return totals[0:3], totals[3:6] + + + + # return np.zeros((3)), np.zeros((3)) def compute_forces_constraints(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot): """ @@ -352,6 +673,8 @@ def write_lm_cond_num(self, iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) + # import pdb + # pdb.set_trace() if structural_step.mb_dict is not None: MBdict = structural_step.mb_dict @@ -380,6 +703,16 @@ def run(self, **kwargs): MB_tstep[ibody].psi_dot_local *= 0. MB_tstep[ibody].psi_ddot *= 0. + + # # Initialize + # # TODO: i belive this can move into disp_and_accel2 state as self.Lambda, self.Lambda_dot + + + # # Predictor step + # q, dqdt, dqddt = mb.disp_and_accel2state(MB_beam, MB_tstep, self.Lambda, self.Lambda_dot, self.sys_size, num_LM_eq) + # self.time_integrator.predictor(q, dqdt, dqddt) + + # else: # Initialize # TODO: i belive this can move into disp_and_accel2 state as self.Lambda, self.Lambda_dot if not num_LM_eq == 0: @@ -393,7 +726,7 @@ def run(self, **kwargs): # Predictor step q, dqdt, dqddt = mb.disp_and_accel2state(MB_beam, MB_tstep, Lambda, Lambda_dot, self.sys_size, num_LM_eq) - self.time_integrator.predictor(q, dqdt, dqddt) + self.time_integrator.predictor(q, dqdt, dqddt) # Reference residuals old_Dq = 1.0 @@ -405,7 +738,9 @@ def run(self, **kwargs): if iteration == self.settings['max_iterations'] - 1: error = ('Solver did not converge in %d iterations.\n res = %e \n LM_res = %e' % (iteration, res, LM_res)) - raise exc.NotConvergedSolver(error) + print(error) + break + # raise exc.NotConvergedSolver(error) # Update positions and velocities Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) @@ -424,6 +759,18 @@ def run(self, **kwargs): Asys, Q = self.time_integrator.build_matrix(MB_M, MB_C, MB_K, MB_Q, kBnh, LM_Q) + np.set_printoptions(threshold=np.inf) + # import pdb + # pdb.set_trace() + + # print(Asys) + # print(Q) + # import sympy + # rref, inds = sympy.Matrix(Asys).rref() + # print(inds) + # print(rref) + # print(np.linalg.inv(Asys)) + if self.settings['write_lm']: cond_num = np.linalg.cond(Asys[:self.sys_size, :self.sys_size]) cond_num_lm = np.linalg.cond(Asys) @@ -479,7 +826,13 @@ def run(self, **kwargs): if (res < self.settings['min_delta']) and (LM_res < self.settings['min_delta']): break + # if (res < self.settings['min_delta']) and (np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq])) < self.settings['abs_threshold']): + # print(f'Relative \'min_delta\' threshold not reached - LM_res is {LM_res} >= \'min_delta\' {self.settings["min_delta"]} abs res is {np.max(np.abs(Dq[0:self.sys_size]))}, abs LM_res is {np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq]))} < {self.settings["abs_threshold"]}') + # break + # import pdb + # pdb.set_trace() + # print("end - check") Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) if self.settings['write_lm']: self.write_lm_cond_num(iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num, cond_num_lm) @@ -499,6 +852,7 @@ def run(self, **kwargs): MB_tstep[ibody].quat = np.dot(Temp, np.dot(np.eye(4) - 0.25*algebra.quadskew(MB_tstep[ibody].for_vel[3:6])*dt, MB_tstep[ibody].quat)) # End of Newmark-beta iterations + # self.beta = self.time_integrator.beta # self.integrate_position(MB_beam, MB_tstep, dt) lagrangeconstraints.postprocess(self.lc_list, MB_beam, MB_tstep, "dynamic") self.compute_forces_constraints(MB_beam, MB_tstep, self.data.ts, dt, Lambda, Lambda_dot) @@ -511,4 +865,16 @@ def run(self, **kwargs): self.Lambda_dot = Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') self.Lambda_ddot = Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') + self.data.Lambda = Lambda.astype(dtype=ct.c_double, copy=True, order='F') + self.data.Lambda_dot = Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') + self.data.Lambda_ddot = Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') + + self.data.previousndm = self + + # name = "struc_" + str(self.data.ts) + ".txt" + # from pprint import pprint as ppr + # file = open(name,'wt') + # ppr(self.data.structure.timestep_info[-1].__dict__, stream=file) + # file.close() + return self.data diff --git a/sharpy/solvers/staticcoupled.py b/sharpy/solvers/staticcoupled.py index e3fde1bae..74f05a159 100644 --- a/sharpy/solvers/staticcoupled.py +++ b/sharpy/solvers/staticcoupled.py @@ -74,10 +74,6 @@ class StaticCoupled(BaseSolver): settings_description['runtime_generators'] = 'The dictionary keys are the runtime generators to be used. ' \ 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' - - settings_types['nonlifting_body_interactions'] = 'bool' - settings_default['nonlifting_body_interactions'] = False - settings_description['nonlifting_body_interactions'] = 'Consider forces induced by nonlifting bodies' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -153,20 +149,17 @@ def increase_ts(self): def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: - self.remove_old_timestep_info(self.data.structure.timestep_info) - self.remove_old_timestep_info(self.data.aero.timestep_info) - if self.settings['nonlifting_body_interactions']: - self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) + # copy last info to first + self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1].copy() + self.data.structure.timestep_info[0] = self.data.structure.timestep_info[-1].copy() + # delete all the rest + while len(self.data.aero.timestep_info) - 1: + del self.data.aero.timestep_info[-1] + while len(self.data.structure.timestep_info) - 1: + del self.data.structure.timestep_info[-1] self.data.ts = 0 - def remove_old_timestep_info(self, tstep_info): - # copy last info to first - tstep_info[0] = tstep_info[-1].copy() - # delete all the rest - while len(tstep_info) - 1: - del tstep_info[-1] - def run(self, **kwargs): for i_step in range(self.settings['n_load_steps'] + 1): if (i_step == self.settings['n_load_steps'] and @@ -196,29 +189,14 @@ def run(self, **kwargs): self.data.structure.node_master_elem, self.data.structure.connectivities, self.data.structure.timestep_info[self.data.ts].cag(), - self.data.aero.data_dict) - + self.data.aero.aero_dict) + if self.correct_forces: struct_forces = \ self.correct_forces_generator.generate(aero_kstep=self.data.aero.timestep_info[self.data.ts], structural_kstep=self.data.structure.timestep_info[self.data.ts], struct_forces=struct_forces, ts=0) - - # map nonlifting forces to structural nodes - if self.settings['nonlifting_body_interactions']: - struct_forces += mapping.aero2struct_force_mapping( - self.data.nonlifting_body.timestep_info[self.data.ts].forces, - self.data.nonlifting_body.struct2aero_mapping, - self.data.nonlifting_body.timestep_info[self.data.ts].zeta, - self.data.structure.timestep_info[self.data.ts].pos, - self.data.structure.timestep_info[self.data.ts].psi, - self.data.structure.node_master_elem, - self.data.structure.connectivities, - self.data.structure.timestep_info[self.data.ts].cag(), - self.data.nonlifting_body.data_dict, - skip_moments_generated_by_forces = True) - self.data.aero.timestep_info[self.data.ts].aero_steady_forces_beam_dof = struct_forces self.data.structure.timestep_info[self.data.ts].postproc_node['aero_steady_forces'] = struct_forces # B @@ -261,7 +239,6 @@ def run(self, **kwargs): # update grid self.aero_solver.update_step() - self.structural_solver.update(self.data.structure.timestep_info[self.data.ts]) # convergence if self.convergence(i_iter, i_step): # create q and dqdt vectors @@ -345,7 +322,10 @@ def change_trim(self, alpha, thrust, thrust_nodes, tail_deflection, tail_cs_inde for i_node, node in enumerate(thrust_nodes): self.force_orientation[i_node, :] = ( algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3])) - # print(self.force_orientation) + print(self.force_orientation) + #TODO: HARDCODE + self.force_orientation = np.array([[0., 1., 0.],[0., -1., 0.]]) + print(self.force_orientation) # thrust # thrust is scaled so that the direction of the forces is conserved @@ -365,7 +345,7 @@ def change_trim(self, alpha, thrust, thrust_nodes, tail_deflection, tail_cs_inde # tail deflection try: - self.data.aero.data_dict['control_surface_deflection'][tail_cs_index] = tail_deflection + self.data.aero.aero_dict['control_surface_deflection'][tail_cs_index] = tail_deflection except KeyError: raise Exception('This model has no control surfaces') except IndexError: diff --git a/sharpy/solvers/staticuvlm.py b/sharpy/solvers/staticuvlm.py index e9fa9b859..66fcd7d35 100644 --- a/sharpy/solvers/staticuvlm.py +++ b/sharpy/solvers/staticuvlm.py @@ -43,18 +43,6 @@ class StaticUvlm(BaseSolver): settings_default['horseshoe'] = False settings_description['horseshoe'] = 'Horseshoe wake modelling for steady simulations.' - settings_types['nonlifting_body_interactions'] = 'bool' - settings_default['nonlifting_body_interactions'] = False - settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' - - settings_types['only_nonlifting'] = 'bool' - settings_default['only_nonlifting'] = False - settings_description['only_nonlifting'] = 'Consider only nonlifting bodies' - - settings_types['phantom_wing_test'] = 'bool' - settings_default['phantom_wing_test'] = False - settings_description['phantom_wing_test'] = 'Debug option' - settings_types['num_cores'] = 'int' settings_default['num_cores'] = 0 settings_description['num_cores'] = 'Number of cores to use in the VLM lib' @@ -123,10 +111,6 @@ class StaticUvlm(BaseSolver): settings_default['map_forces_on_struct'] = False settings_description['map_forces_on_struct'] = 'Maps the forces on the structure at the end of the timestep. Only usefull if the solver is used outside StaticCoupled' - settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' - settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 - settings_description['ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' - settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) @@ -154,84 +138,60 @@ def initialise(self, data, custom_settings=None, restart=False): def add_step(self): self.data.aero.add_timestep() - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.add_timestep() - def update_grid(self, beam): + self.data.aero.generate_zeta(beam, + self.data.aero.aero_settings, + -1, + beam_ts=-1) - if not self.settings['only_nonlifting']: - self.data.aero.generate_zeta(beam, - self.data.aero.aero_settings, - -1, - beam_ts=-1) - if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: - self.data.nonlifting_body.generate_zeta(beam, - self.data.nonlifting_body.aero_settings, - -1, - beam_ts=-1) - - def update_custom_grid(self, structure_tstep, aero_tstep, nonlifting_tstep=None): + def update_custom_grid(self, structure_tstep, aero_tstep): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['rollup_dt']) - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, - nonlifting_tstep, - self.data.structure, - self.data.nonlifting_body.aero_settings) def run(self, **kwargs): - structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[self.data.ts]) - - if not self.settings['only_nonlifting']: - aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[self.data.ts]) - if not self.data.aero.timestep_info[self.data.ts].zeta: - return self.data - - # generate the wake because the solid shape might change - self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, - 'zeta_star': aero_tstep.zeta_star, - 'gamma': aero_tstep.gamma, - 'gamma_star': aero_tstep.gamma_star, - 'dist_to_orig': aero_tstep.dist_to_orig}) - - if self.settings['nonlifting_body_interactions']: - # generate uext - self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, - 'override': True, - 'for_pos': structure_tstep.for_pos[0:3]}, - self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) - # generate uext - self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, - 'override': True, - 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, - self.data.aero.timestep_info[self.data.ts].u_ext) - # grid orientation - uvlmlib.vlm_solver_lifting_and_nonlifting_bodies(self.data.aero.timestep_info[self.data.ts], - self.data.nonlifting_body.timestep_info[self.data.ts], - self.settings) - else: - # generate uext - self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, - 'override': True, - 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, - self.data.aero.timestep_info[self.data.ts].u_ext) - - - # grid orientation - uvlmlib.vlm_solver(self.data.aero.timestep_info[self.data.ts], - self.settings) - else: - self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, - 'override': True, - 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, - self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) - uvlmlib.vlm_solver_nonlifting_body(self.data.nonlifting_body.timestep_info[self.data.ts], - self.settings) + aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) + structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) + dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['rollup_dt']) + t = settings_utils.set_value_or_default(kwargs, 't', self.data.ts*dt) + + unsteady_contribution = False + convect_wake = False + + if not aero_tstep.zeta: + return self.data + + # generate the wake because the solid shape might change + self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, + 'zeta_star': aero_tstep.zeta_star, + 'gamma': aero_tstep.gamma, + 'gamma_star': aero_tstep.gamma_star, + 'dist_to_orig': aero_tstep.dist_to_orig}) + + # generate uext + self.velocity_generator.generate({'zeta': aero_tstep.zeta, + 'override': True, + 'for_pos': structure_tstep.for_pos[0:3]}, + aero_tstep.u_ext) + # grid orientation + uvlmlib.vlm_solver(aero_tstep, + self.settings) + + if self.settings['map_forces_on_struct']: + structure_tstep.steady_applied_forces[:] = mapping.aero2struct_force_mapping( + aero_tstep.forces, + self.data.aero.struct2aero_mapping, + self.data.aero.timestep_info[self.data.ts].zeta, + structure_tstep.pos, + structure_tstep.psi, + self.data.structure.node_master_elem, + self.data.structure.connectivities, + structure_tstep.cag(), + self.data.aero.aero_dict) return self.data @@ -239,17 +199,12 @@ def next_step(self): """ Updates de aerogrid based on the info of the step, and increases the self.ts counter """ self.data.aero.add_timestep() - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.add_timestep() self.update_step() def update_step(self): - if not self.settings['only_nonlifting']: - self.data.aero.generate_zeta(self.data.structure, - self.data.aero.aero_settings, - self.data.ts) - if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: - self.data.nonlifting_body.generate_zeta(self.data.structure, - self.data.nonlifting_body.aero_settings, - self.data.ts) - + self.data.aero.generate_zeta(self.data.structure, + self.data.aero.aero_settings, + self.data.ts) + # for i_surf in range(self.data.aero.timestep_info[self.data.ts].n_surf): + # self.data.aero.timestep_info[self.data.ts].forces[i_surf].fill(0.0) + # self.data.aero.timestep_info[self.data.ts].dynamic_forces[i_surf].fill(0.0) diff --git a/sharpy/solvers/stepuvlm.py b/sharpy/solvers/stepuvlm.py index ba4b2a686..d4cd9ad40 100644 --- a/sharpy/solvers/stepuvlm.py +++ b/sharpy/solvers/stepuvlm.py @@ -130,26 +130,6 @@ class StepUvlm(BaseSolver): settings_types['quasi_steady'] = 'bool' settings_default['quasi_steady'] = False settings_description['quasi_steady'] = 'Use quasi-steady approximation in UVLM' - - settings_types['only_nonlifting'] = 'bool' - settings_default['only_nonlifting'] = False - settings_description['only_nonlifting'] = 'Consider nonlifting body interactions' - - settings_types['nonlifting_body_interactions'] = 'bool' - settings_default['nonlifting_body_interactions'] = False - settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' - - settings_types['phantom_wing_test'] = 'bool' - settings_default['phantom_wing_test'] = False - settings_description['phantom_wing_test'] = 'Debug option' - - settings_types['centre_rot_g'] = 'list(float)' - settings_default['centre_rot_g'] = [0., 0., 0.] - settings_description['centre_rot_g'] = 'Centre of rotation in G FoR around which ``rbm_vel_g`` is applied' - - settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' - settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 - settings_description['ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -238,32 +218,13 @@ def run(self, **kwargs): 'for_pos': structure_tstep.for_pos, 'is_wake': True}, aero_tstep.u_ext_star) - if self.settings['nonlifting_body_interactions']: - nl_body_tstep = settings_utils.set_value_or_default(kwargs, 'nl_body_tstep', self.data.nonlifting_body.timestep_info[-1]) - self.velocity_generator.generate({'zeta': nl_body_tstep.zeta, - 'override': True, - 'ts': self.data.ts, - 'dt': dt, - 't': t, - 'for_pos': structure_tstep.for_pos, - 'is_wake': False}, - nl_body_tstep.u_ext) - - uvlmlib.uvlm_solver_lifting_and_nonlifting(self.data.ts, - aero_tstep, - nl_body_tstep, - structure_tstep, - self.settings, - convect_wake=convect_wake, - dt=dt) - else: - uvlmlib.uvlm_solver(self.data.ts, - aero_tstep, - structure_tstep, - self.settings, - convect_wake=convect_wake, - dt=dt) + uvlmlib.uvlm_solver(self.data.ts, + aero_tstep, + structure_tstep, + self.settings, + convect_wake=convect_wake, + dt=dt) if unsteady_contribution and not self.settings['quasi_steady']: # calculate unsteady (added mass) forces: @@ -287,38 +248,24 @@ def run(self, **kwargs): else: for i_surf in range(len(aero_tstep.gamma)): aero_tstep.gamma_dot[i_surf][:] = 0.0 + return self.data def add_step(self): self.data.aero.add_timestep() - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.add_timestep() def update_grid(self, beam): self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) - if self.settings['nonlifting_body_interactions']: - self.data.nonlifting_body.generate_zeta(beam, - self.data.aero.aero_settings, - -1, - beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep = None): + def update_custom_grid(self, structure_tstep, aero_tstep): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['dt']) - if self.settings['nonlifting_body_interactions']: - if nl_body_tstep is None: - nl_body_tstep = self.data.nonlifting_body.timestep_info[-1] - self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, - nl_body_tstep, - self.data.structure, - self.data.nonlifting_body.aero_settings, - dt = self.settings['dt']) @staticmethod def filter_gamma_dot(tstep, history, filter_param): diff --git a/sharpy/solvers/trim.py b/sharpy/solvers/trim.py index 4cf0a5a05..b8543f21e 100644 --- a/sharpy/solvers/trim.py +++ b/sharpy/solvers/trim.py @@ -291,7 +291,7 @@ def solver_wrapper(x, x_info, solver_data, i_dim=-1): tstep.quat[:] = orientation_quat # control surface deflection for i_cs in range(len(x_info['i_control_surfaces'])): - solver_data.data.aero.data_dict['control_surface_deflection'][x_info['control_surfaces_id'][i_cs]] = x[x_info['i_control_surfaces'][i_cs]] + solver_data.data.aero.aero_dict['control_surface_deflection'][x_info['control_surfaces_id'][i_cs]] = x[x_info['i_control_surfaces'][i_cs]] # thrust input tstep.steady_applied_forces[:] = 0.0 try: From 017583b700b24779478f3dd73f0e1ee17333c45b Mon Sep 17 00:00:00 2001 From: kccwing <60852830+kccwing@users.noreply.github.com> Date: Tue, 9 Apr 2024 00:42:00 +0100 Subject: [PATCH 52/53] Revert "fixes for initialisation" This reverts commit 282cb31e04c13c5315fb8aa1de7fd7830c5db192. --- sharpy/solvers/aerogridloader.py | 48 +- sharpy/solvers/dynamiccoupled.py | 391 ++-------- sharpy/solvers/dynamictrim.py | 732 ------------------ sharpy/solvers/noaero.py | 2 +- sharpy/solvers/nonlineardynamiccoupledstep.py | 3 +- sharpy/solvers/nonlineardynamicmultibody.py | 430 +--------- sharpy/solvers/staticcoupled.py | 50 +- sharpy/solvers/staticuvlm.py | 143 ++-- sharpy/solvers/stepuvlm.py | 69 +- sharpy/solvers/trim.py | 2 +- 10 files changed, 298 insertions(+), 1572 deletions(-) delete mode 100644 sharpy/solvers/dynamictrim.py diff --git a/sharpy/solvers/aerogridloader.py b/sharpy/solvers/aerogridloader.py index bc8f9a8df..af8098676 100644 --- a/sharpy/solvers/aerogridloader.py +++ b/sharpy/solvers/aerogridloader.py @@ -6,12 +6,13 @@ import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils import sharpy.utils.generator_interface as gen_interface +from sharpy.solvers.gridloader import GridLoader @solver -class AerogridLoader(BaseSolver): +class AerogridLoader(GridLoader): """ - ``AerogridLoader`` class, inherited from ``BaseSolver`` + ``AerogridLoader`` class, inherited from ``GridLoader`` Generates aerodynamic grid based on the input data @@ -36,9 +37,9 @@ class AerogridLoader(BaseSolver): settings_types (dict): Acceptable types for the values in ``settings`` settings_default (dict): Name-value pair of default values for the aerodynamic settings data (ProblemData): class structure - aero_file_name (str): name of the ``.aero.h5`` HDF5 file + file_name (str): name of the ``.aero.h5`` HDF5 file aero: empty attribute - aero_data_dict (dict): key-value pairs of aerodynamic data + data_dict (dict): key-value pairs of aerodynamic data wake_shape_generator (class): Wake shape generator """ @@ -89,28 +90,13 @@ class AerogridLoader(BaseSolver): settings_options=settings_options) def __init__(self): - self.data = None - self.settings = None - self.aero_file_name = '' - # storage of file contents - self.aero_data_dict = dict() - - # aero storage + super().__init__ + self.file_name = '.aero.h5' self.aero = None - self.wake_shape_generator = None def initialise(self, data, restart=False): - self.data = data - self.settings = data.settings[self.solver_id] - - # init settings - settings_utils.to_custom_types(self.settings, - self.settings_types, - self.settings_default, options=self.settings_options) - - # read input file (aero) - self.read_files() + super().initialise(data) wake_shape_generator_type = gen_interface.generator_from_string( self.settings['wake_shape_generator']) @@ -119,25 +105,9 @@ def initialise(self, data, restart=False): self.settings['wake_shape_generator_input'], restart=restart) - def read_files(self): - # open aero file - # first, file names - self.aero_file_name = (self.data.case_route + - '/' + - self.data.case_name + - '.aero.h5') - - # then check that the file exists - h5utils.check_file_exists(self.aero_file_name) - - # read and store the hdf5 file - with h5.File(self.aero_file_name, 'r') as aero_file_handle: - # store files in dictionary - self.aero_data_dict = h5utils.load_h5_in_dict(aero_file_handle) - def run(self, **kwargs): self.data.aero = aerogrid.Aerogrid() - self.data.aero.generate(self.aero_data_dict, + self.data.aero.generate(self.data_dict, self.data.structure, self.settings, self.data.ts) diff --git a/sharpy/solvers/dynamiccoupled.py b/sharpy/solvers/dynamiccoupled.py index 0e5950d22..719a58380 100644 --- a/sharpy/solvers/dynamiccoupled.py +++ b/sharpy/solvers/dynamiccoupled.py @@ -178,6 +178,10 @@ class DynamicCoupled(BaseSolver): 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' + settings_types['nonlifting_body_interactions'] = 'bool' + settings_default['nonlifting_body_interactions'] = False + settings_description['nonlifting_body_interactions'] = 'Effect of Nonlifting Bodies on Lifting bodies are considered' + settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -365,17 +369,20 @@ def initialise(self, data, custom_settings=None, restart=False): def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: - # copy last info to first - self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1] - self.data.structure.timestep_info[0] = self.data.structure.timestep_info[-1] - # delete all the rest - while len(self.data.aero.timestep_info) - 1: - del self.data.aero.timestep_info[-1] - while len(self.data.structure.timestep_info) - 1: - del self.data.structure.timestep_info[-1] + self.remove_old_timestep_info(self.data.structure.timestep_info) + self.remove_old_timestep_info(self.data.aero.timestep_info) + if self.settings['nonlifting_body_interactions']: + self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) self.data.ts = 0 + def remove_old_timestep_info(self, tstep_info): + # copy last info to first + tstep_info[0] = tstep_info[-1].copy() + # delete all the rest + while len(tstep_info) - 1: + del tstep_info[-1] + def process_controller_output(self, controlled_state): """ This function modified the solver properties and parameters as @@ -508,8 +515,6 @@ def network_loop(self, in_queue, out_queue, finish_event): out_network.close() def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=None): - # import pdb - # pdb.set_trace() self.logger.debug('Inside time loop') # dynamic simulations start at tstep == 1, 0 is reserved for the initial state for self.data.ts in range( @@ -527,6 +532,10 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No structural_kstep = self.data.structure.timestep_info[-1].copy() aero_kstep = self.data.aero.timestep_info[-1].copy() + if self.settings['nonlifting_body_interactions']: + nl_body_kstep = self.data.nonlifting_body.timestep_info[-1].copy() + else: + nl_body_kstep = None self.logger.debug('Time step {}'.format(self.data.ts)) # Add the controller here @@ -534,13 +543,10 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No state = {'structural': structural_kstep, 'aero': aero_kstep} for k, v in self.controllers.items(): - state, control = v.control(self.data, state) + state = v.control(self.data, state) # this takes care of the changes in options for the solver structural_kstep, aero_kstep = self.process_controller_output( state) - self.aero_solver.update_custom_grid(state['structural'], state['aero']) - # import pdb - # pdb.set_trace() # Add external forces if self.with_runtime_generators: @@ -570,16 +576,17 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No cout.cout_wrap(("The FSI solver did not converge!!! residuals: %f %f" % (print_res, print_res_dqdt))) self.aero_solver.update_custom_grid( structural_kstep, - aero_kstep) + aero_kstep, + nl_body_kstep) break # generate new grid (already rotated) aero_kstep = controlled_aero_kstep.copy() - # import pdb - # pdb.set_trace() + self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep) + structural_kstep, + aero_kstep, + nl_body_kstep) # compute unsteady contribution force_coeff = 0.0 @@ -611,7 +618,8 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No self.data = self.aero_solver.run(aero_step=aero_kstep, structural_step=structural_kstep, convect_wake=True, - unsteady_contribution=unsteady_contribution) + unsteady_contribution=unsteady_contribution, + nl_body_tstep = nl_body_kstep) self.time_aero += time.perf_counter() - ini_time_aero previous_kstep = structural_kstep.copy() @@ -622,12 +630,15 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No previous_kstep.runtime_unsteady_forces = previous_runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid(structural_kstep, - aero_kstep) - + self.aero_solver.update_custom_grid( + structural_kstep, + aero_kstep, + nl_body_kstep) + self.map_forces(aero_kstep, - structural_kstep, - force_coeff) + structural_kstep, + nl_body_kstep = nl_body_kstep, + unsteady_forces_coeff = force_coeff) # relaxation relax_factor = self.relaxation_factor(k) @@ -671,16 +682,20 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No self.aero_solver, self.with_runtime_generators): # move the aerodynamic surface according to the structural one - self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep) + self.aero_solver.update_custom_grid(structural_kstep, + aero_kstep, + nl_body_tstep = nl_body_kstep) break # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid(structural_kstep, aero_kstep) + self.aero_solver.update_custom_grid(structural_kstep, + aero_kstep, + nl_body_tstep = nl_body_kstep) self.aero_solver.add_step() self.data.aero.timestep_info[-1] = aero_kstep.copy() + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.timestep_info[-1] = nl_body_kstep.copy() self.structural_solver.add_step() self.data.structure.timestep_info[-1] = structural_kstep.copy() @@ -698,11 +713,9 @@ def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=No structural_kstep.for_vel[2], np.sum(structural_kstep.steady_applied_forces[:, 0]), np.sum(structural_kstep.steady_applied_forces[:, 2])]) - # import pdb - # pdb.set_trace() (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( - self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) + self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) # run postprocessors if self.with_postprocessors: for postproc in self.postprocessors: @@ -757,7 +770,9 @@ def convergence(self, k, tstep, previous_tstep, return False # Check the special case of no aero and no runtime generators - if aero_solver.solver_id.lower() == "noaero" and not with_runtime_generators: + if (aero_solver.solver_id.lower() == "noaero"\ + or struct_solver.solver_id.lower() == "nostructural")\ + and not with_runtime_generators: return True # relative residuals @@ -796,7 +811,7 @@ def convergence(self, k, tstep, previous_tstep, return True - def map_forces(self, aero_kstep, structural_kstep, unsteady_forces_coeff=1.0): + def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unsteady_forces_coeff=1.0): # set all forces to 0 structural_kstep.steady_applied_forces.fill(0.0) structural_kstep.unsteady_applied_forces.fill(0.0) @@ -811,8 +826,8 @@ def map_forces(self, aero_kstep, structural_kstep, unsteady_forces_coeff=1.0): self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), - self.data.aero.aero_dict) - dynamic_struct_forces = mapping.aero2struct_force_mapping( + self.data.aero.data_dict) + dynamic_struct_forces = unsteady_forces_coeff*mapping.aero2struct_force_mapping( aero_kstep.dynamic_forces, self.data.aero.struct2aero_mapping, aero_kstep.zeta, @@ -821,7 +836,7 @@ def map_forces(self, aero_kstep, structural_kstep, unsteady_forces_coeff=1.0): self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), - self.data.aero.aero_dict) + self.data.aero.data_dict) if self.correct_forces: struct_forces = \ @@ -834,6 +849,18 @@ def map_forces(self, aero_kstep, structural_kstep, unsteady_forces_coeff=1.0): structural_kstep.postproc_node['aero_steady_forces'] = struct_forces structural_kstep.postproc_node['aero_unsteady_forces'] = dynamic_struct_forces + # if self.settings['nonlifting_body_interactions']: + # struct_forces += mapping.aero2struct_force_mapping( + # nl_body_kstep.forces, + # self.data.nonlifting_body.struct2aero_mapping, + # nl_body_kstep.zeta, + # structural_kstep.pos, + # structural_kstep.psi, + # self.data.structure.node_master_elem, + # self.data.structure.connectivities, + # structural_kstep.cag(), + # self.data.nonlifting_body.data_dict) + # prescribed forces + aero forces # prescribed forces + aero forces + runtime generated structural_kstep.steady_applied_forces += struct_forces structural_kstep.steady_applied_forces += self.data.structure.ini_info.steady_applied_forces @@ -859,296 +886,6 @@ def relaxation_factor(self, k): value = initial + (final - initial)/self.settings['relaxation_steps']*k return value - def change_trim(self, thrust, thrust_nodes, tail_deflection, tail_cs_index): - # self.cleanup_timestep_info() - # self.data.structure.timestep_info = [] - # self.data.structure.timestep_info.append(self.data.structure.ini_info.copy()) - # aero_copy = self.data.aero.timestep_info[-1] - # self.data.aero.timestep_info = [] - # self.data.aero.timestep_info.append(aero_copy) - # self.data.ts = 0 - - - try: - self.force_orientation - except AttributeError: - self.force_orientation = np.zeros((len(thrust_nodes), 3)) - for i_node, node in enumerate(thrust_nodes): - self.force_orientation[i_node, :] = ( - algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3])) - # print(self.force_orientation) - - # thrust - # thrust is scaled so that the direction of the forces is conserved - # in all nodes. - # the `thrust` parameter is the force PER node. - # if there are two or more nodes in thrust_nodes, the total forces - # is n_nodes_in_thrust_nodes*thrust - # thrust forces have to be indicated in structure.ini_info - # print(algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[0, 0:3])*thrust) - for i_node, node in enumerate(thrust_nodes): - # self.data.structure.ini_info.steady_applied_forces[i_node, 0:3] = ( - # algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[i_node, 0:3])*thrust) - self.data.structure.ini_info.steady_applied_forces[node, 0:3] = ( - self.force_orientation[i_node, :]*thrust) - self.data.structure.timestep_info[0].steady_applied_forces[node, 0:3] = ( - self.force_orientation[i_node, :]*thrust) - - # tail deflection - try: - self.data.aero.aero_dict['control_surface_deflection'][tail_cs_index] = tail_deflection - except KeyError: - raise Exception('This model has no control surfaces') - except IndexError: - raise Exception('The tail control surface index > number of surfaces') - - # update grid - self.aero_solver.update_grid(self.data.structure) - - def time_step(self, in_queue=None, out_queue=None, finish_event=None, solvers=None): - # import pdb - # pdb.set_trace() - self.logger.debug('Inside time step') - # dynamic simulations start at tstep == 1, 0 is reserved for the initial state - self.data.ts = len(self.data.structure.timestep_info) - initial_time = time.perf_counter() - - # network only - # get input from the other thread - if in_queue: - self.logger.info('Time Loop - Waiting for input') - values = in_queue.get() # should be list of tuples - self.logger.debug('Time loop - received {}'.format(values)) - self.set_of_variables.update_timestep(self.data, values) - - structural_kstep = self.data.structure.timestep_info[-1].copy() - aero_kstep = self.data.aero.timestep_info[-1].copy() - self.logger.debug('Time step {}'.format(self.data.ts)) - - # Add the controller here - if self.with_controllers: - control = [] - state = {'structural': structural_kstep, - 'aero': aero_kstep} - for k, v in self.controllers.items(): - state, control_command = v.control(self.data, state) - # this takes care of the changes in options for the solver - structural_kstep, aero_kstep = self.process_controller_output(state) - control.append(control_command) - self.aero_solver.update_custom_grid(state['structural'], state['aero']) - # import pdb - # pdb.set_trace() - - # Add external forces - if self.with_runtime_generators: - structural_kstep.runtime_steady_forces.fill(0.) - structural_kstep.runtime_unsteady_forces.fill(0.) - params = dict() - params['data'] = self.data - params['struct_tstep'] = structural_kstep - params['aero_tstep'] = aero_kstep - params['fsi_substep'] = -1 - for id, runtime_generator in self.runtime_generators.items(): - runtime_generator.generate(params) - - self.time_aero = 0.0 - self.time_struc = 0.0 - - # Copy the controlled states so that the interpolation does not - # destroy the previous information - controlled_structural_kstep = structural_kstep.copy() - controlled_aero_kstep = aero_kstep.copy() - - for k in range(self.settings['fsi_substeps'] + 1): - if (k == self.settings['fsi_substeps'] and - self.settings['fsi_substeps']): - print_res = 0 if self.res == 0. else np.log10(self.res) - print_res_dqdt = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) - cout.cout_wrap(("The FSI solver did not converge!!! residuals: %f %f" % (print_res, print_res_dqdt))) - self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep) - break - - # generate new grid (already rotated) - aero_kstep = controlled_aero_kstep.copy() - self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep) - - # compute unsteady contribution - force_coeff = 0.0 - unsteady_contribution = False - if self.settings['include_unsteady_force_contribution']: - if self.data.ts > self.settings['steps_without_unsteady_force']: - unsteady_contribution = True - if k < self.settings['pseudosteps_ramp_unsteady_force']: - force_coeff = k/self.settings['pseudosteps_ramp_unsteady_force'] - else: - force_coeff = 1. - - previous_runtime_steady_forces = structural_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) - previous_runtime_unsteady_forces = structural_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) - # Add external forces - if self.with_runtime_generators: - structural_kstep.runtime_steady_forces.fill(0.) - structural_kstep.runtime_unsteady_forces.fill(0.) - params = dict() - params['data'] = self.data - params['struct_tstep'] = structural_kstep - params['aero_tstep'] = aero_kstep - params['fsi_substep'] = k - for id, runtime_generator in self.runtime_generators.items(): - runtime_generator.generate(params) - - # run the solver - ini_time_aero = time.perf_counter() - self.data = self.aero_solver.run(aero_step=aero_kstep, - structural_step=structural_kstep, - convect_wake=True, - unsteady_contribution=unsteady_contribution) - self.time_aero += time.perf_counter() - ini_time_aero - - previous_kstep = structural_kstep.copy() - structural_kstep = controlled_structural_kstep.copy() - structural_kstep.runtime_steady_forces = previous_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) - structural_kstep.runtime_unsteady_forces = previous_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) - previous_kstep.runtime_steady_forces = previous_runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) - previous_kstep.runtime_unsteady_forces = previous_runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) - - # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid(structural_kstep, - aero_kstep) - - self.map_forces(aero_kstep, - structural_kstep, - force_coeff) - - # relaxation - relax_factor = self.relaxation_factor(k) - relax(self.data.structure, - structural_kstep, - previous_kstep, - relax_factor) - - # check if nan anywhere. - # if yes, raise exception - if np.isnan(structural_kstep.steady_applied_forces).any(): - raise exc.NotConvergedSolver('NaN found in steady_applied_forces!') - if np.isnan(structural_kstep.unsteady_applied_forces).any(): - raise exc.NotConvergedSolver('NaN found in unsteady_applied_forces!') - - copy_structural_kstep = structural_kstep.copy() - ini_time_struc = time.perf_counter() - for i_substep in range( - self.settings['structural_substeps'] + 1): - # run structural solver - coeff = ((i_substep + 1)/ - (self.settings['structural_substeps'] + 1)) - - structural_kstep = self.interpolate_timesteps( - step0=self.data.structure.timestep_info[-1], - step1=copy_structural_kstep, - out_step=structural_kstep, - coeff=coeff) - - self.data = self.structural_solver.run( - structural_step=structural_kstep, - dt=self.substep_dt) - - # self.aero_solver.update_custom_grid( - # structural_kstep, - # aero_kstep) - self.time_struc += time.perf_counter() - ini_time_struc - - # check convergence - if self.convergence(k, - structural_kstep, - previous_kstep, - self.structural_solver, - self.aero_solver, - self.with_runtime_generators): - # move the aerodynamic surface according to the structural one - self.aero_solver.update_custom_grid( - structural_kstep, - aero_kstep) - break - - # move the aerodynamic surface according the the structural one - self.aero_solver.update_custom_grid(structural_kstep, aero_kstep) - - self.aero_solver.add_step() - self.data.aero.timestep_info[-1] = aero_kstep.copy() - self.structural_solver.add_step() - self.data.structure.timestep_info[-1] = structural_kstep.copy() - - final_time = time.perf_counter() - - if self.print_info: - print_res = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) - self.residual_table.print_line([self.data.ts, - self.data.ts*self.dt, - k, - self.time_struc/(self.time_aero + self.time_struc), - final_time - initial_time, - print_res, - structural_kstep.for_vel[0], - structural_kstep.for_vel[2], - np.sum(structural_kstep.steady_applied_forces[:, 0]), - np.sum(structural_kstep.steady_applied_forces[:, 2])]) - (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], - self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( - self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) - # run postprocessors - if self.with_postprocessors: - for postproc in self.postprocessors: - self.data = self.postprocessors[postproc].run(online=True, solvers=solvers) - - # network only - # put result back in queue - if out_queue: - self.logger.debug('Time loop - about to get out variables from data') - self.set_of_variables.get_value(self.data) - if out_queue.full(): - # clear the queue such that it always contains the latest time step - out_queue.get() # clear item from queue - self.logger.debug('Data output Queue is full - clearing output') - out_queue.put(self.set_of_variables) - - if finish_event: - finish_event.set() - self.logger.info('Time loop - Complete') - - return control - - def extract_resultants(self, tstep=None): - return self.structural_solver.extract_resultants(tstep) - - # def extract_controlcommand(self, tstep=None): - # if self.with_controllers: - # structural_kstep = self.data.structure.timestep_info[-1].copy() - # aero_kstep = self.data.aero.timestep_info[-1].copy() - # state = {'structural': structural_kstep, - # 'aero': aero_kstep} - # control = [] - # for k, v in self.controllers.items(): - # state, control_command = v.control(self.data, state) - # control.append(control_command) - # return control - - def get_direction(self, thrust_nodes, tstep=None): - self.force_orientation_G = np.zeros((len(thrust_nodes), 3)) - for i_node, node in enumerate(thrust_nodes): - # import pdb - # pdb.set_trace() - elem_thrust_node = np.where(self.data.structure.connectivities == 0)[0][0] - node_thrust_node = np.where(self.data.structure.connectivities == 0)[1][0] - self.force_orientation_G[i_node, :] = np.dot(algebra.quat2rotation(self.data.structure.ini_info.quat), - np.dot(algebra.crv2rotation(self.data.structure.ini_info.psi[elem_thrust_node, node_thrust_node, :]), - algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3]))) - return self.force_orientation_G - - @staticmethod def interpolate_timesteps(step0, step1, out_step, coeff): """ diff --git a/sharpy/solvers/dynamictrim.py b/sharpy/solvers/dynamictrim.py deleted file mode 100644 index 0f85c3b28..000000000 --- a/sharpy/solvers/dynamictrim.py +++ /dev/null @@ -1,732 +0,0 @@ -import numpy as np - -import sharpy.utils.cout_utils as cout -import sharpy.utils.solver_interface as solver_interface -from sharpy.utils.solver_interface import solver, BaseSolver -import sharpy.utils.settings as settings_utils -import os - - -@solver -class DynamicTrim(BaseSolver): - """ - The ``StaticTrim`` solver determines the longitudinal state of trim (equilibrium) for an aeroelastic system in - static conditions. It wraps around the desired solver to yield the state of trim of the system, in most cases - the :class:`~sharpy.solvers.staticcoupled.StaticCoupled` solver. - - It calculates the required angle of attack, elevator deflection and thrust required to achieve longitudinal - equilibrium. The output angles are shown in degrees. - - The results from the trimming iteration can be saved to a text file by using the `save_info` option. - """ - solver_id = 'DynamicTrim' - solver_classification = 'Flight Dynamics' - - settings_types = dict() - settings_default = dict() - settings_description = dict() - - settings_types['print_info'] = 'bool' - settings_default['print_info'] = True - settings_description['print_info'] = 'Print info to screen' - - settings_types['solver'] = 'str' - settings_default['solver'] = '' - settings_description['solver'] = 'Solver to run in trim routine' - - settings_types['solver_settings'] = 'dict' - settings_default['solver_settings'] = dict() - settings_description['solver_settings'] = 'Solver settings dictionary' - - settings_types['max_iter'] = 'int' - settings_default['max_iter'] = 40000 - settings_description['max_iter'] = 'Maximum number of iterations of trim routine' - - settings_types['fz_tolerance'] = 'float' - settings_default['fz_tolerance'] = 0.01 - settings_description['fz_tolerance'] = 'Tolerance in vertical force' - - settings_types['fx_tolerance'] = 'float' - settings_default['fx_tolerance'] = 0.01 - settings_description['fx_tolerance'] = 'Tolerance in horizontal force' - - settings_types['m_tolerance'] = 'float' - settings_default['m_tolerance'] = 0.01 - settings_description['m_tolerance'] = 'Tolerance in pitching moment' - - settings_types['tail_cs_index'] = ['int', 'list(int)'] - settings_default['tail_cs_index'] = 0 - settings_description['tail_cs_index'] = 'Index of control surfaces that move to achieve trim' - - settings_types['thrust_nodes'] = 'list(int)' - settings_default['thrust_nodes'] = [0] - settings_description['thrust_nodes'] = 'Nodes at which thrust is applied' - - settings_types['initial_alpha'] = 'float' - settings_default['initial_alpha'] = 0. - settings_description['initial_alpha'] = 'Initial angle of attack' - - settings_types['initial_deflection'] = 'float' - settings_default['initial_deflection'] = 0. - settings_description['initial_deflection'] = 'Initial control surface deflection' - - settings_types['initial_thrust'] = 'float' - settings_default['initial_thrust'] = 0.0 - settings_description['initial_thrust'] = 'Initial thrust setting' - - settings_types['initial_angle_eps'] = 'float' - settings_default['initial_angle_eps'] = 0.05 - settings_description['initial_angle_eps'] = 'Initial change of control surface deflection' - - settings_types['initial_thrust_eps'] = 'float' - settings_default['initial_thrust_eps'] = 2. - settings_description['initial_thrust_eps'] = 'Initial thrust setting change' - - settings_types['relaxation_factor'] = 'float' - settings_default['relaxation_factor'] = 0.2 - settings_description['relaxation_factor'] = 'Relaxation factor' - - settings_types['notrim_relax'] = 'bool' - settings_default['notrim_relax'] = False - settings_description['notrim_relax'] = 'Disable gains for trim - releases internal loads at initial values' - - settings_types['notrim_relax_iter'] = 'int' - settings_default['notrim_relax_iter'] = 10000000 - settings_description['notrim_relax_iter'] = 'Terminate notrim_relax at defined number of steps' - - - settings_types['speed_up_factor'] = 'float' - settings_default['speed_up_factor'] = 1.0 - settings_description['speed_up_factor'] = 'increase dt in trim iterations' - - settings_types['save_info'] = 'bool' - settings_default['save_info'] = False - settings_description['save_info'] = 'Save trim results to text file' - - settings_table = settings_utils.SettingsTable() - __doc__ += settings_table.generate(settings_types, settings_default, settings_description) - - def __init__(self): - self.data = None - self.settings = None - self.solver = None - - # The order is - # [0]: alpha/fz - # [1]: alpha + delta (gamma)/moment - # [2]: thrust/fx - - self.n_input = 3 - self.i_iter = 0 - - self.input_history = [] - self.output_history = [] - self.gradient_history = [] - self.trimmed_values = np.zeros((3,)) - - self.table = None - self.folder = None - - def initialise(self, data, restart=False): - self.data = data - self.settings = data.settings[self.solver_id] - settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) - - self.solver = solver_interface.initialise_solver(self.settings['solver']) - - if self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicCoupledStep": - #replace free flying with clamped - oldsettings = self.settings['solver_settings'] - self.settings['solver_settings']['structural_solver'] = 'NonLinearDynamicPrescribedStep' - # self.settings['solver_settings']['structural_solver_settings'] = {'print_info': 'off', - # 'max_iterations': 950, - # 'delta_curved': 1e-1, - # 'min_delta': tolerance, - # 'newmark_damp': 5e-3, - # 'gravity_on': gravity, - # 'gravity': 9.81, - # 'num_steps': n_tstep, - # 'dt': dt, - # 'initial_velocity': u_inf * int(free_flight)} commented since both solvers take (almost) same inputs - u_inf = self.settings['solver_settings']['structural_solver_settings']['initial_velocity'] - self.settings['solver_settings']['structural_solver_settings'].pop('initial_velocity') - self.settings['solver_settings']['structural_solver_settings']['newmark_damp'] = 1.0 - # settings['StepUvlm'] = {'print_info': 'off', - # 'num_cores': num_cores, - # 'convection_scheme': 2, - # 'gamma_dot_filtering': 6, - # 'velocity_field_generator': 'GustVelocityField', - # 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, - # 'u_inf_direction': [1., 0, 0], - # 'gust_shape': '1-cos', - # 'gust_parameters': {'gust_length': gust_length, - # 'gust_intensity': gust_intensity * u_inf}, - # 'offset': gust_offset, - # 'relative_motion': relative_motion}, - # 'rho': rho, - # 'n_time_steps': n_tstep, - # 'dt': dt} - self.settings['solver_settings']['aero_solver_settings']['velocity_field_generator'] = 'SteadyVelocityField' - u_inf_direction = self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input'].clear() - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf'] = u_inf - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] = u_inf_direction - - self.settings['solver_settings'] - # TODO: plus dynamic coupled to add controller - - # import pdb - # pdb.set_trace() - - gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians - gains_alpha = np.array([0.00015, 0.00015, 0.0]) - gains_thrust = -np.array([0.1, 0.05, 0.0]) #we can pick them too! this is 1-1 almost - route = data.settings['SHARPy']['route'] - n_tstep = int(self.settings['solver_settings']['n_time_steps']) - dt = float(self.settings['solver_settings']['dt']) - elev_file = route + '/elev.csv' - alpha_file = route + '/alpha.csv' - thrust_file = route + '/thrust.csv' - - elev_hist = np.linspace(0, n_tstep*dt, n_tstep) - elev_hist = 0.0/180.0*np.pi*elev_hist - - alpha_hist = np.linspace(1, 1, n_tstep) - alpha_hist = 0.0/180.0*np.pi*alpha_hist - - thrust_hist = np.linspace(1, 1, n_tstep) - thrust_hist = 0.0/180.0*np.pi*thrust_hist - - np.savetxt(elev_file, elev_hist) - np.savetxt(alpha_file, alpha_hist) - np.savetxt(thrust_file, thrust_hist) - - - try: - self.settings['solver_settings']['controller_id'].clear() - self.settings['solver_settings']['controller_settings'].clear() - except: - print("original no controller") - - self.settings['solver_settings']['controller_id']= {'controller_elevator': 'ControlSurfacePidController', - 'controller_alpha': 'AlphaController', - 'controller_thrust': 'ThrustController'} - self.settings['solver_settings']['controller_settings']= {'controller_elevator': {'P': gains_elev[0], - 'I': gains_elev[1], - 'D': gains_elev[2], - 'dt': dt, - 'input_type': 'm_y', - 'write_controller_log': True, - 'controlled_surfaces': [0], - 'time_history_input_file': route + '/elev.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_deflection'] - } - , - 'controller_alpha': {'P': gains_alpha[0], - 'I': gains_alpha[1], - 'D': gains_alpha[2], - 'dt': dt, - 'input_type': 'f_z', - 'write_controller_log': True, - 'time_history_input_file': route + '/alpha.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_alpha']} - , - 'controller_thrust': {'thrust_nodes': self.settings['thrust_nodes'], - 'P': gains_thrust[0], - 'I': gains_thrust[1], - 'D': gains_thrust[2], - 'dt': dt, - 'input_type': 'f_x', - 'write_controller_log': True, - 'time_history_input_file': route + '/thrust.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_thrust']} - } - # import pdb - # pdb.set_trace() - # #end - - self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) - - self.folder = data.output_folder + '/statictrim/' - if not os.path.exists(self.folder): - os.makedirs(self.folder) - - self.table = cout.TablePrinter(10, 8, ['g', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'], - filename=self.folder+'trim_iterations.txt') - self.table.print_header(['iter', 'alpha[deg]', 'elev[deg]', 'thrust', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) - - - elif self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicMultibody": - # import pdb - # pdb.set_trace() - self.data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - self.data.structure.timestep_info[0].mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - self.data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - # self.data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - # self.data.structure.timestep_info[0].mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - # self.data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'prescribed_trim' - #replace free flying with clamped - oldsettings = self.settings['solver_settings'] - self.settings['solver_settings']['structural_solver'] = 'NonLinearDynamicMultibody' - # self.settings['solver_settings']['structural_solver_settings'] = {'print_info': 'off', - # 'max_iterations': 950, - # 'delta_curved': 1e-1, - # 'min_delta': tolerance, - # 'newmark_damp': 5e-3, - # 'gravity_on': gravity, - # 'gravity': 9.81, - # 'num_steps': n_tstep, - # 'dt': dt, - # 'initial_velocity': u_inf * int(free_flight)} commented since both solvers take (almost) same inputs - self.settings['solver_settings']['structural_solver_settings'].pop('dyn_trim') - u_inf = self.settings['solver_settings']['structural_solver_settings']['initial_velocity'] - self.settings['solver_settings']['structural_solver_settings'].pop('initial_velocity') - # import pdb - # pdb.set_trace() - dt = self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] - # self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] = float(dt)/5. - self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['newmark_damp'] = 0.1 - # settings['StepUvlm'] = {'print_info': 'off', - # 'num_cores': num_cores, - # 'convection_scheme': 2, - # 'gamma_dot_filtering': 6, - # 'velocity_field_generator': 'GustVelocityField', - # 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, - # 'u_inf_direction': [1., 0, 0], - # 'gust_shape': '1-cos', - # 'gust_parameters': {'gust_length': gust_length, - # 'gust_intensity': gust_intensity * u_inf}, - # 'offset': gust_offset, - # 'relative_motion': relative_motion}, - # 'rho': rho, - # 'n_time_steps': n_tstep, - # 'dt': dt} - # self.settings['solver_settings']['aero_solver_settings']['convection_scheme'] = 2 - - self.settings['solver_settings']['aero_solver_settings']['velocity_field_generator'] = 'SteadyVelocityField' - u_inf_direction = self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input'].clear() - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf'] = u_inf - self.settings['solver_settings']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] = u_inf_direction - - self.settings['solver_settings'] - # TODO: plus dynamic coupled to add controller - - # import pdb - # pdb.set_trace() - - # gains_elev = -np.array([0.000015, 0.000015, 0.0]) #we can pick them! dimensional force/moment versus radians - # gains_alpha = np.array([0.000010, 0.000010, 0.0]) - # # gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians - # # gains_alpha = np.array([0.00010, 0.00010, 0.0]) - # gains_thrust = -np.array([0.1, 0.05, 0.0]) #we can pick them too! this is 1-1 almost - gains_elev = (not self.settings['notrim_relax'])*-np.array([0.000015, 0.000010, 0.0]) #we can pick them! dimensional force/moment versus radians - gains_alpha = (not self.settings['notrim_relax'])*np.array([0.000015, 0.000010, 0.0]) - # gains_elev = -np.array([0.00015, 0.00015, 0.0]) #we can pick them! dimensional force/moment versus radians - # gains_alpha = np.array([0.00010, 0.00010, 0.0]) - gains_thrust = (not self.settings['notrim_relax'])*-np.array([0.1, 0.05, 0.0]) - - - - #we can pick them too! this is 1-1 almost - route = data.settings['SHARPy']['route'] - n_tstep = int(self.settings['solver_settings']['n_time_steps']) - n_tstep *=40 - self.settings['solver_settings']['n_time_steps'] = n_tstep - self.settings['solver_settings']['structural_solver_settings']['num_steps'] = n_tstep - self.settings['solver_settings']['aero_solver_settings']['n_time_steps'] = n_tstep - # import pdb - # pdb.set_trace() - - dt = float(self.settings['solver_settings']['dt'])*self.settings['speed_up_factor'] - # import pdb - # pdb.set_trace() - self.settings['solver_settings']['dt'] = dt - self.settings['solver_settings']['structural_solver_settings']['time_integrator_settings']['dt'] = dt - self.settings['solver_settings']['aero_solver_settings']['dt'] = dt - elev_file = route + '/elev.csv' - alpha_file = route + '/alpha.csv' - thrust_file = route + '/thrust.csv' - - elev_hist = np.linspace(0, n_tstep*dt, n_tstep) - elev_hist = 0.0/180.0*np.pi*elev_hist - - alpha_hist = np.linspace(1, 1, n_tstep) - alpha_hist = 0.0/180.0*np.pi*alpha_hist - - thrust_hist = np.linspace(1, 1, n_tstep) - thrust_hist = 0.0/180.0*np.pi*thrust_hist - - np.savetxt(elev_file, elev_hist) - np.savetxt(alpha_file, alpha_hist) - np.savetxt(thrust_file, thrust_hist) - - try: - self.settings['solver_settings']['controller_id'].clear() - self.settings['solver_settings']['controller_settings'].clear() - except: - print("original no controller") - - self.settings['solver_settings']['controller_id']= {'controller_elevator': 'ControlSurfacePidController' - , - 'controller_alpha': 'AlphaController' - , - 'controller_thrust': 'ThrustController' - } - self.settings['solver_settings']['controller_settings']= {'controller_elevator': {'P': gains_elev[0], - 'I': gains_elev[1], - 'D': gains_elev[2], - 'dt': dt, - 'input_type': 'm_y', - 'write_controller_log': True, - 'controlled_surfaces': [0], - 'time_history_input_file': route + '/elev.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_deflection'] - } - , - 'controller_alpha': {'P': gains_alpha[0], - 'I': gains_alpha[1], - 'D': gains_alpha[2], - 'dt': dt, - 'input_type': 'f_z', - 'write_controller_log': True, - 'time_history_input_file': route + '/alpha.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_alpha']} - , - 'controller_thrust': {'thrust_nodes': self.settings['thrust_nodes'], - 'P': gains_thrust[0], - 'I': gains_thrust[1], - 'D': gains_thrust[2], - 'dt': dt, - 'input_type': 'f_x', - 'write_controller_log': True, - 'time_history_input_file': route + '/thrust.csv', - 'use_initial_value': True, - 'initial_value': self.settings['initial_thrust']} - } - # import pdb - # pdb.set_trace() - # #end - - self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) - - self.folder = data.output_folder + '/statictrim/' - if not os.path.exists(self.folder): - os.makedirs(self.folder) - - self.table = cout.TablePrinter(10, 8, ['g', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'], - filename=self.folder+'trim_iterations.txt') - self.table.print_header(['iter', 'alpha[deg]', 'elev[deg]', 'thrust', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) - else: - raise NotImplementedError('Dynamic trim is only working with nonlinearcoupled or multibody!') - - - def undo_changes(self, data): - - # import pdb - # pdb.set_trace() - if self.settings['solver_settings']['structural_solver'] == "NonLinearDynamicMultibody": - data.structure.ini_mb_dict['body_00']['FoR_movement'] = 'free' - data.structure.timestep_info[-1].mb_dict['body_00']['FoR_movement'] = 'free' - data.structure.ini_info.mb_dict['body_00']['FoR_movement'] = 'free' - print("HARDCODED!! 16723") - - # data.structure.ini_info.pos_dot *= 0. - # data.structure.ini_info.pos_ddot *= 0. - # data.structure.ini_info.psi_dot *= 0. - # data.structure.ini_info.psi_dot_local *= 0. - # data.structure.ini_info.psi_ddot *= 0. - # data.structure.timestep_info[-1].pos_dot *= 0. - # data.structure.timestep_info[-1].pos_ddot *= 0. - # data.structure.timestep_info[-1].psi_dot *= 0. - # data.structure.timestep_info[-1].psi_dot_local *= 0. - # data.structure.timestep_info[-1].psi_ddot *= 0. - # data.structure.timestep_info[-1].mb_FoR_vel *= 0. - # data.structure.timestep_info[-1].mb_FoR_acc *= 0. - # data.structure.timestep_info[-1].mb_dquatdt *= 0. - # data.structure.timestep_info[-1].dqddt *= 0. - # data.structure.timestep_info[-1].forces_constraints_FoR *= 0. - # data.structure.timestep_info[-1].forces_constraints_nodes *= 0. - - # data.structure.timestep_info[-1].dqdt *= 0. - # data.structure.timestep_info[-1].q *= 0. - # data.structure.timestep_info[-1].steady_applied_forces *= 0. - # data.structure.timestep_info[-1].unsteady_applied_forces *= 0. - - # import pdb - # pdb.set_trace() - # data.structure.timestep_info[0].q = np.append(data.structure.timestep_info[0].q, np.zeros(10)) - # data.structure.timestep_info[0].dqdt = np.append(data.structure.timestep_info[0].dqdt, np.zeros(10)) - # data.structure.timestep_info[0].dqddt = np.append(data.structure.timestep_info[0].dqddt, np.zeros(10)) - - - def increase_ts(self): - self.data.ts += 1 - self.structural_solver.next_step() - self.aero_solver.next_step() - - def run(self, **kwargs): - - # In the event the modal solver has been run prior to StaticCoupled (i.e. to get undeformed modes), copy - # results and then attach to the resulting timestep - try: - modal = self.data.structure.timestep_info[-1].modal.copy() - modal_exists = True - except AttributeError: - modal_exists = False - - self.trim_algorithm() - - if modal_exists: - self.data.structure.timestep_info[-1].modal = modal - - if self.settings['save_info']: - np.savetxt(self.folder + '/trim_values.txt', self.trimmed_values) - - # save trimmed values for dynamic coupled multibody access if needed - self.data.trimmed_values = self.trimmed_values - self.undo_changes(self.data) - - return self.data - - def convergence(self, fz, m, fx, thrust): - return_value = np.array([False, False, False]) - - if np.abs(fz) < self.settings['fz_tolerance']: - return_value[0] = True - - if np.abs(m) < self.settings['m_tolerance']: - return_value[1] = True - - # print(fx) - # print(thrust) - if np.abs(fx) < self.settings['fx_tolerance']: - return_value[2] = True - - return return_value - - def trim_algorithm(self): - """ - Trim algorithm method - - The trim condition is found iteratively. - - Returns: - np.array: array of trim values for angle of attack, control surface deflection and thrust. - """ - for self.i_iter in range(self.settings['max_iter'] + 1): - if self.i_iter == self.settings['max_iter']: - raise Exception('The Trim routine reached max iterations without convergence!') - - self.input_history.append([]) - self.output_history.append([]) - # self.gradient_history.append([]) - for i in range(self.n_input): - self.input_history[self.i_iter].append(0) - self.output_history[self.i_iter].append(0) - # self.gradient_history[self.i_iter].append(0) - - # the first iteration requires computing gradients - if not self.i_iter: - # add to input history the initial estimation - self.input_history[self.i_iter][0] = self.settings['initial_alpha'] - self.input_history[self.i_iter][1] = (self.settings['initial_deflection'] + - self.settings['initial_alpha']) - self.input_history[self.i_iter][2] = self.settings['initial_thrust'] - - # compute output - (self.output_history[self.i_iter][0], - self.output_history[self.i_iter][1], - self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], - self.input_history[self.i_iter][1], - self.input_history[self.i_iter][2]) - - # # do not check for convergence in first step to let transient take effect! - # # check for convergence (in case initial values are ok) - # if all(self.convergence(self.output_history[self.i_iter][0], - # self.output_history[self.i_iter][1], - # self.output_history[self.i_iter][2], - # self.input_history[self.i_iter][2])): - # self.trimmed_values = self.input_history[self.i_iter] - # return - - # # compute gradients - # # dfz/dalpha - # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0] + self.settings['initial_angle_eps'], - # self.input_history[self.i_iter][1], - # self.input_history[self.i_iter][2]) - - # self.gradient_history[self.i_iter][0] = ((l - self.output_history[self.i_iter][0]) / - # self.settings['initial_angle_eps']) - - # # dm/dgamma - # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], - # self.input_history[self.i_iter][1] + self.settings['initial_angle_eps'], - # self.input_history[self.i_iter][2]) - - # self.gradient_history[self.i_iter][1] = ((m - self.output_history[self.i_iter][1]) / - # self.settings['initial_angle_eps']) - - # # dfx/dthrust - # (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], - # self.input_history[self.i_iter][1], - # self.input_history[self.i_iter][2] + - # self.settings['initial_thrust_eps']) - - # self.gradient_history[self.i_iter][2] = ((d - self.output_history[self.i_iter][2]) / - # self.settings['initial_thrust_eps']) - - continue - - # if not all(np.isfinite(self.gradient_history[self.i_iter - 1])) - # now back to normal evaluation (not only the i_iter == 0 case) - # compute next alpha with the previous gradient - # convergence = self.convergence(self.output_history[self.i_iter - 1][0], - # self.output_history[self.i_iter - 1][1], - # self.output_history[self.i_iter - 1][2]) - convergence = np.full((3, ), False) - if convergence[0]: - # fz is converged, don't change it - self.input_history[self.i_iter][0] = self.input_history[self.i_iter - 1][0] - # self.gradient_history[self.i_iter][0] = self.gradient_history[self.i_iter - 1][0] - else: - self.input_history[self.i_iter][0] = self.input_history[self.i_iter - 1][0] - - if convergence[1]: - # m is converged, don't change it - self.input_history[self.i_iter][1] = self.input_history[self.i_iter - 1][1] - # self.gradient_history[self.i_iter][1] = self.gradient_history[self.i_iter - 1][1] - else: - # compute next gamma with the previous gradient - self.input_history[self.i_iter][1] = self.input_history[self.i_iter - 1][1] - - if convergence[2]: - # fx is converged, don't change it - self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] - # self.gradient_history[self.i_iter][2] = self.gradient_history[self.i_iter - 1][2] - else: - # compute next gamma with the previous gradient - self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] - # else: - # if convergence[0] and convergence[1]: - - # # compute next gamma with the previous gradient - # self.input_history[self.i_iter][2] = self.balance_thrust(self.output_history[self.i_iter - 1][2]) - # else: - # self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] - - if self.settings['relaxation_factor']: - for i_dim in range(3): - self.input_history[self.i_iter][i_dim] = (self.input_history[self.i_iter][i_dim]*(1 - self.settings['relaxation_factor']) + - self.input_history[self.i_iter][i_dim]*self.settings['relaxation_factor']) - - # evaluate - (self.output_history[self.i_iter][0], - self.output_history[self.i_iter][1], - self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], - self.input_history[self.i_iter][1], - self.input_history[self.i_iter][2]) - - if not convergence[0]: - # self.gradient_history[self.i_iter][0] = ((self.output_history[self.i_iter][0] - - # self.output_history[self.i_iter - 1][0]) / - # (self.input_history[self.i_iter][0] - - # self.input_history[self.i_iter - 1][0])) - pass - - if not convergence[1]: - # self.gradient_history[self.i_iter][1] = ((self.output_history[self.i_iter][1] - - # self.output_history[self.i_iter - 1][1]) / - # (self.input_history[self.i_iter][1] - - # self.input_history[self.i_iter - 1][1])) - pass - - if not convergence[2]: - # self.gradient_history[self.i_iter][2] = ((self.output_history[self.i_iter][2] - - # self.output_history[self.i_iter - 1][2]) / - # (self.input_history[self.i_iter][2] - - # self.input_history[self.i_iter - 1][2])) - pass - - # check convergence - convergence = self.convergence(self.output_history[self.i_iter][0], - self.output_history[self.i_iter][1], - self.output_history[self.i_iter][2], - self.input_history[self.i_iter][2]) - # print(convergence) - if all(convergence) or ((self.settings['notrim_relax']) or (self.i_iter > self.settings['notrim_relax_iter'])): - self.trimmed_values = self.input_history[self.i_iter] - self.table.close_file() - return - - # def balance_thrust(self, drag): - # thrust_nodes = self.settings['thrust_nodes'] - # thrust_nodes_num = len(thrust_nodes) - # orientation = self.solver.get_direction(thrust_nodes) - # thrust = -drag/np.sum(orientation, axis=0)[0] - # return thrust - - - def evaluate(self, alpha, deflection_gamma, thrust): - if not np.isfinite(alpha): - import pdb; pdb.set_trace() - if not np.isfinite(deflection_gamma): - import pdb; pdb.set_trace() - if not np.isfinite(thrust): - import pdb; pdb.set_trace() - - # cout.cout_wrap('--', 2) - # cout.cout_wrap('Trying trim: ', 2) - # cout.cout_wrap('Alpha: ' + str(alpha*180/np.pi), 2) - # cout.cout_wrap('CS deflection: ' + str((deflection_gamma - alpha)*180/np.pi), 2) - # cout.cout_wrap('Thrust: ' + str(thrust), 2) - # modify the trim in the static_coupled solver - # self.solver.change_trim(thrust, - # self.settings['thrust_nodes'], - # deflection_gamma - alpha, - # self.settings['tail_cs_index']) - # run the solver - # import pdb - # pdb.set_trace() - control = self.solver.time_step() - # extract resultants - forces, moments = self.solver.extract_resultants() - # extract controller inputs - # control = self.solver.extract_controlcommand() - alpha = control[1] - self.input_history[self.i_iter][0] = alpha - - deflection_gamma = control[0] - self.input_history[self.i_iter][1] = deflection_gamma - - thrust = control[2] - self.input_history[self.i_iter][2] = thrust - - # deflection_gamma = control[0] - # thrust = control[1] - - forcez = forces[2] - forcex = forces[0] - moment = moments[1] - # cout.cout_wrap('Forces and moments:', 2) - # cout.cout_wrap('fx = ' + str(forces[0]) + ' mx = ' + str(moments[0]), 2) - # cout.cout_wrap('fy = ' + str(forces[1]) + ' my = ' + str(moments[1]), 2) - # cout.cout_wrap('fz = ' + str(forces[2]) + ' mz = ' + str(moments[2]), 2) - - self.table.print_line([self.i_iter, - alpha*180/np.pi, - (deflection_gamma)*180/np.pi, - thrust, - forces[0], - forces[1], - forces[2], - moments[0], - moments[1], - moments[2]]) - - return forcez, moment, forcex diff --git a/sharpy/solvers/noaero.py b/sharpy/solvers/noaero.py index 5a18a68d9..562bbe3a4 100644 --- a/sharpy/solvers/noaero.py +++ b/sharpy/solvers/noaero.py @@ -78,7 +78,7 @@ def update_grid(self, beam): -1, beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep): + def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep = None): # called by DynamicCoupled if self.settings['update_grid']: self.data.aero.generate_zeta_timestep_info(structure_tstep, diff --git a/sharpy/solvers/nonlineardynamiccoupledstep.py b/sharpy/solvers/nonlineardynamiccoupledstep.py index 621686ba5..c6a665b83 100644 --- a/sharpy/solvers/nonlineardynamiccoupledstep.py +++ b/sharpy/solvers/nonlineardynamiccoupledstep.py @@ -77,8 +77,7 @@ def initialise(self, data, custom_settings=None, restart=False): def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) - # TODO: previous_structural_step never used - previous_structural_step = settings_utils.set_value_or_default(kwargs, 'previous_structural_step', self.data.structure.timestep_info[-1]) + dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) xbeamlib.xbeam_step_couplednlndyn(self.data.structure, diff --git a/sharpy/solvers/nonlineardynamicmultibody.py b/sharpy/solvers/nonlineardynamicmultibody.py index ba4046491..bcbd4be7d 100644 --- a/sharpy/solvers/nonlineardynamicmultibody.py +++ b/sharpy/solvers/nonlineardynamicmultibody.py @@ -62,25 +62,6 @@ class NonLinearDynamicMultibody(_BaseStructural): settings_default['zero_ini_dot_ddot'] = False settings_description['zero_ini_dot_ddot'] = 'Set to zero the position and crv derivatives at the first time step' - settings_types['fix_prescribed_quat_ini'] = 'bool' - settings_default['fix_prescribed_quat_ini'] = False - settings_description['fix_prescribed_quat_ini'] = 'Set to initial the quaternion for prescibed bodies' - - # initial speed direction is given in inertial FOR!!! also in a lot of cases coincident with global A frame - settings_types['initial_velocity_direction'] = 'list(float)' - settings_default['initial_velocity_direction'] = [-1.0, 0.0, 0.0] - settings_description['initial_velocity_direction'] = 'Initial velocity of the reference node given in the inertial FOR' - - settings_types['initial_velocity'] = 'float' - settings_default['initial_velocity'] = 0 - settings_description['initial_velocity'] = 'Initial velocity magnitude of the reference node' - - # restart sim after dynamictrim - settings_types['dyn_trim'] = 'bool' - settings_default['dyn_trim'] = False - settings_description['dyn_trim'] = 'flag for dyntrim prior to dyncoup' - - settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) @@ -122,302 +103,42 @@ def initialise(self, data, custom_settings=None, restart=False): self.data.structure.add_unsteady_information( self.data.structure.dyn_dict, self.settings['num_steps']) - # import pdb - # pdb.set_trace() - if self.settings['dyn_trim']: - # import pdb - # pdb.set_trace() - - self.data = self.data.previousndm.data - - self.Lambda = self.data.Lambda - self.Lambda_dot = self.data.Lambda_dot - self.Lambda_ddot = np.zeros_like(self.data.Lambda) - - num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - - new_quat = np.zeros([num_body,4]) - ini_quat = np.zeros([num_body,4]) - new_direction = np.zeros([num_body,3]) - - # import pdb - # pdb.set_trace() - # self.settings['initial_velocity_direction'] = [-0.8, 0, 0.6] - - # if self.settings['initial_velocity']: - # for ibody in range(num_body): - # new_quat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] - # ini_quat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] - # b = algebra.multiply_matrices(algebra.quat2rotation(new_quat[0]),self.settings['initial_velocity_direction']) - # new_direction[ibody] = algebra.multiply_matrices(algebra.quat2rotation(new_quat[ibody]).T,b) - # # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), - # # self.settings['initial_velocity_direction']) - # self.data.structure.timestep_info[-1].for_vel[0:3] += new_direction[0]*self.settings['initial_velocity'] - # # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - # # # self.data.structure.num_bodies - # for ibody in range(num_body): - # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,0:3] += new_direction[ibody]*self.settings['initial_velocity'] - # print(self.data.structure.timestep_info[-1].mb_FoR_vel) - - if self.settings['initial_velocity']: - for ibody in range(num_body): - new_quat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] - ini_quat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] - new_direction[ibody] = algebra.multiply_matrices(algebra.quat2rotation(new_quat[ibody]).T,self.settings['initial_velocity_direction']) - # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), - # self.settings['initial_velocity_direction']) - self.data.structure.timestep_info[-1].for_vel[0:3] += new_direction[0]*self.settings['initial_velocity'] - # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - # # self.data.structure.num_bodies - for ibody in range(num_body): - self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,0:3] += new_direction[ibody]*self.settings['initial_velocity'] - print(self.data.structure.timestep_info[-1].mb_FoR_vel) - - # import pdb - # pdb.set_trace() - # if self.settings['initial_velocity']: - # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), - # self.settings['initial_velocity_direction']) - # self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] - # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - # # self.data.structure.num_bodies - # for ibody in range(num_body): - # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel - - - # nowquat = np.zeros([num_body,4]) - # iniquat = np.zeros([num_body,4]) - # # reset the a2 rot axis for hinge axes onlyyyyyyy!!!!!!! TODO: - # for ibody in range(num_body): - # nowquat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] - # iniquat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] - - - # # hardcodeeeeeee - # for iconstraint in range(2): - # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2'] = algebra.multiply_matrices(algebra.quat2rotation(self.data.structure.timestep_info[-1].mb_quat[iconstraint+1]).T, - # algebra.quat2rotation(self.data.structure.ini_mb_dict['body_%02d' % (iconstraint+1)]['quat']), - # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2']) - - # # reset quat, pos, vel, acc - # for ibody in range(num_body): - # self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] = self.data.structure.timestep_info[-1].mb_quat[ibody] - # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_acceleration'] = self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] - # self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] = np.zeros([1,6]) - - # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_velocity'] = self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] - # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_position'] = self.data.structure.timestep_info[-1].mb_FoR_pos[ibody,:] - - # self.data.structure.timestep_info[-1].mb_dict = self.data.structure.ini_mb_dict - # self.data.structure.ini_info.mb_dict = self.data.structure.ini_mb_dict - - # # Define the number of equations - self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) - - # import pdb - # pdb.set_trace() - # Define the number of dofs - self.define_sys_size() #check - self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) - num_LM_eq = self.num_LM_eq - - - self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) - - self.settings['time_integrator_settings']['sys_size'] = self.sys_size - self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq - - # Initialise time integrator - self.time_integrator = solver_interface.initialise_solver( - self.settings['time_integrator']) - self.time_integrator.initialise( - self.data, self.settings['time_integrator_settings']) - - if self.settings['write_lm']: - dire = self.data.output_folder + '/NonLinearDynamicMultibody/' - if not os.path.isdir(dire): - os.makedirs(dire) - - self.out_files = {'lambda': dire + 'lambda.dat', - 'lambda_dot': dire + 'lambda_dot.dat', - 'lambda_ddot': dire + 'lambda_ddot.dat', - 'cond_number': dire + 'cond_num.dat'} - - - - # # add initial speed to RBM - # if self.settings['initial_velocity']: - # new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), - # self.settings['initial_velocity_direction']) - # self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] - # num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - # # self.data.structure.num_bodies - # for ibody in range(num_body): - # self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel - # # import pdb - # # pdb.set_trace() - - # # nowquat = np.zeros([num_body,4]) - # # iniquat = np.zeros([num_body,4]) - # # # reset the a2 rot axis for hinge axes onlyyyyyyy!!!!!!! TODO: - # # for ibody in range(num_body): - # # nowquat[ibody] = self.data.structure.timestep_info[-1].mb_quat[ibody] - # # iniquat[ibody] = self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] - - - # # # hardcodeeeeeee - # # for iconstraint in range(2): - # # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2'] = algebra.multiply_matrices(algebra.quat2rotation(self.data.structure.timestep_info[-1].mb_quat[iconstraint+1]).T, - # # algebra.quat2rotation(self.data.structure.ini_mb_dict['body_%02d' % (iconstraint+1)]['quat']), - # # self.data.structure.ini_mb_dict['constraint_%02d' % iconstraint]['rot_axisA2']) - - # # # reset quat, pos, vel, acc - # # for ibody in range(num_body): - # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['quat'] = self.data.structure.timestep_info[-1].mb_quat[ibody] - # # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_acceleration'] = self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] - # # self.data.structure.timestep_info[-1].mb_FoR_acc[ibody,:] = np.zeros([1,6]) - - # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_velocity'] = self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] - # # self.data.structure.ini_mb_dict['body_%02d' % ibody]['FoR_position'] = self.data.structure.timestep_info[-1].mb_FoR_pos[ibody,:] - - - - - # # Define the number of equations - # self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) - # print(self.data.structure.ini_mb_dict) - # # import pdb - # # pdb.set_trace() - # self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) - - # # self.num_LM_eq = 10 - - # structural_step = self.data.structure.timestep_info[-1] - # # dt= self.settings['dt'] - # # import pdb - # # pdb.set_trace() - - - # MBdict = structural_step.mb_dict - - - # # import pdb - # # pdb.set_trace() - - # MB_beam, MB_tstep = mb.split_multibody( - # self.data.structure, - # structural_step, - # MBdict, - # -1) - - # # import pdb - # # pdb.set_trace() - - # q = [] - # dqdt = [] - # dqddt = [] - - # for ibody in range(num_body): - # q = np.append(q, MB_tstep[ibody].q) - # dqdt = np.append(dqdt, MB_tstep[ibody].dqdt) - # dqddt = np.append(dqddt, MB_tstep[ibody].dqddt) - - # q = np.append(q, self.data.Lambda) - # dqdt = np.append(dqdt, self.data.Lambda_dot) - # dqddt = np.append(dqddt, np.zeros([10])) - - # # q = np.insert(q, 336, np.zeros([10])) - # # dqdt = np.insert(dqdt, 336, np.zeros([10])) - # # dqddt = np.insert(dqddt, 336, np.zeros([10])) - - - - # self.Lambda, self.Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, self.num_LM_eq) - - # self.Lambda_ddot = np.zeros_like(self.Lambda) - - # # self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - # # self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - # # self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + # Define the number of equations + self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) + self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) - # if self.settings['write_lm']: - # dire = self.data.output_folder + '/NonLinearDynamicMultibody/' - # if not os.path.isdir(dire): - # os.makedirs(dire) + self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') + self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - # self.out_files = {'lambda': dire + 'lambda.dat', - # 'lambda_dot': dire + 'lambda_dot.dat', - # 'lambda_ddot': dire + 'lambda_ddot.dat', - # 'cond_number': dire + 'cond_num.dat'} - # # clean up files - # for file in self.out_files.values(): - # if os.path.isfile(file): - # os.remove(file) + if self.settings['write_lm']: + dire = self.data.output_folder + '/NonLinearDynamicMultibody/' + if not os.path.isdir(dire): + os.makedirs(dire) - # # Define the number of dofs - # self.define_sys_size() + self.out_files = {'lambda': dire + 'lambda.dat', + 'lambda_dot': dire + 'lambda_dot.dat', + 'lambda_ddot': dire + 'lambda_ddot.dat', + 'cond_number': dire + 'cond_num.dat'} + # clean up files + for file in self.out_files.values(): + if os.path.isfile(file): + os.remove(file) - # self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) + # Define the number of dofs + self.define_sys_size() - # self.settings['time_integrator_settings']['sys_size'] = self.sys_size - # self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq + self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) - # # Initialise time integrator - # if not restart: - # self.time_integrator = solver_interface.initialise_solver( - # self.settings['time_integrator']) - # self.time_integrator.initialise( - # self.data, self.settings['time_integrator_settings'], restart=restart) + self.settings['time_integrator_settings']['sys_size'] = self.sys_size + self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq - else: - # add initial speed to RBM - if self.settings['initial_velocity']: - new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), - self.settings['initial_velocity_direction']) - self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] - num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] - # self.data.structure.num_bodies - for ibody in range(num_body): - self.data.structure.timestep_info[-1].mb_FoR_vel[ibody,:] = self.data.structure.timestep_info[-1].for_vel - # import pdb - # pdb.set_trace() - - # Define the number of equations - self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) - self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) - - self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') - - if self.settings['write_lm']: - dire = self.data.output_folder + '/NonLinearDynamicMultibody/' - if not os.path.isdir(dire): - os.makedirs(dire) - - self.out_files = {'lambda': dire + 'lambda.dat', - 'lambda_dot': dire + 'lambda_dot.dat', - 'lambda_ddot': dire + 'lambda_ddot.dat', - 'cond_number': dire + 'cond_num.dat'} - # clean up files - for file in self.out_files.values(): - if os.path.isfile(file): - os.remove(file) - - # Define the number of dofs - self.define_sys_size() - - self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) - - self.settings['time_integrator_settings']['sys_size'] = self.sys_size - self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq - - # Initialise time integrator - if not restart: - self.time_integrator = solver_interface.initialise_solver( - self.settings['time_integrator']) - self.time_integrator.initialise( - self.data, self.settings['time_integrator_settings'], restart=restart) + # Initialise time integrator + if not restart: + self.time_integrator = solver_interface.initialise_solver( + self.settings['time_integrator']) + self.time_integrator.initialise( + self.data, self.settings['time_integrator_settings'], restart=restart) def add_step(self): self.data.structure.next_step() @@ -483,8 +204,6 @@ def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, M first_dof = 0 last_dof = 0 - # import pdb - # pdb.set_trace() # Loop through the different bodies for ibody in range(len(MB_beam)): @@ -494,22 +213,10 @@ def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, M K = None Q = None - - # import pdb - # pdb.set_trace() # Generate the matrices for each body if MB_beam[ibody].FoR_movement == 'prescribed': last_dof = first_dof + MB_beam[ibody].num_dof.value - # old_quat = MB_tstep[ibody].quat.copy() - M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) - # MB_tstep[ibody].quat = old_quat - - elif MB_beam[ibody].FoR_movement == 'prescribed_trim': - last_dof = first_dof + MB_beam[ibody].num_dof.value - # old_quat = MB_tstep[ibody].quat.copy() M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) - # MB_tstep[ibody].quat = old_quat - elif MB_beam[ibody].FoR_movement == 'free': last_dof = first_dof + MB_beam[ibody].num_dof.value + 10 @@ -576,36 +283,8 @@ def integrate_position(self, MB_beam, MB_tstep, dt): MB_tstep[ibody].for_pos[0:3] += dt*np.dot(MB_tstep[ibody].cga(),MB_tstep[ibody].for_vel[0:3]) def extract_resultants(self, tstep): - # import pdb - # pdb.set_trace() - # TODO: code - if tstep is None: - tstep = self.data.structure.timestep_info[self.data.ts] - steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) - totals = steady + unsteady + grav - return totals[0:3], totals[3:6] - - def extract_resultants_not_required(self, tstep): - # as ibody = None returns for entire structure! How convenient! - import pdb - pdb.set_trace() # TODO: code - if tstep is None: - tstep = self.data.structure.timestep_info[self.data.ts] - steady_running = 0 - unsteady_running = 0 - grav_running = 0 - for body in range (0, self.data.structure.ini_mb_dict['num_bodies']): - steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav'], ibody = body) - steady_running += steady - unsteady_running += unsteady - grav_running += grav - totals = steady_running + unsteady_running + grav_running - return totals[0:3], totals[3:6] - - - - # return np.zeros((3)), np.zeros((3)) + return np.zeros((3)), np.zeros((3)) def compute_forces_constraints(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot): """ @@ -673,8 +352,6 @@ def write_lm_cond_num(self, iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) - # import pdb - # pdb.set_trace() if structural_step.mb_dict is not None: MBdict = structural_step.mb_dict @@ -703,16 +380,6 @@ def run(self, **kwargs): MB_tstep[ibody].psi_dot_local *= 0. MB_tstep[ibody].psi_ddot *= 0. - - # # Initialize - # # TODO: i belive this can move into disp_and_accel2 state as self.Lambda, self.Lambda_dot - - - # # Predictor step - # q, dqdt, dqddt = mb.disp_and_accel2state(MB_beam, MB_tstep, self.Lambda, self.Lambda_dot, self.sys_size, num_LM_eq) - # self.time_integrator.predictor(q, dqdt, dqddt) - - # else: # Initialize # TODO: i belive this can move into disp_and_accel2 state as self.Lambda, self.Lambda_dot if not num_LM_eq == 0: @@ -726,7 +393,7 @@ def run(self, **kwargs): # Predictor step q, dqdt, dqddt = mb.disp_and_accel2state(MB_beam, MB_tstep, Lambda, Lambda_dot, self.sys_size, num_LM_eq) - self.time_integrator.predictor(q, dqdt, dqddt) + self.time_integrator.predictor(q, dqdt, dqddt) # Reference residuals old_Dq = 1.0 @@ -738,9 +405,7 @@ def run(self, **kwargs): if iteration == self.settings['max_iterations'] - 1: error = ('Solver did not converge in %d iterations.\n res = %e \n LM_res = %e' % (iteration, res, LM_res)) - print(error) - break - # raise exc.NotConvergedSolver(error) + raise exc.NotConvergedSolver(error) # Update positions and velocities Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) @@ -759,18 +424,6 @@ def run(self, **kwargs): Asys, Q = self.time_integrator.build_matrix(MB_M, MB_C, MB_K, MB_Q, kBnh, LM_Q) - np.set_printoptions(threshold=np.inf) - # import pdb - # pdb.set_trace() - - # print(Asys) - # print(Q) - # import sympy - # rref, inds = sympy.Matrix(Asys).rref() - # print(inds) - # print(rref) - # print(np.linalg.inv(Asys)) - if self.settings['write_lm']: cond_num = np.linalg.cond(Asys[:self.sys_size, :self.sys_size]) cond_num_lm = np.linalg.cond(Asys) @@ -826,13 +479,7 @@ def run(self, **kwargs): if (res < self.settings['min_delta']) and (LM_res < self.settings['min_delta']): break - # if (res < self.settings['min_delta']) and (np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq])) < self.settings['abs_threshold']): - # print(f'Relative \'min_delta\' threshold not reached - LM_res is {LM_res} >= \'min_delta\' {self.settings["min_delta"]} abs res is {np.max(np.abs(Dq[0:self.sys_size]))}, abs LM_res is {np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq]))} < {self.settings["abs_threshold"]}') - # break - # import pdb - # pdb.set_trace() - # print("end - check") Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) if self.settings['write_lm']: self.write_lm_cond_num(iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num, cond_num_lm) @@ -852,7 +499,6 @@ def run(self, **kwargs): MB_tstep[ibody].quat = np.dot(Temp, np.dot(np.eye(4) - 0.25*algebra.quadskew(MB_tstep[ibody].for_vel[3:6])*dt, MB_tstep[ibody].quat)) # End of Newmark-beta iterations - # self.beta = self.time_integrator.beta # self.integrate_position(MB_beam, MB_tstep, dt) lagrangeconstraints.postprocess(self.lc_list, MB_beam, MB_tstep, "dynamic") self.compute_forces_constraints(MB_beam, MB_tstep, self.data.ts, dt, Lambda, Lambda_dot) @@ -865,16 +511,4 @@ def run(self, **kwargs): self.Lambda_dot = Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') self.Lambda_ddot = Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') - self.data.Lambda = Lambda.astype(dtype=ct.c_double, copy=True, order='F') - self.data.Lambda_dot = Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') - self.data.Lambda_ddot = Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') - - self.data.previousndm = self - - # name = "struc_" + str(self.data.ts) + ".txt" - # from pprint import pprint as ppr - # file = open(name,'wt') - # ppr(self.data.structure.timestep_info[-1].__dict__, stream=file) - # file.close() - return self.data diff --git a/sharpy/solvers/staticcoupled.py b/sharpy/solvers/staticcoupled.py index 74f05a159..e3fde1bae 100644 --- a/sharpy/solvers/staticcoupled.py +++ b/sharpy/solvers/staticcoupled.py @@ -74,6 +74,10 @@ class StaticCoupled(BaseSolver): settings_description['runtime_generators'] = 'The dictionary keys are the runtime generators to be used. ' \ 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' + + settings_types['nonlifting_body_interactions'] = 'bool' + settings_default['nonlifting_body_interactions'] = False + settings_description['nonlifting_body_interactions'] = 'Consider forces induced by nonlifting bodies' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -149,17 +153,20 @@ def increase_ts(self): def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: - # copy last info to first - self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1].copy() - self.data.structure.timestep_info[0] = self.data.structure.timestep_info[-1].copy() - # delete all the rest - while len(self.data.aero.timestep_info) - 1: - del self.data.aero.timestep_info[-1] - while len(self.data.structure.timestep_info) - 1: - del self.data.structure.timestep_info[-1] + self.remove_old_timestep_info(self.data.structure.timestep_info) + self.remove_old_timestep_info(self.data.aero.timestep_info) + if self.settings['nonlifting_body_interactions']: + self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) self.data.ts = 0 + def remove_old_timestep_info(self, tstep_info): + # copy last info to first + tstep_info[0] = tstep_info[-1].copy() + # delete all the rest + while len(tstep_info) - 1: + del tstep_info[-1] + def run(self, **kwargs): for i_step in range(self.settings['n_load_steps'] + 1): if (i_step == self.settings['n_load_steps'] and @@ -189,14 +196,29 @@ def run(self, **kwargs): self.data.structure.node_master_elem, self.data.structure.connectivities, self.data.structure.timestep_info[self.data.ts].cag(), - self.data.aero.aero_dict) - + self.data.aero.data_dict) + if self.correct_forces: struct_forces = \ self.correct_forces_generator.generate(aero_kstep=self.data.aero.timestep_info[self.data.ts], structural_kstep=self.data.structure.timestep_info[self.data.ts], struct_forces=struct_forces, ts=0) + + # map nonlifting forces to structural nodes + if self.settings['nonlifting_body_interactions']: + struct_forces += mapping.aero2struct_force_mapping( + self.data.nonlifting_body.timestep_info[self.data.ts].forces, + self.data.nonlifting_body.struct2aero_mapping, + self.data.nonlifting_body.timestep_info[self.data.ts].zeta, + self.data.structure.timestep_info[self.data.ts].pos, + self.data.structure.timestep_info[self.data.ts].psi, + self.data.structure.node_master_elem, + self.data.structure.connectivities, + self.data.structure.timestep_info[self.data.ts].cag(), + self.data.nonlifting_body.data_dict, + skip_moments_generated_by_forces = True) + self.data.aero.timestep_info[self.data.ts].aero_steady_forces_beam_dof = struct_forces self.data.structure.timestep_info[self.data.ts].postproc_node['aero_steady_forces'] = struct_forces # B @@ -239,6 +261,7 @@ def run(self, **kwargs): # update grid self.aero_solver.update_step() + self.structural_solver.update(self.data.structure.timestep_info[self.data.ts]) # convergence if self.convergence(i_iter, i_step): # create q and dqdt vectors @@ -322,10 +345,7 @@ def change_trim(self, alpha, thrust, thrust_nodes, tail_deflection, tail_cs_inde for i_node, node in enumerate(thrust_nodes): self.force_orientation[i_node, :] = ( algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3])) - print(self.force_orientation) - #TODO: HARDCODE - self.force_orientation = np.array([[0., 1., 0.],[0., -1., 0.]]) - print(self.force_orientation) + # print(self.force_orientation) # thrust # thrust is scaled so that the direction of the forces is conserved @@ -345,7 +365,7 @@ def change_trim(self, alpha, thrust, thrust_nodes, tail_deflection, tail_cs_inde # tail deflection try: - self.data.aero.aero_dict['control_surface_deflection'][tail_cs_index] = tail_deflection + self.data.aero.data_dict['control_surface_deflection'][tail_cs_index] = tail_deflection except KeyError: raise Exception('This model has no control surfaces') except IndexError: diff --git a/sharpy/solvers/staticuvlm.py b/sharpy/solvers/staticuvlm.py index 66fcd7d35..e9fa9b859 100644 --- a/sharpy/solvers/staticuvlm.py +++ b/sharpy/solvers/staticuvlm.py @@ -43,6 +43,18 @@ class StaticUvlm(BaseSolver): settings_default['horseshoe'] = False settings_description['horseshoe'] = 'Horseshoe wake modelling for steady simulations.' + settings_types['nonlifting_body_interactions'] = 'bool' + settings_default['nonlifting_body_interactions'] = False + settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' + + settings_types['only_nonlifting'] = 'bool' + settings_default['only_nonlifting'] = False + settings_description['only_nonlifting'] = 'Consider only nonlifting bodies' + + settings_types['phantom_wing_test'] = 'bool' + settings_default['phantom_wing_test'] = False + settings_description['phantom_wing_test'] = 'Debug option' + settings_types['num_cores'] = 'int' settings_default['num_cores'] = 0 settings_description['num_cores'] = 'Number of cores to use in the VLM lib' @@ -111,6 +123,10 @@ class StaticUvlm(BaseSolver): settings_default['map_forces_on_struct'] = False settings_description['map_forces_on_struct'] = 'Maps the forces on the structure at the end of the timestep. Only usefull if the solver is used outside StaticCoupled' + settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' + settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 + settings_description['ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' + settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) @@ -138,60 +154,84 @@ def initialise(self, data, custom_settings=None, restart=False): def add_step(self): self.data.aero.add_timestep() + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.add_timestep() + def update_grid(self, beam): - self.data.aero.generate_zeta(beam, - self.data.aero.aero_settings, - -1, - beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep): + if not self.settings['only_nonlifting']: + self.data.aero.generate_zeta(beam, + self.data.aero.aero_settings, + -1, + beam_ts=-1) + if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: + self.data.nonlifting_body.generate_zeta(beam, + self.data.nonlifting_body.aero_settings, + -1, + beam_ts=-1) + + def update_custom_grid(self, structure_tstep, aero_tstep, nonlifting_tstep=None): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['rollup_dt']) + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, + nonlifting_tstep, + self.data.structure, + self.data.nonlifting_body.aero_settings) def run(self, **kwargs): - aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) - structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) - dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['rollup_dt']) - t = settings_utils.set_value_or_default(kwargs, 't', self.data.ts*dt) - - unsteady_contribution = False - convect_wake = False - - if not aero_tstep.zeta: - return self.data - - # generate the wake because the solid shape might change - self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, - 'zeta_star': aero_tstep.zeta_star, - 'gamma': aero_tstep.gamma, - 'gamma_star': aero_tstep.gamma_star, - 'dist_to_orig': aero_tstep.dist_to_orig}) - - # generate uext - self.velocity_generator.generate({'zeta': aero_tstep.zeta, - 'override': True, - 'for_pos': structure_tstep.for_pos[0:3]}, - aero_tstep.u_ext) - # grid orientation - uvlmlib.vlm_solver(aero_tstep, - self.settings) - - if self.settings['map_forces_on_struct']: - structure_tstep.steady_applied_forces[:] = mapping.aero2struct_force_mapping( - aero_tstep.forces, - self.data.aero.struct2aero_mapping, - self.data.aero.timestep_info[self.data.ts].zeta, - structure_tstep.pos, - structure_tstep.psi, - self.data.structure.node_master_elem, - self.data.structure.connectivities, - structure_tstep.cag(), - self.data.aero.aero_dict) + structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[self.data.ts]) + + if not self.settings['only_nonlifting']: + aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[self.data.ts]) + if not self.data.aero.timestep_info[self.data.ts].zeta: + return self.data + + # generate the wake because the solid shape might change + self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, + 'zeta_star': aero_tstep.zeta_star, + 'gamma': aero_tstep.gamma, + 'gamma_star': aero_tstep.gamma_star, + 'dist_to_orig': aero_tstep.dist_to_orig}) + + if self.settings['nonlifting_body_interactions']: + # generate uext + self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, + 'override': True, + 'for_pos': structure_tstep.for_pos[0:3]}, + self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) + # generate uext + self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, + 'override': True, + 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, + self.data.aero.timestep_info[self.data.ts].u_ext) + # grid orientation + uvlmlib.vlm_solver_lifting_and_nonlifting_bodies(self.data.aero.timestep_info[self.data.ts], + self.data.nonlifting_body.timestep_info[self.data.ts], + self.settings) + else: + # generate uext + self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, + 'override': True, + 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, + self.data.aero.timestep_info[self.data.ts].u_ext) + + + # grid orientation + uvlmlib.vlm_solver(self.data.aero.timestep_info[self.data.ts], + self.settings) + else: + self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, + 'override': True, + 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, + self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) + uvlmlib.vlm_solver_nonlifting_body(self.data.nonlifting_body.timestep_info[self.data.ts], + self.settings) return self.data @@ -199,12 +239,17 @@ def next_step(self): """ Updates de aerogrid based on the info of the step, and increases the self.ts counter """ self.data.aero.add_timestep() + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.add_timestep() self.update_step() def update_step(self): - self.data.aero.generate_zeta(self.data.structure, - self.data.aero.aero_settings, - self.data.ts) - # for i_surf in range(self.data.aero.timestep_info[self.data.ts].n_surf): - # self.data.aero.timestep_info[self.data.ts].forces[i_surf].fill(0.0) - # self.data.aero.timestep_info[self.data.ts].dynamic_forces[i_surf].fill(0.0) + if not self.settings['only_nonlifting']: + self.data.aero.generate_zeta(self.data.structure, + self.data.aero.aero_settings, + self.data.ts) + if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: + self.data.nonlifting_body.generate_zeta(self.data.structure, + self.data.nonlifting_body.aero_settings, + self.data.ts) + diff --git a/sharpy/solvers/stepuvlm.py b/sharpy/solvers/stepuvlm.py index d4cd9ad40..ba4b2a686 100644 --- a/sharpy/solvers/stepuvlm.py +++ b/sharpy/solvers/stepuvlm.py @@ -130,6 +130,26 @@ class StepUvlm(BaseSolver): settings_types['quasi_steady'] = 'bool' settings_default['quasi_steady'] = False settings_description['quasi_steady'] = 'Use quasi-steady approximation in UVLM' + + settings_types['only_nonlifting'] = 'bool' + settings_default['only_nonlifting'] = False + settings_description['only_nonlifting'] = 'Consider nonlifting body interactions' + + settings_types['nonlifting_body_interactions'] = 'bool' + settings_default['nonlifting_body_interactions'] = False + settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' + + settings_types['phantom_wing_test'] = 'bool' + settings_default['phantom_wing_test'] = False + settings_description['phantom_wing_test'] = 'Debug option' + + settings_types['centre_rot_g'] = 'list(float)' + settings_default['centre_rot_g'] = [0., 0., 0.] + settings_description['centre_rot_g'] = 'Centre of rotation in G FoR around which ``rbm_vel_g`` is applied' + + settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' + settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 + settings_description['ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -218,13 +238,32 @@ def run(self, **kwargs): 'for_pos': structure_tstep.for_pos, 'is_wake': True}, aero_tstep.u_ext_star) + if self.settings['nonlifting_body_interactions']: - uvlmlib.uvlm_solver(self.data.ts, - aero_tstep, - structure_tstep, - self.settings, - convect_wake=convect_wake, - dt=dt) + nl_body_tstep = settings_utils.set_value_or_default(kwargs, 'nl_body_tstep', self.data.nonlifting_body.timestep_info[-1]) + self.velocity_generator.generate({'zeta': nl_body_tstep.zeta, + 'override': True, + 'ts': self.data.ts, + 'dt': dt, + 't': t, + 'for_pos': structure_tstep.for_pos, + 'is_wake': False}, + nl_body_tstep.u_ext) + + uvlmlib.uvlm_solver_lifting_and_nonlifting(self.data.ts, + aero_tstep, + nl_body_tstep, + structure_tstep, + self.settings, + convect_wake=convect_wake, + dt=dt) + else: + uvlmlib.uvlm_solver(self.data.ts, + aero_tstep, + structure_tstep, + self.settings, + convect_wake=convect_wake, + dt=dt) if unsteady_contribution and not self.settings['quasi_steady']: # calculate unsteady (added mass) forces: @@ -248,24 +287,38 @@ def run(self, **kwargs): else: for i_surf in range(len(aero_tstep.gamma)): aero_tstep.gamma_dot[i_surf][:] = 0.0 - return self.data def add_step(self): self.data.aero.add_timestep() + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.add_timestep() def update_grid(self, beam): self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) + if self.settings['nonlifting_body_interactions']: + self.data.nonlifting_body.generate_zeta(beam, + self.data.aero.aero_settings, + -1, + beam_ts=-1) - def update_custom_grid(self, structure_tstep, aero_tstep): + def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep = None): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['dt']) + if self.settings['nonlifting_body_interactions']: + if nl_body_tstep is None: + nl_body_tstep = self.data.nonlifting_body.timestep_info[-1] + self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, + nl_body_tstep, + self.data.structure, + self.data.nonlifting_body.aero_settings, + dt = self.settings['dt']) @staticmethod def filter_gamma_dot(tstep, history, filter_param): diff --git a/sharpy/solvers/trim.py b/sharpy/solvers/trim.py index b8543f21e..4cf0a5a05 100644 --- a/sharpy/solvers/trim.py +++ b/sharpy/solvers/trim.py @@ -291,7 +291,7 @@ def solver_wrapper(x, x_info, solver_data, i_dim=-1): tstep.quat[:] = orientation_quat # control surface deflection for i_cs in range(len(x_info['i_control_surfaces'])): - solver_data.data.aero.aero_dict['control_surface_deflection'][x_info['control_surfaces_id'][i_cs]] = x[x_info['i_control_surfaces'][i_cs]] + solver_data.data.aero.data_dict['control_surface_deflection'][x_info['control_surfaces_id'][i_cs]] = x[x_info['i_control_surfaces'][i_cs]] # thrust input tstep.steady_applied_forces[:] = 0.0 try: From 2c018741b48cbe8fe5c740e1d292218d71eeee25 Mon Sep 17 00:00:00 2001 From: Ben Preston <144227999+ben-l-p@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:34:31 +0100 Subject: [PATCH 53/53] Fix Dockerfile to prevent Mamba install failing --- Dockerfile | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index c0cdd9d30..8a7e44ee6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,7 @@ RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux && \ yum clean all -# Install Mamba -# Swapped from Conda to Mamba due to Github runner memory constraint +# Install Mamba - swapped from Conda to Mamba due to Github runner memory constraint RUN wget --no-check-certificate https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -O /mamba.sh && \ chmod +x /mamba.sh && \ /mamba.sh -b -p /mamba/ && \ @@ -26,19 +25,14 @@ RUN wget --no-check-certificate https://github.com/conda-forge/miniforge/release ADD / /sharpy_dir/ -# Update mamba and make it run with no user interaction -# Cleanup mamba installation -RUN mamba init bash -RUN mamba config --set always_yes yes --set changeps1 no -RUN mamba update -q conda -RUN mamba config --set auto_activate_base false -RUN mamba env create -f /sharpy_dir/utils/environment.yml -#RUN mamba clean -afy -RUN find /mamba/ -follow -type f -name '*.a' -delete -RUN find /mamba/ -follow -type f -name '*.pyc' -delete -RUN find /mamba/ -follow -type f -name '*.js.map' -delete - -#COPY /utils/docker/* /root/ +# Initialise mamba installation +RUN mamba init bash && \ + mamba update -q conda && \ + mamba env create -f /sharpy_dir/utils/environment.yml && \ + find /mamba/ -follow -type f -name '*.a' -delete && \ + find /mamba/ -follow -type f -name '*.pyc' -delete && \ + find /mamba/ -follow -type f -name '*.js.map' -delete + RUN ln -s /sharpy_dir/utils/docker/* /root/ RUN cd sharpy_dir && \ @@ -46,9 +40,9 @@ RUN cd sharpy_dir && \ git submodule update --init --recursive && \ mkdir build && \ cd build && \ - CXX=g++ FC=gfortran cmake .. && make install -j 2 && \ + CXX=g++ FC=gfortran cmake .. && make install -j 4 && \ cd .. && \ pip install . && \ rm -rf build -ENTRYPOINT ["/bin/bash", "--init-file", "/root/bashrc"] \ No newline at end of file +ENTRYPOINT ["/bin/bash", "--init-file", "/root/bashrc"]