Skip to main content

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). Currently, PySTOP only involves the SLPG algorithm, which could handle smooth, $\ell_1$-norm regularized and $\ell_{2,1}$-nom regularized optimization problems on the Stiefel manifold. The package is

Installation

The source code of PySTOP package can be found from the website. Besides, it supports direct installation from pip:

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.

# 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,

# 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,

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

# 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,

# 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 = name_of_solver(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 Penalty-free first-order method for smooth problems SLPG_smooth(...)
SLPG Penalty-free first-order method for nonsmooth problems SLPG(...)
SLPG_l21 Penalty-free first-order method for $\ell_{2,1}$-norm regularized problems SLPG_l21(...)

It is worth mentioning that SLPG solver supports customized nonsmooth regularization terms, interested reader could refer to the description page for details.

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.

# 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.

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)

Metadata

Release files for pystop 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pystop 0.2.2
File Size Uploaded Provenance
pystop-0.2.2.tar.gz 23.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pystop 0.2.2
File Interpreter ABI Platform
pystop-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 52.8 kB

Release files / pystop-0.2.2.tar.gz

Download URL pystop-0.2.2.tar.gz
Size 23.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4087cf5140f5339f37691bcc6707c98cef8ecdfbce1b79a824c5f7090d708470
BLAKE2b-256 checksum
How to use checksums
2bd63a001bd344d04e6e7f9aedf0be078c5284a68e39a7efcf3e69e17e15ff38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.6.0 importlib_metadata/4.8.1 pkginfo/1.4.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.26.0 CPython/3.7.0

Release files / pystop-0.2.2-py3-none-any.whl

Download URL pystop-0.2.2-py3-none-any.whl
Size 29.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cd9465a1673261fb78944d3041581cfda8baf4628bfc7eb8545e46dcc0e6a011
BLAKE2b-256 checksum
How to use checksums
7bd46cb3b5e40b4375edbd2cba0548d1817664d4fba0589aad02547f8ff23e08
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.6.0 importlib_metadata/4.8.1 pkginfo/1.4.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.26.0 CPython/3.7.0

Security for pystop 0.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page