Skip to content

Dev - #15

Merged
r-fedorov merged 4 commits into
refactor_namerfrom
dev
Jun 17, 2026
Merged

Dev#15
r-fedorov merged 4 commits into
refactor_namerfrom
dev

Conversation

@r-fedorov

@r-fedorov r-fedorov commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR updates parent selection behavior for spiro-connected ring systems so that spiro ring components are evaluated through their shared backbone before ordinary parent seniority rules are applied.

The change prevents smaller hetero side rings from incorrectly taking the principal parent position solely because senior-element ordering was applied before ring-system complexity.

Changes

  • Adds _prefer_spiro_backbone_components() to narrow eligible parent candidates to the shared spiro backbone component when principal-group coverage is tied.
  • Applies the spiro backbone preference before sorting principal parent candidates.
  • Adjusts ring-system seniority ordering so ring-over-chain selection happens before ring-system-specific criteria.
  • Restores senior element vector comparison for ring parents.
  • Updates parent selection tests to reflect the corrected ordering behavior.
  • Minor import ordering cleanup in the OPSIN PubChem evaluation example.

Testing

  • Updated existing parent selection tests to cover the revised seniority ordering.
  • Added/adjusted assertions for ring-vs-chain, hetero ring ordering, and spiro-related parent selection behavior.

Summary by Sourcery

Adjust parent selection to better handle spiro-connected ring systems and restore senior-element-based ordering for ring parents.

New Features:

  • Introduce a spiro backbone preference that limits competing parent candidates in spiro-connected ring systems to the shared backbone component when principal-group coverage is tied.

Bug Fixes:

  • Prevent small hetero side rings from incorrectly outranking spiro backbone components as principal parents by applying ring-system complexity before senior-element ordering.
  • Restore senior element vector comparison for ring parents so hetero ordering is applied consistently across ring and chain candidates.

Enhancements:

  • Refine ring-system seniority ordering so ring-over-chain selection precedes ring-system-specific criteria and adjust parent seniority tests to match the intended Brief Guide ordering.
  • Tidy long failure messages and import ordering in tests and examples for improved readability.

Tests:

  • Add and update unit tests covering senior element vector ordering for ring vs chain parents, spiro backbone filtering behavior, and revised ring-vs-chain and hetero ring seniority rules.

@sourcery-ai

sourcery-ai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refines principal parent selection to better handle spiro-connected ring systems and ring-vs-chain seniority by introducing a spiro-backbone filter, restoring senior-element comparison for rings, and tightening ring-system seniority rules, along with corresponding test updates and minor formatting/import cleanups.

File-Level Changes

Change Details Files
Adjust seniority profile scoring so ring parents again participate in senior-element vector comparison and use a simplified ring-system seniority key.
  • Remove the special case that returned an empty tuple for senior_element_vector when the candidate is a ring parent so rings are ordered by the vector like chains.
  • Update ring_seniority_key to skip non-ring parents and to use heteroatom vector, ring count, atom count, and path tiebreak without the previous polycycle guard term.
src/bluenamer/parent_selection.py
Introduce a spiro-backbone-aware filter that prunes competing spiro ring components before running full parent seniority sorting.
  • Add _prefer_spiro_backbone_components to compute shared-atom counts per ring path, identify candidates tied on principal-group coverage, and restrict competing spiro ring components to those on the shared backbone.
  • Define _spiro_backbone_rank_key to rank competing spiro ring components by shared connectivity, ring count, atom count, and path tiebreak.
  • Invoke _prefer_spiro_backbone_components in select_principal_parent before sorting candidates by score_tuple.
src/bluenamer/parent_selection.py
src/bluenamer/tests/test_analysis.py
Expand and adjust tests to validate revised ring/chain, hetero ring, and spiro parent seniority behavior.
  • Relax the intent of the parent_selection_criteria_are_data_ordered test so it focuses only on data-ordering guarantees.
  • Add test_senior_element_vector_orders_ring_and_chain_parent_profiles to confirm senior-element ordering now applies to both rings and chains.
  • Add test_spiro_backbone_filter_keeps_noncompeting_candidates_and_matches_all_ring_paths to exercise the new spiro-backbone filter logic.
  • Update brief-guide-based seniority test expectations to reflect revised ring vs element ordering (e.g., phosphorus vs oxygen ring preference, carbon ring vs chain).
