Skip to content

Commit f846b18

Browse files
committed
Written the deflation in cpp
1 parent 6deab0d commit f846b18

File tree

4 files changed

+223
-28
lines changed

4 files changed

+223
-28
lines changed

CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ project(QR_binding VERSION 1.0
77
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pyclassify)
88
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pyclassify)
99

10+
include(FetchContent)
11+
# Fetch Eigen 3.4.0 (or whatever version you need)
12+
FetchContent_Declare(
13+
eigen
14+
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz
15+
)
16+
FetchContent_MakeAvailable(eigen)
17+
1018
set(PYBIND11_FINDPYTHON ON)
1119

1220
# To find pybind11 on Ulysses:
@@ -36,6 +44,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)
3644
add_compile_options(-O3 -Wall -Werror -Wpedantic)
3745

3846
pybind11_add_module(cxx_utils ${CMAKE_SOURCE_DIR}/src/pyclassify/cxx_utils.cpp)
47+
48+
target_link_libraries(cxx_utils PRIVATE Eigen3::Eigen )
3949
set_target_properties(cxx_utils PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
4050

4151
target_compile_features(cxx_utils PUBLIC cxx_std_17)

src/pyclassify/cxx_utils.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
namespace py=pybind11;
17+
#include <pybind11/eigen.h>
1718

1819

1920
std::pair<std::vector<double>, std::vector<std::vector<double>> >
@@ -585,6 +586,183 @@ secular_solver(
585586
}
586587

587588

589+
#include <Eigen/Dense>
590+
#include <Eigen/Sparse>
591+
#include <unordered_set>
592+
593+
/**
594+
* Applies the deflation step in a divide-and-conquer eigenvalue algorithm.
595+
*
596+
* @param D Diagonal entries of the matrix (as Eigen::VectorXd).
597+
* @param v Rank-one update vector (modified in-place).
598+
* @param beta Scalar multiplier for the rank-one update.
599+
* @param tol_factor Factor to scale the deflation tolerance (default 1e-12).
600+
* @return A tuple containing:
601+
* - deflated_eigvals: Vector of trivial eigenvalues (Eigen::VectorXd).
602+
* - deflated_eigvecs: Matrix whose columns are the trivial eigenvectors (Eigen::MatrixXd).
603+
* - D_keep: Remaining diagonal entries after deflation (Eigen::VectorXd).
604+
* - v_keep: Remaining rank-one vector entries after deflation (Eigen::VectorXd).
605+
* - P_final: Combined permutation & Givens rotation as an Eigen::SparseMatrix<double>.
606+
*/
607+
608+
609+
/**
610+
* Applies the deflation step in a divide-and-conquer eigenvalue algorithm.
611+
*
612+
* @param D Diagonal entries of the matrix (as Eigen::VectorXd).
613+
* @param v Rank-one update vector (modified in-place).
614+
* @param beta Scalar multiplier for the rank-one update.
615+
* @param tol_factor Factor to scale the deflation tolerance (default 1e-12).
616+
* @return A tuple containing:
617+
* - deflated_eigvals: Vector of trivial eigenvalues (Eigen::VectorXd).
618+
* - deflated_eigvecs: Matrix whose columns are the trivial eigenvectors (Eigen::MatrixXd).
619+
* - D_keep: Remaining diagonal entries after deflation (Eigen::VectorXd).
620+
* - v_keep: Remaining rank-one vector entries after deflation (Eigen::VectorXd).
621+
* - P_final: Combined permutation & rotation as an Eigen::SparseMatrix<double>.
622+
*/
623+
std::tuple<
624+
Eigen::VectorXd,
625+
Eigen::MatrixXd,
626+
Eigen::VectorXd,
627+
Eigen::VectorXd,
628+
Eigen::SparseMatrix<double>>
629+
deflateEigenpairs(
630+
const Eigen::VectorXd& D,
631+
Eigen::VectorXd v,
632+
double beta,
633+
double tol_factor = 1e-12
634+
) {
635+
int n = D.size();
636+
// 1) Build full matrix M and compute norm for tolerance
637+
Eigen::MatrixXd M = D.asDiagonal();
638+
M += beta * v * v.transpose();
639+
double norm_T = M.norm();
640+
double tol = tol_factor * norm_T;
641+
642+
// 2) Prepare containers for deflation
643+
std::vector<int> keep_indices, deflated_indices;
644+
Eigen::VectorXd deflated_eigvals = Eigen::VectorXd::Zero(n);
645+
std::vector<Eigen::VectorXd> deflated_eigvecs_list;
646+
int j = 0;
647+
648+
// 3) Zero-component deflation
649+
for (int i = 0; i < n; ++i) {
650+
if (std::abs(v(i)) < tol) {
651+
deflated_eigvals(j) = D(i);
652+
Eigen::VectorXd e_vec = Eigen::VectorXd::Zero(n);
653+
e_vec(i) = 1.0;
654+
deflated_eigvecs_list.push_back(e_vec);
655+
deflated_indices.push_back(i);
656+
++j;
657+
} else {
658+
keep_indices.push_back(i);
659+
}
660+
}
661+
662+
// 4) Build permutation P: [keep_indices, deflated_indices]
663+
std::vector<int> new_order;
664+
new_order.reserve(n);
665+
new_order.insert(new_order.end(), keep_indices.begin(), keep_indices.end());
666+
new_order.insert(new_order.end(), deflated_indices.begin(), deflated_indices.end());
667+
Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> P(n);
668+
P.indices() = Eigen::VectorXi::Map(new_order.data(), n);
669+
670+
// 5) Extract subproblem D_keep and v_keep
671+
Eigen::VectorXd D_keep(static_cast<int>(keep_indices.size()));
672+
Eigen::VectorXd v_keep(static_cast<int>(keep_indices.size()));
673+
for (int idx = 0; idx < static_cast<int>(keep_indices.size()); ++idx) {
674+
D_keep(idx) = D(keep_indices[idx]);
675+
v_keep(idx) = v(keep_indices[idx]);
676+
}
677+
678+
// 6) Givens rotations for near-duplicate entries
679+
std::unordered_set<int> to_check;
680+
to_check.reserve(keep_indices.size());
681+
for (int i = 0; i < static_cast<int>(keep_indices.size()); ++i)
682+
to_check.insert(i);
683+
std::vector<std::tuple<int,int,double,double>> rotations;
684+
std::vector<int> vec_idx_list;
685+
std::vector<int> to_check_copy(to_check.begin(), to_check.end());
686+
687+
for (size_t idx_i = 0; idx_i + 1 < to_check_copy.size(); ++idx_i) {
688+
int i = to_check_copy[idx_i];
689+
if (to_check.find(i) == to_check.end()) continue;
690+
for (int k = i + 1; k < static_cast<int>(D_keep.size()); ++k) {
691+
if (std::abs(D_keep(k) - D_keep(i)) < tol) {
692+
to_check.erase(k);
693+
double r = std::hypot(v_keep(i), v_keep(k));
694+
double c = v_keep(i) / r;
695+
double s = -v_keep(k) / r;
696+
v_keep(i) = r;
697+
v_keep(k) = 0.0;
698+
rotations.emplace_back(i, k, c, s);
699+
deflated_eigvals(j) = D_keep(i);
700+
++j;
701+
// local eigenvector in full basis
702+
Eigen::VectorXd tmp = Eigen::VectorXd::Zero(n);
703+
tmp(k) = c;
704+
tmp(i) = s;
705+
deflated_eigvecs_list.push_back(P.transpose() * tmp);
706+
vec_idx_list.push_back(k);
707+
}
708+
}
709+
}
710+
711+
// 7) Final ordering after rotations
712+
std::vector<int> final_order(to_check.begin(), to_check.end());
713+
final_order.insert(final_order.end(), vec_idx_list.begin(), vec_idx_list.end());
714+
715+
// 8) Resize deflated_eigvals to actual number found
716+
deflated_eigvals.conservativeResize(j);
717+
718+
// 9) Build P2: accumulate sparse Givens rotations
719+
Eigen::SparseMatrix<double> P2(n,n);
720+
P2.setIdentity();
721+
for (auto &rot: rotations) {
722+
int i,k; double c,s; std::tie(i,k,c,s) = rot;
723+
Eigen::SparseMatrix<double> G(n,n);
724+
G.setIdentity();
725+
G.coeffRef(i,i) = c;
726+
G.coeffRef(k,k) = c;
727+
G.coeffRef(i,k) = -s;
728+
G.coeffRef(k,i) = s;
729+
P2 = P2 * G;
730+
}
731+
732+
// 10) Build permutation matrix P3 from final_order
733+
Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> P3(n);
734+
P3.indices() = Eigen::VectorXi::Map(final_order.data(), n);
735+
736+
// 11) Convert P and P3 to sparse<double> and combine all transforms
737+
Eigen::SparseMatrix<double> P_sparse = P.toDenseMatrix().cast<double>().sparseView();
738+
Eigen::SparseMatrix<double> P3_sparse = P3.toDenseMatrix().cast<double>().sparseView();
739+
Eigen::SparseMatrix<double> P_final = P3_sparse * P2 * P_sparse;
740+
741+
// 12) Extract final D_keep and v_keep for reduced problem
742+
std::vector<int> final_keep(to_check.begin(), to_check.end());
743+
Eigen::VectorXd D_keep_final(static_cast<int>(final_keep.size()));
744+
Eigen::VectorXd v_keep_final(static_cast<int>(final_keep.size()));
745+
for (int idx = 0; idx < static_cast<int>(final_keep.size()); ++idx) {
746+
D_keep_final(idx) = D_keep(final_keep[idx]);
747+
v_keep_final(idx) = v_keep(final_keep[idx]);
748+
}
749+
750+
// 13) Assemble deflated eigenvectors matrix
751+
Eigen::MatrixXd deflated_eigvecs(n, static_cast<int>(deflated_eigvecs_list.size()));
752+
for (int col = 0; col < static_cast<int>(deflated_eigvecs_list.size()); ++col) {
753+
deflated_eigvecs.col(col) = deflated_eigvecs_list[col];
754+
}
755+
756+
return std::make_tuple(
757+
deflated_eigvals,
758+
deflated_eigvecs.transpose(),
759+
D_keep_final,
760+
v_keep_final,
761+
P_final
762+
);
763+
}
764+
765+
588766
// PYTHON BINDINGS USING PYBIND11
589767

590768
PYBIND11_MODULE(cxx_utils, m) {
@@ -593,4 +771,5 @@ PYBIND11_MODULE(cxx_utils, m) {
593771
m.def("QR_algorithm", &QR_algorithm, py::arg("diag"), py::arg("off_diag"), py::arg("tol")=1e-8, py::arg("max_iter")=5000);
594772
m.def("Eigen_value_calculator", &Eigen_value_calculator, py::arg("diag"), py::arg("off_diag"), py::arg("tol")=1e-8, py::arg("max_iter")=5000);
595773
m.def("secular_solver_cxx", &secular_solver, py::arg("rho"), py::arg("d"), py::arg("v"), py::arg("indices"));
774+
m.def("deflate_eigenpairs_cxx", &deflateEigenpairs, py::arg("D"), py::arg("v"), py::arg("beta"), py::arg("tol_factor") = 1e-12);
596775
}

src/pyclassify/eigenvalues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def power_method_numba(A, max_iter=500, tol=1e-7, x=None):
158158
return x @ A @ x
159159

160160

161-
@jit(nopython=True)
161+
#@jit(nopython=True)
162162
def Lanczos_PRO(A, q, m=None, tol=np.sqrt(np.finfo(float).eps)):
163163
r"""
164164
Perform the Lanczos algorithm for symmetric matrices.

src/pyclassify/parallel_tridiag_eigen.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from mpi4py import MPI
22
import numpy as np
33
from time import time
4-
from .cxx_utils import QR_algorithm, secular_solver_cxx
4+
from pyclassify.cxx_utils import QR_algorithm, secular_solver_cxx, deflate_eigenpairs_cxx
5+
from zero_finder import secular_solver_python as secular_solver
56
from line_profiler import profile, LineProfiler
67
import scipy.sparse as sp
78
from line_profiler import LineProfiler
89
import scipy
910
from pyclassify.utils import make_symmetric
11+
from pyclassify.eigenvalues import EigenSolver, Lanczos_PRO
12+
1013

1114
profile = LineProfiler()
1215

@@ -255,18 +258,22 @@ def parallel_tridiag_eigen(
255258
n1 = len(eigvals_left)
256259
D = np.concatenate((eigvals_left, eigvals_right))
257260
v_vec = np.concatenate((eigvecs_left[-1, :], eigvecs_right[0, :]))
258-
deflated_eigvals, deflated_eigvecs, D_keep, v_keep, P = deflate_eigenpairs(
259-
D, v_vec, tol_factor
261+
deflated_eigvals, deflated_eigvecs, D_keep, v_keep, P = deflate_eigenpairs_cxx(
262+
D, v_vec, beta, tol_factor
260263
)
264+
# deflated_eigvals, deflated_eigvecs, D_keep, v_keep, P = deflate_eigenpairs(
265+
# D, v_vec, beta, tol_factor
266+
# )
261267
reduced_dim = len(D_keep)
262268
if D_keep.size > 0:
263269
# M = np.diag(D_keep) + beta * np.outer(v_keep, v_keep)
264270
# lam , _= np.linalg.eigh(M)
265271
idx = np.argsort(D_keep)
266272
idx_inv = np.arange(0, reduced_dim)
267273
idx_inv = idx_inv[idx]
274+
#lam, changing_position, delta = secular_solver( beta, D_keep[idx], v_keep[idx] )
268275
lam, changing_position, delta = secular_solver_cxx(
269-
beta, D_keep[idx], v_keep[idx]
276+
beta, D_keep[idx], v_keep[idx], np.arange(reduced_dim)
270277
)
271278
lam = np.array(lam)
272279
# #diff=lam_s-lam
@@ -429,7 +436,7 @@ def parallel_tridiag_eigen(
429436
# if colnorm > 1e-14:
430437
# local_block[:, c] /= colnorm
431438

432-
# gathered_blocks = comm.gather(local_block, root=0)
439+
# gathranered_blocks = comm.gather(local_block, root=0)
433440
# gathered_cols = comm.gather(my_cols, root=0)
434441

435442
# if rank == 0:
@@ -481,24 +488,27 @@ def parallel_eigen(
481488
if __name__ == "__main__":
482489
comm = MPI.COMM_WORLD
483490
rank = comm.Get_rank()
484-
n = 2000
491+
n = 1000
485492
if rank == 0:
493+
486494
# import debugpy
487495
# port = 5678 + rank # 5678 for rank 0, 5679 for rank 1
488496
# debugpy.listen(("localhost", port))
489497
# print(f"Rank {rank} waiting for debugger attach on port {port}")
490498
# debugpy.wait_for_client()
491499
np.random.seed(42)
492-
main_diag = np.ones(n, dtype=np.float64) * 2
493-
off_diag = np.ones(n - 1, dtype=np.float64)
494-
eig = np.arange(1, n + 1)
495-
A = np.diag(eig)
496-
U = scipy.stats.ortho_group.rvs(n)
497-
498-
A = U @ A @ U.T
499-
A = make_symmetric(A)
500-
Lanc = EigenSolver(A)
501-
_, main_diag, off_diag = Lanc.Lanczos_PRO(q=np.ones_like(eig), tol=1e-12)
500+
main_diag = np.ones(n, dtype=np.float64) * 2.0
501+
off_diag = np.ones(n - 1, dtype=np.float64) *1.0
502+
# eig = np.arange(1, n + 1)
503+
# A = np.diag(eig)
504+
# U = scipy.stats.ortho_group.rvs(n)
505+
506+
# A = U @ A @ U.T
507+
# A = make_symmetric(A)
508+
509+
# #Lanc = EigenSolver(A)
510+
# #_, main_diag, off_diag = Lanc.Lanczos_PRO(q=np.ones_like(eig), tol=1e-12)
511+
# _, main_diag, off_diag = Lanczos_PRO(A, np.ones_like(eig)*1.0, tol=1e-12)
502512
T = np.diag(main_diag) + np.diag(off_diag, 1) + np.diag(off_diag, -1)
503513
eig_numpy, eig_vec_numpy = np.linalg.eigh(T)
504514
# print(eig_numpy)
@@ -535,13 +545,9 @@ def parallel_eigen(
535545

536546
print("Norm difference eigenaval", np.linalg.norm(eig_numpy - eigvals, np.inf))
537547

538-
for count, i in enumerate(eigvecs[0, :]):
539-
if i < 0:
540-
eigvecs[:, count] = (-1) * eigvecs[:, count]
548+
549+
check_column_directions(eigvecs, eig_vec_numpy)
541550

542-
for count, i in enumerate(eig_vec_numpy[0, :]):
543-
if i < 0:
544-
eig_vec_numpy[:, count] = (-1) * eig_vec_numpy[:, count]
545551

546552
# import sys
547553
# np.set_printoptions(threshold=sys.maxsize)
@@ -552,13 +558,13 @@ def parallel_eigen(
552558

553559
# # print("Eigenvector solver:\n", eigvecs)
554560
# # print("Eigenvector numpy:\n", eig_vec_numpy)
555-
# print("\n\n\nDifference :\n", eig_vec_numpy - eigvecs)
556-
# diff= eig_vec_numpy-eigvecs
557-
# flat_idx = np.argmax(diff) # → 5 (counting row-major: 0..8)
561+
#print("\n\n\nDifference :\n", eig_vec_numpy - eigvecs)
562+
diff= eig_vec_numpy-eigvecs
563+
flat_idx = np.argmax(diff) # → 5 (counting row-major: 0..8)
558564

559565
# # If you want row/column coordinates instead of the flattened index:
560-
# row, col = np.unravel_index(flat_idx, diff.shape) # → (1, 2)
561-
# print(np.max(np.abs(diff),LaEig_vec axis=0))
566+
row, col = np.unravel_index(flat_idx, diff.shape) # → (1, 2)
567+
print(np.max(np.abs(diff)))
562568
# print("\n\n", eig_vec_numpy[:, col], eigvecs[:, col])
563569
# print("Norm difference eigenvec", np.linalg.norm(eig_vec_numpy-eigvecs, np.inf))
564570

0 commit comments

Comments
 (0)