from functools import reduce from math import lcm import pytest from reasoning_gym.arithmetic import LCMConfig, LCMCurriculum, LCMDataset def test_lcm_config_validation(): """Test that invalid configs raise appropriate errors""" with pytest.raises(AssertionError): config = LCMConfig(min_numbers=1) # Should be >= 2 config.validate() with pytest.raises(AssertionError): config = LCMConfig(min_numbers=3, max_numbers=2) # max should be >= min config.validate() with pytest.raises(AssertionError): config = LCMConfig(min_value=0) # Should be positive config.validate() with pytest.raises(AssertionError): config = LCMConfig(min_value=100, max_value=50) # max should be > min config.validate() def test_lcm_deterministic(): """Test that dataset generates same items with same seed""" config = LCMConfig(seed=42, size=10) dataset1 = LCMDataset(config) dataset2 = LCMDataset(config) for i in range(len(dataset1)): assert dataset1[i] == dataset2[i] def test_lcm_items(): """Test basic properties of generated items""" config = LCMConfig( min_numbers=2, max_numbers=4, min_value=1, max_value=20, size=50, seed=42 # Keep small for testing ) dataset = LCMDataset(config) for i in range(len(dataset)): item = dataset[i] assert isinstance(item, dict) assert "question" in item assert "answer" in item assert "metadata" in item # Verify the numbers and result are in metadata metadata = item["metadata"] assert "numbers" in metadata assert "result" in metadata # Verify the numbers are within configured range numbers = metadata["numbers"] assert all(config.min_value <= n <= config.max_value for n in numbers) assert config.min_numbers <= len(numbers) <= config.max_numbers # Verify the LCM calculation is correct result = metadata["result"] assert str(result) == item["answer"] assert result == reduce(lcm, numbers) def test_lcm_number_ranges(): """Test that generated numbers respect value constraints""" config = LCMConfig(min_numbers=2, max_numbers=2, min_value=5, max_value=15, size=20, seed=42) dataset = LCMDataset(config) for i in range(len(dataset)): item = dataset[i] numbers = item["metadata"]["numbers"] assert all(5 <= n <= 15 for n in numbers) def test_lcm_iteration(): """Test that iteration works correctly""" config = LCMConfig(size=5, seed=42) dataset = LCMDataset(config) # Test manual iteration items = [] for item in dataset: items.append(item) assert len(items) == config.size # Test list conversion items = list(dataset) assert len(items) == config.size # Test multiple iterations yield same results first_items = list(dataset) second_items = list(dataset) assert first_items == second_items def test_lcm_special_cases(): """Test some special LCM cases""" config = LCMConfig(min_numbers=2, max_numbers=2, min_value=1, max_value=20, size=100, seed=42) dataset = LCMDataset(config) # Track if we see some interesting LCM cases seen_equal_to_product = False # When numbers are coprime seen_less_than_product = False # When numbers share factors for i in range(len(dataset)): item = dataset[i] numbers = item["metadata"]["numbers"] result = int(item["answer"]) product = reduce(lambda x, y: x * y, numbers) if result == product: seen_equal_to_product = True if result < product: seen_less_than_product = True # With enough samples, we should see both cases assert seen_equal_to_product, "Expected to see some coprime numbers (LCM = product)" assert seen_less_than_product, "Expected to see some numbers with common factors (LCM < product)" def test_lcm_curriculum(): curriculum = LCMCurriculum() base_value = {"size": 150, "seed": 1} base_cfg: LCMConfig = curriculum.generate_configuration(base_value) assert base_cfg.seed == 1 assert base_cfg.size == 150 assert base_cfg.min_numbers == 2 and base_cfg.max_numbers == 2 assert base_cfg.min_value == 100 and base_cfg.max_value == 1000 # test incrementing attribute levels curriculum.increment_attr_level("numbers") curriculum.increment_attr_level("value") increased_cfg = curriculum.generate_configuration(base_value) assert increased_cfg.min_numbers == 2 and increased_cfg.max_numbers == 3 assert increased_cfg.min_value == 100 and increased_cfg.max_value == 10000 # test decrementing attribute level for numbers again curriculum.decrement_attr_level("numbers") partially_decreased_cfg = curriculum.generate_configuration(base_value) assert partially_decreased_cfg.min_numbers == 2 and partially_decreased_cfg.max_numbers == 2 assert partially_decreased_cfg.min_value == 100 and partially_decreased_cfg.max_value == 10000