From d2411d9f73c9a585820b19a1b07d5e5339463339 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Thu, 14 Apr 2022 09:46:28 +0200 Subject: [PATCH 01/10] Only write groups in Turbomole output if necessary (#41) --- src/mctc/io/write/turbomole.f90 | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/mctc/io/write/turbomole.f90 b/src/mctc/io/write/turbomole.f90 index b666d156..c403d3ab 100644 --- a/src/mctc/io/write/turbomole.f90 +++ b/src/mctc/io/write/turbomole.f90 @@ -28,15 +28,25 @@ subroutine write_coord(mol, unit) class(structure_type), intent(in) :: mol integer, intent(in) :: unit integer :: iat, ilt, npbc + logical :: expo write(unit, '(a)') "$coord" - do iat = 1, mol%nat - write(unit, '(3es24.14, 6x, a)') mol%xyz(:, iat), trim(mol%sym(mol%id(iat))) - end do - write(unit, '(a, *(1x, a, "=", i0))') & - "$eht", "charge", nint(mol%charge), "unpaired", mol%uhf - write(unit, '(a, 1x, i0)') "$periodic", count(mol%periodic) + expo = maxval(mol%xyz) > 1.0e+5 .or. minval(mol%xyz) < -1.0e+5 + if (expo) then + do iat = 1, mol%nat + write(unit, '(3es24.14, 6x, a)') mol%xyz(:, iat), trim(mol%sym(mol%id(iat))) + end do + else + do iat = 1, mol%nat + write(unit, '(3f24.14, 6x, a)') mol%xyz(:, iat), trim(mol%sym(mol%id(iat))) + end do + end if + if (any([nint(mol%charge), mol%uhf] /= 0)) then + write(unit, '(a, *(1x, a, "=", i0))') & + "$eht", "charge", nint(mol%charge), "unpaired", mol%uhf + end if if (any(mol%periodic)) then + write(unit, '(a, 1x, i0)') "$periodic", count(mol%periodic) npbc = count(mol%periodic) if (size(mol%lattice, 2) == 3) then write(unit, '(a)') "$lattice bohr" From 2460f93269695d56a31d35cf67508a434a1383c4 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:48:33 +0200 Subject: [PATCH 02/10] Update chain information when switching creating terminators in PDB (#42) - change from HETATM to ATOM now also updates the chain information --- src/mctc/io/write/pdb.f90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mctc/io/write/pdb.f90 b/src/mctc/io/write/pdb.f90 index 76a9713f..6b68934c 100644 --- a/src/mctc/io/write/pdb.f90 +++ b/src/mctc/io/write/pdb.f90 @@ -52,6 +52,7 @@ subroutine write_pdb(mol, unit, number) write(unit, '("TER ",i5,6x,a3,1x,a1,i4)') iat + offset, & & mol%pdb(iat-1)%residue, last_chain, mol%pdb(iat)%residue_number last_het = .not.last_het + last_chain = mol%pdb(iat)%chains offset = offset+1 else if (mol%pdb(iat)%chains /= last_chain) then write(unit, '("TER ",i5,6x,a3,1x,a1,i4)') iat + offset, & From 1f690009020fb5f196c1dc359056741738416db6 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:49:22 +0200 Subject: [PATCH 03/10] Allow adjusting of module install subdirectory (#26) --- config/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 80d33e63..29121ade 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -18,8 +18,14 @@ option(WITH_OpenMP "Enable support for shared memory parallelisation with OpenMP option(WITH_JSON "Enable support for JSON parsing via json-fortran" FALSE) set( - module-dir + "${PROJECT_NAME}-module-dir" "${PROJECT_NAME}/${CMAKE_Fortran_COMPILER_ID}-${CMAKE_Fortran_COMPILER_VERSION}" + CACHE STRING + "Subdirectory to install generated module files to" +) +set( + module-dir + "${${PROJECT_NAME}-module-dir}" ) set(module-dir "${module-dir}" PARENT_SCOPE) From 4e88c5239344dad805c91ae021694891ff0dd372 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Sat, 7 May 2022 20:00:58 +0200 Subject: [PATCH 04/10] Allow building with Intel-CL (#43) --- config/meson.build | 5 +++++ subprojects/packagefiles/json-fortran-8.2.5/meson.build | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/config/meson.build b/config/meson.build index 5b20cb7b..82af9aff 100644 --- a/config/meson.build +++ b/config/meson.build @@ -27,6 +27,11 @@ elif fc_id == 'intel' '-traceback', language: 'fortran', ) +elif fc_id == 'intel-cl' + add_project_arguments( + '-fpp', + language: 'fortran', + ) elif fc_id == 'pgi' or fc_id == 'nvidia_hpc' add_project_arguments( '-Mbackslash', diff --git a/subprojects/packagefiles/json-fortran-8.2.5/meson.build b/subprojects/packagefiles/json-fortran-8.2.5/meson.build index 2dcadd39..cb8f4dcf 100644 --- a/subprojects/packagefiles/json-fortran-8.2.5/meson.build +++ b/subprojects/packagefiles/json-fortran-8.2.5/meson.build @@ -4,6 +4,14 @@ project( version: files('.VERSION'), ) +fc = meson.get_compiler('fortran') +if fc.get_id() == 'intel-cl' + add_project_arguments( + '-fpp', + language: 'fortran', + ) +endif + jsonfortran_lib = library( meson.project_name(), sources: files( From 874680bb150b8e45f7f375db0af1847c77b086ae Mon Sep 17 00:00:00 2001 From: Andy May Date: Tue, 17 May 2022 20:47:45 +0100 Subject: [PATCH 05/10] Fix module install subdirectory functionality (#44) --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8f04b7e..8b999a89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,14 +96,10 @@ install( "${PROJECT_NAME}::" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) -set( - module-id - "${CMAKE_Fortran_COMPILER_ID}-${CMAKE_Fortran_COMPILER_VERSION}" -) install( DIRECTORY "${PROJECT_BINARY_DIR}/include/" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${module-id}" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${module-dir}" ) # Package license files install( From 8def2c5a22e5dbad65eeaba7f4af3008a87430ce Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Tue, 24 May 2022 02:45:08 +0900 Subject: [PATCH 06/10] replace deprecated `make_directory` with `file(MAKE_DIRECTORY)` (#45) https://cmake.org/cmake/help/v3.14/command/make_directory.html --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b999a89..69a1f600 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ target_include_directories( $ ) if(NOT EXISTS "${PROJECT_BINARY_DIR}/include") - make_directory("${PROJECT_BINARY_DIR}/include") + file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/include") endif() # Add example application From f02b5905d6c80fc441ec7bcf150f9a6f46fa6d3f Mon Sep 17 00:00:00 2001 From: Kjell Jorner <36157530+kjelljorner@users.noreply.github.com> Date: Tue, 24 May 2022 02:16:53 -0400 Subject: [PATCH 07/10] Update install-mod.py (#46) Update python to python3 which is consistent with the requirements for meson. --- config/install-mod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/install-mod.py b/config/install-mod.py index 9fbadf51..78e9b02b 100755 --- a/config/install-mod.py +++ b/config/install-mod.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # This file is part of mctc-lib. # # Licensed under the Apache License, Version 2.0 (the "License"); From abfbff11e7187e4a48a3954316c8d6fdc3fe1656 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Tue, 26 Jul 2022 21:11:52 +0200 Subject: [PATCH 08/10] Support chemical JSON (#50) - implement basic reader for chemical JSON - implement writer for chemical JSON - test if chemical JSON produced by mctc-lib round-trips - document implemented format - implementation matches https://github.com/OpenChemistry/avogadrolibs/blob/c707a49/avogadro/io/cjsonformat.cpp --- doc/format-cjson.md | 99 ++++++ doc/index.md | 1 + man/mctc-convert.1.adoc | 1 + src/mctc/io/filetype.f90 | 5 + src/mctc/io/read.f90 | 4 + src/mctc/io/read/CMakeLists.txt | 1 + src/mctc/io/read/cjson.F90 | 262 ++++++++++++++ src/mctc/io/read/meson.build | 1 + src/mctc/io/write.f90 | 4 + src/mctc/io/write/CMakeLists.txt | 1 + src/mctc/io/write/cjson.f90 | 293 ++++++++++++++++ src/mctc/io/write/meson.build | 1 + test/CMakeLists.txt | 2 + test/main.f90 | 4 + test/meson.build | 2 + test/test_read.f90 | 77 +++++ test/test_read_cjson.f90 | 572 +++++++++++++++++++++++++++++++ test/test_write.f90 | 25 ++ test/test_write_cjson.f90 | 108 ++++++ 19 files changed, 1463 insertions(+) create mode 100644 doc/format-cjson.md create mode 100644 src/mctc/io/read/cjson.F90 create mode 100644 src/mctc/io/write/cjson.f90 create mode 100644 test/test_read_cjson.f90 create mode 100644 test/test_write_cjson.f90 diff --git a/doc/format-cjson.md b/doc/format-cjson.md new file mode 100644 index 00000000..c65e37a4 --- /dev/null +++ b/doc/format-cjson.md @@ -0,0 +1,99 @@ +--- +title: Chemical JSON +--- + +## Specification + +@Note [Reference](https://github.com/OpenChemistry/avogadrolibs/blob/master/avogadro/io/cjsonformat.cpp) + +Chemical JSON files are identified by the extension ``cjson`` and parsed following the format implemented in Avogadro 2. +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. + + +## Example + +Caffeine molecule in ``qcschema_molecule`` format. + + +```json +{ + "chemicalJson": 1, + "atoms": { + "elements": { + "number": [ + 6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "coords": { + "3d": [ + 1.0731997649702911E+00, 4.8899989290949721E-02, -7.5699983421776973E-02, + 2.5136994495022558E+00, 1.2599997240612813E-02, -7.5799983399877077E-02, + 3.3519992659154081E+00, 1.0958997599990143E+00, -7.5299983509376570E-02, + 4.6189989884436962E+00, 7.3029984006504256E-01, -7.5499983465576764E-02, + 4.5790989971817559E+00, -6.3139986172404194E-01, -7.5299983509376570E-02, + 3.3012992770186567E+00, -1.1025997585317211E+00, -7.5199983531276451E-02, + 2.9806993472297307E+00, -2.4868994553714288E+00, -7.3799983837875047E-02, + 1.8252996002611557E+00, -2.9003993648153492E+00, -7.5799983399877077E-02, + 4.1143990989505834E+00, -3.3042992763616597E+00, -6.9399984801470568E-02, + 5.4516988060832432E+00, -2.8561993744951040E+00, -7.2399984144473614E-02, + 6.3892986007497967E+00, -3.6596991985294207E+00, -7.2299984166373524E-02, + 5.6623987599401575E+00, -1.4767996765823013E+00, -7.4899983596976152E-02, + 7.0094984649266268E+00, -9.3649979490745228E-01, -7.5199983531276451E-02, + 3.9205991413925863E+00, -4.7408989617477202E+00, -6.1599986509662634E-02, + 7.3399983925474632E-01, 1.0878997617510062E+00, -7.4999983575076257E-02, + 7.1239984398512435E-01, -4.5699989991746470E-01, 8.2339981967623732E-01, + 7.1239984398512435E-01, -4.5579990018026340E-01, -9.7549978636649193E-01, + 2.9929993445360430E+00, 2.1175995362477531E+00, -7.4799983618876062E-02, + 7.7652982994071955E+00, -1.7262996219420552E+00, -7.5899983377977168E-02, + 7.1485984344638682E+00, -3.2179992952612718E-01, 8.1969982048653345E-01, + 7.1479984345952676E+00, -3.2079992974512617E-01, -9.6949978768048573E-01, + 2.8649993725679135E+00, -5.0231988999243073E+00, -5.8299987232359275E-02, + 4.4022990359007768E+00, -5.1591988701404459E+00, 8.2839981858124223E-01, + 4.4001990363606742E+00, -5.1692988679285561E+00, -9.4779979243276369E-01 + ] + } + }, + "bonds": { + "connections": { + "index": [ + 0, 1, + 1, 2, + 2, 3, + 3, 4, + 1, 5, + 4, 5, + 5, 6, + 6, 7, + 6, 8, + 8, 9, + 9, 10, + 4, 11, + 9, 11, + 11, 12, + 8, 13, + 0, 14, + 0, 15, + 0, 16, + 2, 17, + 12, 18, + 12, 19, + 12, 20, + 13, 21, + 13, 22, + 13, 23 + ] + }, + "order": [ + 1, 4, 4, 4, 1, 4, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } +} +``` + + +## Missing features + +The schema is not verified on completeness and not all data is stored in the final structure type. + +@Note Feel free to contribute support for missing features + or bring missing features to our attention by opening an issue. diff --git a/doc/index.md b/doc/index.md index 50afea0d..b4eaeb5d 100644 --- a/doc/index.md +++ b/doc/index.md @@ -11,6 +11,7 @@ This library supports reading and writing of the following formats: - [a subset of PDB format](./format-pdb.html) - [DFTB+ general format](./format-gen.html) - [Gaussian external format](./format-ein.html) +- [Chemical JSON format](./format-cjson.html) - [QCSchema JSON format](./format-qcschema.html) - [FHI-aims geometry.in](./format-aims.html) - [Q-Chem molecule format](./format-qchem.html) diff --git a/man/mctc-convert.1.adoc b/man/mctc-convert.1.adoc index 3e5b6dc5..05b91fa4 100644 --- a/man/mctc-convert.1.adoc +++ b/man/mctc-convert.1.adoc @@ -27,6 +27,7 @@ Supported formats: - Connection table files, molfile (mol) and structure data format (sdf) - Gaussian's external program input (ein) - JSON input with `qcschema_molecule` or `qcschema_input` structure (json) +- Chemical JSON input (cjson) - FHI-AIMS' input files (geometry.in) - Q-Chem molecule block inputs (qchem) diff --git a/src/mctc/io/filetype.f90 b/src/mctc/io/filetype.f90 index 8102287d..55a456b8 100644 --- a/src/mctc/io/filetype.f90 +++ b/src/mctc/io/filetype.f90 @@ -59,6 +59,9 @@ module mctc_io_filetype !> Q-Chem molecule format integer :: qchem = 11 + !> Chemical JSON format (avogadro) + integer :: cjson = 12 + end type enum_filetype !> File type enumerator @@ -102,6 +105,8 @@ elemental function get_filetype(file) result(ftype) ftype = filetype%gaussian case('json') ftype = filetype%qcschema + case('cjson') + ftype = filetype%cjson case('qchem') ftype = filetype%qchem end select diff --git a/src/mctc/io/read.f90 b/src/mctc/io/read.f90 index e8757dda..f3687c77 100644 --- a/src/mctc/io/read.f90 +++ b/src/mctc/io/read.f90 @@ -16,6 +16,7 @@ module mctc_io_read use mctc_env_error, only : error_type, fatal_error use mctc_io_filetype, only : filetype, get_filetype use mctc_io_read_aims, only : read_aims + use mctc_io_read_cjson, only : read_cjson use mctc_io_read_ctfile, only : read_molfile, read_sdf use mctc_io_read_gaussian, only : read_gaussian_external use mctc_io_read_genformat, only : read_genformat @@ -149,6 +150,9 @@ subroutine get_structure_reader(reader, ftype) case(filetype%qcschema) reader => read_qcschema + case(filetype%cjson) + reader => read_cjson + case(filetype%pdb) reader => read_pdb diff --git a/src/mctc/io/read/CMakeLists.txt b/src/mctc/io/read/CMakeLists.txt index 3c75b25b..913f8ebc 100644 --- a/src/mctc/io/read/CMakeLists.txt +++ b/src/mctc/io/read/CMakeLists.txt @@ -17,6 +17,7 @@ set(dir "${CMAKE_CURRENT_SOURCE_DIR}") list( APPEND srcs "${dir}/aims.f90" + "${dir}/cjson.F90" "${dir}/ctfile.f90" "${dir}/gaussian.f90" "${dir}/genformat.f90" diff --git a/src/mctc/io/read/cjson.F90 b/src/mctc/io/read/cjson.F90 new file mode 100644 index 00000000..ff23cd40 --- /dev/null +++ b/src/mctc/io/read/cjson.F90 @@ -0,0 +1,262 @@ +! This file is part of mctc-lib. +! +! Licensed under the Apache License, Version 2.0 (the "License"); +! you may not use this file except in compliance with the License. +! You may obtain a copy of the License at +! +! http://www.apache.org/licenses/LICENSE-2.0 +! +! Unless required by applicable law or agreed to in writing, software +! distributed under the License is distributed on an "AS IS" BASIS, +! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +! See the License for the specific language governing permissions and +! limitations under the License. + +#include "mctc/defs.h" + +module mctc_io_read_cjson + use mctc_env_accuracy, only : wp + use mctc_env_error, only : error_type, fatal_error + use mctc_io_constants, only : pi + use mctc_io_convert, only : aatoau + use mctc_io_structure, only : structure_type, new + use mctc_io_symbols, only : to_number, symbol_length + use mctc_io_utils, only : getline +#if WITH_JSON + use json_value_module, only : json_core, json_value +#endif + implicit none + private + + public :: read_cjson + + +contains + + +subroutine read_cjson(self, unit, error) + + !> Instance of the molecular structure data + type(structure_type), intent(out) :: self + + !> File handle + integer, intent(in) :: unit + + !> Error handling + type(error_type), allocatable, intent(out) :: error + +#if WITH_JSON + type(json_core) :: json + type(json_value), pointer :: root, val, child, array + + logical :: cartesian, found + integer :: stat, schema_version, charge, ibond + character(len=:), allocatable :: input, line, message, comment + integer, allocatable :: num(:), bond(:, :), list(:), order(:) + real(wp) :: cellpar(6) + real(wp), allocatable :: lattice(:, :) + real(wp), allocatable, target :: geo(:) + real(wp), pointer :: xyz(:, :) + + stat = 0 + input = "" + do + call getline(unit, line, stat) + if (stat /= 0) exit + input = input // line + end do + + call json%deserialize(root, input) + if (json%failed()) then + call json%check_for_errors(error_msg=message) + call fatal_error(error, message) + call json%destroy(root) + return + end if + val => root + + call cjson_get(json, val, "chemicalJson", "chemical json", child) + if (.not.associated(child)) then + call fatal_error(error, "No 'chemical json' key found") + call json%destroy(root) + return + end if + + call json%get(child, schema_version) + + ! There seems to be no actual difference between version 0 and 1, though + if (all(schema_version /= [0, 1])) then + call fatal_error(error, "Unsupported schema version for 'chemical json'") + call json%destroy(root) + return + end if + + call json%get(val, "atoms.elements.number", num) + if (.not.allocated(num) .or. json%failed()) then + call fatal_error(error, "List of atomic symbols must be provided") + call json%destroy(root) + return + end if + + call cjson_get(json, val, "unitCell", "unit cell", child) + if (associated(child)) then + call json%get(child, "a", cellpar(1)) + call json%get(child, "b", cellpar(2)) + call json%get(child, "c", cellpar(3)) + call json%get(child, "alpha", cellpar(4)) + call json%get(child, "beta", cellpar(5)) + call json%get(child, "gamma", cellpar(6)) + + if (json%failed()) then + call json%check_for_errors(error_msg=message) + call fatal_error(error, message) + call json%destroy(root) + return + end if + cellpar(1:3) = cellpar(1:3) * aatoau + cellpar(4:6) = cellpar(4:6) * (pi / 180) + allocate(lattice(3, 3)) + call cell_to_dlat(cellpar, lattice) + end if + + call json%get(val, "atoms.coords.3d", geo, found=cartesian) + if (.not.cartesian .and. allocated(lattice)) then + call cjson_get(json, val, "atoms.coords.3dFractional", "atoms.coords.3d fractional", & + & child) + if (associated(child)) call json%get(child, geo) + end if + if (.not.allocated(geo) .or. json%failed()) then + call fatal_error(error, "Cartesian coordinates must be provided") + call json%destroy(root) + return + end if + + if (3*size(num) /= size(geo)) then + call fatal_error(error, "Number of atomic numbers and coordinate triples must match") + call json%destroy(root) + return + end if + + call json%get(val, "bonds.connections.index", list, found=found) + call json%get(val, "bonds.order", order, found=found) + if (.not.allocated(order) .and. allocated(list)) & + allocate(order(size(list)/2), source=1) + + if (json%failed()) then + call fatal_error(error, "Cannot read entries from 'bonds'") + call json%destroy(root) + return + end if + + if (allocated(list)) then + allocate(bond(3, size(list)/2)) + do ibond = 1, size(bond, 2) + bond(:, ibond) = [list(2*ibond-1) + 1, list(2*ibond) + 1, order(ibond)] + end do + end if + + call json%get(val, "name", comment, default="") + call json%get(val, "atoms.formalCharges", list, found=found) + charge = 0 + if (allocated(list)) charge = sum(list) + + if (json%failed()) then + call json%check_for_errors(error_msg=message) + call fatal_error(error, message) + call json%destroy(root) + return + end if + + xyz(1:3, 1:size(geo)/3) => geo + xyz(:, :) = xyz * aatoau + if (.not.cartesian) then + xyz(:, :) = matmul(lattice, xyz(:, :)) + end if + call new(self, num, xyz, lattice=lattice, charge=real(charge, wp)) + if (len(comment) > 0) self%comment = comment + if (allocated(bond)) then + self%nbd = size(bond, 2) + call move_alloc(bond, self%bond) + end if + + call json%destroy(root) + +contains + + subroutine cjson_get(json, val, key1, key2, child) + type(json_core), intent(inout) :: json + type(json_value), pointer, intent(in) :: val + type(json_value), pointer, intent(out) :: child + character(*), intent(in) :: key1, key2 + + logical :: found + + call json%get(val, key1, child, found=found) + if (.not.found) then + call json%get(val, key2, child, found=found) + end if + end subroutine cjson_get + +#else + call fatal_error(error, "JSON support not enabled") +#endif +end subroutine read_cjson + + +!> Calculate the lattice vectors from a set of cell parameters +pure subroutine cell_to_dlat(cellpar, lattice) + + !> Cell parameters + real(wp), intent(in) :: cellpar(6) + + !> Direct lattice + real(wp), intent(out) :: lattice(:, :) + + real(wp) :: dvol + + dvol = cell_to_dvol(cellpar) + + associate(alen => cellpar(1), blen => cellpar(2), clen => cellpar(3), & + & alp => cellpar(4), bet => cellpar(5), gam => cellpar(6)) + + lattice(1, 1) = alen + lattice(2, 1) = 0.0_wp + lattice(3, 1) = 0.0_wp + lattice(3, 2) = 0.0_wp + lattice(1, 2) = blen*cos(gam) + lattice(2, 2) = blen*sin(gam) + lattice(1, 3) = clen*cos(bet) + lattice(2, 3) = clen*(cos(alp) - cos(bet)*cos(gam))/sin(gam); + lattice(3, 3) = dvol/(alen*blen*sin(gam)) + + end associate + +end subroutine cell_to_dlat + + +!> Calculate the cell volume from a set of cell parameters +pure function cell_to_dvol(cellpar) result(dvol) + + !> Cell parameters + real(wp), intent(in) :: cellpar(6) + + !> Cell volume + real(wp) :: dvol + + real(wp) :: vol2 + + associate(alen => cellpar(1), blen => cellpar(2), clen => cellpar(3), & + & alp => cellpar(4), bet => cellpar(5), gam => cellpar(6) ) + + vol2 = 1.0_wp - cos(alp)**2 - cos(bet)**2 - cos(gam)**2 & + & + 2.0_wp*cos(alp)*cos(bet)*cos(gam) + + dvol = sqrt(abs(vol2))*alen*blen*clen + ! return negative volume instead of imaginary one (means bad cell parameters) + if (vol2 < 0.0_wp) dvol = -dvol ! this should not happen, but who knows... + + end associate +end function cell_to_dvol + + +end module mctc_io_read_cjson diff --git a/src/mctc/io/read/meson.build b/src/mctc/io/read/meson.build index 9421b774..54d8c83b 100644 --- a/src/mctc/io/read/meson.build +++ b/src/mctc/io/read/meson.build @@ -14,6 +14,7 @@ srcs += files( 'aims.f90', + 'cjson.F90', 'ctfile.f90', 'gaussian.f90', 'genformat.f90', diff --git a/src/mctc/io/write.f90 b/src/mctc/io/write.f90 index b2efe498..2ab8378e 100644 --- a/src/mctc/io/write.f90 +++ b/src/mctc/io/write.f90 @@ -16,6 +16,7 @@ module mctc_io_write use mctc_env_error, only : error_type, fatal_error use mctc_io_filetype, only : filetype, get_filetype use mctc_io_write_aims, only : write_aims + use mctc_io_write_cjson, only : write_cjson use mctc_io_write_ctfile, only : write_molfile, write_sdf use mctc_io_write_gaussian, only : write_gaussian_external use mctc_io_write_genformat, only : write_genformat @@ -131,6 +132,9 @@ subroutine write_structure_to_unit(self, unit, ftype, error) case(filetype%gaussian) call write_gaussian_external(self, unit) + case(filetype%cjson) + call write_cjson(self, unit) + case(filetype%qcschema) call write_qcschema(self, unit) diff --git a/src/mctc/io/write/CMakeLists.txt b/src/mctc/io/write/CMakeLists.txt index 536c0270..ecce6fea 100644 --- a/src/mctc/io/write/CMakeLists.txt +++ b/src/mctc/io/write/CMakeLists.txt @@ -17,6 +17,7 @@ set(dir "${CMAKE_CURRENT_SOURCE_DIR}") list( APPEND srcs "${dir}/aims.f90" + "${dir}/cjson.f90" "${dir}/ctfile.f90" "${dir}/gaussian.f90" "${dir}/genformat.f90" diff --git a/src/mctc/io/write/cjson.f90 b/src/mctc/io/write/cjson.f90 new file mode 100644 index 00000000..1e5483eb --- /dev/null +++ b/src/mctc/io/write/cjson.f90 @@ -0,0 +1,293 @@ +! This file is part of mctc-lib. +! +! Licensed under the Apache License, Version 2.0 (the "License"); +! you may not use this file except in compliance with the License. +! You may obtain a copy of the License at +! +! http://www.apache.org/licenses/LICENSE-2.0 +! +! Unless required by applicable law or agreed to in writing, software +! distributed under the License is distributed on an "AS IS" BASIS, +! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +! See the License for the specific language governing permissions and +! limitations under the License. + +module mctc_io_write_cjson + use mctc_env_accuracy, only : wp + use mctc_io_constants, only : pi + use mctc_io_convert, only : autoaa + use mctc_io_math, only : matinv_3x3 + use mctc_io_structure, only : structure_type + implicit none + private + + public :: write_cjson + + + interface json_value + module procedure :: json_value_char + module procedure :: json_value_int + module procedure :: json_value_real + end interface json_value + + interface json_array + module procedure :: json_array_char_1 + module procedure :: json_array_int_1 + module procedure :: json_array_real_1 + end interface json_array + + character(len=*), parameter :: nl = new_line('a') + +contains + + +subroutine write_cjson(mol, unit) + type(structure_type), intent(in) :: mol + integer, intent(in) :: unit + + write(unit, '(a)') json_string(mol, " ") +end subroutine write_cjson + +pure function json_string(mol, indent) result(string) + type(structure_type), intent(in) :: mol + character(len=*), intent(in), optional :: indent + character(len=:), allocatable :: string + + real(wp), allocatable :: inv_lat(:, :) + real(wp), allocatable :: abc(:, :) + real(wp) :: cellpar(6) + + string = "{" + if (present(indent)) string = string // nl // indent + string = string // json_key("chemicalJson", indent) // json_value(1) + + if (allocated(mol%comment)) then + string = string // "," + if (present(indent)) string = string // nl // indent + string = string // json_key("name", indent) // json_value(mol%comment) + end if + + string = string // "," + if (present(indent)) string = string // nl // indent + string = string // json_key("atoms", indent) // "{" + + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("elements", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 3) + string = string // json_key("number", indent) // json_array(mol%num(mol%id), 3, indent) + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // "}" + + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("coords", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 3) + if (mol%info%cartesian) then + string = string // json_key("3d", indent) // json_array([mol%xyz * autoaa], 3, indent) + else + inv_lat = matinv_3x3(mol%lattice) + abc = matmul(inv_lat, mol%xyz) + string = string // json_key("3dFractional", indent) // json_array([abc], 3, indent) + end if + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // "}" + + if (present(indent)) string = string // nl // indent + string = string // "}" + + if (allocated(mol%bond)) then + string = string // "," + if (present(indent)) string = string // nl // indent + string = string // json_key("bonds", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("connections", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 3) + string = string // json_key("index", indent) // json_array([mol%bond(1:2, :) - 1], 3, indent) + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // "}" + + if (size(mol%bond, 1) > 2) then + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("order", indent) // json_array(mol%bond(3, :), 2, indent) + if (present(indent)) string = string // nl // indent + end if + string = string // "}" + end if + + if (any(mol%periodic)) then + call dlat_to_cell(mol%lattice, cellpar) + cellpar(1:3) = cellpar(1:3) * autoaa + cellpar(4:6) = cellpar(4:6) * 180 / pi + + string = string // "," + if (present(indent)) string = string // nl // indent + string = string // json_key("unitCell", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("a", indent) // json_value(cellpar(1), "(es23.16)") + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("b", indent) // json_value(cellpar(2), "(es23.16)") + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("c", indent) // json_value(cellpar(3), "(es23.16)") + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("alpha", indent) // json_value(cellpar(4), "(es23.16)") + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("beta", indent) // json_value(cellpar(5), "(es23.16)") + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("gamma", indent) // json_value(cellpar(6), "(es23.16)") + if (present(indent)) string = string // nl // indent + string = string // "}" + end if + + if (present(indent)) string = string // nl + string = string // "}" +end function json_string + +pure function json_array_int_1(array, depth, indent) result(string) + integer, intent(in) :: array(:) + integer, intent(in) :: depth + character(len=*), intent(in), optional :: indent + character(len=:), allocatable :: string + + integer :: i, j + + string = "[" + do i = 1, size(array) + if (present(indent)) string = string // nl // repeat(indent, depth+1) + string = string // json_value(array(i)) + if (i /= size(array)) string = string // "," + end do + if (present(indent)) string = string // nl // repeat(indent, depth) + string = string // "]" +end function json_array_int_1 + +pure function json_array_real_1(array, depth, indent) result(string) + real(wp), intent(in) :: array(:) + integer, intent(in) :: depth + character(len=*), intent(in), optional :: indent + character(len=:), allocatable :: string + + integer :: i, j + + string = "[" + do i = 1, size(array) + if (present(indent)) string = string // nl // repeat(indent, depth+1) + string = string // json_value(array(i), '(es23.16)') + if (i /= size(array)) string = string // "," + end do + if (present(indent)) string = string // nl // repeat(indent, depth) + string = string // "]" +end function json_array_real_1 + +pure function json_array_char_1(array, depth, indent) result(string) + character(len=*), intent(in) :: array(:) + integer, intent(in) :: depth + character(len=*), intent(in), optional :: indent + character(len=:), allocatable :: string + + integer :: i, j + + string = "[" + do i = 1, size(array) + if (present(indent)) string = string // nl // repeat(indent, depth+1) + string = string // json_value(trim(array(i))) + if (i /= size(array)) string = string // "," + end do + if (present(indent)) string = string // nl // repeat(indent, depth) + string = string // "]" +end function json_array_char_1 + +pure function json_key(key, indent) result(string) + character(len=*), intent(in) :: key + character(len=*), intent(in), optional :: indent + character(len=:), allocatable :: string + + if (present(indent)) then + string = json_value(key) // ": " + else + string = json_value(key) // ":" + end if +end function json_key + +pure function json_value_char(val) result(string) + character(len=*), intent(in) :: val + character(len=:), allocatable :: string + + string = """" // val // """" +end function json_value_char + +pure function json_value_real(val, format) result(str) + real(wp), intent(in) :: val + character(len=*), intent(in) :: format + character(len=:), allocatable :: str + + character(len=128) :: buffer + integer :: stat + + write(buffer, format, iostat=stat) val + if (stat == 0) then + str = trim(buffer) + else + str = """*""" + end if +end function json_value_real + +pure function json_value_int(val) result(string) + integer, intent(in) :: val + character(len=:), allocatable :: string + integer, parameter :: buffer_len = range(val)+2 + character(len=buffer_len) :: buffer + integer :: pos + integer :: n + character(len=1), parameter :: numbers(0:9) = & + ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] + + if (val == 0) then + string = numbers(0) + return + end if + + n = abs(val) + buffer = "" + + pos = buffer_len + 1 + do while (n > 0) + pos = pos - 1 + buffer(pos:pos) = numbers(mod(n, 10)) + n = n/10 + end do + if (val < 0) then + pos = pos - 1 + buffer(pos:pos) = '-' + end if + + string = buffer(pos:) +end function json_value_int + +!> Convert direct lattice to cell parameters +pure subroutine dlat_to_cell(lattice,cellpar) + implicit none + real(wp),intent(in) :: lattice(3,3) !< direct lattice + real(wp),intent(out) :: cellpar(6) !< cell parameters + + associate( alen => cellpar(1), blen => cellpar(2), clen => cellpar(3), & + & alp => cellpar(4), bet => cellpar(5), gam => cellpar(6) ) + + alen = norm2(lattice(:,1)) + blen = norm2(lattice(:,2)) + clen = norm2(lattice(:,3)) + + alp = acos(dot_product(lattice(:,2),lattice(:,3))/(blen*clen)) + bet = acos(dot_product(lattice(:,1),lattice(:,3))/(alen*clen)) + gam = acos(dot_product(lattice(:,1),lattice(:,2))/(alen*blen)) + + end associate + +end subroutine dlat_to_cell + +end module mctc_io_write_cjson diff --git a/src/mctc/io/write/meson.build b/src/mctc/io/write/meson.build index f550e7f0..ad60185d 100644 --- a/src/mctc/io/write/meson.build +++ b/src/mctc/io/write/meson.build @@ -14,6 +14,7 @@ srcs += files( 'aims.f90', + 'cjson.f90', 'ctfile.f90', 'gaussian.f90', 'genformat.f90', diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1e417ff9..1509148b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ set( "math" "read" "read-aims" + "read-cjson" "read-ctfile" "read-gaussian" "read-genformat" @@ -30,6 +31,7 @@ set( "symbols" "write" "write-aims" + "write-cjson" "write-ctfile" "write-gaussian" "write-genformat" diff --git a/test/main.f90 b/test/main.f90 index afa3b02a..b89bbb08 100644 --- a/test/main.f90 +++ b/test/main.f90 @@ -21,6 +21,7 @@ program tester use test_math, only : collect_math use test_read, only : collect_read use test_read_aims, only : collect_read_aims + use test_read_cjson, only : collect_read_cjson use test_read_ctfile, only : collect_read_ctfile use test_read_gaussian, only : collect_read_gaussian use test_read_genformat, only : collect_read_genformat @@ -33,6 +34,7 @@ program tester use test_symbols, only : collect_symbols use test_write, only : collect_write use test_write_aims, only : collect_write_aims + use test_write_cjson, only : collect_write_cjson use test_write_ctfile, only : collect_write_ctfile use test_write_gaussian, only : collect_write_gaussian use test_write_genformat, only : collect_write_genformat @@ -54,6 +56,7 @@ program tester & new_testsuite("symbols", collect_symbols), & & new_testsuite("read", collect_read), & & new_testsuite("read-aims", collect_read_aims), & + & new_testsuite("read-cjson", collect_read_cjson), & & new_testsuite("read-ctfile", collect_read_ctfile), & & new_testsuite("read-gaussian", collect_read_gaussian), & & new_testsuite("read-genformat", collect_read_genformat), & @@ -65,6 +68,7 @@ program tester & new_testsuite("read-xyz", collect_read_xyz), & & new_testsuite("write", collect_write), & & new_testsuite("write-aims", collect_write_aims), & + & new_testsuite("write-cjson", collect_write_cjson), & & new_testsuite("write-ctfile", collect_write_ctfile), & & new_testsuite("write-gaussian", collect_write_gaussian), & & new_testsuite("write-genformat", collect_write_genformat), & diff --git a/test/meson.build b/test/meson.build index 3b23b43f..b2acfda0 100644 --- a/test/meson.build +++ b/test/meson.build @@ -16,6 +16,7 @@ tests = [ 'math', 'read', 'read-aims', + 'read-cjson', 'read-ctfile', 'read-gaussian', 'read-genformat', @@ -28,6 +29,7 @@ tests = [ 'symbols', 'write', 'write-aims', + 'write-cjson', 'write-ctfile', 'write-gaussian', 'write-genformat', diff --git a/test/test_read.f90 b/test/test_read.f90 index acf52fe6..b97c3652 100644 --- a/test/test_read.f90 +++ b/test/test_read.f90 @@ -35,6 +35,7 @@ subroutine collect_read(testsuite) type(unittest_type), allocatable, intent(out) :: testsuite(:) testsuite = [ & + & new_unittest("valid-cjson", test_cjson, should_fail=.not.get_mctc_feature("json")), & & new_unittest("valid-mol", test_mol), & & new_unittest("valid-sdf", test_sdf), & & new_unittest("valid-gen", test_gen), & @@ -554,6 +555,82 @@ subroutine test_qcschema(error) end subroutine test_qcschema +subroutine test_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + character(len=:), allocatable :: name + integer :: unit + + name = get_name() // ".cjson" + + open(file=name, newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 1,', & + ' "name": "Compound 11",', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 8, 6, 8, 6, 7, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [', & + ' -2.8211309999999998E+00, -2.7623799999999998E-01, -7.5313099999999999E-01,', & + ' -2.0764070000000001E+00, 2.8899999999999992E-04, 1.7586399999999999E-01,', & + ' -2.4698600000000002E+00, 8.7269300000000005E-01, 1.1265160000000001E+00,', & + ' -6.4830699999999997E-01, -5.0843899999999997E-01, 3.8420700000000002E-01,', & + ' -5.5372500000000002E-01, -1.9082209999999997E+00, -9.2136999999999997E-02,', & + ' 3.0664000000000002E-01, 4.4865899999999997E-01, -3.5210999999999998E-01,', & + ' 1.7648520000000001E+00, 1.6743700000000000E-01, -9.7096000000000002E-02,', & + ' 2.5751040000000001E+00, 9.8444199999999993E-01, 5.8795099999999989E-01,', & + ' -3.3913139999999999E+00, 1.0915140000000001E+00, 8.7361200000000006E-01,', & + ' -4.3888700000000003E-01, -5.1347299999999996E-01, 1.4603180000000000E+00,', & + ' 4.2120599999999997E-01, -2.1974659999999995E+00, -1.1177400000000000E-01,', & + ' -8.9319499999999996E-01, -1.9465760000000001E+00, -1.0554429999999999E+00,', & + ' 7.3376999999999998E-02, 1.4832350000000001E+00, -6.6298999999999997E-02,', & + ' 1.2986000000000000E-01, 3.9679799999999998E-01, -1.4347600000000000E+00,', & + ' 2.1793230000000001E+00, -7.4534500000000004E-01, -5.1925100000000002E-01,', & + ' 2.2195890000000000E+00, 1.9142530000000000E+00, 1.0217970000000001E+00,', & + ' 3.6228750000000001E+00, 7.3643800000000004E-01, 7.3077999999999999E-01', & + ' ]', & + ' }', & + ' },', & + ' "bonds": {', & + ' "connections": {', & + ' "index": [', & + ' 0, 1,', & + ' 1, 2,', & + ' 3, 1,', & + ' 3, 4,', & + ' 3, 5,', & + ' 5, 6,', & + ' 6, 7,', & + ' 2, 8,', & + ' 3, 9,', & + ' 4, 10,', & + ' 4, 11,', & + ' 5, 12,', & + ' 5, 13,', & + ' 6, 14,', & + ' 7, 15,', & + ' 7, 16', & + ' ]', & + ' },', & + ' "order": [ 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]', & + ' }', & + '}' + close(unit) + + call read_structure(struc, name, error) + + open(file=name, newunit=unit) + close(unit, status='delete') + +end subroutine test_cjson + + function get_name() result(name) character(len=18) :: name diff --git a/test/test_read_cjson.f90 b/test/test_read_cjson.f90 new file mode 100644 index 00000000..972454d7 --- /dev/null +++ b/test/test_read_cjson.f90 @@ -0,0 +1,572 @@ +! This file is part of mctc-lib. +! +! Licensed under the Apache License, Version 2.0 (the "License"); +! you may not use this file except in compliance with the License. +! You may obtain a copy of the License at +! +! http://www.apache.org/licenses/LICENSE-2.0 +! +! Unless required by applicable law or agreed to in writing, software +! distributed under the License is distributed on an "AS IS" BASIS, +! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +! See the License for the specific language governing permissions and +! limitations under the License. + +module test_read_cjson + use mctc_env_testing, only : new_unittest, unittest_type, error_type, check + use mctc_io_read_cjson + use mctc_io_structure + use mctc_version, only : get_mctc_feature + implicit none + private + + public :: collect_read_cjson + + +contains + + +!> Collect all exported unit tests +subroutine collect_read_cjson(testsuite) + + !> Collection of tests + type(unittest_type), allocatable, intent(out) :: testsuite(:) + + logical :: with_json + + with_json = get_mctc_feature("json") + + testsuite = [ & + & new_unittest("valid1-cjson", test_valid1_cjson, should_fail=.not.with_json), & + & new_unittest("valid2-cjson", test_valid2_cjson, should_fail=.not.with_json), & + & new_unittest("valid3-cjson", test_valid3_cjson, should_fail=.not.with_json), & + & new_unittest("valid4-cjson", test_valid3_cjson, should_fail=.not.with_json), & + & new_unittest("invalid1-cjson", test_invalid1_cjson, should_fail=.true.), & + & new_unittest("invalid2-cjson", test_invalid2_cjson, should_fail=.true.), & + & new_unittest("invalid3-cjson", test_invalid3_cjson, should_fail=.true.), & + & new_unittest("invalid4-cjson", test_invalid4_cjson, should_fail=.true.), & + & new_unittest("invalid5-cjson", test_invalid5_cjson, should_fail=.true.), & + & new_unittest("invalid6-cjson", test_invalid6_cjson, should_fail=.true.) & + & ] + +end subroutine collect_read_cjson + + +subroutine test_valid1_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 0,', & + ' "name": "ethane",', & + ' "inchi": "1/C2H6/c1-2/h1-2H3",', & + ' "formula": "C 2 H 6",', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' },', & + ' "bonds": {', & + ' "connections": {', & + ' "index": [ 0, 1,', & + ' 1, 2,', & + ' 1, 3,', & + ' 1, 4,', & + ' 4, 5,', & + ' 4, 6,', & + ' 4, 7 ]', & + ' },', & + ' "order": [ 1, 1, 1, 1, 1, 1, 1 ]', & + ' },', & + ' "properties": {', & + ' "molecular mass": 30.0690,', & + ' "melting point": -172,', & + ' "boiling point": -88', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, allocated(struc%comment), "Comment line should be preserved") + if (allocated(error)) return + call check(error, struc%comment, "ethane") + if (allocated(error)) return + call check(error, struc%nat, 8, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 2, "Number of species does not match") + if (allocated(error)) return + call check(error, struc%nbd, 7, "Number of bonds does not match") + if (allocated(error)) return + +end subroutine test_valid1_cjson + + +subroutine test_valid2_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 0,', & + ' "name": "TiO2 rutile",', & + ' "formula": "Ti 2 O 4",', & + ' "unit cell": {', & + ' "a": 2.95812,', & + ' "b": 4.59373,', & + ' "c": 4.59373,', & + ' "alpha": 90.0,', & + ' "beta": 90.0,', & + ' "gamma": 90.0', & + ' },', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 22, 22, 8, 8, 8, 8 ]', & + ' },', & + ' "coords": {', & + ' "3d fractional": [ 0.00000, 0.00000, 0.00000,', & + ' 0.50000, 0.50000, 0.50000,', & + ' 0.00000, 0.30530, 0.30530,', & + ' 0.00000, 0.69470, 0.69470,', & + ' 0.50000, 0.19470, 0.80530,', & + ' 0.50000, 0.80530, 0.19470 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, allocated(struc%comment), "Comment line should be preserved") + if (allocated(error)) return + call check(error, struc%comment, "TiO2 rutile") + if (allocated(error)) return + call check(error, struc%nat, 6, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 2, "Number of species does not match") + if (allocated(error)) return + +end subroutine test_valid2_cjson + + +subroutine test_valid3_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 0,', & + ' "name": "ethane",', & + ' "inchi": "1/C2H6/c1-2/h1-2H3",', & + ' "formula": "C 2 H 6",', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' },', & + ' "bonds": {', & + ' "connections": {', & + ' "index": [ 0, 1,', & + ' 1, 2,', & + ' 1, 3,', & + ' 1, 4,', & + ' 4, 5,', & + ' 4, 6,', & + ' 4, 7 ]', & + ' }', & + ' },', & + ' "properties": {', & + ' "molecular mass": 30.0690,', & + ' "melting point": -172,', & + ' "boiling point": -88', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, allocated(struc%comment), "Comment line should be preserved") + if (allocated(error)) return + call check(error, struc%comment, "ethane") + if (allocated(error)) return + call check(error, struc%nat, 8, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 2, "Number of species does not match") + if (allocated(error)) return + call check(error, struc%nbd, 7, "Number of bonds does not match") + if (allocated(error)) return + +end subroutine test_valid3_cjson + + +subroutine test_valid4_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [', & + ' 6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1', & + ' ]', & + ' },', & + ' "coords": {', & + ' "3d": [', & + ' 1.0731997649702911E+00, 4.8899989290949721E-02, -7.5699983421776973E-02,', & + ' 2.5136994495022558E+00, 1.2599997240612813E-02, -7.5799983399877077E-02,', & + ' 3.3519992659154081E+00, 1.0958997599990143E+00, -7.5299983509376570E-02,', & + ' 4.6189989884436962E+00, 7.3029984006504256E-01, -7.5499983465576764E-02,', & + ' 4.5790989971817559E+00, -6.3139986172404194E-01, -7.5299983509376570E-02,', & + ' 3.3012992770186567E+00, -1.1025997585317211E+00, -7.5199983531276451E-02,', & + ' 2.9806993472297307E+00, -2.4868994553714288E+00, -7.3799983837875047E-02,', & + ' 1.8252996002611557E+00, -2.9003993648153492E+00, -7.5799983399877077E-02,', & + ' 4.1143990989505834E+00, -3.3042992763616597E+00, -6.9399984801470568E-02,', & + ' 5.4516988060832432E+00, -2.8561993744951040E+00, -7.2399984144473614E-02,', & + ' 6.3892986007497967E+00, -3.6596991985294207E+00, -7.2299984166373524E-02,', & + ' 5.6623987599401575E+00, -1.4767996765823013E+00, -7.4899983596976152E-02,', & + ' 7.0094984649266268E+00, -9.3649979490745228E-01, -7.5199983531276451E-02,', & + ' 3.9205991413925863E+00, -4.7408989617477202E+00, -6.1599986509662634E-02,', & + ' 7.3399983925474632E-01, 1.0878997617510062E+00, -7.4999983575076257E-02,', & + ' 7.1239984398512435E-01, -4.5699989991746470E-01, 8.2339981967623732E-01,', & + ' 7.1239984398512435E-01, -4.5579990018026340E-01, -9.7549978636649193E-01,', & + ' 2.9929993445360430E+00, 2.1175995362477531E+00, -7.4799983618876062E-02,', & + ' 7.7652982994071955E+00, -1.7262996219420552E+00, -7.5899983377977168E-02,', & + ' 7.1485984344638682E+00, -3.2179992952612718E-01, 8.1969982048653345E-01,', & + ' 7.1479984345952676E+00, -3.2079992974512617E-01, -9.6949978768048573E-01,', & + ' 2.8649993725679135E+00, -5.0231988999243073E+00, -5.8299987232359275E-02,', & + ' 4.4022990359007768E+00, -5.1591988701404459E+00, 8.2839981858124223E-01,', & + ' 4.4001990363606742E+00, -5.1692988679285561E+00, -9.4779979243276369E-01', & + ' ]', & + ' }', & + ' },', & + ' "bonds": {', & + ' "connections": {', & + ' "index": [', & + ' 0, 1,', & + ' 1, 2,', & + ' 2, 3,', & + ' 3, 4,', & + ' 1, 5,', & + ' 4, 5,', & + ' 5, 6,', & + ' 6, 7,', & + ' 6, 8,', & + ' 8, 9,', & + ' 9, 10,', & + ' 4, 11,', & + ' 9, 11,', & + ' 11, 12,', & + ' 8, 13,', & + ' 0, 14,', & + ' 0, 15,', & + ' 0, 16,', & + ' 2, 17,', & + ' 12, 18,', & + ' 12, 19,', & + ' 12, 20,', & + ' 13, 21,', & + ' 13, 22,', & + ' 13, 23', & + ' ]', & + ' },', & + ' "order": [', & + ' 1, 4, 4, 4, 1, 4, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1', & + ' ]', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + call check(error, struc%nat, 24, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 4, "Number of species does not match") + if (allocated(error)) return + call check(error, struc%nbd, 23, "Number of bonds does not match") + if (allocated(error)) return + +end subroutine test_valid4_cjson + + +subroutine test_invalid1_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": -1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + +end subroutine test_invalid1_cjson + + +subroutine test_invalid2_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + +end subroutine test_invalid2_cjson + + +subroutine test_invalid3_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": null,', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + +end subroutine test_invalid3_cjson + + +subroutine test_invalid4_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + +end subroutine test_invalid4_cjson + + +subroutine test_invalid5_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "numbers": [ 1, 6, 1, 1, 6, 1, 1, 1 ]', & + ' },', & + ' "coords": {', & + ' "3d": [ 1.185080, -0.003838, 0.987524,', & + ' 0.751621, -0.022441, -0.020839,', & + ' 1.166929, 0.833015, -0.569312,', & + ' 1.115519, -0.932892, -0.514525,', & + ' -0.751587, 0.022496, 0.020891,', & + ' -1.166882, -0.833372, 0.568699,', & + ' -1.115691, 0.932608, 0.515082,', & + ' -1.184988, 0.004424, -0.987522 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + +end subroutine test_invalid5_cjson + + +subroutine test_invalid6_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemical json": 0,', & + ' "name": "TiO2 rutile",', & + ' "formula": "Ti 2 O 4",', & + ' "unit cell": {', & + ' "a": 2.95812,', & + ' "b": 4.59373,', & + ' "c": 4.59373,', & + ' "alpha": 90.0,', & + ' "beta": 90.0,', & + ' "gama": 90.0', & + ' },', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [ 22, 22, 8, 8, 8, 8 ]', & + ' },', & + ' "coords": {', & + ' "3d fractional": [ 0.00000, 0.00000, 0.00000,', & + ' 0.50000, 0.50000, 0.50000,', & + ' 0.00000, 0.30530, 0.30530,', & + ' 0.00000, 0.69470, 0.69470,', & + ' 0.50000, 0.19470, 0.80530,', & + ' 0.50000, 0.80530, 0.19470 ]', & + ' }', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, allocated(struc%comment), "Comment line should be preserved") + if (allocated(error)) return + call check(error, struc%comment, "TiO2 rutile") + if (allocated(error)) return + call check(error, struc%nat, 6, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 2, "Number of species does not match") + if (allocated(error)) return + +end subroutine test_invalid6_cjson + + +end module test_read_cjson diff --git a/test/test_write.f90 b/test/test_write.f90 index 37736c39..894db323 100644 --- a/test/test_write.f90 +++ b/test/test_write.f90 @@ -36,6 +36,7 @@ subroutine collect_write(testsuite) type(unittest_type), allocatable, intent(out) :: testsuite(:) testsuite = [ & + & new_unittest("valid-cjson", test_cjson, should_fail=.not.get_mctc_feature("json")), & & new_unittest("valid-mol", test_mol), & & new_unittest("valid-sdf", test_sdf), & & new_unittest("valid-gen", test_gen), & @@ -266,6 +267,30 @@ subroutine test_qcschema(error) end subroutine test_qcschema +subroutine test_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + character(len=:), allocatable :: name + integer :: unit + + name = get_name() // ".cjson" + + call get_structure(struc, "mindless06") + + call write_structure(struc, name, error) + if (.not.allocated(error)) then + call read_structure(struc, name, error) + end if + + open(file=name, newunit=unit) + close(unit, status='delete') + +end subroutine test_cjson + + function get_name() result(name) character(len=18) :: name diff --git a/test/test_write_cjson.f90 b/test/test_write_cjson.f90 new file mode 100644 index 00000000..df29ae30 --- /dev/null +++ b/test/test_write_cjson.f90 @@ -0,0 +1,108 @@ +! This file is part of mctc-lib. +! +! Licensed under the Apache License, Version 2.0 (the "License"); +! you may not use this file except in compliance with the License. +! You may obtain a copy of the License at +! +! http://www.apache.org/licenses/LICENSE-2.0 +! +! Unless required by applicable law or agreed to in writing, software +! distributed under the License is distributed on an "AS IS" BASIS, +! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +! See the License for the specific language governing permissions and +! limitations under the License. + +module test_write_cjson + use mctc_env_testing, only : new_unittest, unittest_type, error_type, check + use testsuite_structure, only : get_structure + use mctc_io_write_cjson + use mctc_io_read_cjson + use mctc_io_structure + use mctc_version, only : get_mctc_feature + implicit none + private + + public :: collect_write_cjson + + +contains + + +!> Collect all exported unit tests +subroutine collect_write_cjson(testsuite) + + !> Collection of tests + type(unittest_type), allocatable, intent(out) :: testsuite(:) + + logical :: with_json + + with_json = get_mctc_feature("json") + + testsuite = [ & + & new_unittest("valid1-cjson", test_valid1_cjson, should_fail=.not.with_json), & + & new_unittest("valid2-cjson", test_valid2_cjson, should_fail=.not.with_json) & + & ] + +end subroutine collect_write_cjson + + +subroutine test_valid1_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit, nat, nid + + call get_structure(struc, "mindless01") + struc%comment = "mindless" + nat = struc%nat + nid = struc%nid + + open(status='scratch', newunit=unit) + call write_cjson(struc, unit) + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, struc%comment, "mindless", "Comment no preserved") + if (allocated(error)) return + call check(error, struc%nat, nat, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, nid, "Number of species does not match") + if (allocated(error)) return + +end subroutine test_valid1_cjson + + +subroutine test_valid2_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit, nat, nid + + call get_structure(struc, "x01") + nat = struc%nat + nid = struc%nid + + open(status='scratch', newunit=unit) + call write_cjson(struc, unit) + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + + call check(error, struc%nat, nat, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, nid, "Number of species does not match") + if (allocated(error)) return + +end subroutine test_valid2_cjson + + +end module test_write_cjson From 8fd9e4397b80f1ed1d0fc3f8ca79fb5098b06c20 Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:12:46 +0200 Subject: [PATCH 09/10] Add support for totalCharge and totalSpinMultiplicity in CJSON (#51) --- src/mctc/io/read/cjson.F90 | 15 +++-- src/mctc/io/write/cjson.f90 | 13 +++++ test/test_read_cjson.f90 | 106 +++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/src/mctc/io/read/cjson.F90 b/src/mctc/io/read/cjson.F90 index ff23cd40..0db802ef 100644 --- a/src/mctc/io/read/cjson.F90 +++ b/src/mctc/io/read/cjson.F90 @@ -50,7 +50,7 @@ subroutine read_cjson(self, unit, error) type(json_value), pointer :: root, val, child, array logical :: cartesian, found - integer :: stat, schema_version, charge, ibond + integer :: stat, schema_version, charge, multiplicity, ibond character(len=:), allocatable :: input, line, message, comment integer, allocatable :: num(:), bond(:, :), list(:), order(:) real(wp) :: cellpar(6) @@ -156,9 +156,14 @@ subroutine read_cjson(self, unit, error) end if call json%get(val, "name", comment, default="") - call json%get(val, "atoms.formalCharges", list, found=found) - charge = 0 - if (allocated(list)) charge = sum(list) + call json%get(val, "properties.totalCharge", charge, found=found) + if (.not.found) then + call json%get(val, "atoms.formalCharges", list, found=found) + charge = 0 + if (allocated(list)) charge = sum(list) + end if + call json%get(val, "properties.totalSpinMultiplicity", multiplicity, found=found) + if (.not.found) multiplicity = 1 if (json%failed()) then call json%check_for_errors(error_msg=message) @@ -172,7 +177,7 @@ subroutine read_cjson(self, unit, error) if (.not.cartesian) then xyz(:, :) = matmul(lattice, xyz(:, :)) end if - call new(self, num, xyz, lattice=lattice, charge=real(charge, wp)) + call new(self, num, xyz, lattice=lattice, charge=real(charge, wp), uhf=multiplicity - 1) if (len(comment) > 0) self%comment = comment if (allocated(bond)) then self%nbd = size(bond, 2) diff --git a/src/mctc/io/write/cjson.f90 b/src/mctc/io/write/cjson.f90 index 1e5483eb..25de9f63 100644 --- a/src/mctc/io/write/cjson.f90 +++ b/src/mctc/io/write/cjson.f90 @@ -95,6 +95,19 @@ pure function json_string(mol, indent) result(string) if (present(indent)) string = string // nl // indent string = string // "}" + string = string // "," + if (present(indent)) string = string // nl // indent + string = string // json_key("properties", indent) // "{" + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("totalCharge", indent) // json_value(nint(mol%charge)) + if (mol%uhf > 0) then + string = string // "," + if (present(indent)) string = string // nl // repeat(indent, 2) + string = string // json_key("totalSpinMultiplicity", indent) // json_value(mol%uhf + 1) + end if + if (present(indent)) string = string // nl // indent + string = string // "}" + if (allocated(mol%bond)) then string = string // "," if (present(indent)) string = string // nl // indent diff --git a/test/test_read_cjson.f90 b/test/test_read_cjson.f90 index 972454d7..48c22832 100644 --- a/test/test_read_cjson.f90 +++ b/test/test_read_cjson.f90 @@ -40,7 +40,9 @@ subroutine collect_read_cjson(testsuite) & new_unittest("valid1-cjson", test_valid1_cjson, should_fail=.not.with_json), & & new_unittest("valid2-cjson", test_valid2_cjson, should_fail=.not.with_json), & & new_unittest("valid3-cjson", test_valid3_cjson, should_fail=.not.with_json), & - & new_unittest("valid4-cjson", test_valid3_cjson, should_fail=.not.with_json), & + & new_unittest("valid4-cjson", test_valid4_cjson, should_fail=.not.with_json), & + & new_unittest("valid5-cjson", test_valid5_cjson, should_fail=.not.with_json), & + & new_unittest("valid6-cjson", test_valid6_cjson, should_fail=.not.with_json), & & new_unittest("invalid1-cjson", test_invalid1_cjson, should_fail=.true.), & & new_unittest("invalid2-cjson", test_invalid2_cjson, should_fail=.true.), & & new_unittest("invalid3-cjson", test_invalid3_cjson, should_fail=.true.), & @@ -332,12 +334,112 @@ subroutine test_valid4_cjson(error) if (allocated(error)) return call check(error, struc%nid, 4, "Number of species does not match") if (allocated(error)) return - call check(error, struc%nbd, 23, "Number of bonds does not match") + call check(error, struc%nbd, 25, "Number of bonds does not match") if (allocated(error)) return end subroutine test_valid4_cjson +subroutine test_valid5_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [', & + ' 8,', & + ' 1', & + ' ]', & + ' },', & + ' "coords": {', & + ' "3d": [', & + ' 1.2358341722502633E+00,', & + ' -9.1774253284895344E-02,', & + ' -6.7936144993384059E-02,', & + ' 1.5475582000473165E+00,', & + ' 5.7192830956765273E-01,', & + ' 5.5691301045614838E-01', & + ' ]', & + ' },', & + ' "formalCharges": [ -1, 0 ]', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + call check(error, struc%nat, 2, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 2, "Number of species does not match") + if (allocated(error)) return + call check(error, nint(struc%charge), -1, "Total charge does not match") + if (allocated(error)) return + +end subroutine test_valid5_cjson + + +subroutine test_valid6_cjson(error) + + !> Error handling + type(error_type), allocatable, intent(out) :: error + + type(structure_type) :: struc + integer :: unit + + open(status='scratch', newunit=unit) + write(unit, '(a)') & + '{', & + ' "chemicalJson": 1,', & + ' "atoms": {', & + ' "elements": {', & + ' "number": [', & + ' 7,', & + ' 7,', & + ' 7', & + ' ]', & + ' },', & + ' "coords": {', & + ' "3d": [', & + ' 3.6361808414857721E-01,', & + ' 1.9287266130863627E+00,', & + ' -1.7850498831821635E+00,', & + ' 8.2217629145179161E-01,', & + ' 2.4066501990670561E+00,', & + ' -2.7896663819784173E+00,', & + ' -9.4568423260748616E-02,', & + ' 1.4516946870018026E+00,', & + ' -7.7682289506097102E-01', & + ' ]', & + ' }', & + ' },', & + ' "properties": {', & + ' "totalCharge": -1', & + ' }', & + '}' + rewind(unit) + + call read_cjson(struc, unit, error) + close(unit) + if (allocated(error)) return + call check(error, struc%nat, 3, "Number of atoms does not match") + if (allocated(error)) return + call check(error, struc%nid, 1, "Number of species does not match") + if (allocated(error)) return + call check(error, nint(struc%charge), -1, "Total charge does not match") + if (allocated(error)) return + +end subroutine test_valid6_cjson + + subroutine test_invalid1_cjson(error) !> Error handling From 87a46cdf5281ad75909083c4f72354d54abdf95d Mon Sep 17 00:00:00 2001 From: Sebastian Ehlert <28669218+awvwgk@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:01:52 +0200 Subject: [PATCH 10/10] Release version 0.3.1 (#52) --- CMakeLists.txt | 2 +- fpm.toml | 2 +- meson.build | 2 +- src/mctc/version.F90 | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69a1f600..b8a8bcd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.14) project( "mctc-lib" LANGUAGES "Fortran" - VERSION "0.3.0" + VERSION "0.3.1" DESCRIPTION "Modular computation tool chain" ) diff --git a/fpm.toml b/fpm.toml index f9598cc4..8ff4041b 100644 --- a/fpm.toml +++ b/fpm.toml @@ -1,5 +1,5 @@ name = "mctc-lib" -version = "0.3.0" +version = "0.3.1" license = "Apache-2.0" maintainer = ["@awvwgk"] author = ["Sebastian Ehlert"] diff --git a/meson.build b/meson.build index 79911c9c..e45cf8e6 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ project( 'mctc-lib', 'fortran', - version: '0.3.0', + version: '0.3.1', license: 'Apache-2.0', meson_version: '>=0.55', default_options: [ diff --git a/src/mctc/version.F90 b/src/mctc/version.F90 index 32795d16..eff4f2bd 100644 --- a/src/mctc/version.F90 +++ b/src/mctc/version.F90 @@ -23,10 +23,10 @@ module mctc_version !> String representation of the mctc-lib version - character(len=*), parameter :: mctc_version_string = "0.3.0" + character(len=*), parameter :: mctc_version_string = "0.3.1" !> Numeric representation of the mctc-lib version - integer, parameter :: mctc_version_compact(3) = [0, 3, 0] + integer, parameter :: mctc_version_compact(3) = [0, 3, 1] !> With support for JSON