Metadata-Version: 2.1
Name: pystop
Version: 0.1.7
Summary: A Toolbox for Stiefel Manifold Optimization
Home-page: https://stmopt.gitee.io/
Author: Nachuan Xiao, Lei Wang, Bin Gao, Xin Liu, and Ya-xiang Yuan
Author-email: stmopt@foxmail.com
License: UNKNOWN
Description: # PySTOP
        
        ## Introduction
        
        The STOP toolbox is designed for **optimization problems on the Stiefel manifold**, which could be expressed as 
        $$
        \begin{aligned}
        	\min_{X \in \mathbb{R}^{n\times p}} ~ &f(X)\\
        	\text{s. t.}~& X^\top X = I_p,
        \end{aligned}
        $$
        where $I_p$ refers to the $p$-th order identity matrix, $X$ is a matrix with $n$ rows and $p$ columns. The feasible set of this optimization problem 
        $$
        \mathcal{S}_{n,p} := \left\{X \in \mathbb{R}^{n\times p}: X^\top X = I_p \right\},
        $$
        can be regarded as a Riemannian manifold in $\mathbb{R}^{n\times p}$, and we also call it as **Stiefel manifold**.  
        
        This document describes the python version of the STOP package (PySTOP).
        
        ## Installation
        
        The source code of PySTOP package can be found from [the website](https://stmopt.gitee.io/). Besides, it supports direct installation from `pip`:
        
        ```shell
        pip install pystop
        ```
        
        
        
        ## Example
        
        ### Problem formulation
        
        In this section, we consider the following nonlinear eigenvalue problem
        $$
        \min_{X \in \mathcal{S}_{n, p}} ~ \frac{1}{2}\mathrm{tr}(X^\top L X) + \frac{\alpha}{4} \rho^\top L^{\dagger} \rho,
        $$
        where $\rho = \mathrm{Diag}(XX^\top)$, and $L^{\dagger}$ denotes the pseudo-inverse of the positive definite matrix $L$, i.e. $L^{\dagger}LL^{\dagger} = L^{\dagger}$, $LL^{\dagger}L = L$.  Here we uses $\mathrm{Diag}(M)$ to denote the vector that is composed of diagonal entries of the square matrix $M$, while $\mathrm{diag}(v)$ refers to a diagonal matrix with $v$ to be its diagonal entries. Then the cost function and its **Euclidean gradient** can be expressed as
        $$
        \begin{aligned}
        	 f(X) ={}& \frac{1}{2}\mathrm{tr}(X^\top L X) + \frac{\alpha}{4} \rho^\top L^{\dagger} \rho,\\
        	\nabla f(X) ={}& LX + \alpha \mathrm{diag}(L^{\dagger}\rho)X.
        \end{aligned}
        $$
        
        
        In this example, we choose $L$ as a tri-diagonal matrix generated by `L = gallery('tridiag',n,-1,2,-1)`. Noting that $L$ is full-rank, then we can conclude that $L^{\dagger} = L^{-1}$ in this case. We solve this simple optimization problem using solvers in STOP to illustrate the most basic usage of the STOP toolbox. For additional theory, readers are recommended to refer the papers in the about page. 
        
        ```python
        # Import packages 
        import numpy as np
        import scipy as sp
        from scipy.sparse import diags
        from scipy.sparse.linalg import spsolve
        
        # Import manifolds and solvers
        from pystop.manifold import Stiefel
        from pystop.solver import SLPG_smooth
        
        
        # Set parameters
        n = 1000
        p = 10
        alpha = 1
        M = Stiefel(n,p)
        
        # Defining objective function
        L = diags(np.array([-1, 2, -1]), np.array([1, 0, -1]), shape = (n,n)).tocsc()
        def obj_fun(X):
            LX = L@X
            rho = np.sum(X * X, 1)
            Lrho = spsolve(L, rho)
            fval = 0.5*np.sum(X* LX) + (alpha /4) * np.sum(rho * Lrho)
            grad = LX + alpha * Lrho[: ,np.newaxis] * X
            return  fval, grad
        
        # Execute the solver
        X, out_dict = SLPG_smooth(obj_fun, M)
        
        ```
        
        
        
        Let us review the code step by step. First, we specify the dimension of the problem and specify the Stiefel manifold. In `pySTOP` package, we need to specify the dimension of the Stiefel manifold before executing the solver. The Stiefel manifold should be specified as the *STOP manifold class*, for example,  
        
        ```python
        # Set parameters
        n = 1000
        p = 10
        alpha = 1
        # Specify the Stiefel manifold
        M = Stiefel(n,p)
        ```
        
        Here `pystop.manifold.stiefel` is a build-in function to specify the Stiefel manifold and hence provides essential tools for the algorithm. 
        
        Then we generate the data (matrix $L$) for the optimization problem by the following code,
        
        ~~~python
        L = diags(np.array([-1, 2, -1]), np.array([1, 0, -1]), shape = (n,n)).tocsc()
        ~~~
        
        Here we utilize `SciPy.sparse` to create a sparse representation of $L$ . Therefore, in each step  we could use the `scipy.sparse.linalg.spsolve` function to compute . 
        
        Then we specify the cost function and its gradient in the following function
        
        ~~~python
        # Defin objective function
        def obj_fun(X):
            LX = L@X
            rho = np.sum(X * X, 1)
            Lrho = spsolve(L, rho)
            fval = 0.5*np.sum(X* LX) + (alpha /4) * np.sum(rho * Lrho)
            grad = LX + alpha * Lrho[: ,np.newaxis] * X
            return fval, grad
        ~~~
        
        Currently, in STOP toolbox, we require the function return the function value and its gradient simultaneously. Usually, computing the function value and gradient simultaneously is much faster than compute them separately, even when cache techniques are involved. To achieve a better performance, we strongly suggest to compute the function value and gradient in a single function. 
        
        Then we call a solver to solve the nonlinear eigenvalue problem, 
        
        ```python
        # Execute the solver
        X, out_dict = SLPG_smooth(obj_fun, M)
        ```
        
        
        
        
        
        ## Solvers
        
        The `PySTOP` solver classes provide the solvers for optimization. Once we specify the Stiefel manifold and define the objective function, the `PySTOP` solver can be executed by 
        
        ```
        X, out_dict = SLPG_smooth(obj_fun, M)
        ```
        
        Here `X` is the final output of the problem, and `out_dict` is a dictionary that contains the log information. 
        
        | Name                                                         | Comment                                                      | Call               |
        | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------ |
        | [SLPG_smooth](https://stmopt.gitee.io/pystop/SLPG_smooth.html) | Penalty-free first-order method for smooth problems          | `SLPG_smooth(...)` |
        | [SLPG](https://stmopt.gitee.io/pystop/SLPG.html)             | Penalty-free first-order method for nonsmooth problems       | `SLPG(...)`        |
        | [SLPG_l21](https://stmopt.gitee.io/pystop/SLPG_l21.html)     | Penalty-free first-order method for $\ell_{2,1}$-norm regularized problems | `SLPG_l21(...)`    |
        
        
        
        
        
        ## Defining the objective function
        
        Usually, computing the function value and gradient simultaneously is much faster than compute them separately, even when cache techniques are involved. Therefore, to achieve a better numerical performance, the existing solvers in PySTOP package requires an integrated call for function value and gradient of the objective function,  i.e. 
        
        ```python
        # Defin objective function
        def obj_fun(X):
            '''
            	fval refers to the function value of f
            	grad refers to the gradient of f
            '''
            return fval, grad
        ```
        
        
        
        Currently, PySTOP package does not involve build-in autodiff packages. However, we provides several useful functions in `pystop.utility` to help run the solvers only with specified objective function $f(X)$. 
        
        If you already know how to use NumPy, then it could be easy to use `autograd` package to generate the function value and gradient simultaneously. Just import `autograd.numpy` and setup the objective function by the build-in function provided in `autograd.numpy` to perform the computation. Once the function value is specified as `obj()`, we could apply the `pystop.utility.fun_autodiff` to generate the function that returns fval and grad simultaneously.
        
        However, it is worth mentioning that `autograd.numpy` package only supports a subset of the standard NumPy package. Besides, the Autograd package does not support SciPy package. To achieve better numerical performance, we suggest the users to specify the `obj_fun` function manually. 
        
        ```python
        import autograd.numpy as anp
        import numpy as np
        
        n = 1000
        p = 30
        Z = np.random.randn(n, p)
        
        def obj_fun(X):
            return  anp.sum((X-Z) **2 )
        
        
        from pystop.utility import fun_autodiff
        
        obj_grad, obj_fungrad = fun_autodiff(obj_fun)
        
        
        from pystop.manifold import Stiefel
        from pystop.solver import SLPG_smooth
        
        M = Stiefel(n,p)
        X, out_dict = SLPG_smooth(obj_fungrad, M)
        ```
        
        
        
        
        
        
        
        
Keywords: optimization,manifold optimization,Stiefel manifold
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown
