An altpenalty compared against the constant 0 (altpenalty X > 0, X >= 0, 0 < X) is silently ignored, so a failed constraint is charged weight times its primary violation instead
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 80/100
Hướng nghiên cứu
Đọc phần chuẩn hóa altpenalty trong pybnf/constraint.py:338-347, sau đó kiểm tra các phép kiểm tra trong get_static_penalty ở dòng 689 và _static_penalty_gradient ở dòng 889. Tái hiện với ví dụ obs.prop và sim.gdat được cung cấp, đồng thời xác minh rằng cả đường dẫn penalty và gradient đều xử lý một hằng số bằng không. Hoàn tất khi các trường hợp bị ảnh hưởng tạo ra các penalty đúng được liệt kê và các kiểm soát hiện có vẫn không thay đổi.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
load_constraint_file turns a numeric side of the altpenalty inequality into a float (pybnf/constraint.py:113-117). Constraint.init then rewrites a >/>= altpenalty as < by swapping its sides (pybnf/constraint.py:338-347). After both steps, altpenalty X > 0, X >= 0, 0 < X and 0 <= X all store alt1 = 0.0, alt2 = 'X'. get_static_penalty uses if self.alt1: (pybnf/constraint.py:689) to decide whether an altpenalty is present. Because 0.0 is falsy, the altpenalty branch is skipped and the penalty becomes weight times the primary inequality's violation. _static_penalty_gradient runs the same test (pybnf/constraint.py:889), so the gradient matches the wrong objective and does not expose the problem. find_keys accepts a float alt1 without complaint (pybnf/constraint.py:421-422, where get_key returns None). No error, warning or log line appears. altpenalty X < 0 is not affected, because there the 0 ends up in alt2.
Failure scenario
docs/config.rst:251 defines the altpenalty rule. When the primary inequality fails, the penalty is weight * max(0, alt1 - alt2). If the altpenalty inequality holds, the penalty is weight * min, or 0 when no min is set. Take A < 1 at 1 weight 10 altpenalty X > 0 with A = 5:
- X = -3: the penalty should be 10 * max(0, 0 - (-3)) = 30. PyBNF returns 40, which is 10 times the primary violation 5 - 1.
- X = +3: the altpenalty inequality holds, so the penalty should be 0. PyBNF returns 40.
- X = +3 with
min 1: the penalty should be 10. PyBNF returns 40.
The error runs in both directions. A parameter set that satisfies the continuous proxy is still charged, and the penalty follows A instead of X, which defeats the purpose of the substitution.
Reproduction
Run from any empty directory with PyBNF importable:
from pybnf import data, constraint
lines = ['A < 1 at 1 weight 10 altpenalty X > 0',
'A < 1 at 1 weight 10 altpenalty 0 < X',
'A < 1 at 1 weight 10 altpenalty X >= 0',
'A < 1 always weight 10 altpenalty X > 0',
'A < 1 at 1 weight 10 altpenalty X > 0 min 1',
'A < 1 at 1 weight 10 altpenalty X > 1e-300 min 1', # control
'A < 1 at 1 weight 10 altpenalty X > 0.5'] # control
with open('obs.prop', 'w') as f:
f.write('\n'.join(lines) + '\n')
for x in (-3, 3):
with open('sim.gdat', 'w') as f:
f.write(f'# time A X\n0 5 {x}\n1 5 {x}\n')
d = data.Data()
d.load_data('sim.gdat')
cs = constraint.ConstraintSet('model', 'obs')
cs.load_constraint_file('obs.prop')
for line, c in zip(lines, cs.constraints):
print(f'X={x:+d} {line:50s} alt1={c.alt1!r:7} penalty={c.penalty({"model": {"obs": d}})}')
| constraint | X | PyBNF | correct |
|---|---|---|---|
altpenalty X > 0 |
-3 | 40.0 | 30 |
altpenalty 0 < X |
-3 | 40.0 | 30 |
altpenalty X >= 0 |
-3 | 40.0 | 30 |
always ... altpenalty X > 0 |
-3 | 40.0 | 30 |
altpenalty X > 0 min 1 |
-3 | 40.0 | 30 |
altpenalty X > 1e-300 min 1 (control) |
-3 | 30.0 | 30 |
altpenalty X > 0.5 (control) |
-3 | 35.0 | 35 |
altpenalty X > 0 |
+3 | 40.0 | 0 |
altpenalty 0 < X |
+3 | 40.0 | 0 |
altpenalty X >= 0 |
+3 | 40.0 | 0 |
always ... altpenalty X > 0 |
+3 | 40.0 | 0 |
altpenalty X > 0 min 1 |
+3 | 40.0 | 10 |
altpenalty X > 1e-300 min 1 (control) |
+3 | 10.0 | 10 |
altpenalty X > 0.5 (control) |
+3 | 0.0 | 0 |
Every affected line prints alt1=0.0. Moving the constant only to 1e-300 gives the correct value.
Reachability
This affects any .prop constraint file (qualitative data read through exp_file / data:) with a weighted constraint whose altpenalty has 0 as its constant side after normalization: X > 0, X >= 0, 0 < X or 0 <= X. It applies to at, between, always and once constraints. The altpenalty grammar accepts a number on either side (pybnf/constraint.py:249-251, :264), and X > 0 is the natural way to say that the continuous proxy should be positive. Nothing upstream rejects the input and nothing downstream corrects it. Gradient-based fits get a gradient consistent with the wrong penalty.
Where
pybnf/constraint.py:689 pybnf/constraint.py:889 pybnf/constraint.py:338-347
The obvious fix is if self.alt1 is not None: at both test sites.
Related: #890, #887.
Found in a whole-codebase audit for silently wrong results (2026-09-23); the reproduction above was re-run independently of the original finding.
- Ngôn ngữ chính
- Python
- Star
- 25
- Fork
- 25
- Merge trung bình
- 2 giờ 38 phút
- Pull request đã merge (30 ngày)
- 98
Chuẩn bị môi trường
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của lanl/PyBNF
-
bug silent-incorrectness
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug silent-incorrectness
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug silent-incorrectness
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
ACK_WAITING HELP_WANTED UPDATE_CS
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
OWASP/CheatSheetSeries#2458 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
BasedHardware/omi#19711 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Qwen3_5MoeModel no longer returns router_logits, breaking aux loss with output_router_logits=TrueĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
huggingface/transformers#49172 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
vllm-project/vllm-metal#885 ·
Maintainer thường phản hồi trong vòng 1 ngày