Skip to main content

lawkit_python/subcommands/
poisson.rs

1use crate::colors;
2use crate::common_options::{get_optimized_reader, setup_automatic_optimization_config};
3use clap::ArgMatches;
4use lawkit_core::{
5    common::{
6        filtering::{apply_number_filter, NumberFilter},
7        input::{parse_input_auto, parse_text_input},
8        memory::{streaming_poisson_analysis, MemoryConfig},
9        streaming_io::OptimizedFileReader,
10    },
11    error::{BenfError, Result},
12    laws::poisson::{
13        analyze_poisson_distribution, analyze_rare_events, predict_event_probabilities,
14        test_poisson_fit, EventProbabilityResult, PoissonResult, PoissonTest, PoissonTestResult,
15        RareEventAnalysis,
16    },
17};
18
19pub fn run(matches: &ArgMatches) -> Result<()> {
20    // 特殊モードの確認(フラグが明示的に指定された場合を優先)
21    if matches.get_flag("predict") {
22        return run_prediction_mode(matches);
23    }
24
25    if matches.get_flag("rare-events") {
26        return run_rare_events_mode(matches);
27    }
28
29    // testパラメータが明示的に指定されている場合(デフォルト値"all"は通常分析で処理)
30    if let Some(test_type) = matches.get_one::<String>("test") {
31        if test_type != "all" {
32            // "all"以外が明示的に指定された場合のみテストモード
33            return run_poisson_test_mode(matches, test_type);
34        }
35    }
36
37    // 自動最適化設定をセットアップ
38    let (_parallel_config, _memory_config) = setup_automatic_optimization_config();
39
40    // Determine input source based on arguments
41    if matches.get_flag("verbose") {
42        eprintln!(
43            "Debug: input argument = {:?}",
44            matches.get_one::<String>("input")
45        );
46    }
47
48    // 入力データ処理
49    let numbers = if let Some(input) = matches.get_one::<String>("input") {
50        // ファイル入力の場合
51        match parse_input_auto(input) {
52            Ok(numbers) => {
53                if numbers.is_empty() {
54                    eprintln!("Error: No valid numbers found in input");
55                    std::process::exit(1);
56                }
57                numbers
58            }
59            Err(e) => {
60                eprintln!("Error processing input '{input}': {e}");
61                std::process::exit(1);
62            }
63        }
64    } else {
65        // stdin入力の場合:ストリーミング処理を使用
66        if matches.get_flag("verbose") {
67            eprintln!("Debug: Reading from stdin, using automatic optimization");
68        }
69
70        let mut reader = OptimizedFileReader::from_stdin();
71
72        if matches.get_flag("verbose") {
73            eprintln!(
74                "Debug: Using automatic optimization (streaming + incremental + memory efficiency)"
75            );
76        }
77
78        let numbers = match reader
79            .read_lines_streaming(|line: String| parse_text_input(&line).map(Some).or(Ok(None)))
80        {
81            Ok(nested_numbers) => {
82                let flattened: Vec<f64> = nested_numbers.into_iter().flatten().collect();
83                if matches.get_flag("verbose") {
84                    eprintln!("Debug: Collected {} numbers from stream", flattened.len());
85                }
86                flattened
87            }
88            Err(e) => {
89                eprintln!("Analysis error: {e}");
90                std::process::exit(1);
91            }
92        };
93
94        if numbers.is_empty() {
95            eprintln!("Error: No valid numbers found in input");
96            std::process::exit(1);
97        }
98
99        // ポアソン分布は非負整数データを想定しているので、データを整数化
100        let event_counts: Vec<usize> = numbers
101            .iter()
102            .filter_map(|&x| if x >= 0.0 { Some(x as usize) } else { None })
103            .collect();
104
105        // インクリメンタルストリーミング分析を実行(大量データの場合)
106        if event_counts.len() > 10000 {
107            let memory_config = MemoryConfig::default();
108            let chunk_result =
109                match streaming_poisson_analysis(event_counts.into_iter(), &memory_config) {
110                    Ok(result) => {
111                        if matches.get_flag("verbose") {
112                            eprintln!(
113                                "Debug: Streaming analysis successful - {} items processed",
114                                result.total_items
115                            );
116                        }
117                        result
118                    }
119                    Err(e) => {
120                        eprintln!("Streaming analysis error: {e}");
121                        std::process::exit(1);
122                    }
123                };
124
125            if matches.get_flag("verbose") {
126                eprintln!(
127                    "Debug: Processed {} numbers in {} chunks",
128                    chunk_result.total_items, chunk_result.chunks_processed
129                );
130                eprintln!("Debug: Memory used: {:.2} MB", chunk_result.memory_used_mb);
131            }
132
133            // usize から f64 に変換
134            chunk_result
135                .result
136                .event_counts()
137                .iter()
138                .map(|&x| x as f64)
139                .collect()
140        } else {
141            // 小さなデータセットの場合は直接処理
142            if matches.get_flag("verbose") {
143                eprintln!("Debug: Memory used: 0.00 MB");
144            }
145            event_counts.iter().map(|&x| x as f64).collect()
146        }
147    };
148
149    let dataset_name = matches
150        .get_one::<String>("input")
151        .map(|s| s.to_string())
152        .unwrap_or_else(|| "stdin".to_string());
153
154    let result = match analyze_numbers_with_options(matches, dataset_name, &numbers) {
155        Ok(result) => result,
156        Err(e) => {
157            eprintln!("Analysis error: {e}");
158            std::process::exit(1);
159        }
160    };
161
162    output_results(matches, &result);
163    std::process::exit(result.risk_level.exit_code())
164}
165
166fn get_numbers_from_input(matches: &ArgMatches) -> Result<Vec<f64>> {
167    let (_parallel_config, _memory_config) = setup_automatic_optimization_config();
168
169    let buffer = if let Some(input) = matches.get_one::<String>("input") {
170        if input == "-" {
171            get_optimized_reader(None)
172        } else {
173            get_optimized_reader(Some(input))
174        }
175    } else {
176        get_optimized_reader(None)
177    };
178
179    let data = buffer.map_err(|e| BenfError::ParseError(e.to_string()))?;
180    parse_text_input(&data)
181}
182
183fn run_poisson_test_mode(matches: &ArgMatches, test_type: &str) -> Result<()> {
184    let numbers = get_numbers_from_input(matches)?;
185
186    let test = match test_type {
187        "chi-square" => PoissonTest::ChiSquare,
188        "ks" => PoissonTest::KolmogorovSmirnov,
189        "variance" => PoissonTest::VarianceTest,
190        "all" => PoissonTest::All,
191        _ => {
192            eprintln!(
193                "Error: Unknown test type '{test_type}'. Available: chi-square, ks, variance, all"
194            );
195            std::process::exit(2);
196        }
197    };
198
199    let test_result = test_poisson_fit(&numbers, test)?;
200    output_poisson_test_result(matches, &test_result);
201
202    let exit_code = if test_result.is_poisson { 0 } else { 1 };
203    std::process::exit(exit_code);
204}
205
206fn run_prediction_mode(matches: &ArgMatches) -> Result<()> {
207    let numbers = get_numbers_from_input(matches)?;
208    let result = analyze_poisson_distribution(&numbers, "prediction")?;
209
210    let max_events = matches
211        .get_one::<String>("max-events")
212        .and_then(|s| s.parse::<u32>().ok())
213        .unwrap_or(10);
214
215    let prediction_result = predict_event_probabilities(result.lambda, max_events);
216    output_prediction_result(matches, &prediction_result);
217
218    std::process::exit(0);
219}
220
221fn run_rare_events_mode(matches: &ArgMatches) -> Result<()> {
222    let numbers = get_numbers_from_input(matches)?;
223    let result = analyze_poisson_distribution(&numbers, "rare_events")?;
224
225    let rare_analysis = analyze_rare_events(&numbers, result.lambda);
226    output_rare_events_result(matches, &rare_analysis);
227
228    let exit_code = if rare_analysis.clustering_detected {
229        2
230    } else {
231        0
232    };
233    std::process::exit(exit_code);
234}
235
236fn output_results(matches: &clap::ArgMatches, result: &PoissonResult) {
237    let format = matches.get_one::<String>("format").unwrap();
238    let quiet = matches.get_flag("quiet");
239    let verbose = matches.get_flag("verbose");
240    let no_color = matches.get_flag("no-color");
241
242    match format.as_str() {
243        "text" => print_text_output(result, quiet, verbose, no_color),
244        "json" => print_json_output(result),
245        "csv" => print_csv_output(result),
246        "yaml" => print_yaml_output(result),
247        "toml" => print_toml_output(result),
248        "xml" => print_xml_output(result),
249        _ => {
250            eprintln!("Error: Unsupported output format: {format}");
251            std::process::exit(2);
252        }
253    }
254}
255
256fn output_poisson_test_result(matches: &clap::ArgMatches, result: &PoissonTestResult) {
257    let format_str = matches
258        .get_one::<String>("format")
259        .map(|s| s.as_str())
260        .unwrap_or("text");
261
262    match format_str {
263        "text" => {
264            println!("Poisson Test Result: {}", result.test_name);
265            println!("Test statistic: {:.6}", result.statistic);
266            println!("P-value: {:.6}", result.p_value);
267            println!("λ: {:.3}", result.parameter_lambda);
268            println!(
269                "Is Poisson: {}",
270                if result.is_poisson { "Yes" } else { "No" }
271            );
272        }
273        "json" => {
274            use serde_json::json;
275            let output = json!({
276                "test_name": result.test_name,
277                "statistic": result.statistic,
278                "p_value": result.p_value,
279                "critical_value": result.critical_value,
280                "lambda": result.parameter_lambda,
281                "is_poisson": result.is_poisson
282            });
283            println!("{}", serde_json::to_string_pretty(&output).unwrap());
284        }
285        _ => println!("Unsupported format for Poisson test"),
286    }
287}
288
289fn output_prediction_result(matches: &clap::ArgMatches, result: &EventProbabilityResult) {
290    let format_str = matches
291        .get_one::<String>("format")
292        .map(|s| s.as_str())
293        .unwrap_or("text");
294
295    match format_str {
296        "text" => {
297            println!("Event Probability Prediction (λ = {:.3})", result.lambda);
298            println!("Most likely count: {}", result.most_likely_count);
299            println!();
300
301            for prob in &result.probabilities {
302                println!(
303                    "P(X = {}) = {:.6} (cumulative: {:.6})",
304                    prob.event_count, prob.probability, prob.cumulative_probability
305                );
306            }
307
308            if result.tail_probability > 0.001 {
309                println!(
310                    "P(X > {}) = {:.6}",
311                    result.max_events, result.tail_probability
312                );
313            }
314        }
315        "json" => {
316            use serde_json::json;
317            let output = json!({
318                "lambda": result.lambda,
319                "max_events": result.max_events,
320                "most_likely_count": result.most_likely_count,
321                "expected_value": result.expected_value,
322                "variance": result.variance,
323                "tail_probability": result.tail_probability,
324                "probabilities": result.probabilities.iter().map(|p| json!({
325                    "event_count": p.event_count,
326                    "probability": p.probability,
327                    "cumulative_probability": p.cumulative_probability
328                })).collect::<Vec<_>>()
329            });
330            println!("{}", serde_json::to_string_pretty(&output).unwrap());
331        }
332        _ => println!("Unsupported format for prediction"),
333    }
334}
335
336fn output_rare_events_result(matches: &clap::ArgMatches, result: &RareEventAnalysis) {
337    let format_str = matches
338        .get_one::<String>("format")
339        .map(|s| s.as_str())
340        .unwrap_or("text");
341
342    match format_str {
343        "text" => {
344            println!("Rare Events Analysis (λ = {:.3})", result.lambda);
345            println!("Total observations: {}", result.total_observations);
346            println!();
347
348            println!("Rare Event Thresholds:");
349            println!(
350                "  95%: {} ({} events)",
351                result.threshold_95, result.rare_events_95
352            );
353            println!(
354                "  99%: {} ({} events)",
355                result.threshold_99, result.rare_events_99
356            );
357            println!(
358                "  99.9%: {} ({} events)",
359                result.threshold_999, result.rare_events_999
360            );
361
362            if !result.extreme_events.is_empty() {
363                println!();
364                println!("Extreme Events:");
365                for event in &result.extreme_events {
366                    println!(
367                        "  Index: {} {} (P = {:.6})",
368                        event.index, event.event_count, event.probability
369                    );
370                }
371            }
372
373            if result.clustering_detected {
374                println!();
375                println!("ALERT: Event clustering detected");
376            }
377        }
378        "json" => {
379            use serde_json::json;
380            let output = json!({
381                "lambda": result.lambda,
382                "total_observations": result.total_observations,
383                "thresholds": {
384                    "95_percent": result.threshold_95,
385                    "99_percent": result.threshold_99,
386                    "99_9_percent": result.threshold_999
387                },
388                "rare_event_counts": {
389                    "95_percent": result.rare_events_95,
390                    "99_percent": result.rare_events_99,
391                    "99_9_percent": result.rare_events_999
392                },
393                "extreme_events": result.extreme_events.iter().map(|e| json!({
394                    "index": e.index,
395                    "event_count": e.event_count,
396                    "probability": e.probability,
397                    "rarity_level": format!("{:?}", e.rarity_level)
398                })).collect::<Vec<_>>(),
399                "clustering_detected": result.clustering_detected
400            });
401            println!("{}", serde_json::to_string_pretty(&output).unwrap());
402        }
403        _ => println!("Unsupported format for rare events analysis"),
404    }
405}
406
407fn print_text_output(result: &PoissonResult, quiet: bool, verbose: bool, no_color: bool) {
408    if quiet {
409        println!("lambda: {:.3}", result.lambda);
410        println!("variance_ratio: {:.3}", result.variance_ratio);
411        println!("goodness_of_fit: {:.3}", result.goodness_of_fit_score);
412        return;
413    }
414
415    println!("Poisson Distribution Analysis Results");
416    println!();
417    println!("Dataset: {}", result.dataset_name);
418    println!("Numbers analyzed: {}", result.numbers_analyzed);
419    println!("Quality Level: {:?}", result.risk_level);
420
421    println!();
422    println!("Probability Distribution:");
423    println!("{}", format_poisson_probability_chart(result));
424
425    println!();
426    println!("Poisson Parameters:");
427    println!("  λ (rate parameter): {:.3}", result.lambda);
428    println!("  Sample mean: {:.3}", result.sample_mean);
429    println!("  Sample variance: {:.3}", result.sample_variance);
430    println!("  Variance/Mean ratio: {:.3}", result.variance_ratio);
431
432    if verbose {
433        println!();
434        println!("Goodness of Fit Tests:");
435        println!(
436            "  Chi-Square: χ²={:.3}, p={:.3}",
437            result.chi_square_statistic, result.chi_square_p_value
438        );
439        println!(
440            "  Kolmogorov-Smirnov: D={:.3}, p={:.3}",
441            result.kolmogorov_smirnov_statistic, result.kolmogorov_smirnov_p_value
442        );
443
444        println!();
445        println!("Fit Assessment:");
446        println!(
447            "  Goodness of fit score: {:.3}",
448            result.goodness_of_fit_score
449        );
450        println!("  Poisson quality: {:.3}", result.poisson_quality);
451        println!(
452            "  Distribution assessment: {:?}",
453            result.distribution_assessment
454        );
455
456        println!();
457        println!("Event Probabilities:");
458        println!("  P(X = 0) = {:.3}", result.probability_zero);
459        println!("  P(X = 1) = {:.3}", result.probability_one);
460        println!("  P(X ≥ 2) = {:.3}", result.probability_two_or_more);
461
462        if result.rare_events_count > 0 {
463            println!();
464            println!(
465                "Rare events: {} (events ≥ {})",
466                result.rare_events_count, result.rare_events_threshold
467            );
468        }
469
470        println!();
471        println!("Interpretation:");
472        print_poisson_interpretation(result, no_color);
473    }
474}
475
476fn print_poisson_interpretation(result: &PoissonResult, no_color: bool) {
477    use lawkit_core::laws::poisson::result::PoissonAssessment;
478
479    match result.distribution_assessment {
480        PoissonAssessment::Excellent => {
481            println!(
482                "{}",
483                colors::level_pass("Excellent Poisson distribution fit", no_color)
484            );
485            println!("   Data closely follows Poisson distribution");
486        }
487        PoissonAssessment::Good => {
488            println!(
489                "{}",
490                colors::level_pass("Good Poisson distribution fit", no_color)
491            );
492            println!("   Acceptable fit to Poisson distribution");
493        }
494        PoissonAssessment::Moderate => {
495            println!(
496                "{}",
497                colors::level_warn("Moderate Poisson distribution fit", no_color)
498            );
499            println!("   Some deviations from Poisson distribution");
500        }
501        PoissonAssessment::Poor => {
502            println!(
503                "{}",
504                colors::level_fail("Poor Poisson distribution fit", no_color)
505            );
506            println!("   Significant deviations from Poisson distribution");
507        }
508        PoissonAssessment::NonPoisson => {
509            println!(
510                "{}",
511                colors::level_critical("Non-Poisson distribution", no_color)
512            );
513            println!("   Data does not follow Poisson distribution");
514        }
515    }
516
517    // 分散/平均比に基づく解釈
518    if result.variance_ratio > 1.5 {
519        println!("   INFO: Distribution is overdispersed");
520    } else if result.variance_ratio < 0.7 {
521        println!("   INFO: Distribution is underdispersed");
522    }
523
524    // 稀少事象の解釈
525    if result.rare_events_count > 0 {
526        println!(
527            "   ALERT: Rare events detected: {}",
528            result.rare_events_count
529        );
530    }
531}
532
533fn print_json_output(result: &PoissonResult) {
534    use serde_json::json;
535
536    let output = json!({
537        "dataset": result.dataset_name,
538        "numbers_analyzed": result.numbers_analyzed,
539        "risk_level": format!("{:?}", result.risk_level),
540        "lambda": result.lambda,
541        "sample_mean": result.sample_mean,
542        "sample_variance": result.sample_variance,
543        "variance_ratio": result.variance_ratio,
544        "chi_square_test": {
545            "statistic": result.chi_square_statistic,
546            "p_value": result.chi_square_p_value
547        },
548        "kolmogorov_smirnov_test": {
549            "statistic": result.kolmogorov_smirnov_statistic,
550            "p_value": result.kolmogorov_smirnov_p_value
551        },
552        "goodness_of_fit_score": result.goodness_of_fit_score,
553        "poisson_quality": result.poisson_quality,
554        "distribution_assessment": format!("{:?}", result.distribution_assessment),
555        "event_probabilities": {
556            "zero": result.probability_zero,
557            "one": result.probability_one,
558            "two_or_more": result.probability_two_or_more
559        },
560        "rare_events": {
561            "threshold": result.rare_events_threshold,
562            "count": result.rare_events_count
563        },
564        "confidence_interval_lambda": result.confidence_interval_lambda
565    });
566
567    println!("{}", serde_json::to_string_pretty(&output).unwrap());
568}
569
570fn print_csv_output(result: &PoissonResult) {
571    println!("dataset,numbers_analyzed,risk_level,lambda,sample_mean,sample_variance,variance_ratio,goodness_of_fit_score");
572    println!(
573        "{},{},{:?},{:.3},{:.3},{:.3},{:.3},{:.3}",
574        result.dataset_name,
575        result.numbers_analyzed,
576        result.risk_level,
577        result.lambda,
578        result.sample_mean,
579        result.sample_variance,
580        result.variance_ratio,
581        result.goodness_of_fit_score
582    );
583}
584
585fn print_yaml_output(result: &PoissonResult) {
586    println!("dataset: \"{}\"", result.dataset_name);
587    println!("numbers_analyzed: {}", result.numbers_analyzed);
588    println!("risk_level: \"{:?}\"", result.risk_level);
589    println!("lambda: {:.3}", result.lambda);
590    println!("sample_mean: {:.3}", result.sample_mean);
591    println!("sample_variance: {:.3}", result.sample_variance);
592    println!("variance_ratio: {:.3}", result.variance_ratio);
593    println!("goodness_of_fit_score: {:.3}", result.goodness_of_fit_score);
594}
595
596fn print_toml_output(result: &PoissonResult) {
597    println!("dataset = \"{}\"", result.dataset_name);
598    println!("numbers_analyzed = {}", result.numbers_analyzed);
599    println!("risk_level = \"{:?}\"", result.risk_level);
600    println!("lambda = {:.3}", result.lambda);
601    println!("sample_mean = {:.3}", result.sample_mean);
602    println!("sample_variance = {:.3}", result.sample_variance);
603    println!("variance_ratio = {:.3}", result.variance_ratio);
604    println!(
605        "goodness_of_fit_score = {:.3}",
606        result.goodness_of_fit_score
607    );
608}
609
610fn print_xml_output(result: &PoissonResult) {
611    println!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
612    println!("<poisson_analysis>");
613    println!("  <dataset>{}</dataset>", result.dataset_name);
614    println!(
615        "  <numbers_analyzed>{}</numbers_analyzed>",
616        result.numbers_analyzed
617    );
618    println!("  <risk_level>{:?}</risk_level>", result.risk_level);
619    println!("  <lambda>{:.3}</lambda>", result.lambda);
620    println!("  <sample_mean>{:.3}</sample_mean>", result.sample_mean);
621    println!(
622        "  <sample_variance>{:.3}</sample_variance>",
623        result.sample_variance
624    );
625    println!(
626        "  <variance_ratio>{:.3}</variance_ratio>",
627        result.variance_ratio
628    );
629    println!(
630        "  <goodness_of_fit_score>{:.3}</goodness_of_fit_score>",
631        result.goodness_of_fit_score
632    );
633    println!("</poisson_analysis>");
634}
635
636/// Analyze numbers with filtering and custom options
637fn analyze_numbers_with_options(
638    matches: &clap::ArgMatches,
639    dataset_name: String,
640    numbers: &[f64],
641) -> Result<PoissonResult> {
642    // Apply number filtering if specified
643    let filtered_numbers = if let Some(filter_str) = matches.get_one::<String>("filter") {
644        let filter = NumberFilter::parse(filter_str)
645            .map_err(|e| BenfError::ParseError(format!("無効なフィルタ: {e}")))?;
646
647        let filtered = apply_number_filter(numbers, &filter);
648
649        // Inform user about filtering results
650        if filtered.len() != numbers.len() {
651            eprintln!(
652                "フィルタリング結果: {} 個の数値が {} 個に絞り込まれました ({})",
653                numbers.len(),
654                filtered.len(),
655                filter.description()
656            );
657        }
658
659        filtered
660    } else {
661        numbers.to_vec()
662    };
663
664    // Parse minimum count requirement
665    let min_count = if let Some(min_count_str) = matches.get_one::<String>("min-count") {
666        min_count_str
667            .parse::<usize>()
668            .map_err(|_| BenfError::ParseError("無効な最小数値数".to_string()))?
669    } else {
670        10 // ポアソン分布分析では最低10個必要
671    };
672
673    // Check minimum count requirement
674    if filtered_numbers.len() < min_count {
675        return Err(BenfError::InsufficientData(filtered_numbers.len()));
676    }
677
678    // Parse confidence level
679    let confidence = if let Some(confidence_str) = matches.get_one::<String>("confidence") {
680        let conf = confidence_str
681            .parse::<f64>()
682            .map_err(|_| BenfError::ParseError("無効な信頼度レベル".to_string()))?;
683        if !(0.01..=0.99).contains(&conf) {
684            return Err(BenfError::ParseError(
685                "信頼度レベルは0.01から0.99の間である必要があります".to_string(),
686            ));
687        }
688        conf
689    } else {
690        0.95
691    };
692
693    // Perform Poisson distribution analysis
694    // TODO: Integrate confidence level into analysis
695    let mut result = analyze_poisson_distribution(&filtered_numbers, &dataset_name)?;
696
697    // For now, just store confidence level as a comment in the dataset name
698    if confidence != 0.95 {
699        result.dataset_name = format!("{} (confidence: {:.2})", result.dataset_name, confidence);
700    }
701
702    Ok(result)
703}
704
705fn format_poisson_probability_chart(result: &PoissonResult) -> String {
706    let mut output = String::new();
707    const CHART_WIDTH: usize = 50;
708
709    let lambda = result.lambda;
710
711    // 確率が十分小さくなるまでの範囲を計算(通常λ + 3√λ程度)
712    let max_k = ((lambda + 3.0 * lambda.sqrt()).ceil() as u32).clamp(10, 20);
713
714    // 各値の理論確率を計算
715    let mut probabilities = Vec::new();
716    let mut max_prob: f64 = 0.0;
717
718    for k in 0..=max_k {
719        // ポアソン確率質量関数: P(X=k) = (λ^k * e^(-λ)) / k!
720        let prob = poisson_pmf(k, lambda);
721        probabilities.push((k, prob));
722        max_prob = max_prob.max(prob);
723    }
724
725    // 確率分布を表示
726    for (k, prob) in &probabilities {
727        if max_prob > 0.0 {
728            let normalized_prob = prob / max_prob;
729            let bar_length = (normalized_prob * CHART_WIDTH as f64).round() as usize;
730            let bar_length = bar_length.min(CHART_WIDTH);
731
732            // Calculate expected line position based on theoretical probability
733            let expected_line_pos = (normalized_prob * CHART_WIDTH as f64).round() as usize;
734            let expected_line_pos = expected_line_pos.min(CHART_WIDTH - 1);
735
736            // Create bar with filled portion, expected value line, and background
737            let mut bar_chars = Vec::new();
738            for pos in 0..CHART_WIDTH {
739                if pos == expected_line_pos {
740                    bar_chars.push('┃'); // Expected value line (theoretical probability)
741                } else if pos < bar_length {
742                    bar_chars.push('█'); // Filled portion
743                } else {
744                    bar_chars.push('░'); // Background portion
745                }
746            }
747            let full_bar: String = bar_chars.iter().collect();
748
749            output.push_str(&format!("P(X={k:2}): {full_bar} {prob:>6.3}\n"));
750        }
751    }
752
753    // 重要な確率値を表示
754    output.push_str(&format!(
755        "\nKey Probabilities: P(X=0)={:.3}, P(X=1)={:.3}, P(X≥2)={:.3}",
756        result.probability_zero, result.probability_one, result.probability_two_or_more
757    ));
758
759    // λとポアソン性の評価
760    output.push_str(&format!(
761        "\nλ={:.2}, Variance/Mean={:.3} (ideal: 1.0), Fit Score={:.3}",
762        lambda, result.variance_ratio, result.goodness_of_fit_score
763    ));
764
765    output
766}
767
768// ポアソン確率質量関数
769fn poisson_pmf(k: u32, lambda: f64) -> f64 {
770    if lambda <= 0.0 {
771        return 0.0;
772    }
773
774    // P(X=k) = (λ^k * e^(-λ)) / k!
775    // 対数計算で数値的安定性を確保
776    let log_prob = k as f64 * lambda.ln() - lambda - log_factorial(k);
777    log_prob.exp()
778}
779
780// 対数階乗の計算(数値的安定性のため)
781fn log_factorial(n: u32) -> f64 {
782    if n <= 1 {
783        0.0
784    } else {
785        (2..=n).map(|i| (i as f64).ln()).sum()
786    }
787}