Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALPulsar 7.1.1.1-b246709
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
lalpulsar_CopyPublicSFTs.py
Go to the documentation of this file.
1##python
2# Copyright (C) 2022 Karl Wette
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with with program; see the file COPYING. If not, write to the
16# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17# MA 02110-1301 USA
18
19## \file
20## \ingroup lalpulsar_bin_SFTTools
21"""Copy SFTs between directories. The destination directory is organised
22following the convention detailed in the SFT spec (T040164)."""
23
24import argparse
25import os
26import sys
27import time
28import shutil
29from contextlib import contextmanager
30from concurrent.futures import ProcessPoolExecutor, as_completed
31from tqdm import tqdm
32
33from lal import LALERRORBIT, LALWARNINGBIT, LALINFOBIT, LALTRACEBIT
34from lal import GetDebugLevel, ClobberDebugLevel
35
36from lalpulsar import git_version
37from lalpulsar import ValidateSFTFile, SFTErrorMessage
38from lalpulsar.public_sft_directory import public_sft_directory
39from lalpulsar.public_sft_directory import public_sft_directory_readme_md
40
41__author__ = "Karl Wette <karl.wette@ligo.org>"
42__version__ = git_version.id
43__date__ = git_version.date
44
45
46@contextmanager
48 saveDebugLevel = GetDebugLevel()
49 silentDebugLevel = saveDebugLevel & ~(
50 LALERRORBIT | LALWARNINGBIT | LALINFOBIT | LALTRACEBIT
51 )
52 ClobberDebugLevel(silentDebugLevel)
53 try:
54 yield None
55 finally:
56 ClobberDebugLevel(saveDebugLevel)
57
58
60 # parse command line
61 parser = argparse.ArgumentParser(description=__doc__)
62 parser.add_argument(
63 "-p", "--processes", type=int, default=1, help="number of copying processes"
64 )
65 parser.add_argument(
66 "-n",
67 "--no-validate",
68 dest="validate",
69 action="store_false",
70 help="do not validate destination SFTs",
71 )
72 parser.add_argument(
73 "--readme-md",
74 dest="readme_md",
75 action="store_true",
76 help="write README.md in the destination directory",
77 )
78 parser.add_argument("source_directory", type=str, help="SFT source directory")
79 parser.add_argument("dest_directory", type=str, help="SFT destination directory")
80 args = parser.parse_args()
81
82 # check arguments
83 if args.processes <= 0:
84 parser.error("--processes must be strictly positive")
85 if not os.path.isdir(args.source_directory):
86 parser.error("source_directory is not a directory")
87 if not os.path.isdir(args.dest_directory):
88 parser.error("dest_directory is not a directory")
89
90 return args
91
92
93def find_SFT_files(source_directory, dest_directory):
94 dest_dirs = set()
95 src_dest_paths = []
96
97 # find source SFT files
98 t0 = time.time()
99 num_SFTs = 0
100 print_progress = 100
101 print_progress_step = 100
102 print_progress_max = 1000
103 for src_root, _, src_files in os.walk(source_directory):
104 for src_file in src_files:
105 if src_file.endswith(".sft"):
106 src_path = os.path.join(src_root, src_file)
107 _, src_name = os.path.split(src_path)
108
109 # build SFT destination directory
110 dest_dir = os.path.join(dest_directory, public_sft_directory(src_name))
111 dest_path = os.path.join(dest_dir, src_name)
112
113 # add to outputs
114 dest_dirs.add(dest_dir)
115 src_dest_paths.append((src_path, dest_path))
116
117 # print progress
118 num_SFTs += 1
119 if num_SFTs % print_progress == 0:
120 dt = time.time() - t0
121 print(
122 f"{__file__}: found {num_SFTs} SFTs in {dt:0.1f} seconds",
123 flush=True,
124 )
125 print_progress += print_progress_step
126 if print_progress == print_progress_max:
127 print_progress_step *= 10
128 print_progress_max *= 10
129
130 print(f"{__file__}: found {num_SFTs} SFTs\n", flush=True)
131
132 return dest_dirs, src_dest_paths
133
134
135def make_dest_dirs(dest_dirs):
136 # make destination SFT directories
137 print(f"{__file__}: making {len(dest_dirs)} directories ...", flush=True)
138 for dest_dir in dest_dirs:
139 if not os.path.isdir(dest_dir):
140 os.makedirs(dest_dir)
141 print(f"{__file__}: making {len(dest_dirs)} directories ... done\n", flush=True)
142
143
144def copy_SFT_file(src_path, dest_path, validate):
145 # copy SFT with a temporary extension
146 tmp_dest_path = dest_path + "_TO_BE_VALIDATED"
147 shutil.copyfile(src_path, tmp_dest_path)
148
149 # validate SFT if requested
150 if validate:
151 with silence_xlal_error_messages() as _:
152 validate_errorcode = ValidateSFTFile(tmp_dest_path)
153 if validate_errorcode != 0:
154 validate_errorstr = SFTErrorMessage(validate_errorcode)
155 return (tmp_dest_path, validate_errorstr)
156
157 # move destination SFT to final location
158 os.rename(tmp_dest_path, dest_path)
159
160 return None
161
162
163def copy_all_SFT_files(src_dest_paths, validate, processes):
164 validate_errors = []
165
166 # create executor
167 print(f"{__file__}: copying {len(src_dest_paths)} SFTs ...", flush=True)
168 with ProcessPoolExecutor(max_workers=args.processes) as executor:
169 # submit tasks
170 pool = [
171 executor.submit(copy_SFT_file, src_path, dest_path, validate)
172 for src_path, dest_path in src_dest_paths
173 ]
174
175 # collect tasks
176 for task in tqdm(as_completed(pool), total=len(pool)):
177 validate_error = task.result()
178 if validate_error is not None:
179 validate_errors.append(validate_error)
180
181 print("")
182
183 # show any validation errors
184 if validate_errors:
185 print(
186 f"{__file__}: failed to validate {len(validate_errors)} SFTs after copying:",
187 flush=True,
188 )
189 for tmp_dest_path, validate_errorstr in validate_errors:
190 print(f" {tmp_dest_path}\n {validate_errorstr}", flush=True)
191 sys.exit(1)
192
193 print(f"{__file__}: copying {len(src_dest_paths)} SFTs ... done\n", flush=True)
194
195
196def write_readme_md(dest_directory):
197 # write README.md
198 with open(os.path.join(dest_directory, "README.md"), "w") as f:
200
201
202if __name__ == "__main__":
204
205 dest_dirs, src_dest_paths = find_SFT_files(
206 args.source_directory, args.dest_directory
207 )
208
209 make_dest_dirs(dest_dirs)
210
211 copy_all_SFT_files(src_dest_paths, args.validate, args.processes)
212
213 if args.readme_md:
214 write_readme_md(args.dest_directory)
215
216 print(f"{__file__}: DONE", flush=True)
const char * SFTErrorMessage(int errorcode)
int ValidateSFTFile(const char *fname)
Verify that the contents of a SFT file are valid.
def copy_all_SFT_files(src_dest_paths, validate, processes)
def copy_SFT_file(src_path, dest_path, validate)
def find_SFT_files(source_directory, dest_directory)