-
Notifications
You must be signed in to change notification settings - Fork 1
⚡ Bolt: Vectorize Newton-Raphson M-step in MMLE estimator #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -122,30 +122,49 @@ def fit_mmle_2pl( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a_new = a.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| b_new = b.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for i in range(n_items): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ai, bi = a[i], b[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Newton steps on the item's expected log-likelihood over nodes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _ in range(25): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eta = ai * nodes + bi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p = _sigmoid(eta) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w = n_iq[i] * p * (1.0 - p) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resid = r_iq[i] - n_iq[i] * p | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| g_a = float((resid * nodes).sum()) - ridge_a * ai | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| g_b = float(resid.sum()) - ridge_b * bi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_aa = -float((w * nodes * nodes).sum()) - ridge_a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_bb = -float(w.sum()) - ridge_b | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_ab = -float((w * nodes).sum()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| det = h_aa * h_bb - h_ab * h_ab | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if abs(det) < 1e-12: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| da = (h_bb * g_a - h_ab * g_b) / det | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db = (h_aa * g_b - h_ab * g_a) / det | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ai -= da | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bi -= db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ai = float(np.clip(ai, 1e-3, 10.0)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if abs(da) + abs(db) < 1e-8: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a_new[i], b_new[i] = ai, bi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Optimized M-step: Vectorize Newton-Raphson across all active items simultaneously | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # to avoid slow Python loops and per-item array overhead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active = np.ones(n_items, dtype=bool) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes_sq = nodes * nodes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _ in range(25): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not active.any(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eta = a_new[active, None] * nodes[None, :] + b_new[active, None] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p = _sigmoid(eta) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| n_act = n_iq[active] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r_act = r_iq[active] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w = n_act * p * (1.0 - p) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resid = r_act - n_act * p | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| g_a = resid @ nodes - ridge_a * a_new[active] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| g_b = resid.sum(axis=1) - ridge_b * b_new[active] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_aa = -(w @ nodes_sq) - ridge_a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_bb = -w.sum(axis=1) - ridge_b | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h_ab = -(w @ nodes) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| det = h_aa * h_bb - h_ab * h_ab | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| det_ok = np.abs(det) >= 1e-12 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| da = np.zeros_like(g_a) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db = np.zeros_like(g_b) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| da[det_ok] = (h_bb[det_ok] * g_a[det_ok] - h_ab[det_ok] * g_b[det_ok]) / det[det_ok] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db[det_ok] = (h_aa[det_ok] * g_b[det_ok] - h_ab[det_ok] * g_a[det_ok]) / det[det_ok] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a_new[active] -= da | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| b_new[active] -= db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a_new[active] = np.clip(a_new[active], 1e-3, 10.0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done = (~det_ok) | ((np.abs(da) + np.abs(db)) < 1e-8) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if done.any(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active_idx = np.where(active)[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active[active_idx[done]] = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+167
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: command -v codegraph || true
codegraph --help 2>&1 | head -80 || trueRepository: ContextualWisdomLab/fast-mlsirm Length of output: 212 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- repository guidance ---'
for f in AGENTS.md; do
if [ -f "$f" ]; then cat -n "$f"; fi
done
printf '%s\n' '--- changed-file summary ---'
git diff --stat -- python/fast_mlsirm/estimators/mmle.py .jules/bolt.md
printf '%s\n' '--- estimator diff ---'
git diff --unified=80 -- python/fast_mlsirm/estimators/mmle.py
printf '%s\n' '--- bolt guidance ---'
cat -n .jules/bolt.md | sed -n '35,55p'
printf '%s\n' '--- related files ---'
git ls-files | grep -E '(^|/)(test.*mmle|mmle|MLSIRM|MLS2PLM|AGENTS|.*fit.*stat)' || true
printf '%s\n' '--- relevant symbols and references ---'
rg -n -C 3 'Newton|observed|n_iq|r_iq|ridge_a|ridge_b|a_new|b_new|MLS2PLM|MLSIRM|fit statistic' \
python tests docs .jules 2>/dev/null | head -400 || trueRepository: ContextualWisdomLab/fast-mlsirm Length of output: 44683 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Python MMLE implementation ---'
cat -n python/fast_mlsirm/estimators/mmle.py | sed -n '1,240p'
printf '%s\n' '--- MMLE estimator tests ---'
cat -n tests/test_estimator_mmle.py | sed -n '1,320p'
printf '%s\n' '--- Rust MMLE implementation ---'
cat -n crates/mlsirm-core/src/mmle.rs | sed -n '1,300p'
printf '%s\n' '--- MMLE design document ---'
cat -n docs/mmle_marginal_lsirm_design.md | sed -n '1,135p'
printf '%s\n' '--- formula compilation ---'
cat -n docs/papers/mmle-lsirm-formula-compilation.md | sed -n '1,180p'
printf '%s\n' '--- recent commits ---'
git log --oneline -8 -- python/fast_mlsirm/estimators/mmle.py .jules/bolt.mdRepository: ContextualWisdomLab/fast-mlsirm Length of output: 43361 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- vectorization commit ---'
git show --stat --oneline c103514
git show --format=fuller --no-ext-diff --unified=80 c103514 -- \
python/fast_mlsirm/estimators/mmle.py .jules/bolt.md tests/test_estimator_mmle.py
printf '%s\n' '--- parent implementation ---'
git show c103514^:python/fast_mlsirm/estimators/mmle.py | \
cat -n | sed -n '105,190p'
printf '%s\n' '--- commit files ---'
git diff-tree --no-commit-id --name-status -r c103514Repository: ContextualWisdomLab/fast-mlsirm Length of output: 18528 🏁 Script executed: #!/bin/bash
python3 - <<'PY'
import numpy as np
from numpy.polynomial.hermite_e import hermegauss
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -35.0, 35.0)))
def fit(y, observed, vectorized, n_nodes=11, max_iter=40, seed=1):
y = np.asarray(y, dtype=np.float64)
observed = np.asarray(observed, dtype=bool)
y_filled = np.where(observed, y, 0.0)
obs_f = observed.astype(np.float64)
nodes, raw = hermegauss(n_nodes)
weights = raw / raw.sum()
rng = np.random.default_rng(seed)
p_item = (y_filled * obs_f).sum(0) / np.clip(obs_f.sum(0), 1.0, None)
p_item = np.clip(p_item, 0.02, 0.98)
a = np.ones(y.shape[1]) + 0.01 * rng.standard_normal(y.shape[1])
b = np.log(p_item / (1.0 - p_item))
trace = []
for _ in range(max_iter):
logit = nodes[:, None] * a[None, :] + b[None, :]
log_p1 = -np.logaddexp(0.0, -logit)
log_p0 = -np.logaddexp(0.0, logit)
pos = (y_filled * obs_f) @ log_p1.T
neg = ((1.0 - y_filled) * obs_f) @ log_p0.T
joint = pos + neg + np.log(weights)[None, :]
max_j = joint.max(axis=1, keepdims=True)
stab = np.exp(joint - max_j)
denom = stab.sum(axis=1, keepdims=True)
posterior = stab / denom
trace.append(float((max_j[:, 0] + np.log(denom[:, 0])).sum()))
n_iq = obs_f.T @ posterior
r_iq = (obs_f * y_filled).T @ posterior
a_new, b_new = a.copy(), b.copy()
if vectorized:
active = np.ones(len(a), dtype=bool)
nodes_sq = nodes * nodes
for _ in range(25):
if not active.any():
break
eta = a_new[active, None] * nodes[None, :] + b_new[active, None]
p = sigmoid(eta)
n_act, r_act = n_iq[active], r_iq[active]
w = n_act * p * (1.0 - p)
resid = r_act - n_act * p
ga = resid @ nodes - 1e-3 * a_new[active]
gb = resid.sum(axis=1) - 1e-3 * b_new[active]
haa = -(w @ nodes_sq) - 1e-3
hbb = -w.sum(axis=1) - 1e-3
hab = -(w @ nodes)
det = haa * hbb - hab * hab
ok = np.abs(det) >= 1e-12
da = np.zeros_like(ga)
db = np.zeros_like(gb)
da[ok] = (hbb[ok] * ga[ok] - hab[ok] * gb[ok]) / det[ok]
db[ok] = (haa[ok] * gb[ok] - hab[ok] * ga[ok]) / det[ok]
a_new[active] -= da
b_new[active] -= db
a_new[active] = np.clip(a_new[active], 1e-3, 10.0)
done = (~ok) | ((np.abs(da) + np.abs(db)) < 1e-8)
if done.any():
idx = np.where(active)[0]
active[idx[done]] = False
else:
for i in range(len(a)):
ai, bi = a[i], b[i]
for _ in range(25):
eta = ai * nodes + bi
p = sigmoid(eta)
w = n_iq[i] * p * (1.0 - p)
resid = r_iq[i] - n_iq[i] * p
ga = float((resid * nodes).sum()) - 1e-3 * ai
gb = float(resid.sum()) - 1e-3 * bi
haa = -float((w * nodes * nodes).sum()) - 1e-3
hbb = -float(w.sum()) - 1e-3
hab = -float((w * nodes).sum())
det = haa * hbb - hab * hab
if abs(det) < 1e-12:
break
da = (hbb * ga - hab * gb) / det
db = (haa * gb - hab * ga) / det
ai -= da
bi -= db
ai = float(np.clip(ai, 1e-3, 10.0))
if abs(da) + abs(db) < 1e-8:
break
a_new[i], b_new[i] = ai, bi
a, b = a_new, b_new
return a, b, np.asarray(trace)
rng = np.random.default_rng(202608)
worst = np.zeros(3)
cases = 0
for n_persons, n_items, n_nodes in [(1, 1, 3), (20, 7, 7), (80, 25, 11), (120, 40, 41)]:
for case in range(20):
y = rng.integers(0, 2, size=(n_persons, n_items)).astype(float)
observed = rng.random(y.shape) > (0.95 if case == 0 else 0.35)
if not observed.any():
observed[0, 0] = True
y[~observed] = np.nan
old = fit(y, observed, False, n_nodes=n_nodes)
new = fit(y, observed, True, n_nodes=n_nodes)
diffs = np.array([
np.max(np.abs(old[0] - new[0])),
np.max(np.abs(old[1] - new[1])),
np.max(np.abs(old[2] - new[2])),
])
worst = np.maximum(worst, diffs)
cases += 1
print(f"cases={cases}")
print(f"max_abs_diff_a={worst[0]:.3e}")
print(f"max_abs_diff_b={worst[1]:.3e}")
print(f"max_abs_diff_loglik={worst[2]:.3e}")
print("equivalent_within_1e-12=", bool(np.all(worst < 1e-12)))
PYRepository: ContextualWisdomLab/fast-mlsirm Length of output: 287 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- paper/design history ---'
git log --oneline --all -- docs/mmle_marginal_lsirm_design.md docs/papers/mmle-lsirm-formula-compilation.md | head -20
printf '%s\n' '--- design document at vectorization parent ---'
git cat-file -e c103514^:docs/mmle_marginal_lsirm_design.md && \
git show c103514^:docs/mmle_marginal_lsirm_design.md | sed -n '1,20p'
printf '%s\n' '--- pure-Python Newton equivalence probe ---'
python3 - <<'PY'
import math
import random
def sigmoid(x):
x = max(-35.0, min(35.0, x))
return 1.0 / (1.0 + math.exp(-x))
def scalar(n_iq, r_iq, nodes, a, b):
aa, bb = list(a), list(b)
for i in range(len(aa)):
ai, bi = aa[i], bb[i]
for _ in range(25):
ga = gb = haa = hbb = hab = 0.0
for q, node in enumerate(nodes):
p = sigmoid(ai * node + bi)
n = n_iq[i][q]
w = n * p * (1.0 - p)
resid = r_iq[i][q] - n * p
ga += resid * node
gb += resid
haa -= w * node * node
hbb -= w
hab -= w * node
ga -= 1e-3 * ai
gb -= 1e-3 * bi
haa -= 1e-3
hbb -= 1e-3
det = haa * hbb - hab * hab
if abs(det) < 1e-12:
break
da = (hbb * ga - hab * gb) / det
db = (haa * gb - hab * ga) / det
ai -= da
bi -= db
ai = max(1e-3, min(10.0, ai))
if abs(da) + abs(db) < 1e-8:
break
aa[i], bb[i] = ai, bi
return aa, bb
def batched(n_iq, r_iq, nodes, a, b):
aa, bb = list(a), list(b)
active = [True] * len(aa)
nodes_sq = [x * x for x in nodes]
for _ in range(25):
if not any(active):
break
active_indices = [i for i, flag in enumerate(active) if flag]
updates = []
for i in active_indices:
eta = [aa[i] * node + bb[i] for node in nodes]
p = [sigmoid(x) for x in eta]
w = [n * pp * (1.0 - pp) for n, pp in zip(n_iq[i], p)]
resid = [r - n * pp for r, n, pp in zip(r_iq[i], n_iq[i], p)]
ga = sum(x * node for x, node in zip(resid, nodes)) - 1e-3 * aa[i]
gb = sum(resid) - 1e-3 * bb[i]
haa = -sum(x * node_sq for x, node_sq in zip(w, nodes_sq)) - 1e-3
hbb = -sum(w) - 1e-3
hab = -sum(x * node for x, node in zip(w, nodes))
det = haa * hbb - hab * hab
ok = abs(det) >= 1e-12
if ok:
da = (hbb * ga - hab * gb) / det
db = (haa * gb - hab * ga) / det
else:
da = db = 0.0
updates.append((i, da, db, ok))
for i, da, db, ok in updates:
aa[i] -= da
bb[i] -= db
aa[i] = max(1e-3, min(10.0, aa[i]))
if (not ok) or abs(da) + abs(db) < 1e-8:
active[i] = False
return aa, bb
rng = random.Random(202608)
worst_a = worst_b = 0.0
cases = 0
for n_items, n_nodes in [(1, 3), (7, 7), (25, 11), (40, 41)]:
for case in range(100):
nodes = [rng.uniform(-12.0, 12.0) for _ in range(n_nodes)]
n_iq, r_iq = [], []
for _ in range(n_items):
counts = [0.0 if case == 0 else rng.uniform(0.0, 100.0)
for _ in range(n_nodes)]
responses = [n * rng.random() for n in counts]
n_iq.append(counts)
r_iq.append(responses)
a = [rng.uniform(1e-3, 10.0) for _ in range(n_items)]
b = [rng.uniform(-20.0, 20.0) for _ in range(n_items)]
sa, sb = scalar(n_iq, r_iq, nodes, a, b)
va, vb = batched(n_iq, r_iq, nodes, a, b)
worst_a = max(worst_a, *(abs(x-y) for x, y in zip(sa, va)))
worst_b = max(worst_b, *(abs(x-y) for x, y in zip(sb, vb)))
cases += 1
print(f"cases={cases}")
print(f"max_abs_diff_a={worst_a:.3e}")
print(f"max_abs_diff_b={worst_b:.3e}")
print("same_within_1e-12=", worst_a < 1e-12 and worst_b < 1e-12)
PYRepository: ContextualWisdomLab/fast-mlsirm Length of output: 1568 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Rust parity test ---'
if [ -f tests/test_rust_parity.py ]; then
cat -n tests/test_rust_parity.py | sed -n '1,320p'
else
printf '%s\n' 'tests/test_rust_parity.py is absent'
fi
printf '%s\n' '--- MMLE parity references ---'
rg -n -C 4 'mmle|MMLE|fit_mmle_2pl|rust.*numpy|numpy.*rust|parity' \
tests python crates | head -500 || true
printf '%s\n' '--- vectorization commit tests ---'
git diff c103514^ c103514 --name-only -- tests
printf '%s\n' '--- available Python runtimes/dependencies ---'
python3 - <<'PY'
try:
import numpy
print("numpy_available", numpy.__version__)
except Exception as exc:
print("numpy_unavailable", type(exc).__name__)
PYRepository: ContextualWisdomLab/fast-mlsirm Length of output: 46038
권장 문서 변경- **Action:** Replace outer Python loops over independent elements with a fully vectorized inner loop.
+ **Action:** Before vectorizing an estimator update, document the paper-supported scope and verify that the formula, observed-mask semantics, bounds, stopping behavior, and Rust–NumPy parity are unchanged. If these conditions hold, replace outer Python loops over independent elements with a fully vectorized inner loop.📝 Committable suggestion
Suggested change
📍 Affects 2 files
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a, b = a_new, b_new | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
활성 항목을 배치 처리하거나 작업 배열 크기를 제한하십시오.
라인 135-142는
eta,p,w,resid를 모두(n_active, n_nodes)크기로 생성합니다.n_items와n_nodes에는 상한이 없습니다. 많은 항목을 가진 입력에서는 이 배열들이 동시에 큰 피크 메모리를 사용하고 프로세스를 OOM으로 종료할 수 있습니다.활성 항목 인덱스를 고정 크기 배치로 나누어 처리하십시오. 각 배치에서는 현재 벡터화 연산을 유지하고,
done결과를 원래 항목 인덱스에 다시 반영하십시오. 또는 할당 전에 명시적인 작업 요소 상한을 검증하십시오.As per coding guidelines, “bound user-derived array dimensions” 규칙을 적용했습니다.
🤖 Prompt for AI Agents
Source: Coding guidelines