""" Tests for analytical module (Jackson's theorem). """ import pytest import math from src.analytics.jackson import JacksonAnalyzer, QueueAnalytics class TestJacksonAnalyzer: """Tests for Jackson's theorem analyzer.""" def test_simple_mm1_queue(self): """Test analysis of a simple M/M/1 queue.""" # Single queue: λ=0.8, μ=1.0, ρ=0.8 analyzer = JacksonAnalyzer( external_arrival_rate=0.8, coordinator_service_rate=1.0, coordinator_exit_prob=1.0, # Always exit (no servers) server_service_rates=[], server_routing_probs=[] ) results = analyzer.analyze() # Check stability assert results.is_stable # Check coordinator metrics coord = results.coordinator assert coord.utilization == pytest.approx(0.8, abs=0.01) assert coord.average_customers == pytest.approx(4.0, abs=0.01) # L = 0.8/(1-0.8) = 4 assert coord.average_time == pytest.approx(5.0, abs=0.01) # W = L/λ = 4/0.8 = 5 def test_unstable_queue(self): """Test detection of unstable queue (ρ ≥ 1).""" # λ=1.0, μ=0.8, ρ=1.25 > 1 analyzer = JacksonAnalyzer( external_arrival_rate=1.0, coordinator_service_rate=0.8, coordinator_exit_prob=1.0, server_service_rates=[], server_routing_probs=[] ) results = analyzer.analyze() # Check instability assert not results.is_stable assert results.instability_reason is not None assert "coordinator" in results.instability_reason # Metrics should be infinite assert results.coordinator.average_customers == math.inf assert results.coordinator.average_time == math.inf def test_network_with_servers(self): """Test network with coordinator and servers.""" # λ=0.1, μc=1.0, p=0.5, q1=0.5, μ1=0.5 analyzer = JacksonAnalyzer( external_arrival_rate=0.1, coordinator_service_rate=1.0, coordinator_exit_prob=0.5, server_service_rates=[0.5], server_routing_probs=[0.5] ) results = analyzer.analyze() # Check stability assert results.is_stable # Coordinator: λc=0.1, μc=1.0, ρc=0.1 coord = results.coordinator assert coord.utilization == pytest.approx(0.1, abs=0.01) # Server 1: λ1=0.1*0.5=0.05, μ1=0.5, ρ1=0.1 server1 = results.servers["server_1"] assert server1.arrival_rate == pytest.approx(0.05, abs=0.01) assert server1.utilization == pytest.approx(0.1, abs=0.01) def test_littles_law(self): """Test that Little's Law holds: L = λ * W.""" analyzer = JacksonAnalyzer( external_arrival_rate=0.5, coordinator_service_rate=1.0, coordinator_exit_prob=0.7, server_service_rates=[0.6, 0.4], server_routing_probs=[0.2, 0.1] ) results = analyzer.analyze() if results.is_stable: # Check Little's Law for coordinator coord = results.coordinator L_from_formula = coord.average_customers L_from_littles = coord.arrival_rate * coord.average_time assert L_from_formula == pytest.approx(L_from_littles, abs=0.01) # Check for each server for server in results.servers.values(): L_from_formula = server.average_customers L_from_littles = server.arrival_rate * server.average_time assert L_from_formula == pytest.approx(L_from_littles, abs=0.01) # Check for entire network total_L = results.total_average_customers total_W = results.total_average_time lambda_0 = analyzer.lambda_0 assert total_L == pytest.approx(lambda_0 * total_W, abs=0.01) def test_probability_conservation(self): """Test that probabilities must sum to 1.0.""" with pytest.raises(ValueError, match="must sum to 1.0"): JacksonAnalyzer( external_arrival_rate=0.1, coordinator_service_rate=1.0, coordinator_exit_prob=0.5, server_service_rates=[0.5], server_routing_probs=[0.3] # 0.5 + 0.3 = 0.8 ≠ 1.0 ) def test_effective_arrival_rates(self): """Test calculation of effective arrival rates.""" analyzer = JacksonAnalyzer( external_arrival_rate=1.0, coordinator_service_rate=2.0, coordinator_exit_prob=0.4, server_service_rates=[1.0, 1.0, 1.0], server_routing_probs=[0.2, 0.2, 0.2] ) arrival_rates = analyzer.calculate_effective_arrival_rates() # Coordinator gets all external arrivals assert arrival_rates["coordinator"] == pytest.approx(1.0) # Each server gets λ * qi assert arrival_rates["server_1"] == pytest.approx(0.2) assert arrival_rates["server_2"] == pytest.approx(0.2) assert arrival_rates["server_3"] == pytest.approx(0.2) def test_multiple_servers_stability(self): """Test stability with multiple servers.""" # All servers stable analyzer = JacksonAnalyzer( external_arrival_rate=0.3, coordinator_service_rate=1.0, coordinator_exit_prob=0.25, server_service_rates=[1.0, 1.0, 1.0], server_routing_probs=[0.25, 0.25, 0.25] ) results = analyzer.analyze() assert results.is_stable # One server unstable analyzer2 = JacksonAnalyzer( external_arrival_rate=0.9, coordinator_service_rate=2.0, coordinator_exit_prob=0.1, server_service_rates=[0.5, 1.0, 1.0], # First server will be unstable server_routing_probs=[0.6, 0.15, 0.15] # λ1 = 0.9*0.6 = 0.54, μ1 = 0.5, ρ1 > 1 ) results2 = analyzer2.analyze() assert not results2.is_stable assert not results2.servers["server_1"].is_stable