omecp/io.rs
1//! File I/O utilities for geometry and checkpoint files.
2//!
3//! This module provides functions for reading and writing molecular geometries
4//! in various formats including XYZ, Gaussian input/output, and checkpoint files.
5
6use crate::geometry::Geometry;
7use std::fs;
8use std::io::Result;
9use std::path::Path;
10
11/// Writes a molecular geometry to an XYZ file.
12///
13/// The XYZ format is a simple plain-text format for molecular geometries,
14/// widely used in chemistry software. It consists of:
15/// 1. Number of atoms
16/// 2. A comment line (empty in this implementation)
17/// 3. Lines for each atom: Element X Y Z
18///
19/// # Arguments
20///
21/// * `geom` - The molecular geometry to write
22/// * `path` - The path to the output XYZ file
23///
24/// # Returns
25///
26/// Returns `Ok(())` on success, or an `std::io::Error` if file writing fails.
27///
28/// # Examples
29///
30/// ```
31/// use omecp::geometry::Geometry;
32/// use omecp::io;
33/// use std::path::Path;
34///
35/// fn main() -> std::io::Result<()> {
36/// let elements = vec!["C".to_string(), "H".to_string()];
37/// let coords = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0];
38/// let geometry = Geometry::new(elements, coords);
39///
40/// io::write_xyz(&geometry, Path::new("molecule.xyz"))?;
41/// std::fs::remove_file("molecule.xyz")?;
42/// Ok(())
43/// }
44/// ```
45pub fn write_xyz(geom: &Geometry, path: &Path) -> Result<()> {
46 let mut content = format!("{}\n\n", geom.num_atoms);
47
48 for i in 0..geom.num_atoms {
49 let coords = geom.get_atom_coords(i);
50 // Coordinates are already in Angstrom - write directly
51 content.push_str(&format!(
52 "{} {:.8} {:.8} {:.8}\n",
53 geom.elements[i], coords[0], coords[1], coords[2]
54 ));
55 }
56
57 fs::write(path, content)
58}
59
60/// Cleans Gaussian keywords by removing comments and extra whitespace.
61///
62/// This function processes multi-line keyword strings to remove:
63/// - Lines starting with '#' (full-line comments)
64/// - Inline comments (text after '#' on the same line as valid keywords)
65/// - Empty lines
66/// - Leading and trailing whitespace from each line
67///
68/// The remaining valid keywords are joined with single spaces to create
69/// a clean string suitable for Gaussian route sections. If all content
70/// is filtered out (e.g., only comments), an empty string is returned,
71/// which is handled gracefully by the Gaussian header generation.
72///
73/// # Arguments
74///
75/// * `keywords` - The raw keyword string that may contain comments and extra whitespace
76///
77/// # Returns
78///
79/// Returns a `String` containing cleaned keywords joined with single spaces.
80/// Returns an empty string if no valid keywords remain after filtering.
81///
82/// # Examples
83///
84/// ```
85/// use omecp::io;
86///
87/// let raw_keywords = "# This is a comment\nTD(NStates=5)\n# Another comment\nRoot=1\n\n";
88/// let cleaned = io::clean_gaussian_keywords(raw_keywords);
89/// assert_eq!(cleaned, "TD(NStates=5) Root=1");
90///
91/// // Empty result when only comments are present
92/// let only_comments = "# Only comments\n# More comments";
93/// let empty_result = io::clean_gaussian_keywords(only_comments);
94/// assert_eq!(empty_result, "");
95///
96/// // Inline comments are removed
97/// let inline_comments = "TD(NStates=5) # This is an inline comment\nRoot=1 # Another inline comment";
98/// let cleaned_inline = io::clean_gaussian_keywords(inline_comments);
99/// assert_eq!(cleaned_inline, "TD(NStates=5) Root=1");
100/// ```
101pub fn clean_gaussian_keywords(keywords: &str) -> String {
102 let result = keywords
103 .lines()
104 .filter_map(|line| {
105 let trimmed = line.trim();
106
107 // Skip empty lines
108 if trimmed.is_empty() {
109 return None;
110 }
111
112 // Skip lines that start with '#' (full-line comments)
113 if trimmed.starts_with('#') {
114 return None;
115 }
116
117 // Remove inline comments (everything after '#' on the same line)
118 let cleaned_line = if let Some(comment_pos) = trimmed.find('#') {
119 trimmed[..comment_pos].trim()
120 } else {
121 trimmed
122 };
123
124 // Return the cleaned line if it's not empty after comment removal
125 if cleaned_line.is_empty() {
126 None
127 } else {
128 Some(cleaned_line)
129 }
130 })
131 .collect::<Vec<_>>()
132 .join(" ");
133
134 // The result may be empty if all content was filtered out (e.g., only comments)
135 // This is acceptable and will be handled gracefully by the caller
136 result
137}
138
139/// Cleans keywords by removing comments and extra whitespace (generic version).
140///
141/// This function works for any quantum chemistry program by removing:
142/// - Lines starting with '#' (full-line comments)
143/// - Inline comments (text after '#' on the same line as valid keywords)
144/// - Empty lines
145/// - Leading and trailing whitespace from each line
146///
147/// The remaining valid keywords are joined with single spaces.
148///
149/// # Arguments
150///
151/// * `keywords` - The raw keyword string that may contain comments and extra whitespace
152///
153/// # Returns
154///
155/// Returns a `String` containing cleaned keywords joined with single spaces.
156pub fn clean_keywords(keywords: &str) -> String {
157 keywords
158 .lines()
159 .filter_map(|line| {
160 let trimmed = line.trim();
161
162 // Skip empty lines
163 if trimmed.is_empty() {
164 return None;
165 }
166
167 // Skip lines that start with '#' (full-line comments)
168 if trimmed.starts_with('#') {
169 return None;
170 }
171
172 // Remove inline comments (everything after '#' on the same line)
173 let cleaned_line = if let Some(comment_pos) = trimmed.find('#') {
174 trimmed[..comment_pos].trim()
175 } else {
176 trimmed
177 };
178
179 // Return the cleaned line if it's not empty after comment removal
180 if cleaned_line.is_empty() {
181 None
182 } else {
183 Some(cleaned_line)
184 }
185 })
186 .collect::<Vec<_>>()
187 .join(" ")
188}
189
190/// Builds a Gaussian input file header string (legacy interface).
191///
192/// This function is maintained for backward compatibility. New code should use
193/// `build_program_header()` which includes dynamic method modification.
194///
195/// # Arguments
196///
197/// * `config` - The global configuration for the MECP calculation
198/// * `charge` - Molecular charge for the current state
199/// * `mult` - Spin multiplicity for the current state
200/// * `td` - TD-DFT keywords (e.g., "TD(NStates=5,Root=1)"), may contain comments
201///
202/// # Returns
203///
204/// Returns a `String` containing the formatted Gaussian input header with clean route section.
205pub fn build_gaussian_header(
206 config: &crate::config::Config,
207 charge: i32,
208 mult: usize,
209 td: &str,
210) -> String {
211 // Use the dynamic method modification for consistency
212 let modified_method =
213 modify_method_for_run_mode(&config.method, config.program, config.run_mode);
214
215 let mut temp_config = config.clone();
216 temp_config.method = modified_method;
217
218 build_gaussian_header_internal(&temp_config, charge, mult, td)
219}
220
221/// Internal Gaussian header builder that doesn't modify the method string.
222///
223/// This function constructs the route section and title card for a Gaussian
224/// input file based on the provided configuration and state-specific parameters.
225/// It assumes the method string has already been modified by `modify_method_for_run_mode()`.
226///
227/// # Arguments
228///
229/// * `config` - The global configuration with pre-modified method string
230/// * `charge` - Molecular charge for the current state
231/// * `mult` - Spin multiplicity for the current state
232/// * `td` - TD-DFT keywords (e.g., "TD(NStates=5,Root=1)"), may contain comments
233///
234/// # Returns
235///
236/// Returns a `String` containing the formatted Gaussian input header.
237fn build_gaussian_header_internal(
238 config: &crate::config::Config,
239 charge: i32,
240 mult: usize,
241 td: &str,
242) -> String {
243 build_gaussian_header_internal_with_chk(config, charge, mult, td, "calc.chk")
244}
245
246fn build_gaussian_header_internal_with_chk(
247 config: &crate::config::Config,
248 charge: i32,
249 mult: usize,
250 td: &str,
251 chk_file: &str,
252) -> String {
253 // Use the method string as-is (already modified by modify_method_for_run_mode)
254 let method_str = &config.method;
255
256 // Clean TD-DFT keywords to remove comments and extra whitespace
257 let clean_td = clean_gaussian_keywords(td);
258
259 // Build route section
260 let route_section = if clean_td.is_empty() {
261 format!("# {} nosymm", method_str)
262 } else {
263 format!("# {} {} nosymm", method_str, clean_td)
264 };
265
266 format!(
267 "%chk={}\n%nprocshared={} \n%mem={} \n{}\n\n Title Card \n\n{} {}",
268 chk_file, config.nprocs, config.mem, route_section, charge, mult
269 )
270}
271
272/// Builds an ORCA input file header string with basename.
273///
274/// This function constructs the header for an ORCA input file based on the
275/// provided configuration and state-specific parameters. It requires a
276/// basename for proper .gbw file path construction.
277///
278/// # Arguments
279///
280/// * `config` - The global configuration for the MECP calculation
281/// * `charge` - Molecular charge for the current state
282/// * `mult` - Spin multiplicity for the current state
283/// * `tail` - Additional ORCA keywords (tail section content)
284/// * `input_basename` - Full path prefix for .gbw files
285///
286/// # Returns
287///
288/// Returns a `String` containing the formatted ORCA input header.
289
290pub fn build_orca_header(
291 config: &crate::config::Config,
292 charge: i32,
293 mult: usize,
294 tail: &str,
295 input_basename: &str,
296) -> String {
297 // Use the dynamic method modification for consistency
298 let modified_method =
299 modify_method_for_run_mode(&config.method, config.program, config.run_mode);
300
301 let mut temp_config = config.clone();
302 temp_config.method = modified_method;
303
304 build_orca_header_internal(&temp_config, charge, mult, tail, input_basename, None)
305}
306
307
308fn build_orca_header_internal(
309 config: &crate::config::Config,
310 charge: i32,
311 mult: usize,
312 tail: &str,
313 input_basename: &str,
314 chk_file: Option<&str>,
315) -> String {
316 // Use the method string as-is (already modified by modify_method_for_run_mode)
317 let method_str = &config.method;
318
319 // Clean tail keywords to remove comments
320 let clean_tail = clean_keywords(tail);
321
322 // Build the method line
323 let method_line = if clean_tail.is_empty() {
324 format!("! {}", method_str)
325 } else {
326 format!("! {} {}", method_str, clean_tail)
327 };
328
329 // If chk_file is provided (e.g. for chain-linking steps), use it directly.
330 // Otherwise, fall back to constructing it from input_basename.
331 let method_line = if method_line.contains("***") {
332 let gbw_file = if let Some(chk) = chk_file {
333 chk.to_string()
334 } else if mult == config.mult_state_a {
335 format!("{}_state_A.gbw", input_basename)
336 } else {
337 format!("{}_state_B.gbw", input_basename)
338 };
339 method_line.replace("***", &gbw_file)
340 } else {
341 method_line
342 };
343
344 format!(
345 "%pal nprocs {} end\n%maxcore {} \n{}\n\n *xyz {} {}",
346 config.nprocs, config.mem, method_line, charge, mult
347 )
348}
349
350/// Builds an XTB input file header string.
351///
352/// XTB uses a simple format with just charge and multiplicity information.
353/// The method is typically specified via command line arguments.
354///
355/// # Arguments
356///
357/// * `config` - The global configuration for the MECP calculation
358/// * `charge` - Molecular charge for the current state
359/// * `mult` - Spin multiplicity for the current state
360/// * `_tail` - Additional keywords (unused for XTB)
361///
362/// # Returns
363///
364/// Returns a `String` containing the formatted XTB input header.
365pub fn build_xtb_header(
366 _config: &crate::config::Config,
367 charge: i32,
368 mult: usize,
369 _tail: &str,
370) -> String {
371 // XTB uses a simple format - just charge and multiplicity
372 // The method is specified via command line arguments
373 format!("$chrg {}\n$uhf {}", charge, mult - 1)
374}
375
376/// Builds a BAGEL input file header string.
377///
378/// BAGEL uses JSON format and requires a model file. This function creates
379/// the basic structure that will be filled with geometry and other parameters.
380///
381/// # Arguments
382///
383/// * `config` - The global configuration for the MECP calculation
384/// * `charge` - Molecular charge for the current state
385/// * `mult` - Spin multiplicity for the current state
386/// * `state` - Electronic state index for multireference calculations
387///
388/// # Returns
389///
390/// Returns a `String` containing the formatted BAGEL input header.
391pub fn build_bagel_header(
392 config: &crate::config::Config,
393 charge: i32,
394 mult: usize,
395 state: usize,
396) -> String {
397 // BAGEL uses JSON format - this is a basic template
398 // The actual geometry will be inserted by the writeBAGEL equivalent function
399 let basis = if config.basis_set.is_empty() {
400 "cc-pVDZ"
401 } else {
402 &config.basis_set
403 };
404 let df_basis = if config.basis_set.is_empty() {
405 "cc-pVDZ-jkfit".to_string()
406 } else {
407 format!("{}-jkfit", config.basis_set)
408 };
409
410 format!(
411 r#"{{
412 "bagel" : [
413 {{
414 "title" : "molecule",
415 "basis" : "{}",
416 "df_basis" : "{}",
417 "charge" : {},
418 "nspin" : {},
419 "target" : {},
420 "geometry" : [
421 // Geometry will be inserted here
422 ]
423 }}
424 ]
425}}"#,
426 basis,
427 df_basis,
428 charge,
429 mult - 1, // nspin = 2S where mult = 2S+1
430 state
431 )
432}
433
434/// Dynamically modifies a QM method string based on run mode and program.
435///
436/// This function implements the core logic,
437/// adding program-specific keywords and run mode-specific modifications to the method string.
438/// This ensures that calculations use the correct keywords for each scenario.
439///
440/// # Method Modification Logic
441///
442/// 1. **Syntax Correction** (added for ORCA):
443/// - Replaces Gaussian-style basis set separators (`/`) with spaces.
444/// - E.g., "B3LYP/6-31G*" -> "B3LYP 6-31G*"
445///
446/// 2. **Program-specific keywords** (always added):
447/// - Gaussian: `force` (for gradient calculations)
448/// - ORCA: `engrad` (for energy and gradient calculations)
449/// - XTB/BAGEL: No modification needed
450///
451/// 3. **Stability keywords** (added for `Stable` mode):
452/// - Gaussian: `stable=opt` (perform stability analysis and reoptimize if unstable)
453/// - ORCA: `%scf stabperform true StabRestartUHFifUnstable true end` (stability analysis)
454///
455/// 4. **Guess keywords** (added for all modes except `NoRead`):
456/// - Gaussian: `guess=read` (read initial guess from checkpoint)
457/// - ORCA: `!moread` with `%moinp "***"` (read molecular orbitals)
458///
459/// # Arguments
460///
461/// * `method` - The base QM method string (e.g., "B3LYP/6-31G*")
462/// * `program` - The quantum chemistry program being used
463/// * `run_mode` - The execution mode for the calculation
464///
465/// # Returns
466///
467/// Returns a `String` containing the modified method with appropriate keywords added.
468///
469/// # Examples
470///
471/// ```
472/// use omecp::config::{QMProgram, RunMode};
473/// use omecp::io;
474///
475/// // Normal mode with Gaussian
476/// let modified = io::modify_method_for_run_mode("B3LYP/6-31G*", QMProgram::Gaussian, RunMode::Normal);
477/// assert_eq!(modified, "B3LYP/6-31G* force guess=read");
478///
479/// // Normal mode with ORCA (handling Gaussian syntax)
480/// let modified = io::modify_method_for_run_mode("B3LYP/6-31G*", QMProgram::Orca, RunMode::Normal);
481/// assert!(modified.contains("B3LYP 6-31G*"));
482/// assert!(modified.contains("engrad"));
483/// ```
484pub fn modify_method_for_run_mode(
485 method: &str,
486 program: crate::config::QMProgram,
487 run_mode: crate::config::RunMode,
488) -> String {
489 let mut modified_method = method.to_string();
490
491 // Fix Gaussian-style method/basis syntax (e.g., "B3LYP/6-31G*") for ORCA
492 if program == crate::config::QMProgram::Orca && modified_method.contains('/') {
493 modified_method = modified_method.replace('/', " ");
494 }
495
496 // Add program-specific keywords
497 match program {
498 crate::config::QMProgram::Gaussian | crate::config::QMProgram::Custom => {
499 if !modified_method.is_empty() {
500 modified_method.push_str(" force");
501 }
502 }
503 crate::config::QMProgram::Orca => {
504 if !modified_method.is_empty() {
505 modified_method.push_str(" engrad");
506 }
507 }
508 // XTB and BAGEL don't need method modification
509 _ => {}
510 }
511
512 // Add stability keywords for stable mode
513 if run_mode == crate::config::RunMode::Stable && !modified_method.is_empty() {
514 match program {
515 crate::config::QMProgram::Gaussian | crate::config::QMProgram::Custom => {
516 modified_method.push_str(" stable=opt");
517 }
518 crate::config::QMProgram::Orca => {
519 modified_method
520 .push_str("\n %scf stabperform true StabRestartUHFifUnstable true end \n");
521 }
522 _ => {}
523 }
524 }
525
526 // Add guess keywords
527 if run_mode != crate::config::RunMode::NoRead && !modified_method.is_empty() {
528 match program {
529 crate::config::QMProgram::Gaussian | crate::config::QMProgram::Custom => {
530 modified_method.push_str(" guess=read");
531 }
532 crate::config::QMProgram::Orca => {
533 modified_method.push_str("\n!moread \n %moinp \"***\"\n");
534 }
535 _ => {}
536 }
537 }
538
539 modified_method
540}
541
542/// Builds a program-specific input file header string.
543///
544/// This function dispatches to the appropriate header building function
545/// based on the quantum chemistry program specified in the configuration.
546/// It now uses dynamic method modification to ensure run mode compatibility.
547///
548/// **Note**: For ORCA programs, this function will panic if no input basename is provided.
549/// Use `build_program_header_with_basename()` instead for ORCA calculations.
550///
551/// # Arguments
552///
553/// * `config` - The global configuration for the MECP calculation
554/// * `charge` - Molecular charge for the current state
555/// * `mult` - Spin multiplicity for the current state
556/// * `td_or_tail` - TD-DFT keywords (Gaussian) or tail section content (other programs)
557/// * `state` - Electronic state index (used for BAGEL)
558///
559/// # Returns
560///
561/// Returns a `String` containing the formatted input header for the specified program.
562///
563/// # Panics
564///
565/// Panics if `config.program` is `QMProgram::Orca` since ORCA requires an input basename.
566///
567/// # Examples
568///
569/// ```
570/// use omecp::config::{Config, QMProgram, RunMode};
571/// use omecp::io;
572///
573/// let mut config = Config::default();
574/// config.program = QMProgram::Gaussian; // Works for Gaussian
575/// config.method = "B3LYP/6-31G*".to_string();
576/// config.run_mode = RunMode::Normal;
577///
578/// let header = io::build_program_header(&config, 0, 1, "", 0);
579/// println!("{}", header);
580/// ```
581pub fn build_program_header(
582 config: &crate::config::Config,
583 charge: i32,
584 mult: usize,
585 td_or_tail: &str,
586 state: usize,
587) -> String {
588 if config.program == crate::config::QMProgram::Orca {
589 panic!("ORCA requires input basename for .gbw file paths. Use build_program_header_with_basename() instead.");
590 }
591 build_program_header_with_chk(config, charge, mult, td_or_tail, state, None, None)
592}
593
594/// Builds a program-specific input file header string with input basename for ORCA .gbw paths.
595///
596/// This function is specifically designed for cases where you need to specify the basename
597/// of the input file for ORCA calculations, which is used to construct proper .gbw file paths
598/// (e.g., "calc/state_A.gbw" instead of "running_dir/state_A.gbw").
599///
600/// # Arguments
601///
602/// * `config` - The global configuration for the MECP calculation
603/// * `charge` - Molecular charge for the current state
604/// * `mult` - Spin multiplicity for the current state
605/// * `td_or_tail` - TD-DFT keywords (Gaussian) or tail section content (ORCA)
606/// * `state` - State index for multi-reference calculations (BAGEL)
607/// * `input_basename` - Basename of input file for ORCA .gbw paths (e.g., "calc" for "calc.inp")
608///
609/// # Returns
610///
611/// Returns a `String` containing the formatted input header for the specified program.
612///
613/// # Examples
614///
615/// ```
616/// use omecp::config::{Config, QMProgram, RunMode};
617/// use omecp::io;
618///
619/// let mut config = Config::default();
620/// config.program = QMProgram::Orca;
621/// config.method = "B3LYP def2-SVP".to_string();
622/// config.run_mode = RunMode::Read;
623///
624/// // This will generate ORCA header with "calc/compound_xyz_123_state_A.gbw" paths
625/// let header = io::build_program_header_with_basename(&config, 0, 1, "", 0, "calc/compound_xyz_123");
626/// ```
627pub fn build_program_header_with_basename(
628 config: &crate::config::Config,
629 charge: i32,
630 mult: usize,
631 td_or_tail: &str,
632 state: usize,
633 input_basename: &str,
634) -> String {
635 build_program_header_with_chk(
636 config,
637 charge,
638 mult,
639 td_or_tail,
640 state,
641 None,
642 Some(input_basename),
643 )
644}
645
646/// Builds a program-specific input file header with custom checkpoint/GBW file support.
647///
648/// This function generates headers for any supported QM program, allowing precise control
649/// over the checkpoint or wavefunction file usage. This is critical for:
650/// - Gaussian: Specifying the `%chk` file path.
651/// - ORCA: Specifying the `%moinp` file path for chain-linked restarts.
652///
653/// It automatically applies run-mode specific modifications (like `force`, `guess=read`,
654/// or `!moread`) to the method string.
655///
656/// # Arguments
657///
658/// * `config` - The global configuration.
659/// * `charge` - Molecular charge.
660/// * `mult` - Spin multiplicity.
661/// * `td_or_tail` - TD-DFT keywords (Gaussian) or tail content (ORCA).
662/// * `state` - Electronic state index (for BAGEL).
663/// * `chk_file` - Optional custom checkpoint/GBW file path.
664/// - For Gaussian: Sets `%chk={chk_file}`.
665/// - For ORCA: Sets `%moinp "{chk_file}"` if `!moread` is active.
666/// * `input_basename` - Optional basename for default naming fallback (required for ORCA if chk_file is None).
667///
668/// # Returns
669///
670/// Returns the formatted input header string.
671
672pub fn build_program_header_with_chk(
673 config: &crate::config::Config,
674 charge: i32,
675 mult: usize,
676 td_or_tail: &str,
677 state: usize,
678 chk_file: Option<&str>,
679 input_basename: Option<&str>,
680) -> String {
681 // Get dynamically modified method based on run mode and program
682 let modified_method =
683 modify_method_for_run_mode(&config.method, config.program, config.run_mode);
684
685 // Create temporary config with modified method for header generation
686 let mut temp_config = config.clone();
687 temp_config.method = modified_method;
688
689 // Determine checkpoint file name
690 let checkpoint_file = chk_file.unwrap_or(
691 // Default checkpoint file names based on charge/mult
692 if mult == config.mult_state_a {
693 "state_A.chk"
694 } else {
695 "state_B.chk"
696 },
697 );
698
699 match config.program {
700 crate::config::QMProgram::Gaussian => build_gaussian_header_internal_with_chk(
701 &temp_config,
702 charge,
703 mult,
704 td_or_tail,
705 checkpoint_file,
706 ),
707 crate::config::QMProgram::Orca => {
708 let basename =
709 input_basename.expect("ORCA requires input_basename parameter for .gbw file paths");
710 // Pass chk_file explicitly to build_orca_header_internal.
711 // In Gaussian this parameter is 'checkpoint_file' (unwrapped), but for Orca
712 // we want the Option to allow fallback to basename logic if not provided.
713 // However, the 'checkpoint_file' variable above unwrap_or's it with "state_A.chk".
714 // We should pass the ORIGINAL chk_file option to Orca builder to preserve None.
715 build_orca_header_internal(&temp_config, charge, mult, td_or_tail, basename, chk_file)
716 }
717 crate::config::QMProgram::Xtb => build_xtb_header(&temp_config, charge, mult, td_or_tail),
718 crate::config::QMProgram::Bagel => build_bagel_header(&temp_config, charge, mult, state),
719 crate::config::QMProgram::Custom => {
720 // For custom programs, fall back to Gaussian format
721 // Users can override this via custom interface files
722 build_gaussian_header_internal_with_chk(
723 &temp_config,
724 charge,
725 mult,
726 td_or_tail,
727 checkpoint_file,
728 )
729 }
730 }
731}
732