From 7b36a3a3346f9986874a806a1e60dac9a7f3c8f4 Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 15 Apr 2026 23:47:22 +0300 Subject: [PATCH 1/3] Update safeguards for GA when could not sample enough rays - just output a warning and continue. --- dreamer/configs/search.py | 2 ++ dreamer/extraction/samplers/raycast_sampler.py | 8 ++++---- dreamer/search/methods/genetic.py | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dreamer/configs/search.py b/dreamer/configs/search.py index d9f96a3..032172f 100644 --- a/dreamer/configs/search.py +++ b/dreamer/configs/search.py @@ -51,5 +51,7 @@ class SearchConfig(Configurable): GA_REFINE_PROB: float = 0.5 # Probability of entering refine mutation mode. GA_REFINE_COORD_PROB: float = 0.5 # Per-coordinate refine perturbation probability. + MAX_TRAJECTORY_COORD: int = 50 + search_config: SearchConfig = SearchConfig() diff --git a/dreamer/extraction/samplers/raycast_sampler.py b/dreamer/extraction/samplers/raycast_sampler.py index 1f2f47a..75841a8 100644 --- a/dreamer/extraction/samplers/raycast_sampler.py +++ b/dreamer/extraction/samplers/raycast_sampler.py @@ -3,8 +3,11 @@ from dreamer.extraction.samplers.conditioner import HyperSpaceConditioner from dreamer.extraction.samplers.raycaster import RayCastingSamplingMethod from dreamer.utils.logger import Logger +from dreamer.configs.search import search_config from .sampler import Sampler from typing import Callable, cast +import math + class RaycastPipelineSampler(Sampler): @@ -197,7 +200,7 @@ def finalize_rays(raw_rays, target_rays): final_rays = best_rays return final_rays - max_radius = 100 + max_radius = math.sqrt(pow(search_config.MAX_TRAJECTORY_COORD, 2) * d_flat) + 1 raw_rays = np.array([]) raddai = [] @@ -212,9 +215,6 @@ def finalize_rays(raw_rays, target_rays): ).log() break - # Logger(f"Sweeping lattice up to R_max = {current_R_max:.2f}...", Logger.Levels.debug).log() - - # Enforce max_per_ray=1 for the "Fair Slice" raw_rays = sampler.harvest( target_rays=guide_rays_to_shoot, R_max=current_R_max, diff --git a/dreamer/search/methods/genetic.py b/dreamer/search/methods/genetic.py index 3d86142..ff795d1 100644 --- a/dreamer/search/methods/genetic.py +++ b/dreamer/search/methods/genetic.py @@ -282,7 +282,10 @@ def _sample_valid_trajectories(self, *, count: int, template_pos: Position) -> L break if len(sampled) < count: - raise ValueError("Genetic search could not sample enough valid trajectories satisfying A v <= 0") + Logger( + f"Genetic search could not sample enough valid trajectories. Sampled {len(sampled)}/{count}", + Logger.Levels.warning + ).log() return sampled[:count] def _get_valid_repair_trajectory(self, template_pos: Position) -> Position: From 16b0d608571beab1e618487a2bf7f95a8c014751 Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 15 Apr 2026 23:59:17 +0300 Subject: [PATCH 2/3] Max sure not max out GA retries --- dreamer/configs/search.py | 3 ++- dreamer/search/methods/genetic.py | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dreamer/configs/search.py b/dreamer/configs/search.py index 032172f..ab42221 100644 --- a/dreamer/configs/search.py +++ b/dreamer/configs/search.py @@ -50,8 +50,9 @@ class SearchConfig(Configurable): GA_MAX_RETRIES: int = 3 # Retry rounds for invalid/failed trajectory evaluations. GA_REFINE_PROB: float = 0.5 # Probability of entering refine mutation mode. GA_REFINE_COORD_PROB: float = 0.5 # Per-coordinate refine perturbation probability. + GA_MAX_NO_IMPROVEMENT_COUNT_RETRY: int = 5 # Max retries when no improvement is observed before giving up. - MAX_TRAJECTORY_COORD: int = 50 + MAX_TRAJECTORY_COORD: int = 50 # Max coordinate value for a trajectory. search_config: SearchConfig = SearchConfig() diff --git a/dreamer/search/methods/genetic.py b/dreamer/search/methods/genetic.py index ff795d1..a0e167d 100644 --- a/dreamer/search/methods/genetic.py +++ b/dreamer/search/methods/genetic.py @@ -296,7 +296,8 @@ def _get_valid_repair_trajectory(self, template_pos: Position) -> Position: count=self._buffer_chunk_size, template_pos=template_pos ) - random.shuffle(self._valid_trajectory_buffer) + if self._valid_trajectory_buffer: + random.shuffle(self._valid_trajectory_buffer) return self._valid_trajectory_buffer.pop() def _repair_trajectory(self, trajectory: Position, template_pos: Position) -> Position: @@ -409,11 +410,28 @@ def _evaluate_population( population[i]["sd"] = sd unresolved = invalid_indices + unchanged_count = 0 + last_found_amount = -1 + for _ in range(self.max_retries): if not unresolved: break + if unchanged_count >= search_config.GA_MAX_NO_IMPROVEMENT_COUNT_RETRY: + Logger( + "Genetic algorithm solving unresolved trajectories - giving up resampling.", Logger.Levels.debug + ).log() + break + retry_trajectories = self._sample_valid_trajectories(count=len(unresolved), template_pos=template_pos) + if len(retry_trajectories) == 0: + Logger("No valid trajectories could be sampled") + + if last_found_amount == -1: + last_found_amount = len(retry_trajectories) + elif last_found_amount == len(retry_trajectories): + unchanged_count += 1 + retry_pairs = [(traj, start) for traj in retry_trajectories] self._compute_missing_search_data(retry_pairs) @@ -434,7 +452,6 @@ def _evaluate_population( next_unresolved.append(i) unresolved = next_unresolved - return population def search( @@ -453,6 +470,10 @@ def search( template = self._resolve_template(template_trajectory) initial_trajectories = self._sample_valid_trajectories(count=self.pop_size, template_pos=template) + if len(initial_trajectories) == 0: + Logger("No valid trajectories could be sampled. Continue...", Logger.Levels.warning).log() + return DataManager(self.use_LIReC) + population: List[Dict[str, Any]] = [ {"trajectory": traj, "delta": None, "sd": None} for traj in initial_trajectories ] From f0655550c1efa23282bcc43aacc7f89640101dfc Mon Sep 17 00:00:00 2001 From: UriKH Date: Thu, 16 Apr 2026 00:03:35 +0300 Subject: [PATCH 3/3] remove irrelevant test --- tests/test_search_genetic.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/test_search_genetic.py b/tests/test_search_genetic.py index efa0fcc..35c68da 100644 --- a/tests/test_search_genetic.py +++ b/tests/test_search_genetic.py @@ -375,24 +375,6 @@ def _always_invalid_mutate(_pos, **_kwargs): assert all(space.is_valid_trajectory(sd.sv.trajectory) for sd in result.values()) -def test_genetic_search_raises_when_constraints_have_no_valid_trajectories(monkeypatch): - """Ensure search fails loudly when constraints admit no valid trajectories. - Assumption: sampler yields only invalid trajectories for constrained space. - Failure mode caught: infinite retries or misleading exception messages. - """ - space = ImpossibleConstrainedSpace() - x, y = space.cmf.symbols - _patch_static_sampler(monkeypatch, [Position({x: 1, y: 1}), Position({x: 2, y: 2})]) - _configure_ga(monkeypatch, generations=1, pop_size=2, max_retries=1, parallel_search=False) - method = GeneticSearchMethod( - cast(Searchable, cast(object, space)), - constant=None, - ) - - with pytest.raises(ValueError, match="could not sample enough valid trajectories"): - method.search(template_trajectory=Position({x: 1, y: 1})) - - def test_evaluate_population_resamples_invalid_trajectories_in_batch(monkeypatch): """Verify invalid individuals are resampled in one batch per retry round. Assumption: initial cache lookup returns no SearchData for all individuals.