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 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 if let Some(test_type) = matches.get_one::<String>("test") {
31 if test_type != "all" {
32 return run_poisson_test_mode(matches, test_type);
34 }
35 }
36
37 let (_parallel_config, _memory_config) = setup_automatic_optimization_config();
39
40 if matches.get_flag("verbose") {
42 eprintln!(
43 "Debug: input argument = {:?}",
44 matches.get_one::<String>("input")
45 );
46 }
47
48 let numbers = if let Some(input) = matches.get_one::<String>("input") {
50 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 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 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 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 chunk_result
135 .result
136 .event_counts()
137 .iter()
138 .map(|&x| x as f64)
139 .collect()
140 } else {
141 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 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 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
626fn analyze_numbers_with_options(
628 matches: &clap::ArgMatches,
629 dataset_name: String,
630 numbers: &[f64],
631) -> Result<PoissonResult> {
632 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 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 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 };
662
663 if filtered_numbers.len() < min_count {
665 return Err(BenfError::InsufficientData(filtered_numbers.len()));
666 }
667
668 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 let mut result = analyze_poisson_distribution(&filtered_numbers, &dataset_name)?;
686
687 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 let max_k = ((lambda + 3.0 * lambda.sqrt()).ceil() as u32).clamp(10, 20);
703
704 let mut probabilities = Vec::new();
706 let mut max_prob: f64 = 0.0;
707
708 for k in 0..=max_k {
709 let prob = poisson_pmf(k, lambda);
711 probabilities.push((k, prob));
712 max_prob = max_prob.max(prob);
713 }
714
715 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 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 let mut bar_chars = Vec::new();
728 for pos in 0..CHART_WIDTH {
729 if pos == expected_line_pos {
730 bar_chars.push('┃'); } else if pos < bar_length {
732 bar_chars.push('█'); } else {
734 bar_chars.push('░'); }
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 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 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
758fn poisson_pmf(k: u32, lambda: f64) -> f64 {
760 if lambda <= 0.0 {
761 return 0.0;
762 }
763
764 let log_prob = k as f64 * lambda.ln() - lambda - log_factorial(k);
767 log_prob.exp()
768}
769
770fn 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}