Skip to content

Commit 0880985

Browse files
committed
Implement monkey-patch for elpigraph's AddNode2Node to fix numpy scalar assignment issues
1 parent 83beba0 commit 0880985

3 files changed

Lines changed: 132 additions & 29 deletions

File tree

.DS_Store

-6 KB
Binary file not shown.

scFates/tools/graph_fitting.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,132 @@
2323
from .utils import get_X
2424

2525

26+
def _patch_elpigraph_grammar():
27+
"""Monkey-patch elpigraph's AddNode2Node to fix numpy 2.x scalar assignment.
28+
29+
In elpigraph 0.3.2, ``ineighbour`` is a 1-element array but is used as a
30+
scalar index, causing ``ValueError: setting an array element with a sequence``
31+
on numpy ≥ 2.0 (Linux x86 build) because:
32+
- ``Mus[ineighbour]`` returns shape ``(1,)`` assigned to a scalar slot
33+
- ``NodePositions[ineighbour,]`` returns shape ``(1, ndims)`` instead of ``(ndims,)``
34+
"""
35+
try:
36+
import elpigraph.src.grammar_operations as _go
37+
except ImportError:
38+
return
39+
40+
_orig = _go.AddNode2Node
41+
42+
def _patched_AddNode2Node(
43+
X,
44+
NodePositions,
45+
ElasticMatrix,
46+
partition,
47+
AdjustVect,
48+
FixNodesAtPoints,
49+
Max_K=float("inf"),
50+
MaxNumberOfGraphCandidates=float("inf"),
51+
PointWeights=None,
52+
):
53+
import numpy as _np
54+
55+
nNodes = NodePositions.shape[0]
56+
Mus = ElasticMatrix.diagonal()
57+
Lambda = ElasticMatrix.copy()
58+
_np.fill_diagonal(Lambda, 0)
59+
indL = Lambda > 0
60+
Connectivities = indL.sum(axis=0)
61+
62+
if PointWeights is not None:
63+
assoc = _np.bincount(
64+
partition[partition > -1].ravel(),
65+
weights=PointWeights[partition > -1].ravel(),
66+
minlength=nNodes,
67+
)
68+
else:
69+
assoc = _np.bincount(
70+
partition[partition > -1].ravel(), minlength=nNodes
71+
)
72+
73+
npProt = _np.vstack(
74+
(NodePositions, _np.zeros((1, NodePositions.shape[1])))
75+
)
76+
emProt = _np.vstack(
77+
(
78+
_np.hstack((Lambda, _np.zeros((nNodes, 1)))),
79+
_np.zeros((1, nNodes + 1)),
80+
)
81+
)
82+
niProt = _np.arange(nNodes + 1, dtype=int)
83+
MuProt = _np.zeros(nNodes + 1)
84+
MuProt[:-1] = Mus
85+
86+
if not _np.isinf(Max_K):
87+
Degree = _np.sum(ElasticMatrix > 0, axis=1)
88+
Degree[Degree > 1] = Degree[Degree > 1] - 1
89+
if _np.sum(Degree <= Max_K) > 1:
90+
idx_nodes = _np.where(Degree <= Max_K)[0]
91+
else:
92+
raise ValueError(
93+
"AddNode2Node impossible with the current parameters!"
94+
)
95+
else:
96+
idx_nodes = _np.array(range(nNodes))
97+
98+
if MaxNumberOfGraphCandidates < len(idx_nodes) and _np.isinf(Max_K):
99+
idx_nodes = _np.argsort(assoc)[::-1][:MaxNumberOfGraphCandidates]
100+
elif MaxNumberOfGraphCandidates < len(idx_nodes) and not (
101+
_np.isinf(Max_K)
102+
):
103+
nGraphs = [
104+
i
105+
for i in _np.argsort(assoc)[::-1]
106+
if i in idx_nodes
107+
]
108+
idx_nodes = _np.array(nGraphs)[:MaxNumberOfGraphCandidates]
109+
110+
if FixNodesAtPoints != []:
111+
idx_nodes = idx_nodes[
112+
~_np.isin(idx_nodes, _np.arange(len(FixNodesAtPoints)))
113+
]
114+
115+
NodePositionsArray = [npProt.copy() for i in range(len(idx_nodes))]
116+
ElasticMatrices = [emProt.copy() for i in range(len(idx_nodes))]
117+
NodeIndicesArray = _np.repeat(
118+
niProt[:, _np.newaxis], len(idx_nodes), axis=1
119+
)
120+
AdjustVectArray = [AdjustVect + [False] for i in range(len(idx_nodes))]
121+
122+
for j, i in enumerate(idx_nodes):
123+
MuProt[-1] = 0
124+
meanL = Lambda[i, indL[i,]].mean(axis=0)
125+
ElasticMatrices[j][nNodes, i] = ElasticMatrices[j][i, nNodes] = meanL
126+
127+
if Connectivities[i] == 1:
128+
ineighbour = _np.nonzero(indL[i,])[0][0] # scalar, not array
129+
NewNodePosition = (
130+
2 * NodePositions[i,] - NodePositions[ineighbour,]
131+
)
132+
MuProt[i] = Mus[ineighbour] # scalar assignment
133+
else:
134+
if assoc[i] == 0:
135+
NewNodePosition = NodePositions[indL[:, i]].mean(axis=0)
136+
else:
137+
NewNodePosition = X[(partition == i).ravel()].mean(axis=0)
138+
139+
NodePositionsArray[j][nNodes, :] = NewNodePosition
140+
_np.fill_diagonal(ElasticMatrices[j], MuProt)
141+
142+
return (
143+
NodePositionsArray,
144+
ElasticMatrices,
145+
AdjustVectArray,
146+
NodeIndicesArray,
147+
)
148+
149+
_go.AddNode2Node = _patched_AddNode2Node
150+
151+
26152
def curve(
27153
adata: AnnData,
28154
Nodes: int = None,
@@ -466,6 +592,8 @@ def tree_epg(
466592

467593
import elpigraph
468594

595+
_patch_elpigraph_grammar()
596+
469597
logg.hint(
470598
"parameters used \n"
471599
" "
@@ -528,6 +656,8 @@ def curve_epg(
528656
):
529657
import elpigraph
530658

659+
_patch_elpigraph_grammar()
660+
531661
X, use_rep = get_data(adata, use_rep, ndims_rep)
532662
X = X.values
533663

@@ -593,6 +723,8 @@ def circle_epg(
593723
):
594724
import elpigraph
595725

726+
_patch_elpigraph_grammar()
727+
596728
X, use_rep = get_data(adata, use_rep, ndims_rep)
597729
X = X.values
598730

test_dendrogram_plot.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)