1010//! - Scientific reporting functions
1111
1212const std = @import ("std" );
13- const sacred_stats = @import ("../temple/sacred_statistics.zig" );
14- const sacred_funcs = @import ("../temple/sacred_functions.zig" );
13+
14+ // ═══════════════════════════════════════════════════════════════════════════
15+ // SACRED CONSTANTS (inline for HSLM module independence)
16+ // ═══════════════════════════════════════════════════════════════════════════
17+
18+ const PHI : f64 = 1.618033988749895 ;
19+ const PHI_INV : f64 = 1.0 / PHI ; // ≈ 0.618
20+ const PHI_INV_SQ : f64 = PHI_INV * PHI_INV ; // ≈ 0.382
21+ const PHI_INV_CUBED : f64 = PHI_INV * PHI_INV * PHI_INV ; // ≈ 0.236
22+
23+ // ═══════════════════════════════════════════════════════════════════════════
24+ // STATISTICAL FUNCTIONS (minimal subset for HSLM)
25+ // ═══════════════════════════════════════════════════════════════════════════
26+
27+ /// Calculate mean of slice
28+ fn mean (data : []const f64 ) f64 {
29+ if (data .len == 0 ) return 0.0 ;
30+ var sum : f64 = 0.0 ;
31+ for (data ) | x | sum += x ;
32+ return sum / @as (f64 , @floatFromInt (data .len ));
33+ }
34+
35+ /// Calculate variance (sample)
36+ fn variance (data : []const f64 ) f64 {
37+ if (data .len < 2 ) return 0.0 ;
38+ const m = mean (data );
39+ var sum_sq : f64 = 0.0 ;
40+ for (data ) | x | {
41+ const diff = x - m ;
42+ sum_sq += diff * diff ;
43+ }
44+ return sum_sq / @as (f64 , @floatFromInt (data .len - 1 ));
45+ }
46+
47+ /// Calculate standard deviation
48+ fn stdDev (data : []const f64 ) f64 {
49+ return std .math .sqrt (variance (data ));
50+ }
51+
52+ /// Calculate standard error
53+ fn stdError (data : []const f64 ) f64 {
54+ if (data .len == 0 ) return 0.0 ;
55+ return stdDev (data ) / std .math .sqrt (@as (f64 , @floatFromInt (data .len )));
56+ }
57+
58+ /// Confidence level options
59+ const ConfidenceLevel = enum (u8 ) {
60+ c95 ,
61+ c99 ,
62+
63+ fn zScore (self : ConfidenceLevel ) f64 {
64+ return switch (self ) {
65+ .c95 = > 1.960 ,
66+ .c99 = > 2.576 ,
67+ };
68+ }
69+ };
70+
71+ /// Confidence interval result
72+ const ConfidenceInterval = struct {
73+ mean : f64 ,
74+ lower : f64 ,
75+ upper : f64 ,
76+
77+ fn format (self : * const ConfidenceInterval , allocator : std.mem.Allocator ) ! []u8 {
78+ return std .fmt .allocPrint (allocator , "[{d:.1}, {d:.1}]" , .{ self .lower , self .upper });
79+ }
80+ };
81+
82+ /// Calculate confidence interval
83+ fn confidenceInterval (data : []const f64 , level : ConfidenceLevel ) ConfidenceInterval {
84+ if (data .len == 0 ) return .{ .mean = 0 , .lower = 0 , .upper = 0 };
85+
86+ const m = mean (data );
87+ const se = stdError (data );
88+ const margin = se * level .zScore ();
89+
90+ return .{
91+ .mean = m ,
92+ .lower = m - margin ,
93+ .upper = m + margin ,
94+ };
95+ }
96+
97+ /// Welch's t-test result
98+ const WelchTestResult = struct {
99+ t_statistic : f64 ,
100+ p_value : f64 ,
101+ significant : bool ,
102+ };
103+
104+ /// Perform Welch's t-test
105+ fn welchTTest (sample1 : []const f64 , sample2 : []const f64 , alpha : f64 ) WelchTestResult {
106+ if (sample1 .len < 2 or sample2 .len < 2 ) {
107+ return .{ .t_statistic = 0 , .p_value = 1 , .significant = false };
108+ }
109+
110+ const m1 = mean (sample1 );
111+ const m2 = mean (sample2 );
112+ const v1 = variance (sample1 );
113+ const v2 = variance (sample2 );
114+ const n1 : f64 = @floatFromInt (sample1 .len );
115+ const n2 : f64 = @floatFromInt (sample2 .len );
116+
117+ const t_stat = (m1 - m2 ) / std .math .sqrt (v1 / n1 + v2 / n2 );
118+
119+ // Approximate p-value
120+ const abs_t = if (t_stat < 0 ) - t_stat else t_stat ;
121+ const p_value = 2.0 * (1.0 - gaussianCDF (abs_t ));
122+
123+ return .{
124+ .t_statistic = t_stat ,
125+ .p_value = p_value ,
126+ .significant = p_value < alpha ,
127+ };
128+ }
129+
130+ fn gaussianCDF (x : f64 ) f64 {
131+ const a1 : f64 = 0.254829592 ;
132+ const a2 : f64 = -0.284496736 ;
133+ const a3 : f64 = 1.421413741 ;
134+ const a4 : f64 = -1.453152027 ;
135+ const a5 : f64 = 1.061405429 ;
136+ const p : f64 = 0.3275911 ;
137+
138+ const sign : f64 = if (x < 0 ) -1.0 else 1.0 ;
139+ const a = @abs (x );
140+
141+ const t = 1.0 / (1.0 + p * a );
142+ const y = 1.0 - (((((a5 * t + a4 ) * t ) + a3 ) * t + a2 ) * t + a1 ) * t * std .math .exp (- a * a );
143+
144+ return sign * y ;
145+ }
146+
147+ /// Cohen's d effect size
148+ const EffectSize = struct {
149+ cohens_d : f64 ,
150+
151+ fn getInterpretation (self : * const EffectSize ) []const u8 {
152+ const abs_d = if (self .cohens_d < 0 ) - self .cohens_d else self .cohens_d ;
153+ if (abs_d < 0.2 ) return "negligible" ;
154+ if (abs_d < 0.5 ) return "small" ;
155+ if (abs_d < 0.8 ) return "medium" ;
156+ return "large" ;
157+ }
158+ };
159+
160+ fn cohensD (sample1 : []const f64 , sample2 : []const f64 ) EffectSize {
161+ if (sample1 .len == 0 or sample2 .len == 0 ) return .{ .cohens_d = 0 };
162+
163+ const m1 = mean (sample1 );
164+ const m2 = mean (sample2 );
165+ const v1 = variance (sample1 );
166+ const v2 = variance (sample2 );
167+ const n1 : f64 = @floatFromInt (sample1 .len );
168+ const n2 : f64 = @floatFromInt (sample2 .len );
169+
170+ const pooled_var = ((n1 - 1.0 ) * v1 + (n2 - 1.0 ) * v2 ) / (n1 + n2 - 2.0 );
171+ const pooled_sd = std .math .sqrt (pooled_var );
172+
173+ const d = if (pooled_sd > 0 ) (m1 - m2 ) / pooled_sd else 0.0 ;
174+ return .{ .cohens_d = d };
175+ }
15176
16177// ═══════════════════════════════════════════════════════════════════════════
17178// HSLM TRAINING CONFIG WITH SACRED SCHEDULES
@@ -103,25 +264,25 @@ pub fn getLR(schedule: LRSchedule, config: HSLMConfig, step: u32) f64 {
103264 .linear = > {
104265 const progress : f64 = @as (f64 , @floatFromInt (step )) /
105266 @as (f64 , @floatFromInt (config .total_steps ));
106- config .lr_peak * (1.0 - progress ) + config .lr_min * progress ;
267+ return config .lr_peak * (1.0 - progress ) + config .lr_min * progress ;
107268 },
108269
109270 .cosine = > {
110271 const progress : f64 = @as (f64 , @floatFromInt (step )) /
111272 @as (f64 , @floatFromInt (config .total_steps ));
112273 const cosine = 0.5 * (1.0 + std .math .cos (std .math .pi * progress ));
113- config .lr_peak * cosine + config .lr_min * (1.0 - cosine );
274+ return config .lr_peak * cosine + config .lr_min * (1.0 - cosine );
114275 },
115276
116- .sacred = > config .lr_peak * sacred_funcs . PHI_INV *
117- std .math .pow (f64 , sacred_funcs . PHI , - @as (f64 , @floatFromInt (step )) /
118- @as (f64 , @floatFromInt (config .total_steps )) / sacred_funcs . PHI ),
277+ .sacred = > config .lr_peak * PHI_INV *
278+ std .math .pow (f64 , PHI , - @as (f64 , @floatFromInt (step )) /
279+ @as (f64 , @floatFromInt (config .total_steps )) / PHI ),
119280
120281 .sacred_cosine = > {
121282 const progress : f64 = @as (f64 , @floatFromInt (step )) /
122283 @as (f64 , @floatFromInt (config .total_steps ));
123- const cosine = 0.5 * (1.0 + std .math .cos (std .math .pi * progress / sacred_funcs . PHI ));
124- config .lr_peak * cosine + config .lr_min * (1.0 - cosine );
284+ const cosine = 0.5 * (1.0 + std .math .cos (std .math .pi * progress / PHI ));
285+ return config .lr_peak * cosine + config .lr_min * (1.0 - cosine );
125286 },
126287
127288 .warmup_sacred = > {
@@ -136,7 +297,7 @@ pub fn getLR(schedule: LRSchedule, config: HSLMConfig, step: u32) f64 {
136297 const decay_step = step - config .warmup_steps ;
137298 const progress : f64 = @as (f64 , @floatFromInt (decay_step )) /
138299 @as (f64 , @floatFromInt (remaining ));
139- return config .lr_peak * std .math .pow (f64 , sacred_funcs . PHI , - progress / sacred_funcs . PHI );
300+ return config .lr_peak * std .math .pow (f64 , PHI , - progress / PHI );
140301 },
141302 };
142303}
@@ -152,7 +313,7 @@ pub const StepMetrics = struct {
152313 perplexity : f64 ,
153314 lr : f64 ,
154315 tokens_per_sec : f64 ,
155- timestamp : i64 ,
316+ timestamp_ns : u64 ,
156317
157318 /// Create from loss value
158319 pub fn fromLoss (step : u32 , loss : f64 , lr : f64 ) StepMetrics {
@@ -162,7 +323,7 @@ pub const StepMetrics = struct {
162323 .perplexity = std .math .exp (loss ),
163324 .lr = lr ,
164325 .tokens_per_sec = 0.0 ,
165- .timestamp = std .time .nanoTimestamp (),
326+ .timestamp_ns = std .time .nanoTimestamp (),
166327 };
167328 }
168329};
@@ -178,17 +339,17 @@ pub const RunStatistics = struct {
178339
179340 /// Calculate mean final perplexity
180341 pub fn meanFinalPPL (self : * const RunStatistics ) f64 {
181- return sacred_stats . mean (self .final_ppls );
342+ return mean (self .final_ppls );
182343 }
183344
184345 /// Calculate std error of final perplexity
185346 pub fn stderrFinalPPL (self : * const RunStatistics ) f64 {
186- return sacred_stats . stdError (self .final_ppls );
347+ return stdError (self .final_ppls );
187348 }
188349
189350 /// Calculate CI95 for final perplexity
190- pub fn ci95FinalPPL (self : * const RunStatistics ) sacred_stats. ConfidenceInterval {
191- return sacred_stats . confidenceInterval (self .final_ppls , .c95 );
351+ pub fn ci95FinalPPL (self : * const RunStatistics ) ConfidenceInterval {
352+ return confidenceInterval (self .final_ppls , .c95 );
192353 }
193354
194355 /// Format as NeurIPS table row
@@ -236,7 +397,7 @@ pub const TrainingSession = struct {
236397 .perplexity = std .math .exp (loss ),
237398 .lr = lr ,
238399 .tokens_per_sec = tokens_per_sec ,
239- .timestamp = std .time .nanoTimestamp (),
400+ .timestamp_ns = @as ( u64 , @intCast ( std .time .nanoTimestamp ()) ),
240401 };
241402 try self .steps .append (metrics );
242403 }
@@ -297,8 +458,8 @@ pub const ComparisonResult = struct {
297458 ppl_a : f64 ,
298459 ppl_b : f64 ,
299460 improvement : f64 ,
300- welch_result : sacred_stats. WelchTestResult ,
301- effect_size : sacred_stats. EffectSize ,
461+ welch_result : WelchTestResult ,
462+ effect_size : EffectSize ,
302463 significant : bool ,
303464
304465 /// Format comparison as LaTeX table row
@@ -321,11 +482,11 @@ pub const ComparisonResult = struct {
321482
322483/// Perform statistical comparison between two methods
323484pub fn compareMethods (allocator : std.mem.Allocator , method_a : []const u8 , ppl_values_a : []const f64 , method_b : []const u8 , ppl_values_b : []const f64 , alpha : f64 ) ! ComparisonResult {
324- const mean_a = sacred_stats . mean (ppl_values_a );
325- const mean_b = sacred_stats . mean (ppl_values_b );
485+ const mean_a = mean (ppl_values_a );
486+ const mean_b = mean (ppl_values_b );
326487
327- const welch = sacred_stats . welchTTest (ppl_values_a , ppl_values_b , alpha );
328- const effect = sacred_stats . cohensD (ppl_values_a , ppl_values_b );
488+ const welch = welchTTest (ppl_values_a , ppl_values_b , alpha );
489+ const effect = cohensD (ppl_values_a , ppl_values_b );
329490
330491 return ComparisonResult {
331492 .method_a = try allocator .dupe (u8 , method_a ),
@@ -356,7 +517,7 @@ pub fn sacredCrossEntropyLoss(logits: []const f64, targets: []const u32) f64 {
356517 }
357518 }
358519
359- return - sum / sacred_funcs . PHI ;
520+ return - sum / PHI ;
360521}
361522
362523/// Calculate perplexity from loss
@@ -366,7 +527,7 @@ pub fn lossToPerplexity(loss: f64) f64 {
366527
367528/// Calculate sacred perplexity (φ-adjusted)
368529pub fn sacredPerplexity (loss : f64 ) f64 {
369- return std .math .exp (loss / sacred_funcs . PHI );
530+ return std .math .exp (loss / PHI );
370531}
371532
372533// ═══════════════════════════════════════════════════════════════════════════
@@ -375,29 +536,32 @@ pub fn sacredPerplexity(loss: f64) f64 {
375536
376537/// Generate NeurIPS-style results table
377538pub fn generateNeurIPSTable (allocator : std.mem.Allocator , comparisons : []const ComparisonResult ) ! []u8 {
378- var table = std .ArrayList (u8 ).init (allocator );
379- defer table .deinit ();
539+ if (comparisons .len == 0 ) return allocator .dupe (u8 , "" );
540+
541+ // Build result directly
542+ var result = std .ArrayList (u8 ).init (allocator );
543+ defer result .deinit ();
380544
381- try table .appendSlice ("\\ begin{table}[h]\n " );
382- try table .appendSlice ("\\ centering\n " );
383- try table .appendSlice ("\\ caption{Perplexity Comparison on TinyStories}\n " );
384- try table .appendSlice ("\\ begin{tabular}{lccc}\n " );
385- try table .appendSlice ("\\ toprule\n " );
386- try table .appendSlice ("Method & PPL & StdErr & CI95 \\\\ \n " );
387- try table .appendSlice ("\\ midrule\n " );
545+ try result .appendSlice ("\\ begin{table}[h]\n " );
546+ try result .appendSlice ("\\ centering\n " );
547+ try result .appendSlice ("\\ caption{Perplexity Comparison on TinyStories}\n " );
548+ try result .appendSlice ("\\ begin{tabular}{lccc}\n " );
549+ try result .appendSlice ("\\ toprule\n " );
550+ try result .appendSlice ("Method & PPL & StdErr & CI95 \\\\ \n " );
551+ try result .appendSlice ("\\ midrule\n " );
388552
389553 for (comparisons ) | comp | {
390554 const row = try comp .formatLatex (allocator );
391555 defer allocator .free (row );
392- try table .appendSlice (row );
393- try table .appendSlice ("\n " );
556+ try result .appendSlice (row );
557+ try result .appendSlice ("\ \ n" );
394558 }
395559
396- try table .appendSlice ("\\ bottomrule\n " );
397- try table .appendSlice ("\\ end{tabular}\n " );
398- try table .appendSlice ("\\ end{table}\n " );
560+ try result .appendSlice ("\\ bottomrule\n " );
561+ try result .appendSlice ("\\ end{tabular}\n " );
562+ try result .appendSlice ("\\ end{table}\n " );
399563
400- return table .toOwnedSlice ();
564+ return result .toOwnedSlice ();
401565}
402566
403567// ═══════════════════════════════════════════════════════════════════════════
0 commit comments