|
8 | 8 | import time |
9 | 9 | from pathlib import Path |
10 | 10 |
|
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 | +) |
12 | 16 |
|
13 | 17 | logger = logging.getLogger(__name__) |
14 | 18 |
|
15 | 19 |
|
16 | | -def main() -> None: # noqa: C901, PLR0912, PLR0915 |
| 20 | +def main() -> None: |
17 | 21 | """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.""" |
18 | 55 | parser = argparse.ArgumentParser( |
19 | 56 | description="Generate a database, containing energies and matrix elements, for a given species.", |
20 | 57 | 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 | + ), |
22 | 64 | ) |
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", |
26 | 71 | 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.", |
30 | 75 | ) |
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)", |
36 | 81 | ) |
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", |
39 | 98 | 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.", |
43 | 102 | ) |
44 | | - parser.add_argument( |
45 | | - "--nu-max", |
| 103 | + species_common.add_argument( |
| 104 | + "--f-tot-max", |
46 | 105 | 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.", |
49 | 109 | ) |
50 | | - parser.add_argument( |
| 110 | + species_common.add_argument( |
51 | 111 | "--max-delta-nu", |
52 | 112 | default=float("inf"), |
53 | 113 | type=float, |
54 | 114 | help="The maximum difference in effective principal quantum number nu for matrix elements to be calculated.", |
55 | 115 | ) |
56 | | - parser.add_argument( |
| 116 | + species_common.add_argument( |
57 | 117 | "--all-nu-up-to", |
58 | 118 | default=float("inf"), |
59 | 119 | type=float, |
60 | 120 | help="Calculate all matrix elements where at least one state has effective principal quantum number nu " |
61 | 121 | "smaller than or equal to this value.", |
62 | 122 | ) |
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, |
66 | 155 | 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.", |
68 | 158 | ) |
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.", |
74 | 164 | ) |
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.", |
80 | 171 | ) |
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.", |
85 | 177 | ) |
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}") |
110 | 194 | directory = directory.resolve() |
| 195 | + |
111 | 196 | if directory.exists(): |
112 | | - if args.overwrite: |
113 | | - shutil.rmtree(directory) |
114 | | - else: |
| 197 | + if not args.overwrite: |
115 | 198 | raise FileExistsError(f"The folder {directory} already exists. Use --overwrite to overwrite it.") |
| 199 | + check_is_generated_database(directory) |
| 200 | + shutil.rmtree(directory) |
116 | 201 | directory.mkdir(parents=True) |
117 | 202 | os.chdir(directory) |
| 203 | + return directory |
118 | 204 |
|
119 | | - configure_logging(args.log_level, directory, args.warnings_as_exceptions) |
120 | 205 |
|
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." |
145 | 222 | ) |
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 | + ) |
148 | 236 |
|
149 | 237 |
|
150 | 238 | def log_memory_usage() -> None: |
|
0 commit comments