mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
- Robust ID extraction in CentralityCalculator, CommunityDetector, and ConnectivityAnalyzer - Support for direct Entity objects and dictionaries as node identifiers - Improved Entity hashability in utils/types.py - Added integration test to verify fix and prevent regression
151 lines
5.6 KiB
Python
151 lines
5.6 KiB
Python
import unittest
|
|
import sys
|
|
import os
|
|
import networkx as nx
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
|
|
from semantica.kg.centrality_calculator import CentralityCalculator
|
|
from semantica.kg.community_detector import CommunityDetector
|
|
from semantica.kg.connectivity_analyzer import ConnectivityAnalyzer
|
|
|
|
class TestCentralityCalculator(unittest.TestCase):
|
|
def setUp(self):
|
|
self.calculator = CentralityCalculator()
|
|
self.graph = {
|
|
"entities": [
|
|
{"id": "A"}, {"id": "B"}, {"id": "C"}, {"id": "D"}, {"id": "E"}
|
|
],
|
|
"relationships": [
|
|
{"source": "A", "target": "B"},
|
|
{"source": "A", "target": "C"},
|
|
{"source": "A", "target": "D"},
|
|
{"source": "A", "target": "E"}
|
|
]
|
|
}
|
|
# This is a star graph with center A.
|
|
# A should have highest degree centrality.
|
|
|
|
def test_degree_centrality(self):
|
|
result = self.calculator.calculate_degree_centrality(self.graph)
|
|
centrality = result["centrality"]
|
|
# A connects to 4 nodes (B, C, D, E). Total nodes = 5.
|
|
# Degree centrality for A = 4 / (5-1) = 1.0
|
|
self.assertAlmostEqual(centrality["A"], 1.0)
|
|
# Leaves have degree 1. 1 / 4 = 0.25
|
|
self.assertAlmostEqual(centrality["B"], 0.25)
|
|
|
|
def test_betweenness_centrality(self):
|
|
result = self.calculator.calculate_betweenness_centrality(self.graph)
|
|
centrality = result["centrality"]
|
|
# A is on all shortest paths between any pair of leaves.
|
|
# It should have high betweenness.
|
|
self.assertGreater(centrality["A"], centrality["B"])
|
|
|
|
def test_closeness_centrality(self):
|
|
result = self.calculator.calculate_closeness_centrality(self.graph)
|
|
centrality = result["centrality"]
|
|
# A is distance 1 from everyone. Closeness = 1.0
|
|
self.assertAlmostEqual(centrality["A"], 1.0)
|
|
|
|
def test_eigenvector_centrality(self):
|
|
result = self.calculator.calculate_eigenvector_centrality(self.graph)
|
|
centrality = result["centrality"]
|
|
# A should be highest
|
|
self.assertEqual(max(centrality, key=centrality.get), "A")
|
|
|
|
|
|
class TestCommunityDetector(unittest.TestCase):
|
|
def setUp(self):
|
|
self.detector = CommunityDetector()
|
|
# Create two cliques connected by a single edge
|
|
# Clique 1: 1, 2, 3
|
|
# Clique 2: 4, 5, 6
|
|
# Edge: 3-4
|
|
self.graph = {
|
|
"entities": [
|
|
{"id": "1"}, {"id": "2"}, {"id": "3"},
|
|
{"id": "4"}, {"id": "5"}, {"id": "6"}
|
|
],
|
|
"relationships": [
|
|
# Clique 1
|
|
{"source": "1", "target": "2"}, {"source": "2", "target": "3"}, {"source": "3", "target": "1"},
|
|
# Clique 2
|
|
{"source": "4", "target": "5"}, {"source": "5", "target": "6"}, {"source": "6", "target": "4"},
|
|
# Bridge
|
|
{"source": "3", "target": "4"}
|
|
]
|
|
}
|
|
|
|
def test_louvain_communities(self):
|
|
# Louvain should find 2 communities
|
|
result = self.detector.detect_communities(self.graph, algorithm="louvain")
|
|
communities = result["communities"]
|
|
# We expect 2 communities, but small graphs can be tricky for heuristics.
|
|
# Let's just check structure.
|
|
self.assertTrue(len(communities) > 0)
|
|
# Check that nodes in same clique are likely in same community
|
|
# communities is a list of lists/sets
|
|
comm_map = {}
|
|
for c_id, nodes in enumerate(communities):
|
|
for node in nodes:
|
|
comm_map[node] = c_id
|
|
|
|
self.assertEqual(comm_map["1"], comm_map["2"])
|
|
self.assertEqual(comm_map["4"], comm_map["5"])
|
|
|
|
|
|
class TestConnectivityAnalyzer(unittest.TestCase):
|
|
def setUp(self):
|
|
self.analyzer = ConnectivityAnalyzer()
|
|
# Disconnected graph
|
|
# Component 1: A-B
|
|
# Component 2: C-D
|
|
self.graph = {
|
|
"entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}, {"id": "D"}],
|
|
"relationships": [
|
|
{"source": "A", "target": "B"},
|
|
{"source": "C", "target": "D"}
|
|
]
|
|
}
|
|
|
|
def test_connected_components(self):
|
|
result = self.analyzer.find_connected_components(self.graph)
|
|
self.assertEqual(result["num_components"], 2)
|
|
# Components are just lists of nodes, not dicts with size
|
|
# Wait, let's check find_connected_components return value
|
|
# It returns { "components": [[...], [...]], ... }
|
|
# So c is a list of nodes.
|
|
sizes = [len(c) for c in result["components"]]
|
|
self.assertIn(2, sizes)
|
|
|
|
def test_shortest_path(self):
|
|
graph = {
|
|
"entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}],
|
|
"relationships": [
|
|
{"source": "A", "target": "B"},
|
|
{"source": "B", "target": "C"}
|
|
]
|
|
}
|
|
result = self.analyzer.calculate_shortest_paths(graph, source="A", target="C")
|
|
# When source and target are provided, it returns specific keys
|
|
self.assertEqual(result["distance"], 2)
|
|
self.assertEqual(result["path"], ["A", "B", "C"])
|
|
|
|
def test_bridges(self):
|
|
# A-B-C. Both edges are bridges.
|
|
graph = {
|
|
"entities": [{"id": "A"}, {"id": "B"}, {"id": "C"}],
|
|
"relationships": [
|
|
{"source": "A", "target": "B"},
|
|
{"source": "B", "target": "C"}
|
|
]
|
|
}
|
|
result = self.analyzer.identify_bridges(graph)
|
|
self.assertEqual(len(result["bridges"]), 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|