diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a59ac4a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# CHANGELOG +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] - YYYY-MM-DD + +### Added + +### Changed + +### Fixed + +## [0.0.5] - 2025-05-27 + +### Added +- This CHANGELOG, to keep track of new releases in the future. + +### Changed +- Remove unnecessary dependencies (pandas, pylgmath, gurobipy) +- Make some dependencies optional (plotly, sparseqr) +- More consistent use of install_requires, requirements.txt, environment.yml diff --git a/_test/utils.py b/_test/utils.py index 9905cc9..d3e98c0 100644 --- a/_test/utils.py +++ b/_test/utils.py @@ -1,6 +1,6 @@ import numpy as np from poly_matrix import PolyMatrix -from pylgmath import so3op +from scipy.spatial.transform import Rotation as R from cert_tools import HomQCQP @@ -8,6 +8,30 @@ ER_MIN = 1e6 +def vec2rot(aaxis_ba): + """Replacement for pylgmath.so3.operations.vec2rot""" + mats = [] + if np.ndim(aaxis_ba) == 3: + for ai in aaxis_ba: + assert ai.shape[-1] == 1 + r = R.from_rotvec(ai[:, 0]) + mats.append(r.as_matrix()) + mat = np.stack(mats) + elif np.ndim(aaxis_ba) == 2: + assert aaxis_ba.shape[-1] == 1 + r = R.from_rotvec(aaxis_ba[:, 0]) + mat = r.as_matrix() + + # For performance, we can switch back to using pylgmath. + # For now it was removed to avoid dependeny for one function only. + # Below was ensured to pass before removing dependncy. + # + # from pylgmath import so3op + # mat_test = so3op.vec2rot(aaxis_ba) + # np.testing.assert_allclose(mat, mat_test) + return mat + + class RotSynchLoopProblem(HomQCQP): """Class to generate and solve a rotation synchronization problem with loop constraints. The problem is generated with ground truth rotations and noisy @@ -34,7 +58,7 @@ def __init__(self, N=10, sigma=1e-3, loop_pose=3, locked_pose=0, seed=0): np.random.seed(seed) # generate ground truth poses aaxis_ab_rand = np.random.uniform(-np.pi / 2, np.pi / 2, size=(N, 3, 1)) - R_gt = so3op.vec2rot(aaxis_ab_rand) + R_gt = vec2rot(aaxis_ab_rand) # Associated variable list self.var_sizes = {"h": 1} for i in range(N): @@ -44,7 +68,7 @@ def __init__(self, N=10, sigma=1e-3, loop_pose=3, locked_pose=0, seed=0): self.locked_pose = str(locked_pose) # Pose locked at this pose self.meas_dict = {} for i in range(0, N): - R_pert = so3op.vec2rot(sigma * np.random.randn(3, 1)) + R_pert = vec2rot(sigma * np.random.randn(3, 1)) if i == N - 1: if loop_pose > 0: j = loop_pose diff --git a/cert_tools/__init__.py b/cert_tools/__init__.py index 6e079ab..59fea62 100644 --- a/cert_tools/__init__.py +++ b/cert_tools/__init__.py @@ -4,4 +4,4 @@ from .linalg_tools import * from .sdp_solvers import * -__version__ = "0.0.3" +__version__ = "0.0.5" diff --git a/cert_tools/eopt_solvers.py b/cert_tools/eopt_solvers.py index 2b53902..9b8ebe4 100644 --- a/cert_tools/eopt_solvers.py +++ b/cert_tools/eopt_solvers.py @@ -3,11 +3,11 @@ import mosek import numpy as np import numpy.linalg as la -import pandas as pd import scipy.sparse as sp +from scipy.optimize import linprog + from cert_tools.eig_tools import get_min_eigpairs from cert_tools.linalg_tools import get_nullspace -from scipy.optimize import linprog # Number of eigenvalues to compute EIG_METHOD = "direct" # "lobpcg" @@ -474,6 +474,10 @@ def solve_eopt_cuts( """Solve the certificate/eigenvalue optimization problem using a cutting plane algorithm. Current algorithm uses the level method with the target level at a tolerance below zero """ + print( + "Warning: when using this function, you might want to convert iter_info to a pandas Dataframe." + "This used to be the default behavior but was removed to avoid pandas dependency." + ) # Initialize cut plane model m = CutPlaneModel(xinit.shape[0], A_eq=A_eq, b_eq=b_eq) # Intialize status vars for optimization @@ -589,7 +593,7 @@ def solve_eopt_cuts( gap=gap, t_min=t_min, t_max=t_max, - iter_info=pd.DataFrame(iter_info), + iter_info=iter_info, model=m, ) diff --git a/cert_tools/hom_qcqp.py b/cert_tools/hom_qcqp.py index db9d169..02dde0c 100644 --- a/cert_tools/hom_qcqp.py +++ b/cert_tools/hom_qcqp.py @@ -5,8 +5,6 @@ import igraph as ig import matplotlib.pyplot as plt import numpy as np -import plotly.graph_objects as go -import plotly.io as pio import scipy.sparse as sp from cvxopt import amd, spmatrix from igraph import Graph @@ -735,6 +733,9 @@ def get_slices(self, mat, var_list_row, var_list_col=[]): def plot_graph(graph, **kwargs): + import plotly.graph_objects as go + import plotly.io as pio + layout = kwargs.get("layout", graph.layout("kk")) vertex_label = kwargs.get( "vertex_label", graph.vs["name"] if "name" in graph.vs.attributes() else None diff --git a/environment.yml b/environment.yml index 630dde4..238a9ef 100644 --- a/environment.yml +++ b/environment.yml @@ -9,19 +9,12 @@ dependencies: - python=3.10 - pip=22.3 - - numpy>=1.23.5 - - scipy==1.9.1 - - matplotlib>=3.6.2 - - pandas>=1.5.3 - - cvxpy>=1.3.2 - - pytest>=7.2.2 - - black>=23.1.0 - - mosek>=11 - - casadi>=3.6.3 - - scikit-sparse>=0.4.14 - - plotly>=5.24.1 - - suitesparse + # packages for local development that are not installed by setup.cfg + - black>=23.1 + - plotly>=5.24 + - pytest + - suitesparse # required for sparseqr - pip: - -r requirements.txt - - -e . + - -e . diff --git a/requirements.txt b/requirements.txt index ebc238d..5930804 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,2 @@ -sparseqr>=1.2.1 -gurobipy==10.0.3 -quadprog==0.1.11 -cvxopt>=1.3.2 -igraph>=0.11.8 -asrl-pylgmath>=1.0.3 -chompack>=2.3.2 -git+https://github.com/utiasASRL/poly_matrix.git@v0.2 +# packages that cannot be installed by install_requires or conda +sparseqr diff --git a/setup.cfg b/setup.cfg index bb9e146..92320b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = cert_tools -version = 0.0.3 +version = 0.0.5 authors = [ {name = "Frederike Dümbgen", email = "frederike.dumbgen@utoronto.ca" }, {name = "Connor Holmes", email = "connor.holmes@mail.utoronto.ca" }] @@ -16,18 +16,17 @@ license = { file="LICENSE" } [options] packages = find: install_requires = - casadi - matplotlib - numpy - pandas - scipy - mosek - cvxpy - igraph + casadi>=3.6 # used for BM interface to IPOPT + cvxpy>=1.3 + matplotlib>=3.6 + mosek>=11 + numpy>=1.23 + scipy==1.9 + igraph # for aggregate sparsity definition chompack - plotly + poly_matrix @ git+https://github.com/utiasASRL/poly_matrix.git@v0.3.1#egg=poly_matrix -[options.packages.find] # do not mistake tests/ for a package directory +[options.packages.find] # do not mistake _tests/ for a package directory exclude=_test* [flake8]