diff --git a/docs/src/file_formats.md b/docs/src/file_formats.md index 6d10626..b4d3840 100644 --- a/docs/src/file_formats.md +++ b/docs/src/file_formats.md @@ -46,6 +46,7 @@ The following list all currently available parsers, in the order they are tried ```@docs AtomsIO.ExtxyzParser +AtomsIO.XcrysdenstructureformatParser AtomsIO.ChemfilesParser AtomsIOPython.AseParser ``` diff --git a/docs/src/index.md b/docs/src/index.md index 3c812df..4c925d3 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -6,6 +6,7 @@ AtomsIO currently integrates with - [Chemfiles](https://github.com/chemfiles/Chemfiles.jl) - [ExtXYZ](https://github.com/libAtoms/ExtXYZ.jl) + - [XCrySDenStructureFormat](https://github.com/azadoks/XCrySDenStructureFormat.jl) - [ASEconvert](https://github.com/mfherbst/ASEconvert.jl) (respectively [ASE](https://wiki.fysik.dtu.dk/ase/)) @@ -16,6 +17,7 @@ and supports all file formats any of these packages support - [Quantum Espresso](https://www.quantum-espresso.org/Doc/INPUT_PW.html) / [ABINIT](https://docs.abinit.org/variables/) / [VASP](https://www.vasp.at/wiki/) input files - ASE / [Gromacs](http://manual.gromacs.org/archive/5.0.7/online/trj.html) / [LAMMPS](https://lammps.sandia.gov/doc/dump.html) / [Amber](http://ambermd.org/netcdf/nctraj.xhtml) trajectory files - [XYZ](https://openbabel.org/wiki/XYZ) and [extxyz](https://github.com/libAtoms/extxyz#extended-xyz-specification-and-parsing-tools) files + - [XSF](http://www.xcrysden.org/doc/XSF.html) (XCrySDen) atomic structure files. For more details see [Saving and loading files](@ref) and [File Formats](@ref). diff --git a/src/AtomsIO.jl b/src/AtomsIO.jl index 52a719a..1c674a2 100644 --- a/src/AtomsIO.jl +++ b/src/AtomsIO.jl @@ -6,8 +6,10 @@ import PeriodicTable include("parser.jl") include("chemfiles.jl") include("extxyz.jl") +include("xsf.jl") include("saveload.jl") export load_system, save_system, load_trajectory, save_trajectory -export AbstractParser, ChemfilesParser, ExtxyzParser +export AbstractParser +export ChemfilesParser, ExtxyzParser, XcrysdenstructureformatParser end diff --git a/src/saveload.jl b/src/saveload.jl index 1a24e5f..e88b76e 100644 --- a/src/saveload.jl +++ b/src/saveload.jl @@ -4,7 +4,9 @@ # *breaking change* (as it alters the behaviour of AtomsIO.load_system). # Moreover since not all users will want to rely on Python dependencies, it is # crucial that the python packages are only *appended* to this list. -const DEFAULT_PARSER_ORDER = AbstractParser[ExtxyzParser(), ChemfilesParser()] +const DEFAULT_PARSER_ORDER = AbstractParser[ + ExtxyzParser(), XcrysdenstructureformatParser(), ChemfilesParser(), +] function determine_parser(file; save=false, trajectory=false) idx_parser = findfirst(DEFAULT_PARSER_ORDER) do parser diff --git a/src/xsf.jl b/src/xsf.jl index 914af09..e0374e8 100644 --- a/src/xsf.jl +++ b/src/xsf.jl @@ -1,43 +1,55 @@ import XCrySDenStructureFormat as XSF """ -Parse or write file using [XSF](https://github.com/azadoks/XCrySDenStructureFormat.jl) +Parse or write file using [XCrySDenStructureFormat](https://github.com/azadoks/XCrySDenStructureFormat.jl). Supported formats: - - [XSF](http://www.xcrysden.org/doc/XSF.html) + - [XSF](http://www.xcrysden.org/doc/XSF.html) and [AXSF](XCrySDenStructureFormat) + atomic structure files. These are the files typically used by the + [XCrySDen](http://www.xcrysden.org/) visualisation program. """ -struct XsfParser <: AbstractParser end +struct XcrysdenstructureformatParser <: AbstractParser end -function supports_parsing(::XsfParser, file; save, trajectory) +function supports_parsing(::XcrysdenstructureformatParser, file; save, trajectory) _, ext = splitext(file) ext in (".xsf", ".axsf") end -function load_system(::XsfParser, file::AbstractString, index=nothing) - if isnothing(index) - frames = XSF.load_xsf(file) +function load_system(::XcrysdenstructureformatParser, file::AbstractString, index=nothing) + if !isnothing(index) + return XSF.load_xsf(file)[index] + end + + + frames = XSF.load_xsf(file) + if !(frames isa AbstractVector) + # load_xsf Returns plain structure in case only a single structure in the file + return frames + else isempty(frames) && error( "XSF returned no frames. Check the passed file is a valid (a)xsf file." ) return last(frames) - else - return XSF.load_xsf(file)[index] end end -function save_system(::XsfParser, file::AbstractString, system::AbstractSystem) +function save_system(::XcrysdenstructureformatParser, + file::AbstractString, system::AbstractSystem) XSF.save_xsf(file, system) end -function load_trajectory(::XsfParser, file::AbstractString) - XSF.load_xsf(file) -end - -function save_system(::XsfParser, file::AbstractString, system::AbstractSystem) - XSF.save_xsf(file, system) +function load_trajectory(::XcrysdenstructureformatParser, file::AbstractString) + # load_xsf Returns plain structure in case only a single structure in the file, + # so we need to re-wrap to keep a consistent interface. + ret = XSF.load_xsf(file) + if !(ret isa AbstractVector) + return [ret] + else + return ret + end end -function save_trajectory(::XsfParser, file::AbstractString, +function save_trajectory(::XcrysdenstructureformatParser, file::AbstractString, systems::AbstractVector{<:AbstractSystem}) XSF.save_xsf(file, systems) end diff --git a/test/failed_files.jl b/test/failed_files.jl index 318488e..a78f100 100644 --- a/test/failed_files.jl +++ b/test/failed_files.jl @@ -5,6 +5,14 @@ using LinearAlgebra # Tests parsing files, where previously users reported problems @testset "Failed files" begin +@testset "Empty XYZ" begin + @test_throws "ExtXYZ returned no frames." load_system("files/empty.xyz") +end + +@testset "XYZ from Lammps" begin + @test_throws "ExtXYZ returned no frames." load_system("files/lammps.xyz") +end + @testset "CIF Graphene P6/mmm" begin parsed = load_system("files/graphene.cif") reduced = reduce(hcat, bounding_box(parsed)) @@ -15,7 +23,5 @@ using LinearAlgebra @test atomic_symbol(parsed) == [:C1, :C2, :C3, :C4] @test atomic_number(parsed) == [6, 6, 6, 6] @test parsed[:name] == "Graphene" - - end end diff --git a/test/files/empty.xyz b/test/files/empty.xyz new file mode 100644 index 0000000..fd40910 --- /dev/null +++ b/test/files/empty.xyz @@ -0,0 +1,4 @@ + + + + diff --git a/test/files/lammps.xyz b/test/files/lammps.xyz new file mode 100644 index 0000000..a711fae --- /dev/null +++ b/test/files/lammps.xyz @@ -0,0 +1,3 @@ +1 +Atoms. Timestep: 1000000 +Ar 1.4102613692638457 0.9647607662828660 1.3209769521273491 diff --git a/test/runtests.jl b/test/runtests.jl index 312ad56..96c0e02 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,6 +8,7 @@ if GROUP == "Core" @testset "AtomsIO.jl" begin include("chemfiles.jl") include("extxyz.jl") + include("xsf.jl") include("failed_files.jl") # For the comparison tests (also between Chemfiles and ExtXYZ and other # non-python libraries) see the AtomsIOPython subproject diff --git a/test/xsf.jl b/test/xsf.jl index 39876cc..fcd3079 100644 --- a/test/xsf.jl +++ b/test/xsf.jl @@ -3,6 +3,8 @@ using Test using AtomsBaseTesting @testset "XSF system write / read" begin + XsfParser = AtomsIO.XcrysdenstructureformatParser + system = make_test_system().system mktempdir() do d outfile = joinpath(d, "output.xsf") @@ -13,6 +15,8 @@ using AtomsBaseTesting end @testset "XSF trajectory write/read" begin + XsfParser = AtomsIO.XcrysdenstructureformatParser + systems = [make_test_system().system for _ in 1:3] mktempdir() do d outfile = joinpath(d, "output.axsf") @@ -28,6 +32,8 @@ end @testset "ExtXYZ supports_parsing" begin import AtomsIO: supports_parsing + XsfParser = AtomsIO.XcrysdenstructureformatParser + prefix = "test" save = trajectory = true @test supports_parsing(XsfParser(), prefix * ".xsf"; save, trajectory)