Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
24989b8
docs(seeds): seed-004 v1 — Generation Runtime architecture
aurascoper Jul 21, 2026
84e5dee
feat(cloudbridge): decouple system prompts into repository resources
aurascoper Jul 21, 2026
b9b166a
feat(cloudbridge): GenerationRuntime + GenerationTransport protocols
aurascoper Jul 21, 2026
ce68a53
feat(cloudbridge): OllamaHTTPTransport + OllamaGenerationRuntime
aurascoper Jul 21, 2026
dc518b9
feat(telemetry): GeneratorFingerprint on DialecticalTurnEvent
aurascoper Jul 21, 2026
4002d3e
feat(harness): runtime selection for dialectic-session
aurascoper Jul 21, 2026
a155af5
fix(loop): log a silent turn when both voices return empty text
aurascoper Jul 21, 2026
b9c09fd
feat(telemetry): thread generator metadata end-to-end via adapter onM…
aurascoper Jul 22, 2026
4bdc4d5
feat(scripts): analyze_dialectic.py — quantitative baseline for Resea…
aurascoper Jul 22, 2026
80e9945
docs(rvs): soak-001-findings — post-soak analysis + 8 acceptance crit…
aurascoper Jul 22, 2026
eb1e373
feat(app): wire LiveRuntimeFactory into AppViewModel — completes two-…
aurascoper Jul 22, 2026
d354fe9
feat(app): wire attachMetadataCaptureFromAdapter in live AppViewModel…
aurascoper Jul 22, 2026
94541a3
fix(adapter): MetadataCallbackBox so onMetadata propagates across exi…
aurascoper Jul 22, 2026
09d0775
feat(scripts): soak-matrix.sh — empirical test matrix for the dialect…
aurascoper Jul 22, 2026
83cdc67
docs(rvs): soak-002-matrix — empirical test matrix results
aurascoper Jul 22, 2026
5ad39d0
artifacts(soak): commit Soak 002 matrix aggregate + leak audit
aurascoper Jul 22, 2026
6e0ce59
feat(analyzer): inertia (semantic/linguistic/policy) + critical-slowi…
aurascoper Jul 22, 2026
75dcc1c
feat(analysis): Pareto frontier analysis + refreshed matrix aggregate
aurascoper Jul 22, 2026
2bb1601
docs(rvs): soak-003-inertia-pareto — three-component inertia + Pareto…
aurascoper Jul 22, 2026
b9b902a
feat(analyzer): symbolic_drift (H₂ hypothesis test)
aurascoper Jul 22, 2026
bc4820c
docs(rvs): soak-004-symbolic-drift — H₂ hypothesis test (preliminary)
aurascoper Jul 22, 2026
6271ce8
docs+feat: RRB (Relational Representation Bias) — the cross-model metric
aurascoper Jul 22, 2026
f718c61
docs+feat: rhetorical motifs + epistemic orientation (H₃ hypothesis s…
aurascoper Jul 22, 2026
2258daf
docs+feat: Level of Abstraction (LoA) — H₄ hypothesis test
aurascoper Jul 22, 2026
0461070
docs(arch): three orthogonal concerns + metric contracts + experiment…
aurascoper Jul 22, 2026
1c0410c
docs(scoping): EEG encoder re-alignment (the "gloss only High load" t…
aurascoper Jul 22, 2026
e40fd37
chore(soak): gitignore the 1-cell smoke-test runs
aurascoper Jul 22, 2026
3e7361a
docs(arch): engineering runtime design — the Codex scope, before any …
aurascoper Jul 22, 2026
a29e180
docs(arch): add Julia science workspace boundary
aurascoper Jul 22, 2026
9fc7fed
docs(science): add state reconstruction goal
aurascoper Jul 22, 2026
c2fa51a
docs(science): add telemetry trajectory reconstruction
aurascoper Jul 22, 2026
9fa1850
science: add trajectory hypothesis analysis
aurascoper Jul 22, 2026
b6a60a0
feat(core): add prosody prediction trace contract
aurascoper Jul 22, 2026
ce9799d
feat(core): trace requested prosody features
aurascoper Jul 22, 2026
3abc2e8
feat(rust): add deterministic prosody feature kernel
aurascoper Jul 22, 2026
0441c1c
docs(eeg): scope mathematics, physics, and methods research
aurascoper Jul 23, 2026
a90a56f
feat(eeg): add offline encoder benchmark contract
aurascoper Jul 23, 2026
3e1b55b
docs(eeg): record scope contract audit
aurascoper Jul 23, 2026
efde475
docs(architecture): repair principles links
aurascoper Jul 23, 2026
9726626
fix(eeg): validate encoder pilot capture integrity
aurascoper Jul 23, 2026
b8c1336
feat(logging): quarantine dialectic replay corpus
aurascoper Jul 23, 2026
c007c2b
feat(logging): add local dialectic review
aurascoper Jul 23, 2026
6ae9b96
fix(logging): fail closed on review citations
aurascoper Jul 23, 2026
76a90e2
feat(review): add fail-closed local review cascade
aurascoper Jul 23, 2026
2a8869e
docs(review): record local review governance
aurascoper Jul 23, 2026
3e1f88e
fix(runtime): fail closed when prompt resources are unavailable
aurascoper Jul 24, 2026
0d03e76
fix(packaging): include SwiftPM resource bundles in macOS app
aurascoper Jul 24, 2026
8072247
fix(runtime): resolve Claude CLI without env-wrapper execution
aurascoper Jul 25, 2026
a6751e7
refactor(app): make fail-closed runtime resolution testable
aurascoper Jul 25, 2026
6b8d1e4
test(runtime): pin the R2 call site and remove vacuous assertions
aurascoper Jul 25, 2026
23c56ea
Merge PR #31: A1 runtime-trust foundation (R2 + R7)
aurascoper Jul 25, 2026
1fdf48b
feat(runtime): add resolved identity and readiness probing
aurascoper Jul 25, 2026
3067cd8
fix(dialectic): resolve Witness with a distinct prompt runtime
aurascoper Jul 25, 2026
14c8b6a
fix(ui): display actual runtime provider and readiness
aurascoper Jul 25, 2026
d0d5dd7
fix(runtime): distinguish unresolved locality and canonical model ide…
aurascoper Jul 26, 2026
db01432
fix(telemetry): persist Witness runtime fingerprint
aurascoper Jul 26, 2026
4c5e275
fix(ui): retain failed runtime identity after loop disablement
aurascoper Jul 26, 2026
b3a1ddb
test(runtime): record packaged Apple Silicon A2 acceptance
aurascoper Jul 26, 2026
900e19e
Merge pull request #23 from aurascoper/fix/eeg-protocol-v1
aurascoper Jul 27, 2026
47dfdfe
Merge pull request #24 from aurascoper/fix/eeg-protocol-v1-contract
aurascoper Jul 27, 2026
54e0458
Merge pull request #25 from aurascoper/feat/dialectic-corpus-quarantine
aurascoper Jul 27, 2026
5a72fd1
feat(eeg): add fail-closed structured-state shadow artifacts
aurascoper Jul 27, 2026
ed843bd
fix(eeg): resolve NumPy trapezoid compatibility lazily
aurascoper Jul 27, 2026
bb7dff4
ci: run the Python contract suites
aurascoper Jul 27, 2026
09ac18a
ci: reference issue #38 for the deselected eval cases
aurascoper Jul 27, 2026
5488951
Merge pull request #36 from aurascoper/fix/numpy-trapezoid-compat
aurascoper Jul 27, 2026
80d8d8e
Merge pull request #37 from aurascoper/ci/python-contract-suites
aurascoper Jul 27, 2026
7d1493a
Merge docs/eeg-methods-scope into fix/structured-state-shadow-contract
aurascoper Jul 27, 2026
bbeb979
fix(eeg): enforce immutable and provenance-consistent shadow replay
aurascoper Jul 27, 2026
54080a6
Merge pull request #35 from aurascoper/fix/structured-state-shadow-co…
aurascoper Jul 27, 2026
13599e4
Merge docs/eeg-methods-scope into feat/local-dialectic-review-clean
aurascoper Jul 27, 2026
5117a9e
test(review): publish shadow replay artifacts outside the fixture root
aurascoper Jul 27, 2026
48652e9
Merge pull request #33 from aurascoper/feat/local-dialectic-review-clean
aurascoper Jul 27, 2026
5f2cf64
fix(eval): stop zero-variance Cohen's d from reporting no effect
aurascoper Jul 27, 2026
b0695c3
test(eval): use an attainable exact Mann-Whitney significance case
aurascoper Jul 27, 2026
c1aba1a
test(eval): pin biased linear CKA behavior across sample regimes
aurascoper Jul 27, 2026
b8894ee
Merge pull request #39 from aurascoper/fix/eval-stats-zero-variance-c…
aurascoper Jul 27, 2026
c064bcb
Merge pull request #40 from aurascoper/test/mann-whitney-attainable-s…
aurascoper Jul 27, 2026
41f559a
Merge pull request #41 from aurascoper/test/biased-cka-null-regimes
aurascoper Jul 27, 2026
9eabad6
Merge current EEG methods staging into A2 final acceptance
aurascoper Jul 27, 2026
c2e1f71
ci: run A2 app and session suites
aurascoper Jul 27, 2026
9548da4
Merge pull request #42 from aurascoper/ci/a2-app-session-suites
aurascoper Jul 27, 2026
fe20fec
Merge A2 app/session CI prerequisite into A2 final acceptance
aurascoper Jul 27, 2026
2367281
fix(runtime): distinguish configured from verified readiness
aurascoper Jul 27, 2026
71c5c02
fix(runtime): report unverified Claude checks as not run
aurascoper Jul 27, 2026
abb0eea
fix(runtime): publish Claude metadata in the live app
aurascoper Jul 28, 2026
0dfae3c
docs(evaluation): record A2 final-head acceptance
aurascoper Jul 28, 2026
14df545
Merge PR #32: A2 role-specific runtime identity and readiness
aurascoper Jul 28, 2026
98f1b6a
merge: S1, origin/docs/eeg-methods-scope into the integration trunk
aurascoper Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 94 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,44 @@ jobs:
# covered socket-free by MuseOSCDecoderTests / MindMonitorDecoderTests,
# which still run. Run the socket integration test locally.

# The step above names five targets. The package declares nine, and the
# app-level fail-closed, runtime-presentation, cancellation, and headless
# runtime-factory regressions live in two it never selected:
# NeuralComposeAppTests (116 tests) and DialecticSessionTests (5).
#
# Assert they are discoverable before running them. A `--filter` that
# matches nothing exits 0, so a renamed, moved, or emptied target would
# otherwise turn into another green step that ran nothing.
- name: Assert A2 suites are discoverable
run: |
swift test list > /tmp/neuralcompose-tests.txt

# Sentinel classes, one per target.
grep -q "AppViewModelRuntimeFailClosedTests" /tmp/neuralcompose-tests.txt \
|| { echo "::error::AppViewModelRuntimeFailClosedTests not discoverable"; exit 1; }
grep -q "RuntimeFactoryClaudeResolutionTests" /tmp/neuralcompose-tests.txt \
|| { echo "::error::RuntimeFactoryClaudeResolutionTests not discoverable"; exit 1; }

# Sentinels alone would still pass if the rest of a target vanished,
# so floor each target's discovered count too. Floors are set below
# the current 116/5 so ordinary test additions never trip them.
app=$(grep -c '^NeuralComposeAppTests\.' /tmp/neuralcompose-tests.txt)
session=$(grep -c '^DialecticSessionTests\.' /tmp/neuralcompose-tests.txt)
echo "discovered: NeuralComposeAppTests=$app DialecticSessionTests=$session"
[ "$app" -ge 100 ] \
|| { echo "::error::NeuralComposeAppTests discovered $app, expected >= 100"; exit 1; }
[ "$session" -ge 5 ] \
|| { echo "::error::DialecticSessionTests discovered $session, expected >= 5"; exit 1; }

# RuntimeIdentityPresentationTests is deliberately NOT asserted here: it
# arrives with PR #32. Add it as a third sentinel when that lands, so the
# guard stays true at every revision rather than failing on this base.
- name: Test (A2 app and session suites)
run: |
swift test \
--filter NeuralComposeAppTests \
--filter DialecticSessionTests

# Tests/eval had no CI at all before this job. Seven modules, and only
# test_adr_references.py ran (via the direct python3 step above) — the other
# six executed nowhere, which is how three failing assertions and an
Expand Down Expand Up @@ -133,6 +171,15 @@ jobs:
# contract depends on SciPy's exact/asymptotic behaviour. An
# unconstrained install would let a major bump change a p-value
# without any commit here.
#
# torch and the NeuralComposeEEG requirements come first because one
# module here, test_local_open_weight_review.py, imports that
# package's test helpers, and those import torch. The CPU wheel is
# named explicitly: the default Linux torch pulls a ~800MB CUDA build
# for a runner with no GPU. The bounded pins go last so they decide
# the numpy and scipy versions.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -r NeuralComposeEEG/requirements.txt
pip install \
'pytest>=8,<9' \
'numpy>=2,<3' \
Expand All @@ -152,9 +199,9 @@ jobs:
echo "discovered: Tests/eval=${collected:-0} collection_errors=${errors:-0}"
[ "${errors:-0}" -eq 0 ] \
|| { echo "::error::${errors} module(s) failed to collect"; exit 1; }
# Floor sits below the current 63 so ordinary additions never trip it.
[ "${collected:-0}" -ge 55 ] \
|| { echo "::error::Tests/eval collected ${collected:-0}, expected >= 55"; exit 1; }
# Floor sits below the current 137 so ordinary additions never trip it.
[ "${collected:-0}" -ge 125 ] \
|| { echo "::error::Tests/eval collected ${collected:-0}, expected >= 125"; exit 1; }

- name: Test (eval suites)
run: |
Expand All @@ -163,21 +210,53 @@ jobs:
python -m pytest Tests/eval -v \
--ignore=Tests/eval/test_joint_embedding.py

# NeuralComposeEEG is not on main yet — it arrives with lane B. Absent is
# expected and must be said out loud; present-but-empty is a defect, since
# a package whose tests silently collect nothing is the failure this whole
# job exists to prevent.
- name: Assert NeuralComposeEEG is absent or non-vacuous
# NeuralComposeEEG had no CI at all until this job, which is how a test
# importing an uncommitted module (neuralcompose_eeg.structured_state) reached
# a pull request unnoticed: build-and-test above is Swift-only.
#
# Linux, not macOS: nothing here is platform-specific, and the runner is both
# cheaper and faster.
#
# This job covers NeuralComposeEEG only. Tests/eval runs in python-eval above,
# which bounds the numpy and scipy majors its assertions depend on. Running
# it here too would test it twice, the second time in an unbounded
# environment.
python-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: NeuralComposeEEG/requirements.txt

- name: Install Python dependencies
run: |
# CPU wheel explicitly: the default Linux torch pulls a ~800MB CUDA
# build for a runner with no GPU. Installed first so the pinned
# requirements below see torch>=2.0 as already satisfied.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -r NeuralComposeEEG/requirements.txt
# pytest is here to count what is collectable, for the guard below.
pip install 'pytest>=8,<9'

# A package whose tests silently collect nothing is the failure this
# job exists to prevent, so count before running.
- name: Assert NeuralComposeEEG is non-vacuous
run: |
if [ ! -d NeuralComposeEEG ]; then
echo "NeuralComposeEEG/ absent on this branch — expected until lane B integrates it"
exit 0
fi
echo "NeuralComposeEEG/ present — its suite must not be vacuous"
n=$(PYTHONPATH=NeuralComposeEEG/src python -m pytest NeuralComposeEEG/tests \
--collect-only -q 2>/dev/null \
| grep -oE '^[0-9]+ tests? collected' | grep -oE '^[0-9]+' | head -1)
echo "discovered: NeuralComposeEEG=${n:-0}"
[ "${n:-0}" -gt 0 ] \
|| { echo "::error::NeuralComposeEEG/ is present but collected 0 tests"; exit 1; }
PYTHONPATH=NeuralComposeEEG/src python -m pytest NeuralComposeEEG/tests -v
|| { echo "::error::NeuralComposeEEG/ collected 0 tests"; exit 1; }

# Offline scientific package: canonical dataset contracts, capture
# manifests, and the structured-state shadow bridge. Discovered rather
# than enumerated so new contract suites are picked up automatically.
- name: Test (NeuralComposeEEG contracts)
run: |
PYTHONPATH=NeuralComposeEEG/src \
python -m unittest discover -s NeuralComposeEEG/tests -t . --verbose
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ venv.*.parked/
__pycache__/
*.pyc

## Rust utility crates
target/
**/target/

## Synthetic data outputs (regeneratable from Scripts/generate_synthetic_sleep.py)
Data/*
!Data/.gitkeep
Expand Down
36 changes: 4 additions & 32 deletions Evaluation/scripts/analyze_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
import json
import math
import statistics
import sys
from pathlib import Path

import numpy as np

sys.path.insert(0, str(Path(__file__).parent))
from eval_stats import cohens_d
import pandas as pd

REPO_ROOT = Path(__file__).resolve().parent.parent.parent
Expand All @@ -39,38 +43,6 @@ def bootstrap_ci(data, confidence=0.95, n_boot=10000):
return (lo, hi)


def cohens_d(a, b):
"""Cohen's d effect size (pooled SD)."""
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
if len(a) < 2 or len(b) < 2:
return float("nan")
# Degrees-of-freedom-weighted pooled variance. The unweighted
# (var_a + var_b) / 2 form coincides with this only when the groups are the
# same size, and this function accepts arbitrary lengths — so every
# unequal-n caller was dividing by the wrong denominator.
n_a, n_b = len(a), len(b)
pooled_variance = (
(n_a - 1) * a.var(ddof=1) + (n_b - 1) * b.var(ddof=1)
) / (n_a + n_b - 2)
pooled_std = math.sqrt(pooled_variance)
mean_difference = a.mean() - b.mean()
if pooled_std == 0:
# PROJECT POLICY, not a universal definition. With both samples constant
# the denominator is zero and Cohen's d is strictly undefined. We report
# the limiting value — signed infinity — because 0.0 claimed "no effect"
# for the most extreme separation representable, and nan would discard
# the direction. Identical constants are the one genuinely zero case.
#
# Callers that PERSIST this must convert at the boundary: JSON has no
# Infinity literal (`json.dumps` emits a bare `Infinity`, which strict
# parsers reject) and CSV has no convention at all. Decide on a
# representation before writing it, rather than assuming the reader copes.
if mean_difference == 0:
return 0.0
return float("inf") if mean_difference > 0 else float("-inf")
return float(mean_difference / pooled_std)


def safe_mean(values):
return float(np.nanmean(values)) if len(values) > 0 else float("nan")

Expand Down
34 changes: 3 additions & 31 deletions Evaluation/scripts/embedding_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

import numpy as np
from scipy import stats as sp_stats

sys.path.insert(0, str(Path(__file__).parent))
from eval_stats import cohens_d
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

Expand All @@ -50,37 +53,6 @@ def bootstrap_ci(data, confidence=0.95, n_boot=10000):
return (lo, hi, len(data))


def cohens_d(a, b):
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
if len(a) < 2 or len(b) < 2:
return float("nan")
# Degrees-of-freedom-weighted pooled variance. The unweighted
# (var_a + var_b) / 2 form coincides with this only when the groups are the
# same size, and this function accepts arbitrary lengths — so every
# unequal-n caller was dividing by the wrong denominator.
n_a, n_b = len(a), len(b)
pooled_variance = (
(n_a - 1) * a.var(ddof=1) + (n_b - 1) * b.var(ddof=1)
) / (n_a + n_b - 2)
pooled_std = math.sqrt(pooled_variance)
mean_difference = a.mean() - b.mean()
if pooled_std == 0:
# PROJECT POLICY, not a universal definition. With both samples constant
# the denominator is zero and Cohen's d is strictly undefined. We report
# the limiting value — signed infinity — because 0.0 claimed "no effect"
# for the most extreme separation representable, and nan would discard
# the direction. Identical constants are the one genuinely zero case.
#
# Callers that PERSIST this must convert at the boundary: JSON has no
# Infinity literal (`json.dumps` emits a bare `Infinity`, which strict
# parsers reject) and CSV has no convention at all. Decide on a
# representation before writing it, rather than assuming the reader copes.
if mean_difference == 0:
return 0.0
return float("inf") if mean_difference > 0 else float("-inf")
return float(mean_difference / pooled_std)


# Above this per-group size the exact Mann-Whitney null distribution is too
# expensive to enumerate, so the asymptotic approximation is used. Stated here
# rather than inherited from SciPy's `auto` threshold, which is a library
Expand Down
7 changes: 7 additions & 0 deletions Evaluation/scripts/embedding_space_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@


def cka(X, Y):
"""Biased linear CKA over centered Gram matrices.

Null values can be substantially inflated when feature dimension approaches
or exceeds sample count — independent Gaussians at n=50, d=128/256 score
~0.78, not ~0. Do not compare scores across different n/d regimes without a
matched null or permutation baseline.
"""
X = np.array(X, dtype=np.float64)
Y = np.array(Y, dtype=np.float64)
X = X - X.mean(axis=0)
Expand Down
7 changes: 6 additions & 1 deletion Evaluation/scripts/eval_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ def bootstrap_ci(data, confidence=0.95, n_boot=10000):


def cohens_d(a, b):
"""Cohen's d effect size between two samples."""
"""Cohen's d effect size between two samples.

Returns NaN where d is undefined rather than a finite value that would rank
alongside real effects: under-sized samples, and zero pooled variance with
separated means. Only equal constant groups are a true zero.
"""
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
if len(a) < 2 or len(b) < 2:
return float("nan")
Expand Down
35 changes: 3 additions & 32 deletions Evaluation/scripts/statistical_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@

import numpy as np
from scipy import stats as sp_stats

sys.path.insert(0, str(Path(__file__).parent))
from eval_stats import cohens_d
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

Expand All @@ -55,38 +58,6 @@ def bootstrap_ci(data, confidence=0.95, n_boot=10000):
return (lo, hi, len(data))


def cohens_d(a, b):
"""Cohen's d effect size with pooled SD."""
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
if len(a) < 2 or len(b) < 2:
return float("nan")
# Degrees-of-freedom-weighted pooled variance. The unweighted
# (var_a + var_b) / 2 form coincides with this only when the groups are the
# same size, and this function accepts arbitrary lengths — so every
# unequal-n caller was dividing by the wrong denominator.
n_a, n_b = len(a), len(b)
pooled_variance = (
(n_a - 1) * a.var(ddof=1) + (n_b - 1) * b.var(ddof=1)
) / (n_a + n_b - 2)
pooled_std = math.sqrt(pooled_variance)
mean_difference = a.mean() - b.mean()
if pooled_std == 0:
# PROJECT POLICY, not a universal definition. With both samples constant
# the denominator is zero and Cohen's d is strictly undefined. We report
# the limiting value — signed infinity — because 0.0 claimed "no effect"
# for the most extreme separation representable, and nan would discard
# the direction. Identical constants are the one genuinely zero case.
#
# Callers that PERSIST this must convert at the boundary: JSON has no
# Infinity literal (`json.dumps` emits a bare `Infinity`, which strict
# parsers reject) and CSV has no convention at all. Decide on a
# representation before writing it, rather than assuming the reader copes.
if mean_difference == 0:
return 0.0
return float("inf") if mean_difference > 0 else float("-inf")
return float(mean_difference / pooled_std)


# Above this per-group size the exact Mann-Whitney null distribution is too
# expensive to enumerate, so the asymptotic approximation is used. Stated here
# rather than inherited from SciPy's `auto` threshold, which is a library
Expand Down
Loading
Loading