From 62d3bacba65b73322cb403edd5dc51e0c7197835 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Thu, 27 Jul 2023 15:27:16 -0400 Subject: [PATCH] Support for .gz and .bz2 files, and multi-structure .xyz files * Handle .gz and .bz2 files for .sdf and .xyz extensions. * Handle multi-structure XYZ files with a blank line between records. --- HISTORY.rst | 4 + read_structure_step/formats/sdf/sdf.py | 18 +- read_structure_step/formats/xyz/xyz.py | 456 +++++++++++++++++-------- read_structure_step/utils.py | 13 +- tests/data/3TR_model.sdf.bz2 | Bin 0 -> 288 bytes tests/data/3TR_model.sdf.gz | Bin 0 -> 298 bytes tests/data/tmQM_test.xyz.bz2 | Bin 0 -> 13395 bytes tests/data/tmQM_test.xyz.gz | Bin 0 -> 16514 bytes tests/test_formats.py | 26 +- 9 files changed, 365 insertions(+), 152 deletions(-) create mode 100644 tests/data/3TR_model.sdf.bz2 create mode 100644 tests/data/3TR_model.sdf.gz create mode 100644 tests/data/tmQM_test.xyz.bz2 create mode 100644 tests/data/tmQM_test.xyz.gz diff --git a/HISTORY.rst b/HISTORY.rst index 3e87ea4..113faf4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,10 @@ History ======= +2023.7.27 -- Support for .gz and .bz2 files, and multi-structure .xyz files + * Handle .gz and .bz2 files for .sdf and .xyz extensions. + * Handle multi-structure XYZ files with a blank line between records. + 2023.7.6 -- Bugfixes * Fixed output of number of structures written to SDF files, which was off by 1. * Cleaned up the output for the write-structure step diff --git a/read_structure_step/formats/sdf/sdf.py b/read_structure_step/formats/sdf/sdf.py index d35bfc1..51c0c0b 100644 --- a/read_structure_step/formats/sdf/sdf.py +++ b/read_structure_step/formats/sdf/sdf.py @@ -2,6 +2,7 @@ Implementation of the reader for SDF files using OpenBabel """ +import bz2 import gzip from pathlib import Path import shutil @@ -133,10 +134,15 @@ def load_sdf( path.expanduser().resolve() # Get the information for progress output, if requested. - compress = path.suffix == ".gz" if printer is not None: n_structures = 0 - with gzip.open(path, mode="rt") if compress else open(path, "r") as fd: + with ( + gzip.open(path, mode="rt") + if path.suffix == ".gz" + else bz2.open(path, mode="rt") + if path.suffix == ".bz2" + else open(path, "r") + ) as fd: for line in fd: if line[0:4] == "$$$$": n_structures += 1 @@ -154,7 +160,13 @@ def load_sdf( n_errors = 0 obMol = openbabel.OBMol() text = "" - with gzip.open(path, mode="rt") if compress else open(path, "r") as fd: + with ( + gzip.open(path, mode="rt") + if path.suffix == ".gz" + else bz2.open(path, mode="rt") + if path.suffix == ".bz2" + else open(path, "r") + ) as fd: for line in fd: text += line diff --git a/read_structure_step/formats/xyz/xyz.py b/read_structure_step/formats/xyz/xyz.py index 8b47e55..3fe38c8 100644 --- a/read_structure_step/formats/xyz/xyz.py +++ b/read_structure_step/formats/xyz/xyz.py @@ -2,6 +2,8 @@ Implementation of the reader for XYZ files using OpenBabel """ +import bz2 +import gzip import logging import os from pathlib import Path @@ -119,7 +121,6 @@ def load_xyz( printer=None, references=None, bibliography=None, - save_data=True, **kwargs, ): """Read an XYZ input file. @@ -132,6 +133,49 @@ def load_xyz( configuration : molsystem.Configuration The configuration to put the imported structure into. + extension : str, optional, default: None + The extension, including initial dot, defining the format. + + add_hydrogens : bool = True + Whether to add any missing hydrogen atoms. + + system_db : System_DB = None + The system database, used if multiple structures in the file. + + system : System = None + The system to use if adding subsequent structures as configurations. + + indices : str = "1:end" + The generalized indices (slices, SMARTS, etc.) to select structures + from a file containing multiple structures. + + subsequent_as_configurations : bool = False + Normally and subsequent structures are loaded into new systems; however, + if this option is True, they will be added as configurations. + + system_name : str = "from file" + The name for systems. Can be directives like "SMILES" or + "Canonical SMILES". If None, no name is given. + + configuration_name : str = "sequential" + The name for configurations. Can be directives like "SMILES" or + "Canonical SMILES". If None, no name is given. + + printer : Logger or Printer + A function that prints to the appropriate place, used for progress. + + references : ReferenceHandler = None + The reference handler object or None + + bibliography : dict + The bibliography as a dictionary. + + Returns + ------- + [Configuration] + The list of configurations created. + + We'll use OpenBabel to read the file; however, OpenBabel is somewhat limited, so we'll first preprocess the file to extract extra data and also to fit it to the format that OpenBabel can handle. @@ -143,6 +187,12 @@ def load_xyz( #. symbol, x, y, z #. ... + Some XYZ files, for instance those from tmQM encode data in the comment line:: + + CSD_code = WIXKOE | q = 0 | S = 0 | Stoichiometry = C47H65LaN2O | MND = 7 + + We'll try to handle this type of comment. + The Minnesota Solvation database uses a slightly modified form: #. A comment, often the structure name and provenance @@ -164,151 +214,267 @@ def load_xyz( path = Path(file_name) else: path = file_name - path.expanduser().resolve() - lines = path.read_text().splitlines() - - # Examine the first lines and see what the format might be - file_type = "unknown" - n_lines = len(lines) - line1 = lines[0].strip() - fields1 = line1.split() - n_fields1 = len(fields1) - - if n_lines <= 1: - line2 = None - fields2 = [] - n_fields2 = 0 - else: - line2 = lines[1].strip() - fields2 = line2.split() - n_fields2 = len(fields2) - - if n_lines <= 2: - line3 = None - fields3 = [] - n_fields3 = 0 - else: - line3 = lines[2].strip() - fields3 = line3.split() - n_fields3 = len(fields3) - - # Check for "standard" file - if n_fields1 == 1: - try: - n_atoms = int(fields1[0]) - except Exception: - pass - else: - # Might be traditional file. Check 3rd line for atom - if n_fields3 == 4: - file_type = "standard" - elif n_fields1 == 0: - # Might be standard file without atom count. - if n_fields3 == 4: - file_type = "standard" - n_atoms = 0 - for line in lines[2:]: - n_fields = len(line.split()) - if n_fields == 0: - break + path = path.expanduser().resolve() + + print(f"In xyz.py, path = {path}") + + # Get the information for progress output, if requested. + n_structures = 0 + last_line = 0 + with ( + gzip.open(path, mode="rt") + if path.suffix == ".gz" + else bz2.open(path, mode="rt") + if path.suffix == ".bz2" + else open(path, "r") + ) as fd: + for line in fd: + last_line += 1 + if line.strip() == "": + n_structures += 1 + # may not have blank line at end + if line.strip() != "": + n_structures += 1 + if printer is not None: + printer("") + printer(f" The XYZ file contains {n_structures} structures.") + last_percent = 0 + t0 = time.time() + last_t = t0 + + obConversion = openbabel.OBConversion() + obConversion.SetInFormat("xyz") + + configurations = [] + structure_no = 0 + n_errors = 0 + obMol = openbabel.OBMol() + + total_lines = 0 + with ( + gzip.open(path, mode="rt") + if path.suffix == ".gz" + else bz2.open(path, mode="rt") + if path.suffix == ".bz2" + else open(path, "r") + ) as fd: + print(f"{fd=}") + lines = [] + line_no = 0 + for line in fd: + # lines have \n at end. It is not stripped as is more normal. + total_lines += 1 + line_no += 1 + lines.append(line) + if total_lines == last_line or line_no > 3 and line.strip() == "": + # End of block, so examine the first lines and see which format + file_type = "unknown" + n_lines = len(lines) + print(f"Found block of {n_lines} lines") + line1 = lines[0].strip() + fields1 = line1.split() + n_fields1 = len(fields1) + + if n_lines <= 1: + line2 = None + fields2 = [] + n_fields2 = 0 else: - n_atoms += 1 - # Put the count in line 1 - lines[0] = str(n_atoms) - - # And Minnesota variant with three headers. - if n_lines > 3 and n_fields3 == 2: - try: - charge = int(fields3[0]) - multiplicity = int(fields3[1]) - except Exception: - pass - else: - file_type = "Minnesota" - if n_fields2 != 0: - logger.warning( - f"Minnesota style XYZ file, 2nd line is not blank:\n\t{lines[1]}" - ) - # Count atoms - n_atoms = 0 - for line in lines[3:]: - n_fields = len(line.split()) - if n_fields == 0: - break + line2 = lines[1].strip() + fields2 = line2.split() + n_fields2 = len(fields2) + + if n_lines <= 2: + line3 = None + fields3 = [] + n_fields3 = 0 else: - n_atoms += 1 - # Move comment to 2nd line - lines[1] = lines[0] - # Put the count in line 1 - lines[0] = str(n_atoms) - # Remove 3rd line with charge and multiplicity - del lines[2] - - # Reassemble an input file. - input_data = "\n".join(lines) - logger.info(f"Input data:\n\n{input_data}\n") - - title = lines[1].strip() - - # Now try to convert using OpenBabel - out = OutputGrabber(sys.stderr) - with out: - obConversion = openbabel.OBConversion() - obConversion.SetInFormat("xyz") - obMol = openbabel.OBMol() - - success = obConversion.ReadString(obMol, input_data) - if not success: - raise RuntimeError("obConversion failed") - - if add_hydrogens: - obMol.AddHydrogens() - - configuration.from_OBMol(obMol) - - # Check any stderr information from obabel. - if out.capturedtext != "": - tmp = out.capturedtext - if "Failed to kekulize aromatic bonds in OBMol::PerceiveBondOrders" not in tmp: - logger.warning(tmp) - - if file_type == "Minnesota": - # Record the charge, and the spin state - configuration.charge = charge - configuration.spin_multiplicity = multiplicity - - logger.info(f"{charge=} {multiplicity=}") - - # Set the system name - if system_name is not None and system_name != "": - lower_name = system_name.lower() - if "from file" in lower_name: - system.name = str(path) - elif lower_name == "title": - if len(title) > 0: - system.name = title - else: - system.name = str(path) - elif "canonical smiles" in lower_name: - system.name = configuration.canonical_smiles - elif "smiles" in lower_name: - system.name = configuration.smiles - else: - system.name = system_name - - # And the configuration name - if configuration_name is not None and configuration_name != "": - lower_name = configuration_name.lower() - if "from file" in lower_name: - configuration.name = obMol.GetTitle() - elif "canonical smiles" in lower_name: - configuration.name = configuration.canonical_smiles - elif "smiles" in lower_name: - configuration.name = configuration.smiles - elif lower_name == "sequential": - configuration.name = "1" - else: - configuration.name = configuration_name + line3 = lines[2].strip() + fields3 = line3.split() + n_fields3 = len(fields3) + + # Check for "standard" file + if n_fields1 == 1: + try: + n_atoms = int(fields1[0]) + except Exception: + pass + else: + # Might be traditional file. Check 3rd line for atom + if n_fields3 == 4: + file_type = "standard" + elif n_fields1 == 0: + # Might be standard file without atom count. + if n_fields3 == 4: + file_type = "standard" + n_atoms = 0 + for line in lines[2:]: + n_fields = len(line.split()) + if n_fields == 0: + break + else: + n_atoms += 1 + # Put the count in line 1 + lines[0] = str(n_atoms) + + # And Minnesota variant with three headers. + if n_lines > 3 and n_fields3 == 2: + try: + charge = int(fields3[0]) + multiplicity = int(fields3[1]) + except Exception: + pass + else: + file_type = "Minnesota" + if n_fields2 != 0: + logger.warning( + "Minnesota style XYZ file, 2nd line is not blank:" + f"\n\t{lines[1]}" + ) + # Count atoms + n_atoms = 0 + for line in lines[3:]: + n_fields = len(line.split()) + if n_fields == 0: + break + else: + n_atoms += 1 + # Move comment to 2nd line + lines[1] = lines[0] + # Put the count in line 1 + lines[0] = str(n_atoms) + "\n" + # Remove 3rd line with charge and multiplicity + del lines[2] + + # Reassemble an input file. + input_data = "".join(lines) + + print("Input data") + print(input_data) + print("---------") + + logger.info(f"Input data:\n\n{input_data}\n") + + title = lines[1].strip() + + lines = [] + line_no = 0 + + # Now try to convert using OpenBabel + out = OutputGrabber(sys.stderr) + with out: + success = obConversion.ReadString(obMol, input_data) + if not success: + raise RuntimeError("obConversion failed") + + if add_hydrogens: + obMol.AddHydrogens() + + structure_no += 1 + if structure_no > 1: + if subsequent_as_configurations: + configuration = system.create_configuration() + else: + system = system_db.create_system() + configuration = system.create_configuration() + + configuration.from_OBMol(obMol) + configurations.append(configuration) + + # Check any stderr information from obabel. + if out.capturedtext != "": + tmp = out.capturedtext + if ( + "Failed to kekulize aromatic bonds in OBMol::PerceiveBondOrders" + not in tmp + ): + logger.warning(f"{structure_no}: {tmp}") + + if file_type == "Minnesota": + # Record the charge, and the spin state + configuration.charge = charge + configuration.spin_multiplicity = multiplicity + + logger.info(f"{charge=} {multiplicity=}") + elif "|" in title: + for tmp in title.split("|"): + if "=" in tmp: + key, val = tmp.split(maxsplit=1) + key = key.strip() + val = val.strip() + if key == "q": + try: + configuration.charge = float(val) + except Exception: + pass + elif key == "S": + try: + configuration.spin_multiplicity = int(val) + except Exception: + pass + elif key == "CSD_code": + title = val + + # Set the system name + if system_name is not None and system_name != "": + lower_name = system_name.lower() + if "from file" in lower_name: + system.name = str(path) + elif lower_name == "title": + if len(title) > 0: + system.name = title + else: + system.name = str(path) + elif "canonical smiles" in lower_name: + system.name = configuration.canonical_smiles + elif "smiles" in lower_name: + system.name = configuration.smiles + else: + system.name = system_name + + # And the configuration name + if configuration_name is not None and configuration_name != "": + lower_name = configuration_name.lower() + if "from file" in lower_name: + configuration.name = obMol.GetTitle() + elif lower_name == "title": + if len(title) > 0: + configuration.name = title + else: + configuration.name = str(path) + elif "canonical smiles" in lower_name: + configuration.name = configuration.canonical_smiles + elif "smiles" in lower_name: + configuration.name = configuration.smiles + elif lower_name == "sequential": + configuration.name = str(structure_no) + else: + configuration.name = configuration_name + + if printer: + percent = int(100 * structure_no / n_structures) + if percent > last_percent: + t1 = time.time() + if t1 - last_t >= 60: + t = int(t1 - t0) + rate = structure_no / (t1 - t0) + t_left = int((n_structures - structure_no) / rate) + printer( + f"\t{structure_no:6} ({percent}%) structures read in " + f"{t} seconds. About {t_left} seconds remaining." + ) + last_t = t1 + last_percent = percent + + if printer: + t1 = time.time() + rate = structure_no / (t1 - t0) + printer( + f" Read {structure_no - n_errors - 1} structures in {t1 - t0:.1f} " + f"seconds = {rate:.2f} per second" + ) + if n_errors > 0: + printer(f" {n_errors} structures could not be read due to errors.") if references: # Add the citations for Open Babel @@ -368,4 +534,4 @@ def load_xyz( except Exception: pass - return [configuration] + return configurations diff --git a/read_structure_step/utils.py b/read_structure_step/utils.py index ccc5f1d..87cc655 100644 --- a/read_structure_step/utils.py +++ b/read_structure_step/utils.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from . import formats import re @@ -8,6 +8,8 @@ def guess_extension(file_name, use_file_name=False): Returns the file format. It can either use the file name extension or guess based on signatures found in the file. + Correctly handles .gz and .bz2 files. + Parameters ---------- file_name: str @@ -24,8 +26,13 @@ def guess_extension(file_name, use_file_name=False): """ if use_file_name is True: - (root, ext) = os.path.splitext(file_name) - + path = Path(file_name) + suffixes = path.suffixes + ext = "" + if len(suffixes) > 0: + ext = suffixes[-1] + if ext in (".gz", ".bz2") and len(suffixes) > 1: + ext = suffixes[-2] if ext == "": return None diff --git a/tests/data/3TR_model.sdf.bz2 b/tests/data/3TR_model.sdf.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..6fc989e0859d2489390979d4ce7acad1c02a4a6d GIT binary patch literal 288 zcmV+*0pI>YT4*^jL0KkKS)^c6M zpay_2h7B}nrV|q;0tT2QiJ~+h4K!!~6PRwP^@JA5;1rka&Mag)fG8xjEo?I<1Paj% z1h-?q9vNA3s0LNOAQkRSqUVzmrSbR41Q0Fdfg~_Q1JwZ6FEbGRy5DWu(iNY1hr=qd zfry!@LO|@_D3g`SR0l?CnV?Ar2ucQ;2)I^@g035jp`Z%}2ofszECVP}1}zwHEMT?9 ze$0^s@1Y8c!YIvL9}aocsI1($O1U#IBw9UUfRnXChZTw%eVxF-bCB_kB5Q+P>ox{j m#=SgPk8brNJ;!tO|?3KA5Jgsi}gx_Joz literal 0 HcmV?d00001 diff --git a/tests/data/3TR_model.sdf.gz b/tests/data/3TR_model.sdf.gz new file mode 100644 index 0000000000000000000000000000000000000000..a9d33a1eceb979938f658f1fa9a420ca2bac6b8e GIT binary patch literal 298 zcmV+_0oDE=iwFo1#KL3%12a@oUu|z>Wo#~UWM%-BlFw?xFbu};c?unNoslfdvCd1O zFxo>Idx5Qk!MZ@%`!iW_LJr%(#u$D6^W&3be!0LSoaXoI7rf2iAM^coy$?{wWqSVn zfoA7D}^w`L{2FI(?W?s6uT%C zNLGrJ;uH=X1>vcOk}|c7%EB`WVd~5%bMm(sC~C`yNISh07WXm+jUL4mn7PM`^@xO9 zU38?@BNX#@?8#`Ysnt2Ioz6+SDArYx=7kqmmXT7QmuUYbx4K~cCEg7bCk@wExr wi*XZnhkdQWqO_fu$eTzGXxA(%+lk%Lov7t^aRNA;r{TfIFP;Mpnx6vz02{WDH~;_u literal 0 HcmV?d00001 diff --git a/tests/data/tmQM_test.xyz.bz2 b/tests/data/tmQM_test.xyz.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..b3e10fecd962978b213599db8ca3561e93b81f3c GIT binary patch literal 13395 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¥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 literal 0 HcmV?d00001 diff --git a/tests/data/tmQM_test.xyz.gz b/tests/data/tmQM_test.xyz.gz new file mode 100644 index 0000000000000000000000000000000000000000..e8e18b93777f901a2740e06cdbb454a59cb5cdc0 GIT binary patch literal 16514 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