linop-0.8.2/0000755000175000017500000000000012320451606015537 5ustar ghislain-debghislain-deb00000000000000linop-0.8.2/setup.py0000644000175000017500000000235612317462353017266 0ustar ghislain-debghislain-deb00000000000000try: from setuptools import setup use_setuptools = True except ImportError: from distutils.core import setup use_setuptools = False if use_setuptools: extra_setup_args = dict( tests_require=['nose', 'numpy', 'scipy'], test_suite="nose.collector", use_2to3=True, zip_safe=False ) else: extra_setup_args = dict() f = open('README.txt') try: README = f.read() finally: f.close() setup( name='linop', version='0.8.2', author='Ghislain Vaillant', author_email='ghisvail@gmail.com', description='A pythonic abstraction for linear mathematical operators', long_description=README, license='BSD', url='https://github.com/ghisvail/linop', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering', 'Topic :: Software Development', ], keywords=['linear', 'operator', 'mathematics'], packages=['linop'], **extra_setup_args ) linop-0.8.2/CHANGES.txt0000644000175000017500000000361112320451117017346 0ustar ghislain-debghislain-deb00000000000000version 0.8.2 ------------- * drop setuptools dependency in setup * drop scipy dependency in linop * drop some old tests version 0.8.1 ------------- * simplify the build system further * make text files formatting compliant with PyPI and debian packaging guidelines * now considered beta quality version 0.8 ----------- * API: adjoint_of becomes default, transpose_of still supported * py3 support: drop dependency on pysparse * source distribution now provides documentation source not a build * switch licensing of the package from LGPL to BSD version 0.7 ----------- * API: rmatvec becomes default, matvec_transp still supported * add .dot() to LinearOperator interface as alias to __mul__ * both .T and .H are provided, leaves choice of notation to the user * typo and bug fixes + update of test suite version 0.6 ----------- * test suite is now completed * PysparseLinearOperator now deprecated in favour of aslinearoperator * add aslinop alias for aslinearoperator version 0.5 ----------- * add support for Python 3 * add shorter aliases for several operators * further simplification of the setup script version 0.4 ----------- * add BlockVert. and BlockHoriz. operators * add aslinearoperator convenience function * fix handling of sysmmetric flag for MatrixOperator * fix support for custom loggin in BlockOperator * fix setup script, now cleaner and uses setuptools * more test coverage * various bug fixes * various pep8 fixes version 0.3 ----------- * add MatrixOperator * add documentation * bug fixes * pep8 fixes version 0.2 ----------- * add test suite for main operators * various bug fixes caught during unit testing * API is now compatible with scipy's interface * real and complex operators have .H, additional .T for real dtype * ensure consistency between operator and operand version 0.1 ----------- * initial import of sources, forked from pykrylov.linop linop-0.8.2/MANIFEST.in0000644000175000017500000000010412317461146017276 0ustar ghislain-debghislain-deb00000000000000include *.txt recursive-include doc * recursive-exclude doc/build * linop-0.8.2/PKG-INFO0000644000175000017500000000545112320451606016641 0ustar ghislain-debghislain-deb00000000000000Metadata-Version: 1.1 Name: linop Version: 0.8.2 Summary: A pythonic abstraction for linear mathematical operators Home-page: https://github.com/ghisvail/linop Author: Ghislain Vaillant Author-email: ghisvail@gmail.com License: BSD Description: ===== linop ===== This project provides a standalone set of classes to abstract the creation and management of linear operators, to be used as a common basis for the development of advanced mathematical frameworks. The code base was originally forked from the `pykrylov project `_ developed by Dominique Orban. This project has added missing features such as Python 3 support, a comprehensive test suite, bug fixes and feature enhancements. Requirements ============ * Python 2 (>=2.6) or 3 (>=3.2) * NumPy Installation ============ Using pip / easy_install (recommended):: pip install linop From the cloned repository or unpacked source distribution:: python setup.py install Documentation ============= The package documentation can be found `here `_. The documentation can be built using Sphinx. Within the root location of the source directory, run:: python setup.py build_sphinx The html documentation will be available in doc/build/html. Changelog ========= See the CHANGES.txt file. Thanks to ========= A list of contributors to the project is kept updated in the AUTHORS.txt file. Contributing ============ The code source is released under a permissive license. Anyone is welcome to contribute to the improvement of the existing code base. Please feel free to submit an issue to the bug tracker, clone the repository and submit your changes by pull-request. The test suite uses `nose `_ and can be run with:: python setup.py test Keywords: linear,operator,mathematics Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Software Development linop-0.8.2/README.txt0000644000175000017500000000306712317461146017251 0ustar ghislain-debghislain-deb00000000000000===== linop ===== This project provides a standalone set of classes to abstract the creation and management of linear operators, to be used as a common basis for the development of advanced mathematical frameworks. The code base was originally forked from the `pykrylov project `_ developed by Dominique Orban. This project has added missing features such as Python 3 support, a comprehensive test suite, bug fixes and feature enhancements. Requirements ============ * Python 2 (>=2.6) or 3 (>=3.2) * NumPy Installation ============ Using pip / easy_install (recommended):: pip install linop From the cloned repository or unpacked source distribution:: python setup.py install Documentation ============= The package documentation can be found `here `_. The documentation can be built using Sphinx. Within the root location of the source directory, run:: python setup.py build_sphinx The html documentation will be available in doc/build/html. Changelog ========= See the CHANGES.txt file. Thanks to ========= A list of contributors to the project is kept updated in the AUTHORS.txt file. Contributing ============ The code source is released under a permissive license. Anyone is welcome to contribute to the improvement of the existing code base. Please feel free to submit an issue to the bug tracker, clone the repository and submit your changes by pull-request. The test suite uses `nose `_ and can be run with:: python setup.py test linop-0.8.2/linop.egg-info/0000755000175000017500000000000012320451606020352 5ustar ghislain-debghislain-deb00000000000000linop-0.8.2/linop.egg-info/top_level.txt0000644000175000017500000000000612320451603023075 0ustar ghislain-debghislain-deb00000000000000linop linop-0.8.2/linop.egg-info/not-zip-safe0000644000175000017500000000000112320447770022607 0ustar ghislain-debghislain-deb00000000000000 linop-0.8.2/linop.egg-info/PKG-INFO0000644000175000017500000000545112320451603021451 0ustar ghislain-debghislain-deb00000000000000Metadata-Version: 1.1 Name: linop Version: 0.8.2 Summary: A pythonic abstraction for linear mathematical operators Home-page: https://github.com/ghisvail/linop Author: Ghislain Vaillant Author-email: ghisvail@gmail.com License: BSD Description: ===== linop ===== This project provides a standalone set of classes to abstract the creation and management of linear operators, to be used as a common basis for the development of advanced mathematical frameworks. The code base was originally forked from the `pykrylov project `_ developed by Dominique Orban. This project has added missing features such as Python 3 support, a comprehensive test suite, bug fixes and feature enhancements. Requirements ============ * Python 2 (>=2.6) or 3 (>=3.2) * NumPy Installation ============ Using pip / easy_install (recommended):: pip install linop From the cloned repository or unpacked source distribution:: python setup.py install Documentation ============= The package documentation can be found `here `_. The documentation can be built using Sphinx. Within the root location of the source directory, run:: python setup.py build_sphinx The html documentation will be available in doc/build/html. Changelog ========= See the CHANGES.txt file. Thanks to ========= A list of contributors to the project is kept updated in the AUTHORS.txt file. Contributing ============ The code source is released under a permissive license. Anyone is welcome to contribute to the improvement of the existing code base. Please feel free to submit an issue to the bug tracker, clone the repository and submit your changes by pull-request. The test suite uses `nose `_ and can be run with:: python setup.py test Keywords: linear,operator,mathematics Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Software Development linop-0.8.2/linop.egg-info/dependency_links.txt0000644000175000017500000000000112320451603024415 0ustar ghislain-debghislain-deb00000000000000 linop-0.8.2/linop.egg-info/SOURCES.txt0000644000175000017500000000060112320451603022230 0ustar ghislain-debghislain-deb00000000000000AUTHORS.txt CHANGES.txt LICENSE.txt MANIFEST.in README.txt setup.cfg setup.py doc/Makefile doc/source/blkop.rst doc/source/conf.py doc/source/index.rst doc/source/intro.rst doc/source/linop.rst linop/__init__.py linop/blkop.py linop/linop.py linop.egg-info/PKG-INFO linop.egg-info/SOURCES.txt linop.egg-info/dependency_links.txt linop.egg-info/not-zip-safe linop.egg-info/top_level.txtlinop-0.8.2/AUTHORS.txt0000644000175000017500000000037412317461146017437 0ustar ghislain-debghislain-deb00000000000000linop ----- Ghislain Vaillant : * bug fixes * feature enhancements * packaging * python 3 support * test suite pykrylov.linop -------------- Dominique Orban : * original author linop-0.8.2/setup.cfg0000644000175000017500000000041412320451606017357 0ustar ghislain-debghislain-deb00000000000000[metadata] description-file = README.md [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [nosetests] verbosity = 1 detailed-errors = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 linop-0.8.2/LICENSE.txt0000644000175000017500000000310012317461056017362 0ustar ghislain-debghislain-deb00000000000000Copyright (c) 2008-2013, Dominique Orban All rights reserved. Copyright (c) 2013-2014, Ghislain Vaillant All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the linop developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. linop-0.8.2/doc/0000755000175000017500000000000012320451606016304 5ustar ghislain-debghislain-deb00000000000000linop-0.8.2/doc/Makefile0000644000175000017500000000445112317461056017756 0ustar ghislain-debghislain-deb00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf build/* html: mkdir -p build/html build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html @echo @echo "Build finished. The HTML pages are in build/html." pickle: mkdir -p build/pickle build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle @echo @echo "Build finished; now you can process the pickle files." web: pickle json: mkdir -p build/json build/doctrees $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: mkdir -p build/htmlhelp build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in build/htmlhelp." latex: mkdir -p build/latex build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex @echo @echo "Build finished; the LaTeX files are in build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p build/changes build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes @echo @echo "The overview file is in build/changes." linkcheck: mkdir -p build/linkcheck build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in build/linkcheck/output.txt." linop-0.8.2/doc/source/0000755000175000017500000000000012320451606017604 5ustar ghislain-debghislain-deb00000000000000linop-0.8.2/doc/source/conf.py0000644000175000017500000001405212320451145021103 0ustar ghislain-debghislain-deb00000000000000# -*- coding: utf-8 -*- # # linop documentation build configuration file, inspired from Pykrylov # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. sys.path.append(os.path.abspath('../../linop')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] extensions += ['sphinx.ext.todo'] extensions += ['sphinx.ext.mathjax'] #extensions += ['mathjax'] mathjax_path = 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML' # Add any paths that contain templates here, relative to this directory. #templates_path = ['.templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'linop' copyright = u'2013, G. Vaillant' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. release = '0.8.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. #exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' #todo_include_todos = True # Options for HTML output # ----------------------- # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. #html_style = 'default.css' html_theme = "agogo" # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['.static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'linopdoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('contents', 'linop.tex', ur'linop documentation', ur'G. Vaillant', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. latex_preamble = '\usepackage{amsfonts}' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True linop-0.8.2/doc/source/linop.rst0000644000175000017500000001347012317461056021472 0ustar ghislain-debghislain-deb00000000000000.. Description of linear operators .. _linop-page: The :mod:`linop` Module ======================= .. automodule:: linop.linop Base Class for Linear Operators ------------------------------- All linear operators derive from the base class ``BaseLinearOperator``. This base class is not meant to be used directly to define linear operators, other than by subclassing to define classes of more specific linear operators. .. autoclass:: BaseLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Linear Operators Defined by Functions ------------------------------------- It is intuitive to define an operator by its *action* on vectors. The ``LinearOperator`` class takes arguments ``matvec`` and ``rmatvec`` to define the action of the operator and of its transpose. Here is a simple example: .. code-block:: python import numpy as np A = LinearOperator(nargin=3, nargout=3, matvec=lambda v: 2*v, symmetric=True) B = LinearOperator(nargin=4, nargout=3, matvec=lambda v: np.arange(3)*v[:3], rmatvec=lambda v: np.concatenate((np.arange(3)*v, np.zeros(1)))) The API also supports using the keyword argument ``matvec_transp`` to replace to maintain compatibility with pysparse-style instantiation. Here, ``A`` represents the operator :math:`2I`, where :math:`I` is the identity and ``B`` could be represented by the matrix .. math:: \begin{bmatrix} 1 & & & \\ & 2 & & \\ & & 3 & 0 \\ \end{bmatrix}. Note that any callable object can be used to pass values for ``matvec`` and ``rmatvec``. For example : .. code-block:: python def func(v): return np.arange(3) * v class MyClass(object): def __call__(self, u): return np.concatenate((np.arange(3)*v, np.zeros(1))) myobject = MyClass() B = LinearOperator(nargin=4, nargout=3, matvec=func, rmatvec=myobject) is perfectly valid. Based on this example, arbitrarily complex operators may be built. .. autoclass:: LinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Simple Common Predefined Linear Operators ----------------------------------------- A few common operators are predefined, such as the identity, the zero operator, and a class for diagonal operators. .. autoclass:: IdentityOperator :show-inheritance: :members: :inherited-members: :undoc-members: .. autoclass:: ZeroOperator :show-inheritance: :members: :inherited-members: :undoc-members: Diagonal operators are simply defined by their diagonal as a Numpy array. For example: .. code-block:: python d = np.random.random(10) D = DiagonalOperator(d) .. autoclass:: DiagonalOperator :show-inheritance: :members: :inherited-members: :undoc-members: Matrix operators wraps calls to the dot and tranposed dot product of the provided Numpy array. For example: .. code-block:: python m = np.arange(12).reshape([4, 3]) M = MatrixLinearOperator(m) .. autoclass:: MatrixLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Convenience Functions --------------------- Typically, linear operators don't come alone and an operator is often used to define other operators. An example is reduction. Suppose :math:`A` is a linear operator from :math:`\mathbb{R}^n` into :math:`\mathbb{R^m}`, :math:`\mathcal{Z}` is a subspace of :math:`\mathbb{R}^n` and :math:`\mathcal{Y}` is a subspace of :math:`\mathbb{R}^m`. Sometimes it is useful to consider :math:`A` restricted to :math:`\mathcal{Z}` and co-restricted to :math:`\mathcal{Y}`. Assuming that :math:`A` is a matrix representing the linear operator and :math:`Z` and :math:`Y` are matrices whose columns form bases of the subspaces :math:`\mathcal{Z}` and :math:`\mathcal{Y}`, respectively, then the restricted operator may be written :math:`Y^T A Z`. A simple version of this type of reduction is where we only consider a subset of the rows and columns of the matrix :math:`A`, which corresponds to subspaces :math:`\mathcal{Z}` and :math:`\mathcal{Y}` aligned with the axes of coordinates. Note that by default, the reduced linear operator is considered to be non-symmetric even if the original operator was symmetric. .. autofunction:: ReducedLinearOperator A special case of this type of reduction is when ``row_indices`` and ``col_indices`` are the same. This is often useful in combination with square symmetric operators. In this case, the reduced operator possesses the same symmetry as the original operator. .. autofunction:: SymmetricallyReducedLinearOperator An obvious use case of linear operators is matrices themselves! The following convenience functions allows to build linear operators from various matrix-like input, such as `Pysparse `_ sparse matrices or Numpy arrays. .. autofunction:: PysparseLinearOperator .. autofunction:: linop_from_ndarray .. autofunction:: aslinearoperator Aliases ------- .. versionadded:: 0.5 Shorter aliases to some linear operators are now available and listed below: * `MatrixOperator` for :class:`MatrixLinearOperator` * `aslinop` for :func:`aslinearoperator` Exceptions ---------- .. autoexception:: ShapeError Operations with operators ------------------------- Linear operators, whether defined by blocks or not, may be added together or composed following the usual rules of linear algebra. An operator may be multiplied by a scalar or by another operator. Operators of the same shape may be added or subtracted. Those operations are essentially free in the sense that a new linear operator results of them, which encapsulates the appropriate rules for multiplication by a vector. It is only when the resulting operator is applied to a vector that the appropriate chain of operations is applied. For example: .. code-block:: python AB = A * B AA = A * A.T G = E + 2 * B.T * B linop-0.8.2/doc/source/intro.rst0000644000175000017500000000245312317461056021503 0ustar ghislain-debghislain-deb00000000000000Introduction ============ When working towards a solution of a linear system :math:`Ax=b`, Krylov methods do not need to know anything structural about the matrix :math:`A`; all they require is the ability to form matrix-vector products :math:`v \mapsto Av` and, possibly, products with the transpose :math:`u \mapsto A^T u`. In essence, we do not even need the *operator* :math:`A` to be represented by a matrix at all; we simply consider it as a linear function. In PyKrylov, such linear functions can be conveniently packaged as ``LinearOperator`` objects. If ``A`` is an instance of ``LinearOperator`` and represents the "matrix" :math:`A` above, we may computes matrix-vector products by simply writing ``A*v``, where ``v`` is a Numpy array of appropriate size. Similarly, if a Krylov method requires access to the transpose operator :math:`A^T`, it is conveniently available as ``A.T`` and products may be computed using, e.g., ``A.T * u``. If ``A`` represents a symmetric operator :math:`A = A^T`, then ``A.T`` is simply a reference to ``A`` itself. More generally, since :math:`(A^T)^T = A`, the Python statement ``A.T.T is A`` always evaluates to ``True``, which means that they are the *same* object. In the next two sections, we describe generic linear operators and linear operators constructed by blocks. linop-0.8.2/doc/source/index.rst0000644000175000017500000000056512317461056021461 0ustar ghislain-debghislain-deb00000000000000=================== linop documentation =================== :Release: |version| :Date: |today| .. module:: linop Contents ======== .. toctree:: Introduction Linear Operators Block Linear Operators .. TODO List .. ========= .. .. todolist:: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` linop-0.8.2/doc/source/blkop.rst0000644000175000017500000001113612317461056021455 0ustar ghislain-debghislain-deb00000000000000The :mod:`blkop` Module ======================= Linear operators are sometimes defined by blocks. This is often the case in numerical optimization and the solution of partial-differential equations. An example of operator defined by blocks is .. math:: K = \begin{bmatrix} A & B \\ C & D \end{bmatrix} where :math:`A`, :math:`B`, :math:`C` and :math:`D` are linear operators (perhaps themselves defined by blocks) of appropriate shape. The general class ``BlockLinearOperator`` may be used to represent the operator above. If more structure is present, for example if the off-diagonal blocks are zero, :math:`K` is a block-diagonal operator and the class ``BlockDiagonalLinearOperator`` may be used to define it. .. automodule:: linop.blkop General Block Operators ----------------------- General block operators are defined using a list of lists, each of which defines a block row. If the block operator is specified as symmetric, each block on the diagonal must be symmetric. For example: .. code-block:: python A = LinearOperator(nargin=3, nargout=3, matvec=lambda v: 2*v, symmetric=True) B = LinearOperator(nargin=4, nargout=3, matvec=lambda v: v[:3], rmatvec=lambda v: np.concatenate((v, np.zeros(1)))) C = LinearOperator(nargin=3, nargout=2, matvec=lambda v: v[:2], rmatvec=lambda v: np.concatenate((v, np.zeros(1)))) D = LinearOperator(nargin=4, nargout=2, matvec=lambda v: v[:2], rmatvec=lambda v: np.concatenate((v, np.zeros(2)))) E = LinearOperator(nargin=4, nargout=4, matvec=lambda v: -v, symmetric=True) # Build [A B]. K1 = BlockLinearOperator([[A, B]]) # Build [A B] # [C D]. K2 = BlockLinearOperator([[A, B], [C, D]]) # Build [A] # [C]. K3 = BlockLinearOperator([[A], [C]]) # Build [A B] # [B' E]. K4 = BlockLinearOperator([[A, B], [E]], symmetric=True) .. autoclass:: BlockLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Block Diagonal Operators ------------------------ Block diagonal operators are a special case of block operators and are defined with a list containing the blocks on the diagonal. If the block operator is specified as symmetric, each block must be symmetric. For example: .. code-block:: python K5 = BlockDiagonalLinearOperator([A, E], symmetric=True) .. autoclass:: BlockDiagonalLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Block Vertical and Horizontal Operators --------------------------------------- Block vertical and horizontal operators are special cases of the generic block operator, where the list of blocks are either stacked vertically or horizontally. They must be defined in a flattened list. .. code-block:: python # same result as K1 K6 = BlockHorizontalLinearOperator([A, B]) # same result as K3 K7 = BlockVerticalLinearOperator([A, C]) .. autoclass:: BlockHorizontalLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: .. autoclass:: BlockVerticalLinearOperator :show-inheritance: :members: :inherited-members: :undoc-members: Aliases ------- .. versionadded:: 0.5 Shorter aliases to some linear operators are now available and listed below: * `BlockOperator` for :class:`BlockLinearOperator` * `BlockDiagonalOperator` for :class:`BlockDiagonalLinearOperator` * `BlockHorizontalOperator` for :class:`BlockHorizontalLinearOperator` * `BlockVerticalOperator` for :class:`BlockVerticalLinearOperator` Iterating and indexing ---------------------- Block operators also support iteration and indexing. Iterating over a block operator amounts to iterating row-wise over its blocks. Iterating over a block diagonal operator amounts to iterating over its diagonal blocks. Indexing works as expected. Indexing general block operators requires two indices, much as when indexing a matrix, while indexing a block diagonal operator requires a single indices. For example: .. code-block:: python K2 = BlockLinearOperator([[A, B], [C, D]]) K2[0,:] # Returns the block operator defined by [[A, B]]. K2[:,1] # Returns the block operator defined by [[C], [D]]. K2[1,1] # Returns the linear operator D. K4 = BlockLinearOperator([[A, B], [E]], symmetric=True) K4[0,1] # Returns the linear operator B.T. K5 = BlockDiagonalLinearOperator([A, E], symmetric=True) K5[0] # Returns the linear operator A. K5[1] # Returns the linear operator B. K5[:] # Returns the diagonal operator defines by [A, E]. linop-0.8.2/linop/0000755000175000017500000000000012320451606016660 5ustar ghislain-debghislain-deb00000000000000linop-0.8.2/linop/__init__.py0000644000175000017500000000327712317461056021010 0ustar ghislain-debghislain-deb00000000000000#Copyright (c) 2008-2013, Dominique Orban #All rights reserved. # #Copyright (c) 2013-2014, Ghislain Vaillant #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions #are met: #1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. #2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. #3. Neither the name of the linop developers nor the names of any contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE #ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE #FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS #OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) #HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY #OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF #SUCH DAMAGE. from .linop import * from .blkop import * __all__ = [s for s in dir() if not s.startswith('_')] linop-0.8.2/linop/blkop.py0000644000175000017500000002735012317461056020356 0ustar ghislain-debghislain-deb00000000000000#Copyright (c) 2008-2013, Dominique Orban #All rights reserved. # #Copyright (c) 2013-2014, Ghislain Vaillant #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions #are met: #1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. #2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. #3. Neither the name of the linop developers nor the names of any contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE #ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE #FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS #OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) #HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY #OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF #SUCH DAMAGE. from .linop import BaseLinearOperator, LinearOperator from .linop import ShapeError, null_log import numpy as np import itertools from functools import reduce class BlockLinearOperator(LinearOperator): """ A linear operator defined by blocks. Each block must be a linear operator. `blocks` should be a list of lists describing the blocks row-wise. If there is only one block row, it should be specified as `[[b1, b2, ..., bn]]`, not as `[b1, b2, ..., bn]`. If the overall linear operator is symmetric, only its upper triangle need be specified, e.g., `[[A,B,C], [D,E], [F]]`, and the blocks on the diagonal must be square and symmetric. """ def __init__(self, blocks, symmetric=False, **kwargs): # If building a symmetric operator, fill in the blanks. # They're just references to existing objects. try: for block_row in blocks: for block_col in block_row: op_shape = block_col.shape except (TypeError, AttributeError): raise ValueError('blocks should be a nested list of operators') if symmetric: nrow = len(blocks) ncol = len(blocks[0]) if nrow != ncol: raise ShapeError('Inconsistent shape.') for block_row in blocks: if not block_row[0].symmetric: raise ValueError('Blocks on diagonal must be symmetric.') self._blocks = blocks[:] for i in range(1, nrow): for j in range(i - 1, -1, -1): self._blocks[i].insert(0, self._blocks[j][i].T) else: self._blocks = blocks log = kwargs.get('logger', null_log) log.debug('Building new BlockLinearOperator') nargins = [[blk.shape[-1] for blk in row] for row in self._blocks] log.debug('nargins = ' + repr(nargins)) nargins_by_row = [nargin[0] for nargin in nargins] if min(nargins_by_row) != max(nargins_by_row): raise ShapeError('Inconsistent block shapes') nargouts = [[blk.shape[0] for blk in row] for row in self._blocks] log.debug('nargouts = ' + repr(nargouts)) for row in nargouts: if min(row) != max(row): raise ShapeError('Inconsistent block shapes') nargin = sum(nargins[0]) nargout = sum([out[0] for out in nargouts]) # Create blocks of transpose operator. blocksT = list(map(lambda *row: [blk.T for blk in row], *self._blocks)) def blk_matvec(x, blks): nargins = [[blk.shape[-1] for blk in blkrow] for blkrow in blks] nargouts = [[blk.shape[0] for blk in blkrow] for blkrow in blks] nargin = sum(nargins[0]) nargout = sum([out[0] for out in nargouts]) nx = len(x) self.logger.debug('Multiplying with a vector of size %d' % nx) self.logger.debug('nargin=%d, nargout=%d' % (nargin, nargout)) if nx != nargin: raise ShapeError('Multiplying with vector of wrong shape.') result_type = np.result_type(self.dtype, x.dtype) y = np.zeros(nargout, dtype=result_type) nblk_row = len(blks) nblk_col = len(blks[0]) row_start = col_start = 0 for row in range(nblk_row): row_end = row_start + nargouts[row][0] yout = y[row_start:row_end] for col in range(nblk_col): col_end = col_start + nargins[0][col] xin = x[col_start:col_end] B = blks[row][col] yout[:] += B * xin col_start = col_end row_start = row_end col_start = 0 return y flat_blocks = list(itertools.chain(*blocks)) blk_dtypes = [blk.dtype for blk in flat_blocks] op_dtype = np.result_type(*blk_dtypes) super(BlockLinearOperator, self).__init__( nargin, nargout, symmetric=symmetric, matvec=lambda x: blk_matvec(x, self._blocks), rmatvec=lambda x: blk_matvec(x, blocksT), dtype=op_dtype, **kwargs) self.H._blocks = blocksT @property def blocks(self): """The list of blocks defining the block operator.""" return self._blocks def __getitem__(self, indices): blks = np.matrix(self._blocks, dtype=object)[indices] # If indexing narrowed it down to a single block, return it. if isinstance(blks, BaseLinearOperator): return blks # Otherwise, we have a matrix of blocks. return BlockLinearOperator(blks.tolist(), symmetric=False) def __contains__(self, op): flat_blocks = list(itertools.chain(*self.blocks)) return op in flat_blocks def __iter__(self): for block in self._blocks: yield block class BlockDiagonalLinearOperator(LinearOperator): """ A block diagonal linear operator. Each block must be a linear operator. The blocks may be specified as one list, e.g., `[A, B, C]`. """ def __init__(self, blocks, **kwargs): try: for block in blocks: op_shape = block.shape except (TypeError, AttributeError): raise ValueError('blocks should be a flattened list of operators') symmetric = reduce( lambda x, y: x and y, [blk.symmetric for blk in blocks]) self._blocks = blocks log = kwargs.get('logger', null_log) log.debug('Building new BlockDiagonalLinearOperator') nargins = [blk.shape[-1] for blk in blocks] log.debug('nargins = ' + repr(nargins)) nargouts = [blk.shape[0] for blk in blocks] log.debug('nargouts = ' + repr(nargouts)) nargin = sum(nargins) nargout = sum(nargouts) # Create blocks of transpose operator. blocksT = [blk.T for blk in blocks] def blk_matvec(x, blks): nx = len(x) nargins = [blk.shape[-1] for blk in blocks] nargin = sum(nargins) nargouts = [blk.shape[0] for blk in blocks] nargout = sum(nargouts) self.logger.debug('Multiplying with a vector of size %d' % nx) self.logger.debug('nargin=%d, nargout=%d' % (nargin, nargout)) if nx != nargin: raise ShapeError('Multiplying with vector of wrong shape.') result_type = np.result_type(self.dtype, x.dtype) y = np.empty(nargout, dtype=result_type) nblks = len(blks) row_start = col_start = 0 for blk in range(nblks): row_end = row_start + nargouts[blk] yout = y[row_start:row_end] col_end = col_start + nargins[blk] xin = x[col_start:col_end] B = blks[blk] yout[:] = B * xin col_start = col_end row_start = row_end return y blk_dtypes = [blk.dtype for blk in blocks] op_dtype = np.result_type(*blk_dtypes) super(BlockDiagonalLinearOperator, self).__init__( nargin, nargout, symmetric=symmetric, matvec=lambda x: blk_matvec(x, self._blocks), rmatvec=lambda x: blk_matvec(x, blocksT), dtype=op_dtype, **kwargs) self.H._blocks = blocksT @property def blocks(self): """The list of blocks defining the block diagonal operator.""" return self._blocks def __getitem__(self, idx): blks = self._blocks[idx] if isinstance(idx, slice): return BlockDiagonalLinearOperator(blks, symmetric=self.symmetric) return blks def __setitem__(self, idx, ops): if not isinstance(ops, BaseLinearOperator): if isinstance(ops, list) or isinstance(ops, tuple): for op in ops: if not isinstance(op, BaseLinearOperator): msg = 'Block operators can only contain' msg += ' linear operators' raise ValueError(msg) self._blocks[idx] = ops class BlockPreconditioner(BlockLinearOperator): """An alias for ``BlockLinearOperator``. Holds an additional ``solve`` method equivalent to ``__mul__``. """ def solve(self, x): """An alias to __call__.""" return self.__call__(x) class BlockDiagonalPreconditioner(BlockDiagonalLinearOperator): """ An alias for ``BlockDiagonalLinearOperator``. Holds an additional ``solve`` method equivalent to ``__mul__``. """ def solve(self, x): """An alias to __call__.""" return self.__call__(x) class BlockHorizontalLinearOperator(BlockLinearOperator): """ A block horizontal linear operator. Each block must be a linear operator. The blocks must be specified as one list, e.g., `[A, B, C]`. """ def __init__(self, blocks, **kwargs): try: for block in blocks: op_shape = block.shape except (TypeError, AttributeError): raise ValueError('blocks should be a flattened list of operators') blocks=[[blk for blk in blocks]] super(BlockHorizontalLinearOperator, self).__init__( blocks=blocks, symmetric=False, **kwargs) class BlockVerticalLinearOperator(BlockLinearOperator): """ A block vertical linear operator. Each block must be a linear operator. The blocks must be specified as one list, e.g., `[A, B, C]`. """ def __init__(self, blocks, **kwargs): try: for block in blocks: op_shape = block.shape except (TypeError, AttributeError): raise ValueError('blocks should be a flattened list of operators') blocks=[[blk] for blk in blocks] super(BlockVerticalLinearOperator, self).__init__( blocks=blocks, symmetric=False, **kwargs) # some shorter aliases BlockOperator = BlockLinearOperator BlockDiagonalOperator = BlockDiagonalLinearOperator BlockHorizontalOperator = BlockHorizontalLinearOperator BlockVerticalOperator = BlockVerticalLinearOperator linop-0.8.2/linop/linop.py0000644000175000017500000005072212320450505020356 0ustar ghislain-debghislain-deb00000000000000#Copyright (c) 2008-2013, Dominique Orban #All rights reserved. # #Copyright (c) 2013-2014, Ghislain Vaillant #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions #are met: #1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. #2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. #3. Neither the name of the linop developers nor the names of any contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE #ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE #FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS #OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) #HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY #OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF #SUCH DAMAGE. from __future__ import division import numpy as np import logging __docformat__ = 'restructuredtext' # Default (null) logger. null_log = logging.getLogger('linop') null_log.setLevel(logging.INFO) null_log.addHandler(logging.NullHandler()) class BaseLinearOperator(object): """ Base class defining the common interface shared by all linear operators. A linear operator is a linear mapping x -> A(x) such that the size of the input vector x is `nargin` and the size of the output is `nargout`. It can be visualized as a matrix of shape (`nargout`, `nargin`). Its type is any valid Numpy `dtype`. By default, it has `dtype` `numpy.float` but this can be changed to, e.g., `numpy.complex` via the `dtype` keyword argument and attribute. A logger may be attached to the linear operator via the `logger` keyword argument. """ def __init__(self, nargin, nargout, symmetric=False, **kwargs): self.__nargin = nargin self.__nargout = nargout self.__symmetric = symmetric self.__shape = (nargout, nargin) self.__dtype = kwargs.get('dtype', np.float) self._nMatvec = 0 # Log activity. self.logger = kwargs.get('logger', null_log) self.logger.info('New linear operator with shape ' + str(self.shape)) return @property def nargin(self): """The size of an input vector.""" return self.__nargin @property def nargout(self): """The size of an output vector.""" return self.__nargout @property def symmetric(self): """Indicate whether the operator is symmetric or not.""" return self.__symmetric @property def shape(self): """The shape of the operator.""" return self.__shape @property def dtype(self): """The data type of the operator.""" return self.__dtype @property def nMatvec(self): """The number of products with vectors computed so far.""" return self._nMatvec def reset_counters(self): """Reset operator/vector product counter to zero.""" self._nMatvec = 0 def __call__(self, *args, **kwargs): # An alias for __mul__. return self.__mul__(*args, **kwargs) def __mul__(self, x): raise NotImplementedError('Please subclass to implement __mul__.') def __repr__(self): if self.symmetric: s = 'Symmetric' else: s = 'Unsymmetric' s += ' <' + self.__class__.__name__ + '>' s += ' of type %s' % self.dtype s += ' with shape (%d,%d)' % (self.nargout, self.nargin) return s def dot(self, x): """Numpy-like dot() method.""" return self.__mul__(x) class LinearOperator(BaseLinearOperator): """ Generic linear operator class. A linear operator constructed from a `matvec` and (possibly) a `rmatvec` function. If `symmetric` is `True`, `rmatvec` is ignored. All other keyword arguments are passed directly to the superclass. """ def __init__(self, nargin, nargout, matvec, rmatvec=None, **kwargs): super(LinearOperator, self).__init__(nargin, nargout, **kwargs) adjoint_of = (kwargs.get('adjoint_of', None) or kwargs.get('transpose_of', None)) rmatvec = rmatvec or kwargs.get('matvec_transp', None) self.__matvec = matvec if self.symmetric: self.__H = self else: if adjoint_of is None: if rmatvec is not None: # Create 'pointer' to transpose operator. self.__H = LinearOperator(nargout, nargin, matvec=rmatvec, rmatvec=matvec, adjoint_of=self, **kwargs) else: self.__H = None else: # Use operator supplied as transpose operator. if isinstance(adjoint_of, BaseLinearOperator): self.__H = adjoint_of else: msg = 'kwarg adjoint_of / transpose_of must be of type LinearOperator.' msg += ' Got ' + str(adjoint_of.__class__) raise ValueError(msg) @property def T(self): """The transpose operator. .. note:: this is an alias to the adjoint operator """ return self.__H @property def H(self): """The adjoint operator.""" return self.__H def matvec(self, x): """ Matrix-vector multiplication. The matvec property encapsulates the matvec routine specified at construct time, to ensure the consistency of the input and output arrays with the operator's shape. """ x = np.asanyarray(x) M, N = self.shape # check input data consistency try: x = x.reshape(N) except ValueError: msg = 'input array size incompatible with operator dimensions' raise ValueError(msg) y = self.__matvec(x) # check output data consistency try: y = y.reshape(M) except ValueError: msg = 'output array size incompatible with operator dimensions' raise ValueError(msg) return y def to_array(self): n, m = self.shape H = np.empty((n, m)) for j in range(m): ej = np.zeros(m) ej[j] = 1.0 H[:, j] = self * ej return H def __mul_scalar(self, x): """Product between a linear operator and a scalar.""" result_type = np.result_type(self.dtype, type(x)) if x != 0: def matvec(y): return x * (self(y)) def rmatvec(y): return x * (self.H(y)) return LinearOperator(self.nargin, self.nargout, symmetric=self.symmetric, matvec=matvec, rmatvec=rmatvec, dtype=result_type) else: return ZeroOperator(self.nargin, self.nargout, dtype=result_type) def __mul_linop(self, op): """Product between two linear operators.""" if self.nargin != op.nargout: raise ShapeError('Cannot multiply operators together') def matvec(x): return self(op(x)) def rmatvec(x): return op.T(self.H(x)) result_type = np.result_type(self.dtype, op.dtype) return LinearOperator(op.nargin, self.nargout, symmetric=False, # Generally. matvec=matvec, rmatvec=rmatvec, dtype=result_type) def __mul_vector(self, x): """Product between a linear operator and a vector.""" self._nMatvec += 1 result_type = np.result_type(self.dtype, x.dtype) return self.matvec(x).astype(result_type) def __mul__(self, x): if np.isscalar(x): return self.__mul_scalar(x) elif isinstance(x, BaseLinearOperator): return self.__mul_linop(x) elif isinstance(x, np.ndarray): return self.__mul_vector(x) else: raise ValueError('Cannot multiply') def __rmul__(self, x): if np.isscalar(x): return self.__mul__(x) raise ValueError('Cannot multiply') def __add__(self, other): if not isinstance(other, BaseLinearOperator): raise ValueError('Cannot add') if self.shape != other.shape: raise ShapeError('Cannot add') def matvec(x): return self(x) + other(x) def rmatvec(x): return self.H(x) + other.T(x) result_type = np.result_type(self.dtype, other.dtype) return LinearOperator(self.nargin, self.nargout, symmetric=self.symmetric and other.symmetric, matvec=matvec, rmatvec=rmatvec, dtype=result_type) def __neg__(self): return self * (-1) def __sub__(self, other): if not isinstance(other, BaseLinearOperator): raise ValueError('Cannot add') if self.shape != other.shape: raise ShapeError('Cannot add') def matvec(x): return self(x) - other(x) def rmatvec(x): return self.H(x) - other.T(x) result_type = np.result_type(self.dtype, other.dtype) return LinearOperator(self.nargin, self.nargout, symmetric=self.symmetric and other.symmetric, matvec=matvec, rmatvec=rmatvec, dtype=result_type) def __truediv__(self, other): if np.isscalar(other): return self * (1 / other) else: raise ValueError('Cannot divide') def __pow__(self, other): if not isinstance(other, int): raise ValueError('Can only raise to integer power') if other < 0: raise ValueError('Can only raise to nonnegative power') if self.nargin != self.nargout: raise ShapeError('Can only raise square operators to a power') if other == 0: return IdentityOperator(self.nargin) if other == 1: return self return self * self ** (other - 1) class IdentityOperator(LinearOperator): """Class representing the identity operator of size `nargin`.""" def __init__(self, nargin, **kwargs): if 'symmetric' in kwargs: kwargs.pop('symmetric') if 'matvec' in kwargs: kwargs.pop('matvec') super(IdentityOperator, self).__init__(nargin, nargin, symmetric=True, matvec=lambda x: x, **kwargs) class DiagonalOperator(LinearOperator): """ Class representing a diagonal operator. A diagonal linear operator defined by its diagonal `diag` (a Numpy array.) The type must be specified in the `diag` argument, e.g., `np.ones(5, dtype=np.complex)` or `np.ones(5).astype(np.complex)`. """ def __init__(self, diag, **kwargs): if 'symmetric' in kwargs: kwargs.pop('symmetric') if 'matvec' in kwargs: kwargs.pop('matvec') if 'dtype' in kwargs: kwargs.pop('dtype') diag = np.asarray(diag) if diag.ndim != 1: msg = "diag array must be 1-d" raise ValueError(msg) super(DiagonalOperator, self).__init__(diag.shape[0], diag.shape[0], symmetric=True, matvec=lambda x: diag * x, dtype=diag.dtype, **kwargs) class MatrixLinearOperator(LinearOperator): """ Class representing a matrix operator. A linear operator wrapping the multiplication with a matrix and its transpose (real) or conjugate transpose (complex). The operator's dtype is the same as the specified `matrix` argument. .. versionadded:: 0.3 """ def __init__(self, matrix, **kwargs): if 'symmetric' in kwargs: kwargs.pop('symmetric') if 'matvec' in kwargs: kwargs.pop('matvec') if 'dtype' in kwargs: kwargs.pop('dtype') if not hasattr(matrix, 'shape'): matrix = np.asanyarray(matrix) if matrix.ndim != 2: msg = "matrix must be 2-d (shape can be [M, N], [M, 1] or [1, N])" raise ValueError(msg) matvec = matrix.dot iscomplex = issubclass(np.dtype(matrix.dtype).type, np.complex) symmetric = (np.all(matrix == matrix.conj().T) if iscomplex else np.all(matrix == matrix.T)) if not symmetric: rmatvec = (matrix.conj().T.dot if iscomplex else matrix.T.dot) else: rmatvec = None super(MatrixLinearOperator, self).__init__(matrix.shape[1], matrix.shape[0], symmetric=symmetric, matvec=matvec, rmatvec=rmatvec, dtype=matrix.dtype, **kwargs) class ZeroOperator(LinearOperator): """Class representing the zero operator of shape `nargout`-by-`nargin`.""" def __init__(self, nargin, nargout, **kwargs): if 'matvec' in kwargs: kwargs.pop('matvec') if 'rmatvec' in kwargs: kwargs.pop('rmatvec') def matvec(x): if x.shape != (nargin,): msg = 'Input has shape ' + str(x.shape) msg += ' instead of (%d,)' % self.nargin raise ValueError(msg) return np.zeros(nargout) def rmatvec(x): if x.shape != (nargout,): msg = 'Input has shape ' + str(x.shape) msg += ' instead of (%d,)' % self.nargout raise ValueError(msg) return np.zeros(nargin) super(ZeroOperator, self).__init__(nargin, nargout, matvec=matvec, rmatvec=rmatvec, **kwargs) def ReducedLinearOperator(op, row_indices, col_indices): """ Implement reduction of a linear operator (non symmetrical). Reduce a linear operator by limiting its input to `col_indices` and its output to `row_indices`. """ nargin, nargout = len(col_indices), len(row_indices) m, n = op.shape # Shape of non-reduced operator. def matvec(x): z = np.zeros(n, dtype=x.dtype) z[col_indices] = x[:] y = op * z return y[row_indices] def rmatvec(x): z = np.zeros(m, dtype=x.dtype) z[row_indices] = x[:] y = op.H * z return y[col_indices] return LinearOperator(nargin, nargout, matvec=matvec, symmetric=False, rmatvec=rmatvec) def SymmetricallyReducedLinearOperator(op, indices): """ Implement reduction of a linear operator (symmetrical). Reduce a linear operator symmetrically by reducing boths its input and output to `indices`. """ nargin = len(indices) m, n = op.shape # Shape of non-reduced operator. def matvec(x): z = np.zeros(n, dtype=x.dtype) z[indices] = x[:] y = op * z return y[indices] def rmatvec(x): z = np.zeros(m, dtype=x.dtype) z[indices] = x[:] y = op * z return y[indices] return LinearOperator(nargin, nargin, matvec=matvec, symmetric=op.symmetric, rmatvec=rmatvec) class ShapeError(Exception): """ Exception class for handling shape mismatch errors. Exception raised when defining a linear operator of the wrong shape or multiplying a linear operator with a vector of the wrong shape. """ def __init__(self, value): super(ShapeError, self).__init__() self.value = value def __str__(self): return repr(self.value) def PysparseLinearOperator(A): """ Return a linear operator from a Pysparse sparse matrix. .. deprecated:: 0.6 Use :func:`aslinearoperator` instead. """ nargout, nargin = A.shape try: symmetric = A.issym except: symmetric = A.isSymmetric() def matvec(x): if x.shape != (nargin,): msg = 'Input has shape ' + str(x.shape) msg += ' instead of (%d,)' % nargin raise ValueError(msg) if hasattr(A, '__mul__'): return A * x Ax = np.empty(nargout) A.matvec(x, Ax) return Ax def rmatvec(y): if y.shape != (nargout,): msg = 'Input has shape ' + str(y.shape) msg += ' instead of (%d,)' % nargout raise ValueError(msg) if hasattr(A, '__rmul__'): return y * A ATy = np.empty(nargin) A.rmatvec(y, ATy) return ATy return LinearOperator(nargin, nargout, matvec=matvec, rmatvec=rmatvec, symmetric=symmetric) def linop_from_ndarray(A): """ Return a linear operator from a Numpy `ndarray`. .. deprecated:: 0.4 Use :class:`MatrixLinearOperator` or :func:`aslinearoperator` instead. """ return LinearOperator(A.shape[1], A.shape[0], lambda v: np.dot(A, v), rmatvec=lambda u: np.dot(A.T, u), symmetric=False, dtype=A.dtype) def aslinearoperator(A): """Return A as a LinearOperator. 'A' may be any of the following types: - linop.LinearOperator - scipy.LinearOperator - ndarray - matrix - sparse matrix (e.g. csr_matrix, lil_matrix, etc.) - any object with .shape and .matvec attributes See the :class:`LinearOperator` documentation for additonal information. .. versionadded:: 0.4 """ if isinstance(A, LinearOperator): return A try: import numpy as np if isinstance(A, np.ndarray) or isinstance(A, np.matrix): return MatrixLinearOperator(A) except ImportError: pass try: import scipy.sparse as ssp if ssp.isspmatrix(A): return MatrixLinearOperator(A) except ImportError: pass if hasattr(A, 'shape'): nargout, nargin = A.shape matvec = None rmatvec = None dtype = None symmetric = False if hasattr(A, 'matvec'): matvec = A.matvec if hasattr(A, 'rmatvec'): rmatvec = A.rmatvec elif hasattr(A, 'matvec_transp'): rmatvec = A.matvec_transp if hasattr(A, 'dtype'): dtype = A.dtype if hasattr(A, 'symmetric'): symmetric = A.symmetric elif hasattr(A, '__mul__'): matvec = lambda x: A * x if hasattr(A, '__rmul__'): rmatvec = lambda x: x * A if hasattr(A, 'dtype'): dtype = A.dtype try: symmetric = A.isSymmetric() except: symmetric = False return LinearOperator( nargin, nargout, symmetric=symmetric, matvec=matvec, rmatvec=rmatvec, dtype=dtype) else: raise TypeError('unsupported object type') # some shorter aliases MatrixOperator = MatrixLinearOperator aslinop = aslinearoperator