Skip to content

feat: add greedy lookahead algorithm with rolling horizon for hyperpixel building#9

Open
dhalmazna wants to merge 10 commits intofeat/algorithm-utilsfrom
feat/lookahead-algorithm
Open

feat: add greedy lookahead algorithm with rolling horizon for hyperpixel building#9
dhalmazna wants to merge 10 commits intofeat/algorithm-utilsfrom
feat/lookahead-algorithm

Conversation

@dhalmazna
Copy link
Copy Markdown
Collaborator

@dhalmazna dhalmazna commented Mar 16, 2026

Context:
This PR introduces the lookahead.py module, which implements a greedy search algorithm with a rolling horizon strategy for constructing hyperpixels. This serves as a baseline algorithm before we introduce full Monte Carlo search methods.

What's Changed / Added:

  • ciao/algorithm/lookahead.py: Added the implementation of the greedy lookahead builder. It uses BFS to explore paths.

How it works:

  1. BFS Lookahead: From the current mask, we run a BFS to generate all possible connected supersets up to lookahead_distance steps away.
  2. Batched Evaluation: All generated candidate masks are evaluated in a single forward pass.
  3. Rolling Horizon (Step Committing): We identify the candidate mask with the best score, but we only commit the first step (a single segment) of the path that leads to it. (Often, there are multiple "first steps". We simple choose the first one we found.) The state is updated, and the process repeats.

Note:
If it is not possible to reach the desired length, the algorithm stops and returns the longest region it could find. It is a question whether this is what we really want or whether it should end with an error.

Related Task:
XAI-29

Summary by CodeRabbit

New Features

  • Added a greedy lookahead algorithm for hyperpixel construction that evaluates multiple candidate regions over a rolling horizon and selects the optimal choice based on connectivity metrics and optimization criteria.

@dhalmazna dhalmazna self-assigned this Mar 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add greedy lookahead algorithm with rolling horizon for hyperpixel building' accurately and specifically describes the main change: a new greedy lookahead algorithm with rolling-horizon strategy for hyperpixel construction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lookahead-algorithm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes a foundational greedy lookahead algorithm for hyperpixel construction, employing a rolling horizon approach. This new algorithm provides a crucial baseline for evaluating more complex Monte Carlo search methods that will be integrated in the future, ensuring a structured progression in the development of hyperpixel building strategies.

Highlights

  • New Algorithm: Introduced a new greedy lookahead algorithm with a rolling horizon strategy for building hyperpixels, serving as a baseline for future Monte Carlo search methods.
  • File Structure: Added a new module ciao/algorithm/lookahead.py to house the new algorithm and updated README.md to reflect this change and the algorithm's description.
  • Module Export: Exported the build_hyperpixel_greedy_lookahead function from the ciao.algorithm package's __init__.py for external use.
Changelog
  • README.md
    • Updated the description of the 'Lookahead' algorithm to remove the 'using efficient bitset operations' phrase.
    • Corrected the file path reference for the lookahead algorithm from lookahead_bitset.py to lookahead.py.
  • ciao/algorithm/init.py
    • Imported the new build_hyperpixel_greedy_lookahead function.
    • Added build_hyperpixel_greedy_lookahead to the __all__ export list, making it publicly accessible.
  • ciao/algorithm/lookahead.py
    • Added a new Python module implementing the build_hyperpixel_greedy_lookahead function.
    • Implemented a rolling horizon strategy that uses BFS to generate candidate masks up to a specified lookahead_distance.
    • Included a helper function _generate_lookahead_candidates to perform the BFS for connected supersets.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a greedy lookahead algorithm, which is a solid addition as a baseline for hyperpixel construction. The implementation is well-structured, with a clear separation between the main building loop and the candidate generation logic. The use of a rolling horizon strategy is correctly implemented. My main suggestion is to use TypedDict for the return type to improve type safety and code clarity, which is a lightweight improvement over the current dict[str, object] while you plan for a future refactor to a dataclass.

