Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở Phù hợp với người mới
#889 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

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
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
data

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ả

bug silent-incorrectness

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

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của lanl/PyBNF

Tất cả issue của lanl/PyBNF

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.