Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

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

未关闭 适合新手
#889 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
80/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python
领域
data

调研方向

阅读 pybnf/constraint.py:338-347 中的 altpenalty 归一化,然后检查第 689 行的 get_static_penalty 和第 889 行的 _static_penalty_gradient 中的检查。使用提供的 obs.prop 和 sim.gdat 示例复现,并验证 penalty 和 gradient 路径都能处理零常数。在受影响的案例产生所列的正确 penalty,且现有控制保持不变时,即可完成。

由索引模型根据 Issue 内容生成。

描述

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.

主要语言
Python
星标
25
派生
25
平均合并
2 小时 38 分钟
30 天内合并 PR
98

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

lanl/PyBNF 的其他 Issue

查看 lanl/PyBNF 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。