@dhalmazna dhalmazna marked this pull request as ready for review March 16, 2026 15:52
Copilot AI review requested due to automatic review settings March 16, 2026 15:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a greedy lookahead algorithm for building hyperpixels using a rolling horizon strategy (BFS lookahead + commit-one-step), along with a HyperpixelResult TypedDict to formalize the return contract of hyperpixel builders.

Changes:

  • New ciao/algorithm/lookahead.py implementing BFS-based greedy lookahead with rolling horizon for hyperpixel construction.
  • New HyperpixelResult TypedDict in ciao/scoring/hyperpixel.py and migration of select_top_hyperpixels to use it (including renaming the sort key from hyperpixel_score to score).
  • README and __init__.py updates to reflect the new module.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
ciao/algorithm/lookahead.py New greedy lookahead builder using BFS candidate generation and rolling horizon commit strategy
ciao/scoring/hyperpixel.py Added HyperpixelResult TypedDict; updated select_top_hyperpixels to use it
ciao/algorithm/__init__.py Re-exports the new build_hyperpixel_greedy_lookahead function
README.md Updated algorithm description and file tree to reflect the renamed module

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +169 to +210
def _generate_lookahead_candidates(
current_mask: int,
adj_masks: tuple[int, ...],
used_mask: int,
lookahead_distance: int,
desired_length: int,
) -> dict[int, int]:
"""Generate all connected supersets up to lookahead_distance steps via BFS.

Returns:
Dict mapping candidate_mask -> first_step_segment_id
"""
candidates: dict[int, int] = {} # mask -> first_step

# Queue stores tuples of: (current_mask, first_step_that_led_here, current_depth)
queue: deque[tuple[int, int | None, int]] = deque([(current_mask, None, 0)])
visited = {current_mask}

while queue:
mask, first_step, depth = queue.popleft()

# Store valid candidates (depth > 0)
if depth > 0 and first_step is not None and mask not in candidates:
# Only add if not already seen (shortest path wins)
candidates[mask] = first_step

# Stop expanding if we reached the lookahead limit or maximum size
if depth >= lookahead_distance or mask.bit_count() >= desired_length:
continue

frontier = get_frontier(mask, adj_masks, used_mask)
for seg_id in iter_bits(frontier):
new_mask = add_node(mask, seg_id)

if new_mask not in visited:
visited.add(new_mask)
# If at the first layer (depth 0), this seg_id is our first_step.
# Otherwise, pass along the first_step inherited from the parent.
next_first_step = seg_id if depth == 0 else first_step
queue.append((new_mask, next_first_step, depth + 1))

return candidates
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the user's problem if he sets the lookahead_distance too large :D

@dhalmazna dhalmazna force-pushed the feat/lookahead-algorithm branch from 33727dd to d260b36 Compare March 17, 2026 13:38
@dhalmazna dhalmazna force-pushed the feat/algorithm-utils branch from dd0ad92 to b2ab7f8 Compare March 17, 2026 13:38
@dhalmazna dhalmazna marked this pull request as draft March 23, 2026 06:51
@dhalmazna dhalmazna force-pushed the feat/algorithm-utils branch from b2ab7f8 to 4282be2 Compare March 23, 2026 07:21
@dhalmazna dhalmazna force-pushed the feat/lookahead-algorithm branch from d260b36 to 4935570 Compare March 23, 2026 07:21
@dhalmazna dhalmazna force-pushed the feat/algorithm-utils branch from 4282be2 to a53863c Compare March 25, 2026 08:08
@dhalmazna dhalmazna force-pushed the feat/lookahead-algorithm branch from 4935570 to e2ebbc1 Compare March 25, 2026 08:16
@dhalmazna dhalmazna force-pushed the feat/lookahead-algorithm branch from e2ebbc1 to 4cfdb99 Compare March 25, 2026 08:45
@dhalmazna dhalmazna changed the base branch from feat/algorithm-utils to feat/segment-and-hyperpixel-scoring March 25, 2026 09:27
@dhalmazna dhalmazna changed the base branch from feat/segment-and-hyperpixel-scoring to feat/algorithm-utils March 25, 2026 09:37
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ciao/algorithm/lookahead.py (1)