src/bluenamer/tests/test_analysis.py
Tidy integration tests and example script formatting and imports.
  • Simplify the failure message construction in test_corpus_golden to use a single f-string.
  • Adjust the failure message in test_corpus_naming_pass_rate to improve readability of the newline-separated text.
  • Reorder imports in the OPSIN PubChem evaluation example so bluenamer.utils import follows bluenamer.namer.
tests/integration/test_corpus_golden.py
tests/integration/test_diverse_corpus.py
examples/opsin_eval_pubchem.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 2f2c8a59-81f6-43e7-8066-ebe72731db88

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@r-fedorov r-fedorov self-assigned this Jun 17, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • In _prefer_spiro_backbone_components, when a backbone is identified you return only backbone, which drops all other candidates (including non-ring ones); consider returning the backbone-filtered subset only for the tied spiro components and keeping non-competing candidates in the list so they are still considered by the global ranking.
  • The mapping between ring_systems and ParentCandidates in _prefer_spiro_backbone_components relies on system.paths[0] and candidate.path being identical sequences; if path ordering can differ, it may be safer to key on a more stable representation (e.g. a frozenset of atoms or a canonicalized path) to avoid missing or misclassifying spiro components.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_prefer_spiro_backbone_components`, when a backbone is identified you return only `backbone`, which drops all other candidates (including non-ring ones); consider returning the backbone-filtered subset only for the tied spiro components and keeping non-competing candidates in the list so they are still considered by the global ranking.
- The mapping between `ring_systems` and `ParentCandidate`s in `_prefer_spiro_backbone_components` relies on `system.paths[0]` and `candidate.path` being identical sequences; if path ordering can differ, it may be safer to key on a more stable representation (e.g. a frozenset of atoms or a canonicalized path) to avoid missing or misclassifying spiro components.

## Individual Comments

### Comment 1
<location path="src/bluenamer/parent_selection.py" line_range="379-381" />
<code_context>
+    if len(ring_systems) < 2:
+        return candidates
+
+    ring_system_by_path = {tuple(system.paths[0]): system for system in ring_systems}
+    shared_counts: dict[tuple[int, ...], int] = {}
+    for system in ring_systems:
+        path_key = tuple(system.paths[0])
+        shared_counts[path_key] = sum(1 for other in ring_systems if other is not system and system.atoms & other.atoms)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid unused variables and clarify reliance on `paths[0]` for ring-system identity.

`path_key` is computed but never used, and `ring_system_by_path` seems redundant if you only need the membership information already implied by `shared_counts`. More critically, both structures are keyed by `system.paths[0]`, which assumes index 0 is always the canonical path. If a candidate can reference other entries in `system.paths`, those ring systems will be missed. Either key by all relevant `system.paths` or centralize and reuse a single, explicit “canonical path” definition shared by candidates and ring systems.

Suggested implementation:

```python
    if len(ring_systems) < 2:
        return candidates

    shared_counts: dict[tuple[int, ...], int] = {}
    for system in ring_systems:
        shared_count = sum(
            1
            for other in ring_systems
            if other is not system and system.atoms & other.atoms
        )
        for path in system.paths:
            shared_counts[tuple(path)] = shared_count

```

If other parts of the file still reference `ring_system_by_path`, they should be updated to work off `shared_counts` or otherwise use `system.paths` consistently. If there is (or should be) a shared “canonical path” helper elsewhere in the codebase, consider replacing `for path in system.paths:` with that helper to guarantee consistent keying across candidates and ring systems.
</issue_to_address>

### Comment 2
<location path="src/bluenamer/parent_selection.py" line_range="405-411" />
<code_context>
+        )
+        == best_principal_key
+    ]
+    eligible_ring_components = [
+        candidate
+        for candidate in eligible
+        if candidate.is_ring
+        and shared_counts.get(tuple(candidate.path), 0) > 0
+        and tuple(candidate.path) in ring_system_by_path
+    ]
+    if len(eligible_ring_components) < 2:
</code_context>
<issue_to_address>
**suggestion:** Reduce duplicate work and potential mismatch in lookups for `candidate.path`.

This comprehension converts `candidate.path` to a tuple twice and performs an extra membership check in `ring_system_by_path`. Because `shared_counts` is derived from `system.paths[0]`, anything failing `shared_counts.get(tuple(candidate.path), 0) > 0` is already excluded, so the `tuple(candidate.path) in ring_system_by_path` check appears redundant. Consider binding `path_key = tuple(candidate.path)` once and using only `shared_counts.get(path_key, 0) > 0`, or centralizing key derivation in a helper to keep lookups consistent.

```suggestion
    eligible_ring_components = []
    for candidate in eligible:
        if not candidate.is_ring:
            continue
        path_key = tuple(candidate.path)
        if shared_counts.get(path_key, 0) <= 0:
            continue
        eligible_ring_components.append(candidate)
