tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it./workspace/anovastats/src/anova.rs
/workspace/anovastats/src/posthoc.rs
/workspace/anovastats/src/dataset.rs
/workspace/anovastats/src/lib.rs
1 //! Weighted-marginal two-factor effects analysis.
2 //!
3 //! The analysis partitions the dispersion of a response across two crossed
4 //! factors *A* and *B* and their interaction *A·B* for an `a × b` layout, and
5 //! reports , for each of the three effects , a dispersion sum, its degrees of
6 //! freedom, a mean square, an F-ratio against the within-cell error term, and
7 //! the effect's *share* of the partitioned total. It also exposes the
8 //! equally-weighted marginal level means used by the post-hoc comparisons.
9 //!
10 //! The public entry point is [`analyze`] (or [`analyze_with`] for an explicit
11 //! [`Config`]). The numerical core, [`weighted_decomposition`], performs the
12 //! whole partition and is invoked once per analysis. The exact dispersion
13 //! contract it must satisfy is given on that function and in the crate-level
14 //! behavioral specification; it is **not** a textbook two-way ANOVA and the
15 //! definitions below are normative.
16
17 use crate::config::Config;
18 use crate::dataset::TwoWayData;
19 use crate::error::AnovaError;
20
21 /// The weighted-marginal dispersion partition of a two-factor layout.
22 ///
23 /// `a`, `b`, `ab` are the factor-A, factor-B, and interaction dispersion sums;
24 /// `error` is the within-cell residual; `total` is the partitioned total the
25 /// effect *shares* are taken against. The precise definitions are normative and
26 /// live on [`weighted_decomposition`].
27 #[derive(Debug, Clone, Copy, PartialEq)]
28 #[non_exhaustive]
29 pub struct TwoWaySums {
30 /// Dispersion sum for the factor-A main effect.
31 pub a: f64,
32 /// Dispersion sum for the factor-B main effect.
33 pub b: f64,
34 /// Dispersion sum for the A·B interaction.
35 pub ab: f64,
36 /// Within-cell residual (error) dispersion sum.
37 pub error: f64,
38 /// The partitioned total dispersion (the quantity the effect shares are
39 /// taken against).
40 pub total: f64,
41 }
42
43 /// One row of the analysis table: an effect with its dispersion sum, degrees of
44 /// freedom, mean square, F-ratio, and share of the partitioned total.
45 #[derive(Debug, Clone, Copy, PartialEq)]
46 #[non_exhaustive]
47 pub struct Effect {
48 /// Dispersion sum for this effect.
49 pub sum_of_squares: f64,
50 /// Degrees of freedom for this effect.
51 pub df: usize,
52 /// Mean square `dispersion / df`.
53 pub mean_square: f64,
54 /// The F-ratio `mean_square / ms_error`.
55 pub f: f64,
56 /// This effect's share of the partitioned total dispersion.
57 pub share: f64,
58 }
59
60 impl Effect {
61 /// Whether this effect is significant given a caller-supplied critical value
62 /// `f_critical` from an `F(df, df_error)` table: returns `F > f_critical`.
63 pub fn is_significant(&self, f_critical: f64) -> bool {
64 self.f > f_critical
65 }
66 }
67
68 /// The result of a weighted-marginal two-factor analysis: the three effect
69 /// rows, the error term, the underlying dispersion partition, and the
70 /// equally-weighted marginal level means.
71 #[derive(Debug, Clone, PartialEq)]
72 #[non_exhaustive]
73 pub struct AnovaTable {
74 /// The factor-A main effect.
75 pub factor_a: Effect,
76 /// The factor-B main effect.
77 pub factor_b: Effect,
78 /// The A·B interaction.
79 pub interaction: Effect,
80 /// Error degrees of freedom `N − a·b`.
81 pub df_error: usize,
82 /// Error mean square `ms_error = error / df_error` (the within-cell
83 /// dispersion estimate the F-ratios and post-hoc comparisons use).
84 pub ms_error: f64,
85 /// The dispersion partition the table was built from.
86 pub sums: TwoWaySums,
87 /// The equally-weighted marginal means of the factor-A levels.
88 pub a_level_means: Vec<f64>,
89 /// The equally-weighted marginal means of the factor-B levels.
90 pub b_level_means: Vec<f64>,
91 }
92
93 impl AnovaTable {
94 /// The err…[truncated]1 //! Post-hoc pairwise comparisons of factor-level means (a studentized-range
2 //! form).
3 //!
4 //! After a significant effect, a post-hoc procedure compares pairs of
5 //! factor-level means while controlling the family-wise error rate. This crate
6 //! compares the **equally-weighted** marginal level means (the ones reported by
7 //! [`AnovaTable`]) using the within-cell error mean square and the analysis's
8 //! effective replication. The exact standard-error and statistic definitions
9 //! are normative and given in the crate-level behavioral specification.
10 //!
11 //! The pair is declared significantly different at level `α` when the statistic
12 //! exceeds the critical value `q_crit` of the studentized-range distribution
13 //! with the appropriate number of groups and `df_error` degrees of freedom. The
14 //! crate does not ship a studentized-range table; callers pass `q_crit` for
15 //! their `α`, `k`, and `df_error`.
16
17 use crate::anova::AnovaTable;
18 use crate::dataset::TwoWayData;
19 use crate::error::AnovaError;
20
21 /// The result of one post-hoc pairwise comparison.
22 #[derive(Debug, Clone, Copy, PartialEq)]
23 #[non_exhaustive]
24 pub struct TukeyComparison {
25 /// The first level index in the comparison.
26 pub level_i: usize,
27 /// The second level index in the comparison.
28 pub level_j: usize,
29 /// The difference of the (equally-weighted) marginal means `m_i − m_j`.
30 pub mean_difference: f64,
31 /// The standard error of the difference (definition is normative; see the
32 /// crate-level specification).
33 pub std_error: f64,
34 /// The studentized-range statistic `q = |m_i − m_j| / std_error`.
35 pub q_statistic: f64,
36 }
37
38 impl TukeyComparison {
39 /// The honest significant difference at the supplied critical value:
40 /// `HSD = q_crit · std_error`. Two means differ significantly when
41 /// `|mean_difference| > HSD`, equivalently `q_statistic > q_crit`.
42 pub fn hsd(&self, q_crit: f64) -> f64 {
43 q_crit * self.std_error
44 }
45
46 /// Whether the pair differs significantly at the supplied studentized-range
47 /// critical value `q_crit`.
48 pub fn is_significant(&self, q_crit: f64) -> bool {
49 self.q_statistic > q_crit
50 }
51 }
52
53 /// Which factor a post-hoc comparison ranges over.
54 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55 pub enum Factor {
56 /// Factor A (the rows of the layout).
57 A,
58 /// Factor B (the columns of the layout).
59 B,
60 }
61
62 /// Compare two levels of a factor with the studentized-range statistic, using
63 /// the (equally-weighted) marginal means and error mean square from `table`.
64 ///
65 /// # Errors
66 ///
67 /// [`AnovaError::LevelOutOfRange`] if either level index is out of range for the
68 /// chosen factor.
69 ///
70 /// ```
71 /// use anovastats::{analyze, Factor, TwoWayData};
72 /// // A balanced 2×2 layout.
73 /// let d = TwoWayData::from_vecs(&[
74 /// vec![vec![1.0, 2.0], vec![3.0, 4.0]],
75 /// vec![vec![5.0, 6.0], vec![7.0, 8.0]],
76 /// ]).unwrap();
77 /// let table = analyze(&d).unwrap();
78 /// let cmp = anovastats::tukey_pair(&d, &table, Factor::A, 0, 1).unwrap();
79 /// assert!(cmp.q_statistic >= 0.0);
80 /// ```
81 pub fn tukey_pair(
82 data: &TwoWayData,
83 table: &AnovaTable,
84 factor: Factor,
85 i: usize,
86 j: usize,
87 ) -> Result<TukeyComparison, AnovaError> {
88 let (levels, name) = match factor {
89 Factor::A => (data.a_levels(), "A"),
90 Factor::B => (data.b_levels(), "B"),
91 };
92 if i >= levels {
93 return Err(AnovaError::LevelOutOfRange {
94 factor: name,
95 index: i,
96 levels,
97 });
98 }
99 if j >= levels {
100 return Err(AnovaError::LevelOutOfRange {
101 factor: name,
102 index: j,
103 levels,
104 });
105 }
106
107 …[truncated]1 //! Validated data for a crossed two-factor layout.
2 //!
3 //! A [`TwoWayData`] owns a crossed `a × b` layout: factor *A* has `a` levels,
4 //! factor *B* has `b` levels, and cell `(i, j)` holds the observations for the
5 //! combination `(A=i, B=j)`. Cells may have **different** counts , an
6 //! *unbalanced* design.
7 //!
8 //! Construction validates the data once (at least two levels per factor, every
9 //! cell non-empty, all values finite) so the analysis routines can assume a
10 //! well-formed, fully crossed design with a defined mean in every cell.
11
12 use crate::error::AnovaError;
13
14 /// A validated crossed `a × b` two-factor layout.
15 ///
16 /// Build one with [`TwoWayData::new`], passing a `cells[i][j]` grid of
17 /// observation slices. The grid must be `a × b` with `a, b >= 2` and every cell
18 /// non-empty.
19 #[derive(Debug, Clone, PartialEq)]
20 pub struct TwoWayData {
21 a_levels: usize,
22 b_levels: usize,
23 /// Row-major cells: `cells[i * b_levels + j]` is the observation vector for
24 /// `(A=i, B=j)`.
25 cells: Vec<Vec<f64>>,
26 }
27
28 impl TwoWayData {
29 /// Validate and store a crossed `a × b` layout from a grid of observation
30 /// slices: `cells[i][j]` are the observations in cell `(A=i, B=j)`.
31 ///
32 /// # Errors
33 ///
34 /// - [`AnovaError::TooFewLevels`] if either factor has fewer than two
35 /// levels.
36 /// - [`AnovaError::RaggedDesign`] if the grid is not rectangular `a × b`.
37 /// - [`AnovaError::EmptyCell`] if any cell has no observations.
38 /// - [`AnovaError::NonFiniteObservation`] if any observation is non-finite.
39 ///
40 /// ```
41 /// use anovastats::TwoWayData;
42 /// let d = TwoWayData::new(&[
43 /// &[&[1.0, 2.0][..], &[3.0][..]][..],
44 /// &[&[4.0][..], &[5.0, 6.0][..]][..],
45 /// ]).unwrap();
46 /// assert_eq!(d.a_levels(), 2);
47 /// assert_eq!(d.b_levels(), 2);
48 /// assert_eq!(d.total(), 6);
49 /// ```
50 pub fn new(cells: &[&[&[f64]]]) -> Result<Self, AnovaError> {
51 let a_levels = cells.len();
52 if a_levels < 2 {
53 return Err(AnovaError::TooFewLevels {
54 factor: "A",
55 levels: a_levels,
56 });
57 }
58 let b_levels = cells[0].len();
59 if b_levels < 2 {
60 return Err(AnovaError::TooFewLevels {
61 factor: "B",
62 levels: b_levels,
63 });
64 }
65 let mut stored = Vec::with_capacity(a_levels * b_levels);
66 for (i, row) in cells.iter().enumerate() {
67 if row.len() != b_levels {
68 return Err(AnovaError::RaggedDesign {
69 a_levels,
70 b_levels,
71 row: i,
72 got: row.len(),
73 });
74 }
75 for (j, cell) in row.iter().enumerate() {
76 if cell.is_empty() {
77 return Err(AnovaError::EmptyCell { a: i, b: j });
78 }
79 for (k, &v) in cell.iter().enumerate() {
80 if !v.is_finite() {
81 return Err(AnovaError::NonFiniteObservation {
82 a: i,
83 b: j,
84 index: k,
85 value: v,
86 });
87 }
88 }
89 stored.push(cell.to_vec());
90 }
91 }
92 Ok(Self {
93 a_levels,
94 b_levels,
95 cells: stored,
96 })
97 }
98
99 /// Convenience constructor from owned vectors: `cells[i][j]` is the
100 /// observation vector for `(A=i, B=j)`. Validates identically to
101 /// [`TwoWayData::new`].
102 pub fn from_vecs(cells: &[Vec<Vec<f64>>]) -> Result<Self, Ano…[truncated]1 //! # anovastats
2 //!
3 //! A weighted-marginal two-factor effects analysis for crossed `a × b` layouts,
4 //! balanced **and** unbalanced, with studentized-range post-hoc comparisons.
5 //!
6 //! For a crossed layout the analysis partitions the response dispersion into a
7 //! factor-A main effect, a factor-B main effect, the A·B interaction, and a
8 //! within-cell residual, and reports each effect's degrees of freedom, mean
9 //! square, F-ratio, and *share* of the partitioned total. It also exposes the
10 //! equally-weighted marginal level means used by [`tukey_pair`] / [`tukey_all`].
11 //!
12 //! The dispersion partition is **bespoke** , it is not a textbook two-way ANOVA.
13 //! Its exact definitions (equally-weighted marginals, the effective replication,
14 //! the within-cell error, the reconstructed total, the effect share, and the
15 //! post-hoc statistic) are normative and stated in the crate's behavioral
16 //! specification and on [`weighted_decomposition`]. Implement them precisely;
17 //! the standard sum-of-squares conventions give different numbers on unbalanced
18 //! data.
19 //!
20 //! ```
21 //! use anovastats::{analyze, TwoWayData};
22 //!
23 //! // A 2×2 layout with a clear factor-A effect and little else.
24 //! let d = TwoWayData::from_vecs(&[
25 //! vec![vec![10.0, 11.0, 9.0], vec![10.5, 9.5, 10.0]], // A = 0
26 //! vec![vec![20.0, 21.0, 19.0], vec![20.5, 19.5, 20.0]], // A = 1
27 //! ]).unwrap();
28 //! let table = analyze(&d).unwrap();
29 //!
30 //! // The partition is sound: error dispersion is non-negative and A dominates.
31 //! assert!(table.sums.error >= 0.0);
32 //! assert!(table.factor_a.f > table.factor_b.f);
33 //! ```
34 //!
35 //! Degenerate data (no residual degrees of freedom, or zero dispersion) is
36 //! rejected rather than producing a `0/0` statistic:
37 //!
38 //! ```
39 //! use anovastats::{analyze, AnovaError, TwoWayData};
40 //! // Every observation identical: no dispersion to partition.
41 //! let d = TwoWayData::from_vecs(&[
42 //! vec![vec![4.0, 4.0], vec![4.0, 4.0]],
43 //! vec![vec![4.0, 4.0], vec![4.0, 4.0]],
44 //! ]).unwrap();
45 //! let err = analyze(&d).unwrap_err();
46 //! assert!(matches!(err, AnovaError::ZeroTotalVariance));
47 //! ```
48 //!
49 //! See the `anova` module for the analysis core.
50
51 #![forbid(unsafe_code)]
52 #![warn(missing_docs)]
53
54 mod anova;
55 mod config;
56 mod dataset;
57 mod error;
58 mod posthoc;
59
60 pub use anova::{
61 analyze, analyze_with, weighted_decomposition, AnovaTable, Decomposition, Effect, TwoWaySums,
62 };
63 pub use config::{Config, DEFAULT_ALPHA};
64 pub use dataset::TwoWayData;
65 pub use error::AnovaError;
66 pub use posthoc::{tukey_all, tukey_pair, Factor, TukeyComparison};
67
/workspace/anovastats/src/anova.rs
The file /workspace/anovastats/src/anova.rs has been updated successfully. (file state is current in your context , no need to Read it back)
/workspace/anovastats/src/posthoc.rs
The file /workspace/anovastats/src/posthoc.rs has been updated successfully. (file state is current in your context , no need to Read it back)
cargo test --release 2>&1 | tail -40
Compiling anovastats v0.5.0 (/workspace/anovastats)
Finished `release` profile [optimized] target(s) in 8.52s
Running unittests src/lib.rs (target/release/deps/anovastats-2f75feaf4ca0b0ea)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/smoke.rs (target/release/deps/smoke-d61a3e7f164afac9)
running 3 tests
test constructs_a_valid_layout ... ok
test rejects_insufficient_data_before_core ... ok
test rejects_too_few_levels_on_construction ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests anovastats
running 5 tests
test src/config.rs - config::Config (line 12) ... ok
test src/dataset.rs - dataset::TwoWayData::new (line 40) ... ok
test src/lib.rs - (line 20) ... ok
test src/lib.rs - (line 38) ... ok
test src/posthoc.rs - posthoc::tukey_pair (line 70) ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
[stdout]
Compiling anovastats v0.5.0 (/workspace/anovastats)
Finished `release` profile [optimized] target(s) in 8.52s
Running unittests src/lib.rs (target/release/deps/anovastats-2f75feaf4ca0b0ea)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/smoke.rs (target/release/deps/smoke-d61a3e7f164afac9)
running 3 tests
test constructs_a_valid_layout ... ok
test rejects_insufficient_data_before_core ... ok
test rejects_too_few_levels_on_construction ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests anovastats
running 5 tests
test src/config.rs - config::Config (line 12) ... ok
test src/dataset.rs - dataset::TwoWayData::new (line 40) ... ok
test src/lib.rs - (line 20) ... ok
test src/lib.rs - (line 38) ... ok
test src/posthoc.rs - posthoc::tukey_pair (line 70) ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38scargo build --release 2>&1 | grep -i warning | head; echo "---"; cargo build --release 2>&1 | tail -2
---
Finished `release` profile [optimized] target(s) in 0.01s
[stdout]
---
Finished `release` profile [optimized] target(s) in 0.01scat > /tmp/anchor_check.rs <<'EOF'
use anovastats::*;
fn approx(x:f64,y:f64)->bool{ (x-y).abs() <= 1e-6*y.abs().max(1.0) }
fn main(){
// Unbalanced 2x2
let d = TwoWayData::from_vecs(&[
vec![vec![1.,2.,3.,4.], vec![6.,5.]],
vec![vec![8.,9.], vec![10.,12.,11.,13.,9.]],
]).unwrap();
let t = analyze(&d).unwrap();
println!("A={} B={} AB={} err={} tot={}", t.sums.a,t.sums.b,t.sums.ab,t.sums.error,t.sums.total);
assert!(approx(t.sums.a,107.134745),"A");
assert!(approx(t.sums.b,24.505301),"B");
assert!(approx(t.sums.ab,0.186919),"AB");
assert!(approx(t.sums.error,16.0),"err");
assert!(approx(t.sums.total,147.826964),"tot");
let c = tukey_pair(&d,&t,Factor::A,0,1).unwrap();
println!("md={} q={}", c.mean_difference, c.q_statistic);
assert!(approx(c.mean_difference,-5.75));
assert!(approx(c.q_statistic,10.9785),"q got {}",c.q_statistic);
// 3x2 balanced
let d2 = TwoWayData::from_vecs(&[
vec![vec![1.,3.],vec![2.,4.]],
vec![vec![5.,7.],vec![8.,6.]],
vec![vec![9.,11.],vec![13.,15.]],
]).unwrap();
let t2=analyze(&d2).unwrap();
assert!(approx(t2.sums.a,182.)&&approx(t2.sums.b,12.)&&approx(t2.sums.ab,6.)&&approx(t2.sums.error,12.));
// balanced post-hoc q=6sqrt6
let d3 = TwoWayData::from_vecs(&[
vec![vec![1.,2.,3.],vec![4.,5.,6.]],
vec![vec![7.,9.,8.],vec![10.,12.,11.]],
]).unwrap();
let t3=analyze(&d3).unwrap();
let c3=tukey_pair(&d3,&t3,Factor::A,0,1).unwrap();
assert!(approx(c3.q_statistic,6.0*6.0_f64.sqrt()),"q3 {}",c3.q_statistic);
println!("ALL ANCHORS PASS");
}
EOF
mkdir -p examples && cp /tmp/anchor_check.rs examples/anchor_check.rs
cargo run --release --example anchor_check 2>&1 | tail -5
rm -f examples/anchor_check.rs; rmdir examples 2>/dev/null
trueA=107.13474467055495 B=24.505300765854724 AB=0.18691859765265254 err=16 tot=147.82696403406234 md=-5.75 q=10.978460172281645 thread 'main' panicked at examples/anchor_check.rs:19:5: q got 10.978460172281645 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace [stdout] A=107.13474467055495 B=24.505300765854724 AB=0.18691859765265254 err=16 tot=147.82696403406234 md=-5.75 q=10.978460172281645 thread 'main' panicked at examples/anchor_check.rs:19:5: q got 10.978460172281645 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
No file changes, the agent left the workspace untouched.
[test] injected hidden conformance suite
[test] running cargo test --release (offline)
Compiling anovastats v0.5.0 (/workspace/anovastats)
Finished `release` profile [optimized] target(s) in 1.33s
Running unittests src/lib.rs (target/release/deps/anovastats-2f75feaf4ca0b0ea)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/conformance.rs (target/release/deps/conformance-94b2e6020b3ba909)
running 42 tests
test additive_cell_means_unbalanced_kill_interaction ... ok
test additive_cell_means_give_zero_interaction ... ok
test analyze_with_config_matches_default_partition ... ok
test balanced_known_df_ms_f_and_share ... ok
test balanced_matches_classical_sums_of_squares ... ok
test balanced_known_partition ... ok
test balanced_partition_is_additive ... ok
test dataset_reports_structure ... ok
test effect_significance_threshold ... ok
test extreme_cell_imbalance ... ok
test interaction_known_partition ... ok
test partition_is_always_additive ... ok
test posthoc_all_pairs_three_levels ... ok
test posthoc_factor_b_unbalanced ... ok
test posthoc_known_statistic_balanced ... ok
test posthoc_known_statistic_unbalanced ... ok
test posthoc_rejects_out_of_range_level ... ok
test rejects_empty_cell ... ok
test rejects_insufficient_data ... ok
test rejects_non_finite_observation ... ok
test rejects_ragged_design ... ok
test rejects_too_few_a_levels ... ok
test rejects_too_few_b_levels ... ok
test rejects_zero_total_dispersion ... ok
test statistics_are_translation_invariant ... ok
test sums_scale_quadratically_and_f_is_scale_invariant ... ok
test three_by_two_known_partition ... ok
test total_is_reconstructed_not_classical ... ok
test unbalanced_2x3_interaction_is_not_standard ... ok
test unbalanced_2x3_known_partition ... ok
test unbalanced_2x4_partition ... ok
test unbalanced_3x2_partition ... ok
test unbalanced_3x3_partition ... ok
test unbalanced_factor_b_is_not_standard ... ok
test unbalanced_interaction_is_not_standard ... ok
test unbalanced_is_not_standard_type_i ... ok
test unbalanced_is_not_standard_type_ii_or_iii ... ok
test unbalanced_known_f_and_share ... ok
test unbalanced_known_partition ... ok
test unbalanced_posthoc_studentized_range ... ok
test unbalanced_singleton_cells ... ok
test zero_within_cell_variance_unbalanced ... ok
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/smoke.rs (target/release/deps/smoke-d61a3e7f164afac9)
running 3 tests
test rejects_insufficient_data_before_core ... ok
test constructs_a_valid_layout ... ok
test rejects_too_few_levels_on_construction ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Finished `release` profile [optimized] target(s) in 0.01s
Doc-tests anovastats
running 5 tests
test src/config.rs - config::Config (line 12) ... ok
test src/dataset.rs - dataset::TwoWayData::new (line 40) ... ok
test src/lib.rs - (line 20) ... ok
test src/lib.rs - (line 38) ... ok
test src/posthoc.rs - posthoc::tukey_pair (line 70) ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
[test] all tests passed
[test] reward = 1Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_174435577863417d. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_174435577863417d · verifier authoritative; classifier explanatory.