아래 튜토리얼은 graph-tool 라이브러리의 AdjacencyOperator 클래스를 활용하여, 그래프의 인접 행렬(Adjacency matrix)을 직접 메모리에 구성하지 않고도 효율적으로 스펙트럴 연산(예: 고윳값 계산, 행렬-벡터 곱 등)을 수행하는 방법을 소개합니다. 초보자도 쉽게 이해할 수 있도록 단계별 파이썬 코드 예시와 친절한 한글 주석을 포함했습니다.
AdjacencyOperator란?AdjacencyOperator는 scipy.sparse.linalg.LinearOperator를 상속받아, 그래프의 인접행렬 (A)에 해당하는 선형 연산을 “구체 행렬 없이” 정의해주는 클래스입니다.
AdjacencyOperator는 “가상 행렬”: 행렬 곱 등에 필요한 연산(matvec, matmat)만 정의 → 실제 행렬을 생성하지 않고도 곱셈 가능. adjacency vs AdjacencyOperatoradjacency()와의 관계graph_tool.spectral.adjacency(...) 함수:
csr_matrix) 혹은 AdjacencyOperator(operator=True 시)로 반환.AdjacencyOperator 클래스를 통해 operator를 만든다고 생각하면 됨.즉,
op = gt.adjacency(g, operator=True)
이런 식으로 쓰면, 반환객체가 사실상 AdjacencyOperator의 인스턴스.
# adjacency_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) 그래프 생성 (간단 예시)
g = gt.Graph(directed=False)
vlist = [g.add_vertex() for _ in range(5)]
# 에지 추가
g.add_edge(vlist[0], vlist[1])
g.add_edge(vlist[1], vlist[2])
g.add_edge(vlist[2], vlist[3])
g.add_edge(vlist[3], vlist[4])
g.add_edge(vlist[4], vlist[0])
# => 5개 노드를 연결해 "오각형" 구조를 만듦
# 2) AdjacencyOperator 생성
# - adjacency(g, operator=True)가 내부적으로 AdjacencyOperator를 반환
A_op = gt.adjacency(g, operator=True)
# 3) 크기 확인 (오각형이므로 5x5)
print("Shape of the operator:", A_op.shape)
# 4) 임의 벡터 x와 곱해보기
x = np.array([1, 2, 3, 4, 5], dtype=np.float64)
y = A_op.matvec(x) # = A * x
print("Result of A*x:", y)
# 5) 부분 고윳값 계산: largest real (LR) + smallest real (SR)
# 인접행렬은 대칭 => 고윳값이 모두 실수
# n=5 전체이므로 그냥 전부 구해도 됨(k=5)
# 하지만 예시로 LR, SR로 나눠서 해보자
ew_lr = spla.eigs(A_op, k=2, which="LR", return_eigenvectors=False)
ew_sr = spla.eigs(A_op, k=3, which="SR", return_eigenvectors=False)
ew = np.concatenate((ew_lr, ew_sr))
print("Eigenvalues (approx):", ew)
# 6) 시각화(실수부 vs 허수부)
plt.figure()
plt.scatter(np.real(ew), np.imag(ew), c="blue", alpha=0.7)
plt.xlabel("Re(λ)")
plt.ylabel("Im(λ)")
plt.title("Approx. eigenvalues from AdjacencyOperator")
plt.grid(True)
plt.show()
if __name__=="__main__":
main()
A_op = gt.adjacency(g, operator=True) → (\text{AdjacencyOperator}) 객체 반환 .shape → (5,5) (노드 수 × 노드 수) matvec(x): np.array로 만든 벡터와 곱. 실제로는 A * x (오각형 인접행렬 × x). AdjacencyOperator 직접 사용하기위 예제는 사실 adjacency() 함수를 통해 AdjacencyOperator를 받았지만, 더 직접적으로 AdjacencyOperator를 다루려면(예: 생성자 인수), 문서상 아래와 같은 방식을 활용할 수 있습니다:
class graph_tool.spectral.AdjacencyOperator(*args, **kwargs)
일반 사용자는 보통 adjacency(g, operator=True)를 쓰면 되고, AdjacencyOperator를 명시적으로 인스턴스화할 일은 드뭅니다. 하지만, operator 객체가 가진 메소드들(dot, matvec 등)은 동일하게 제공.
matvec(x) 와 matmat(X)matvec(x): x가 1D numpy array(길이 N)일 때, 결과는 (\in \mathbb{R}^N).matmat(X): X가 (N,K) 크기의 2D 배열(행렬)일 때, 결과 (N,K).둘 다 내부적으로 _matvec, _matmat를 호출 → AdjacencyOperator의 구현에서는 “간선을 순회하며” 곱 계산.
rmatvec(x) 등 수반/전치rmatvec(x): (A^\top x) (adjoint mul). 인접행렬이 무방향이면 (A^\top=A). transpose() or T: 전치. 무방향 그래프의 인접행렬이면 사실 동일. adjoint() or H: 에르미트 수반(복소 그래프에서).AdjacencyOperator 는 graph-tool의 인접행렬을 “LinearOperator”로 구현해, 희소 선형대수 작업에 최적화된 객체.op = gt.adjacency(g, operator=True)로 얻고, op.matvec(...), op.matmat(...), op.dot(...) 등을 통해 행렬 곱 연산. 이상으로 AdjacencyOperator 클래스의 사용법, 메소드, 예시 코드를 살펴보았습니다. 희소 네트워크(도시·교통 빅데이터 등)에서 “인접행렬 전체를 메모리에 올리지 않고도” 부분 고윳값을 구하는 데 매우 유용하니, 참고하시기 바랍니다.
아래 튜토리얼은 graph-tool 라이브러리의 LaplacianOperator 클래스를 사용하여, 그래프 라플라시안(Laplacian) 행렬을 효율적으로 다루는 방법을 단계별로 설명합니다. LaplacianOperator는 실제 (|V|\times|V|) 라플라시안 행렬을 생성·저장하지 않고도, 행렬-벡터 곱이나 고윳값 계산 등에 활용할 수 있도록 해주는 scipy.sparse.linalg.LinearOperator 기반의 객체입니다. 초보자도 쉽게 따라 할 수 있도록, 친절한 한글 주석과 함께 예시 코드를 준비했습니다.
그래프 (G)가 있을 때,
일반적인 (비정규화) 라플라시안은
[
L \;=\; D \;-\; A.
]
그래프 이론에서 라플라시안의 고윳값·고유벡터는 연결성, 군집 구조, 스펙트럴 임베딩 등 다양한 분석에 쓰입니다. 예를 들어,
LaplacianOperator?LaplacianOperator는 실제 행렬 없이 “선형 연산자”만 정의. eigs 등에서 matvec(x) => Lx 연산만 구체적으로 구현.laplacian vs. LaplacianOperatorgraph-tool에서 라플라시안 행렬을 얻는 두 가지:
laplacian(g, ...): 라플라시안 희소행렬(csr_matrix나 coo_matrix) 반환, 또는 operator=True로 LaplacianOperator 반환.LaplacianOperator 클래스 직접 사용.일반 사용자에게는
op = gt.laplacian(g, operator=True)
가 가장 간단합니다. 내부적으로 LaplacianOperator 인스턴스를 생성해 반환합니다.
아래 예시는 작은 그래프를 만든 뒤, LaplacianOperator로 라플라시안 곱 연산과 간단한 부분 고윳값 계산을 시연합니다.
# laplacian_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) 예시 그래프 생성 (소규모)
g = gt.Graph(directed=False)
v_list = [g.add_vertex() for _ in range(5)]
# 에지 추가: (0-1, 1-2, 2-3, 3-4, 4-0) => 5개 노드로 오각형
g.add_edge(v_list[0], v_list[1])
g.add_edge(v_list[1], v_list[2])
g.add_edge(v_list[2], v_list[3])
g.add_edge(v_list[3], v_list[4])
g.add_edge(v_list[4], v_list[0])
# 2) LaplacianOperator 생성
# - laplacian(..., operator=True) => LaplacianOperator 리턴
L_op = gt.laplacian(g, deg="total", norm=False, operator=True)
# deg="total": 무방향 그래프에서 모든 차수
# norm=False: 비정규화 라플라시안 L = D - A
# 3) 크기 확인
print("Laplacian operator shape:", L_op.shape) # (5,5)
# 4) matvec 테스트: Lx
x = np.array([1, 2, 3, 4, 5], dtype=np.float64)
y = L_op.matvec(x)
print("L*x =", y)
# 5) 부분 고윳값(전체) 계산
# - n=5 -> 굳이 부분 구할 필요 없으나, 예시로 "SR"+"LR" 분리
ew_lr = spla.eigs(L_op, k=2, which="LR", return_eigenvectors=False)
ew_sr = spla.eigs(L_op, k=3, which="SR", return_eigenvectors=False)
ew = np.concatenate([ew_lr, ew_sr])
print("Eigenvalues (approx):", ew)
# 6) 시각화: 실수부 vs 허수부 (사실 라플라시안은 대칭 => 허수부 거의 0)
plt.figure()
plt.scatter(np.real(ew), np.imag(ew), c="red", alpha=0.7)
plt.xlabel("Re(λ)")
plt.ylabel("Im(λ)")
plt.title("Eigenvalues from LaplacianOperator")
plt.grid(True)
plt.show()
if __name__=="__main__":
main()
L_op = gt.laplacian(g, deg="total", norm=False, operator=True) → 라플라시안 연산자(LaplacianOperator) 획득.matvec(x)로 (Lx) 계산 테스트eigs) 시연. 라플라시안은 대칭 → 고윳값이 실수. LaplacianOperator의 주요 메소드LaplacianOperator는 LinearOperator를 상속 → 아래 메소드들이 주로 사용됨:
matvec(x): (L \times x) 계산(벡터)matmat(X): (L \times X) 계산(행렬)rmatvec(x): 수반((L^\top) 또는 에르미트) 곱. 무방향 그래프면 동일.transpose(), adjoint(): 전치, 에르미트 수반 연산자.dot(x): matvec와 동일 기능.대칭 그래프 라플라시안인 경우, L^\top = L, L^\mathrm{H} = L (복소가 아니라면).
laplacian(g, norm=True, ...) 하면 정규화(normalized) 라플라시안:
[
L_{\text{norm}} = I - D^{-1/2} A D^{-1/2}.
]
다만, operator=True일 때도 비슷한 방식으로 내부 계산. 사용법 동일.
대규모 그래프에서, 행렬 전체를 메모리에 저장하지 않고도, LaplacianOperator로 부분 고윳값(예: 가장 작은 2~3개)을 구할 수 있어 효율적.
LaplacianOperator는 그래프 라플라시안을 메모리 효율적으로 다룰 수 있는 scipy.sparse.linalg.LinearOperator 구현.op = gt.laplacian(g, operator=True)로 얻음 → .matvec(x), spla.eigs(...) 등.이상으로 LaplacianOperator에 대한 소개와 예시 코드를 마칩니다. 도시·교통 네트워크 등 규모가 큰 그래프에서 라플라시안을 스펙트럴 분석할 때, 이 방식을 이용하면 메모리, 속도 측면에서 큰 이점을 얻을 수 있습니다.
아래 튜토리얼은 graph-tool 라이브러리의 IncidenceOperator 클래스를 이용해서 그래프의 인시던스 행렬을 효율적으로 다루는 방법을 단계별로 설명합니다. 실제 ((#V) \times (#E)) 또는 ((#E) \times (#V)) 인시던스 행렬을 메모리에 올리지 않고도, 행렬-벡터 곱이나 고윳값 등의 선형대수 연산을 수행할 수 있게 해주는 scipy.sparse.linalg.LinearOperator 형태의 객체입니다.
인시던스(Incidence) 행렬은 그래프에서 “정점(vertex)와 간선(edge) 간의 연결 관계”를 행렬 형태로 나타낸 것입니다.
무방향 그래프일 때, 인시던스 행렬 (B)는 ((#V) \times (#E)) 크기이고,
방향 그래프인 경우에는, “유향 간선”에서 정점과의 연결(들어오는 방향인지, 나가는 방향인지)에 따라 (\pm 1)의 값으로 확장하기도 함.
이 인시던스 행렬을 통해, 네트워크의 “유량(flow)”나 “사이클(cycle)” 분석(예: (\mathrm{cycle} \sim \mathrm{ker}(B^\top)))에 유용하게 쓰입니다.
graph-tool에서 incidence() 함수로 인시던스 행렬(또는 연산자)를 생성할 수 있으며, 이 중 IncidenceOperator는 실제 행렬 없이도 연산을 제공하므로, 큰 그래프에서 메모리 절약과 효율적인 연산이 가능해집니다.
IncidenceOperator와 graph-toolincidence() 함수 vs. IncidenceOperatorgraph_tool.spectral.incidence(g, ..., operator=True) 를 호출하면 IncidenceOperator가 반환됨. (operator=False 면 csr_matrix 등 희소행렬이 반환)IncidenceOperator는 scipy.sparse.linalg.LinearOperator 를 상속 → .matvec(), .rmatvec(), .dot() 등으로 인시던스 행렬 곱을 수행.incidence(...)로 인시던스 연산자 만들기아래 코드는 작은 그래프를 구성하고, 인시던스 연산자를 얻어, 벡터 곱을 시연해 봅니다.
# incidence_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) 간단한 예시 그래프 생성
# - 4개 노드, 3개 에지
g = gt.Graph(directed=False)
v0 = g.add_vertex()
v1 = g.add_vertex()
v2 = g.add_vertex()
v3 = g.add_vertex()
g.add_edge(v0, v1)
g.add_edge(v1, v2)
g.add_edge(v2, v3)
# => 선형으로 연결된 4노드 (v0 - v1 - v2 - v3)
print("Number of vertices:", g.num_vertices()) # 4
print("Number of edges:", g.num_edges()) # 3
# 2) 인시던스 연산자 생성
# - incidence(g, operator=True)는 IncidenceOperator 반환
B_op = gt.incidence(g, operator=True, eindex=None)
# eindex=None => 내부 edge 인덱스 사용
# (vindex도 없음 => 내부 vertex index 사용)
# 인시던스 행렬 크기: (#V x #E)
print("Shape of Incidence operator:", B_op.shape) # (4,3)
# 3) matvec: (V x E) "행렬" * 벡터
# - 여기서 x dimension = #E => x 길이는 3
x = np.array([10, 20, 30], dtype=np.float64)
y = B_op.matvec(x)
print("y = B * x =", y)
# y의 길이는 #V=4
# 4) rmatvec: (B^T x)
# - 결과 길이는 #E=3
z = B_op.rmatvec(y)
print("z = B^T * y =", z)
# 5) 부분 고윳값 (사실 incidence는 직사각형이라 보통 '특이값'이 적절)
# - 여기서는 operator=False 로 matrix 받고 svds 써도 됨
# - 또는 크기가 작으니 직접 transform 가능
# * 여기서는 예시로 matvec만 시연
# 6) 그래프 시각화(간단)
gt.graph_draw(g, vertex_text=g.vertex_index, output="incidence_example.png")
if __name__ == "__main__":
main()
B_op = gt.incidence(g, operator=True) → “인시던스 연산자(사이즈=4×3)” matvec(x): 벡터 x 길이는 3(에지 수), 결과 y 길이는 4(노드 수).rmatvec(y): adjoint 곱((B^\top y)) → 길이 3으로 돌아옴.IncidenceOperator 주요 메소드IncidenceOperator는 이런 연산(곱, kernel 찾기)에 직접 사용 가능.IncidenceOperator 는 그래프 인시던스 행렬을 “LinearOperator” 형태로 구현하여, 큰 그래프에서 희소 선형 연산 시 효율적.B_op = gt.incidence(g, operator=True)
y = B_op.matvec(x) # B * x
z = B_op.rmatvec(y) # B^T * y추가 아이디어:
operator=False → 실제 희소행렬(csr_matrix) 반환 후, svds(특이값 분해)로 cycle space, cut space 분석.operator=True로 부분특이값/커널만 계산. 이상으로 IncidenceOperator 활용 튜토리얼을 마칩니다. 도시·교통·네트워크 분석에서, 인시던스 행렬 기반 기법(유량, 사이클, 커트) 등에 큰 도움을 줄 수 있으니 참고하시기 바랍니다.
아래 튜토리얼은 graph-tool 라이브러리에서 제공하는 TransitionOperator 클래스를 활용하여, 그래프의 전이(Transition) 행렬을 메모리 효율적으로 다루고, 이를 이용해 랜덤워크나 마코프 체인 해석 등을 수행하는 방법을 설명합니다. 실제로 (|V|\times|V|) 전이행렬 전체를 만들지 않고도, 부분 고윳값 계산이나 벡터곱 연산을 할 수 있으므로 대규모 그래프에서도 활용하기 좋습니다.
transition()와 TransitionOperatorgraph_tool.spectral.transition(g, weight=None, vindex=None, operator=False, csr=True):operator=True → TransitionOperator 리턴operator=False → 희소행렬(csr_matrix나 coo_matrix) 리턴TransitionOperator 특징.matvec(), .rmatvec(), .dot() 등으로 (\mathbf{T}\times \mathbf{x}) 연산 가능.다음은 작은 그래프로 전이 연산자를 생성하고, 벡터 곱을 시연한 예입니다.
# transition_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) 예시 그래프 생성(무방향, 5노드 오각형)
g = gt.Graph(directed=False)
v_list = [g.add_vertex() for _ in range(5)]
g.add_edge(v_list[0], v_list[1])
g.add_edge(v_list[1], v_list[2])
g.add_edge(v_list[2], v_list[3])
g.add_edge(v_list[3], v_list[4])
g.add_edge(v_list[4], v_list[0])
print("Num vertices:", g.num_vertices()) # 5
print("Num edges:", g.num_edges()) # 5
# 2) 전이 행렬 연산자 생성
# - operator=True => TransitionOperator
T_op = gt.transition(g, operator=True) # weight=None => unweighted
print("Shape of Transition operator:", T_op.shape) # (5,5)
# 3) matvec 연산: T*x
# - x는 노드 수 길이=5
x = np.array([1, 2, 3, 4, 5], dtype=np.float64)
y = T_op.matvec(x)
print("y = T*x:", y)
# 4) rmatvec 연산: T^H * y
# - 전이 행렬은 일반적으로 대칭 아님 => T^T != T
z = T_op.rmatvec(y)
print("z = T^H * y:", z)
# 5) 고윳값(eigs) 시연
# 전이 행렬은 확률행렬 => 최대 고윳값=1
# (LR: largest real, SR: smallest real)
ew_lr = spla.eigs(T_op, k=2, which="LR", return_eigenvectors=False)
ew_sr = spla.eigs(T_op, k=3, which="SR", return_eigenvectors=False)
ew = np.concatenate([ew_lr, ew_sr])
print("Approx eigenvals:", ew)
# 6) 시각화
plt.figure()
plt.scatter(np.real(ew), np.imag(ew), c='blue', alpha=0.7)
plt.title("Eigenvalues of TransitionOperator (approx)")
plt.xlabel("Re(λ)")
plt.ylabel("Im(λ)")
plt.grid(True)
plt.show()
if __name__=="__main__":
main()
T_op = gt.transition(g, operator=True) → TransitionOperator 객체 획득.matvec(x): 전이연산 × 벡터. 각 노드로부터 이웃 노드로 1/degree 확률..rmatvec(y): 수반(에르미트Transpose) 곱 → “(T^\top y)” 같은 연산.TransitionOperator 메소드 요약matvec(x): (\mathbf{T}\cdot\mathbf{x}). matmat(X): (\mathbf{T}\cdot\mathbf{X}) (행렬 곱).rmatvec(x): 수반. (\mathbf{T}^\mathrm{H}\cdot\mathbf{x}). transpose(), adjoint(): 전치, 에르미트 수반 연산자 반환.dot(x): matvec와 유사.전이행렬 (\mathbf{T})는 일반적으로 대칭이 아님 → (\mathbf{T}^\top \neq \mathbf{T}).
spla.eigs(T_op, which="LR") 통해 1 근처 고윳값, 고유벡터 찾기. TransitionOperator 는 그래프의 확률적 전이행렬을 scipy.sparse.linalg.LinearOperator 형태로 제공 → 실제 (|V|\times|V|) 생성 없이 행렬-벡터 곱 등 가능.T_op = gt.transition(g, weight=None, operator=True)
v_new = T_op.matvec(v_old)이런 식으로 확률 분포 업데이트 or 고윳값 계산에 활용.이상으로 TransitionOperator 클래스의 소개와 예시 코드를 마칩니다. 대규모 그래프의 확률적 이동(랜덤서퍼, 교통수요 분산 등)을 효율적으로 처리하고자 할 때 매우 유용하니, 실무에서도 적극 활용할 수 있습니다.
아래 튜토리얼은 graph-tool 라이브러리에서 제공하는 HashimotoOperator 클래스를 활용하여, Hashimoto(=Non-backtracking) 행렬을 효율적으로 다루는 방법을 단계별로 설명합니다. 이는 Ihara–Hashimoto, non-backtracking, edge adjacency operator 등 여러 이름으로 불리며, 그래프의 스펙트럴 분석(특히 희소 그래프에서 군집 검출 등)에 큰 이점을 주는 중요한 도구입니다.
이렇게 즉시 역추적(backtracking)을 금지한 “non-backtracking walk”를 표현. 따라서 고차수 노드에서 역추적 잡음이 줄어들어, 희소 그래프에서 스펙트럼이 더 선명하게 군집 구조나 고유모드 등을 반영합니다.
HashimotoOperator의 의의HashimotoOperator는 scipy.sparse.linalg.LinearOperator를 상속 → 행렬 없이 matvec 등 곱셈 연산만 정의하여 희소 선형대수(예: 부분 고윳값)를 효율적으로 지원.hashimoto(g, operator=True)일반적으로 graph_tool.spectral.hashimoto(g, ...) 함수를 사용해 HashimotoOperator를 얻습니다:
H_op = gt.hashimoto(g, operator=True, compact=False)
operator=True를 지정해야 실제 희소행렬 대신 HashimotoOperator 객체로 반환.compact=False면 전형적인 non-backtracking 행렬((2|E|\times 2|E|)) 형태(“full mode”). compact=True일 경우, Krzakala 등에서 소개한 “compact”한 변형이지만, 일반적으로 bipartite 변환 등에 쓰임.아래는 작은 그래프(5노드로 형성된 오각형)에서 HashimotoOperator를 사용해 행렬–벡터 곱, 부분 고윳값 등을 시연합니다.
# hashimoto_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) 예시: 무방향 그래프 (오각형)
g = gt.Graph(directed=False)
v_list = [g.add_vertex() for _ in range(5)]
g.add_edge(v_list[0], v_list[1])
g.add_edge(v_list[1], v_list[2])
g.add_edge(v_list[2], v_list[3])
g.add_edge(v_list[3], v_list[4])
g.add_edge(v_list[4], v_list[0])
# => 5개 노드를 순환 연결 (오각형)
print("Number of vertices:", g.num_vertices()) # 5
print("Number of edges:", g.num_edges()) # 5 (무방향 간선)
# 2) Hashimoto Operator 생성
# - operator=True => HashimotoOperator
H_op = gt.hashimoto(g, operator=True, compact=False)
# compact=False => (2|E| x 2|E|) = (10 x 10)
print("Shape of Hashimoto operator:", H_op.shape)
# (10, 10), 무방향이라 유향간선은 2*5=10
# 3) matvec: H*x 시연
# - x의 길이는 2*|E|=10
x = np.arange(10, dtype=np.float64) # ex) [0,1,2,3,4,5,6,7,8,9]
y = H_op.matvec(x)
print("y = H*x:", y)
# 4) rmatvec: H^H * y (수반 곱)
z = H_op.rmatvec(y)
print("z = H^H * y:", z)
# 5) 부분 고윳값 계산
# - 해시모토 행렬은 비대칭 => 고윳값이 복소 가능
# - "LR": 실수부 큰 쪽, "SR": 실수부 작은 쪽
# - 10x10 전체를 다 구해도 되지만, 예시로 3개 + 2개 등 시연
ew_lr = spla.eigs(H_op, k=3, which="LR", return_eigenvectors=False)
ew_sr = spla.eigs(H_op, k=2, which="SR", return_eigenvectors=False)
ew = np.concatenate((ew_lr, ew_sr))
print("Approx eigenvalues (subset):", ew)
# 6) 시각화(복소평면)
plt.figure()
plt.scatter(np.real(ew), np.imag(ew), c="blue", alpha=0.7)
plt.title("Eigenvalues from HashimotoOperator (sample subset)")
plt.xlabel("Re(λ)")
plt.ylabel("Im(λ)")
plt.grid(True)
plt.show()
if __name__=="__main__":
main()
gt.hashimoto(g, operator=True) → HashimotoOperator 객체 (compact=False)..matvec(x) : ((2|E|))-dim 벡터 x 곱, 결과도 ((2|E|))-dim..rmatvec(y) : 수반곱((H^\top)), (2|E|)-dim -> (2|E|)-dim.matvec(x): (\mathbf{H}\cdot x) (행렬–벡터 곱)matmat(X): 행렬–행렬 곱rmatvec(x): 수반(Adjoint) 곱 → (\mathbf{H}^\mathrm{H} x)transpose(), adjoint(): 전치/에르미트 수반 연산자를 담은 LinearOperator 리턴dot(x): matvec(x)와 유사HashimotoOperator = 해시모토(non-backtracking) 행렬을 “행렬 없이” 정의한 LinearOperator.H_op = gt.hashimoto(g, operator=True).matvec(x) → 곱spla.eigs(H_op, ...) → 부분 고윳값.rmatvec(...) 등 → 수반 연산이상으로 HashimotoOperator 활용 튜토리얼을 마칩니다. 특히 희소 네트워크에서 스펙트럴 군집 효율과 정확도를 높이는 핵심 연산자로 쓰이니, 도시·교통·소셜·생물 네트워크 등에서 폭넓게 응용할 수 있습니다.
아래 튜토리얼은 graph-tool 라이브러리에서 제공하는 CompactHashimotoOperator 클래스를 이용해, Compact Hashimoto(=non-backtracking) 행렬을 효과적으로 다루는 방법을 단계별로 설명합니다. 이는 일반적인 Hashimoto(=non-backtracking) 행렬을 더 간략화한 형태로, 특정한 스펙트럴 분석(특히 양단성(bipartite) 그래프) 등에 유리하게 쓰일 수 있습니다.
hashimoto(g, compact=True) 옵션을 통해, 이 압축 버전 행렬을 만들 수 있으며, CompactHashimotoOperator로 바로 사용할 수 있습니다. CompactHashimotoOperator와 graph-toolop = gt.hashimoto(g, compact=True, operator=True)→ CompactHashimotoOperator 반환. compact=False로 호출 시 일반 HashimotoOperator, operator=False 시 희소행렬(csr_matrix 등) 반환.아래는 6노드 bipartite 그래프(예: 3개의 노드 그룹1, 3개의 노드 그룹2, 서로 간 간선 연결)에서 CompactHashimotoOperator를 생성하고, 연산·고윳값을 시연하는 예입니다.
# compact_hashimoto_operator_tutorial.py
import graph_tool.all as gt
import numpy as np
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
def main():
# 1) bipartite 예시 그래프 구성
g = gt.Graph(directed=False)
# V1={v0, v1, v2}, V2={v3, v4, v5}
vlist = [g.add_vertex() for _ in range(6)]
# 간단히, 각 v_i(0..2)와 v_j(3..5) 간에 간선 연결
for i in range(3):
for j in range(3, 6):
g.add_edge(vlist[i], vlist[j])
# => 3x3 bipartite 완전연결(K(3,3)) -> 총 9개 간선
print("Vertices:", g.num_vertices()) # 6
print("Edges:", g.num_edges()) # 9
# 2) Compact Hashimoto Operator 생성
# - compact=True, operator=True
H_cop = gt.hashimoto(g, compact=True, operator=True)
# => CompactHashimotoOperator
print("Shape of compact Hashimoto operator:", H_cop.shape)
# (6,6), bipartite일 때 유용
# 3) matvec 연산
x = np.array([1,2,3,4,5,6], dtype=np.float64)
y = H_cop.matvec(x)
print("y = H_compact * x:", y)
# 4) rmatvec: 수반 곱
z = H_cop.rmatvec(y)
print("z = H_compact^H * y:", z)
# 5) 부분 고윳값 계산
# - shape=(6,6)이므로 k<6
ew_lr = spla.eigs(H_cop, k=2, which="LR", return_eigenvectors=False)
ew_sr = spla.eigs(H_cop, k=2, which="SR", return_eigenvectors=False)
ew = np.concatenate((ew_lr, ew_sr))
print("Eigenvalues approx:", ew)
# 6) 시각화
plt.figure()
plt.scatter(np.real(ew), np.imag(ew), c='red', alpha=0.7)
plt.title("Eigenvalues of Compact Hashimoto Operator (sample)")
plt.xlabel("Re(λ)")
plt.ylabel("Im(λ)")
plt.grid(True)
plt.show()
if __name__=="__main__":
main()
gt.hashimoto(g, compact=True, operator=True) → CompactHashimotoOperator 객체 획득..matvec(x) : 인덱스는 (6,6) → “노드별” (compact)로 곱 실행..rmatvec(...): 수반 곱(에르미트).CompactHashimotoOperator 메소드CompactHashimotoOperator는 LinearOperator를 상속하므로, 주요 메소드는 다음과 같습니다:
matvec(x): (\mathbf{H_{compact}}\times x) (벡터 곱)matmat(X): 행렬–행렬 곱rmatvec(x): 수반 곱transpose(), adjoint(): 전치, 에르미트 수반dot(x): matvec(x) 유사주의: 일반 해시모토((2|E|\times 2|E|))와 달리, compact는 (|V|\times |V|). bipartite일 때 유용(정규성, 이분성 등 가정).
CompactHashimotoOperator는 해시모토(non-backtracking) 행렬을 “(|V|\times |V|) 크기로 압축”한 LinearOperator**.cop = hashimoto(g, compact=True, operator=True).matvec(), .rmatvec(), .dot() 등으로 곱, spla.eigs(...)로 고윳값 추출. 이상으로 CompactHashimotoOperator 튜토리얼을 마칩니다. 도시·교통·네트워크 분석에서, 특히 이분(bipartite) 구조가 있을 때, 효율적인 스펙트럴 군집이나 라플라시안-like 해석에 활용할 수 있습니다.