diff --git a/.github/conda-env/build-env.yml b/.github/conda-env/build-env.yml index f7597364..f747a77e 100644 --- a/.github/conda-env/build-env.yml +++ b/.github/conda-env/build-env.yml @@ -1,4 +1,4 @@ name: build-env dependencies: - boa - - numpy !=1.23.0 + - numpy diff --git a/.github/conda-env/test-env.yml b/.github/conda-env/test-env.yml index 7d69d330..bdc01362 100644 --- a/.github/conda-env/test-env.yml +++ b/.github/conda-env/test-env.yml @@ -1,11 +1,9 @@ name: test-env dependencies: # in addtion to package dependencies and explicit LAPACK/BLAS implementations installed in workflow - - conda-build # for conda index - scipy - matplotlib - pytest - pytest-cov - pytest-timeout - coverage - - coveralls >= 3.3 diff --git a/.github/scripts/run-tests.sh b/.github/scripts/run-tests.sh index 0c12237e..2ba81756 100644 --- a/.github/scripts/run-tests.sh +++ b/.github/scripts/run-tests.sh @@ -11,11 +11,14 @@ echo "::endgroup::" echo "::group::python-control unit tests" pushd ${python_control_srcdir:=./python-control} -# test_root_locus_zoom, test_sisotool: problems with the toolbar for MPL backends, not relevant to Slycot +# problems with the toolbar for MPL backends, not relevant to Slycot +donttest="test_root_locus_zoom or test_sisotool" +# don't care about deprecation warnings here +donttest="$donttest or test_default_deprecation" pytest control/tests \ --cov=$slycot_libdir \ --cov-config=${slycot_srcdir}/.coveragerc \ - -k "not (test_root_locus_zoom or test_sisotool)" + -k "not ($donttest)" mv .coverage ${slycot_srcdir}/.coverage.control popd echo "::endgroup::" @@ -31,4 +34,5 @@ cd ${slycot_srcdir} echo " ${slycot_libdir}" >> .coveragerc coverage combine coverage report +coverage xml echo "::endgroup::" diff --git a/.github/workflows/slycot-build-and-test.yml b/.github/workflows/slycot-build-and-test.yml index 050a6097..60b978c6 100644 --- a/.github/workflows/slycot-build-and-test.yml +++ b/.github/workflows/slycot-build-and-test.yml @@ -57,24 +57,24 @@ jobs: - 'ubuntu' - 'macos' python: - - '3.8' - - '3.11' + - '3.10' + - '3.12' bla_vendor: [ 'unset' ] include: - os: 'ubuntu' - python: '3.11' + python: '3.12' bla_vendor: 'Generic' - os: 'ubuntu' - python: '3.11' + python: '3.12' bla_vendor: 'OpenBLAS' - os: 'macos' - python: '3.11' + python: '3.12' bla_vendor: 'Apple' - os: 'macos' - python: '3.11' + python: '3.12' bla_vendor: 'Generic' - os: 'macos' - python: '3.11' + python: '3.12' bla_vendor: 'OpenBLAS' steps: @@ -132,7 +132,7 @@ jobs: path: slycot-wheels build-conda: - name: Build conda Py${{ matrix.python }}, ${{ matrix.os }} + name: Build conda, ${{ matrix.os }} runs-on: ${{ matrix.os }}-latest needs: build-sdist strategy: @@ -143,7 +143,7 @@ jobs: - 'macos' - 'windows' python: - - '3.9' + # this is not the packaged version, just the version conda-build runs on. - '3.11' steps: @@ -167,14 +167,14 @@ jobs: shell: bash -l {0} run: | set -e - numpyversion=$(python -c 'import numpy; print(numpy.version.version)') - conda mambabuild --python "${{ matrix.python }}" --numpy $numpyversion conda-recipe + conda mambabuild conda-recipe # preserve directory structure for custom conda channel find "${CONDA_PREFIX}/conda-bld" -maxdepth 2 -name 'slycot*.tar.bz2' | while read -r conda_pkg; do conda_platform=$(basename $(dirname "${conda_pkg}")) mkdir -p "slycot-conda-pkgs/${conda_platform}" cp "${conda_pkg}" "slycot-conda-pkgs/${conda_platform}/" done + python -m conda_index ./slycot-conda-pkgs - name: Save to local conda pkg channel uses: actions/upload-artifact@v3 with: @@ -279,7 +279,7 @@ jobs: - name: Install Wheel run: | python -m pip install --upgrade pip - pip install matplotlib scipy pytest pytest-cov pytest-timeout coverage coveralls + pip install matplotlib scipy pytest pytest-cov pytest-timeout coverage pip install slycot-wheels/${{ matrix.packagekey }}/slycot*.whl pip show slycot - name: Slycot and python-control tests @@ -287,13 +287,11 @@ jobs: env: JOBNAME: wheel ${{ matrix.packagekey }} ${{ matrix.blas_lib }} - name: report coverage - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: wheel-${{ matrix.packagekey }}-${{matrix.blas_lib}} - COVERALLS_PARALLEL: true - working-directory: slycot-src - # https://github.com/TheKevJames/coveralls-python/issues/252 - run: coveralls --service=github + uses: coverallsapp/github-action@v2 + with: + flag-name: wheel-${{ matrix.packagekey }}-${{matrix.blas_lib}} + parallel: true + file: slycot-src/coverage.xml test-conda: name: Test conda ${{ matrix.packagekey }}, ${{matrix.blas_lib}} BLAS lib ${{ matrix.failok }} @@ -357,21 +355,18 @@ jobs: echo "libblas * *mkl" >> $CONDA_PREFIX/conda-meta/pinned ;; esac - conda index --no-progress ./slycot-conda-pkgs mamba install -c ./slycot-conda-pkgs slycot conda list - name: Slycot and python-control tests run: JOBNAME="$JOBNAME" bash slycot-src/.github/scripts/run-tests.sh env: JOBNAME: conda ${{ matrix.packagekey }} ${{ matrix.blas_lib }} - - name: Report coverage - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: conda-${{ matrix.packagekey }}-${{matrix.blas_lib}} - COVERALLS_PARALLEL: true - working-directory: slycot-src - # https://github.com/TheKevJames/coveralls-python/issues/252 - run: coveralls --service=github + - name: report coverage + uses: coverallsapp/github-action@v2 + with: + flag-name: wheel-${{ matrix.packagekey }}-${{matrix.blas_lib}} + parallel: true + file: slycot-src/coverage.xml coveralls-final: name: Finalize parallel coveralls @@ -382,7 +377,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Coveralls Finished - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: - github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true diff --git a/CMakeLists.txt b/CMakeLists.txt index d7b015c5..3cfa425f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,4 +22,18 @@ message(STATUS "F2PY headers included from: ${F2PY_INCLUDE_DIRS}") message(STATUS "LAPACK: ${LAPACK_LIBRARIES}") message(STATUS "BLAS: ${BLAS_LIBRARIES}") +# https://github.com/python-control/Slycot/issues/193 +if((EXISTS "${Python_INCLUDE_DIRS}/numpy") + AND (NOT ("${Python_INCLUDE_DIRS}/numpy" EQUAL "${Python_NumPy_INCLUDE_DIRS}"))) + + message(FATAL_ERROR + "Python include directory has a numpy sub-directory, + ${Python_INCLUDE_DIRS}/numpy, + which is different from Numpy include directory + ${Python_NumPy_INCLUDE_DIRS}. + You're probably building in a virtual environment, in which case + uninstall numpy from the base environment and try again.") + +endif() + add_subdirectory(slycot) diff --git a/MANIFEST.in b/MANIFEST.in index b8c04e7c..cd90447e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include CMakeLists.txt include pyproject.toml include conda-recipe/* include slycot/CMakeLists.txt -include slycot/tests/CMakeLists.txt include slycot/*.py include slycot/src/*.f include slycot/tests/*.py diff --git a/README.rst b/README.rst index fe82d095..0551d685 100644 --- a/README.rst +++ b/README.rst @@ -19,18 +19,18 @@ Riccati, Lyapunov, and Sylvester equations. Dependencies ------------ -Slycot supports Python versions 3.8 or later. +Slycot supports Python versions 3.10 or later. To run the compiled Slycot package, the following must be installed as dependencies: -- Python 3.8+ +- Python 3.10+ - NumPy If you are compiling and installing Slycot from source, you will need the following dependencies: -- Python 3.8+ +- Python 3.10+ - NumPy - scikit-build - CMake @@ -189,8 +189,6 @@ A similar method can be used for Linux and macOS, but is detailed here for Windows. This method uses conda and conda-forge to get most build dependencies, *except* for the C compiler. -This procedure has been tested on Python 3.8. - 1. Install `Microsoft Visual Studio`_. 2. Unpack the source code to a directory of your choice, 3. Create a command shell setup that can run the conda commands and the Visual @@ -199,7 +197,7 @@ This procedure has been tested on Python 3.8. following commands to build and install Slycot (this example creates a Python 3.8 environment):: - conda create --channel conda-forge --name build-slycot python=3.8 numpy scipy libblas=*=*netlib liblapack=*=*netlib scikit-build flang pytest + conda create --channel conda-forge --name build-slycot python=3.10 numpy scipy libblas=*=*netlib liblapack=*=*netlib scikit-build flang pytest conda activate build-slycot pip install -v . diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat index 4b2811d0..9f4db0b3 100644 --- a/conda-recipe/bld.bat +++ b/conda-recipe/bld.bat @@ -1,3 +1,5 @@ +:: correct FC, apparently pointed to host prefix?? +set FC=%BUILD_PREFIX%\Library\bin\flang.exe set BLAS_ROOT=%PREFIX% set LAPACK_ROOT=%PREFIX% diff --git a/conda-recipe/conda_build_config.yaml b/conda-recipe/conda_build_config.yaml new file mode 100644 index 00000000..6c968691 --- /dev/null +++ b/conda-recipe/conda_build_config.yaml @@ -0,0 +1,46 @@ +# https://github.com/conda-forge/blas-feedstock/issues/106#issuecomment-1771747983 +# https://github.com/conda-forge/conda-forge-pinning-feedstock/blob/main/recipe/conda_build_config.yaml +# https://github.com/conda-forge/conda-forge-pinning-feedstock/blob/main/recipe/migrations/python312.yaml + +# zip_keys Python/Numpy matrix to build for +python: + - 3.10.* *_cpython + - 3.12.* *_cpython +numpy: + - 1.23 + - 1.26 + +zip_keys: + - + - python + - numpy + +# Compiler selection +c_compiler: + - gcc # [linux] + - clang # [osx] + - vs2019 # [win and x86_64] + - vs2022 # [win and arm64] +c_compiler_version: # [unix] + - 12 # [linux] + - 16 # [osx] +fortran_compiler: # [unix or win64] + - gfortran # [linux64 or (osx and x86_64)] + - gfortran # [aarch64 or ppc64le or armv7l or s390x] + - flang # [win64] +fortran_compiler_version: # [unix or win64] + - 12 # [linux] + - 12 # [osx] + - 5 # [win64] + +# Pinning + +# blas +libblas: + - 3.9 *netlib +libcblas: + - 3.9 *netlib +liblapack: + - 3.9 *netlib +liblapacke: + - 3.9 *netlib \ No newline at end of file diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 601aafed..e01ad376 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -11,32 +11,23 @@ build: requirements: build: - - {{ compiler('fortran') }} # [not win] + - {{ compiler('fortran') }} - {{ compiler('c') }} - cmake >=3.14 - make # [linux] - - flang >=11 # [win] - host: - # Always build against NETLIB ('Generic') LAPACK/Blas - # https://conda-forge.org/docs/maintainer/knowledge_base.html#blas - # deviating from above link: we have to specifiy netlib variant, because - # the mkl variant selected by default for older pythons on windows - # does not provide the generic headers - - libblas * *netlib - - libcblas * *netlib - - liblapack * *netlib + - libblas + - libcblas + - liblapack - python - - numpy >=1.19,!=1.23.0 - - pip + - numpy - scikit-build >=0.15 + - pip - setuptools >=45 - setuptools_scm >=7 - run: - python {{ PY_VER }} - {{ pin_compatible('numpy') }} - - libflang # [win] test: requires: diff --git a/pyproject.toml b/pyproject.toml index 94af52dd..c984562f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [build-system] requires = [ "setuptools>=45", - "setuptools_scm>=7", + "setuptools_scm>=8", "wheel", "scikit-build>=0.15", "cmake>=3.14", - "numpy!=1.23.0"] + "numpy >= 1.23.1"] build-backend = "setuptools.build_meta" [project] @@ -25,10 +25,9 @@ classifiers = [ "Programming Language :: C", "Programming Language :: Fortran", "Programming Language :: Python", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development", "Topic :: Scientific/Engineering", "Operating System :: Microsoft :: Windows", @@ -36,9 +35,9 @@ classifiers = [ "Operating System :: Unix", "Operating System :: MacOS", ] -requires-python = ">=3.8" +requires-python = ">=3.10" dependencies = [ - "numpy", + "numpy >= 1.23.1", ] dynamic = ["version"] diff --git a/setup.py b/setup.py index 230460fe..e832d146 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,5 @@ def run(self): packages=['slycot', 'slycot.tests'], cmdclass={'sdist': sdist_checked}, cmake_languages=('C', 'Fortran'), - use_scm_version = True, include_package_data = False, ) diff --git a/slycot/CMakeLists.txt b/slycot/CMakeLists.txt index eb49edd1..dc2c59b1 100644 --- a/slycot/CMakeLists.txt +++ b/slycot/CMakeLists.txt @@ -58,6 +58,7 @@ src/SLICOT-Reference/src/AB13DD.f src/SLICOT-Reference/src/AB13DX.f src/SLICOT-Reference/src/AB13ED.f src/SLICOT-Reference/src/AB13FD.f +src/SLICOT-Reference/src/AB13HD.f src/SLICOT-Reference/src/AB13ID.f src/SLICOT-Reference/src/AB13MD.f src/SLICOT-Reference/src/AB8NXZ.f @@ -105,6 +106,8 @@ src/SLICOT-Reference/src/MA01AD.f src/SLICOT-Reference/src/MA01BD.f src/SLICOT-Reference/src/MA01BZ.f src/SLICOT-Reference/src/MA01CD.f +src/SLICOT-Reference/src/MA01DD.f +src/SLICOT-Reference/src/MA01DZ.f src/SLICOT-Reference/src/MA02AD.f src/SLICOT-Reference/src/MA02AZ.f src/SLICOT-Reference/src/MA02BD.f @@ -131,6 +134,8 @@ src/SLICOT-Reference/src/MA02OD.f src/SLICOT-Reference/src/MA02OZ.f src/SLICOT-Reference/src/MA02PD.f src/SLICOT-Reference/src/MA02PZ.f +src/SLICOT-Reference/src/MA02RD.f +src/SLICOT-Reference/src/MA02SD.f src/SLICOT-Reference/src/MB01KD.f src/SLICOT-Reference/src/MB01LD.f src/SLICOT-Reference/src/MB01MD.f @@ -322,7 +327,13 @@ src/SLICOT-Reference/src/MB04QF.f src/SLICOT-Reference/src/MB04QS.f src/SLICOT-Reference/src/MB04QU.f src/SLICOT-Reference/src/MB04RB.f +src/SLICOT-Reference/src/MB04RD.f +src/SLICOT-Reference/src/MB04RS.f +src/SLICOT-Reference/src/MB04RT.f src/SLICOT-Reference/src/MB04RU.f +src/SLICOT-Reference/src/MB04RV.f +src/SLICOT-Reference/src/MB04RW.f +src/SLICOT-Reference/src/MB04RZ.f src/SLICOT-Reference/src/MB04SU.f src/SLICOT-Reference/src/MB04TB.f src/SLICOT-Reference/src/MB04TS.f @@ -613,6 +624,7 @@ src/SLICOT-Reference/src/UD01MD.f src/SLICOT-Reference/src/UD01MZ.f src/SLICOT-Reference/src/UD01ND.f src/SLICOT-Reference/src/UE01MD.f +src/SLICOT-Reference/src/zelctg.f src/SLICOT-Reference/src/delctg.f src/SLICOT-Reference/src/select.f @@ -630,12 +642,6 @@ set(F2PYSOURCE_DEPS src/transform.pyf src/synthesis.pyf src/_helper.pyf) -set(PYSOURCE - - __init__.py examples.py exceptions.py - analysis.py math.py synthesis.py transform.py -) - set(SLYCOT_MODULE "_wrapper") set(GENERATED_MODULE @@ -681,6 +687,3 @@ endif() python_extension_module(${SLYCOT_MODULE}) install(TARGETS ${SLYCOT_MODULE} LIBRARY DESTINATION slycot) -install(FILES ${PYSOURCE} DESTINATION slycot) - -add_subdirectory(tests) diff --git a/slycot/__init__.py b/slycot/__init__.py index 6bd59014..c1243ea5 100644 --- a/slycot/__init__.py +++ b/slycot/__init__.py @@ -12,38 +12,70 @@ # import slycot.examples - # Analysis routines (15/40 wrapped) - from .analysis import ab01nd, ab05md, ab05nd, ab07nd, ab08nd, ab08nz - from .analysis import ab09ad, ab09ax, ab09bd, ab09md, ab09nd - from .analysis import ab13bd, ab13dd, ab13ed, ab13fd, ab13md + # The Slycot library is organised by 11-chapters. Each chapter can be identified by a single letter. + # The following chapters are included: + # A : Analysis Routines (included) + # B : Benchmark + # C : Adaptive Control + # D : Data Analysis + # F : Filtering + # I : Identification + # M : Mathematical Routines (included) + # N : Nonlinear Systems + # S : Synthesis Routines (included) + # T : Transformation Routines (included) + # U : Utility Routines - # Data analysis routines (0/7 wrapped) + # Analysis routines (18/61 wrapped) + from .analysis import (ab01nd, + ab04md, + ab05md, ab05nd, + ab07nd, + ab08nd, ab08nz, + ab09ad, ab09ax, ab09bd, ab09md, ab09nd, + ab13bd, ab13dd, ab13ed, ab13fd, ab13md, + ag08bd) + + # Benchmark routines (0/6 wrapped) + + # Adaptive control routines (0/0 wrapped) + + # Data analysis routines (0/8 wrapped) # Filtering routines (0/6 wrapped) - # Identification routines (0/5 wrapped) + # Identification routines (0/15 wrapped) - # Mathematical routines (7/81 wrapped) - from .math import mc01td, mb03rd, mb03vd, mb03vy, mb03wd, mb05md, mb05nd + # Mathematical routines (8/291 wrapped) + from .math import (mb02ed, mb03rd, mb03vd, mb03vy, mb03wd, + mb05md, mb05nd, + mc01td) - # Synthesis routines (15/50 wrapped) + # Nonlinear Systems (0/16 wrapped) + # Synthesis routines ((17+1)/131 wrapped), sb03md57 is not part of slicot from .synthesis import (sb01bd, - sb02md, sb02mt, sb02od, + sb02md, sb02mt, sb02od, sb03md, sb03md57, sb03od, sb04md, sb04qd, - sb10ad, sb10dd, sb10fd, sb10hd, + sb10ad, sb10dd, sb10fd, sb10hd, sb10jd, sb10yd, sg02ad, sg03ad, sg03bd) + # Transformation routines (12/77 wrapped) + from .transform import (tb01id, tb01pd, + tb03ad, + tb04ad, + tb05ad, + tc01od, + tc04ad, + td04ad, + tf01md, tf01rd, + tg01ad, tg01fd) + + # Utility routines (0/7 wrapped) - # Transformation routines (9/40 wrapped) - from .transform import tb01id, tb03ad, tb04ad - from .transform import tb05ad - from .transform import tc04ad, tc01od - from .transform import tf01md, tf01rd - from .transform import td04ad, tb01pd from .version import __version__ diff --git a/slycot/analysis.py b/slycot/analysis.py index 32050236..230eedb9 100644 --- a/slycot/analysis.py +++ b/slycot/analysis.py @@ -67,15 +67,14 @@ def ab01nd(n, m, A, B, jobz='N', tol=0, ldwork=None): the order of the matrix A. ``n > 0``. m : int The number of system inputs, or of columns of B. ``m > 0``. - A : (n,n) array_like + A : (n, n) array_like The original state dynamics matrix A. - B : (n,m) array_like + B : (n, m) array_like The input matrix B. jobz : {'N', 'F', 'I'}, optional Indicates whether the user wishes to accumulate in a matrix Z the orthogonal similarity transformations for reducing the system, as follows: - := 'N': Do not form Z and do not store the orthogonal transformations; (default) := 'F': Do not form Z, but store the orthogonal transformations in @@ -92,12 +91,12 @@ def ab01nd(n, m, A, B, jobz='N', tol=0, ldwork=None): Returns ------- - Ac : (n,n) ndarray + Ac : (n, n) ndarray The leading ncont-by-ncont part contains the upper block Hessenberg state dynamics matrix Acont in Ac, given by Z'*A*Z, of a controllable realization for the original system. The elements below the first block-subdiagonal are set to zero. - Bc : (n,m) ndarray + Bc : (n, m) ndarray The leading ncont-by-m part of this array contains the transformed input matrix Bcont in Bc, given by ``Z'*B``, with all elements but the first block set to zero. @@ -106,10 +105,10 @@ def ab01nd(n, m, A, B, jobz='N', tol=0, ldwork=None): indcon : int The controllability index of the controllable part of the system representation. - nblk : (n,) int ndarray + nblk : (n, ) int ndarray The leading indcon elements of this array contain the the orders of the diagonal blocks of Acont. - Z : (n,n) ndarray + Z : (n, n) ndarray - If jobz = 'I', then the leading N-by-N part of this array contains the matrix of accumulated orthogonal similarity transformations which reduces the given system to orthogonal canonical form. @@ -139,6 +138,81 @@ def ab01nd(n, m, A, B, jobz='N', tol=0, ldwork=None): Z = None return Ac, Bc, ncont, indcon, nblk, Z, tau +def ab04md(type_t, n, m, p, A, B, C, D, alpha=1.0, beta=1.0, ldwork=None): + """ At,Bt,Ct,Dt = ab04md(type_t, n, m, p, A, B, C, D, [alpha, beta,ldwork]) + + Parameters + ---------- + type_t : {'D','C'} + Indicates the type of the original system and the + transformation to be performed as follows: + = 'D': discrete-time -> continuous-time; + = 'C': continuous-time -> discrete-time. + n : int + The order of the matrix A, the number of rows of matrix B and + the number of columns of matrix C. It represents the dimension of + the state vector. n > 0. + m : int + The number of columns of matrix B. It represents the dimension of + the input vector. m > 0. + p : int + The number of rows of matrix C. It represents the dimension of + the output vector. p > 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the system state + matrix A. + B : (n, m) array_like + The leading n-by-m part of this array must contain the system input + matrix B. + C : (p, n) array_like + The leading p-by-n part of this array must contain the system output + matrix C. + D : (p, m) array_like + The leading p-by-m part of this array must contain the system direct + transmission matrix D. + alpha : float, optional + Parameter specifying the bilinear transformation. + Recommended values for stable systems: alpha = 1, alpha != 0, + Default is 1.0. + beta : float, optional + Parameter specifying the bilinear transformation. + Recommended values for stable systems: beta = 1, beta != 0, + Default is 1.0. + ldwork : int, optional + The length of the cache array. + ldwork >= max(1, n), default is max(1, n) + Returns + ------- + At : (n, n) ndarray + The state matrix At of the transformed system. + Bt : (n, m) ndarray + The input matrix Bt of the transformed system. + Ct : (p, n) ndarray + The output matrix Ct of the transformed system. + Dt : (p, m) ndarray + The transmission matrix Dt of the transformed system. + Raises + ------ + SlycotArithmeticError + :info == 1: + If the matrix (ALPHA*I + A) is exactly singular + :info == 2: + If the matrix (BETA*I - A) is exactly singular. + """ + + hidden = ' (hidden by the wrapper)' + arg_list = ['type_t', 'n', 'm', 'p', 'alpha', 'beta', + 'A', 'LDA'+hidden, 'B', 'LDB'+hidden, 'C', 'LDC'+hidden, 'D', 'LDD'+hidden, + 'IWORK'+hidden, 'DWORK'+hidden, 'ldwork', 'info'+hidden] + + if ldwork is None: + ldwork = max(1, n) + + out = _wrapper.ab04md(type_t, n, m, p, alpha, beta, A, B, C, D, ldwork=ldwork) + info=out[-1] + raise_if_slycot_error(info, arg_list) + + return out[:-1] def ab05md(n1,m1,p1,n2,p2,A1,B1,C1,D1,A2,B2,C2,D2,uplo='U'): """ n,a,b,c,d = ab05md(n1,m1,p1,n2,p2,a1,b1,c1,d1,a2,b2,c2,d2,[uplo]) @@ -146,72 +220,76 @@ def ab05md(n1,m1,p1,n2,p2,A1,B1,C1,D1,A2,B2,C2,D2,uplo='U'): To obtain the state-space model (A,B,C,D) for the cascaded inter-connection of two systems, each given in state-space form. - Required arguments: - n1 : input int - The number of state variables in the first system, i.e. the order - of the matrix A1. n1 > 0. - m1 : input int - The number of input variables for the first system. m1 > 0. - p1 : input int - The number of output variables from the first system and the number - of input variables for the second system. p1 > 0. - n2 : input int - The number of state variables in the second system, i.e. the order - of the matrix A2. n2 > 0. - p2 : input int - The number of output variables from the second system. p2 > 0. - A1 : input rank-2 array('d') with bounds (n1,n1) - The leading n1-by-n1 part of this array must contain the state - transition matrix A1 for the first system. - B1 : input rank-2 array('d') with bounds (n1,m1) - The leading n1-by-m1 part of this array must contain the input/state - matrix B1 for the first system. - C1 : input rank-2 array('d') with bounds (p1,n1) - The leading p1-by-n1 part of this array must contain the state/output - matrix C1 for the first system. - D1 : input rank-2 array('d') with bounds (p1,m1) - The leading p1-by-m1 part of this array must contain the input/output - matrix D1 for the first system. - A2 : input rank-2 array('d') with bounds (n2,n2) - The leading n2-by-n2 part of this array must contain the state - transition matrix A2 for the second system. - B2 : input rank-2 array('d') with bounds (n2,p1) - The leading n2-by-p1 part of this array must contain the input/state - matrix B2 for the second system. - C2 : input rank-2 array('d') with bounds (p2,n2) - The leading p2-by-n2 part of this array must contain the state/output - matrix C2 for the second system. - D2 : input rank-2 array('d') with bounds (p2,p1) - The leading p2-by-p1 part of this array must contain the input/output - matrix D2 for the second system. - Optional arguments: - uplo := 'U' input string(len=1) - Indicates whether the user wishes to obtain the matrix A in - the upper or lower block diagonal form, as follows: - = 'U': Obtain A in the upper block diagonal form; - = 'L': Obtain A in the lower block diagonal form. - Return objects: - n : int - The number of state variables (n1 + n2) in the resulting system, - i.e. the order of the matrix A, the number of rows of B and - the number of columns of C. - A : rank-2 array('d') with bounds (n1+n2,n1+n2) - The leading N-by-N part of this array contains the state transition - matrix A for the cascaded system. - B : rank-2 array('d') with bounds (n1+n2,m1) - The leading n-by-m1 part of this array contains the input/state - matrix B for the cascaded system. - C : rank-2 array('d') with bounds (p2,n1+n2) - The leading p2-by-n part of this array contains the state/output - matrix C for the cascaded system. - D : rank-2 array('d') with bounds (p2,m1) - The leading p2-by-m1 part of this array contains the input/output - matrix D for the cascaded system. - - Notes: - The implemented methods rely on accuracy enhancing square-root or - balancing-free square-root techniques. - The algorithms require less than 30N^3 floating point operations. + Parameters + ---------- + n1 : int + The number of state variables in the first system, i.e. the order + of the matrix A1. n1 > 0. + m1 : int + The number of input variables for the first system. m1 > 0. + p1 : int + The number of output variables from the first system and the number + of input variables for the second system. p1 > 0. + n2 : int + The number of state variables in the second system, i.e. the order + of the matrix A2. n2 > 0. + p2 : int + The number of output variables from the second system. p2 > 0. + A1 : (n1, n1) array_like + The leading n1-by-n1 part of this array must contain the state + transition matrix A1 for the first system. + B1 : (n1, m1) array_like + The leading n1-by-m1 part of this array must contain the input/state + matrix B1 for the first system. + C1 : (p1, n1) array_like + The leading p1-by-n1 part of this array must contain the state/output + matrix C1 for the first system. + D1 : (p1, m1) array_like + The leading p1-by-m1 part of this array must contain the input/output + matrix D1 for the first system. + A2 : (n2, n2) array_like + The leading n2-by-n2 part of this array must contain the state + transition matrix A2 for the second system. + B2 : (n2, p1) array_like + The leading n2-by-p1 part of this array must contain the input/state + matrix B2 for the second system. + C2 : (p2, n2) array_like + The leading p2-by-n2 part of this array must contain the state/output + matrix C2 for the second system. + D2 : (p2, p1) array_like + The leading p2-by-p1 part of this array must contain the input/output + matrix D2 for the second system. + uplo : {'U', 'L'}, optional + Indicates whether the user wishes to obtain the matrix A in + the upper or lower block diagonal form, as follows: + = 'U': Obtain A in the upper block diagonal form; + = 'L': Obtain A in the lower block diagonal form. + Default is `U`. + + Returns + ------- + n : int + The number of state variables (n1 + n2) in the resulting system, + i.e. the order of the matrix A, the number of rows of B and + the number of columns of C. + A : (n1+n2, n1+n2) ndarray + The leading N-by-N part of this array contains the state transition + matrix A for the cascaded system. + B : (n1+n2, m1) ndarray + The leading n-by-m1 part of this array contains the input/state + matrix B for the cascaded system. + C : (p2, n1+n2) ndarray + The leading p2-by-n part of this array contains the state/output + matrix C for the cascaded system. + D : (p2, m1) ndarray + The leading p2-by-m1 part of this array contains the input/output + matrix D for the cascaded system. + + Notes + ----- + The implemented methods rely on accuracy enhancing square-root or + balancing-free square-root techniques. + The algorithms require less than 30N^3 floating point operations. """ hidden = ' (hidden by the wrapper)' arg_list = ['uplo', 'OVER'+hidden, 'n1', 'm1', 'p1', 'n2', 'p2', 'A1', @@ -230,68 +308,72 @@ def ab05nd(n1,m1,p1,n2,A1,B1,C1,D1,A2,B2,C2,D2,alpha=1.0,ldwork=None): To obtain the state-space model (A,B,C,D) for the feedback inter-connection of two systems, each given in state-space form. - Required arguments: - n1 : input int - The number of state variables in the first system, i.e. the order - of the matrix A1. n1 > 0. - m1 : input int - The number of input variables for the first system and the number - of output variables from the second system. m1 > 0. - p1 : input int - The number of output variables from the first system and the number - of input variables for the second system. p1 > 0. - n2 : input int - The number of state variables in the second system, i.e. the order - of the matrix A2. n2 > 0. - A1 : input rank-2 array('d') with bounds (n1,n1) - The leading n1-by-n1 part of this array must contain the state - transition matrix A1 for the first system. - B1 : input rank-2 array('d') with bounds (n1,m1) - The leading n1-by-m1 part of this array must contain the input/state - matrix B1 for the first system. - C1 : input rank-2 array('d') with bounds (p1,n1) - The leading p1-by-n1 part of this array must contain the state/output - matrix C1 for the first system. - D1 : input rank-2 array('d') with bounds (p1,m1) - The leading p1-by-m1 part of this array must contain the input/output - matrix D1 for the first system. - A2 : input rank-2 array('d') with bounds (n2,n2) - The leading n2-by-n2 part of this array must contain the state - transition matrix A2 for the second system. - B2 : input rank-2 array('d') with bounds (n2,p1) - The leading n2-by-p1 part of this array must contain the input/state - matrix B2 for the second system. - C2 : input rank-2 array('d') with bounds (m1,n2) - The leading m1-by-n2 part of this array must contain the state/output - matrix C2 for the second system. - D2 : input rank-2 array('d') with bounds (m1,p1) - The leading m1-by-p1 part of this array must contain the input/output - matrix D2 for the second system. - Optional arguments: - alpha := 1.0 input float - A coefficient multiplying the transfer-function matrix (or the - output equation) of the second system. i.e alpha = +1 corresponds - to positive feedback, and alpha = -1 corresponds to negative - feedback. - ldwork := max(p1*p1,m1*m1,n1*p1) input int - The length of the cache array. ldwork >= max(p1*p1,m1*m1,n1*p1). - Return objects: - n : int - The number of state variables (n1 + n2) in the connected system, i.e. - the order of the matrix A, the number of rows of B and the number of - columns of C. - A : rank-2 array('d') with bounds (n1+n2,n1+n2) - The leading n-by-n part of this array contains the state transition - matrix A for the connected system. - B : rank-2 array('d') with bounds (n1+n2,m1) - The leading n-by-m1 part of this array contains the input/state - matrix B for the connected system. - C : rank-3 array('d') with bounds (p1,n1,n2) - The leading p1-by-n part of this array contains the state/output - matrix C for the connected system. - D : rank-2 array('d') with bounds (p1,m1) - The leading p1-by-m1 part of this array contains the input/output - matrix D for the connected system. + Parameters + ---------- + n1 : int + The number of state variables in the first system, i.e. the order + of the matrix A1. n1 > 0. + m1 : int + The number of input variables for the first system and the number + of output variables from the second system. m1 > 0. + p1 : int + The number of output variables from the first system and the number + of input variables for the second system. p1 > 0. + n2 : int + The number of state variables in the second system, i.e. the order + of the matrix A2. n2 > 0. + A1 : (n1, n1) array_like + The leading n1-by-n1 part of this array must contain the state + transition matrix A1 for the first system. + B1 : (n1, m1) array_like + The leading n1-by-m1 part of this array must contain the input/state + matrix B1 for the first system. + C1 : (p1, n1) array_like + The leading p1-by-n1 part of this array must contain the state/output + matrix C1 for the first system. + D1 : (p1, m1) array_like + The leading p1-by-m1 part of this array must contain the input/output + matrix D1 for the first system. + A2 : (n2, n2) array_like + The leading n2-by-n2 part of this array must contain the state + transition matrix A2 for the second system. + B2 : (n2, p1) array_like + The leading n2-by-p1 part of this array must contain the input/state + matrix B2 for the second system. + C2 : (m1, n2) array_like + The leading m1-by-n2 part of this array must contain the state/output + matrix C2 for the second system. + D2 : (m1, p1) array_like + The leading m1-by-p1 part of this array must contain the input/output + matrix D2 for the second system. + alpha : float, optional + A coefficient multiplying the transfer-function matrix (or the + output equation) of the second system. i.e alpha = +1 corresponds + to positive feedback, and alpha = -1 corresponds to negative + feedback. + Default is `1.0`. + ldwork : int, optional + The length of the cache array. ldwork >= max(p1*p1,m1*m1,n1*p1). + Default is max(p1*p1,m1*m1,n1*p1). + + Returns + ------- + n : int + The number of state variables (n1 + n2) in the connected system, i.e. + the order of the matrix A, the number of rows of B and the number of + columns of C. + A : (n1+n2, n1+n2) ndarray + The leading n-by-n part of this array contains the state transition + matrix A for the connected system. + B : (n1+n2, m1) ndarray + The leading n-by-m1 part of this array contains the input/state + matrix B for the connected system. + C : (p1, n1, n2) ndarray + The leading p1-by-n part of this array contains the state/output + matrix C for the connected system. + D : (p1, m1) ndarray + The leading p1-by-m1 part of this array contains the input/output + matrix D for the connected system. Raises ------ @@ -317,47 +399,49 @@ def ab05nd(n1,m1,p1,n2,A1,B1,C1,D1,A2,B2,C2,D2,alpha=1.0,ldwork=None): return out[:-1] def ab07nd(n,m,A,B,C,D,ldwork=None): - """ A_i,B_i,C_i,D_i,rcond = ab07nd(n,m,A,B,C,D,[ldwork]) - - To compute the inverse (A_i,B_i,C_i,D_i) of a given system (A,B,C,D). - - Required arguments: - n : input int - The order of the state matrix A. n >= 0. - m : input int - The number of system inputs and outputs. m >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state matrix - A of the original system. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input matrix - B of the original system. - C : input rank-2 array('d') with bounds (m,n) - The leading m-by-n part of this array must contain the output matrix - C of the original system. - D : input rank-2 array('d') with bounds (m,m) - The leading m-by-m part of this array must contain the feedthrough - matrix D of the original system. - Optional arguments: - ldwork := None input int - The length of the cache array. The default value is max(1,4*m), - for better performance should be larger. - Return objects: - A_i : rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array contains the state matrix A_i - of the inverse system. - B_i : rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array contains the input matrix B_i - of the inverse system. - C_i : rank-2 array('d') with bounds (m,n) - The leading m-by-n part of this array contains the output matrix C_i - of the inverse system. - D_i : rank-2 array('d') with bounds (m,m) - The leading m-by-m part of this array contains the feedthrough - matrix D_i of the inverse system. - rcond : float - The estimated reciprocal condition number of the feedthrough matrix - D of the original system. + """ Ai,Bi,Ci,Di,rcond = ab07nd(n,m,A,B,C,D,[ldwork]) + + To compute the inverse (Ai,Bi,Ci,Di) of a given system (A,B,C,D). + + Parameters + ---------- + n : int + The order of the state matrix A. n >= 0. + m : int + The number of system inputs and outputs. m >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state matrix + A of the original system. + B : (n, m) array_like + The leading n-by-m part of this array must contain the input matrix + B of the original system. + C : (m, n) array_like + The leading m-by-n part of this array must contain the output matrix + C of the original system. + D : (m, m) array_like + The leading m-by-m part of this array must contain the feedthrough + matrix D of the original system. + ldwork : int, optional + The length of the cache array. The default value is max(1,4*m), + for better performance should be larger. + + Returns + ------- + Ai : (n, n) ndarray + The leading n-by-n part of this array contains the state matrix Ai + of the inverse system. + Bi : (n, m) ndarray + The leading n-by-m part of this array contains the input matrix Bi + of the inverse system. + Ci : (m, n) ndarray + The leading m-by-n part of this array contains the output matrix Ci + of the inverse system. + Di : (m, m) ndarray + The leading m-by-m part of this array contains the feedthrough + matrix Di of the inverse system. + rcond : float + The estimated reciprocal condition number of the feedthrough matrix + D of the original system. Warns ----- @@ -392,68 +476,73 @@ def ab08nd(n,m,p,A,B,C,D,equil='N',tol=0,ldwork=None): The routine also computes the orders of the infinite zeros and the right and left Kronecker indices of the system (A,B,C,D). - Required arguments: - n : input int - The number of state variables. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state - dynamics matrix A of the system. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input/state - matrix B of the system. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the state/output - matrix C of the system. - D : input rank-2 array('d') with bounds (p,m) - The leading p-by-m part of this array must contain the direct - transmission matrix D of the system. - Optional arguments: - equil := 'N' input string(len=1) - Specifies whether the user wishes to balance the compound matrix - as follows: - = 'S': Perform balancing (scaling); - = 'N': Do not perform balancing. - tol := 0.0 input float - A tolerance used in rank decisions to determine the effective rank, - which is defined as the order of the largest leading (or trailing) - triangular submatrix in the QR (or RQ) factorization with column - (or row) pivoting whose estimated condition number is less than 1/tol. - ldwork := None input int - The length of the cache array. The default value is n + 3*max(m,p), - for better performance should be larger. - Return objects: - nu : int - The number of (finite) invariant zeros. - rank : int - The normal rank of the transfer function matrix. - dinfz : int - The maximum degree of infinite elementary divisors. - nkror : int - The number of right Kronecker indices. - nkrol : int - The number of left Kronecker indices. - infz : rank-1 array('i') with bounds (n) - The leading dinfz elements of infz contain information on the - infinite elementary divisors as follows: the system has infz(i) - infinite elementary divisors of degree i, where i = 1,2,...,dinfz. - kronr : rank-1 array('i') with bounds (max(n,m)+1) - the leading nkror elements of this array contain the right kronecker - (column) indices. - kronl : rank-1 array('i') with bounds (max(n,p)+1) - the leading nkrol elements of this array contain the left kronecker - (row) indices. - Af : rank-2 array('d') with bounds (max(1,n+m),n+min(p,m)) - the leading nu-by-nu part of this array contains the coefficient - matrix Af of the reduced pencil. the remainder of the leading - (n+m)-by-(n+min(p,m)) part is used as internal workspace. - Bf : rank-2 array('d') with bounds (max(1,n+p),n+m) - The leading nu-by-nu part of this array contains the coefficient - matrix Bf of the reduced pencil. the remainder of the leading - (n+p)-by-(n+m) part is used as internal workspace. + Parameters + ---------- + n : int + The number of state variables. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state + dynamics matrix A of the system. + B : (n, m) array_like + The leading n-by-m part of this array must contain the input/state + matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must contain the state/output + matrix C of the system. + D : (p, m) array_like + The leading p-by-m part of this array must contain the direct + transmission matrix D of the system. + equil : {'S', 'N'}, optional + Specifies whether the user wishes to balance the compound matrix + as follows: + = 'S': Perform balancing (scaling); + = 'N': Do not perform balancing. + Default is `N`. + tol : float, optional + A tolerance used in rank decisions to determine the effective rank, + which is defined as the order of the largest leading (or trailing) + triangular submatrix in the QR (or RQ) factorization with column + (or row) pivoting whose estimated condition number is less than 1/tol. + Default is `0.0`. + ldwork : int, optional + The length of the cache array. The default value is n + 3*max(m,p), + for better performance should be larger. + Default is None. + + Returns + ------- + nu : int + The number of (finite) invariant zeros. + rank : int + The normal rank of the transfer function matrix. + dinfz : int + The maximum degree of infinite elementary divisors. + nkror : int + The number of right Kronecker indices. + nkrol : int + The number of left Kronecker indices. + infz : (n, ) ndarray + The leading dinfz elements of infz contain information on the + infinite elementary divisors as follows: the system has infz(i) + infinite elementary divisors of degree i, where i = 1,2,...,dinfz. + kronr :(max(n,m)+1, ) ndarray + the leading nkror elements of this array contain the right kronecker + (column) indices. + kronl : (max(n,p)+1, ) ndarray + the leading nkrol elements of this array contain the left kronecker + (row) indices. + Af : (max(1,n+m), n+min(p,m)) ndarray + the leading nu-by-nu part of this array contains the coefficient + matrix Af of the reduced pencil. the remainder of the leading + (n+m)-by-(n+min(p,m)) part is used as internal workspace. + Bf : (max(1,n+p), n+m) ndarray + The leading nu-by-nu part of this array contains the coefficient + matrix Bf of the reduced pencil. the remainder of the leading + (n+p)-by-(n+m) part is used as internal workspace. """ hidden = ' (hidden by the wrapper)' arg_list = ['equil', 'n', 'm', 'p', 'A', 'LDA'+hidden, 'B', 'LDB'+hidden, @@ -476,82 +565,87 @@ def ab08nz(n, m, p, A, B, C, D, equil='N', tol=0., lzwork=None): The routine also computes the orders of the infinite zeros and the right and left Kronecker indices of the system (A,B,C,D). - Required arguments: - n : input int - The number of state variables. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state - dynamics matrix A of the system. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input/state - matrix B of the system. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the state/output - matrix C of the system. - D : input rank-2 array('d') with bounds (p,m) - The leading p-by-m part of this array must contain the direct - transmission matrix D of the system. - Optional arguments: - equil := 'N' input string(len=1) - Specifies whether the user wishes to balance the compound matrix - as follows: - = 'S': Perform balancing (scaling); - = 'N': Do not perform balancing. - tol := 0.0 input float - A tolerance used in rank decisions to determine the effective rank, - which is defined as the order of the largest leading (or trailing) - triangular submatrix in the QR (or RQ) factorization with column - (or row) pivoting whose estimated condition number is less than 1/tol. - If tol is set to less than SQRT((N+P)*(N+M))*EPS - then the tolerance is taken as SQRT((N+P)*(N+M))*EPS, - where EPS is the machine precision (see LAPACK Library - Routine DLAMCH). - lzwork := None input int - The length of the internal cache array ZWORK. The default value is - calculated to - MAX( 1, - MIN(P,M) + MAX(3*M-1,N), - MIN(P,N) + MAX(3*P-1,N+P,N+M), - MIN(M,N) + MAX(3*M-1,N+M) ) - For optimum performance lzwork should be larger. - If lzwork = -1, then a workspace query is assumed; - the routine only calculates the optimal size of the - ZWORK array, and returns this value in lzwork_opt - Return objects: - nu : int - The number of (finite) invariant zeros. - rank : int - The normal rank of the transfer function matrix. - dinfz : int - The maximum degree of infinite elementary divisors. - nkror : int - The number of right Kronecker indices. - nkrol : int - The number of left Kronecker indices. - infz : rank-1 array('i') with bounds (n) - The leading dinfz elements of infz contain information on the - infinite elementary divisors as follows: the system has infz(i) - infinite elementary divisors of degree i, where i = 1,2,...,dinfz. - kronr : rank-1 array('i') with bounds (max(n,m)+1) - the leading nkror elements of this array contain the right kronecker - (column) indices. - kronl : rank-1 array('i') with bounds (max(n,p)+1) - the leading nkrol elements of this array contain the left kronecker - (row) indices. - Af : rank-2 array('d') with bounds (max(1,n+m),n+min(p,m)) - the leading nu-by-nu part of this array contains the coefficient - matrix Af of the reduced pencil. the remainder of the leading - (n+m)-by-(n+min(p,m)) part is used as internal workspace. - Bf : rank-2 array('d') with bounds (max(1,n+p),n+m) - The leading nu-by-nu part of this array contains the coefficient - matrix Bf of the reduced pencil. the remainder of the leading - (n+p)-by-(n+m) part is used as internal workspace. - lzwork_opt : int - The optimal value of lzwork. + Parameters + ---------- + n : int + The number of state variables. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state + dynamics matrix A of the system. + B : (n, m) array_like + The leading n-by-m part of this array must contain the input/state + matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must contain the state/output + matrix C of the system. + D : (p, m) array_like + The leading p-by-m part of this array must contain the direct + transmission matrix D of the system. + equil : {'S', 'N'}, optional + Specifies whether the user wishes to balance the compound matrix + as follows: + = 'S': Perform balancing (scaling); + = 'N': Do not perform balancing. + Default is `N`. + tol : float, optional + A tolerance used in rank decisions to determine the effective rank, + which is defined as the order of the largest leading (or trailing) + triangular submatrix in the QR (or RQ) factorization with column + (or row) pivoting whose estimated condition number is less than 1/tol. + If tol is set to less than SQRT((N+P)*(N+M))*EPS + then the tolerance is taken as SQRT((N+P)*(N+M))*EPS, + where EPS is the machine precision (see LAPACK Library + Routine DLAMCH). + Default is 0.0. + lzwork : int, optional + The length of the internal cache array ZWORK. The default value is + calculated to + MAX( 1, + MIN(P,M) + MAX(3*M-1,N), + MIN(P,N) + MAX(3*P-1,N+P,N+M), + MIN(M,N) + MAX(3*M-1,N+M) ) + For optimum performance lzwork should be larger. + If lzwork = -1, then a workspace query is assumed; + the routine only calculates the optimal size of the + ZWORK array, and returns this value in lzwork_opt + Default is None. + + Returns + ------- + nu : int + The number of (finite) invariant zeros. + rank : int + The normal rank of the transfer function matrix. + dinfz : int + The maximum degree of infinite elementary divisors. + nkror : int + The number of right Kronecker indices. + nkrol : int + The number of left Kronecker indices. + infz : (n, ) ndarray + The leading dinfz elements of infz contain information on the + infinite elementary divisors as follows: the system has infz(i) + infinite elementary divisors of degree i, where i = 1,2,...,dinfz. + kronr : (max(n,m)+1, ) ndarray + the leading nkror elements of this array contain the right kronecker + (column) indices. + kronl : (max(n,p)+1, ) ndarray + the leading nkrol elements of this array contain the left kronecker + (row) indices. + Af : (max(1,n+m), n+min(p,m)) ndarray + the leading nu-by-nu part of this array contains the coefficient + matrix Af of the reduced pencil. the remainder of the leading + (n+m)-by-(n+min(p,m)) part is used as internal workspace. + Bf : (max(1,n+p), n+m) ndarray + The leading nu-by-nu part of this array contains the coefficient + matrix Bf of the reduced pencil. the remainder of the leading + (n+p)-by-(n+m) part is used as internal workspace. + lzwork_opt : int + The optimal value of lzwork. """ hidden = ' (hidden by the wrapper)' arg_list = ['equil', 'n', 'm', 'p', @@ -582,77 +676,79 @@ def ab09ad(dico,job,equil,n,m,p,A,B,C,nr=None,tol=0,ldwork=None): (A, B, C) by using either the square-root or the balancing-free square- root Balance & truncate (B & T) model reduction method. - Required arguments: - dico : {'D', 'C'} input string(len=1) - Indicate whether the system is discrete `D` or continuous `C` - job : {'B', 'N'} input string(len=1) - Balance `B` or not `N` - equil : {'S', 'N'} input string(len=1) - Scale `S` or not `N` - n : input int - The number of state variables. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state - dynamics matrix A of the system. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input/state - matrix B of the system. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the - state/output matrix C of the system. - - Optional arguments: - nr := None input int - `nr` is the desired order of the resulting reduced order - system. ``0 <= nr <= n``. Automatically determined by `tol` if - ``nr is None`` and returned. See return object `nr`. - tol := 0 input double precision - If ``nr is None``, `tol`contains the tolerance for determining the - order of the reduced system. For model reduction, th recommended - value is ``tol = c * HNORM(A, B, C)``, where `c` is a constan in the - interval ``[0.00001, 0.001]`` and ``HNORM(A, B, C)`` is the - Hankel-Norm of the given sysstem (computed in ``HSV(1)``). For - computing a minimal realization, the recommended value is - ``tol = n * eps * HNORM(A, B, C)``, where `eps` is the machine - precision (see LAPACK Library Routine `DLAMCH`). This value is - used by default if ``tol <= 0`` on entry. If `nr` is specified, - the value of `tol` is ignored. - ldwork := None input int - The length of the cache array. The default value is - ``n*(2*n+max(n,m,p)+5) + n*(n+1)/2 ~= 3.5*n**2 + 5*n``, - a larger value should lead to better performance. - - Return objects : - nr : output int - `nr` is the order of the resulting reduced order model. - `nr` is set as follows: - If on input ``nr is not None``, `nr` is equal to ``MIN(nr,NMIN)``, - where `nr` is the desired order on entry and `NMIN` is the order - of a minimal realization of the given system; `NMIN` is - determined as the number of Hankel singular values greater - than ``n*eps*HNORM(A,B,C)``, where `eps` is the machine - precision (see LAPACK Library Routine DLAMCH) and - ``HNORM(A,B,C)`` is the Hankel norm of the system (computed - in ``HSV(1)``); - If on input ``nr is None``, `nr` is equal to the number of Hankel - singular values greater than ``MAX(tol,n*eps*HNORM(A,B,C))``. - Ar : rank-2 array('d') with bounds ``(nr,nr)`` - This array contains the state dynamics matrix `Ar` of the reduced - order system. - Br : rank-2 array('d') with bounds ``(nr,m)`` - Tthis array contains the input/state matrix `Br` of the reduced - order system. - Cr : rank-2 array('d') with bounds ``(p,nr)`` - This array contains the state/output matrix `Cr` of the reduced - order system. - hsv : output double precision array, dimension ``(n)`` - If ``INFO = 0``, it contains the Hankel singular values of - the original system ordered decreasingly. ``HSV(1)`` is the - Hankel norm of the system. + Parameters + ---------- + dico : {'D', 'C'} + Indicate whether the system is discrete `D` or continuous `C` + job : {'B', 'N'} + Balance `B` or not `N` + equil : {'S', 'N'} + Scale `S` or not `N` + n : int + The number of state variables. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state + dynamics matrix A of the system. + B : (n, m) array_like + The leading n-by-m part of this array must contain the input/state + matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must contain the + state/output matrix C of the system. + nr : int, optional + `nr` is the desired order of the resulting reduced order + system. ``0 <= nr <= n``. Automatically determined by `tol` if + ``nr is None`` and returned. See return object `nr`. + Default is None. + tol : float, optional + If ``nr is None``, `tol`contains the tolerance for determining the + order of the reduced system. For model reduction, th recommended + value is ``tol = c * HNORM(A, B, C)``, where `c` is a constan in the + interval ``[0.00001, 0.001]`` and ``HNORM(A, B, C)`` is the + Hankel-Norm of the given sysstem (computed in ``HSV(1)``). For + computing a minimal realization, the recommended value is + ``tol = n * eps * HNORM(A, B, C)``, where `eps` is the machine + precision (see LAPACK Library Routine `DLAMCH`). This value is + used by default if ``tol <= 0`` on entry. If `nr` is specified, + the value of `tol` is ignored. Default is `0.0`. + ldwork : int, optional + The length of the cache array. The default value is + ``n*(2*n+max(n,m,p)+5) + n*(n+1)/2 ~= 3.5*n**2 + 5*n``, + a larger value should lead to better performance. + Default is None. + + Returns + ------- + nr : int + `nr` is the order of the resulting reduced order model. + `nr` is set as follows: + If on input ``nr is not None``, `nr` is equal to ``MIN(nr,NMIN)``, + where `nr` is the desired order on entry and `NMIN` is the order + of a minimal realization of the given system; `NMIN` is + determined as the number of Hankel singular values greater + than ``n*eps*HNORM(A,B,C)``, where `eps` is the machine + precision (see LAPACK Library Routine DLAMCH) and + ``HNORM(A,B,C)`` is the Hankel norm of the system (computed + in ``HSV(1)``); + If on input ``nr is None``, `nr` is equal to the number of Hankel + singular values greater than ``MAX(tol,n*eps*HNORM(A,B,C))``. + Ar : (nr, nr) ndarray + This array contains the state dynamics matrix `Ar` of the reduced + order system. + Br : (nr, m) ndarray + This array contains the input/state matrix `Br` of the reduced + order system. + Cr : (p, nr) ndarray + This array contains the state/output matrix `Cr` of the reduced + order system. + hsv : (n, ) ndarray + If ``INFO = 0``, it contains the Hankel singular values of + the original system ordered decreasingly. ``HSV(1)`` is the + Hankel norm of the system. Raises ------ @@ -708,81 +804,83 @@ def ab09ax(dico,job,n,m,p,A,B,C,nr=None,tol=0.0,ldwork=None): ``Ar = TI * A * T , Br = TI * B , Cr = C * T`` . - Required arguments : - dico : {'D', 'C'} input string(len=1) - Indicate whether the system is discrete `D` or continuous `C` - job : {'B', 'N'} input string(len=1) - Balance `B` or not `N` - n : input int - The number of state variables. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state - dynamics matrix A of the system *in real Schur form.* - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input/state - matrix B of the system. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the - state/output matrix C of the system. - - Optional arguments: - nr := None input int - `nr` is the desired order of the resulting reduced order - system. ``0 <= nr <= n``. Automatically determined by `tol` if - ``nr is None`` and returned. See return object `nr`. - tol := 0 input double precision - If ``nr is None``, `tol`contains the tolerance for determining the - order of the reduced system. For model reduction, the recommended - value is ``tol = c * HNORM(A, B, C)``, where `c` is a constant in - the interval ``[0.00001, 0.001]`` and ``HNORM(A, B, C)`` is - the Hankel-Norm of the given sysstem (computed in ``HSV(1)``). For - computing a minimal realization, the recommended value is - ``tol = n * eps * HNORM(A, B, C)``, where `eps` is the machine - precision (see LAPACK Library Routine `DLAMCH`). This value is - used by default if ``tol <= 0`` on entry. If `nr` is specified, - the value of `tol` is ignored. - ldwork := None input int - The length of the cache array. The default value is - ``n*(2*n+max(n,m,p)+5) + n*(n+1)/2 ~= 3.5*n**2 + 5*n``, - a larger value should lead to better performance. - - Return objects : - nr : output int - `nr` is the order of the resulting reduced order model. - `nr` is set as follows: - If on input ``nr is not None``, `nr` is equal to ``MIN(nr,NMIN)``, - where `nr` is the desired order on entry and `NMIN` is the order - of a minimal realization of the given system; `NMIN` is - determined as the number of Hankel singular values greater - than ``n*eps*HNORM(A,B,C)``, where `eps` is the machine - precision (see LAPACK Library Routine DLAMCH) and - ``HNORM(A,B,C)`` is the Hankel norm of the system (computed - in ``HSV(1)``); - If on input ``nr is None``, `nr` is equal to the number of Hankel - singular values greater than ``MAX(tol,n*eps*HNORM(A,B,C))``. - Ar : rank-2 array('d') with bounds ``(nr,nr)`` - This array contains the state dynamics matrix `Ar` of the reduced - order system. - Br : rank-2 array('d') with bounds ``(nr,m)`` - Tthis array contains the input/state matrix `Br` of the reduced - order system. - Cr : rank-2 array('d') with bounds ``(p,nr)`` - This array contains the state/output matrix `Cr` of the reduced - order system. - hsv : output double precision array, dimension ``(n)`` - If ``INFO = 0``, it contains the Hankel singular values of - the original system ordered decreasingly. ``HSV(1)`` is the - Hankel norm of the system. - T : rank-2 array('d') with bounds ``(n,nr)`` - This array contains the right truncation matrix `T` of the reduced - order system. - Ti : rank-2 array('d') with bounds ``(nr,n)`` - This array contains the left truncation matrix `Ti` of the reduced - order system. + Parameters + ---------- + dico : {'D', 'C'} + Indicate whether the system is discrete `D` or continuous `C` + job : {'B', 'N'} + Balance `B` or not `N` + n : int + The number of state variables. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state + dynamics matrix A of the system *in real Schur form.* + B : (n, m) array_like + The leading n-by-m part of this array must contain the input/state + matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must contain the + state/output matrix C of the system. + nr : int, optional + `nr` is the desired order of the resulting reduced order + system. ``0 <= nr <= n``. Automatically determined by `tol` if + ``nr is None`` and returned. See return object `nr`. + Default is None. + tol : float, optional + If ``nr is None``, `tol`contains the tolerance for determining the + order of the reduced system. For model reduction, the recommended + value is ``tol = c * HNORM(A, B, C)``, where `c` is a constant in + the interval ``[0.00001, 0.001]`` and ``HNORM(A, B, C)`` is + the Hankel-Norm of the given sysstem (computed in ``HSV(1)``). For + computing a minimal realization, the recommended value is + ``tol = n * eps * HNORM(A, B, C)``, where `eps` is the machine + precision (see LAPACK Library Routine `DLAMCH`). This value is + used by default if ``tol <= 0`` on entry. If `nr` is specified, + the value of `tol` is ignored. Default is `0.0`. + ldwork : int, optional + The length of the cache array. The default value is + ``n*(2*n+max(n,m,p)+5) + n*(n+1)/2 ~= 3.5*n**2 + 5*n``, + a larger value should lead to better performance. + Default is None. + + Returns + ------- + nr : int + `nr` is the order of the resulting reduced order model. + `nr` is set as follows: + If on input ``nr is not None``, `nr` is equal to ``MIN(nr,NMIN)``, + where `nr` is the desired order on entry and `NMIN` is the order + of a minimal realization of the given system; `NMIN` is + determined as the number of Hankel singular values greater + than ``n*eps*HNORM(A,B,C)``, where `eps` is the machine + precision (see LAPACK Library Routine DLAMCH) and + ``HNORM(A,B,C)`` is the Hankel norm of the system (computed + in ``HSV(1)``); + If on input ``nr is None``, `nr` is equal to the number of Hankel + singular values greater than ``MAX(tol,n*eps*HNORM(A,B,C))``. + Ar : (nr, nr) ndarray + This array contains the state dynamics matrix `Ar` of the reduced + order system. + Br : (nr, m) ndarray + Tthis array contains the input/state matrix `Br` of the reduced + order system. + Cr : (p, nr) ndarray + This array contains the state/output matrix `Cr` of the reduced + order system. + hsv : (n, ) ndarray + If ``INFO = 0``, it contains the Hankel singular values of + the original system ordered decreasingly. ``HSV(1)`` is the + Hankel norm of the system. + T : (n, nr) ndarray + This array contains the right truncation matrix `T` of the reduced + order system. + Ti : (nr, n) ndarray + This array contains the left truncation matrix `Ti` of the reduced + order system. Raises ------ @@ -830,100 +928,101 @@ def ab09bd(dico,job,equil,n,m,p,A,B,C,D,nr=None,tol1=0,tol2=0,ldwork=None): Perturbation Approximation (SPA) model reduction method. Must supply either nr or tolerance values. - Arguments - Mode Parameters - dico - Specifies the type of the original system as follows: - = 'C': continuous-time system; - = 'D': discrete-time system. - job - Specifies the model reduction approach to be used - as follows: - = 'B': use the square-root SPA method; - = 'N': use the balancing-free square-root SPA method. - equil - Specifies whether the user wishes to preliminarily - equilibrate the triplet (A,B,C) as follows: - = 'S': perform equilibration (scaling); - = 'N': do not perform equilibration. - - Required arguments - n : input int - The order of the original state-space representation, i.e. - the order of the matrix A. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - On entry, the leading n-by-n part of this array must - contain the state dynamics matrix A. - B : input rank-2 array('d') with bounds (n,m) - On entry, the leading n-by-m part of this array must - contain the original input/state matrix B. - C : input rank-2 array('d') with bounds (p,n) - On entry, the leading p-by-n part of this array must - contain the original state/output matrix C. - D : input rank-2 array('d') with bounds (p,m) - On entry, the leading p-by-m part of this array must - contain the original input/output matrix D. - - Optional arguments : - nr :=None input int - nr is the desired order of - the resulting reduced order system. 0 <= nr <= n. - tol1 :=0 input double precision - If ordsel = 'A', tol1 contains the tolerance for - determining the order of reduced system. - For model reduction, the recommended value is - tol1 = c*hnorm(A,B,C), where c is a constant in the - interval [0.00001,0.001], and hnorm(A,B,C) is the - Hankel-norm of the given system (computed in hsv(1)). - For computing a minimal realization, the recommended - value is tol1 = n*eps*hnorm(A,B,C), where eps is the - machine precision (see LAPACK Library Routine DLAMCH). - This value is used by default if tol1 <= 0 on entry. - If ordsel = 'F', the value of tol1 is ignored. - tol2 :=0 input double precision - The tolerance for determining the order of a minimal - realization of the given system. The recommended value is - tol2 = n*eps*hnorm(A,B,C). This value is used by default - if tol2 <= 0 on entry. - If tol2 > 0, then tol2 <= tol1. - ldwork := None input int - The length of the cache array. The default value is n + 3*max(m,p), - for better performance should be larger. - - Return objects - nr : output int - nr is the order of the resulting reduced order model. - nr is set as follows: - if ordsel = 'F', nr is equal to min(nr,nmin), where nr - is the desired order on entry and nmin is the order of a - minimal realization of the given system; nmin is - determined as the number of Hankel singular values greater - than n*eps*hnorm(A,B,C), where eps is the machine - precision (see LAPACK Library Routine DLAMCH) and - hnorm(A,B,C) is the Hankel norm of the system (computed - in hsv(1)); - if ordsel = 'A', nr is equal to the number of Hankel - singular values greater than max(tol1,n*eps*hnorm(A,B,C)). - Ar : rank-2 array('d') with bounds (nr,nr) - the leading nr-by-nr part of this array contains the - state dynamics matrix Ar of the reduced order system. - Br : rank-2 array('d') with bounds (nr,m) - the leading nr-by-m part of this array contains the - input/state matrix Br of the reduced order system. - Cr : rank-2 array('d') with bounds (p,nr) - the leading p-by-nr part of this array contains the - state/output matrix Cr of the reduced order system. - Dr : rank-2 array('d') with bounds (p,m) - the leading p-by-m part of this array contains the - input/output matrix Dr of the reduced order system. - hsv : output double precision array, dimension (n) - If info = 0, it contains the Hankel singular values of - the original system ordered decreasingly. hsv(1) is the - Hankel norm of the system. + Parameters + ---------- + dico : {'C', 'D'} + Specifies the type of the original system as follows: + = 'C': continuous-time system; + = 'D': discrete-time system. + job : {'B', 'N'} + Specifies the model reduction approach to be used + as follows: + = 'B': use the square-root SPA method; + = 'N': use the balancing-free square-root SPA method. + equil : {'S', 'N'} + Specifies whether the user wishes to preliminarily + equilibrate the triplet (A,B,C) as follows: + = 'S': perform equilibration (scaling); + = 'N': do not perform equilibration. + n : int + The order of the original state-space representation, i.e. + the order of the matrix A. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + On entry, the leading n-by-n part of this array must + contain the state dynamics matrix A. + B : (n, m) array_like + On entry, the leading n-by-m part of this array must + contain the original input/state matrix B. + C : (p, n) array_like + On entry, the leading p-by-n part of this array must + contain the original state/output matrix C. + D : (p, m) array_like + On entry, the leading p-by-m part of this array must + contain the original input/output matrix D. + nr : int, optional + nr is the desired order of + the resulting reduced order system. 0 <= nr <= n. + Default is None. + tol1 : float, optional + If ordsel = 'A', tol1 contains the tolerance for + determining the order of reduced system. + For model reduction, the recommended value is + tol1 = c*hnorm(A,B,C), where c is a constant in the + interval [0.00001,0.001], and hnorm(A,B,C) is the + Hankel-norm of the given system (computed in hsv(1)). + For computing a minimal realization, the recommended + value is tol1 = n*eps*hnorm(A,B,C), where eps is the + machine precision (see LAPACK Library Routine DLAMCH). + This value is used by default if tol1 <= 0 on entry. + If ordsel = 'F', the value of tol1 is ignored. + Default is `0.0`. + tol2 : float, optional + The tolerance for determining the order of a minimal + realization of the given system. The recommended value is + tol2 = n*eps*hnorm(A,B,C). This value is used by default + if tol2 <= 0 on entry. + If tol2 > 0, then tol2 <= tol1. + Default is `0.0`. + ldwork : int, optional + The length of the cache array. The default value is n + 3*max(m,p), + for better performance should be larger. + Default is None. + + Returns + ------- + nr : int + nr is the order of the resulting reduced order model. + nr is set as follows: + if ordsel = 'F', nr is equal to min(nr,nmin), where nr + is the desired order on entry and nmin is the order of a + minimal realization of the given system; nmin is + determined as the number of Hankel singular values greater + than n*eps*hnorm(A,B,C), where eps is the machine + precision (see LAPACK Library Routine DLAMCH) and + hnorm(A,B,C) is the Hankel norm of the system (computed + in hsv(1)); + if ordsel = 'A', nr is equal to the number of Hankel + singular values greater than max(tol1,n*eps*hnorm(A,B,C)). + Ar : (nr, nr) ndarray + the leading nr-by-nr part of this array contains the + state dynamics matrix Ar of the reduced order system. + Br : (nr, m) ndarray + the leading nr-by-m part of this array contains the + input/state matrix Br of the reduced order system. + Cr : (p, nr) ndarray + the leading p-by-nr part of this array contains the + state/output matrix Cr of the reduced order system. + Dr : (p, m) ndarray + the leading p-by-m part of this array contains the + input/output matrix Dr of the reduced order system. + hsv : (n, ) ndarray + If info = 0, it contains the Hankel singular values of + the original system ordered decreasingly. hsv(1) is the + Hankel norm of the system. Raises ------ @@ -974,119 +1073,120 @@ def ab09md(dico,job,equil,n,m,p,A,B,C,alpha=None,nr=None,tol=0,ldwork=None): or the balancing-free square-root Balance & Truncate (B & T) model reduction method for the ALPHA-stable part of the system. - Arguments - Mode Parameters - dico - Specifies the type of the original system as follows: - = 'C': continuous-time system; - = 'D': discrete-time system. - job - Specifies the model reduction approach to be used - as follows: - = 'B': use the square-root Balance & Truncate method; - = 'N': use the balancing-free square-root - Balance & Truncate method. - equil - Specifies whether the user wishes to preliminarily - equilibrate the triplet (A,B,C) as follows: - = 'S': perform equilibration (scaling); - = 'N': do not perform equilibration. - - Required arguments - n : input int - The order of the original state-space representation, i.e. - the order of the matrix A. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d'), dimension (n,n) - On entry, the leading N-by-N part of this array must - contain the state dynamics matrix A. - B : input rank-2 array('d'), dimension (n,m) - On entry, the leading N-by-M part of this array must - contain the original input/state matrix B. - C : input rank-2 array('d'), dimension (p,n) - On entry, the leading P-by-N part of this array must - contain the original state/output matrix C. - - Optional arguments - alpha :=None input double precision - Specifies the alpha-stability boundary for the eigenvalues - of the state dynamics matrix A. For a continuous-time - system (dico = 'C'), alpha <= 0 is the boundary value for - the real parts of eigenvalues, while for a discrete-time - system (dico = 'D'), 0 <= alpha <= 1 represents the - boundary value for the moduli of eigenvalues. - The alpha-stability domain does not include the boundary. - nr := None input int - On entry with ordsel = 'F', nr is the desired order of the - resulting reduced order system. 0 <= nr <= n. - tol :=0 input double precision - If ordsel = 'A', tol contains the tolerance for - determining the order of reduced system. - For model reduction, the recommended value is - tol = c*hnorm(As,Bs,Cs), where c is a constant in the - interval [0.00001,0.001], and hnorm(As,Bs,Cs) is the - Hankel-norm of the alpha-stable part of the given system - (computed in hsv(1)). - If tol <= 0 on entry, the used default value is - tol = ns*eps*hnorm(As,Bs,Cs), where ns is the number of - alpha-stable eigenvalues of A and eps is the machine - precision (see LAPACK Library Routine DLAMCH). - This value is appropriate to compute a minimal realization - of the alpha-stable part. - If ordsel = 'F', the value of tol is ignored. - ldwork :=None input int - The length of the array dwork. - ldwork >= max(1,n*(2*n+max(n,m,p)+5) + n*(n+1)/2). - For optimum performance ldwork should be larger. - - Return objects - nr : output int - On exit, if info = 0, nr is the order of the resulting - reduced order model. For a system with nu alpha-unstable - eigenvalues and ns alpha-stable eigenvalues (nu+ns = n), - nr is set as follows: if ordsel = 'F', nr is equal to - nu+min(max(0,nr-nu),nmin), where nr is the desired order - on entry, and nmin is the order of a minimal realization - of the alpha-stable part of the given system; nmin is - determined as the number of Hankel singular values greater - than ns*eps*hnorm(As,Bs,Cs), where eps is the machine - precision (see LAPACK Library Routine DLAMCH) and - hnorm(As,Bs,Cs) is the Hankel norm of the alpha-stable - part of the given system (computed in hsv(1)); - if ordsel = 'A', nr is the sum of nu and the number of - Hankel singular values greater than - max(tol,ns*eps*hnorm(As,Bs,Cs)). - Ar : rank-2 array('d') with bounds (nr,nr) - On exit, if info = 0, the leading nr-by-nr part of this - array contains the state dynamics matrix Ar of the reduced - order system. - The resulting A has a block-diagonal form with two blocks. - For a system with nu alpha-unstable eigenvalues and - ns alpha-stable eigenvalues (nu+ns = n), the leading - nu-by-nu block contains the unreduced part of A - corresponding to alpha-unstable eigenvalues in an - upper real Schur form. - The trailing (nr+ns-n)-by-(nr+ns-n) block contains - the reduced part of A corresponding to alpha-stable - eigenvalues. - Br : rank-2 array('d') with bounds (nr,m) - On exit, if info = 0, the leading nr-by-m part of this - array contains the input/state matrix Br of the reduced - order system. - Cr : rank-2 array('d') with bounds (p,nr) - On exit, if info = 0, the leading p-by-nr part of this - array contains the state/output matrix Cr of the reduced - order system. - ns : output int - The dimension of the alpha-stable subsystem. - hsv : output double precision array, dimension (n) - If info = 0, the leading ns elements of hsv contain the - Hankel singular values of the alpha-stable part of the - original system ordered decreasingly. - hsv(1) is the Hankel norm of the alpha-stable subsystem. + Parameters + ---------- + dico : {'C', 'D'} + Specifies the type of the original system as follows: + = 'C': continuous-time system; + = 'D': discrete-time system. + job : {'B', 'N'} + Specifies the model reduction approach to be used + as follows: + = 'B': use the square-root Balance & Truncate method; + = 'N': use the balancing-free square-root + Balance & Truncate method. + equil : {'S', 'N'} + Specifies whether the user wishes to preliminarily + equilibrate the triplet (A,B,C) as follows: + = 'S': perform equilibration (scaling); + = 'N': do not perform equilibration. + n : int + The order of the original state-space representation, i.e. + the order of the matrix A. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + On entry, the leading N-by-N part of this array must + contain the state dynamics matrix A. + B : (n, m) array_like + On entry, the leading N-by-M part of this array must + contain the original input/state matrix B. + C : (p, n) array_like + On entry, the leading P-by-N part of this array must + contain the original state/output matrix C. + alpha : float, optional + Specifies the alpha-stability boundary for the eigenvalues + of the state dynamics matrix A. For a continuous-time + system (dico = 'C'), alpha <= 0 is the boundary value for + the real parts of eigenvalues, while for a discrete-time + system (dico = 'D'), 0 <= alpha <= 1 represents the + boundary value for the moduli of eigenvalues. + The alpha-stability domain does not include the boundary. + Default is None. + nr : int, optional + On entry with ordsel = 'F', nr is the desired order of the + resulting reduced order system. 0 <= nr <= n. + Default is None. + tol : float, optional + If ordsel = 'A', tol contains the tolerance for + determining the order of reduced system. + For model reduction, the recommended value is + tol = c*hnorm(As,Bs,Cs), where c is a constant in the + interval [0.00001,0.001], and hnorm(As,Bs,Cs) is the + Hankel-norm of the alpha-stable part of the given system + (computed in hsv(1)). + If tol <= 0 on entry, the used default value is + tol = ns*eps*hnorm(As,Bs,Cs), where ns is the number of + alpha-stable eigenvalues of A and eps is the machine + precision (see LAPACK Library Routine DLAMCH). + This value is appropriate to compute a minimal realization + of the alpha-stable part. + If ordsel = 'F', the value of tol is ignored. + Default is `0.0`. + ldwork : int, optional + The length of the array dwork. + ldwork >= max(1,n*(2*n+max(n,m,p)+5) + n*(n+1)/2). + For optimum performance ldwork should be larger. + Default is None. + + Returns + ------- + nr : int + On exit, if info = 0, nr is the order of the resulting + reduced order model. For a system with nu alpha-unstable + eigenvalues and ns alpha-stable eigenvalues (nu+ns = n), + nr is set as follows: if ordsel = 'F', nr is equal to + nu+min(max(0,nr-nu),nmin), where nr is the desired order + on entry, and nmin is the order of a minimal realization + of the alpha-stable part of the given system; nmin is + determined as the number of Hankel singular values greater + than ns*eps*hnorm(As,Bs,Cs), where eps is the machine + precision (see LAPACK Library Routine DLAMCH) and + hnorm(As,Bs,Cs) is the Hankel norm of the alpha-stable + part of the given system (computed in hsv(1)); + if ordsel = 'A', nr is the sum of nu and the number of + Hankel singular values greater than + max(tol,ns*eps*hnorm(As,Bs,Cs)). + Ar : (nr, nr) array_like + On exit, if info = 0, the leading nr-by-nr part of this + array contains the state dynamics matrix Ar of the reduced + order system. + The resulting A has a block-diagonal form with two blocks. + For a system with nu alpha-unstable eigenvalues and + ns alpha-stable eigenvalues (nu+ns = n), the leading + nu-by-nu block contains the unreduced part of A + corresponding to alpha-unstable eigenvalues in an + upper real Schur form. + The trailing (nr+ns-n)-by-(nr+ns-n) block contains + the reduced part of A corresponding to alpha-stable + eigenvalues. + Br : (nr, m) array_like + On exit, if info = 0, the leading nr-by-m part of this + array contains the input/state matrix Br of the reduced + order system. + Cr : (p, nr) array_like + On exit, if info = 0, the leading p-by-nr part of this + array contains the state/output matrix Cr of the reduced + order system. + ns : int + The dimension of the alpha-stable subsystem. + hsv : (n, ) array_like + If info = 0, the leading ns elements of hsv contain the + Hankel singular values of the alpha-stable part of the + original system ordered decreasingly. + hsv(1) is the Hankel norm of the alpha-stable subsystem. Raises ------ @@ -1143,115 +1243,116 @@ def ab09nd(dico,job,equil,n,m,p,A,B,C,D,alpha=None,nr=None,tol1=0,tol2=0,ldwork= Perturbation Approximation (SPA) model reduction method for the alpha-stable part of the system. - Arguments - Mode Parameters - dico - Specifies the type of the original system as follows: - = 'C': continuous-time system; - = 'D': discrete-time system. - job - Specifies the model reduction approach to be used - as follows: - = 'B': use the square-root SPA method; - = 'N': use the balancing-free square-root SPA method. - equil - Specifies whether the user wishes to preliminarily - equilibrate the triplet (A,B,C) as follows: - = 'S': perform equilibration (scaling); - = 'N': do not perform equilibration. - - Required arguments - n : input int - The order of the original state-space representation, i.e. - the order of the matrix A. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - On entry, the leading n-by-n part of this array must - contain the state dynamics matrix A. - B : input rank-2 array('d') with bounds (n,m) - On entry, the leading n-by-m part of this array must - contain the original input/state matrix B. - C : input rank-2 array('d') with bounds (p,n) - On entry, the leading p-by-n part of this array must - contain the original state/output matrix C. - D : input rank-2 array('d') with bounds (p,m) - On entry, the leading p-by-m part of this array must - contain the original input/output matrix D. - - Optional arguments - alpha :=None input double precision - Specifies the alpha-stability boundary for the eigenvalues - of the state dynamics matrix A. For a continuous-time - system (dico = 'C'), alpha <= 0 is the boundary value for - the real parts of eigenvalues, while for a discrete-time - system (dico = 'D'), 0 <= alpha <= 1 represents the - boundary value for the moduli of eigenvalues. - The alpha-stability domain does not include the boundary. - nr :=None input int - nr is the desired order of - the resulting reduced order system. 0 <= nr <= n. - tol1 :=0 input double precision - If ordsel = 'A', tol1 contains the tolerance for - determining the order of reduced system. - For model reduction, the recommended value is - tol1 = c*hnorm(As,Bs,Cs), where c is a constant in the - interval [0.00001,0.001], and hnorm(As,Bs,Cs) is the - Hankel-norm of the alpha-stable part of the given system - (computed in hsv(1)). - If tol1 <= 0 on entry, the used default value is - tol1 = ns*eps*hnorm(As,Bs,Cs), where NS is the number of - alpha-stable eigenvalues of A and eps is the machine - precision (see LAPACK Library Routine DLAMCH). - This value is appropriate to compute a minimal realization - of the alpha-stable part. - If ordsel = 'F', the value of tol1 is ignored. - tol2 :=0 input double precision - The tolerance for determining the order of a minimal - realization of the alpha-stable part of the given system. - The recommended value is tol2 = ns*eps*hnorm(As,Bs,Cs). - This value is used by default if tol2 <= 0 on entry. - If tol2 > 0, then tol2 <= tol1. - ldwork := None input int - The length of the array dwork. - ldwork >= max(1,n*(2*n+max(n,m,p)+5) + n*(n+1)/2). - For optimum performance ldwork should be larger. - - Return objects - nr : output int - nr is the order of the resulting reduced order model. - nr is set as follows: - if ordsel = 'F', nr is equal to min(nr,nmin), where nr - is the desired order on entry and nmin is the order of a - minimal realization of the given system; nmin is - determined as the number of Hankel singular values greater - than n*eps*hnorm(A,B,C), where eps is the machine - precision (see LAPACK Library Routine DLAMCH) and - hnorm(A,B,C) is the Hankel norm of the system (computed - in hsv(1)); - if ordsel = 'A', nr is equal to the number of Hankel - singular values greater than max(TOL1,n*eps*hnorm(A,B,C)). - Ar : rank-2 array('d') with bounds (nr,nr) - the leading nr-by-nr part of this array contains the - state dynamics matrix Ar of the reduced order system. - Br : rank-2 array('d') with bounds (nr,m) - the leading nr-by-m part of this array contains the - input/state matrix Br of the reduced order system. - Cr : rank-2 array('d') with bounds (p,nr) - the leading p-by-nr part of this array contains the - state/output matrix Cr of the reduced order system. - Dr : rank-2 array('d') with bounds (p,m) - the leading p-by-m part of this array contains the - input/output matrix Dr of the reduced order system. - ns : output int - The dimension of the alpha-stable subsystem. - hsv : output double precision array, dimension (n) - If info = 0, it contains the Hankel singular values of - the original system ordered decreasingly. hsv(1) is the - Hankel norm of the system. + Parameters + ---------- + dico : {'C', 'D'} + Specifies the type of the original system as follows: + = 'C': continuous-time system; + = 'D': discrete-time system. + job : {'B', 'N'} + Specifies the model reduction approach to be used + as follows: + = 'B': use the square-root SPA method; + = 'N': use the balancing-free square-root SPA method. + equil : {'S', 'N'} + Specifies whether the user wishes to preliminarily + equilibrate the triplet (A,B,C) as follows: + = 'S': perform equilibration (scaling); + = 'N': do not perform equilibration. + n : int + The order of the original state-space representation, i.e. + the order of the matrix A. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + On entry, the leading n-by-n part of this array must + contain the state dynamics matrix A. + B : (n, m) array_like + On entry, the leading n-by-m part of this array must + contain the original input/state matrix B. + C : (p, n) array_like + On entry, the leading p-by-n part of this array must + contain the original state/output matrix C. + D : (p, m) array_like + On entry, the leading p-by-m part of this array must + contain the original input/output matrix D. + alpha : float, optional + Specifies the alpha-stability boundary for the eigenvalues + of the state dynamics matrix A. For a continuous-time + system (dico = 'C'), alpha <= 0 is the boundary value for + the real parts of eigenvalues, while for a discrete-time + system (dico = 'D'), 0 <= alpha <= 1 represents the + boundary value for the moduli of eigenvalues. + The alpha-stability domain does not include the boundary. + Default is None. + nr : int, optional + nr is the desired order of + the resulting reduced order system. 0 <= nr <= n. + Default is None. + tol1 : float, optional + If ordsel = 'A', tol1 contains the tolerance for + determining the order of reduced system. + For model reduction, the recommended value is + tol1 = c*hnorm(As,Bs,Cs), where c is a constant in the + interval [0.00001,0.001], and hnorm(As,Bs,Cs) is the + Hankel-norm of the alpha-stable part of the given system + (computed in hsv(1)). + If tol1 <= 0 on entry, the used default value is + tol1 = ns*eps*hnorm(As,Bs,Cs), where NS is the number of + alpha-stable eigenvalues of A and eps is the machine + precision (see LAPACK Library Routine DLAMCH). + This value is appropriate to compute a minimal realization + of the alpha-stable part. + If ordsel = 'F', the value of tol1 is ignored. + Default is `0.0`. + tol2 : float, optional + The tolerance for determining the order of a minimal + realization of the alpha-stable part of the given system. + The recommended value is tol2 = ns*eps*hnorm(As,Bs,Cs). + This value is used by default if tol2 <= 0 on entry. + If tol2 > 0, then tol2 <= tol1. + Default is `0.0`. + ldwork : int, optional + The length of the array dwork. + ldwork >= max(1,n*(2*n+max(n,m,p)+5) + n*(n+1)/2). + For optimum performance ldwork should be larger. + Default is None. + Returns + ------- + nr : int + nr is the order of the resulting reduced order model. + nr is set as follows: + if ordsel = 'F', nr is equal to min(nr,nmin), where nr + is the desired order on entry and nmin is the order of a + minimal realization of the given system; nmin is + determined as the number of Hankel singular values greater + than n*eps*hnorm(A,B,C), where eps is the machine + precision (see LAPACK Library Routine DLAMCH) and + hnorm(A,B,C) is the Hankel norm of the system (computed + in hsv(1)); + if ordsel = 'A', nr is equal to the number of Hankel + singular values greater than max(TOL1,n*eps*hnorm(A,B,C)). + Ar : (nr, nr) ndarray + the leading nr-by-nr part of this array contains the + state dynamics matrix Ar of the reduced order system. + Br : (nr, m) ndarray + the leading nr-by-m part of this array contains the + input/state matrix Br of the reduced order system. + Cr : (p, nr) ndarray + the leading p-by-nr part of this array contains the + state/output matrix Cr of the reduced order system. + Dr : (p, m) ndarray + the leading p-by-m part of this array contains the + input/output matrix Dr of the reduced order system. + ns : int + The dimension of the alpha-stable subsystem. + hsv : (n, ) ndarray + If info = 0, it contains the Hankel singular values of + the original system ordered decreasingly. hsv(1) is the + Hankel norm of the system. Raises ------ @@ -1316,21 +1417,21 @@ def ab13bd(dico, jobn, n, m, p, A, B, C, D, tol = 0.0): jobn : {'H', 'L'} H2-norm 'H' or L2-norm 'L' to be computed. n : int - The number of state variables. n >= 0. + The number of state variables. n >= 0. m : int - The number of system inputs. m >= 0. + The number of system inputs. m >= 0. p : int - The number of system outputs. p >= 0. - A : (n,n) ndarray + The number of system outputs. p >= 0. + A : (n, n) ndarray The leading n-by-n part of this array must contain the state dynamics matrix A of the system. - B : (n,m) ndarray + B : (n, m) ndarray The leading n-by-m part of this array must contain the input/state matrix B of the system. - C : (p,n) ndarray + C : (p, n) ndarray The leading p-by-n part of this array must contain the state/output matrix C of the system. - D : (p,m) ndarray + D : (p, m) ndarray The leading p-by-m part of this array must contain the direct transmission matrix D of the system. tol : float, optional @@ -1378,13 +1479,14 @@ def ab13bd(dico, jobn, n, m, p, A, B, C, D, tol = 0.0): denominator of `G` (see the SLICOT subroutine SB08DD). """ - out = _wrapper.ab13bd(dico, jobn, n, m, p, A, B, C, D, tol) - hidden = ' (hidden by the wrapper)' arg_list = ('dico', 'jobn', 'n', 'm', 'p', 'A', 'lda' + hidden, 'B', 'ldb' + hidden, 'C', 'ldc' + hidden, 'D', 'ldd' + hidden, 'nq' + hidden,'tol', 'dwork' + hidden, 'ldwork' + hidden, 'iwarn', 'info') + + out = _wrapper.ab13bd(dico, jobn, n, m, p, A, B, C, D, tol) + raise_if_slycot_error(out[-2:], arg_list, ab13bd.__doc__, locals()) return out[0] @@ -1402,65 +1504,66 @@ def ab13dd(dico, jobe, equil, jobd, n, m, p, A, E, B, C, D, tol = 1e-10): imaginary axis, or the unit circle, respectively. It is assumed that the matrix E is nonsingular. - Required arguments: - dico : {'D', 'C'} input string(len=1) - Indicate whether the system is discrete 'D' or continuous 'C'. - jobe : {'G', 'I'} input string(len=1) - Specifies whether E is a general square or an identity - matrix, as follows: - = 'G': E is a general square matrix; - = 'I': E is the identity matrix. - equil : {'S', 'N'} input string(len=1) - Specifies whether the user wishes to preliminarily - equilibrate the system (A,E,B,C) or (A,B,C), as follows: - = 'S': perform equilibration (scaling); - = 'N': do not perform equilibration. - jobd : {'D', 'Z'} input string(len=1) - Specifies whether or not a non-zero matrix D appears in - the given state space model: - = 'D': D is present; - = 'Z': D is assumed a zero matrix. - n : input int - The number of state variables. n >= 0. - m : input int - The number of system inputs. m >= 0. - p : input int - The number of system outputs. p >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the state - dynamics matrix A of the system. - E : input rank-2 array('d') with bounds (n,n) - If jobe = 'G', the leading N-by-N part of this array must - contain the descriptor matrix E of the system. - If jobe = 'I', then E is assumed to be the identity - matrix and is not referenced. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the input/state - matrix B of the system. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the state/output - matrix C of the system. - D : input rank-2 array('d') with bounds (p,m) - The leading p-by-m part of this array must contain the direct - transmission matrix D of the system. - - Optional arguments: - tol : Tolerance used to set the accuracy in determining the - norm. 0 <= tol < 1. - - Return objects: - gpeak : float - The L-infinity norm of the system, i.e., the peak gain - of the frequency response (as measured by the largest - singular value in the MIMO case). - fpeak : float - The frequency where the gain of the frequency response - achieves its peak value gpeak, i.e., - - || G ( j*fpeak ) || = gpeak , if dico = 'C', or - - j*fpeak - || G ( e ) || = gpeak , if dico = 'D'. + Parameters + ---------- + dico : {'D', 'C'} + Indicate whether the system is discrete 'D' or continuous 'C'. + jobe : {'G', 'I'} + Specifies whether E is a general square or an identity + matrix, as follows: + = 'G': E is a general square matrix; + = 'I': E is the identity matrix. + equil : {'S', 'N'} + Specifies whether the user wishes to preliminarily + equilibrate the system (A,E,B,C) or (A,B,C), as follows: + = 'S': perform equilibration (scaling); + = 'N': do not perform equilibration. + jobd : {'D', 'Z'} + Specifies whether or not a non-zero matrix D appears in + the given state space model: + = 'D': D is present; + = 'Z': D is assumed a zero matrix. + n : int + The number of state variables. n >= 0. + m : int + The number of system inputs. m >= 0. + p : int + The number of system outputs. p >= 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the state + dynamics matrix A of the system. + E : (n, n) array_like + If jobe = 'G', the leading N-by-N part of this array must + contain the descriptor matrix E of the system. + If jobe = 'I', then E is assumed to be the identity + matrix and is not referenced. + B : (n, m) array_like + The leading n-by-m part of this array must contain the input/state + matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must contain the state/output + matrix C of the system. + D : (p, m) array_like + The leading p-by-m part of this array must contain the direct + transmission matrix D of the system. + tol : float + Tolerance used to set the accuracy in determining the norm. + 0 <= tol < 1. Default tol=1e-10. + + Returns + ------- + gpeak : float + The L-infinity norm of the system, i.e., the peak gain + of the frequency response (as measured by the largest + singular value in the MIMO case). + fpeak : float + The frequency where the gain of the frequency response + achieves its peak value gpeak, i.e., + + || G ( j*fpeak ) || = gpeak , if dico = 'C', or + + j*fpeak + || G ( e ) || = gpeak , if dico = 'D'. Raises ------ @@ -1530,9 +1633,9 @@ def ab13ed(n, A, tol = 9.0): ---------- n : int The order of the matrix A. ``n >= 0.`` - A : (n,n) array_like + A : (n, n) array_like The leading n-by-n part of this array must contain the matrix A. - tol : float optional + tol : float, optional Specifies the accuracy with which low and high approximate beta(A). If the user sets tol to be less than sqrt(eps), where eps is the machine precision (see LAPACK Library @@ -1573,41 +1676,27 @@ def ab13fd(n, A, tol = 0.0): smallest singular value of (A - jwI), taken over all real w. The value of w corresponding to the minimum is also computed. - Required arguments: - n : input int - The order of the matrix A. n >= 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the matrix A. - - Optional arguments: - tol : Specifies the accuracy with which beta(A) is to be - calculated. (See the Numerical Aspects section below.) - If the user sets tol to be less than eps, where eps is the - machine precision (see LAPACK Library Routine DLAMCH), - then the tolerance is taken to be eps. - - Return objects: - beta : float - The computed value of beta(A), which actually is an upper - bound. - omega : float - The value of w such that the smallest singular value of - (A - jwI) equals beta(A). - - Numerical Aspects: - In the presence of rounding errors, the computed function value - beta satisfies - beta(A) <= beta + epsilon, - beta/(1+tol) - delta <= max(beta(A), sqrt(2*n*eps)*norm(A)), - where norm(A) is the Frobenius norm of A, - epsilon = p(n) * eps * norm(A), - and - delta = p(n) * sqrt(eps) * norm(A), - and p(n) is a low degree polynomial. It is recommended to choose - tol greater than sqrt(eps). Although rounding errors can cause - AB13FD to fail for smaller values of tol, nevertheless, it usually - succeeds. Regardless of success or failure, the first inequality - holds. + Parameters + ---------- + n : int + The order of the matrix A. n >= 0. + A : (n, n), array_like + The leading n-by-n part of this array must contain the matrix A. + tol : float, optional + Specifies the accuracy with which beta(A) is to be + calculated. (See the Numerical Aspects section below.) + If the user sets tol to be less than eps, where eps is the + machine precision (see LAPACK Library Routine DLAMCH), + then the tolerance is taken to be eps. + + Returns + ------- + beta : float + The computed value of beta(A), which actually is an upper + bound. + omega : float + The value of w such that the smallest singular value of + (A - jwI) equals beta(A). Raises ------ @@ -1621,6 +1710,22 @@ def ab13fd(n, A, tol = 0.0): :info = 1: Failed to compute beta(A) within the specified tolerance. Nevertheless, the returned value is an upper bound on beta(A); + + Notes + ----- + In the presence of rounding errors, the computed function value + beta satisfies + beta(A) <= beta + epsilon, + beta/(1+tol) - delta <= max(beta(A), sqrt(2*n*eps)*norm(A)), + where norm(A) is the Frobenius norm of A, + epsilon = p(n) * eps * norm(A), + and + delta = p(n) * sqrt(eps) * norm(A), + and p(n) is a low degree polynomial. It is recommended to choose + tol greater than sqrt(eps). Although rounding errors can cause + AB13FD to fail for smaller values of tol, nevertheless, it usually + succeeds. Regardless of success or failure, the first inequality + holds. """ hidden = ' (hidden by the wrapper)' arg_list = ['n', 'A', 'lda' + hidden, 'beta' + hidden, 'omega' + hidden, 'tol', @@ -1639,40 +1744,34 @@ def ab13md(Z, nblock, itype, x=None): Parameters ---------- - Z : (n,n) complex array - Matrix to find structured singular value upper bound of - - nblock : (m,) integer array - The size of the block diagonals of the uncertainty structure; - i.e., nblock(i)=p means that the ith block is pxp. - - itype : (m,) integer array - The type of each block diagonal uncertainty defined in nblock. - itype(i)==1 means that the ith block is real, while itype(i)==2 - means the the ith block is complex. Real blocks must be 1x1, - i.e., if itype(i)==1, nblock(i) must be 1. - - x : (q,) real array or None - If not None, must be the output of a previous call to ab13md. - The previous call must have been with the same values of n, - nblock, and itype; and the previous call's Z should be "close" - to the current call's Z. - - q is determined by the block structure; see SLICOT AB13MD for - details. + Z : (n, n) array_like + Matrix to find structured singular value upper bound of + nblock : (m, ) array_like + The size of the block diagonals of the uncertainty structure; + i.e., nblock(i)=p means that the ith block is pxp. + itype : (m, ) array_like + The type of each block diagonal uncertainty defined in nblock. + itype(i)==1 means that the ith block is real, while itype(i)==2 + means the the ith block is complex. Real blocks must be 1x1, + i.e., if itype(i)==1, nblock(i) must be 1. + x : (q, ) array_like, optional + If not None, must be the output of a previous call to ab13md. + The previous call must have been with the same values of n, + nblock, and itype; and the previous call's Z should be "close" + to the current call's Z. + q is determined by the block structure; see SLICOT AB13MD for + details. Default is None. Returns ------- mubound : non-negative real scalar - Upper bound on structure singular value for given arguments - - d, g : (n,) real arrays - Real arrays such that if D=np.diag(g), G=np.diag(G), and ZH = Z.T.conj(), then - ZH @ D**2 @ Z + 1j * (G@Z - ZH@G) - mu**2 * D**2 - will be negative semi-definite. - - xout : (q,) real array - For use as ``x`` argument in subsequent call to ``ab13md``. + Upper bound on structure singular value for given arguments + d, g : (n, ) ndarray + Real arrays such that if D=np.diag(g), G=np.diag(G), and ZH = Z.T.conj(), then + ZH @ D**2 @ Z + 1j * (G@Z - ZH@G) - mu**2 * D**2 + will be negative semi-definite. + xout : (q, ) ndarray + For use as ``x`` argument in subsequent call to ``ab13md``. For scalar Z and real uncertainty (ntype=1, itype=1), returns 0 instead of abs(Z). @@ -1755,75 +1854,75 @@ def ag08bd(l,n,m,p,A,E,B,C,D,equil='N',tol=0.0,ldwork=None): and left Kronecker indices, and the multiplicities of infinite eigenvalues. - Required arguments: - l : input int - The number of rows of matrices A, B, and E. l >= 0. - n : input int - The number of columns of matrices A, E, and C. n >= 0. - m : input int - The number of columns of matrix B. m >= 0. - p : input int - The number of rows of matrix C. p >= 0. - A : rank-2 array('d') with bounds (l,n) - The leading l-by-n part of this array must - contain the state dynamics matrix A of the system. - E : rank-2 array('d') with bounds (l,n) - The leading l-by-n part of this array must - contain the descriptor matrix E of the system. - B : rank-2 array('d') with bounds (l,m) - The leading l-by-m part of this array must - contain the input/state matrix B of the system. - C : rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must - contain the state/output matrix C of the system. - D : rank-2 array('d') with bounds (p,m) - The leading p-by-m part of this array must contain the - direct transmission matrix D of the system. - Optional arguments: - equil := 'N' input string(len=1) - Specifies whether the user wishes to balance the system - matrix as follows: - = 'S': Perform balancing (scaling); - = 'N': Do not perform balancing. - tol := 0 input float - A tolerance used in rank decisions to determine the - effective rank, which is defined as the order of the - largest leading (or trailing) triangular submatrix in the - QR (or RQ) factorization with column (or row) pivoting - whose estimated condition number is less than 1/TOL. - If the user sets TOL <= 0, then default tolerances are - used instead, as follows: TOLDEF = L*N*EPS in TG01FD - (to determine the rank of E) and TOLDEF = (L+P)*(N+M)*EPS - in the rest, where EPS is the machine precision - (see LAPACK Library routine DLAMCH). TOL < 1. - ldwork : input int - The length of the cache array. - ldwork >= max( 4*(l,n), ldw ), if equil = 'S', - ldwork >= ldw, if equil = 'N', where - ldw = max(l+p,m+n)*(m+n) + max(1,5*max(l+p,m+n)). - For optimum performance ldwork should be larger. - Return objects: - Af : rank-2 array('d') - the leading NFZ-by-NFZ part of this array - contains the matrix Af of the reduced pencil. - Ef : rank-2 array('d') - the leading NFZ-by-NFZ part of this array - contains the matrix Ef of the reduced pencil. - nrank : output int - The normal rank of the system pencil. - niz : output int - The number of infinite zeros. - infz : rank-1 array('i') - The leading DINFZ elements of infz contain information - on the infinite elementary divisors as follows: - the system has infz(i) infinite elementary divisors of - degree i in the Smith form, where i = 1,2,...,DINFZ. - kronr : rank-1 array('i') - The leading NKROR elements of this array contain the - right Kronecker (column) indices. - infe : rank-1 array('i') - The leading NINFE elements of infe contain the - multiplicities of infinite eigenvalues. + Parameters + ---------- + l : int + The number of rows of matrices A, B, and E. l >= 0. + n : int + The number of columns of matrices A, E, and C. n >= 0. + m : int + The number of columns of matrix B. m >= 0. + p : int + The number of rows of matrix C. p >= 0. + A : (l, n) array_like + The leading l-by-n part of this array must + contain the state dynamics matrix A of the system. + E : (l, n) array_like + The leading l-by-n part of this array must + contain the descriptor matrix E of the system. + B : (l, m) array_like + The leading l-by-m part of this array must + contain the input/state matrix B of the system. + C : (p, n) array_like + The leading p-by-n part of this array must + contain the state/output matrix C of the system. + D : (p, m) array_like + The leading p-by-m part of this array must contain the + direct transmission matrix D of the system. + equil : {'S', 'N'}, optional + Specifies whether the user wishes to balance the system + matrix as follows: + = 'S': Perform balancing (scaling); + = 'N': Do not perform balancing. + Default is `N` + tol : float, optional + A tolerance used in rank decisions to determine the + effective rank, which is defined as the order of the + largest leading (or trailing) triangular submatrix in the + QR (or RQ) factorization with column (or row) pivoting + whose estimated condition number is less than 1/TOL. + If the user sets TOL <= 0, then default tolerances are + used instead, as follows: TOLDEF = L*N*EPS in TG01FD + (to determine the rank of E) and TOLDEF = (L+P)*(N+M)*EPS + in the rest, where EPS is the machine precision + (see LAPACK Library routine DLAMCH). TOL < 1. + Default is 0. + ldwork : int, optional + The length of the cache array. + ldwork >= max( 4*(l,n), ldw ), if equil = 'S', + ldwork >= ldw, if equil = 'N', where + ldw = max(l+p,m+n)*(m+n) + max(1,5*max(l+p,m+n)). + For optimum performance ldwork should be larger. + Default is None. + + Returns + ------- + Af : (nfz, nfz) ndarray + the leading NFZ-by-NFZ part of this array + contains the matrix Af of the reduced pencil. + Ef : (nfz, nfz) ndarray + the leading NFZ-by-NFZ part of this array + contains the matrix Ef of the reduced pencil. + nrank : int + The normal rank of the system pencil. + niz : int + The number of infinite zeros. + infz : (n+1, ) ndarray + Contains information on the infinite elementary divisors. + kronr : (n+m+1, ) ndarray + Contains the right Kronecker (column) indices. + infe : (1+min(l+p,n+m), ) ndarray + Contains the multiplicities of infinite eigenvalues. """ hidden = ' (hidden by the wrapper)' arg_list = ['equil', 'l', 'n', 'm', 'p', diff --git a/slycot/math.py b/slycot/math.py index ae9aab18..5966458b 100644 --- a/slycot/math.py +++ b/slycot/math.py @@ -23,6 +23,107 @@ import numpy as np +def mb02ed(typet: str, T: np.ndarray, B: np.ndarray, n: int, k: int, nrhs: int): + """ X, T = mb02ed(typet, T, B, n, k, nrhs) + + Solve a system of linear equations T*X = B or X*T = B with a positive + definite block Toeplitz matrix T. + + Parameters + ---------- + typet: str + Specifies the type of T: + - 'R': T contains the first block row of an s.p.d. block Toeplitz matrix, + and the system X*T = B is solved. + - 'C': T contains the first block column of an s.p.d. block Toeplitz matrix, + and the system T*X = B is solved. + Note: the notation x / y means that x corresponds to + typet = 'R' and y corresponds to typet = 'C'. + T : array_like + The leading k-by-n*k / n*k-by-k part of this array must contain the first + block row/column of an s.p.d. block Toeplitz matrix. + B : array_like + The leading nrhs-by-n*k / n*k-by-nrhs part of this array must contain the + right-hand side matrix B. + n : int + The number of blocks in T. n >= 0. + k : int + The number of rows/columns in T, equal to the blocksize. k >= 0. + nrhs : int + The number of right-hand sides. nrhs >= 0. + + Returns + ------- + X : ndarray + Leading nrhs-by-n*k / n*k-by-nrhs part of + this array contains the solution matrix X. + T: ndarray + If no error is thrown and nrhs > 0, then the leading + k-by-n*k / n*k-by-k part of this array contains the last + row / column of the Cholesky factor of inv(T). + + Raises + ------ + SlycotArithmeticError + :info = 1: + The reduction algorithm failed. The Toeplitz matrix associated + with T is not numerically positive definite. + SlycotParameterError + :info = -1: + typet must be either "R" or "C" + :info = -2: + k must be >= 0 + :info = -3: + n must be >= 0 + :info = -4: + nrhs must be >= 0 + + Notes + ----- + The algorithm uses Householder transformations, modified hyperbolic rotations, + and block Gaussian eliminations in the Schur algorithm [1], [2]. + + References + ---------- + [1] Kailath, T. and Sayed, A. + Fast Reliable Algorithms for Matrices with Structure. + SIAM Publications, Philadelphia, 1999. + + [2] Kressner, D. and Van Dooren, P. + Factorizations and linear system solvers for matrices with Toeplitz structure. + SLICOT Working Note 2000-2, 2000. + + Numerical Aspects + ----------------- + The implemented method is numerically equivalent to forming the Cholesky factor R and the + inverse Cholesky factor of T using the generalized Schur algorithm and solving the systems + of equations R*X = L*B or X*R = B*L by a blocked backward substitution algorithm. + The algorithm requires O(K * N^2 + K * N * NRHS) floating-point operations. + + """ + + hidden = " (hidden by the wrapper)" + arg_list = [ + "typet", + "k", + "n", + "nrhs", + "t", + "ldt" + hidden, + "b", + "ldb" + hidden, + "ldwork" + hidden, + "dwork" + hidden, + "info", + ] + + T, X, info = _wrapper.mb02ed(typet=typet, k=k, n=n, nrhs=nrhs, t=T, b=B) + + raise_if_slycot_error(info, arg_list, docstring=mb02ed.__doc__, checkvars=locals()) + + return X, T + + def mb03rd(n, A, X=None, jobx='U', sort='N', pmax=1.0, tol=0.0): """Ar, Xr, blsize, W = mb03rd(n, A, [X, jobx, sort, pmax, tol]) @@ -36,80 +137,80 @@ def mb03rd(n, A, X=None, jobx='U', sort='N', pmax=1.0, tol=0.0): Parameters ---------- - n : int - The order of the matrices `A` and `X`. `n` >= 0. - A : (n, n) array_like - The matrix `A` to be block-diagonalized, in real Schur form. - X : (n, n) array_like, optional - A given matrix `X`, for accumulation of transformations (only if - `jobx`='U'). Default value is identity matrix of order `n`. - jobx : {'N', 'U'}, optional - Specifies whether or not the transformations are - accumulated, as follows: - - := 'N': The transformations are not accumulated - := 'U': The transformations are accumulated in `Xr` (default) - - sort : {'N', 'S', 'C', 'B'}, optional - Specifies whether or not the diagonal blocks of the real - Schur form are reordered, as follows: - - := 'N': The diagonal blocks are not reordered (default); - := 'S': The diagonal blocks are reordered before each - step of reduction, so that clustered eigenvalues - appear in the same block; - := 'C': The diagonal blocks are not reordered, but the - "closest-neighbour" strategy is used instead of - the standard "closest to the mean" strategy - (see Notes_); - := 'B': The diagonal blocks are reordered before each - step of reduction, and the "closest-neighbour" - strategy is used (see Notes_). - - pmax : float, optional - An upper bound for the infinity norm of elementary - submatrices of the individual transformations used for - reduction (see Notes_). `pmax` >= 1.0 - tol : float, optional - The tolerance to be used in the ordering of the diagonal - blocks of the real Schur form matrix. - If the user sets `tol` > 0, then the given value of `tol` is - used as an absolute tolerance: a block `i` and a temporarily - fixed block 1 (the first block of the current trailing - submatrix to be reduced) are considered to belong to the - same cluster if their eigenvalues satisfy - - .. math:: | \\lambda_1 - \\lambda_i | <= tol. - - If the user sets `tol` < 0, then the given value of tol is - used as a relative tolerance: a block i and a temporarily - fixed block 1 are considered to belong to the same cluster - if their eigenvalues satisfy, for ``j = 1, ..., n`` - - .. math:: | \\lambda_1 - \\lambda_i | <= | tol | * \\max | \\lambda_j |. - - If the user sets `tol` = 0, then an implicitly computed, - default tolerance, defined by ``tol = SQRT( SQRT( EPS ) )`` - is used instead, as a relative tolerance, where `EPS` is - the machine precision (see LAPACK Library routine DLAMCH). - If `sort` = 'N' or 'C', this parameter is not referenced. + n : int + The order of the matrices `A` and `X`. `n` >= 0. + A : (n, n) array_like + The matrix `A` to be block-diagonalized, in real Schur form. + X : (n, n) array_like, optional + A given matrix `X`, for accumulation of transformations (only if + `jobx`='U'). Default value is identity matrix of order `n`. + jobx : {'N', 'U'}, optional + Specifies whether or not the transformations are + accumulated, as follows: + + := 'N': The transformations are not accumulated + := 'U': The transformations are accumulated in `Xr` (default) + + sort : {'N', 'S', 'C', 'B'}, optional + Specifies whether or not the diagonal blocks of the real + Schur form are reordered, as follows: + + := 'N': The diagonal blocks are not reordered (default); + := 'S': The diagonal blocks are reordered before each + step of reduction, so that clustered eigenvalues + appear in the same block; + := 'C': The diagonal blocks are not reordered, but the + "closest-neighbour" strategy is used instead of + the standard "closest to the mean" strategy + (see Notes_); + := 'B': The diagonal blocks are reordered before each + step of reduction, and the "closest-neighbour" + strategy is used (see Notes_). + + pmax : float, optional + An upper bound for the infinity norm of elementary + submatrices of the individual transformations used for + reduction (see Notes_). `pmax` >= 1.0 + tol : float, optional + The tolerance to be used in the ordering of the diagonal + blocks of the real Schur form matrix. + If the user sets `tol` > 0, then the given value of `tol` is + used as an absolute tolerance: a block `i` and a temporarily + fixed block 1 (the first block of the current trailing + submatrix to be reduced) are considered to belong to the + same cluster if their eigenvalues satisfy + + .. math:: | \\lambda_1 - \\lambda_i | <= tol. + + If the user sets `tol` < 0, then the given value of tol is + used as a relative tolerance: a block i and a temporarily + fixed block 1 are considered to belong to the same cluster + if their eigenvalues satisfy, for ``j = 1, ..., n`` + + .. math:: | \\lambda_1 - \\lambda_i | <= | tol | * \\max | \\lambda_j |. + + If the user sets `tol` = 0, then an implicitly computed, + default tolerance, defined by ``tol = SQRT( SQRT( EPS ) )`` + is used instead, as a relative tolerance, where `EPS` is + the machine precision (see LAPACK Library routine DLAMCH). + If `sort` = 'N' or 'C', this parameter is not referenced. Returns ------- - Ar : (n, n) ndarray - Contains the computed block-diagonal matrix, in real Schur - canonical form. The non-diagonal blocks are set to zero. - Xr : (n, n) ndarray or None - Contains the product of the given matrix `X` and the - transformation matrix that reduced `A` to block-diagonal - form. The transformation matrix is itself a product of - non-orthogonal similarity transformations having elements - with magnitude less than or equal to `pmax`. - If `jobx` = 'N', this array is returned as None - blsize : (n,) ndarray - The orders of the resulting diagonal blocks of the matrix `Ar`. - W : (n,) complex ndarray - Contains the complex eigenvalues of the matrix `A`. + Ar : (n, n) ndarray + Contains the computed block-diagonal matrix, in real Schur + canonical form. The non-diagonal blocks are set to zero. + Xr : (n, n) ndarray or None + Contains the product of the given matrix `X` and the + transformation matrix that reduced `A` to block-diagonal + form. The transformation matrix is itself a product of + non-orthogonal similarity transformations having elements + with magnitude less than or equal to `pmax`. + If `jobx` = 'N', this array is returned as None + blsize : (n,) ndarray + The orders of the resulting diagonal blocks of the matrix `Ar`. + W : (n,) complex ndarray + Contains the complex eigenvalues of the matrix `A`. Notes ----- @@ -260,11 +361,9 @@ def mb03vd(n, ilo, ihi, A): Parameters ---------- - n : int The order of the square matrices A_1, A_2, ..., A_p. n >= 0. - ilo, ihi : int It is assumed that all matrices A_j, j = 2, ..., p, are already upper triangular in rows and columns [:ilo-1] and @@ -274,15 +373,12 @@ def mb03vd(n, ilo, ihi, A): If this is not the case, ilo and ihi should be set to 1 and n, respectively. 1 <= ilo <= max(1,n); min(ilo,n) <= ihi <= n. - A : ndarray A[:n,:n,:p] must contain the matrices of factors to be reduced; specifically, A[:,:,j-1] must contain A_j, j = 1, ..., p. - Returns ------- - HQ : ndarray 3D array with same shape as A. The upper triangle and the first subdiagonal of HQ[:n,:n,0] contain the upper Hessenberg @@ -295,16 +391,14 @@ def mb03vd(n, ilo, ihi, A): below the diagonal, with the j-th column of the array TAU represent the orthogonal matrix Q_j as a product of elementary reflectors. See FURTHER COMMENTS. - Tau : ndarray 2D array with shape (max(1, n-1), p). The leading n-1 elements in the j-th column contain the scalar factors of the elementary reflectors used to form the matrix Q_j, j = 1, ..., p. See FURTHER COMMENTS. - Further Comments - ---------------- - + Notes + ----- Each matrix Q_j is represented as a product of (ihi-ilo) elementary reflectors, @@ -377,26 +471,21 @@ def mb03vy(n, ilo, ihi, A, Tau, ldwork=None): Parameters ---------- - n : int The order of the matrices Q_1, Q_2, ..., Q_p. N >= 0. - ilo, ihi : int The values of the indices ilo and ihi, respectively, used in the previous call of the SLICOT Library routine MB03VD. 1 <= ilo <= max(1,n); min(ilo,n) <= ihi <= n. - A : ndarray A[:n,:n,j-1] must contain the vectors which define the elementary reflectors used for reducing A_j, as returned by SLICOT Library routine MB03VD, j = 1, ..., p. - Tau : ndarray The leading N-1 elements in the j-th column must contain the scalar factors of the elementary reflectors used to form the matrix Q_j, as returned by SLICOT Library routine MB03VD. - ldwork : int, optional The length of the internal array DWORK. ldwork >= max(1, n). For optimum performance ldwork should be larger. @@ -404,11 +493,9 @@ def mb03vy(n, ilo, ihi, A, Tau, ldwork=None): Returns ------- - Q : ndarray 3D array with same shape as A. Q[:n,:n,j-1] contains the N-by-N orthogonal matrix Q_j, j = 1, ..., p. - """ hidden = ' (hidden by the wrapper)' @@ -460,7 +547,7 @@ def mb03wd(job, compz, n, ilo, ihi, iloz, ihiz, H, Q, ldwork=None): = 'E': Compute the eigenvalues only; = 'S': Compute the factors T_1, ..., T_p of the full Schur form, T = T_1*T_2*...*T_p. - compz : {'N', 'I', 'V'} + compz : {'N', 'I', 'V'} Indicates whether or not the user wishes to accumulate the matrices Z_1, ..., Z_p, as follows: = 'N': The matrices Z_1, ..., Z_p are not required; @@ -675,9 +762,10 @@ def mb05nd(a, delta, tol=1e-7): Square matrix delta : float The scalar value delta of the problem. - tol : float - Tolerance. A good value is sqrt(eps) - + tol : float, optional + Tolerance. A good value is sqrt(eps). + Default is 1e-7. + Returns ------- F : (n, n) ndarray @@ -730,7 +818,6 @@ def mc01td(dico, dp, p): = 'C': continuous-time case; = 'D': discrete-time case. - dp : int The degree of the polynomial `P(x)`. ``dp >= 0``. p : (dp+1, ) array_like diff --git a/slycot/src/SLICOT-Reference b/slycot/src/SLICOT-Reference index 979f39d7..795051cb 160000 --- a/slycot/src/SLICOT-Reference +++ b/slycot/src/SLICOT-Reference @@ -1 +1 @@ -Subproject commit 979f39d7863628407b0f9cae6804efc2833849ab +Subproject commit 795051cbc2a1d4766753e9ab3bac13eaf731f8d6 diff --git a/slycot/src/analysis.pyf b/slycot/src/analysis.pyf index 633ff6ec..1789d893 100644 --- a/slycot/src/analysis.pyf +++ b/slycot/src/analysis.pyf @@ -19,6 +19,26 @@ subroutine ab01nd(jobz,n,m,a,lda,b,ldb,ncont,indcon,nblk,z,ldz,tau,tol,iwork,dwo integer :: ldwork = max(n,3*m) integer intent(out) :: info end subroutine ab01nd +subroutine ab04md(type_t,n,m,p,alpha,beta,a,lda,b,ldb,c,ldc,d,ldd,iwork,dwork,ldwork,info) ! in AB04MD.f + character :: type_t + integer check(n>=0) :: n + integer check(m>=0) :: m + integer check(p>=0) :: p + double precision intent(in) :: alpha + double precision intent(in) :: beta + double precision intent(in,out,copy), dimension(n,n),depend(n) :: a + integer intent(hide),depend(a) :: lda = shape(a,0) + double precision intent(in,out,copy), dimension(n,m),depend(n,m) :: b + integer intent(hide),depend(b) :: ldb = shape(b,0) + double precision intent(in,out,copy), dimension(p,n),depend(n,p) :: c + integer intent(hide),depend(c) :: ldc = shape(c,0) + double precision intent(in,out,copy), dimension(p,m),depend(m,p) :: d + integer intent(hide),depend(d) :: ldd = shape(d,0) + integer intent(hide,cache),dimension(n),depend(n) :: iwork + double precision intent(hide,cache),dimension(ldwork),depend(ldwork) :: dwork + integer :: ldwork = max(1,n) + integer intent(out) :: info +end subroutine ab04md subroutine ab05md(uplo,over,n1,m1,p1,n2,p2,a1,lda1,b1,ldb1,c1,ldc1,d1,ldd1,a2,lda2,b2,ldb2,c2,ldc2,d2,ldd2,n,a,lda,b,ldb,c,ldc,d,ldd,dwork,ldwork,info) ! in AB05MD.f character :: uplo = 'U' character intent(hide) :: over = 'N' ! not sure how the overlap works @@ -305,15 +325,15 @@ function ab13bd(dico,jobn,n,m,p,a,lda,b,ldb,c,ldc,d,ldd,nq,tol,dwork,ldwork,iwar character intent(in) :: dico character intent(in) :: jobn integer check(n>=0) :: n - integer check(n>=0) :: m - integer check(n>=0) :: p - double precision dimension(n,n),depend(n) :: a + integer check(m>=0) :: m + integer check(p>=0) :: p + double precision intent(in,out,copy), dimension(n,n),depend(n) :: a integer intent(hide),depend(a) :: lda = shape(a,0) - double precision dimension(n,m),depend(n,m) :: b + double precision intent(in,out,copy), dimension(n,m),depend(n,m) :: b integer intent(hide),depend(b) :: ldb = shape(b,0) - double precision dimension(p,n),depend(n,p) :: c + double precision intent(in,out,copy), dimension(p,n),depend(n,p) :: c integer intent(hide),depend(c) :: ldc = shape(c,0) - double precision dimension(p,m),depend(m,p) :: d + double precision intent(in,out,copy), dimension(p,m),depend(m,p) :: d integer intent(hide),depend(d) :: ldd = shape(d,0) integer intent(out) :: nq double precision :: tol diff --git a/slycot/src/math.pyf b/slycot/src/math.pyf index 7dc53ff9..fe78cdde 100644 --- a/slycot/src/math.pyf +++ b/slycot/src/math.pyf @@ -12,6 +12,20 @@ subroutine mc01td(dico,dp,p,stable,nz,dwork,iwarn,info) ! in :new:MC01TD.f integer intent(out) :: info end subroutine mc01td +subroutine mb02ed(typet,k,n,nrhs,t,ldt,b,ldb,dwork,ldwork,info) ! in MB02ED.f + character :: typet + integer intent(in),required :: k + integer intent(in),required :: n + integer intent(in),required :: nrhs + double precision intent(in,out,copy),dimension(ldt,*) :: t + integer, intent(hide),optional,check(shape(t, 0) == ldt),depend(t) :: ldt=shape(t, 0) + double precision intent(in,out,copy),dimension(ldb,*) :: b + integer, intent(hide),optional,check(shape(b, 0) == ldb),depend(b) :: ldb=shape(b, 0) + double precision intent(cache,hide),dimension(ldwork) :: dwork + integer optional,check(ldwork>=n*k*k+(n+2)*k), depend(n,k) :: ldwork=max(1,n*k*k+(n+2)*k) + integer intent(out):: info +end subroutine mb02ed + subroutine mb03rd(jobx,sort,n,pmax,a,lda,x,ldx,nblcks,blsize,wr,wi,tol,dwork,info) ! in MB03RD.f character intent(in) :: jobx character intent(in),required :: sort diff --git a/slycot/src/synthesis.pyf b/slycot/src/synthesis.pyf index a84cd03f..1f797763 100644 --- a/slycot/src/synthesis.pyf +++ b/slycot/src/synthesis.pyf @@ -575,6 +575,27 @@ subroutine sb10jd(n,m,np,a,lda,b,ldb,c,ldc,d,ldd,e,lde,nsys,dwork,ldwork,info) ! integer required intent(in) :: ldwork integer intent(out) :: info end subroutine sb10jd +subroutine sb10yd(discfl,flag,lendat,rfrdat,ifrdat,omega,n,a,lda,b,c,d,tol,iwork,dwork,ldwork,zwork,lzwork,info) ! in SB10YD.f + integer intent(in) :: discfl + integer intent(in) :: flag + integer intent(in) :: lendat + double precision intent(in), dimension(lendat), depend(lendat) :: rfrdat + double precision intent(in), dimension(lendat), depend(lendat) :: ifrdat + double precision intent(in), dimension(lendat), depend(lendat) :: omega + integer intent(in,out,copy), :: n + double precision intent(out), dimension(lda,n) :: a + integer, intent(hide) :: lda=max(n,1) + double precision intent(out), dimension(n,1) :: b + double precision intent(out), dimension(1,n) :: c + double precision intent(out), dimension(1,1) :: d + double precision :: tol + integer intent(hide, cache), dimension(max(2,2*n+1)), depend(n) :: iwork + double precision intent(hide, cache), dimension(ldwork), depend(ldwork) :: dwork + integer intent(in) :: ldwork + complex*16 intent(hide, cache), dimension(lzwork), depend(lzwork) :: zwork + integer intent(in):: lzwork + integer intent(out):: info +end subroutine sb10yd subroutine sg03ad(dico,job,fact,trans,uplo,n,a,lda,e,lde,q,ldq,z,ldz,x,ldx,scale,sep,ferr,alphar,alphai,beta,iwork,dwork,ldwork,info) ! in SG03AD.f character :: dico character :: job diff --git a/slycot/synthesis.py b/slycot/synthesis.py index 0ffbbd93..2b9fbf9b 100644 --- a/slycot/synthesis.py +++ b/slycot/synthesis.py @@ -25,7 +25,6 @@ from . import _wrapper from .exceptions import raise_if_slycot_error, SlycotParameterError - def sb01bd(n,m,np,alpha,A,B,w,dico,tol=0.0,ldwork=None): """ A_z,w,nfp,nap,nup,F,Z = sb01bd(n,m,np,alpha,A,B,w,dico,[tol,ldwork]) @@ -59,9 +58,10 @@ def sb01bd(n,m,np,alpha,A,B,w,dico,tol=0.0,ldwork=None): := 'C': continuous-time system; := 'D': discrete-time system. tol : float, optional - The absolute tolerance level below which the elements of A or B are - considered zero (used for controllability tests). - If tol <= 0 the default value is used. + The absolute tolerance level below which the elements of A or B are + considered zero (used for controllability tests). + If tol <= 0 the default value is used. + Default is `0.0`. ldwork : int, optional The length of the cache array. The default value is max(1,5*m,5*n,2*n+4*m), for optimum performance it should be larger. @@ -129,7 +129,6 @@ def sb01bd(n,m,np,alpha,A,B,w,dico,tol=0.0,ldwork=None): Example ------- - >>> import numpy as np >>> import slycot >>> A = np.array([[0, 1, 0], [0, 0, 1], [-2, 1, 3]]) @@ -282,8 +281,6 @@ def sb02md(n,A,G,Q,dico,hinv='D',uplo='U',scal='N',sort='S',ldwork=None): Raises ------ - SlycotParameterError - :info = -i: the i-th argument had an illegal value; SlycotArithmeticError :info = 1: Matrix A is (numerically) singular in discrete- @@ -305,7 +302,6 @@ def sb02md(n,A,G,Q,dico,hinv='D',uplo='U',scal='N',sort='S',ldwork=None): Example ------- - >>> import numpy as np >>> import slycot >>> A = np.array([[0, 1], [0, 0]]) @@ -623,7 +619,6 @@ def sb02od(n,m,A,B,Q,R,dico,p=None,L=None,fact='N',uplo='U',sort='S',tol=0.0,ldw Example ------- - >>> import numpy as np >>> import slycot >>> A = np.array([[0, 1], [0, 0]]) @@ -708,7 +703,7 @@ def sb03md57(A, U=None, C=None, C : (n, n) array_like If job = 'X' or 'B', this array must contain the symmetric matrix C. If job = 'S', C is not referenced. - dico : {'C', 'D'} + dico : {'C', 'D'}, optional Specifies the equation from which X is to be determined as follows: := 'C': Equation (1), continuous-time case; := 'D': Equation (2), discrete-time case. @@ -906,7 +901,7 @@ def sb03od(n,m,A,Q,B,dico,fact='N',trans='N',ldwork=None): For optimum performance ldwork should sometimes be larger. Returns - _______ + ------- U : (n, n) ndarray The leading n-by-n part of this array contains the upper triangular Cholesky factor U of the solution @@ -1020,6 +1015,9 @@ def sb04md(n,m,A,B,C,ldwork=None): Matrix B C : (n, m) array_like Matrix C + ldwork : int, optional + The length of the array DWORK. + Default is None. Returns ------- @@ -1069,6 +1067,9 @@ def sb04qd(n,m,A,B,C,ldwork=None): Matrix B C : (n, m) array_like Matrix C + ldwork : int, optional + The length of the array DWORK. + Default is None. Returns ------- @@ -1700,6 +1701,119 @@ def sb10jd(n,m,np,A,B,C,D,E,ldwork=None): return A[:nsys,:nsys],B[:nsys,:m],C[:np, :nsys],D[:np, :m] +def sb10yd(discfl,flag,lendat,rfrdat,ifrdat,omega,n,tol,ldwork=None): + """ A,B,C,D = sb10yd(discfl,flag,lendat,rfrdat,ifrdat,omega,n,tol,[ldwork]) + + To fit a supplied frequency response data with a stable, minimum + phase SISO (single-input single-output) system represented by its + matrices A, B, C, D. It handles both discrete- and continuous-time + cases. + + :: + + dx/dt = A*x + B*u + y = C*x + D*u + + :: + + x[n+1] = A*x[n] + B*u[n] + y[n] = C*x[n] + D*u[n] . + + Parameters + ---------- + discfl : int + Indicatres the type of the system, as follows: + = 0: continuous-time system; + = 1: discrete-time system. + flag : int + If flag = 0, then the system zeros and poles are not constrained. + If flag = 1, then the system zeros and poles will have negative + real parts in the continuous-time case, or moduli less than 1 in + the discrete-time case. Consequently, flag must be equal to 1 in + mu-synthesis routines. + lendat : int + The length of the vectors rfrdat, ifrdat and omega. + length >= 2. + rfrdat : dimension (lendat), array_like + The real part of the frequency data to be fitted. + ifrdat : dimension (lendat), array_like + The imaginary part of the frequency data to be fitted. + omega : dimension (lendat), array_like + The frequencies corresponding to rfrdat and ifrdat. + These value must be nonnegative and monotonically increasing. + Additionally, for discrete-time systems they must be between 0 and PI. + n : int + On entry, the desired order of the system to be fitted. + n <= lendat-1. + tol : int + The length of the cache array. + ldwork : int, optional + With None it will be automatically calculated. + For details see SLICOT help. + + Returns + ------- + n : int + The order of the obtained system. The value of n + could only be modified if n > 0 and flag = 1. + A : (n, n) ndarray + The computed matrix A. + matrix A. + B : (n, 1) ndarray + The computed column vector B. + C : (1, n) ndarray + The computed row vector C. + D : (1, 1) ndarray + The computed scalar D. + + Raises + ------ + SlycotArithmeticError + :info == 0: successful exit; + :info < 0: if info = -i, the i-th argument had an illegal value + :info = 1: if the discret --> continous transformation cannot be made; + :info = 2: if the system poles cannot be found; + :info = 3: if the inverse system cannot be found, i.e., D is (close to) zero; + :info = 4: if the system zeros cannot be found; + :info = 5: if the state-space representation of the new transfer function T(s) cannot found; + :info = 6: if the continous --> discrete transformation cannot be made. + The iteration for computing singular value + decomposition did not converge. + """ + + hidden = ' (hidden by the wrapper)' + arg_list = ['discfl', 'flag', 'lendat', 'rfrdat', 'ifrdat', 'omega', + 'n', 'A', 'lda' + hidden, 'B', 'C', 'D', + 'TOL', 'IWORK' + hidden, 'DWORK' + hidden, 'LDWORK', + 'ZWORK' + hidden, 'LZWORK', 'INFO' + hidden] + + if ldwork is None: + lw1 = 2*lendat + 4*2048 + lw2 = lendat + 6*2048 + mn = min(2*lendat,2*n+1) + if n > 0: + lw3 = 2*lendat*(2*n+1) + max(2*lendat,2*n+1) + max(mn+6*n+4,2*mn+1) + elif n == 0: + lw3 = 4*lendat + 5 + if flag == 1: + lw4 = max(n*n+5*n,6*n+1+min(1,n)) + elif flag == 0: + lw4 = 0 + ldwork = max(2, lw1, lw2, lw3, lw4) + if n > 0: + lzwork = lendat*(2*n+3) + elif n == 0: + lzwork = lendat + + out = _wrapper.sb10yd( + discfl, flag, lendat, rfrdat, ifrdat, omega, + n, + tol,ldwork,lzwork) + + raise_if_slycot_error(out[-1], arg_list, sb10yd.__doc__) + + return out[:-1] + def sg03ad(dico,job,fact,trans,uplo,N,A,E,Q,Z,X,ldwork=None): """ A,E,Q,Z,X,scale,sep,ferr,alphar,alphai,beta = sg03ad(dico,job,fact,trans,uplo,N,A,E,Q,Z,X,[ldwork]) @@ -2094,6 +2208,7 @@ def sg02ad(dico,jobb,fact,uplo,jobl,scal,sort,acc,N,M,P,A,E,B,Q,R,L,ldwork=None, LDR >= MAX(1,M) if JOBB = 'B' and FACT = 'N' or 'C'; LDR >= MAX(1,P) if JOBB = 'B' and FACT = 'D' or 'B'; LDR >= 1 if JOBB = 'G'. + L : (n, M) array_like If JOBL = 'N' and JOBB = 'B', the leading N-by-M part of this array must contain the cross weighting matrix L. @@ -2105,6 +2220,7 @@ def sg02ad(dico,jobb,fact,uplo,jobl,scal,sort,acc,N,M,P,A,E,B,Q,R,L,ldwork=None, LDWORK >= MAX(7*(2*N+1)+16,16*N), if JOBB = 'G'; LDWORK >= MAX(7*(2*N+1)+16,16*N,2*N+M,3*M), if JOBB = 'B'. + For optimum performance LDWORK should be larger. Default: ``max(7*(2*n+1)+16,16*n)`` tol : float, optional @@ -2320,7 +2436,7 @@ def sg03bd(n,m,A,E,Q,Z,B,dico,fact='N',trans='N',ldwork=None): than one). Parameters - __________ + ---------- n : int The order of the matrix A. n >= 0. m : int @@ -2383,11 +2499,13 @@ def sg03bd(n,m,A,E,Q,Z,B,dico,fact='N',trans='N',ldwork=None): on entry or not: := 'N': Factorization is not supplied; := 'F': Factorization is supplied. + Default is 'N'. trans : {'N', 'T'}, optional Specifies whether the transposed equation is to be solved or not: := 'N': op(A) = A, op(E) = E; := 'T': op(A) = A**T, op(E) = E**T. + Default is 'N'. ldwork : int, optional The dimension of the array dwork:: @@ -2396,7 +2514,7 @@ def sg03bd(n,m,A,E,Q,Z,B,dico,fact='N',trans='N',ldwork=None): For good performance, ldwork should be larger. Returns - _______ + ------- U : (n, n) ndarray The leading n-by-b part of this array contains the Cholesky factor U of the solution matrix X of the @@ -2409,6 +2527,7 @@ def sg03bd(n,m,A,E,Q,Z,B,dico,fact='N',trans='N',ldwork=None): If INFO = 0, 3, 5, 6, or 7, then ((j), j=1,...,n, are the eigenvalues of the matrix pencil A - lambda * E. + Default is None. Raises ------ @@ -2535,7 +2654,7 @@ def sb10fd(n,m,np,ncon,nmeas,gamma,A,B,C,D,tol=0.0,ldwork=None): whose reciprocal condition numbers are less than tol are not allowed. If tol <= 0, then a default value equal to sqrt(eps) is used, where eps is the relative machine - precision. + precision. Default is `0.0`. ldwork : int, optional The dimension of the cache array:: @@ -2571,7 +2690,7 @@ def sb10fd(n,m,np,ncon,nmeas,gamma,A,B,C,D,tol=0.0,ldwork=None): if the default (None) value is used, the size for good performance is automatically used, when ldwork is set to zero, the minimum - cache size will be used. + cache size will be used. Default is None. Returns ------- diff --git a/slycot/tests/CMakeLists.txt b/slycot/tests/CMakeLists.txt deleted file mode 100644 index 2aafab3f..00000000 --- a/slycot/tests/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -set(PYSOURCE - - __init__.py - test_ab01.py - test_ab08n.py - test_ag08bd.py - test_examples.py - test_exceptions.py - test_mb.py - test_mc.py - test_sb.py - test_analysis.py - test_transform.py - test_sg02ad.py - test_sg03ad.py - test_tb05ad.py - test_td04ad.py - test_tg01ad.py - test_tg01fd.py ) - -install(FILES ${PYSOURCE} - DESTINATION slycot/tests) diff --git a/slycot/tests/test_ab01.py b/slycot/tests/test_ab01.py index ec544b13..01c1242b 100644 --- a/slycot/tests/test_ab01.py +++ b/slycot/tests/test_ab01.py @@ -6,7 +6,6 @@ from numpy import array from numpy.testing import assert_allclose, assert_equal - from scipy.linalg.lapack import dorgqr from slycot.analysis import ab01nd diff --git a/slycot/tests/test_ab04md.py b/slycot/tests/test_ab04md.py new file mode 100644 index 00000000..bafee439 --- /dev/null +++ b/slycot/tests/test_ab04md.py @@ -0,0 +1,66 @@ +import numpy as np +from numpy.testing import assert_allclose + +from slycot import analysis + + +class Test_ab04md: + """Test ab04md. + + Example data taken from + https://www.slicot.org/objects/software/shared/doc/AB04MD.html + """ + + Ac = np.array([[1.0, 0.5], + [0.5, 1.0]]) + Bc = np.array([[0.0, -1.0], + [1.0, 0.0]]) + Cc = np.array([[-1.0, 0.0], + [0.0, 1.0]]) + Dc = np.array([[1.0, 0.0], + [0.0, -1.0]]) + + Ad = np.array([[-1.0, -4.0], + [-4.0, -1.0]]) + Bd = np.array([[2.8284, 0.0], + [0.0, -2.8284]]) + Cd = np.array([[0.0, 2.8284], + [-2.8284, 0.0]]) + Dd = np.array([[-1.0, 0.0], + [0.0, -3.0]]) + + def test_ab04md_cont_disc_cont(self): + """Test transformation from continuous - to discrete - to continuous time. + """ + + n, m = self.Bc.shape + p = self.Cc.shape[0] + + Ad_t, Bd_t, Cd_t, Dd_t = analysis.ab04md( + 'C', n, m, p, self.Ac, self.Bc, self.Cc, self.Dc) + + Ac_t, Bc_t, Cc_t, Dc_t = analysis.ab04md( + 'D', n, m, p, Ad_t, Bd_t, Cd_t, Dd_t) + + assert_allclose(self.Ac, Ac_t) + assert_allclose(self.Bc, Bc_t) + assert_allclose(self.Cc, Cc_t) + assert_allclose(self.Dc, Dc_t) + + def test_ab04md_disc_cont_disc(self): + """Test transformation from discrete - to continuous - to discrete time. + """ + + n, m = self.Bc.shape + p = self.Cc.shape[0] + + Ac_t, Bc_t, Cc_t, Dc_t = analysis.ab04md( + 'D', n, m, p, self.Ad, self.Bd, self.Cd, self.Dd) + + Ad_t, Bd_t, Cd_t, Dd_t = analysis.ab04md( + 'C', n, m, p, Ac_t, Bc_t, Cc_t, Dc_t) + + assert_allclose(self.Ad, Ad_t) + assert_allclose(self.Bd, Bd_t) + assert_allclose(self.Cd, Cd_t) + assert_allclose(self.Dd, Dd_t) diff --git a/slycot/tests/test_ab08n.py b/slycot/tests/test_ab08n.py index 06fd0c0d..c777fc07 100644 --- a/slycot/tests/test_ab08n.py +++ b/slycot/tests/test_ab08n.py @@ -1,15 +1,14 @@ # =================================================== # ab08n* tests -import unittest -from slycot import analysis import numpy as np - +from numpy.testing import assert_allclose, assert_equal from scipy.linalg import eig -from numpy.testing import assert_equal, assert_allclose + +from slycot import analysis -class test_ab08nX(unittest.TestCase): +class Test_ab08nX: """ Test regular pencil construction ab08nX with input parameters according to example in documentation """ @@ -77,7 +76,3 @@ def test_ab08nz(self): Ac, Bc, Cc, Dc = [M.astype(np.complex128) for M in [self.A, self.B, self.C, self.D]] self.ab08nX(analysis.ab08nz, Ac, Bc, Cc, Dc) - - -if __name__ == "__main__": - unittest.main() diff --git a/slycot/tests/test_ab13bd.py b/slycot/tests/test_ab13bd.py new file mode 100644 index 00000000..dd2a735a --- /dev/null +++ b/slycot/tests/test_ab13bd.py @@ -0,0 +1,119 @@ +# =================================================== +# ab08n* tests + +import numpy as np +from numpy.testing import assert_allclose, assert_array_equal, assert_equal +from scipy import linalg, signal + +from slycot import analysis + + +class Test_ab13bd: + """ Test regular pencil construction ab08nX with input parameters + according to example in documentation """ + + A = np.array([[0.0, 1.0],[-0.5, -0.1]]) + B = np.array([[0.],[1.]]) + C = np.eye(2) + D = np.zeros((2,1)) + + Ad, Bd, Cd, Dd, dt = signal.cont2discrete((A, B, C, D), 0.1, method='zoh') + + def test_no_change_args_ccase(self): + """ ab13md must not change its arguments. continuous system case. + """ + + acopy = self.A.copy() + bcopy = self.B.copy() + ccopy = self.C.copy() + dcopy = self.D.copy() + + dico = 'C' + jobn = 'H' + + n, m = self.B.shape + p = self.C.shape[0] + + analysis.ab13bd(dico, jobn, n, m, p, self.A, self.B, self.C, self.D) + assert_array_equal(self.A, acopy) + assert_array_equal(self.B, bcopy) + assert_array_equal(self.C, ccopy) + assert_array_equal(self.D, dcopy) + + def test_no_change_args_dcase(self): + """ ab13md must not change its arguments. discrete system case. + """ + + acopy = self.Ad.copy() + bcopy = self.Bd.copy() + ccopy = self.Cd.copy() + dcopy = self.Dd.copy() + + dico = 'D' + jobn = 'H' + + n, m = self.Bd.shape + p = self.Cd.shape[0] + + analysis.ab13bd(dico, jobn, n, m, p, self.Ad, self.Bd, self.Cd, self.Dd) + assert_array_equal(self.Ad, acopy) + assert_array_equal(self.Bd, bcopy) + assert_array_equal(self.Cd, ccopy) + assert_array_equal(self.Dd, dcopy) + + def test_ab13bd_2norm_ccase(self): + """ Compare ab13md with scipy solution (Lyapunov Equation). + continuous system case. + """ + + A = self.A + B = self.B + C = self.C + D = self.D + + n, m = self.B.shape + p = self.C.shape[0] + + dico = 'C' + jobn = 'H' + + h2norm = analysis.ab13bd(dico, jobn, n, m, p, A, B, C, D) + + Lc = linalg.solve_continuous_lyapunov(A, -B@B.T) + h2norm_Lc = np.sqrt(np.trace(C@Lc@C.T)) + print(h2norm_Lc, h2norm) + assert_allclose(h2norm_Lc, h2norm, atol=1e-5) + + Lo = linalg.solve_continuous_lyapunov(A.T, -C.T@C) + h2norm_Lo = np.sqrt(np.trace(B.T@Lo@B)) + print(h2norm_Lo, h2norm) + assert_allclose(h2norm_Lo, h2norm, atol=1e-5) + + def test_ab13bd_2norm_dcase(self): + """ Compare ab13md with scipy solution (Lyapunov Equation). + discrete system case. + """ + + Ad = self.Ad + Bd = self.Bd + Cd = self.Cd + Dd = self.Dd + + n, m = Bd.shape + p = Cd.shape[0] + + dico = 'D' + jobn = 'H' + + h2norm = analysis.ab13bd(dico, jobn, n, m, p, Ad, Bd, Cd, Dd) + + Lc = linalg.solve_discrete_lyapunov(Ad, Bd@Bd.T) + h2norm_Lc = np.sqrt(np.trace(Cd@Lc@Cd.T + Dd@Dd.T)) + print(h2norm, h2norm_Lc) + assert_allclose(h2norm_Lc, h2norm, atol=1e-5) + + Lo = linalg.solve_discrete_lyapunov(Ad.T, Cd.T@Cd) + h2norm_Lo = np.sqrt(np.trace(Bd.T@Lo@Bd + Dd.T@Dd)) + print(h2norm, h2norm_Lo) + assert_allclose(h2norm_Lo, h2norm, atol=1e-5) + diff --git a/slycot/tests/test_ab13md.py b/slycot/tests/test_ab13md.py index df69a6b3..5a30f5ba 100644 --- a/slycot/tests/test_ab13md.py +++ b/slycot/tests/test_ab13md.py @@ -1,7 +1,6 @@ import numpy as np -from numpy.testing import assert_allclose, assert_array_less - import pytest +from numpy.testing import assert_allclose, assert_array_less from slycot import ab13md diff --git a/slycot/tests/test_ag08bd.py b/slycot/tests/test_ag08bd.py index 7c0706aa..97aa2b41 100644 --- a/slycot/tests/test_ag08bd.py +++ b/slycot/tests/test_ag08bd.py @@ -1,12 +1,10 @@ -# =================================================== -# ag08bd tests +"""Verify ag08bd with input parameters according to example in documentation.""" -import unittest -from slycot import analysis import numpy as np - from numpy.testing import assert_almost_equal, assert_equal +from slycot import analysis + # test1 input parameters test1_l = 9 @@ -47,77 +45,70 @@ [ 0, 0, 0]]) -class test_ag08bd(unittest.TestCase): - """Verify ag08bd with input parameters according to example in documentation.""" - - def test1_ag08bd(self): - """test [A-lambda*E] - - B,C,D must have correct dimensions according to l,n,m and p, but cannot - have zero length in any dimenstion. Then the wrapper will complain. - The length is then set to one. - """ - - Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=0,p=0,A=test1_A,E=test1_E,B=np.zeros((test1_l,1)),C=np.zeros((1,test1_n)),D=np.zeros((1,1)),equil=test1_equil, tol=test1_tol) - - assert_equal(Af, np.zeros((0,0))) - assert_equal(Ef, np.zeros((0,0))) - assert_equal(nrank, 9) - assert_equal(niz, 6) - assert_equal(infz, [0,3]) - assert_equal(kronr, []) - assert_equal(infe, [3,3,3]) - assert_equal(kronl, []) - - def test2_ag08bd(self): - """test [A-lambda*E;C] - - B,D must have correct dimensions as before - """ - - Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=0,p=test1_p,A=test1_A,E=test1_E,B=np.zeros((test1_l,1)),C=test1_C,D=np.zeros((test1_p,1)),equil=test1_equil, tol=test1_tol) - - assert_equal(Af, np.zeros((0,0))) - assert_equal(Ef, np.zeros((0,0))) - assert_equal(nrank, 9) - assert_equal(niz, 4) - assert_equal(infz, [0,2]) - assert_equal(kronr, []) - assert_equal(infe, [1,3,3]) - assert_equal(kronl, [0,1,1]) - - def test3_ag08bd(self): - """test [A-lambda*E,B] - - C,D must have correct dimensions as before - """ - - Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=test1_m,p=0,A=test1_A,E=test1_E,B=test1_B,C=np.zeros((1,test1_n)),D=np.zeros((1,test1_m)),equil=test1_equil, tol=test1_tol) - - assert_equal(Af, np.zeros((0,0))) - assert_equal(Ef, np.zeros((0,0))) - assert_equal(nrank, 9) - assert_equal(niz, 0) - assert_equal(infz, []) - assert_equal(kronr, [2,2,2]) - assert_equal(infe, [1,1,1]) - assert_equal(kronl, []) - - def test4_ag08bd(self): - """test [A-lambda*E,B;C,D]""" - - Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,D=test1_D,equil=test1_equil, tol=test1_tol) - - # Af-lambda*Ef==0. => lambda==1. => Finite Smith zero of S(lambda) == 1. - assert Af.shape == (1, 1) - assert_almost_equal(Af, Ef) - assert_equal(nrank, 11) - assert_equal(niz, 2) - assert_equal(infz, [0,1]) - assert_equal(kronr, [2]) - assert_equal(infe, [1,1,1,1,3]) - assert_equal(kronl, [1]) - - -if __name__ == "__main__": - unittest.main() +def test1_ag08bd(): + """test [A-lambda*E] + + B,C,D must have correct dimensions according to l,n,m and p, but cannot + have zero length in any dimenstion. Then the wrapper will complain. + The length is then set to one. + """ + + Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=0,p=0,A=test1_A,E=test1_E,B=np.zeros((test1_l,1)),C=np.zeros((1,test1_n)),D=np.zeros((1,1)),equil=test1_equil, tol=test1_tol) + + assert_equal(Af, np.zeros((0,0))) + assert_equal(Ef, np.zeros((0,0))) + assert_equal(nrank, 9) + assert_equal(niz, 6) + assert_equal(infz, [0,3]) + assert_equal(kronr, []) + assert_equal(infe, [3,3,3]) + assert_equal(kronl, []) + +def test2_ag08bd(): + """test [A-lambda*E;C] + + B,D must have correct dimensions as before + """ + + Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=0,p=test1_p,A=test1_A,E=test1_E,B=np.zeros((test1_l,1)),C=test1_C,D=np.zeros((test1_p,1)),equil=test1_equil, tol=test1_tol) + + assert_equal(Af, np.zeros((0,0))) + assert_equal(Ef, np.zeros((0,0))) + assert_equal(nrank, 9) + assert_equal(niz, 4) + assert_equal(infz, [0,2]) + assert_equal(kronr, []) + assert_equal(infe, [1,3,3]) + assert_equal(kronl, [0,1,1]) + +def test3_ag08bd(): + """test [A-lambda*E,B] + + C,D must have correct dimensions as before + """ + + Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=test1_m,p=0,A=test1_A,E=test1_E,B=test1_B,C=np.zeros((1,test1_n)),D=np.zeros((1,test1_m)),equil=test1_equil, tol=test1_tol) + + assert_equal(Af, np.zeros((0,0))) + assert_equal(Ef, np.zeros((0,0))) + assert_equal(nrank, 9) + assert_equal(niz, 0) + assert_equal(infz, []) + assert_equal(kronr, [2,2,2]) + assert_equal(infe, [1,1,1]) + assert_equal(kronl, []) + +def test4_ag08bd(): + """test [A-lambda*E,B;C,D]""" + + Af,Ef,nrank,niz,infz,kronr,infe,kronl = analysis.ag08bd(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,D=test1_D,equil=test1_equil, tol=test1_tol) + + # Af-lambda*Ef==0. => lambda==1. => Finite Smith zero of S(lambda) == 1. + assert Af.shape == (1, 1) + assert_almost_equal(Af, Ef) + assert_equal(nrank, 11) + assert_equal(niz, 2) + assert_equal(infz, [0,1]) + assert_equal(kronr, [2]) + assert_equal(infe, [1,1,1,1,3]) + assert_equal(kronl, [1]) diff --git a/slycot/tests/test_analysis.py b/slycot/tests/test_analysis.py index 701eab4a..0b899580 100644 --- a/slycot/tests/test_analysis.py +++ b/slycot/tests/test_analysis.py @@ -3,10 +3,12 @@ # repagh , Aug 2019 import sys -import unittest -import pytest - -from slycot import math -from slycot import mb03rd, mb03vd, mb03vy, mb03wd, mb05md, mb05nd -from slycot.exceptions import SlycotResultWarning, SlycotArithmeticError -from .test_exceptions import assert_docstring_parse import numpy as np +import pytest +from numpy.testing import assert_allclose from scipy.linalg import schur -from numpy.testing import assert_allclose +from slycot import math, mb02ed, mb03rd, mb03vd, mb03vy, mb03wd, mb05md, mb05nd +from slycot.exceptions import SlycotArithmeticError, SlycotResultWarning, SlycotParameterError + +from .test_exceptions import assert_docstring_parse -class test_mb(unittest.TestCase): - - def test_mb03rd(self): - """ Test for Schur form reduction. - - RvP, 31 Jul 2019""" - - test1_A = np.array([ - [ 1., -1., 1., 2., 3., 1., 2., 3.], - [ 1., 1., 3., 4., 2., 3., 4., 2.], - [ 0., 0., 1., -1., 1., 5., 4., 1.], - [ 0., 0., 0., 1., -1., 3., 1., 2.], - [ 0., 0., 0., 1., 1., 2., 3., -1.], - [ 0., 0., 0., 0., 0., 1., 5., 1.], - [ 0., 0., 0., 0., 0., 0., 0.99999999, -0.99999999 ], - [ 0., 0., 0., 0., 0., 0., 0.99999999, 0.99999999 ] - ]) - test1_n = test1_A.shape[0] - - test1_Ar = np.array([ - [ 1.0000, -1.0000, -1.2247, -0.7071, -3.4186, 1.4577, 0.0000, 0.0000 ], - [ 1.0000, 1.0000, 0.0000, 1.4142, -5.1390, 3.1637, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 1.0000, -1.7321, -0.0016, 2.0701, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 0.5774, 1.0000, 0.7516, 1.1379, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, -5.8606, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 0.1706, 1.0000, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, -0.8850 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000 ], - ]) - - test1_Xr = np.array([ - [ 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.9045, 0.1957 ], - [ 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, -0.3015, 0.9755 ], - [ 0.0000, 0.0000, 0.8165, 0.0000, -0.5768, -0.0156, -0.3015, 0.0148 ], - [ 0.0000, 0.0000, -0.4082, 0.7071, -0.5768, -0.0156, 0.0000, -0.0534 ], - [ 0.0000, 0.0000, -0.4082, -0.7071, -0.5768, -0.0156, 0.0000, 0.0801 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, -0.0276, 0.9805, 0.0000, 0.0267 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0332, -0.0066, 0.0000, 0.0000 ], - [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0011, 0.1948, 0.0000, 0.0000 ] - ]) - - test1_W = np.array([1+1j, 1-1j, - 1+1j, 1-1j, - 0.99999+0.99999j, 0.99999-0.99999j, - 1., 1.]) - - test1_pmax = 1e3 - test1_tol = 0.01 - # create schur form with scipy - A, X = schur(test1_A) - Ah, Xh = np.copy(A), np.copy(X) - # on this basis, get the transform +def test_mb02ed_ex(): + """Test MB02ED using the example given in the MB02ED SLICOT Documentation""" + n = 3 + k = 3 + nrhs = 2 + TYPET = "C" + T = np.array( + [ + [3.0000, 1.0000, 0.2000], + [1.0000, 4.0000, 0.4000], + [0.2000, 0.4000, 5.0000], + [0.1000, 0.1000, 0.2000], + [0.2000, 0.0400, 0.0300], + [0.0500, 0.2000, 0.1000], + [0.1000, 0.0300, 0.1000], + [0.0400, 0.0200, 0.2000], + [0.0100, 0.0300, 0.0200], + ] + ) + B = np.array( + [ + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + ] + ) + X = np.array( + [ + [0.2408, 0.4816], + [0.1558, 0.3116], + [0.1534, 0.3068], + [0.2302, 0.4603], + [0.1467, 0.2934], + [0.1537, 0.3075], + [0.2349, 0.4698], + [0.1498, 0.2995], + [0.1653, 0.3307], + ] + ) + + result,_ = mb02ed(T=T, B=B, n=n, k=k, typet=TYPET, nrhs=nrhs) + np.testing.assert_almost_equal(result, X, decimal=4) + +def test_mb02ed_parameter_errors(): + """Test for errors in the input parameters of MB02ED""" + n = 3 + k = 3 + nrhs = 2 + TYPET = "C" + T = np.array( + [ + [3.0000, 1.0000, 0.2000], + [1.0000, 4.0000, 0.4000], + [0.2000, 0.4000, 5.0000], + [0.1000, 0.1000, 0.2000], + [0.2000, 0.0400, 0.0300], + [0.0500, 0.2000, 0.1000], + [0.1000, 0.0300, 0.1000], + [0.0400, 0.0200, 0.2000], + [0.0100, 0.0300, 0.0200], + ] + ) + B = np.array( + [ + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + ] + ) + X = np.array( + [ + [0.2408, 0.4816], + [0.1558, 0.3116], + [0.1534, 0.3068], + [0.2302, 0.4603], + [0.1467, 0.2934], + [0.1537, 0.3075], + [0.2349, 0.4698], + [0.1498, 0.2995], + [0.1653, 0.3307], + ] + ) + + # Test for wrong parameter typet + with pytest.raises(expected_exception=SlycotParameterError, match='typet must be either "R" or "C"') as cm: + mb02ed(T=T, B=B, n=n, k=k, typet='U', nrhs=nrhs) + assert cm.value.info == -1 + #Test for negative number of columns + with pytest.raises(expected_exception=SlycotParameterError, match="k must be >= 0") as cm: + mb02ed(T=T, B=B, n=n, k=-1, typet=TYPET, nrhs=nrhs) + assert cm.value.info == -2 + #Test for negative number of blocks + with pytest.raises(expected_exception=SlycotParameterError, match="n must be >= 0") as cm: + mb02ed(T=T, B=B, n=-1, k=k, typet=TYPET, nrhs=nrhs) + assert cm.value.info == -3 + #Test for negative number of right hand sides + with pytest.raises(expected_exception=SlycotParameterError, match="nrhs must be >= 0") as cm: + mb02ed(T=T, B=B, n=n, k=k, typet=TYPET, nrhs=-1) + assert cm.value.info == -4 + + +def test_mb02ed_matrix_error(): + """Test for a negative definite input matrix in MB02ED""" + n = 3 + k = 3 + nrhs = 2 + TYPET = "C" + T = np.array( + [ + [3.0000, 1.0000, 0.2000], + [1.0000, 4.0000, 0.4000], + [0.2000, 0.4000, 5.0000], + [0.1000, 0.1000, 0.2000], + [0.2000, 0.0400, 0.0300], + [0.0500, 0.2000, 0.1000], + [0.1000, 0.0300, 0.1000], + [0.0400, 0.0200, 0.2000], + [0.0100, 0.0300, 0.0200], + ] + ) + # Create a negative definite matrix + T = -1 * T + B = np.array( + [ + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + [1.0000, 2.0000], + ] + ) + X = np.array( + [ + [0.2408, 0.4816], + [0.1558, 0.3116], + [0.1534, 0.3068], + [0.2302, 0.4603], + [0.1467, 0.2934], + [0.1537, 0.3075], + [0.2349, 0.4698], + [0.1498, 0.2995], + [0.1653, 0.3307], + ] + ) + + with pytest.raises(SlycotArithmeticError, + match = "The reduction algorithm failed. " + "The Toeplitz matrix associated\nwith T " + r"is not numerically positive definite.") as cm: + mb02ed(T=T, B=B, n=n, k=k, typet=TYPET, nrhs=nrhs) + assert cm.value.info == 1 + + +def test_mb03rd(): + """ Test for Schur form reduction. + + RvP, 31 Jul 2019""" + + test1_A = np.array([ + [ 1., -1., 1., 2., 3., 1., 2., 3.], + [ 1., 1., 3., 4., 2., 3., 4., 2.], + [ 0., 0., 1., -1., 1., 5., 4., 1.], + [ 0., 0., 0., 1., -1., 3., 1., 2.], + [ 0., 0., 0., 1., 1., 2., 3., -1.], + [ 0., 0., 0., 0., 0., 1., 5., 1.], + [ 0., 0., 0., 0., 0., 0., 0.99999999, -0.99999999 ], + [ 0., 0., 0., 0., 0., 0., 0.99999999, 0.99999999 ] + ]) + test1_n = test1_A.shape[0] + + test1_Ar = np.array([ + [ 1.0000, -1.0000, -1.2247, -0.7071, -3.4186, 1.4577, 0.0000, 0.0000 ], + [ 1.0000, 1.0000, 0.0000, 1.4142, -5.1390, 3.1637, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 1.0000, -1.7321, -0.0016, 2.0701, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 0.5774, 1.0000, 0.7516, 1.1379, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, -5.8606, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 0.1706, 1.0000, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, -0.8850 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000 ], + ]) + + test1_Xr = np.array([ + [ 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.9045, 0.1957 ], + [ 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, -0.3015, 0.9755 ], + [ 0.0000, 0.0000, 0.8165, 0.0000, -0.5768, -0.0156, -0.3015, 0.0148 ], + [ 0.0000, 0.0000, -0.4082, 0.7071, -0.5768, -0.0156, 0.0000, -0.0534 ], + [ 0.0000, 0.0000, -0.4082, -0.7071, -0.5768, -0.0156, 0.0000, 0.0801 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, -0.0276, 0.9805, 0.0000, 0.0267 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0332, -0.0066, 0.0000, 0.0000 ], + [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0011, 0.1948, 0.0000, 0.0000 ] + ]) + + test1_W = np.array([1+1j, 1-1j, + 1+1j, 1-1j, + 0.99999+0.99999j, 0.99999-0.99999j, + 1., 1.]) + + test1_pmax = 1e3 + test1_tol = 0.01 + # create schur form with scipy + A, X = schur(test1_A) + Ah, Xh = np.copy(A), np.copy(X) + # on this basis, get the transform + Ar, Xr, blsize, W = mb03rd( + test1_n, A, X, 'U', 'S', test1_pmax, test1_tol) + # ensure X and A are unchanged + assert_allclose(A, Ah) + assert_allclose(X, Xh) + # compare to test case results + assert_allclose(Ar, test1_Ar, atol=0.0001) + assert_allclose(Xr, test1_Xr, atol=0.0001) + assert_allclose(W, test1_W, atol=0.0001) + + # Test that the non sorting options do not throw errors and that Xr is + # returned as None for jobx='N' + for sort in ['N', 'C', 'B']: Ar, Xr, blsize, W = mb03rd( - test1_n, A, X, 'U', 'S', test1_pmax, test1_tol) - # ensure X and A are unchanged - assert_allclose(A, Ah) - assert_allclose(X, Xh) - # compare to test case results - assert_allclose(Ar, test1_Ar, atol=0.0001) - assert_allclose(Xr, test1_Xr, atol=0.0001) - assert_allclose(W, test1_W, atol=0.0001) - - # Test that the non sorting options do not throw errors and that Xr is - # returned as None for jobx='N' - for sort in ['N', 'C', 'B']: - Ar, Xr, blsize, W = mb03rd( - test1_n, A, X, 'N', sort, test1_pmax, test1_tol) - assert Xr is None - - def test_mb03rd_default(self): - # regression: mb03rd was failing with no third arg (X) supplied - A = np.array([[ 6, -1, -7, -2, 2], - [-3, 4, 2, -7, 6], - [-6, -9, -3, -1, 10], - [-2, -4, 1, 5, 7], - [-7, -5, -6, 6, 7]]) - - Aschur, Tschur = schur(A) - - X = Tschur.copy() - - Ar, Xr, blsize, W = mb03rd(Aschur.shape[0], Aschur, X, 'U', 'N', pmax=1.0, tol=0.0) - - Ar2, Xr2, blsize2, W2 = mb03rd(Aschur.shape[0], Aschur) - - assert_allclose(Ar, Ar2) - assert_allclose(Xr, Tschur.dot(Xr2)) - - def test_mb03vd_mb03vy_ex(self): - """Test MB03VD and MB03VY - with the example given in the MB03VD SLICOT documentation""" - - n = 4 - p = 2 - ilo = 1 - ihi = 4 - A = np.zeros((n, n, p)) - A[:, :, 0] = [[1.5, -.7, 3.5, -.7], - [1. , 0. , 2. , 3. ], - [1.5, -.7, 2.5, -.3], - [1. , 0. , 2. , 1. ]] - A[:, :, 1] = [[1.5, -.7, 3.5, -.7], - [1. , 0. , 2. , 3. ], - [1.5, -.7, 2.5, -.3], - [1. , 0. , 2. , 1. ]] - - H_ref = np.zeros((n, n, p)) - H_ref[:, :, 0] = [[-2.3926, 2.7042, -0.9598, -1.2335], - [ 4.1417, -1.7046, 1.3001, -1.3120], - [ 0.0000, -1.6247, -0.2534, 1.6453], - [ 0.0000, 0.0000, -0.0169, -0.4451]] - - H_ref[:, :, 1] = [[-2.5495, 2.3402, 4.7021, 0.2329], - [ 0.0000, 1.9725, -0.2483, -2.3493], - [ 0.0000, 0.0000, -0.6290, -0.5975], - [ 0.0000, 0.0000, 0.0000, -0.4426]] - - Q_ref = np.zeros((n, n, p)) - Q_ref[:, :, 0] = [[ 1.0000, 0.0000, 0.0000, 0.0000], - [ 0.0000, -0.7103, 0.5504, -0.4388], - [ 0.0000, -0.4735, -0.8349, -0.2807], - [ 0.0000, -0.5209, 0.0084, 0.8536]] - - Q_ref[:, :, 1] = [[-0.5883, 0.2947, 0.7528, -0.0145], - [-0.3922, -0.8070, 0.0009, -0.4415], - [-0.5883, 0.4292, -0.6329, -0.2630], - [-0.3922, -0.2788, -0.1809, 0.8577]] - - HQ, Tau = mb03vd(n, ilo, ihi, A) - - H = np.zeros_like(HQ) - Q = np.zeros_like(HQ) - - for k in range(p): - Q[:, :, k] = np.tril(HQ[:, :, k]) - if k == 0: - H[:, :, k] = np.triu(HQ[:n, :n, k], -1) - elif k > 0: - H[:, :, k] = np.triu(HQ[:n, :n, k]) - assert_allclose(H[:, :, k], H_ref[:, :, k], atol=1e-4) - - Qr = mb03vy(n, ilo, ihi, Q, Tau) - - for k in range(p): - assert_allclose(Qr[:, :, k], Q_ref[:, :, k], atol=1e-4) - - # Computer Error: too machine dependent to test to reference value - # SSQ_ref = 2.93760e-15 - # SSQ = 0. - # for k in range(p): - # kp1 = k+1 - # if kp1 > p-1: - # kp1 = 0 - # P = Qr[:, :, k].T.dot(A[: ,: ,k]).dot(Qr[: ,: ,kp1]) - H[: ,: ,k] - # SSQ = np.sqrt(SSQ**2 + np.linalg.norm(P,'fro')**2) - - def test_mb03wd_ex(self): - """Test MB03WD with the example given in the SLICOT documentation""" - - n = 4 - p = 2 - ilo = 1 - ihi = 4 - iloz = 1 - ihiz = 4 - job = 'S' - compz = 'V' - A = np.zeros((n, n, p)) - A[:, :, 0] = [[1.5, -.7, 3.5, -.7], - [1. , 0. , 2. , 3. ], - [1.5, -.7, 2.5, -.3], - [1. , 0. , 2. , 1. ]] - A[:, :, 1] = [[1.5, -.7, 3.5, -.7], - [1. , 0. , 2. , 3. ], - [1.5, -.7, 2.5, -.3], - [1. , 0. , 2. , 1. ]] - - W_ref = np.array([6.449861+7.817717J, - 6.449861-7.817717J, - 0.091315+0.000000J, - 0.208964+0.000000J]) - - T_ref = np.zeros((n, n, p)) - T_ref[:, :, 0] = [[ 2.2112, 4.3718, -2.3362, 0.8907], - [ -0.9179, 2.7688, -0.6570, -2.2426], - [ 0.0000, 0.0000, 0.3022, 0.1932], - [ 0.0000, 0.0000, 0.0000, -0.4571]] - - T_ref[:, :, 1] = [[ 2.9169, 3.4539, 2.2016, 1.2367], - [ 0.0000, 3.4745, 1.0209, -2.0720], - [ 0.0000, 0.0000, 0.3022, -0.1932], - [ 0.0000, 0.0000, 0.0000, -0.4571]] - - Z_ref = np.zeros((n, n, p)) - Z_ref[:, :, 0] = [[ 0.3493, 0.6751, -0.6490, 0.0327], - [ 0.7483, -0.4863, -0.1249, -0.4336], - [ 0.2939, 0.5504, 0.7148, -0.3158], - [ 0.4813, -0.0700, 0.2286, 0.8433]] - - - Z_ref[:, :, 1] = [[ 0.2372, 0.7221, 0.6490, 0.0327], - [ 0.8163, -0.3608, 0.1249, -0.4336], - [ 0.2025, 0.5902, -0.7148, -0.3158], - [ 0.4863, 0.0076, -0.2286, 0.8433]] - - HQ, Tau = mb03vd(n, ilo, ihi, A) - Q = mb03vy(n, ilo, ihi, HQ, Tau) - T, Z, W = mb03wd(job, compz, n, ilo, ihi, iloz, ihiz, HQ, Q) - - # TODO (?) - # isolate eigenvalues with math.mb03wx - - assert_allclose(W, W_ref, atol=1e-5) - assert_allclose(T, T_ref, atol=1e-4) - assert_allclose(Z, Z_ref, atol=1e-4) - - # Computer Error: too machine dependent to test to reference value - # SSQ_ref = 7.18432D-15 - # SSQ = 0. - # for k in range(p): - # kp1 = k+1 - # if kp1 > p-1: - # kp1 = 0 - # P = Zrr[:, :, k].T.dot(A[: ,: ,k]).dot(Zrr[: ,: ,kp1]) - Hrr[: ,: ,k] - # SSQ = np.sqrt(SSQ**2 + np.linalg.norm(P,'fro')**2) - - - def test_mb05md(self): - """ test_mb05md: verify Matrix exponential with slicot doc example - - data from http://slicot.org/objects/software/shared/doc/MB05MD.html - """ - A = np.array([[ 0.5, 0., 2.3, -2.6], - [ 0., 0.5, -1.4, -0.7], - [ 2.3, -1.4, 0.5, 0.0], - [-2.6, -0.7, 0.0, 0.5]]) - delta = 1.0 - Ar_ref = np.array([[ 26.8551, -3.2824, 18.7409, -19.4430], - [ -3.2824, 4.3474, -5.1848, 0.2700], - [ 18.7409, -5.1848, 15.6012, -11.7228], - [ -19.4430, 0.2700, -11.7228, 15.6012]]) - Vr_ref = np.array([[-0.7, 0.7, 0.1, -0.1], - [ 0.1, -0.1, 0.7, -0.7], - [ 0.5, 0.5, 0.5, 0.5], - [-0.5, -0.5, 0.5, 0.5]]) - Yr_ref = np.array([[ -0.0349, 0.0050, 0.0249, -0.0249], - [ 38.2187, -5.4598, 27.2991, -27.2991], - [ 0.0368, 0.2575, 0.1839, 0.1839], - [ -0.7389, -5.1723, 3.6945, 3.6945]]) - VAL_ref = np.array([-3., 4., -1., 2.]) + test1_n, A, X, 'N', sort, test1_pmax, test1_tol) + assert Xr is None + +def test_mb03rd_default(): + # regression: mb03rd was failing with no third arg (X) supplied + A = np.array([[ 6, -1, -7, -2, 2], + [-3, 4, 2, -7, 6], + [-6, -9, -3, -1, 10], + [-2, -4, 1, 5, 7], + [-7, -5, -6, 6, 7]]) + + Aschur, Tschur = schur(A) + + X = Tschur.copy() + + Ar, Xr, blsize, W = mb03rd(Aschur.shape[0], Aschur, X, 'U', 'N', pmax=1.0, tol=0.0) + + Ar2, Xr2, blsize2, W2 = mb03rd(Aschur.shape[0], Aschur) + + assert_allclose(Ar, Ar2) + assert_allclose(Xr, Tschur.dot(Xr2)) + +def test_mb03vd_mb03vy_ex(): + """Test MB03VD and MB03VY + with the example given in the MB03VD SLICOT documentation""" + + n = 4 + p = 2 + ilo = 1 + ihi = 4 + A = np.zeros((n, n, p)) + A[:, :, 0] = [[1.5, -.7, 3.5, -.7], + [1. , 0. , 2. , 3. ], + [1.5, -.7, 2.5, -.3], + [1. , 0. , 2. , 1. ]] + A[:, :, 1] = [[1.5, -.7, 3.5, -.7], + [1. , 0. , 2. , 3. ], + [1.5, -.7, 2.5, -.3], + [1. , 0. , 2. , 1. ]] + + H_ref = np.zeros((n, n, p)) + H_ref[:, :, 0] = [[-2.3926, 2.7042, -0.9598, -1.2335], + [ 4.1417, -1.7046, 1.3001, -1.3120], + [ 0.0000, -1.6247, -0.2534, 1.6453], + [ 0.0000, 0.0000, -0.0169, -0.4451]] + + H_ref[:, :, 1] = [[-2.5495, 2.3402, 4.7021, 0.2329], + [ 0.0000, 1.9725, -0.2483, -2.3493], + [ 0.0000, 0.0000, -0.6290, -0.5975], + [ 0.0000, 0.0000, 0.0000, -0.4426]] + + Q_ref = np.zeros((n, n, p)) + Q_ref[:, :, 0] = [[ 1.0000, 0.0000, 0.0000, 0.0000], + [ 0.0000, -0.7103, 0.5504, -0.4388], + [ 0.0000, -0.4735, -0.8349, -0.2807], + [ 0.0000, -0.5209, 0.0084, 0.8536]] + + Q_ref[:, :, 1] = [[-0.5883, 0.2947, 0.7528, -0.0145], + [-0.3922, -0.8070, 0.0009, -0.4415], + [-0.5883, 0.4292, -0.6329, -0.2630], + [-0.3922, -0.2788, -0.1809, 0.8577]] + + HQ, Tau = mb03vd(n, ilo, ihi, A) + + H = np.zeros_like(HQ) + Q = np.zeros_like(HQ) + + for k in range(p): + Q[:, :, k] = np.tril(HQ[:, :, k]) + if k == 0: + H[:, :, k] = np.triu(HQ[:n, :n, k], -1) + elif k > 0: + H[:, :, k] = np.triu(HQ[:n, :n, k]) + assert_allclose(H[:, :, k], H_ref[:, :, k], atol=1e-4) + + Qr = mb03vy(n, ilo, ihi, Q, Tau) + + for k in range(p): + assert_allclose(Qr[:, :, k], Q_ref[:, :, k], atol=1e-4) + + # Computer Error: too machine dependent to test to reference value + # SSQ_ref = 2.93760e-15 + # SSQ = 0. + # for k in range(p): + # kp1 = k+1 + # if kp1 > p-1: + # kp1 = 0 + # P = Qr[:, :, k].T.dot(A[: ,: ,k]).dot(Qr[: ,: ,kp1]) - H[: ,: ,k] + # SSQ = np.sqrt(SSQ**2 + np.linalg.norm(P,'fro')**2) + +def test_mb03wd_ex(): + """Test MB03WD with the example given in the SLICOT documentation""" + + n = 4 + p = 2 + ilo = 1 + ihi = 4 + iloz = 1 + ihiz = 4 + job = 'S' + compz = 'V' + A = np.zeros((n, n, p)) + A[:, :, 0] = [[1.5, -.7, 3.5, -.7], + [1. , 0. , 2. , 3. ], + [1.5, -.7, 2.5, -.3], + [1. , 0. , 2. , 1. ]] + A[:, :, 1] = [[1.5, -.7, 3.5, -.7], + [1. , 0. , 2. , 3. ], + [1.5, -.7, 2.5, -.3], + [1. , 0. , 2. , 1. ]] + + W_ref = np.array([6.449861+7.817717J, + 6.449861-7.817717J, + 0.091315+0.000000J, + 0.208964+0.000000J]) + + T_ref = np.zeros((n, n, p)) + T_ref[:, :, 0] = [[ 2.2112, 4.3718, -2.3362, 0.8907], + [ -0.9179, 2.7688, -0.6570, -2.2426], + [ 0.0000, 0.0000, 0.3022, 0.1932], + [ 0.0000, 0.0000, 0.0000, -0.4571]] + + T_ref[:, :, 1] = [[ 2.9169, 3.4539, 2.2016, 1.2367], + [ 0.0000, 3.4745, 1.0209, -2.0720], + [ 0.0000, 0.0000, 0.3022, -0.1932], + [ 0.0000, 0.0000, 0.0000, -0.4571]] + + Z_ref = np.zeros((n, n, p)) + Z_ref[:, :, 0] = [[ 0.3493, 0.6751, -0.6490, 0.0327], + [ 0.7483, -0.4863, -0.1249, -0.4336], + [ 0.2939, 0.5504, 0.7148, -0.3158], + [ 0.4813, -0.0700, 0.2286, 0.8433]] + + + Z_ref[:, :, 1] = [[ 0.2372, 0.7221, 0.6490, 0.0327], + [ 0.8163, -0.3608, 0.1249, -0.4336], + [ 0.2025, 0.5902, -0.7148, -0.3158], + [ 0.4863, 0.0076, -0.2286, 0.8433]] + + HQ, Tau = mb03vd(n, ilo, ihi, A) + Q = mb03vy(n, ilo, ihi, HQ, Tau) + T, Z, W = mb03wd(job, compz, n, ilo, ihi, iloz, ihiz, HQ, Q) + + # TODO (?) + # isolate eigenvalues with math.mb03wx + + assert_allclose(W, W_ref, atol=1e-5) + assert_allclose(T, T_ref, atol=1e-4) + assert_allclose(Z, Z_ref, atol=1e-4) + + # Computer Error: too machine dependent to test to reference value + # SSQ_ref = 7.18432D-15 + # SSQ = 0. + # for k in range(p): + # kp1 = k+1 + # if kp1 > p-1: + # kp1 = 0 + # P = Zrr[:, :, k].T.dot(A[: ,: ,k]).dot(Zrr[: ,: ,kp1]) - Hrr[: ,: ,k] + # SSQ = np.sqrt(SSQ**2 + np.linalg.norm(P,'fro')**2) + + +def test_mb05md(): + """ test_mb05md: verify Matrix exponential with slicot doc example + + data from http://slicot.org/objects/software/shared/doc/MB05MD.html + """ + A = np.array([[ 0.5, 0., 2.3, -2.6], + [ 0., 0.5, -1.4, -0.7], + [ 2.3, -1.4, 0.5, 0.0], + [-2.6, -0.7, 0.0, 0.5]]) + delta = 1.0 + Ar_ref = np.array([[ 26.8551, -3.2824, 18.7409, -19.4430], + [ -3.2824, 4.3474, -5.1848, 0.2700], + [ 18.7409, -5.1848, 15.6012, -11.7228], + [ -19.4430, 0.2700, -11.7228, 15.6012]]) + Vr_ref = np.array([[-0.7, 0.7, 0.1, -0.1], + [ 0.1, -0.1, 0.7, -0.7], + [ 0.5, 0.5, 0.5, 0.5], + [-0.5, -0.5, 0.5, 0.5]]) + Yr_ref = np.array([[ -0.0349, 0.0050, 0.0249, -0.0249], + [ 38.2187, -5.4598, 27.2991, -27.2991], + [ 0.0368, 0.2575, 0.1839, 0.1839], + [ -0.7389, -5.1723, 3.6945, 3.6945]]) + VAL_ref = np.array([-3., 4., -1., 2.]) + (Ar, Vr, Yr, VAL) = mb05md(A, delta) + + assert_allclose(Ar, Ar_ref, atol=0.0001) + + # Order of eigenvalues is not guaranteed, so we check them one by one. + for i, e in enumerate(VAL): + erow = np.ones(VAL.shape)*e + i_ref = np.isclose(erow, VAL_ref) + assert any(i_ref), f"eigenvalue {e} not expected" + # Eigenvectors can have different scaling. + vr_ref = Vr_ref[:, i_ref]*Vr[0, i]/Vr_ref[0, i_ref][0] + assert_allclose(Vr[:, (i,)], vr_ref, atol=0.0001) + + assert_allclose(np.dot(Vr, Yr), np.dot(Vr_ref, Yr_ref), atol=0.0001) + +def test_mb05md_warning(): + """Check that the correct warning is raised from docstring""" + A = np.diag([3., 3., 3., 3.]) + np.diag([1., 1., 1.], k=1) + delta = 0.1 + + with pytest.warns(SlycotResultWarning, + match="\n" + "Matrix A is defective, possibly " + "due to rounding errors.") as record: (Ar, Vr, Yr, VAL) = mb05md(A, delta) - - assert_allclose(Ar, Ar_ref, atol=0.0001) - - # Order of eigenvalues is not guaranteed, so we check them one by one. - for i, e in enumerate(VAL): - erow = np.ones(VAL.shape)*e - i_ref = np.isclose(erow, VAL_ref) - self.assertTrue(any(i_ref), - msg="eigenvalue {} not expected".format(e)) - # Eigenvectors can have different scaling. - vr_ref = Vr_ref[:, i_ref]*Vr[0, i]/Vr_ref[0, i_ref][0] - assert_allclose(Vr[:, (i,)], vr_ref, atol=0.0001) - - assert_allclose(np.dot(Vr, Yr), np.dot(Vr_ref, Yr_ref), atol=0.0001) - - # TODO: move this to pytest recwarn together with the whole class - @unittest.skipIf(sys.version < "3", "no assertWarns in old Python") - def test_mb05md_warning(self): - """Check that the correct warning is raised from docstring""" - A = np.diag([3., 3., 3., 3.]) + np.diag([1., 1., 1.], k=1) - delta = 0.1 - - with self.assertWarns(SlycotResultWarning, - msg="\n" - "Matrix A is defective, possibly " - "due to rounding errors.") as cm: - (Ar, Vr, Yr, VAL) = mb05md(A, delta) - assert cm.warning.info == 6 - - def test_mb05nd(self): - """ test_mb05nd: verify Matrix exponential and integral - data from http://slicot.org/objects/software/shared/doc/MB05ND.html - """ - A = np.array([[5.0, 4.0, 3.0, 2.0, 1.0], - [1.0, 6.0, 0.0, 4.0, 3.0], - [2.0, 0.0, 7.0, 6.0, 5.0], - [1.0, 3.0, 1.0, 8.0, 7.0], - [2.0, 5.0, 7.0, 1.0, 9.0]]) - delta = 0.1 - F_ref = np.array([[1.8391, 0.9476, 0.7920, 0.8216, 0.7811], - [0.3359, 2.2262, 0.4013, 1.0078, 1.0957], - [0.6335, 0.6776, 2.6933, 1.6155, 1.8502], - [0.4804, 1.1561, 0.9110, 2.7461, 2.0854], - [0.7105, 1.4244, 1.8835, 1.0966, 3.4134]]) - H_ref = np.array([[0.1347, 0.0352, 0.0284, 0.0272, 0.0231], - [0.0114, 0.1477, 0.0104, 0.0369, 0.0368], - [0.0218, 0.0178, 0.1624, 0.0580, 0.0619], - [0.0152, 0.0385, 0.0267, 0.1660, 0.0732], - [0.0240, 0.0503, 0.0679, 0.0317, 0.1863]]) - - (F, H) = mb05nd(A, delta) - - assert_allclose(F, F_ref, atol=0.0001) - assert_allclose(H, H_ref, atol=0.0001) + assert record[0].message.info == 6 + +def test_mb05nd(): + """ test_mb05nd: verify Matrix exponential and integral + data from http://slicot.org/objects/software/shared/doc/MB05ND.html + """ + A = np.array([[5.0, 4.0, 3.0, 2.0, 1.0], + [1.0, 6.0, 0.0, 4.0, 3.0], + [2.0, 0.0, 7.0, 6.0, 5.0], + [1.0, 3.0, 1.0, 8.0, 7.0], + [2.0, 5.0, 7.0, 1.0, 9.0]]) + delta = 0.1 + F_ref = np.array([[1.8391, 0.9476, 0.7920, 0.8216, 0.7811], + [0.3359, 2.2262, 0.4013, 1.0078, 1.0957], + [0.6335, 0.6776, 2.6933, 1.6155, 1.8502], + [0.4804, 1.1561, 0.9110, 2.7461, 2.0854], + [0.7105, 1.4244, 1.8835, 1.0966, 3.4134]]) + H_ref = np.array([[0.1347, 0.0352, 0.0284, 0.0272, 0.0231], + [0.0114, 0.1477, 0.0104, 0.0369, 0.0368], + [0.0218, 0.0178, 0.1624, 0.0580, 0.0619], + [0.0152, 0.0385, 0.0267, 0.1660, 0.0732], + [0.0240, 0.0503, 0.0679, 0.0317, 0.1863]]) + + (F, H) = mb05nd(A, delta) + + assert_allclose(F, F_ref, atol=0.0001) + assert_allclose(H, H_ref, atol=0.0001) @pytest.mark.parametrize( @@ -335,6 +497,3 @@ def test_mb05nd(self): def test_mb_docparse(fun, exception_class, erange, checkvars): assert_docstring_parse(fun.__doc__, exception_class, erange, checkvars) - -if __name__ == "__main__": - unittest.main() diff --git a/slycot/tests/test_mc.py b/slycot/tests/test_mc.py index b27c0481..e1650068 100644 --- a/slycot/tests/test_mc.py +++ b/slycot/tests/test_mc.py @@ -2,9 +2,10 @@ # test_mc.py - test suite for polynomial and rational function manipulation # bnavigator , Aug 2019 -import pytest import re +import pytest + from slycot import mc01td from slycot.exceptions import SlycotResultWarning diff --git a/slycot/tests/test_sb.py b/slycot/tests/test_sb.py index 00e406db..e6f97a07 100644 --- a/slycot/tests/test_sb.py +++ b/slycot/tests/test_sb.py @@ -1,14 +1,14 @@ # =================================================== # sb* synthesis tests -from slycot import synthesis -from slycot.exceptions import raise_if_slycot_error, \ - SlycotParameterError, SlycotArithmeticError, \ - SlycotResultWarning - +import pytest from numpy import array, eye, zeros from numpy.testing import assert_allclose, assert_raises -import pytest + +from slycot import synthesis +from slycot.exceptions import (SlycotArithmeticError, SlycotParameterError, + SlycotResultWarning, raise_if_slycot_error) + from .test_exceptions import assert_docstring_parse diff --git a/slycot/tests/test_sb10yd.py b/slycot/tests/test_sb10yd.py new file mode 100644 index 00000000..8574bd51 --- /dev/null +++ b/slycot/tests/test_sb10yd.py @@ -0,0 +1,173 @@ +import numpy as np +from scipy import signal + +from slycot import synthesis + + +class Test_sb10yd(): + + # TODO: There might be better systems/filters to do these tests. + + def test_sb10yd_cont_exec_case_n0(self): + """A simple execution test. Case n=0. + """ + sys_tf = signal.TransferFunction(1, 1) + num, den = sys_tf.num, sys_tf.den + + omega, H = signal.freqs(num, den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 0 + dico = 0 # 0 for continuous time + flag = 0 # 0 for no constraints on the poles + n_id, *_ = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + # Because flag = 0, we expect n == n_id + np.testing.assert_equal(n, n_id) + + def test_sb10yd_cont_exec(self): + """A simple execution test. Case n=2. + """ + A = np.array([[0.0, 1.0], [-0.5, -0.1]]) + B = np.array([[0.0], [1.0]]) + C = np.array([[1.0, 0.0]]) + D = np.zeros((1, 1)) + + sys_tf = signal.ss2tf(A, B, C, D) + num, den = sys_tf + + omega, H = signal.freqs(num.squeeze(), den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 2 + dico = 0 # 0 for continuous time + flag = 0 # 0 for no constraints on the poles + n_id, *_ = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + # Because flag = 0, we expect n == n_id + np.testing.assert_equal(n, n_id) + + def test_sb10yd_cont_allclose(self): + """Compare given and identified frequency response. Case n=2. + """ + + A = np.array([[0.0, 1.0], [-0.5, -0.1]]) + B = np.array([[0.0], [1.0]]) + C = np.array([[1.0, 0.0]]) + D = np.zeros((1, 1)) + + sys_tf = signal.ss2tf(A, B, C, D) + num, den = sys_tf + + omega, H = signal.freqs(num.squeeze(), den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 2 + dico = 0 # 0 for continuous time + flag = 0 # 0 for no constraints on the poles + n_id, A_id, B_id, C_id, D_id = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + sys_tf_id = signal.ss2tf(A_id, B_id, C_id, D_id) + num_id, den_id = sys_tf_id + w_id, H_id = signal.freqs(num_id.squeeze(), den_id, worN=omega) + + np.testing.assert_allclose(abs(H_id), abs(H), rtol=0, atol=0.1) + + def test_sb10yd_disc_exec_case_n0(self): + """A simple execution test. Case n=0. + """ + + sys_tf = signal.TransferFunction(1, 1, dt=0.1) + num, den = sys_tf.num, sys_tf.den + + omega, H = signal.freqz(num.squeeze(), den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 0 + dico = 1 # 0 for discrete time + flag = 0 # 0 for no constraints on the poles + n_id, *_ = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + # Because flag = 0, we expect n == n_id + np.testing.assert_equal(n, n_id) + + def test_sb10yd_disc_exec(self): + """A simple execution test. Case n=2. + """ + + A = np.array([[0.0, 1.0], [-0.5, -0.1]]) + B = np.array([[0.0], [1.0]]) + C = np.array([[1.0, 0.0]]) + D = np.zeros((1, 1)) + + sys_tf = signal.ss2tf(A, B, C, D) + num, den = sys_tf + + dt = 0.1 + num, den, dt = signal.cont2discrete((num, den), dt, method="zoh") + print(den) + + omega, H = signal.freqz(num.squeeze(), den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 2 + dico = 1 # 0 for discrete time + flag = 0 # 0 for no constraints on the poles + n_id, *_ = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + # Because flag = 0, we expect n == n_id + np.testing.assert_equal(n, n_id) + + def test_sb10yd_disc_allclose(self): + """Compare given and identified frequency response. Case n=2. + """ + + A = np.array([[0.0, 1.0], [-0.5, -0.1]]) + B = np.array([[0.0], [1.0]]) + C = np.array([[1.0, 0.0]]) + D = np.zeros((1, 1)) + + sys_tf = signal.ss2tf(A, B, C, D) + num, den = sys_tf + + dt = 0.01 + num, den, dt = signal.cont2discrete((num, den), dt, method="zoh") + + omega, H = signal.freqz(num.squeeze(), den) + + real_H_resp = np.real(H) + imag_H_resp = np.imag(H) + + n = 2 + dico = 1 # 0 for discrete time + flag = 0 # 0 for no constraints on the poles + n_id, A_id, B_id, C_id, D_id = synthesis.sb10yd( + dico, flag, len(omega), + real_H_resp, imag_H_resp, omega, n, tol=0) + + sys_id = signal.dlti(A_id, B_id, C_id, D_id, dt=dt) + sys_tf_id = signal.TransferFunction(sys_id) + num_id, den_id = sys_tf_id.num, sys_tf_id.den + w_id, H_id = signal.freqz(num_id.squeeze(), den_id, worN=omega) + + np.testing.assert_allclose(abs(H_id), abs(H), rtol=0, atol=1.0) diff --git a/slycot/tests/test_sg02ad.py b/slycot/tests/test_sg02ad.py index df5e4a02..1576a8ec 100644 --- a/slycot/tests/test_sg02ad.py +++ b/slycot/tests/test_sg02ad.py @@ -1,46 +1,38 @@ # # test_sg02ad.py - test suite for ricatti equation solving # RvP, 19 Jun 2017 -from __future__ import print_function -import unittest -from slycot import synthesis import numpy as np - from numpy.testing import assert_almost_equal - -class test_sg02ad(unittest.TestCase): - - def test_sg02ad_case1(self): - n = 3 - m = 1 - # from a discussion here: - # https://github.com/scipy/scipy/issues/2251 - A = np.array([[ 0.63399379, 0.54906824, 0.76253406], - [ 0.5404729 , 0.53745766, 0.08731853], - [ 0.27524045, 0.84922129, 0.4681622 ]]) - B = np.array([[ 0.96861695], - [ 0.05532739], - [ 0.78934047]]) - Q = np.eye(3) - E = np.eye(3) - R = np.ones((1,1), dtype=float) - S = np.array([[-2.67522766, -5.39447418, 2.19128542], - [-1.94918951, -3.15480639, 5.24379117], - [ 4.29133973, 8.10585767, -5.88895897]]) - L = np.array(np.zeros((3,1))) - rcondu, X, alphar, alphai, beta, S, T, U, iwarn = \ - synthesis.sg02ad('D', 'B', 'N', 'U', 'Z', 'N', 'S', 'R', - n, m, 1, - A, E, B, Q, R, L) - LATXB = L + A.T.dot(X).dot(B) - assert_almost_equal( - A.T.dot(X).dot(A) - - E.T.dot(X).dot(E) - - LATXB.dot(np.linalg.solve(R+B.T.dot(X).dot(B), LATXB.T)) + Q, - np.zeros((n, n))) +from slycot import synthesis -if __name__ == "__main__": - unittest.main() +def test_sg02ad_case1(): + n = 3 + m = 1 + # from a discussion here: + # https://github.com/scipy/scipy/issues/2251 + A = np.array([[ 0.63399379, 0.54906824, 0.76253406], + [ 0.5404729 , 0.53745766, 0.08731853], + [ 0.27524045, 0.84922129, 0.4681622 ]]) + B = np.array([[ 0.96861695], + [ 0.05532739], + [ 0.78934047]]) + Q = np.eye(3) + E = np.eye(3) + R = np.ones((1,1), dtype=float) + S = np.array([[-2.67522766, -5.39447418, 2.19128542], + [-1.94918951, -3.15480639, 5.24379117], + [ 4.29133973, 8.10585767, -5.88895897]]) + L = np.array(np.zeros((3,1))) + rcondu, X, alphar, alphai, beta, S, T, U, iwarn = \ + synthesis.sg02ad('D', 'B', 'N', 'U', 'Z', 'N', 'S', 'R', + n, m, 1, + A, E, B, Q, R, L) + LATXB = L + A.T.dot(X).dot(B) + assert_almost_equal( + A.T.dot(X).dot(A) - + E.T.dot(X).dot(E) - + LATXB.dot(np.linalg.solve(R+B.T.dot(X).dot(B), LATXB.T)) + Q, + np.zeros((n, n))) diff --git a/slycot/tests/test_sg03ad.py b/slycot/tests/test_sg03ad.py index dc0f262a..a5976e0d 100644 --- a/slycot/tests/test_sg03ad.py +++ b/slycot/tests/test_sg03ad.py @@ -1,73 +1,66 @@ # # test_sg03ad.py - test suite for stability margin commands # RvP, 15 Jun 2017 -from __future__ import print_function -import unittest -from slycot import synthesis import numpy as np - from numpy.testing import assert_almost_equal +from slycot import synthesis + # test cases from # Penzl T., Numerical Solution of Generalized Lyapunov Equations # http://www.qucosa.de/fileadmin/data/qucosa/documents/4168/data/b002.pdf -class test_sg03ad(unittest.TestCase): - - def test_sg03ad_ex1c(self): - """ Example 1 continuous case""" - n = 100 - Xref = np.ones((n, n)) - U = np.tril(Xref) - for t in range(0, 50, 10): - A = (2**(-t) - 1) * np.eye(n) + np.diag(np.arange(1., n+1.)) + U.T - E = np.eye(n) + 2**(-t) * U - Y = A.T.dot(Xref).dot(E) + E.T.dot(Xref).dot(A) - Q = np.zeros((n, n)) - Z = np.zeros((n, n)) - A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ - synthesis.sg03ad('C', 'B', 'N', 'N', 'L', n, A, E, Q, Z, Y) - assert_almost_equal(X, Xref) - - def test_sg03ad_ex1d(self): - """ Example 1 discrete case""" - n = 100 - Xref = np.ones((n, n)) - U = np.tril(Xref) - for t in range(0, 50, 10): - A = 2**(-t) * np.eye(n) + np.diag(np.arange(1., n+1.)) + U.T - E = np.eye(n) + 2**(-t) * U - Y = A.T.dot(Xref).dot(A) - E.T.dot(Xref).dot(E) - Q = np.zeros((n, n)) - Z = np.zeros((n, n)) - A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ - synthesis.sg03ad('D', 'B', 'N', 'N', 'L', n, A, E, Q, Z, Y) - assert_almost_equal(X, Xref) +def test_sg03ad_ex1c(): + """ Example 1 continuous case""" + n = 100 + Xref = np.ones((n, n)) + U = np.tril(Xref) + for t in range(0, 50, 10): + A = (2**(-t) - 1) * np.eye(n) + np.diag(np.arange(1., n+1.)) + U.T + E = np.eye(n) + 2**(-t) * U + Y = A.T.dot(Xref).dot(E) + E.T.dot(Xref).dot(A) + Q = np.zeros((n, n)) + Z = np.zeros((n, n)) + A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ + synthesis.sg03ad('C', 'B', 'N', 'N', 'L', n, A, E, Q, Z, Y) + assert_almost_equal(X, Xref) - def test_sg03ad_b1(self): - """ SLICOT doc example / Penzl B.1 """ - n = 3 - A = np.array([[3.0, 1.0, 1.0], - [1.0, 3.0, 0.0], - [1.0, 0.0, 2.0]]) - E = np.array([[1.0, 3.0, 0.0], - [3.0, 2.0, 1.0], - [1.0, 0.0, 1.0]]) - Y = np.array([[64.0, 73.0, 28.0], - [73.0, 70.0, 25.0], - [28.0, 25.0, 18.0]]) - Xref = np.array([[-2.0000, -1.0000, 0.0000], - [-1.0000, -3.0000, -1.0000], - [0.0000, -1.0000, -3.0000]]) - Q = np.zeros((3, 3)) - Z = np.zeros((3, 3)) +def test_sg03ad_ex1d(): + """ Example 1 discrete case""" + n = 100 + Xref = np.ones((n, n)) + U = np.tril(Xref) + for t in range(0, 50, 10): + A = 2**(-t) * np.eye(n) + np.diag(np.arange(1., n+1.)) + U.T + E = np.eye(n) + 2**(-t) * U + Y = A.T.dot(Xref).dot(A) - E.T.dot(Xref).dot(E) + Q = np.zeros((n, n)) + Z = np.zeros((n, n)) A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ - synthesis.sg03ad('C', 'B', 'N', 'N', 'L', n, A, E, Q, Z, -Y) - # print(A, E, Q, Z, X, scale, sep) + synthesis.sg03ad('D', 'B', 'N', 'N', 'L', n, A, E, Q, Z, Y) assert_almost_equal(X, Xref) +def test_sg03ad_b1(): + """ SLICOT doc example / Penzl B.1 """ + n = 3 + A = np.array([[3.0, 1.0, 1.0], + [1.0, 3.0, 0.0], + [1.0, 0.0, 2.0]]) + E = np.array([[1.0, 3.0, 0.0], + [3.0, 2.0, 1.0], + [1.0, 0.0, 1.0]]) + Y = np.array([[64.0, 73.0, 28.0], + [73.0, 70.0, 25.0], + [28.0, 25.0, 18.0]]) + Xref = np.array([[-2.0000, -1.0000, 0.0000], + [-1.0000, -3.0000, -1.0000], + [0.0000, -1.0000, -3.0000]]) + Q = np.zeros((3, 3)) + Z = np.zeros((3, 3)) + A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ + synthesis.sg03ad('C', 'B', 'N', 'N', 'L', n, A, E, Q, Z, -Y) + # print(A, E, Q, Z, X, scale, sep) + assert_almost_equal(X, Xref) -if __name__ == "__main__": - unittest.main() diff --git a/slycot/tests/test_tb05ad.py b/slycot/tests/test_tb05ad.py index 5732a660..900a49a2 100644 --- a/slycot/tests/test_tb05ad.py +++ b/slycot/tests/test_tb05ad.py @@ -1,13 +1,15 @@ # =================================================== # tb05ad tests -from slycot import transform -from slycot.exceptions import SlycotArithmeticError, SlycotParameterError import sys + import numpy as np -from scipy.linalg import matrix_balance, eig -import unittest +import pytest from numpy.testing import assert_almost_equal +from scipy.linalg import eig, matrix_balance + +from slycot import transform +from slycot.exceptions import SlycotArithmeticError, SlycotParameterError # set the random seed so we can get consistent results. np.random.seed(40) @@ -35,213 +37,215 @@ 'C': np.random.randn(p, n)} -class test_tb05ad(unittest.TestCase): - - def test_tb05ad_ng(self): - """ - Test that tb05ad with job 'NG' computes the correct - frequency response. - """ - for key in CASES: - sys = CASES[key] - self.check_tb05ad_AG_NG(sys, 10*1j, 'NG') - - def test_tb05ad_ag(self): - """ - Test that tb05ad with job 'AG' computes the correct - frequency response. - """ - for key in CASES: - sys = CASES[key] - self.check_tb05ad_AG_NG(sys, 10*1j, 'AG') - - def test_tb05ad_nh(self): - """Test that tb05ad with job = 'NH' computes the correct - frequency response after conversion to Hessenberg form. - - First call tb05ad with job='NH' to transform to upper Hessenberg - form which outputs the transformed system. - Subsequently, call tb05ad with job='NH' using this transformed system. - """ - jomega = 10*1j - for key in CASES: - sys = CASES[key] - sys_transformed = self.check_tb05ad_AG_NG(sys, jomega, 'NG') - self.check_tb05ad_NH(sys_transformed, sys, jomega) - - def test_tb05ad_errors(self): - """ - Test tb05ad error handling. We give wrong inputs and - and check that this raises an error. - """ - self.check_tb05ad_errors(CASES['pass1']) - - def check_tb05ad_AG_NG(self, sys, jomega, job): - """ - Check that tb05ad computes the correct frequency response when - running jobs 'AG' and/or 'NG'. - - Inputs - ------ - - sys: A a dict of system matrices with keys 'A', 'B', and 'C'. - jomega: A complex scalar, which is the frequency we are - evaluating the system at. - job: A string, either 'AG' or 'NH' - - Returns - ------- - sys_transformed: A dict of the system matrices which have been - transformed according the job. - """ - n, m = sys['B'].shape - p = sys['C'].shape[0] - result = transform.tb05ad(n, m, p, jomega, - sys['A'], sys['B'], sys['C'], job=job) - g_i = result[3] - hinvb = np.linalg.solve(np.eye(n) * jomega - sys['A'], sys['B']) - g_i_solve = sys['C'].dot(hinvb) - assert_almost_equal(g_i_solve, g_i) - sys_transformed = {'A': result[0], 'B': result[1], 'C': result[2]} - return sys_transformed - - def check_tb05ad_NH(self, sys_transformed, sys, jomega): - """ - Check tb05ad, computes the correct frequency response when - job='NH' and we supply system matrices 'A', 'B', and 'C' - which have been transformed by a previous call to tb05ad. - We check we get the same result as computing C(sI - A)^-1B - with the original system. - - Inputs - ------ - - sys_transformed: A a dict of the transformed (A in upper - hessenberg form) system matrices with keys - 'A', 'B', and 'C'. - - sys: A dict of the original un-transformed system matrices. - - jomega: A complex scalar, which is the frequency to evaluate at. - - """ - - n, m = sys_transformed['B'].shape - p = sys_transformed['C'].shape[0] - result = transform.tb05ad(n, m, p, jomega, sys_transformed['A'], - sys_transformed['B'], sys_transformed['C'], - job='NH') - g_i = result[0] - hinvb = np.linalg.solve(np.eye(n) * jomega - sys['A'], sys['B']) - g_i_solve = sys['C'].dot(hinvb) - assert_almost_equal(g_i_solve, g_i) - - def check_tb05ad_errors(self, sys): - """ - Check the error handling of tb05ad. We give wrong inputs and - and check that this raises an error. - """ - n, m = sys['B'].shape - p = sys['C'].shape[0] - jomega = 10*1j - # test error handling - # wrong size A - with self.assertRaises(SlycotParameterError) as cm: - transform.tb05ad( - n+1, m, p, jomega, sys['A'], sys['B'], sys['C'], job='NH') - assert cm.exception.info == -7 - # wrong size B - with self.assertRaises(SlycotParameterError) as cm: - transform.tb05ad( - n, m+1, p, jomega, sys['A'], sys['B'], sys['C'], job='NH') - assert cm.exception.info == -9 - # wrong size C - with self.assertRaises(SlycotParameterError) as cm: - transform.tb05ad( - n, m, p+1, jomega, sys['A'], sys['B'], sys['C'], job='NH') - assert cm.exception.info == -11 - # unrecognized job - with self.assertRaises(SlycotParameterError) as cm: - transform.tb05ad( - n, m, p, jomega, sys['A'], sys['B'], sys['C'], job='a') - assert cm.exception.info == -1 - - @unittest.skipIf(sys.version < "3", "no assertRaisesRegex in old Python") - def test_tb05ad_resonance(self): - """ Test tb05ad resonance failure. - - Actually test one of the exception messages. These - are parsed from the docstring, tests both the info index and the - message - """ - A = np.array([[0, -1], - [1, 0]]) - B = np.array([[1], - [0]]) - C = np.array([[0, 1]]) - jomega = 1j - with self.assertRaisesRegex( - SlycotArithmeticError, - r"Either `freq`.* is too near to an eigenvalue of A,\n" - r"or `rcond` is less than the machine precision EPS.") as cm: - transform.tb05ad(2, 1, 1, jomega, A, B, C, job='NH') - assert cm.exception.info == 2 - - def test_tb05ad_balance(self): - """Test balancing in tb05ad. - - Tests for the cause of the problem reported in issue #11 - balancing permutations were not correctly applied to the - C and D matrix. - """ - - # find a good test case. Some sparsity, - # some zero eigenvalues, some non-zero eigenvalues, - # and proof that the 1st step, with dgebal, does some - # permutation and some scaling - crit = False - n = 8 - while not crit: - A = np.random.randn(n, n) - A[np.random.uniform(size=(n, n)) > 0.35] = 0.0 - - Aeig = eig(A)[0] - neig0 = np.sum(np.abs(Aeig) == 0) - As, T = matrix_balance(A) - nperm = np.sum(np.diag(T == 0)) - nscale = n - np.sum(T == 1.0) - crit = nperm < n and nperm >= n//2 and \ - neig0 > 1 and neig0 <= 3 and nscale > 0 - - # print("number of permutations", nperm, "eigenvalues=0", neig0) - B = np.random.randn(8, 4) - C = np.random.randn(3, 8) - - # do a run - jomega = 1.0 - At, Bt, Ct, rcond, g_jw, ev, hinvb, info = transform.tb05ad( - 8, 4, 3, jomega, A, B, C, job='AG') - - # remove information on Q, in lower sub-triangle part of A - At = np.triu(At, k=-1) - - # now after the balancing in DGEBAL, and conversion to - # upper Hessenberg form: - # At = Q^T * (P^-1 * A * P ) * Q - # with Q orthogonal - # Ct = C * P * Q - # Bt = Q^T * P^-1 * B - # so test with Ct * At * Bt == C * A * B - # and verify that eigenvalues of both A matrices are close - assert_almost_equal(np.dot(np.dot(Ct, At), Bt), - np.dot(np.dot(C, A), B)) - # uses a sort, there is no guarantee on the order of eigenvalues - eigAt = eig(At)[0] - idxAt = np.argsort(eigAt) - eigA = eig(A)[0] - idxA = np.argsort(eigA) - assert_almost_equal(eigA[idxA], eigAt[idxAt]) - - -if __name__ == "__main__": - unittest.main() +def test_tb05ad_ng(): + """ + Test that tb05ad with job 'NG' computes the correct + frequency response. + """ + for key in CASES: + sys = CASES[key] + check_tb05ad_AG_NG(sys, 10*1j, 'NG') + + +def test_tb05ad_ag(): + """ + Test that tb05ad with job 'AG' computes the correct + frequency response. + """ + for key in CASES: + sys = CASES[key] + check_tb05ad_AG_NG(sys, 10*1j, 'AG') + + +def test_tb05ad_nh(): + """Test that tb05ad with job = 'NH' computes the correct + frequency response after conversion to Hessenberg form. + + First call tb05ad with job='NH' to transform to upper Hessenberg + form which outputs the transformed system. + Subsequently, call tb05ad with job='NH' using this transformed system. + """ + jomega = 10*1j + for key in CASES: + sys = CASES[key] + sys_transformed = check_tb05ad_AG_NG(sys, jomega, 'NG') + check_tb05ad_NH(sys_transformed, sys, jomega) + + +def test_tb05ad_errors(): + """ + Test tb05ad error handling. We give wrong inputs and + and check that this raises an error. + """ + check_tb05ad_errors(CASES['pass1']) + + +def check_tb05ad_AG_NG(sys, jomega, job): + """ + Check that tb05ad computes the correct frequency response when + running jobs 'AG' and/or 'NG'. + + Inputs + ------ + + sys: A a dict of system matrices with keys 'A', 'B', and 'C'. + jomega: A complex scalar, which is the frequency we are + evaluating the system at. + job: A string, either 'AG' or 'NH' + + Returns + ------- + sys_transformed: A dict of the system matrices which have been + transformed according the job. + """ + n, m = sys['B'].shape + p = sys['C'].shape[0] + result = transform.tb05ad(n, m, p, jomega, + sys['A'], sys['B'], sys['C'], job=job) + g_i = result[3] + hinvb = np.linalg.solve(np.eye(n) * jomega - sys['A'], sys['B']) + g_i_solve = sys['C'].dot(hinvb) + assert_almost_equal(g_i_solve, g_i) + sys_transformed = {'A': result[0], 'B': result[1], 'C': result[2]} + return sys_transformed + + +def check_tb05ad_NH(sys_transformed, sys, jomega): + """ + Check tb05ad, computes the correct frequency response when + job='NH' and we supply system matrices 'A', 'B', and 'C' + which have been transformed by a previous call to tb05ad. + We check we get the same result as computing C(sI - A)^-1B + with the original system. + + Inputs + ------ + + sys_transformed: A a dict of the transformed (A in upper + hessenberg form) system matrices with keys + 'A', 'B', and 'C'. + + sys: A dict of the original un-transformed system matrices. + + jomega: A complex scalar, which is the frequency to evaluate at. + + """ + + n, m = sys_transformed['B'].shape + p = sys_transformed['C'].shape[0] + result = transform.tb05ad(n, m, p, jomega, sys_transformed['A'], + sys_transformed['B'], sys_transformed['C'], + job='NH') + g_i = result[0] + hinvb = np.linalg.solve(np.eye(n) * jomega - sys['A'], sys['B']) + g_i_solve = sys['C'].dot(hinvb) + assert_almost_equal(g_i_solve, g_i) + + +def check_tb05ad_errors(sys): + """ + Check the error handling of tb05ad. We give wrong inputs and + and check that this raises an error. + """ + n, m = sys['B'].shape + p = sys['C'].shape[0] + jomega = 10*1j + # test error handling + # wrong size A + with pytest.raises(SlycotParameterError) as cm: + transform.tb05ad( + n+1, m, p, jomega, sys['A'], sys['B'], sys['C'], job='NH') + assert cm.value.info == -7 + # wrong size B + with pytest.raises(SlycotParameterError) as cm: + transform.tb05ad( + n, m+1, p, jomega, sys['A'], sys['B'], sys['C'], job='NH') + assert cm.value.info == -9 + # wrong size C + with pytest.raises(SlycotParameterError) as cm: + transform.tb05ad( + n, m, p+1, jomega, sys['A'], sys['B'], sys['C'], job='NH') + assert cm.value.info == -11 + # unrecognized job + with pytest.raises(SlycotParameterError) as cm: + transform.tb05ad( + n, m, p, jomega, sys['A'], sys['B'], sys['C'], job='a') + assert cm.value.info == -1 + + +def test_tb05ad_resonance(): + """ Test tb05ad resonance failure. + + Actually test one of the exception messages. These + are parsed from the docstring, tests both the info index and the + message + """ + A = np.array([[0, -1], + [1, 0]]) + B = np.array([[1], + [0]]) + C = np.array([[0, 1]]) + jomega = 1j + with pytest.raises( + SlycotArithmeticError, + match=r"Either `freq`.* is too near to an eigenvalue of A,\n" + r"or `rcond` is less than the machine precision EPS.") as cm: + transform.tb05ad(2, 1, 1, jomega, A, B, C, job='NH') + assert cm.value.info == 2 + + +def test_tb05ad_balance(): + """Test balancing in tb05ad. + + Tests for the cause of the problem reported in issue #11 + balancing permutations were not correctly applied to the + C and D matrix. + """ + + # find a good test case. Some sparsity, + # some zero eigenvalues, some non-zero eigenvalues, + # and proof that the 1st step, with dgebal, does some + # permutation and some scaling + crit = False + n = 8 + while not crit: + A = np.random.randn(n, n) + A[np.random.uniform(size=(n, n)) > 0.35] = 0.0 + + Aeig = eig(A)[0] + neig0 = np.sum(np.abs(Aeig) == 0) + As, T = matrix_balance(A) + nperm = np.sum(np.diag(T == 0)) + nscale = n - np.sum(T == 1.0) + crit = nperm < n and nperm >= n//2 and \ + neig0 > 1 and neig0 <= 3 and nscale > 0 + + # print("number of permutations", nperm, "eigenvalues=0", neig0) + B = np.random.randn(8, 4) + C = np.random.randn(3, 8) + + # do a run + jomega = 1.0 + At, Bt, Ct, rcond, g_jw, ev, hinvb, info = transform.tb05ad( + 8, 4, 3, jomega, A, B, C, job='AG') + + # remove information on Q, in lower sub-triangle part of A + At = np.triu(At, k=-1) + + # now after the balancing in DGEBAL, and conversion to + # upper Hessenberg form: + # At = Q^T * (P^-1 * A * P ) * Q + # with Q orthogonal + # Ct = C * P * Q + # Bt = Q^T * P^-1 * B + # so test with Ct * At * Bt == C * A * B + # and verify that eigenvalues of both A matrices are close + assert_almost_equal(np.dot(np.dot(Ct, At), Bt), + np.dot(np.dot(C, A), B)) + # uses a sort, there is no guarantee on the order of eigenvalues + eigAt = eig(At)[0] + idxAt = np.argsort(eigAt) + eigA = eig(A)[0] + idxA = np.argsort(eigA) + assert_almost_equal(eigA[idxA], eigAt[idxAt]) + diff --git a/slycot/tests/test_td04ad.py b/slycot/tests/test_td04ad.py index 50206360..57c557d0 100644 --- a/slycot/tests/test_td04ad.py +++ b/slycot/tests/test_td04ad.py @@ -2,226 +2,219 @@ # test_td04ad.py - test suite for tf -> ss conversion # RvP, 04 Jun 2018 -from __future__ import print_function, division +import numpy as np -import unittest from slycot import transform -import numpy as np -class TestTf2SS(unittest.TestCase): - - def test_td04ad_c(self): - """td04ad: Convert with 'C' option""" - - # for octave: - """ - num = { [0.0, 0.0, 1.0 ], [ 1.0, 0.0 ]; - [3.0, -1.0, 1.0 ], [ 0.0, 1.0 ]; - [0.0, 0.0, 1.0], [ 0.0, 2.0 ] }; - den = { [1.0, 0.4, 3.0], [ 1.0, 1.0 ]; - [1.0, 0.4, 3.0], [ 1.0, 1.0 ]; - [1.0, 0.4, 3.0], [ 1.0, 1.0 ]}; - """ - - m = 2 - p = 3 - d = 3 - num = np.array([ - [ [0.0, 0.0, 1.0], [1.0, 0.0, 0.0] ], - [ [3.0, -1.0, 1.0], [0.0, 1.0, 0.0] ], - [ [0.0, 0.0, 1.0], [0.0, 2.0, 0.0] ] ]) - - numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) - numc[:p,:m,:] = num - denc = np.array( - [ [1.0, 0.4, 3.0], [ 1.0, 1.0, 0.0 ] ]) - indc = np.array( - [ 2, 1 ], dtype=int) - - nref = 3 - Aref = np.array([ [-1, 0, 0], - [ 0, -0.4, -0.3], - [ 0, 10, 0] ]) - Bref = np.array([ [0, -1], - [1, 0], - [0, 0] ]) - Cref = np.array([ [1, 0, 0.1], - [-1, -2.2, -0.8], - [-2, 0, 0.1] ]) - Dref = np.array([ [0, 1], - [3, 0], - [0, 0] ]) - - nr, A, B, C, D = transform.td04ad('C', m, p, indc, denc, numc) - #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) - np.testing.assert_equal(nref, nr) - # the returned state space representation is not guaranteed to - # be of one form for all architectures, so we transform back - # to tf and check for equality then - _, _, _, _, _, dcoeff, ucoeff = transform.tb04ad( - nr, m, p, A, B, C, D) - _, _, _, _, _, dcoeffref, ucoeffref = transform.tb04ad( - nref, m, p, Aref, Bref, Cref, Dref) - np.testing.assert_array_almost_equal(dcoeff,dcoeffref) - np.testing.assert_array_almost_equal(ucoeff,ucoeffref) - - def test_td04ad_r(self): - """td04ad: Convert with 'R' option - - example program from - http://slicot.org/objects/software/shared/doc/TD04AD.html - """ - - m = 2 - p = 2 - rowcol = 'R' - index = [3, 3] - dcoeff = np.array([ [1.0, 6.0, 11.0, 6.0], [1.0, 6.0, 11.0, 6.0] ]) - - ucoeff = np.array([ [[1.0, 6.0, 12.0, 7.0], [0.0, 1.0, 4.0, 3.0]], - [[0.0, 0.0, 1.0, 1.0], [1.0, 8.0, 20.0, 15.0]] ]) - - nref = 3 - - Aref = np.array([ [ 0.5000, -0.8028, 0.9387], - [ 4.4047, -2.3380, 2.5076], - [-5.5541, 1.6872, -4.1620] ]) - Bref = np.array([ [-0.2000, -1.2500], - [ 0.0000, -0.6097], - [ 0.0000, 2.2217] ]) - Cref = np.array([ [0.0000, -0.8679, 0.2119], - [0.0000, 0.0000, 0.9002] ]) - Dref = np.array([ [1.0000, 0.0000], - [0.0000, 1.0000] ]) - - nr, A, B, C, D = transform.td04ad(rowcol, m, p, index, dcoeff, ucoeff) - #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) - np.testing.assert_equal(nref, nr) - # order of states is not guaranteed, so we reorder the reference - rindex = np.flip(np.argsort(np.diag(A))) - Arref = Aref[rindex, :][:, rindex] - Brref = Bref[rindex, :] - Crref = Cref[:, rindex] - Drref = Dref - np.testing.assert_array_almost_equal(A, Arref,decimal=4) - np.testing.assert_array_almost_equal(B, Brref,decimal=4) - np.testing.assert_array_almost_equal(C, Crref,decimal=4) - np.testing.assert_array_almost_equal(D, Drref,decimal=4) - - - def test_staticgain(self): - """td04ad: Convert a transferfunction to SS with only static gain""" - - # 2 inputs, 3 outputs? columns share a denominator - num = np.array([ [ [1.0], [2.0] ], - [ [0.2], [4.3] ], - [ [1.2], [3.2] ] ]) - p, m, d = num.shape - numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) - numc[:p,:m,:] = num - - # denc, columns share a common denominator - denc = np.array([ [ 1.0], [0.5] ]) - Dc = (num / denc).reshape((3,2)) - idxc = np.zeros((2,), dtype=int) - - # denr, rows share a common denominator - denr = np.array([ [1.0], [0.5], [3.0] ]) - idxr = np.zeros((3,), dtype=int) - Dr = (num / denr[:, np.newaxis]).reshape((3,2)) - - # fails with: - # On entry to TB01XD parameter number 5 had an illegal value - - n, A, B, C, D = transform.td04ad('C', 2, 3, idxc, denc, numc) - #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) - self.assertEqual(A.shape, (0,0)) - self.assertEqual(B.shape, (0,2)) - self.assertEqual(C.shape, (3,0)) - np.testing.assert_array_almost_equal(D, Dc) - - n, A, B, C, D = transform.td04ad('R', 2, 3, idxr, denr, num) - #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) - self.assertEqual(A.shape, (0,0)) - self.assertEqual(B.shape, (0,2)) - self.assertEqual(C.shape, (3,0)) - np.testing.assert_array_almost_equal(D, Dr) - - def test_td04ad_static(self): - """Regression: td04ad (TFM -> SS transformation) for static TFM""" - from itertools import product - for nout, nin, rc in product(range(1, 6), range(1, 6), ['R', 'C']): - Dref = np.zeros((nout, nin)) - if rc == 'R': - num = np.reshape(np.arange(nout * nin), (nout, nin, 1)) - den = np.reshape(np.arange(1, 1 + nout), (nout, 1)) - index = np.repeat(0, nout) - Dref = num[:nout, :nin, 0] / np.broadcast_to(den, (nout, nin)) - else: - maxn = max(nout, nin) - num = np.zeros((maxn, maxn, 1)) - num[:nout, :nin, 0] = np.reshape( - np.arange(nout * nin), (nout, nin)) - den = np.reshape(np.arange(1, 1 + nin), (nin, 1)) - index = np.repeat(0, nin) - Dref = num[:nout, :nin, 0] / np.broadcast_to(den.T, (nout, nin)) - nr, A, B, C, D = transform.td04ad(rc, nin, nout, index, den, num) - np.testing.assert_equal(nr, 0) - for M in [A, B, C]: - np.testing.assert_equal(M, np.zeros_like(M)) - np.testing.assert_almost_equal(D, Dref) - - def test_mixfeedthrough(self): - """Test case popping up from control testing - - a mix of feedthrough and dynamics. The problem from the control - package was somewhere else - """ - num = np.array([ [ [ 0.0, 0.0 ], [ 0.0, -0.2 ] ], - [ [ -0.1, 0.0 ], [ 0.0, 0.0 ] ] ]) - p, m, d = num.shape - numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) - numc[:p,:m,:] = num - denc = np.array([[1.0, 1.1], - [1.0, 0.0]]) - idxc = np.array([1, 0]) - n, A, B, C, D = transform.td04ad('C', 2, 2, idxc, denc, numc) - np.testing.assert_array_almost_equal(D, np.array([[0, 0],[-0.1, 0]])) - - def test_toandfrom(self): - A = np.array([[-3.0]]) - B = np.array([[0.1, 0.0]]) - C = np.array([[1.0], - [0.0]]) - D = np.array([[0.0, 0.0], - [0.0, 1.0]]) - - tfout = transform.tb04ad(1, 2, 2, A, B, C, D) - - num = tfout[6] - den = tfout[5] - idxc = np.array([1, 0]) - n, At, Bt, Ct, Dt = transform.td04ad('R', 2, 2, idxc, den, num) - np.testing.assert_array_almost_equal(D, Dt) - np.testing.assert_array_almost_equal(A, At) - - def test_tfm2ss_6(self): - """Python version of Fortran test program from - -- Bug in TD04AD when ROWCOL='C' #6 - This bug was fixed in PR #27""" - m = 1 - p = 1 - index = np.array([0]) - dcoeff = np.array([[0.5]]) - ucoeff = np.array([[[32]]]) - n, A, B, C, D = transform.td04ad('R', m, p, index, dcoeff, ucoeff) - self.assertEqual(n, 0) - np.testing.assert_array_almost_equal(D, np.array([[64]])) - n, A, B, C, D = transform.td04ad('C', m, p, index, dcoeff, ucoeff) - self.assertEqual(n, 0) - np.testing.assert_array_almost_equal(D, np.array([[64]])) - - -if __name__ == "__main__": - unittest.main() +def test_td04ad_c(): + """td04ad: Convert with 'C' option""" + + # for octave: + """ + num = { [0.0, 0.0, 1.0 ], [ 1.0, 0.0 ]; + [3.0, -1.0, 1.0 ], [ 0.0, 1.0 ]; + [0.0, 0.0, 1.0], [ 0.0, 2.0 ] }; + den = { [1.0, 0.4, 3.0], [ 1.0, 1.0 ]; + [1.0, 0.4, 3.0], [ 1.0, 1.0 ]; + [1.0, 0.4, 3.0], [ 1.0, 1.0 ]}; + """ + + m = 2 + p = 3 + d = 3 + num = np.array([ + [ [0.0, 0.0, 1.0], [1.0, 0.0, 0.0] ], + [ [3.0, -1.0, 1.0], [0.0, 1.0, 0.0] ], + [ [0.0, 0.0, 1.0], [0.0, 2.0, 0.0] ] ]) + + numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) + numc[:p,:m,:] = num + denc = np.array( + [ [1.0, 0.4, 3.0], [ 1.0, 1.0, 0.0 ] ]) + indc = np.array( + [ 2, 1 ], dtype=int) + + nref = 3 + Aref = np.array([ [-1, 0, 0], + [ 0, -0.4, -0.3], + [ 0, 10, 0] ]) + Bref = np.array([ [0, -1], + [1, 0], + [0, 0] ]) + Cref = np.array([ [1, 0, 0.1], + [-1, -2.2, -0.8], + [-2, 0, 0.1] ]) + Dref = np.array([ [0, 1], + [3, 0], + [0, 0] ]) + + nr, A, B, C, D = transform.td04ad('C', m, p, indc, denc, numc) + #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) + np.testing.assert_equal(nref, nr) + # the returned state space representation is not guaranteed to + # be of one form for all architectures, so we transform back + # to tf and check for equality then + _, _, _, _, _, dcoeff, ucoeff = transform.tb04ad( + nr, m, p, A, B, C, D) + _, _, _, _, _, dcoeffref, ucoeffref = transform.tb04ad( + nref, m, p, Aref, Bref, Cref, Dref) + np.testing.assert_array_almost_equal(dcoeff,dcoeffref) + np.testing.assert_array_almost_equal(ucoeff,ucoeffref) + +def test_td04ad_r(): + """td04ad: Convert with 'R' option + + example program from + http://slicot.org/objects/software/shared/doc/TD04AD.html + """ + + m = 2 + p = 2 + rowcol = 'R' + index = [3, 3] + dcoeff = np.array([ [1.0, 6.0, 11.0, 6.0], [1.0, 6.0, 11.0, 6.0] ]) + + ucoeff = np.array([ [[1.0, 6.0, 12.0, 7.0], [0.0, 1.0, 4.0, 3.0]], + [[0.0, 0.0, 1.0, 1.0], [1.0, 8.0, 20.0, 15.0]] ]) + + nref = 3 + + Aref = np.array([ [ 0.5000, -0.8028, 0.9387], + [ 4.4047, -2.3380, 2.5076], + [-5.5541, 1.6872, -4.1620] ]) + Bref = np.array([ [-0.2000, -1.2500], + [ 0.0000, -0.6097], + [ 0.0000, 2.2217] ]) + Cref = np.array([ [0.0000, -0.8679, 0.2119], + [0.0000, 0.0000, 0.9002] ]) + Dref = np.array([ [1.0000, 0.0000], + [0.0000, 1.0000] ]) + + nr, A, B, C, D = transform.td04ad(rowcol, m, p, index, dcoeff, ucoeff) + #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) + np.testing.assert_equal(nref, nr) + # order of states is not guaranteed, so we reorder the reference + rindex = np.flip(np.argsort(np.diag(A))) + Arref = Aref[rindex, :][:, rindex] + Brref = Bref[rindex, :] + Crref = Cref[:, rindex] + Drref = Dref + np.testing.assert_array_almost_equal(A, Arref,decimal=4) + np.testing.assert_array_almost_equal(B, Brref,decimal=4) + np.testing.assert_array_almost_equal(C, Crref,decimal=4) + np.testing.assert_array_almost_equal(D, Drref,decimal=4) + + +def test_staticgain(): + """td04ad: Convert a transferfunction to SS with only static gain""" + + # 2 inputs, 3 outputs? columns share a denominator + num = np.array([ [ [1.0], [2.0] ], + [ [0.2], [4.3] ], + [ [1.2], [3.2] ] ]) + p, m, d = num.shape + numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) + numc[:p,:m,:] = num + + # denc, columns share a common denominator + denc = np.array([ [ 1.0], [0.5] ]) + Dc = (num / denc).reshape((3,2)) + idxc = np.zeros((2,), dtype=int) + + # denr, rows share a common denominator + denr = np.array([ [1.0], [0.5], [3.0] ]) + idxr = np.zeros((3,), dtype=int) + Dr = (num / denr[:, np.newaxis]).reshape((3,2)) + + # fails with: + # On entry to TB01XD parameter number 5 had an illegal value + + n, A, B, C, D = transform.td04ad('C', 2, 3, idxc, denc, numc) + #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) + assert A.shape == (0,0) + assert B.shape == (0,2) + assert C.shape == (3,0) + np.testing.assert_array_almost_equal(D, Dc) + + n, A, B, C, D = transform.td04ad('R', 2, 3, idxr, denr, num) + #print('A=\n', A, '\nB=\n', B, '\nC=\n', C, '\nD=\n', D) + assert A.shape == (0,0) + assert B.shape == (0,2) + assert C.shape == (3,0) + np.testing.assert_array_almost_equal(D, Dr) + +def test_td04ad_static(): + """Regression: td04ad (TFM -> SS transformation) for static TFM""" + from itertools import product + for nout, nin, rc in product(range(1, 6), range(1, 6), ['R', 'C']): + Dref = np.zeros((nout, nin)) + if rc == 'R': + num = np.reshape(np.arange(nout * nin), (nout, nin, 1)) + den = np.reshape(np.arange(1, 1 + nout), (nout, 1)) + index = np.repeat(0, nout) + Dref = num[:nout, :nin, 0] / np.broadcast_to(den, (nout, nin)) + else: + maxn = max(nout, nin) + num = np.zeros((maxn, maxn, 1)) + num[:nout, :nin, 0] = np.reshape( + np.arange(nout * nin), (nout, nin)) + den = np.reshape(np.arange(1, 1 + nin), (nin, 1)) + index = np.repeat(0, nin) + Dref = num[:nout, :nin, 0] / np.broadcast_to(den.T, (nout, nin)) + nr, A, B, C, D = transform.td04ad(rc, nin, nout, index, den, num) + np.testing.assert_equal(nr, 0) + for M in [A, B, C]: + np.testing.assert_equal(M, np.zeros_like(M)) + np.testing.assert_almost_equal(D, Dref) + +def test_mixfeedthrough(): + """Test case popping up from control testing + + a mix of feedthrough and dynamics. The problem from the control + package was somewhere else + """ + num = np.array([ [ [ 0.0, 0.0 ], [ 0.0, -0.2 ] ], + [ [ -0.1, 0.0 ], [ 0.0, 0.0 ] ] ]) + p, m, d = num.shape + numc = np.zeros((max(1, m, p), max(1, m, p), d), dtype=float) + numc[:p,:m,:] = num + denc = np.array([[1.0, 1.1], + [1.0, 0.0]]) + idxc = np.array([1, 0]) + n, A, B, C, D = transform.td04ad('C', 2, 2, idxc, denc, numc) + np.testing.assert_array_almost_equal(D, np.array([[0, 0],[-0.1, 0]])) + +def test_toandfrom(): + A = np.array([[-3.0]]) + B = np.array([[0.1, 0.0]]) + C = np.array([[1.0], + [0.0]]) + D = np.array([[0.0, 0.0], + [0.0, 1.0]]) + + tfout = transform.tb04ad(1, 2, 2, A, B, C, D) + + num = tfout[6] + den = tfout[5] + idxc = np.array([1, 0]) + n, At, Bt, Ct, Dt = transform.td04ad('R', 2, 2, idxc, den, num) + np.testing.assert_array_almost_equal(D, Dt) + np.testing.assert_array_almost_equal(A, At) + +def test_tfm2ss_6(): + """Python version of Fortran test program from + -- Bug in TD04AD when ROWCOL='C' #6 + This bug was fixed in PR #27""" + m = 1 + p = 1 + index = np.array([0]) + dcoeff = np.array([[0.5]]) + ucoeff = np.array([[[32]]]) + n, A, B, C, D = transform.td04ad('R', m, p, index, dcoeff, ucoeff) + assert n == 0 + np.testing.assert_array_almost_equal(D, np.array([[64]])) + n, A, B, C, D = transform.td04ad('C', m, p, index, dcoeff, ucoeff) + assert n == 0 + np.testing.assert_array_almost_equal(D, np.array([[64]])) + diff --git a/slycot/tests/test_tg01ad.py b/slycot/tests/test_tg01ad.py index 3b509204..864d41be 100644 --- a/slycot/tests/test_tg01ad.py +++ b/slycot/tests/test_tg01ad.py @@ -1,11 +1,10 @@ # =================================================== # tg01ad tests -import unittest -from slycot import transform import numpy as np +from numpy.testing import assert_almost_equal, assert_equal, assert_raises -from numpy.testing import assert_raises, assert_almost_equal, assert_equal +from slycot import transform # test1 input parameters @@ -66,20 +65,15 @@ test1_rscale_desired = \ np.array([ 0.1, 0.1, 1.0, 10.0 ]) -class test_tg01ad(unittest.TestCase): - """ test1: Verify tg01ad with input parameters according to example in documentation """ - - def test1_tg01ad(self): - - A,E,B,C,lscale,rscale = transform.tg01ad(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,job=test1_job, thresh=test1_thresh) - assert_almost_equal(A, test1_A_desired) - assert_almost_equal(E, test1_E_desired) - assert_almost_equal(B, test1_B_desired) - assert_almost_equal(C, test1_C_desired) - assert_almost_equal(lscale, test1_lscale_desired) - assert_almost_equal(rscale, test1_rscale_desired) +def test1_tg01ad(): + """Verify tg01ad with input parameters according to example in documentation.""" + A,E,B,C,lscale,rscale = transform.tg01ad(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,job=test1_job, thresh=test1_thresh) -if __name__ == "__main__": - unittest.main() + assert_almost_equal(A, test1_A_desired) + assert_almost_equal(E, test1_E_desired) + assert_almost_equal(B, test1_B_desired) + assert_almost_equal(C, test1_C_desired) + assert_almost_equal(lscale, test1_lscale_desired) + assert_almost_equal(rscale, test1_rscale_desired) diff --git a/slycot/tests/test_tg01fd.py b/slycot/tests/test_tg01fd.py index e80ff6e3..902d1ef2 100644 --- a/slycot/tests/test_tg01fd.py +++ b/slycot/tests/test_tg01fd.py @@ -1,11 +1,10 @@ # =================================================== # tg01fd tests -import unittest -from slycot import transform import numpy as np +from numpy.testing import assert_almost_equal, assert_equal, assert_raises -from numpy.testing import assert_raises, assert_almost_equal, assert_equal +from slycot import transform # test1 input parameters test1_l = 4 @@ -62,51 +61,45 @@ test1_ranke_exp = 3 test1_rnka22_exp = 1 -class test_tg01fd(unittest.TestCase): - - def test1_tg01fd(self): - """ test1: Verify from tg01fd with input parameters according to test in documentation """ - A,E,B,C,ranke,rnka22,Q,Z = transform.tg01fd(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,compq='I',compz='I',joba='T',tol=test1_tol) - assert_almost_equal(A, test1_Aexp) - assert_almost_equal(E, test1_Eexp) - assert_almost_equal(B, test1_Bexp) - assert_almost_equal(C, test1_Cexp) - assert_almost_equal(Q, test1_Qexp) - assert_almost_equal(Z, test1_Zexp) - assert_equal(test1_ranke_exp, ranke) - assert_equal(test1_rnka22_exp, rnka22) - - def test2_tg01fd(self): - """ verify that Q and Z output with compq and compz set to 'U' equals the dot product of Q and Z input and Q and Z output with compq and compz set to 'I' """ - - l = 30 - n = 30 - m = 70 - p = 44 - - np.random.seed(0) - - Ain = np.random.rand(l, n) - Ein = np.random.rand(l, n) - Bin = np.random.rand(n, m) - Cin = np.random.rand(p, n) - Qin = np.random.randn(l,l) - Zin = np.random.randn(n,n) - - A_1,E_1,B_1,C_1,ranke_1,rnka22_1,Q_1,Z_1= transform.tg01fd(l=l,n=n,m=m,p=p,A=Ain,E=Ein,B=Bin,C=Cin,compq='I', compz='I', joba='T', tol=0.0) - - A_2,E_2,B_2,C_2,ranke_2,rnka22_2,Q_2,Z_2= transform.tg01fd(l=l,n=n,m=m,p=p,A=Ain,E=Ein,B=Bin,C=Cin,Q=Qin,Z=Zin,compq='U', compz='U', joba='T', tol=0.0) - - assert_equal(A_1, A_2) - assert_equal(E_1, E_2) - assert_equal(B_1, B_2) - assert_equal(C_1, C_2) - assert_equal(ranke_1, ranke_2) - assert_equal(rnka22_1, rnka22_2) - - assert_almost_equal(np.dot(Qin, Q_1), Q_2) - assert_almost_equal(np.dot(Zin, Z_1), Z_2) - - -if __name__ == "__main__": - unittest.main() +def test1_tg01fd(): + """ test1: Verify from tg01fd with input parameters according to test in documentation """ + A,E,B,C,ranke,rnka22,Q,Z = transform.tg01fd(l=test1_l,n=test1_n,m=test1_m,p=test1_p,A=test1_A,E=test1_E,B=test1_B,C=test1_C,compq='I',compz='I',joba='T',tol=test1_tol) + assert_almost_equal(A, test1_Aexp) + assert_almost_equal(E, test1_Eexp) + assert_almost_equal(B, test1_Bexp) + assert_almost_equal(C, test1_Cexp) + assert_almost_equal(Q, test1_Qexp) + assert_almost_equal(Z, test1_Zexp) + assert_equal(test1_ranke_exp, ranke) + assert_equal(test1_rnka22_exp, rnka22) + +def test2_tg01fd(): + """ verify that Q and Z output with compq and compz set to 'U' equals the dot product of Q and Z input and Q and Z output with compq and compz set to 'I' """ + + l = 30 + n = 30 + m = 70 + p = 44 + + np.random.seed(0) + + Ain = np.random.rand(l, n) + Ein = np.random.rand(l, n) + Bin = np.random.rand(n, m) + Cin = np.random.rand(p, n) + Qin = np.random.randn(l,l) + Zin = np.random.randn(n,n) + + A_1,E_1,B_1,C_1,ranke_1,rnka22_1,Q_1,Z_1= transform.tg01fd(l=l,n=n,m=m,p=p,A=Ain,E=Ein,B=Bin,C=Cin,compq='I', compz='I', joba='T', tol=0.0) + + A_2,E_2,B_2,C_2,ranke_2,rnka22_2,Q_2,Z_2= transform.tg01fd(l=l,n=n,m=m,p=p,A=Ain,E=Ein,B=Bin,C=Cin,Q=Qin,Z=Zin,compq='U', compz='U', joba='T', tol=0.0) + + assert_equal(A_1, A_2) + assert_equal(E_1, E_2) + assert_equal(B_1, B_2) + assert_equal(C_1, C_2) + assert_equal(ranke_1, ranke_2) + assert_equal(rnka22_1, rnka22_2) + + assert_almost_equal(np.dot(Qin, Q_1), Q_2) + assert_almost_equal(np.dot(Zin, Z_1), Z_2) diff --git a/slycot/tests/test_transform.py b/slycot/tests/test_transform.py index fa22c514..928ac5d4 100644 --- a/slycot/tests/test_transform.py +++ b/slycot/tests/test_transform.py @@ -3,10 +3,12 @@ # repagh 0. - m : input int - The number of columns of matrix B. It represents the dimension of - the input vector. m > 0. - p : input int - The number of rows of matrix C. It represents the dimension of - the output vector. p > 0. - maxred : input float - The maximum allowed reduction in the 1-norm of S (in an iteration) - if zero rows or columns are encountered. - If maxred > 0.0, maxred must be larger than one (to enable the norm - reduction). - If maxred <= 0.0, then the value 10.0 for maxred is used. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the system state - matrix A. - B : input rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array must contain the system input - matrix B. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the system output - matrix C. - Optional arguments: - job := 'A' input string(len=1) - Indicates which matrices are involved in balancing, as follows: - = 'A': All matrices are involved in balancing; - = 'B': B and A matrices are involved in balancing; - = 'C': C and A matrices are involved in balancing; - = 'N': B and C matrices are not involved in balancing. - Return objects: - s_norm : float - The 1-norm of the given matrix S is non-zero, the ratio between - the 1-norm of the given matrix and the 1-norm of the balanced matrix. - A : rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array contains the balanced matrix - inv(D)*A*D. - B : rank-2 array('d') with bounds (n,m) - The leading n-by-m part of this array contains the balanced matrix - inv(D)*B. - C : rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array contains the balanced matrix C*D. - scale : rank-1 array('d') with bounds (n) - The scaling factors applied to S. If D(j) is the scaling factor - applied to row and column j, then scale(j) = D(j), for j = 1,...,n. + Parameters + ---------- + n : int + The order of the matrix A, the number of rows of matrix B and + the number of columns of matrix C. It represents the dimension of + the state vector. n > 0. + m : int + The number of columns of matrix B. It represents the dimension of + the input vector. m > 0. + p : int + The number of rows of matrix C. It represents the dimension of + the output vector. p > 0. + maxred : float + The maximum allowed reduction in the 1-norm of S (in an iteration) + if zero rows or columns are encountered. + If maxred > 0.0, maxred must be larger than one (to enable the norm + reduction). + If maxred <= 0.0, then the value 10.0 for maxred is used. + A : (n, n) array_like + The leading n-by-n part of this array must contain the system state + matrix A. + B : (n, m) array_like + The leading n-by-m part of this array must contain the system input + matrix B. + C : (p, n) array_like + The leading p-by-n part of this array must contain the system output + matrix C. + job := {'A', 'B', 'C', 'N'}, optional + Indicates which matrices are involved in balancing, as follows: + = 'A': All matrices are involved in balancing; + = 'B': B and A matrices are involved in balancing; + = 'C': C and A matrices are involved in balancing; + = 'N': B and C matrices are not involved in balancing. + Returns + ------- + s_norm : float + The 1-norm of the given matrix S is non-zero, the ratio between + the 1-norm of the given matrix and the 1-norm of the balanced matrix. + A : (n, n) ndarray + The leading n-by-n part of this array contains the balanced matrix + inv(D)*A*D. + B : (n, m) ndarray + The leading n-by-m part of this array contains the balanced matrix + inv(D)*B. + C : (p ,n) ndarray + The leading p-by-n part of this array contains the balanced matrix C*D. + scale : rank-1 array('d') with bounds (n) + The scaling factors applied to S. If D(j) is the scaling factor + applied to row and column j, then scale(j) = D(j), for j = 1,...,n. """ hidden = ' (hidden by the wrapper)' arg_list = ['job', 'N', 'M', 'P', 'maxred', 'A', 'LDA'+hidden, 'B', @@ -100,6 +100,98 @@ def tb01id(n,m,p,maxred,a,b,c,job='A'): raise_if_slycot_error(out[-1], arg_list) return out[:-1] +def tb01pd(n, m, p, A, B, C, job='M', equil='S', tol=1e-8, ldwork=None): + """Ar, Br, Cr, nr = tb01pd(n,m,p,A,B,C,[job,equil,tol,ldwork]) + + To find a reduced (controllable, observable, or minimal) state- + space representation (Ar,Br,Cr) for any original state-space + representation (A,B,C). The matrix Ar is in upper block + Hessenberg form. + + Parameters + ---------- + n : int + Order of the State-space representation. + m : int + Number of inputs. + p : int + Number of outputs. + A : (n, n) array_like + State dynamics matrix. + B : (n, max(m,p)) array_like + The leading n-by-m part of this array must contain the original + input/state matrix B; the remainder of the leading n-by-max(m,p) + part is used as internal workspace. + C : (p, n) array_like + The leading p-by-n part of this array must contain the original + state/output matrix C; the remainder of the leading max(1,m,p)-by-n + part is used as internal workspace. + job : {'M', 'C', 'O'}, optional + Indicates whether the user wishes to remove the + uncontrollable and/or unobservable parts as follows: + = 'M': Remove both the uncontrollable and unobservable + parts to get a minimal state-space representation; + = 'C': Remove the uncontrollable part only to get a + controllable state-space representation; + = 'O': Remove the unobservable part only to get an + observable state-space representation. + Default is 'M'. + equil : {'S', 'N'}, optional + Specifies whether the user wishes to preliminarily balance + the triplet (A,B,C) as follows: + = 'S': Perform balancing (scaling); + = 'N': Do not perform balancing. + tol : float, optional + The tolerance to be used in rank determination when + transforming (A, B, C). If the user sets tol > 0, then + the given value of tol is used as a lower bound for the + reciprocal condition number. + Default is `1e-8`. + ldwork : int, optional + The length of the cache array. + ldwork >= max( 1, n + max(n, 3*m, 3*p)) + Default is None. + + Returns + ------- + Ar : (nr, nr) ndarray + Contains the upper block Hessenberg state dynamics matrix + Ar of a minimal, controllable, or observable realization + for the original system, depending on the value of JOB, + JOB = 'M', JOB = 'C', or JOB = 'O', respectively. + Br : (nr, m) ndarray + Contains the transformed input/state matrix Br of a + minimal, controllable, or observable realization for the + original system, depending on the value of JOB, JOB = 'M', + JOB = 'C', or JOB = 'O', respectively. If JOB = 'C', only + the first IWORK(1) rows of B are nonzero. + Cr : (p, nr) ndarray + Contains the transformed state/output matrix Cr of a + minimal, C controllable, or observable realization for the + original C system, depending on the value of JOB, JOB = + 'M', C JOB = 'C', or JOB = 'O', respectively. C If JOB = + 'M', or JOB = 'O', only the last IWORK(1) columns C (in + the first NR columns) of C are nonzero. + nr : int + The order of the reduced state-space representation + (Ar,Br,Cr) of a minimal, controllable, or observable + realization for the original system, depending on + JOB = 'M', JOB = 'C', or JOB = 'O'. + """ + hidden = ' (hidden by the wrapper)' + arg_list = ['job', 'equil', 'n','m','p','A','lda'+hidden,'B','ldb'+hidden, + 'C','ldc'+hidden,'nr','tol','iwork'+hidden,'dwork'+hidden, + 'ldwork','info'+hidden] + if ldwork is None: + ldwork = max(1, n+max(n,3*m,3*p)) + elif ldwork < max(1, n+max(n,3*m,3*p)): + raise SlycotParameterError("ldwork is too small", -15) + out = _wrapper.tb01pd(n=n,m=m,p=p,a=A,b=B,c=C, + job=job,equil=equil,tol=tol,ldwork=ldwork) + + raise_if_slycot_error(out[-1], arg_list) + return out[:-1] + def tb03ad(n,m,p,A,B,C,D,leri,equil='N',tol=0.0,ldwork=None): """ A_min,b_min,C_min,nr,index,pcoeff,qcoeff,vcoeff = tb03ad_l(n,m,p,A,B,C,D,leri,[equil,tol,ldwork]) @@ -116,90 +208,98 @@ def tb03ad(n,m,p,A,B,C,D,leri,equil='N',tol=0.0,ldwork=None): Additionally a minimal realization (A_min,B_min,C_min) of the original system (A,B,C) is returned. - Required arguments: - n : input int - The order of the state-space representation, i.e. the order of - the original state dynamics matrix A. n > 0. - m : input int - The number of system inputs. m > 0. - p : input int - The number of system outputs. p > 0. - A : input rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array must contain the original - state dynamics matrix A. - B : input rank-2 array('d') with bounds (n,max(m,p)) - The leading n-by-m part of this array must contain the original - input/state matrix B; the remainder of the leading n-by-max(m,p) - part is used as internal workspace. - C : input rank-2 array('d') with bounds (max(m,p),n) - The leading p-by-n part of this array must contain the original - state/output matrix C; the remainder of the leading max(m,p)-by-n - part is used as internal workspace. - D : input rank-2 array('d') with bounds (max(m,p),max(m,p)) - The leading p-by-m part of this array must contain the original - direct transmission matrix D; the remainder of the leading - max(m,p)-by-max(m,p) part is used as internal workspace. - leri : input string(len=1) - Indicates whether the left polynomial matrix representation or - the right polynomial matrix representation is required. - Optional arguments: - equil := 'N' input string(len=1) - Specifies whether the user wishes to balance the triplet (A,B,C), - before computing a minimal state-space representation, as follows: - = 'S': Perform balancing (scaling); - = 'N': Do not perform balancing. - tol := 0.0 input float - The tolerance to be used in rank determination when transforming - (A, B). If tol <= 0 a default value is used. - ldwork := max(2*n+3*max(m,p), p*(p+2)) input int - The length of the cache array. - ldwork >= max( n + max(n, 3*m, 3*p), pm*(pm + 2)) - where pm = p, if leri = 'L'; - pm = m, if leri = 'R'. - For optimum performance it should be larger. - Return objects: - A_min : rank-2 array('d') with bounds (n,n) - The leading nr-by-nr part of this array contains the upper block - Hessenberg state dynamics matrix A_min of a minimal realization for - the original system. - B_min : rank-2 array('d') with bounds (n,max(m,p)) - The leading nr-by-m part of this array contains the transformed - input/state matrix B_min. - C_min : rank-2 array('d') with bounds (max(m,p),n) - The leading p-by-nr part of this array contains the transformed - state/output matrix C_min. - nr : int - The order of the minimal state-space representation - (A_min,B_min,C_min). - index : rank-1 array('i') with bounds either (p) or (m) - If leri = 'L', index(i), i = 1,2,...,p, contains the maximum degree - of the polynomials in the i-th row of the denominator matrix P(s) - of the left polynomial matrix representation. These elements are - ordered so that index(1) >= index(2) >= ... >= index(p). - If leri = 'R', index(i), i = 1,2,...,m, contains the maximum degree - of the polynomials in the i-th column of the denominator matrix P(s) - of the right polynomial matrix representation. These elements are - ordered so that index(1) >= index(2) >= ... >= index(m). - pcoeff : rank-3 array('d') with bounds either (p,p,n+1) or (m,m,n+1) - If leri = 'L' then porm = p, otherwise porm = m. - The leading porm-by-porm-by-kpcoef part of this array contains - the coefficients of the denominator matrix P(s), where - kpcoef = max(index) + 1. - pcoeff(i,j,k) is the coefficient in s**(index(iorj)-k+1) of - polynomial (i,j) of P(s), where k = 1,2,...,kpcoef; if leri = 'L' - then iorj = I, otherwise iorj = J. Thus for leri = 'L', - P(s) = diag(s**index)*(pcoeff(.,.,1)+pcoeff(.,.,2)/s+...). - qcoeff : rank-3 array('d') with bounds (p,m,n + 1) or (max(m,p),max(m,p)) - If leri = 'L' then porp = m, otherwise porp = p. - If leri = 'L', the leading porm-by-porp-by-kpcoef part of this array - contains the coefficients of the numerator matrix Q(s). - If leri = 'R', the leading porp-by-porm-by-kpcoef part of this array - contains the coefficients of the numerator matrix Q(s). - qcoeff(i,j,k) is defined as for pcoeff(i,j,k). - vcoeff : rank-3 array('d') with bounds (p,n,n+1) or (m,n,n+1) - The leading porm-by-nr-by-kpcoef part of this array contains - the coefficients of the intermediate matrix V(s). - vcoeff(i,j,k) is defined as for pcoeff(i,j,k). + Parameters + ---------- + n : int + The order of the state-space representation, i.e. the order of + the original state dynamics matrix A. n > 0. + m : int + The number of system inputs. m > 0. + p : int + The number of system outputs. p > 0. + A : (n, n) array_like + The leading n-by-n part of this array must contain the original + state dynamics matrix A. + B : (n, max(m,p)) array_like + The leading n-by-m part of this array must contain the original + input/state matrix B; the remainder of the leading n-by-max(m,p) + part is used as internal workspace. + C : (max(m,p), n) + The leading p-by-n part of this array must contain the original + state/output matrix C; the remainder of the leading max(m,p)-by-n + part is used as internal workspace. + D : (max(m,p), max(m,p)) array_like + The leading p-by-m part of this array must contain the original + direct transmission matrix D; the remainder of the leading + max(m,p)-by-max(m,p) part is used as internal workspace. + leri : {'L', 'R'} + Indicates whether the left polynomial matrix representation or + the right polynomial matrix representation is required. + = 'L': A left matrix fraction is required; + = 'R': A right matrix fraction is required. + equil : {'S', 'N'}, optional + Specifies whether the user wishes to balance the triplet (A,B,C), + before computing a minimal state-space representation, as follows: + = 'S': Perform balancing (scaling); + = 'N': Do not perform balancing. + Default is `N`. + tol : float, optional + The tolerance to be used in rank determination when transforming + (A, B). If tol <= 0 a default value is used. + Default is `0.0`. + ldwork : int, optional + The length of the cache array. + ldwork >= max( n + max(n, 3*m, 3*p), pm*(pm + 2)) + where pm = p, if leri = 'L'; + pm = m, if leri = 'R'. + For optimum performance it should be larger. + Default is None. + + Returns + ------- + A_min : (n, n) ndarray + The leading nr-by-nr part of this array contains the upper block + Hessenberg state dynamics matrix A_min of a minimal realization for + the original system. + B_min : (n, max(m,p)) ndarray + The leading nr-by-m part of this array contains the transformed + input/state matrix B_min. + C_min : (max(m,p), n) ndarray + The leading p-by-nr part of this array contains the transformed + state/output matrix C_min. + nr : int + The order of the minimal state-space representation + (A_min,B_min,C_min). + index : (p, ) or (m, ) ndarray + If leri = 'L', index(i), i = 1,2,...,p, contains the maximum degree + of the polynomials in the i-th row of the denominator matrix P(s) + of the left polynomial matrix representation. These elements are + ordered so that index(1) >= index(2) >= ... >= index(p). + If leri = 'R', index(i), i = 1,2,...,m, contains the maximum degree + of the polynomials in the i-th column of the denominator matrix P(s) + of the right polynomial matrix representation. These elements are + ordered so that index(1) >= index(2) >= ... >= index(m). + pcoeff : (p, p, n+1) or (m, m, n+1) ndarray + If leri = 'L' then porm = p, otherwise porm = m. + The leading porm-by-porm-by-kpcoef part of this array contains + the coefficients of the denominator matrix P(s), where + kpcoef = max(index) + 1. + pcoeff(i,j,k) is the coefficient in s**(index(iorj)-k+1) of + polynomial (i,j) of P(s), where k = 1,2,...,kpcoef; if leri = 'L' + then iorj = I, otherwise iorj = J. Thus for leri = 'L', + P(s) = diag(s**index)*(pcoeff(.,.,1)+pcoeff(.,.,2)/s+...). + qcoeff : (p, m, n+1) or (max(m,p), max(m,p), n+1) ndarray + If leri = 'L' then porp = m, otherwise porp = p. + If leri = 'L', the leading porm-by-porp-by-kpcoef part of this array + contains the coefficients of the numerator matrix Q(s). + If leri = 'R', the leading porp-by-porm-by-kpcoef part of this array + contains the coefficients of the numerator matrix Q(s). + qcoeff(i,j,k) is defined as for pcoeff(i,j,k). + vcoeff : (p, n, n+1) or (m, n, n+1) ndarray + The leading porm-by-nr-by-kpcoef part of this array contains + the coefficients of the intermediate matrix V(s). + vcoeff(i,j,k) is defined as for pcoeff(i,j,k). + Raises ------ SlycotArithmeticError @@ -209,7 +309,6 @@ def tb03ad(n,m,p,A,B,C,D,leri,equil='N',tol=0.0,ldwork=None): :info == 2: A singular matrix was encountered during the computation of P(s). - """ hidden = ' (hidden by the wrapper)' arg_list = ['leri', 'equil', 'n', 'm', 'P', 'A', 'LDA'+hidden, 'B', @@ -233,58 +332,54 @@ def tb03ad(n,m,p,A,B,C,D,leri,equil='N',tol=0.0,ldwork=None): def tb04ad(n,m,p,A,B,C,D,tol1=0.0,tol2=0.0,ldwork=None): """ Ar,Br,Cr,nr,denom_degs,denom_coeffs,num_coeffs = tb04ad(n,m,p,A,B,C,D,[tol1,tol2,ldwork]) - Convert a state-space system to a tranfer function or matrix of transfer functions. + Convert a state-space system to a transfer function or matrix of transfer functions. The transfer function is given as rows over common denominators. - Required arguments - ------------------ - - n : integer - state dimension - m : integer - input dimension - p : integer - output dimension - A : rank-2 array, shape(n,n) - state dynamics matrix. - B : rank-2 array, shape (n,m) - input matrix - C : rank-2 array, shape (p,n) - output matri - D : rank-2 array, shape (p,m) - direct transmission matrix - - Optional arguments - ------------------ - - tol1 = 0.0: double - tolerance in determining the transfer function coefficients, - when set to 0, a default value is used - tol2 = 0.0: double - tolerance in separating out a controllable/observable subsystem - of (A,B,C), when set to 0, a default value is used - ldwork : int - The length of the cache array. The default values is - max(1,n*(n+1)+max(n*m+2*n+max(n,p),max(3*m,p))) - - Returns - ------- - - nr : int - state dimension of the controllable subsystem - Ar : rank-2 array, shape(nr,nr) - state dynamics matrix of the controllable subsystem - Br : rank-2 array, shape (nr,m) - input matrix of the controllable subsystem - Cr : rank-2 array, shape (p,nr) - output matri of the controllable subsystem - index : rank-1 array, shape (p) - array of orders of the denominator polynomials - dcoeff : rank-2 array, shape (p,max(index)+1) - array of denominator coefficients - ucoeff : rank-3 array, shape (p,m,max(index)+1) - array of numerator coefficients + Parameters + ---------- + n : int + state dimension + m : int + input dimension + p : int + output dimension + A : (n, n) array_like + state dynamics matrix. + B : (n, m) array_like + input matrix + C : (p, n) array_like + output matrix + D : (p, m) array_like + direct transmission matrix + tol1 : float, optional + tolerance in determining the transfer function coefficients, + when set to 0, a default value is used + Default is `0.0`. + tol2 : float, optional + tolerance in separating out a controllable/observable subsystem + of (A,B,C), when set to 0, a default value is used + Default is `0.0`. + ldwork : int, optional + The length of the cache array. The default values is + max(1,n*(n+1)+max(n*m+2*n+max(n,p),max(3*m,p))) + Default is None. + Returns + ------- + nr : int + state dimension of the controllable subsystem + Ar : (nr, nr) ndarray + state dynamics matrix of the controllable subsystem + Br : (nr, m) ndarray + input matrix of the controllable subsystem + Cr : (p, nr) ndarray + output matrix of the controllable subsystem + index : (p, ) ndarray + array of orders of the denominator polynomials + dcoeff : (p, max(index)+1) ndarray + array of denominator coefficients + ucoeff : (p, m, max(index)+1) ndarray + array of numerator coefficients """ hidden = ' (hidden by the wrapper)' arg_list = ['rowcol','n','m','p','A','lda'+hidden,'B','ldb'+hidden,'C','ldc'+hidden,'D', 'ldd'+hidden, @@ -340,7 +435,7 @@ def tb05ad(n, m, p, jomega, A, B, C, job='NG'): m : int The number of inputs, i.e. the number of columns in the matrix B. - p : int + p : int The number of outputs, i.e. the number of rows in the matrix C. jomega : complex float @@ -349,14 +444,14 @@ def tb05ad(n, m, p, jomega, A, B, C, job='NG'): systems, this is j*omega, where omega is the frequency to be evaluated. For discrete time systems, freq = exp(j*omega*Ts) - A : (n,n) ndarray + A : (n, n) ndarray On entry, this array must contain the state transition matrix A. - B : (n,m) ndarray + B : (n, m) ndarray On entry, this array must contain the input/state matrix B. - C : (p,n) ndarray + C : (p, n) ndarray On entry, of this array must contain the state/output matrix C. - job : {'AG', 'NG', 'NH'} + job : {'AG', 'NG', 'NH'}, optional If job = 'AG' (i.e., 'all', 'general matrix'), the A matrix is first balanced. The balancing transformation is then appropriately applied to matrices B and C. The A matrix @@ -377,72 +472,61 @@ def tb05ad(n, m, p, jomega, A, B, C, job='NG'): Returns ------- - if job = 'AG': - -------------- - At: The A matrix which has been both balanced and - transformed to upper Hessenberg form. The balancing - transforms A according to - A1 = P^-1 * A * P. - The transformation to upper Hessenberg form then yields - At = Q^T * (P^-1 * A * P ) * Q. - Note that the lower triangle of At is in general not zero. - Rather, it contains information on the orthogonal matrix Q - used to transform A1 to Hessenberg form. See docs for lappack - DGEHRD(): - http://www.netlib.org/lapack/explore-3.1.1-html/dgehrd.f.html - However, it does not apparently contain information on P, the - matrix used in the balancing procedure. - - Bt: The matrix B transformed according to - Bt = Q^T * P^-1 * B. - - Ct: The matrix C transformed according to - Ct = C * P * Q - - rcond: RCOND contains an estimate of the reciprocal of the - condition number of matrix H with respect to inversion, where - H = (j*freq * I - A) - - g_jw: complex p-by-m array, which contains the frequency response - matrix G(freq). - - ev: Eigenvalues of the matrix A. - - hinvb : complex n-by-m array, which contains the product - -1 - H B. - - if job = 'NG': - -------------- - At: The matrix A transformed to upper Hessenberg form according - to - At = Q^T * A * Q. - The lower triangle is not zero. It containts info on the - orthoganal transformation. See docs for linpack DGEHRD() - http://www.netlib.org/lapack/explore-3.1.1-html/dgehrd.f.html - - Bt: The matrix B transformed according to - Bt = Q^T * B. - - Ct: The matrix C transformed according to - Ct = C * Q - g_jw: complex array with dim p-by-m which contains the frequency - response matrix G(freq). - - hinvb : complex array with dimension p-by-m. - This array contains the - -1 - product H B. - + if job = 'AG' + ------------- + At : The A matrix which has been both balanced and + transformed to upper Hessenberg form. The balancing + transforms A according to + A1 = P^-1 * A * P. + The transformation to upper Hessenberg form then yields + At = Q^T * (P^-1 * A * P ) * Q. + Note that the lower triangle of At is in general not zero. + Rather, it contains information on the orthogonal matrix Q + used to transform A1 to Hessenberg form. See docs for lappack + DGEHRD(): + http://www.netlib.org/lapack/explore-3.1.1-html/dgehrd.f.html + However, it does not apparently contain information on P, the + matrix used in the balancing procedure. + Bt : The matrix B transformed according to + Bt = Q^T * P^-1 * B. + Ct : The matrix C transformed according to + Ct = C * P * Q + rcond : RCOND contains an estimate of the reciprocal of the + condition number of matrix H with respect to inversion, where + H = (j*freq * I - A) + g_jw : complex p-by-m array, which contains the frequency response + matrix G(freq). + ev : Eigenvalues of the matrix A. + hinvb : complex n-by-m array, which contains the product + -1 + H B. + + if job = 'NG' + ------------- + At : The matrix A transformed to upper Hessenberg form according + to + At = Q^T * A * Q. + The lower triangle is not zero. It containts info on the + orthoganal transformation. See docs for linpack DGEHRD() + http://www.netlib.org/lapack/explore-3.1.1-html/dgehrd.f.html + Bt : The matrix B transformed according to + Bt = Q^T * B. + Ct : The matrix C transformed according to + Ct = C * Q + g_jw : complex array with dim p-by-m which contains the frequency + response matrix G(freq). + hinvb : complex array with dimension p-by-m. + This array contains the + -1 + product H B. if job = 'NH' - -------------- - g_jw: complex p-by-m array which contains the frequency - response matrix G(freq). - - hinvb : complex p-by-m array which contains the - -1 - product H B. + ------------- + g_jw : complex p-by-m array which contains the frequency + response matrix G(freq). + hinvb : complex p-by-m array which contains the + -1 + product H B. Raises ------ @@ -644,65 +728,70 @@ def tc04ad(m,p,index,pcoeff,qcoeff,leri,ldwork=None): respectively. - Required arguments: - m : input int - The number of system inputs. m > 0. - p := len(index) input int - The number of system outputs. p > 0. - index : input rank-1 array('i') with bounds (p) or (m) - If leri = 'L', index(i), i = 1,2,...,p, must contain the maximum - degree of the polynomials in the I-th row of the denominator matrix - P(s) of the given left polynomial matrix representation. - If leri = 'R', index(i), i = 1,2,...,m, must contain the maximum - degree of the polynomials in the I-th column of the denominator - matrix P(s) of the given right polynomial matrix representation. - pcoeff : input rank-3 array('d') with bounds (p,p,*) or (m,m,*) - If leri = 'L' then porm = p, otherwise porm = m. The leading - porm-by-porm-by-kpcoef part of this array must contain - the coefficients of the denominator matrix P(s). pcoeff(i,j,k) is - the coefficient in s**(index(iorj)-K+1) of polynomial (I,J) of P(s), - where k = 1,2,...,kpcoef and kpcoef = max(index) + 1; if leri = 'L' - then iorj = i, otherwise iorj = j. Thus for leri = 'L', - P(s) = diag(s**index)*(pcoeff(.,.,1)+pcoeff(.,.,2)/s+...). - If leri = 'R', pcoeff is modified by the routine but restored on exit. - qcoeff : input rank-3 array('d') with bounds (p,m,*) or (max(m,p),max(m,p),*) - If leri = 'L' then porp = m, otherwise porp = p. The leading - porm-by-porp-by-kpcoef part of this array must contain - the coefficients of the numerator matrix Q(s). - qcoeff(i,j,k) is defined as for pcoeff(i,j,k). - If leri = 'R', qcoeff is modified by the routine but restored on exit. - leri : input string(len=1) - Indicates whether a left polynomial matrix representation or a right - polynomial matrix representation is input as follows: - = 'L': A left matrix fraction is input; - = 'R': A right matrix fraction is input. - Optional arguments: - ldwork := max(m,p)*(max(m,p)+4) input int - The length of the cache array. ldwork >= max(m,p)*(max(m,p)+4) - For optimum performance it should be larger. - Return objects: - n : int - The order of the resulting state-space representation. - That is, n = sum(index). - rcond : float - The estimated reciprocal of the condition number of the leading row - (if leri = 'L') or the leading column (if leri = 'R') coefficient - matrix of P(s). - If rcond is nearly zero, P(s) is nearly row or column non-proper. - A : rank-2 array('d') with bounds (n,n) - The leading n-by-n part of this array contains the state dynamics matrix A. - B : rank-2 array('d') with bounds (n,max(m,p)) - The leading n-by-n part of this array contains the input/state matrix B; - the remainder of the leading n-by-max(m,p) part is used as internal - workspace. - C : rank-2 array('d') with bounds (max(m,p),n) - The leading p-by-n part of this array contains the state/output matrix C; - the remainder of the leading max(m,p)-by-n part is used as internal - workspace. - D : rank-2 array('d') with bounds (max(m,p),max(m,p)) - The leading p-by-m part of this array contains the direct transmission - matrix D; the remainder of the leading max(m,p)-by-max(m,p) part is - used as internal workspace. + Parameters + ---------- + m : int + The number of system inputs. m > 0. + p : int + The number of system outputs. p > 0. + lend(index) + index : (p) or (m) array_like + If leri = 'L', index(i), i = 1,2,...,p, must contain the maximum + degree of the polynomials in the I-th row of the denominator matrix + P(s) of the given left polynomial matrix representation. + If leri = 'R', index(i), i = 1,2,...,m, must contain the maximum + degree of the polynomials in the I-th column of the denominator + matrix P(s) of the given right polynomial matrix representation. + pcoeff : (p,p,*) or (m,m,*) array_like + If leri = 'L' then porm = p, otherwise porm = m. The leading + porm-by-porm-by-kpcoef part of this array must contain + the coefficients of the denominator matrix P(s). pcoeff(i,j,k) is + the coefficient in s**(index(iorj)-K+1) of polynomial (I,J) of P(s), + where k = 1,2,...,kpcoef and kpcoef = max(index) + 1; if leri = 'L' + then iorj = i, otherwise iorj = j. Thus for leri = 'L', + P(s) = diag(s**index)*(pcoeff(.,.,1)+pcoeff(.,.,2)/s+...). + If leri = 'R', pcoeff is modified by the routine but restored on exit. + qcoeff : (p, m, *) or (max(m,p), max(m,p), *) array_like + If leri = 'L' then porp = m, otherwise porp = p. The leading + porm-by-porp-by-kpcoef part of this array must contain + the coefficients of the numerator matrix Q(s). + qcoeff(i,j,k) is defined as for pcoeff(i,j,k). + If leri = 'R', qcoeff is modified by the routine but restored on exit. + leri : {'L', 'R'} + Indicates whether a left polynomial matrix representation or a right + polynomial matrix representation is input as follows: + = 'L': A left matrix fraction is input; + = 'R': A right matrix fraction is input. + ldwork : int, optional + The length of the cache array. ldwork >= max(m,p)*(max(m,p)+4) + For optimum performance it should be larger. + Default is None. + + Returns + ------- + n : int + The order of the resulting state-space representation. + That is, n = sum(index). + rcond : float + The estimated reciprocal of the condition number of the leading row + (if leri = 'L') or the leading column (if leri = 'R') coefficient + matrix of P(s). + If rcond is nearly zero, P(s) is nearly row or column non-proper. + A : (n, n) ndarray + The leading n-by-n part of this array contains the state dynamics matrix A. + B : rank-2 array('d') with bounds (n,max(m,p)) + The leading n-by-n part of this array contains the input/state matrix B; + the remainder of the leading n-by-max(m,p) part is used as internal + workspace. + C : (max(m,p), n) ndarray + The leading p-by-n part of this array contains the state/output matrix C; + the remainder of the leading max(m,p)-by-n part is used as internal + workspace. + D : (max(m,p), max(m,p)) ndarray + The leading p-by-m part of this array contains the direct transmission + matrix D; the remainder of the leading max(m,p)-by-max(m,p) part is + used as internal workspace. + Raises ------ SlycotArithmeticError @@ -730,7 +819,6 @@ def tc04ad(m,p,index,pcoeff,qcoeff,leri,ldwork=None): raise_if_slycot_error(out[-1], arg_list, tc04ad.__doc__, locals()) return out[:-1] - def tc01od(m,p,indlin,pcoeff,qcoeff,leri): """ pcoeff,qcoeff = tc01od_l(m,p,indlim,pcoeff,qcoeff,leri) @@ -739,39 +827,41 @@ def tc01od(m,p,indlin,pcoeff,qcoeff,leri): polynomial matrix representations are of the form Q(s)*inv(P(s)) and inv(P(s))*Q(s) respectively. - Required arguments: - m : input int - The number of system inputs. m > 0. - p : input int - The number of system outputs. p > 0. - indlim : input int - The highest value of k for which pcoeff(.,.,k) and qcoeff(.,.,k) - are to be transposed. - k = kpcoef + 1, where kpcoef is the maximum degree of the polynomials - in P(s). indlim > 0. - pcoeff : input rank-3 array('d') with bounds (p,p,indlim) or (m,m,indlim) - If leri = 'L' then porm = p, otherwise porm = m. - On entry, the leading porm-by-porm-by-indlim part of this array - must contain the coefficients of the denominator matrix P(s). - pcoeff(i,j,k) is the coefficient in s**(indlim-k) of polynomial - (i,j) of P(s), where k = 1,2,...,indlim. - qcoeff : input rank-3 array('d') with bounds (max(m,p),max(m,p),indlim) - On entry, the leading p-by-m-by-indlim part of this array must - contain the coefficients of the numerator matrix Q(s). - qcoeff(i,j,k) is the coefficient in s**(indlim-k) of polynomial - (i,j) of Q(s), where k = 1,2,...,indlim. - leri : input string(len=1) - Return objects: - pcoeff : rank-3 array('d') with bounds (p,p,indlim) - On exit, the leading porm-by-porm-by-indlim part of this array - contains the coefficients of the denominator matrix P'(s) of - the dual system. - qcoeff : rank-3 array('d') with bounds (max(m,p),max(m,p),indlim) - On exit, the leading m-by-p-by-indlim part of the array contains - the coefficients of the numerator matrix Q'(s) of the dual system. - info : int - = 0: successful exit; - < 0: if info = -i, the i-th argument had an illegal value. + Parameters + ---------- + m : int + The number of system inputs. m > 0. + p : int + The number of system outputs. p > 0. + indlim : int + The highest value of k for which pcoeff(.,.,k) and qcoeff(.,.,k) + are to be transposed. + k = kpcoef + 1, where kpcoef is the maximum degree of the polynomials + in P(s). indlim > 0. + pcoeff : (p, p, indlim) or (m, m, indlim) array_like + If leri = 'L' then porm = p, otherwise porm = m. + On entry, the leading porm-by-porm-by-indlim part of this array + must contain the coefficients of the denominator matrix P(s). + pcoeff(i,j,k) is the coefficient in s**(indlim-k) of polynomial + (i,j) of P(s), where k = 1,2,...,indlim. + qcoeff : (max(m,p), max(m,p), indlim) array_like + On entry, the leading p-by-m-by-indlim part of this array must + contain the coefficients of the numerator matrix Q(s). + qcoeff(i,j,k) is the coefficient in s**(indlim-k) of polynomial + (i,j) of Q(s), where k = 1,2,...,indlim. + leri : {'L', 'R'} + = 'L': A left matrix fraction is input; + = 'R': A right matrix fraction is input. + + Returns + ------- + pcoeff : (p, p, indlim) ndarray + On exit, the leading porm-by-porm-by-indlim part of this array + contains the coefficients of the denominator matrix P'(s) of + the dual system. + qcoeff : (max(m,p), max(m,p), indlim) ndarray + On exit, the leading m-by-p-by-indlim part of the array contains + the coefficients of the numerator matrix Q'(s) of the dual system. """ hidden = ' (hidden by the wrapper)' arg_list = ['leri', 'M', 'P', 'indlim', 'pcoeff', 'LDPCO1'+hidden, @@ -793,32 +883,35 @@ def tf01md(n,m,p,N,A,B,C,D,u,x0): To compute the output sequence of a linear time-invariant open-loop system given by its discrete-time state-space model - Required arguments: - n : input int - Order of the State-space representation. - m : input int - Number of inputs. - p : input int - Number of outputs. - N : input int - Number of output samples to be computed. - A : input rank-2 array('d') with bounds (n,n) - State dynamics matrix. - B : input rank-2 array('d') with bounds (n,m) - Input/state matrix. - C : input rank-2 array('d') with bounds (p,n) - State/output matrix. - D : input rank-2 array('d') with bounds (p,m) - Direct transmission matrix. - u : input rank-2 array('d') with bounds (m,N) - Input signal. - x0 : input rank-1 array('d') with bounds (n) - Initial state, at time 0. - Return objects: - xf : rank-1 array('d') with bounds (n) - Final state, at time N+1. - y : rank-2 array('d') with bounds (p,N) - Output signal. + Parameters + ---------- + n : int + Order of the State-space representation. + m : int + Number of inputs. + p : int + Number of outputs. + N : int + Number of output samples to be computed. + A : (n, n) array_like + State dynamics matrix. + B : (n, m) array_like + Input/state matrix. + C : (p, n) array_like + State/output matrix. + D : (p, m) array_like + Direct transmission matrix. + u : (m, n) + Input signal. + x0 : (n, ) array_like + Initial state, at time 0. + + Returns + ------- + xf : (n) ndarray + Final state, at time n+1. + y : (p, n) ndarray + Output signal. """ hidden = ' (hidden by the wrapper)' arg_list = ['n','m','p','ny','A','lda'+hidden,'B','ldb'+hidden, @@ -839,28 +932,31 @@ def tf01rd(n,m,p,N,A,B,C,ldwork=None): All matrices are treated as dense, and hence TF01RD is not intended for large sparse problems. + Parameters + ---------- + n : int + Order of the State-space representation. + m : int + Number of inputs. + p : int + Number of outputs. + N : int + Number of Markov parameters to be computed. + A : (n, n) array_like + State dynamics matrix. + B : (n, m) array_like + Input/state matrix. + C : (p, n) array_like + State/output matrix. + ldwork : int, optional + The length of the array DWORK. + ldwork >= max(1, 2*n*p). - Required arguments: - n : input int - Order of the State-space representation. - m : input int - Number of inputs. - p : input int - Number of outputs. - N : input int - Number of Markov parameters to be computed. - A : input rank-2 array('d') with bounds (n,n) - State dynamics matrix. - B : input rank-2 array('d') with bounds (n,m) - Input/state matrix. - C : input rank-2 array('d') with bounds (p,n) - State/output matrix. - Optional arguments: - ldwork := 2*na*nc input int - Return objects: - H : rank-2 array('d') with bounds (p,N*m) - H[:,(k-1)*m : k*m] contains the k-th Markov parameter, - for k = 1,2...N. + Returns + ------- + H : (p, N*m) ndarray + H[:,(k-1)*m : k*m] contains the k-th Markov parameter, + for k = 1,2...N. """ hidden = ' (hidden by the wrapper)' arg_list = ['n','m','p','N','A','lda'+hidden,'B','ldb'+hidden,'C', @@ -873,86 +969,6 @@ def tf01rd(n,m,p,N,A,B,C,ldwork=None): raise_if_slycot_error(out[-1], arg_list) return out[0] -def tb01pd(n, m, p, A, B, C, job='M', equil='S', tol=1e-8, ldwork=None): - """Ar, Br, Cr, nr = tb01pd(n,m,p,A,B,C,[job,equil,tol,ldwork]) - - To find a reduced (controllable, observable, or minimal) state- - space representation (Ar,Br,Cr) for any original state-space - representation (A,B,C). The matrix Ar is in upper block - Hessenberg form. - - Required arguments: - n : input int - Order of the State-space representation. - m : input int - Number of inputs. - p : input int - Number of outputs. - A : input rank-2 array('d') with bounds (n,n) - State dynamics matrix. - B : input rank-2 array('d') with bounds (n,max(m,p)) - The leading n-by-m part of this array must contain the original - input/state matrix B; the remainder of the leading n-by-max(m,p) - part is used as internal workspace. - C : input rank-2 array('d') with bounds (p,n) - The leading p-by-n part of this array must contain the original - state/output matrix C; the remainder of the leading max(1,m,p)-by-n - part is used as internal workspace. - Optional arguments: - job : input char*1 - Indicates whether the user wishes to remove the - uncontrollable and/or unobservable parts as follows: - = 'M': Remove both the uncontrollable and unobservable - parts to get a minimal state-space representation; - = 'C': Remove the uncontrollable part only to get a - controllable state-space representation; - = 'O': Remove the unobservable part only to get an - observable state-space representation. - equil : input char*1 - Specifies whether the user wishes to preliminarily balance - the triplet (A,B,C) as follows: - = 'S': Perform balancing (scaling); - = 'N': Do not perform balancing. - Return objects: - Ar : output rank-2 array('d') with bounds (nr,nr) - Contains the upper block Hessenberg state dynamics matrix - Ar of a minimal, controllable, or observable realization - for the original system, depending on the value of JOB, - JOB = 'M', JOB = 'C', or JOB = 'O', respectively. - Br : output rank-2 array('d') with bounds (nr,m) - Contains the transformed input/state matrix Br of a - minimal, controllable, or observable realization for the - original system, depending on the value of JOB, JOB = 'M', - JOB = 'C', or JOB = 'O', respectively. If JOB = 'C', only - the first IWORK(1) rows of B are nonzero. - Cr : output rank-2 array('d') with bounds (p,nr) - - Contains the transformed state/output matrix Cr of a - minimal, C controllable, or observable realization for the - original C system, depending on the value of JOB, JOB = - 'M', C JOB = 'C', or JOB = 'O', respectively. C If JOB = - 'M', or JOB = 'O', only the last IWORK(1) columns C (in - the first NR columns) of C are nonzero. - nr : output int - The order of the reduced state-space representation - (Ar,Br,Cr) of a minimal, controllable, or observable - realization for the original system, depending on - JOB = 'M', JOB = 'C', or JOB = 'O'. - """ - hidden = ' (hidden by the wrapper)' - arg_list = ['job', 'equil', 'n','m','p','A','lda'+hidden,'B','ldb'+hidden, - 'C','ldc'+hidden,'nr','tol','iwork'+hidden,'dwork'+hidden, - 'ldwork','info'+hidden] - if ldwork is None: - ldwork = max(1, n+max(n,3*m,3*p)) - elif ldwork < max(1, n+max(n,3*m,3*p)): - raise SlycotParameterError("ldwork is too small", -15) - out = _wrapper.tb01pd(n=n,m=m,p=p,a=A,b=B,c=C, - job=job,equil=equil,tol=tol,ldwork=ldwork) - - raise_if_slycot_error(out[-1], arg_list) - return out[:-1] - def tg01ad(l,n,m,p,A,E,B,C,thresh=0.0,job='A'): """ A,E,B,C,lscale,rscale = tg01ad(l,n,m,p,A,E,B,C,[thresh,job]) @@ -981,64 +997,69 @@ def tg01ad(l,n,m,p,A,E,B,C,thresh=0.0,job='A'): S = ( A-lambda E ). ( C ) - Required arguments: - l : input int - The number of rows of matrices A, B, and E. l >= 0. - n : input int - The number of columns of matrices A, E, and C. n >= 0. - m : input int - The number of columns of matrix B. m >= 0. - p : input int - The number of rows of matrix C. P >= 0. - A : rank-2 array('d') with bounds (l,n) - The leading L-by-N part of this array must - contain the state dynamics matrix A. - E : rank-2 array('d') with bounds (l,n) - The leading L-by-N part of this array must - contain the descriptor matrix E. - B : rank-2 array('d') with bounds (l,m) - The leading L-by-M part of this array must - contain the input/state matrix B. - The array B is not referenced if M = 0. - C : rank-2 array('d') with bounds (p,n) - The leading P-by-N part of this array must - contain the state/output matrix C. - The array C is not referenced if P = 0. - Optional arguments: - job := 'A' input string(len=1) - Indicates which matrices are involved in balancing, as - follows: - = 'A': All matrices are involved in balancing; - = 'B': B, A and E matrices are involved in balancing; - = 'C': C, A and E matrices are involved in balancing; - = 'N': B and C matrices are not involved in balancing. - thresh := 0.0 input float - Threshold value for magnitude of elements: - elements with magnitude less than or equal to - THRESH are ignored for balancing. THRESH >= 0. - Return objects: - A : rank-2 array('d') with bounds (l,n) - The leading L-by-N part of this array contains - the balanced matrix Dl*A*Dr. - E : rank-2 array('d') with bounds (l,n) - The leading L-by-N part of this array contains - the balanced matrix Dl*E*Dr. - B : rank-2 array('d') with bounds (l,m) - If M > 0, the leading L-by-M part of this array - contains the balanced matrix Dl*B. - The array B is not referenced if M = 0. - C : rank-2 array('d') with bounds (p,n) - If P > 0, the leading P-by-N part of this array - contains the balanced matrix C*Dr. - The array C is not referenced if P = 0. - lscale : rank-1 array('d') with bounds (l) - The scaling factors applied to S from left. If Dl(j) is - the scaling factor applied to row j, then - SCALE(j) = Dl(j), for j = 1,...,L. - rscale : rank-1 array('d') with bounds (n) - The scaling factors applied to S from right. If Dr(j) is - the scaling factor applied to column j, then - SCALE(j) = Dr(j), for j = 1,...,N. + + Parameters + ---------- + l : int + The number of rows of matrices A, B, and E. l >= 0. + n : int + The number of columns of matrices A, E, and C. n >= 0. + m : int + The number of columns of matrix B. m >= 0. + p : int + The number of rows of matrix C. P >= 0. + A : (l, n) array_like + The leading L-by-N part of this array must + contain the state dynamics matrix A. + E : (l, n) array_like + The leading L-by-N part of this array must + contain the descriptor matrix E. + B : (l, m) array_like + The leading L-by-M part of this array must + contain the input/state matrix B. + The array B is not referenced if M = 0. + C : (p, n) array_like + The leading P-by-N part of this array must + contain the state/output matrix C. + The array C is not referenced if P = 0. + job : {'A', 'B', 'C', 'N'}, optional + Indicates which matrices are involved in balancing, as + follows: + = 'A': All matrices are involved in balancing; + = 'B': B, A and E matrices are involved in balancing; + = 'C': C, A and E matrices are involved in balancing; + = 'N': B and C matrices are not involved in balancing. + Default is 'A'. + thresh : float, optional + Threshold value for magnitude of elements: + elements with magnitude less than or equal to + THRESH are ignored for balancing. THRESH >= 0. + Default is `0.0`. + + Returns + ------- + A : (l, n) ndarray + The leading L-by-N part of this array contains + the balanced matrix Dl*A*Dr. + E : (l, n) ndarray + The leading L-by-N part of this array contains + the balanced matrix Dl*E*Dr. + B : (l, m) ndarray + If M > 0, the leading L-by-M part of this array + contains the balanced matrix Dl*B. + The array B is not referenced if M = 0. + C : (p, n) ndarray + If P > 0, the leading P-by-N part of this array + contains the balanced matrix C*Dr. + The array C is not referenced if P = 0. + lscale : (l, ) ndarray + The scaling factors applied to S from left. If Dl(j) is + the scaling factor applied to row j, then + SCALE(j) = Dl(j), for j = 1,...,L. + rscale : (n, ) ndarray + The scaling factors applied to S from right. If Dr(j) is + the scaling factor applied to column j, then + SCALE(j) = Dr(j), for j = 1,...,N. """ hidden = ' (hidden by the wrapper)' @@ -1072,132 +1093,141 @@ def tg01fd(l,n,m,p,A,E,B,C,Q=None,Z=None,compq='N',compz='N',joba='N',tol=0.0,ld The left and/or right orthogonal transformations performed to reduce E and A22 can be optionally accumulated. - Required arguments: - l : input int - The number of rows of matrices A, B, and E. l >= 0. - n : input int - The number of columns of matrices A, E, and C. n >= 0. - m : input int - The number of columns of matrix B. m >= 0. - p : input int - The number of rows of matrix C. p >= 0. - A : rank-2 array('d') with bounds (l,n) - The leading l-by-n part of this array must - contain the state dynamics matrix A. - E : rank-2 array('d') with bounds (l,n) - The leading l-by-n part of this array must - contain the descriptor matrix E. - B : rank-2 array('d') with bounds (l,m) - The leading L-by-M part of this array must - contain the input/state matrix B. - C : rank-2 array('d') with bounds (p,n) - The leading P-by-N part of this array must - contain the state/output matrix C. - Optional arguments: - Q : rank-2 array('d') with bounds (l,l) - If COMPQ = 'N': Q is not referenced. - If COMPQ = 'I': Q need not be set. - If COMPQ = 'U': The leading l-by-l part of this - array must contain an orthogonal matrix - Q1. - Z : rank-2 array('d') with bounds (n,n) - If COMPZ = 'N': Z is not referenced. - If COMPZ = 'I': Z need not be set. - If COMPZ = 'U': The leading n-by-n part of this - array must contain an orthogonal matrix - Z1. - compq := 'N' input string(len=1) - = 'N': do not compute Q. - = 'I': Q is initialized to the unit matrix, and the - orthogonal matrix Q is returned. - = 'U': Q must contain an orthogonal matrix Q1 on entry, - and the product Q1*Q is returned. - compz := 'N' input string(len=1) - = 'N': do not compute Z. - = 'I': Z is initialized to the unit matrix, and the - orthogonal matrix Z is returned. - = 'U': Z must contain an orthogonal matrix Z1 on entry, - and the product Z1*Z is returned. - joba := 'N' input string(len=1) - = 'N': do not reduce A22. - = 'R': reduce A22 to a SVD-like upper triangular form. - = 'T': reduce A22 to an upper trapezoidal form. - tol := 0 input float - The tolerance to be used in determining the rank of E - and of A22. If the user sets TOL > 0, then the given - value of TOL is used as a lower bound for the - reciprocal condition numbers of leading submatrices - of R or R22 in the QR decompositions E * P = Q * R of E - or A22 * P22 = Q22 * R22 of A22. - A submatrix whose estimated condition number is less than - 1/TOL is considered to be of full rank. If the user sets - TOL <= 0, then an implicitly computed, default tolerance, - defined by TOLDEF = L*N*EPS, is used instead, where - EPS is the machine precision (see LAPACK Library routine - DLAMCH). TOL < 1. - ldwork : input int - The length of the cache array. - ldwork >= MAX( 1, n+p, MIN(l,n)+MAX(3*n-1,m,l) ). - For optimal performance, ldwork should be larger. - Return objects: - A : rank-2 array('d') with bounds (l,n) - On entry, the leading L-by-N part of this array must - contain the state dynamics matrix A. - On exit, the leading L-by-N part of this array contains - the transformed matrix Q'*A*Z. If JOBA = 'T', this matrix - is in the form - - ( A11 * * ) - Q'*A*Z = ( * Ar X ) , - ( * 0 0 ) - - where A11 is a RANKE-by-RANKE matrix and Ar is a - RNKA22-by-RNKA22 invertible upper triangular matrix. - If JOBA = 'R' then A has the above form with X = 0. - E : rank-2 array('d') with bounds (l,n) - The leading L-by-N part of this array contains - the transformed matrix Q'*E*Z. - - ( Er 0 ) - Q'*E*Z = ( ) , - ( 0 0 ) - - where Er is a RANKE-by-RANKE upper triangular invertible - matrix. - B : rank-2 array('d') with bounds (l,m) - The leading L-by-M part of this array contains - the transformed matrix Q'*B. - C : rank-2 array('d') with bounds (p,n) - The leading P-by-N part of this array contains - the transformed matrix C*Z. - Q : rank-2 array('d') with bounds (l,l) - If COMPQ = 'N': Q is not referenced. - If COMPQ = 'I': The leading L-by-L part of this - array contains the orthogonal matrix Q, - where Q' is the product of Householder - transformations which are applied to A, - E, and B on the left. - If COMPQ = 'U': The leading L-by-L part of this - array contains the orthogonal matrix - Q1*Q. - Z : rank-2 array('d') with bounds (n,n) - If COMPZ = 'N': Z is not referenced. - If COMPZ = 'I': The leading N-by-N part of this - array contains the orthogonal matrix Z, - which is the product of Householder - transformations applied to A, E, and C - on the right. - If COMPZ = 'U': The leading N-by-N part of this - array contains the orthogonal matrix - Z1*Z. - ranke : output int - The estimated rank of matrix E, and thus also the order - of the invertible upper triangular submatrix Er. - rnka22 : output int - If JOBA = 'R' or 'T', then RNKA22 is the estimated rank of - matrix A22, and thus also the order of the invertible - upper triangular submatrix Ar. - If JOBA = 'N', then RNKA22 is not referenced. + Parameters + ---------- + l : int + The number of rows of matrices A, B, and E. l >= 0. + n : int + The number of columns of matrices A, E, and C. n >= 0. + m : int + The number of columns of matrix B. m >= 0. + p : int + The number of rows of matrix C. p >= 0. + A : (l, n) array_like + The leading l-by-n part of this array must + contain the state dynamics matrix A. + E : (l, n) array_like + The leading l-by-n part of this array must + contain the descriptor matrix E. + B : (l, m) array_like + The leading L-by-M part of this array must + contain the input/state matrix B. + C : (p, n) array_like + The leading P-by-N part of this array must + contain the state/output matrix C. + Q : (l, l) array_like, optional + If COMPQ = 'N': Q is not referenced. + If COMPQ = 'I': Q need not be set. + If COMPQ = 'U': The leading l-by-l part of this + array must contain an orthogonal matrix + Q1. + Default is None. + Z : (n, n) array_like, optional + If COMPZ = 'N': Z is not referenced. + If COMPZ = 'I': Z need not be set. + If COMPZ = 'U': The leading n-by-n part of this + array must contain an orthogonal matrix + Z1. + Default is None. + compq : {'N', 'I', 'U'}, optional + = 'N': do not compute Q. + = 'I': Q is initialized to the unit matrix, and the + orthogonal matrix Q is returned. + = 'U': Q must contain an orthogonal matrix Q1 on entry, + and the product Q1*Q is returned. + Default is 'N'. + compz : {'N', 'I', 'U'}, optional + = 'N': do not compute Z. + = 'I': Z is initialized to the unit matrix, and the + orthogonal matrix Z is returned. + = 'U': Z must contain an orthogonal matrix Z1 on entry, + and the product Z1*Z is returned. + Default is 'N'. + joba : {'N', 'R', 'T'}, optional + = 'N': do not reduce A22. + = 'R': reduce A22 to a SVD-like upper triangular form. + = 'T': reduce A22 to an upper trapezoidal form. + Default is 'N'. + tol : float, optional + The tolerance to be used in determining the rank of E + and of A22. If the user sets TOL > 0, then the given + value of TOL is used as a lower bound for the + reciprocal condition numbers of leading submatrices + of R or R22 in the QR decompositions E * P = Q * R of E + or A22 * P22 = Q22 * R22 of A22. + A submatrix whose estimated condition number is less than + 1/TOL is considered to be of full rank. If the user sets + TOL <= 0, then an implicitly computed, default tolerance, + defined by TOLDEF = L*N*EPS, is used instead, where + EPS is the machine precision (see LAPACK Library routine + DLAMCH). TOL < 1. + Default is `0.0`. + ldwork : int, optional + The length of the cache array. + ldwork >= MAX( 1, n+p, MIN(l,n)+MAX(3*n-1,m,l) ). + For optimal performance, ldwork should be larger. + Default is None. + + Returns + ------- + A : (l, n) ndarray + On entry, the leading L-by-N part of this array must + contain the state dynamics matrix A. + On exit, the leading L-by-N part of this array contains + the transformed matrix Q'*A*Z. If JOBA = 'T', this matrix + is in the form + + ( A11 * * ) + Q'*A*Z = ( * Ar X ) , + ( * 0 0 ) + + where A11 is a RANKE-by-RANKE matrix and Ar is a + RNKA22-by-RNKA22 invertible upper triangular matrix. + If JOBA = 'R' then A has the above form with X = 0. + E : (l, n) ndarray + The leading L-by-N part of this array contains + the transformed matrix Q'*E*Z. + + ( Er 0 ) + Q'*E*Z = ( ) , + ( 0 0 ) + + where Er is a RANKE-by-RANKE upper triangular invertible + matrix. + B : (l, m) ndarray + The leading L-by-M part of this array contains + the transformed matrix Q'*B. + C : (p, n) ndarray + The leading P-by-N part of this array contains + the transformed matrix C*Z. + ranke : int + The estimated rank of matrix E, and thus also the order + of the invertible upper triangular submatrix Er. + rnka22 : int + If JOBA = 'R' or 'T', then RNKA22 is the estimated rank of + matrix A22, and thus also the order of the invertible + upper triangular submatrix Ar. + If JOBA = 'N', then RNKA22 is not referenced. + Q : (l, l) ndarray + If COMPQ = 'N': Q is not referenced. + If COMPQ = 'I': The leading L-by-L part of this + array contains the orthogonal matrix Q, + where Q' is the product of Householder + transformations which are applied to A, + E, and B on the left. + If COMPQ = 'U': The leading L-by-L part of this + array contains the orthogonal matrix + Q1*Q. + Z : (n, n) ndarray + If COMPZ = 'N': Z is not referenced. + If COMPZ = 'I': The leading N-by-N part of this + array contains the orthogonal matrix Z, + which is the product of Householder + transformations applied to A, E, and C + on the right. + If COMPZ = 'U': The leading N-by-N part of this + array contains the orthogonal matrix + Z1*Z. """ hidden = ' (hidden by the wrapper)'