Skip to content

Commit 4ef18cc

Browse files
update cli generate database
1 parent 53e04c8 commit 4ef18cc

4 files changed

Lines changed: 245 additions & 122 deletions

File tree

docs/api/generate_database.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ Generate Database module
66
.. autosummary::
77
:toctree: ../_autosummary/
88

9-
generate_database.create_tables_for_one_species
9+
generate_database.create_tables_for_sqdt
10+
generate_database.create_tables_for_mqdt
1011
generate_database.create_tables_for_misc

src/rydstate/cli.py

Lines changed: 185 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8,143 +8,231 @@
88
import time
99
from pathlib import Path
1010

11-
from rydstate.generate_database.generate_database import create_tables_for_misc, create_tables_for_one_species
11+
from rydstate.generate_database.generate_database import (
12+
create_tables_for_misc,
13+
create_tables_for_mqdt,
14+
create_tables_for_sqdt,
15+
)
1216

1317
logger = logging.getLogger(__name__)
1418

1519

16-
def main() -> None: # noqa: C901, PLR0912, PLR0915
20+
def main() -> None:
1721
"""Entry point for the generate_database script."""
22+
args = build_parser().parse_args()
23+
24+
directory = prepare_directory(args)
25+
configure_logging(args.log_level, directory, args.warnings_as_exceptions)
26+
27+
time_start = time.perf_counter()
28+
if args.mode == "misc":
29+
create_tables_for_misc(f_max=args.f_max, kappa_max=3)
30+
elif args.mode == "sqdt":
31+
create_tables_for_sqdt(
32+
args.species,
33+
n=(args.n_min, args.n_max),
34+
f_tot=get_f_tot_range(args),
35+
max_delta_nu=args.max_delta_nu,
36+
all_nu_up_to=args.all_nu_up_to,
37+
)
38+
elif args.mode == "mqdt":
39+
create_tables_for_mqdt(
40+
args.species,
41+
nu=(args.nu_min, args.nu_max),
42+
f_tot=get_f_tot_range(args),
43+
max_delta_nu=args.max_delta_nu,
44+
all_nu_up_to=args.all_nu_up_to,
45+
)
46+
else:
47+
raise ValueError(f"Unknown mode: {args.mode}")
48+
49+
logger.info("Time taken: %.2f seconds", time.perf_counter() - time_start)
50+
log_memory_usage()
51+
52+
53+
def build_parser() -> argparse.ArgumentParser:
54+
"""Build the argument parser with the sqdt, mqdt and misc subcommands."""
1855
parser = argparse.ArgumentParser(
1956
description="Generate a database, containing energies and matrix elements, for a given species.",
2057
formatter_class=argparse.RawDescriptionHelpFormatter,
21-
epilog=("Example:\n generate_database Rb --log-level INFO\n"),
58+
epilog=(
59+
"Examples:\n"
60+
" generate_database sqdt Rb --n-max 60\n"
61+
" generate_database mqdt Yb174 --nu-max 60\n"
62+
" generate_database misc --f-max 10\n"
63+
),
2264
)
23-
parser.add_argument("species", help="The species name to generate the database for.")
24-
parser.add_argument(
25-
"--n-min",
65+
subparsers = parser.add_subparsers(dest="mode", required=True, title="modes")
66+
67+
# arguments shared by all modes
68+
common = argparse.ArgumentParser(add_help=False)
69+
common.add_argument(
70+
"--directory",
2671
default=None,
27-
type=int,
28-
help="The minimal principal quantum number n for the states to be included in the database. "
29-
"Default 1 will start with the ground state configuration of the specific species (e.g. n=5 for Rb).",
72+
type=str,
73+
help="The directory where the database will be saved. Default database/<species> for the sqdt mode, "
74+
"database/<species>_mqdt for the mqdt mode and database/misc for the misc mode.",
3075
)
31-
parser.add_argument(
32-
"--n-max",
33-
default=None,
34-
type=int,
35-
help="The maximum principal quantum number n for the states to be included in the database.",
76+
common.add_argument(
77+
"--log-level",
78+
default="INFO",
79+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
80+
help="set the logging level (default: INFO)",
3681
)
37-
parser.add_argument(
38-
"--nu-min",
82+
common.add_argument(
83+
"--warnings-as-exceptions",
84+
action="store_true",
85+
help="Treat warnings in rydstate as exceptions.",
86+
)
87+
common.add_argument(
88+
"--overwrite",
89+
action="store_true",
90+
help="Delete the database folder if it exists and create a new one.",
91+
)
92+
93+
# arguments shared by the species modes (sqdt and mqdt)
94+
species_common = argparse.ArgumentParser(add_help=False)
95+
species_common.add_argument("species", help="The species name to generate the database for (e.g. Rb).")
96+
species_common.add_argument(
97+
"--f-tot-min",
3998
default=None,
40-
type=int,
41-
help="The minimal effective principal quantum number nu for the states to be included in the database. "
42-
"Default 0 will include all low lying states.",
99+
type=float,
100+
help="The minimal total angular momentum quantum number f_tot for the states to be included in the database. "
101+
"Default 0 will include all states with small f_tot.",
43102
)
44-
parser.add_argument(
45-
"--nu-max",
103+
species_common.add_argument(
104+
"--f-tot-max",
46105
default=None,
47-
type=int,
48-
help="The maximum effective principal quantum number nu for the states to be included in the database.",
106+
type=float,
107+
help="The maximum total angular momentum quantum number f_tot for the states to be included in the database. "
108+
"Default inf will include all states with large f_tot.",
49109
)
50-
parser.add_argument(
110+
species_common.add_argument(
51111
"--max-delta-nu",
52112
default=float("inf"),
53113
type=float,
54114
help="The maximum difference in effective principal quantum number nu for matrix elements to be calculated.",
55115
)
56-
parser.add_argument(
116+
species_common.add_argument(
57117
"--all-nu-up-to",
58118
default=float("inf"),
59119
type=float,
60120
help="Calculate all matrix elements where at least one state has effective principal quantum number nu "
61121
"smaller than or equal to this value.",
62122
)
63-
parser.add_argument(
64-
"--f-max",
65-
default=None,
123+
124+
sqdt_parser = subparsers.add_parser(
125+
"sqdt",
126+
parents=[species_common, common],
127+
help="Generate the database for a species using single-channel quantum defect theory.",
128+
description="Generate the database for a species using single-channel quantum defect theory. "
129+
"The basis is defined via the n-range.",
130+
)
131+
sqdt_parser.add_argument(
132+
"--n-min",
133+
default=1,
134+
type=int,
135+
help="The minimal principal quantum number n for the states to be included in the database. "
136+
"Default 1 will start with the ground state configuration of the specific species (e.g. n=5 for Rb).",
137+
)
138+
sqdt_parser.add_argument(
139+
"--n-max",
140+
required=True,
141+
type=int,
142+
help="The maximum principal quantum number n for the states to be included in the database.",
143+
)
144+
145+
mqdt_parser = subparsers.add_parser(
146+
"mqdt",
147+
parents=[species_common, common],
148+
help="Generate the database for a species using multi-channel quantum defect theory.",
149+
description="Generate the database for a species using multi-channel quantum defect theory. "
150+
"The basis is defined via the nu-range.",
151+
)
152+
mqdt_parser.add_argument(
153+
"--nu-min",
154+
default=0,
66155
type=float,
67-
help="The maximum angular momentum quantum number f for misc database tables.",
156+
help="The minimal effective principal quantum number nu for the states to be included in the database. "
157+
"Default 0 will include all low lying states.",
68158
)
69-
parser.add_argument(
70-
"--directory",
71-
default=None,
72-
type=str,
73-
help="The directory where the database will be saved.",
159+
mqdt_parser.add_argument(
160+
"--nu-max",
161+
required=True,
162+
type=float,
163+
help="The maximum effective principal quantum number nu for the states to be included in the database.",
74164
)
75-
parser.add_argument(
76-
"--log-level",
77-
default="INFO",
78-
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
79-
help="set the logging level (default: INFO)",
165+
166+
misc_parser = subparsers.add_parser(
167+
"misc",
168+
parents=[common],
169+
help="Generate the misc database tables, which do not depend on a species.",
170+
description="Generate the misc database tables, which do not depend on a species.",
80171
)
81-
parser.add_argument(
82-
"--warnings-as-exceptions",
83-
action="store_true",
84-
help="Treat warnings in rydstate as exceptions.",
172+
misc_parser.add_argument(
173+
"--f-max",
174+
required=True,
175+
type=float,
176+
help="The maximum angular momentum quantum number f for misc database tables.",
85177
)
86-
parser.add_argument(
87-
"--overwrite",
88-
action="store_true",
89-
help="Delete the species folder if it exists and create a new one.",
90-
)
91-
92-
args = parser.parse_args()
93-
if args.species == "misc":
94-
if (
95-
args.n_min is not None
96-
or args.n_max is not None
97-
or args.nu_min is not None
98-
or args.nu_max is not None
99-
or args.max_delta_nu != float("inf")
100-
or args.all_nu_up_to != float("inf")
101-
):
102-
parser.error(
103-
"--n-min, --n-max, --nu-min, --nu-max, --max-delta-nu, and --all-nu-up-to are only valid "
104-
"when generating a species database."
105-
)
106-
elif args.f_max is not None:
107-
parser.error("--f-max is only valid when generating the misc database.")
108-
109-
directory = Path(args.directory) if args.directory is not None else Path("database") / args.species
178+
179+
return parser
180+
181+
182+
def prepare_directory(args: argparse.Namespace) -> Path:
183+
"""Create the (empty) database directory and change into it."""
184+
if args.directory is not None:
185+
directory = Path(args.directory)
186+
elif args.mode == "misc":
187+
directory = Path("database") / "misc"
188+
elif args.mode == "sqdt":
189+
directory = Path("database") / args.species
190+
elif args.mode == "mqdt":
191+
directory = Path("database") / f"{args.species}_mqdt"
192+
else:
193+
raise ValueError(f"Unknown mode: {args.mode}")
110194
directory = directory.resolve()
195+
111196
if directory.exists():
112-
if args.overwrite:
113-
shutil.rmtree(directory)
114-
else:
197+
if not args.overwrite:
115198
raise FileExistsError(f"The folder {directory} already exists. Use --overwrite to overwrite it.")
199+
check_is_generated_database(directory)
200+
shutil.rmtree(directory)
116201
directory.mkdir(parents=True)
117202
os.chdir(directory)
203+
return directory
118204

119-
configure_logging(args.log_level, directory, args.warnings_as_exceptions)
120205

121-
time_start = time.perf_counter()
122-
if args.species == "misc":
123-
if args.f_max is None:
124-
parser.error("--f-max is required when generating the misc database.")
125-
create_tables_for_misc(f_max=args.f_max, kappa_max=3)
126-
else:
127-
if args.n_max is None and args.nu_max is None:
128-
parser.error("At least one of --n-max or --nu-max must be provided.")
129-
130-
if args.n_min is None and args.n_max is None:
131-
n = None
132-
else:
133-
n_min = args.n_min if args.n_min is not None else 1
134-
n_max = args.n_max if args.n_max is not None else int(args.nu_max) + 10
135-
n = (n_min, n_max)
136-
137-
if args.nu_min is None and args.nu_max is None:
138-
nu = None
139-
else:
140-
nu_min = args.nu_min if args.nu_min is not None else 0
141-
nu_max = args.nu_max if args.nu_max is not None else args.n_max
142-
nu = (nu_min, nu_max)
143-
create_tables_for_one_species(
144-
args.species, n=n, nu=nu, max_delta_nu=args.max_delta_nu, all_nu_up_to=args.all_nu_up_to
206+
def check_is_generated_database(directory: Path) -> None:
207+
"""Raise if the directory contains anything but the files written by a previous run.
208+
209+
Since --overwrite deletes the whole directory tree, only ever delete a directory that looks like a
210+
previously generated database, i.e. that solely contains the log file and the parquet tables.
211+
"""
212+
unexpected = sorted(
213+
entry.name
214+
for entry in directory.iterdir()
215+
if not (entry.is_file() and (entry.name == "log" or entry.suffix == ".parquet"))
216+
)
217+
if unexpected:
218+
listed = ", ".join(unexpected[:5]) + (", ..." if len(unexpected) > 5 else "")
219+
raise FileExistsError(
220+
f"Refusing to overwrite the folder {directory}, since it does not look like a generated database "
221+
f"(it contains {listed}). Delete it manually if this is really what you want."
145222
)
146-
logger.info("Time taken: %.2f seconds", time.perf_counter() - time_start)
147-
log_memory_usage()
223+
224+
225+
def get_f_tot_range(args: argparse.Namespace) -> tuple[float, float] | None:
226+
"""Get the (f_tot_min, f_tot_max) range from the parsed arguments.
227+
228+
Returns None if neither the minimum nor the maximum is given, i.e. all f_tot values are included.
229+
"""
230+
if args.f_tot_min is None and args.f_tot_max is None:
231+
return None
232+
return (
233+
args.f_tot_min if args.f_tot_min is not None else 0,
234+
args.f_tot_max if args.f_tot_max is not None else float("inf"),
235+
)
148236

149237

150238
def log_memory_usage() -> None:
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
from rydstate.generate_database.generate_database import create_tables_for_misc, create_tables_for_one_species
1+
from rydstate.generate_database.generate_database import (
2+
create_tables_for_misc,
3+
create_tables_for_mqdt,
4+
create_tables_for_sqdt,
5+
)
26

3-
__all__ = ["create_tables_for_misc", "create_tables_for_one_species"]
7+
__all__ = ["create_tables_for_misc", "create_tables_for_mqdt", "create_tables_for_sqdt"]

0 commit comments

Comments
 (0)