Download code/offload/launch/windows/tests/test_decision_v2.py from ProprietaryLegal/dsv41-window-harness: direct link, hf CLI and curl.
- Browser
- Download file 21.3 kB
-
https://huggingface.co/ProprietaryLegal/dsv41-window-harness/resolve/main/code/offload/launch/windows/tests/test_decision_v2.py
- Command line
-
hf download hf://ProprietaryLegal/dsv41-window-harness/code/offload/launch/windows/tests/test_decision_v2.py
-
curl -L -o test_decision_v2.py https://huggingface.co/ProprietaryLegal/dsv41-window-harness/resolve/main/code/offload/launch/windows/tests/test_decision_v2.py
21.3 kB
| """WIN2 API: fairer decision rule and control archive (CPU only).""" | |
| from __future__ import annotations | |
| import json | |
| import random | |
| import statistics | |
| from pathlib import Path | |
| from typing import cast | |
| import pytest | |
| from offload.prefill_maxlen.evidence import Json, number, record | |
| from offload.launch.windows import decision_v2 as d | |
| PROMPT = 'c' * 64 # the one proved prompt identity of a cell's values (DP-2) | |
| OTHER = 'd' * 64 | |
| def Series(cell: str, metric: str, higher: bool, control: tuple[float, ...], candidate: tuple[float, ...], | |
| paired: bool = True, prompt: str | None = PROMPT) -> d.Series: | |
| ids = () if prompt is None else (prompt,) | |
| return d.Series(cell, metric, higher, tuple(control), tuple(candidate), paired, ids * len(control), | |
| ids * len(candidate)) | |
| def test_t_quantiles_match_table_and_floor_fractional_df() -> None: | |
| assert d.t975(2) == 4.303 and d.t975(2.9) == 4.303 and d.t975(200) == 2.042 | |
| with pytest.raises(ValueError): | |
| d.t975(0.5) | |
| def test_clear_paired_win_lower_bound_above_zero() -> None: | |
| s = Series('short-b1', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6)) | |
| r = d.decide_cell(s) | |
| interval = r['interval95'] | |
| assert isinstance(interval, list) | |
| lo = number(interval[0]) | |
| assert r['verdict'] == 'WIN' and number(r['point_gain']) > .05 and lo > 0 | |
| def test_gain_below_min_is_inconclusive_even_with_tight_interval() -> None: | |
| s = Series('short-b1', 'decode_tps', True, (60.0, 60.0, 60.0), (60.9, 60.9, 60.9)) | |
| assert d.decide_cell(s)['verdict'] == 'INCONCLUSIVE' | |
| def test_noisy_five_percent_is_inconclusive_not_loss() -> None: | |
| s = Series('32k-b1', 'ttft_s', False, (15.0, 14.0, 15.4), (13.0, 15.2, 13.6)) | |
| assert d.decide_cell(s)['verdict'] == 'INCONCLUSIVE' | |
| def test_loss_needs_two_of_three_repeats_beyond_own_spread_and_the_interval_below_zero() -> None: | |
| worse = Series('short-b1', 'ms_per_round', False, (40.0, 40.4, 40.2), (43.0, 43.5, 43.2)) | |
| assert d.decide_cell(worse)['verdict'] == 'LOSS' | |
| one_bad = Series('short-b1', 'ms_per_round', False, (40.0, 40.4, 40.2), (43.0, 40.3, 40.1)) | |
| assert d.decide_cell(one_bad)['verdict'] != 'LOSS' | |
| # Revision 3: two of three repeats far beyond the spread, but the third is equal, so the interval crosses zero. | |
| noisy = Series('short-b1', 'ms_per_round', False, (40.0, 40.4, 40.2), (43.0, 43.5, 40.1)) | |
| r = d.decide_cell(noisy) | |
| assert r['loss_repeat_test'] is True and number(cast(list[Json], r['interval95'])[1]) > 0 | |
| assert r['verdict'] == 'INCONCLUSIVE' # revision 2: LOSS | |
| def test_orientation_lower_is_better() -> None: | |
| faster = Series('150k-b1', 'ttft_s', False, (69.0, 69.5, 69.2), (64.0, 64.3, 64.1)) | |
| r = d.decide_cell(faster) | |
| assert r['verdict'] == 'WIN' and number(r['point_gain']) > 0 | |
| def test_unpaired_welch_interval() -> None: | |
| s = Series('mixed', 'decode_tps', True, (50.0, 51.0, 49.5, 50.5), (55.0, 56.0, 55.5), paired=False) | |
| r = d.decide_cell(s) | |
| assert r['paired'] is False and r['verdict'] == 'WIN' and 'Welch' in str(r['interval_method']) | |
| def test_fewer_than_three_repeats_refused(control: tuple[float, ...], candidate: tuple[float, ...]) -> None: | |
| with pytest.raises(ValueError, match='at least 3'): | |
| d.decide_cell(Series('c', 'm', True, control, candidate)) | |
| def test_paired_needs_equal_lengths_and_positive_values() -> None: | |
| with pytest.raises(ValueError, match='equal repeats'): | |
| d.decide_cell(Series('c', 'm', True, (1.0, 2.0, 3.0), (1.0, 2.0, 3.0, 4.0))) | |
| with pytest.raises(ValueError, match='positive'): | |
| d.decide_cell(Series('c', 'm', True, (1.0, 0.0, 3.0), (1.0, 2.0, 3.0))) | |
| def _win(cell: str) -> d.Series: | |
| return Series(cell, 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6)) | |
| def _loss(cell: str) -> d.Series: | |
| return Series(cell, 'decode_tps', True, (60.0, 60.2, 60.1), (55.0, 55.1, 55.3)) | |
| def _flat(cell: str) -> d.Series: | |
| return Series(cell, 'decode_tps', True, (60.0, 61.0, 60.5), (60.2, 61.1, 60.6)) | |
| def test_window_candidate_needs_a_win_and_no_loss() -> None: | |
| r = d.decide_window([_win('a'), _flat('b')], ['a', 'b']) | |
| assert r['rule'] == 'win2-v1' and r['status'] == 'CANDIDATE' | |
| assert d.decide_window([_win('a'), _loss('b')], ['a', 'b'])['status'] == 'NOT A CANDIDATE' | |
| assert d.decide_window([_flat('a'), _flat('b')], ['a', 'b'])['status'] == 'INCONCLUSIVE' | |
| def test_reported_cells_never_decide_and_may_have_fewer_repeats() -> None: | |
| reported = Series('mixed', 'decode_tps', True, (60.0,), (40.0,)) | |
| r = d.decide_window([_win('a'), reported], ['a']) | |
| assert r['status'] == 'CANDIDATE' | |
| cell = record(record(r['cells'])['mixed']) | |
| assert cell['decides'] is False and cell['verdict'] == 'REPORTED' | |
| def test_decision_cells_never_pool_or_pair_across_prompt_hashes() -> None: | |
| """DP-2: a decision cell refuses values without prompt identities, of two prompts in one arm, of different prompts in | |
| the two arms, and a guard of another prompt than its primary; a reported cell checks consistency when given.""" | |
| with pytest.raises(ValueError, match='no prompt sha256'): | |
| d.decide_window([Series('a', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6), prompt=None)], ['a']) | |
| mixed_arm = d.Series('a', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6), True, | |
| (PROMPT, PROMPT, OTHER), (PROMPT,) * 3) | |
| other_arm = d.Series('a', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6), True, | |
| (PROMPT,) * 3, (OTHER,) * 3) | |
| guard_other = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.30, 1.31, 1.30), prompt=OTHER) | |
| for rows in ([mixed_arm], [other_arm], [_win('a'), guard_other]): | |
| with pytest.raises(ValueError, match='never pools or pairs across prompt hashes'): | |
| d.decide_window(rows, ['a']) | |
| with pytest.raises(ValueError, match='one prompt sha256 per value'): | |
| d.decide_window([d.Series('a', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6), True, | |
| (PROMPT,) * 2, (PROMPT,) * 3)], ['a']) | |
| with pytest.raises(ValueError, match='never pools or pairs across prompt hashes'): | |
| d.decide_window([_win('a'), Series('r', 'decode_tps', True, (60.0,), (40.0,), prompt=None), | |
| d.Series('r2', 'decode_tps', True, (60.0,), (40.0,), True, (PROMPT,), (OTHER,))], ['a']) | |
| r = d.decide_window([_win('a'), Series('r', 'decode_tps', True, (60.0,), (40.0,), prompt=None)], ['a']) | |
| assert record(record(r['cells'])['a'])['prompt_sha256'] == PROMPT | |
| assert d.decide_cell(Series('a', 'decode_tps', True, (60.0, 61.0, 60.5), (64.0, 65.2, 64.6), prompt=None))[ | |
| 'prompt_sha256'] is None # cell arithmetic alone; decision cells require it | |
| def test_guard_metric_can_only_cause_loss() -> None: | |
| primary = _win('a') | |
| guard_bad = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.10, 1.12, 1.11)) | |
| assert d.decide_window([primary, guard_bad], ['a'])['status'] == 'NOT A CANDIDATE' | |
| guard_win = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.50, 1.52, 1.51)) | |
| r = d.decide_window([_flat('a'), guard_win], ['a']) | |
| assert r['status'] == 'INCONCLUSIVE' and r['rule_revision'] == 3 | |
| assert record(record(record(record(r['cells'])['a'])['metrics'])['accepted_per_round'])['verdict'] == 'INCONCLUSIVE' | |
| def test_guard_loss_needs_whole_interval_below_zero() -> None: | |
| """S92 FIX 3: two of three guard repeats beyond the spread but the interval crosses zero -> no LOSS (rev 1: LOSS).""" | |
| noisy = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.10, 1.12, 1.30)) | |
| as_guard = d.decide_cell(noisy, guard=True) | |
| assert as_guard['loss_repeat_test'] is True and number(cast(list[Json], as_guard['interval95'])[1]) > 0 | |
| assert as_guard['verdict'] == 'INCONCLUSIVE' | |
| assert d.decide_window([_win('a'), noisy], ['a'])['status'] == 'CANDIDATE' | |
| def test_primary_loss_needs_whole_interval_below_zero_revision_3() -> None: | |
| """WIN2-R3 item 0: the primary follows the guard's LOSS rule (revision 2 lost on the loss-repeat test alone).""" | |
| noisy = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.10, 1.12, 1.30)) | |
| primary = d.decide_cell(noisy) | |
| assert primary['role'] == 'primary' and primary['loss_repeat_test'] is True | |
| assert number(cast(list[Json], primary['interval95'])[1]) > 0 and primary['verdict'] == 'INCONCLUSIVE' | |
| r = d.decide_window([noisy], ['a']) | |
| assert r['status'] == 'INCONCLUSIVE' and r['rule_revision'] == d.RULE_REVISION == 3 | |
| clear = Series('a', 'accepted_per_round', True, (1.30, 1.31, 1.30), (1.10, 1.12, 1.11)) | |
| assert d.decide_cell(clear)['verdict'] == 'LOSS' and d.decide_window([clear], ['a'])['status'] == 'NOT A CANDIDATE' | |
| def test_paired_loss_threshold_is_sqrt2_spread() -> None: | |
| control = (40.0, 40.4, 40.2) # s 0.2, c4(3) 0.8862: spread 0.949 %; paired threshold 1.342 % | |
| within = Series('c', 'ms_per_round', False, control, (40.45, 40.9, 40.2)) # -1.12 %, -1.24 %, 0 % | |
| r = d.decide_cell(within) | |
| assert abs(number(r['control_spread']) - 1.69 * 0.2 / d.c4(3) / 40.2) < 1e-12 and abs(d.c4(3) - 0.886227) < 1e-6 | |
| assert abs(number(r['loss_threshold']) - 2 ** .5 * number(r['control_spread'])) < 1e-15 | |
| assert r['loss_repeats'] == 0 and r['verdict'] != 'LOSS' # single-value spread: 2 of 3 -> LOSS | |
| unpaired = d.decide_cell(Series('c', 'ms_per_round', False, control, (40.7, 40.9, 40.8), paired=False)) | |
| assert unpaired['loss_threshold'] == unpaired['control_spread'] and unpaired['loss_repeats'] == 3 | |
| def test_control_spread_does_not_grow_with_control_repeats() -> None: | |
| """S92 FIX 3 item 5: the range of 8 controls is ~1.7 x the range of 3; the 1.69 x SD spread stays put.""" | |
| rng = random.Random(9) | |
| def mean_of(n: int, key: str) -> float: | |
| values = [] | |
| for _ in range(2000): | |
| control = tuple(40 * (1 + rng.gauss(0, .02)) for _ in range(n)) | |
| values.append(number(d.decide_cell(Series('c', 'ms_per_round', False, control, (40.0, 40.0, 40.0), | |
| paired=False))[key])) | |
| return statistics.fmean(values) | |
| assert mean_of(8, 'control_range') / mean_of(3, 'control_range') > 1.5 | |
| assert 0.95 < mean_of(8, 'control_spread') / mean_of(3, 'control_spread') < 1.05 | |
| assert abs(mean_of(3, 'control_spread') / mean_of(3, 'control_range') - 1.0) < 0.05 # = expected range of 3 | |
| def test_groups_give_separate_single_and_multi_user_verdicts() -> None: | |
| r = d.decide_window([_win('mixed'), _flat('short')], ['mixed', 'short'], | |
| groups={'multi-user': ['mixed'], 'single-user': ['short']}) | |
| assert r['groups'] == {'multi-user': 'CANDIDATE', 'single-user': 'INCONCLUSIVE'} | |
| with pytest.raises(ValueError, match='decision cells only'): | |
| d.decide_window([_win('a'), _flat('b')], ['a'], groups={'x': ['b']}) | |
| def test_one_or_two_distinct_decision_cells(decision: list[str]) -> None: | |
| with pytest.raises(ValueError): | |
| d.decide_window([_win('a'), _win('b'), _win('c')], decision) | |
| def test_missing_decision_cell_refused() -> None: | |
| with pytest.raises(ValueError, match='without measurements'): | |
| d.decide_window([_win('a')], ['z']) | |
| PROD = '596ba1cf4e3b11be29b41a57e7f69a5d8efa4799' | |
| def _row(window: str, value: float, *, cell: str = 'short-b1', metric: str = 'decode_tps', **extra: object) -> dict[str, Json]: | |
| row: dict[str, Json] = {'window': window, 'cell': cell, 'metric': metric, 'value': value, 'max_tokens': 512, | |
| 'commit': PROD, 'clock_cap_mhz': 2150, 'date': '20261027', 'start': 's1-control', | |
| 'repeat': 0, 'prompt_sha256': PROMPT} | |
| row.update(cast(dict[str, Json], extra)) | |
| return row | |
| def test_archive_chain_spread_and_tamper(tmp_path: Path) -> None: | |
| path = tmp_path / 'archive.jsonl' | |
| assert d.archive_spread(path, 'short-b1', 'decode_tps') is None | |
| d.archive_append(path, [_row('W1', 60.0), _row('W1', 62.0, repeat=1)]) | |
| assert d.archive_spread(path, 'short-b1', 'decode_tps') is None | |
| d.archive_append(path, [_row('W2', 55.0)]) | |
| spread = d.archive_spread(path, 'short-b1', 'decode_tps') | |
| assert spread is not None and abs(spread - (61.0 - 55.0) / 58.0) < 1e-12 | |
| assert d.archive_head(path)['rows'] == 3 | |
| lines = path.read_text().splitlines() | |
| assert json.loads(lines[0])['prev_sha256'] == d.GENESIS | |
| tampered = json.loads(lines[1]); tampered['value'] = 99.0 | |
| path.write_text('\n'.join([lines[0], json.dumps(tampered, sort_keys=True), lines[2]]) + '\n') | |
| with pytest.raises(ValueError, match='chain broken'): | |
| d.archive_spread(path, 'short-b1', 'decode_tps') | |
| def test_archive_row_requirements(tmp_path: Path, field: str, bad: object) -> None: | |
| """S92 FIX 2: rows without a 40-hex production commit, the start's clock cap, date, start and repeat are refused; | |
| DP-2: and without a 64-hex prompt identity (prompt_members must give it).""" | |
| row = _row('w', 1.0) | |
| row[field] = cast(Json, bad) | |
| with pytest.raises(ValueError): | |
| d.archive_append(tmp_path / 'a.jsonl', [row]) | |
| assert not (tmp_path / 'a.jsonl').exists() or d.archive_rows(tmp_path / 'a.jsonl') == [] | |
| def test_archive_required_fields(tmp_path: Path, field: str) -> None: | |
| row = _row('w', 1.0) | |
| del row[field] | |
| with pytest.raises(ValueError, match='lacks'): | |
| d.archive_append(tmp_path / 'a.jsonl', [row]) | |
| d.archive_append(tmp_path / 'a.jsonl', [_row('w', 1.0, clock_caps_mhz='unknown', clock_source='clock-log')]) | |
| def test_archive_prompt_identity_and_spread_never_pools_prompts(tmp_path: Path) -> None: | |
| """DP-2: same-day prompts differ by up to 0.59 accepted/round, so rows of two prompts never pool in a spread.""" | |
| members = ['1' * 64, '2' * 64, '3' * 64] | |
| group = d.prompt_identity(members) | |
| assert group == d.prompt_identity(list(reversed(members))) and d.prompt_identity(['1' * 64]) == '1' * 64 | |
| path = tmp_path / 'archive.jsonl' | |
| d.archive_append(path, [_row('W1', 1.0, metric='accepted_per_round', prompt_sha256=group, prompt_members=members), | |
| _row('W2', 1.1, metric='accepted_per_round', prompt_sha256=group, prompt_members=members), | |
| _row('W3', 1.59, metric='accepted_per_round', prompt_sha256=OTHER), | |
| _row('W4', 1.60, metric='accepted_per_round', prompt_sha256=OTHER)]) | |
| with pytest.raises(ValueError, match='prompt_sha256'): | |
| d.archive_spread(path, 'short-b1', 'accepted_per_round') | |
| spread = d.archive_spread(path, 'short-b1', 'accepted_per_round', where={'prompt_sha256': OTHER}) | |
| assert spread is not None and abs(spread - 0.01 / 1.595) < 1e-12 | |
| assert len(d.archive_spreads(path, 'short-b1', 'accepted_per_round')) == 2 | |
| with pytest.raises(ValueError, match='64-hex'): | |
| d.prompt_identity(['1' * 64, 'x']) | |
| def test_archive_spread_refuses_mixed_max_tokens_unless_filtered(tmp_path: Path) -> None: | |
| path = tmp_path / 'archive.jsonl' | |
| row = {'cell': '32k-b3', 'metric': 'ms_per_round'} | |
| d.archive_append(path, [_row('W1', 72.0, max_tokens=1500, **row), _row('W2', 73.0, max_tokens=1500, **row), | |
| _row('W3', 81.0, **row), _row('W4', 80.0, **row)]) | |
| with pytest.raises(ValueError, match='max_tokens'): | |
| d.archive_spread(path, '32k-b3', 'ms_per_round') | |
| spread = d.archive_spread(path, '32k-b3', 'ms_per_round', where={'max_tokens': 512}) | |
| assert spread is not None and abs(spread - 1.0 / 80.5) < 1e-12 | |
| assert d.archive_spread(path, '32k-b3', 'ms_per_round', where={'max_tokens': 1500}) is not None | |
| def test_archive_spread_refuses_mixed_commits_and_clocks(tmp_path: Path) -> None: | |
| """S92 counterexamples: rows without a commit were accepted, the spread pooled two production commits, and 2150 | |
| and 2300 MHz rows pooled silently (5.73 % pooled against 0.51 % at 2150).""" | |
| path = tmp_path / 'archive.jsonl' | |
| other = 'b' * 40 | |
| d.archive_append(path, [_row('W1', 40.0), _row('W2', 40.4), _row('W3', 37.0, commit=other), | |
| _row('W4', 41.0, clock_cap_mhz=2300), _row('W5', 37.5, clock_cap_mhz=2300)]) | |
| with pytest.raises(ValueError, match='commit'): | |
| d.archive_spread(path, 'short-b1', 'decode_tps') | |
| with pytest.raises(ValueError, match='clock_cap_mhz'): | |
| d.archive_spread(path, 'short-b1', 'decode_tps', where={'commit': PROD}) | |
| spread = d.archive_spread(path, 'short-b1', 'decode_tps', where={'commit': PROD, 'clock_cap_mhz': 2150}) | |
| assert spread is not None and abs(spread - 0.4 / 40.2) < 1e-12 | |
| per = d.archive_spreads(path, 'short-b1', 'decode_tps') | |
| tail = ' prompt=' + PROMPT[:12] | |
| assert set(per) == {f'max_tokens=512 clock_cap_mhz=2150 commit={PROD[:12]}' + tail, | |
| 'max_tokens=512 clock_cap_mhz=2300 commit=' + PROD[:12] + tail, | |
| 'max_tokens=512 clock_cap_mhz=2150 commit=bbbbbbbbbbbb' + tail} | |
| assert per[f'max_tokens=512 clock_cap_mhz=2150 commit={PROD[:12]}' + tail] == spread | |
| def test_older_archive_is_refused_by_name(tmp_path: Path) -> None: | |
| path = tmp_path / 'a.jsonl' | |
| path.write_text(json.dumps({**_row('W1', 1.0), 'schema': 'dsv41-control-archive-v3', 'prev_sha256': d.GENESIS}) + '\n') | |
| with pytest.raises(ValueError, match="expected 'dsv41-control-archive-v4'"): | |
| d.archive_append(path, [_row('W2', 1.0)]) | |
| def _null_window_rates(paired: bool, n_control: int, trials: int) -> dict[str, float]: | |
| """S92 sim_corr's model: two decision cells, no real effect; ms/round cv 1.5 %, acceptance cv 12 %, tok/s derived.""" | |
| rng = random.Random(5) | |
| def arm(n: int) -> tuple[list[float], list[float], list[float]]: | |
| ms = [40 * (1 + rng.gauss(0, .015)) for _ in range(n)] | |
| acc = [max(.3, 1.35 * (1 + rng.gauss(0, .12))) for _ in range(n)] | |
| return ms, acc, [(1 + a) * 1000 / m for a, m in zip(acc, ms)] | |
| out = {'CANDIDATE': 0, 'INCONCLUSIVE': 0, 'NOT A CANDIDATE': 0} | |
| for _ in range(trials): | |
| series = [] | |
| for cell in 'ab': | |
| k, c = arm(n_control), arm(3) | |
| for i, (metric, hib) in enumerate((('ms_per_round', False), ('accepted_per_round', True), ('decode_tps', True))): | |
| series.append(Series(cell, metric, hib, tuple(k[i]), tuple(c[i]), paired)) | |
| out[str(d.decide_window(series, ['a', 'b'])['status'])] += 1 | |
| return {k: v / trials for k, v in out.items()} | |
| def test_no_effect_window_rates(paired: bool, n_control: int, reject_max: float) -> None: | |
| """S92 FIX 3: with both guards a no-effect window was rejected 32.5 % (paired) / 16.0 % (unpaired) of the time. | |
| Revision 2 (10,000 windows, seed 5): 11.4 % / 14.6 % rejected, 1.8 % / 1.9 % accepted. Revision 3 (WIN2.md): | |
| 4.0 % / 6.2 % rejected, 1.9 % / 2.0 % accepted.""" | |
| rates = _null_window_rates(paired, n_control, 3000) | |
| assert rates['NOT A CANDIDATE'] < reject_max and rates['CANDIDATE'] > .01 | |
| def test_revision_3_power_per_cell() -> None: | |
| """WIN2.md power table (cv 2 %, seed 9): unpaired 4 v 3 catches a true 5 % loss about half the time (53 %; revision 2: | |
| 77 %) while a no-effect cell's false LOSS falls from 5.8 % to 1.4 %; a true 5 % gain wins 62 %.""" | |
| rng = random.Random(9) | |
| trials = 2000 | |
| counts = {-0.05: [0, 0], 0.0: [0, 0], 0.05: [0, 0]} # effect: [LOSS, WIN] | |
| for effect, tally in counts.items(): | |
| for _ in range(trials): | |
| control = tuple(40 * (1 + rng.gauss(0, .02)) for _ in range(4)) | |
| cand = tuple(40 * (1 - effect) * (1 + rng.gauss(0, .02)) for _ in range(3)) | |
| verdict = d.decide_cell(Series('c', 'ms_per_round', False, control, cand, paired=False))['verdict'] | |
| tally[0] += verdict == 'LOSS' | |
| tally[1] += verdict == 'WIN' | |
| assert counts[-0.05][0] / trials > .45 and counts[-0.05][1] == 0 | |
| assert counts[0.0][0] / trials < .03 and counts[0.0][1] / trials < .03 | |
| assert counts[0.05][1] / trials > .5 and counts[0.05][0] == 0 | |