Skip to content

fix: reject impossible CALYPSO atom choices - #382

Open
njzjz-bot wants to merge 1 commit into
deepmodeling:masterfrom
njzjz-bot:fix/issue-356-calypso-atom-choices
Open

fix: reject impossible CALYPSO atom choices#382
njzjz-bot wants to merge 1 commit into
deepmodeling:masterfrom
njzjz-bot:fix/issue-356-calypso-atom-choices

Conversation

@njzjz-bot

Copy link
Copy Markdown

Summary

  • compare nested atom-choice differences against an empty set correctly
  • raise ValueError before entering the random selection loop
  • add a regression test for [["Li"], ["Li"]]

Tests

  • PYTHONPATH=tests python -m unittest -v tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_rejects_impossible_random_atom_choices tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_caly_task_group
  • ruff format --check dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.py
  • isort --check-only dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.py
  • git diff --check

Closes #356

Coding agent: Codex
Codex version: codex-cli 0.149.0
Model: gpt-5.6-sol
Reasoning effort: xhigh

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Aug 23, 2026
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 312172fa-88f6-495e-b98f-c1dadf1af8c7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b01f29 and d3ca156.

📒 Files selected for processing (2)
  • dpgen2/exploration/task/caly_task_group.py
  • tests/exploration/test_make_task_group_from_config.py

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.

Correct the empty-set validation so nested atom choices that cannot produce unique species fail before the random selection loop.

Closes deepmodeling#356

Coding-Agent: Codex
Codex-Version: codex-cli 0.149.1
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@njzjz-bot
njzjz-bot force-pushed the fix/issue-356-calypso-atom-choices branch from 5507103 to d3ca156 Compare August 26, 2026 11:00
@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Aug 26, 2026
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.45%. Comparing base (6b01f29) to head (d3ca156).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #382      +/-   ##
==========================================
+ Coverage   84.43%   84.45%   +0.01%     
==========================================
  Files         104      104              
  Lines        6110     6110              
==========================================
+ Hits         5159     5160       +1     
+ Misses        951      950       -1     

☔ View full report in Codecov by Harness.
📢 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.

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

You are right that (set(s) - overlap) == 0 is always False and the guard has been dead since it was written. But the guard's predicate was never correct either, and turning it on as-is rejects configurations that work on master today, while still leaving the infinite loop in place for the cases it misses.

The predicate is the wrong test

The loop below needs one distinct element per sub-list. That is exactly a system of distinct representatives, so the right condition is Hall's marriage theorem: for every subset of sub-lists, the union of their candidates must be at least as large as the subset. "Some sub-list is a subset of the global intersection" is neither sufficient nor necessary for that.

Brute-forcing every nested config over a 3-symbol universe (343 families, 247 satisfiable, 96 impossible):

master:   wrongly rejects   0/247 satisfiable    catches  0/96 impossible   (dead code)
this PR:  wrongly rejects  82/247  (33%)         catches 51/96, misses 45

False positives - these all succeed on master and raise ValueError at d3ca156, verified through the real make_calypso_task_group_from_config:

config satisfiable as
[["Li"]] Li
[["Li","Na"]] either
[["Li","Na"],["Li","Na"]] Li, Na
[["Li","Na","K"],["Na","K"],["K"]] Li, Na, K

The last row is worth pausing on: it is structurally the same as [[A,B,C], [B,C], [C]], the example the error message itself cites as "not allowed". That example is satisfiable - pick C for the third slot, B for the second, A for the first. So the message documents the wrong rule, and this PR now enforces it.

False negatives - [["Li","Na"],["Li","Na"],["Li","Na"],["K"]] has three slots drawing from two symbols, so no distinct assignment exists. The disjoint fourth sub-list drives the global intersection to empty, so the guard never fires and it still hangs at PR head. #356 asks to "avoid infinite loops"; roughly half of genuinely impossible configs still loop.

Two ways forward

Either works for me:

  1. Test Hall's condition. numb_of_species is tiny, so iterating combinations of the sub-lists and raising when len(set().union(*sub)) < len(sub) is cheap, sound and complete. Fix the error message and its example at the same time.
  2. Replace the guard and the loop together with a matching-based pick - shuffle each sub-list's candidates and run an augmenting-path assignment. That is exact, always terminates, preserves the randomness the feature exists for, and makes the separate guard unnecessary.

If neither is appealing right now, the minimum I would want is a retry cap on the while True so an unsatisfiable config fails fast instead of spinning - but note that alone does not address the false positives above.

Why this has been invisible

The guard was born broken in 07df321 (#217, 2024-04-30) - there was never a len(...) == 0 that a cleanup dropped, and the loop never had a cap. tests/exploration/test_exploration_group.py does exercise the nested form via config_random, but only with configs whose global intersection is empty, so the guard's true branch has never been reached by any test. That is the cell this PR fills, and it is why a wrong predicate could sit there for two years.

One more thing worth knowing about the loop: the spin happens at the logging.info call inside it, so pre-fix this is a hot loop writing log lines at full CPU, running client-side inside dpgen2 submit before wf.submit(). Nothing reclaims it.

Separately and not for this PR: numb_of_species is never validated against len(name_of_atoms). A mismatch currently dies with a bare IndexError from the distance_of_ions branch rather than a clear message.

overlap = overlap & set(temp)

if any(map(lambda s: (set(s) - overlap) == 0, name_of_atoms)):
if any(not (set(atom_choices) - overlap) for atom_choices in name_of_atoms):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The truthiness fix is right - the old == 0 compared a set to an int and never fired. But the condition being tested is not the one the loop below needs.

The loop requires a distinct element per sub-list, i.e. a system of distinct representatives, so the correct test is Hall's condition. "Some sub-list is a subset of the global intersection" is neither sufficient nor necessary. Over all 343 nested configs on a 3-symbol universe this predicate wrongly rejects 82 of 247 satisfiable ones and misses 45 of 96 impossible ones.

Concretely, [["Li"]], [["Li","Na"]] and [["Li","Na","K"],["Na","K"],["K"]] all work on master and raise here - the last being the same shape as the [[A,B,C],[B,C],[C]] in the message below, which is satisfiable as A,B,C.

Since the sub-lists are tiny, any(len(set().union(*sub)) < len(sub) for k in range(1, n+1) for sub in combinations(name_of_atoms, k)) is sound, complete and cheap. Please also correct the example in the error string - it currently documents a valid config as forbidden.

def test_rejects_impossible_random_atom_choices(self):
"""Fail before random selection when unique choices are impossible."""
config = {
"name_of_atoms": [["Li"], ["Li"]],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[["Li"],["Li"]] is the one input where the wrong predicate and the correct one agree, so this passes for the right reason by coincidence and gives no signal about whether the rule generalises. That is how a 33% false-positive rate ships with green CI.

Two cases would pin the behaviour properly:

  • a satisfiable nested config that must be accepted, e.g. [["Li","Na","K"],["Na","K"],["K"]];
  • a genuinely impossible config the current guard misses, e.g. [["Li","Na"],["Li","Na"],["Li","Na"],["K"]], which must raise rather than hang.

Worth noting in the docstring that this is a liveness test: with the fix reverted it does not fail, it hangs - I had to kill it with a timeout. Without a per-test timeout, a regression here wedges the whole CI job rather than reporting one failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code scan] Fix CALYPSO nested atom-choice validation to avoid infinite loops

2 participants