From 9b009ef593bd08d91592d04000bc374354a90afe Mon Sep 17 00:00:00 2001 From: Samuel Bargallo Date: Tue, 11 Nov 2025 19:47:02 +0000 Subject: [PATCH] Added early stopping to the headless simulation. --- config/simulation_config.py | 1 + configs/headless_default.json | 2 +- engines/headless_engine.py | 20 ++++++++++++++++++-- entry_points/headless_main.py | 12 +++++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/config/simulation_config.py b/config/simulation_config.py index 5d9da4b..dda6d37 100644 --- a/config/simulation_config.py +++ b/config/simulation_config.py @@ -68,6 +68,7 @@ class HeadlessConfig: # Simulation settings max_ticks: Optional[int] = None max_duration: Optional[float] = None # seconds + early_stop: bool = False # Stop when 0 cells remaining # Output settings output: OutputConfig = field(default_factory=OutputConfig) diff --git a/configs/headless_default.json b/configs/headless_default.json index 95efff1..88ec68f 100644 --- a/configs/headless_default.json +++ b/configs/headless_default.json @@ -35,7 +35,7 @@ "starting_energy": 500, "interaction_radius": 50, "drag_coefficient": 0.02, - "energy_cost_base": 1.5, + "energy_cost_base": 1.4, "neural_network_complexity_cost": 0.05, "movement_cost": 0.25, "food_energy_value": 140, diff --git a/engines/headless_engine.py b/engines/headless_engine.py index dfd0749..391ace6 100644 --- a/engines/headless_engine.py +++ b/engines/headless_engine.py @@ -33,6 +33,7 @@ class HeadlessConfig: evolution_interval: int = 1000 output_formats: List[str] = None # ['json', 'csv'] real_time: bool = False # Whether to run in real-time or as fast as possible + early_stop: bool = False # Stop when 0 cells remaining def __post_init__(self): if self.output_formats is None: @@ -121,14 +122,14 @@ class HeadlessSimulationEngine: unit=unit, desc="Simulation", leave=True, # Keep the bar when done - bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]' + bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}] {postfix}' ) else: self.progress_bar = tqdm( unit='ticks', desc="Simulation", leave=True, - bar_format='{l_bar}{bar}| {n_fmt} [{elapsed}, {rate_fmt}]' + bar_format='{l_bar}{bar}| {n_fmt} [{elapsed}, {rate_fmt}] {postfix}' ) def _update_progress_bar(self): @@ -141,6 +142,10 @@ class HeadlessSimulationEngine: tps = self.simulation_core.state.actual_tps elapsed = current_time - self.start_time + # Get current cell count + world_state = self.simulation_core.get_world_state() + cell_count = world_state.get('entity_counts', {}).get('cells', 0) + if TQDM_AVAILABLE and self.progress_bar: # Use tqdm progress bar if self.config.max_ticks: @@ -149,6 +154,7 @@ class HeadlessSimulationEngine: self.progress_bar.n = progress self.progress_bar.set_postfix({ 'TPS': f'{tps:.1f}', + 'Cells': cell_count, 'Files': self.files_written }) elif self.config.max_duration: @@ -157,6 +163,7 @@ class HeadlessSimulationEngine: self.progress_bar.n = int(progress) self.progress_bar.set_postfix({ 'TPS': f'{tps:.1f}', + 'Cells': cell_count, 'Files': self.files_written, 'Tick': current_tick }) @@ -165,6 +172,7 @@ class HeadlessSimulationEngine: self.progress_bar.n = current_tick self.progress_bar.set_postfix({ 'TPS': f'{tps:.1f}', + 'Cells': cell_count, 'Files': self.files_written }) @@ -295,6 +303,14 @@ class HeadlessSimulationEngine: print(f"Reached max duration: {self.config.max_duration} seconds") return True + # Check early stopping condition (0 cells remaining) + if self.config.early_stop: + world_state = self.simulation_core.get_world_state() + cell_count = world_state.get('entity_counts', {}).get('cells', 0) + if cell_count == 0: + print(f"Early stopping: 0 cells remaining at tick {self.simulation_core.state.total_ticks}") + return True + return False def _collect_data(self): diff --git a/entry_points/headless_main.py b/entry_points/headless_main.py index 9154c8b..7fbc251 100644 --- a/entry_points/headless_main.py +++ b/entry_points/headless_main.py @@ -81,6 +81,11 @@ def main(): action="store_true", help="Create sample configuration files and exit" ) + parser.add_argument( + "--early-stop", + action="store_true", + help="Stop simulation when there are 0 cells remaining" + ) args = parser.parse_args() @@ -140,6 +145,10 @@ def main(): headless_config.output.collect_entities = True headless_config.output.collect_evolution = True + # Set early stopping flag + if args.early_stop: + headless_config.early_stop = True + except Exception as e: print(f"Error loading configuration: {e}") sys.exit(1) @@ -157,7 +166,8 @@ def main(): entities_interval=headless_config.output.entities_interval, evolution_interval=headless_config.output.evolution_interval, output_formats=headless_config.output.formats, - real_time=headless_config.output.real_time + real_time=headless_config.output.real_time, + early_stop=headless_config.early_stop ) # Create and run simulation