| """Tests for the memory graph module.""" |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| import pytest |
|
|
| from agents.wayfinder.memory_graph import MemoryGraph |
| from agents.wayfinder.perception import GRID_SIZE |
|
|
|
|
| class TestMemoryGraph: |
| """Test cases for MemoryGraph.""" |
|
|
| @pytest.fixture |
| def graph(self) -> MemoryGraph: |
| """Create a test memory graph.""" |
| return MemoryGraph() |
|
|
| @pytest.fixture |
| def sample_frame(self) -> np.ndarray: |
| """Create a sample frame.""" |
| return np.random.randint(0, 16, size=(GRID_SIZE, GRID_SIZE), dtype=np.uint8) |
|
|
| def test_hash_frame_stable(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test that hashing is deterministic.""" |
| h1 = graph.hash_frame(sample_frame) |
| h2 = graph.hash_frame(sample_frame) |
| assert h1 == h2 |
|
|
| def test_hash_frame_different_for_different_frames(self, graph: MemoryGraph) -> None: |
| """Test that different frames have different hashes.""" |
| f1 = np.zeros((GRID_SIZE, GRID_SIZE), dtype=np.uint8) |
| f2 = np.ones((GRID_SIZE, GRID_SIZE), dtype=np.uint8) |
| assert graph.hash_frame(f1) != graph.hash_frame(f2) |
|
|
| def test_add_node_new(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test adding a new node.""" |
| h = graph.hash_frame(sample_frame) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h, latent, score=0.5) |
| assert h in graph.nodes |
| assert graph.nodes[h]["visit_count"] == 1 |
| assert graph.nodes[h]["score"] == 0.5 |
|
|
| def test_add_node_existing_increments_visits(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test that re-adding a node increments its visit count.""" |
| h = graph.hash_frame(sample_frame) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h, latent) |
| graph.add_node(h, latent) |
| graph.add_node(h, latent) |
| assert graph.nodes[h]["visit_count"] == 3 |
|
|
| def test_novelty_new_node(self, graph: MemoryGraph) -> None: |
| """Test novelty is 1.0 for unseen nodes.""" |
| assert graph.novelty("nonexistent") == 1.0 |
|
|
| def test_novelty_decreases_with_visits(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test that novelty decreases as visit count increases.""" |
| h = graph.hash_frame(sample_frame) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h, latent) |
| n1 = graph.novelty(h) |
| graph.add_node(h, latent) |
| n2 = graph.novelty(h) |
| assert n2 < n1 |
| assert 0 < n2 < 1 |
|
|
| def test_add_edge(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test adding an edge between nodes.""" |
| h1 = graph.hash_frame(sample_frame) |
| h2 = graph.hash_frame(sample_frame + 1) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h1, latent) |
| graph.add_node(h2, latent) |
| graph.add_edge(h1, h2, "ACTION1") |
|
|
| assert (h1, h2) in graph.edges |
| assert graph.edges[(h1, h2)]["action"] == "ACTION1" |
| assert len(graph.adjacency[h1]) == 1 |
|
|
| def test_reset_clears_graph(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test that reset clears all nodes and edges.""" |
| h = graph.hash_frame(sample_frame) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h, latent) |
| graph.add_edge(h, h, "ACTION1") |
|
|
| graph.reset() |
| assert len(graph.nodes) == 0 |
| assert len(graph.edges) == 0 |
|
|
| def test_stats(self, graph: MemoryGraph, sample_frame: np.ndarray) -> None: |
| """Test stats returns correct summary.""" |
| h = graph.hash_frame(sample_frame) |
| latent = np.random.randn(64).astype(np.float32) |
| graph.add_node(h, latent) |
| graph.add_node(h, latent) |
| stats = graph.stats() |
| assert stats["node_count"] == 1 |
| assert stats["avg_visits"] == 2.0 |
|
|