diff --git a/sparc/calculator.py b/sparc/calculator.py index f4b5d78c..90510a2e 100644 --- a/sparc/calculator.py +++ b/sparc/calculator.py @@ -5,7 +5,7 @@ import subprocess from .io import SparcBundle -from .utils import _find_default_sparc, h2gpts +from .utils import _find_default_sparc, h2gpts, deprecated from warnings import warn, warn_explicit from .api import SparcAPI import datetime @@ -27,22 +27,6 @@ defaultAPI = SparcAPI() -def deprecated(message): - def decorator(func): - def new_func(*args, **kwargs): - warn( - "Function {} is deprecated sparc-dft-api >= v0.2! {}".format( - func.__name__, message - ), - category=DeprecationWarning, - ) - return func(*args, **kwargs) - - return new_func - - return decorator - - class SPARC(FileIOCalculator): # TODO: magmom should be a possible input implemented_properties = ["energy", "forces", "fermi", "stress"] diff --git a/sparc/io.py b/sparc/io.py index dc420c8f..8f63a2fe 100644 --- a/sparc/io.py +++ b/sparc/io.py @@ -27,7 +27,7 @@ from .sparc_parsers.pseudopotential import copy_psp_file, parse_psp8_header from .common import psp_dir as default_psp_dir from .download_data import is_psp_download_complete -from .utils import string2index +from .utils import string2index, deprecated from ase.calculators.singlepoint import SinglePointDFTCalculator from ase.units import Hartree @@ -671,6 +671,61 @@ def write_sparc(filename, images, **kwargs): return +@deprecated("Reading individual .ion is not recommended. Please use read_sparc instead.") +def read_ion(filename, **kwargs): + """Parse an .ion file inside the SPARC bundle using a wrapper around SparcBundle + The reader works only when other files (.inpt) exist. + + The returned Atoms object of read_ion method only contains the initial positions + """ + parent_dir = Path(filename).parent + sb = SparcBundle(directory=parent_dir) + atoms = sb._read_ion_and_inpt() + return atoms + +@deprecated("Writing individual .ion file is not recommended. Please use write_sparc instead.") +def write_ion(filename, atoms, **kwargs): + """Write .ion and .inpt files using the SparcBundle wrapper. + + This is only for backward compatibility + """ + label = Path(filename).with_suffix("").name + parent_dir = Path(filename).parent + sb = SparcBundle(directory=parent_dir, label=label) + sb._write_ion_and_inpt(atoms, **kwargs) + return atoms + + +def read_static(filename, index=-1, **kwargs): + """Parse a .static file bundle using a wrapper around SparcBundle + The reader works only when other files (.ion, .inpt) exist. + """ + parent_dir = Path(filename).parent + sb = SparcBundle(directory=parent_dir) + atoms_or_images = sb.convert_to_ase(index=index, **kwargs) + return atoms_or_images + + +def read_geopt(filename, index=-1, **kwargs): + """Parse a .geopt file bundle using a wrapper around SparcBundle + The reader works only when other files (.ion, .inpt) exist. + """ + parent_dir = Path(filename).parent + sb = SparcBundle(directory=parent_dir) + atoms_or_images = sb.convert_to_ase(index=index, **kwargs) + return atoms_or_images + + +def read_aimd(filename, index=-1, **kwargs): + """Parse a .static file bundle using a wrapper around SparcBundle + The reader works only when other files (.ion, .inpt) exist. + """ + parent_dir = Path(filename).parent + sb = SparcBundle(directory=parent_dir) + atoms_or_images = sb.convert_to_ase(index=index, **kwargs) + return atoms_or_images + + def register_ase_io_sparc(name="sparc"): """ Monkey patching the ase.io and ase.io.formats @@ -772,6 +827,29 @@ def _new_filetype(filename, read=True, guess=True): "Atomatic format inference for sparc is not correctly registered. " "You may need to use format=sparc in ase.io.read and ase.io.write. " ) + # Add additional formats including .ion (r/w), .static, .geopt, .aimd + F( + "ion", + desc="SPARC .ion file", + module="sparc", + code="1S", + ext="ion", + ) + F( + "static", + desc="SPARC single point results", + module="sparc", + code="+S", + ext="static", + ) + F( + "geopt", + desc="SPARC geometric optimization results", + module="sparc", + code="+S", + ext="geopt", + ) + F("aimd", desc="SPARC AIMD results", module="sparc", code="+S", ext="aimd") # TODO: remove print options as it may be redundant - print("Successfully registered sparc format with ase.io!") + print("Successfully registered sparc formats with ase.io!") diff --git a/sparc/utils.py b/sparc/utils.py index a6e88571..71aa2905 100644 --- a/sparc/utils.py +++ b/sparc/utils.py @@ -3,9 +3,26 @@ import os import shutil import numpy as np +from warnings import warn from typing import Union, List, Optional +def deprecated(message): + def decorator(func): + def new_func(*args, **kwargs): + warn( + "Function {} is deprecated sparc-dft-api >= v0.2! {}".format( + func.__name__, message + ), + category=DeprecationWarning, + ) + return func(*args, **kwargs) + + return new_func + + return decorator + + def string2index(string: str) -> Union[int, slice, str]: """Convert index string to either int or slice This method is a copy of ase.io.formats.string2index diff --git a/tests/test_000_ase_io.py b/tests/test_000_ase_io.py index ac25eac8..39d25c2d 100644 --- a/tests/test_000_ase_io.py +++ b/tests/test_000_ase_io.py @@ -178,9 +178,31 @@ def fake_read_sparc(filename, *args, **kwargs): assert atoms.get_chemical_formula() == "Al" -def test_ase_io_filetype(): - """If hacked ase.io.formats.filetype correctly recognized sparc format""" +def test_ase_io_filetype(fs): + """If hacked ase.io.formats.filetype correctly recognized sparc format + + Due to the implementation of ase.io.formats, single file tests should be + done on non-empty files + """ import sparc from ase.io.formats import filetype + fs.create_dir("test.sparc") + fs.create_file("test.sparc/test.ion") + fs.create_file("test.sparc/test.static") + fs.create_file("test.sparc/test.geopt") + fs.create_file("test.sparc/test.aimd") + with open("test.sparc/test.ion", "w") as fd: + fd.write("\n") + with open("test.sparc/test.static", "w") as fd: + fd.write("\n") + with open("test.sparc/test.geopt", "w") as fd: + fd.write("\n") + with open("test.sparc/test.aimd", "w") as fd: + fd.write("\n") + assert filetype("test.sparc") == "sparc" + assert filetype("test.sparc/test.ion") == "ion" + assert filetype("test.sparc/test.static") == "static" + assert filetype("test.sparc/test.geopt") == "geopt" + assert filetype("test.sparc/test.aimd") == "aimd"