Source code for odatse.scripts.separateT

# SPDX-License-Identifier: MPL-2.0
#
# ODAT-SE -- an open framework for data analysis
# Copyright (C) 2020- The University of Tokyo
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
This script separates MCMC data files into multiple files based on temperature values.
It reads MCMC result files and splits them into separate files for each temperature.
"""

import os
import sys
import argparse
import glob
try:
    from tqdm import tqdm  # Import tqdm for progress bar if available
except ImportError:
    tqdm = None  # Set tqdm to None if not available

[docs] def do_separate(filename: str, buffer_limit: int = 200_000) -> None: """ Separate a single MCMC data file into multiple files, one per temperature. Lines are routed to an output file keyed by the temperature value in the 3rd column (index 2). Unlike a simple "split whenever the value changes from the previous line", this groups all lines with the same temperature together even when temperatures are interleaved (as in exchange MC), so each temperature gets exactly one file. Temperatures are indexed in order of first appearance. Lines are buffered per temperature and flushed by open-append-close, so at most two file descriptors are in use at any time regardless of how many distinct temperatures the input contains (keeping one descriptor open per temperature exhausts the default file-descriptor limit for runs with hundreds of temperature points). Parameters ---------- filename : str Path to the MCMC data file to be separated. buffer_limit : int Total number of buffered data lines that triggers a flush to disk. Returns ------- None Files are written to disk with naming pattern: original_filename_T{index}.ext """ file_base, file_ext = os.path.splitext(filename) header = [] # header lines (comments starting with #) paths: dict = {} # temperature value (str) -> output file path buffers: dict = {} # temperature value (str) -> pending data lines pending = 0 # total buffered lines across all temperatures def flush() -> None: nonlocal pending for temperature, lines in buffers.items(): if not lines: continue with open(paths[temperature], "a") as out: out.writelines(lines) lines.clear() pending = 0 with open(filename, "r") as fp: try: for line in fp: # Preserve header lines (comments) if line.startswith("#"): header.append(line) continue # Split line into columns; skip blank / malformed lines items = line.split() if len(items) < 3: continue temperature = items[2] # temperature is in the 3rd column # Create the file the first time a temperature is seen, indexed # by first-appearance order, and write the header collected so # far (all comment lines precede the data in result.txt). if temperature not in paths: index = len(paths) new_file = file_base + f"_T{index}" + file_ext with open(new_file, "w") as out: out.writelines(header) out.write(f"# T (or beta) = {temperature}\n") paths[temperature] = new_file buffers[temperature] = [] buffers[temperature].append(line) pending += 1 if pending >= buffer_limit: flush() finally: # Always write out buffered lines, even on error. flush()
[docs] def main() -> None: """ Main function to parse arguments and process MCMC data files. Command line arguments: -d/--data_dir: Directory containing MCMC data -t/--file_type: File type of MCMC data (default: result.txt) --progress: Show progress bar input_files: Optional list of specific files to process Returns ------- None """ # Set up command line argument parser parser = argparse.ArgumentParser(description="Separate MCMC data file by temperature values") parser.add_argument("-d", "--data_dir", type=str, help="Directory of MCMC data") parser.add_argument("-t", "--file_type", type=str, default="result.txt", help="File type of MCMC data") parser.add_argument("--progress", action="store_true", default=False, help="Show progress bar.") parser.add_argument("input_files", nargs="*", help="Files to extract in combined format.") args = parser.parse_args() # Determine input files from arguments if args.input_files: # Use explicitly provided files input_files = args.input_files elif args.data_dir: # Find files matching pattern in data directory file_pattern = os.path.join(args.data_dir, "*", args.file_type) input_files = sorted(glob.glob(file_pattern)) else: input_files = [] # Nothing to do: warn rather than exiting silently with success. if not input_files: parser.error("no input files: specify input files or --data_dir") # Add progress bar if requested and tqdm is available if tqdm and args.progress: input_files = tqdm(input_files) # Process each input file for input_file in input_files: # Print progress message if not using progress bar if not args.progress or not tqdm: print("processing file {}...".format(input_file)) # Separate the file by temperature do_separate(input_file)
if __name__ == "__main__": main()