├── .nojekyll
├── .gitattributes
├── vibrationtesting
├── tests
│ ├── __init__.py
│ ├── test_sparsematriceshandler.py
│ └── test_vibrationtesting.py
├── data
│ ├── case1.mat
│ ├── case2.mat
│ ├── mckiddat.mat
│ ├── testdata.mat
│ ├── frf_data1.mat
│ ├── WingBeamforMAC.mat
│ └── Modeshapedata
│ │ ├── 1stNaturalFreq15.mat
│ │ ├── 1stNaturalFreq18.mat
│ │ ├── 2ndNaturalFreq15.mat
│ │ ├── 2ndNaturalFreq18.mat
│ │ ├── 3rdNaturalFreq15.mat
│ │ ├── 3rdNaturalFreq18.mat
│ │ ├── 5thNaturalFreq15.mat
│ │ ├── 5thNaturalFreq18.mat
│ │ ├── 6thNaturalFreq15.mat
│ │ ├── 6thNaturalFreq18.mat
│ │ ├── 4rthNaturalFreq15.mat
│ │ └── 4rthNaturalFreq18.mat
├── conftest.py
├── __init__.py
├── .ropeproject
│ └── config.py
├── sparsematriceshandler.py
├── identification.py
└── system.py
├── pytest.ini
├── requirements.txt
├── docs
├── reference
│ ├── index.rst
│ ├── signal.rst
│ ├── system.rst
│ └── identification.rst
├── tutorial
│ ├── index.rst
│ └── Notebooks
│ │ └── Ho-Kalman-Demo.ipynb
├── Makefile
├── Installing_Python.rst
├── readme.rst
├── installation.rst
├── index.rst
└── conf.py
├── setup.cfg
├── .travis.yml
├── LICENSE.txt
├── .gitignore
├── setup.py
├── Makefile
├── readme.rst
├── JupyterNotebooks
├── secondorderstatespacematlab.ipynb
├── Final_Solution.ipynb
├── Examples.ipynb
└── Ho-Kalman-Demo.ipynb
├── .ropeproject
└── config.py
├── CONTRIBUTING.rst
└── old
└── conf.py-old
/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vibrationtesting/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --ignore=setup.py
3 | doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | scipy
3 | matplotlib
4 | sphinx
5 | ipython
6 | control
7 | vibration_toolbox
8 | xarray
9 |
--------------------------------------------------------------------------------
/vibrationtesting/data/case1.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/case1.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/case2.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/case2.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/mckiddat.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/mckiddat.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/testdata.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/testdata.mat
--------------------------------------------------------------------------------
/vibrationtesting/conftest.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | np.set_printoptions(precision=4, suppress=True)
4 | print('Running conftest.py')
5 |
--------------------------------------------------------------------------------
/vibrationtesting/data/frf_data1.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/frf_data1.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/WingBeamforMAC.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/WingBeamforMAC.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/1stNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/1stNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/1stNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/1stNaturalFreq18.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/2ndNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/2ndNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/2ndNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/2ndNaturalFreq18.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/3rdNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/3rdNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/3rdNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/3rdNaturalFreq18.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/5thNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/5thNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/5thNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/5thNaturalFreq18.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/6thNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/6thNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/6thNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/6thNaturalFreq18.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/4rthNaturalFreq15.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/4rthNaturalFreq15.mat
--------------------------------------------------------------------------------
/vibrationtesting/data/Modeshapedata/4rthNaturalFreq18.mat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vibration-Testing/vibrationtesting/HEAD/vibrationtesting/data/Modeshapedata/4rthNaturalFreq18.mat
--------------------------------------------------------------------------------
/docs/reference/index.rst:
--------------------------------------------------------------------------------
1 | Reference
2 | ---------
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | signal
8 | system
9 | identification
10 |
11 | .. systemidentification
12 | .. modelupdating
13 |
--------------------------------------------------------------------------------
/docs/reference/signal.rst:
--------------------------------------------------------------------------------
1 | Signal Processing and Generation (:mod:`signals`)
2 | -------------------------------------------------
3 |
4 | .. sectionauthor:: Joseph C. Slater
5 |
6 | .. automodule:: signals
7 | :members:
8 |
--------------------------------------------------------------------------------
/docs/reference/system.rst:
--------------------------------------------------------------------------------
1 | System Manipulation, Reduction, and Correction (:mod:`system`)
2 | --------------------------------------------------------------
3 |
4 | .. sectionauthor:: Joseph C. Slater
5 |
6 | .. automodule:: system
7 | :members:
8 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = readme.rst
3 |
4 | [build_sphinx]
5 | source-dir = docs
6 | build-dir = docs/build
7 | all_files = 1
8 |
9 | [upload_sphinx]
10 | upload-dir = docs/build/html
11 |
12 | [bdist_wheel]
13 | python-tag = py35
14 |
--------------------------------------------------------------------------------
/docs/reference/identification.rst:
--------------------------------------------------------------------------------
1 | Modal Analysis and System Identification (:mod:`identification`)
2 | ----------------------------------------------------------------
3 |
4 | .. sectionauthor:: Joseph C. Slater
5 |
6 | .. automodule:: identification
7 | :members:
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - "3.5"
5 | - "3.6"
6 |
7 | before_install:
8 | - mkdir -p $HOME/.config/matplotlib
9 |
10 | install:
11 | - pip install -r requirements.txt
12 | - pip install .
13 |
14 | cache:
15 | directories:
16 | - $HOME/.cache/matplotlib
17 |
18 | script:
19 | - pytest
20 |
21 |
--------------------------------------------------------------------------------
/docs/tutorial/index.rst:
--------------------------------------------------------------------------------
1 | Tutorial
2 | --------
3 |
4 | Tutorials with worked examples using the Vibration Testing module.
5 |
6 | Please excuse the mess while this is brought online.
7 |
8 | .. sectionauthor:: Joseph C. Slater
9 |
10 | .. toctree::
11 | :maxdepth: 2
12 |
13 | Notebooks/Signal.ipynb
14 | Notebooks/Ho-Kalman-Demo.ipynb
15 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SPHINXPROJ = VibrationTesting
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 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | vibrationtesting
2 | ==================
3 | Copyright (C) 2002 by Joseph C. Slater
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/Installing_Python.rst:
--------------------------------------------------------------------------------
1 | .. _installing_python:
2 |
3 | Installing Python
4 | _________________
5 |
6 | In order to be able to use the Vibration Testing module you need a working scientific python installation.
7 |
8 | The easiest path to this is to install Python via `Anaconda`_. **You must install** Python 3.5 or later for the Vibration Testing module to work. **Do note install Python 2.7.**
9 |
10 | This proceeds as a normal install on your platform (Mac, Windows, Linux...).
11 |
12 | Subsequently you must update some components of Anaconda by using the *conda* command from the *Anaconda Command Prompt*. On Windows, this runs as an actual program.
13 |
14 | To ensure the conda package list is as up to date as possible::
15 |
16 | conda update conda
17 |
18 | Then update everything else with::
19 |
20 | conda update --all
21 |
22 | Then,
23 |
24 | .. code-block:: bash
25 |
26 | conda install jupyter
27 |
28 | To use `Jupyter`_ (the notebook), launch a terminal on Mac or Linux, or the Anaconda Terminal on Windows (or similar name) and type:
29 |
30 | .. code-block:: bash
31 |
32 | jupyter notebook
33 |
34 | It's all in the GUI from here. You just need to play around a bit.
35 |
36 | .. _github: http://www.github.com
37 | .. _Anaconda: http://continuum.io/downloads
38 | .. _Jupyter: http://www.jupyter.org
39 |
--------------------------------------------------------------------------------
/.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 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | .ipynb_check*
21 | .ipynb*check*/*
22 | .#.*
23 | .ipynb_checkpoints/**
24 | .ipynb*
25 | parts/
26 | sdist/
27 | var/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 |
32 | # Misc
33 | pncr*
34 | Untitled*
35 |
36 | # Sphinx
37 | _build*
38 | latex*
39 | html*
40 | *~
41 | *.html
42 |
43 | # Mac
44 | aquamacs
45 |
46 | # PyInstaller
47 | # Usually these files are written by a python script from a template
48 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
49 | *.manifest
50 | *.spec
51 |
52 | # Installer logs
53 | pip-log.txt
54 | pip-delete-this-directory.txt
55 |
56 | # Unit test / coverage reports
57 | htmlcov/
58 | .tox/
59 | .coverage
60 | .coverage.*
61 | .cache
62 | nosetests.xml
63 | coverage.xml
64 | *,cover
65 | .hypothesis/
66 |
67 | # Translations
68 | *.mo
69 | *.pot
70 |
71 | # Django stuff:
72 | *.log
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | target/
79 | vibrationtesting/.DS_Store
80 | .DS_Store
81 | *.lock
82 | .pytest_cache/v/cache/lastfailed
83 |
--------------------------------------------------------------------------------
/vibrationtesting/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Vibration Testing module
3 | ------------------------
4 |
5 | This module is a companion to the manuscript Vibration Testing with Health
6 | Monitoring by Joseph C. Slater. It includes functions for:
7 |
8 | - Signal processing
9 | - System manipulation and solution
10 | - System identification
11 |
12 | """
13 | from __future__ import division, print_function, absolute_import
14 |
15 | __title__ = 'vibrationtesting'
16 | __version__ = '0.24'
17 | __author__ = u'Joseph C. Slater'
18 | __license__ = 'MIT'
19 | __copyright__ = 'Copyright 2002-2017 Joseph C. Slater'
20 | __all__ = ['identification', 'signals', 'system',
21 | '__version__']
22 |
23 | import sys
24 | import matplotlib as mpl
25 | import numpy as np
26 |
27 | if 'pytest' in sys.argv[0]:
28 | # print('Setting backend to agg to run tests')
29 | _ = mpl.use('agg')
30 | np.set_printoptions(precision=4, suppress=True)
31 |
32 | from .signals import *
33 | from .system import *
34 | from .identification import *
35 | from .sparsematriceshandler import *
36 |
37 | # Signal processing (:mod:`vibrationtesting.signal`)
38 |
39 | # print options were change inside modules to produce better
40 | # outputs at examples. Here we set the print options to the
41 | # default values after importing the modules to avoid changing
42 | # np default print options when importing the module.
43 |
--------------------------------------------------------------------------------
/docs/readme.rst:
--------------------------------------------------------------------------------
1 | This is the folder for building the complete documentation for the Vibration Testing module. The actual documentation is hosted via `githubpages `_.
2 |
3 | The remainder of this document contains notes for developers.
4 |
5 | To make the doc, in the `docs` folder type::
6 |
7 | make docs
8 |
9 | To serve the pages, move to the `docs/_build/html` folder and type::
10 |
11 | python -m http.server
12 |
13 |
14 |
15 | ===============
16 | Title
17 | ===============
18 |
19 |
20 | Section Title
21 | -------------
22 |
23 | Subsection Title
24 | ________________
25 |
26 | Subsubsection Title
27 | ~~~~~~~~~~~~~~~~~~~
28 |
29 | Subsubsubsection Title
30 | ``````````````````````
31 |
32 | Subsubsubsubection Title
33 | ''''''''''''''''''''''''
34 |
35 | We shouldn't ever need this many.
36 |
37 |
38 | All documentation will be built with via `sphinx `_ using ``make html`` in the ``docs`` directory. Updating on ``github`` is documented
39 | in the ``developers.rst`` file at the top of the repository.
40 |
41 | Documentation of functions help (uses `autodoc `_):
42 |
43 |
44 | Module and function help needs to then follow the `numpy convention.
45 | `_
46 |
47 |
48 | For information on how function docstrings are used, see `numpydoc `_
49 |
50 | For plots:
51 | http://matplotlib.org/sampledoc/extensions.html
52 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from setuptools import setup
4 | import os
5 | import sys
6 |
7 | if sys.version_info < (3, 5):
8 | sys.exit('Sorry, Python < 3.5 is not supported.')
9 | # Utility function to read the README file.
10 | # Used for the long_description. It's nice, because now 1) we have a top level
11 | # README file and 2) it's easier to type in the README file than to put a raw
12 | # string in below ...
13 |
14 |
15 | def read(fname):
16 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
17 |
18 |
19 | with open('vibrationtesting/__init__.py', 'rb') as fid:
20 | for line in fid:
21 | line = line.decode('utf-8')
22 | if line.startswith('__version__'):
23 | version = line.strip().split()[-1][1:-1]
24 | break
25 |
26 | download_url = ('https://github.com/Vibration-Testing/vibrationtesting/\
27 | blob/master/dist/vibrationtesting-' + version + '.whl')
28 |
29 | setup(name='vibrationtesting',
30 | version=version,
31 | description=('Signal processing, modal analysis, plotting, and system\
32 | identification for vibrating systems'),
33 | author=u'Joseph C. Slater',
34 | author_email='joseph.c.slater@gmail.com',
35 | url='https://github.com/Vibration-Testing/vibrationtesting',
36 | packages=['vibrationtesting'],
37 | package_data={'vibrationtesting': ['../readme.rst', 'data/*.mat'],
38 | '': ['readme.rst']},
39 | long_description=read('readme.rst'),
40 | keywords=['vibration', 'mechanical engineering', 'testing',
41 | 'civil engineering', 'modal analysis'],
42 | install_requires=['numpy', 'scipy', 'matplotlib'],
43 | setup_requires=['pytest-runner'],
44 | tests_require=['pytest']
45 | )
46 |
47 | # https://docs.python.org/3/distutils/setupscript.html#additional-meta-data
48 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ------------
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | Installing_Python
8 |
9 | Easy Installation
10 | _________________
11 |
12 | If you aren't familiar at all with Python, please see `Installing Python `_.
13 |
14 | Installation is made easy with ``pip`` (or ``pip3``), with releases as we have time while we try
15 | to create a full first release. Much of it works already, but we certainly need
16 | issue reports (on `github `_).
17 |
18 | To install::
19 |
20 | pip install --user vibrationtesting
21 |
22 | where ``--user`` isn't necessary if you are using a locally installed version of Python such as `Anaconda `_.
23 |
24 | To run, I recommend you open a `Jupyter`_ notebook by using ``jupyter notebook`` and then type::
25 |
26 | import vibrationtesting as vt
27 |
28 | For examples, see the `example ipynb notebooks `_. Some of these have interactive capabilities that are only apparent when you load them with `Jupyter`_ instead of just looking at them on github. Sorry- these are very rough right now. You can see the `less rough notebooks `_ used to make the manual as well.
29 |
30 | Installation of current development version
31 | ___________________________________________
32 |
33 | The usage documentation is far behind the current code, while the reference is way ahead of the released code due to the `autodoc `_ capability of `Sphinx `_. Especially as of 2017, the code is in rapid development. So is the documentation. To get the most current version and stay up to date see the file `CONTRIBUTING.rst `_ in the github repository.
34 |
35 | .. _Jupyter: jupyter.org
36 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # This Makefile was created by transforming the one for oct2py
2 | # The line #------------------ shows where editing to date has been completed
3 | # for the first attempt.
4 | # Note: This is meant for vibrationtesting developer use only
5 |
6 | .PHONY: all clean test cover release gh-pages docs
7 |
8 | # I don't know what the following line means/does
9 | #export TEST_ARGS=--exe -v --with-doctest
10 | export NAME=vibrationtesting
11 |
12 | export GHP_MSG="Generated gh-pages for `git log master -1 --pretty=short --abbrev-commit`"
13 | export VERSION=`python -c "import $(NAME); print($(NAME).__version__)"`
14 |
15 | #all: clean
16 | # python setup.py install
17 |
18 | #----------------------------------------------------
19 |
20 | help:
21 | @echo "Please use \`make ' where is one of:"
22 | @echo " clean to clear build files"
23 | @echo " test to test all docstring examples"
24 | @echo " cover to test coverage (not working yet)"
25 | @echo " release to edit version, build docs and release"
26 | @echo " wheel build wheel file (for local use)"
27 | @echo " wheel-dist build wheel and push to github"
28 | @echo " docs build docs using sphin"
29 | @echo " html alias for docs"
30 | @echo " gh-pages build and release docs"
31 |
32 | clean:
33 | rm -rf build
34 | rm -rf dist
35 | find . -name "*.pyc" -o -name "*.py,cover"| xargs rm -f
36 | # killall -9 nosetests; true
37 |
38 | test:
39 | pytest
40 |
41 | cover: clean
42 | pip install nose-cov
43 | nosetests $(TEST_ARGS) --with-cov --cov $(NAME) $(NAME)
44 | coverage annotate
45 |
46 | release: clean
47 | pip install --user readme_renderer
48 | #python setup.py check -r -s
49 | pytest
50 | #python setup.py register
51 | rm -rf dist
52 | python setup.py bdist_wheel
53 | # python setup.py sdist
54 | git tag v$(VERSION)
55 | git push origin --all
56 | git push origin --tags
57 | printf '\nUpgrade vibration testing with release and sha256 sum:'
58 | printf '\nOK, no sha256 sum yet:'
59 | twine upload dist/*
60 | shasum -a 256 dist/*.whl
61 |
62 | wheel:
63 | rm -rf dist
64 | python setup.py bdist_wheel
65 |
66 | wheel-dist: gh-pages
67 | rm -rf dist
68 | python setup.py bdist_wheel
69 |
70 | docs:
71 | # Warnings become errors and stop build
72 | export SPHINXOPTS=-W
73 | # pip install sphinx-bootstrap-theme numpydoc sphinx ghp-import
74 | # Run the make file in the docs directory
75 | make -C docs clean
76 | make -C docs html
77 |
78 | html: docs
79 |
80 | gh-pages:
81 | git checkout master
82 | git pull origin master
83 | git commit -a -m "Keep examples in sync"; true
84 | git push origin; true
85 | make docs
86 | ghp-import -n -p -m $(GHP_MSG) docs/_build/html
87 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Vibration Testing documentation master file, created by
2 | sphinx-quickstart on Sun Apr 9 10:02:35 2017.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | =============================================
7 | The Vibration Testing Module
8 | =============================================
9 |
10 | .. .. include::
11 | .. image:: https://badge.fury.io/py/vibrationtesting.png/
12 | :target: http://badge.fury.io/py/vibrationtesting
13 |
14 | .. image:: https://travis-ci.org/vibrationtesting/vibrationtesting.svg?branch=master
15 | :target: https://travis-ci.org/vibrationtesting/vibrationtesting
16 |
17 | .. image:: https://img.shields.io/pypi/v/vibrationtesting.svg
18 | :target: https://img.shields.io/pypi/v/vibrationtesting
19 |
20 | .. #image:: https://coveralls.io/repos/vibrationtesting/vibrationtesting/badge.png?branch=master
21 | .. #:target: https://coveralls.io/r/vibrationtesting/vibrationtesting
22 |
23 |
24 | Joseph C. Slater
25 |
26 | Welcome to the `Vibration Testing` module. This module has three separate pieces in varying degrees of completion:
27 |
28 | - Signal processing
29 | - Model identification (Modal Analysis)
30 | - Model updating
31 |
32 | If you are looking for a `Matlab version `_, one does exist, but is not being actively developed by me. You'll find more features hear. Volunteering to support Open Source is much more rewarding than volunteering to support Matlab.
33 |
34 | This software is nominally provided without support, etc., but I will certainly answer a question or two. If you have professional-level needs please `contact the authors `_. Further, please also `submit bug reports `_ with code indicating the error.
35 |
36 | For more information, please see the `documentation for the Python version `_ but please excuse that it is still under development. Such documentation has never existed for the other ports of the module so this is taking some time. We don't need feedback at this time, but we will take assistance in improving documentation and code.
37 |
38 | Table of Contents
39 | -----------------
40 |
41 | .. toctree::
42 | :maxdepth: 3
43 |
44 | installation
45 | tutorial/index
46 | reference/index
47 |
48 | Download Tutorial Notebooks
49 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
50 |
51 | .. :download:`Single Degree of Freedom `
52 |
53 | .. :download:`Multiple Degree of Freedom `
54 |
55 | .. :download:`Continuous Systems `
56 |
57 | .. :download:`Vibration System `
58 |
59 |
60 | Indices and tables
61 | __________________
62 |
63 | * :ref:`genindex`
64 | * :ref:`modindex`
65 | * :ref:`search`
66 |
--------------------------------------------------------------------------------
/readme.rst:
--------------------------------------------------------------------------------
1 | Vibration Testing:
2 | ==================
3 |
4 | A package for signal processing, modal analysis, and model reduction and model updating
5 |
6 | .. .. include::
7 | .. image:: https://badge.fury.io/py/vibrationtesting.png/
8 | :target: http://badge.fury.io/py/vibrationtesting
9 |
10 | .. image:: https://travis-ci.org/Vibration-Testing/vibrationtesting.svg?branch=master
11 | :target: https://travis-ci.org/Vibration-Testing/vibrationtesting
12 |
13 | .. image:: https://zenodo.org/badge/50037940.svg
14 | :target: https://zenodo.org/badge/latestdoi/50037940
15 |
16 | .. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
17 | :target: https://saythanks.io/to/josephcslater
18 |
19 | .. image:: https://img.shields.io/badge/patreon-donate-yellow.svg
20 | :target: https://www.patreon.com/josephcslater
21 |
22 | .. image:: http://pepy.tech/badge/vibrationtesting
23 | :target: http://pepy.tech/project/vibrationtesting
24 | :alt: PyPi Download stats
25 |
26 | Joseph C. Slater
27 | ----------------
28 |
29 | Welcome to `Vibration Testing `_.
30 |
31 | For more information, please see the `documentation for the Python version `_.
32 |
33 | Installation
34 | ------------
35 |
36 | If you aren't familiar at all with Python, please see `Installing Python `_.
37 |
38 | Installation is made easy with ``pip`` (or ``pip3``), with releases as we have time while we try
39 | to create a full first release. Much of it works already, but I certainly need
40 | issue reports (on `github `_) when something is not working as it should.
41 |
42 | To install type::
43 |
44 | pip install --user vibrationtesting
45 |
46 | at your command prompt **(not the python prompt)** where ``--user`` isn't necessary if you are using a locally installed version of Python such as `Anaconda `_.
47 |
48 | To run, I recommend you open a `Jupyter `_ notebook by using ``jupyter notebook`` and then type::
49 |
50 | import vibrationtesting as vt
51 |
52 | For examples, see the `JupyterNotebooks folder `_. (In flux- also look in doc/Tutorials for now) Some of these have interactive capabilities that are only apparent when you run them yourself instead of just looking at them on github. Unfortunately our organization of these still leaves a little to be desired.
53 |
54 | Installation of current code
55 | ____________________________
56 |
57 | The usage documentation is far behind the current code, while the reference is way ahead of the released code due to the `autodoc `_ capability of `Sphinx `_. Especially as of 2017, the code is in flux. So is the documentation. Releases to `PyPI `_ are far behind current status as stopping to deploy would cost more time that it is worth. I have the objective of releasing a first non-beta version at the end of May 2018, but even this cannot be promised.
58 |
59 | If you wish to install the current version of the software, read the instructions in `Contributing.rst `_
60 |
61 | That should be it. Please note issues on the `issues tab `_ on GitHub.
62 |
63 | Like this module, `buy me a coffee! `_
64 |
65 | Quick notes:
66 | -------------
67 |
68 | The `convention used for the signal processing `_ is that of the `python controls module `_.
69 |
--------------------------------------------------------------------------------
/vibrationtesting/tests/test_sparsematriceshandler.py:
--------------------------------------------------------------------------------
1 | """pytest unit tests for vibrationtesting"""
2 |
3 | import numpy as np
4 | import vibrationtesting as vt
5 | import numpy.testing as nt
6 | import scipy.io as sio
7 |
8 | def test_sos_modal_forsparse():
9 | mat_contents=sio.loadmat('vibrationtesting/data/WingBeamforMAC.mat') # WFEM generated .mat file
10 | K = (mat_contents['K'])
11 | M = (mat_contents['M'])
12 | ## Mr and Kr are WFEM outputs after Guyan reduction
13 | Kr = (mat_contents['Kr'])
14 | Mr = (mat_contents['Mr'])
15 | master = np.array([[ 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 20,
16 | 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 38, 39,
17 | 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,
18 | 59, 60, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 74, 75, 76, 77,
19 | 78, 80, 81, 82, 83, 84, 86, 87, 88, 89, 90]])
20 | ## Mred and Kred are from guyan_forsparse
21 | [Mred, Kred, master] = vt.guyan_forsparse(M, K, master=master, fraction=None)
22 | [omega_sp,zeta_sp,Psi_sp] = vt.sos_modal_forsparse(Mred,Kred)
23 | Kbm = Kr.todense()
24 | Mbm = Mr.todense()
25 | omega, zeta, Psi = vt.sos_modal(Mbm, Kbm)
26 | ## The below compares sparsematriceshandler.py vs system.py results
27 | nt.assert_array_almost_equal(omega_sp,omega)
28 | nt.assert_array_almost_equal(zeta_sp,zeta)
29 | nt.assert_array_almost_equal(Psi_sp,Psi)
30 |
31 | def test_guyan_forsparse():
32 | mat_contents=sio.loadmat('vibrationtesting/data/WingBeamforMAC.mat') # WFEM generated .mat file
33 | K = (mat_contents['K'])
34 | M = (mat_contents['M'])
35 | ## Mr and Kr are WFEM outputs after Guyan reduction
36 | Kr = (mat_contents['Kr'])
37 | Mr = (mat_contents['Mr'])
38 | master = np.array([[ 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 20,
39 | 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 38, 39,
40 | 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,
41 | 59, 60, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 74, 75, 76, 77,
42 | 78, 80, 81, 82, 83, 84, 86, 87, 88, 89, 90]])
43 | ## Mred and Kred are from guyan_forsparse
44 | [Mred, Kred, master] = vt.guyan_forsparse(M, K, master=master, fraction=None)
45 | Kred = Kred.todense()
46 | Mred = Mred.todense()
47 | Kr = Kr.todense()
48 | Mr = Mr.todense()
49 | ## The below compares the two outputs guyanWFEM vs guyanVibrationtesting
50 | nt.assert_array_almost_equal(Mred,Mr)
51 | nt.assert_array_almost_equal(Kred,Kr)
52 |
53 | def test_mode_expansion_from_model_forsparse():
54 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/1stNaturalFreq15.mat')
55 | U1 = mat_contents['U1']
56 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/2ndNaturalFreq15.mat')
57 | U2 = mat_contents['U2']
58 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/3rdNaturalFreq15.mat')
59 | U3 = mat_contents['U3']
60 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/4rthNaturalFreq15.mat')
61 | U4 = mat_contents['U4']
62 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/5thNaturalFreq15.mat')
63 | U5 = mat_contents['U5']
64 | mat_contents=sio.loadmat('vibrationtesting/data/Modeshapedata/6thNaturalFreq15.mat')
65 | U6 = mat_contents['U6']
66 | Psi_1 = np.array(U1)
67 | Psi_2 = np.array(U2)
68 | Psi_3 = np.array(U3)
69 | Psi_4 = np.array(U4)
70 | Psi_5 = np.array(U5)
71 | Psi_6 = np.array(U6)
72 | Psi_1=np.column_stack((Psi_1,Psi_2,Psi_3,Psi_4,Psi_5,Psi_6))
73 | Psi_abs = np.abs(Psi_1)*np.real(np.sign(Psi_1))
74 | Psi_1 = Psi_abs
75 | mat_contents=sio.loadmat('vibrationtesting/data/WingBeamforMAC.mat') # WFEM generated .mat file
76 | K = (mat_contents['Kr'])
77 | M = (mat_contents['Mr'])
78 | Kbm = K.todense()
79 | Mbm = M.todense()
80 | measured = np.array([[1,6,11,16,21,26,31,36,41,46,51,56,61,66,71]])
81 | omega=np.array([13.54, 51.81, 125.19, 167.76, 228.05, 328.41])
82 | Psi_fullBM_1=vt.mode_expansion_from_model(Psi_1, omega, Mbm, Kbm, measured)
83 | Psi_fullBM_2=vt.mode_expansion_from_model_forsparse(Psi_1, omega, M, K, measured)
84 |
85 | ## The below compares sparsematriceshandler.py vs system.py results
86 | nt.assert_array_almost_equal(Psi_fullBM_1,Psi_fullBM_2)
87 |
--------------------------------------------------------------------------------
/JupyterNotebooks/secondorderstatespacematlab.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 16,
6 | "metadata": {
7 | "collapsed": false
8 | },
9 | "outputs": [
10 | {
11 | "name": "stdout",
12 | "output_type": "stream",
13 | "text": [
14 | "m =\n",
15 | " 2\n",
16 | "c =\n",
17 | " 0.0100\n",
18 | "k =\n",
19 | " 100\n",
20 | "f =\n",
21 | " 7\n",
22 | "A =\n",
23 | " 0 1.0000\n",
24 | " -50.0000 -0.0050\n",
25 | "B =\n",
26 | " 0\n",
27 | " 3.5000\n",
28 | "C =\n",
29 | " 1 0\n",
30 | "D =\n",
31 | " 0\n",
32 | "v =\n",
33 | " -0.0000 - 0.1400i -0.0000 + 0.1400i\n",
34 | " 0.9901 + 0.0000i 0.9901 + 0.0000i\n",
35 | "d =\n",
36 | " -0.0025 + 7.0711i 0.0000 + 0.0000i\n",
37 | " 0.0000 + 0.0000i -0.0025 - 7.0711i\n",
38 | "Bm =\n",
39 | " 1.7674 + 0.0006i\n",
40 | " 1.7674 - 0.0006i\n",
41 | "Cm =\n",
42 | " -0.0000 - 0.1400i -0.0000 + 0.1400i\n",
43 | "Am =\n",
44 | " -0.0025 + 7.0711i -0.0000 + 0.0000i\n",
45 | " -0.0000 - 0.0000i -0.0025 - 7.0711i\n",
46 | "lam1 =\n",
47 | " -0.0025 + 7.0711i\n",
48 | "ans =\n",
49 | " -0.0025 + 7.0711i\n",
50 | " -0.0025 - 7.0711i\n",
51 | "ans =\n",
52 | " 0.0000 + 3.5707i 0.5050 + 0.0002i\n",
53 | " 0.0000 - 3.5707i 0.5050 - 0.0002i\n",
54 | "P2 =\n",
55 | " 0.0025 + 7.0711i 1.0000 + 0.0000i\n",
56 | " -0.0025 - 7.0711i 1.0000 - 0.0007i\n",
57 | "Aso =\n",
58 | " 0.0000 + 0.0000i 1.0000 - 0.0000i\n",
59 | " -50.0000 - 0.0000i -0.0050 - 0.0000i\n",
60 | "Bso =\n",
61 | " -0.0000 + 0.0000i\n",
62 | " 1.7674 + 0.0006i\n",
63 | "Cso =\n",
64 | " 1.9803 - 0.0007i 0.0000 + 0.0000i\n"
65 | ]
66 | }
67 | ],
68 | "source": [
69 | "m = 2, c = .01, k = 100 \n",
70 | "%roots([m c k]) \n",
71 | "f = 7 \n",
72 | "A = [0 1;-m\\k -m\\c] \n",
73 | "B = [0;f/m] \n",
74 | "C = [1 0]\n",
75 | "D=0 \n",
76 | "[v,d]=eig(A) \n",
77 | "P = v;Bm = P\\B, Cm = C*P \n",
78 | "Am=v\\A*v\n",
79 | "lam1=d(1,1)\n",
80 | "roots([m c k]) \n",
81 | "inv(v)\n",
82 | "P2=[-conj(lam1) 1;conj(lam1) exp(j*(pi-2*angle(lam1)))]\n",
83 | "Aso = P2\\Am*P2\n",
84 | "Bso = P2\\Bm\n",
85 | "Cso = Cm * P2"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "metadata": {
92 | "collapsed": true
93 | },
94 | "outputs": [],
95 | "source": []
96 | }
97 | ],
98 | "metadata": {
99 | "hide_input": false,
100 | "kernelspec": {
101 | "display_name": "Matlab",
102 | "language": "matlab",
103 | "name": "matlab"
104 | },
105 | "language_info": {
106 | "codemirror_mode": "octave",
107 | "file_extension": ".m",
108 | "help_links": [
109 | {
110 | "text": "MetaKernel Magics",
111 | "url": "https://github.com/calysto/metakernel/blob/master/metakernel/magics/README.md"
112 | }
113 | ],
114 | "mimetype": "text/x-matlab",
115 | "name": "matlab",
116 | "version": "0.8.0"
117 | },
118 | "latex_envs": {
119 | "bibliofile": "biblio.bib",
120 | "cite_by": "apalike",
121 | "current_citInitial": 1,
122 | "eqLabelWithNumbers": true,
123 | "eqNumInitial": 0
124 | }
125 | },
126 | "nbformat": 4,
127 | "nbformat_minor": 0
128 | }
129 |
--------------------------------------------------------------------------------
/.ropeproject/config.py:
--------------------------------------------------------------------------------
1 | # The default ``config.py``
2 | # flake8: noqa
3 |
4 |
5 | def set_prefs(prefs):
6 | """This function is called before opening the project"""
7 |
8 | # Specify which files and folders to ignore in the project.
9 | # Changes to ignored resources are not added to the history and
10 | # VCSs. Also they are not returned in `Project.get_files()`.
11 | # Note that ``?`` and ``*`` match all characters but slashes.
12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
14 | # '.svn': matches 'pkg/.svn' and all of its children
15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
18 | '.hg', '.svn', '_svn', '.git', '.tox']
19 |
20 | # Specifies which files should be considered python files. It is
21 | # useful when you have scripts inside your project. Only files
22 | # ending with ``.py`` are considered to be python files by
23 | # default.
24 | #prefs['python_files'] = ['*.py']
25 |
26 | # Custom source folders: By default rope searches the project
27 | # for finding source folders (folders that should be searched
28 | # for finding modules). You can add paths to that list. Note
29 | # that rope guesses project source folders correctly most of the
30 | # time; use this if you have any problems.
31 | # The folders should be relative to project root and use '/' for
32 | # separating folders regardless of the platform rope is running on.
33 | # 'src/my_source_folder' for instance.
34 | #prefs.add('source_folders', 'src')
35 |
36 | # You can extend python path for looking up modules
37 | #prefs.add('python_path', '~/python/')
38 |
39 | # Should rope save object information or not.
40 | prefs['save_objectdb'] = True
41 | prefs['compress_objectdb'] = False
42 |
43 | # If `True`, rope analyzes each module when it is being saved.
44 | prefs['automatic_soa'] = True
45 | # The depth of calls to follow in static object analysis
46 | prefs['soa_followed_calls'] = 0
47 |
48 | # If `False` when running modules or unit tests "dynamic object
49 | # analysis" is turned off. This makes them much faster.
50 | prefs['perform_doa'] = True
51 |
52 | # Rope can check the validity of its object DB when running.
53 | prefs['validate_objectdb'] = True
54 |
55 | # How many undos to hold?
56 | prefs['max_history_items'] = 32
57 |
58 | # Shows whether to save history across sessions.
59 | prefs['save_history'] = True
60 | prefs['compress_history'] = False
61 |
62 | # Set the number spaces used for indenting. According to
63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's
64 | # unit-tests use 4 spaces it is more reliable, too.
65 | prefs['indent_size'] = 4
66 |
67 | # Builtin and c-extension modules that are allowed to be imported
68 | # and inspected by rope.
69 | prefs['extension_modules'] = []
70 |
71 | # Add all standard c-extensions to extension_modules list.
72 | prefs['import_dynload_stdmods'] = True
73 |
74 | # If `True` modules with syntax errors are considered to be empty.
75 | # The default value is `False`; When `False` syntax errors raise
76 | # `rope.base.exceptions.ModuleSyntaxError` exception.
77 | prefs['ignore_syntax_errors'] = False
78 |
79 | # If `True`, rope ignores unresolvable imports. Otherwise, they
80 | # appear in the importing namespace.
81 | prefs['ignore_bad_imports'] = False
82 |
83 | # If `True`, rope will insert new module imports as
84 | # `from import ` by default.
85 | prefs['prefer_module_from_imports'] = False
86 |
87 | # If `True`, rope will transform a comma list of imports into
88 | # multiple separate import statements when organizing
89 | # imports.
90 | prefs['split_imports'] = False
91 |
92 | # If `True`, rope will remove all top-level import statements and
93 | # reinsert them at the top of the module when making changes.
94 | prefs['pull_imports_to_top'] = True
95 |
96 | # If `True`, rope will sort imports alphabetically by module name instead of
97 | # alphabetically by import statement, with from imports after normal
98 | # imports.
99 | prefs['sort_imports_alphabetically'] = False
100 |
101 | # Location of implementation of rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
102 | # In general case, you don't have to change this value, unless you're an rope expert.
103 | # Change this value to inject you own implementations of interfaces
104 | # listed in module rope.base.oi.type_hinting.providers.interfaces
105 | # For example, you can add you own providers for Django Models, or disable the search
106 | # type-hinting in a class hierarchy, etc.
107 | prefs['type_hinting_factory'] = 'rope.base.oi.type_hinting.factory.default_type_hinting_factory'
108 |
109 |
110 | def project_opened(project):
111 | """This function is called after opening the project"""
112 | # Do whatever you like here!
113 |
--------------------------------------------------------------------------------
/vibrationtesting/.ropeproject/config.py:
--------------------------------------------------------------------------------
1 | # The default ``config.py``
2 | # flake8: noqa
3 |
4 |
5 | def set_prefs(prefs):
6 | """This function is called before opening the project"""
7 |
8 | # Specify which files and folders to ignore in the project.
9 | # Changes to ignored resources are not added to the history and
10 | # VCSs. Also they are not returned in `Project.get_files()`.
11 | # Note that ``?`` and ``*`` match all characters but slashes.
12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
14 | # '.svn': matches 'pkg/.svn' and all of its children
15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
18 | '.hg', '.svn', '_svn', '.git', '.tox']
19 |
20 | # Specifies which files should be considered python files. It is
21 | # useful when you have scripts inside your project. Only files
22 | # ending with ``.py`` are considered to be python files by
23 | # default.
24 | #prefs['python_files'] = ['*.py']
25 |
26 | # Custom source folders: By default rope searches the project
27 | # for finding source folders (folders that should be searched
28 | # for finding modules). You can add paths to that list. Note
29 | # that rope guesses project source folders correctly most of the
30 | # time; use this if you have any problems.
31 | # The folders should be relative to project root and use '/' for
32 | # separating folders regardless of the platform rope is running on.
33 | # 'src/my_source_folder' for instance.
34 | #prefs.add('source_folders', 'src')
35 |
36 | # You can extend python path for looking up modules
37 | #prefs.add('python_path', '~/python/')
38 |
39 | # Should rope save object information or not.
40 | prefs['save_objectdb'] = True
41 | prefs['compress_objectdb'] = False
42 |
43 | # If `True`, rope analyzes each module when it is being saved.
44 | prefs['automatic_soa'] = True
45 | # The depth of calls to follow in static object analysis
46 | prefs['soa_followed_calls'] = 0
47 |
48 | # If `False` when running modules or unit tests "dynamic object
49 | # analysis" is turned off. This makes them much faster.
50 | prefs['perform_doa'] = True
51 |
52 | # Rope can check the validity of its object DB when running.
53 | prefs['validate_objectdb'] = True
54 |
55 | # How many undos to hold?
56 | prefs['max_history_items'] = 32
57 |
58 | # Shows whether to save history across sessions.
59 | prefs['save_history'] = True
60 | prefs['compress_history'] = False
61 |
62 | # Set the number spaces used for indenting. According to
63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's
64 | # unit-tests use 4 spaces it is more reliable, too.
65 | prefs['indent_size'] = 4
66 |
67 | # Builtin and c-extension modules that are allowed to be imported
68 | # and inspected by rope.
69 | prefs['extension_modules'] = []
70 |
71 | # Add all standard c-extensions to extension_modules list.
72 | prefs['import_dynload_stdmods'] = True
73 |
74 | # If `True` modules with syntax errors are considered to be empty.
75 | # The default value is `False`; When `False` syntax errors raise
76 | # `rope.base.exceptions.ModuleSyntaxError` exception.
77 | prefs['ignore_syntax_errors'] = False
78 |
79 | # If `True`, rope ignores unresolvable imports. Otherwise, they
80 | # appear in the importing namespace.
81 | prefs['ignore_bad_imports'] = False
82 |
83 | # If `True`, rope will insert new module imports as
84 | # `from import ` by default.
85 | prefs['prefer_module_from_imports'] = False
86 |
87 | # If `True`, rope will transform a comma list of imports into
88 | # multiple separate import statements when organizing
89 | # imports.
90 | prefs['split_imports'] = False
91 |
92 | # If `True`, rope will remove all top-level import statements and
93 | # reinsert them at the top of the module when making changes.
94 | prefs['pull_imports_to_top'] = True
95 |
96 | # If `True`, rope will sort imports alphabetically by module name instead of
97 | # alphabetically by import statement, with from imports after normal
98 | # imports.
99 | prefs['sort_imports_alphabetically'] = False
100 |
101 | # Location of implementation of rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
102 | # In general case, you don't have to change this value, unless you're an rope expert.
103 | # Change this value to inject you own implementations of interfaces
104 | # listed in module rope.base.oi.type_hinting.providers.interfaces
105 | # For example, you can add you own providers for Django Models, or disable the search
106 | # type-hinting in a class hierarchy, etc.
107 | prefs['type_hinting_factory'] = 'rope.base.oi.type_hinting.factory.default_type_hinting_factory'
108 |
109 |
110 | def project_opened(project):
111 | """This function is called after opening the project"""
112 | # Do whatever you like here!
113 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | Reporting bugs
2 | --------------
3 | If you find a bug, please open an issue on the `Github issues tracker `_.
4 | Please provide some code that reproduces the error and versions of the packages installed.
5 |
6 | Contributing code
7 | -----------------
8 | To contribute code we recommend you follow these steps:
9 |
10 | To contribute code we recommend you follow these steps:
11 |
12 | #. Fork the repository on github
13 |
14 | #. Set up travis-ci for your branch. This is actually pretty quick and easy:
15 |
16 | #. Go to travis-ci.org and Sign in with GitHub.
17 |
18 | #. Account page will show all the repositories attached to GitHub.
19 |
20 | #. Find the right repository and enable Travis CI.
21 |
22 | #. Once this is done, Travis CI will be turned-on in GitHub fork.
23 |
24 | #. Go back to the fork on GitHub, click
25 |
26 | ``Settings`` -> ``Webhooks`` -> ``updated travis-ci.org link``.
27 |
28 | #. Default or customize the options based on needs and click
29 |
30 | ``Update webhook``.
31 |
32 | #. Clone the repository to your favorite location on your drive where you want to work on it.
33 |
34 | #. To work in `developer mode `_, from a terminal (python enabled) at the top level directory inside the ``vibration testing module`` type::
35 |
36 | $ pip install -e .
37 |
38 | This will allow you to edit the code while having it pretend to be installed. Keep in mind, if you have actually installed the ``vibration testing module`` you may have a conflict. You must uninstall it and install your development version with the command above.
39 |
40 | #. If a new function is added
41 | please provide docstrings following the `Numpy standards for docstrings `_.
42 | The docstrings should contain examples to be tested.
43 |
44 | Specifically note:
45 |
46 | 1. Parameters should be listed similarly to:
47 |
48 | | filename : str
49 | | copy : bool
50 | | dtype : data-type
51 | | iterable : iterable object
52 | | shape : int or tuple of int
53 | | files : list of str
54 | | time : array_like
55 |
56 | 2. First line should be inline with the ``"""`` and brief enough to fit on one line.
57 |
58 | 3. There must be a blank line after the first line.
59 |
60 | This is not exhaustive. It just highlights some consistent errors made.
61 |
62 | #. Run the doctests regularly when you make edits.
63 |
64 | To run the doctests ``_ is needed and can be installed with ``pip install -U pytest``.
65 |
66 | To run the tests from the shell you can `cd` to the project's root directory and type::
67 |
68 | $ pytest
69 |
70 | 1. To run the tests from ``pycharm`` you can do: Run -> Edit Configurations -> Add -> python tests -> pytest Then just set the path to the project directory.
71 |
72 | 2. To run the tests from ``spyder`` see `spyder-unittest `_ before submitting a pull request. To do this, as your terminal within your clone ofthe repository::
75 |
76 | $ git pull origin
77 |
78 | #. Commit and check `travis-ci `_ tests regularly. Having a great number of changes before a commit can make tracing errors very hard. Make sure you are looking at your branch when assessing whether it's working.
79 |
80 | #. If the tests are passing, make a git pull (in your GitHub app) to assure that your code is up to date with the master branch and that your code has no conflicts with the current base. Doing this regularly ensures that your accumulated edits won't be massively in conflict with the existing code base. After that, push your branch to github and then open a pull request.
81 |
82 | #. Please provide feedback and corrections to these instructions.
83 |
84 | Instructions bellow are directed to main developers
85 | ===================================================
86 |
87 | To make distribution and release
88 | --------------------------------
89 |
90 | 1) Edit the version number in ``vibrationtesting/__init__.py``
91 | 2) Use the Makefile, ``make release``
92 |
93 | The ``conf.py`` file for the documentation pulls the version from ``__init__.py``
94 |
95 | To make a distribution (for testing or posting to github)
96 | -----------------------------------------------------------
97 |
98 | .. code-block:: bash
99 |
100 | >> make wheel
101 |
102 | To test before release
103 | ----------------------
104 |
105 | TravisCI does this for us.
106 |
107 | See `notes `_ on working in development mode.
108 |
109 | To test distribution installabilty
110 | -----------------------------------
111 | Note: these are out of date.
112 |
113 | python setup.py register -r pypitest
114 | python setup.py sdist upload -r pypitest
115 |
116 | look at https://testpypi.python.org/pypi
117 |
118 | Other information sites
119 | ------------------------
120 |
121 | `twine notes `_
122 |
123 | https://pypi.python.org/pypi/wheel
124 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Vibration Testing documentation build configuration file, created by
5 | # sphinx-quickstart on Sun Apr 9 10:02:35 2017.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #
20 | import os
21 | import sys
22 | import sphinx.environment
23 | from docutils.utils import get_source_line
24 |
25 | # Find modules and their help for inclusion in documentation
26 | sys.path.insert(0, os.path.abspath('.'))
27 | sys.path.append('../vibrationtesting')
28 | sys.path.append('../JupyterNotebooks')
29 | sys.path.append('..')
30 | sys.path.append(os.path.join(os.path.dirname(__name__), '..'))
31 |
32 | # Avoid needin to update version here for release
33 | from vibrationtesting import __version__, __title__, __author__, __license__,\
34 | __copyright__
35 |
36 | # print("license is", __version__)
37 |
38 |
39 | # -- General configuration ------------------------------------------------
40 |
41 | # If your documentation needs a minimal Sphinx version, state it here.
42 | #
43 | # needs_sphinx = '1.0'
44 |
45 | # Add any Sphinx extension module names here, as strings. They can be
46 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
47 | # ones.
48 | extensions = ['sphinx.ext.autodoc',
49 | 'numpydoc',
50 | 'sphinx.ext.doctest',
51 | 'sphinx.ext.todo',
52 | 'sphinx.ext.coverage',
53 | 'sphinx.ext.mathjax',
54 | 'sphinx.ext.ifconfig',
55 | 'sphinx.ext.viewcode',
56 | 'sphinx.ext.githubpages',
57 | 'sphinx.ext.autosectionlabel',
58 | 'matplotlib.sphinxext.plot_directive',
59 | 'nbsphinx',
60 | 'sphinx.ext.mathjax',
61 | 'IPython.sphinxext.ipython_console_highlighting',
62 | 'sphinx.ext.autosummary']
63 |
64 | # Add any paths that contain templates here, relative to this directory.
65 | templates_path = ['_templates']
66 |
67 | # The suffix(es) of source filenames.
68 | # You can specify multiple suffix as a list of string:
69 | #
70 | # source_suffix = ['.rst', '.md']
71 | source_suffix = '.rst'
72 |
73 | # The master toctree document.
74 | master_doc = 'index'
75 |
76 | numpydoc_show_class_members = False
77 |
78 | # Whether to produce plot:: directives for Examples sections that contain
79 | # import matplotlib
80 | numpydoc_use_plots = True
81 |
82 |
83 | # General information about the project.
84 | project = 'Vibration Testing'
85 | copyright = u'2017, Joseph C. Slater'
86 | author = u'Joseph C. Slater'
87 |
88 | # The version info for the project you're documenting, acts as replacement for
89 | # |version| and |release|, also used in various other places throughout the
90 | # built documents.
91 | #
92 | # The short X.Y version.
93 | version = __version__
94 | # The full version, including alpha/beta/rc tags.
95 | release = version
96 |
97 | # The language for content autogenerated by Sphinx. Refer to documentation
98 | # for a list of supported languages.
99 | #
100 | # This is also used if you do content translation via gettext catalogs.
101 | # Usually you set "language" from the command line for these cases.
102 | language = None
103 |
104 | # List of patterns, relative to source directory, that match files and
105 | # directories to ignore when looking for source files.
106 | # This patterns also effect to html_static_path and html_extra_path
107 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'readme.rst',
108 | '**.ipynb_checkpoints']
109 |
110 | # The name of the Pygments (syntax highlighting) style to use.
111 | pygments_style = 'sphinx'
112 |
113 | # If true, `todo` and `todoList` produce output, else they produce nothing.
114 | todo_include_todos = True
115 |
116 |
117 | # Block out warning from nonlocal image in main readme file of repository when
118 | # building docs in sphinx
119 | # http://stackoverflow.com/questions/12772927/specifying-an-online-image-in-sphinx-restructuredtext-format
120 | def _warn_node(self, msg, node, **kwargs):
121 | if not msg.startswith('nonlocal image URI found:'):
122 | self._warnfunc(msg, '%s:%s' % get_source_line(node), **kwargs)
123 |
124 |
125 | sphinx.environment.BuildEnvironment.warn_node = _warn_node
126 |
127 |
128 | # -- Options for HTML output ----------------------------------------------
129 |
130 | # The theme to use for HTML and HTML Help pages. See the documentation for
131 | # a list of builtin themes.
132 | #
133 | html_theme = 'alabaster'
134 |
135 |
136 | html_sidebars = {
137 | '**': [
138 | 'about.html', 'navigation.html', 'searchbox.html', 'sourcelink.html',
139 | ]
140 | }
141 |
142 |
143 | # Theme options are theme-specific and customize the look and feel of a theme
144 | # further. For a list of options available for each theme, see the
145 | # documentation.
146 | #
147 | # html_theme_options = {}
148 |
149 | # Add any paths that contain custom static files (such as style sheets) here,
150 | # relative to this directory. They are copied after the builtin static files,
151 | # so a file named "default.css" will overwrite the builtin "default.css".
152 | html_static_path = ['_static']
153 |
154 |
155 | # -- Options for HTMLHelp output ------------------------------------------
156 |
157 | # Output file base name for HTML help builder.
158 | htmlhelp_basename = 'VibrationTesting'
159 |
160 | html_theme_options = {
161 | # 'logo': 'js.jpg',
162 | 'logo_name': 'VTest',
163 | 'description': 'Vibration Testing Analysis',
164 | 'github_button': 'True',
165 | 'github_user': 'vibrationtesting',
166 | 'analytics_id': 'UA-62100376-6',
167 | }
168 |
169 |
170 | # -- Options for LaTeX output ---------------------------------------------
171 |
172 | latex_elements = {
173 | # The paper size ('letterpaper' or 'a4paper').
174 | #
175 | # 'papersize': 'letterpaper',
176 |
177 | # The font size ('10pt', '11pt' or '12pt').
178 | #
179 | # 'pointsize': '10pt',
180 |
181 | # Additional stuff for the LaTeX preamble.
182 | #
183 | # 'preamble': '',
184 |
185 | # Latex figure (float) alignment
186 | #
187 | # 'figure_align': 'htbp',
188 | }
189 |
190 | # Grouping the document tree into LaTeX files. List of tuples
191 | # (source start file, target name, title,
192 | # author, documentclass [howto, manual, or own class]).
193 | latex_documents = [
194 | (master_doc, 'VibrationTesting.tex', 'Vibration Testing Documentation',
195 | 'Joseph C. Slater', 'manual'),
196 | ]
197 |
198 |
199 | # -- Options for manual page output ---------------------------------------
200 |
201 | # One entry per manual page. List of tuples
202 | # (source start file, name, description, authors, manual section).
203 | man_pages = [
204 | (master_doc, 'vibrationtesting', 'Vibration Testing Documentation',
205 | [author], 1)
206 | ]
207 |
208 |
209 | # -- Options for Texinfo output -------------------------------------------
210 |
211 | # Grouping the document tree into Texinfo files. List of tuples
212 | # (source start file, target name, title, author,
213 | # dir menu entry, description, category)
214 | texinfo_documents = [
215 | (master_doc, 'VibrationTesting', 'Vibration Testing Documentation',
216 | author, 'VibrationTesting', 'One line description of project.',
217 | 'Miscellaneous'),
218 | ]
219 |
--------------------------------------------------------------------------------
/vibrationtesting/tests/test_vibrationtesting.py:
--------------------------------------------------------------------------------
1 | """pytest unit tests for vibrationtesting"""
2 |
3 | import numpy as np
4 | import vibrationtesting as vt
5 | import numpy.testing as nt
6 |
7 | np.set_printoptions(precision=4, suppress=True)
8 |
9 | M = np.array([[4, 0, 0],
10 | [0, 4, 0],
11 | [0, 0, 4]])
12 | Cso = np.array([[.1, 0, 0],
13 | [0, 0, 0],
14 | [0, 0, 0]])
15 | K = np.array([[8, -4, 0],
16 | [-4, 8, -4],
17 | [0, -4, 4]])
18 | omega, zeta, Psi = vt.sos_modal(M, K)
19 |
20 |
21 | #########################################################
22 | # system.py tests
23 | #########################################################
24 |
25 | def test_d2c():
26 | Ad = np.array([[0.9999, 0.0001, 0.01, 0.],
27 | [0., 0.9999, 0., 0.01],
28 | [-0.014, 0.012, 0.9999, 0.0001],
29 | [0.008, -0.014, 0.0001, 0.9999]])
30 | Bd = np.array([[0.],
31 | [0.],
32 | [0.],
33 | [0.01]])
34 | C = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
35 | D = np.array([[-0.2]])
36 | A, B, *_ = vt.d2c(Ad, Bd, C, D, 0.01)
37 | nt.assert_array_almost_equal(A,
38 | np.array([[-0.002999, 0.003999, 1.000053,
39 | -0.00006],
40 | [-0.004001, -0.002999,
41 | - 0.000023, 1.000053],
42 | [-1.400083, 1.200148,
43 | - 0.003, 0.003999],
44 | [0.800075, -1.400143, 0.006,
45 | -0.002999]]))
46 |
47 | # def test_c2d():
48 |
49 |
50 | # def test_ssfrf():
51 |
52 |
53 | # def test_sos_frf():
54 |
55 |
56 | # def test_so2ss():
57 |
58 | # Damp- no test
59 |
60 | def test_sos_modal():
61 | M = np.array([[4, 0, 0],
62 | [0, 4, 0],
63 | [0, 0, 4]])
64 | K = np.array([[8, -4, 0],
65 | [-4, 8, -4],
66 | [0, -4, 4]])
67 | omega, zeta, Psi = vt.sos_modal(M, K, K / 10)
68 | nt.assert_array_almost_equal(
69 | omega, np.array([0.445042, 1.24698, 1.801938]))
70 | K_diag = np.array([[0.198062, 0., -0.],
71 | [0., 1.554958, -0.],
72 | [-0., -0., 3.24698]])
73 |
74 | nt.assert_array_almost_equal(Psi.T@K@Psi, K_diag)
75 |
76 | K2 = K - np.eye(K.shape[0])@M * (Psi.T@K@Psi)[0, 0]
77 | omega, zeta, Psi = vt.sos_modal(M, K2)
78 | nt.assert_array_almost_equal(omega, np.array([0., 1.164859, 1.746115]))
79 | Psi_true = np.array([[-0.163993, 0.368488, -0.295505],
80 | [-0.295505, 0.163993, 0.368488],
81 | [-0.368488, -0.295505, -0.163993]])
82 | nt.assert_array_almost_equal(Psi_true, Psi)
83 |
84 | # Diagonalizes?
85 | Psi_true = np.array([[0., 0., -0.],
86 | [-0., 1.356896, 0.],
87 | [-0., 0., 3.048917]])
88 | nt.assert_array_almost_equal(Psi.T@K2@Psi, Psi_true)
89 |
90 | # How about non-proportional damping
91 |
92 | C = K / 10
93 | C[0, 0] = 2 * C[0, 0]
94 | omega, zeta, Psi = vt.sos_modal(M, K2, C)
95 |
96 | # Damping matrix cannot be completely diagonalized.
97 |
98 | nt.assert_array_almost_equal(omega, np.array([0., 1.164859, 1.746115]))
99 |
100 | nt.assert_array_almost_equal(zeta, np.array([0., 0.113371, 0.112981]))
101 | C_diag = np.array([[0.041321, -0.048343, 0.038768],
102 | [-0.048343, 0.264123, -0.087112],
103 | [0.038768, -0.087112, 0.394556]])
104 | nt.assert_array_almost_equal(C_diag, Psi.T@C@Psi)
105 |
106 |
107 | def test_serep():
108 | M = np.array([[4, 0, 0],
109 | [0, 4, 0],
110 | [0, 0, 4]])
111 | K = np.array([[8, -4, 0],
112 | [-4, 8, -4],
113 | [0, -4, 4]])
114 | retained = np.array([[1, 2]])
115 | Mred, Kred, T, truncated_dofs = vt.serep(M, K, retained)
116 | Mr_soln = np.array([[16.98791841, -16.19566936],
117 | [-16.19566936, 24.19566936]])
118 | Kr_soln = np.array([[20.98791841, -12.98791841],
119 | [-12.98791841, 10.21983253]])
120 | nt.assert_array_almost_equal(Mred, Mr_soln)
121 | nt.assert_array_almost_equal(Kred, Kr_soln)
122 |
123 |
124 | # def test_guyan():
125 |
126 |
127 | # def test_mode_expansion_from_model():
128 |
129 |
130 | # def improved_reduction(when written):
131 |
132 |
133 | # def test_model_correction_direct():
134 |
135 |
136 | # slice- no test
137 |
138 |
139 | # def test_rsolve():
140 |
141 |
142 | # def test_real_modes():
143 |
144 |
145 | def test_ss_modal():
146 | Bt = np.array([[1], [0], [0]])
147 | Ca = np.array([[1, 0, 0]])
148 | Cd = Cv = np.zeros_like(Ca)
149 | A, B, C, D = vt.so2ss(M, Cso, K, Bt, Cd, Cv, Ca)
150 | Am, Bm, Cm, Dm, eigenvalues, modes = vt.ss_modal(A, B, C, D)
151 | nt.assert_array_almost_equal(Am, np.array(
152 | [[-0.0013 + 0.445j, 0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j,
153 | 0.0000 + 0.j, 0.0000 + 0.j],
154 | [0.0000 + 0.j, -0.0013 - 0.445j, 0.0000 + 0.j, 0.0000 + 0.j,
155 | 0.0000 + 0.j, 0.0000 + 0.j],
156 | [0.0000 + 0.j, 0.0000 + 0.j, -0.0068 + 1.247j, 0.0000 + 0.j,
157 | 0.0000 + 0.j, 0.0000 + 0.j],
158 | [0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j, -0.0068 - 1.247j,
159 | 0.0000 + 0.j, 0.0000 + 0.j],
160 | [0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j,
161 | -0.0044 + 1.8019j, 0.0000 + 0.j],
162 | [0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j, 0.0000 + 0.j,
163 | 0.0000 + 0.j, -0.0044 - 1.8019j]]), decimal=3)
164 | nt.assert_array_almost_equal(Cm, np.array(
165 | [[0.0594 - 0.0001j, 0.0594 + 0.0001j, 0.0039 - 0.717j, 0.0039 + 0.717j,
166 | 0.0241 - 0.9307j, 0.0241 + 0.9307j]]), decimal=3)
167 |
168 |
169 | def test_sos_modal_non_rigid():
170 | omega, zeta, Psi = vt.sos_modal(M, K, K / 10)
171 | nt.assert_array_almost_equal(omega, np.array(
172 | [0.445, 1.247, 1.8019]), decimal=3)
173 |
174 |
175 | def test_sos_modal_non_rigid_diag():
176 | omega, zeta, Psi = vt.sos_modal(M, K, K / 10)
177 | nt.assert_array_almost_equal(Psi.T@K@Psi,
178 | np.array([[0.1981, 0., -0.],
179 | [0., 1.555, -0.],
180 | [-0., -0., 3.247]]),
181 | decimal=3)
182 |
183 |
184 | def test_sos_modal_rigid():
185 | omega, zeta, Psi = vt.sos_modal(M, K)
186 | K2 = K - np.eye(K.shape[0])@M * (Psi.T@K@Psi)[0, 0]
187 | omega, zeta, Psi = vt.sos_modal(M, K2)
188 | nt.assert_array_almost_equal(omega, np.array(
189 | [0., 1.1649, 1.7461]), decimal=3)
190 |
191 |
192 | def test_sos_modal_rigid_diag():
193 | omega, zeta, Psi = vt.sos_modal(M, K)
194 | K2 = K - np.eye(K.shape[0])@M * (Psi.T@K@Psi)[0, 0]
195 | omega, zeta, Psi = vt.sos_modal(M, K2)
196 | nt.assert_array_almost_equal(Psi,
197 | np.array([[-0.164, 0.3685, -0.2955],
198 | [-0.2955, 0.164, 0.3685],
199 | [-0.3685, -0.2955, -0.164]]),
200 | decimal=3)
201 |
202 |
203 | def test_sos_modal_non_proportional():
204 | omega, zeta, Psi = vt.sos_modal(M, K)
205 | K2 = K - np.eye(K.shape[0])@M * (Psi.T@K@Psi)[0, 0]
206 | C = K / 10
207 | C[0, 0] = 2 * C[0, 0]
208 | omega, zeta, Psi = vt.sos_modal(M, K2, C)
209 | nt.assert_array_almost_equal(omega,
210 | np.array([0., 1.1649, 1.7461]), decimal=3)
211 | nt.assert_array_almost_equal(zeta,
212 | np.array([0., 0.1134, 0.113]), decimal=3)
213 | nt.assert_array_almost_equal(Psi.T@C@Psi,
214 | np.array([[0.0413, -0.0483, 0.0388],
215 | [-0.0483, 0.2641, -0.0871],
216 | [0.0388, -0.0871, 0.3946]]),
217 | decimal=3)
218 |
--------------------------------------------------------------------------------
/old/conf.py-old:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Vibration Testing Tools documentation build configuration file, created by
5 | # sphinx-quickstart on Thu May 14 17:13:49 2015.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | import sys
17 | import os
18 | import shlex
19 |
20 | # If extensions (or modules to document with autodoc) are in another directory,
21 | # add these directories to sys.path here. If the directory is relative to the
22 | # documentation root, use os.path.abspath to make it absolute, like shown here.
23 | #sys.path.insert(0, os.path.abspath('.'))
24 |
25 | # -- General configuration ------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | #needs_sphinx = '1.0'
29 |
30 | # Add any Sphinx extension module names here, as strings. They can be
31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32 | # ones.
33 | extensions = [
34 | 'sphinx.ext.autodoc',
35 | 'sphinx.ext.doctest',
36 | 'sphinx.ext.todo',
37 | 'sphinx.ext.coverage',
38 | 'sphinx.ext.mathjax',
39 | 'sphinx.ext.viewcode',
40 | ]
41 |
42 | # Add any paths that contain templates here, relative to this directory.
43 | templates_path = ['_templates']
44 |
45 | # The suffix(es) of source filenames.
46 | # You can specify multiple suffix as a list of string:
47 | # source_suffix = ['.rst', '.md']
48 | source_suffix = '.rst'
49 |
50 | # The encoding of source files.
51 | #source_encoding = 'utf-8-sig'
52 |
53 | # The master toctree document.
54 | master_doc = 'index'
55 |
56 | # General information about the project.
57 | project = 'Vibration Testing Tools'
58 | copyright = '2015, Joseph C. Slater'
59 | author = 'Joseph C. Slater'
60 |
61 | # The version info for the project you're documenting, acts as replacement for
62 | # |version| and |release|, also used in various other places throughout the
63 | # built documents.
64 | #
65 | # The short X.Y version.
66 | version = '0.1'
67 | # The full version, including alpha/beta/rc tags.
68 | release = '0.1'
69 |
70 | # The language for content autogenerated by Sphinx. Refer to documentation
71 | # for a list of supported languages.
72 | #
73 | # This is also used if you do content translation via gettext catalogs.
74 | # Usually you set "language" from the command line for these cases.
75 | language = None
76 |
77 | # There are two options for replacing |today|: either, you set today to some
78 | # non-false value, then it is used:
79 | #today = ''
80 | # Else, today_fmt is used as the format for a strftime call.
81 | #today_fmt = '%B %d, %Y'
82 |
83 | # List of patterns, relative to source directory, that match files and
84 | # directories to ignore when looking for source files.
85 | exclude_patterns = ['_build']
86 |
87 | # The reST default role (used for this markup: `text`) to use for all
88 | # documents.
89 | #default_role = None
90 |
91 | # If true, '()' will be appended to :func: etc. cross-reference text.
92 | #add_function_parentheses = True
93 |
94 | # If true, the current module name will be prepended to all description
95 | # unit titles (such as .. function::).
96 | #add_module_names = True
97 |
98 | # If true, sectionauthor and moduleauthor directives will be shown in the
99 | # output. They are ignored by default.
100 | #show_authors = False
101 |
102 | # The name of the Pygments (syntax highlighting) style to use.
103 | pygments_style = 'sphinx'
104 |
105 | # A list of ignored prefixes for module index sorting.
106 | #modindex_common_prefix = []
107 |
108 | # If true, keep warnings as "system message" paragraphs in the built documents.
109 | #keep_warnings = False
110 |
111 | # If true, `todo` and `todoList` produce output, else they produce nothing.
112 | todo_include_todos = True
113 |
114 |
115 | # -- Options for HTML output ----------------------------------------------
116 |
117 | # The theme to use for HTML and HTML Help pages. See the documentation for
118 | # a list of builtin themes.
119 | html_theme = 'alabaster'
120 |
121 | # Theme options are theme-specific and customize the look and feel of a theme
122 | # further. For a list of options available for each theme, see the
123 | # documentation.
124 | #html_theme_options = {}
125 |
126 | # Add any paths that contain custom themes here, relative to this directory.
127 | #html_theme_path = []
128 |
129 | # The name for this set of Sphinx documents. If None, it defaults to
130 | # " v documentation".
131 | #html_title = None
132 |
133 | # A shorter title for the navigation bar. Default is the same as html_title.
134 | #html_short_title = None
135 |
136 | # The name of an image file (relative to this directory) to place at the top
137 | # of the sidebar.
138 | #html_logo = None
139 |
140 | # The name of an image file (within the static path) to use as favicon of the
141 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
142 | # pixels large.
143 | #html_favicon = None
144 |
145 | # Add any paths that contain custom static files (such as style sheets) here,
146 | # relative to this directory. They are copied after the builtin static files,
147 | # so a file named "default.css" will overwrite the builtin "default.css".
148 | html_static_path = ['_static']
149 |
150 | # Add any extra paths that contain custom files (such as robots.txt or
151 | # .htaccess) here, relative to this directory. These files are copied
152 | # directly to the root of the documentation.
153 | #html_extra_path = []
154 |
155 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
156 | # using the given strftime format.
157 | #html_last_updated_fmt = '%b %d, %Y'
158 |
159 | # If true, SmartyPants will be used to convert quotes and dashes to
160 | # typographically correct entities.
161 | #html_use_smartypants = True
162 |
163 | # Custom sidebar templates, maps document names to template names.
164 | #html_sidebars = {}
165 |
166 | # Additional templates that should be rendered to pages, maps page names to
167 | # template names.
168 | #html_additional_pages = {}
169 |
170 | # If false, no module index is generated.
171 | #html_domain_indices = True
172 |
173 | # If false, no index is generated.
174 | #html_use_index = True
175 |
176 | # If true, the index is split into individual pages for each letter.
177 | #html_split_index = False
178 |
179 | # If true, links to the reST sources are added to the pages.
180 | #html_show_sourcelink = True
181 |
182 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
183 | #html_show_sphinx = True
184 |
185 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
186 | #html_show_copyright = True
187 |
188 | # If true, an OpenSearch description file will be output, and all pages will
189 | # contain a tag referring to it. The value of this option must be the
190 | # base URL from which the finished HTML is served.
191 | #html_use_opensearch = ''
192 |
193 | # This is the file name suffix for HTML files (e.g. ".xhtml").
194 | #html_file_suffix = None
195 |
196 | # Language to be used for generating the HTML full-text search index.
197 | # Sphinx supports the following languages:
198 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
199 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr'
200 | #html_search_language = 'en'
201 |
202 | # A dictionary with options for the search language support, empty by default.
203 | # Now only 'ja' uses this config value
204 | #html_search_options = {'type': 'default'}
205 |
206 | # The name of a javascript file (relative to the configuration directory) that
207 | # implements a search results scorer. If empty, the default will be used.
208 | #html_search_scorer = 'scorer.js'
209 |
210 | # Output file base name for HTML help builder.
211 | htmlhelp_basename = 'VibrationTestingToolsdoc'
212 |
213 | # -- Options for LaTeX output ---------------------------------------------
214 |
215 | latex_elements = {
216 | # The paper size ('letterpaper' or 'a4paper').
217 | #'papersize': 'letterpaper',
218 |
219 | # The font size ('10pt', '11pt' or '12pt').
220 | #'pointsize': '10pt',
221 |
222 | # Additional stuff for the LaTeX preamble.
223 | #'preamble': '',
224 |
225 | # Latex figure (float) alignment
226 | #'figure_align': 'htbp',
227 | }
228 |
229 | # Grouping the document tree into LaTeX files. List of tuples
230 | # (source start file, target name, title,
231 | # author, documentclass [howto, manual, or own class]).
232 | latex_documents = [
233 | (master_doc, 'VibrationTestingTools.tex', 'Vibration Testing Tools Documentation',
234 | 'Joseph C. Slater', 'manual'),
235 | ]
236 |
237 | # The name of an image file (relative to this directory) to place at the top of
238 | # the title page.
239 | #latex_logo = None
240 |
241 | # For "manual" documents, if this is true, then toplevel headings are parts,
242 | # not chapters.
243 | #latex_use_parts = False
244 |
245 | # If true, show page references after internal links.
246 | #latex_show_pagerefs = False
247 |
248 | # If true, show URL addresses after external links.
249 | #latex_show_urls = False
250 |
251 | # Documents to append as an appendix to all manuals.
252 | #latex_appendices = []
253 |
254 | # If false, no module index is generated.
255 | #latex_domain_indices = True
256 |
257 |
258 | # -- Options for manual page output ---------------------------------------
259 |
260 | # One entry per manual page. List of tuples
261 | # (source start file, name, description, authors, manual section).
262 | man_pages = [
263 | (master_doc, 'vibrationtestingtools', 'Vibration Testing Tools Documentation',
264 | [author], 1)
265 | ]
266 |
267 | # If true, show URL addresses after external links.
268 | #man_show_urls = False
269 |
270 |
271 | # -- Options for Texinfo output -------------------------------------------
272 |
273 | # Grouping the document tree into Texinfo files. List of tuples
274 | # (source start file, target name, title, author,
275 | # dir menu entry, description, category)
276 | texinfo_documents = [
277 | (master_doc, 'VibrationTestingTools', 'Vibration Testing Tools Documentation',
278 | author, 'VibrationTestingTools', 'One line description of project.',
279 | 'Miscellaneous'),
280 | ]
281 |
282 | # Documents to append as an appendix to all manuals.
283 | #texinfo_appendices = []
284 |
285 | # If false, no module index is generated.
286 | #texinfo_domain_indices = True
287 |
288 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
289 | #texinfo_show_urls = 'footnote'
290 |
291 | # If true, do not generate a @detailmenu in the "Top" node's menu.
292 | #texinfo_no_detailmenu = False
293 |
--------------------------------------------------------------------------------
/vibrationtesting/sparsematriceshandler.py:
--------------------------------------------------------------------------------
1 | """
2 | System expansion, reduction, and corrections functions for Sparse systems.
3 |
4 | @author: Joseph C. Slater and Sainag Immidisetty
5 | """
6 | __license__ = "Joseph C. Slater"
7 |
8 | __docformat__ = 'reStructuredText'
9 |
10 |
11 | #import math
12 | import numpy as np
13 | #import scipy.signal as sig
14 | import scipy.linalg as la
15 | import scipy.sparse.linalg as spla
16 | from scipy.sparse import lil_matrix
17 | #from scipy.sparse import csr_matrix
18 | import scipy.sparse as sps
19 |
20 |
21 | def sos_modal_forsparse(M, K, C=False, damp_diag=0.03, shift=1):
22 | r"""Eigen analysis of proportionally damped system.
23 |
24 | Optimally find mass normalized mode shapes and natural frequencies
25 | of a system modelled by :math:`M\ddot{x}+Kx=0`.
26 |
27 | If provided, obtain damping ratios presuming :math:`C` can be decoupled.
28 |
29 | Provides a warning if diagonalization of damping matrix fails worse than
30 | relative error of `damp_diag`.
31 |
32 | Parameters
33 | ----------
34 | M, K : float arrays
35 | Mass and stiffness matrices
36 | C : float array, optional
37 | Damping matrix
38 | damp_diag : float, optional
39 | Maximum amount of off-diagonal error allowed in assuming C can be
40 | diagonalized
41 | shift : float, optional
42 | Shift used in eigensolution. Should be approximately equal to the first
43 | non-zero eigenvalue.
44 |
45 | Returns
46 | -------
47 | omega : float array (1xN)
48 | Vector of natural frequencies (rad/sec)
49 | zeta : float array (1xN)
50 | Vector of damping ratios
51 | Psi : float array (NxN)
52 | Matrix of mass normalized mode shapes by column
53 |
54 |
55 | Examples
56 | --------
57 |
58 |
59 | """
60 |
61 | #Kdiag = K.diagonal().reshape(-1,1)
62 |
63 | #Mdiag = M.diagonal().reshape(-1,1)
64 |
65 | #minvals = np.sort((Kdiag/Mdiag),axis=0)
66 |
67 | #shift = minvals[min(7,len(minvals))]
68 |
69 | #shift = shift[0]
70 |
71 | #K = ((K.tocsr() + (K.T).tocsr()).tolil()) * 0.5 + shift * ((M.tocsr() + (M.T).tocsr()).tolil()) * 0.5
72 |
73 | #M = ((M.tocsr() + (M.T).tocsr()).tolil()) * 0.5
74 |
75 | K = lil_matrix(K)
76 |
77 | M = lil_matrix(M)
78 |
79 | K = K + M * shift
80 |
81 | [lam, Psi] = la.eigh(K.toarray(), M.toarray())
82 |
83 | lam = lam.reshape(-1,1)
84 |
85 | omega = np.sqrt(np.abs(lam - shift))
86 |
87 | omega = omega.reshape(-1,)
88 |
89 | norms = np.diag(1.0 / np.sqrt(np.diag(Psi.T@M@Psi)))
90 |
91 | Psi = Psi @ norms
92 |
93 | zeta = np.zeros_like(omega)
94 |
95 | if C is not False:
96 | diagonalized_C = Psi.T@C@Psi
97 |
98 | diagonal_C = np.diag(diagonalized_C)
99 |
100 | if min(omega) > 1e-5:
101 | zeta = diagonal_C / 2 / omega # error if omega = 0
102 | max_off_diagonals = np.amax(np.abs(diagonalized_C
103 | - np.diag(diagonal_C)), axis=0)
104 | # error if no damping
105 | damp_error = np.max(max_off_diagonals / diagonal_C)
106 | else:
107 | zeta = np.zeros_like(omega)
108 | damp_error = np.zeros_like(omega)
109 | de_diag_C = diagonalized_C - np.diag(diagonal_C)
110 | for mode_num, omega_i in enumerate(omega):
111 | if omega[mode_num] > 1e-5:
112 | zeta[mode_num] = diagonal_C[mode_num] / 2 / omega_i
113 | damp_error = (np.max(np.abs(de_diag_C[:, mode_num]))
114 | / diagonal_C[mode_num])
115 |
116 | if damp_error > damp_diag:
117 | print('Damping matrix cannot be completely diagonalized.')
118 | print('Off diagonal error of {:4.0%}.'.format(damp_error))
119 |
120 | return omega, zeta, Psi
121 |
122 |
123 | def guyan_forsparse(M, K, master=None, fraction=None):
124 | r"""Guyan reduced model.
125 |
126 | Applies Guyan Reductions to second order system of equations of the form
127 |
128 | .. math:: M \ddot{x} + K x = 0
129 |
130 | which are reduced to the form
131 |
132 | .. math:: M_r \ddot{x}_m + K_r x_m = 0
133 |
134 | where
135 |
136 | .. math::
137 |
138 | x = T x_m
139 |
140 | M_r= T^T M T
141 |
142 | K_r= T^T K T
143 |
144 | Parameters
145 | ----------
146 | M, K : float arrays
147 | Mass and Stiffness matrices
148 | master : float array or list, optional
149 | List of retained degrees of freedom (0 indexing)
150 | fraction : float, optional
151 | Fraction of degrees of freedom (0< `fraction` <1.0) to retain in model.
152 | If both master and fraction are neglected, fraction is set to 0.25.
153 |
154 | Returns
155 | -------
156 | Mred, Kred, T : float arrays
157 | Reduced Mass matric, Reduced Stiffness matrix, Transformation matrix
158 | master_dofs : int list
159 | List of master degrees of freedom (0 indexing)
160 | truncated_dofs : int list
161 | List of truncated degrees of freedom (0 indexing)
162 |
163 | Examples
164 | --------
165 |
166 | Notes
167 | -----
168 | Reduced coordinate system forces can be obtained by `Fr = T.T @ F`.
169 |
170 | Reduced damping matrix can be obtained using `Cr = T.T @ C @ T`.
171 |
172 | If mode shapes are obtained for the reduced system, full system mode shapes
173 | are `phi = T @ phi_r`.
174 |
175 | Code is not as efficient as possible. Using block submatrices would be
176 | more efficient.
177 |
178 | """
179 |
180 |
181 | if master is None:
182 | if fraction is None:
183 | fraction = 0.25
184 |
185 | ratios = np.diag(M) / np.diag(K)
186 | ranked = [i[0] for i in sorted(enumerate(ratios), key=lambda x: x[1])]
187 | thresh = int(fraction * ratios.size)
188 | if (thresh >= ratios.size) or thresh == 0:
189 | print("Can't keep", thresh, 'DOFs.')
190 | print("Fraction of", fraction, "is too low or too high.")
191 | return 0, 0, 0, 0, 0
192 |
193 | master = ranked[-thresh:]
194 |
195 | master = np.array(master)
196 |
197 | ncoord = M.shape[0]
198 |
199 | i = np.arange(0, ncoord)
200 |
201 | i = i.reshape(1,-1)
202 |
203 | i = i + np.ones((1,i.shape[1]),int)
204 |
205 | lmaster = master.shape[1]
206 |
207 | i[0,master-1] = np.transpose(np.zeros((lmaster,1)))
208 |
209 | i = np.sort((i), axis =1)
210 |
211 | slave = i[0,lmaster + 0:ncoord]
212 |
213 | K= lil_matrix(K)
214 |
215 | slave = slave.reshape(1,-1)
216 |
217 | master = master-np.ones((1,master.shape[0]),int)
218 |
219 | master = master.ravel()
220 |
221 | slave = slave - np.ones((1,slave.shape[0]),int)
222 |
223 | slave = slave.ravel()
224 |
225 | kss = slice_forSparse(K, slave, slave)
226 |
227 | ksm = slice_forSparse(K, slave, master)
228 |
229 | T= np.zeros((len(master)+len(slave), len(master)))
230 |
231 | T= lil_matrix(T)
232 |
233 | T[master,:lmaster] = sps.eye(lmaster,lmaster)
234 |
235 | T[slave,0:lmaster]=spla.spsolve(-kss,ksm)
236 |
237 | Mred = T.T * M * T
238 |
239 | Kred = T.T * K * T
240 |
241 | return Mred, Kred, master
242 |
243 |
244 | def mode_expansion_from_model_forsparse(Psi, omega, M, K, measured):
245 | r"""Deflection extrapolation to full FEM model coordinates, matrix method.
246 |
247 | Provided an equation of the form:
248 |
249 | :math:`\begin{pmatrix}-\begin{bmatrix}M_{mm}&M_{mu}\\ M_{um}&M_{uu}
250 | \end{bmatrix} \omega_i^2
251 | +\begin{bmatrix}K_{mm}&K_{mu}\\ K_{um}&K_{uu}\end{bmatrix}\end{pmatrix}`
252 | :math:`\begin{bmatrix}\Psi_{i_m}\\ \Psi_{i_u}\end{bmatrix}= 0`
253 |
254 | Where:
255 |
256 | - :math:`M` and :math:`K` are the mass and stiffness matrices, likely from
257 | a finite element model
258 | - :math:`\Psi_i` and :math:`\omega_i` represent a mode/frequency pair
259 | - subscripts :math:`m` and :math:`u` represent measure and unmeasured
260 | of the mode
261 |
262 | Determines the unknown portion of the mode (or operational deflection)
263 | shape, :math:`\Psi_{i_u}` by
264 | direct algebraic solution, aka
265 |
266 | :math:`\Psi_{i_u} = - (K_{uu}- M_{ss} \omega_i^2) ^{-1}
267 | (K_{um}-M_{um}\omega_i^2)\Psi_{i_m}`
268 |
269 | Parameters
270 | ----------
271 | Psi : float array
272 | mode shape or shapes, 2-D array columns of which are mode shapes
273 | omega : float or 1-D float array
274 | natural (or driving) frequencies
275 | M, K : float arrays
276 | Mass and Stiffness matrices
277 | measured : float or integer array or list
278 | List of measured degrees of freedom (0 indexed)
279 |
280 | Returns
281 | -------
282 | Psi_full : float array
283 | Complete mode shape
284 |
285 | Examples
286 | --------
287 | >>> import vibrationtesting as vt
288 | >>> M = np.array([[4, 0, 0],
289 | ... [0, 4, 0],
290 | ... [0, 0, 4]])
291 | >>> K = np.array([[8, -4, 0],
292 | ... [-4, 8, -4],
293 | ... [0, -4, 4]])
294 | >>> measured = np.array([[0, 2]])
295 | >>> omega, zeta, Psi = vt.sos_modal(M, K)
296 | >>> Psi_measured = np.array([[-0.15], [-0.37]])
297 | >>> Psi_full = vt.mode_expansion_from_model(Psi_measured, omega[0], M, K,
298 | ... measured)
299 | >>> print(np.hstack((Psi[:,0].reshape(-1,1), Psi_full)))
300 | [[-0.164 -0.15 ]
301 | [-0.2955 0.2886]
302 | [-0.3685 -0.37 ]]
303 |
304 | Notes
305 | -----
306 | .. seealso:: incomplete multi-mode update. Would require each at a
307 | different frequency.
308 |
309 | """
310 |
311 |
312 | measured = measured.reshape(-1) # retained dofs
313 | num_measured = len(measured)
314 | ndof = int(M.shape[0]) # length(M);
315 | unmeasured_dofs = list(set(np.arange(ndof)) - set(measured))
316 | num_unmeasured = len(unmeasured_dofs)
317 |
318 | M= lil_matrix(M)
319 |
320 | K= lil_matrix(K)
321 |
322 | Muu = slice_forSparse(M, unmeasured_dofs, unmeasured_dofs)
323 |
324 | Kuu = slice_forSparse(K, unmeasured_dofs, unmeasured_dofs)
325 |
326 | Mum = slice_forSparse(M, unmeasured_dofs, measured)
327 |
328 | Kum = slice_forSparse(K, unmeasured_dofs, measured)
329 |
330 | if isinstance(omega, float):
331 | omega = np.array(omega).reshape(1)
332 |
333 | Psi_full = np.zeros((num_measured + num_unmeasured, Psi.shape[1]))
334 | Psi_full[measured] = Psi
335 |
336 | for i, omega_n in enumerate(omega):
337 | Psi_i = Psi[:, i].reshape(-1, 1)
338 | Psi_unmeasured = la.solve((Kuu - Muu * omega_n**2),
339 | (Kum - Mum * omega_n**2)@Psi_i)
340 | Psi_unmeasured = Psi_unmeasured.reshape(-1, )
341 | Psi_full[unmeasured_dofs, i] = Psi_unmeasured
342 | # Psi_full = Psi_full.reshape(-1, 1)
343 | return Psi_full
344 |
345 | def slice_forSparse(Matrix, a, b):
346 | """
347 |
348 | Parameters
349 | ----------
350 | Matrix : float array
351 | Arbitrary array
352 | a, b : int lists or arrays
353 | list of rows and columns to be selected from `Matrix`
354 |
355 | Returns
356 | -------
357 | Matrix : float array
358 |
359 | """
360 | Maa = Matrix[a,:].toarray()
361 |
362 | Maa = Maa[:, a]
363 |
364 | Mab = Matrix[a,:].toarray()
365 |
366 | Mab = Mab[:, b]
367 |
368 |
369 | if(np.array_equiv(a,b)):
370 | return Maa
371 |
372 | else:
373 | return Mab
374 |
--------------------------------------------------------------------------------
/JupyterNotebooks/Final_Solution.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 99,
6 | "metadata": {
7 | "collapsed": false
8 | },
9 | "outputs": [
10 | {
11 | "name": "stdout",
12 | "output_type": "stream",
13 | "text": [
14 | "The autoreload extension is already loaded. To reload it, use:\n",
15 | " %reload_ext autoreload\n"
16 | ]
17 | }
18 | ],
19 | "source": [
20 | "%load_ext autoreload\n",
21 | "%autoreload 2\n",
22 | "%matplotlib inline\n",
23 | "sp.set_printoptions(precision=3)\n",
24 | "\n",
25 | "import control as ctrl\n",
26 | "import matplotlib.pyplot as plt\n",
27 | "import scipy as sp\n",
28 | "import scipy.linalg as la\n",
29 | "import vibrationtesting as vt\n",
30 | "import numpy.linalg as lanp"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": 4,
36 | "metadata": {
37 | "collapsed": false
38 | },
39 | "outputs": [
40 | {
41 | "name": "stdout",
42 | "output_type": "stream",
43 | "text": [
44 | "Help on function so2ss in module vibrationtesting.signal:\n",
45 | "\n",
46 | "so2ss(M, C, K, Bt, Cd, Cv, Ca)\n",
47 | " returns A, B, C, D\n",
48 | " Given second order linear matrix equation of the form \n",
49 | " :math:`M\\ddot{x} + C \\dot{x} + K x= Bt u`\n",
50 | " and\n",
51 | " :math:`y = Cd x + + Cv \\dot{x} + Ca\\ddot{x}`\n",
52 | " returns the state space form equations\n",
53 | " :math:`\\dot{z} = A z + B u`\n",
54 | " :math:`y = C z + D u`\n",
55 | "\n"
56 | ]
57 | }
58 | ],
59 | "source": [
60 | "help(vt.so2ss)"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 59,
66 | "metadata": {
67 | "collapsed": true
68 | },
69 | "outputs": [],
70 | "source": [
71 | "M = np.array([[1,0],[0,1]])\n",
72 | "C = np.array([[0.4, -.1],[-.1,.2]])\n",
73 | "K = np.array([[2,-1],[-1,2]])\n",
74 | "Cd = sp.eye(2)\n",
75 | "Cv = Cd*0\n",
76 | "Ca = Cv\n",
77 | "Bt = np.array([[1],[0]])"
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": 62,
83 | "metadata": {
84 | "collapsed": false
85 | },
86 | "outputs": [],
87 | "source": [
88 | "A, B, Css, D = vt.so2ss(M, C, K, Bt, Cd, Cv, Ca)"
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": 63,
94 | "metadata": {
95 | "collapsed": false
96 | },
97 | "outputs": [
98 | {
99 | "name": "stdout",
100 | "output_type": "stream",
101 | "text": [
102 | "A = \n",
103 | "[[ 0. 0. 1. 0. ]\n",
104 | " [ 0. 0. 0. 1. ]\n",
105 | " [-2. 1. -0.4 0.1]\n",
106 | " [ 1. -2. 0.1 -0.2]],\n",
107 | " B = \n",
108 | "[[ 0.]\n",
109 | " [ 0.]\n",
110 | " [ 1.]\n",
111 | " [ 0.]],\n",
112 | " C = \n",
113 | "[[ 1. 0. 0. 0.]\n",
114 | " [ 0. 1. 0. 0.]],\n",
115 | " D = \n",
116 | "[[ 0.]\n",
117 | " [ 0.]]\n"
118 | ]
119 | }
120 | ],
121 | "source": [
122 | "print('A = \\n{},\\n B = \\n{},\\n C = \\n{},\\n D = \\n{}'.format(A, B, Css, D))"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": 64,
128 | "metadata": {
129 | "collapsed": true
130 | },
131 | "outputs": [],
132 | "source": [
133 | "e, vec = la.eig(A)"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": 65,
139 | "metadata": {
140 | "collapsed": false
141 | },
142 | "outputs": [
143 | {
144 | "name": "stdout",
145 | "output_type": "stream",
146 | "text": [
147 | "............... Eigenvalue ........... Damping Frequency\n",
148 | "--------[re]---------[im]--------[abs]----------------------[Hz]\n",
149 | " -0.200 +1.716 1.728 0.116 0.275\n",
150 | " -0.200 -1.716 1.728 0.116 0.275\n",
151 | " -0.100 +0.998 1.003 0.100 0.160\n",
152 | " -0.100 -0.998 1.003 0.100 0.160\n"
153 | ]
154 | }
155 | ],
156 | "source": [
157 | "vt.damp(A)"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": 66,
163 | "metadata": {
164 | "collapsed": false,
165 | "scrolled": true
166 | },
167 | "outputs": [
168 | {
169 | "name": "stdout",
170 | "output_type": "stream",
171 | "text": [
172 | "[[-0.041-0.354j]\n",
173 | " [ 0.101+0.338j]\n",
174 | " [ 0.615+0.j ]\n",
175 | " [-0.600+0.106j]]\n"
176 | ]
177 | }
178 | ],
179 | "source": [
180 | "print(vec[:,0:1])"
181 | ]
182 | },
183 | {
184 | "cell_type": "code",
185 | "execution_count": 67,
186 | "metadata": {
187 | "collapsed": false
188 | },
189 | "outputs": [
190 | {
191 | "name": "stdout",
192 | "output_type": "stream",
193 | "text": [
194 | "[[-0.041-0.354j -0.041+0.354j -0.100-0.489j -0.100+0.489j]\n",
195 | " [ 0.101+0.338j 0.101-0.338j -0.050-0.497j -0.050+0.497j]\n",
196 | " [ 0.615+0.j 0.615-0.j 0.498-0.05j 0.498+0.05j ]\n",
197 | " [-0.600+0.106j -0.600-0.106j 0.501+0.j 0.501-0.j ]]\n"
198 | ]
199 | }
200 | ],
201 | "source": [
202 | "sp.set_printoptions(precision=3)\n",
203 | "print('{}'.format(vec))"
204 | ]
205 | },
206 | {
207 | "cell_type": "code",
208 | "execution_count": 88,
209 | "metadata": {
210 | "collapsed": true
211 | },
212 | "outputs": [],
213 | "source": [
214 | "Ca = np.array([[1, 0]])\n",
215 | "Cd = Ca*0\n",
216 | "Cv = Cd\n",
217 | "Bt = np.array([[1],[0]])\n",
218 | "A, B, Css, D = vt.so2ss(M, C, K, Bt, Cd, Cv, Ca)"
219 | ]
220 | },
221 | {
222 | "cell_type": "code",
223 | "execution_count": 89,
224 | "metadata": {
225 | "collapsed": true
226 | },
227 | "outputs": [],
228 | "source": [
229 | "Ad, Bd, _, _ = vt.c2d(A, B, C, D, .1)"
230 | ]
231 | },
232 | {
233 | "cell_type": "code",
234 | "execution_count": 90,
235 | "metadata": {
236 | "collapsed": false
237 | },
238 | "outputs": [
239 | {
240 | "data": {
241 | "text/plain": [
242 | "array([[ 9.902e-01, 4.885e-03, 9.770e-02, 6.523e-04],\n",
243 | " [ 4.918e-03, 9.901e-01, 6.523e-04, 9.868e-02],\n",
244 | " [ -1.948e-01, 9.640e-02, 9.512e-01, 1.452e-02],\n",
245 | " [ 9.737e-02, -1.967e-01, 1.452e-02, 9.704e-01]])"
246 | ]
247 | },
248 | "execution_count": 90,
249 | "metadata": {},
250 | "output_type": "execute_result"
251 | }
252 | ],
253 | "source": [
254 | "Ad"
255 | ]
256 | },
257 | {
258 | "cell_type": "code",
259 | "execution_count": 91,
260 | "metadata": {
261 | "collapsed": false
262 | },
263 | "outputs": [
264 | {
265 | "data": {
266 | "text/plain": [
267 | "array([[ 4.926e-03],\n",
268 | " [ 2.050e-05],\n",
269 | " [ 9.770e-02],\n",
270 | " [ 6.523e-04]])"
271 | ]
272 | },
273 | "execution_count": 91,
274 | "metadata": {},
275 | "output_type": "execute_result"
276 | }
277 | ],
278 | "source": [
279 | "Bd"
280 | ]
281 | },
282 | {
283 | "cell_type": "code",
284 | "execution_count": 92,
285 | "metadata": {
286 | "collapsed": false
287 | },
288 | "outputs": [
289 | {
290 | "data": {
291 | "text/plain": [
292 | "array([[-2. , 1. , -0.4, 0.1]])"
293 | ]
294 | },
295 | "execution_count": 92,
296 | "metadata": {},
297 | "output_type": "execute_result"
298 | }
299 | ],
300 | "source": [
301 | "Css"
302 | ]
303 | },
304 | {
305 | "cell_type": "code",
306 | "execution_count": 93,
307 | "metadata": {
308 | "collapsed": false
309 | },
310 | "outputs": [
311 | {
312 | "data": {
313 | "text/plain": [
314 | "array([[ 1.]])"
315 | ]
316 | },
317 | "execution_count": 93,
318 | "metadata": {},
319 | "output_type": "execute_result"
320 | }
321 | ],
322 | "source": [
323 | "D"
324 | ]
325 | },
326 | {
327 | "cell_type": "code",
328 | "execution_count": 94,
329 | "metadata": {
330 | "collapsed": false
331 | },
332 | "outputs": [
333 | {
334 | "name": "stdout",
335 | "output_type": "stream",
336 | "text": [
337 | "Help on function ctrb in module control.statefbk:\n",
338 | "\n",
339 | "ctrb(A, B)\n",
340 | " Controllabilty matrix\n",
341 | " \n",
342 | " Parameters\n",
343 | " ----------\n",
344 | " A, B: array_like or string\n",
345 | " Dynamics and input matrix of the system\n",
346 | " \n",
347 | " Returns\n",
348 | " -------\n",
349 | " C: matrix\n",
350 | " Controllability matrix\n",
351 | " \n",
352 | " Examples\n",
353 | " --------\n",
354 | " >>> C = ctrb(A, B)\n",
355 | "\n"
356 | ]
357 | }
358 | ],
359 | "source": [
360 | "help(ctrl.ctrb)"
361 | ]
362 | },
363 | {
364 | "cell_type": "code",
365 | "execution_count": 95,
366 | "metadata": {
367 | "collapsed": false
368 | },
369 | "outputs": [
370 | {
371 | "data": {
372 | "text/plain": [
373 | "matrix([[ 4.926e-03, 1.442e-02, 2.327e-02, 3.133e-02],\n",
374 | " [ 2.050e-05, 1.726e-04, 5.513e-04, 1.225e-03],\n",
375 | " [ 9.770e-02, 9.198e-02, 8.473e-02, 7.619e-02],\n",
376 | " [ 6.523e-04, 2.528e-03, 5.160e-03, 8.395e-03]])"
377 | ]
378 | },
379 | "execution_count": 95,
380 | "metadata": {},
381 | "output_type": "execute_result"
382 | }
383 | ],
384 | "source": [
385 | "CC = ctrl.ctrb(Ad, Bd)\n",
386 | "CC"
387 | ]
388 | },
389 | {
390 | "cell_type": "code",
391 | "execution_count": 100,
392 | "metadata": {
393 | "collapsed": false
394 | },
395 | "outputs": [
396 | {
397 | "data": {
398 | "text/plain": [
399 | "4"
400 | ]
401 | },
402 | "execution_count": 100,
403 | "metadata": {},
404 | "output_type": "execute_result"
405 | }
406 | ],
407 | "source": [
408 | "lanp.matrix_rank(CC)"
409 | ]
410 | },
411 | {
412 | "cell_type": "code",
413 | "execution_count": 101,
414 | "metadata": {
415 | "collapsed": false
416 | },
417 | "outputs": [
418 | {
419 | "data": {
420 | "text/plain": [
421 | "matrix([[-2. , 1. , -0.4 , 0.1 ],\n",
422 | " [-1.888, 0.922, -0.574, 0.189],\n",
423 | " [-1.735, 0.811, -0.727, 0.264],\n",
424 | " [-1.546, 0.673, -0.856, 0.325]])"
425 | ]
426 | },
427 | "execution_count": 101,
428 | "metadata": {},
429 | "output_type": "execute_result"
430 | }
431 | ],
432 | "source": [
433 | "OO = ctrl.obsv(Ad, Css)\n",
434 | "OO"
435 | ]
436 | },
437 | {
438 | "cell_type": "code",
439 | "execution_count": 102,
440 | "metadata": {
441 | "collapsed": false
442 | },
443 | "outputs": [
444 | {
445 | "data": {
446 | "text/plain": [
447 | "4"
448 | ]
449 | },
450 | "execution_count": 102,
451 | "metadata": {},
452 | "output_type": "execute_result"
453 | }
454 | ],
455 | "source": [
456 | "lanp.matrix_rank(OO)"
457 | ]
458 | },
459 | {
460 | "cell_type": "code",
461 | "execution_count": null,
462 | "metadata": {
463 | "collapsed": true
464 | },
465 | "outputs": [],
466 | "source": []
467 | }
468 | ],
469 | "metadata": {
470 | "hide_input": false,
471 | "kernelspec": {
472 | "display_name": "Python 3",
473 | "language": "python",
474 | "name": "python3"
475 | },
476 | "language_info": {
477 | "codemirror_mode": {
478 | "name": "ipython",
479 | "version": 3
480 | },
481 | "file_extension": ".py",
482 | "mimetype": "text/x-python",
483 | "name": "python",
484 | "nbconvert_exporter": "python",
485 | "pygments_lexer": "ipython3",
486 | "version": "3.5.1"
487 | },
488 | "latex_envs": {
489 | "bibliofile": "biblio.bib",
490 | "cite_by": "apalike",
491 | "current_citInitial": 1,
492 | "eqLabelWithNumbers": true,
493 | "eqNumInitial": 0
494 | }
495 | },
496 | "nbformat": 4,
497 | "nbformat_minor": 0
498 | }
499 |
--------------------------------------------------------------------------------
/JupyterNotebooks/Examples.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 3,
6 | "metadata": {
7 | "init_cell": true
8 | },
9 | "outputs": [
10 | {
11 | "name": "stdout",
12 | "output_type": "stream",
13 | "text": [
14 | "The autoreload extension is already loaded. To reload it, use:\n",
15 | " %reload_ext autoreload\n"
16 | ]
17 | }
18 | ],
19 | "source": [
20 | "%load_ext autoreload\n",
21 | "%autoreload 2\n",
22 | "%matplotlib inline\n",
23 | "import vibrationtesting as vt\n",
24 | "import matplotlib.pyplot as plt\n",
25 | "import numpy as np\n",
26 | "import array_to_latex as a2t\n",
27 | "np.set_printoptions(precision = 9, linewidth = 220, suppress = True)"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "metadata": {},
33 | "source": [
34 | "Convert complex modes to real modes"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": 4,
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "M = np.array([[4, 0, 0],\n",
44 | " [0, 4, 0],\n",
45 | " [0, 0, 4]])\n",
46 | "Cso = np.array([[1,0,0],\n",
47 | " [0,0,0],\n",
48 | " [0,0,0]])\n",
49 | "K = np.array([[8, -4, 0],\n",
50 | " [-4, 8, -4],\n",
51 | " [0, -4, 4]])\n",
52 | "omega, _, Psi_true = vt.sos_modal(M, K)\n",
53 | "Bt = np.array([[1],[0],[0]])\n",
54 | "Ca = np.array([[1,0,0]])\n",
55 | "Cd = Cv = np.zeros_like(Ca)\n",
56 | "A, B, C, D = vt.so2ss(M, Cso, K, Bt, Cd, Cv, Ca)\n",
57 | "Am, Bm, Cm, Dm, eigenvalues, modes = vt.ss_modal(A, B, C, D)\n",
58 | "complex_modes = modes[:3,0::2]\n",
59 | "complex_modes = complex_modes@np.linalg.inv(np.diag(np.diag(complex_modes)))\n",
60 | "Psi_corrected = vt.real_modes(complex_modes)\n"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 5,
66 | "metadata": {
67 | "scrolled": true
68 | },
69 | "outputs": [
70 | {
71 | "data": {
72 | "text/plain": [
73 | "array([[-0.163992639, 0.368488115, -0.295504524],\n",
74 | " [-0.295504524, 0.163992639, 0.368488115],\n",
75 | " [-0.368488115, -0.295504524, -0.163992639]])"
76 | ]
77 | },
78 | "execution_count": 5,
79 | "metadata": {},
80 | "output_type": "execute_result"
81 | }
82 | ],
83 | "source": [
84 | "Psi_true"
85 | ]
86 | },
87 | {
88 | "cell_type": "code",
89 | "execution_count": 6,
90 | "metadata": {},
91 | "outputs": [
92 | {
93 | "data": {
94 | "text/plain": [
95 | "array([[ 1. +0.j , 2.099234839-0.684531423j, 1.678352494+0.52790572j ],\n",
96 | " [ 1.798247408+0.099411477j, 1. -0.j , -2.218125195-0.153628421j],\n",
97 | " [ 2.240934713+0.157537198j, -1.651470818+0.512991869j, 1. -0.j ]])"
98 | ]
99 | },
100 | "execution_count": 6,
101 | "metadata": {},
102 | "output_type": "execute_result"
103 | }
104 | ],
105 | "source": [
106 | "complex_modes"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 7,
112 | "metadata": {},
113 | "outputs": [
114 | {
115 | "name": "stdout",
116 | "output_type": "stream",
117 | "text": [
118 | "\\begin{bmatrix}\n",
119 | " 1.00000+0.00000j & 2.09923-0.68453j & 1.67835+0.52791j\\\\\n",
120 | " 1.79825+0.09941j & 1.00000-0.00000j & -2.21813-0.15363j\\\\\n",
121 | " 2.24093+0.15754j & -1.65147+0.51299j & 1.00000-0.00000j\n",
122 | "\\end{bmatrix}\n"
123 | ]
124 | }
125 | ],
126 | "source": [
127 | "a2t.to_ltx(complex_modes, frmt = '{:3.5f}', imstring='j')"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": 8,
133 | "metadata": {},
134 | "outputs": [
135 | {
136 | "data": {
137 | "text/plain": [
138 | "array([[ 1. , 2.099234839, 1.678352494],\n",
139 | " [ 1.798247408, 1. , -2.218125195],\n",
140 | " [ 2.240934713, -1.651470818, 1. ]])"
141 | ]
142 | },
143 | "execution_count": 8,
144 | "metadata": {},
145 | "output_type": "execute_result"
146 | }
147 | ],
148 | "source": [
149 | "Psi_real = np.real(complex_modes)\n",
150 | "Psi_real"
151 | ]
152 | },
153 | {
154 | "cell_type": "code",
155 | "execution_count": 9,
156 | "metadata": {},
157 | "outputs": [
158 | {
159 | "name": "stdout",
160 | "output_type": "stream",
161 | "text": [
162 | "\\begin{bmatrix}\n",
163 | " -0.1639 & 0.3684 & -0.2955\\\\\n",
164 | " -0.2955 & 0.1639 & 0.3684\\\\\n",
165 | " -0.3684 & -0.2955 & -0.1639\n",
166 | "\\end{bmatrix}\n"
167 | ]
168 | }
169 | ],
170 | "source": [
171 | "a2t.to_ltx(Psi_true, frmt = '{:3.5f}')"
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": 10,
177 | "metadata": {},
178 | "outputs": [],
179 | "source": [
180 | "?a2t.to_clp"
181 | ]
182 | },
183 | {
184 | "cell_type": "code",
185 | "execution_count": 11,
186 | "metadata": {
187 | "scrolled": true
188 | },
189 | "outputs": [
190 | {
191 | "data": {
192 | "text/plain": [
193 | "array([[ 1. , 2.208024044, 1.759417956],\n",
194 | " [ 1.800993165, 1. , -2.223439019],\n",
195 | " [ 2.246465302, -1.729310996, 1. ]])"
196 | ]
197 | },
198 | "execution_count": 11,
199 | "metadata": {},
200 | "output_type": "execute_result"
201 | }
202 | ],
203 | "source": [
204 | "Psi_abs = np.abs(complex_modes)*np.real(np.sign(complex_modes))\n",
205 | "Psi_abs"
206 | ]
207 | },
208 | {
209 | "cell_type": "code",
210 | "execution_count": 12,
211 | "metadata": {},
212 | "outputs": [
213 | {
214 | "data": {
215 | "text/plain": [
216 | "array([[ 1. , 2.208024044, 1.759417956],\n",
217 | " [ 1.80253425 , 0.974920375, -2.116079409],\n",
218 | " [ 2.229245067, -1.760956913, 0.902996771]])"
219 | ]
220 | },
221 | "execution_count": 12,
222 | "metadata": {},
223 | "output_type": "execute_result"
224 | }
225 | ],
226 | "source": [
227 | "Psi_corrected"
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": 13,
233 | "metadata": {},
234 | "outputs": [
235 | {
236 | "data": {
237 | "text/plain": [
238 | "array([[0.999999338, 0.000479191, 0.000063106],\n",
239 | " [0.000000603, 0.999343589, 0.000762406],\n",
240 | " [0.000000059, 0.00017722 , 0.999174488]])"
241 | ]
242 | },
243 | "execution_count": 13,
244 | "metadata": {},
245 | "output_type": "execute_result"
246 | }
247 | ],
248 | "source": [
249 | "vt.mac(Psi_true,Psi_real)"
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": 14,
255 | "metadata": {},
256 | "outputs": [
257 | {
258 | "name": "stdout",
259 | "output_type": "stream",
260 | "text": [
261 | "\\begin{bmatrix}\n",
262 | " 1.0000 & 0.0004 & 0.0000\\\\\n",
263 | " 0.0000 & 0.9993 & 0.0007\\\\\n",
264 | " 0.0000 & 0.0001 & 0.9991\n",
265 | "\\end{bmatrix}\n"
266 | ]
267 | }
268 | ],
269 | "source": [
270 | "a2t.to_ltx(vt.mac(Psi_true,Psi_real), frmt = '{:3.5f}')"
271 | ]
272 | },
273 | {
274 | "cell_type": "code",
275 | "execution_count": 15,
276 | "metadata": {},
277 | "outputs": [
278 | {
279 | "data": {
280 | "text/plain": [
281 | "array([[0.99999997 , 0.000187273, 0. ],\n",
282 | " [0. , 0.999812655, 0.000061695],\n",
283 | " [0.00000003 , 0.000000072, 0.999938305]])"
284 | ]
285 | },
286 | "execution_count": 15,
287 | "metadata": {},
288 | "output_type": "execute_result"
289 | }
290 | ],
291 | "source": [
292 | "vt.mac(Psi_true,Psi_abs)"
293 | ]
294 | },
295 | {
296 | "cell_type": "code",
297 | "execution_count": 16,
298 | "metadata": {},
299 | "outputs": [
300 | {
301 | "name": "stdout",
302 | "output_type": "stream",
303 | "text": [
304 | "\\begin{bmatrix}\n",
305 | " 1.0000 & 0.0001 & 0.0000\\\\\n",
306 | " 0.0000 & 0.9998 & 0.0000\\\\\n",
307 | " 0.0000 & 0.0000 & 0.9999\n",
308 | "\\end{bmatrix}\n"
309 | ]
310 | }
311 | ],
312 | "source": [
313 | "a2t.to_ltx(vt.mac(Psi_true,Psi_abs), frmt = '{:3.5f}')"
314 | ]
315 | },
316 | {
317 | "cell_type": "code",
318 | "execution_count": 17,
319 | "metadata": {},
320 | "outputs": [
321 | {
322 | "data": {
323 | "text/plain": [
324 | "array([[0.999983388, 0.000000759, 0.000007767],\n",
325 | " [0.000012366, 0.999990366, 0.000566347],\n",
326 | " [0.000004246, 0.000008875, 0.999425887]])"
327 | ]
328 | },
329 | "execution_count": 17,
330 | "metadata": {},
331 | "output_type": "execute_result"
332 | }
333 | ],
334 | "source": [
335 | "vt.mac(Psi_true,Psi_corrected)"
336 | ]
337 | },
338 | {
339 | "cell_type": "code",
340 | "execution_count": 18,
341 | "metadata": {},
342 | "outputs": [
343 | {
344 | "name": "stdout",
345 | "output_type": "stream",
346 | "text": [
347 | "\\begin{bmatrix}\n",
348 | " 0.9999 & 0.0000 & 0.0000\\\\\n",
349 | " 0.0000 & 0.9999 & 0.0005\\\\\n",
350 | " 0.0000 & 0.0000 & 0.9994\n",
351 | "\\end{bmatrix}\n"
352 | ]
353 | }
354 | ],
355 | "source": [
356 | "a2t.to_ltx(vt.mac(Psi_true,Psi_corrected), frmt = '{:3.5f}')"
357 | ]
358 | },
359 | {
360 | "cell_type": "code",
361 | "execution_count": 19,
362 | "metadata": {},
363 | "outputs": [],
364 | "source": [
365 | "Psi_real = vt.mass_normalize(Psi_real, M)"
366 | ]
367 | },
368 | {
369 | "cell_type": "code",
370 | "execution_count": 20,
371 | "metadata": {},
372 | "outputs": [
373 | {
374 | "data": {
375 | "text/plain": [
376 | "array([[ 1. , 0.022663407, -0.007723197],\n",
377 | " [ 0.022663407, 1. , -0.041083449],\n",
378 | " [-0.007723197, -0.041083449, 1. ]])"
379 | ]
380 | },
381 | "execution_count": 20,
382 | "metadata": {},
383 | "output_type": "execute_result"
384 | }
385 | ],
386 | "source": [
387 | "Psi_real.T@M@Psi_real"
388 | ]
389 | },
390 | {
391 | "cell_type": "code",
392 | "execution_count": 21,
393 | "metadata": {
394 | "scrolled": false
395 | },
396 | "outputs": [
397 | {
398 | "name": "stdout",
399 | "output_type": "stream",
400 | "text": [
401 | "\\begin{bmatrix}\n",
402 | " 1.0 & 0.0 & -0.0\\\\\n",
403 | " 0.0 & 1.0 & -0.0\\\\\n",
404 | " -0.0 & -0.0 & 1.0\n",
405 | "\\end{bmatrix}\n"
406 | ]
407 | }
408 | ],
409 | "source": [
410 | "a2t.to_ltx(Psi_real.T@M@Psi_real)"
411 | ]
412 | },
413 | {
414 | "cell_type": "code",
415 | "execution_count": 22,
416 | "metadata": {},
417 | "outputs": [
418 | {
419 | "name": "stdout",
420 | "output_type": "stream",
421 | "text": [
422 | "\\begin{bmatrix}\n",
423 | " 8.0 & -4.0 & 0.0\\\\\n",
424 | " -4.0 & 8.0 & -4.0\\\\\n",
425 | " 0.0 & -4.0 & 4.0\n",
426 | "\\end{bmatrix}\n"
427 | ]
428 | }
429 | ],
430 | "source": [
431 | "a2t.to_ltx(K)"
432 | ]
433 | },
434 | {
435 | "cell_type": "code",
436 | "execution_count": null,
437 | "metadata": {},
438 | "outputs": [],
439 | "source": []
440 | }
441 | ],
442 | "metadata": {
443 | "hide_input": false,
444 | "kernelspec": {
445 | "display_name": "Python 3",
446 | "language": "python",
447 | "name": "python3"
448 | },
449 | "language_info": {
450 | "codemirror_mode": {
451 | "name": "ipython",
452 | "version": 3
453 | },
454 | "file_extension": ".py",
455 | "mimetype": "text/x-python",
456 | "name": "python",
457 | "nbconvert_exporter": "python",
458 | "pygments_lexer": "ipython3",
459 | "version": "3.6.4"
460 | },
461 | "nbTranslate": {
462 | "displayLangs": [
463 | "*"
464 | ],
465 | "hotkey": "alt-t",
466 | "langInMainMenu": true,
467 | "sourceLang": "en",
468 | "targetLang": "fr",
469 | "useGoogleTranslate": true
470 | },
471 | "toc": {
472 | "nav_menu": {},
473 | "number_sections": true,
474 | "sideBar": true,
475 | "skip_h1_title": false,
476 | "title_cell": "Table of Contents",
477 | "title_sidebar": "Contents",
478 | "toc_cell": false,
479 | "toc_position": {},
480 | "toc_section_display": true,
481 | "toc_window_display": false
482 | },
483 | "varInspector": {
484 | "cols": {
485 | "lenName": 16,
486 | "lenType": 16,
487 | "lenVar": 40
488 | },
489 | "kernels_config": {
490 | "python": {
491 | "delete_cmd_postfix": "",
492 | "delete_cmd_prefix": "del ",
493 | "library": "var_list.py",
494 | "varRefreshCmd": "print(var_dic_list())"
495 | },
496 | "r": {
497 | "delete_cmd_postfix": ") ",
498 | "delete_cmd_prefix": "rm(",
499 | "library": "var_list.r",
500 | "varRefreshCmd": "cat(var_dic_list()) "
501 | }
502 | },
503 | "types_to_exclude": [
504 | "module",
505 | "function",
506 | "builtin_function_or_method",
507 | "instance",
508 | "_Feature"
509 | ],
510 | "window_display": false
511 | }
512 | },
513 | "nbformat": 4,
514 | "nbformat_minor": 2
515 | }
516 |
--------------------------------------------------------------------------------
/vibrationtesting/identification.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Nov. 26, 2017
3 | @author: Joseph C. Slater
4 | """
5 | __license__ = "Joseph C. Slater"
6 |
7 | __docformat__ = 'reStructuredText'
8 |
9 | import numpy as np
10 | import scipy.linalg as la
11 | import matplotlib.pyplot as plt
12 |
13 | '''
14 | ======================
15 | System Identification
16 | ======================
17 |
18 |
19 | Array convention for data/FRFs:
20 |
21 | Frequency Response Functions
22 | ----------------------------
23 | | 0 dimension is the output
24 | | 1 dimension is the frequency index
25 | | 2 dimension is the input
26 |
27 | Time Histories
28 | --------------
29 | | 0 dimension is the output
30 | | 1 dimension is the time
31 | | 2 dimension is the instance index
32 | | 3 dimension is the input
33 |
34 | Spectrum Densities
35 | ------------------
36 | | 0 dimension is the output (Cross Spectrum) or channel (Auto Spectrums)
37 | | 1 dimension is the frequency index
38 | | 2 dimension is the instance index
39 | | 3 dimension is the input (Cross Spectrum Densities)
40 |
41 | '''
42 |
43 |
44 | def sdof_cf(f, TF, Fmin=None, Fmax=None):
45 | """Curve fit to a single degree of freedom FRF.
46 |
47 | Only one peak may exist in the segment of the FRF passed to sdofcf. No
48 | zeros may exist within this segment. If so, curve fitting becomes
49 | unreliable.
50 |
51 | If Fmin and Fmax are not entered, the first and last elements of TF are
52 | used.
53 |
54 | Parameters
55 | ----------
56 | f: array
57 | The frequency vector in Hz. Does not have to start at 0 Hz.
58 | TF: array
59 | The complex transfer function
60 | Fmin: int
61 | The minimum frequency to be used for curve fitting in the FRF
62 | Fmax: int
63 | The maximum frequency to be used for curve fitting in the FRF
64 |
65 | Returns
66 | -------
67 | z: double
68 | The damping ratio
69 | nf: double
70 | Natural frequency (Hz)
71 | a: double
72 | The numerator of the identified transfer functions
73 |
74 | Plot of the FRF magnitude and phase.
75 |
76 | Examples
77 | --------
78 | >>> # First we need to load the sampled data which is in a .mat file
79 | >>> import vibrationtesting as vt
80 | >>> import scipy.io as sio
81 | >>> data = sio.loadmat(vt.__path__[0] + '/data/case1.mat')
82 | >>> #print(data)
83 | >>> # Data is imported as arrays. Modify then to fit our function.
84 | >>> TF = data['Hf_chan_2']
85 | >>> f = data['Freq_domain']
86 | >>> # Now we are able to call the function
87 | >>> z, nf, a = vt.sdof_cf(f,TF,500,1000)
88 | >>> nf
89 | 212.092530551...
90 |
91 | Notes
92 | -----
93 | Author: Original Joseph C. Slater. Python version, Gabriel Loranger
94 | """
95 |
96 | # check fmin fmax existance
97 | if Fmin is None:
98 | inlow = 0
99 | else:
100 | inlow = Fmin
101 |
102 | if Fmax is None:
103 | inhigh = np.size(f)
104 | else:
105 | inhigh = Fmax
106 |
107 | if f[inlow] == 0:
108 | inlow = 1
109 |
110 | f = f[inlow:inhigh, :]
111 | TF = TF[inlow:inhigh, :]
112 |
113 | R = TF
114 | y = np.amax(np.abs(TF))
115 | cin = np.argmax(np.abs(TF))
116 |
117 | ll = np.size(f)
118 |
119 | w = f * 2 * np.pi * 1j
120 |
121 | w2 = w * 0
122 | R3 = R * 0
123 |
124 | for i in range(1, ll + 1):
125 | R3[i - 1] = np.conj(R[ll - i])
126 | w2[i - 1] = np.conj(w[ll - i])
127 |
128 | w = np.vstack((w2, w))
129 | R = np.vstack((R3, R))
130 |
131 | N = 2
132 | x, y = np.meshgrid(np.arange(0, N + 1), R)
133 | x, w2d = np.meshgrid(np.arange(0, N + 1), w)
134 | c = -1 * w**N * R
135 |
136 | aa1 = w2d[:, np.arange(0, N)] \
137 | ** x[:, np.arange(0, N)] \
138 | * y[:, np.arange(0, N)]
139 | aa2 = -w2d[:, np.arange(0, N + 1)] \
140 | ** x[:, np.arange(0, N + 1)]
141 | aa = np.hstack((aa1, aa2))
142 |
143 | aa = np.reshape(aa, [-1, 5])
144 |
145 | b, _, _, _ = la.lstsq(aa, c)
146 |
147 | rs = np.roots(np.array([1,
148 | b[1],
149 | b[0]]))
150 | omega = np.abs(rs[1])
151 | z = -1 * np.real(rs[1]) / np.abs(rs[1])
152 | nf = omega / 2 / np.pi
153 |
154 | XoF1 = np.hstack(([1 / (w - rs[0]), 1 / (w - rs[1])]))
155 | XoF2 = 1 / (w**0)
156 | XoF3 = 1 / w**2
157 | XoF = np.hstack((XoF1, XoF2, XoF3))
158 |
159 | # check if extra _ needed
160 |
161 | a, _, _, _ = la.lstsq(XoF, R)
162 | XoF = XoF[np.arange(ll, 2 * ll), :].dot(a)
163 |
164 | a = np.sqrt(-2 * np.imag(a[0]) * np.imag(rs[0]) -
165 | 2 * np.real(a[0]) * np.real(rs[0]))
166 | Fmin = np.min(f)
167 | Fmax = np.max(f)
168 | phase = np.unwrap(np.angle(TF), np.pi, 0) * 180 / np.pi
169 | phase2 = np.unwrap(np.angle(XoF), np.pi, 0) * 180 / np.pi
170 | while phase2[cin] > 50:
171 | phase2 = phase2 - 360
172 | phased = phase2[cin] - phase[cin]
173 | phase = phase + np.round(phased / 360) * 360
174 |
175 | fig = plt.figure()
176 | ax1 = fig.add_subplot(2, 1, 1)
177 | ax2 = fig.add_subplot(2, 1, 2)
178 | fig.tight_layout()
179 |
180 | ax1.set_xlabel('Frequency (Hz)')
181 | ax1.set_ylabel('Magnitude (dB)')
182 | ax1.plot(f, 20 * np.log10(np.abs(XoF)), label="Identified FRF")
183 | ax1.plot(f, 20 * np.log10(np.abs(TF)), label="Experimental FRF")
184 | ax1.legend()
185 |
186 | ax2.set_xlabel('Frequency (Hz)')
187 | ax2.set_ylabel('Phase (deg)')
188 | ax2.plot(f, phase2, label="Identified FRF")
189 | ax2.plot(f, phase, label="Experimental FRF")
190 | ax2.legend()
191 |
192 | plt.show()
193 |
194 | a = a[0]**2 / (2 * np.pi * nf)**2
195 | return z, nf, a
196 |
197 |
198 | def mdof_cf(f, TF, Fmin=None, Fmax=None):
199 | """
200 | Curve fit to multiple degree of freedom FRF
201 |
202 | If Fmin and Fmax are not entered, the first and last elements of TF are
203 | used.
204 |
205 | If the first column of TF is a collocated (input and output location are
206 | the same), then the mode shape returned is the mass normalized mode shape.
207 | This can then be used to generate an identified mass, damping, and
208 | stiffness matrix as shown in the following example.
209 |
210 | Parameters
211 | ----------
212 | f: array
213 | The frequency vector in Hz. Does not have to start at 0 Hz.
214 | TF: array
215 | The complex transfer function
216 | Fmin: int
217 | The minimum frequency to be used for curve fitting in the FRF
218 | Fmax: int
219 | The maximum frequency to be used for curve fitting in the FRF
220 |
221 | Returns
222 | -------
223 | z: double
224 | The damping ratio
225 | nf: double
226 | Natural frequency (Hz)
227 | u: array
228 | The mode shape
229 |
230 | Notes
231 | -----
232 | FRF are columns comprised of the FRFs presuming single input, multiple
233 | output z and nf are the damping ratio and natural frequency (Hz) u is the
234 | mode shape. Only one peak may exist in the segment of the FRF passed to
235 | sdofcf. No zeros may exist within this segment. If so, curve fitting
236 | becomes unreliable.
237 |
238 | Author: Original Joseph C. Slater. Python version, Gabriel Loranger
239 |
240 | Examples
241 | --------
242 | >>> # First we need to load the sampled data which is in a .mat file
243 | >>> import vibrationtesting as vt
244 | >>> import scipy.io as sio
245 | >>> data = sio.loadmat(vt.__path__[0] + '/data/case2.mat')
246 | >>> #print(data)
247 | >>> # Data is imported as arrays. Modify then to fit our function
248 | >>> TF = data['Hf_chan_2']
249 | >>> f = data['Freq_domain']
250 | >>> # Now we are able to call the function
251 | >>> z, nf, a = vt.mdof_cf(f,TF,500,1000)
252 | >>> nf
253 | 192.59382330...
254 | """
255 |
256 | # check fmin fmax existance
257 | if Fmin is None:
258 | inlow = 0
259 | else:
260 | inlow = Fmin
261 |
262 | if Fmax is None:
263 | inhigh = np.size(f)
264 | else:
265 | inhigh = Fmax
266 |
267 | if f[inlow] == 0:
268 | inlow = 1
269 |
270 | f = f[inlow:inhigh, :]
271 | TF = TF[inlow:inhigh, :]
272 |
273 | R = TF.T
274 |
275 | U, _, _ = np.linalg.svd(R)
276 | T = U[:, 0]
277 | Hp = np.transpose(T).dot(R)
278 | R = np.transpose(Hp)
279 |
280 | ll = np.size(f)
281 | w = f * 2 * np.pi * 1j
282 |
283 | w2 = w * 0
284 | R3 = R * 0
285 | TF2 = TF * 0
286 | for i in range(1, ll + 1):
287 | R3[i - 1] = np.conj(R[ll - i])
288 | w2[i - 1] = np.conj(w[ll - i])
289 | TF2[i - 1, :] = np.conj(TF[ll - i, :])
290 |
291 | w = np.vstack((w2, w))
292 | R = np.hstack((R3, R))
293 |
294 | N = 2
295 | x, y = np.meshgrid(np.arange(0, N + 1), R)
296 |
297 | x, w2d = np.meshgrid(np.arange(0, N + 1), w)
298 |
299 | R = np.ndarray.flatten(R)
300 | w = np.ndarray.flatten(w)
301 | c = -1 * w**N * R
302 |
303 | aa1 = w2d[:, np.arange(0, N)] \
304 | ** x[:, np.arange(0, N)] \
305 | * y[:, np.arange(0, N)]
306 | aa2 = -w2d[:, np.arange(0, N + 1)] \
307 | ** x[:, np.arange(0, N + 1)]
308 | aa = np.hstack((aa1, aa2))
309 |
310 | b, _, _, _ = la.lstsq(aa, c)
311 |
312 | rs = np.roots(np.array([1,
313 | b[1],
314 | b[0]]))
315 |
316 | omega = np.abs(rs[1])
317 | z = -1 * np.real(rs[1]) / np.abs(rs[1])
318 | nf = omega / 2 / np.pi
319 |
320 | XoF1 = 1 / ((rs[0] - w) * (rs[1] - w))
321 |
322 | XoF2 = 1 / (w**0)
323 | XoF3 = 1 / w**2
324 |
325 | XoF = np.vstack((XoF1, XoF2, XoF3)).T
326 | TF3 = np.vstack((TF2, TF))
327 |
328 | a, _, _, _ = la.lstsq(XoF, TF3)
329 |
330 | u = np.transpose(a[0, :])
331 |
332 | u = u / np.sqrt(np.abs(a[0, 0]))
333 |
334 | return z, nf, u
335 |
336 |
337 | def cmif(freq, H, freq_min=None, freq_max=None, plot=True):
338 | """Complex mode indicator function.
339 |
340 | Plots the complex mode indicator function
341 |
342 | Parameters
343 | ----------
344 | freq : array
345 | The frequency vector in Hz. Does not have to start at 0 Hz.
346 | H : array
347 | The complex frequency response function
348 | freq_min : float, optional
349 | The minimum frequency to be plotted
350 | freq_max : float, optional
351 | The maximum frequency to be plotted
352 | plot : boolean, optional (True)
353 | Whether to also plot mode indicator functions
354 |
355 | Returns
356 | -------
357 | cmifs : complex mode indicator functions
358 |
359 | Examples
360 | --------
361 | >>> import vibrationtesting as vt
362 | >>> M = np.diag([1,1,1])
363 | >>> K = K = np.array([[3.03, -1, -1],[-1, 2.98, -1],[-1, -1, 3]])
364 | >>> Damping = K/100
365 | >>> Cd = np.eye(3)
366 | >>> Cv = Ca = np.zeros_like(Cd)
367 | >>> Bt = np.eye(3)
368 | >>> H_all = np.zeros((3,1000,3), dtype = 'complex128')
369 | >>> for i in np.arange(1, 4):
370 | ... for j in np.arange(1, 4):
371 | ... omega, H_all[i-1,:,j-1] = vt.sos_frf(M, Damping/10, K, Bt,
372 | ... Cd, Cv, Ca, 0, 3, i,
373 | ... j, num_freqs = 1000)
374 | >>> _ = vt.cmif(omega, H_all)
375 |
376 | Notes
377 | -----
378 | .. note:: Allemang, R. and Brown, D., “A Complete Review of the Complex
379 | Mode Indicator Function (CMIF) With Applications,” Proceedings of ISMA
380 | International Conference on Noise and Vibration Engineering, Katholieke
381 | Universiteit Leuven, Belgium, 2006.
382 |
383 | """
384 | if freq_max is None:
385 | freq_max = np.max(freq)
386 | # print(str(freq_max))
387 |
388 | if freq_min is None:
389 | freq_min = 0
390 |
391 | if freq_min < np.min(freq):
392 | freq_min = np.min(freq)
393 |
394 | if freq_min > freq_max:
395 | raise ValueError('freq_min must be less than freq_max.')
396 |
397 | # print(str(np.amin(freq)))
398 | # lenF = freq.shape[1]
399 |
400 | cmifs = np.zeros((max([H.shape[0], H.shape[2]]), max(freq.shape)))
401 | for i, freq_i in enumerate(freq.reshape(-1)):
402 | _, vals, _ = np.linalg.svd(H[:, i, :])
403 | cmifs[:, i] = np.sort(vals).T
404 |
405 | if plot is True:
406 | fig, ax = plt.subplots(1, 1)
407 | ax.plot(freq.T, np.log10(cmifs.T))
408 | ax.grid('on')
409 | ax.set_ylabel('Maginitudes')
410 | ax.set_title('Complex Mode Indicator Functions')
411 | ax.set_xlabel('Frequency')
412 | ax.set_xlim(xmax=freq_max, xmin=freq_min)
413 | return cmifs
414 |
415 |
416 | def mac(Psi_1, Psi_2):
417 | """Modal Assurance Criterion.
418 |
419 | Parameters
420 | ----------
421 | Psi_1, Psi_2 : float arrays
422 | Mode shape matrices to be compared.
423 |
424 | Returns
425 | -------
426 | mac : float array
427 |
428 | Examples
429 | --------
430 |
431 | """
432 | nummodes = Psi_1.shape[1]
433 | MAC = np.zeros((nummodes, nummodes))
434 | if Psi_1.shape == Psi_2.shape:
435 | for i in np.arange(nummodes):
436 | for j in np.arange(nummodes):
437 | MAC[i, j] = (abs(np.conj(Psi_1[:, i]) @ Psi_2[:, j])**2 /
438 | (np.conj(Psi_1[:, i]) @ Psi_1[:, i] *
439 | np.conj(Psi_2[:, j]) @ Psi_2[:, j]))
440 | else:
441 | print('Mode shape arrays must have the same size.')
442 | return MAC
443 |
444 |
445 | def comac(Psi_1, Psi_2, dof):
446 | """Co-modal assurance criterion
447 |
448 | """
449 | comac = 1
450 | return comac
451 |
452 |
453 | def mass_normalize(Psi, M):
454 | """Mass normalize mode shapes.
455 |
456 | Parameters
457 | ----------
458 | Psi : float array
459 | 1-dimensional (single mode) or 2-dimensional array of mode shapes
460 |
461 | Returns
462 | -------
463 | Psi : float array
464 | 2-dimensional array of mass normalized mode shapes
465 |
466 | """
467 | if len(Psi.shape) is 1:
468 | Psi = Psi.reshape((-1, 1))
469 |
470 | for i in np.arange(Psi.shape[1]):
471 | alpha_sqr = Psi[:, i].T @ M @ Psi[:, i]
472 | Psi[:, i] = Psi[:, i] / np.sqrt(alpha_sqr)
473 |
474 | return Psi
475 |
--------------------------------------------------------------------------------
/docs/tutorial/Notebooks/Ho-Kalman-Demo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Ho-Kalman Identification Example"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {
14 | "init_cell": true
15 | },
16 | "outputs": [],
17 | "source": [
18 | "%load_ext autoreload\n",
19 | "%autoreload 2\n",
20 | "%matplotlib inline\n",
21 | "\n",
22 | "import control as ctrl\n",
23 | "import matplotlib.pyplot as plt\n",
24 | "import scipy as sp\n",
25 | "import scipy.linalg as la\n",
26 | "import vibrationtesting as vt\n",
27 | "import scipy.signal as sig"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "metadata": {},
33 | "source": [
34 | "Considers a system \n",
35 | "\n",
36 | "$$\\ddot{x} + 2 \\dot{x} + 100 x = f(t)$$\n",
37 | "with a displacement sensor. \n",
38 | "\n",
39 | "The state space representation is \n",
40 | "\n",
41 | "$$\\dot{\\mathbf{z}} = A \\mathbf{z} + B u$$\n",
42 | "\n",
43 | "$$y = C \\mathbf{z} + D u$$ \n",
44 | "\n",
45 | "where \n",
46 | "\n",
47 | "$$A = \n",
48 | "\\begin{bmatrix}\n",
49 | "0&1\\\\\n",
50 | "-100&-2\n",
51 | "\\end{bmatrix}\n",
52 | ",\\quad\n",
53 | "B = \n",
54 | "\\begin{bmatrix}0\\\\1\\end{bmatrix}\n",
55 | ",\\quad\n",
56 | "C = \\begin{bmatrix}1&0\\end{bmatrix}\n",
57 | ", \\text{ and }\n",
58 | "D = [0]$$"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": 2,
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "sample_freq = 1e3\n",
68 | "A = np.array([[0, 1],\\\n",
69 | " [-100, 2]])\n",
70 | "B = np.array([[0], [1]])\n",
71 | "C = np.array([[1, 0]])\n",
72 | "D = np.array([[0]])\n",
73 | "sys = ctrl.ss(A, B, C, D)\n"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "metadata": {},
79 | "source": [
80 | "For this example, we will generate an impulse response for only 4 discrete times."
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": 3,
86 | "metadata": {},
87 | "outputs": [
88 | {
89 | "data": {
90 | "text/plain": [
91 | "array([ 0. , 0.16666667, 0.33333333, 0.5 , 0.66666667,\n",
92 | " 0.83333333])"
93 | ]
94 | },
95 | "execution_count": 3,
96 | "metadata": {},
97 | "output_type": "execute_result"
98 | }
99 | ],
100 | "source": [
101 | "t = np.linspace(0, 1, num = 6, endpoint = False)\n",
102 | "dt = t[1]\n",
103 | "t"
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "metadata": {},
109 | "source": [
110 | "The impulse response for a second order underdamped system is known to be\n",
111 | "\n",
112 | "$$h(t) = \\frac{1}{\\omega_d}e^{-\\zeta \\omega_n t}\\sin\\left(\\omega_d t\\right)$$"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": 4,
118 | "metadata": {},
119 | "outputs": [],
120 | "source": [
121 | "omega_n = sp.sqrt(100)\n",
122 | "zeta = 2/(2*omega_n)\n",
123 | "omega_d = omega_n * sp.sqrt(1 - zeta**2)"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": 5,
129 | "metadata": {},
130 | "outputs": [
131 | {
132 | "data": {
133 | "text/plain": [
134 | "array([ 0. , 0.08474903, -0.01254052, -0.05886968, 0.01769677,\n",
135 | " 0.03956333])"
136 | ]
137 | },
138 | "execution_count": 5,
139 | "metadata": {},
140 | "output_type": "execute_result"
141 | }
142 | ],
143 | "source": [
144 | "h = 1/omega_d * sp.exp(-zeta*omega_n*t)*sp.sin(omega_d*t)\n",
145 | "h"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 6,
151 | "metadata": {},
152 | "outputs": [
153 | {
154 | "data": {
155 | "image/png": "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\n",
156 | "text/plain": [
157 | ""
158 | ]
159 | },
160 | "metadata": {},
161 | "output_type": "display_data"
162 | }
163 | ],
164 | "source": [
165 | "plt.plot(t,h,'.')\n",
166 | "plt.axis([0,1,-0.1,0.1])\n",
167 | "plt.grid()"
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": 6,
173 | "metadata": {},
174 | "outputs": [
175 | {
176 | "data": {
177 | "text/plain": [
178 | "array([[ 0. , 0.08474903],\n",
179 | " [ 0.08474903, -0.01254052],\n",
180 | " [-0.01254052, -0.05886968],\n",
181 | " [-0.05886968, 0.01769677]])"
182 | ]
183 | },
184 | "execution_count": 6,
185 | "metadata": {},
186 | "output_type": "execute_result"
187 | }
188 | ],
189 | "source": [
190 | "Hankel_0 = sp.vstack((h[0:-2],h[1:-1]))\n",
191 | "Hankel_0 = Hankel_0.T\n",
192 | "Hankel_0"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": 7,
198 | "metadata": {},
199 | "outputs": [
200 | {
201 | "data": {
202 | "text/plain": [
203 | "array([ 0.08474903, -0.01254052, -0.05886968, 0.01769677])"
204 | ]
205 | },
206 | "execution_count": 7,
207 | "metadata": {},
208 | "output_type": "execute_result"
209 | }
210 | ],
211 | "source": [
212 | "h[1:-1]"
213 | ]
214 | },
215 | {
216 | "cell_type": "code",
217 | "execution_count": 8,
218 | "metadata": {},
219 | "outputs": [
220 | {
221 | "data": {
222 | "text/plain": [
223 | "array([-0.01254052, -0.05886968, 0.01769677, 0.03956333])"
224 | ]
225 | },
226 | "execution_count": 8,
227 | "metadata": {},
228 | "output_type": "execute_result"
229 | }
230 | ],
231 | "source": [
232 | "h[2:]"
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": 9,
238 | "metadata": {
239 | "scrolled": true
240 | },
241 | "outputs": [
242 | {
243 | "data": {
244 | "text/plain": [
245 | "array([[ 0.08474903, -0.01254052],\n",
246 | " [-0.01254052, -0.05886968],\n",
247 | " [-0.05886968, 0.01769677],\n",
248 | " [ 0.01769677, 0.03956333]])"
249 | ]
250 | },
251 | "execution_count": 9,
252 | "metadata": {},
253 | "output_type": "execute_result"
254 | }
255 | ],
256 | "source": [
257 | "Hankel_1 = sp.vstack((h[1:-1],h[2:]))\n",
258 | "Hankel_1 = Hankel_1.T\n",
259 | "Hankel_1"
260 | ]
261 | },
262 | {
263 | "cell_type": "code",
264 | "execution_count": 10,
265 | "metadata": {},
266 | "outputs": [
267 | {
268 | "name": "stdout",
269 | "output_type": "stream",
270 | "text": [
271 | "[[ 0.56941225 0.57615451]\n",
272 | " [-0.59214005 0.56070011]\n",
273 | " [-0.32038129 -0.49580091]\n",
274 | " [ 0.47169449 -0.32839431]]\n",
275 | "[[-0.66563569 0.74627684]\n",
276 | " [ 0.74627684 0.66563569]]\n"
277 | ]
278 | }
279 | ],
280 | "source": [
281 | "U, sig, Vt = la.svd(Hankel_0)\n",
282 | "V = Vt.T\n",
283 | "U = U[:,:2]\n",
284 | "print(U)\n",
285 | "V = V[:,:2]\n",
286 | "print(V)"
287 | ]
288 | },
289 | {
290 | "cell_type": "code",
291 | "execution_count": 11,
292 | "metadata": {},
293 | "outputs": [
294 | {
295 | "name": "stdout",
296 | "output_type": "stream",
297 | "text": [
298 | "[[ 0.11107284 0. ]\n",
299 | " [ 0. 0.0979112 ]]\n"
300 | ]
301 | }
302 | ],
303 | "source": [
304 | "sig = sp.diag(sig)\n",
305 | "print(sig)"
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": 12,
311 | "metadata": {},
312 | "outputs": [
313 | {
314 | "name": "stdout",
315 | "output_type": "stream",
316 | "text": [
317 | "[[-0.22322281 0.85303151]\n",
318 | " [-0.85967388 0.07525037]]\n"
319 | ]
320 | }
321 | ],
322 | "source": [
323 | "A_d = la.inv(sp.sqrt(sig))@U.T@Hankel_1@V@la.inv(sp.sqrt(sig))\n",
324 | "print(A_d)"
325 | ]
326 | },
327 | {
328 | "cell_type": "code",
329 | "execution_count": 13,
330 | "metadata": {},
331 | "outputs": [
332 | {
333 | "name": "stdout",
334 | "output_type": "stream",
335 | "text": [
336 | "[-0.07398622+0.84324217j -0.07398622-0.84324217j]\n",
337 | "[[ 0.12298924-0.69493489j 0.12298924+0.69493489j]\n",
338 | " [ 0.70847664+0.j 0.70847664-0.j ]]\n"
339 | ]
340 | }
341 | ],
342 | "source": [
343 | "lam_d, vec = la.eig(A_d)\n",
344 | "print(lam_d)\n",
345 | "print(vec)"
346 | ]
347 | },
348 | {
349 | "cell_type": "code",
350 | "execution_count": 14,
351 | "metadata": {},
352 | "outputs": [
353 | {
354 | "name": "stdout",
355 | "output_type": "stream",
356 | "text": [
357 | "[[-0.22322281 0.85303151]\n",
358 | " [-0.85967388 0.07525037]]\n",
359 | "[[-0.22322281 +0.00000000e+00j 0.85303151 +2.77555756e-17j]\n",
360 | " [-0.85967388 -6.93889390e-18j 0.07525037 +0.00000000e+00j]]\n"
361 | ]
362 | }
363 | ],
364 | "source": [
365 | "# This should be the same as A_d\n",
366 | "print(A_d)\n",
367 | "print(vec@sp.diag(lam_d)@la.inv(vec))"
368 | ]
369 | },
370 | {
371 | "cell_type": "code",
372 | "execution_count": 15,
373 | "metadata": {},
374 | "outputs": [
375 | {
376 | "data": {
377 | "text/plain": [
378 | "array([-1.+9.94987437j, -1.-9.94987437j])"
379 | ]
380 | },
381 | "execution_count": 15,
382 | "metadata": {},
383 | "output_type": "execute_result"
384 | }
385 | ],
386 | "source": [
387 | "lam = sp.log(lam_d)/dt\n",
388 | "lam"
389 | ]
390 | },
391 | {
392 | "cell_type": "code",
393 | "execution_count": 16,
394 | "metadata": {},
395 | "outputs": [
396 | {
397 | "name": "stdout",
398 | "output_type": "stream",
399 | "text": [
400 | "The undamped natural frequency is 10.0 rad/sec.\n",
401 | "The damping ratio is 0.09999999999999998.\n"
402 | ]
403 | }
404 | ],
405 | "source": [
406 | "# These are the continuous time eigenvalues\n",
407 | "print('The undamped natural frequency is {} rad/sec.'.format(abs(lam[0])))\n",
408 | "print('The damping ratio is {}.'.format(-sp.real(lam[0])/abs(lam[0])))\n"
409 | ]
410 | },
411 | {
412 | "cell_type": "markdown",
413 | "metadata": {},
414 | "source": [
415 | "The identified state matrix is"
416 | ]
417 | },
418 | {
419 | "cell_type": "code",
420 | "execution_count": 17,
421 | "metadata": {},
422 | "outputs": [
423 | {
424 | "name": "stdout",
425 | "output_type": "stream",
426 | "text": [
427 | "[[ -2.76092394 10.06538424]\n",
428 | " [-10.1437611 0.76092394]]\n"
429 | ]
430 | }
431 | ],
432 | "source": [
433 | "A = la.logm(A_d)/dt\n",
434 | "print(A)"
435 | ]
436 | },
437 | {
438 | "cell_type": "markdown",
439 | "metadata": {},
440 | "source": [
441 | "which is the system the result in a balanced realization form. "
442 | ]
443 | },
444 | {
445 | "cell_type": "code",
446 | "execution_count": 18,
447 | "metadata": {},
448 | "outputs": [
449 | {
450 | "name": "stdout",
451 | "output_type": "stream",
452 | "text": [
453 | "[[-0.22184035]\n",
454 | " [ 0.23351573]]\n"
455 | ]
456 | }
457 | ],
458 | "source": [
459 | "# The discrete input matrix is\n",
460 | "B_d = sp.sqrt(sig)@V.T[:,0].T.reshape((2,1))\n",
461 | "print(B_d)"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": 19,
467 | "metadata": {},
468 | "outputs": [
469 | {
470 | "name": "stdout",
471 | "output_type": "stream",
472 | "text": [
473 | "[[-2.58036274]\n",
474 | " [-0.22677789]]\n"
475 | ]
476 | }
477 | ],
478 | "source": [
479 | "# The continuous input matrix is\n",
480 | "B = la.solve((A_d - sp.eye(2)), A) @ B_d\n",
481 | "print(B)"
482 | ]
483 | },
484 | {
485 | "cell_type": "code",
486 | "execution_count": 20,
487 | "metadata": {},
488 | "outputs": [
489 | {
490 | "name": "stdout",
491 | "output_type": "stream",
492 | "text": [
493 | "[ 0.18977139 0.18028315]\n"
494 | ]
495 | }
496 | ],
497 | "source": [
498 | "C = (U @ sp.sqrt(sig))[0,:]\n",
499 | "print(C)"
500 | ]
501 | },
502 | {
503 | "cell_type": "code",
504 | "execution_count": 21,
505 | "metadata": {},
506 | "outputs": [
507 | {
508 | "data": {
509 | "text/plain": [
510 | "0.0"
511 | ]
512 | },
513 | "execution_count": 21,
514 | "metadata": {},
515 | "output_type": "execute_result"
516 | }
517 | ],
518 | "source": [
519 | "# Of course, D is\n",
520 | "D = h[0]\n",
521 | "D"
522 | ]
523 | },
524 | {
525 | "cell_type": "code",
526 | "execution_count": null,
527 | "metadata": {},
528 | "outputs": [],
529 | "source": []
530 | }
531 | ],
532 | "metadata": {
533 | "hide_input": false,
534 | "kernelspec": {
535 | "display_name": "Python 3",
536 | "language": "python",
537 | "name": "python3"
538 | },
539 | "language_info": {
540 | "codemirror_mode": {
541 | "name": "ipython",
542 | "version": 3
543 | },
544 | "file_extension": ".py",
545 | "mimetype": "text/x-python",
546 | "name": "python",
547 | "nbconvert_exporter": "python",
548 | "pygments_lexer": "ipython3",
549 | "version": "3.6.3"
550 | },
551 | "latex_envs": {
552 | "bibliofile": "biblio.bib",
553 | "cite_by": "apalike",
554 | "current_citInitial": 1,
555 | "eqLabelWithNumbers": true,
556 | "eqNumInitial": 0
557 | },
558 | "toc": {
559 | "nav_menu": {},
560 | "number_sections": true,
561 | "sideBar": true,
562 | "skip_h1_title": false,
563 | "toc_cell": false,
564 | "toc_position": {},
565 | "toc_section_display": "block",
566 | "toc_window_display": false
567 | },
568 | "varInspector": {
569 | "cols": {
570 | "lenName": 16,
571 | "lenType": 16,
572 | "lenVar": 40
573 | },
574 | "kernels_config": {
575 | "python": {
576 | "delete_cmd_postfix": "",
577 | "delete_cmd_prefix": "del ",
578 | "library": "var_list.py",
579 | "varRefreshCmd": "print(var_dic_list())"
580 | },
581 | "r": {
582 | "delete_cmd_postfix": ") ",
583 | "delete_cmd_prefix": "rm(",
584 | "library": "var_list.r",
585 | "varRefreshCmd": "cat(var_dic_list()) "
586 | }
587 | },
588 | "types_to_exclude": [
589 | "module",
590 | "function",
591 | "builtin_function_or_method",
592 | "instance",
593 | "_Feature"
594 | ],
595 | "window_display": false
596 | }
597 | },
598 | "nbformat": 4,
599 | "nbformat_minor": 1
600 | }
601 |
--------------------------------------------------------------------------------
/JupyterNotebooks/Ho-Kalman-Demo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {
7 | "init_cell": true
8 | },
9 | "outputs": [],
10 | "source": [
11 | "%load_ext autoreload\n",
12 | "%autoreload 2\n",
13 | "%matplotlib inline\n",
14 | "\n",
15 | "import control as ctrl\n",
16 | "import matplotlib.pyplot as plt\n",
17 | "import numpy as np\n",
18 | "\n",
19 | "import scipy.linalg as la\n",
20 | "import vibrationtesting as vt\n",
21 | "import scipy.signal as sig"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Considers a system \n",
29 | "\n",
30 | "$$\\ddot{x} + 2 \\dot{x} + 100 x = f(t)$$\n",
31 | "with a displacement sensor. \n",
32 | "\n",
33 | "The state space representation is \n",
34 | "\n",
35 | "$$\\dot{\\mathbf{z}} = A \\mathbf{z} + B \\mathbf{u}$$\n",
36 | "\n",
37 | "$$\\mathbf{y} = C \\mathbf{z} + D \\mathbf{u}$$ \n",
38 | "\n",
39 | "where \n",
40 | "\n",
41 | "$$A = \n",
42 | "\\begin{bmatrix}\n",
43 | "0&1\\\\\n",
44 | "-100&-2\n",
45 | "\\end{bmatrix}\n",
46 | ",\\quad\n",
47 | "B = \n",
48 | "\\begin{bmatrix}0\\\\1\\end{bmatrix}\n",
49 | ",\\quad\n",
50 | "C = \\begin{bmatrix}1&0\\end{bmatrix}\n",
51 | ", \\text{ and }\n",
52 | "D = [0]$$"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": 3,
58 | "metadata": {},
59 | "outputs": [],
60 | "source": [
61 | "sample_freq = 1e3\n",
62 | "A = np.array([[0, 1],\\\n",
63 | " [-100, -2]])\n",
64 | "B = np.array([[0], [1]])\n",
65 | "C = np.array([[1, 0]])\n",
66 | "D = np.array([[0]])\n",
67 | "sys = ctrl.ss(A, B, C, D)\n"
68 | ]
69 | },
70 | {
71 | "cell_type": "markdown",
72 | "metadata": {},
73 | "source": [
74 | "For this example, we will generate an impulse response for only 4 discrete times."
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": 4,
80 | "metadata": {},
81 | "outputs": [
82 | {
83 | "data": {
84 | "text/plain": [
85 | "array([ 0. , 0.16666667, 0.33333333, 0.5 , 0.66666667,\n",
86 | " 0.83333333])"
87 | ]
88 | },
89 | "execution_count": 4,
90 | "metadata": {},
91 | "output_type": "execute_result"
92 | }
93 | ],
94 | "source": [
95 | "t = np.linspace(0, 1, num = 6, endpoint = False)\n",
96 | "dt = t[1]\n",
97 | "t"
98 | ]
99 | },
100 | {
101 | "cell_type": "markdown",
102 | "metadata": {},
103 | "source": [
104 | "The impulse response for a second order underdamped system is known to be\n",
105 | "\n",
106 | "$$h(t) = \\frac{1}{\\omega_d}e^{-\\zeta \\omega_n t}\\sin\\left(\\omega_d t\\right)$$"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 5,
112 | "metadata": {},
113 | "outputs": [],
114 | "source": [
115 | "omega_n = np.sqrt(100)\n",
116 | "zeta = 2/(2*omega_n)\n",
117 | "omega_d = omega_n * np.sqrt(1 - zeta**2)"
118 | ]
119 | },
120 | {
121 | "cell_type": "code",
122 | "execution_count": 6,
123 | "metadata": {},
124 | "outputs": [
125 | {
126 | "data": {
127 | "text/plain": [
128 | "array([ 0. , 0.08474903, -0.01254052, -0.05886968, 0.01769677,\n",
129 | " 0.03956333])"
130 | ]
131 | },
132 | "execution_count": 6,
133 | "metadata": {},
134 | "output_type": "execute_result"
135 | }
136 | ],
137 | "source": [
138 | "h = 1/omega_d * np.exp(-zeta*omega_n*t)*np.sin(omega_d*t)\n",
139 | "h"
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": 7,
145 | "metadata": {},
146 | "outputs": [
147 | {
148 | "data": {
149 | "image/png": "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\n",
150 | "text/plain": [
151 | ""
152 | ]
153 | },
154 | "metadata": {},
155 | "output_type": "display_data"
156 | }
157 | ],
158 | "source": [
159 | "plt.plot(t,h,'.')\n",
160 | "plt.axis([0,1,-0.1,0.1])\n",
161 | "plt.grid()"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": 8,
167 | "metadata": {},
168 | "outputs": [
169 | {
170 | "data": {
171 | "text/plain": [
172 | "array([[ 0. , 0.08474903],\n",
173 | " [ 0.08474903, -0.01254052],\n",
174 | " [-0.01254052, -0.05886968],\n",
175 | " [-0.05886968, 0.01769677]])"
176 | ]
177 | },
178 | "execution_count": 8,
179 | "metadata": {},
180 | "output_type": "execute_result"
181 | }
182 | ],
183 | "source": [
184 | "Hankel_0 = np.vstack((h[0:-2],h[1:-1]))\n",
185 | "Hankel_0 = Hankel_0.T\n",
186 | "Hankel_0"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": 9,
192 | "metadata": {},
193 | "outputs": [
194 | {
195 | "data": {
196 | "text/plain": [
197 | "array([ 0.08474903, -0.01254052, -0.05886968, 0.01769677])"
198 | ]
199 | },
200 | "execution_count": 9,
201 | "metadata": {},
202 | "output_type": "execute_result"
203 | }
204 | ],
205 | "source": [
206 | "h[1:-1]"
207 | ]
208 | },
209 | {
210 | "cell_type": "code",
211 | "execution_count": 10,
212 | "metadata": {},
213 | "outputs": [
214 | {
215 | "data": {
216 | "text/plain": [
217 | "array([-0.01254052, -0.05886968, 0.01769677, 0.03956333])"
218 | ]
219 | },
220 | "execution_count": 10,
221 | "metadata": {},
222 | "output_type": "execute_result"
223 | }
224 | ],
225 | "source": [
226 | "h[2:]"
227 | ]
228 | },
229 | {
230 | "cell_type": "code",
231 | "execution_count": 11,
232 | "metadata": {
233 | "scrolled": true
234 | },
235 | "outputs": [
236 | {
237 | "data": {
238 | "text/plain": [
239 | "array([[ 0.08474903, -0.01254052],\n",
240 | " [-0.01254052, -0.05886968],\n",
241 | " [-0.05886968, 0.01769677],\n",
242 | " [ 0.01769677, 0.03956333]])"
243 | ]
244 | },
245 | "execution_count": 11,
246 | "metadata": {},
247 | "output_type": "execute_result"
248 | }
249 | ],
250 | "source": [
251 | "Hankel_1 = np.vstack((h[1:-1],h[2:]))\n",
252 | "Hankel_1 = Hankel_1.T\n",
253 | "Hankel_1"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": 12,
259 | "metadata": {},
260 | "outputs": [
261 | {
262 | "name": "stdout",
263 | "output_type": "stream",
264 | "text": [
265 | "[[ 0.56941225 0.57615451]\n",
266 | " [-0.59214005 0.56070011]\n",
267 | " [-0.32038129 -0.49580091]\n",
268 | " [ 0.47169449 -0.32839431]]\n",
269 | "[[-0.66563569 0.74627684]\n",
270 | " [ 0.74627684 0.66563569]]\n"
271 | ]
272 | }
273 | ],
274 | "source": [
275 | "U, sig, Vt = la.svd(Hankel_0)\n",
276 | "V = Vt.T\n",
277 | "U = U[:,:2]\n",
278 | "print(U)\n",
279 | "V = V[:,:2]\n",
280 | "print(V)"
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": 13,
286 | "metadata": {},
287 | "outputs": [
288 | {
289 | "name": "stdout",
290 | "output_type": "stream",
291 | "text": [
292 | "[[ 0.11107284 0. ]\n",
293 | " [ 0. 0.0979112 ]]\n"
294 | ]
295 | }
296 | ],
297 | "source": [
298 | "sig = np.diag(sig)\n",
299 | "print(sig)"
300 | ]
301 | },
302 | {
303 | "cell_type": "code",
304 | "execution_count": 14,
305 | "metadata": {},
306 | "outputs": [
307 | {
308 | "name": "stdout",
309 | "output_type": "stream",
310 | "text": [
311 | "[[-0.22322281 0.85303151]\n",
312 | " [-0.85967388 0.07525037]]\n"
313 | ]
314 | }
315 | ],
316 | "source": [
317 | "A_d = la.inv(np.sqrt(sig))@U.T@Hankel_1@V@la.inv(np.sqrt(sig))\n",
318 | "print(A_d)"
319 | ]
320 | },
321 | {
322 | "cell_type": "code",
323 | "execution_count": 15,
324 | "metadata": {},
325 | "outputs": [
326 | {
327 | "name": "stdout",
328 | "output_type": "stream",
329 | "text": [
330 | "[-0.07398622+0.84324217j -0.07398622-0.84324217j]\n",
331 | "[[ 0.12298924-0.69493489j 0.12298924+0.69493489j]\n",
332 | " [ 0.70847664+0.j 0.70847664-0.j ]]\n"
333 | ]
334 | }
335 | ],
336 | "source": [
337 | "lam_d, vec = la.eig(A_d)\n",
338 | "print(lam_d)\n",
339 | "print(vec)"
340 | ]
341 | },
342 | {
343 | "cell_type": "code",
344 | "execution_count": 16,
345 | "metadata": {},
346 | "outputs": [
347 | {
348 | "name": "stdout",
349 | "output_type": "stream",
350 | "text": [
351 | "[[-0.22322281 0.85303151]\n",
352 | " [-0.85967388 0.07525037]]\n",
353 | "[[-0.22322281 -6.06518177e-18j 0.85303151 -3.08660016e-18j]\n",
354 | " [-0.85967388 -5.89516371e-18j 0.07525037 -2.24128429e-17j]]\n"
355 | ]
356 | }
357 | ],
358 | "source": [
359 | "# This should be the same as A_d\n",
360 | "print(A_d)\n",
361 | "print(vec@np.diag(lam_d)@la.inv(vec))"
362 | ]
363 | },
364 | {
365 | "cell_type": "code",
366 | "execution_count": 17,
367 | "metadata": {},
368 | "outputs": [
369 | {
370 | "data": {
371 | "text/plain": [
372 | "array([-1.+9.94987437j, -1.-9.94987437j])"
373 | ]
374 | },
375 | "execution_count": 17,
376 | "metadata": {},
377 | "output_type": "execute_result"
378 | }
379 | ],
380 | "source": [
381 | "lam = np.log(lam_d)/dt\n",
382 | "lam"
383 | ]
384 | },
385 | {
386 | "cell_type": "code",
387 | "execution_count": 18,
388 | "metadata": {},
389 | "outputs": [
390 | {
391 | "name": "stdout",
392 | "output_type": "stream",
393 | "text": [
394 | "The undamped natural frequency is 10.0 rad/sec.\n",
395 | "The damping ratio is 0.09999999999999991.\n"
396 | ]
397 | }
398 | ],
399 | "source": [
400 | "# These are the continuous time eigenvalues\n",
401 | "print('The undamped natural frequency is {} rad/sec.'.format(abs(lam[0])))\n",
402 | "print('The damping ratio is {}.'.format(-np.real(lam[0])/abs(lam[0])))\n"
403 | ]
404 | },
405 | {
406 | "cell_type": "markdown",
407 | "metadata": {},
408 | "source": [
409 | "The identified state matrix is"
410 | ]
411 | },
412 | {
413 | "cell_type": "code",
414 | "execution_count": 19,
415 | "metadata": {},
416 | "outputs": [
417 | {
418 | "name": "stdout",
419 | "output_type": "stream",
420 | "text": [
421 | "[[ -2.76092394 10.06538424]\n",
422 | " [-10.1437611 0.76092394]]\n"
423 | ]
424 | }
425 | ],
426 | "source": [
427 | "A = la.logm(A_d)/dt\n",
428 | "print(A)"
429 | ]
430 | },
431 | {
432 | "cell_type": "markdown",
433 | "metadata": {},
434 | "source": [
435 | "which is the system the result in a balanced realization form. "
436 | ]
437 | },
438 | {
439 | "cell_type": "code",
440 | "execution_count": 20,
441 | "metadata": {},
442 | "outputs": [
443 | {
444 | "name": "stdout",
445 | "output_type": "stream",
446 | "text": [
447 | "[[-0.22184035]\n",
448 | " [ 0.23351573]]\n"
449 | ]
450 | }
451 | ],
452 | "source": [
453 | "# The discrete input matrix is\n",
454 | "B_d = np.sqrt(sig)@V.T[:,0].T.reshape((2,1))\n",
455 | "print(B_d)"
456 | ]
457 | },
458 | {
459 | "cell_type": "code",
460 | "execution_count": 21,
461 | "metadata": {},
462 | "outputs": [
463 | {
464 | "name": "stdout",
465 | "output_type": "stream",
466 | "text": [
467 | "[[-2.58036274]\n",
468 | " [-0.22677789]]\n"
469 | ]
470 | }
471 | ],
472 | "source": [
473 | "# The continuous input matrix is\n",
474 | "B = la.solve((A_d - np.eye(2)), A) @ B_d\n",
475 | "print(B)"
476 | ]
477 | },
478 | {
479 | "cell_type": "code",
480 | "execution_count": 22,
481 | "metadata": {},
482 | "outputs": [
483 | {
484 | "name": "stdout",
485 | "output_type": "stream",
486 | "text": [
487 | "[ 0.18977139 0.18028315]\n"
488 | ]
489 | }
490 | ],
491 | "source": [
492 | "C = (U @ np.sqrt(sig))[0,:]\n",
493 | "print(C)"
494 | ]
495 | },
496 | {
497 | "cell_type": "code",
498 | "execution_count": 23,
499 | "metadata": {},
500 | "outputs": [
501 | {
502 | "data": {
503 | "text/plain": [
504 | "0.0"
505 | ]
506 | },
507 | "execution_count": 23,
508 | "metadata": {},
509 | "output_type": "execute_result"
510 | }
511 | ],
512 | "source": [
513 | "# Of course, D is\n",
514 | "D = h[0]\n",
515 | "D"
516 | ]
517 | },
518 | {
519 | "cell_type": "markdown",
520 | "metadata": {},
521 | "source": [
522 | "# New functions available"
523 | ]
524 | },
525 | {
526 | "cell_type": "code",
527 | "execution_count": 24,
528 | "metadata": {
529 | "scrolled": true
530 | },
531 | "outputs": [
532 | {
533 | "data": {
534 | "text/plain": [
535 | "(array([[ -2.76092394, 10.06538424],\n",
536 | " [-10.1437611 , 0.76092394]]), array([[-2.58036274],\n",
537 | " [-0.22677789]]), array([ 0.18977139, 0.18028315]), 0.0)"
538 | ]
539 | },
540 | "execution_count": 24,
541 | "metadata": {},
542 | "output_type": "execute_result"
543 | }
544 | ],
545 | "source": [
546 | "vt.d2c(A_d, B_d, C, D, dt)"
547 | ]
548 | },
549 | {
550 | "cell_type": "code",
551 | "execution_count": 25,
552 | "metadata": {},
553 | "outputs": [],
554 | "source": [
555 | "C = np.array([[1, 0]])"
556 | ]
557 | },
558 | {
559 | "cell_type": "code",
560 | "execution_count": 26,
561 | "metadata": {},
562 | "outputs": [
563 | {
564 | "data": {
565 | "text/plain": [
566 | "(1, 2)"
567 | ]
568 | },
569 | "execution_count": 26,
570 | "metadata": {},
571 | "output_type": "execute_result"
572 | }
573 | ],
574 | "source": [
575 | "C.shape"
576 | ]
577 | },
578 | {
579 | "cell_type": "code",
580 | "execution_count": 27,
581 | "metadata": {},
582 | "outputs": [
583 | {
584 | "data": {
585 | "text/plain": [
586 | "(2, 2)"
587 | ]
588 | },
589 | "execution_count": 27,
590 | "metadata": {},
591 | "output_type": "execute_result"
592 | }
593 | ],
594 | "source": [
595 | "A.shape"
596 | ]
597 | },
598 | {
599 | "cell_type": "code",
600 | "execution_count": 28,
601 | "metadata": {},
602 | "outputs": [
603 | {
604 | "name": "stdout",
605 | "output_type": "stream",
606 | "text": [
607 | "[[-0.22322281 0.85303151]\n",
608 | " [-0.85967388 0.07525037]]\n",
609 | "[[-0.22184035]\n",
610 | " [ 0.23351573]]\n"
611 | ]
612 | }
613 | ],
614 | "source": [
615 | "Ad, Bd, _, _ = vt.c2d(A, B, C, D, dt)\n",
616 | "print(Ad)\n",
617 | "print(Bd)"
618 | ]
619 | },
620 | {
621 | "cell_type": "code",
622 | "execution_count": 29,
623 | "metadata": {
624 | "scrolled": true
625 | },
626 | "outputs": [
627 | {
628 | "name": "stdout",
629 | "output_type": "stream",
630 | "text": [
631 | "............... Eigenvalue ........... Damping Frequency\n",
632 | "--------[re]---------[im]--------[abs]----------------------[Hz]\n",
633 | " -1.000 +9.950 10.000 0.100 1.592\n",
634 | " -1.000 -9.950 10.000 0.100 1.592\n"
635 | ]
636 | }
637 | ],
638 | "source": [
639 | "vt.damp(A)"
640 | ]
641 | },
642 | {
643 | "cell_type": "code",
644 | "execution_count": 30,
645 | "metadata": {},
646 | "outputs": [
647 | {
648 | "data": {
649 | "text/plain": [
650 | "array([[ 0.06375786],\n",
651 | " [ 0.36686755]])"
652 | ]
653 | },
654 | "execution_count": 30,
655 | "metadata": {},
656 | "output_type": "execute_result"
657 | }
658 | ],
659 | "source": [
660 | "(A_d*dt)@B"
661 | ]
662 | },
663 | {
664 | "cell_type": "code",
665 | "execution_count": null,
666 | "metadata": {},
667 | "outputs": [],
668 | "source": []
669 | }
670 | ],
671 | "metadata": {
672 | "hide_input": false,
673 | "kernelspec": {
674 | "display_name": "Python 3",
675 | "language": "python",
676 | "name": "python3"
677 | },
678 | "language_info": {
679 | "codemirror_mode": {
680 | "name": "ipython",
681 | "version": 3
682 | },
683 | "file_extension": ".py",
684 | "mimetype": "text/x-python",
685 | "name": "python",
686 | "nbconvert_exporter": "python",
687 | "pygments_lexer": "ipython3",
688 | "version": "3.6.3"
689 | },
690 | "latex_envs": {
691 | "bibliofile": "biblio.bib",
692 | "cite_by": "apalike",
693 | "current_citInitial": 1,
694 | "eqLabelWithNumbers": true,
695 | "eqNumInitial": 0
696 | },
697 | "toc": {
698 | "nav_menu": {},
699 | "number_sections": true,
700 | "sideBar": true,
701 | "skip_h1_title": false,
702 | "toc_cell": false,
703 | "toc_position": {},
704 | "toc_section_display": "block",
705 | "toc_window_display": false
706 | },
707 | "varInspector": {
708 | "cols": {
709 | "lenName": 16,
710 | "lenType": 16,
711 | "lenVar": 40
712 | },
713 | "kernels_config": {
714 | "python": {
715 | "delete_cmd_postfix": "",
716 | "delete_cmd_prefix": "del ",
717 | "library": "var_list.py",
718 | "varRefreshCmd": "print(var_dic_list())"
719 | },
720 | "r": {
721 | "delete_cmd_postfix": ") ",
722 | "delete_cmd_prefix": "rm(",
723 | "library": "var_list.r",
724 | "varRefreshCmd": "cat(var_dic_list()) "
725 | }
726 | },
727 | "types_to_exclude": [
728 | "module",
729 | "function",
730 | "builtin_function_or_method",
731 | "instance",
732 | "_Feature"
733 | ],
734 | "window_display": false
735 | }
736 | },
737 | "nbformat": 4,
738 | "nbformat_minor": 1
739 | }
740 |
--------------------------------------------------------------------------------
/vibrationtesting/system.py:
--------------------------------------------------------------------------------
1 | """
2 | System manipulation, reduction, and corrections functions.
3 |
4 | @author: Joseph C. Slater
5 | """
6 | __license__ = "Joseph C. Slater"
7 |
8 | __docformat__ = 'reStructuredText'
9 |
10 |
11 | import math
12 |
13 | import numpy as np
14 | import scipy.signal as sig
15 | import scipy.linalg as la
16 |
17 |
18 | def d2c(Ad, Bd, C, D, dt):
19 | r"""Return continuous A, B, C, D from discrete form.
20 |
21 | Converts a set of digital state space system matrices to their
22 | continuous counterpart.
23 |
24 | Parameters
25 | ----------
26 | Ad, Bd, C, D : float arrays
27 | Discrete state space system matrices
28 | dt : float
29 | Time step of discrete system
30 |
31 | Returns
32 | -------
33 | A, B, C, D : float arrays
34 | State space system matrices
35 |
36 | Examples
37 | --------
38 | >>> import vibrationtesting as vt
39 | >>> Ad = np.array([[ 0.9999,0.0001,0.01,0.],
40 | ... [ 0.,0.9999,0.,0.01],
41 | ... [-0.014,0.012,0.9999,0.0001],
42 | ... [ 0.008, -0.014,0.0001,0.9999]])
43 | >>> Bd = np.array([[ 0. ],
44 | ... [ 0. ],
45 | ... [ 0. ],
46 | ... [ 0.01]])
47 | >>> C = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
48 | >>> D = np.array([[-0.2]])
49 | >>> A, B, *_ = vt.d2c(Ad, Bd, C, D, 0.01)
50 | >>> print(A)
51 | [[-0.003 0.004 1.0001 -0.0001]
52 | [-0.004 -0.003 -0. 1.0001]
53 | [-1.4001 1.2001 -0.003 0.004 ]
54 | [ 0.8001 -1.4001 0.006 -0.003 ]]
55 |
56 | Notes
57 | -----
58 | .. note:: Zero-order hold solution
59 | .. note:: discrepancies between :func:`c2d` and :func:`d2c` are due to \
60 | typing truncation errors.
61 | .. seealso:: :func:`c2d`.
62 |
63 | """
64 | # Old school (very old)
65 | # A = la.logm(Ad) / dt
66 | # B = la.solve((Ad - np.eye(A.shape[0])), A) @ Bd
67 | sa = Ad.shape[0]
68 | sb = Bd.shape[1]
69 | AAd = np.vstack((np.hstack((Ad, Bd)),
70 | np.hstack((np.zeros((sb, sa)), np.eye(sb)))))
71 | AA = la.logm(AAd) / dt
72 | A = AA[0:sa, 0:sa]
73 | B = AA[0:sa, sa:]
74 | return A, B, C, D
75 |
76 |
77 | def c2d(A, B, C, D, dt):
78 | """Convert continuous state system to discrete time.
79 |
80 | Converts a set of continuous state space system matrices to their
81 | discrete counterpart.
82 |
83 | Parameters
84 | ----------
85 | A, B, C, D : float arrays
86 | State space system matrices
87 | dt : float
88 | Time step of discrete system
89 |
90 | Returns
91 | -------
92 | Ad, Bd, C, D : float arrays
93 | Discrete state space system matrices
94 |
95 | Examples
96 | --------
97 | >>> import numpy as np
98 | >>> np.set_printoptions(precision=4, suppress=True)
99 | >>> import vibrationtesting as vt
100 | >>> A1 = np.array([[ 0., 0. , 1. , 0. ]])
101 | >>> A2 = np.array([[ 0., 0. , 0. , 1. ]])
102 | >>> A3 = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
103 | >>> A4 = np.array([[ 0.8, -1.4, 0.0016, -0.0038]])
104 | >>> A = np.array([[ 0., 0. , 1. , 0. ],
105 | ... [ 0., 0. , 0. , 1. ],
106 | ... [-1.4, 1.2, -0.0058, 0.0014],
107 | ... [ 0.8, -1.4, 0.0016, -0.0038]])
108 | >>> B = np.array([[ 0.],
109 | ... [ 0.],
110 | ... [ 0.],
111 | ... [ 1.]])
112 | >>> C = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
113 | >>> D = np.array([[-0.2]])
114 | >>> Ad, Bd, *_ = vt.c2d(A, B, C, D, 0.01)
115 |
116 | Notes
117 | -----
118 | .. note:: Zero-order hold solution
119 | .. seealso:: :func:`d2c`.
120 |
121 | """
122 | Ad, Bd, _, _, _ = sig.cont2discrete((A, B, C, D), dt)
123 | # Ad = la.expm(A * dt)
124 | # Bd = la.solve(A, (Ad - np.eye(A.shape[0]))) @ B
125 | return Ad, Bd, C, D
126 |
127 |
128 | def ssfrf(A, B, C, D, omega_low, omega_high, in_index, out_index,
129 | num_freqs=1000):
130 | """Return FRF of state space system.
131 |
132 | Obtains the computed FRF of a state space system between selected input
133 | and output over frequency range of interest.
134 |
135 | Parameters
136 | ----------
137 | A, B, C, D : float arrays
138 | state system matrices
139 | omega_low, omega_high : floats
140 | low and high frequencies for evaluation
141 | in_index, out_index : ints
142 | input and output numbers (starting at 1)
143 | num_freqs : int
144 | number of frequencies at which to return FRF
145 |
146 | Returns
147 | -------
148 | omega : float array
149 | frequency vector
150 | H : float array
151 | frequency response function
152 |
153 | Examples
154 | --------
155 | >>> import vibrationtesting as vt
156 | >>> A = np.array([[ 0., 0. , 1. , 0. ],
157 | ... [ 0., 0. , 0. , 1. ],
158 | ... [-1.4, 1.2, -0.0058, 0.0014],
159 | ... [ 0.8, -1.4, 0.0016, -0.0038]])
160 | >>> B = np.array([[ 0.],
161 | ... [ 0.],
162 | ... [ 0.],
163 | ... [ 1.]])
164 | >>> C = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
165 | >>> D = np.array([[-0.2]])
166 | >>> omega, H = vt.ssfrf(A, B, C, D, 0, 3.5, 1, 1)
167 | >>> vt.frfplot(omega, H) # doctest: +SKIP
168 |
169 | """
170 | # A, B, C, D = ctrl.ssdata(sys)
171 | if 0 < in_index < (B.shape[1] + 1) and 0 < out_index < (C.shape[0] + 1):
172 | sa = A.shape[0]
173 | omega = np.linspace(omega_low, omega_high, num_freqs)
174 | H = omega * 1j
175 | i = 0
176 | for i, w in enumerate(omega):
177 | H[i] = (C@la.solve(w * 1j * np.eye(sa) - A, B) + D)[out_index - 1,
178 | in_index - 1]
179 | else:
180 | raise ValueError(
181 | 'Input {} or output {} infeasible.'.format(in_index, out_index))
182 | return omega.reshape(1, -1), H.reshape(1, -1)
183 |
184 |
185 | def sos_frf(M, C, K, Bt, Cd, Cv, Ca, omega_low, omega_high,
186 | in_index, out_index, num_freqs=1000):
187 | r"""Return FRF of second order system.
188 |
189 | Given second order linear matrix equation of the form
190 | :math:`M\\ddot{x} + C \\dot{x} + K x= \\tilde{B} u`
191 | and
192 | :math:`y = C_d x + C_v \\dot{x} + C_a\\ddot{x}`
193 | converts to state space form and returns the requested frequency response
194 | function
195 |
196 | Parameters
197 | ----------
198 | M, C, K, Bt, Cd, Cv, Cd : float arrays
199 | Mass, damping, stiffness, input, displacement sensor, velocimeter,
200 | and accelerometer matrices
201 | omega_low, omega_high : floats
202 | low and high frequencies for evaluation
203 | in_index, out_index : ints
204 | input and output numbers (starting at 1)
205 | num_freqs : int
206 | number of frequencies at which to return FRF
207 |
208 | Returns
209 | -------
210 | omega : float array
211 | frequency vector
212 | H : float array
213 | frequency response function
214 |
215 | Examples not working for second order system
216 |
217 | Need to make one for second order expansion
218 |
219 | Examples
220 | --------
221 | >>> import vibrationtesting as vt
222 | >>> A = np.array([[ 0., 0. , 1. , 0. ],
223 | ... [ 0., 0. , 0. , 1. ],
224 | ... [-1.4, 1.2, -0.0058, 0.0014],
225 | ... [ 0.8, -1.4, 0.0016, -0.0038]])
226 | >>> B = np.array([[ 0.],
227 | ... [ 0.],
228 | ... [ 0.],
229 | ... [ 1.]])
230 | >>> C = np.array([[-1.4, 1.2, -0.0058, 0.0014]])
231 | >>> D = np.array([[-0.2]])
232 | >>> omega, H = vt.ssfrf(A, B, C, D, 0, 3.5, 1, 1)
233 | >>> vt.frfplot(omega, H) # doctest: +SKIP
234 |
235 | """
236 | A, B, C, D = so2ss(M, C, K, Bt, Cd, Cv, Ca)
237 |
238 | omega, H = ssfrf(A, B, C, D, omega_low, omega_high, in_index, out_index,
239 | num_freqs)
240 |
241 | return omega, H
242 |
243 |
244 | def so2ss(M, C, K, Bt, Cd, Cv, Ca):
245 | r"""Convert second order system to state space.
246 |
247 | Given second order linear matrix equation of the form
248 | :math:`M\\ddot{x} + C \\dot{x} + K x= \\tilde{B} u`
249 | and
250 | :math:`y = C_d x + C_v \\dot{x} + C_a\\ddot{x}`
251 | returns the state space form equations
252 | :math:`\\dot{z} = A z + B u`,
253 | :math:`y = C z + D u`
254 |
255 | Parameters
256 | ----------
257 | M, C, K, Bt, Cd, Cv, Cd : float arrays
258 | Mass , damping, stiffness, input, displacement sensor, velocimeter,
259 | and accelerometer matrices
260 |
261 | Returns
262 | -------
263 | A, B, C, D : float arrays
264 | State matrices
265 |
266 | Examples
267 | --------
268 | >>> import vibrationtesting as vt
269 | >>> M = np.array([[2, 1],
270 | ... [1, 3]])
271 | >>> K = np.array([[2, -1],
272 | ... [-1, 3]])
273 | >>> C = np.array([[0.01, 0.001],
274 | ... [0.001, 0.01]])
275 | >>> Bt = np.array([[0], [1]])
276 | >>> Cd = Cv = np.zeros((1,2))
277 | >>> Ca = np.array([[1, 0]])
278 | >>> A, B, Css, D = vt.so2ss(M, C, K, Bt, Cd, Cv, Ca)
279 | >>> print('A: {}'.format(A))
280 | A: [[ 0. 0. 1. 0. ]
281 | [ 0. 0. 0. 1. ]
282 | [-1.4 1.2 -0.0058 0.0014]
283 | [ 0.8 -1.4 0.0016 -0.0038]]
284 | >>> print('B: ', B)
285 | B: [[ 0. ]
286 | [ 0. ]
287 | [-0.2]
288 | [ 0.4]]
289 | >>> print('C: ', Css)
290 | C: [[-1.4 1.2 -0.0058 0.0014]]
291 | >>> print('D: ', D)
292 | D: [[-0.2]]
293 |
294 | """
295 | A = np.vstack((np.hstack((np.zeros_like(M), np.eye(M.shape[0]))),
296 | np.hstack((-la.solve(M, K), -la.solve(M, C)))))
297 | B = np.vstack((np.zeros_like(Bt), la.solve(M, Bt)))
298 | C_ss = np.hstack((Cd - Ca@la.solve(M, K), Cv - Ca@la.solve(M, C)))
299 | D = Ca@la.solve(M, Bt)
300 |
301 | return A, B, C_ss, D
302 |
303 |
304 | def damp(A):
305 | """Display natural frequencies and damping ratios of state matrix."""
306 | # Original Author: Kai P. Mueller for Octave
307 | # Created: September 29, 1997.
308 |
309 | print("............... Eigenvalue ........... Damping Frequency")
310 | print("--------[re]---------[im]--------[abs]----------------------[Hz]")
311 | e, _ = la.eig(A)
312 |
313 | for i in range(len(e)):
314 | pole = e[i]
315 |
316 | d0 = -np.cos(math.atan2(np.imag(pole), np.real(pole)))
317 | f0 = 0.5 / np.pi * abs(pole)
318 | if (abs(np.imag(pole)) < abs(np.real(pole))):
319 | print(' {:.3f} {:.3f} \
320 | {:.3f} {:.3f}'.format(float(np.real(pole)),
321 | float(abs(pole)), float(d0),
322 | float(f0)))
323 |
324 | else:
325 | print(' {:.3f} {:+.3f} {:.3f} \
326 | {:.3f} {:.3f}'.format(float(np.real(pole)),
327 | float(np.imag(pole)), float(
328 | abs(pole)), float(d0),
329 | float(f0)))
330 |
331 |
332 | def sos_modal(M, K, C=False, damp_diag=0.03, shift=1):
333 | r"""Eigen analysis of proportionally damped system.
334 |
335 | Optimally find mass normalized mode shapes and natural frequencies
336 | of a system modelled by :math:`M\ddot{x}+Kx=0`.
337 |
338 | If provided, obtain damping ratios presuming :math:`C` can be decoupled.
339 |
340 | Provides a warning if diagonalization of damping matrix fails worse than
341 | relative error of `damp_diag`.
342 |
343 | Parameters
344 | ----------
345 | M, K : float arrays
346 | Mass and stiffness matrices
347 | C : float array, optional
348 | Damping matrix
349 | damp_diag : float, optional
350 | Maximum amount of off-diagonal error allowed in assuming C can be
351 | diagonalized
352 | shift : float, optional
353 | Shift used in eigensolution. Should be approximately equal to the first
354 | non-zero eigenvalue.
355 |
356 | Returns
357 | -------
358 | omega : float array (1xN)
359 | Vector of natural frequencies (rad/sec)
360 | zeta : float array (1xN)
361 | Vector of damping ratios
362 | Psi : float array (NxN)
363 | Matrix of mass normalized mode shapes by column
364 |
365 |
366 | Examples
367 | --------
368 | >>> import numpy as np
369 | >>> import vibrationtesting as vt
370 | >>> M = np.array([[4, 0, 0],
371 | ... [0, 4, 0],
372 | ... [0, 0, 4]])
373 | >>> K = np.array([[8, -4, 0],
374 | ... [-4, 8, -4],
375 | ... [0, -4, 4]])
376 | >>> omega, zeta, Psi = vt.sos_modal(M, K, K/10)
377 |
378 | """
379 | K = K + M * shift
380 |
381 | lam, Psi = la.eigh(K, M)
382 |
383 | omega = np.sqrt(np.abs(lam - shift)) # round to zero
384 |
385 | norms = np.diag(1.0 / np.sqrt(np.diag(Psi.T@M@Psi)))
386 |
387 | Psi = Psi @ norms
388 |
389 | zeta = np.zeros_like(omega)
390 |
391 | if C is not False:
392 | diagonalized_C = Psi.T@C@Psi
393 |
394 | diagonal_C = np.diag(diagonalized_C)
395 |
396 | if min(omega) > 1e-5:
397 | zeta = diagonal_C / 2 / omega # error if omega = 0
398 | max_off_diagonals = np.amax(np.abs(diagonalized_C
399 | - np.diag(diagonal_C)), axis=0)
400 | # error if no damping
401 | damp_error = np.max(max_off_diagonals / diagonal_C)
402 | else:
403 | zeta = np.zeros_like(omega)
404 | damp_error = np.zeros_like(omega)
405 | de_diag_C = diagonalized_C - np.diag(diagonal_C)
406 | for mode_num, omega_i in enumerate(omega):
407 | if omega[mode_num] > 1e-5:
408 | zeta[mode_num] = diagonal_C[mode_num] / 2 / omega_i
409 | damp_error = (np.max(np.abs(de_diag_C[:, mode_num]))
410 | / diagonal_C[mode_num])
411 |
412 | if damp_error > damp_diag:
413 | print('Damping matrix cannot be completely diagonalized.')
414 | print('Off diagonal error of {:4.0%}.'.format(damp_error))
415 |
416 | return omega, zeta, Psi
417 |
418 |
419 | def serep(M, K, master):
420 | r"""System Equivalent Reduction Expansion Process reduced model.
421 |
422 | Reduce size of second order system of equations by SEREP processs while
423 | returning expansion matrix
424 |
425 | Equation of the form:
426 | :math:`M \ddot{x} + K x = 0`
427 | is reduced to the form
428 | :math:`M_r \ddot{x}_m + Kr x_m = 0`
429 | where :math:`x = T x_m`, :math:`M_r= T^T M T`, :math:`K_r= T^T K T`
430 |
431 | Parameters
432 | ----------
433 | M, K : float arrays
434 | Mass and Stiffness matrices
435 | master : float array or list
436 | List of retained degrees of freedom
437 |
438 | Returns
439 | -------
440 | Mred, Kred, T : float arrays
441 | Reduced Mass matric, reduced stiffness matrix, Transformation matrix
442 | truncated_dofs : int list
443 | List of truncated degrees of freedom, zero indexed
444 |
445 | Examples
446 | --------
447 | >>> import vibrationtesting as vt
448 | >>> M = np.array([[4, 0, 0],
449 | ... [0, 4, 0],
450 | ... [0, 0, 4]])
451 | >>> K = np.array([[8, -4, 0],
452 | ... [-4, 8, -4],
453 | ... [0, -4, 4]])
454 | >>> retained = np.array([[1, 2]])
455 | >>> Mred, Kred, T, truncated_dofs = vt.serep(M, K, retained)
456 |
457 | Notes
458 | -----
459 | Reduced coordinate system forces can be obtained by
460 | `Fr = T.T @ F`
461 |
462 | Reduced damping matrix can be obtained using `Cr = T.T*@ C @ T`.
463 |
464 | If mode shapes are obtained for the reduced system, full system mode shapes
465 | are `phi = T @ phi_r`
466 |
467 | .. seealso:: :func:`guyan`
468 |
469 | """
470 | nm = int(max(master.shape)) # number of modes to keep;
471 | master = master.reshape(-1) - 1 # retained dofs
472 |
473 | ndof = int(M.shape[0]) # length(M);
474 |
475 | omega, _, Psi = sos_modal(M, K)
476 | Psi_tr = Psi[nm:, :nm] # Truncated modes
477 | Psi_rr = Psi[:nm, :nm] # Retained modes
478 |
479 | truncated_dofs = list(set(np.arange(ndof)) - set(master))
480 |
481 | T = np.zeros((ndof, nm))
482 | T[master, :nm] = np.eye(nm)
483 | T[truncated_dofs, :nm] = la.solve(Psi_rr.T, Psi_tr.T).T
484 | Mred = T.T @ M @T
485 | Kred = T.T @ K @T
486 |
487 | return Mred, Kred, T, np.array(truncated_dofs)
488 |
489 |
490 | def guyan(M, K, master=None, fraction=None):
491 | r"""Guyan reduced model.
492 |
493 | Applies Guyan Reductions to second order system of equations of the form
494 |
495 | .. math:: M \ddot{x} + K x = 0
496 |
497 | which are reduced to the form
498 |
499 | .. math:: M_r \ddot{x}_m + K_r x_m = 0
500 |
501 | where
502 |
503 | .. math::
504 |
505 | x = T x_m
506 |
507 | M_r= T^T M T
508 |
509 | K_r= T^T K T
510 |
511 | Parameters
512 | ----------
513 | M, K : float arrays
514 | Mass and Stiffness matrices
515 | master : float array or list, optional
516 | List of retained degrees of freedom (0 indexing)
517 | fraction : float, optional
518 | Fraction of degrees of freedom (0< `fraction` <1.0) to retain in model.
519 | If both master and fraction are neglected, fraction is set to 0.25.
520 |
521 | Returns
522 | -------
523 | Mred, Kred, T : float arrays
524 | Reduced Mass matric, Reduced Stiffness matrix, Transformation matrix
525 | master_dofs : int list
526 | List of master degrees of freedom (0 indexing)
527 | truncated_dofs : int list
528 | List of truncated degrees of freedom (0 indexing)
529 |
530 | Examples
531 | --------
532 | >>> import vibrationtesting as vt
533 | >>> M = np.array([[4, 0, 0],
534 | ... [0, 4, 0],
535 | ... [0, 0, 4]])
536 | >>> K = np.array([[8, -4, 0],
537 | ... [-4, 8, -4],
538 | ... [0, -4, 4]])
539 | >>> Mred, Kred, T, master, truncated_dofs = vt.guyan(M, K, fraction = 0.5)
540 |
541 |
542 | Notes
543 | -----
544 | Reduced coordinate system forces can be obtained by `Fr = T.T @ F`.
545 |
546 | Reduced damping matrix can be obtained using `Cr = T.T @ C @ T`.
547 |
548 | If mode shapes are obtained for the reduced system, full system mode shapes
549 | are `phi = T @ phi_r`.
550 |
551 | Code is not as efficient as possible. Using block submatrices would be
552 | more efficient.
553 |
554 | """
555 | if master is None:
556 | if fraction is None:
557 | fraction = 0.25
558 |
559 | ratios = np.diag(M) / np.diag(K)
560 | ranked = [i[0] for i in sorted(enumerate(ratios), key=lambda x: x[1])]
561 | thresh = int(fraction * ratios.size)
562 | if (thresh >= ratios.size) or thresh == 0:
563 | print("Can't keep", thresh, 'DOFs.')
564 | print("Fraction of", fraction, "is too low or too high.")
565 | return 0, 0, 0, 0, 0
566 |
567 | master = ranked[-thresh:]
568 |
569 | master = np.array(master)
570 | nm = master.size # number of dofs to keep;
571 | master = master.reshape(-1) # retained dofs
572 |
573 | ndof = int(M.shape[0]) # length(M);
574 |
575 | truncated_dofs = list(set(np.arange(ndof)) - set(master))
576 | # truncated_dofs = np.array(set(np.arange(ndof)) - set(master))
577 | """
578 | Mmm = M[master].T[master].T
579 | Kmm = K[master].T[master].T
580 | Mtm = M[truncated_dofs].T[master].T
581 | Ktm = K[truncated_dofs].T[master].T
582 | Mtt = M[truncated_dofs].T[truncated_dofs].T
583 | Ktt = K[truncated_dofs].T[truncated_dofs].T"""
584 |
585 | # Mmm = slice(M, master, master)
586 | # Kmm = slice(K, master, master)
587 | # Mtm = slice(M, truncated_dofs, master)
588 | Ktm = slice(K, truncated_dofs, master)
589 | # Mtt = slice(M, truncated_dofs, truncated_dofs)
590 | Ktt = slice(K, truncated_dofs, truncated_dofs)
591 |
592 | T = np.zeros((ndof, nm))
593 | T[master, :nm] = np.eye(nm)
594 | T[truncated_dofs, :nm] = la.solve(-Ktt, Ktm)
595 | Mred = T.T @ M @ T
596 | Kred = T.T @ K @ T
597 | return Mred, Kred, T, master, truncated_dofs
598 |
599 |
600 | def mode_expansion_from_model(Psi, omega, M, K, measured):
601 | r"""Deflection extrapolation to full FEM model coordinates, matrix method.
602 |
603 | Provided an equation of the form:
604 |
605 | :math:`\begin{pmatrix}-\begin{bmatrix}M_{mm}&M_{mu}\\ M_{um}&M_{uu}
606 | \end{bmatrix} \omega_i^2
607 | +\begin{bmatrix}K_{mm}&K_{mu}\\ K_{um}&K_{uu}\end{bmatrix}\end{pmatrix}`
608 | :math:`\begin{bmatrix}\Psi_{i_m}\\ \Psi_{i_u}\end{bmatrix}= 0`
609 |
610 | Where:
611 |
612 | - :math:`M` and :math:`K` are the mass and stiffness matrices, likely from
613 | a finite element model
614 | - :math:`\Psi_i` and :math:`\omega_i` represent a mode/frequency pair
615 | - subscripts :math:`m` and :math:`u` represent measure and unmeasured
616 | of the mode
617 |
618 | Determines the unknown portion of the mode (or operational deflection)
619 | shape, :math:`\Psi_{i_u}` by
620 | direct algebraic solution, aka
621 |
622 | :math:`\Psi_{i_u} = - (K_{uu}- M_{ss} \omega_i^2) ^{-1}
623 | (K_{um}-M_{um}\omega_i^2)\Psi_{i_m}`
624 |
625 | Parameters
626 | ----------
627 | Psi : float array
628 | mode shape or shapes, 2-D array columns of which are mode shapes
629 | omega : float or 1-D float array
630 | natural (or driving) frequencies
631 | M, K : float arrays
632 | Mass and Stiffness matrices
633 | measured : float or integer array or list
634 | List of measured degrees of freedom (0 indexed)
635 |
636 | Returns
637 | -------
638 | Psi_full : float array
639 | Complete mode shape
640 |
641 | Examples
642 | --------
643 | >>> import vibrationtesting as vt
644 | >>> M = np.array([[4, 0, 0],
645 | ... [0, 4, 0],
646 | ... [0, 0, 4]])
647 | >>> K = np.array([[8, -4, 0],
648 | ... [-4, 8, -4],
649 | ... [0, -4, 4]])
650 | >>> measured = np.array([[0, 2]])
651 | >>> omega, zeta, Psi = vt.sos_modal(M, K)
652 | >>> Psi_measured = np.array([[-0.15], [-0.37]])
653 | >>> Psi_full = vt.mode_expansion_from_model(Psi_measured, omega[0], M, K,
654 | ... measured)
655 | >>> print(np.hstack((Psi[:,0].reshape(-1,1), Psi_full)))
656 | [[-0.164 -0.15 ]
657 | [-0.2955 0.2886]
658 | [-0.3685 -0.37 ]]
659 |
660 | Notes
661 | -----
662 | .. seealso:: incomplete multi-mode update. Would require each at a
663 | different frequency.
664 |
665 | """
666 | measured = measured.reshape(-1) # retained dofs
667 | num_measured = len(measured)
668 | ndof = int(M.shape[0]) # length(M);
669 | unmeasured_dofs = list(set(np.arange(ndof)) - set(measured))
670 | num_unmeasured = len(unmeasured_dofs)
671 |
672 | # Code from before my slicing code
673 | """
674 | Muu = np.array(M[unmeasured_dofs].T[unmeasured_dofs].T).
675 | reshape(num_unmeasured, num_unmeasured)
676 |
677 | Kuu = np.array(K[unmeasured_dofs].T[unmeasured_dofs].T)
678 | .reshape(num_unmeasured, num_unmeasured)
679 | Mum = np.array(M[unmeasured_dofs].T[measured].T).reshape(num_unmeasured,
680 | num_measured)
681 | Kum = np.array(K[unmeasured_dofs].T[measured].T).reshape(num_unmeasured,
682 | num_measured)
683 | """
684 |
685 | Muu = slice(M, unmeasured_dofs, unmeasured_dofs)
686 | Kuu = slice(K, unmeasured_dofs, unmeasured_dofs)
687 | Mum = slice(M, unmeasured_dofs, measured)
688 | Kum = slice(K, unmeasured_dofs, measured)
689 |
690 | if isinstance(omega, float):
691 | omega = np.array(omega).reshape(1)
692 |
693 | Psi_full = np.zeros((num_measured + num_unmeasured, Psi.shape[1]))
694 | Psi_full[measured] = Psi
695 |
696 | for i, omega_n in enumerate(omega):
697 | Psi_i = Psi[:, i].reshape(-1, 1)
698 | Psi_unmeasured = la.solve((Kuu - Muu * omega_n**2),
699 | (Kum - Mum * omega_n**2)@Psi_i)
700 | Psi_unmeasured = Psi_unmeasured.reshape(-1, )
701 | Psi_full[unmeasured_dofs, i] = Psi_unmeasured
702 | # Psi_full = Psi_full.reshape(-1, 1)
703 | return Psi_full
704 |
705 |
706 | def improved_reduction(M, K, master=None, fraction=None):
707 | """Incomplete.
708 |
709 | 4.14 Friswell
710 |
711 | """
712 | print('not written yet')
713 | return
714 |
715 |
716 | def model_correction_direct(Psi, omega, M, K, method='Baruch'):
717 | """Direct model updating using model data.
718 |
719 | Parameters
720 | ----------
721 | Psi : float array
722 | Expanded mode shapes from experimental measurements. Must be
723 | real-valued. See `mode_expansion_from_model`.
724 | omega : float array
725 | Natural frequencies identified from modal analysis (diagonal matrix or
726 | vector).
727 | M, K : float arrays
728 | Analytical mass and stiffness matrices
729 | method : string, optional
730 | `Baruch` and Bar-Itzhack [1]_ or `Berman` and Nagy [2]_
731 | (default Baruch)
732 |
733 | Returns
734 | -------
735 | Mc, Kc : float arrays
736 | Corrected mass and stiffness matrices.
737 |
738 | Notes
739 | -----
740 | .. [1] Baruch, M. and Bar-Itzhack, I.Y., "Optimal Weighted
741 | Orthogonalization of Measured Modes," *AIAA Journal*, 16(4), 1978, pp.
742 | 346-351.
743 | .. [2] Berman, A. and Nagy, E.J., 1983, "Improvements of a Large Analytical
744 | Model using Test Data," *AIAA Journal*, 21(8), 1983, pp. 1168-1173.
745 |
746 | """
747 | if len(omega.shape) == 1:
748 | omega = np.diag(omega)
749 | elif omega.shape[0] != omega.shape[1]:
750 | omega = np.diag(omega)
751 |
752 | lam = omega @ omega
753 |
754 | if method is 'Berman':
755 |
756 | Mdiag = Psi.T@M@Psi
757 | eye_size = Mdiag.shape[0]
758 | Mc = (M + M @ Psi @ la.solve(Mdiag, np.eye(eye_size) - Mdiag)
759 | @ la.solve(Mdiag, Psi.T) @ M)
760 |
761 | Kc = (K - K @ Psi @ Psi.T @ M
762 | - M @ Psi @ Psi.T @ K
763 | + M @Psi@ Psi.T @ K @ Psi @ Psi.T @ M
764 | + M @ Psi @ lam @ Psi.T @ M)
765 |
766 | else: # Defaults to Baruch method.
767 | Phi = rsolve(la.sqrtm(Psi.T @ M @ Psi), Psi, assume_a='pos')
768 |
769 | PhiPhiT = Phi@Phi.T
770 |
771 | sec_term = K @ PhiPhiT @ M
772 |
773 | Kc = K - sec_term - sec_term.T \
774 | + M @ PhiPhiT @ K @ PhiPhiT @ M\
775 | + M @ Phi @ lam @ Phi.T @ M
776 |
777 | Mc = M
778 |
779 | return Mc, Kc
780 |
781 |
782 | def slice(Matrix, a, b):
783 | """Slice a matrix properly- like Octave.
784 |
785 | Addresses the confounding inconsistency that `M[a,b]` acts differently if
786 | `a` and `b` are the same length or different lengths.
787 |
788 | Parameters
789 | ----------
790 | Matrix : float array
791 | Arbitrary array
792 | a, b : int lists or arrays
793 | list of rows and columns to be selected from `Matrix`
794 |
795 | Returns
796 | -------
797 | Matrix : float array
798 | Properly sliced matrix- no casting allowed.
799 |
800 | """
801 | # a = a.reshape(-1)
802 | # b = b.reshape(-1)
803 |
804 | return (Matrix[np.array(a).reshape(-1, 1), b]
805 | .reshape(np.array(a).shape[0], np.array(b).shape[0]))
806 |
807 |
808 | def rsolve(B, C, **kwargs):
809 | """Solve right Gauss elimination equation.
810 |
811 | Given :math:`A B = C` return :math:`A = C B^{-1}`
812 |
813 | This uses `scipy.linalg.solve` with a little matrix manipulation first.
814 | All keyword arguments of `scipy.linalg.solve` may be used.
815 |
816 | Parameters
817 | ----------
818 | B, C : float arrays
819 |
820 | Returns
821 | -------
822 | A : float array
823 |
824 | Examples
825 | --------
826 | >>> import numpy as np
827 | >>> import vibrationtesting as vt
828 | >>> B = np.array([[ 8, -4, 0],
829 | ... [-4, 8, -4],
830 | ... [ 0, -4, 4]])
831 | >>> C = np.array([[ 32, -16, 0],
832 | ... [-16, 36, -20],
833 | ... [ 4, -24, 20]])
834 | >>> A = vt.rsolve(B, C)
835 | >>> print(np.round(rsolve(B, C)))
836 | [[ 4. 0. 0.]
837 | [-0. 4. -1.]
838 | [ 0. -1. 4.]]
839 |
840 | Notes
841 | -----
842 | .. seealso:: `scipy.linalg.solve`
843 |
844 | """
845 | return la.solve(B.T, C.T, **kwargs).T
846 |
847 |
848 | def real_modes(Psi, autorotate=True):
849 | r"""Real modes from complex modes.
850 |
851 | Assuming a transformation
852 |
853 | .. math:: Psi_{real} = Psi_{complex} T
854 |
855 | exists, where :math:`T` is a complex transformation matrix, find
856 | :math:`Psi_{real}` using linear algebra.
857 |
858 | Parameters
859 | ----------
860 | Psi : complex float array
861 | Complex mode shapes (displacement)
862 | autorotate : Boolean, optional
863 | Attempt to rotate to near-real first
864 |
865 | Returns
866 | -------
867 | Psi : float array
868 | Real modes
869 |
870 | Examples
871 | --------
872 | >>> import vibrationtesting as vt
873 | >>> M = np.array([[4, 0, 0],
874 | ... [0, 4, 0],
875 | ... [0, 0, 4]])
876 | >>> Cso = np.array([[.1,0,0],
877 | ... [0,0,0],
878 | ... [0,0,0]])
879 | >>> K = np.array([[8, -4, 0],
880 | ... [-4, 8, -4],
881 | ... [0, -4, 4]])
882 | >>> Bt = np.array([[1],[0],[0]])
883 | >>> Ca = np.array([[1,0,0]])
884 | >>> Cd = Cv = np.zeros_like(Ca)
885 | >>> A, B, C, D = vt.so2ss(M, Cso, K, Bt, Cd, Cv, Ca)
886 | >>> Am, Bm, Cm, Dm, eigenvalues, modes = vt.ss_modal(A, B, C, D)
887 | >>> Psi = vt.real_modes(modes[:,0::2])
888 |
889 | Notes
890 | -----
891 | .. note:: Rotation of modes should be performed to get them as close to
892 | real as possible first.
893 | .. warning:: Current autorotate bases the rotation on de-rotating the first
894 | element of each vector. User can use their own pre-process by doing to
895 | and setting `autorotate` to False.
896 |
897 | """
898 | if autorotate is True:
899 | Psi = Psi@np.diag(np.exp(np.angle(Psi[0, :]) * -1j))
900 | Psi_real = np.real(Psi)
901 | Psi_im = np.imag(Psi)
902 |
903 | Psi = Psi_real - Psi_im @ la.lstsq(Psi_real, Psi_im)[0]
904 | return Psi
905 |
906 |
907 | def ss_modal(A, B=None, C=None, D=None):
908 | r"""State space modes, frequencies, damping ratios, and modal matrices.
909 |
910 | Parameters
911 | ----------
912 | A, B, C, D : float arrays
913 | State space system matrices
914 |
915 | Returns
916 | -------
917 | Am, Bm, Cm, Dm : float arrays
918 | Modal state space system matrices
919 |
920 | Examples
921 | --------
922 | >>> import vibrationtesting as vt
923 | >>> M = np.array([[4, 0, 0],
924 | ... [0, 4, 0],
925 | ... [0, 0, 4]])
926 | >>> Cso = np.array([[.1,0,0],
927 | ... [0,0,0],
928 | ... [0,0,0]])
929 | >>> K = np.array([[8, -4, 0],
930 | ... [-4, 8, -4],
931 | ... [0, -4, 4]])
932 | >>> Bt = np.array([[1],[0],[0]])
933 | >>> Ca = np.array([[1,0,0]])
934 | >>> Cd = Cv = np.zeros_like(Ca)
935 | >>> A, B, C, D = vt.so2ss(M, Cso, K, Bt, Cd, Cv, Ca)
936 | >>> Am, Bm, Cm, Dm, eigenvalues, modes = vt.ss_modal(A, B, C, D)
937 | >>> np.allclose(Am,np.array(
938 | ... [[-0.0013+0.445j, 0.0000+0.j, 0.0000+0.j, 0.0000+0.j,
939 | ... 0.0000+0.j, 0.0000+0.j ],
940 | ... [ 0.0000+0.j, -0.0013-0.445j, 0.0000+0.j, 0.0000+0.j,
941 | ... 0.0000+0.j, 0.0000+0.j ],
942 | ... [ 0.0000+0.j, 0.0000+0.j, -0.0068+1.247j, 0.0000+0.j,
943 | ... 0.0000+0.j, 0.0000+0.j ],
944 | ... [ 0.0000+0.j, 0.0000+0.j, 0.0000+0.j, -0.0068-1.247j,
945 | ... 0.0000+0.j, 0.0000+0.j ],
946 | ... [ 0.0000+0.j, 0.0000+0.j, 0.0000+0.j, 0.0000+0.j,
947 | ... -0.0044+1.8019j, 0.0000+0.j ],
948 | ... [ 0.0000+0.j, 0.0000+0.j, 0.0000+0.j, 0.0000+0.j,
949 | ... 0.0000+0.j, -0.0044-1.8019j]]), atol=0.001)
950 | True
951 | >>> Cm
952 | [[ 0.0594-0.0001j 0.0594+0.0001j 0.0039-0.717j 0.0039+0.717j
953 | 0.0241-0.9307j 0.0241+0.9307j]]
954 |
955 |
956 | """
957 | if B is None:
958 | B = np.zeros_like(A)
959 |
960 | if C is None:
961 | C = np.zeros_like(A)
962 |
963 | if D is None:
964 | D = np.zeros_like(A)
965 |
966 | eigenvalues, vectors = la.eig(A)
967 |
968 | idxp = abs(eigenvalues).argsort()
969 | eigenvalues = eigenvalues[idxp]
970 | vectors = vectors[:, idxp]
971 |
972 | A_modal = la.solve(vectors, A)@vectors
973 | B_modal = la.solve(vectors, B)
974 | C_modal = C@vectors
975 | # D_modal = D wasted CPUs
976 | return A_modal, B_modal, C_modal, D, eigenvalues, vectors
977 |
--------------------------------------------------------------------------------