feat(core): 实现 get_rng 与 set_seed

L0 第 2 项落地。

- get_rng(int) 固定种子可复现;get_rng(Generator) 原样透传;
  get_rng() 跟随 set_seed 设过的全局种子,没设过则交给系统熵源
- set_seed(seed) 除记录 _GLOBAL_SEED 外,还调用 np.random.seed,
  让 sklearn / statsmodels 这些走 numpy 旧全局状态的库也能复现;
  这是全项目唯一碰旧接口的位置,标注了 noqa: NPY002 与理由
- 已知取舍写进 docstring:同一全局种子下多次 get_rng() 是同一条流
- tests/test_core.py:TestRandom 去掉 skip;把原来那条自证式的
  test_set_seed_makes_global_reproducible 换成两条真断言
  (不传 random_state 也要可复现、旧全局状态被覆盖)
- STATUS.md:L0 进度 2/12,新增决策 23(允许 set_seed 碰旧接口)
  与 24(不做 SeedSequence.spawn 拆分);CHANGELOG.md 同步
This commit is contained in:
guzhujushi committed 2026-10-01 19:19:15 +08:00
1 parent cb96c847dd
commit 658b266614
4 files changed
+62 -20

No files matched your search

+16 -6
View File
@@ -1,6 +1,6 @@
"""core 包测试。
TODO(L0):TestRandom / TestValidate 实现后去掉 skip 标记(Result 已完成)。
TODO(L0):TestValidate 实现后去掉 skip 标记(Result 与 Random 已完成)。
"""
from __future__ import annotations
@@ -9,7 +9,6 @@ import numpy as np
import pytest
@pytest.mark.skip(reason="未实现:见 docs/03-roadmap.md L0")
class TestRandom:
def test_get_rng_is_reproducible(self):
from mcm_toolkit.core import get_rng
@@ -22,14 +21,25 @@ class TestRandom:
g = np.random.default_rng(0)
assert get_rng(g) is g
def test_set_seed_makes_global_reproducible(self):
def test_get_rng_default_follows_global_seed(self):
"""set_seed 之后,不传 random_state 也必须复现——这是"论文能重跑"的底线。"""
from mcm_toolkit.core import get_rng, set_seed
set_seed(7)
first = get_rng().random()
set_seed(7)
second = get_rng().random()
assert first == second
def test_set_seed_also_covers_third_party_global_state(self):
"""sklearn / statsmodels 走的是 np.random 旧全局,set_seed 要连它一起覆盖。"""
from mcm_toolkit.core import set_seed
set_seed(7)
a = np.random.default_rng(7).random()
first = np.random.random() # noqa: NPY002 - 这里就是要验证旧全局被设上了
set_seed(7)
b = np.random.default_rng(7).random()
assert a == b
second = np.random.random() # noqa: NPY002
assert first == second
@pytest.mark.skip(reason="未实现:见 docs/03-roadmap.md L0")