```
</issue_to_address>

### Comment 3
<location path="src/bluenamer/parent_selection.py" line_range="415-422" />
<code_context>
+    if len(eligible_ring_components) < 2:
+        return candidates
+
+    best_backbone_key = min(
+        (
+            -shared_counts[tuple(candidate.path)],
+            -candidate.seniority_profile.ring_count,
+            -candidate.seniority_profile.parent_atom_count,
+            candidate.seniority_profile.path_tiebreak,
+        )
+        for candidate in eligible_ring_components
+    )
+    backbone = [
</code_context>
<issue_to_address>
**suggestion:** Avoid recomputing the backbone ranking tuple and keep key construction DRY.

The ranking tuple is built twice: once for `best_backbone_key = min(...)` and again when filtering `backbone`. This duplicates logic and risks the two uses diverging if the key changes. Please extract a helper like `_backbone_rank_key(candidate)` (or precompute a `{candidate: key}` dict) and reuse it for both `min(...)` and the equality check to keep the key definition in one place and avoid repeated work.
</issue_to_address>

### Comment 4
<location path="src/bluenamer/tests/test_analysis.py" line_range="3246" />
<code_context>


-def test_parent_selection_criteria_are_data_ordered_and_behavior_preserving():
+def test_parent_selection_criteria_are_data_ordered():
     profile = ParentSeniorityProfile(
         principal_group_count=1,
</code_context>
<issue_to_address>
**suggestion (testing):** Add explicit tests to cover the restored `senior_element_vector` behavior for ring parents.

This only checks data ordering, but the code change also restores `senior_element_vector` for `ring_parent`. Please add tests that construct two `ParentSeniorityProfile` instances differing only in `senior_element_vector` (for both ring and non-ring parents) and verify the resulting `score_tuple` ordering. That will guard against regressions where ring parents again ignore `senior_element_vector`.
</issue_to_address>

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/bluenamer/parent_selection.py Outdated
Comment thread src/bluenamer/parent_selection.py Outdated
Comment thread src/bluenamer/parent_selection.py Outdated


def test_parent_selection_criteria_are_data_ordered_and_behavior_preserving():
def test_parent_selection_criteria_are_data_ordered():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add explicit tests to cover the restored senior_element_vector behavior for ring parents.

This only checks data ordering, but the code change also restores senior_element_vector for ring_parent. Please add tests that construct two ParentSeniorityProfile instances differing only in senior_element_vector (for both ring and non-ring parents) and verify the resulting score_tuple ordering. That will guard against regressions where ring parents again ignore senior_element_vector.

…very RingSystem.paths entry, not just paths[0].

Removed the redundant ring_system_by_path lookup.
Added _spiro_backbone_rank_key() so the local backbone ranking tuple is defined once.
@r-fedorov

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In _prefer_spiro_backbone_components, you always compute shared_counts for all ring systems even when there is no principal-group tie; consider first checking for a tie on (contains_principal_group, principal_group_count) and only building the shared-count machinery when needed to avoid unnecessary work on large molecules.
  • _prefer_spiro_backbone_components and _spiro_backbone_rank_key assume that each ParentCandidate.path exactly matches one of the RingSystem.paths; it may be worth making this invariant explicit (e.g. via an assertion or a short comment) so that future changes to path construction don’t silently break the backbone filtering.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_prefer_spiro_backbone_components`, you always compute `shared_counts` for all ring systems even when there is no principal-group tie; consider first checking for a tie on `(contains_principal_group, principal_group_count)` and only building the shared-count machinery when needed to avoid unnecessary work on large molecules.
- `_prefer_spiro_backbone_components` and `_spiro_backbone_rank_key` assume that each `ParentCandidate.path` exactly matches one of the `RingSystem.paths`; it may be worth making this invariant explicit (e.g. via an assertion or a short comment) so that future changes to path construction don’t silently break the backbone filtering.

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@r-fedorov
r-fedorov merged commit 00258ed into refactor_namer Jun 17, 2026
11 checks passed
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.

1 participant