Skip to content

sqs improve interface#476

Open
dgehringer wants to merge 13 commits intomainfrom
sqs-improve-interface
Open

sqs improve interface#476
dgehringer wants to merge 13 commits intomainfrom
sqs-improve-interface

Conversation

@dgehringer
Copy link
Copy Markdown

@dgehringer dgehringer commented Apr 1, 2026

Add more features to the sqsgenerator interface (>0.4.9)

Summary by CodeRabbit

  • Refactor

    • Removed legacy SQS utilities and reorganized SQS into a new dedicated package with a consolidated public API.
  • New Features

    • Added a public SQS entry point returning iterable result objects with per-result structures, objectives, SRO access, and sublattice-aware modes.
  • Tests

    • Revised tests for the new result API, multi-sublattice flows, validation/error paths, logging, iteration metadata, and failure handling.
  • Chores

    • Bumped sqsgenerator dependency to 0.5.5.

@dgehringer dgehringer requested a review from jan-janssen as a code owner April 1, 2026 18:19
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Monolithic src/structuretoolkit/build/sqs.py was removed and replaced by a new src/structuretoolkit/build/sqs/ package (_types.py, _interface.py, __init__.py) exposing a sqs_structures result-object API; tests updated accordingly and the CI sqsgenerator pin bumped to 0.5.5.

Changes

Cohort / File(s) Summary
Dependency Update
\.ci_support/environment.yml
Bumped pinned sqsgenerator from 0.5.40.5.5.
Removed Monolith
src/structuretoolkit/build/sqs.py
Deleted legacy single-file SQS utilities and previous sqs_structures implementation; callers must use the new package API.
New Package Init
src/structuretoolkit/build/sqs/__init__.py
Added package initializer re-exporting public types and sqs_structures, defining __all__.
New Types
src/structuretoolkit/build/sqs/_types.py
Introduced SQS type aliases (Shell, Composition, etc.), SroParameter, and SqsResultInteract/SqsResultSplit Protocols modeling result shapes.
New Interface
src/structuretoolkit/build/sqs/_interface.py
Added sqs_structures(...) with overloads, config parsing (sqsgenerator.parse_config), threaded optimizer invocation with stop callback, and SqsResultPack / _SqsResultProxy wrappers exposing iteration, .best(), .atoms(), .sro(), .sublattices(), and metadata.
Tests
tests/test_sqs.py
Reworked tests to the composition-based result-object API; added error and multi-sublattice scenarios; removed old mole-fraction/statistics assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Caller
    participant API as stk.build.sqs_structures
    participant Parser as sqsgenerator.parse_config
    participant Worker as WorkerThread
    participant Core as sqsgenerator.core.optimize
    participant Pack as SqsResultPack

    Client->>API: call sqs_structures(Atoms, composition, ...)
    API->>Parser: parse configuration
    alt parse ok
        API->>Worker: start optimizer thread (with stop callback)
        Worker->>Core: run optimize(config, callback)
        Core-->>Worker: optimization result
        Worker->>API: signal completion
        API->>Pack: wrap optimizer result
        API-->>Client: return SqsResultPack
    else parse failure
        Parser-->>API: raise ValueError
        API-->>Client: raise ValueError
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I chopped a long file into tidy rows,
Types on the hill where the optimizer goes.
Threads hum, results bundled, tests skip and meet,
I hop with a carrot — new interfaces to greet. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'sqs improve interface' is vague and generic, using non-descriptive terms that do not convey specific information about the changeset, such as the migration from a monolithic module to a package structure with new types and interface layers. Consider a more descriptive title like 'Refactor SQS module into structured package with typed interface' or 'Migrate SQS from monolithic module to typed package structure with sqsgenerator integration' to clarify the main architectural change.
✅ Passed checks (1 passed)
Check name Status Explanation
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sqs-improve-interface

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.

Copy link
Copy Markdown
Contributor

@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: 6

🧹 Nitpick comments (2)
src/structuretoolkit/build/sqs/__init__.py (1)

19-35: Optional: __all__ is not sorted.

