141 lines
4.0 KiB
Python
141 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
|
|
"""Pi explorer powered by mathstream.
|
|
|
|
Computes π using the Nilakantha series with streamed big-int arithmetic and
|
|
optionally renders the progress in a curses dashboard (`--ui`).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
from typing import Iterable, Optional
|
|
|
|
from mathstream import (
|
|
clear_logs,
|
|
manual_free_only_enabled,
|
|
set_manual_free_only,
|
|
)
|
|
|
|
from pi_finder import iterate_nilakantha
|
|
|
|
|
|
def run_cli_mode(
|
|
*,
|
|
digits: int,
|
|
iterations: int,
|
|
extra_precision: int,
|
|
show_steps: bool,
|
|
) -> tuple[str, int]:
|
|
previous_manual = manual_free_only_enabled()
|
|
set_manual_free_only(True)
|
|
clear_logs()
|
|
|
|
final_value: Optional[str] = None
|
|
last_iteration = 0
|
|
try:
|
|
for state in iterate_nilakantha(
|
|
iterations,
|
|
digits=digits,
|
|
extra_precision=extra_precision,
|
|
):
|
|
final_value = state.digits
|
|
last_iteration = state.iteration
|
|
if show_steps:
|
|
print(f"[iter {state.iteration:02d}] {state.digits}")
|
|
|
|
if final_value is None:
|
|
final_value = "0"
|
|
last_iteration = 0
|
|
if show_steps:
|
|
print(final_value)
|
|
finally:
|
|
set_manual_free_only(previous_manual)
|
|
|
|
return final_value, last_iteration
|
|
|
|
|
|
def run_ui_mode(max_digits: Optional[int]) -> None:
|
|
try:
|
|
from pi_finder.ui import launch_pi_ui
|
|
except ImportError as exc: # pragma: no cover - import guard
|
|
raise SystemExit(f"pi UI is unavailable: {exc}") from exc
|
|
launch_pi_ui(max_digits=max_digits)
|
|
|
|
|
|
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Nilakantha-based π explorer using mathstream streamed arithmetic."
|
|
)
|
|
parser.add_argument(
|
|
"--digits",
|
|
type=int,
|
|
default=25,
|
|
help="Digits of π to keep after the decimal point (default: 25).",
|
|
)
|
|
parser.add_argument(
|
|
"--iterations",
|
|
type=int,
|
|
default=10,
|
|
help="Nilakantha iterations to run (each adds roughly two decimal digits).",
|
|
)
|
|
parser.add_argument(
|
|
"--extra",
|
|
type=int,
|
|
default=4,
|
|
help="Extra guard digits maintained during calculation for stability (default: 4).",
|
|
)
|
|
parser.add_argument(
|
|
"--show-steps",
|
|
action="store_true",
|
|
help="Print every intermediate approximation instead of just the final value.",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
type=Path,
|
|
default=Path("found.pi"),
|
|
help="Optional path to write the final approximation (default: found.pi).",
|
|
)
|
|
parser.add_argument(
|
|
"--ui",
|
|
action="store_true",
|
|
help="Launch the curses dashboard instead of printing digits.",
|
|
)
|
|
parser.add_argument(
|
|
"--max-digits",
|
|
type=int,
|
|
default=None,
|
|
help="Limit digits in UI mode (defaults to --digits).",
|
|
)
|
|
return parser.parse_args(list(argv) if argv is not None else None)
|
|
|
|
|
|
def main(argv: Optional[Iterable[str]] = None) -> None:
|
|
args = parse_args(argv)
|
|
if args.ui:
|
|
precision = args.max_digits if args.max_digits is not None else max(10, args.digits)
|
|
run_ui_mode(precision)
|
|
else:
|
|
digits = max(0, args.digits)
|
|
iterations = max(0, args.iterations)
|
|
extra = max(0, args.extra)
|
|
result, last_iter = run_cli_mode(
|
|
digits=digits,
|
|
iterations=iterations,
|
|
extra_precision=extra,
|
|
show_steps=args.show_steps,
|
|
)
|
|
output_path: Path = args.output
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
data = result if result.endswith("\n") else f"{result}\n"
|
|
output_path.write_text(data, encoding="utf-8")
|
|
digits_written = sum(ch.isdigit() for ch in result)
|
|
print(
|
|
f"π approximation (iteration {last_iter}, {digits_written} digits) written to {output_path}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|