diff --git a/reasoning_gym/algorithmic/__init__.py b/reasoning_gym/algorithmic/__init__.py index 241dd369..337dae60 100644 --- a/reasoning_gym/algorithmic/__init__.py +++ b/reasoning_gym/algorithmic/__init__.py @@ -13,7 +13,7 @@ from .binary_matrix import BinaryMatrixConfig, BinaryMatrixCurriculum, BinaryMat from .caesar_cipher import CaesarCipherConfig, CaesarCipherCurriculum, CaesarCipherDataset from .count_primes import CountPrimesConfig, CountPrimesCurriculum, CountPrimesDataset from .cryptarithm import CryptarithmConfig, CryptarithmCurriculum, CryptarithmDataset -from .game_of_life import GameOfLifeConfig, GameOfLifeDataset +from .game_of_life import GameOfLifeConfig, GameOfLifeCurriculum, GameOfLifeDataset from .game_of_life_halting import GameOfLifeHaltingConfig, GameOfLifeHaltingDataset from .graph_color import GraphColorConfig, GraphColorCurriculum, GraphColorDataset from .group_anagrams import GroupAnagramsConfig, GroupAnagramsCurriculum, GroupAnagramsDataset @@ -64,6 +64,7 @@ __all__ = [ "CryptarithmCurriculum", "GameOfLifeConfig", "GameOfLifeDataset", + "GameOfLifeCurriculum", "GameOfLifeHaltingConfig", "GameOfLifeHaltingDataset", "LetterCountingConfig", diff --git a/reasoning_gym/algorithmic/game_of_life.py b/reasoning_gym/algorithmic/game_of_life.py index ae2b0e5a..12857e52 100644 --- a/reasoning_gym/algorithmic/game_of_life.py +++ b/reasoning_gym/algorithmic/game_of_life.py @@ -5,6 +5,7 @@ from typing import Any, Optional import cellpylib as cpl +from ..coaching import AttributeType, BaseCurriculum, ScalarAttributeDefinition from ..factory import ProceduralDataset, register_dataset @@ -14,7 +15,8 @@ class GameOfLifeConfig: grid_size_x: int = 10 grid_size_y: int = 10 - filled_cells: int = 100 # actually a max + filled_cells_weights: float = 0.1 + filled_cells: int = int(filled_cells_weights * grid_size_x * grid_size_y) # actually a max simulation_steps: int = 1 seed: Optional[int] = None size: int = 500 @@ -83,6 +85,12 @@ class GameOfLifeDataset(ProceduralDataset): "grid_size_y": self.config.grid_size_y, "filled_cells": self.config.filled_cells, "simulation_steps": self.config.simulation_steps, + "difficulty": { + "grid_size_x": self.config.grid_size_x, + "grid_size_y": self.config.grid_size_y, + "filled_cells_weights": self.config.filled_cells_weights, + "simulation_steps": self.config.simulation_steps, + }, }, } @@ -143,4 +151,52 @@ class GameOfLifeDataset(ProceduralDataset): return correct_cells / total_cells -register_dataset("game_of_life", GameOfLifeDataset, GameOfLifeConfig) +class GameOfLifeCurriculum(BaseCurriculum): + """Curriculum for Game of Life dataset""" + + def __init__(self): + super().__init__(GameOfLifeCurriculum.__name__, GameOfLifeConfig) + + # Define attributes + self._define_attributes( + ScalarAttributeDefinition( + name="grid_size_x", + field_name="grid_size_x", + levels=[10, 100, 500, 999], + default_level=0, + description="Grid size in the x direction", + attr_type=AttributeType.STATIC, + min_value=10, + ), + ScalarAttributeDefinition( + name="grid_size_y", + field_name="grid_size_y", + levels=[10, 100, 500, 999], + default_level=0, + description="Grid size in the y direction", + attr_type=AttributeType.STATIC, + min_value=-10, + ), + # Filled cells should be 10%, 20%, 30%, 50% of the grid_size_x * grid_size_y + ScalarAttributeDefinition( + name="filled_cells_weights", + field_name="filled_cells_weights", + levels=[0.1, 0.2, 0.5, 0.8], + default_level=0, + description="Percentage of filled cells in the grid", + attr_type=AttributeType.STATIC, + min_value=0.1, + ), + ScalarAttributeDefinition( + name="simulation_steps", + field_name="simulation_steps", + levels=[1, 2, 5, 10], + default_level=0, + description="Number of simulation steps to run", + attr_type=AttributeType.STATIC, + min_value=1, + ), + ) + + +register_dataset("game_of_life", GameOfLifeDataset, GameOfLifeConfig, GameOfLifeCurriculum) diff --git a/tests/test_game_of_life.py b/tests/test_game_of_life.py index a0b8798e..9083914a 100644 --- a/tests/test_game_of_life.py +++ b/tests/test_game_of_life.py @@ -2,7 +2,7 @@ import json import pytest -from reasoning_gym.algorithmic.game_of_life import GameOfLifeConfig, GameOfLifeDataset +from reasoning_gym.algorithmic.game_of_life import GameOfLifeConfig, GameOfLifeCurriculum, GameOfLifeDataset def test_game_of_life_config_validation(): @@ -93,3 +93,70 @@ def test_game_of_life_iteration(): first_items = list(dataset) second_items = list(dataset) assert first_items == second_items, "Multiple iterations should yield same items" + + +def test_game_of_life_curriculum(): + """Test the curriculum for complex arithmetic.""" + curriculum = GameOfLifeCurriculum() + base_value = {"size": 150, "seed": 1} + + base_cfg: GameOfLifeCurriculum = curriculum.generate_configuration(base_value) + + assert base_cfg.seed == 1 + assert base_cfg.size == 150 + assert base_cfg.grid_size_x == 10 + assert base_cfg.grid_size_y == 10 + assert base_cfg.filled_cells <= base_cfg.grid_size_x * base_cfg.grid_size_y + assert base_cfg.simulation_steps == 1 + + # Test and validate increase in levels + curriculum.increment_attr_level("grid_size_x") + curriculum.increment_attr_level("grid_size_y") + curriculum.increment_attr_level("filled_cells_weights") + curriculum.increment_attr_level("simulation_steps") + + increased_cfg: GameOfLifeCurriculum = curriculum.generate_configuration(base_value) + assert increased_cfg.grid_size_x == 100 + assert increased_cfg.grid_size_y == 100 + assert increased_cfg.filled_cells_weights == 0.2 + assert increased_cfg.filled_cells <= increased_cfg.grid_size_x * increased_cfg.grid_size_y + assert increased_cfg.simulation_steps == 2 + + # Test and validate decrease in levels + curriculum.decrement_attr_level("grid_size_x") + curriculum.decrement_attr_level("grid_size_y") + curriculum.decrement_attr_level("filled_cells_weights") + curriculum.decrement_attr_level("simulation_steps") + + decreased_cfg: GameOfLifeCurriculum = curriculum.generate_configuration(base_value) + assert decreased_cfg.grid_size_x == 10 + assert decreased_cfg.grid_size_y == 10 + assert decreased_cfg.filled_cells_weights == 0.1 + assert decreased_cfg.filled_cells <= decreased_cfg.grid_size_x * decreased_cfg.grid_size_y + assert decreased_cfg.simulation_steps == 1 + + # Test upper bound boundary condition + for _ in range(10): + curriculum.increment_attr_level("grid_size_x") + curriculum.increment_attr_level("grid_size_y") + curriculum.increment_attr_level("filled_cells_weights") + curriculum.increment_attr_level("simulation_steps") + upper_bound_cfg: GameOfLifeCurriculum = curriculum.generate_configuration(base_value) + assert upper_bound_cfg.grid_size_x == 999 + assert upper_bound_cfg.grid_size_y == 999 + assert upper_bound_cfg.filled_cells_weights == 0.8 + assert upper_bound_cfg.filled_cells <= upper_bound_cfg.grid_size_x * upper_bound_cfg.grid_size_y + assert upper_bound_cfg.simulation_steps == 10 + + # Test lower bound boundary condition + for _ in range(10): + curriculum.decrement_attr_level("grid_size_x") + curriculum.decrement_attr_level("grid_size_y") + curriculum.decrement_attr_level("filled_cells_weights") + curriculum.decrement_attr_level("simulation_steps") + lower_bound_cfg: GameOfLifeCurriculum = curriculum.generate_configuration(base_value) + assert lower_bound_cfg.grid_size_x == 10 + assert lower_bound_cfg.grid_size_y == 10 + assert lower_bound_cfg.filled_cells_weights == 0.1 + assert lower_bound_cfg.filled_cells <= lower_bound_cfg.grid_size_x * lower_bound_cfg.grid_size_y + assert lower_bound_cfg.simulation_steps == 1