Per static analysis (RUF022), __all__ entries should follow isort-style sorting for consistency.

♻️ Sorted `__all__`
 __all__ = [
     "Composition",
     "Element",
     "IterationMode",
     "Prec",
-    "ResultType",
     "Shell",
     "ShellRadii",
     "ShellWeights",
     "Site",
+    "SqsResult",
     "SroParameter",
-    "SqsResult",
     "SqsResultInteract",
     "SqsResultSplit",
     "SublatticeMode",
     "sqs_structures",
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/structuretoolkit/build/sqs/__init__.py` around lines 19 - 35, The __all__
list in this module is not alphabetically sorted; update the __all__ variable to
use isort-style (alphabetical) ordering so exports are consistent and satisfy
RUF022. Locate the __all__ declaration (the list containing "Composition",
"Element", ..., "sqs_structures") and reorder its string entries alphabetically
(preserving exact symbol names and casing), then run the project's
linters/formatters to confirm the change.
src/structuretoolkit/build/sqs/_interface.py (1)

284-288: Exception in worker thread is silently lost.

If sqs_optimize raises an exception, it's swallowed by the thread and optimization_result remains None, resulting in a generic RuntimeError. Consider capturing and re-raising the actual exception for better debugging.

♻️ Proposed enhancement to preserve exception info
+    optimization_exception: BaseException | None = None
+
     def _optimize():
+        nonlocal optimization_exception
         try:
             result_local = sqs_optimize(config, log_level=level, callback=_callback)
-            stop_event.set()
             nonlocal optimization_result
             optimization_result = result_local
+        except BaseException as e:
+            optimization_exception = e
+        finally:
+            stop_event.set()

Then at line 300:

     if optimization_result is None:
+        if optimization_exception is not None:
+            raise RuntimeError("Optimization failed") from optimization_exception
         raise RuntimeError("Optimization failed to produce a result.")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/structuretoolkit/build/sqs/_interface.py` around lines 284 - 288, The
worker thread function _optimize currently calls sqs_optimize and assigns
optimization_result but swallows exceptions; change _optimize to catch Exception
as e, store that exception (e) or its traceback into a shared variable (e.g.,
optimization_result or a new optimization_exception) and ensure stop_event.set()
is still called, then in the main thread where optimization_result is inspected
re-raise the captured exception (or raise RuntimeError from the captured
exception) so the actual error/traceback from sqs_optimize is preserved;
reference _optimize, sqs_optimize, optimization_result and stop_event when
implementing this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/structuretoolkit/build/sqs/__init__.py`:
- Around line 1-16: The import of ResultType in
src/structuretoolkit/build/sqs/__init__.py is invalid because ResultType is not
defined in _types.py; either remove ResultType from the import list in
__init__.py (and from any __all__ export there) or add a proper ResultType
definition to _types.py if it's required; update usages referencing ResultType
(search for ResultType in the package) to use the new type or appropriate
existing type to avoid broken references.

In `@src/structuretoolkit/build/sqs/_interface.py`:
- Around line 99-118: The overload for sqs_structures has a type mismatch:
objective is annotated as list[float] but given a default 0.0; update the
overload signature for sqs_structures so the objective parameter matches the
implementation (accepts list[float] | float with default 0.0) — i.e., change the
overload's objective type to list[float] | float = 0.0 (or alternatively provide
a list default like [0.0] if you prefer list-only), ensuring the overload and
the implementation (which declares objective: list[float] | float = 0.0) are
consistent.
- Around line 292-298: The loop waiting on stop_event can block forever if the
worker thread (t running sqs_optimize) crashes before setting stop_event; change
the wait to a timed wait so the main thread wakes periodically and re-checks
thread liveness. Update the loop around t.is_alive() and stop_event.wait() to
call stop_event.wait(timeout=...) (e.g., 0.5–1s) or replace with
t.join(timeout=...) so that if t.is_alive() becomes False the loop breaks; keep
the existing except/finally behavior (including t.join()) but ensure the timed
wait lets the main thread detect a dead worker and exit instead of hanging.

In `@src/structuretoolkit/build/sqs/_types.py`:
- Line 131: The Composition type fails because Element is currently defined as a
list object rather than a type alias; change the Element symbol into a proper
type alias (e.g., a Literal union or Typed alias) so Composition = dict[Element
| Literal["sites"], Union[int, Site]] is valid; update the Element declaration
(and imports if necessary) to use typing/typing_extensions TypeAlias or
Literal/Union types and ensure Site and Composition use those type names
consistently.
- Around line 9-125: Element is currently a runtime list literal, not a type, so
using it in annotations like Composition causes errors; replace it with a proper
type definition and keep the runtime list separate: create a runtime constant
(e.g., ELEMENT_SYMBOLS) holding the list of symbols and define Element as a type
alias or a Literal union (e.g., Element: TypeAlias = Literal["0","H","He",...])
or simply Element: TypeAlias = str if you don't need strict literals, then
update usages (e.g., Composition) to reference the new Element type alias
instead of the list object.
- Around line 1-195: Define the missing exported type alias ResultType in this
module by adding a TypeAlias that points to the existing union SqsResult (e.g.
ResultType: TypeAlias = SqsResult) so imports from __init__.py succeed; place
the declaration near the other aliases (after SqsResult) and ensure the name
ResultType is available at module level for export.

---

Nitpick comments:
In `@src/structuretoolkit/build/sqs/__init__.py`:
- Around line 19-35: The __all__ list in this module is not alphabetically
sorted; update the __all__ variable to use isort-style (alphabetical) ordering
so exports are consistent and satisfy RUF022. Locate the __all__ declaration
(the list containing "Composition", "Element", ..., "sqs_structures") and
reorder its string entries alphabetically (preserving exact symbol names and
casing), then run the project's linters/formatters to confirm the change.

In `@src/structuretoolkit/build/sqs/_interface.py`:
- Around line 284-288: The worker thread function _optimize currently calls
sqs_optimize and assigns optimization_result but swallows exceptions; change
_optimize to catch Exception as e, store that exception (e) or its traceback
into a shared variable (e.g., optimization_result or a new
optimization_exception) and ensure stop_event.set() is still called, then in the
main thread where optimization_result is inspected re-raise the captured
exception (or raise RuntimeError from the captured exception) so the actual
error/traceback from sqs_optimize is preserved; reference _optimize,
sqs_optimize, optimization_result and stop_event when implementing this change.
🪄 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: 529c7ba7-59c5-4ed0-90a3-6e9e805cb137

📥 Commits

Reviewing files that changed from the base of the PR and between 2af45d9 and 09e48a1.

📒 Files selected for processing (6)
  • .ci_support/environment.yml
  • src/structuretoolkit/build/sqs.py
  • src/structuretoolkit/build/sqs/__init__.py
  • src/structuretoolkit/build/sqs/_interface.py
  • src/structuretoolkit/build/sqs/_types.py
  • tests/test_sqs.py
💤 Files with no reviewable changes (1)
  • src/structuretoolkit/build/sqs.py

Comment on lines +1 to +195
import numpy as np
from ase import Atoms
from typing import Literal, TypeAlias, Protocol, overload, Union

Shell: TypeAlias = int

SublatticeMode = Literal["split", "interact"]
IterationMode = Literal["random", "systematic"]
Element = [
"0",
"H",
"He",
"Li",
"Be",
"B",
"C",
"N",
"O",
"F",
"Ne",
"Na",
"Mg",
"Al",
"Si",
"P",
"S",
"Cl",
"Ar",
"K",
"Ca",
"Sc",
"Ti",
"V",
"Cr",
"Mn",
"Fe",
"Co",
"Ni",
"Cu",
"Zn",
"Ga",
"Ge",
"As",
"Se",
"Br",
"Kr",
"Rb",
"Sr",
"Y",
"Zr",
"Nb",
"Mo",
"Tc",
"Ru",
"Rh",
"Pd",
"Ag",
"Cd",
"In",
"Sn",
"Sb",
"Te",
"I",
"Xe",
"Cs",
"Ba",
"La",
"Ce",
"Pr",
"Nd",
"Pm",
"Sm",
"Eu",
"Gd",
"Tb",
"Dy",
"Ho",
"Er",
"Tm",
"Yb",
"Lu",
"Hf",
"Ta",
"W",
"Re",
"Os",
"Ir",
"Pt",
"Au",
"Hg",
"Tl",
"Pb",
"Bi",
"Po",
"At",
"Rn",
"Fr",
"Ra",
"Ac",
"Th",
"Pa",
"U",
"Np",
"Pu",
"Am",
"Cm",
"Bk",
"Cf",
"Es",
"Fm",
"Md",
"No",
"Lr",
"Rf",
"Db",
"Sg",
"Bh",
"Hs",
"Mt",
"Ds",
"Rg",
"Cn",
"Uut",
"Fl",
]

Site = Union[str, list[int]]

Prec = Literal["single", "double"]

Composition = dict[Element | Literal["sites"], Union[int, Site]]

ShellWeights = dict[Shell, float]

ShellRadii = list[float]

LogLevel = Literal["warn", "info", "debug", "error", "trace"]


class SroParameter:
@property
def i(self) -> int: ...

@property
def j(self) -> int: ...

@property
def shell(self) -> int: ...

def __float__(self) -> float: ...

@property
def value(self) -> float: ...


class SqsResultInteract(Protocol):
def shell_index(self, shell: int) -> int: ...

def species_index(self, species: int) -> int: ...

def rank(self) -> str: ...

@overload
def sro(
self,
) -> (
np.ndarray[tuple[int, int, int], np.dtype[np.float32]]
| np.ndarray[tuple[int, int, int], np.dtype[np.float64]]
): ...

@overload
def sro(self, shell: int) -> list[SroParameter]: ...

@overload
def sro(self, i: int, j: int) -> list[SroParameter]: ...

@overload
def sro(self, shell: int, i: int, j: int) -> SroParameter: ...

@property
def objective(self) -> float: ...

def atoms(self) -> Atoms: ...


class SqsResultSplit(Protocol):
@property
def objective(self) -> float: ...

def atoms(self) -> Atoms: ...

def sublattices(self) -> list[SqsResultInteract]: ...


SqsResult = SqsResultSplit | SqsResultInteract
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ResultType is not defined in _types.py
echo "Searching for ResultType definition in _types.py:"
rg -n "ResultType" src/structuretoolkit/build/sqs/_types.py || echo "ResultType NOT found in _types.py"

echo ""
echo "Checking if ResultType is imported in __init__.py:"
rg -n "ResultType" src/structuretoolkit/build/sqs/__init__.py

Repository: pyiron/structuretoolkit

Length of output: 241


Critical: ResultType is not defined in this file but is imported in __init__.py.

The pipeline fails because __init__.py imports ResultType from this module (line 9) and exports it (line 24), but it's not defined here. Add the ResultType definition to this file—likely as a type alias for SqsResult or another result type.

🧰 Tools
🪛 GitHub Actions: Pipeline

[error] 1-1: ImportError during test collection: cannot import name 'ResultType' from 'structuretoolkit.build.sqs._types' (/home/runner/miniconda3/envs/test/lib/python3.14/site-packages/structuretoolkit/build/sqs/_types.py).

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

In `@src/structuretoolkit/build/sqs/_types.py` around lines 1 - 195, Define the
missing exported type alias ResultType in this module by adding a TypeAlias that
points to the existing union SqsResult (e.g. ResultType: TypeAlias = SqsResult)
so imports from __init__.py succeed; place the declaration near the other
aliases (after SqsResult) and ensure the name ResultType is available at module
level for export.

Comment on lines +9 to +125
Element = [
"0",
"H",
"He",
"Li",
"Be",
"B",
"C",
"N",
"O",
"F",
"Ne",
"Na",
"Mg",
"Al",
"Si",
"P",
"S",
"Cl",
"Ar",
"K",
"Ca",
"Sc",
"Ti",
"V",
"Cr",
"Mn",
"Fe",
"Co",
"Ni",
"Cu",
"Zn",
"Ga",
"Ge",
"As",
"Se",
"Br",
"Kr",
"Rb",
"Sr",
"Y",
"Zr",
"Nb",
"Mo",
"Tc",
"Ru",
"Rh",
"Pd",
"Ag",
"Cd",
"In",
"Sn",
"Sb",
"Te",
"I",
"Xe",
"Cs",
"Ba",
"La",
"Ce",
"Pr",
"Nd",
"Pm",
"Sm",
"Eu",
"Gd",
"Tb",
"Dy",
"Ho",
"Er",
"Tm",
"Yb",
"Lu",
"Hf",
"Ta",
"W",
"Re",
"Os",
"Ir",
"Pt",
"Au",
"Hg",
"Tl",
"Pb",
"Bi",
"Po",
"At",
"Rn",
"Fr",
"Ra",
"Ac",
"Th",
"Pa",
"U",
"Np",
"Pu",
"Am",
"Cm",
"Bk",
"Cf",
"Es",
"Fm",
"Md",
"No",
"Lr",
"Rf",
"Db",
"Sg",
"Bh",
"Hs",
"Mt",
"Ds",
"Rg",
"Cn",
"Uut",
"Fl",
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Element is defined as a list, not a type alias.

Element is assigned a list literal, which makes it a runtime list object rather than a type. This will cause issues when used in type annotations like Composition = dict[Element | Literal["sites"], ...] at line 131 — a list cannot be used as a type key.

If the intent is to define a type for valid element symbols, use Literal:

🔧 Proposed fix using Literal type
-Element = [
-    "0",
-    "H",
-    "He",
-    ...
-    "Fl",
-]
+Element: TypeAlias = Literal[
+    "0", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
+    "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca",
+    "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn",
+    "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr",
+    "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn",
+    "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd",
+    "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb",
+    "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg",
+    "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th",
+    "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm",
+    "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds",
+    "Rg", "Cn", "Uut", "Fl",
+]

Alternatively, if you want to keep the list for runtime validation and have a separate type, define both:

ELEMENT_SYMBOLS = ["0", "H", "He", ...]  # runtime list
Element: TypeAlias = str  # simple type alias
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/structuretoolkit/build/sqs/_types.py` around lines 9 - 125, Element is
currently a runtime list literal, not a type, so using it in annotations like
Composition causes errors; replace it with a proper type definition and keep the
runtime list separate: create a runtime constant (e.g., ELEMENT_SYMBOLS) holding
the list of symbols and define Element as a type alias or a Literal union (e.g.,
Element: TypeAlias = Literal["0","H","He",...]) or simply Element: TypeAlias =
str if you don't need strict literals, then update usages (e.g., Composition) to
reference the new Element type alias instead of the list object.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 97.77778% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.34%. Comparing base (98e90bd) to head (4a981dd).

Files with missing lines Patch % Lines
src/structuretoolkit/build/sqs/_interface.py 97.39% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #476      +/-   ##
==========================================
+ Coverage   82.92%   84.34%   +1.41%     
==========================================
  Files          26       28       +2     
  Lines        1857     1929      +72     
==========================================
+ Hits         1540     1627      +87     
+ Misses        317      302      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
src/structuretoolkit/build/sqs/__init__.py (1)

18-33: Consider sorting __all__ for consistency.

Ruff flags that __all__ is not sorted. Sorting the list alphabetically improves consistency and makes future additions easier to review.

📋 Proposed alphabetical sort
 __all__ = [
     "Composition",
     "Element",
     "IterationMode",
     "Prec",
     "Shell",
     "ShellRadii",
     "ShellWeights",
     "Site",
-    "SroParameter",
     "SqsResult",
     "SqsResultInteract",
     "SqsResultSplit",
+    "SroParameter",
     "SublatticeMode",
     "sqs_structures",
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/structuretoolkit/build/sqs/__init__.py` around lines 18 - 33, The __all__
list in the module is unsorted; sort the entries in the __all__ list
alphabetically (by string) while keeping the same items and punctuation so
semantics don't change—update the __all__ definition (which currently contains
"Composition", "Element", "IterationMode", "Prec", "Shell", "ShellRadii",
"ShellWeights", "Site", "SroParameter", "SqsResult", "SqsResultInteract",
"SqsResultSplit", "SublatticeMode", "sqs_structures") to an alphabetically
ordered sequence to satisfy the linter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/structuretoolkit/build/sqs/__init__.py`:
- Around line 18-33: The __all__ list in the module is unsorted; sort the
entries in the __all__ list alphabetically (by string) while keeping the same
items and punctuation so semantics don't change—update the __all__ definition
(which currently contains "Composition", "Element", "IterationMode", "Prec",
"Shell", "ShellRadii", "ShellWeights", "Site", "SroParameter", "SqsResult",
"SqsResultInteract", "SqsResultSplit", "SublatticeMode", "sqs_structures") to an
alphabetically ordered sequence to satisfy the linter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb11e8f4-48e2-4d88-b418-239b2b8743cb

📥 Commits

Reviewing files that changed from the base of the PR and between 09e48a1 and 5d01b00.

📒 Files selected for processing (1)
  • src/structuretoolkit/build/sqs/__init__.py

Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/structuretoolkit/build/sqs/_interface.py`:
- Around line 298-305: The current except TimeoutError after calling
t.join(timeout=5.0) is ineffective because Thread.join never raises
TimeoutError; instead check t.is_alive() after join and raise the RuntimeError
if the thread remains alive. Update the block around t.join(timeout=5.0) (the
thread variable `t`) to call t.join(timeout=5.0) then if t.is_alive(): raise the
same RuntimeError (leave message unchanged) rather than relying on an except
TimeoutError branch so stopping logic in sqs_structures becomes effective.
🪄 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: 23325877-d56f-4034-bb90-09a2e736845b

📥 Commits

Reviewing files that changed from the base of the PR and between 5d01b00 and a4fb0e1.

📒 Files selected for processing (3)
  • src/structuretoolkit/build/sqs/_interface.py
  • src/structuretoolkit/build/sqs/_types.py
  • tests/test_sqs.py
✅ Files skipped from review due to trivial changes (1)
  • src/structuretoolkit/build/sqs/_types.py

Comment on lines +298 to +305
try:
t.join(timeout=5.0)
except TimeoutError:
raise RuntimeError(
"Optimization thread did not finish within the timeout period after requesting it to stop. "
"The optimization may still be running in the background. Try to decrease chunk_size by passing it as a "
"keyword argument to sqs_structures to make the optimization more responsive to stop requests."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Thread.join() does not raise TimeoutError—the timeout check is ineffective.

Thread.join(timeout=5.0) returns None regardless of whether the thread terminated or the timeout elapsed. To detect a timeout, you must check t.is_alive() afterwards. The current code's except TimeoutError block will never execute.

🐛 Proposed fix
     finally:
-        try:
-            t.join(timeout=5.0)
-        except TimeoutError:
+        t.join(timeout=5.0)
+        if t.is_alive():
             raise RuntimeError(
                 "Optimization thread did not finish within the timeout period after requesting it to stop. "
                 "The optimization may still be running in the background. Try to decrease chunk_size by passing it as a "
                 "keyword argument to sqs_structures to make the optimization more responsive to stop requests."
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
t.join(timeout=5.0)
except TimeoutError:
raise RuntimeError(
"Optimization thread did not finish within the timeout period after requesting it to stop. "
"The optimization may still be running in the background. Try to decrease chunk_size by passing it as a "
"keyword argument to sqs_structures to make the optimization more responsive to stop requests."
)
finally:
t.join(timeout=5.0)
if t.is_alive():
raise RuntimeError(
"Optimization thread did not finish within the timeout period after requesting it to stop. "
"The optimization may still be running in the background. Try to decrease chunk_size by passing it as a "
"keyword argument to sqs_structures to make the optimization more responsive to stop requests."
)
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 301-305: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

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

In `@src/structuretoolkit/build/sqs/_interface.py` around lines 298 - 305, The
current except TimeoutError after calling t.join(timeout=5.0) is ineffective
because Thread.join never raises TimeoutError; instead check t.is_alive() after
join and raise the RuntimeError if the thread remains alive. Update the block
around t.join(timeout=5.0) (the thread variable `t`) to call t.join(timeout=5.0)
then if t.is_alive(): raise the same RuntimeError (leave message unchanged)
rather than relying on an except TimeoutError branch so stopping logic in
sqs_structures becomes effective.

@jan-janssen
Copy link
Copy Markdown
Member

@dgehringer Thanks for the update. I just returned from holidays and start the review now. In terms of the suggested changes, I am wondering if those should be included in structuretoolkit or directly in sqsgenerator. In particular the types might also be useful for other users of sqsgenerator.

jan-janssen and others added 2 commits April 10, 2026 21:41
* Add unit tests for SQS functionality

Added several test cases to tests/test_sqs.py to increase coverage of the
sqs_structures function and its helper classes. Covered parameters include
atol, rtol, shell_radii, log_level, and num_threads. Also added tests for
error handling (ParseError, AttributeError, RuntimeError) and graceful
stopping via KeyboardInterrupt. Coverage for _interface.py increased to 97%.

Co-authored-by: jan-janssen <3854739+jan-janssen@users.noreply.github.com>

* Add comprehensive unit tests for SQS functionality

- Added tests for atol, rtol, and shell_radii in both interact and split modes.
- Added tests for different log levels (info, debug, error, trace).
- Added tests for kwargs passing (e.g., chunk_size).
- Added error handling tests for invalid compositions and non-split result sublattices access.
- Added simulation of KeyboardInterrupt to test graceful stopping.
- Added test for num_threads parameter.
- Added test for optimization failure handling.
- Coverage for _interface.py increased to 97%.

Co-authored-by: jan-janssen <3854739+jan-janssen@users.noreply.github.com>

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: jan-janssen <3854739+jan-janssen@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
tests/test_sqs.py (1)

35-41: Avoid bare attribute access in the AttributeError assertion (Ruff B018).

The current pattern can trigger lint noise; using getattr(...) keeps the same behavior while avoiding the warning.

Proposed lint-safe change
         with self.assertRaises(AttributeError):
             # ensure proxy works as expected
-            stk.build.sqs_structures(
-                structure=bulk("Au", cubic=True),
-                composition=dict(Cu=16, Au=16),
-                supercell=(2, 2, 2),
-            ).best().not_defined
+            getattr(
+                stk.build.sqs_structures(
+                    structure=bulk("Au", cubic=True),
+                    composition=dict(Cu=16, Au=16),
+                    supercell=(2, 2, 2),
+                ).best(),
+                "not_defined",
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_sqs.py` around lines 35 - 41, Replace the bare attribute access
that triggers Ruff B018 by calling getattr on the returned object from
stk.build.sqs_structures(...).best() inside the with
self.assertRaises(AttributeError) block; specifically, call
getattr(stk.build.sqs_structures(structure=bulk("Au", cubic=True),
composition=dict(Cu=16, Au=16), supercell=(2,2,2)).best(), "not_defined") so the
test still asserts AttributeError but avoids direct attribute access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_sqs.py`:
- Around line 35-41: Replace the bare attribute access that triggers Ruff B018
by calling getattr on the returned object from
stk.build.sqs_structures(...).best() inside the with
self.assertRaises(AttributeError) block; specifically, call
getattr(stk.build.sqs_structures(structure=bulk("Au", cubic=True),
composition=dict(Cu=16, Au=16), supercell=(2,2,2)).best(), "not_defined") so the
test still asserts AttributeError but avoids direct attribute access.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9017f03f-76a8-4824-a667-89b178f41b4c

📥 Commits

Reviewing files that changed from the base of the PR and between 618be26 and 2f99f20.

📒 Files selected for processing (1)
  • tests/test_sqs.py

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