Skip to content

Commit

Permalink
use linalg.eigh instead of linalg.eig
Browse files Browse the repository at this point in the history
  • Loading branch information
d-schindler committed Nov 8, 2024
1 parent ea208ad commit fe20adc
Showing 1 changed file with 43 additions and 16 deletions.
59 changes: 43 additions & 16 deletions src/pygenstability/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ def load_constructor(constructor, graph, **kwargs):
"""Load a constructor from its name, or as a custom Constructor class."""
if isinstance(constructor, str):
if graph is None:
raise Exception(f"No graph was provided with a generic constructor {constructor}")
raise Exception(
f"No graph was provided with a generic constructor {constructor}"
)
try:
return getattr(sys.modules[__name__], f"constructor_{constructor}")(graph, **kwargs)
return getattr(sys.modules[__name__], f"constructor_{constructor}")(
graph, **kwargs
)
except AttributeError as exc:
raise Exception(f"Could not load constructor {constructor}") from exc
if not isinstance(constructor, Constructor):
Expand All @@ -53,8 +57,8 @@ def limit(*args, **kwargs):


def _compute_spectral_decomp(matrix):
"""Solve eigenalue problem."""
lambdas, v = la.eig(matrix.toarray())
"""Solve eigenalue problem for symmetric matrix."""
lambdas, v = la.eigh(matrix.toarray())
vinv = la.inv(v.real)
return lambdas.real, v.real, vinv

Expand All @@ -67,7 +71,9 @@ def _check_total_degree(degrees):

def _get_spectral_gap(laplacian):
"""Compute spectral gap."""
spectral_gap = np.round(max(np.real(sp.linalg.eigs(laplacian, which="SM", k=2)[0])), 8)
spectral_gap = np.round(
max(np.real(sp.linalg.eigs(laplacian, which="SM", k=2)[0])), 8
)
L.info("Spectral gap = 10^%s", np.around(np.log10(spectral_gap), 2))
return spectral_gap

Expand All @@ -80,7 +86,9 @@ class Constructor:
to return quality matrix, null model, and possible global shift.
"""

def __init__(self, graph, with_spectral_gap=False, exp_comp_mode="spectral", **kwargs):
def __init__(
self, graph, with_spectral_gap=False, exp_comp_mode="spectral", **kwargs
):
"""The constructor calls the prepare method upon initialisation.
Args:
Expand All @@ -105,7 +113,9 @@ def __init__(self, graph, with_spectral_gap=False, exp_comp_mode="spectral", **k
def _get_exp(self, scale):
"""Compute matrix exponential at a given scale."""
if self.exp_comp_mode == "expm":
exp = sp.linalg.expm(-scale * self.partial_quality_matrix.toarray().astype(_DTYPE))
exp = sp.linalg.expm(
-scale * self.partial_quality_matrix.toarray().astype(_DTYPE)
)
if self.exp_comp_mode == "spectral":
lambdas, v, vinv = self.spectral_decomp
exp = v @ np.diag(np.exp(-scale * lambdas)) @ vinv
Expand Down Expand Up @@ -181,7 +191,9 @@ class constructor_continuous_combinatorial(Constructor):
@_limit_numpy
def prepare(self, **kwargs):
"""Prepare the constructor with non-scale dependent computations."""
laplacian, degrees = sp.csgraph.laplacian(self.graph, return_diag=True, normed=False)
laplacian, degrees = sp.csgraph.laplacian(
self.graph, return_diag=True, normed=False
)
_check_total_degree(degrees)
laplacian /= degrees.mean()
pi = np.ones(self.graph.shape[0]) / self.graph.shape[0]
Expand Down Expand Up @@ -221,7 +233,9 @@ class constructor_continuous_normalized(Constructor):
@_limit_numpy
def prepare(self, **kwargs):
"""Prepare the constructor with non-scale dependent computations."""
laplacian, degrees = sp.csgraph.laplacian(self.graph, return_diag=True, normed=False)
laplacian, degrees = sp.csgraph.laplacian(
self.graph, return_diag=True, normed=False
)
_check_total_degree(degrees)
normed_laplacian = sp.diags(1.0 / degrees).dot(laplacian)

Expand Down Expand Up @@ -354,7 +368,9 @@ class constructor_directed(Constructor):
@_limit_numpy
def prepare(self, **kwargs):
"""Prepare the constructor with non-scale dependent computations."""
assert self.exp_comp_mode == "expm"
assert (
self.exp_comp_mode == "expm"
), 'exp_comp_mode="expm" is required for "constructor_directed"'

alpha = kwargs.get("alpha", 0.8)
n_nodes = self.graph.shape[0]
Expand All @@ -366,11 +382,17 @@ def prepare(self, **kwargs):

self.partial_quality_matrix = sp.csr_matrix(
alpha * np.diag(dinv).dot(self.graph.toarray())
+ ((1 - alpha) * np.diag(np.ones(n_nodes)) + np.diag(alpha * (dinv == 0.0))).dot(ones)
+ (
(1 - alpha) * np.diag(np.ones(n_nodes)) + np.diag(alpha * (dinv == 0.0))
).dot(ones)
- np.eye(n_nodes)
)

pi = abs(sp.linalg.eigs(self.partial_quality_matrix.transpose(), which="SM", k=1)[1][:, 0])
pi = abs(
sp.linalg.eigs(self.partial_quality_matrix.transpose(), which="SM", k=1)[1][
:, 0
]
)
pi /= pi.sum()
self.partial_null_model = np.array([pi, pi])

Expand Down Expand Up @@ -424,9 +446,10 @@ def prepare(self, **kwargs):

self.partial_quality_matrix = sp.csr_matrix(
alpha * np.diag(dinv).dot(self.graph.toarray())
+ ((1 - alpha) * np.diag(np.ones(n_nodes)) + np.diag(alpha * (dinv == 0.0))).dot(
ones
)
+ (
(1 - alpha) * np.diag(np.ones(n_nodes))
+ np.diag(alpha * (dinv == 0.0))
).dot(ones)
- np.eye(n_nodes)
)

Expand All @@ -435,7 +458,11 @@ def prepare(self, **kwargs):
sp.diags(dinv).dot(self.graph) - sp.diags(np.ones(n_nodes))
)

pi = abs(sp.linalg.eigs(self.partial_quality_matrix.transpose(), which="SM", k=1)[1][:, 0])
pi = abs(
sp.linalg.eigs(self.partial_quality_matrix.transpose(), which="SM", k=1)[1][
:, 0
]
)
pi /= pi.sum()
self.partial_null_model = np.array([pi, pi])

Expand Down

0 comments on commit fe20adc

Please sign in to comment.