Commit f45edb0
authored
Work around a Cython comparison miscompilation in GroupBond._change_bond (#1029)
`arc/molecule/group_test.py::TestGroupBond::{test_apply_action_increment_bond,test_apply_action_decrement_bond}`
fail with
```
ActionError: Unable to update Bond due to CHANGE_BOND action: Invalid resulting order "[2]"
```
for an order the guard accepts — it raises only when a value is below 0
or above 4.
## Cause
**Cython 3.3.0 miscompiles a comparison in the element expression of a
list comprehension.** Minimal reproducer, built with `language_level:
3`:
```python
def element_form(list vals):
return [v > 4 for v in vals]
```
| `vals` | Python | compiled |
|---|---|---|
| `[2]` | `[False]` | **`[True]`** |
| `[5]` | `[True]` | **`[False]`** |
| `[100]` | `[True]` | **`[False]`** |
| `[0]` | `[False]` | **`[True]`** |
| `[4]` | `[False]` | `[False]` |
| `[-1]` | `[True]` | `[True]` |
Three forms of the same comparison are correct: a generator expression,
an explicit loop, and a comprehension's **filter** clause. Arithmetic in
an element expression is also correct, verified separately, so
`new_order` itself was never wrong — only the guard reading it.
The guard used the broken form:
```python
new_order = [value + order for value in self.order]
if any([value < 0 or value > 4 for value in new_order]):
```
`any()` is not implicated; it reported a list that had already been
computed incorrectly.
## Change
The guard collects the out-of-range entries with a filter clause, which
is measured correct for every value tested, and the error names them:
```
Invalid resulting order "[5]". The orders outside the range [0, 4] are [5] of types [int],
reached from the orders [4] of types [int] by an increment of 1 of type int.
```
The previous message printed only the resulting list, so it could not
distinguish an `int` from another type that formats identically, did not
say which entry tripped the guard, and did not report the increment.
That is most of why this took a day to characterise.
## It is not intermittent
It appeared to be, because it turned up on unrelated branches and the
same commit both passed and failed. What varied was the Cython version
each run resolved: `environment.yml` requires `cython >=3.1` with no
upper bound, so runs that picked up 3.3.0 failed and earlier resolves
passed. In an environment built from `environment.yml`, it reproduces
**serially, in 1.3 seconds**:
```
pytest arc/molecule/group_test.py -> 2 failed, 58 passed in 1.26s
```
Isolating the two halves of this change confirms which one matters:
renaming the loop variables while keeping `any([...])` still fails, and
replacing the comprehension while keeping the names passes.
## Two commits
**Work around the miscompilation at the failing call site.** The guard
collects the out-of-range entries with a filter clause, which is
measured correct for every value tested.
**Bound the compiler.** `environment.yml` required `cython >=3.1` with
no upper bound, so the compiler that builds `arc.molecule` was whatever
conda-forge had published most recently, and it changed between two runs
of the same commit. It is now `>=3.1,<3.3`. Cython 3.2.4 returns the
correct result for all eight values above; the bound is below 3.3 rather
than below 3.4 because 3.4 would still admit 3.3.0.
The second commit is the one that matters more. The first fixes the call
site whose failure is visible; the same construct appears elsewhere in
the Cythonised modules, and it is wrong in both directions — a value
above the range compiling to `False` means an invalid input is
**accepted** rather than rejected, which no test would catch.2 files changed
Lines changed: 18 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1039 | 1039 | | |
1040 | 1040 | | |
1041 | 1041 | | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
1042 | 1047 | | |
1043 | 1048 | | |
1044 | | - | |
| 1049 | + | |
| 1050 | + | |
1045 | 1051 | | |
1046 | | - | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
1047 | 1062 | | |
1048 | 1063 | | |
1049 | 1064 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| |||
0 commit comments