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
241    match format.as_str() {
242        "text" => print_text_output(result, quiet, verbose),
243        "json" => print_json_output(result),
244        "csv" => print_csv_output(result),
245        "yaml" => print_yaml_output(result),
246        "toml" => print_toml_output(result),
247        "xml" => print_xml_output(result),
248        _ => {
249            eprintln!("Error: Unsupported output format: {format}");
250            std::process::exit(2);
251        }
252    }
253}
254
255fn output_poisson_test_result(matches: &clap::ArgMatches, result: &PoissonTestResult) {
256    let format_str = matches
257        .get_one::<String>("format")
258        .map(|s| s.as_str())
259        .unwrap_or("text");
260
261    match format_str {
262        "text" => {
263            println!("Poisson Test Result: {}", result.test_name);
264            println!("Test statistic: {:.6}", result.statistic);
265            println!("P-value: {:.6}", result.p_value);
266            println!("λ: {:.3}", result.parameter_lambda);
267            println!(
268                "Is Poisson: {}",
269                if result.is_poisson { "Yes" } else { "No" }
270            );
271        }
272        "json" => {
273            use serde_json::json;
274            let output = json!({
275                "test_name": result.test_name,
276                "statistic": result.statistic,
277                "p_value": result.p_value,
278                "critical_value": result.critical_value,
279                "lambda": result.parameter_lambda,
280                "is_poisson": result.is_poisson
281            });
282            println!("{}", serde_json::to_string_pretty(&output).unwrap());
283        }
284        _ => println!("Unsupported format for Poisson test"),
285    }
286}
287
288fn output_prediction_result(matches: &clap::ArgMatches, result: &EventProbabilityResult) {
289    let format_str = matches
290        .get_one::<String>("format")
291        .map(|s| s.as_str())
292        .unwrap_or("text");
293
294    match format_str {
295        "text" => {
296            println!("Event Probability Prediction (λ = {:.3})", result.lambda);
297            println!("Most likely count: {}", result.most_likely_count);
298            println!();
299
300            for prob in &result.probabilities {
301                println!(
302                    "P(X = {}) = {:.6} (cumulative: {:.6})",
303                    prob.event_count, prob.probability, prob.cumulative_probability
304                );
305            }
306
307            if result.tail_probability > 0.001 {
308                println!(
309                    "P(X > {}) = {:.6}",
310                    result.max_events, result.tail_probability
311                );
312            }
313        }
314        "json" => {
315            use serde_json::json;
316            let output = json!({
317                "lambda": result.lambda,
318                "max_events": result.max_events,
319                "most_likely_count": result.most_likely_count,
320                "expected_value": result.expected_value,
321                "variance": result.variance,
322                "tail_probability": result.tail_probability,
323                "probabilities": result.probabilities.iter().map(|p| json!({
324                    "event_count": p.event_count,
325                    "probability": p.probability,
326                    "cumulative_probability": p.cumulative_probability
327                })).collect::<Vec<_>>()
328            });
329            println!("{}", serde_json::to_string_pretty(&output).unwrap());
330        }
331        _ => println!("Unsupported format for prediction"),
332    }
333}
334
335fn output_rare_events_result(matches: &clap::ArgMatches, result: &RareEventAnalysis) {
336    let format_str = matches
337        .get_one::<String>("format")
338        .map(|s| s.as_str())
339        .unwrap_or("text");
340
341    match format_str {
342        "text" => {
343            println!("Rare Events Analysis (λ = {:.3})", result.lambda);
344            println!("Total observations: {}", result.total_observations);
345            println!();
346
347            println!("Rare Event Thresholds:");
348            println!(
349                "  95%: {} ({} events)",
350                result.threshold_95, result.rare_events_95
351            );
352            println!(
353                "  99%: {} ({} events)",
354                result.threshold_99, result.rare_events_99
355            );
356            println!(
357                "  99.9%: {} ({} events)",
358                result.threshold_999, result.rare_events_999
359            );
360
361            if !result.extreme_events.is_empty() {
362                println!();
363                println!("Extreme Events:");
364                for event in &result.extreme_events {
365                    println!(
366                        "  Index: {} {} (P = {:.6})",
367                        event.index, event.event_count, event.probability
368                    );
369                }
370            }
371
372            if result.clustering_detected {
373                println!();
374                println!("ALERT: Event clustering detected");
375            }
376        }
377        "json" => {
378            use serde_json::json;
379            let output = json!({
380                "lambda": result.lambda,
381                "total_observations": result.total_observations,
382                "thresholds": {
383                    "95_percent": result.threshold_95,
384                    "99_percent": result.threshold_99,
385                    "99_9_percent": result.threshold_999
386                },
387                "rare_event_counts": {
388                    "95_percent": result.rare_events_95,
389                    "99_percent": result.rare_events_99,
390                    "99_9_percent": result.rare_events_999
391                },
392                "extreme_events": result.extreme_events.iter().map(|e| json!({
393                    "index": e.index,
394                    "event_count": e.event_count,
395                    "probability": e.probability,
396                    "rarity_level": format!("{:?}", e.rarity_level)
397                })).collect::<Vec<_>>(),
398                "clustering_detected": result.clustering_detected
399            });
400            println!("{}", serde_json::to_string_pretty(&output).unwrap());
401        }
402        _ => println!("Unsupported format for rare events analysis"),
403    }
404}
405
406fn print_text_output(result: &PoissonResult, quiet: bool, verbose: bool) {
407    if quiet {
408        println!("lambda: {:.3}", result.lambda);
409        println!("variance_ratio: {:.3}", result.variance_ratio);
410        println!("goodness_of_fit: {:.3}", result.goodness_of_fit_score);
411        return;
412    }
413
414    println!("Poisson Distribution Analysis Results");
415    println!();
416    println!("Dataset: {}", result.dataset_name);
417    println!("Numbers analyzed: {}", result.numbers_analyzed);
418    println!("Quality Level: {:?}", result.risk_level);
419
420    println!();
421    println!("Probability Distribution:");
422    println!("{}", format_poisson_probability_chart(result));
423
424    println!();
425    println!("Poisson Parameters:");
426    println!("  λ (rate parameter): {:.3}", result.lambda);
427    println!("  Sample mean: {:.3}", result.sample_mean);
428    println!("  Sample variance: {:.3}", result.sample_variance);
429    println!("  Variance/Mean ratio: {:.3}", result.variance_ratio);
430
431    if verbose {
432        println!();
433        println!("Goodness of Fit Tests:");
434        println!(
435            "  Chi-Square: χ²={:.3}, p={:.3}",
436            result.chi_square_statistic, result.chi_square_p_value
437        );
438        println!(
439            "  Kolmogorov-Smirnov: D={:.3}, p={:.3}",
440            result.kolmogorov_smirnov_statistic, result.kolmogorov_smirnov_p_value
441        );
442
443        println!();
444        println!("Fit Assessment:");
445        println!(
446            "  Goodness of fit score: {:.3}",
447            result.goodness_of_fit_score
448        );
449        println!("  Poisson quality: {:.3}", result.poisson_quality);
450        println!(
451            "  Distribution assessment: {:?}",
452            result.distribution_assessment
453        );
454
455        println!();
456        println!("Event Probabilities:");
457        println!("  P(X = 0) = {:.3}", result.probability_zero);
458        println!("  P(X = 1) = {:.3}", result.probability_one);
459        println!("  P(X ≥ 2) = {:.3}", result.probability_two_or_more);
460
461        if result.rare_events_count > 0 {
462            println!();
463            println!(
464                "Rare events: {} (events ≥ {})",
465                result.rare_events_count, result.rare_events_threshold
466            );
467        }
468
469        println!();
470        println!("Interpretation:");
471        print_poisson_interpretation(result);
472    }
473}
474
475fn print_poisson_interpretation(result: &PoissonResult) {
476    use lawkit_core::laws::poisson::result::PoissonAssessment;
477
478    match result.distribution_assessment {
479        PoissonAssessment::Excellent => {
480            println!(
481                "{}",
482                colors::level_pass("Excellent Poisson distribution fit")
483            );
484            println!("   Data closely follows Poisson distribution");
485        }
486        PoissonAssessment::Good => {
487            println!("{}", colors::level_pass("Good Poisson distribution fit"));
488            println!("   Acceptable fit to Poisson distribution");
489        }
490        PoissonAssessment::Moderate => {
491            println!(
492                "{}",
493                colors::level_warn("Moderate Poisson distribution fit")
494            );
495            println!("   Some deviations from Poisson distribution");
496        }
497        PoissonAssessment::Poor => {
498            println!("{}", colors::level_fail("Poor Poisson distribution fit"));
499            println!("   Significant deviations from Poisson distribution");
500        }
501        PoissonAssessment::NonPoisson => {
502            println!("{}", colors::level_critical("Non-Poisson distribution"));
503            println!("   Data does not follow Poisson distribution");
504        }
505    }
506
507    // 分散/平均比に基づく解釈
508    if result.variance_ratio > 1.5 {
509        println!("   INFO: Distribution is overdispersed");
510    } else if result.variance_ratio < 0.7 {
511        println!("   INFO: Distribution is underdispersed");
512    }
513
514    // 稀少事象の解釈
515    if result.rare_events_count > 0 {
516        println!(
517            "   ALERT: Rare events detected: {}",
518            result.rare_events_count
519        );
520    }
521}
522
523fn print_json_output(result: &PoissonResult) {
524    use serde_json::json;
525
526    let output = json!({
527        "dataset": result.dataset_name,
528        "numbers_analyzed": result.numbers_analyzed,
529        "risk_level": format!("{:?}", result.risk_level),
530        "lambda": result.lambda,
531        "sample_mean": result.sample_mean,
532        "sample_variance": result.sample_variance,
533        "variance_ratio": result.variance_ratio,
534        "chi_square_test": {
535            "statistic": result.chi_square_statistic,
536            "p_value": result.chi_square_p_value
537        },
538        "kolmogorov_smirnov_test": {
539            "statistic": result.kolmogorov_smirnov_statistic,
540            "p_value": result.kolmogorov_smirnov_p_value
541        },
542        "goodness_of_fit_score": result.goodness_of_fit_score,
543        "poisson_quality": result.poisson_quality,
544        "distribution_assessment": format!("{:?}", result.distribution_assessment),
545        "event_probabilities": {
546            "zero": result.probability_zero,
547            "one": result.probability_one,
548            "two_or_more": result.probability_two_or_more
549        },
550        "rare_events": {
551            "threshold": result.rare_events_threshold,
552            "count": result.rare_events_count
553        },
554        "confidence_interval_lambda": result.confidence_interval_lambda
555    });
556
557    println!("{}", serde_json::to_string_pretty(&output).unwrap());
558}
559
560fn print_csv_output(result: &PoissonResult) {
561    println!("dataset,numbers_analyzed,risk_level,lambda,sample_mean,sample_variance,variance_ratio,goodness_of_fit_score");
562    println!(
563        "{},{},{:?},{:.3},{:.3},{:.3},{:.3},{:.3}",
564        result.dataset_name,
565        result.numbers_analyzed,
566        result.risk_level,
567        result.lambda,
568        result.sample_mean,
569        result.sample_variance,
570        result.variance_ratio,
571        result.goodness_of_fit_score
572    );
573}
574
575fn print_yaml_output(result: &PoissonResult) {
576    println!("dataset: \"{}\"", result.dataset_name);
577    println!("numbers_analyzed: {}", result.numbers_analyzed);
578    println!("risk_level: \"{:?}\"", result.risk_level);
579    println!("lambda: {:.3}", result.lambda);
580    println!("sample_mean: {:.3}", result.sample_mean);
581    println!("sample_variance: {:.3}", result.sample_variance);
582    println!("variance_ratio: {:.3}", result.variance_ratio);
583    println!("goodness_of_fit_score: {:.3}", result.goodness_of_fit_score);
584}
585
586fn print_toml_output(result: &PoissonResult) {
587    println!("dataset = \"{}\"", result.dataset_name);
588    println!("numbers_analyzed = {}", result.numbers_analyzed);
589    println!("risk_level = \"{:?}\"", result.risk_level);
590    println!("lambda = {:.3}", result.lambda);
591    println!("sample_mean = {:.3}", result.sample_mean);
592    println!("sample_variance = {:.3}", result.sample_variance);
593    println!("variance_ratio = {:.3}", result.variance_ratio);
594    println!(
595        "goodness_of_fit_score = {:.3}",
596        result.goodness_of_fit_score
597    );
598}
599
600fn print_xml_output(result: &PoissonResult) {
601    println!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
602    println!("<poisson_analysis>");
603    println!("  <dataset>{}</dataset>", result.dataset_name);
604    println!(
605        "  <numbers_analyzed>{}</numbers_analyzed>",
606        result.numbers_analyzed
607    );
608    println!("  <risk_level>{:?}</risk_level>", result.risk_level);
609    println!("  <lambda>{:.3}</lambda>", result.lambda);
610    println!("  <sample_mean>{:.3}</sample_mean>", result.sample_mean);
611    println!(
612        "  <sample_variance>{:.3}</sample_variance>",
613        result.sample_variance
614    );
615    println!(
616        "  <variance_ratio>{:.3}</variance_ratio>",
617        result.variance_ratio
618    );
619    println!(
620        "  <goodness_of_fit_score>{:.3}</goodness_of_fit_score>",
621        result.goodness_of_fit_score
622    );
623    println!("</poisson_analysis>");
624}
625
626/// Analyze numbers with filtering and custom options
627fn analyze_numbers_with_options(
628    matches: &clap::ArgMatches,
629    dataset_name: String,
630    numbers: &[f64],
631) -> Result<PoissonResult> {
632    // Apply number filtering if specified
633    let filtered_numbers = if let Some(filter_str) = matches.get_one::<String>("filter") {
634        let filter = NumberFilter::parse(filter_str)
635            .map_err(|e| BenfError::ParseError(format!("無効なフィルタ: {e}")))?;
636
637        let filtered = apply_number_filter(numbers, &filter);
638
639        // Inform user about filtering results
640        if filtered.len() != numbers.len() {
641            eprintln!(
642                "フィルタリング結果: {} 個の数値が {} 個に絞り込まれました ({})",
643                numbers.len(),
644                filtered.len(),
645                filter.description()
646            );
647        }
648
649        filtered
650    } else {
651        numbers.to_vec()
652    };
653
654    // Parse minimum count requirement
655    let min_count = if let Some(min_count_str) = matches.get_one::<String>("min-count") {
656        min_count_str
657            .parse::<usize>()
658            .map_err(|_| BenfError::ParseError("無効な最小数値数".to_string()))?
659    } else {
660        10 // ポアソン分布分析では最低10個必要
661    };
662
663    // Check minimum count requirement
664    if filtered_numbers.len() < min_count {
665        return Err(BenfError::InsufficientData(filtered_numbers.len()));
666    }
667
668    // Parse confidence level
669    let confidence = if let Some(confidence_str) = matches.get_one::<String>("confidence") {
670        let conf = confidence_str
671            .parse::<f64>()
672            .map_err(|_| BenfError::ParseError("無効な信頼度レベル".to_string()))?;
673        if !(0.01..=0.99).contains(&conf) {
674            return Err(BenfError::ParseError(
675                "信頼度レベルは0.01から0.99の間である必要があります".to_string(),
676            ));
677        }
678        conf
679    } else {
680        0.95
681    };
682
683    // Perform Poisson distribution analysis
684    // TODO: Integrate confidence level into analysis
685    let mut result = analyze_poisson_distribution(&filtered_numbers, &dataset_name)?;
686
687    // For now, just store confidence level as a comment in the dataset name
688    if confidence != 0.95 {
689        result.dataset_name = format!("{} (confidence: {:.2})", result.dataset_name, confidence);
690    }
691
692    Ok(result)
693}
694
695fn format_poisson_probability_chart(result: &PoissonResult) -> String {
696    let mut output = String::new();
697    const CHART_WIDTH: usize = 50;
698
699    let lambda = result.lambda;
700
701    // 確率が十分小さくなるまでの範囲を計算(通常λ + 3√λ程度)
702    let max_k = ((lambda + 3.0 * lambda.sqrt()).ceil() as u32).clamp(10, 20);
703
704    // 各値の理論確率を計算
705    let mut probabilities = Vec::new();
706    let mut max_prob: f64 = 0.0;
707
708    for k in 0..=max_k {
709        // ポアソン確率質量関数: P(X=k) = (λ^k * e^(-λ)) / k!
710        let prob = poisson_pmf(k, lambda);
711        probabilities.push((k, prob));
712        max_prob = max_prob.max(prob);
713    }
714
715    // 確率分布を表示
716    for (k, prob) in &probabilities {
717        if max_prob > 0.0 {
718            let normalized_prob = prob / max_prob;
719            let bar_length = (normalized_prob * CHART_WIDTH as f64).round() as usize;
720            let bar_length = bar_length.min(CHART_WIDTH);
721
722            // Calculate expected line position based on theoretical probability
723            let expected_line_pos = (normalized_prob * CHART_WIDTH as f64).round() as usize;
724            let expected_line_pos = expected_line_pos.min(CHART_WIDTH - 1);
725
726            // Create bar with filled portion, expected value line, and background
727            let mut bar_chars = Vec::new();
728            for pos in 0..CHART_WIDTH {
729                if pos == expected_line_pos {
730                    bar_chars.push('┃'); // Expected value line (theoretical probability)
731                } else if pos < bar_length {
732                    bar_chars.push('█'); // Filled portion
733                } else {
734                    bar_chars.push('░'); // Background portion
735                }
736            }
737            let full_bar: String = bar_chars.iter().collect();
738
739            output.push_str(&format!("P(X={k:2}): {full_bar} {prob:>6.3}\n"));
740        }
741    }
742
743    // 重要な確率値を表示
744    output.push_str(&format!(
745        "\nKey Probabilities: P(X=0)={:.3}, P(X=1)={:.3}, P(X≥2)={:.3}",
746        result.probability_zero, result.probability_one, result.probability_two_or_more
747    ));
748
749    // λとポアソン性の評価
750    output.push_str(&format!(
751        "\nλ={:.2}, Variance/Mean={:.3} (ideal: 1.0), Fit Score={:.3}",
752        lambda, result.variance_ratio, result.goodness_of_fit_score
753    ));
754
755    output
756}
757
758// ポアソン確率質量関数
759fn poisson_pmf(k: u32, lambda: f64) -> f64 {
760    if lambda <= 0.0 {
761        return 0.0;
762    }
763
764    // P(X=k) = (λ^k * e^(-λ)) / k!
765    // 対数計算で数値的安定性を確保
766    let log_prob = k as f64 * lambda.ln() - lambda - log_factorial(k);
767    log_prob.exp()
768}
769
770// 対数階乗の計算(数値的安定性のため)
771fn log_factorial(n: u32) -> f64 {
772    if n <= 1 {
773        0.0
774    } else {
775        (2..=n).map(|i| (i as f64).ln()).sum()
776    }
777}