refactor: refine CLI options and enhance debug logging

- Remove --mode, --transition-type, and --decay-index CLI flags
  (parameters now come from ENSDF data or custom JSON input)
- Clarify energy units in all CLI help text (MeV throughout, keV for detector params)
- Enhance logging verbosity:
  - -v (INFO): shows source, component initialization, workflow steps
  - -vv (DEBUG): shows detailed internals of all spectrum components
    (parameters, evaluation counts, input/output ranges)
- Improve CSV headers with element notation (e.g. Tc99 -> Ru99)
- Add CONVENTIONS.md documenting energy unit and logging conventions
- Update TODO.md with completion status
This commit is contained in:
2026-05-05 09:08:08 +03:00
parent eb52c3b918
commit 330ebddaef
14 changed files with 470 additions and 78 deletions
+35 -52
View File
@@ -9,13 +9,13 @@ Usage examples:
bs_pnpi --input custom_input.json --output spectrum.csv
# Full analysis with plotting
bs_pnpi --nuclide Co60 --mode BM --plot analysis.png --output spectrum.csv
bs_pnpi --nuclide Co60 --plot analysis.png --output spectrum.csv
# With detector response
bs_pnpi --nuclide Tc99 --detector sigma=1.0 --output convolved.csv
bs_pnpi --nuclide Tc99 --detector gaussian --output convolved.csv
# Verbose with log file
bs_pnpi --nuclide Tc99 --output spectrum.csv -vv --log-file calc.log
bs_pnpi --nuclide Tc99 --output spectrum.csv -vv --log-file /tmp/calc.log
# Dry run to inspect resolved configuration
bs_pnpi --nuclide Tc99 --dry-run
@@ -42,8 +42,8 @@ def _build_parser() -> argparse.ArgumentParser:
" bs_pnpi --nuclide Tc99 --output spectrum.csv\n"
" bs_pnpi --input custom_input.json --output spectrum.csv\n"
" bs_pnpi --nuclide Tc99 --plot spectrum.png --output spectrum.csv\n"
" bs_pnpi --nuclide Tc99 --detector sigma=1.0 --output convolved.csv\n"
" bs_pnpi --nuclide Tc99 --output spectrum.csv -vv --log-file calc.log\n"
" bs_pnpi --nuclide Tc99 --detector gaussian --output convolved.csv\n"
" bs_pnpi --nuclide Tc99 --output spectrum.csv -vv --log-file /tmp/calc.log\n"
" bs_pnpi --nuclide Tc99 --dry-run\n"
),
)
@@ -71,31 +71,11 @@ def _build_parser() -> argparse.ArgumentParser:
)
# Common parameters
parser.add_argument(
"--mode",
type=str,
default="beta_minus",
choices=["beta_minus", "beta_plus", "ec"],
help="Decay mode for paceENSDF source (default: beta_minus).",
)
parser.add_argument(
"--decay-index",
type=int,
default=None,
help="Decay index for paceENSDF (0 = ground state, default: auto-select).",
)
parser.add_argument(
"--transition-type",
type=str,
default=None,
choices=["A", "F1", "F1U", "F2", "F2U", "F3", "F3U", "F4"],
help="Override transition type (forbiddenness classification).",
)
parser.add_argument(
"--e-step",
type=float,
default=0.001,
help="Energy step size in MeV (default: 0.001).",
help="Energy step size in MeV (default: 0.001). All energies are in MeV throughout the toolkit.",
)
# Output
@@ -122,28 +102,29 @@ def _build_parser() -> argparse.ArgumentParser:
default=None,
metavar="MODEL",
help="Enable detector response convolution. "
"Specify model: 'gaussian', 'gaussian_tail', 'tikhonov', 'tabulated'.",
"Specify model: 'gaussian', 'gaussian_tail', 'tikhonov', 'tabulated'. "
"All detector parameters are in keV.",
)
detector_group.add_argument(
"--sigma",
type=float,
default=1.0,
metavar="KEV",
help="Resolution parameter sigma in keV (default: 1.0).",
help="Resolution parameter sigma in keV (default: 1.0). Energy-dependent part: sigma(E) = sigma_a + sigma_b * sqrt(E).",
)
detector_group.add_argument(
"--tau",
type=float,
default=5.0,
metavar="KEV",
help="Tail decay constant tau in keV (default: 5.0).",
help="Low-energy tail decay constant tau in keV (default: 5.0). Only used with 'gaussian_tail' model.",
)
detector_group.add_argument(
"--tail-fraction",
type=float,
default=0.0,
metavar="FRAC",
help="Low-energy tail fraction (0.0–1.0, default: 0.0).",
help="Low-energy tail fraction (0.0–1.0, default: 0.0). Only used with 'gaussian_tail' model.",
)
# Logging and output control
@@ -153,7 +134,8 @@ def _build_parser() -> argparse.ArgumentParser:
"--verbose",
action="count",
default=0,
help="Increase verbosity: -v=INFO, -vv=DEBUG (repeatable).",
help="Increase verbosity: -v=INFO (shows source, components, and workflow), "
"-vv=DEBUG (detailed internals of all spectrum components). Repeatable.",
)
log_group.add_argument(
"-q",
@@ -164,8 +146,9 @@ def _build_parser() -> argparse.ArgumentParser:
log_group.add_argument(
"--log-file",
type=str,
metavar="FILE",
help="Append log messages to the specified file.",
metavar="PATH",
help="Write log messages to the specified file path (absolute or relative). "
"Log file is always opened in append mode at DEBUG level.",
)
log_group.add_argument(
"--dry-run",
@@ -228,12 +211,10 @@ def _run(args: argparse.Namespace) -> None:
source_type: str
if args.nuclide:
logger.info("Source: paceENSDF nuclide=%s mode=%s", args.nuclide, args.mode)
logger.info("Source: paceENSDF nuclide=%s", args.nuclide)
config = create_config_from_source(
"paceENSDF",
nuclide=args.nuclide,
decay_type=args.mode,
decay_index=args.decay_index,
e_step_MeV=args.e_step,
use_detector_response=detector_kwargs.get("use_detector_response", False),
detector_model=detector_kwargs.get("detector_model", "gaussian"),
@@ -245,19 +226,12 @@ def _run(args: argparse.Namespace) -> None:
elif args.input:
logger.info("Source: JSON file=%s", args.input)
data = load_json_input(args.input)
if args.transition_type:
data["transition_type"] = args.transition_type
config = json_to_config(data)
source_type = "json"
else:
logger.error("No source specified (use --nuclide or --input)")
sys.exit(1)
# Override transition type if explicitly provided
if args.transition_type:
config.transition_type = args.transition_type
logger.info("Override transition_type=%s", args.transition_type)
# Dry run: display resolved config and exit
if args.dry_run:
_print_dry_run_output(config, source_type)
@@ -266,15 +240,12 @@ def _run(args: argparse.Namespace) -> None:
# Create and calculate spectrum
start_time = time.time()
logger.info(
"Creating BetaSpectrum for %s -> Z=%d,%d, A=%d, endpoint=%.3f MeV",
_element_symbol(config.Z_parent),
config.Z_parent,
config.Z_daughter,
config.A_number,
"Creating BetaSpectrum for %s, endpoint=%.3f MeV",
_decay_notation(config.Z_parent, config.Z_daughter, config.A_number),
config.endpoint_MeV,
)
spectrum = BetaSpectrum.from_config(config)
spectrum = BetaSpectrum.from_config(config, logger=logger)
_W, _kinetic_MeV = spectrum.get_energy_grid(config)
_ = spectrum(_W)
@@ -285,13 +256,13 @@ def _run(args: argparse.Namespace) -> None:
# Export to CSV
if args.output:
analyzer = BetaSpectrumAnalyzer(spectrum, config)
analyzer = BetaSpectrumAnalyzer(spectrum, config, logger=logger)
analyzer.export_to_csv(args.output, source_type=source_type)
logger.info("Spectrum exported to %s", args.output)
# Generate plot
if args.plot:
analyzer = BetaSpectrumAnalyzer(spectrum, config)
analyzer = BetaSpectrumAnalyzer(spectrum, config, logger=logger)
analyzer.plot_analysis(save_path=args.plot)
logger.info("Analysis plot saved to %s", args.plot)
@@ -322,7 +293,9 @@ def _print_dry_run_output(config, source_type: str) -> None:
print(f"Package: beta-spectrum v{__import__('beta_spectrum').__version__}")
print(f"Git commit: {get_git_short_hash()}")
print(f"Source: {source_type}")
print(f"Nuclide: {config.Z_parent} -> {config.Z_daughter}, A={config.A_number}")
print(
f"Nuclide: {_decay_notation(config.Z_parent, config.Z_daughter, config.A_number)}"
)
print(f"Endpoint: {config.endpoint_MeV * 1000:.1f} keV")
print(f"Transition: {config.transition_type}")
print(f"e_step: {config.e_step_MeV:.4f} MeV")
@@ -445,6 +418,16 @@ def _element_symbol(Z: int) -> str:
return symbols[Z] if Z < len(symbols) else f"Z{Z}"
def _nuclide_symbol(Z: int, A: int) -> str:
"""Get nuclide notation like 'Tc99' from atomic and mass numbers."""
return f"{_element_symbol(Z)}{A}"
def _decay_notation(Z_parent: int, Z_daughter: int, A: int) -> str:
"""Get decay notation like 'Tc99 -> Ru99' from atomic and mass numbers."""
return f"{_nuclide_symbol(Z_parent, A)} -> {_nuclide_symbol(Z_daughter, A)}"
def main() -> None:
"""Main entry point for the CLI."""
parser = _build_parser()