Skip to content

Commit d36699d

Browse files
committed
merge last commit
2 parents 5d24c13 + b6a257a commit d36699d

File tree

6 files changed

+238
-42
lines changed

6 files changed

+238
-42
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)

experiments/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
dim: 200
22
density: 0.2
3-
n_processes: 1
3+
n_processes: 2
44
plot: true

scripts/profiling_memory.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from pyclassify import parallel_tridiag_eigen, Lanczos_PRO
1+
from pyclassify import Lanczos_PRO
22
from pyclassify.utils import read_config, make_symmetric
3-
from time import time
43
import numpy as np
54
import argparse
65
from mpi4py import MPI
@@ -18,14 +17,14 @@
1817
np.random.seed(seed)
1918

2019

21-
def parallel_eig(d, off_d, nprocs):
20+
def parallel_eig(diag, off_diag, nprocs):
2221
print("inside parallel_eig")
2322
comm = MPI.COMM_SELF.Spawn(
2423
sys.executable, args=["scripts/run.py"], maxprocs=nprocs
2524
)
2625
print("sending")
27-
comm.send(d, dest=0, tag=11)
28-
comm.send(off_d, dest=0, tag=12)
26+
comm.send(diag, dest=0, tag=11)
27+
comm.send(off_diag, dest=0, tag=12)
2928
print("Sent data to child, waiting for results...")
3029
sys.stdout.flush()
3130

@@ -44,6 +43,8 @@ def compute_eigvals(A, n_procs):
4443
if initial_guess[0] == 0:
4544
initial_guess[0] += 1
4645
Q, diag, off_diag = Lanczos_PRO(A, np.ones_like(np.diag(A)) * 1.)
46+
main_diag = np.ones(A.shape[0], dtype=np.float64) * 2.0
47+
off_diag = np.ones(A.shape[0] - 1, dtype=np.float64) * 1.0
4748
eigvals, _, __, total_mem_children = parallel_eig(diag, off_diag, n_procs)
4849
print("Eigenvalues computed")
4950
return total_mem_children

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.

0 commit comments

Comments
 (0)