68-83: Sort candidate regions and frontier traversal for deterministic iteration.

When candidate scores tie, the result depends on frozenset iteration order from get_frontier(), causing identical inputs to yield different first committed steps across runs. Sort candidate_regions by their segment IDs and use tuple sorting as a tiebreaker in the max() call. Also sort frontier at line 159 during BFS to prevent non-deterministic candidate dict insertion order.

Proposed fix
-        candidate_regions = list(candidates.keys())
+        candidate_regions = sorted(candidates.keys(), key=lambda r: tuple(sorted(r)))
@@
-        best_idx = max(
-            range(len(scores_list)), key=lambda i: scores_list[i] * optimization_sign
-        )
+        best_idx = max(
+            range(len(scores_list)),
+            key=lambda i: (
+                scores_list[i] * optimization_sign,
+                tuple(sorted(candidate_regions[i])),
+            ),
+        )
@@
-        for seg_id in frontier:
+        for seg_id in sorted(frontier):
             new_region = frozenset(region | {seg_id})

Also applies to: 159-167

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ciao/algorithm/lookahead.py` around lines 68 - 83, Sort candidate_regions
deterministically and make tie-breaking deterministic in the best selection:
after building candidate_regions (used as the segment_sets argument to
calculate_hyperpixel_deltas), sort it by a stable key such as a tuple of segment
IDs (use image_graph.segments to derive IDs) and then when computing best_idx
use max(..., key=lambda i: (scores_list[i] * optimization_sign,
tuple(sorted(candidate_regions[i])))) so ties fall back to the segment-id tuple
order; additionally, in the BFS frontier construction in get_frontier (the code
that builds frontier at the lines around the BFS loop) sort the frontier items
(e.g., convert frozensets to sorted tuples and iterate in sorted order) before
inserting into the candidate dict to ensure deterministic insertion order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ciao/algorithm/lookahead.py`:
- Around line 50-55: Before entering the growth loop validate preconditions:
check that desired_length is a positive int and >=1 (and optionally >=
current_region size), lookahead_distance is a positive int (>=1 and <=
desired_length), optimization_sign is one of the expected values (e.g. 1 or -1
or the accepted enum/strings), and seed_idx is a valid index and not present in
used_segments; if any check fails raise a clear ValueError with context. Add
these checks immediately before the loop that uses current_region and seed_idx
(the while block that grows the hyperpixel) so invalid inputs fail fast and
prevent silent degradation or exclusion-constraint violations.

---

Nitpick comments:
In `@ciao/algorithm/lookahead.py`:
- Around line 68-83: Sort candidate_regions deterministically and make
tie-breaking deterministic in the best selection: after building
candidate_regions (used as the segment_sets argument to
calculate_hyperpixel_deltas), sort it by a stable key such as a tuple of segment
IDs (use image_graph.segments to derive IDs) and then when computing best_idx
use max(..., key=lambda i: (scores_list[i] * optimization_sign,
tuple(sorted(candidate_regions[i])))) so ties fall back to the segment-id tuple
order; additionally, in the BFS frontier construction in get_frontier (the code
that builds frontier at the lines around the BFS loop) sort the frontier items
(e.g., convert frozensets to sorted tuples and iterate in sorted order) before
inserting into the candidate dict to ensure deterministic insertion order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f44c16c-0e75-4456-9c10-48b543c502c8

📥 Commits

Reviewing files that changed from the base of the PR and between e8fa57b and f600dd9.

📒 Files selected for processing (1)
  • ciao/algorithm/lookahead.py

@dhalmazna dhalmazna requested a review from vejtek March 28, 2026 11:29
@dhalmazna dhalmazna marked this pull request as ready for review March 28, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants