├── tests ├── __init__.py ├── test_shipmmg.py ├── test_kt.py └── test_mmg_3dof.py ├── shipmmg ├── __init__.py ├── draw_obj.py ├── kt.py ├── ship_obj_3dof.py └── mmg_3dof.py ├── docs ├── modules.rst ├── Makefile ├── tests.rst ├── shipmmg.rst ├── make.bat ├── index.rst └── conf.py ├── .gitmodules ├── Dockerfile ├── docker-compose.yml ├── pyproject.toml ├── .github └── workflows │ ├── codecov.yml │ └── codeql-analysis.yml ├── LICENSE ├── README.md └── .gitignore /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shipmmg/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.11" 2 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | shipmmg 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | shipmmg 8 | tests 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/_build/html"] 2 | path = docs/_build/html 3 | url = git@github.com:ShipMMG/shipmmg.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /tests/test_shipmmg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """test_shipmmg. 4 | 5 | - version check 6 | """ 7 | 8 | from shipmmg import __version__ 9 | 10 | 11 | def test_version(): 12 | """Test version.""" 13 | assert __version__ == "0.0.11" 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | USER root 3 | WORKDIR /usr/src/app 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | RUN apt-get update && apt-get install --no-install-recommends -y \ 7 | curl \ 8 | sudo \ 9 | git && \ 10 | apt-get clean 11 | 12 | RUN pip install numpy scipy matplotlib pandas 13 | RUN pip install pytest 14 | 15 | RUN python -m pip install --upgrade pip && pip install \ 16 | poetry \ 17 | jupyterlab 18 | 19 | RUN jupyter serverextension enable --py jupyterlab 20 | 21 | ENV DEBIAN_FRONTEND dialog -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | jupyterlab: 4 | build: 5 | context: . 6 | dockerfile: "Dockerfile" 7 | user: root 8 | volumes: 9 | - .:/home/codes 10 | ports: 11 | - "8888:8888" 12 | environment: 13 | TZ: Asia/Tokyo 14 | command: jupyter lab --ip=0.0.0.0 --allow-root --no-browser --NotebookApp.notebook_dir='/home/codes' --NotebookApp.token='shipmmg' 15 | volumes: 16 | jupyterlab-dir: 17 | driver_opts: 18 | type: none 19 | device: . 20 | o: bind 21 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "shipmmg" 3 | version = "0.0.11" 4 | description = "A Python package of ship maneuvering simulation" 5 | readme = "README.md" 6 | authors = ["Taiga MITSUYUKI "] 7 | repository="https://github.com/ShipMMG/shipmmg" 8 | documentation = "https://shipmmg.github.io/shipmmg/" 9 | license = "MIT" 10 | 11 | [tool.poetry.dependencies] 12 | python = ">=3.11,<3.14" 13 | scipy = "^1.16.0" 14 | matplotlib = "^3.10.0" 15 | Pillow = ">=9.4,<11.0" 16 | numpy = "^2.3" 17 | scikit-learn = "^1.7" 18 | 19 | [tool.poetry.group.dev.dependencies] 20 | pytest = "^8.3" 21 | pytest-cov = "^5.0" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /docs/tests.rst: -------------------------------------------------------------------------------- 1 | tests package 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | tests.test\_kt module 8 | --------------------- 9 | 10 | .. automodule:: tests.test_kt 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | tests.test\_mmg\_3dof module 16 | ---------------------------- 17 | 18 | .. automodule:: tests.test_mmg_3dof 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | tests.test\_shipmmg module 24 | -------------------------- 25 | 26 | .. automodule:: tests.test_shipmmg 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: tests 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/shipmmg.rst: -------------------------------------------------------------------------------- 1 | shipmmg package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | shipmmg.draw\_obj module 8 | ------------------------ 9 | 10 | .. automodule:: shipmmg.draw_obj 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | shipmmg.kt module 16 | ----------------- 17 | 18 | .. automodule:: shipmmg.kt 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | shipmmg.mmg\_3dof module 24 | ------------------------ 25 | 26 | .. automodule:: shipmmg.mmg_3dof 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | shipmmg.ship\_obj\_3dof module 32 | ------------------------------ 33 | 34 | .. automodule:: shipmmg.ship_obj_3dof 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: shipmmg 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | python-version: [3.8] 12 | os: [ubuntu-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install poetry 24 | poetry install 25 | - name: Test with pytest 26 | run: | 27 | poetry run pytest --cov=./ --cov-report=xml 28 | - uses: codecov/codecov-action@v4 29 | with: 30 | fail_ci_if_error: true # optional (default = false) 31 | file: ./coverage.xml 32 | flags: unittests # optional 33 | name: codecov-pDESy # optional 34 | token: ${{ secrets.CODECOV_TOKEN }} # required 35 | verbose: true # optional (default = false) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Taiga MITSUYUKI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. shipmmg documentation master file, created by 2 | sphinx-quickstart on Fri Jan 8 17:57:59 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | shipmmg: A Python package of ship maneuvering simulation 7 | =========================================================== 8 | 9 | ShipMMG is a unofficial Python package of ship maneuvering simulation with respect to the research committee on “standardization of mathematical model for ship maneuvering predictions” was organized by the JASNAOE. 10 | 11 | .. image:: https://github.com/ShipMMG/shipmmg/workflows/codecov/badge.svg 12 | 13 | 14 | .. image:: https://codecov.io/gh/ShipMMG/shipmmg/branch/main/graph/badge.svg?token=VQ1J2RTC7X 15 | :target: https://codecov.io/gh/ShipMMG/shipmmg 16 | 17 | 18 | Install 19 | -------- 20 | 21 | .. code-block:: bash 22 | 23 | pip install shipmmg 24 | # pip install git+https://git@github.com/ShipMMG/shipmmg.git # INSTALL FROM GITHUB 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | :caption: Contents: 29 | 30 | How to use shipmmg? 31 | -------------------- 32 | 33 | Comming soon. 34 | 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "shipmmg" 22 | copyright = "2022, Taiga MITSUYUKI" 23 | author = "Taiga MITSUYUKI" 24 | 25 | # The short X.Y version. 26 | version = "0.0" 27 | # The full version, including alpha/beta/rc tags 28 | release = "0.0.11" 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | "sphinx.ext.autodoc", 38 | "sphinx.ext.todo", 39 | "sphinx.ext.viewcode", 40 | "sphinx.ext.napoleon", 41 | "sphinx_rtd_theme", 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ["_templates"] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = "sphinx_rtd_theme" 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | html_static_path = ["_static"] 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShipMMG: Ship Maneuvering Simulation Model 2 | 3 | [![PyPI version](https://badge.fury.io/py/shipmmg.svg)](https://badge.fury.io/py/shipmmg) 4 | [![Anaconda-Server Badge](https://anaconda.org/taiga4112/shipmmg/badges/version.svg)](https://anaconda.org/taiga4112/shipmmg) 5 | ![codecov](https://github.com/ShipMMG/shipmmg/workflows/codecov/badge.svg) 6 | [![codecov](https://codecov.io/gh/ShipMMG/shipmmg/branch/main/graph/badge.svg?token=VQ1J2RTC7X)](https://codecov.io/gh/ShipMMG/shipmmg) 7 | 8 | ## What is it? 9 | 10 | **ShipMMG** is a unofficial Python package of ship maneuvering simulation with respect to the research committee on “standardization of mathematical model for ship maneuvering predictions” was organized by the JASNAOE. 11 | 12 | ## Where to get it 13 | 14 | The source code is currently hosted on GitHub at: [https://github.com/ShipMMG/shipmmg](https://github.com/ShipMMG/shipmmg) 15 | 16 | Binary installers for the latest released version will be available at the Python package index. Now, please install pDESy as following. 17 | 18 | ```sh 19 | pip install shipmmg 20 | # pip install git+ssh://git@github.com/ShipMMG/shipmmg.git # Install from GitHub 21 | # conda install -c conda-forge -c taiga4112 shipmmg # Install from Anaconda 22 | ``` 23 | 24 | ## License 25 | 26 | [MIT](https://github.com/ShipMMG/shipmmg/blob/master/LICENSE) 27 | 28 | ## For developers 29 | 30 | ### Developing shipmmg API 31 | 32 | Here is an example of constructing a developing environment. 33 | 34 | ```sh 35 | docker build -t shipmmg-dev-env . 36 | docker run --rm --name shipmmg-dev -v `pwd`:/code -w /code -it shipmmg-dev-env /bin/bash 37 | ``` 38 | 39 | In this docker container, we can run `pytest` for checking this library. 40 | 41 | ### Checking shipmmg API 42 | 43 | Here is an example of checking the shipmmg developing version using JupyterLab. 44 | 45 | ```sh 46 | docker-compose build 47 | docker-compose up 48 | ``` 49 | 50 | After that, access [http://localhost:8888](http://localhost:8888). 51 | 52 | - Password is `shipmmg`. 53 | 54 | ## Contribution 55 | 56 | 1. Fork it ( ) 57 | 2. Create your feature branch (git checkout -b my-new-feature) 58 | 3. Commit your changes (git commit -am 'Add some feature') 59 | 4. Push to the branch (git push origin my-new-feature) 60 | 5. Create new Pull Request 61 | 62 | If you want to join this project as a researcher, please contact [me](https://github.com/taiga4112). 63 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '24 12 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # static files generated from Django application using `collectstatic` 142 | media 143 | static 144 | 145 | # vscode project settings 146 | .vscode 147 | 148 | # figure 149 | *.gif 150 | *.png -------------------------------------------------------------------------------- /tests/test_kt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """test_kt. 4 | 5 | - pytest code of shipmmg/kt.py 6 | """ 7 | 8 | import os 9 | 10 | import numpy as np 11 | 12 | import pytest 13 | 14 | from shipmmg.kt import KTParams, simulate_kt, zigzag_test_kt 15 | from shipmmg.ship_obj_3dof import ShipObj3dof 16 | 17 | 18 | @pytest.fixture 19 | def sim_result(): 20 | """Just check shimmg.kt.simulate().""" 21 | K = 0.155 22 | T = 80.5 23 | kt_params = KTParams(K=K, T=T) 24 | duration = 50 25 | num_of_sampling = 1000 26 | time_list = np.linspace(0.00, duration, num_of_sampling) 27 | Ts = 50.0 28 | δ_list = 10 * np.pi / 180 * np.sin(2.0 * np.pi / Ts * time_list) 29 | result = simulate_kt(kt_params, time_list, δ_list, 0.0) 30 | return time_list, result 31 | 32 | 33 | @pytest.fixture 34 | def ship_kt(sim_result): 35 | """Fixture for testing in this file.""" 36 | time_list, sol = sim_result 37 | simulation_result = sol.sol(time_list) 38 | u_list = np.full(len(time_list), 20 * (1852.0 / 3600)) 39 | v_list = np.zeros(len(time_list)) 40 | r_list = simulation_result[0] 41 | ship = ShipObj3dof(L=100, B=10) 42 | ship.load_simulation_result(time_list, u_list, v_list, r_list) 43 | return ship 44 | 45 | 46 | def test_Ship3DOF_drawing_function(ship_kt, tmpdir): 47 | """Check drawing functions of Ship3DOF class by using KT simulation results.""" 48 | # Ship3DOF.draw_xy_trajectory() 49 | save_fig_path = os.path.join(str(tmpdir), "test.png") 50 | ship_kt.draw_xy_trajectory(dimensionless=True, fmt="ro") 51 | ship_kt.draw_xy_trajectory(save_fig_path=save_fig_path) 52 | 53 | # Ship3DOF.draw_chart() 54 | save_fig_path = os.path.join(str(tmpdir), "test.png") 55 | ship_kt.draw_chart( 56 | "time", 57 | "u", 58 | xlabel="time [sec]", 59 | ylabel=r"$u$" + " [m/s]", 60 | save_fig_path=save_fig_path, 61 | ) 62 | ship_kt.draw_chart( 63 | "time", 64 | "u", 65 | xlabel="time [sec]", 66 | ylabel=r"$u$" + " [m/s]", 67 | fmt="ro", 68 | save_fig_path=save_fig_path, 69 | ) 70 | 71 | x_index_list = ["time", "u", "v", "r", "x", "y", "psi"] 72 | y_index_list = ["time", "u", "v", "r", "x", "y", "psi"] 73 | for x_index in x_index_list: 74 | for y_index in y_index_list: 75 | ship_kt.draw_chart(x_index, y_index) 76 | 77 | with pytest.raises(Exception): 78 | ship_kt.draw_chart("time", "hogehoge") 79 | with pytest.raises(Exception): 80 | ship_kt.draw_chart("hogehoge", "y") 81 | 82 | # Ship3DOF.draw_gif() 83 | ship_kt.draw_gif(fmt=None, save_fig_path=save_fig_path) 84 | ship_kt.draw_gif(dimensionless=True, save_fig_path=save_fig_path) 85 | 86 | 87 | def test_zigzag_test_kt(tmpdir): 88 | """Just check shimmg.kt.zigzag_test_kt().""" 89 | K = 0.155 90 | T = 80.5 91 | kt_params = KTParams(K=K, T=T) 92 | target_δ_rad = 20.0 * np.pi / 180.0 93 | target_ψ_rad_deviation = 20.0 * np.pi / 180.0 94 | duration = 500 95 | num_of_sampling = 50000 96 | time_list = np.linspace(0.00, duration, num_of_sampling) 97 | δ_list, r_list = zigzag_test_kt( 98 | kt_params, 99 | target_δ_rad, 100 | target_ψ_rad_deviation, 101 | time_list, 102 | δ_rad_rate=10.0 * np.pi / 180, 103 | ) 104 | 105 | u_list = np.full(len(time_list), 20 * (1852.0 / 3600)) 106 | v_list = np.zeros(len(time_list)) 107 | ship = ShipObj3dof(L=100, B=10) 108 | ship.load_simulation_result(time_list, u_list, v_list, r_list) 109 | ship.δ = δ_list 110 | 111 | save_fig_path = os.path.join(str(tmpdir), "test.png") 112 | ship.draw_xy_trajectory(save_fig_path=save_fig_path) 113 | ship.draw_chart( 114 | "time", 115 | "psi", 116 | xlabel="time [sec]", 117 | ylabel=r"$\psi$" + " [rad]", 118 | save_fig_path=save_fig_path, 119 | ) 120 | ship.draw_chart( 121 | "time", 122 | "r", 123 | xlabel="time [sec]", 124 | ylabel=r"$r$" + " [rad/s]", 125 | save_fig_path=save_fig_path, 126 | ) 127 | ship.draw_chart( 128 | "time", 129 | "delta", 130 | xlabel="time [sec]", 131 | ylabel=r"$\delta$" + " [rad]", 132 | save_fig_path=save_fig_path, 133 | ) 134 | 135 | save_fig_path = os.path.join(str(tmpdir), "test_delta_psi.png") 136 | ship.draw_multi_y_chart( 137 | "time", 138 | ["delta", "psi"], 139 | xlabel="time [sec]", 140 | ylabel="[rad]", 141 | save_fig_path=save_fig_path, 142 | ) 143 | -------------------------------------------------------------------------------- /shipmmg/draw_obj.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """draw_obj. 4 | 5 | * Base class for drawing object not only for shipmmg. 6 | 7 | """ 8 | 9 | import numpy as np 10 | 11 | 12 | class DrawObj: 13 | """DrawObj class. 14 | 15 | General class for drawing object by using matplotlib.animation. 16 | Multiple ships can be drawn by using this class. 17 | """ 18 | 19 | def __init__(self, ax): 20 | """init.""" 21 | self.ax = ax 22 | self.img = [] 23 | self.img.append(ax.plot([], [], color="b")) 24 | self.img.append(ax.plot([], [], color="y")) 25 | 26 | def draw_obj_with_angle( 27 | self, center_x_list, center_y_list, shape_list, angle_list, obj="ship" 28 | ): 29 | """Draw square image with angle. 30 | 31 | Args: 32 | center_x_list (List[float]): list of the center x position of the square 33 | center_y_list (List[float]): list of the center y position of the square 34 | shape_list (List[float]): list of the square's shape(length/2, width/2) 35 | angle_list (List[float]): list of in radians 36 | obj (str: optional): object type, 'ship' or 'square' 37 | Returns: 38 | Image: List of Image 39 | """ 40 | for i in range(len(shape_list)): 41 | if obj == "square": 42 | square_x, square_y, angle_x, angle_y = self.__square_with_angle( 43 | center_x_list[i], center_y_list[i], shape_list[i], angle_list[i] 44 | ) 45 | elif obj == "ship": 46 | square_x, square_y, angle_x, angle_y = self.__ship_with_angle( 47 | center_x_list[i], center_y_list[i], shape_list[i], angle_list[i] 48 | ) 49 | self.img[i][0].set_xdata(square_x) 50 | self.img[i][0].set_ydata(square_y) 51 | return self.img 52 | 53 | def __rotate_pos(self, pos, angle): 54 | """Transform the coordinate in the angle. 55 | 56 | Args: 57 | pos (numpy.ndarray): local state, shape(data_size, 2) 58 | angle (float): rotate angle, in radians 59 | Returns: 60 | rotated_pos (numpy.ndarray): shape(data_size, 2) 61 | """ 62 | rot_mat = np.array( 63 | [[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]] 64 | ) 65 | 66 | return np.dot(pos, rot_mat.T) 67 | 68 | def __square(self, center_x, center_y, shape, angle): 69 | """Create square. 70 | 71 | Args: 72 | center_x (float): the center x position of the square 73 | center_y (float): the center y position of the square 74 | shape (tuple): the square's shape(width/2, height/2) 75 | angle (float): in radians 76 | Returns: 77 | square_x (numpy.ndarray): shape(5, ), counterclockwise from right-up 78 | square_y (numpy.ndarray): shape(5, ), counterclockwise from right-up 79 | """ 80 | # start with the up right points 81 | # create point in counterclockwise, local 82 | square_xy = np.array( 83 | [ 84 | [shape[0], shape[1]], 85 | [-shape[0], shape[1]], 86 | [-shape[0], -shape[1]], 87 | [shape[0], -shape[1]], 88 | [shape[0], shape[1]], 89 | ] 90 | ) 91 | # translate position to world 92 | # rotation 93 | trans_points = self.__rotate_pos(square_xy, angle) 94 | # translation 95 | trans_points += np.array([center_x, center_y]) 96 | 97 | return trans_points[:, 0], trans_points[:, 1] 98 | 99 | def __ship(self, center_x, center_y, shape, angle): 100 | """Create ship. 101 | 102 | Args: 103 | center_x (float): the center x position of the ship 104 | center_y (float): the center y position of the ship 105 | shape (tuple): the ship's shape(width/2, height/2) 106 | angle (float): in radians 107 | Returns: 108 | ship_x (numpy.ndarray): shape(5, ), counterclockwise from right-up 109 | shipe_y (numpy.ndarray): shape(5, ), counterclockwise from right-up 110 | """ 111 | # start with the up right points 112 | # create point in counterclockwise, local 113 | ship_xy = np.array( 114 | [ 115 | [shape[0] * 0.75, shape[1]], 116 | [-shape[0], shape[1]], 117 | [-shape[0], -shape[1]], 118 | [shape[0] * 0.75, -shape[1]], 119 | [shape[0], 0], 120 | [shape[0] * 0.75, shape[1]], 121 | ] 122 | ) 123 | # translate position to world 124 | # rotation 125 | trans_points = self.__rotate_pos(ship_xy, angle) 126 | # translation 127 | trans_points += np.array([center_x, center_y]) 128 | 129 | return trans_points[:, 0], trans_points[:, 1] 130 | 131 | def __square_with_angle(self, center_x, center_y, shape, angle): 132 | """Create square with angle line. 133 | 134 | Args: 135 | center_x (float): the center x position of the square 136 | center_y (float): the center y position of the square 137 | shape (tuple): the square's shape(width/2, height/2) 138 | angle (float): in radians 139 | Returns: 140 | square_x (numpy.ndarray): shape(5, ), counterclockwise from right-up 141 | square_y (numpy.ndarray): shape(5, ), counterclockwise from right-up 142 | angle_x (numpy.ndarray): x data of square angle 143 | angle_y (numpy.ndarray): y data of square angle 144 | """ 145 | square_x, square_y = self.__square(center_x, center_y, shape, angle) 146 | 147 | angle_x = np.array([center_x, center_x + np.cos(angle) * shape[0]]) 148 | angle_y = np.array([center_y, center_y + np.sin(angle) * shape[1]]) 149 | 150 | return square_x, square_y, angle_x, angle_y 151 | 152 | def __ship_with_angle(self, center_x, center_y, shape, angle): 153 | """Create ship with angle line. 154 | 155 | Args: 156 | center_x (float): the center x position of the ship 157 | center_y (float): the center y position of the ship 158 | shape (tuple): the ship's shape(width/2, height/2) 159 | angle (float): in radians 160 | Returns: 161 | ship_x (numpy.ndarray): shape(5, ), counterclockwise from right-up 162 | ship_y (numpy.ndarray): shape(5, ), counterclockwise from right-up 163 | angle_x (numpy.ndarray): x data of ship angle 164 | angle_y (numpy.ndarray): y data of ship angle 165 | """ 166 | ship_x, ship_y = self.__ship(center_x, center_y, shape, angle) 167 | 168 | angle_x = np.array([center_x, center_x + np.cos(angle) * shape[0]]) 169 | angle_y = np.array([center_y, center_y + np.sin(angle) * shape[1]]) 170 | 171 | return ship_x, ship_y, angle_x, angle_y 172 | -------------------------------------------------------------------------------- /tests/test_mmg_3dof.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """test_mmg_3dof. 4 | 5 | - pytest code of shipmmg/mmg_3dof.py 6 | """ 7 | 8 | import os 9 | 10 | import matplotlib.pyplot as plt 11 | 12 | import numpy as np 13 | 14 | import pytest 15 | 16 | from shipmmg.mmg_3dof import ( 17 | Mmg3DofBasicParams, 18 | Mmg3DofManeuveringParams, 19 | get_sub_values_from_simulation_result, 20 | simulate_mmg_3dof, 21 | zigzag_test_mmg_3dof, 22 | ) 23 | from shipmmg.ship_obj_3dof import ShipObj3dof 24 | 25 | 26 | @pytest.fixture 27 | def ship_KVLCC2_L7_model(): 28 | ρ = 1025.0 # 海水密度[kg/m^3] 29 | 30 | L_pp = 7.00 # 船長Lpp[m] 31 | B = 1.27 # 船幅[m] 32 | d = 0.46 # 喫水[m] 33 | nabla = 3.27 # 排水量[m^3] 34 | x_G = 0.25 # 重心位置[m] 35 | # C_b = 0.810 # 方形係数[-] 36 | D_p = 0.216 # プロペラ直径[m] 37 | H_R = 0.345 # 舵高さ[m] 38 | A_R = 0.0539 # 舵断面積[m^2] 39 | 40 | t_P = 0.220 # 推力減少率 41 | w_P0 = 0.40 # 有効伴流率 42 | m_x_dash = 0.022 # 付加質量x(無次元) 43 | m_y_dash = 0.223 # 付加質量y(無次元) 44 | J_z_dash = 0.011 # 付加質量Izz(無次元) 45 | t_R = 0.387 # 操縦抵抗減少率 46 | x_R_dash = -0.500 # 舵の相対位置 47 | a_H = 0.312 # 舵力増加係数 48 | x_H_dash = -0.464 # 舵力増分作用位置 49 | γ_R_minus = 0.395 # 整流係数 50 | γ_R_plus = 0.640 # 整流係数 51 | l_r_dash = -0.710 # 船長に対する舵位置 52 | x_P_dash = -0.690 # 船長に対するプロペラ位置 53 | ϵ = 1.09 # プロペラ・舵位置伴流係数比 54 | κ = 0.50 # 修正係数 55 | f_α = 2.747 # 直圧力勾配係数 56 | 57 | basic_params = Mmg3DofBasicParams( 58 | L_pp=L_pp, # 船長Lpp[m] 59 | B=B, # 船幅[m] 60 | d=d, # 喫水[m] 61 | x_G=x_G, # 重心位置[] 62 | D_p=D_p, # プロペラ直径[m] 63 | m=ρ * nabla, # 質量(無次元化)[kg] 64 | I_zG=ρ * nabla * ((0.25 * L_pp) ** 2), # 慣性モーメント[-] 65 | A_R=A_R, # 船の断面に対する舵面積比[-] 66 | η=D_p / H_R, # プロペラ直径に対する舵高さ(Dp/H) 67 | m_x=(0.5 * ρ * (L_pp**2) * d) * m_x_dash, # 付加質量x(無次元) 68 | m_y=(0.5 * ρ * (L_pp**2) * d) * m_y_dash, # 付加質量y(無次元) 69 | J_z=(0.5 * ρ * (L_pp**4) * d) * J_z_dash, # 付加質量Izz(無次元) 70 | f_α=f_α, 71 | ϵ=ϵ, # プロペラ・舵位置伴流係数比 72 | t_R=t_R, # 操縦抵抗減少率 73 | x_R=x_R_dash * L_pp, # 舵の相対位置 74 | a_H=a_H, # 舵力増加係数 75 | x_H=x_H_dash * L_pp, # 舵力増分作用位置 76 | γ_R_minus=γ_R_minus, # 整流係数 77 | γ_R_plus=γ_R_plus, # 整流係数 78 | l_R=l_r_dash, # 船長に対する舵位置 79 | κ=κ, # 修正係数 80 | t_P=t_P, # 推力減少率 81 | w_P0=w_P0, # 有効伴流率 82 | x_P=x_P_dash, # 船長に対するプロペラ位置 83 | ) 84 | 85 | k_0 = 0.2931 86 | k_1 = -0.2753 87 | k_2 = -0.1385 88 | R_0_dash = 0.022 89 | X_vv_dash = -0.040 90 | X_vr_dash = 0.002 91 | X_rr_dash = 0.011 92 | X_vvvv_dash = 0.771 93 | Y_v_dash = -0.315 94 | Y_r_dash = 0.083 95 | Y_vvv_dash = -1.607 96 | Y_vvr_dash = 0.379 97 | Y_vrr_dash = -0.391 98 | Y_rrr_dash = 0.008 99 | N_v_dash = -0.137 100 | N_r_dash = -0.049 101 | N_vvv_dash = -0.030 102 | N_vvr_dash = -0.294 103 | N_vrr_dash = 0.055 104 | N_rrr_dash = -0.013 105 | maneuvering_params = Mmg3DofManeuveringParams( 106 | k_0=k_0, 107 | k_1=k_1, 108 | k_2=k_2, 109 | R_0_dash=R_0_dash, 110 | X_vv_dash=X_vv_dash, 111 | X_vr_dash=X_vr_dash, 112 | X_rr_dash=X_rr_dash, 113 | X_vvvv_dash=X_vvvv_dash, 114 | Y_v_dash=Y_v_dash, 115 | Y_r_dash=Y_r_dash, 116 | Y_vvv_dash=Y_vvv_dash, 117 | Y_vvr_dash=Y_vvr_dash, 118 | Y_vrr_dash=Y_vrr_dash, 119 | Y_rrr_dash=Y_rrr_dash, 120 | N_v_dash=N_v_dash, 121 | N_r_dash=N_r_dash, 122 | N_vvv_dash=N_vvv_dash, 123 | N_vvr_dash=N_vvr_dash, 124 | N_vrr_dash=N_vrr_dash, 125 | N_rrr_dash=N_rrr_dash, 126 | ) 127 | return basic_params, maneuvering_params 128 | 129 | 130 | @pytest.fixture 131 | def kvlcc2_L7_35_turning(ship_KVLCC2_L7_model): 132 | """Do turning test using KVLCC2 L7 model.""" 133 | basic_params, maneuvering_params = ship_KVLCC2_L7_model 134 | duration = 200 # [s] 135 | # steering_rate = 1.76 * 4 # [°/s] 136 | max_δ_rad = 35 * np.pi / 180.0 # [rad] 137 | n_const = 17.95 # [rps] 138 | sampling = duration * 10 139 | time_list = np.linspace(0.00, duration, sampling) 140 | δ_rad_list = [0] * sampling 141 | for i in range(sampling): 142 | δ_rad_list[i] = max_δ_rad 143 | 144 | nps_list = np.array([n_const for i in range(sampling)]) 145 | 146 | sol = simulate_mmg_3dof( 147 | basic_params, 148 | maneuvering_params, 149 | time_list, 150 | δ_rad_list, 151 | nps_list, 152 | u0=2.29 * 0.512, 153 | v0=0.0, 154 | r0=0.0, 155 | ) 156 | sim_result = sol.sol(time_list) 157 | ship = ShipObj3dof(L=basic_params.L_pp, B=basic_params.B) 158 | ship.load_simulation_result(time_list, sim_result[0], sim_result[1], sim_result[2]) 159 | ship.nps = nps_list 160 | ship.δ = δ_rad_list 161 | return ship 162 | 163 | 164 | def test_get_sub_values_from_simulation_result( 165 | kvlcc2_L7_35_turning, ship_KVLCC2_L7_model, tmpdir 166 | ): 167 | """Test get_sub_values_from_simulation_result() using KVLCC2 L7 model.""" 168 | basic_params, maneuvering_params = ship_KVLCC2_L7_model 169 | ( 170 | X_H_list, 171 | X_R_list, 172 | X_P_list, 173 | Y_H_list, 174 | Y_R_list, 175 | N_H_list, 176 | N_R_list, 177 | ) = get_sub_values_from_simulation_result( 178 | kvlcc2_L7_35_turning.u, 179 | kvlcc2_L7_35_turning.v, 180 | kvlcc2_L7_35_turning.r, 181 | kvlcc2_L7_35_turning.δ, 182 | kvlcc2_L7_35_turning.nps, 183 | basic_params, 184 | maneuvering_params, 185 | ) 186 | ( 187 | X_H_list, 188 | X_R_list, 189 | X_P_list, 190 | Y_H_list, 191 | Y_R_list, 192 | N_H_list, 193 | N_R_list, 194 | U_list, 195 | β_list, 196 | v_dash_list, 197 | r_dash_list, 198 | β_P_list, 199 | w_P_list, 200 | J_list, 201 | K_T_list, 202 | β_R_list, 203 | γ_R_list, 204 | v_R_list, 205 | u_R_list, 206 | U_R_list, 207 | α_R_list, 208 | F_N_list, 209 | ) = get_sub_values_from_simulation_result( 210 | kvlcc2_L7_35_turning.u, 211 | kvlcc2_L7_35_turning.v, 212 | kvlcc2_L7_35_turning.r, 213 | kvlcc2_L7_35_turning.δ, 214 | kvlcc2_L7_35_turning.nps, 215 | basic_params, 216 | maneuvering_params, 217 | return_all_vals=True, 218 | ) 219 | 220 | save_fig_path = os.path.join(str(tmpdir), "testFN.png") 221 | 222 | fig = plt.figure() 223 | plt.plot(kvlcc2_L7_35_turning.time, F_N_list) 224 | fig.savefig(save_fig_path) 225 | 226 | 227 | def test_Ship3DOF_drawing_function(kvlcc2_L7_35_turning, tmpdir): 228 | """Check drawing functions of Ship3DOF class by using MMG 3DOF simulation results.""" 229 | # Ship3DOF.draw_xy_trajectory() 230 | save_fig_path = os.path.join(str(tmpdir), "trajectory.png") 231 | 232 | kvlcc2_L7_35_turning.draw_xy_trajectory(dimensionless=True) 233 | kvlcc2_L7_35_turning.draw_xy_trajectory(save_fig_path=save_fig_path) 234 | 235 | # Ship3DOF.draw_chart() 236 | save_fig_path = os.path.join(str(tmpdir), "param.png") 237 | 238 | kvlcc2_L7_35_turning.draw_chart( 239 | "time", 240 | "u", 241 | xlabel="time [sec]", 242 | ylabel=r"$u$" + " [m/s]", 243 | save_fig_path=save_fig_path, 244 | ) 245 | 246 | x_index_list = ["time", "u", "v", "r", "x", "y", "psi"] 247 | y_index_list = ["time", "u", "v", "r", "x", "y", "psi"] 248 | for x_index in x_index_list: 249 | for y_index in y_index_list: 250 | kvlcc2_L7_35_turning.draw_chart(x_index, y_index) 251 | 252 | with pytest.raises(Exception): 253 | kvlcc2_L7_35_turning.draw_chart("time", "hogehoge") 254 | with pytest.raises(Exception): 255 | kvlcc2_L7_35_turning.draw_chart("hogehoge", "y") 256 | 257 | # Ship3DOF.draw_gif() 258 | save_fig_path = os.path.join(str(tmpdir), "test.gif") 259 | 260 | kvlcc2_L7_35_turning.draw_gif(save_fig_path=save_fig_path) 261 | 262 | kvlcc2_L7_35_turning.draw_gif(dimensionless=True, save_fig_path=save_fig_path) 263 | 264 | 265 | def test_zigzag_test_mmg_before(ship_KVLCC2_L7_model, tmpdir): 266 | basic_params, maneuvering_params = ship_KVLCC2_L7_model 267 | target_δ_rad = 20.0 * np.pi / 180.0 268 | target_ψ_rad_deviation = -20.0 * np.pi / 180.0 269 | duration = 100 270 | num_of_sampling = 10000 271 | time_list = np.linspace(0.00, duration, num_of_sampling) 272 | n_const = 17.95 # [rps] 273 | nps_list = np.array([n_const for i in range(num_of_sampling)]) 274 | 275 | δ_list, u_list, v_list, r_list, x_list, y_list, ψ_list = zigzag_test_mmg_3dof( 276 | basic_params, 277 | maneuvering_params, 278 | target_δ_rad, 279 | target_ψ_rad_deviation, 280 | time_list, 281 | nps_list, 282 | δ_rad_rate=10.0 * np.pi / 180, 283 | ) 284 | 285 | ship = ShipObj3dof(L=100, B=10) 286 | ship.load_simulation_result(time_list, u_list, v_list, r_list) 287 | ship.δ = δ_list 288 | 289 | save_fig_path = os.path.join(str(tmpdir), "test_psi.png") 290 | 291 | ship.draw_xy_trajectory(save_fig_path=save_fig_path) 292 | ship.draw_chart( 293 | "time", 294 | "psi", 295 | xlabel="time [sec]", 296 | ylabel=r"$\psi$" + " [rad]", 297 | save_fig_path=save_fig_path, 298 | ) 299 | 300 | save_fig_path = os.path.join(str(tmpdir), "test_delta.png") 301 | ship.draw_xy_trajectory(save_fig_path=save_fig_path) 302 | ship.draw_chart( 303 | "time", 304 | "delta", 305 | xlabel="time [sec]", 306 | ylabel=r"$\delta$" + " [rad]", 307 | save_fig_path=save_fig_path, 308 | ) 309 | 310 | save_fig_path = os.path.join(str(tmpdir), "test_delta_psi.png") 311 | 312 | # save_fig_path = os.path.join(str(tmpdir), "test_delta_psi.png") 313 | 314 | ship.draw_multi_x_chart( 315 | ["delta", "psi"], 316 | "time", 317 | ylabel="time [sec]", 318 | xlabel="[rad]", 319 | save_fig_path=save_fig_path, 320 | ) 321 | 322 | 323 | def test_zigzag_test_mmg(ship_KVLCC2_L7_model, tmpdir): 324 | """Test zigzag test mmg simulation using KVLCC2 L7 model.""" 325 | basic_params, maneuvering_params = ship_KVLCC2_L7_model 326 | target_δ_rad = 20.0 * np.pi / 180.0 327 | target_ψ_rad_deviation = 20.0 * np.pi / 180.0 328 | duration = 80 329 | num_of_sampling = 10000 330 | time_list = np.linspace(0.00, duration, num_of_sampling) 331 | n_const = 17.95 # [rps] 332 | nps_list = np.array([n_const for i in range(num_of_sampling)]) 333 | 334 | δ_list, u_list, v_list, r_list, x_list, y_list, ψ_list = zigzag_test_mmg_3dof( 335 | basic_params, 336 | maneuvering_params, 337 | target_δ_rad, 338 | target_ψ_rad_deviation, 339 | time_list, 340 | nps_list, 341 | δ_rad_rate=15.0 * np.pi / 180, 342 | ) 343 | 344 | ship = ShipObj3dof(L=100, B=10) 345 | ship.load_simulation_result(time_list, u_list, v_list, r_list) 346 | ship.δ = δ_list 347 | ship.nps = nps_list 348 | 349 | save_fig_path = os.path.join(str(tmpdir), "delta_psi.png") 350 | 351 | fig = plt.figure() 352 | plt.plot(time_list, list(map(lambda δ: δ * 180 / np.pi, ship.δ))) 353 | plt.plot(time_list, list(map(lambda psi: psi * 180 / np.pi, ship.psi))) 354 | fig.savefig(save_fig_path) 355 | plt.close() 356 | 357 | ( 358 | X_H_list, 359 | X_R_list, 360 | X_P_list, 361 | Y_H_list, 362 | Y_R_list, 363 | N_H_list, 364 | N_R_list, 365 | U_list, 366 | β_list, 367 | v_dash_list, 368 | r_dash_list, 369 | β_P_list, 370 | w_P_list, 371 | J_list, 372 | K_T_list, 373 | β_R_list, 374 | γ_R_list, 375 | v_R_list, 376 | u_R_list, 377 | U_R_list, 378 | α_R_list, 379 | F_N_list, 380 | ) = get_sub_values_from_simulation_result( 381 | ship.u, 382 | ship.v, 383 | ship.r, 384 | ship.δ, 385 | ship.nps, 386 | basic_params, 387 | maneuvering_params, 388 | return_all_vals=True, 389 | ) 390 | 391 | save_fig_path = os.path.join(str(tmpdir), "w_P.png") 392 | fig = plt.figure() 393 | plt.plot(time_list, w_P_list) 394 | fig.savefig(save_fig_path) 395 | plt.close() 396 | 397 | save_fig_path = os.path.join(str(tmpdir), "J.png") 398 | fig = plt.figure() 399 | plt.plot(time_list, J_list) 400 | fig.savefig(save_fig_path) 401 | plt.close() 402 | 403 | save_fig_path = os.path.join(str(tmpdir), "K_T.png") 404 | 405 | fig = plt.figure() 406 | plt.plot(time_list, K_T_list) 407 | fig.savefig(save_fig_path) 408 | plt.close() 409 | 410 | save_fig_path = os.path.join(str(tmpdir), "U_R.png") 411 | fig = plt.figure() 412 | plt.plot(time_list, U_R_list) 413 | fig.savefig(save_fig_path) 414 | plt.close() 415 | 416 | save_fig_path = os.path.join(str(tmpdir), "α_R.png") 417 | fig = plt.figure() 418 | plt.plot(time_list, α_R_list) 419 | fig.savefig(save_fig_path) 420 | plt.close() 421 | 422 | save_fig_path = os.path.join(str(tmpdir), "F_N.png") 423 | fig = plt.figure() 424 | plt.plot(time_list, F_N_list) 425 | fig.savefig(save_fig_path) 426 | plt.close() 427 | 428 | save_fig_path = os.path.join(str(tmpdir), "gamma_R.png") 429 | fig = plt.figure() 430 | plt.plot(time_list, γ_R_list) 431 | fig.savefig(save_fig_path) 432 | plt.close() 433 | -------------------------------------------------------------------------------- /shipmmg/kt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """kt. 4 | 5 | * KT (1DOF) simulation code 6 | 7 | :math:`T dr = -r + K \\delta` 8 | 9 | """ 10 | 11 | import dataclasses 12 | from typing import List 13 | 14 | import numpy as np 15 | 16 | from scipy.integrate import solve_ivp 17 | from scipy.interpolate import CubicSpline 18 | from .ship_obj_3dof import ShipObj3dof 19 | 20 | 21 | @dataclasses.dataclass 22 | class KTParams: 23 | """Dataclass for setting KT parameters of KT simulation. 24 | 25 | Attributes: 26 | K (float): One of parameters in KT model. [1/s] 27 | T (float): One of parameters in KT model. [s] 28 | """ 29 | 30 | K: float 31 | T: float 32 | 33 | 34 | def simulate_kt( 35 | kt_params: KTParams, 36 | time_list: List[float], 37 | δ_list: List[float], 38 | r0: float = 0.0, 39 | method: str = "RK45", 40 | t_eval=None, 41 | events=None, 42 | vectorized=False, 43 | **options 44 | ): 45 | """KT simulation. 46 | 47 | KT simulation by following equation of motion. 48 | 49 | :math:`T dr = -r + K \\delta` 50 | 51 | Args: 52 | kt_params (KTParams): 53 | KT parameters. 54 | time_list (list[float]): 55 | time list of simulation. 56 | δ_list (list[float]): 57 | rudder angle list of simulation. 58 | r0 (float, optional): 59 | rate of turn [rad/s] in initial condition (`time_list[0]`). 60 | Defaults to 0.0. 61 | method (str, optional): 62 | Integration method to use in 63 | `scipy.integrate.solve_ivp() 64 | `_: 65 | 66 | "RK45" (default): 67 | Explicit Runge-Kutta method of order 5(4). 68 | The error is controlled assuming accuracy of the fourth-order method, 69 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 70 | A quartic interpolation polynomial is used for the dense output. 71 | Can be applied in the complex domain. 72 | "RK23": 73 | Explicit Runge-Kutta method of order 3(2). 74 | The error is controlled assuming accuracy of the second-order method, 75 | but steps are taken using the third-order accurate formula (local extrapolation is done). 76 | A cubic Hermite polynomial is used for the dense output. 77 | Can be applied in the complex domain. 78 | "DOP853": 79 | Explicit Runge-Kutta method of order 8. 80 | Python implementation of the “DOP853” algorithm originally written in Fortran. 81 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 82 | Can be applied in the complex domain. 83 | "Radau": 84 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 85 | The error is controlled with a third-order accurate embedded formula. 86 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 87 | "BDF": 88 | Implicit multi-step variable-order (1 to 5) method 89 | based on a backward differentiation formula for the derivative approximation. 90 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 91 | Can be applied in the complex domain. 92 | "LSODA": 93 | Adams/BDF method with automatic stiffness detection and switching. 94 | This is a wrapper of the Fortran solver from ODEPACK. 95 | 96 | t_eval (array_like or None, optional): 97 | Times at which to store the computed solution, must be sorted and lie within t_span. 98 | If None (default), use points selected by the solver. 99 | events (callable, or list of callables, optional): 100 | Events to track. If None (default), no events will be tracked. 101 | Each event occurs at the zeros of a continuous function of time and state. 102 | Each function must have the signature event(t, y) and return a float. 103 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 104 | By default, all zeros will be found. The solver looks for a sign change over each step, 105 | so if multiple zero crossings occur within one step, events may be missed. 106 | Additionally each event function might have the following attributes: 107 | terminal (bool, optional): 108 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 109 | direction (float, optional): 110 | Direction of a zero crossing. 111 | If direction is positive, event will only trigger when going from negative to positive, 112 | and vice versa if direction is negative. 113 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 114 | You can assign attributes like `event.terminal = True` to any function in Python. 115 | vectorized (bool, optional): 116 | Whether `fun` is implemented in a vectorized fashion. Default is False. 117 | options: 118 | Options passed to a chosen solver. 119 | All options available for already implemented solvers are listed in 120 | `scipy.integrate.solve_ivp() 121 | `_: 122 | 123 | Returns: 124 | Bunch object with the following fields defined: 125 | t (ndarray, shape (`n_points`,)): 126 | Time points. 127 | y (ndarray, shape (`n_points`,)): 128 | Values of the solution at t. 129 | sol (OdeSolution): 130 | Found solution as OdeSolution instance from KT simulation. 131 | t_events (list of ndarray or None): 132 | Contains for each event type a list of arrays at which an event of that type event was detected. 133 | None if events was None. 134 | y_events (list of ndarray or None): 135 | For each value of t_events, the corresponding value of the solution. 136 | None if events was None. 137 | nfev (int): 138 | Number of evaluations of the right-hand side. 139 | njev (int): 140 | Number of evaluations of the jacobian. 141 | nlu (int): 142 | Number of LU decomposition. 143 | status (int): 144 | Reason for algorithm termination: 145 | - -1: Integration step failed. 146 | - 0: The solver successfully reached the end of `tspan`. 147 | - 1: A termination event occurred. 148 | message (string): 149 | Human-readable description of the termination reason. 150 | success (bool): 151 | True if the solver reached the interval end or a termination event occurred (`status >= 0`). 152 | 153 | Examples: 154 | >>> kt_params = KTParams(K=0.15, T=60.0) 155 | >>> duration = 300 156 | >>> num_of_sampling = 3000 157 | >>> time_list = np.linspace(0.00, duration, num_of_sampling) 158 | >>> δ_list = 35 * np.pi / 180 * np.sin(3.0 * np.pi / Ts * time_list) 159 | >>> r0 = 0.0 160 | >>> sol = simulate_kt(kt_params, time_list, δ_list, r0) 161 | >>> result = sol.sol(time_list) 162 | """ 163 | return simulate( 164 | K=kt_params.K, 165 | T=kt_params.T, 166 | time_list=time_list, 167 | δ_list=δ_list, 168 | r0=r0, 169 | method=method, 170 | t_eval=t_eval, 171 | events=events, 172 | vectorized=vectorized, 173 | **options 174 | ) 175 | 176 | 177 | def simulate( 178 | K: float, 179 | T: float, 180 | time_list: List[float], 181 | δ_list: List[float], 182 | r0: float = 0.0, 183 | method: str = "RK45", 184 | t_eval=None, 185 | events=None, 186 | vectorized=False, 187 | **options 188 | ): 189 | """KT simulation. 190 | 191 | KT simulation by following equation of motion. 192 | 193 | :math:`T dr = -r + K \\delta` 194 | 195 | Args: 196 | K (float): 197 | parameter K of KT model. 198 | T (float): 199 | parameter T of KT model. 200 | time_list (list[float]): 201 | time list of simulation. 202 | δ_list (list[float]): 203 | rudder angle list of simulation. 204 | r0 (float, optional): 205 | rate of turn [rad/s] in initial condition (`time_list[0]`). 206 | Defaults to 0.0. 207 | method (str, optional): 208 | Integration method to use in 209 | `scipy.integrate.solve_ivp() 210 | `_: 211 | 212 | "RK45" (default): 213 | Explicit Runge-Kutta method of order 5(4). 214 | The error is controlled assuming accuracy of the fourth-order method, 215 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 216 | A quartic interpolation polynomial is used for the dense output. 217 | Can be applied in the complex domain. 218 | "RK23": 219 | Explicit Runge-Kutta method of order 3(2). 220 | The error is controlled assuming accuracy of the second-order method, 221 | but steps are taken using the third-order accurate formula (local extrapolation is done). 222 | A cubic Hermite polynomial is used for the dense output. 223 | Can be applied in the complex domain. 224 | "DOP853": 225 | Explicit Runge-Kutta method of order 8. 226 | Python implementation of the “DOP853” algorithm originally written in Fortran. 227 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 228 | Can be applied in the complex domain. 229 | "Radau": 230 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 231 | The error is controlled with a third-order accurate embedded formula. 232 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 233 | "BDF": 234 | Implicit multi-step variable-order (1 to 5) method 235 | based on a backward differentiation formula for the derivative approximation. 236 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 237 | Can be applied in the complex domain. 238 | "LSODA": 239 | Adams/BDF method with automatic stiffness detection and switching. 240 | This is a wrapper of the Fortran solver from ODEPACK. 241 | 242 | t_eval (array_like or None, optional): 243 | Times at which to store the computed solution, must be sorted and lie within t_span. 244 | If None (default), use points selected by the solver. 245 | events (callable, or list of callables, optional): 246 | Events to track. If None (default), no events will be tracked. 247 | Each event occurs at the zeros of a continuous function of time and state. 248 | Each function must have the signature event(t, y) and return a float. 249 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 250 | By default, all zeros will be found. The solver looks for a sign change over each step, 251 | so if multiple zero crossings occur within one step, events may be missed. 252 | Additionally each event function might have the following attributes: 253 | terminal (bool, optional): 254 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 255 | direction (float, optional): 256 | Direction of a zero crossing. 257 | If direction is positive, event will only trigger when going from negative to positive, 258 | and vice versa if direction is negative. 259 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 260 | You can assign attributes like `event.terminal = True` to any function in Python. 261 | vectorized (bool, optional): 262 | Whether `fun` is implemented in a vectorized fashion. Default is False. 263 | options: 264 | Options passed to a chosen solver. 265 | All options available for already implemented solvers are listed in 266 | `scipy.integrate.solve_ivp() 267 | `_: 268 | 269 | Returns: 270 | Bunch object with the following fields defined: 271 | t (ndarray, shape (`n_points`,)): 272 | Time points. 273 | y (ndarray, shape (`n_points`,)): 274 | Values of the solution at t. 275 | sol (OdeSolution): 276 | Found solution as OdeSolution instance from KT simulation. 277 | t_events (list of ndarray or None): 278 | Contains for each event type a list of arrays at which an event of that type event was detected. 279 | None if events was None. 280 | y_events (list of ndarray or None): 281 | For each value of t_events, the corresponding value of the solution. 282 | None if events was None. 283 | nfev (int): 284 | Number of evaluations of the right-hand side. 285 | njev (int): 286 | Number of evaluations of the jacobian. 287 | nlu (int): 288 | Number of LU decomposition. 289 | status (int): 290 | Reason for algorithm termination: 291 | - -1: Integration step failed. 292 | - 0: The solver successfully reached the end of `tspan`. 293 | - 1: A termination event occurred. 294 | message (string): 295 | Human-readable description of the termination reason. 296 | success (bool): 297 | True if the solver reached the interval end or a termination event occurred (`status >= 0`). 298 | 299 | 300 | Examples: 301 | >>> K = 0.15 302 | >>> T = 60.0 303 | >>> duration = 300 304 | >>> num_of_sampling = 3000 305 | >>> time_list = np.linspace(0.00, duration, num_of_sampling) 306 | >>> δ_list = 35 * np.pi / 180 * np.sin(3.0 * np.pi / Ts * time_list) 307 | >>> r0 = 0.0 308 | >>> sol = simulate_kt(K, T, time_list, δ_list, r0) 309 | >>> result = sol.sol(time_list) 310 | """ 311 | spl_δ = CubicSpline(time_list, δ_list, bc_type="natural", extrapolate=True) 312 | spl_δ_dot = spl_δ.derivative() 313 | 314 | def kt_eom_solve_ivp(t, X, K, T): 315 | r, δ = X 316 | d_r = 1.0 / T * (-r + K * δ) 317 | d_δ = spl_δ_dot(t) 318 | return [d_r, d_δ] 319 | 320 | sol = solve_ivp( 321 | kt_eom_solve_ivp, 322 | [time_list[0], time_list[-1]], 323 | [r0, δ_list[0]], 324 | args=(K, T), 325 | dense_output=True, 326 | method=method, 327 | t_eval=t_eval, 328 | events=events, 329 | vectorized=vectorized, 330 | **options 331 | ) 332 | return sol 333 | 334 | 335 | def zigzag_test_kt( 336 | kt_params: KTParams, 337 | target_δ_rad: float, 338 | target_ψ_rad_deviation: float, 339 | time_list: List[float], 340 | δ0: float = 0.0, 341 | δ_rad_rate: float = 1.0 * np.pi / 180, 342 | r0: float = 0.0, 343 | ψ0: float = 0.0, 344 | method: str = "RK45", 345 | t_eval=None, 346 | events=None, 347 | vectorized=False, 348 | **options 349 | ): 350 | """Zig-zag test simulation. 351 | 352 | Args: 353 | kt_params (KTParams): 354 | KT parameters. 355 | target_δ_rad (float): 356 | target absolute value of rudder angle. 357 | target_ψ_rad_deviation (float): 358 | target absolute value of psi deviation from ψ0[rad]. 359 | time_list (list[float]): 360 | time list of simulation. 361 | δ0 (float): 362 | Initial rudder angle [rad]. 363 | Defaults to 0.0. 364 | δ_rad_rate (float): 365 | Initial rudder angle rate [rad/s]. 366 | Defaults to 1.0. 367 | r0 (float, optional): 368 | rate of turn [rad/s] in initial condition (`time_list[0]`). 369 | Defaults to 0.0. 370 | ψ0 (float, optional): 371 | Inital azimuth [rad] in initial condition (`time_list[0]`).. 372 | Defaults to 0.0. 373 | method (str, optional): 374 | Integration method to use in 375 | `scipy.integrate.solve_ivp() 376 | `_: 377 | 378 | "RK45" (default): 379 | Explicit Runge-Kutta method of order 5(4). 380 | The error is controlled assuming accuracy of the fourth-order method, 381 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 382 | A quartic interpolation polynomial is used for the dense output. 383 | Can be applied in the complex domain. 384 | "RK23": 385 | Explicit Runge-Kutta method of order 3(2). 386 | The error is controlled assuming accuracy of the second-order method, 387 | but steps are taken using the third-order accurate formula (local extrapolation is done). 388 | A cubic Hermite polynomial is used for the dense output. 389 | Can be applied in the complex domain. 390 | "DOP853": 391 | Explicit Runge-Kutta method of order 8. 392 | Python implementation of the “DOP853” algorithm originally written in Fortran. 393 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 394 | Can be applied in the complex domain. 395 | "Radau": 396 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 397 | The error is controlled with a third-order accurate embedded formula. 398 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 399 | "BDF": 400 | Implicit multi-step variable-order (1 to 5) method 401 | based on a backward differentiation formula for the derivative approximation. 402 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 403 | Can be applied in the complex domain. 404 | "LSODA": 405 | Adams/BDF method with automatic stiffness detection and switching. 406 | This is a wrapper of the Fortran solver from ODEPACK. 407 | 408 | t_eval (array_like or None, optional): 409 | Times at which to store the computed solution, must be sorted and lie within t_span. 410 | If None (default), use points selected by the solver. 411 | events (callable, or list of callables, optional): 412 | Events to track. If None (default), no events will be tracked. 413 | Each event occurs at the zeros of a continuous function of time and state. 414 | Each function must have the signature event(t, y) and return a float. 415 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 416 | By default, all zeros will be found. The solver looks for a sign change over each step, 417 | so if multiple zero crossings occur within one step, events may be missed. 418 | Additionally each event function might have the following attributes: 419 | terminal (bool, optional): 420 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 421 | direction (float, optional): 422 | Direction of a zero crossing. 423 | If direction is positive, event will only trigger when going from negative to positive, 424 | and vice versa if direction is negative. 425 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 426 | You can assign attributes like `event.terminal = True` to any function in Python. 427 | vectorized (bool, optional): 428 | Whether `fun` is implemented in a vectorized fashion. Default is False. 429 | options: 430 | Options passed to a chosen solver. 431 | All options available for already implemented solvers are listed in 432 | `scipy.integrate.solve_ivp() 433 | `_: 434 | 435 | Returns: 436 | final_δ_list (list[float])) : list of rudder angle. 437 | final_r_list (list[float])) : list of rate of turn. 438 | """ 439 | target_ψ_rad_deviation = np.abs(target_ψ_rad_deviation) 440 | 441 | final_δ_list = [0.0] * len(time_list) 442 | final_r_list = [0.0] * len(time_list) 443 | 444 | next_stage_index = 0 445 | target_δ_rad = -target_δ_rad # for changing in while loop 446 | ψ = ψ0 447 | 448 | while next_stage_index < len(time_list): 449 | target_δ_rad = -target_δ_rad 450 | start_index = next_stage_index 451 | 452 | # Make delta list 453 | δ_list = [0.0] * (len(time_list) - start_index) 454 | if start_index == 0: 455 | δ_list[0] = δ0 456 | r0 = r0 457 | else: 458 | δ_list[0] = final_δ_list[start_index - 1] 459 | r0 = final_r_list[start_index - 1] 460 | 461 | for i in range(start_index + 1, len(time_list)): 462 | Δt = time_list[i] - time_list[i - 1] 463 | if target_δ_rad > 0: 464 | δ = δ_list[i - 1 - start_index] + δ_rad_rate * Δt 465 | if δ >= target_δ_rad: 466 | δ = target_δ_rad 467 | δ_list[i - start_index] = δ 468 | elif target_δ_rad <= 0: 469 | δ = δ_list[i - 1 - start_index] - δ_rad_rate * Δt 470 | if δ <= target_δ_rad: 471 | δ = target_δ_rad 472 | δ_list[i - start_index] = δ 473 | 474 | # Simulate & project simulation result to ShipObj3dof for getting ψ information 475 | sol = simulate(kt_params.K, kt_params.T, time_list[start_index:], δ_list, r0=r0) 476 | simulation_result = sol.sol(time_list[start_index:]) 477 | u_list = np.zeros(len(time_list[start_index:])) 478 | v_list = np.zeros(len(time_list[start_index:])) 479 | r_list = simulation_result[0] 480 | ship = ShipObj3dof(L=100, B=10) 481 | ship.load_simulation_result( 482 | time_list[start_index:], u_list, v_list, r_list, psi0=ψ 483 | ) 484 | 485 | # get finish index 486 | target_ψ_rad = ψ0 + target_ψ_rad_deviation 487 | if target_δ_rad < 0: 488 | target_ψ_rad = ψ0 - target_ψ_rad_deviation 489 | ψ_list = ship.psi 490 | bool_ψ_list = [True if ψ < target_ψ_rad else False for ψ in ψ_list] 491 | if target_δ_rad < 0: 492 | bool_ψ_list = [True if ψ > target_ψ_rad else False for ψ in ψ_list] 493 | over_index_list = [i for i, flag in enumerate(bool_ψ_list) if flag is False] 494 | next_stage_index = len(time_list) 495 | if len(over_index_list) > 0: 496 | ψ = ψ_list[over_index_list[0]] 497 | next_stage_index = over_index_list[0] + start_index 498 | final_δ_list[start_index:next_stage_index] = δ_list[: over_index_list[0]] 499 | final_r_list[start_index:next_stage_index] = r_list[: over_index_list[0]] 500 | else: 501 | final_δ_list[start_index:next_stage_index] = δ_list 502 | final_r_list[start_index:next_stage_index] = r_list 503 | 504 | return final_δ_list, final_r_list 505 | -------------------------------------------------------------------------------- /shipmmg/ship_obj_3dof.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ship_obj_3dof. 4 | 5 | * Ship class for drawing the simulation results and estimating maneuvering parameters. 6 | 7 | Todo: 8 | Developing the function of estimating maneuvering parameters. 9 | * KT 10 | * MMG3DOF 11 | """ 12 | 13 | import dataclasses 14 | from typing import List 15 | 16 | import matplotlib 17 | import matplotlib.pyplot as plt 18 | from matplotlib.animation import FuncAnimation 19 | 20 | import numpy as np 21 | 22 | from scipy.interpolate import CubicSpline 23 | 24 | from .draw_obj import DrawObj 25 | 26 | 27 | @dataclasses.dataclass 28 | class ShipObj3dof: 29 | """Ship 3DOF class just for drawing. 30 | 31 | Attributes: 32 | L (float): 33 | ship length [m] 34 | B (float) 35 | ship breath [m] 36 | time (list[float]): 37 | Time list of simulation result. 38 | u (list[float]): 39 | List of axial velocity [m/s] in simulation result. 40 | v (list[float]): 41 | List of lateral velocity [m/s] in simulation result. 42 | r (list[float]): 43 | List of rate of turn [rad/s] in simulation result. 44 | x (list[float]): 45 | List of position of X axis [m] in simulation result. 46 | y (list[float]): 47 | List of position of Y axis [m/s] in simulation result. 48 | psi (list[float]): 49 | List of azimuth [rad] in simulation result. 50 | δ (list[float]): 51 | rudder angle list of simulation. 52 | nps (List[float]): 53 | nps list of simulation. 54 | """ 55 | 56 | # Ship overview 57 | L: float 58 | B: float 59 | # Simulation result 60 | time: List[float] = dataclasses.field(default_factory=list) 61 | u: List[float] = dataclasses.field(default_factory=list) 62 | v: List[float] = dataclasses.field(default_factory=list) 63 | r: List[float] = dataclasses.field(default_factory=list) 64 | x: List[float] = dataclasses.field(default_factory=list) 65 | y: List[float] = dataclasses.field(default_factory=list) 66 | psi: List[float] = dataclasses.field(default_factory=list) 67 | δ: List[float] = dataclasses.field(default_factory=list) 68 | nps: List[float] = dataclasses.field(default_factory=list) 69 | 70 | def register_simulation_result( 71 | self, 72 | time: List[float], 73 | u: List[float], 74 | v: List[float], 75 | r: List[float], 76 | x: List[float], 77 | y: List[float], 78 | psi: List[float], 79 | ): 80 | """register simulation result (time, u, v, r, x, y, psi). 81 | 82 | Args: 83 | time (list[float]): 84 | Time list of simulation result. 85 | u (list[float]): 86 | List of axial velocity [m/s] in simulation result. 87 | v (list[float]): 88 | List of lateral velocity [m/s] in simulation result. 89 | r (list[float]): 90 | List of rate of turn [rad/s] in simulation result. 91 | x (list[float]): 92 | List of position of X axis [m]. 93 | y (list[float]): 94 | List of position of Y axis [m/s]. 95 | psi (list[float]): 96 | List of inital azimuth [rad]. 97 | """ 98 | self.time = time 99 | self.u = u 100 | self.v = v 101 | self.r = r 102 | self.x = x 103 | self.y = y 104 | self.psi = psi 105 | 106 | def load_simulation_result( 107 | self, 108 | time: List[float], 109 | u: List[float], 110 | v: List[float], 111 | r: List[float], 112 | x0: float = 0.0, 113 | y0: float = 0.0, 114 | psi0: float = 0.0, 115 | ): 116 | """Load simulation result (time, u, v, r). 117 | 118 | By running this, `x`, `y` and `psi` of this class are registered automatically. 119 | 120 | Args: 121 | time (list[float]): 122 | Time list of simulation result. 123 | u (list[float]): 124 | List of axial velocity [m/s] in simulation result. 125 | v (list[float]): 126 | List of lateral velocity [m/s] in simulation result. 127 | r (list[float]): 128 | List of rate of turn [rad/s] in simulation result. 129 | x0 (float, optional): 130 | Inital position of X axis [m]. 131 | Defaults to 0.0. 132 | y0 (float, optional): 133 | Inital position of Y axis [m/s]. 134 | Defaults to 0.0. 135 | psi0 (float, optional): 136 | Inital azimuth [rad]. 137 | Defaults to 0.0. 138 | 139 | Examples: 140 | >>> time_list = np.linspace(0.00, duration, num_of_sampling) 141 | >>> delta_list = np.full(len(time_list), 10 * np.pi / 180) 142 | >>> kt_params = KTParams(K=0.15, T=60.0) 143 | >>> result = kt.simulate_kt(kt_params, time_list, delta_list) 144 | >>> u_list = np.full(len(time_list), 20 * (1852.0 / 3600)) 145 | >>> v_list = np.zeros(len(time_list)) 146 | >>> r_list = result[0] 147 | >>> ship = ShipObj3dof(L = 180, B = 20) 148 | >>> ship.load_simulation_result(time_list, u_list, v_list, r_list) 149 | >>> print(ship.x, ship.y, ship.psi) 150 | """ 151 | x = [x0] 152 | y = [y0] 153 | psi = [psi0] 154 | for i, (ut, vt, rt) in enumerate(zip(u, v, r)): 155 | if i > 0: 156 | dt = time[i] - time[i - 1] 157 | x.append(x[-1] + (ut * np.cos(psi[-1]) - vt * np.sin(psi[-1])) * dt) 158 | y.append(y[-1] + (ut * np.sin(psi[-1]) + vt * np.cos(psi[-1])) * dt) 159 | psi.append(psi[-1] + rt * dt) 160 | 161 | # Register 162 | self.time = time 163 | self.u = u 164 | self.v = v 165 | self.r = r 166 | self.x = x 167 | self.y = y 168 | self.psi = psi 169 | 170 | def estimate_KT_LSM(self): 171 | """Estimate KT by least square method.""" 172 | A = np.c_[self.δ, self.r] 173 | spl_r = CubicSpline(self.time, self.r, bc_type="natural", extrapolate=True) 174 | B = spl_r.derivative()(self.time) 175 | THETA = np.linalg.pinv(A).dot(B.T) 176 | T = -1.0 / THETA[1] 177 | K = THETA[0] * T 178 | return K, T 179 | 180 | def draw_xy_trajectory( 181 | self, 182 | dimensionless: bool = False, 183 | aspect_equal: bool = True, 184 | num: int or str = None, 185 | figsize: List[float] = [6.4, 4.8], 186 | dpi: float = 100.0, 187 | fmt: str = None, 188 | facecolor: str = None, 189 | edgecolor: str = None, 190 | frameon: bool = True, 191 | FigureClass: matplotlib.figure.Figure = matplotlib.figure.Figure, 192 | clear: bool = False, 193 | layout: str = None, 194 | save_fig_path: str = None, 195 | **kwargs 196 | ) -> plt.Figure: 197 | """Draw trajectry(x,y). 198 | 199 | Args: 200 | dimensionless (bool, optional): 201 | drawing with dimensionless by using L or not. 202 | Defaults to False 203 | aspect_equal (bool, optional): 204 | Set equal of figure aspect or not. 205 | Defaults to True. 206 | num (int or str, optional): 207 | A unique identifier for the figure. 208 | If a figure with that identifier already exists, this figure is made active and returned. 209 | An integer refers to the Figure.number attribute, a string refers to the figure label. 210 | If there is no figure with the identifier or num is not given, 211 | a new figure is created, made active and returned. 212 | If num is an int, it will be used for the Figure.number attribute. 213 | Otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). 214 | If num is a string, the figure label and the window title is set to this value. 215 | Default to None. 216 | figsize ((float, float), optional): 217 | Width, height in inches. 218 | Default to [6.4, 4.8] 219 | dpi (float, optional): 220 | The resolution of the figure in dots-per-inch. 221 | Default to 100.0. 222 | figsize ((float, float), optional): 223 | Width, height in inches. 224 | Default to [6.4, 4.8] 225 | dpi (float, optional): 226 | The resolution of the figure in dots-per-inch. 227 | Default to 100.0 228 | facecolor (str, optional): 229 | The background color. 230 | edgecolor (str, optional): 231 | The border color. 232 | frameon (bool, optional): 233 | If False, suppress drawing the figure frame. 234 | Defaults to True. 235 | FigureClass (subclass of matplotlib.figure.Figure, optional): 236 | Optionally use a custom Figure instance. 237 | Defaults to matplotlib.figure.Figure. 238 | clear (bool, optional): 239 | If True and the figure already exists, then it is cleared. 240 | Defaults to False. 241 | layout (str, optional): 242 | If 'tight', adjust subplot parameters using tight_layout with default padding. 243 | If 'constrained', use constrained layout to adjust positioning of plot elements. 244 | Defaults to None. 245 | fmt (str, optional): 246 | A format string, e.g. 'ro' for red circles. 247 | See the Notes section for a full description of the format strings. 248 | Format strings are just an abbreviation for quickly setting basic line properties. 249 | All of these and more can also be controlled by keyword arguments. 250 | This argument cannot be passed as keyword. 251 | Defaults to None. 252 | save_fig_path (str, optional): 253 | Path of saving figure. 254 | Defaults to None. 255 | **kwargs (matplotlib.lines.Line2D properties, optional): 256 | kwargs are used to specify properties 257 | like a line label (for auto legends), linewidth, antialiasing, marker face color. 258 | You can show the detailed information at `matplotlib.lines.Line2D 259 | `_ 260 | 261 | Returns: 262 | matplotlib.pyplot.Figure: Figure 263 | 264 | Examples: 265 | >>> ship.draw_xy_trajectory(save_fig_path="test.png") 266 | """ 267 | fig = plt.figure( 268 | num=num, 269 | figsize=figsize, 270 | dpi=dpi, 271 | facecolor=facecolor, 272 | edgecolor=edgecolor, 273 | frameon=frameon, 274 | FigureClass=FigureClass, 275 | clear=clear, 276 | layout=layout, 277 | ) 278 | 279 | if dimensionless: 280 | if fmt is None: 281 | plt.plot(np.array(self.x) / self.L, np.array(self.y) / self.L, **kwargs) 282 | else: 283 | plt.plot( 284 | np.array(self.x) / self.L, np.array(self.y) / self.L, fmt, **kwargs 285 | ) 286 | plt.xlabel(r"$x/L$") 287 | plt.ylabel(r"$y/L$") 288 | else: 289 | plt.plot(self.x, self.y) 290 | plt.xlabel(r"$x$") 291 | plt.ylabel(r"$y$") 292 | if aspect_equal: 293 | plt.gca().set_aspect("equal") 294 | if save_fig_path is not None: 295 | plt.savefig(save_fig_path) 296 | plt.close() 297 | return fig 298 | 299 | def draw_chart( 300 | self, 301 | x_index: str, 302 | y_index: str, 303 | xlabel: str = None, 304 | ylabel: str = None, 305 | num: int or str = None, 306 | figsize: List[float] = [6.4, 4.8], 307 | dpi: float = 100.0, 308 | facecolor: str = None, 309 | edgecolor: str = None, 310 | frameon: bool = True, 311 | FigureClass: matplotlib.figure.Figure = matplotlib.figure.Figure, 312 | clear: bool = False, 313 | layout: str = None, 314 | fmt: str = None, 315 | save_fig_path: str = None, 316 | **kwargs 317 | ) -> plt.Figure: 318 | """Draw chart. 319 | 320 | Args: 321 | x_index (string): 322 | Index value of X axis. 323 | y_index (string): 324 | Index value of Y axis. 325 | xlabel (string, optional): 326 | Label of X axis. 327 | Defaults to None. 328 | ylabel (string, optional): 329 | Label of Y axis. 330 | Defaults to None. 331 | num (int or str, optional): 332 | A unique identifier for the figure. 333 | If a figure with that identifier already exists, this figure is made active and returned. 334 | An integer refers to the Figure.number attribute, a string refers to the figure label. 335 | If there is no figure with the identifier or num is not given, 336 | a new figure is created, made active and returned. 337 | If num is an int, it will be used for the Figure.number attribute. 338 | Otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). 339 | If num is a string, the figure label and the window title is set to this value. 340 | Default to None. 341 | figsize ((float, float), optional): 342 | Width, height in inches. 343 | Default to [6.4, 4.8] 344 | dpi (float, optional): 345 | The resolution of the figure in dots-per-inch. 346 | Default to 100.0. 347 | facecolor (str, optional): 348 | The background color. 349 | edgecolor (str, optional): 350 | The border color. 351 | frameon (bool, optional): 352 | If False, suppress drawing the figure frame. 353 | Defaults to True. 354 | FigureClass (subclass of matplotlib.figure.Figure, optional): 355 | Optionally use a custom Figure instance. 356 | Defaults to matplotlib.figure.Figure. 357 | clear (bool, optional): 358 | If True and the figure already exists, then it is cleared. 359 | Defaults to False. 360 | layout (str, optional): 361 | If 'tight', adjust subplot parameters using tight_layout with default padding. 362 | If 'constrained', use constrained layout to adjust positioning of plot elements. 363 | Defaults to None. 364 | fmt (str, optional): 365 | A format string, e.g. 'ro' for red circles. 366 | See the Notes section for a full description of the format strings. 367 | Format strings are just an abbreviation for quickly setting basic line properties. 368 | All of these and more can also be controlled by keyword arguments. 369 | This argument cannot be passed as keyword. 370 | Defaults to None. 371 | save_fig_path (str, optional): 372 | Path of saving figure. 373 | Defaults to None. 374 | **kwargs (matplotlib.lines.Line2D properties, optional): 375 | kwargs are used to specify properties 376 | like a line label (for auto legends), linewidth, antialiasing, marker face color. 377 | You can show the detailed information at `matplotlib.lines.Line2D 378 | `_ 379 | Returns: 380 | matplotlib.pyplot.Figure: Figure 381 | 382 | Examples: 383 | >>> ship.draw_chart("time", "r", xlabel="time [sec]", \ 384 | >>> ylabel=r"$u$" + " [rad/s]",save_fig_path='test.png') 385 | """ 386 | target_x = None 387 | if x_index == "time": 388 | target_x = self.time 389 | elif x_index == "u": 390 | target_x = self.u 391 | elif x_index == "v": 392 | target_x = self.v 393 | elif x_index == "r": 394 | target_x = self.r 395 | elif x_index == "x": 396 | target_x = self.x 397 | elif x_index == "y": 398 | target_x = self.y 399 | elif x_index == "psi": 400 | target_x = self.psi 401 | elif x_index == "delta": 402 | target_x = self.δ 403 | elif x_index == "δ": 404 | target_x = self.δ 405 | elif x_index == "nps": 406 | target_x = self.nps 407 | if target_x is None: 408 | raise Exception( 409 | "`x_index` is not good. Please set `x_index` from [" 410 | "time" 411 | ", " 412 | " u" 413 | ", " 414 | " v" 415 | ", " 416 | " r" 417 | ", " 418 | " x" 419 | ", " 420 | " y" 421 | ", " 422 | " psi" 423 | ", " 424 | " delta" 425 | ", " 426 | " δ" 427 | ", " 428 | " nps" 429 | "]" 430 | ) 431 | 432 | target_y = None 433 | if y_index == "time": 434 | target_y = self.time 435 | elif y_index == "u": 436 | target_y = self.u 437 | elif y_index == "v": 438 | target_y = self.v 439 | elif y_index == "r": 440 | target_y = self.r 441 | elif y_index == "x": 442 | target_y = self.x 443 | elif y_index == "y": 444 | target_y = self.y 445 | elif y_index == "psi": 446 | target_y = self.psi 447 | elif y_index == "delta": 448 | target_y = self.δ 449 | elif y_index == "δ": 450 | target_y = self.δ 451 | elif y_index == "nps": 452 | target_y = self.nps 453 | if target_y is None: 454 | raise Exception( 455 | "`y_index` is not good. Please set `y_index` from [" 456 | "time" 457 | ", " 458 | " u" 459 | ", " 460 | " v" 461 | ", " 462 | " r" 463 | ", " 464 | " x" 465 | ", " 466 | " y" 467 | ", " 468 | " psi" 469 | ", " 470 | " delta" 471 | ", " 472 | " δ" 473 | ", " 474 | " nps" 475 | "]" 476 | "]" 477 | ) 478 | fig = plt.figure( 479 | num=num, 480 | figsize=figsize, 481 | dpi=dpi, 482 | facecolor=facecolor, 483 | edgecolor=edgecolor, 484 | frameon=frameon, 485 | FigureClass=FigureClass, 486 | clear=clear, 487 | layout=layout, 488 | ) 489 | if xlabel is not None: 490 | plt.xlabel(xlabel) 491 | if ylabel is not None: 492 | plt.ylabel(ylabel) 493 | if fmt is None: 494 | plt.plot(target_x, target_y, **kwargs) 495 | else: 496 | plt.plot(target_x, target_y, fmt, **kwargs) 497 | if save_fig_path is not None: 498 | plt.savefig(save_fig_path) 499 | plt.close() 500 | 501 | return fig 502 | 503 | def draw_multi_x_chart( 504 | self, 505 | x_index_list: List[str], 506 | y_index: str, 507 | xlabel: str = None, 508 | ylabel: str = None, 509 | num: int or str = None, 510 | figsize: List[float] = [6.4, 4.8], 511 | dpi: float = 100.0, 512 | facecolor: str = None, 513 | edgecolor: str = None, 514 | frameon: bool = True, 515 | FigureClass: matplotlib.figure.Figure = matplotlib.figure.Figure, 516 | clear: bool = False, 517 | layout: str = None, 518 | fmt: str = None, 519 | save_fig_path: str = None, 520 | **kwargs 521 | ) -> plt.Figure: 522 | """Draw chart of multiple Y variables. 523 | 524 | Args: 525 | x_index_list (List[string]): 526 | List of index value of X axis. 527 | y_index (string): 528 | Index value of Y axis. 529 | xlabel (string, optional): 530 | Label of X axis. 531 | Defaults to None. 532 | ylabel (string, optional): 533 | Label of Y axis. 534 | Defaults to None. 535 | num (int or str, optional): 536 | A unique identifier for the figure. 537 | If a figure with that identifier already exists, this figure is made active and returned. 538 | An integer refers to the Figure.number attribute, a string refers to the figure label. 539 | If there is no figure with the identifier or num is not given, 540 | a new figure is created, made active and returned. 541 | If num is an int, it will be used for the Figure.number attribute. 542 | Otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). 543 | If num is a string, the figure label and the window title is set to this value. 544 | Default to None. 545 | figsize ((float, float), optional): 546 | Width, height in inches. 547 | Default to [6.4, 4.8] 548 | dpi (float, optional): 549 | The resolution of the figure in dots-per-inch. 550 | Default to 100.0. 551 | facecolor (str, optional): 552 | The background color. 553 | edgecolor (str, optional): 554 | The border color. 555 | frameon (bool, optional): 556 | If False, suppress drawing the figure frame. 557 | Defaults to True. 558 | FigureClass (subclass of matplotlib.figure.Figure, optional): 559 | Optionally use a custom Figure instance. 560 | Defaults to matplotlib.figure.Figure. 561 | clear (bool, optional): 562 | If True and the figure already exists, then it is cleared. 563 | Defaults to False. 564 | layout (str, optional): 565 | If 'tight', adjust subplot parameters using tight_layout with default padding. 566 | If 'constrained', use constrained layout to adjust positioning of plot elements. 567 | Defaults to None. 568 | fmt (str, optional): 569 | A format string, e.g. 'ro' for red circles. 570 | See the Notes section for a full description of the format strings. 571 | Format strings are just an abbreviation for quickly setting basic line properties. 572 | All of these and more can also be controlled by keyword arguments. 573 | This argument cannot be passed as keyword. 574 | Defaults to None. 575 | save_fig_path (str, optional): 576 | Path of saving figure. 577 | Defaults to None. 578 | **kwargs (matplotlib.lines.Line2D properties, optional): 579 | kwargs are used to specify properties 580 | like a line label (for auto legends), linewidth, antialiasing, marker face color. 581 | You can show the detailed information at `matplotlib.lines.Line2D 582 | `_ 583 | Returns: 584 | matplotlib.pyplot.Figure: Figure 585 | 586 | Examples: 587 | >>> ship.draw_chart("time", "r", xlabel="time [sec]", \ 588 | >>> ylabel=r"$u$" + " [rad/s]",save_fig_path='test.png') 589 | """ 590 | target_y = None 591 | if y_index == "time": 592 | target_y = self.time 593 | elif y_index == "u": 594 | target_y = self.u 595 | elif y_index == "v": 596 | target_y = self.v 597 | elif y_index == "r": 598 | target_y = self.r 599 | elif y_index == "x": 600 | target_y = self.x 601 | elif y_index == "y": 602 | target_y = self.y 603 | elif y_index == "psi": 604 | target_y = self.psi 605 | elif y_index == "delta": 606 | target_y = self.δ 607 | elif y_index == "δ": 608 | target_y = self.δ 609 | elif y_index == "nps": 610 | target_y = self.nps 611 | if target_y is None: 612 | raise Exception( 613 | "`x_index` is not good. Please set `x_index` from [" 614 | "time" 615 | ", " 616 | " u" 617 | ", " 618 | " v" 619 | ", " 620 | " r" 621 | ", " 622 | " x" 623 | ", " 624 | " y" 625 | ", " 626 | " psi" 627 | ", " 628 | " delta" 629 | ", " 630 | " δ" 631 | ", " 632 | " nps" 633 | "]" 634 | ) 635 | 636 | target_x_list = [] 637 | for x_index in x_index_list: 638 | if x_index == "time": 639 | target_x_list.append(self.time) 640 | elif x_index == "u": 641 | target_x_list.append(self.u) 642 | elif x_index == "v": 643 | target_x_list.append(self.v) 644 | elif x_index == "r": 645 | target_x_list.append(self.r) 646 | elif x_index == "x": 647 | target_x_list.append(self.x) 648 | elif x_index == "y": 649 | target_x_list.append(self.y) 650 | elif x_index == "psi": 651 | target_x_list.append(self.psi) 652 | elif x_index == "delta": 653 | target_x_list.append(self.δ) 654 | elif x_index == "δ": 655 | target_x_list.append(self.δ) 656 | elif x_index == "nps": 657 | target_x_list.append(self.nps) 658 | if len(target_x_list) == 0: 659 | raise Exception( 660 | "`y_index` is not good. Please set `y_index` from [" 661 | "time" 662 | ", " 663 | " u" 664 | ", " 665 | " v" 666 | ", " 667 | " r" 668 | ", " 669 | " x" 670 | ", " 671 | " y" 672 | ", " 673 | " psi" 674 | ", " 675 | " delta" 676 | ", " 677 | " δ" 678 | ", " 679 | " nps" 680 | "]" 681 | "]" 682 | ) 683 | fig = plt.figure( 684 | num=num, 685 | figsize=figsize, 686 | dpi=dpi, 687 | facecolor=facecolor, 688 | edgecolor=edgecolor, 689 | frameon=frameon, 690 | FigureClass=FigureClass, 691 | clear=clear, 692 | layout=layout, 693 | ) 694 | if xlabel is not None: 695 | plt.xlabel(xlabel) 696 | if ylabel is not None: 697 | plt.ylabel(ylabel) 698 | if fmt is None: 699 | for target_x in target_x_list: 700 | plt.plot(target_x, target_y, **kwargs) 701 | else: 702 | for target_x in target_x_list: 703 | plt.plot(target_x, target_y, fmt, **kwargs) 704 | if save_fig_path is not None: 705 | plt.savefig(save_fig_path) 706 | plt.close() 707 | 708 | return fig 709 | 710 | def draw_multi_y_chart( 711 | self, 712 | x_index: str, 713 | y_index_list: List[str], 714 | xlabel: str = None, 715 | ylabel: str = None, 716 | num: int or str = None, 717 | figsize: List[float] = [6.4, 4.8], 718 | dpi: float = 100.0, 719 | facecolor: str = None, 720 | edgecolor: str = None, 721 | frameon: bool = True, 722 | FigureClass: matplotlib.figure.Figure = matplotlib.figure.Figure, 723 | clear: bool = False, 724 | layout: str = None, 725 | fmt: str = None, 726 | save_fig_path: str = None, 727 | **kwargs 728 | ) -> plt.Figure: 729 | """Draw chart of multiple Y variables. 730 | 731 | Args: 732 | x_index (string): 733 | Index value of X axis. 734 | y_index_list (List[string]): 735 | List of index value of Y axis. 736 | xlabel (string, optional): 737 | Label of X axis. 738 | Defaults to None. 739 | ylabel (string, optional): 740 | Label of Y axis. 741 | Defaults to None. 742 | num (int or str, optional): 743 | A unique identifier for the figure. 744 | If a figure with that identifier already exists, this figure is made active and returned. 745 | An integer refers to the Figure.number attribute, a string refers to the figure label. 746 | If there is no figure with the identifier or num is not given, 747 | a new figure is created, made active and returned. 748 | If num is an int, it will be used for the Figure.number attribute. 749 | Otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). 750 | If num is a string, the figure label and the window title is set to this value. 751 | Default to None. 752 | figsize ((float, float), optional): 753 | Width, height in inches. 754 | Default to [6.4, 4.8] 755 | dpi (float, optional): 756 | The resolution of the figure in dots-per-inch. 757 | Default to 100.0. 758 | facecolor (str, optional): 759 | The background color. 760 | edgecolor (str, optional): 761 | The border color. 762 | frameon (bool, optional): 763 | If False, suppress drawing the figure frame. 764 | Defaults to True. 765 | FigureClass (subclass of matplotlib.figure.Figure, optional): 766 | Optionally use a custom Figure instance. 767 | Defaults to matplotlib.figure.Figure. 768 | clear (bool, optional): 769 | If True and the figure already exists, then it is cleared. 770 | Defaults to False. 771 | layout (str, optional): 772 | If 'tight', adjust subplot parameters using tight_layout with default padding. 773 | If 'constrained', use constrained layout to adjust positioning of plot elements. 774 | Defaults to None. 775 | fmt (str, optional): 776 | A format string, e.g. 'ro' for red circles. 777 | See the Notes section for a full description of the format strings. 778 | Format strings are just an abbreviation for quickly setting basic line properties. 779 | All of these and more can also be controlled by keyword arguments. 780 | This argument cannot be passed as keyword. 781 | Defaults to None. 782 | save_fig_path (str, optional): 783 | Path of saving figure. 784 | Defaults to None. 785 | **kwargs (matplotlib.lines.Line2D properties, optional): 786 | kwargs are used to specify properties 787 | like a line label (for auto legends), linewidth, antialiasing, marker face color. 788 | You can show the detailed information at `matplotlib.lines.Line2D 789 | `_ 790 | Returns: 791 | matplotlib.pyplot.Figure: Figure 792 | 793 | Examples: 794 | >>> ship.draw_chart("time", "r", xlabel="time [sec]", \ 795 | >>> ylabel=r"$u$" + " [rad/s]",save_fig_path='test.png') 796 | """ 797 | target_x = None 798 | if x_index == "time": 799 | target_x = self.time 800 | elif x_index == "u": 801 | target_x = self.u 802 | elif x_index == "v": 803 | target_x = self.v 804 | elif x_index == "r": 805 | target_x = self.r 806 | elif x_index == "x": 807 | target_x = self.x 808 | elif x_index == "y": 809 | target_x = self.y 810 | elif x_index == "psi": 811 | target_x = self.psi 812 | elif x_index == "delta": 813 | target_x = self.δ 814 | elif x_index == "δ": 815 | target_x = self.δ 816 | elif x_index == "nps": 817 | target_x = self.nps 818 | if target_x is None: 819 | raise Exception( 820 | "`x_index` is not good. Please set `x_index` from [" 821 | "time" 822 | ", " 823 | " u" 824 | ", " 825 | " v" 826 | ", " 827 | " r" 828 | ", " 829 | " x" 830 | ", " 831 | " y" 832 | ", " 833 | " psi" 834 | ", " 835 | " delta" 836 | ", " 837 | " δ" 838 | ", " 839 | " nps" 840 | "]" 841 | ) 842 | 843 | target_y_list = [] 844 | for y_index in y_index_list: 845 | if y_index == "time": 846 | target_y_list.append(self.time) 847 | elif y_index == "u": 848 | target_y_list.append(self.u) 849 | elif y_index == "v": 850 | target_y_list.append(self.v) 851 | elif y_index == "r": 852 | target_y_list.append(self.r) 853 | elif y_index == "x": 854 | target_y_list.append(self.x) 855 | elif y_index == "y": 856 | target_y_list.append(self.y) 857 | elif y_index == "psi": 858 | target_y_list.append(self.psi) 859 | elif y_index == "delta": 860 | target_y_list.append(self.δ) 861 | elif y_index == "δ": 862 | target_y_list.append(self.δ) 863 | elif y_index == "nps": 864 | target_y_list.append(self.nps) 865 | if len(target_y_list) == 0: 866 | raise Exception( 867 | "`y_index` is not good. Please set `y_index` from [" 868 | "time" 869 | ", " 870 | " u" 871 | ", " 872 | " v" 873 | ", " 874 | " r" 875 | ", " 876 | " x" 877 | ", " 878 | " y" 879 | ", " 880 | " psi" 881 | ", " 882 | " delta" 883 | ", " 884 | " δ" 885 | ", " 886 | " nps" 887 | "]" 888 | "]" 889 | ) 890 | fig = plt.figure( 891 | num=num, 892 | figsize=figsize, 893 | dpi=dpi, 894 | facecolor=facecolor, 895 | edgecolor=edgecolor, 896 | frameon=frameon, 897 | FigureClass=FigureClass, 898 | clear=clear, 899 | layout=layout, 900 | ) 901 | if xlabel is not None: 902 | plt.xlabel(xlabel) 903 | if ylabel is not None: 904 | plt.ylabel(ylabel) 905 | if fmt is None: 906 | for target_y in target_y_list: 907 | plt.plot(target_x, target_y, **kwargs) 908 | else: 909 | for target_y in target_y_list: 910 | plt.plot(target_x, target_y, fmt, **kwargs) 911 | if save_fig_path is not None: 912 | plt.savefig(save_fig_path) 913 | plt.close() 914 | 915 | return fig 916 | 917 | def draw_gif( 918 | self, 919 | dimensionless: bool = False, 920 | aspect_equal: bool = True, 921 | frate: int = 10, 922 | interval: int = 100, 923 | num: int or str = None, 924 | figsize: List[float] = [6.4, 4.8], 925 | dpi: float = 100.0, 926 | facecolor: str = None, 927 | edgecolor: str = None, 928 | frameon: bool = True, 929 | FigureClass: matplotlib.figure.Figure = matplotlib.figure.Figure, 930 | clear: bool = False, 931 | layout: str = None, 932 | fmt: str = "--k", 933 | save_fig_path: str = None, 934 | **kwargs 935 | ) -> plt.Figure: 936 | """Draw GIF of ship trajectory. 937 | 938 | Args: 939 | dimensionless (bool, optional): 940 | drawing with dimensionless by using L or not. 941 | Defaults to False 942 | aspect_equal (bool, optional): 943 | Set equal of figure aspect or not. 944 | Defaults to True. 945 | frate (int, optional): 946 | One of the parameter of `frames` in matplotlib.FuncAnimation(). 947 | `frames` expresses source of data to pass func and each frame of the animation. 948 | `frames = int (len(time) / frate)` 949 | Defaults to 10. 950 | interval (int, optional): 951 | Delay between frames in milliseconds. 952 | Defaults to 100. 953 | num (int or str, optional): 954 | A unique identifier for the figure. 955 | If a figure with that identifier already exists, this figure is made active and returned. 956 | An integer refers to the Figure.number attribute, a string refers to the figure label. 957 | If there is no figure with the identifier or num is not given, 958 | a new figure is created, made active and returned. 959 | If num is an int, it will be used for the Figure.number attribute. 960 | Otherwise, an auto-generated integer value is used (starting at 1 and incremented for each new figure). 961 | If num is a string, the figure label and the window title is set to this value. 962 | Default to None. 963 | figsize ((float, float), optional): 964 | Width, height in inches. 965 | Default to [6.4, 4.8] 966 | dpi (float, optional): 967 | The resolution of the figure in dots-per-inch. 968 | Default to 100.0. 969 | facecolor (str, optional): 970 | The background color. 971 | edgecolor (str, optional): 972 | The border color. 973 | frameon (bool, optional): 974 | If False, suppress drawing the figure frame. 975 | Defaults to True. 976 | FigureClass (subclass of matplotlib.figure.Figure, optional): 977 | Optionally use a custom Figure instance. 978 | Defaults to matplotlib.figure.Figure. 979 | clear (bool, optional): 980 | If True and the figure already exists, then it is cleared. 981 | Defaults to False. 982 | layout (str, optional): 983 | If 'tight', adjust subplot parameters using tight_layout with default padding. 984 | If 'constrained', use constrained layout to adjust positioning of plot elements. 985 | Defaults to None. 986 | fmt (str, optional): 987 | A format string, e.g. 'ro' for red circles. 988 | See the Notes section for a full description of the format strings. 989 | Format strings are just an abbreviation for quickly setting basic line properties. 990 | All of these and more can also be controlled by keyword arguments. 991 | This argument cannot be passed as keyword. 992 | Defaults to "--k". 993 | save_fig_path (str, optional): 994 | Path of saving figure. 995 | Defaults to None. 996 | **kwargs (matplotlib.lines.Line2D properties, optional): 997 | kwargs are used to specify properties 998 | like a line label (for auto legends), linewidth, antialiasing, marker face color. 999 | You can show the detailed information at `matplotlib.lines.Line2D 1000 | `_ 1001 | 1002 | Examples: 1003 | >>> ship.draw_gif(save_fig_path='test.gif') 1004 | """ 1005 | fig = plt.figure( 1006 | num=num, 1007 | figsize=figsize, 1008 | dpi=dpi, 1009 | facecolor=facecolor, 1010 | edgecolor=edgecolor, 1011 | frameon=frameon, 1012 | FigureClass=FigureClass, 1013 | clear=clear, 1014 | layout=layout, 1015 | ) 1016 | ax = fig.add_subplot(111) 1017 | if dimensionless: 1018 | draw_x = np.array(self.x) / self.L 1019 | draw_y = np.array(self.y) / self.L 1020 | ax.set_xlabel(r"$x/L$") 1021 | ax.set_ylabel(r"$y/L$") 1022 | shape = (1 / 2, self.B / (2 * self.L)) 1023 | else: 1024 | draw_x = np.array(self.x) 1025 | draw_y = np.array(self.y) 1026 | ax.set_xlabel(r"$x$") 1027 | ax.set_ylabel(r"$y$") 1028 | shape = (self.L / 2, self.B / 2) 1029 | 1030 | if fmt is not None: 1031 | plt.plot(draw_x, draw_y, fmt, **kwargs) 1032 | else: 1033 | plt.plot(draw_x, draw_y, ls="--", color="k", **kwargs) 1034 | 1035 | if aspect_equal: 1036 | ax.set_aspect("equal") 1037 | 1038 | drawer = DrawObj(ax) 1039 | 1040 | def update_obj(i, x_list, y_list, shape_list, ψ_list, frate): 1041 | j = int(frate * i) 1042 | plt.title(r"$t$ = " + "{:.1f}".format(self.time[j])) 1043 | 1044 | xT = np.array(x_list).T 1045 | _x_list_j = list(xT[j].T) 1046 | yT = np.array(y_list).T 1047 | _y_list_j = list(yT[j].T) 1048 | ψT = np.array(ψ_list).T 1049 | _ψ_list_j = list(ψT[j].T) 1050 | 1051 | return drawer.draw_obj_with_angle( 1052 | _x_list_j, _y_list_j, shape_list, _ψ_list_j 1053 | ) 1054 | 1055 | ani = FuncAnimation( 1056 | fig, 1057 | update_obj, 1058 | fargs=( 1059 | [draw_x], 1060 | [draw_y], 1061 | [shape], 1062 | [self.psi], 1063 | frate, 1064 | ), 1065 | interval=interval, 1066 | frames=int(len(self.time) / frate), 1067 | ) 1068 | gif = ani.save(save_fig_path, writer="pillow") 1069 | plt.close() 1070 | return gif 1071 | -------------------------------------------------------------------------------- /shipmmg/mmg_3dof.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """mmg_3dof. 4 | 5 | * MMG (3DOF) simulation code 6 | 7 | .. math:: 8 | 9 | m (\\dot{u}-vr)&=-m_x\\dot{u}+m_yvr+X_H+X_P+X_R 10 | 11 | m (\\dot{v}+ur)&=-m_y\\dot{v}+m_xur+Y_H+Y_R 12 | 13 | I_{zG}\\dot{r}&=-J_Z\\dot{r}+N_H+N_R 14 | 15 | """ 16 | 17 | import dataclasses 18 | from typing import List 19 | 20 | import numpy as np 21 | 22 | from scipy.integrate import solve_ivp 23 | from scipy.interpolate import CubicSpline 24 | 25 | 26 | @dataclasses.dataclass 27 | class Mmg3DofBasicParams: 28 | """Dataclass for setting basic parameters of MMG 3DOF. 29 | 30 | Attributes: 31 | L_pp (float): 32 | Ship length between perpendiculars [m] 33 | B (float): 34 | Ship breadth [m] 35 | d (float): 36 | Ship draft [m] 37 | x_G (float): 38 | Longitudinal coordinate of center of gravity of ship [m] 39 | D_p (float): 40 | Propeller diameter [m] 41 | m (float): 42 | Ship mass [kg] 43 | I_zG (float): 44 | Moment of inertia of ship around center of gravity 45 | A_R (float): 46 | Profile area of movable part of mariner rudder [m^2] 47 | η (float): 48 | Ratio of propeller diameter to rudder span (=D_p/HR) 49 | m_x (float): 50 | Added masses of x axis direction [kg] 51 | m_y (float): 52 | Added masses of y axis direction [kg] 53 | J_z (float): 54 | Added moment of inertia 55 | f_α (float): 56 | Rudder lift gradient coefficient 57 | ϵ (float): 58 | Ratio of wake fraction at propeller and rudder positions 59 | t_R (float): 60 | Steering resistance deduction factor 61 | x_R (float): 62 | Longitudinal coordinate of rudder position. 63 | a_H (float): 64 | Rudder force increase factor 65 | x_H (float): 66 | Longitudinal coordinate of acting point of the additional lateral force component induced by steering 67 | γ_R_minus (float): 68 | Flow straightening coefficient if βR < 0 69 | γ_R_plus (float): 70 | Flow straightening coefficient if βR > 0 71 | l_R (float): 72 | Effective longitudinal coordinate of rudder position in formula of βR 73 | κ (float): 74 | An experimental constant for expressing uR 75 | t_P (float): 76 | Thrust deduction factor 77 | w_P0 (float): 78 | Wake coefficient at propeller position in straight moving 79 | x_P (float): 80 | Effective Longitudinal coordinate of propeller position in formula of βP 81 | 82 | Note: 83 | For more information, please see the following articles. 84 | 85 | - Yasukawa, H., Yoshimura, Y. (2015) Introduction of MMG standard method for ship maneuvering predictions. 86 | J Mar Sci Technol 20, 37-52 https://doi.org/10.1007/s00773-014-0293-y 87 | """ 88 | 89 | L_pp: float 90 | B: float 91 | d: float 92 | x_G: float 93 | D_p: float 94 | m: float 95 | I_zG: float 96 | A_R: float 97 | η: float 98 | m_x: float 99 | m_y: float 100 | J_z: float 101 | f_α: float 102 | ϵ: float 103 | t_R: float 104 | x_R: float 105 | a_H: float 106 | x_H: float 107 | γ_R_minus: float 108 | γ_R_plus: float 109 | l_R: float 110 | κ: float 111 | t_P: float 112 | w_P0: float 113 | x_P: float 114 | 115 | 116 | @dataclasses.dataclass 117 | class Mmg3DofManeuveringParams: 118 | """Dataclass for setting maneuvering parameters of MMG 3ODF. 119 | 120 | Attributes: 121 | k_0 (float): One of manuevering parameters of coefficients representing K_T 122 | k_1 (float): One of manuevering parameters of coefficients representing K_T 123 | k_2 (float): One of manuevering parameters of coefficients representing K_T 124 | R_0_dash (float): One of manuevering parameters of Ship resistance coefficient in straight moving 125 | X_vv_dash (float): One of manuevering parameters of MMG 3DOF 126 | X_vr_dash (float): One of manuevering parameters of MMG 3DOF 127 | X_rr_dash (float): One of manuevering parameters of MMG 3DOF 128 | X_vvvv_dash (float): One of manuevering parameters of MMG 3DOF 129 | Y_v_dash (float): One of manuevering parameters of MMG 3DOF 130 | Y_r_dash (float): One of manuevering parameters of MMG 3DOF 131 | Y_vvv_dash (float): One of manuevering parameters of MMG 3DOF 132 | Y_vvr_dash (float): One of manuevering parameters of MMG 3DOF 133 | Y_vrr_dash (float): One of manuevering parameters of MMG 3DOF 134 | Y_rrr_dash (float): One of manuevering parameters of MMG 3DOF 135 | N_v_dash (float): One of manuevering parameters of MMG 3DOF 136 | N_r_dash (float): One of manuevering parameters of MMG 3DOF 137 | N_vvv_dash (float): One of manuevering parameters of MMG 3DOF 138 | N_vvr_dash (float): One of manuevering parameters of MMG 3DOF 139 | N_vrr_dash (float): One of manuevering parameters of MMG 3DOF 140 | N_rrr_dash (float): One of manuevering parameters of MMG 3DOF 141 | 142 | Note: 143 | For more information, please see the following articles. 144 | 145 | - Yasukawa, H., Yoshimura, Y. (2015) Introduction of MMG standard method for ship maneuvering predictions. 146 | J Mar Sci Technol 20, 37-52 https://doi.org/10.1007/s00773-014-0293-y 147 | """ 148 | 149 | k_0: float 150 | k_1: float 151 | k_2: float 152 | R_0_dash: float 153 | X_vv_dash: float 154 | X_vr_dash: float 155 | X_rr_dash: float 156 | X_vvvv_dash: float 157 | Y_v_dash: float 158 | Y_r_dash: float 159 | Y_vvv_dash: float 160 | Y_vvr_dash: float 161 | Y_vrr_dash: float 162 | Y_rrr_dash: float 163 | N_v_dash: float 164 | N_r_dash: float 165 | N_vvv_dash: float 166 | N_vvr_dash: float 167 | N_vrr_dash: float 168 | N_rrr_dash: float 169 | 170 | 171 | def simulate_mmg_3dof( 172 | basic_params: Mmg3DofBasicParams, 173 | maneuvering_params: Mmg3DofManeuveringParams, 174 | time_list: List[float], 175 | δ_list: List[float], 176 | nps_list: List[float], 177 | u0: float = 0.0, 178 | v0: float = 0.0, 179 | r0: float = 0.0, 180 | x0: float = 0.0, 181 | y0: float = 0.0, 182 | ψ0: float = 0.0, 183 | ρ: float = 1025.0, 184 | method: str = "RK45", 185 | t_eval=None, 186 | events=None, 187 | vectorized=False, 188 | **options 189 | ): 190 | """MMG 3DOF simulation. 191 | 192 | MMG 3DOF simulation by follwoing equation of motion. 193 | 194 | .. math:: 195 | 196 | m (\\dot{u}-vr)&=-m_x\\dot{u}+m_yvr+X_H+X_P+X_R 197 | 198 | m (\\dot{v}+ur)&=-m_y\\dot{v}+m_xur+Y_H+Y_R 199 | 200 | I_{zG}\\dot{r}&=-J_Z\\dot{r}+N_H+N_R 201 | 202 | Args: 203 | basic_params (Mmg3DofBasicParams): 204 | Basic parameters for MMG 3DOF simulation. 205 | maneuvering_params (Mmg3DofManeuveringParams): 206 | Maneuvering parameters for MMG 3DOF simulation. 207 | time_list (list[float]): 208 | time list of simulation. 209 | δ_list (list[float]): 210 | rudder angle list of simulation. 211 | nps_list (List[float]): 212 | nps list of simulation. 213 | u0 (float, optional): 214 | axial velocity [m/s] in initial condition (`time_list[0]`). 215 | Defaults to 0.0. 216 | v0 (float, optional): 217 | lateral velocity [m/s] in initial condition (`time_list[0]`). 218 | Defaults to 0.0. 219 | r0 (float, optional): 220 | rate of turn [rad/s] in initial condition (`time_list[0]`). 221 | Defaults to 0.0. 222 | x0 (float, optional): 223 | x (surge) position [m] in initial condition (`time_list[0]`). 224 | Defaults to 0.0. 225 | y0 (float, optional): 226 | y (sway) position [m] in initial condition (`time_list[0]`). 227 | Defaults to 0.0. 228 | ψ0 (float, optional): 229 | ψ (yaw) azimuth [rad] in initial condition (`time_list[0]`). 230 | Defaults to 0.0. 231 | ρ (float, optional): 232 | seawater density [kg/m^3] 233 | Defaults to 1025.0. 234 | method (str, optional): 235 | Integration method to use in 236 | `scipy.integrate.solve_ivp() 237 | `_: 238 | 239 | "RK45" (default): 240 | Explicit Runge-Kutta method of order 5(4). 241 | The error is controlled assuming accuracy of the fourth-order method, 242 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 243 | A quartic interpolation polynomial is used for the dense output. 244 | Can be applied in the complex domain. 245 | "RK23": 246 | Explicit Runge-Kutta method of order 3(2). 247 | The error is controlled assuming accuracy of the second-order method, 248 | but steps are taken using the third-order accurate formula (local extrapolation is done). 249 | A cubic Hermite polynomial is used for the dense output. 250 | Can be applied in the complex domain. 251 | "DOP853": 252 | Explicit Runge-Kutta method of order 8. 253 | Python implementation of the “DOP853” algorithm originally written in Fortran. 254 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 255 | Can be applied in the complex domain. 256 | "Radau": 257 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 258 | The error is controlled with a third-order accurate embedded formula. 259 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 260 | "BDF": 261 | Implicit multi-step variable-order (1 to 5) method 262 | based on a backward differentiation formula for the derivative approximation. 263 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 264 | Can be applied in the complex domain. 265 | "LSODA": 266 | Adams/BDF method with automatic stiffness detection and switching. 267 | This is a wrapper of the Fortran solver from ODEPACK. 268 | 269 | t_eval (array_like or None, optional): 270 | Times at which to store the computed solution, must be sorted and lie within t_span. 271 | If None (default), use points selected by the solver. 272 | events (callable, or list of callables, optional): 273 | Events to track. If None (default), no events will be tracked. 274 | Each event occurs at the zeros of a continuous function of time and state. 275 | Each function must have the signature event(t, y) and return a float. 276 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 277 | By default, all zeros will be found. The solver looks for a sign change over each step, 278 | so if multiple zero crossings occur within one step, events may be missed. 279 | Additionally each event function might have the following attributes: 280 | terminal (bool, optional): 281 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 282 | direction (float, optional): 283 | Direction of a zero crossing. 284 | If direction is positive, event will only trigger when going from negative to positive, 285 | and vice versa if direction is negative. 286 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 287 | You can assign attributes like `event.terminal = True` to any function in Python. 288 | vectorized (bool, optional): 289 | Whether `fun` is implemented in a vectorized fashion. Default is False. 290 | options: 291 | Options passed to a chosen solver. 292 | All options available for already implemented solvers are listed in 293 | `scipy.integrate.solve_ivp() 294 | `_: 295 | 296 | Returns: 297 | Bunch object with the following fields defined: 298 | t (ndarray, shape (`n_points`,)): 299 | Time points. 300 | y (ndarray, shape (`n_points`,)): 301 | Values of the solution at t. 302 | sol (OdeSolution): 303 | Found solution as OdeSolution instance from MMG 3DOF simulation. 304 | t_events (list of ndarray or None): 305 | Contains for each event type a list of arrays at which an event of that type event was detected. 306 | None if events was None. 307 | y_events (list of ndarray or None): 308 | For each value of t_events, the corresponding value of the solution. 309 | None if events was None. 310 | nfev (int): 311 | Number of evaluations of the right-hand side. 312 | njev (int): 313 | Number of evaluations of the jacobian. 314 | nlu (int): 315 | Number of LU decomposition. 316 | status (int): 317 | Reason for algorithm termination: 318 | - -1: Integration step failed. 319 | - 0: The solver successfully reached the end of `tspan`. 320 | - 1: A termination event occurred. 321 | message (string): 322 | Human-readable description of the termination reason. 323 | success (bool): 324 | True if the solver reached the interval end or a termination event occurred (`status >= 0`). 325 | 326 | Examples: 327 | >>> duration = 200 # [s] 328 | >>> sampling = 2000 329 | >>> time_list = np.linspace(0.00, duration, sampling) 330 | >>> δ_list = np.full(len(time_list), 35.0 * np.pi / 180.0) 331 | >>> nps_list = np.full(len(time_list), 20.338) 332 | >>> basic_params = Mmg3DofBasicParams( 333 | >>> L_pp=7.00, 334 | >>> B=1.27, 335 | >>> d=0.46, 336 | >>> x_G=0.25, 337 | >>> D_p=0.216, 338 | >>> m=3.27*1.025, 339 | >>> I_zG=m*((0.25 * L_pp) ** 2), 340 | >>> A_R=0.0539, 341 | >>> η=D_p/0.345, 342 | >>> m_x=0.022*(0.5 * ρ * (L_pp ** 2) * d), 343 | >>> m_y=0.223*(0.5 * ρ * (L_pp ** 2) * d), 344 | >>> J_z=0.011*(0.5 * ρ * (L_pp ** 4) * d), 345 | >>> f_α=2.747, 346 | >>> ϵ=1.09, 347 | >>> t_R=0.387, 348 | >>> x_R=-0.500*L_pp, 349 | >>> a_H=0.312, 350 | >>> x_H=-0.464*L_pp, 351 | >>> γ_R_minus=0.395, 352 | >>> γ_R_plus=0.640, 353 | >>> l_R=-0.710, 354 | >>> κ=0.50, 355 | >>> t_P=0.220, 356 | >>> w_P0=0.40, 357 | >>> x_P=-0.650, 358 | >>> ) 359 | >>> maneuvering_params = Mmg3DofManeuveringParams( 360 | >>> k_0 = 0.2931, 361 | >>> k_1 = -0.2753, 362 | >>> k_2 = -0.1385, 363 | >>> R_0_dash = 0.022, 364 | >>> X_vv_dash = -0.040, 365 | >>> X_vr_dash = 0.002, 366 | >>> X_rr_dash = 0.011, 367 | >>> X_vvvv_dash = 0.771, 368 | >>> Y_v_dash = -0.315, 369 | >>> Y_r_dash = 0.083, 370 | >>> Y_vvv_dash = -1.607, 371 | >>> Y_vvr_dash = 0.379, 372 | >>> Y_vrr_dash = -0.391, 373 | >>> Y_rrr_dash = 0.008, 374 | >>> N_v_dash = -0.137, 375 | >>> N_r_dash = -0.049, 376 | >>> N_vvv_dash = -0.030, 377 | >>> N_vvr_dash = -0.294, 378 | >>> N_vrr_dash = 0.055, 379 | >>> N_rrr_dash = -0.013, 380 | >>> ) 381 | >>> sol = simulate_mmg_3dof( 382 | >>> basic_params, 383 | >>> maneuvering_params, 384 | >>> time_list, 385 | >>> δ_rad_list, 386 | >>> nps_list, 387 | >>> u0=2.29 * 0.512, 388 | >>> ) 389 | >>> result = sol.sol(time_list) 390 | 391 | 392 | Note: 393 | For more information, please see the following articles. 394 | 395 | - Yasukawa, H., Yoshimura, Y. (2015) Introduction of MMG standard method for ship maneuvering predictions. 396 | J Mar Sci Technol 20, 37-52 https://doi.org/10.1007/s00773-014-0293-y 397 | 398 | """ 399 | return simulate( 400 | L_pp=basic_params.L_pp, 401 | B=basic_params.B, 402 | d=basic_params.d, 403 | x_G=basic_params.x_G, 404 | D_p=basic_params.D_p, 405 | m=basic_params.m, 406 | I_zG=basic_params.I_zG, 407 | A_R=basic_params.A_R, 408 | η=basic_params.η, 409 | m_x=basic_params.m_x, 410 | m_y=basic_params.m_y, 411 | J_z=basic_params.J_z, 412 | f_α=basic_params.f_α, 413 | ϵ=basic_params.ϵ, 414 | t_R=basic_params.t_R, 415 | x_R=basic_params.x_R, 416 | a_H=basic_params.a_H, 417 | x_H=basic_params.x_H, 418 | γ_R_minus=basic_params.γ_R_minus, 419 | γ_R_plus=basic_params.γ_R_plus, 420 | l_R=basic_params.l_R, 421 | κ=basic_params.κ, 422 | t_P=basic_params.t_P, 423 | w_P0=basic_params.w_P0, 424 | x_P=basic_params.x_P, 425 | k_0=maneuvering_params.k_0, 426 | k_1=maneuvering_params.k_1, 427 | k_2=maneuvering_params.k_2, 428 | R_0_dash=maneuvering_params.R_0_dash, 429 | X_vv_dash=maneuvering_params.X_vv_dash, 430 | X_vr_dash=maneuvering_params.X_vr_dash, 431 | X_rr_dash=maneuvering_params.X_rr_dash, 432 | X_vvvv_dash=maneuvering_params.X_vvvv_dash, 433 | Y_v_dash=maneuvering_params.Y_v_dash, 434 | Y_r_dash=maneuvering_params.Y_r_dash, 435 | Y_vvv_dash=maneuvering_params.Y_vvv_dash, 436 | Y_vvr_dash=maneuvering_params.Y_vvr_dash, 437 | Y_vrr_dash=maneuvering_params.Y_vrr_dash, 438 | Y_rrr_dash=maneuvering_params.Y_rrr_dash, 439 | N_v_dash=maneuvering_params.N_v_dash, 440 | N_r_dash=maneuvering_params.N_r_dash, 441 | N_vvv_dash=maneuvering_params.N_vvv_dash, 442 | N_vvr_dash=maneuvering_params.N_vvr_dash, 443 | N_vrr_dash=maneuvering_params.N_vrr_dash, 444 | N_rrr_dash=maneuvering_params.N_rrr_dash, 445 | time_list=time_list, 446 | δ_list=δ_list, 447 | nps_list=nps_list, 448 | u0=u0, 449 | v0=v0, 450 | r0=r0, 451 | x0=x0, 452 | y0=y0, 453 | ψ0=ψ0, 454 | ρ=ρ, 455 | method=method, 456 | t_eval=t_eval, 457 | events=events, 458 | vectorized=vectorized, 459 | **options 460 | ) 461 | 462 | 463 | def simulate( 464 | L_pp: float, 465 | B: float, 466 | d: float, 467 | x_G: float, 468 | D_p: float, 469 | m: float, 470 | I_zG: float, 471 | A_R: float, 472 | η: float, 473 | m_x: float, 474 | m_y: float, 475 | J_z: float, 476 | f_α: float, 477 | ϵ: float, 478 | t_R: float, 479 | x_R: float, 480 | a_H: float, 481 | x_H: float, 482 | γ_R_minus: float, 483 | γ_R_plus: float, 484 | l_R: float, 485 | κ: float, 486 | t_P: float, 487 | w_P0: float, 488 | x_P: float, 489 | k_0: float, 490 | k_1: float, 491 | k_2: float, 492 | R_0_dash: float, 493 | X_vv_dash: float, 494 | X_vr_dash: float, 495 | X_rr_dash: float, 496 | X_vvvv_dash: float, 497 | Y_v_dash: float, 498 | Y_r_dash: float, 499 | Y_vvv_dash: float, 500 | Y_vvr_dash: float, 501 | Y_vrr_dash: float, 502 | Y_rrr_dash: float, 503 | N_v_dash: float, 504 | N_r_dash: float, 505 | N_vvv_dash: float, 506 | N_vvr_dash: float, 507 | N_vrr_dash: float, 508 | N_rrr_dash: float, 509 | time_list: List[float], 510 | δ_list: List[float], 511 | nps_list: List[float], 512 | u0: float = 0.0, 513 | v0: float = 0.0, 514 | r0: float = 0.0, 515 | x0: float = 0.0, 516 | y0: float = 0.0, 517 | ψ0: float = 0.0, 518 | ρ: float = 1025.0, 519 | method: str = "RK45", 520 | t_eval=None, 521 | events=None, 522 | vectorized=False, 523 | **options 524 | ): 525 | """MMG 3DOF simulation. 526 | 527 | MMG 3DOF simulation by follwoing equation of motion. 528 | 529 | .. math:: 530 | 531 | m (\\dot{u}-vr)&=-m_x\\dot{u}+m_yvr+X_H+X_P+X_R 532 | 533 | m (\\dot{v}+ur)&=-m_y\\dot{v}+m_xur+Y_H+Y_R 534 | 535 | I_{zG}\\dot{r}&=-J_Z\\dot{r}+N_H+N_R 536 | 537 | Args: 538 | L_pp (float): 539 | Ship length between perpendiculars [m] 540 | B (float): 541 | Ship breadth [m] 542 | d (float): 543 | Ship draft [m] 544 | x_G (float): 545 | Longitudinal coordinate of center of gravity of ship 546 | D_p (float): 547 | Propeller diameter [m] 548 | m (float): 549 | Ship mass [kg] 550 | I_zG (float): 551 | Moment of inertia of ship around center of gravity 552 | A_R (float): 553 | Profile area of movable part of mariner rudder [m^2] 554 | η (float): 555 | Ratio of propeller diameter to rudder span (=D_p/HR) 556 | m_x (float): 557 | Added masses of x axis direction [kg] 558 | m_y (float): 559 | Added masses of y axis direction [kg] 560 | J_z (float): 561 | Added moment of inertia 562 | f_α (float): 563 | Rudder lift gradient coefficient 564 | ϵ (float): 565 | Ratio of wake fraction at propeller and rudder positions 566 | t_R (float): 567 | Steering resistance deduction factor 568 | x_R (float): 569 | Longitudinal coordinate of rudder position 570 | a_H (float): 571 | Rudder force increase factor 572 | x_H (float): 573 | Longitudinal coordinate of acting point of the additional lateral force component induced by steering 574 | γ_R_minus (float): 575 | Flow straightening coefficient if βR < 0 576 | γ_R_plus (float): 577 | Flow straightening coefficient if βR > 0 578 | l_R (float): 579 | Effective longitudinal coordinate of rudder position in formula of βR 580 | κ (float): 581 | An experimental constant for expressing uR 582 | t_P (float): 583 | Thrust deduction factor 584 | w_P0 (float): 585 | Wake coefficient at propeller position in straight moving 586 | x_P (float): 587 | Effective Longitudinal coordinate of propeller position in formula of βP 588 | k_0 (float): 589 | One of manuevering parameters of coefficients representing K_T 590 | k_1 (float): 591 | One of manuevering parameters of coefficients representing K_T 592 | k_2 (float): 593 | One of manuevering parameters of coefficients representing K_T 594 | R_0_dash (float): 595 | One of manuevering parameters of MMG 3DOF 596 | X_vv_dash (float): 597 | One of manuevering parameters of MMG 3DOF 598 | X_vr_dash (float): 599 | One of manuevering parameters of MMG 3DOF 600 | X_rr_dash (float): 601 | One of manuevering parameters of MMG 3DOF 602 | X_vvvv_dash (float): 603 | One of manuevering parameters of MMG 3DOF 604 | Y_v_dash (float): 605 | One of manuevering parameters of MMG 3DOF 606 | Y_r_dash (float): 607 | One of manuevering parameters of MMG 3DOF 608 | Y_vvv_dash (float): 609 | One of manuevering parameters of MMG 3DOF 610 | Y_vvr_dash (float): 611 | One of manuevering parameters of MMG 3DOF 612 | Y_vrr_dash (float): 613 | One of manuevering parameters of MMG 3DOF 614 | Y_rrr_dash (float): 615 | One of manuevering parameters of MMG 3DOF 616 | N_v_dash (float): 617 | One of manuevering parameters of MMG 3DOF 618 | N_r_dash (float): 619 | One of manuevering parameters of MMG 3DOF 620 | N_vvv_dash (float): 621 | One of manuevering parameters of MMG 3DOF 622 | N_vvr_dash (float): 623 | One of manuevering parameters of MMG 3DOF 624 | N_vrr_dash (float): 625 | One of manuevering parameters of MMG 3DOF 626 | N_rrr_dash (float): 627 | One of manuevering parameters of MMG 3DOF 628 | time_list (list[float]): 629 | time list of simulation. 630 | δ_list (list[float]): 631 | rudder angle list of simulation. 632 | nps_list (List[float]): 633 | nps list of simulation. 634 | u0 (float, optional): 635 | axial velocity [m/s] in initial condition (`time_list[0]`). 636 | Defaults to 0.0. 637 | v0 (float, optional): 638 | lateral velocity [m/s] in initial condition (`time_list[0]`). 639 | Defaults to 0.0. 640 | r0 (float, optional): 641 | rate of turn [rad/s] in initial condition (`time_list[0]`). 642 | Defaults to 0.0. 643 | x0 (float, optional): 644 | x (surge) position [m] in initial condition (`time_list[0]`). 645 | Defaults to 0.0. 646 | y0 (float, optional): 647 | y (sway) position [m] in initial condition (`time_list[0]`). 648 | Defaults to 0.0. 649 | ψ0 (float, optional): 650 | ψ (yaw) azimuth [rad] in initial condition (`time_list[0]`). 651 | Defaults to 0.0. 652 | ρ (float, optional): 653 | seawater density [kg/m^3] 654 | Defaults to 1025.0. 655 | method (str, optional): 656 | Integration method to use in 657 | `scipy.integrate.solve_ivp() 658 | `_: 659 | 660 | "RK45" (default): 661 | Explicit Runge-Kutta method of order 5(4). 662 | The error is controlled assuming accuracy of the fourth-order method, 663 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 664 | A quartic interpolation polynomial is used for the dense output. 665 | Can be applied in the complex domain. 666 | "RK23": 667 | Explicit Runge-Kutta method of order 3(2). 668 | The error is controlled assuming accuracy of the second-order method, 669 | but steps are taken using the third-order accurate formula (local extrapolation is done). 670 | A cubic Hermite polynomial is used for the dense output. 671 | Can be applied in the complex domain. 672 | "DOP853": 673 | Explicit Runge-Kutta method of order 8. 674 | Python implementation of the “DOP853” algorithm originally written in Fortran. 675 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 676 | Can be applied in the complex domain. 677 | "Radau": 678 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 679 | The error is controlled with a third-order accurate embedded formula. 680 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 681 | "BDF": 682 | Implicit multi-step variable-order (1 to 5) method 683 | based on a backward differentiation formula for the derivative approximation. 684 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 685 | Can be applied in the complex domain. 686 | "LSODA": 687 | Adams/BDF method with automatic stiffness detection and switching. 688 | This is a wrapper of the Fortran solver from ODEPACK. 689 | 690 | t_eval (array_like or None, optional): 691 | Times at which to store the computed solution, must be sorted and lie within t_span. 692 | If None (default), use points selected by the solver. 693 | events (callable, or list of callables, optional): 694 | Events to track. If None (default), no events will be tracked. 695 | Each event occurs at the zeros of a continuous function of time and state. 696 | Each function must have the signature event(t, y) and return a float. 697 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 698 | By default, all zeros will be found. The solver looks for a sign change over each step, 699 | so if multiple zero crossings occur within one step, events may be missed. 700 | Additionally each event function might have the following attributes: 701 | terminal (bool, optional): 702 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 703 | direction (float, optional): 704 | Direction of a zero crossing. 705 | If direction is positive, event will only trigger when going from negative to positive, 706 | and vice versa if direction is negative. 707 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 708 | You can assign attributes like `event.terminal = True` to any function in Python. 709 | vectorized (bool, optional): 710 | Whether `fun` is implemented in a vectorized fashion. Default is False. 711 | options: 712 | Options passed to a chosen solver. 713 | All options available for already implemented solvers are listed in 714 | `scipy.integrate.solve_ivp() 715 | `_: 716 | 717 | Returns: 718 | Bunch object with the following fields defined: 719 | t (ndarray, shape (`n_points`,)): 720 | Time points. 721 | y (ndarray, shape (`n_points`,)): 722 | Values of the solution at t. 723 | sol (OdeSolution): 724 | Found solution as OdeSolution instance from MMG 3DOF simulation. 725 | t_events (list of ndarray or None): 726 | Contains for each event type a list of arrays at which an event of that type event was detected. 727 | None if events was None. 728 | y_events (list of ndarray or None): 729 | For each value of t_events, the corresponding value of the solution. 730 | None if events was None. 731 | nfev (int): 732 | Number of evaluations of the right-hand side. 733 | njev (int): 734 | Number of evaluations of the jacobian. 735 | nlu (int): 736 | Number of LU decomposition. 737 | status (int): 738 | Reason for algorithm termination: 739 | - -1: Integration step failed. 740 | - 0: The solver successfully reached the end of `tspan`. 741 | - 1: A termination event occurred. 742 | message (string): 743 | Human-readable description of the termination reason. 744 | success (bool): 745 | True if the solver reached the interval end or a termination event occurred (`status >= 0`). 746 | 747 | Examples: 748 | >>> duration = 200 # [s] 749 | >>> sampling = 2000 750 | >>> time_list = np.linspace(0.00, duration, sampling) 751 | >>> δ_list = np.full(len(time_list), 35.0 * np.pi / 180.0) 752 | >>> nps_list = np.full(len(time_list), 17.95) 753 | >>> L_pp=7.00 754 | >>> B=1.27 755 | >>> d=0.46 756 | >>> x_G=0.25 757 | >>> D_p=0.216 758 | >>> m=3.27*1.025 759 | >>> I_zG=m*((0.25 * L_pp) ** 2) 760 | >>> A_R=0.0539 761 | >>> η=D_p/0.345 762 | >>> m_x=0.022*(0.5 * ρ * (L_pp ** 2) * d) 763 | >>> m_y=0.223*(0.5 * ρ * (L_pp ** 2) * d) 764 | >>> J_z=0.011*(0.5 * ρ * (L_pp ** 4) * d) 765 | >>> f_α=2.747 766 | >>> ϵ=1.09 767 | >>> t_R=0.387 768 | >>> x_R=-0.500*L_pp 769 | >>> a_H=0.312 770 | >>> x_H=-0.464*L_pp 771 | >>> γ_R=0.395 772 | >>> l_R=-0.710 773 | >>> κ=0.50 774 | >>> t_P=0.220 775 | >>> w_P0=0.40 776 | >>> x_P=-0.650 777 | >>> k_0 = 0.2931 778 | >>> k_1 = -0.2753 779 | >>> k_2 = -0.1385 780 | >>> R_0_dash = 0.022 781 | >>> X_vv_dash = -0.040 782 | >>> X_vr_dash = 0.002 783 | >>> X_rr_dash = 0.011 784 | >>> X_vvvv_dash = 0.771 785 | >>> Y_v_dash = -0.315 786 | >>> Y_r_dash = 0.083 787 | >>> Y_vvv_dash = -1.607 788 | >>> Y_vvr_dash = 0.379 789 | >>> Y_vrr_dash = -0.391 790 | >>> Y_rrr_dash = 0.008 791 | >>> N_v_dash = -0.137 792 | >>> N_r_dash = -0.049 793 | >>> N_vvv_dash = -0.030 794 | >>> N_vvr_dash = -0.294 795 | >>> N_vrr_dash = 0.055 796 | >>> N_rrr_dash = -0.013 797 | >>> sol = simulate_mmg_3dof( 798 | >>> L_pp=L_pp, 799 | >>> B=B, 800 | >>> d=d, 801 | >>> x_G=x_G, 802 | >>> D_p=D_p, 803 | >>> m=m, 804 | >>> I_zG=I_zG, 805 | >>> A_R=A_R, 806 | >>> η=η, 807 | >>> m_x=m_x, 808 | >>> m_y=m_y, 809 | >>> J_z=J_z, 810 | >>> f_α=f_α, 811 | >>> ϵ=ϵ, 812 | >>> t_R=t_R, 813 | >>> x_R=x_R, 814 | >>> a_H=a_H, 815 | >>> x_H=x_H, 816 | >>> γ_R=γ_R, 817 | >>> l_R=l_R, 818 | >>> κ=κ, 819 | >>> t_P=t_P, 820 | >>> w_P0=w_P0, 821 | >>> x_P=x_P, 822 | >>> k_0=k_0, 823 | >>> k_1=k_1, 824 | >>> k_2=k_2, 825 | >>> X_0=X_0, 826 | >>> X_ββ=X_ββ, 827 | >>> X_βγ=X_βγ, 828 | >>> X_γγ=X_γγ, 829 | >>> X_vvvv_dash=X_vvvv_dash, 830 | >>> Y_β=Y_β, 831 | >>> Y_γ=Y_γ, 832 | >>> Y_βββ=Y_βββ, 833 | >>> Y_vvr_dash=Y_vvr_dash, 834 | >>> Y_vrr_dash=Y_vrr_dash, 835 | >>> Y_rrr_dash=Y_rrr_dash, 836 | >>> N_β=N_β, 837 | >>> N_γ=N_γ, 838 | >>> N_vvv_dash=N_vvv_dash, 839 | >>> N_vvr_dash=N_vvr_dash, 840 | >>> N_vrr_dash=N_vrr_dash, 841 | >>> N_rrr_dash=N_rrr_dash, 842 | >>> time_list, 843 | >>> δ_rad_list, 844 | >>> nps_list, 845 | >>> u0=2.29 * 0.512, 846 | >>> ) 847 | >>> result = sol.sol(time_list) 848 | 849 | Note: 850 | For more information, please see the following articles. 851 | 852 | - Yasukawa, H., Yoshimura, Y. (2015) Introduction of MMG standard method for ship maneuvering predictions. 853 | J Mar Sci Technol 20, 37-52 https://doi.org/10.1007/s00773-014-0293-y 854 | 855 | """ 856 | spl_δ = CubicSpline(time_list, δ_list, bc_type="natural", extrapolate=True) 857 | spl_nps = CubicSpline(time_list, nps_list, bc_type="natural", extrapolate=True) 858 | 859 | spl_δ_dot = spl_δ.derivative() 860 | spl_nps_dot = spl_nps.derivative() 861 | 862 | def mmg_3dof_eom_solve_ivp(t, X): 863 | 864 | u, v, r, x, y, ψ, δ, nps = X 865 | 866 | U = np.sqrt(u**2 + (v - r * x_G) ** 2) 867 | 868 | β = 0.0 if U == 0.0 else np.arcsin(-(v - r * x_G) / U) 869 | v_dash = 0.0 if U == 0.0 else v / U 870 | r_dash = 0.0 if U == 0.0 else r * L_pp / U 871 | 872 | # w_P = w_P0 873 | w_P = w_P0 * np.exp(-4.0 * (β - x_P * r_dash) ** 2) 874 | 875 | J = 0.0 if nps == 0.0 else (1 - w_P) * u / (nps * D_p) 876 | K_T = k_0 + k_1 * J + k_2 * J**2 877 | β_R = β - l_R * r_dash 878 | γ_R = γ_R_minus if β_R < 0.0 else γ_R_plus 879 | v_R = U * γ_R * β_R 880 | u_R = ( 881 | np.sqrt(η * (κ * ϵ * 8.0 * k_0 * nps**2 * D_p**4 / np.pi) ** 2) 882 | if J == 0.0 883 | else u 884 | * (1 - w_P) 885 | * ϵ 886 | * np.sqrt( 887 | η * (1.0 + κ * (np.sqrt(1.0 + 8.0 * K_T / (np.pi * J**2)) - 1)) ** 2 888 | + (1 - η) 889 | ) 890 | ) 891 | U_R = np.sqrt(u_R**2 + v_R**2) 892 | α_R = δ - np.arctan2(v_R, u_R) 893 | F_N = 0.5 * A_R * ρ * f_α * (U_R**2) * np.sin(α_R) 894 | 895 | X_H = ( 896 | 0.5 897 | * ρ 898 | * L_pp 899 | * d 900 | * (U**2) 901 | * ( 902 | -R_0_dash 903 | + X_vv_dash * (v_dash**2) 904 | + X_vr_dash * v_dash * r_dash 905 | + X_rr_dash * (r_dash**2) 906 | + X_vvvv_dash * (v_dash**4) 907 | ) 908 | ) 909 | X_R = -(1 - t_R) * F_N * np.sin(δ) 910 | X_P = (1 - t_P) * ρ * K_T * nps**2 * D_p**4 911 | Y_H = ( 912 | 0.5 913 | * ρ 914 | * L_pp 915 | * d 916 | * (U**2) 917 | * ( 918 | Y_v_dash * v_dash 919 | + Y_r_dash * r_dash 920 | + Y_vvv_dash * (v_dash**3) 921 | + Y_vvr_dash * (v_dash**2) * r_dash 922 | + Y_vrr_dash * v_dash * (r_dash**2) 923 | + Y_rrr_dash * (r_dash**3) 924 | ) 925 | ) 926 | Y_R = -(1 + a_H) * F_N * np.cos(δ) 927 | N_H = ( 928 | 0.5 929 | * ρ 930 | * (L_pp**2) 931 | * d 932 | * (U**2) 933 | * ( 934 | N_v_dash * v_dash 935 | + N_r_dash * r_dash 936 | + N_vvv_dash * (v_dash**3) 937 | + N_vvr_dash * (v_dash**2) * r_dash 938 | + N_vrr_dash * v_dash * (r_dash**2) 939 | + N_rrr_dash * (r_dash**3) 940 | ) 941 | ) 942 | N_R = -(x_R + a_H * x_H) * F_N * np.cos(δ) 943 | d_u = ((X_H + X_R + X_P) + (m + m_y) * v * r + x_G * m * (r**2)) / (m + m_x) 944 | d_v = ( 945 | (x_G**2) * (m**2) * u * r 946 | - (N_H + N_R) * x_G * m 947 | + ((Y_H + Y_R) - (m + m_x) * u * r) * (I_zG + J_z + (x_G**2) * m) 948 | ) / ((I_zG + J_z + (x_G**2) * m) * (m + m_y) - (x_G**2) * (m**2)) 949 | d_r = (N_H + N_R - x_G * m * (d_v + u * r)) / (I_zG + J_z + (x_G**2) * m) 950 | d_x = u * np.cos(ψ) - v * np.sin(ψ) 951 | d_y = u * np.sin(ψ) + v * np.cos(ψ) 952 | d_ψ = r 953 | d_δ = spl_δ_dot(t) 954 | d_nps = spl_nps_dot(t) 955 | return [d_u, d_v, d_r, d_x, d_y, d_ψ, d_δ, d_nps] 956 | 957 | sol = solve_ivp( 958 | mmg_3dof_eom_solve_ivp, 959 | [time_list[0], time_list[-1]], 960 | [u0, v0, r0, x0, y0, ψ0, δ_list[0], nps_list[0]], 961 | dense_output=True, 962 | method=method, 963 | t_eval=t_eval, 964 | events=events, 965 | vectorized=vectorized, 966 | **options 967 | ) 968 | return sol 969 | 970 | 971 | def get_sub_values_from_simulation_result( 972 | u_list: List[float], 973 | v_list: List[float], 974 | r_list: List[float], 975 | δ_list: List[float], 976 | nps_list: List[float], 977 | basic_params: Mmg3DofBasicParams, 978 | maneuvering_params: Mmg3DofManeuveringParams, 979 | ρ: float = 1025.0, 980 | return_all_vals: bool = False, 981 | ): 982 | """Get sub values of MMG calculation from simulation result. 983 | 984 | Args: 985 | u_list (List[float]): 986 | u list of MMG simulation result. 987 | v_list (List[float]): 988 | v list of MMG simulation result. 989 | r_list (List[float]): 990 | r list of MMG simulation result. 991 | δ_list (List[float]): 992 | δ list of MMG simulation result. 993 | nps_list (List[float]): 994 | nps list of MMG simulation result. 995 | basic_params (Mmg3DofBasicParams): 996 | u of MMG simulation result. 997 | maneuvering_params (Mmg3DofManeuveringParams): 998 | u of MMG simulation result. 999 | ρ (float, optional): 1000 | seawater density [kg/m^3] 1001 | Defaults to 1025.0. 1002 | return_all_vals (bool, optional): 1003 | Whether all sub values are returned or not. 1004 | Defaults to false. 1005 | Returns: 1006 | X_H_list (List[float]): List of X_H 1007 | X_R_list (List[float]): List of X_R 1008 | X_P_list (List[float]): List of X_P 1009 | Y_H_list (List[float]): List of Y_H 1010 | Y_R_list (List[float]): List of Y_R 1011 | N_H_list (List[float]): List of N_H 1012 | N_R_list (List[float]): List of N_R 1013 | U_list (List[float], optional): List of U if return_all_vals is True 1014 | β_list (List[float], optional): List of β if return_all_vals is True 1015 | v_dash_list (List[float], optional): List of v_dash if return_all_vals is True 1016 | r_dash_list (List[float], optional): List of r_dash if return_all_vals is True 1017 | w_P_list (List[float], optional): List of w_P if return_all_vals is True 1018 | J_list (List[float], optional): List of J if return_all_vals is True 1019 | K_T_list (List[float], optional): List of K_T if return_all_vals is True 1020 | v_R_list (List[float], optional): List of v_R if return_all_vals is True 1021 | u_R_list (List[float], optional): List of u_R if return_all_vals is True 1022 | U_R_list (List[float], optional): List of U_R if return_all_vals is True 1023 | α_R_list (List[float], optional): List of α_R if return_all_vals is True 1024 | F_N_list (List[float], optional): List of F_N if return_all_vals is True 1025 | """ 1026 | U_list = list( 1027 | map( 1028 | lambda u, v, r: np.sqrt(u**2 + (v - r * basic_params.x_G) ** 2), 1029 | u_list, 1030 | v_list, 1031 | r_list, 1032 | ) 1033 | ) 1034 | β_list = list( 1035 | map( 1036 | lambda U, v, r: 0.0 1037 | if U == 0.0 1038 | else np.arcsin(-(v - r * basic_params.x_G) / U), 1039 | U_list, 1040 | v_list, 1041 | r_list, 1042 | ) 1043 | ) 1044 | v_dash_list = list(map(lambda U, v: 0.0 if U == 0.0 else v / U, U_list, v_list)) 1045 | r_dash_list = list( 1046 | map(lambda U, r: 0.0 if U == 0.0 else r * basic_params.L_pp / U, U_list, r_list) 1047 | ) 1048 | β_P_list = list( 1049 | map( 1050 | lambda β, r_dash: β - basic_params.x_P * r_dash, 1051 | β_list, 1052 | r_dash_list, 1053 | ) 1054 | ) 1055 | # w_P_list = [basic_params.w_P0 for i in range(len(r_dash_list))] 1056 | w_P_list = list( 1057 | map(lambda β_P: basic_params.w_P0 * np.exp(-4.0 * β_P**2), β_P_list) 1058 | ) 1059 | J_list = list( 1060 | map( 1061 | lambda w_P, u, nps: 0.0 1062 | if nps == 0.0 1063 | else (1 - w_P) * u / (nps * basic_params.D_p), 1064 | w_P_list, 1065 | u_list, 1066 | nps_list, 1067 | ) 1068 | ) 1069 | K_T_list = list( 1070 | map( 1071 | lambda J: maneuvering_params.k_0 1072 | + maneuvering_params.k_1 * J 1073 | + maneuvering_params.k_2 * J**2, 1074 | J_list, 1075 | ) 1076 | ) 1077 | β_R_list = list( 1078 | map( 1079 | lambda β, r_dash: β - basic_params.l_R * r_dash, 1080 | β_list, 1081 | r_dash_list, 1082 | ) 1083 | ) 1084 | γ_R_list = list( 1085 | map( 1086 | lambda β_R: basic_params.γ_R_minus if β_R < 0.0 else basic_params.γ_R_plus, 1087 | β_R_list, 1088 | ) 1089 | ) 1090 | v_R_list = list( 1091 | map( 1092 | lambda U, γ_R, β_R: U * γ_R * β_R, 1093 | U_list, 1094 | γ_R_list, 1095 | β_R_list, 1096 | ) 1097 | ) 1098 | u_R_list = list( 1099 | map( 1100 | lambda u, J, nps, K_T, w_P: np.sqrt( 1101 | basic_params.η 1102 | * ( 1103 | basic_params.κ 1104 | * basic_params.ϵ 1105 | * 8.0 1106 | * maneuvering_params.k_0 1107 | * nps**2 1108 | * basic_params.D_p**4 1109 | / np.pi 1110 | ) 1111 | ** 2 1112 | ) 1113 | if J == 0.0 1114 | else u 1115 | * (1 - w_P) 1116 | * basic_params.ϵ 1117 | * np.sqrt( 1118 | basic_params.η 1119 | * ( 1120 | 1.0 1121 | + basic_params.κ * (np.sqrt(1.0 + 8.0 * K_T / (np.pi * J**2)) - 1) 1122 | ) 1123 | ** 2 1124 | + (1 - basic_params.η) 1125 | ), 1126 | u_list, 1127 | J_list, 1128 | nps_list, 1129 | K_T_list, 1130 | w_P_list, 1131 | ) 1132 | ) 1133 | U_R_list = list( 1134 | map(lambda u_R, v_R: np.sqrt(u_R**2 + v_R**2), u_R_list, v_R_list) 1135 | ) 1136 | α_R_list = list( 1137 | map(lambda δ, u_R, v_R: δ - np.arctan2(v_R, u_R), δ_list, u_R_list, v_R_list) 1138 | ) 1139 | F_N_list = list( 1140 | map( 1141 | lambda U_R, α_R: 0.5 1142 | * basic_params.A_R 1143 | * ρ 1144 | * basic_params.f_α 1145 | * (U_R**2) 1146 | * np.sin(α_R), 1147 | U_R_list, 1148 | α_R_list, 1149 | ) 1150 | ) 1151 | X_H_list = list( 1152 | map( 1153 | lambda U, v_dash, r_dash: 0.5 1154 | * ρ 1155 | * basic_params.L_pp 1156 | * basic_params.d 1157 | * (U**2) 1158 | * ( 1159 | -maneuvering_params.R_0_dash 1160 | + maneuvering_params.X_vv_dash * (v_dash**2) 1161 | + maneuvering_params.X_vr_dash * v_dash * r_dash 1162 | + maneuvering_params.X_rr_dash * (r_dash**2) 1163 | + maneuvering_params.X_vvvv_dash * (v_dash**4) 1164 | ), 1165 | U_list, 1166 | v_dash_list, 1167 | r_dash_list, 1168 | ) 1169 | ) 1170 | X_R_list = list( 1171 | map(lambda F_N, δ: -(1 - basic_params.t_R) * F_N * np.sin(δ), F_N_list, δ_list) 1172 | ) 1173 | X_P_list = list( 1174 | map( 1175 | lambda K_T, nps: (1 - basic_params.t_P) 1176 | * ρ 1177 | * K_T 1178 | * nps**2 1179 | * basic_params.D_p**4, 1180 | K_T_list, 1181 | nps_list, 1182 | ) 1183 | ) 1184 | Y_H_list = list( 1185 | map( 1186 | lambda U, v_dash, r_dash: 0.5 1187 | * ρ 1188 | * basic_params.L_pp 1189 | * basic_params.d 1190 | * (U**2) 1191 | * ( 1192 | maneuvering_params.Y_v_dash * v_dash 1193 | + maneuvering_params.Y_r_dash * r_dash 1194 | + maneuvering_params.Y_vvv_dash * (v_dash**3) 1195 | + maneuvering_params.Y_vvr_dash * (v_dash**2) * r_dash 1196 | + maneuvering_params.Y_vrr_dash * v_dash * (r_dash**2) 1197 | + maneuvering_params.Y_rrr_dash * (r_dash**3) 1198 | ), 1199 | U_list, 1200 | v_dash_list, 1201 | r_dash_list, 1202 | ) 1203 | ) 1204 | Y_R_list = list( 1205 | map(lambda F_N, δ: -(1 - basic_params.t_R) * F_N * np.cos(δ), F_N_list, δ_list) 1206 | ) 1207 | N_H_list = list( 1208 | map( 1209 | lambda U, v_dash, r_dash: 0.5 1210 | * ρ 1211 | * (basic_params.L_pp**2) 1212 | * basic_params.d 1213 | * (U**2) 1214 | * ( 1215 | maneuvering_params.N_v_dash * v_dash 1216 | + maneuvering_params.N_r_dash * r_dash 1217 | + maneuvering_params.N_vvv_dash * (v_dash**3) 1218 | + maneuvering_params.N_vvr_dash * (v_dash**2) * r_dash 1219 | + maneuvering_params.N_vrr_dash * v_dash * (r_dash**2) 1220 | + maneuvering_params.N_rrr_dash * (r_dash**3) 1221 | ), 1222 | U_list, 1223 | v_dash_list, 1224 | r_dash_list, 1225 | ) 1226 | ) 1227 | N_R_list = list( 1228 | map( 1229 | lambda F_N, δ: -(basic_params.x_R + basic_params.a_H * basic_params.x_H) 1230 | * F_N 1231 | * np.cos(δ), 1232 | F_N_list, 1233 | δ_list, 1234 | ) 1235 | ) 1236 | if return_all_vals: 1237 | return ( 1238 | X_H_list, 1239 | X_R_list, 1240 | X_P_list, 1241 | Y_H_list, 1242 | Y_R_list, 1243 | N_H_list, 1244 | N_R_list, 1245 | U_list, 1246 | β_list, 1247 | v_dash_list, 1248 | r_dash_list, 1249 | β_P_list, 1250 | w_P_list, 1251 | J_list, 1252 | K_T_list, 1253 | β_R_list, 1254 | γ_R_list, 1255 | v_R_list, 1256 | u_R_list, 1257 | U_R_list, 1258 | α_R_list, 1259 | F_N_list, 1260 | ) 1261 | else: 1262 | return ( 1263 | X_H_list, 1264 | X_R_list, 1265 | X_P_list, 1266 | Y_H_list, 1267 | Y_R_list, 1268 | N_H_list, 1269 | N_R_list, 1270 | ) 1271 | 1272 | 1273 | def zigzag_test_mmg_3dof( 1274 | basic_params: Mmg3DofBasicParams, 1275 | maneuvering_params: Mmg3DofManeuveringParams, 1276 | target_δ_rad: float, 1277 | target_ψ_rad_deviation: float, 1278 | time_list: List[float], 1279 | nps_list: List[float], 1280 | δ0: float = 0.0, 1281 | δ_rad_rate: float = 1.0 * np.pi / 180, 1282 | u0: float = 0.0, 1283 | v0: float = 0.0, 1284 | r0: float = 0.0, 1285 | x0: float = 0.0, 1286 | y0: float = 0.0, 1287 | ψ0: float = 0.0, 1288 | ρ: float = 1025.0, 1289 | method: str = "RK45", 1290 | t_eval=None, 1291 | events=None, 1292 | vectorized=False, 1293 | **options 1294 | ): 1295 | """Zig-zag test simulation. 1296 | 1297 | Args: 1298 | basic_params (Mmg3DofBasicParams): 1299 | Basic paramters for MMG 3DOF simulation. 1300 | maneuvering_params (Mmg3DofManeuveringParams): 1301 | Maneuvering parameters for MMG 3DOF simulation. 1302 | target_δ_rad (float): 1303 | target absolute value of rudder angle. 1304 | target_ψ_rad_deviation (float): 1305 | target absolute value of psi deviation from ψ0[rad]. 1306 | time_list (list[float]): 1307 | time list of simulation. 1308 | nps_list (List[float]): 1309 | nps list of simulation. 1310 | δ0 (float): 1311 | Initial rudder angle [rad]. 1312 | Defaults to 0.0. 1313 | δ_rad_rate (float): 1314 | Initial rudder angle rate [rad/s]. 1315 | Defaults to 1.0. 1316 | u0 (float, optional): 1317 | axial velocity [m/s] in initial condition (`time_list[0]`). 1318 | Defaults to 0.0. 1319 | v0 (float, optional): 1320 | lateral velocity [m/s] in initial condition (`time_list[0]`). 1321 | Defaults to 0.0. 1322 | r0 (float, optional): 1323 | rate of turn [rad/s] in initial condition (`time_list[0]`). 1324 | Defaults to 0.0. 1325 | x0 (float, optional): 1326 | x (surge) position [m] in initial condition (`time_list[0]`). 1327 | Defaults to 0.0. 1328 | y0 (float, optional): 1329 | y (sway) position [m] in initial condition (`time_list[0]`). 1330 | Defaults to 0.0. 1331 | ψ0 (float, optional): 1332 | Inital azimuth [rad] in initial condition (`time_list[0]`).. 1333 | Defaults to 0.0. 1334 | ρ (float, optional): 1335 | seawater density [kg/m^3] 1336 | Defaults to 1025.0. 1337 | method (str, optional): 1338 | Integration method to use in 1339 | `scipy.integrate.solve_ivp() 1340 | `_: 1341 | 1342 | "RK45" (default): 1343 | Explicit Runge-Kutta method of order 5(4). 1344 | The error is controlled assuming accuracy of the fourth-order method, 1345 | but steps are taken using the fifth-order accurate formula (local extrapolation is done). 1346 | A quartic interpolation polynomial is used for the dense output. 1347 | Can be applied in the complex domain. 1348 | "RK23": 1349 | Explicit Runge-Kutta method of order 3(2). 1350 | The error is controlled assuming accuracy of the second-order method, 1351 | but steps are taken using the third-order accurate formula (local extrapolation is done). 1352 | A cubic Hermite polynomial is used for the dense output. 1353 | Can be applied in the complex domain. 1354 | "DOP853": 1355 | Explicit Runge-Kutta method of order 8. 1356 | Python implementation of the “DOP853” algorithm originally written in Fortran. 1357 | A 7-th order interpolation polynomial accurate to 7-th order is used for the dense output. 1358 | Can be applied in the complex domain. 1359 | "Radau": 1360 | Implicit Runge-Kutta method of the Radau IIA family of order 5. 1361 | The error is controlled with a third-order accurate embedded formula. 1362 | A cubic polynomial which satisfies the collocation conditions is used for the dense output. 1363 | "BDF": 1364 | Implicit multi-step variable-order (1 to 5) method 1365 | based on a backward differentiation formula for the derivative approximation. 1366 | A quasi-constant step scheme is used and accuracy is enhanced using the NDF modification. 1367 | Can be applied in the complex domain. 1368 | "LSODA": 1369 | Adams/BDF method with automatic stiffness detection and switching. 1370 | This is a wrapper of the Fortran solver from ODEPACK. 1371 | 1372 | t_eval (array_like or None, optional): 1373 | Times at which to store the computed solution, must be sorted and lie within t_span. 1374 | If None (default), use points selected by the solver. 1375 | events (callable, or list of callables, optional): 1376 | Events to track. If None (default), no events will be tracked. 1377 | Each event occurs at the zeros of a continuous function of time and state. 1378 | Each function must have the signature event(t, y) and return a float. 1379 | The solver will find an accurate value of t at which event(t, y(t)) = 0 using a root-finding algorithm. 1380 | By default, all zeros will be found. The solver looks for a sign change over each step, 1381 | so if multiple zero crossings occur within one step, events may be missed. 1382 | Additionally each event function might have the following attributes: 1383 | terminal (bool, optional): 1384 | Whether to terminate integration if this event occurs. Implicitly False if not assigned. 1385 | direction (float, optional): 1386 | Direction of a zero crossing. 1387 | If direction is positive, event will only trigger when going from negative to positive, 1388 | and vice versa if direction is negative. 1389 | If 0, then either direction will trigger event. Implicitly 0 if not assigned. 1390 | You can assign attributes like `event.terminal = True` to any function in Python. 1391 | vectorized (bool, optional): 1392 | Whether `fun` is implemented in a vectorized fashion. Default is False. 1393 | options: 1394 | Options passed to a chosen solver. 1395 | All options available for already implemented solvers are listed in 1396 | `scipy.integrate.solve_ivp() 1397 | `_: 1398 | 1399 | Returns: 1400 | final_δ_list (list[float])) : list of rudder angle. 1401 | final_u_list (list[float])) : list of surge velocity. 1402 | final_v_list (list[float])) : list of sway velocity. 1403 | final_r_list (list[float])) : list of rate of turn. 1404 | """ 1405 | target_ψ_rad_deviation = np.abs(target_ψ_rad_deviation) 1406 | 1407 | final_δ_list = [0.0] * len(time_list) 1408 | final_u_list = [0.0] * len(time_list) 1409 | final_v_list = [0.0] * len(time_list) 1410 | final_r_list = [0.0] * len(time_list) 1411 | final_x_list = [0.0] * len(time_list) 1412 | final_y_list = [0.0] * len(time_list) 1413 | final_ψ_list = [0.0] * len(time_list) 1414 | 1415 | next_stage_index = 0 1416 | target_δ_rad = -target_δ_rad # for changing in while loop 1417 | ψ = ψ0 1418 | 1419 | while next_stage_index < len(time_list): 1420 | target_δ_rad = -target_δ_rad 1421 | start_index = next_stage_index 1422 | 1423 | # Make delta list 1424 | δ_list = [0.0] * (len(time_list) - start_index) 1425 | if start_index == 0: 1426 | δ_list[0] = δ0 1427 | u0 = u0 1428 | v0 = v0 1429 | r0 = r0 1430 | x0 = x0 1431 | y0 = y0 1432 | else: 1433 | δ_list[0] = final_δ_list[start_index - 1] 1434 | u0 = final_u_list[start_index - 1] 1435 | v0 = final_v_list[start_index - 1] 1436 | r0 = final_r_list[start_index - 1] 1437 | x0 = final_x_list[start_index - 1] 1438 | y0 = final_y_list[start_index - 1] 1439 | 1440 | for i in range(start_index + 1, len(time_list)): 1441 | Δt = time_list[i] - time_list[i - 1] 1442 | if target_δ_rad > 0: 1443 | δ = δ_list[i - 1 - start_index] + δ_rad_rate * Δt 1444 | if δ >= target_δ_rad: 1445 | δ = target_δ_rad 1446 | δ_list[i - start_index] = δ 1447 | elif target_δ_rad <= 0: 1448 | δ = δ_list[i - 1 - start_index] - δ_rad_rate * Δt 1449 | if δ <= target_δ_rad: 1450 | δ = target_δ_rad 1451 | δ_list[i - start_index] = δ 1452 | 1453 | sol = simulate_mmg_3dof( 1454 | basic_params, 1455 | maneuvering_params, 1456 | time_list[start_index:], 1457 | δ_list, 1458 | nps_list[start_index:], 1459 | u0=u0, 1460 | v0=v0, 1461 | r0=r0, 1462 | x0=x0, 1463 | y0=y0, 1464 | ψ0=ψ, 1465 | ρ=ρ, 1466 | # TODO 1467 | ) 1468 | sim_result = sol.sol(time_list[start_index:]) 1469 | u_list = sim_result[0] 1470 | v_list = sim_result[1] 1471 | r_list = sim_result[2] 1472 | x_list = sim_result[3] 1473 | y_list = sim_result[4] 1474 | ψ_list = sim_result[5] 1475 | # ship = ShipObj3dof(L=basic_params.L_pp, B=basic_params.B) 1476 | # ship.load_simulation_result(time_list, u_list, v_list, r_list, psi0=ψ) 1477 | 1478 | # get finish index 1479 | target_ψ_rad = ψ0 + target_ψ_rad_deviation 1480 | if target_δ_rad < 0: 1481 | target_ψ_rad = ψ0 - target_ψ_rad_deviation 1482 | # ψ_list = ship.psi 1483 | bool_ψ_list = [True if ψ < target_ψ_rad else False for ψ in ψ_list] 1484 | if target_δ_rad < 0: 1485 | bool_ψ_list = [True if ψ > target_ψ_rad else False for ψ in ψ_list] 1486 | over_index_list = [i for i, flag in enumerate(bool_ψ_list) if flag is False] 1487 | next_stage_index = len(time_list) 1488 | if len(over_index_list) > 0: 1489 | ψ = ψ_list[over_index_list[0]] 1490 | next_stage_index = over_index_list[0] + start_index 1491 | final_δ_list[start_index:next_stage_index] = δ_list[: over_index_list[0]] 1492 | final_u_list[start_index:next_stage_index] = u_list[: over_index_list[0]] 1493 | final_v_list[start_index:next_stage_index] = v_list[: over_index_list[0]] 1494 | final_r_list[start_index:next_stage_index] = r_list[: over_index_list[0]] 1495 | final_x_list[start_index:next_stage_index] = x_list[: over_index_list[0]] 1496 | final_y_list[start_index:next_stage_index] = y_list[: over_index_list[0]] 1497 | final_ψ_list[start_index:next_stage_index] = ψ_list[: over_index_list[0]] 1498 | else: 1499 | final_δ_list[start_index:next_stage_index] = δ_list 1500 | final_u_list[start_index:next_stage_index] = u_list 1501 | final_v_list[start_index:next_stage_index] = v_list 1502 | final_r_list[start_index:next_stage_index] = r_list 1503 | final_x_list[start_index:next_stage_index] = x_list 1504 | final_y_list[start_index:next_stage_index] = y_list 1505 | final_ψ_list[start_index:next_stage_index] = ψ_list 1506 | 1507 | return ( 1508 | final_δ_list, 1509 | final_u_list, 1510 | final_v_list, 1511 | final_r_list, 1512 | final_x_list, 1513 | final_y_list, 1514 | final_ψ_list, 1515 | ) 1516 | --------------------------------------------------------------------------------