Skip to content

Commit f159ff0

Browse files
authored
Allow for dynamically determining JSON schema of input (grimme-lab#96)
- all JSON formats are available with `json` extension, the `read_json` procedure dispatches to the format specific readers based on the schema information in the JSON file - update README to list all formats supported in mctc-lib
1 parent 69bda51 commit f159ff0

18 files changed

Lines changed: 745 additions & 55 deletions

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,47 @@
77
[![codecov](https://codecov.io/gh/grimme-lab/mctc-lib/branch/main/graph/badge.svg)](https://codecov.io/gh/grimme-lab/mctc-lib)
88

99

10+
## Supported formats
11+
12+
This library supports reading and writing in more than ten different geometry formats, including general ASCII formats, like xyz, JSON based formats, like QCSchema, and program specific formats, for example compatible with Turbomole or Vasp.
13+
14+
*General geometry formats*
15+
16+
- [xyz][xyz] file format with ``xyz`` extension
17+
- [Protein data base][pdb] file format with ``pdb`` extension
18+
- [mol][ctfile] and [structure data][ctfile] connection table file formats with ``mol`` and ``sdf`` extension, respectively
19+
20+
[xyz]: http://www.ccl.net/chemistry/resources/messages/1996/10/21.005-dir/index.html
21+
[pdb]: http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html
22+
[ctfile]: https://www.daylight.com/meetings/mug05/Kappler/ctfile.pdf
23+
24+
*JSON based formats*
25+
26+
- [Pymatgen JSON][pmg] with ``pmgjson`` or ``json`` extension
27+
- [QCSchema JSON][qcsk] with ``qcjson`` or ``json`` extension
28+
- [Chemical JSON][cjson] with ``cjson`` or ``json`` extension
29+
30+
[pmg]: https://pymatgen.org
31+
[qcsk]: https://molssi-qc-schema.readthedocs.io
32+
[cjson]: https://github.com/OpenChemistry/avogadrolibs/blob/master/avogadro/io/cjsonformat.cpp
33+
34+
*Program specific formats*
35+
36+
- [Q-Chem molecule][qchem] file format with ``qchem`` extension
37+
- [Turbomole coord][tmol] file format with ``tmol`` or ``coord`` extension
38+
- [VASP POSCAR and CONTCAR][vasp] files with ``vasp``, ``poscar``, or ``contcar`` extension
39+
- [DFTB+ gen][gen] format with ``gen`` extension
40+
- [Gaussian external][ein] format with ``ein`` extension
41+
- [FHI-aims][aims] ``geometry.in`` input files
42+
43+
[aims]: https://fhi-aims.org
44+
[qchem]: https://manual.q-chem.com
45+
[tmol]: https://www.turbomole.org
46+
[gen]: https://dftbplus.org
47+
[ein]: https://gaussian.com/external/
48+
[vasp]: https://www.vasp.at/wiki/index.php
49+
50+
1051
## Installation
1152

1253
To build this project from the source code in this repository you need to have

doc/format-cjson.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: Chemical JSON
66

77
@Note [Reference](https://github.com/OpenChemistry/avogadrolibs/blob/master/avogadro/io/cjsonformat.cpp)
88

9-
Chemical JSON files are identified by the extension ``cjson`` and parsed following the format implemented in Avogadro 2.
9+
Chemical JSON files are identified by the extension ``cjson`` or ``json`` and parsed following the format implemented in Avogadro 2.
1010
The entries *name*, *atoms.elements.number*, *atoms.coords.3d*, *atoms.coords.3d fractional*, *unit cell*, *atoms.formalCharges*, *bonds.connections.index*, and *bonds.order* are recognized by the reader.
1111

1212

doc/format-pymatgen.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: Pymatgen JSON
66

77
@Note [Reference](https://pymatgen.org)
88

9-
Pymatgen formatted JSON files are identified by the extension ``pmgjson`` and parsed following the ``Molecule`` or ``Structure`` format.
9+
Pymatgen formatted JSON files are identified by the extension ``pmgjson`` or ``json`` and parsed following the ``Molecule`` or ``Structure`` format.
1010

1111

1212
## Example

doc/format-qcschema.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: QCSchema JSON
66

77
@Note [Reference](https://molssi-qc-schema.readthedocs.io)
88

9-
JSON files are identified by the extension ``json`` and parsed following the ``qcschema_molecule`` or ``qcschema_input`` format.
9+
JSON files are identified by the extension ``qcjson`` or ``json`` and parsed following the ``qcschema_molecule`` or ``qcschema_input`` format.
1010
The ``molecule`` entry from a ``qcschema_input`` will be extracted, but there is no guarantee that the input information will be used by the program.
1111

1212

man/mctc-convert.1.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ Supported formats:
2626
- Protein Database files, only single files (pdb)
2727
- Connection table files, molfile (mol) and structure data format (sdf)
2828
- Gaussian's external program input (ein)
29-
- JSON input with `qcschema_molecule` or `qcschema_input` structure (json)
30-
- Chemical JSON input (cjson)
31-
- Pymatgen JSON with `Molecule` or `Structure` schema (pmgjson)
29+
- JSON input with `qcschema_molecule` or `qcschema_input` structure (qcjson, json)
30+
- Chemical JSON input (cjson, json)
31+
- Pymatgen JSON with `Molecule` or `Structure` schema (pmgjson, json)
3232
- FHI-AIMS' input files (geometry.in)
3333
- Q-Chem molecule block inputs (qchem)
3434

src/mctc/io/filetype.f90

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ module mctc_io_filetype
6666
!> Pymatgen JSON format
6767
integer :: pymatgen = 13
6868

69+
!> General JSON format
70+
integer :: json = 14
71+
6972
end type enum_filetype
7073

7174
!> File type enumerator
@@ -107,14 +110,16 @@ elemental function get_filetype(file) result(ftype)
107110
ftype = filetype%gen
108111
case('ein')
109112
ftype = filetype%gaussian
110-
case('json')
113+
case('qcjson')
111114
ftype = filetype%qcschema
112115
case('cjson')
113116
ftype = filetype%cjson
114117
case('qchem')
115118
ftype = filetype%qchem
116119
case('pmgjson')
117120
ftype = filetype%pymatgen
121+
case('json')
122+
ftype = filetype%json
118123
end select
119124
if (ftype /= filetype%unknown) return
120125
else

src/mctc/io/read.f90

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module mctc_io_read
2020
use mctc_io_read_ctfile, only : read_molfile, read_sdf
2121
use mctc_io_read_gaussian, only : read_gaussian_external
2222
use mctc_io_read_genformat, only : read_genformat
23+
use mctc_io_read_json, only : read_json
2324
use mctc_io_read_qchem, only : read_qchem
2425
use mctc_io_read_qcschema, only : read_qcschema
2526
use mctc_io_read_pdb, only : read_pdb
@@ -181,6 +182,9 @@ subroutine get_structure_reader(reader, ftype)
181182
case(filetype%qchem)
182183
reader => read_qchem
183184

185+
case(filetype%json)
186+
reader => read_json
187+
184188
end select
185189

186190
end subroutine get_structure_reader

src/mctc/io/read/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ list(
2121
"${dir}/ctfile.f90"
2222
"${dir}/gaussian.f90"
2323
"${dir}/genformat.f90"
24+
"${dir}/json.F90"
2425
"${dir}/qchem.f90"
2526
"${dir}/qcschema.F90"
2627
"${dir}/pdb.f90"

src/mctc/io/read/cjson.F90

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ module mctc_io_read_cjson
3232

3333
public :: read_cjson
3434

35+
#if WITH_JSON
36+
interface read_cjson
37+
module procedure read_cjson
38+
module procedure load_cjson
39+
end interface read_cjson
40+
#endif
41+
3542

3643
contains
3744

@@ -49,22 +56,10 @@ subroutine read_cjson(self, unit, error)
4956

5057
#if WITH_JSON
5158
class(json_value), allocatable :: root
52-
type(json_object), pointer :: object, child, child2
53-
type(json_array), pointer :: array, child_array
54-
type(json_keyval), pointer :: val
59+
type(json_object), pointer :: object
5560
type(json_error), allocatable :: parse_error
5661
type(json_context) :: ctx
5762

58-
logical :: cartesian, found
59-
integer :: stat, origin, schema_version, charge, multiplicity, ibond
60-
integer :: origin_elements, origin_coords
61-
character(len=:), allocatable :: input, line, message, comment
62-
integer, allocatable :: num(:), bond(:, :), list(:), order(:)
63-
real(wp) :: cellpar(6)
64-
real(wp), allocatable :: lattice(:, :)
65-
real(wp), allocatable, target :: geo(:)
66-
real(wp), pointer :: xyz(:, :)
67-
6863
call json_load(root, unit, config=json_parser_config(context_detail=1), &
6964
& context=ctx, error=parse_error)
7065
if (allocated(parse_error)) then
@@ -78,6 +73,42 @@ subroutine read_cjson(self, unit, error)
7873
return
7974
end if
8075

76+
call load_cjson(self, object, ctx, error)
77+
#else
78+
call fatal_error(error, "JSON support not enabled")
79+
#endif
80+
end subroutine read_cjson
81+
82+
83+
#if WITH_JSON
84+
subroutine load_cjson(self, object, ctx, error)
85+
86+
!> Instance of the molecular structure data
87+
type(structure_type), intent(out) :: self
88+
89+
!> JSON object representing the structure
90+
type(json_object), intent(inout) :: object
91+
92+
!> JSON context for error reporting
93+
type(json_context), intent(inout) :: ctx
94+
95+
!> Error handling
96+
type(error_type), allocatable, intent(out) :: error
97+
98+
type(json_object), pointer :: child, child2
99+
type(json_array), pointer :: array, child_array
100+
type(json_keyval), pointer :: val
101+
102+
logical :: cartesian, found
103+
integer :: stat, origin, schema_version, charge, multiplicity, ibond
104+
integer :: origin_elements, origin_coords
105+
character(len=:), allocatable :: input, line, message, comment
106+
integer, allocatable :: num(:), bond(:, :), list(:), order(:)
107+
real(wp) :: cellpar(6)
108+
real(wp), allocatable :: lattice(:, :)
109+
real(wp), allocatable, target :: geo(:)
110+
real(wp), pointer :: xyz(:, :)
111+
81112
call cjson_get_value(object, "chemicalJson", "chemical json", val, stat=stat, origin=origin)
82113
if (.not.associated(val) .or. stat /= json_stat%success) then
83114
call fatal_error(error, ctx%report("Could find chemical json", origin))
@@ -247,10 +278,8 @@ subroutine read_cjson(self, unit, error)
247278
call move_alloc(bond, self%bond)
248279
end if
249280

250-
#else
251-
call fatal_error(error, "JSON support not enabled")
281+
end subroutine load_cjson
252282
#endif
253-
end subroutine read_cjson
254283

255284
#if WITH_JSON
256285
subroutine cjson_get_child(object, key1, key2, child, stat, origin)

src/mctc/io/read/json.F90

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
! This file is part of mctc-lib.
2+
!
3+
! Licensed under the Apache License, Version 2.0 (the "License");
4+
! you may not use this file except in compliance with the License.
5+
! You may obtain a copy of the License at
6+
!
7+
! http://www.apache.org/licenses/LICENSE-2.0
8+
!
9+
! Unless required by applicable law or agreed to in writing, software
10+
! distributed under the License is distributed on an "AS IS" BASIS,
11+
! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
! See the License for the specific language governing permissions and
13+
! limitations under the License.
14+
15+
#include "mctc/defs.h"
16+
17+
module mctc_io_read_json
18+
use mctc_env_accuracy, only : wp
19+
use mctc_env_error, only : error_type, fatal_error
20+
use mctc_io_structure, only : structure_type, new
21+
use mctc_io_symbols, only : to_number, symbol_length
22+
use mctc_io_utils, only : to_string
23+
use mctc_io_read_cjson, only : read_cjson
24+
use mctc_io_read_qcschema, only : read_qcschema
25+
use mctc_io_read_pymatgen, only : read_pymatgen
26+
#if WITH_JSON
27+
use jonquil, only : json_value, json_object, json_array, json_keyval, &
28+
& json_load, json_error, json_context, json_stat, get_value, &
29+
& json_parser_config, json_context, cast_to_object, len
30+
#endif
31+
implicit none
32+
private
33+
34+
public :: read_json
35+
36+
37+
contains
38+
39+
40+
subroutine read_json(self, unit, error)
41+
42+
!> Instance of the molecular structure data
43+
type(structure_type), intent(out) :: self
44+
45+
!> File handle
46+
integer, intent(in) :: unit
47+
48+
!> Error handling
49+
type(error_type), allocatable, intent(out) :: error
50+
51+
#if WITH_JSON
52+
class(json_value), allocatable :: root
53+
type(json_object), pointer :: object
54+
type(json_error), allocatable :: parse_error
55+
type(json_context) :: ctx
56+
57+
call json_load(root, unit, config=json_parser_config(context_detail=1), &
58+
& context=ctx, error=parse_error)
59+
if (allocated(parse_error)) then
60+
allocate(error)
61+
call move_alloc(parse_error%message, error%message)
62+
return
63+
end if
64+
object => cast_to_object(root)
65+
if (.not.associated(object)) then
66+
call fatal_error(error, ctx%report("Invalid JSON object", root%origin, "Expected JSON object"))
67+
return
68+
end if
69+
70+
! QCSchema JSON uses "schema_name" and "schema_version" keys
71+
if (object%has_key("schema_name") .or. object%has_key("schema_version")) then
72+
call read_qcschema(self, object, ctx, error)
73+
return
74+
end if
75+
76+
! Pymatgen serialized via monty adds "@module" and "@class" keys
77+
if (object%has_key("@module") .or. object%has_key("@class")) then
78+
call read_pymatgen(self, object, ctx, error)
79+
return
80+
end if
81+
82+
! Chemical JSON (cjson) tracks version via "chemical json" or "chemicalJson" keys
83+
if (object%has_key("chemical json") .or. object%has_key("chemicalJson")) then
84+
call read_cjson(self, object, ctx, error)
85+
return
86+
end if
87+
88+
! Default to QCSchema if no specific schema is detected
89+
call read_qcschema(self, object, ctx, error)
90+
#else
91+
call fatal_error(error, "JSON support not enabled")
92+
#endif
93+
end subroutine read_json
94+
95+
96+
end module mctc_io_read_json

0 commit comments

Comments
 (0)