From 0b7b473f8b2f2bef90f7d5181d5fcebc69a3e305 Mon Sep 17 00:00:00 2001 From: SY Wang Date: Wed, 16 Sep 2026 23:57:08 +0800 Subject: [PATCH] Refactor: Replace RapidJSON with nlohmann-json --- .github/workflows/build_test_cmake.yml | 4 +- .github/workflows/build_test_makefile.yml | 2 + .github/workflows/coverage.yml | 2 +- .github/workflows/test.yml | 2 +- CMakeLists.txt | 25 +- Dockerfile.cuda | 12 +- Dockerfile.gnu | 12 +- Dockerfile.intel | 12 +- cmake/CollectBuildInfoVars.cmake | 10 +- docs/advanced/install.md | 4 + docs/advanced/json/json_add.md | 267 +++---- docs/advanced/json/json_para.md | 134 ++-- docs/quick_start/easy_install.md | 5 +- source/CMakeLists.txt | 8 +- source/Makefile.Objects | 1 - source/source_esolver/esolver_ks.cpp | 4 +- .../source_esolver/esolver_lr_lcao_tddft.cpp | 7 + source/source_io/CMakeLists.txt | 2 +- source/source_io/build_info.h.in | 2 +- source/source_io/module_json/CMakeLists.txt | 1 - source/source_io/module_json/abacusjson.cpp | 201 ++--- source/source_io/module_json/abacusjson.h | 136 +--- source/source_io/module_json/general_info.cpp | 43 +- source/source_io/module_json/general_info.h | 14 +- source/source_io/module_json/init_info.cpp | 209 ++--- source/source_io/module_json/init_info.h | 28 +- source/source_io/module_json/json_node.h | 26 +- source/source_io/module_json/output_info.cpp | 245 +++--- source/source_io/module_json/output_info.h | 47 +- source/source_io/module_json/para_json.cpp | 16 +- source/source_io/module_json/para_json.h | 24 +- source/source_io/module_json/readin_info.cpp | 11 - source/source_io/module_json/readin_info.h | 18 - .../module_json/test/para_json_test.cpp | 719 ++++++++++-------- source/source_io/parse_args.cpp | 2 +- source/source_main/driver_run.cpp | 2 +- source/source_md/run_md.cpp | 12 + source/source_md/test/CMakeLists.txt | 9 + source/source_relax/relax_driver.cpp | 19 +- source/source_relax/socket_handlers.cpp | 7 + source/source_relax/test/CMakeLists.txt | 8 + toolchain/README.md | 13 +- toolchain/build_abacus_aocc-aocl.sh | 2 +- toolchain/build_abacus_gcc-aocl.sh | 2 +- toolchain/build_abacus_gcc-mkl.sh | 2 +- toolchain/build_abacus_gnu.sh | 2 +- toolchain/build_abacus_intel.sh | 2 +- toolchain/scripts/lib/config_manager.sh | 4 +- toolchain/scripts/lib/package_manager.sh | 2 +- toolchain/scripts/lib/user_interface.sh | 2 +- toolchain/scripts/package_versions.sh | 10 +- toolchain/scripts/stage4/install_json.sh | 67 ++ toolchain/scripts/stage4/install_rapidjson.sh | 131 ---- toolchain/scripts/stage4/install_stage4.sh | 2 +- toolchain/scripts/tool_kit.sh | 2 +- toolchain/tests/test_rapidjson_cmake.sh | 123 --- toolchain/toolchain_aocc-aocl.sh | 4 +- toolchain/toolchain_gcc-aocl.sh | 4 +- toolchain/toolchain_gcc-mkl.sh | 4 +- toolchain/toolchain_gnu.sh | 4 +- toolchain/toolchain_intel.sh | 4 +- tools/03_code_analysis/code_quality_score.py | 0 62 files changed, 1159 insertions(+), 1539 deletions(-) delete mode 100644 source/source_io/module_json/readin_info.cpp delete mode 100644 source/source_io/module_json/readin_info.h create mode 100755 toolchain/scripts/stage4/install_json.sh delete mode 100755 toolchain/scripts/stage4/install_rapidjson.sh delete mode 100755 toolchain/tests/test_rapidjson_cmake.sh mode change 100644 => 100755 tools/03_code_analysis/code_quality_score.py diff --git a/.github/workflows/build_test_cmake.yml b/.github/workflows/build_test_cmake.yml index c14ad0ccdf9..ed760753f58 100644 --- a/.github/workflows/build_test_cmake.yml +++ b/.github/workflows/build_test_cmake.yml @@ -24,11 +24,11 @@ jobs: - tag: gnu external_toolchain_args: "" - build_args: "-DENABLE_LIBXC=ON -DENABLE_MLALGO=ON -DENABLE_LIBRI=ON -DENABLE_DFTD4=ON -DENABLE_PEXSI=ON" + build_args: "-DENABLE_LIBXC=ON -DENABLE_MLALGO=ON -DENABLE_LIBRI=ON -DENABLE_DFTD4=ON -DENABLE_PEXSI=ON -DENABLE_JSON=ON" name: "Build extra components with GNU toolchain" - tag: intel external_toolchain_args: "--with-intel" - build_args: "-DENABLE_LIBXC=ON -DENABLE_MLALGO=ON -DENABLE_LIBRI=ON -DENABLE_DFTD4=ON" + build_args: "-DENABLE_LIBXC=ON -DENABLE_MLALGO=ON -DENABLE_LIBRI=ON -DENABLE_DFTD4=ON -DENABLE_JSON=ON" name: "Build extra components with Intel toolchain" - tag: cuda diff --git a/.github/workflows/build_test_makefile.yml b/.github/workflows/build_test_makefile.yml index 70f5dd5f6cc..bb6f25c206d 100644 --- a/.github/workflows/build_test_makefile.yml +++ b/.github/workflows/build_test_makefile.yml @@ -22,7 +22,9 @@ jobs: - name: Checkout uses: actions/checkout@v7.0.1 - name: Build + # TODO: Remove nlohmann-json installation here since Dockerfile will have installed it. run: | + apt update && apt install -y nlohmann-json3-dev export I_MPI_CXX=icpx chmod a+x generate_build_info.sh cd source diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f6dcf32a9eb..a758bb4b511 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -63,7 +63,7 @@ jobs: -DENABLE_LIBXC=ON \ -DENABLE_LIBRI=ON \ -DENABLE_GOOGLEBENCH=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="--coverage -fprofile-arcs -ftest-coverage" \ -DCMAKE_EXE_LINKER_FLAGS="--coverage" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ed069dc499..e4654bdab3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,7 +93,7 @@ jobs: -DENABLE_LIBXC=ON \ -DENABLE_LIBRI=ON \ -DENABLE_GOOGLEBENCH=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_FLOAT_FFTW=ON \ -DENABLE_DFTD4=ON \ -DENABLE_PEXSI=ON \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 33b852f0cd1..34505c5c24e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ option(ENABLE_NATIVE_OPTIMIZATION option(COMMIT_INFO "Print commit information in log" ON) option(ENABLE_GOOGLEBENCH "Enable GOOGLE-benchmark usage" OFF) -option(ENABLE_RAPIDJSON "Enable rapid-json usage" OFF) +option(ENABLE_JSON "Enable JSON output" OFF) option(ENABLE_CNPY "Enable cnpy usage" OFF) # Options requiring MPI and LCAO @@ -110,6 +110,14 @@ abacus_rename_option(USE_OPENMP ENABLE_OPENMP) abacus_rename_option(USE_ABACUS_LIBM ENABLE_ABACUS_LIBM) abacus_rename_option(INFO MATH_INFO) +if(DEFINED CACHE{ENABLE_RAPIDJSON}) + message( + FATAL_ERROR + "ENABLE_RAPIDJSON is no longer supported. " + "ABACUS now uses nlohmann-json as its JSON backend; " + "please use -DENABLE_JSON=ON instead.") +endif() + if(DEFINED CACHE{ENABLE_LIBCOMM}) message( DEPRECATION @@ -139,15 +147,8 @@ if(NOT DEFINED NVHPC_ROOT_DIR AND DEFINED ENV{NVHPC_ROOT}) endif() # enable json support -if(ENABLE_RAPIDJSON) - find_package(RapidJSON CONFIG REQUIRED) - if(NOT TARGET RapidJSON) - message( - FATAL_ERROR - "RapidJSON was found, but target RapidJSON is missing. " - "Check if your RapidJSON installation provides a complete exported CMake configuration." - ) - endif() +if(ENABLE_JSON) + find_package(nlohmann_json REQUIRED) endif() # get commit info @@ -284,8 +285,8 @@ if(CCACHE) set(CMAKE_CUDA_COMPILER_LAUNCHER ${CCACHE} ${CMAKE_CUDA_COMPILER_LAUNCHER}) endif() -# Choose build type from: Debug Release RelWithDebInfo MinSizeRel Select -# 'Release' configuration for best performance; this will disable all +# Choose build type from: Debug Release RelWithDebInfo MinSizeRel +# Select 'Release' configuration for best performance; this will disable all # assertions. Other default configurations are also available, see: # https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#default-and-custom-configurations # For default flags, see: diff --git a/Dockerfile.cuda b/Dockerfile.cuda index e5ecd76bde1..ac8a09596ce 100644 --- a/Dockerfile.cuda +++ b/Dockerfile.cuda @@ -2,7 +2,7 @@ FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN apt update && apt install -y --no-install-recommends \ libopenblas-openmp-dev liblapack-dev libscalapack-mpi-dev libfftw3-dev libcereal-dev \ - libxc-dev libgtest-dev libgmock-dev libbenchmark-dev python3-numpy ca-certificates \ + libxc-dev libgtest-dev libgmock-dev libbenchmark-dev python3-numpy ca-certificates nlohmann-json3-dev \ bc cmake git g++ make time sudo unzip vim wget libopenmpi-dev gfortran libtool-bin && \ rm -rf /var/lib/apt/lists/* @@ -25,19 +25,11 @@ RUN cd /tmp && \ ln -s /usr/local/include/elpa_openmp-$ELPA_VER/elpa /usr/local/include/ && \ cd /tmp && rm -rf elpa-$ELPA_VER -# RapidJSON -RUN cd /tmp && \ - wget --quiet https://codeload.github.com/Tencent/rapidjson/tar.gz/24b5e7a -O rapidjson-24b5e7a.tar.gz && \ - tar -xzf rapidjson-24b5e7a.tar.gz && cd rapidjson-24b5e7a && \ - cmake -B build -DRAPIDJSON_BUILD_DOC=OFF -DRAPIDJSON_BUILD_EXAMPLES=OFF -DRAPIDJSON_BUILD_TESTS=OFF && \ - cmake --build build --target install && \ - cd /tmp && rm -rf rapidjson-24b5e7a rapidjson-24b5e7a.tar.gz - ADD https://api.github.com/repos/deepmodeling/abacus-develop/git/refs/heads/develop /dev/null RUN git clone https://github.com/deepmodeling/abacus-develop.git --depth 1 && \ cd abacus-develop && \ - cmake -B build -DUSE_CUDA=ON -DENABLE_RAPIDJSON=ON && \ + cmake -B build -DUSE_CUDA=ON -DENABLE_JSON=ON && \ cmake --build build -j $(nproc) && \ cmake --install build && \ rm -rf build && \ diff --git a/Dockerfile.gnu b/Dockerfile.gnu index 943ecf5e7ab..2f0cd53e818 100644 --- a/Dockerfile.gnu +++ b/Dockerfile.gnu @@ -10,7 +10,7 @@ FROM ubuntu:22.04 RUN apt update && apt install -y --no-install-recommends \ libopenblas-openmp-dev liblapack-dev libscalapack-mpi-dev libelpa-dev libfftw3-dev libcereal-dev \ - libxc-dev libgtest-dev libgmock-dev libbenchmark-dev python3-numpy \ + libxc-dev libgtest-dev libgmock-dev libbenchmark-dev python3-numpy nlohmann-json3-dev \ libmetis-dev libparmetis-dev libsuperlu-dist-dev \ bc cmake git g++ make time sudo unzip vim wget gfortran ca-certificates && \ rm -rf /var/lib/apt/lists/* @@ -28,14 +28,6 @@ RUN wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-wit --quiet -O libtorch.zip && \ unzip -q libtorch.zip -d /opt && rm libtorch.zip -# RapidJSON -RUN cd /tmp && \ - wget --quiet https://codeload.github.com/Tencent/rapidjson/tar.gz/24b5e7a -O rapidjson-24b5e7a.tar.gz && \ - tar -xzf rapidjson-24b5e7a.tar.gz && cd rapidjson-24b5e7a && \ - cmake -B build -DRAPIDJSON_BUILD_DOC=OFF -DRAPIDJSON_BUILD_EXAMPLES=OFF -DRAPIDJSON_BUILD_TESTS=OFF && \ - cmake --build build --target install && \ - cd /tmp && rm -r rapidjson-24b5e7a rapidjson-24b5e7a.tar.gz - ENV CMAKE_PREFIX_PATH=/opt/libtorch/share/cmake # PEXSI @@ -56,7 +48,7 @@ ADD https://api.github.com/repos/deepmodeling/abacus-develop/git/refs/heads/deve RUN git clone https://github.com/deepmodeling/abacus-develop.git --depth 1 && \ cd abacus-develop && \ - cmake -B build -DENABLE_MLALGO=ON -DENABLE_PEXSI=ON -DENABLE_LIBXC=ON -DENABLE_LIBRI=ON -DENABLE_RAPIDJSON=ON && \ + cmake -B build -DENABLE_MLALGO=ON -DENABLE_PEXSI=ON -DENABLE_LIBXC=ON -DENABLE_LIBRI=ON -DENABLE_JSON=ON && \ cmake --build build -j $(nproc) && \ cmake --install build && \ rm -rf build diff --git a/Dockerfile.intel b/Dockerfile.intel index 6cfcfc4daa3..b9ac0f8821c 100644 --- a/Dockerfile.intel +++ b/Dockerfile.intel @@ -2,7 +2,7 @@ FROM intel/oneapi-hpckit:2025.2.2-0-devel-ubuntu22.04 RUN apt-get update && apt-get install -y \ bc cmake git gnupg gcc g++ python3-numpy sudo wget vim unzip \ - libcereal-dev libxc-dev libgtest-dev libgmock-dev libbenchmark-dev \ + libcereal-dev libxc-dev libgtest-dev libgmock-dev libbenchmark-dev nlohmann-json3-dev \ pkg-config build-essential autoconf automake libtool ca-certificates && \ rm -rf /var/lib/apt/lists/* @@ -22,14 +22,6 @@ RUN cd /tmp && \ ln -s /usr/local/include/elpa_openmp-$ELPA_VER/elpa /usr/local/include/ && \ cd /tmp && rm -rf elpa-$ELPA_VER -# RapidJSON -RUN cd /tmp && \ - wget --quiet https://codeload.github.com/Tencent/rapidjson/tar.gz/24b5e7a -O rapidjson-24b5e7a.tar.gz && \ - tar -xzf rapidjson-24b5e7a.tar.gz && cd rapidjson-24b5e7a && \ - cmake -B build -DRAPIDJSON_BUILD_DOC=OFF -DRAPIDJSON_BUILD_EXAMPLES=OFF -DRAPIDJSON_BUILD_TESTS=OFF && \ - cmake --build build --target install && \ - cd /tmp && rm -r rapidjson-24b5e7a rapidjson-24b5e7a.tar.gz - # LibTorch (Note: Using pre-built Torch library with MKL might cause issues) RUN wget -q https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.0.0%2Bcpu.zip -O /tmp/libtorch.zip && \ unzip -q /tmp/libtorch.zip -d /opt && rm -f /tmp/libtorch.zip @@ -43,7 +35,7 @@ RUN cd /tmp && git clone https://github.com/deepmodeling/abacus-develop.git --de -DENABLE_MLALGO=ON \ -DENABLE_LIBXC=ON \ -DENABLE_LIBRI=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DCMAKE_BUILD_TYPE=Release && \ cmake --build build -j $(nproc) && \ cmake --install build && \ diff --git a/cmake/CollectBuildInfoVars.cmake b/cmake/CollectBuildInfoVars.cmake index 8fc2603c32f..4fce9188f80 100644 --- a/cmake/CollectBuildInfoVars.cmake +++ b/cmake/CollectBuildInfoVars.cmake @@ -361,12 +361,10 @@ else() set(ABACUS_GOOGLEBENCH_VERSION "Disabled") endif() -if(DEFINED RapidJSON_DIR AND ENABLE_RAPIDJSON) - set(ABACUS_RAPIDJSON_VERSION "yes (path: ${RapidJSON_DIR})") -elseif(ENABLE_RAPIDJSON) - set(ABACUS_RAPIDJSON_VERSION "yes (from git origin/master)") +if(ENABLE_JSON) + set(ABACUS_JSON_VERSION "yes (nlohmann-json v${nlohmann_json_VERSION})") else() - set(ABACUS_RAPIDJSON_VERSION "no") + set(ABACUS_JSON_VERSION "no") endif() if(ENABLE_MLALGO) @@ -458,7 +456,7 @@ if(ENABLE_EXX_DEV) message(" (EXX developing features enabled)") endif() message(" DFT-D4 = ${ABACUS_DFTD4_VERSION}") -message(" RAPIDJSON = ${ABACUS_RAPIDJSON_VERSION}") +message(" JSON = ${ABACUS_JSON_VERSION}") message(" PEXSI = ${ABACUS_PEXSI_VERSION}") message(" CNPY = ${ABACUS_CNPY_VERSION}") message(" -------------------------------------------------------------------") diff --git a/docs/advanced/install.md b/docs/advanced/install.md index a69352dfb97..71319e737d3 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -99,6 +99,10 @@ vdw_d4_model d4 # or d4s for the smooth D4S model If `vdw_d4_xc` is set to `default`, ABACUS will infer the functional name from `dft_functional` or pseudopotential metadata and pass it to the DFT-D4 library. The `vdw_d4_model` keyword selects the dispersion model inside the DFT-D4 library; the default is `d4`, while `d4s` enables the smooth D4S model. +## Build with JSON support + +ABACUS uses [nlohmann-json](https://github.com/nlohmann/json) for optional JSON output. To build ABACUS with JSON support, pass `-DENABLE_JSON=ON` to CMake. + ## Build Unit Tests To build tests for ABACUS, define `BUILD_TESTING` flag. You can also specify path to local installation of [Googletest](https://github.com/google/googletest) by setting `GTEST_DIR` flags. If not found in local, the configuration process will try to download it automatically. diff --git a/docs/advanced/json/json_add.md b/docs/advanced/json/json_add.md index 826b77119f1..6040328843a 100644 --- a/docs/advanced/json/json_add.md +++ b/docs/advanced/json/json_add.md @@ -1,219 +1,182 @@ -# Table of Contents +# ABACUS JSON Development Guide -1. [Abacus-Json Usage Instructions](#1-abacus-json-usage-instructions) - - [Normal Usage](#normal-usage) - - [Add/Modify a value to object json node](#addmodify-a-value-to-object-json-node-key2-is-a-object-node) - - [Pushback a value to array json node](#pushback-a-value-to-array-json-node-key2-is-a-array-node) - - [Initialization and Assignment Functions for Different Value Types in Arrays](#initialization-and-assignment-functions-for-different-value-types-in-arrays) - - [Object Type](#object-type) - - [Array Type](#array-type) - - [Array Modification Instructions](#array-modification-instructions) -2. [Json Codes Addition Guidelines](#2-json-codes-addition-guidelines) - - [Abacus JSON Functionality Code Structure](#abacus-json-functionality-code-structure) - - [Add JSON code principles](#add-json-code-principles) +## Overview +ABACUS uses [nlohmann-json](https://github.com/nlohmann/json) as the backend for its optional JSON output. The JSON implementation is kept under `source/source_io/module_json`, with `AbacusJson` providing the small interface used to build and write `abacus.json`. +The public alias and mutation interfaces are: -# 1. Abacus-Json Usage Instructions +```cpp +using jsonValue = nlohmann::ordered_json; -In Abacus, the main utility functions for manipulating JSON trees are outlined below. These functions are used to add objects to Abacus JSON trees. +// Public static members of Json::AbacusJson: +static void set_json(const std::vector& keys, jsonValue value); +static void append_json(const std::vector& keys, jsonValue value); +``` -Function signature: -void AbacusJson::add_json (std::vector keys, const T& value,bool IsArray) -Where: -- `keys` is a vector of string dictionaries, representing the node paths where values are to be added in the JSON tree. -- `value` is a generic value type, including int, bool, double, string, or rapidjson value type, indicating the value to be added to the Abacus JSON. -- `IsArray` is a boolean object, indicating whether the current node being added is an array. `true` represents an array node, while `false` represents a non-array node. +`jsonValue` uses `nlohmann::ordered_json` so that object keys are written in insertion order. `jsonKeyNode` accepts either a string key or an integer array index, so paths can mix JSON objects and arrays. -Example usage: -const std::string version = "v3.5.2"; -AbacusJson::add_json({"general_info", "version"}, version, false); +`abacusjson.h` includes only `nlohmann/json_fwd.hpp`. A source file that constructs or operates on `jsonValue` must include `` itself, inside the `__JSON` guard. Callers of the higher-level functions in `init_info.h` and `output_info.h` do not need the backend header. +## Adding values +### Add or replace an object member -## Normal usage +Use `set_json()` to assign a value at a path: -### Add/Modify a value to object json node (key2 is a object node): ```cpp -Json::AbacusJson::add_json({"key1","key2"}, 3.1415,false); +Json::AbacusJson::set_json({"general_info", "version"}, version); ``` -### Pushback a value to array json node (key2 is a array node): +Missing intermediate named nodes are created as objects. The final value is replaced regardless of its previous type, including when it is an array or an object. For example, setting a complete coordinate array replaces the old coordinates rather than adding another nested array: + ```cpp -Json::AbacusJson::add_json({"key1","key2"}, 3.1415,true); +Json::AbacusJson::set_json({"init", "coordinate"}, coordinates); ``` -Through this function alone, the addition of the majority of JSON parameters can be achieved. However, for complex array types, additional operations are required. - +Replacing a complete object also replaces all of its members; this is not a merge operation. -## Initialization and Assignment Functions for Different Value Types in Arrays +### Append to an array -### Object Type: -Since the object type consists of key-value pairs, four member functions are divided based on whether key and val are of type std::string. +Use `append_json()` to append one value to an array: -- JaddStringV(str,val): key is not string, val is string -- JaddStringK(str,val): key is string, val is not string -- JaddStringKV(str,val): both key and val are string -- JaddNormal(str,val): both key and val are not string - - -### Array Type: -For array types, the following member functions are used directly. +```cpp +Json::AbacusJson::append_json({"init", "label"}, label); +``` -- JPushBack(val): val is not string -- JPushBackString(val): val is string +A missing final named member is created as an array. An existing destination must already be an array: appending to a scalar, an object, or `null` is an error rather than an implicit conversion. -For example, to add nodes to a JSON tree with multiple arrays in Abacus, the following code is needed: +For nested arrays, construct the value with `jsonValue::array()`: ```cpp -// add key-val to an object array -for(int i=0;i<1;i++){ - Json::jsonValue object(JobjectType); - std::string str = std::to_string(i*100); - - object.JaddNormal("int",i); - object.JaddStringV("string", str); - Json::AbacusJson::add_json({"array"}, object,true); -} +Json::jsonValue coordinate = Json::jsonValue::array({x, y, z}); +Json::AbacusJson::append_json({"init", "coordinate"}, coordinate); ``` -```cpp -// add array in array -Json::jsonValue object0(JarrayType); - -object0.JPushBack(1); -object0.JPushBack(2); -object0.JPushBack(3); +The coordinate is appended as **one row**; its elements are not flattened into the destination array. An empty path is a no-op for both `set_json()` and `append_json()`. -Json::AbacusJson::add_json({"Darray"}, object0,true); -``` +### Construct objects and arrays +Use the nlohmann-json initializer syntax through the `Json::jsonValue` alias. There is no need for backend-specific helper macros. +Object example: +```cpp +Json::jsonValue scf = { + {"energy", energy}, + {"ediff", ediff}, + {"drho", drho}, + {"time", time}, +}; +``` +Array example: -## Array Modification Instructions +```cpp +Json::jsonValue row = Json::jsonValue::array({x, y, z}); +``` -For values that need to be modified in arrays, the following method can be used: -- The index number of the array starts at 0, if it's negative, it's going from back to front. eg. If the index is -1, it means that the last element of the array is modified: -- If the path contains an array, use the array index directly. +Append a completed SCF record with: ```cpp -AbacusJson::add_json({"path",index }, value, is_array); +Json::AbacusJson::append_json({"output", -1, "scf"}, scf); ``` -Here, index is a number. index >= 0 indicates the index from the beginning of the array, while index < 0 indicates traversal from the end of the array. +Construct complete sections or arrays locally before storing them where practical. `gen_general_info()` assigns its complete section once. `gen_stru()` constructs each structure field locally, and `gen_init()` does the same for calculation metadata. These two generators share `init` with `add_nkstot()`, so they replace only their own fields through a file-local helper; they must not replace the entire `init` object and discard fields written by another generator. + +For a current output record, coordinates, magnetic moments, the cell, forces, and stress are replaced as complete arrays. Repeating the geometry update for the same record therefore does not accumulate extra rows. Only genuinely sequential data, such as `output` records and `scf` iteration records, use `append_json()`. + +## Addressing array elements + +Integer path components address existing array elements. Non-negative indices count from the beginning, while negative indices count from the end (`-1` is the last element). Indexed traversal never grows an array. -For example, to modify the value of "vasp" to "cp2k" in the following JSON tree: +For example, given: ```json -"Json":{ - "key6": { - "key7": [ - { - "a":1, - "new":2 - } - "vasp", - "abacus" - ] +{ + "Json": { + "key6": { + "key7": [ + {"a": 1, "new": 2}, + "vasp", + "abacus" + ] + } } } ``` -The relative path of "vasp" in layman's terms is Json - key6 - key7[0]. To use the JSON modification method in abacus, simply change the index [0] to "0". -```cpp -AbacusJson::add_json({"Json","key6","key7",1}, "cp2k" , false); -``` +replace `"vasp"` with `"cp2k"` using either its forward index: -If traversal is done from the end: ```cpp -AbacusJson::add_json({"Json","key6","key7",-2}, "cp2k", false); +Json::AbacusJson::set_json({"Json", "key6", "key7", 1}, "cp2k"); ``` -An error is reported if index exceeds the array length! +or the corresponding negative index: + ```cpp -AbacusJson::add_json({"Json","key6","key7",3}, "cp2k", false); +Json::AbacusJson::set_json({"Json", "key6", "key7", -2}, "cp2k"); ``` +When the destination selected by an integer is itself an array, `append_json()` appends to that nested array; it does not replace the selected element. Out-of-range indices and mismatched object/array path components are errors. +The workflow must call `init_output_array_obj()` before filling the corresponding calculation/ionic-step record. `set_json()` and `append_json()` do not create an implicit current output record when traversing `{"output", -1, ...}`. Record initialization remains the responsibility of the existing driver/solver entry points, not the generic path interface. +## Migrating older JSON call sites -# 2. Abacus Json Codes Addition Guidelines +The former `add_json(keys, value, is_array)` interface has been removed. Choose the new operation by intent, not just by the old boolean: -## Abacus JSON Functionality Code Structure +- Use `set_json()` for scalar assignments, whole-container replacement, and replacement of an indexed element. +- Use `append_json()` for adding one element to a named or indexed array. -The current code structure of JSON functionality in Abacus is roughly as follows: +The old interface appended to an existing named array even when `is_array` was `false`, and it replaced an indexed element even when the flag was `true`. Neither implicit behavior is retained by the new operation names. -- source/source_io - - para_json.cpp: Contains JSON generation and output interfaces directly called by the device in Abacus. - - json_output/: Contains the functionality encapsulation class `abacusjson.cpp` of RapidJSON in Abacus and code classes for parameter generation in various JSON modules. - - test: Code testing files in `json_output`. +## Code structure +The JSON implementation is organized as follows: -## Add JSON code principles: -In Abacus JSON addition, the following principles need to be followed: +```text +source/source_io/module_json/ +├── abacusjson.cpp/.h # set/append path handling and file output +├── json_node.h # object-key / array-index path component +├── general_info.cpp/.h # general_info section +├── init_info.cpp/.h # comment and init sections +├── output_info.cpp/.h # output section +├── para_json.cpp/.h # integration-facing wrappers +└── test/ # focused unit tests +``` -1. Whenever possible, code to be added in the module should be written in the `json_output` module (there may also be cases where parameters cannot be directly obtained through parameter passing in `json_output`), and then called in the path `para_json.cpp` -> `device.cpp` or other main execution paths. (Ensure minimal impact on other modules as much as possible) - -2. For parameters that can be obtained without depending on other modules, do not reference parameter values saved in other modules. (Such as `mpi_num`, `start_time`) +JSON support is compiled under `__JSON`, which is enabled by the CMake option `ENABLE_JSON`. -3. Use classes as function parameters as much as possible instead of using global classes for obtained parameters. (For example, in `gen_general_info`, `Input`) +## Guidelines for extending JSON output -4. After adding parameters, supplement test code in `source_io/json_output/test`. +When adding JSON output: -For the current JSON file, there are two JSON modules: `init` and `general_info`, `output_info`. -Taking `general_info` as an example, the code to be added is as follows: +1. Keep JSON construction in `source/source_io/module_json` whenever practical, rather than spreading nlohmann-json details into unrelated modules. +2. Pass the data required for output explicitly through function parameters. Do not add new `GlobalV`, `GlobalC`, or `PARAM` accesses merely to obtain a value for JSON output. +3. Prefer existing domain objects or small scalar/reference parameters over introducing new cross-module dependencies. +4. Use `Json::jsonValue` for compound JSON values, `set_json()` for assignment, and `append_json()` for sequence growth. +5. Preserve the existing JSON schema unless the change intentionally modifies the public output format. +6. Add or update focused tests under `source/source_io/module_json/test` for new fields and for array/object behavior. -```cpp -namespace Json -{ +For example, `output_info` receives the required values as function arguments and adds them to the current output record: -#ifdef __RAPIDJSON -void gen_general_info(const Parameter& param) +```cpp +void add_output_scf_mag(const double total_mag, + const double absolute_mag, + const double energy, + const double ediff, + const double drho, + const double time) { - -#ifdef VERSION - const std::string version = VERSION; -#else - const std::string version = "unknown"; -#endif -#ifdef COMMIT - const std::string commit = COMMIT; -#else - const std::string commit = "unknown"; -#endif - - // start_time - std::time_t start_time = input->get_start_time(); - std::string start_time_str; - convert_time(start_time, start_time_str); - - // end_time - std::time_t time_now = std::time(NULL); - std::string end_time_str; - convert_time(time_now, end_time_str); - -#ifdef __MPI - int mpi_num = Parallel_Global::mpi_number; - int omp_num = Parallel_Global::omp_number; -#elif - int mpi_num = 1; - int omp_num = 1; -#endif - - AbacusJson::add_json({"general_info", "version"}, version,false); - AbacusJson::add_json({"general_info", "commit"}, commit,false); - AbacusJson::add_json({"general_info", "device"}, input->device,false); - AbacusJson::add_json({"general_info", "mpi_num"}, mpi_num,false); - AbacusJson::add_json({"general_info", "omp_num"}, omp_num,false); - AbacusJson::add_json({"general_info", "pseudo_dir"}, input->pseudo_dir,false); - AbacusJson::add_json({"general_info", "orbital_dir"}, input->orbital_dir,false); - AbacusJson::add_json({"general_info", "stru_file"}, input->stru_file,false); - AbacusJson::add_json({"general_info", "kpt_file"}, input->kpoint_file,false); - AbacusJson::add_json({"general_info", "start_time"}, start_time_str,false); - AbacusJson::add_json({"general_info", "end_time"}, end_time_str,false); + AbacusJson::set_json({"output", -1, "total_mag"}, total_mag); + AbacusJson::set_json({"output", -1, "absolute_mag"}, absolute_mag); + AbacusJson::append_json({"output", -1, "scf"}, + {{"energy", energy}, + {"ediff", ediff}, + {"drho", drho}, + {"time", time}}); } -#endif -} // namespace Json -``` \ No newline at end of file +``` + +This keeps the JSON layer explicit and avoids introducing additional global dependencies into the output path. diff --git a/docs/advanced/json/json_para.md b/docs/advanced/json/json_para.md index 36e5cbebc9b..745e0a5f883 100644 --- a/docs/advanced/json/json_para.md +++ b/docs/advanced/json/json_para.md @@ -1,85 +1,79 @@ -# JSON Configuration Parameters Documentation +# JSON Output Documentation -- [JSON Configuration Parameters Documentation](#json-configuration-parameters-documentation) +- [JSON Output Documentation](#json-output-documentation) - [Overview](#overview) - [General Information](#general-information) - - [Input](#input) - - [Init](#init) + - [Initialization Information](#initialization-information) - [Output](#output) - - [Final Structure](#final-structure) ## Overview -This JSON template provides input and output configurations for ABACUS. It contains parameters for the execution of the program and the output of results, primarily for recording computational processes and outcomes for post processing. - -Notice: one need to add the option `-DENABLE_RAPIDJSON=ON` when compiling ABACUS to enable the output of "abacus.json". +When JSON support is enabled, ABACUS writes calculation metadata and results to `abacus.json` for post-processing. +The current top-level JSON members are `comment`, `init`, `output`, and `general_info`. Some fields are populated only when the corresponding calculation data are available. ## General Information -- `version` - [str] The version number of ABACUS. -- `commit` - [str] The commit hash of ABACUS code at the time of computation. -- `start_time` - [str] The start time of the computation. -- `end_time` - [str] The end time of the computation. -- `device` - [str] The name of the hardware device on which the computation was run. -- `omp_number` - [int] The number of OpenMP threads. -- `mpi_number` - [int] The number of MPI processes. -- `out_dir` - [str] The output directory, e.g., "OUT.ABACUS". -- `log_file` - [str] The name of the log file, e.g., "running_scf.log". -- `pseudo_dir` - [str] The directory where pseudopotential files are stored. -- `orbital_dir` - [str] The directory where atomic orbital files are stored. -- `stru_file` - [str] The name of the structure file. -- `kpt_file` - [str] The name of the k-point file. - -## Input -- A dictionary of parameters and their values as defined by the user in the INPUT file. (This part of the content will not be output in the current version yet.) - - -## Init - - -- `Input` - Lists the value of all input parameters. (This part of the content will not be output in the current version yet.) -- `point_group` - [str] the Schoenflies name of the point group. -- `point_group_in_space` - [str] the Schoenflies name of the point group in the space group. -- `nkstot`, `nkstot_ibz` - [int] Total number of k-points and total number of irreducible k-points. -- `nelectron_each_type` - [object(str-int)] The number of valence electron for each atom type, e.g., `{"C": 2, "H":1}`. -- `nelectron` - [int] Total number of electrons. -- `nband` - [int] Number of bands. +The `general_info` object records basic build and runtime metadata: + +- `version` - [string] ABACUS version. +- `commit` - [string] Git commit information when available at build time. +- `device` - [string] Hardware device selected for the calculation. +- `mpi_num` - [int] Number of MPI processes. +- `omp_num` - [int] Number of OpenMP threads. +- `pseudo_dir` - [string] Pseudopotential directory. +- `orbital_dir` - [string] Numerical atomic orbital directory. +- `stru_file` - [string] Structure input file. +- `kpt_file` - [string] K-point input file. +- `start_time` - [string] Calculation start time. +- `end_time` - [string] Time at which the JSON output is finalized. + +## Initialization Information + +The top-level `comment` describes the default units used by the JSON output. The `init` object records the initial structure and calculation settings. Depending on the calculation path, it can contain: + +- `element` - [object(string:string)] Element/pseudopotential element information keyed by atom label. +- `orb` - [object(string:string/null)] Numerical orbital file for each atom type; `null` when no orbital file is used. +- `pp` - [object(string:string)] Pseudopotential file for each atom type. +- `coordinate` - [array(array(double))] Initial Cartesian coordinates in Angstrom. +- `mag` - [array(double)] Initial magnetic moment for each atom. +- `label` - [array(string)] Atomic labels. +- `cell` - [array(array(double))] Initial lattice vectors in Angstrom. +- `point_group` - [string] Schoenflies name of the point group. +- `point_group_in_space` - [string] Schoenflies name of the point group in the space group. - `natom` - [int] Total number of atoms. -- `natom_each_type` - [object(str-int)] The atom number of each atom type, e.g., `{"C": 2, "H":1}`. -- `label` - [array(str)] An array of atomic labels. -- `element` - [array(object(str:str))] The element of each atom type. -- `cell` - [array(array(double))] The lattice vector. Unit in Angstrom. -- `coordinate` - [array(array(double))] The cartesian coordinates of each atom. Unit in Angstrom. -- `mag` - [array(double)] The magnetic moments for each atom. -- `pp` - [object(str-str)] The pseudopotential file of each atom type. -- `orb` - [object(str-str)] The orbital file of each atom type. - +- `nband` - [int] Number of bands. +- `natom_each_type` - [object(string:int)] Number of atoms of each type. +- `nelectron_each_type` - [object(string:double)] Number of valence electrons for each atom type. +- `nelectron` - [int] Total number of electrons. +- `ecutwfc` - [double] Wavefunction energy cutoff. +- `ecutwfc_unit` - [string] Unit of `ecutwfc`, currently `Ry`. +- `smearing_method` - [string] Smearing method. +- `smearing_sigma` - [double] Smearing width. +- `smearing_sigma_unit` - [string] Unit of `smearing_sigma`, currently `Ry`. +- `kmesh_type` - [string] K-point mesh type. +- `kspacing` - [array(double)] K-point spacing parameters. +- `koffset` - [array(double)] K-point mesh offsets. +- `nkstot` - [int] Total number of k-points, when available on the calculation path. ## Output -An array of dicts, including information about each self-consistent field (SCF) step, such as energy, convergence, and configuration: - -- `energy`, `e_fermi` - [double] The total energy and Fermi energy. Unit in eV. -- `force` - [array(array(double))] The forces calculated on each atom. Unit in eV/Angstrom. -- `stress` - [array(array(double))] The stress tensor. Unit in Kbar. -- `cell` - [array(array(double))] The cell parameters. Unit in Angstrom. -- `coordinate` - [array(array(double))] The coordinates of the atoms in the box after the simulation. Unit in Angstrom. -- `total_mag` , `absolute_mag` , `mag` - [double] The total magnetic moment; total absolute magnetic moment; and a list of magnetic moments for each atom, respectively. -- `scf_converge` - [bool] A boolean indicating whether the scf optimization has converged. -- `scf` - [array(object(str:double)] A list of each scf step, each item contains: - - `energy` - [double] The total energy. Unit in eV. - - `ediff` - [double] The energy difference between the current and previous step. Unit in eV. - - `drho` - [double] The charge density difference between the current and previous step. - - `time` - [double] The time used for the current step. Unit in seconds. - -## Final Structure -Parameters regarding the final converged results and the optimized geometry: - -- `energy` - [double] The final energy. -- `label` - [str] An array of atomic labels. -- `cell` - [array(array(double))] The resulting cell parameters. -- `coordinate` - [array(array(double))] The final atomic coordinates. -- `relax_converge` - [bool] A boolean indicating whether the geometry optimization has converged. -- `dos` - [array(array(array(double)))] The state energy, and the dimension is NSPIN\*NKPOINT\*NBAND. -- `dos_weight` - [array(array(array(double)))] The weight of each state, and the dimension is same as `dos`. \ No newline at end of file +`output` is an array. Each element represents one calculation/ionic-step output record. Fields are filled as the corresponding results become available: + +- `energy` - [double/null] Total energy in eV. +- `e_fermi` - [double/null] Fermi energy in eV. +- `scf_converge` - [bool/null] Whether the SCF calculation converged. +- `force` - [array(array(double))/null] Atomic forces in eV/Angstrom when force calculation is enabled. +- `stress` - [array(array(double))/null] Stress tensor in kbar when stress calculation is enabled. +- `coordinate` - [array(array(double))] Cartesian coordinates in Angstrom. +- `mag` - [array(double)] Magnetic moment for each atom. +- `cell` - [array(array(double))] Lattice vectors in Angstrom. +- `total_mag` - [double] Total magnetic moment when available. +- `absolute_mag` - [double] Absolute magnetic moment when available. +- `scf` - [array(object)] SCF iteration history. Each entry contains: + - `energy` - [double] Total energy in eV. + - `ediff` - [double] Energy change from the previous SCF step in eV. + - `drho` - [double] Charge-density difference. + - `time` - [double] Time used by the SCF step in seconds. + +JSON numbers are intended to be consumed as numeric values. Their textual representation (for example, decimal versus scientific notation) is not part of the output schema. diff --git a/docs/quick_start/easy_install.md b/docs/quick_start/easy_install.md index b8db35a5fd8..474f31c1c16 100644 --- a/docs/quick_start/easy_install.md +++ b/docs/quick_start/easy_install.md @@ -124,7 +124,7 @@ Possible command: `cmake -B build -DENABLE_MLALGO=ON -DENABLE_LIBXC=ON -DENABLE_ Some of these packages can be installed with popular package management system via root permission if you have, such as `apt` and `yum`. For example: ```bash -sudo apt update && sudo apt install -y libopenmpi-dev libopenblas-openmp-dev libscalapack-openmpi-dev libelpa-dev libfftw3-dev libcereal-dev libxc-dev g++ make cmake bc git pkgconf +sudo apt update && sudo apt install -y libopenmpi-dev libopenblas-openmp-dev libscalapack-openmpi-dev libelpa-dev libfftw3-dev libcereal-dev libxc-dev nlohmann-json3-dev g++ make cmake bc git pkgconf ``` > Installing ELPA by apt only matches requirements on Ubuntu 22.04. For earlier linux distributions, you should build ELPA from source. @@ -164,13 +164,14 @@ Here, 'build' is the path for building ABACUS; and '-D' is used for setting up s - `LIBCOMM_DIR`: (Optional) Path to LibComm when `ENABLE_LIBRI=ON`. ```{important} -For some dependencies built with CMake, such as Libxc, dftd4, cereal, and RapidJSON, you'll have to add their prefix paths to the environment variable `CMAKE_PREFIX_PATH` so that CMake can correctly find and use their CMake configuration files. A non-general variable such as `PKG_DIR` is discouraged for these packages. +For some dependencies built with CMake, such as Libxc, dftd4, cereal, and nlohmann-json, you'll have to add their prefix paths to the environment variable `CMAKE_PREFIX_PATH` so that CMake can correctly find and use their CMake configuration files. A non-general variable such as `PKG_DIR` is discouraged for these packages. ``` - Components: The values of these variables should be 'ON', '1' or 'OFF', '0'. The default values are given below. - `ENABLE_LCAO=ON`: Enable LCAO calculation. If SCALAPACK, ELPA or CEREAL is absent and only require plane-wave calculations, the feature of calculating LCAO basis can be turned off. - `ENABLE_LIBXC=OFF`: [Enable Libxc](../advanced/install.md#add-libxc-support) to suppport variety of functionals. - `ENABLE_LIBRI=OFF`: [Enable LibRI](../advanced/install.md#add-libri-support) and its LibComm dependency for hybrid-functional calculations. Set `LIBRI_DIR` and `LIBCOMM_DIR` to use manually installed libraries. + - `ENABLE_JSON=OFF`: [Enable JSON output](../advanced/install.md#build-with-json-support) through nlohmann-json. - `ENABLE_OPENMP=ON`: Enable OpenMP support. Building ABACUS without OpenMP is not fully tested yet. - `BUILD_TESTING=OFF`: [Build unit tests](../advanced/install.md#build-unit-tests). - `ENABLE_GOOGLEBENCH=OFF`: [Build performance tests](../advanced/install.md#build-performance-tests) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 8a617276a9f..6ebf01656ae 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -31,7 +31,7 @@ include(CompilerConfiguration) set(_abacus_feature_definitions __FFTW3 - $<$:__RAPIDJSON> + $<$:__JSON> $<$:__CUDA_ON_DCU> $<$:__CUDA_MPI> $<$:__ABACUS_LIBM> @@ -250,7 +250,7 @@ endif() # ------------------------------------------------------------------------------ set(_abacus_feature_libs - $<$:RapidJSON> + $<$:nlohmann_json::nlohmann_json> $<$:ELPA::ELPA> $<$:PEXSI::PEXSI> $<$:cnpy> @@ -374,7 +374,7 @@ target_compile_options(abacus_compile_requirements INTERFACE ${_abacus_feature_compile_options}) foreach(_abacus_feature_target IN ITEMS - RapidJSON + nlohmann_json::nlohmann_json cereal::cereal ELPA::ELPA Libxc::xc @@ -744,7 +744,7 @@ endif() if(ENABLE_LIBRI) target_link_libraries(${ABACUS_BIN_NAME} PRIVATE ri module_exx_symmetry) endif() -if(ENABLE_RAPIDJSON) +if(ENABLE_JSON) target_link_libraries(${ABACUS_BIN_NAME} PRIVATE json_output) endif() diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 27158e05069..f15d5883ea1 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -677,7 +677,6 @@ OBJS_IO=module_parameter/input_conv.o\ abacusjson.o\ general_info.o\ init_info.o\ - readin_info.o\ output_info.o\ parse_args.o\ module_parameter/availability.o\ diff --git a/source/source_esolver/esolver_ks.cpp b/source/source_esolver/esolver_ks.cpp index 65bcd52665f..a424059645a 100644 --- a/source/source_esolver/esolver_ks.cpp +++ b/source/source_esolver/esolver_ks.cpp @@ -288,13 +288,13 @@ void ESolver_KS::iter_finish(UnitCell& ucell, const int istep, int& iter, bool & dkin, duration, diag_ethr, 0, true, this->ds_rms_); -#ifdef __RAPIDJSON +#ifdef __JSON // add Json of scf mag Json::add_output_scf_mag(ucell.magnet.tot_mag, ucell.magnet.abs_mag, this->pelec->f_en.etot * ModuleBase::Ry_to_eV, this->pelec->f_en.etot_delta * ModuleBase::Ry_to_eV, drho, duration); -#endif //__RAPIDJSON +#endif //__JSON } diff --git a/source/source_esolver/esolver_lr_lcao_tddft.cpp b/source/source_esolver/esolver_lr_lcao_tddft.cpp index 968aef6c9d6..22fa5389018 100644 --- a/source/source_esolver/esolver_lr_lcao_tddft.cpp +++ b/source/source_esolver/esolver_lr_lcao_tddft.cpp @@ -17,6 +17,9 @@ #include "source_lcao/module_lr/utils/lr_util_print.h" #include "source_base/module_external/scalapack_connector.h" #include "source_io/module_parameter/parameter.h" +#ifdef __JSON +#include "source_io/module_json/output_info.h" +#endif #include "source_lcao/module_lr/ri_benchmark/ri_benchmark.h" #include "source_lcao/module_lr/operator_casida/operator_lr_diag.h" // for precondition #ifdef __EXX @@ -222,6 +225,10 @@ void ModuleESolver::ESolver_LR::before_all_runners(BaseCell& basecell, co this->inp_ = &inp; if (inp.esolver_type == "ks-lr") { +#ifdef __JSON + // The embedded KS run happens before Relax_Driver starts its first step. + Json::init_output_array_obj(); +#endif ModuleESolver::ESolver_KS_LCAO ks_solver; ks_solver.before_all_runners(basecell, inp); ks_solver.runner(basecell, 0); diff --git a/source/source_io/CMakeLists.txt b/source/source_io/CMakeLists.txt index 72520a82d93..ebe1a3a9ce2 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -152,6 +152,6 @@ if(BUILD_TESTING) add_subdirectory(test) endif() -if(ENABLE_RAPIDJSON) +if(ENABLE_JSON) add_subdirectory(module_json) endif() diff --git a/source/source_io/build_info.h.in b/source/source_io/build_info.h.in index 707a49d99f7..543ef3e1b41 100644 --- a/source/source_io/build_info.h.in +++ b/source/source_io/build_info.h.in @@ -52,7 +52,7 @@ // --- Other Libraries --- #define ABACUS_DFTD4_VERSION "@ABACUS_DFTD4_VERSION@" -#define ABACUS_RAPIDJSON_VERSION "@ABACUS_RAPIDJSON_VERSION@" +#define ABACUS_JSON_VERSION "@ABACUS_JSON_VERSION@" #define ABACUS_PEXSI_VERSION "@ABACUS_PEXSI_VERSION@" #define ABACUS_CNPY_VERSION "@ABACUS_CNPY_VERSION@" diff --git a/source/source_io/module_json/CMakeLists.txt b/source/source_io/module_json/CMakeLists.txt index 9a94822f570..e60a646bb98 100644 --- a/source/source_io/module_json/CMakeLists.txt +++ b/source/source_io/module_json/CMakeLists.txt @@ -2,7 +2,6 @@ list(APPEND objects_json abacusjson.cpp general_info.cpp init_info.cpp - readin_info.cpp output_info.cpp ) diff --git a/source/source_io/module_json/abacusjson.cpp b/source/source_io/module_json/abacusjson.cpp index ea6a59992eb..a37ba19a94c 100644 --- a/source/source_io/module_json/abacusjson.cpp +++ b/source/source_io/module_json/abacusjson.cpp @@ -1,152 +1,107 @@ #include "abacusjson.h" +#ifdef __JSON +#include +#include #include -#include -#include -#include -#include +#include +#include + namespace Json { - -#ifdef __RAPIDJSON -rapidjson::Document AbacusJson::doc; - -bool isNum(std::string str) -{ - std::stringstream sin; - sin<> d)) - return false; - - if (sin >> c) - return false; - return true; -} - - -void AbacusJson::add_nested_member(std::vector::iterator begin, - std::vector::iterator end, - rapidjson::Value& val, - rapidjson::Value& parent, - rapidjson::Document::AllocatorType& allocator, - bool IsArray - ) +namespace { - if (begin != end) +// Only missing named nodes are created. Indexed access never grows an array. +jsonValue* resolve_path(jsonValue& root, + const std::vector& keys, + jsonValue initial_value) +{ + if (keys.empty()) { - jsonKeyNode keyNode = *begin; - rapidjson::Value key((*begin).key.c_str(), allocator); - + return nullptr; + } - if (begin + 1 == end) + jsonValue* parent = &root; + for (std::size_t i = 0; i < keys.size(); ++i) + { + const jsonKeyNode& key = keys[i]; + if (key.is_index) { - - if( keyNode.key.empty() && parent.IsArray()){ - int index = keyNode.i; - if(index>=0){ - parent[index] = val; - } - else { - int arr_size = parent.Size(); - parent[arr_size+index] = val; - } + if (!parent->is_array()) + { + throw std::invalid_argument("JSON output: an integer path component requires an array"); } - // if key exists, then overwrite it - else if (parent.HasMember(key)) + const std::ptrdiff_t size = static_cast(parent->size()); + std::ptrdiff_t index = static_cast(key.i); + if (index < 0) { - if(parent[key].IsArray()){ - parent[key].PushBack(val, allocator); - }else{ - // if key is an object, then warn the user - if (parent[key].IsObject()) - { - std::cout << "Warning: write to json, key " << (*begin).key - << " exist and is an object, and abacus will overwrite it with a value." << std::endl; - } - parent[key] = val; - } + index += size; } - else{ - if(IsArray==true){ - rapidjson::Value arr(rapidjson::kArrayType); - arr.PushBack(val, allocator); - parent.AddMember(key, arr, allocator); - } else{ - parent.AddMember(key, val, allocator); - } - + if (index < 0 || index >= size) + { + throw std::out_of_range("JSON output: array index out of range"); } + parent = &parent->at(static_cast(index)); } else { - if( keyNode.key.empty()&&parent.IsArray()){ - int index = keyNode.i; - - if(index>=0){ - add_nested_member(begin + 1, end, val, parent[index], allocator,IsArray); - } - else { - int arr_size = parent.Size(); - add_nested_member(begin + 1, end, val, parent[arr_size+index], allocator,IsArray); - } - } - // need to check if the key exists - else if (parent.HasMember(key)) + if (!parent->is_object()) { - // this key should be an object - if (!parent[key].IsObject()&&!parent[key].IsArray()) - { - std::cout << "Warning: write to json, key " << (*begin).key - << " exist and is not an object or array, and abacus will add it as a middle node." << std::endl; - } - add_nested_member(begin + 1, end, val, parent[key], allocator,IsArray); + throw std::invalid_argument("JSON output: a named path component requires an object"); } - else + jsonValue::iterator child = parent->find(key.key); + if (child == parent->end()) { - rapidjson::Value paraent_val(rapidjson::kObjectType); - add_nested_member(begin + 1, end, val, paraent_val, allocator,IsArray); - parent.AddMember(key, paraent_val, allocator); + jsonValue initial = i + 1 == keys.size() ? std::move(initial_value) : jsonValue::object(); + child = parent->emplace(key.key, std::move(initial)).first; } + parent = &child.value(); } } + return parent; } -// Output the json to a file -void AbacusJson::write_to_json(std::string filename) -{ - rapidjson::StringBuffer buffer; - rapidjson::PrettyWriter writer(buffer); - doc.Accept(writer); +} // namespace - std::ofstream ofs(filename); - ofs << buffer.GetString(); - ofs.close(); -}; - template <> - void AbacusJson::add_json(std::vector keys, const std::string& value,bool IsArray) - { - if (!doc.IsObject()) - { - doc.SetObject(); - } - rapidjson::Value val(value.c_str(), doc.GetAllocator()); - add_nested_member(keys.begin(), keys.end(), val, doc, doc.GetAllocator(),IsArray); - } +jsonValue AbacusJson::doc = jsonValue::object(); +void AbacusJson::set_json(const std::vector& keys, jsonValue value) +{ + jsonValue* target = resolve_path(doc, keys, nullptr); + if (target != nullptr) + { + *target = std::move(value); + } +} -// Overloaded template functions for json class objects - template <> - void AbacusJson::add_json(std::vector keys, const rapidjson::Value& value,bool IsArray) - { +void AbacusJson::append_json(const std::vector& keys, jsonValue value) +{ + jsonValue* target = resolve_path(doc, keys, jsonValue::array()); + if (target == nullptr) + { + return; + } + if (!target->is_array()) + { + throw std::invalid_argument("JSON output: append requires an array"); + } + target->push_back(std::move(value)); +} - if (!doc.IsObject()) - { - doc.SetObject(); - } +void AbacusJson::write_to_json(const std::string& filename) +{ + const auto content = doc.dump(4); + std::ofstream file(filename); + if (!file) + { + throw std::runtime_error("Cannot open JSON output file: " + filename); + } + file << content; + file.close(); + if (!file) + { + throw std::runtime_error("Cannot write JSON output file: " + filename); + } +} - rapidjson::Value val(value,doc.GetAllocator()); - add_nested_member(keys.begin(), keys.end(), val, doc, doc.GetAllocator(),IsArray); - } -#endif } // namespace Json +#endif // __JSON diff --git a/source/source_io/module_json/abacusjson.h b/source/source_io/module_json/abacusjson.h index 7bc003bbc92..382cc460cee 100644 --- a/source/source_io/module_json/abacusjson.h +++ b/source/source_io/module_json/abacusjson.h @@ -1,132 +1,48 @@ #ifndef ABACUS_JSON_H #define ABACUS_JSON_H -#include -#include #include #include #include "json_node.h" - -#ifdef __RAPIDJSON -#include "rapidjson/document.h" -#include "rapidjson/prettywriter.h" -#include "rapidjson/stringbuffer.h" - -/** -* @brief Define of AbacusJson:These macro definitions simplify the complex parameters -* required to add objects in rapidjson, making it easy to use. -* @usage: 1. create the json value by Type: eg. -* Json::jsonValue object(JobjectType); -* Json::jsonValue array(JarrayType); -* 2 - object. add the parameter by using correct function. If the constructed object contains -* type std::string, select the correct macro definition function in the key -* or value position based on std::string. -* eg. key is std::string, using: object.JaddStringK(str,val) -* eg. val is std::string, using: object.JaddStringV (str,val) -* eg. both key,val is std::string, using: object.JaddStringKV(str,val) -* eg. none of key,val is std::string, using: object.JaddNormal(str,val) -* -* 2 - array. using: array.JPushBack(val) -* or : JPushBackString(val) -*/ - -#define JobjectType rapidjson::kObjectType -#define JarrayType rapidjson::kArrayType -#define Get_Jallocator Json::AbacusJson::allocator() -#define Set_JString(str) Json::jsonValue().SetString(str.c_str(),str.length(),Get_Jallocator) - - -#define JaddStringV(str,val) AddMember(str, Set_JString(val), Get_Jallocator) -#define JaddStringK(str,val) AddMember(Set_JString(str), val, Get_Jallocator) -#define JaddStringKV(str,val) AddMember(Set_JString(str), Set_JString(val), Get_Jallocator) -#define JaddNormal(str,val) AddMember(str, val, Get_Jallocator) -#define JPushBack(val) PushBack(val, Get_Jallocator) -#define JPushBackString(val) PushBack(Set_JString(val), Get_Jallocator) -/** -* @brief The json processing module in abacus contains basic meta-operations such as -* adding, modifying, and checking json. -*/ +#ifdef __JSON +// Keep the implementation-heavy json.hpp out of this header. +#include namespace Json { -using jsonValue = rapidjson::Value; -// This class is used to construct a json value, and output some key values to a json file. +using jsonValue = nlohmann::ordered_json; + +class AbacusJsonTestAccess; + class AbacusJson { public: - - - - // Output the json to a file - static void write_to_json(std::string filename); - - static rapidjson::Document::AllocatorType& allocator(){ - return doc.GetAllocator(); - } + static void write_to_json(const std::string& filename); /** - * @brief: The template specialization method adds value to the doc tree - * - * @param: 'keys' is a vector string, represents the path to be added to the json tree. - * - * 'value' is the value that needs to be added to the json tree, which type can be - * Json::jsonValue or other common value type(such as int, double ,bool ,std::string). - * - * 'IsArray' is a bool value, means the whether the root node to which 'value' is added is an array. - * - * @usage: 1. Add/Modify a double val to object json node (key2 is a object node): - * Json::AbacusJson::add_json({"key1","key2"}, 3.1415,false); - * - * 2. Pushback a double val to array json node (key2 is a array node): - * Json::AbacusJson::add_json({"key1","key2"}, 3.1415,true); - * - * 3. Modify a doble val to array json node (key2 is a array node), when use this method, - * The index number of the array starts at 0, if it's negative, it's going from back to front. - * eg. If the index is -1, it means that the last element of the array is modified: - * If we have a json array: {"key":[1,2,3]} - * i). Json::AbacusJson::add_json({"key",0}, 4,true); => {"key":[4,2,3]} - * ii). Json::AbacusJson::add_json({"key",-1}, 4,true); => {"key":[1,2,4]} - * iii). Json::AbacusJson::add_json({"key",1}, 4,true); => {"key":[1,4,3]} - * iv). Json::AbacusJson::add_json({"key",2}, 4,true); => {"key":[1,2,4]} - * iv). Json::AbacusJson::add_json({"key",3}, 4,true); => error!, The array element corresponding - * to the index has no value. - */ - template - static void add_json(std::vector keys, const T& value,bool IsArray) - { - if (!doc.IsObject()) - { - doc.SetObject(); - } - rapidjson::Value val(value); - add_nested_member(keys.begin(), keys.end(), val, doc, doc.GetAllocator(),IsArray); - } - - + * Replace a value at a named or indexed path, including whole containers. + * Missing named parents are created as objects. Integer indices must refer + * to existing array elements; negative indices count from the end. + * An empty path leaves the document unchanged. + */ + static void set_json(const std::vector& keys, jsonValue value); + /** + * Append one value to an array at the path, without flattening that value. + * A missing named destination is created as an array. An existing + * destination must be an array, including when selected by an integer + * index; nulls, objects and scalars are rejected. Path rules match set_json. + */ + static void append_json(const std::vector& keys, jsonValue value); private: - static rapidjson::Document doc; - - static void add_nested_member(std::vector::iterator begin, - std::vector::iterator end, - rapidjson::Value& val, - rapidjson::Value& parent, - rapidjson::Document::AllocatorType& allocator, - bool IsArray - ); - }; - -template <> -void AbacusJson::add_json(std::vector keys, const std::string& value,bool IsArray); - -template <> -void AbacusJson::add_json(std::vector keys, const rapidjson::Value& value,bool IsArray); - + friend class AbacusJsonTestAccess; + static jsonValue doc; +}; } // namespace Json -#endif +#endif // __JSON -#endif \ No newline at end of file +#endif diff --git a/source/source_io/module_json/general_info.cpp b/source/source_io/module_json/general_info.cpp index 8b45c0f1921..0e3b7c01c19 100644 --- a/source/source_io/module_json/general_info.cpp +++ b/source/source_io/module_json/general_info.cpp @@ -2,14 +2,18 @@ #include "para_json.h" #include "abacusjson.h" +#include "source_io/module_parameter/parameter.h" + +#ifdef __JSON +#include +#endif #include "source_base/parallel_global.h" #include "source_main/version.h" -// Add json objects to gener_info namespace Json { -#ifdef __RAPIDJSON +#ifdef __JSON void gen_general_info(const Parameter& param) { @@ -43,29 +47,18 @@ void gen_general_info(const Parameter& param) int omp_num = 1; #endif - AbacusJson::add_json({"general_info", "version"}, version, false); - AbacusJson::add_json({"general_info", "commit"}, commit, false); - AbacusJson::add_json({"general_info", "device"}, param.inp.device, false); - AbacusJson::add_json({"general_info", "mpi_num"}, mpi_num, false); - AbacusJson::add_json({"general_info", "omp_num"}, omp_num, false); - AbacusJson::add_json({"general_info", "pseudo_dir"}, param.inp.pseudo_dir, false); - AbacusJson::add_json({"general_info", "orbital_dir"}, param.inp.orbital_dir, false); - AbacusJson::add_json({"general_info", "stru_file"}, param.globalv.global_in_stru, false); - AbacusJson::add_json({"general_info", "kpt_file"}, param.inp.kpoint_file, false); - AbacusJson::add_json({"general_info", "start_time"}, start_time_str, false); - AbacusJson::add_json({"general_info", "end_time"}, end_time_str, false); - - // AbacusJson::add_Json(version,false,"general_info", "version"); - // AbacusJson::add_Json(commit,false,"general_info", "commit"); - // AbacusJson::add_Json(param.inp.device,false,"general_info", "device"); - // AbacusJson::add_Json(mpi_num,false,"general_info", "mpi_num"); - // AbacusJson::add_Json(omp_num,false,"general_info", "omp_num"); - // AbacusJson::add_Json(param.inp.pseudo_dir,false,"general_info", "pseudo_dir"); - // AbacusJson::add_Json(param.inp.orbital_dir,false,"general_info", "orbital_dir"); - // AbacusJson::add_Json(param.inp.stru_file,false,"general_info", "stru_file"); - // AbacusJson::add_Json(param.inp.kpoint_file,false,"general_info", "kpt_file"); - // AbacusJson::add_Json(start_time_str,false,"general_info", "start_time"); - // AbacusJson::add_Json(end_time_str,false,"general_info", "end_time"); + AbacusJson::set_json({"general_info"}, + {{"version", version}, + {"commit", commit}, + {"device", param.inp.device}, + {"mpi_num", mpi_num}, + {"omp_num", omp_num}, + {"pseudo_dir", param.inp.pseudo_dir}, + {"orbital_dir", param.inp.orbital_dir}, + {"stru_file", param.globalv.global_in_stru}, + {"kpt_file", param.inp.kpoint_file}, + {"start_time", start_time_str}, + {"end_time", end_time_str}}); } #endif } // namespace Json diff --git a/source/source_io/module_json/general_info.h b/source/source_io/module_json/general_info.h index 8cb6a1f6d02..d338281e068 100644 --- a/source/source_io/module_json/general_info.h +++ b/source/source_io/module_json/general_info.h @@ -1,14 +1,16 @@ #ifndef GENERAL_INFO_H #define GENERAL_INFO_H -#include "source_io/module_parameter/parameter.h" + +class Parameter; /** -* @brief In this part of the code to complete the general_info part of the json tree. -*/ + * @brief Generate the general_info section of the JSON tree. + */ namespace Json { -#ifdef __RAPIDJSON +#ifdef __JSON void gen_general_info(const Parameter& param); #endif -} -#endif \ No newline at end of file +} // namespace Json + +#endif diff --git a/source/source_io/module_json/init_info.cpp b/source/source_io/module_json/init_info.cpp index 721ffd2781b..1d750a2092d 100644 --- a/source/source_io/module_json/init_info.cpp +++ b/source/source_io/module_json/init_info.cpp @@ -1,173 +1,102 @@ #include "init_info.h" -#include "source_io/module_parameter/input_parameter.h" -#include "para_json.h" #include "abacusjson.h" +#include "source_cell/atom_spec.h" +#include "source_cell/unitcell.h" +#include "source_io/module_parameter/input_parameter.h" + +#ifdef __JSON +#include +#include -// Add json objects to init namespace Json { - -#ifdef __RAPIDJSON +namespace +{ +// Structure, k-point metadata and calculation metadata share the init section. +// Replace only the fields built by this generator, not the entire section. +void set_init_fields(jsonValue fields) +{ + for (jsonValue::iterator field = fields.begin(); field != fields.end(); ++field) + { + AbacusJson::set_json({"init", field.key()}, std::move(field.value())); + } +} +} // namespace void gen_init(UnitCell* ucell, const Input_para& inp) { - std::string pgname = ucell->symm.pgname; - std::string spgname = ucell->symm.spgname; - AbacusJson::add_json({"init", "point_group"}, pgname, false); - AbacusJson::add_json({"init", "point_group_in_space"}, spgname, false); - - // Json::AbacusJson::add_Json(pgname,false,"init", "point_group"); - // Json::AbacusJson::add_Json(spgname,false,"init","point_group_in_space"); + jsonValue info = {{"point_group", ucell->symm.pgname}, + {"point_group_in_space", ucell->symm.spgname}, + {"natom", ucell->nat}, + {"nband", inp.nbands}}; - int numAtoms = ucell->nat; - AbacusJson::add_json({"init", "natom"}, numAtoms, false); - AbacusJson::add_json({"init", "nband"}, inp.nbands, false); - - // Json::AbacusJson::add_Json(numAtoms,false,"init", "natom"); - // Json::AbacusJson::add_Json(PARAM.inp.nbands,false,"init", "nband"); - - int ntype = ucell->ntype, nelec_total = 0; - for (int it = 0; it < ntype; it++) + int nelec_total = 0; + for (int it = 0; it < ucell->ntype; ++it) { - std::string label = ucell->atoms[it].label; - int atom_number = ucell->atoms[it].na; - double number = ucell->atoms[it].ncpp.zv; - - nelec_total += ucell->atoms[it].ncpp.zv * ucell->atoms[it].na; - AbacusJson::add_json({"init", "natom_each_type", label}, atom_number, false); - AbacusJson::add_json({"init", "nelectron_each_type", label}, number, false); - - // Json::AbacusJson::add_Json(number,false,"init", "nelectron_each_type",label); + const Atom& atom = ucell->atoms[it]; + nelec_total += atom.ncpp.zv * atom.na; + info["natom_each_type"][atom.label] = atom.na; + info["nelectron_each_type"][atom.label] = atom.ncpp.zv; } - AbacusJson::add_json({"init", "nelectron"}, nelec_total, false); - - // Json::AbacusJson::add_Json(nelec_total,false,"init", "nelectron"); - - // energy cutoff for wavefunctions (Ry) - AbacusJson::add_json({"init", "ecutwfc"}, inp.ecutwfc, false); - AbacusJson::add_json({"init", "ecutwfc_unit"}, "Ry", false); - - // smearing method and sigma (Ry) - AbacusJson::add_json({"init", "smearing_method"}, inp.smearing_method, false); - AbacusJson::add_json({"init", "smearing_sigma"}, inp.smearing_sigma, false); - AbacusJson::add_json({"init", "smearing_sigma_unit"}, "Ry", false); - - // k-point mesh generation parameters - AbacusJson::add_json({"init", "kmesh_type"}, inp.kmesh_type, false); - Json::jsonValue kspacing_array(JarrayType); - kspacing_array.JPushBack(inp.kspacing[0]); - kspacing_array.JPushBack(inp.kspacing[1]); - kspacing_array.JPushBack(inp.kspacing[2]); - AbacusJson::add_json({"init", "kspacing"}, kspacing_array, false); - Json::jsonValue koffset_array(JarrayType); - koffset_array.JPushBack(inp.koffset[0]); - koffset_array.JPushBack(inp.koffset[1]); - koffset_array.JPushBack(inp.koffset[2]); - AbacusJson::add_json({"init", "koffset"}, koffset_array, false); + info["nelectron"] = nelec_total; + info["ecutwfc"] = inp.ecutwfc; + info["ecutwfc_unit"] = "Ry"; + info["smearing_method"] = inp.smearing_method; + info["smearing_sigma"] = inp.smearing_sigma; + info["smearing_sigma_unit"] = "Ry"; + info["kmesh_type"] = inp.kmesh_type; + info["kspacing"] = jsonValue::array({inp.kspacing[0], inp.kspacing[1], inp.kspacing[2]}); + info["koffset"] = jsonValue::array({inp.koffset[0], inp.koffset[1], inp.koffset[2]}); + set_init_fields(std::move(info)); } void add_nkstot(int nkstot) { - Json::AbacusJson::add_json({"init", "nkstot"}, nkstot, false); - - // Json::AbacusJson::add_Json(nkstot,false,"init", "nkstot"); - // Json::AbacusJson::add_Json(nkstot_ibz,false,"init", "nkstot_ibz"); + AbacusJson::set_json({"init", "nkstot"}, nkstot); } void gen_stru(UnitCell* ucell, const Input_para& inp) { - AbacusJson::add_json({"comment"}, - "Unless otherwise specified, the unit of energy is eV and the unit of length is Angstrom", - false); + AbacusJson::set_json({"comment"}, + "Unless otherwise specified, the unit of energy is eV and the unit of length is Angstrom"); - int ntype = ucell->ntype; - - // array of pseudopotential file - std::string* pseudo_fn = ucell->pseudo_fn.data(); - - // array of orbital file - std::string* orbital_fn = ucell->orbital_fn.data(); - - // add atom element,orbital file and pseudopotential file - for (int i = 0; i < ntype; i++) + jsonValue info = jsonValue::object(); + for (int it = 0; it < ucell->ntype; ++it) { - std::string atom_label = ucell->atoms[i].label; - - std::string atom_element = ucell->atoms[i].ncpp.psd; - - Json::AbacusJson::add_json({"init", "element", atom_label}, atom_element, false); - - std::string orbital_str = inp.orbital_dir + orbital_fn[i]; - if (!orbital_str.compare("")) - { - Json::jsonValue nullValue; - nullValue.SetNull(); - Json::AbacusJson::add_json({"init", "orb", atom_label}, nullValue, false); - - // Json::AbacusJson::add_Json(nullValue,false,"init","orb",atom_label); - } - else - { - Json::AbacusJson::add_json({"init", "orb", atom_label}, orbital_str, false); - // Json::AbacusJson::add_Json(orbital_str,false,"init","orb",atom_label); - } - std::string pseudo_str = pseudo_fn[i]; - Json::AbacusJson::add_json({"init", "pp", atom_label}, pseudo_str, false); - - // Json::AbacusJson::add_Json(pseudo_str,false,"init","pp",atom_label); + const Atom& atom = ucell->atoms[it]; + info["element"][atom.label] = atom.ncpp.psd; + const std::string orbital = inp.orbital_dir + ucell->orbital_fn[it]; + info["orb"][atom.label] = orbital.empty() ? jsonValue(nullptr) : jsonValue(orbital); + info["pp"][atom.label] = ucell->pseudo_fn[it]; } - // atom coordinate, mag and label const double lat0_angstrom = ucell->lat0_angstrom; - for (int i = 0; i < ntype; i++) + for (int it = 0; it < ucell->ntype; ++it) { - ModuleBase::Vector3* tau = ucell->atoms[i].tau.data(); - int na = ucell->atoms[i].na; - for (int j = 0; j < na; j++) + const Atom& atom = ucell->atoms[it]; + for (int ia = 0; ia < atom.na; ++ia) { - Json::jsonValue coordinateArray(JarrayType); - coordinateArray.JPushBack(tau[j][0] * lat0_angstrom); - coordinateArray.JPushBack(tau[j][1] * lat0_angstrom); - coordinateArray.JPushBack(tau[j][2] * lat0_angstrom); - Json::AbacusJson::add_json({"init", "coordinate"}, coordinateArray, true); - // Json::AbacusJson::add_Json(coordinateArray,true,"init","coordinate"); - - Json::AbacusJson::add_json({"init", "mag"}, ucell->atoms[i].mag[j], true); - - // Json::AbacusJson::add_Json(ucell->atoms[i].mag[j],true,"init","mag"); - - std::string str = ucell->atoms[i].label; - Json::AbacusJson::add_json({"init", "label"}, str, true); - // Json::AbacusJson::add_Json(str,true,"init","label"); + const ModuleBase::Vector3& tau = atom.tau[ia]; + info["coordinate"].push_back(jsonValue::array({tau[0] * lat0_angstrom, + tau[1] * lat0_angstrom, + tau[2] * lat0_angstrom})); + info["mag"].push_back(atom.mag[ia]); + info["label"].push_back(atom.label); } } - - // cell - { - Json::jsonValue cellArray1(JarrayType); - Json::jsonValue cellArray2(JarrayType); - Json::jsonValue cellArray3(JarrayType); - cellArray1.JPushBack(ucell->latvec.e11 * lat0_angstrom); - cellArray1.JPushBack(ucell->latvec.e12 * lat0_angstrom); - cellArray1.JPushBack(ucell->latvec.e13 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e21 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e22 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e23 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e31 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e32 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e33 * lat0_angstrom); - Json::AbacusJson::add_json({"init", "cell"}, cellArray1, true); - Json::AbacusJson::add_json({"init", "cell"}, cellArray2, true); - Json::AbacusJson::add_json({"init", "cell"}, cellArray3, true); - - // Json::AbacusJson::add_Json(cellArray1,true,"init","cell"); - // Json::AbacusJson::add_Json(cellArray2,true,"init","cell"); - // Json::AbacusJson::add_Json(cellArray3,true,"init","cell"); - } - return; + info["cell"] = {{ucell->latvec.e11 * lat0_angstrom, + ucell->latvec.e12 * lat0_angstrom, + ucell->latvec.e13 * lat0_angstrom}, + {ucell->latvec.e21 * lat0_angstrom, + ucell->latvec.e22 * lat0_angstrom, + ucell->latvec.e23 * lat0_angstrom}, + {ucell->latvec.e31 * lat0_angstrom, + ucell->latvec.e32 * lat0_angstrom, + ucell->latvec.e33 * lat0_angstrom}}; + set_init_fields(std::move(info)); } -#endif } // namespace Json +#endif // __JSON diff --git a/source/source_io/module_json/init_info.h b/source/source_io/module_json/init_info.h index 5fa4196698f..f5b01fed92f 100644 --- a/source/source_io/module_json/init_info.h +++ b/source/source_io/module_json/init_info.h @@ -1,35 +1,21 @@ #ifndef INIT_INFO_H #define INIT_INFO_H -#include "source_cell/atom_spec.h" -#include "source_cell/module_symmetry/symmetry.h" -#include "source_cell/unitcell.h" +class UnitCell; struct Input_para; /** - * @brief In this part of the code to complete the init part of the json tree. + * @brief Generate the init section of the JSON tree. */ namespace Json { -#ifdef __RAPIDJSON -// void gen_init(ModuleSymmetry::Symmetry *symm,Atom *atoms); +#ifdef __JSON -/** - * @param ucell: ucell for reading json parameters. - * @param inp: input parameters for reading json parameters. - */ void gen_init(UnitCell* ucell, const Input_para& inp); - -/** - * @param nkstot,nkstot_ibz: two param in json tree - */ void add_nkstot(int nkstot); - -/** - * @param ucell: ucell for reading structure init in abacus. - * @param inp: input parameters for reading orbital directory. - */ void gen_stru(UnitCell* ucell, const Input_para& inp); -#endif + +#endif // __JSON } // namespace Json -#endif \ No newline at end of file + +#endif diff --git a/source/source_io/module_json/json_node.h b/source/source_io/module_json/json_node.h index f19bddcf787..1b91d754445 100644 --- a/source/source_io/module_json/json_node.h +++ b/source/source_io/module_json/json_node.h @@ -1,21 +1,23 @@ #ifndef JSON_NODE_H #define JSON_NODE_H +#include + namespace Json { - class jsonKeyNode{ - public: - jsonKeyNode(int i): i(i) {}; - jsonKeyNode(const std::string& s): key(s) {}; +class jsonKeyNode +{ + public: + jsonKeyNode(int index) : i(index), is_index(true) {} + jsonKeyNode(const std::string& name) : key(name) {} + jsonKeyNode(const char* name) : key(name) {} - template - jsonKeyNode(const char (&s)[N]): key(s) {}; - - int i=0; - std::string key; - }; + int i = 0; + std::string key; + bool is_index = false; +}; -} +} // namespace Json -#endif \ No newline at end of file +#endif diff --git a/source/source_io/module_json/output_info.cpp b/source/source_io/module_json/output_info.cpp index aa6d60aef1a..b893ef9c45b 100644 --- a/source/source_io/module_json/output_info.cpp +++ b/source/source_io/module_json/output_info.cpp @@ -1,165 +1,124 @@ #include "output_info.h" -#include "para_json.h" -#include "source_io/module_parameter/parameter.h" + #include "abacusjson.h" +#include "source_base/matrix.h" +#include "source_cell/unitcell.h" + +#ifdef __JSON +#include +#endif +#include +#include -//Add json objects to init namespace Json { -#ifdef __RAPIDJSON - - - // Adjust the position of the json object and set the initial value - void init_output_array_obj(){ - - - - jsonValue scf_obj(JobjectType); - - Json::jsonValue nullValue; - nullValue.SetNull(); - scf_obj.JaddNormal("e_fermi",nullValue); - scf_obj.JaddNormal("energy",nullValue); - scf_obj.JaddNormal("scf_converge",nullValue); - - jsonValue force(JobjectType); - jsonValue stress(JobjectType); - jsonValue coordinate(JarrayType); - jsonValue mag(JarrayType); - jsonValue cell(JarrayType); - - scf_obj.JaddNormal("force",nullValue); - scf_obj.JaddNormal("stress",nullValue); - scf_obj.JaddNormal("coordinate",coordinate); - scf_obj.JaddNormal("mag",mag); - scf_obj.JaddNormal("cell",cell); - +#ifdef __JSON - AbacusJson::add_json({"output"},scf_obj,true); - } - - void add_output_cell_coo_stress_force( - const UnitCell *ucell, - const ModuleBase::matrix force, const double fac, - const ModuleBase::matrix stress, const double unit_transform - ) { +void init_output_array_obj() +{ + AbacusJson::append_json({"output"}, + {{"e_fermi", nullptr}, + {"energy", nullptr}, + {"scf_converge", nullptr}, + {"force", nullptr}, + {"stress", nullptr}, + {"coordinate", jsonValue::array()}, + {"mag", jsonValue::array()}, + {"cell", jsonValue::array()}}); +} + +void add_output_cell_coo_stress_force(const UnitCell& ucell, + const ModuleBase::matrix& force, + const double fac, + const ModuleBase::matrix& stress, + const double unit_transform, + const bool cal_force, + const bool cal_stress) +{ + const double output_acc = 1.0e-8; + if (cal_force) + { + jsonValue force_array = jsonValue::array(); int iat = 0; - const double output_acc = 1.0e-8; - - if (PARAM.inp.cal_force){ - //add force - Json::jsonValue force_array(JarrayType); - for (int it = 0; it < ucell->ntype; it++) + for (int it = 0; it < ucell.ntype; ++it) + { + for (int ia = 0; ia < ucell.atoms[it].na; ++ia) { - for (int ia = 0; ia < ucell->atoms[it].na; ia++) - { - Json::jsonValue force_subarray(JarrayType); - double fx = std::abs(force(iat, 0)) > output_acc ? force(iat, 0) * fac : 0.0; - double fy = std::abs(force(iat, 1)) > output_acc ? force(iat, 1) * fac : 0.0; - double fz = std::abs(force(iat, 2)) > output_acc ? force(iat, 2) * fac : 0.0; - - force_subarray.JPushBack(fx); - force_subarray.JPushBack(fy); - force_subarray.JPushBack(fz); - force_array.JPushBack(force_subarray); - iat++; - } + const double fx = std::abs(force(iat, 0)) > output_acc ? force(iat, 0) * fac : 0.0; + const double fy = std::abs(force(iat, 1)) > output_acc ? force(iat, 1) * fac : 0.0; + const double fz = std::abs(force(iat, 2)) > output_acc ? force(iat, 2) * fac : 0.0; + force_array.push_back(jsonValue::array({fx, fy, fz})); + ++iat; } - Json::AbacusJson::add_json({"output",-1,"force"}, force_array,false); - - // AbacusJson::add_Json(force_array,false,"output",-1,"force"); } + AbacusJson::set_json({"output", -1, "force"}, std::move(force_array)); + } - if (PARAM.inp.cal_stress){ - //add stress - Json::jsonValue stress_array(JarrayType); - for (int i = 0; i < 3; i++) - { - Json::jsonValue stress_subarray(JarrayType); - double sx = stress(i, 0) * unit_transform; - double sy = stress(i, 1) * unit_transform; - double sz = stress(i, 2) * unit_transform; - stress_subarray.JPushBack(sx); - stress_subarray.JPushBack(sy); - stress_subarray.JPushBack(sz); - stress_array.JPushBack(stress_subarray); - } - Json::AbacusJson::add_json({"output",-1,"stress"}, stress_array,false); - - // AbacusJson::add_Json(stress_array,false,"output",-1,"stress"); - } - //add coordinate - int ntype = ucell->ntype; - const double lat0_angstrom = ucell->lat0_angstrom; - for(int i=0;i* tau = ucell->atoms[i].tau.data(); - int na = ucell->atoms[i].na; - for(int j=0;jatoms[i].mag[j],true); - } - } - - //add cell + if (cal_stress) + { + jsonValue stress_array = jsonValue::array(); + for (int i = 0; i < 3; ++i) { - Json::jsonValue cellArray1(JarrayType); - Json::jsonValue cellArray2(JarrayType); - Json::jsonValue cellArray3(JarrayType); - cellArray1.JPushBack(ucell->latvec.e11 * lat0_angstrom); - cellArray1.JPushBack(ucell->latvec.e12 * lat0_angstrom); - cellArray1.JPushBack(ucell->latvec.e13 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e21 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e22 * lat0_angstrom); - cellArray2.JPushBack(ucell->latvec.e23 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e31 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e32 * lat0_angstrom); - cellArray3.JPushBack(ucell->latvec.e33 * lat0_angstrom); - Json::AbacusJson::add_json({"output",-1,"cell"}, cellArray1,true); - Json::AbacusJson::add_json({"output",-1,"cell"}, cellArray2,true); - Json::AbacusJson::add_json({"output",-1,"cell"}, cellArray3,true); - - // Json::AbacusJson::add_Json(cellArray1,true,"output",-1,"cell"); - // Json::AbacusJson::add_Json(cellArray2,true,"output",-1,"cell"); - // Json::AbacusJson::add_Json(cellArray2,true,"output",-1,"cell"); + stress_array.push_back(jsonValue::array({stress(i, 0) * unit_transform, + stress(i, 1) * unit_transform, + stress(i, 2) * unit_transform})); } - - } - - void add_output_efermi_converge(const double efermi, const bool scf_converge ){ - Json::AbacusJson::add_json({"output",-1,"e_fermi"}, efermi,false); - Json::AbacusJson::add_json({"output",-1,"scf_converge"}, scf_converge,false); + AbacusJson::set_json({"output", -1, "stress"}, std::move(stress_array)); } - void add_output_energy(const double energy) + const double lat0_angstrom = ucell.lat0_angstrom; + jsonValue coordinates = jsonValue::array(); + jsonValue mag = jsonValue::array(); + for (int it = 0; it < ucell.ntype; ++it) { - Json::AbacusJson::add_json({"output",-1,"energy"}, energy,false); + for (int ia = 0; ia < ucell.atoms[it].na; ++ia) + { + const ModuleBase::Vector3& tau = ucell.atoms[it].tau[ia]; + coordinates.push_back(jsonValue::array({tau[0] * lat0_angstrom, + tau[1] * lat0_angstrom, + tau[2] * lat0_angstrom})); + mag.push_back(ucell.atoms[it].mag[ia]); + } } + AbacusJson::set_json({"output", -1, "coordinate"}, std::move(coordinates)); + AbacusJson::set_json({"output", -1, "mag"}, std::move(mag)); + AbacusJson::set_json({"output", -1, "cell"}, + {{ucell.latvec.e11 * lat0_angstrom, + ucell.latvec.e12 * lat0_angstrom, + ucell.latvec.e13 * lat0_angstrom}, + {ucell.latvec.e21 * lat0_angstrom, + ucell.latvec.e22 * lat0_angstrom, + ucell.latvec.e23 * lat0_angstrom}, + {ucell.latvec.e31 * lat0_angstrom, + ucell.latvec.e32 * lat0_angstrom, + ucell.latvec.e33 * lat0_angstrom}}); +} + +void add_output_efermi_converge(const double efermi, const bool scf_converge) +{ + AbacusJson::set_json({"output", -1, "e_fermi"}, efermi); + AbacusJson::set_json({"output", -1, "scf_converge"}, scf_converge); +} - void add_output_scf_mag( - double total_mag, double absolute_mag, - double energy, double ediff, double drho,double time - ){ - Json::AbacusJson::add_json({"output",-1,"total_mag"}, total_mag,false); - Json::AbacusJson::add_json({"output",-1,"absolute_mag"}, absolute_mag,false); - - // Json::AbacusJson::add_Json(total_mag,false,"output",-1,"total_mag"); - // Json::AbacusJson::add_Json(absolute_mag,false,"output",-1,"absolute_mag"); - - Json::jsonValue scf_obj(JobjectType); - scf_obj.JaddNormal("energy",energy); - scf_obj.JaddNormal("ediff",ediff); - scf_obj.JaddNormal("drho",drho); - scf_obj.JaddNormal("time",time); - Json::AbacusJson::add_json({"output",-1,"scf"}, scf_obj,true); - - // Json::AbacusJson::add_Json(scf_obj,true,"output",-1,"scf"); - } +void add_output_energy(const double energy) +{ + AbacusJson::set_json({"output", -1, "energy"}, energy); +} + +void add_output_scf_mag(const double total_mag, + const double absolute_mag, + const double energy, + const double ediff, + const double drho, + const double time) +{ + AbacusJson::set_json({"output", -1, "total_mag"}, total_mag); + AbacusJson::set_json({"output", -1, "absolute_mag"}, absolute_mag); + AbacusJson::append_json({"output", -1, "scf"}, + {{"energy", energy}, {"ediff", ediff}, {"drho", drho}, {"time", time}}); +} -#endif +#endif // __JSON } // namespace Json diff --git a/source/source_io/module_json/output_info.h b/source/source_io/module_json/output_info.h index f61c60c0b99..56f68fb0d9b 100644 --- a/source/source_io/module_json/output_info.h +++ b/source/source_io/module_json/output_info.h @@ -1,34 +1,41 @@ #ifndef OUTPUT_INFO_H #define OUTPUT_INFO_H -#include "source_cell/unitcell.h" -#include "source_base/matrix.h" +class UnitCell; +namespace ModuleBase +{ +class matrix; +} /** -* @brief In this part of the code to complete the output part of the json tree. -* @param ucell: ucell for reading json parameters -*/ + * @brief Generate the output section of the JSON tree. + */ namespace Json { -#ifdef __RAPIDJSON +#ifdef __JSON - void init_output_array_obj(); +void init_output_array_obj(); - void add_output_cell_coo_stress_force( - const UnitCell *ucell, - const ModuleBase::matrix force, const double fac, - const ModuleBase::matrix stress, const double unit_transform - ); +void add_output_cell_coo_stress_force(const UnitCell& ucell, + const ModuleBase::matrix& force, + double fac, + const ModuleBase::matrix& stress, + double unit_transform, + bool cal_force, + bool cal_stress); - void add_output_efermi_converge(const double efermi, const bool scf_converge ); - void add_output_energy(const double energy ); +void add_output_efermi_converge(double efermi, bool scf_converge); +void add_output_energy(double energy); - void add_output_scf_mag( - const double total_mag, const double absolute_mag, - const double energy, const double ediff, const double drho,const double time - ); +void add_output_scf_mag(double total_mag, + double absolute_mag, + double energy, + double ediff, + double drho, + double time); + +#endif // __JSON +} // namespace Json #endif -} -#endif \ No newline at end of file diff --git a/source/source_io/module_json/para_json.cpp b/source/source_io/module_json/para_json.cpp index 7f49c75ce5b..75bfb8c43e9 100644 --- a/source/source_io/module_json/para_json.cpp +++ b/source/source_io/module_json/para_json.cpp @@ -4,18 +4,18 @@ #include #include #include -#ifdef __RAPIDJSON +#ifdef __JSON +#include "source_io/module_parameter/parameter.h" #include "abacusjson.h" #include "general_info.h" #include "init_info.h" -#include "readin_info.h" -#endif // __RAPIDJSON +#endif // __JSON namespace Json { // void create_Json(ModuleSymmetry::Symmetry *symm,Atom *atoms,Input *input){ -// #ifdef __RAPIDJSON +// #ifdef __JSON // gen_general_info(input); // gen_init(symm,atoms); // #endif @@ -23,19 +23,19 @@ namespace Json void json_output() { -#ifdef __RAPIDJSON +#ifdef __JSON #ifdef __MPI if (GlobalV::MY_RANK == 0) AbacusJson::write_to_json("abacus.json"); #else AbacusJson::write_to_json("abacus.json"); #endif -#endif // __RAPIDJSON +#endif // __JSON } void create_Json(UnitCell* ucell, const Parameter& param) { -#ifdef __RAPIDJSON +#ifdef __JSON gen_general_info(param); gen_init(ucell, param.inp); // gen_stru(ucell, param.inp); @@ -45,7 +45,7 @@ void create_Json(UnitCell* ucell, const Parameter& param) void gen_stru_wrapper(UnitCell* ucell, const Input_para& inp) { -#ifdef __RAPIDJSON +#ifdef __JSON #ifdef __MPI if (GlobalV::MY_RANK == 0) gen_stru(ucell, inp); diff --git a/source/source_io/module_json/para_json.h b/source/source_io/module_json/para_json.h index 990ad4c99b7..0bf239a4a00 100644 --- a/source/source_io/module_json/para_json.h +++ b/source/source_io/module_json/para_json.h @@ -1,23 +1,21 @@ +#ifndef PARA_JSON_H +#define PARA_JSON_H + #include #include -#include "source_cell/module_symmetry/symmetry.h" -#include "source_cell/atom_spec.h" -#include "source_io/module_parameter/parameter.h" -#include "source_cell/unitcell.h" +class Parameter; +class UnitCell; +struct Input_para; + namespace Json { -// void create_Json(ModuleSymmetry::Symmetry *symm,Atom *atoms,Input *input); - -void create_Json(UnitCell *ucell, const Parameter& input); - -// Output the json to abacus.json file +void create_Json(UnitCell* ucell, const Parameter& param); void json_output(); - -// Convert time_t to string void convert_time(std::time_t time_now, std::string& time_str); +void gen_stru_wrapper(UnitCell* ucell, const Input_para& inp); -// generate struture wrapper function -void gen_stru_wrapper(UnitCell *ucell, const Input_para& inp); } // namespace Json + +#endif diff --git a/source/source_io/module_json/readin_info.cpp b/source/source_io/module_json/readin_info.cpp deleted file mode 100644 index a150967af80..00000000000 --- a/source/source_io/module_json/readin_info.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "readin_info.h" -#include "para_json.h" -#include "abacusjson.h" - -//Add json objects to init -namespace Json -{ - -#ifdef __RAPIDJSON -#endif -} // namespace Json diff --git a/source/source_io/module_json/readin_info.h b/source/source_io/module_json/readin_info.h deleted file mode 100644 index c4a598c999d..00000000000 --- a/source/source_io/module_json/readin_info.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef READIN_INFO_H -#define READIN_INFO_H - - - -/** -* @brief In this part of the code to complete the readin part of the json tree. -* @param ucell: ucell for reading json parameters -*/ -namespace Json -{ -#ifdef __RAPIDJSON - - - -#endif -} -#endif \ No newline at end of file diff --git a/source/source_io/module_json/test/para_json_test.cpp b/source/source_io/module_json/test/para_json_test.cpp index 6ab32a2c866..a26ba69d1fb 100644 --- a/source/source_io/module_json/test/para_json_test.cpp +++ b/source/source_io/module_json/test/para_json_test.cpp @@ -1,255 +1,141 @@ #include "gtest/gtest.h" -#define private public +#include + +#include +#include +#include +#include +#include +#include + +#include "source_base/constants.h" +#include "source_base/matrix.h" +#include "source_base/matrix3.h" +#include "source_base/parallel_global.h" +#include "source_base/vector3.h" +#include "source_cell/atom_spec.h" +#include "source_cell/magnetism.h" +#include "source_cell/unitcell.h" #include "source_io/module_json/abacusjson.h" #include "source_io/module_json/general_info.h" #include "source_io/module_json/init_info.h" -#include "source_io/module_json/readin_info.h" +#include "source_io/module_json/output_info.h" #include "source_io/module_parameter/parameter.h" -#include "source_io/module_json/para_json.h" -#include "source_base/constants.h" #include "source_main/version.h" -#undef private -/************************************************ - * unit test of json output module - ************************************************ -/** - * - Tested Functions: - * - AddJson() - * - Verify the normal addition of json structure parameters in the json function. - * - OutputJson() - * - Verify the correctness of the json output. - * - GeneralInfo() - * - Test the correctness of the json output of the General Info module. - * - InitInfo() - * - Test the correctness of the json output of the Init info module. - */ - -TEST(AbacusJsonTest, AddJson) + +namespace Json +{ +class AbacusJsonTestAccess { - Json::AbacusJson::doc.SetObject(); - - // add a string - Json::AbacusJson::add_json({"key1"}, "value1", false); - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("key1")); - ASSERT_TRUE(Json::AbacusJson::doc["key1"].IsString()); - ASSERT_STREQ(Json::AbacusJson::doc["key1"].GetString(), "value1"); - - // add a string to a nested object - Json::AbacusJson::add_json({"key2", "key3"}, "value2", false); - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("key2")); - ASSERT_TRUE(Json::AbacusJson::doc["key2"].IsObject()); - ASSERT_TRUE(Json::AbacusJson::doc["key2"].HasMember("key3")); - ASSERT_TRUE(Json::AbacusJson::doc["key2"]["key3"].IsString()); - ASSERT_STREQ(Json::AbacusJson::doc["key2"]["key3"].GetString(), "value2"); - - // add an int - Json::AbacusJson::add_json({"key2"}, 123, false); - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("key2")); - ASSERT_TRUE(Json::AbacusJson::doc["key2"].IsInt()); - ASSERT_EQ(Json::AbacusJson::doc["key2"].GetInt(), 123); - - // add a bool - Json::AbacusJson::add_json({"key3"}, true, false); - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("key3")); - ASSERT_TRUE(Json::AbacusJson::doc["key3"].IsBool()); - ASSERT_EQ(Json::AbacusJson::doc["key3"].GetBool(), true); - - // add a double - Json::AbacusJson::add_json({"key4"}, 1.23, false); - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("key4")); - ASSERT_TRUE(Json::AbacusJson::doc["key4"].IsDouble()); - ASSERT_EQ(Json::AbacusJson::doc["key4"].GetDouble(), 1.23); - - // modify a value - Json::AbacusJson::add_json({"key4"}, 4.56, false); - ASSERT_EQ(Json::AbacusJson::doc["key4"].GetDouble(), 4.56); - - // array test - Json::AbacusJson::add_json({"key6", "key7"}, true, true); - Json::AbacusJson::add_json({"key6", "key7"}, false, true); - - // add key-val to a object array - for (int i = 0; i < 3; i++) + public: + static void reset() { - Json::jsonValue object(JobjectType); - object.JaddNormal("int", i); - - std::string str = std::to_string(i * 100); - std::string str2 = "Kstring"; - - object.JaddStringV("string", str); - object.JaddStringK(str, "string"); - object.JaddStringKV(str2, str); - object.JaddNormal("double", 0.01 * i); - Json::AbacusJson::add_json({"array"}, object, true); + AbacusJson::doc = jsonValue::object(); } - Json::AbacusJson::add_json({"array", 1, "new_add_notLast"}, "correct1", false); - Json::AbacusJson::add_json({"array", -1, "new_add_Last"}, "correct2", false); - - // Validate json parameters in doc objects - - ASSERT_EQ(Json::AbacusJson::doc["array"][0]["int"].GetInt(), 0); - ASSERT_STREQ(Json::AbacusJson::doc["array"][0]["string"].GetString(), "0"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][0]["0"].GetString(), "string"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][0]["Kstring"].GetString(), "0"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][1]["new_add_notLast"].GetString(), "correct1"); - - ASSERT_EQ(Json::AbacusJson::doc["array"][0]["double"].GetDouble(), 0.0); - - ASSERT_EQ(Json::AbacusJson::doc["array"][1]["int"].GetInt(), 1); - ASSERT_STREQ(Json::AbacusJson::doc["array"][1]["string"].GetString(), "100"); - - ASSERT_STREQ(Json::AbacusJson::doc["array"][1]["100"].GetString(), "string"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][1]["Kstring"].GetString(), "100"); - - ASSERT_EQ(Json::AbacusJson::doc["array"][1]["double"].GetDouble(), 0.01); - - ASSERT_EQ(Json::AbacusJson::doc["array"][2]["int"].GetInt(), 2); - ASSERT_STREQ(Json::AbacusJson::doc["array"][2]["string"].GetString(), "200"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][2]["200"].GetString(), "string"); - ASSERT_STREQ(Json::AbacusJson::doc["array"][2]["Kstring"].GetString(), "200"); - ASSERT_EQ(Json::AbacusJson::doc["array"][2]["double"].GetDouble(), 0.02); - - ASSERT_STREQ(Json::AbacusJson::doc["array"][2]["new_add_Last"].GetString(), "correct2"); - - // add array in array - Json::jsonValue object0(JarrayType); - - object0.JPushBack(1); - object0.JPushBack(2); - object0.JPushBack(3); - - Json::jsonValue object1(JarrayType); - - object1.JPushBack(2.1); - object1.JPushBack(3.1); - object1.JPushBack(4.1); - - Json::jsonValue object2(JarrayType); - - object2.JPushBack("str1"); - object2.JPushBack("str2"); - object2.JPushBack("str3"); - - Json::jsonValue object3(JarrayType); - - std::string astr1 = "string1"; - std::string astr2 = "string2"; - std::string astr3 = "string3"; - object3.JPushBackString(astr1); - object3.JPushBackString(astr2); - object3.JPushBackString(astr3); - - Json::AbacusJson::add_json({"Darray"}, object0, true); - Json::AbacusJson::add_json({"Darray"}, object1, true); - Json::AbacusJson::add_json({"Darray"}, object2, true); - Json::AbacusJson::add_json({"Darray"}, object3, true); - - Json::AbacusJson::add_json({"Darray", 1, 0}, "new_add_method", false); - Json::AbacusJson::add_json({"Darray", 1, -2}, 40, false); - - ASSERT_EQ(Json::AbacusJson::doc["Darray"][1][0].GetString(), "new_add_method"); - - ASSERT_EQ(Json::AbacusJson::doc["Darray"][0][0].GetInt(), 1); - ASSERT_EQ(Json::AbacusJson::doc["Darray"][0][1].GetInt(), 2); - ASSERT_EQ(Json::AbacusJson::doc["Darray"][0][2].GetInt(), 3); + static const jsonValue& document() + { + return AbacusJson::doc; + } +}; +} // namespace Json - ASSERT_EQ(Json::AbacusJson::doc["Darray"][1][1].GetDouble(), 40); - ASSERT_EQ(Json::AbacusJson::doc["Darray"][1][2].GetDouble(), 4.1); +class AbacusJsonTest : public testing::Test +{ + protected: + void SetUp() override + { + Json::AbacusJsonTestAccess::reset(); + } - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][2][0].GetString(), "str1"); - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][2][1].GetString(), "str2"); - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][2][2].GetString(), "str3"); + const Json::jsonValue& document() const + { + return Json::AbacusJsonTestAccess::document(); + } +}; - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][3][0].GetString(), "string1"); - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][3][1].GetString(), "string2"); - ASSERT_STREQ(Json::AbacusJson::doc["Darray"][3][2].GetString(), "string3"); +TEST_F(AbacusJsonTest, SetAndAppendJson) +{ + Json::AbacusJson::set_json({"key"}, "value"); + Json::AbacusJson::set_json({"nested", "value"}, 1); + Json::AbacusJson::set_json({"nested", "value"}, 2); + Json::AbacusJson::append_json({"array"}, Json::jsonValue{{"index", 0}}); + Json::AbacusJson::append_json({"array"}, Json::jsonValue{{"index", 1}}); + Json::AbacusJson::set_json({"array", -1, "label"}, "last"); + + const Json::jsonValue& root = document(); + EXPECT_EQ(root["key"], "value"); + EXPECT_EQ(root["nested"]["value"], 2); + ASSERT_EQ(root["array"].size(), 2u); + EXPECT_EQ(root["array"][0]["index"], 0); + EXPECT_EQ(root["array"][1]["index"], 1); + EXPECT_EQ(root["array"][1]["label"], "last"); } -TEST(AbacusJsonTest, OutputJson) +TEST_F(AbacusJsonTest, OutputJson) { - Json::AbacusJson::doc.SetObject(); - - Json::AbacusJson::add_json({"key1"}, "value1", false); - Json::AbacusJson::add_json({"key2", "key3"}, 1, false); - Json::AbacusJson::add_json({"key4"}, 0.1, false); - Json::AbacusJson::add_json({"key5"}, true, false); + Json::AbacusJson::set_json({"key"}, "value"); + Json::AbacusJson::set_json( + {"nested"}, Json::jsonValue{{"value", 1}, {"array", Json::jsonValue::array({1, 2, 3})}}); - Json::jsonValue object(JobjectType); - object.JaddNormal("int", 1); - Json::jsonValue object2(JarrayType); - - object.JaddNormal("arr", object2); - - // array test - Json::AbacusJson::add_json({"key6", "key7"}, object, true); - Json::AbacusJson::add_json({"key6", "key7", 0, "arr"}, 13, true); - Json::AbacusJson::add_json({"key6", "key7", 0, "arr"}, 14, true); - Json::AbacusJson::add_json({"key6", "key7", 0, "arr", 0}, 1, true); - - std::string filename = "test.json"; + const std::string filename = "test.json"; Json::AbacusJson::write_to_json(filename); std::ifstream file(filename); ASSERT_TRUE(file.is_open()); - - std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ASSERT_NE(content.find("\"key1\": \"value1\","), std::string::npos); - ASSERT_NE(content.find("\"key2\": {"), std::string::npos); - ASSERT_NE(content.find("\"key3\": 1"), std::string::npos); - ASSERT_NE(content.find("\"key4\": 0.1"), std::string::npos); - ASSERT_NE(content.find("\"key5\": true"), std::string::npos); - + const Json::jsonValue result = Json::jsonValue::parse(file); + EXPECT_EQ(result, document()); file.close(); + EXPECT_EQ(std::remove(filename.c_str()), 0); } -TEST(AbacusJsonTest, GeneralInfo) +TEST_F(AbacusJsonTest, GeneralInfo) { - std::time_t time_now = std::time(nullptr); - std::string start_time_str; - Json::convert_time(time_now, start_time_str); - Parameter param; - param.sys.start_time = time_now; - param.input.device = "cpu"; - param.input.pseudo_dir = "./abacus/test/pseudo_dir"; - param.input.orbital_dir = "./abacus/test/orbital_dir"; - param.sys.global_in_stru = "./abacus/test/stru_file"; - param.input.kpoint_file = "./abacus/test/kpoint_file"; - // output the json file - Json::AbacusJson::doc.Parse("{}"); Json::gen_general_info(param); - Json::json_output(); - std::string filename = "abacus.json"; - std::ifstream file(filename); - ASSERT_TRUE(file.is_open()); - - std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - - ASSERT_NE(content.find(VERSION), std::string::npos); - ASSERT_NE(content.find("\"device\": \"cpu\","), std::string::npos); - ASSERT_NE(content.find("\"omp_num\": 0,"), std::string::npos); - ASSERT_NE(content.find("\"mpi_num\": 0,"), std::string::npos); - ASSERT_NE(content.find("\"orbital_dir\": \"./abacus/test/orbital_dir\","), std::string::npos); - ASSERT_NE(content.find("\"pseudo_dir\": \"./abacus/test/pseudo_dir\","), std::string::npos); - ASSERT_NE(content.find("\"stru_file\": \"./abacus/test/stru_file\","), std::string::npos); - ASSERT_NE(content.find("\"kpt_file\": \"./abacus/test/kpoint_file\","), std::string::npos); - ASSERT_NE(content.find(start_time_str), std::string::npos); + const Json::jsonValue& info = document().at("general_info"); + EXPECT_EQ(info["version"], VERSION); + EXPECT_EQ(info["device"], param.inp.device); +#ifdef __MPI + EXPECT_EQ(info["mpi_num"], Parallel_Global::mpi_number); + EXPECT_EQ(info["omp_num"], Parallel_Global::omp_number); +#else + EXPECT_EQ(info["mpi_num"], 1); + EXPECT_EQ(info["omp_num"], 1); +#endif + EXPECT_EQ(info["orbital_dir"], param.inp.orbital_dir); + EXPECT_EQ(info["pseudo_dir"], param.inp.pseudo_dir); + EXPECT_EQ(info["stru_file"], param.globalv.global_in_stru); + EXPECT_EQ(info["kpt_file"], param.inp.kpoint_file); + EXPECT_TRUE(info["start_time"].is_string()); + EXPECT_TRUE(info["end_time"].is_string()); + std::vector keys; + for (Json::jsonValue::const_iterator field = info.begin(); field != info.end(); ++field) + { + keys.push_back(field.key()); + } + EXPECT_EQ(keys, (std::vector{"version", "commit", "device", "mpi_num", "omp_num", + "pseudo_dir", "orbital_dir", "stru_file", "kpt_file", + "start_time", "end_time"})); + Json::AbacusJson::set_json({"init", "nkstot"}, 2); + Json::gen_general_info(param); + EXPECT_EQ(document()["init"]["nkstot"], 2); + EXPECT_EQ(document()["general_info"].size(), keys.size()); } - Magnetism::Magnetism() { this->tot_mag = 0.0; this->abs_mag = 0.0; } + Magnetism::~Magnetism() { } -TEST(AbacusJsonTest, InitInfo) + +TEST_F(AbacusJsonTest, InitInfo) { UnitCell ucell; Atom atomlist[3]; @@ -258,6 +144,7 @@ TEST(AbacusJsonTest, InitInfo) ucell.symm.spgname = "O_h"; ucell.atoms = atomlist; ucell.ntype = 3; + Input_para inp; inp.nbands = 10; inp.ecutwfc = 50.0; @@ -276,132 +163,336 @@ TEST(AbacusJsonTest, InitInfo) ucell.atoms[2].label = "O"; ucell.atoms[2].ncpp.zv = 5; ucell.atoms[2].na = 3; - ucell.nat = 0; - for (int i = 0; i < ucell.ntype; i++) + + ucell.nat = 6; + + Json::add_nkstot(1); + Json::gen_init(&ucell, inp); + + const Json::jsonValue& init = document().at("init"); + EXPECT_EQ(init["nkstot"], 1); + EXPECT_EQ(init["natom"], 6); + EXPECT_EQ(init["nband"], 10); + EXPECT_EQ(init["point_group"], "T_d"); + EXPECT_EQ(init["point_group_in_space"], "O_h"); + EXPECT_EQ(init.at("nelectron_each_type"), (Json::jsonValue{{"Si", 3.0}, {"C", 4.0}, {"O", 5.0}})); + EXPECT_EQ(init.at("natom_each_type"), (Json::jsonValue{{"Si", 1}, {"C", 2}, {"O", 3}})); + EXPECT_EQ(init.at("nelectron"), 26); + EXPECT_TRUE(init.at("nelectron").is_number_integer()); + EXPECT_TRUE(init.at("nelectron_each_type").at("C").is_number_float()); + EXPECT_EQ(init["ecutwfc"], 50.0); + EXPECT_EQ(init["ecutwfc_unit"], "Ry"); + EXPECT_EQ(init["smearing_method"], "gauss"); + EXPECT_EQ(init["smearing_sigma"], 0.015); + EXPECT_EQ(init["smearing_sigma_unit"], "Ry"); + EXPECT_EQ(init["kmesh_type"], "gamma"); + EXPECT_EQ(init["kspacing"], Json::jsonValue::array({0.04, 0.04, 0.04})); + EXPECT_EQ(init["koffset"], Json::jsonValue::array({0.0, 0.0, 0.0})); +} + +TEST_F(AbacusJsonTest, InitStructure) +{ + UnitCell ucell; + Atom atom; + + ucell.latvec = ModuleBase::Matrix3(0.1, 0.1, 0.1, + 0.2, 0.2, 0.2, + 0.3, 0.3, 0.3); + ucell.ntype = 1; + ucell.nat = 2; + ucell.pseudo_fn = {"si.ufp"}; + ucell.orbital_fn = {""}; + ucell.atoms = &atom; + ucell.lat0 = 10.0; + ucell.lat0_angstrom = ucell.lat0 * ModuleBase::BOHR_TO_A; + + atom.label = "Si"; + atom.na = 2; + atom.ncpp.zv = 4.0; + atom.tau = {ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.1, 0.1, 0.1)}; + atom.mag = {0.0, 131.0}; + + Input_para inp; + Json::gen_stru(&ucell, inp); + + const Json::jsonValue& init = document().at("init"); + EXPECT_EQ(init["mag"], Json::jsonValue::array({0.0, 131.0})); + EXPECT_EQ(init["pp"]["Si"], "si.ufp"); + EXPECT_TRUE(init["orb"]["Si"].is_null()); + EXPECT_EQ(init["label"][0], "Si"); + EXPECT_EQ(init["element"]["Si"], ""); + EXPECT_EQ(init["coordinate"][0], Json::jsonValue::array({0.0, 0.0, 0.0})); + for (int i = 0; i < 3; ++i) { - ucell.nat += ucell.atoms[i].na; + EXPECT_NEAR(init["coordinate"][1][i].get(), ModuleBase::BOHR_TO_A, 1.0e-12); + for (int j = 0; j < 3; ++j) + { + EXPECT_NEAR(init["cell"][i][j].get(), (i + 1) * ModuleBase::BOHR_TO_A, 1.0e-12); + } } - // init the doc allocator - Json::AbacusJson::doc.Parse("{}"); - int Jnkstot = 1; - Json::add_nkstot(Jnkstot); + // Reuse the cell to check shared init fields and repeated generation. + ucell.orbital_fn[0] = "Si.orb"; + inp.orbital_dir = "orbitals/"; + inp.kspacing = {0.1, 0.2, 0.3}; + inp.koffset = {0.0, 0.5, 0.0}; + Json::gen_stru(&ucell, inp); + Json::add_nkstot(3); + Json::gen_init(&ucell, inp); + const Json::jsonValue first = document(); + EXPECT_EQ(first["init"]["orb"]["Si"], "orbitals/Si.orb"); + EXPECT_EQ(first["init"]["nkstot"], 3); + EXPECT_EQ(first["init"]["natom"], 2); Json::gen_init(&ucell, inp); + Json::gen_stru(&ucell, inp); + EXPECT_EQ(document(), first); + EXPECT_EQ(document().dump(), first.dump()); // Preserve key order, too. +} - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("init")); - ASSERT_EQ(Json::AbacusJson::doc["init"]["nkstot"].GetInt(), 1); - - ASSERT_EQ(Json::AbacusJson::doc["init"]["natom"].GetInt(), 6); - ASSERT_EQ(Json::AbacusJson::doc["init"]["nband"].GetInt(), 10); - - ASSERT_STREQ(Json::AbacusJson::doc["init"]["point_group"].GetString(), "T_d"); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["point_group_in_space"].GetString(), "O_h"); - - ASSERT_EQ(Json::AbacusJson::doc["init"]["nelectron_each_type"]["Si"].GetDouble(), 3); - ASSERT_EQ(Json::AbacusJson::doc["init"]["nelectron_each_type"]["C"].GetDouble(), 4); - ASSERT_EQ(Json::AbacusJson::doc["init"]["nelectron_each_type"]["O"].GetDouble(), 5); - - ASSERT_EQ(Json::AbacusJson::doc["init"]["natom_each_type"]["Si"].GetInt(), 1); - ASSERT_EQ(Json::AbacusJson::doc["init"]["natom_each_type"]["C"].GetInt(), 2); - ASSERT_EQ(Json::AbacusJson::doc["init"]["natom_each_type"]["O"].GetInt(), 3); - - ASSERT_EQ(Json::AbacusJson::doc["init"]["ecutwfc"].GetDouble(), 50.0); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["ecutwfc_unit"].GetString(), "Ry"); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["smearing_method"].GetString(), "gauss"); - ASSERT_EQ(Json::AbacusJson::doc["init"]["smearing_sigma"].GetDouble(), 0.015); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["smearing_sigma_unit"].GetString(), "Ry"); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["kmesh_type"].GetString(), "gamma"); - ASSERT_EQ(Json::AbacusJson::doc["init"]["kspacing"][0].GetDouble(), 0.04); - ASSERT_EQ(Json::AbacusJson::doc["init"]["kspacing"][1].GetDouble(), 0.04); - ASSERT_EQ(Json::AbacusJson::doc["init"]["kspacing"][2].GetDouble(), 0.04); - ASSERT_EQ(Json::AbacusJson::doc["init"]["koffset"][0].GetDouble(), 0.0); - ASSERT_EQ(Json::AbacusJson::doc["init"]["koffset"][1].GetDouble(), 0.0); - ASSERT_EQ(Json::AbacusJson::doc["init"]["koffset"][2].GetDouble(), 0.0); +TEST_F(AbacusJsonTest, NullAndEmptyContainers) +{ + Json::AbacusJson::set_json({"null"}, nullptr); + Json::AbacusJson::set_json({"object"}, Json::jsonValue::object()); + Json::AbacusJson::set_json({"array"}, Json::jsonValue::array()); + Json::AbacusJson::append_json({"wrapped"}, Json::jsonValue::array()); + + const Json::jsonValue& root = document(); + EXPECT_TRUE(root.at("null").is_null()); + EXPECT_EQ(root.at("object"), Json::jsonValue::object()); + EXPECT_EQ(root.at("array"), Json::jsonValue::array()); + EXPECT_EQ(root.at("wrapped"), Json::jsonValue::array({Json::jsonValue::array()})); } -TEST(AbacusJsonTest, Init_stru_test) +TEST_F(AbacusJsonTest, SetReplacesContainers) { - // init ucell - UnitCell ucell; + Json::AbacusJson::set_json({"value"}, Json::jsonValue::array({1, 2})); + Json::AbacusJson::set_json({"value"}, Json::jsonValue::array({3})); + EXPECT_EQ(document()["value"], Json::jsonValue::array({3})); + + Json::AbacusJson::set_json({"value"}, Json::jsonValue{{"old", 1}}); + Json::AbacusJson::set_json({"value"}, Json::jsonValue{{"new", 2}}); + EXPECT_EQ(document()["value"], (Json::jsonValue{{"new", 2}})); + Json::AbacusJson::set_json({"value"}, true); + EXPECT_TRUE(document()["value"].is_boolean()); + EXPECT_EQ(document()["value"], true); + Json::AbacusJson::set_json({"value"}, 1.25); + EXPECT_TRUE(document()["value"].is_number_float()); + EXPECT_DOUBLE_EQ(document()["value"].get(), 1.25); +} - Atom atomlist[1]; - std::string label[1]; +TEST_F(AbacusJsonTest, ArrayAppendAndIndexedReplacement) +{ + Json::AbacusJson::append_json({"array"}, 1); + Json::AbacusJson::append_json({"array"}, 2); + Json::AbacusJson::set_json({"array", -1}, 3); + Json::AbacusJson::set_json({"array", -2}, Json::jsonValue::array({4, 5})); + Json::AbacusJson::append_json({"array", 0}, 6); + EXPECT_EQ(document()["array"][0], Json::jsonValue::array({4, 5, 6})); + Json::AbacusJson::set_json({"array", 0}, 6); + EXPECT_EQ(document()["array"], Json::jsonValue::array({6, 3})); + + // Numeric strings and empty strings are object keys, not array indices. + Json::AbacusJson::set_json({"object", "0"}, 7); + Json::AbacusJson::set_json({"object", ""}, 8); + EXPECT_EQ(document()["object"]["0"], 7); + EXPECT_EQ(document()["object"][""], 8); +} - ModuleBase::Matrix3 latvec; - latvec.e11 = 0.1; - latvec.e12 = 0.1; - latvec.e13 = 0.1; +TEST_F(AbacusJsonTest, AppendRejectsNonArrays) +{ + Json::AbacusJson::set_json({"null"}, nullptr); + Json::AbacusJson::set_json({"object"}, Json::jsonValue::object()); + Json::AbacusJson::set_json({"scalar"}, 1); + Json::AbacusJson::set_json({"array"}, Json::jsonValue::array({2})); + const Json::jsonValue before = document(); + + for (const char* key : {"null", "object", "scalar"}) + { + EXPECT_THROW(Json::AbacusJson::append_json({key}, 3), std::invalid_argument); + } + EXPECT_THROW(Json::AbacusJson::append_json({"array", 0}, 3), std::invalid_argument); + EXPECT_EQ(document(), before); +} - latvec.e21 = 0.2; - latvec.e22 = 0.2; - latvec.e23 = 0.2; +TEST_F(AbacusJsonTest, InvalidPathsDoNotGrowArrays) +{ + Json::AbacusJson::append_json({"array"}, 1); + Json::AbacusJson::set_json({"empty"}, Json::jsonValue::array()); + Json::AbacusJson::set_json({"scalar"}, 2); - latvec.e31 = 0.3; - latvec.e32 = 0.3; - latvec.e33 = 0.3; - ucell.latvec = latvec; + for (const int index : {1, -2, std::numeric_limits::min()}) + { + EXPECT_THROW(Json::AbacusJson::set_json({"array", index}, 3), std::out_of_range); + EXPECT_THROW(Json::AbacusJson::append_json({"array", index}, 3), std::out_of_range); + } + EXPECT_THROW(Json::AbacusJson::set_json({"empty", -1}, 3), std::out_of_range); + EXPECT_THROW(Json::AbacusJson::append_json({"empty", -1}, 3), std::out_of_range); + EXPECT_THROW(Json::AbacusJson::set_json({"array", "key"}, 3), std::invalid_argument); + EXPECT_THROW(Json::AbacusJson::set_json({"scalar", "key"}, 3), std::invalid_argument); + EXPECT_THROW(Json::AbacusJson::set_json({0}, 3), std::invalid_argument); + EXPECT_THROW(Json::AbacusJson::append_json({0}, 3), std::invalid_argument); + EXPECT_EQ(document()["array"], Json::jsonValue::array({1})); + EXPECT_TRUE(document()["empty"].empty()); + + const Json::jsonValue before = document(); + Json::AbacusJson::set_json({}, 9); + Json::AbacusJson::append_json({}, 9); + EXPECT_EQ(document(), before); +} - double lat0 = 10.0; - ucell.ntype = 1; - ucell.pseudo_fn.resize(1); - ucell.orbital_fn.resize(1); - ucell.atoms = atomlist; - ucell.lat0 = lat0; - ucell.lat0_angstrom = lat0 * ModuleBase::BOHR_TO_A; +TEST_F(AbacusJsonTest, OwnedValuesAndStringEscaping) +{ + Json::jsonValue original = {{"value", "original"}}; + Json::AbacusJson::set_json({"copy"}, original); + original["value"] = "changed"; + EXPECT_EQ(document()["copy"]["value"], "original"); + + const std::string text = "quote: \"; slash: \\; newline: \n; UTF-8: \xCE\xB1"; + const std::string embedded_nul("a\0b", 3); + Json::AbacusJson::set_json({"text"}, text); + Json::AbacusJson::set_json({"embedded_nul"}, embedded_nul); + const Json::jsonValue result = Json::jsonValue::parse(document().dump(4)); + EXPECT_EQ(result["text"], text); + EXPECT_EQ(result["embedded_nul"].get(), embedded_nul); +} - ModuleBase::Vector3 tau[2]; +TEST_F(AbacusJsonTest, PreservesInsertionOrder) +{ + Json::AbacusJson::set_json({"z"}, 1); + Json::AbacusJson::set_json({"a"}, 2); + Json::AbacusJson::set_json({"m"}, 3); + Json::AbacusJson::set_json({"a"}, 4); + + const Json::jsonValue result = Json::jsonValue::parse(document().dump()); + std::vector keys; + for (Json::jsonValue::const_iterator it = result.begin(); it != result.end(); ++it) + { + keys.push_back(it.key()); + } + EXPECT_EQ(keys, (std::vector{"z", "a", "m"})); + EXPECT_EQ(result["a"], 4); +} - Json::AbacusJson::doc.Parse("{}"); +TEST_F(AbacusJsonTest, OutputRecords) +{ + EXPECT_THROW(Json::add_output_energy(-1.0), std::invalid_argument); + Json::AbacusJson::set_json({"output"}, Json::jsonValue::array()); + EXPECT_THROW(Json::add_output_energy(-1.0), std::out_of_range); + Json::init_output_array_obj(); + ASSERT_EQ(document().at("output").size(), 1u); + const Json::jsonValue initial = document()["output"][0]; + for (const char* key : {"e_fermi", "energy", "scf_converge", "force", "stress"}) + { + EXPECT_TRUE(initial[key].is_null()); + } + for (const char* key : {"coordinate", "mag", "cell"}) + { + EXPECT_TRUE(initial[key].is_array()); + EXPECT_TRUE(initial[key].empty()); + } - double mag[2]; - // fill ucell - for (int i = 0; i < 1; i++) + Json::add_output_efermi_converge(1.5, true); + Json::add_output_energy(-10.0); + Json::add_output_scf_mag(1.0, 2.0, -9.0, -0.2, 1.0e-3, 0.5); + Json::add_output_scf_mag(1.0, 2.0, -10.0, -1.0, 1.0e-5, 0.6); + + const Json::jsonValue first = document()["output"][0]; + EXPECT_EQ(first["e_fermi"], 1.5); + EXPECT_EQ(first["energy"], -10.0); + EXPECT_EQ(first["scf_converge"], true); + EXPECT_EQ(first["total_mag"], 1.0); + EXPECT_EQ(first["absolute_mag"], 2.0); + ASSERT_EQ(first["scf"].size(), 2u); + EXPECT_EQ(first["scf"][1]["energy"], -10.0); + EXPECT_EQ(first["scf"][1]["ediff"], -1.0); + EXPECT_EQ(first["scf"][1]["drho"], 1.0e-5); + EXPECT_EQ(first["scf"][1]["time"], 0.6); + + Json::init_output_array_obj(); + Json::add_output_energy(-11.0); + ASSERT_EQ(document()["output"].size(), 2u); + EXPECT_EQ(document()["output"][0], first); + EXPECT_EQ(document()["output"][1]["energy"], -11.0); +} + +TEST_F(AbacusJsonTest, OutputStructureForceAndStress) +{ + UnitCell ucell; + Atom atom; + ucell.atoms = &atom; + ucell.ntype = 1; + ucell.nat = 1; + ucell.lat0_angstrom = 2.0; + ucell.latvec = ModuleBase::Matrix3(1.0, 0.0, 0.0, + 0.0, 2.0, 0.0, + 0.0, 0.0, 3.0); + atom.na = 1; + atom.tau = {ModuleBase::Vector3(0.25, -0.5, 0.75)}; + atom.mag = {1.5}; + + ModuleBase::matrix force(1, 3); + force(0, 0) = 0.5e-8; + force(0, 1) = 2.0; + force(0, 2) = -3.0; + + ModuleBase::matrix stress(3, 3); + for (int i = 0; i < 3; ++i) { - ucell.atoms[i].label = "Si"; - atomlist[i].na = 2; - ucell.pseudo_fn[i] = "si.ufp"; - ucell.atoms[i].tau.resize(2); - atomlist[i].mag.resize(2); - for (int j = 0; j < atomlist[i].na; j++) + for (int j = 0; j < 3; ++j) { - atomlist[i].mag[j] = j * 131; - ucell.atoms[i].tau[j] = 0.1 * j; + stress(i, j) = 3 * i + j + 1; } } - Json::gen_stru(&ucell, Input_para{}); - - std::string filename = "readin.json"; - Json::AbacusJson::write_to_json(filename); - // compare result - ASSERT_TRUE(Json::AbacusJson::doc.HasMember("init")); - ASSERT_EQ(Json::AbacusJson::doc["init"]["mag"][0].GetDouble(), 0); - ASSERT_EQ(Json::AbacusJson::doc["init"]["mag"][1].GetDouble(), 131.0); - - ASSERT_STREQ(Json::AbacusJson::doc["init"]["pp"]["Si"].GetString(), "si.ufp"); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["label"][0].GetString(), "Si"); - ASSERT_STREQ(Json::AbacusJson::doc["init"]["element"]["Si"].GetString(), ""); - - ASSERT_EQ(Json::AbacusJson::doc["init"]["coordinate"][0][0].GetDouble(), 0); - ASSERT_EQ(Json::AbacusJson::doc["init"]["coordinate"][0][1].GetDouble(), 0); - ASSERT_EQ(Json::AbacusJson::doc["init"]["coordinate"][0][2].GetDouble(), 0); - - EXPECT_NEAR(Json::AbacusJson::doc["init"]["coordinate"][1][0].GetDouble(), - ModuleBase::BOHR_TO_A, - 1.0e-12); - EXPECT_NEAR(Json::AbacusJson::doc["init"]["coordinate"][1][1].GetDouble(), - ModuleBase::BOHR_TO_A, - 1.0e-12); - EXPECT_NEAR(Json::AbacusJson::doc["init"]["coordinate"][1][2].GetDouble(), - ModuleBase::BOHR_TO_A, - 1.0e-12); + Json::init_output_array_obj(); + Json::add_output_cell_coo_stress_force(ucell, force, 2.0, stress, 0.5, true, true); + const Json::jsonValue first = document()["output"][0]; + ASSERT_EQ(first["force"].size(), 1u); + EXPECT_EQ(first["force"][0], Json::jsonValue::array({0.0, 4.0, -6.0})); + EXPECT_EQ(first["coordinate"][0], Json::jsonValue::array({0.5, -1.0, 1.5})); + EXPECT_EQ(first["mag"], Json::jsonValue::array({1.5})); + ASSERT_EQ(first["stress"].size(), 3u); + ASSERT_EQ(first["cell"].size(), 3u); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - EXPECT_NEAR(Json::AbacusJson::doc["init"]["cell"][i][j].GetDouble(), - (i + 1) * ModuleBase::BOHR_TO_A, - 1.0e-12); + EXPECT_DOUBLE_EQ(first["stress"][i][j].get(), stress(i, j) * 0.5); + EXPECT_DOUBLE_EQ(first["cell"][i][j].get(), i == j ? 2.0 * (i + 1) : 0.0); } } + + // Replacing the same step's data must not append extra rows or nested arrays. + Json::add_output_cell_coo_stress_force(ucell, force, 2.0, stress, 0.5, true, true); + EXPECT_EQ(document()["output"][0], first); + + Json::init_output_array_obj(); + Json::add_output_cell_coo_stress_force(ucell, force, 2.0, stress, 0.5, false, false); + EXPECT_EQ(document()["output"][0], first); + const Json::jsonValue& second = document()["output"][1]; + EXPECT_TRUE(second["force"].is_null()); + EXPECT_TRUE(second["stress"].is_null()); + EXPECT_EQ(second["coordinate"], first["coordinate"]); + EXPECT_EQ(second["cell"], first["cell"]); +} + +TEST_F(AbacusJsonTest, NonFiniteNumbersSerializeAsNull) +{ + Json::AbacusJson::set_json({"nan"}, std::numeric_limits::quiet_NaN()); + Json::AbacusJson::set_json({"inf"}, std::numeric_limits::infinity()); + const Json::jsonValue result = Json::jsonValue::parse(document().dump()); + EXPECT_TRUE(result["nan"].is_null()); + EXPECT_TRUE(result["inf"].is_null()); +} + +TEST_F(AbacusJsonTest, FileOpenFailureIsReported) +{ + const std::string blocker = "json-output-not-a-directory"; + { + std::ofstream file(blocker); + ASSERT_TRUE(file.is_open()); + } + EXPECT_THROW(Json::AbacusJson::write_to_json(blocker + "/abacus.json"), std::runtime_error); + EXPECT_EQ(std::remove(blocker.c_str()), 0); } diff --git a/source/source_io/parse_args.cpp b/source/source_io/parse_args.cpp index 1a6fa1fe386..046bc527028 100644 --- a/source/source_io/parse_args.cpp +++ b/source/source_io/parse_args.cpp @@ -165,7 +165,7 @@ void print_build_info() // --- 11. Other Libraries --- print_section("Other Libraries"); print_info("DFT-D4 Support", ABACUS_DFTD4_VERSION); - print_info("RapidJSON Support", ABACUS_RAPIDJSON_VERSION); + print_info("JSON Support", ABACUS_JSON_VERSION); print_info("PEXSI Support", ABACUS_PEXSI_VERSION); print_info("cnpy Support", ABACUS_CNPY_VERSION); diff --git a/source/source_main/driver_run.cpp b/source/source_main/driver_run.cpp index e55be43c9b0..60b75710ad6 100644 --- a/source/source_main/driver_run.cpp +++ b/source/source_main/driver_run.cpp @@ -87,7 +87,7 @@ void Driver::driver_run() unitcell::check_atomic_stru(ucell, input.min_dist_coef); ucell_initialized = true; -#ifdef __RAPIDJSON +#ifdef __JSON Json::gen_stru_wrapper(&ucell, input); #endif }; diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index b2eb16420da..7e65436de7e 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -13,6 +13,9 @@ #include "md_func.h" #include "source_base/global_file.h" #include "source_base/timer.h" +#ifdef __JSON +#include "source_io/module_json/output_info.h" +#endif #include "source_io/module_output/print_info.h" #include "msst.h" #include "nhchain.h" @@ -111,6 +114,15 @@ void md_line(MDCell& mdcell, /// md cycle, mohan update 2026-01-04, change '<=' to '<' while ((mdrun->step_ + mdrun->step_rst_) < param_in.mdp.md_nstep && !mdrun->stop) { +#ifdef __JSON + // JSON output currently follows the UnitCell-backed electronic-structure path. + // Start one output record before the solver appends SCF information for this MD step. + if (mdcell.has_backing_unitcell()) + { + Json::init_output_array_obj(); + } +#endif + if (mdrun->step_ == 0) { mdrun->setup(p_esolver, param_in.globalv.global_readin_dir, decomp); diff --git a/source/source_md/test/CMakeLists.txt b/source/source_md/test/CMakeLists.txt index 5fd8b8babe6..df92fe37f73 100644 --- a/source/source_md/test/CMakeLists.txt +++ b/source/source_md/test/CMakeLists.txt @@ -85,6 +85,14 @@ AddTest( ${depend_files} ) +set(md_run_json_sources) +if(ENABLE_JSON) + list(APPEND md_run_json_sources + ../../source_io/module_json/abacusjson.cpp + ../../source_io/module_json/output_info.cpp + ) +endif() + AddTest( TARGET MODULE_MD_run LIBS parameter psi device @@ -98,6 +106,7 @@ AddTest( ../verlet.cpp ../../source_cell/mdcell_reader.cpp ../../source_cell/magnetism.cpp + ${md_run_json_sources} ${depend_files} ) diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 38a75198b4e..2c844c176eb 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -101,8 +101,13 @@ void Relax_Driver::iter_info(const std::vector& steps, const Input_para& in ModuleIO::print_screen(steps[2], steps[1], steps[0]+1); } -#ifdef __RAPIDJSON - Json::init_output_array_obj(); +#ifdef __JSON + // ks-lr runs an embedded KS calculation in before_all_runners(), which + // already starts the first output record. + if (inp.esolver_type != "ks-lr" || steps[0] != 0) + { + Json::init_output_array_obj(); + } #endif } @@ -270,12 +275,18 @@ void Relax_Driver::stru_out(const int istep, UnitCell& ucell, const Input_para& void Relax_Driver::json_out(ModuleESolver::ESolver* p_esolver, UnitCell& ucell, const Input_para& inp, const ModuleBase::matrix& force, const ModuleBase::matrix& stress) { -#ifdef __RAPIDJSON +#ifdef __JSON Json::add_output_energy(p_esolver->cal_energy() * ModuleBase::Ry_to_eV); double unit_transform = ModuleBase::RYDBERG_SI / pow(ModuleBase::BOHR_RADIUS_SI, 3) * 1.0e-8; double fac = ModuleBase::Ry_to_eV / 0.529177; - Json::add_output_cell_coo_stress_force(&ucell, force, fac, stress, unit_transform); + Json::add_output_cell_coo_stress_force(ucell, + force, + fac, + stress, + unit_transform, + inp.cal_force, + inp.cal_stress); #endif } diff --git a/source/source_relax/socket_handlers.cpp b/source/source_relax/socket_handlers.cpp index dc333ced087..94fa504beb7 100644 --- a/source/source_relax/socket_handlers.cpp +++ b/source/source_relax/socket_handlers.cpp @@ -5,6 +5,9 @@ #include "source_cell/unitcell.h" #include "source_esolver/esolver.h" #include "source_io/module_parameter/input_parameter.h" +#ifdef __JSON +#include "source_io/module_json/output_info.h" +#endif #include #include @@ -443,6 +446,10 @@ void handle_posdata(IpiSocket& socket, PosdataPayload payload = read_posdata(socket, *context.ucell, context.state); bcast_posdata(payload); check_posdata_geometry(context, payload, context); +#ifdef __JSON + // Each POSDATA frame is one electronic-structure output step. + Json::init_output_array_obj(); +#endif run_esolver_for_positions(*context.ucell, context.esolver, payload.positions, context.istep); ComputedFrame computed; diff --git a/source/source_relax/test/CMakeLists.txt b/source/source_relax/test/CMakeLists.txt index 22376932aa3..05c2c405e9d 100644 --- a/source/source_relax/test/CMakeLists.txt +++ b/source/source_relax/test/CMakeLists.txt @@ -17,6 +17,13 @@ AddTest( SOURCES test_socket_frame.cpp ../socket_frame.cpp ../socket_frame_utils.cpp ) +set(socket_json_sources) +if(ENABLE_JSON) + list(APPEND socket_json_sources + ../../source_io/module_json/abacusjson.cpp + ../../source_io/module_json/output_info.cpp) +endif() + AddTest( TARGET MODULE_RELAX_socket_driver_test LIBS MPI::MPI_CXX base device @@ -30,6 +37,7 @@ AddTest( ../socket_ipi.cpp ../../source_cell/update_cell.cpp ../../source_cell/bcast_cell.cpp + ${socket_json_sources} ) set_tests_properties(MODULE_RELAX_socket_driver_test PROPERTIES TIMEOUT 15) diff --git a/toolchain/README.md b/toolchain/README.md index 89dd8c5e9ab..5f81fedfc5b 100644 --- a/toolchain/README.md +++ b/toolchain/README.md @@ -127,9 +127,16 @@ Before running the toolchain, please make sure you have loaded the related envir - [LibComm](https://github.com/abacusmodeling/LibComm) - Communication library - [NEP](https://github.com/brucefan1983/NEP_CPU) - Neuroevolution Potential - [Cereal](https://github.com/USCiLab/cereal) - C++ serialization library - - [RapidJSON](https://github.com/Tencent/rapidjson) - Fast JSON parser/generator + - [nlohmann-json](https://github.com/nlohmann/json) - Fast JSON parser/generator - **Reference mirror:** [CP2K static downloads](https://www.cp2k.org/static/downloads) -- All package from GitHub will be downloaded by `wget` from `codeload.github.com`, which bypass the difficulty of CN Internet in some extent. +- Most GitHub source archives are downloaded from `codeload.github.com`. nlohmann-json uses the official `json.tar.xz` release asset. + +JSON output is enabled with `-DENABLE_JSON=ON`. `ENABLE_RAPIDJSON` is no longer +supported because the JSON backend has been replaced by nlohmann-json. The +toolchain package option is `--with-json=install|system|/prefix|no`. In `system` +mode, ABACUS uses CMake's normal package search; the installation must provide +`nlohmann_jsonConfig.cmake`, not just the headers. Release archives are cached as +`build/json-.tar.xz` for offline installation. ### Offline Installation @@ -200,7 +207,7 @@ Mix online and offline packages as needed - the toolchain automatically detects | ELPA | 2026.02.001 / 2024.05.001 | Eigenvalue solver | LGPL-3.0-only | Install | | **Advanced Features** ||||| | Cereal | pinned commit | C++ Serialization | BSD | Install | -| RapidJSON | pinned commit | JSON parsing | MIT | Install | +| nlohmann-json | 3.12.0 | JSON parsing | MIT | Install | | LibRI | pinned commit | EXX calculations | GPL-3.0 | Install | | LibComm | pinned commit | EXX calculations | GPL-3.0 | Install | | LibTorch | 2.1.2 / 1.12.1 | MLALGO support | BSD-3-Clause | Optional | diff --git a/toolchain/build_abacus_aocc-aocl.sh b/toolchain/build_abacus_aocc-aocl.sh index c78b86b1370..08bfab5fe43 100755 --- a/toolchain/build_abacus_aocc-aocl.sh +++ b/toolchain/build_abacus_aocc-aocl.sh @@ -71,7 +71,7 @@ cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DENABLE_LIBXC=ON \ -DENABLE_OPENMP=ON \ -DENABLE_ELPA=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_LIBRI=ON \ -DLIBRI_DIR=$LIBRI \ -DLIBCOMM_DIR=$LIBCOMM \ diff --git a/toolchain/build_abacus_gcc-aocl.sh b/toolchain/build_abacus_gcc-aocl.sh index f2f96b1509d..153b898b21e 100755 --- a/toolchain/build_abacus_gcc-aocl.sh +++ b/toolchain/build_abacus_gcc-aocl.sh @@ -67,7 +67,7 @@ cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DENABLE_LIBXC=ON \ -DENABLE_OPENMP=ON \ -DENABLE_ELPA=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_LIBRI=ON \ -DLIBRI_DIR=$LIBRI \ -DLIBCOMM_DIR=$LIBCOMM \ diff --git a/toolchain/build_abacus_gcc-mkl.sh b/toolchain/build_abacus_gcc-mkl.sh index db1a30e463f..dbce5db16c7 100755 --- a/toolchain/build_abacus_gcc-mkl.sh +++ b/toolchain/build_abacus_gcc-mkl.sh @@ -64,7 +64,7 @@ cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DENABLE_OPENMP=ON \ -DENABLE_ELPA=ON \ -DENABLE_DFTD4=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_LIBRI=ON \ -DLIBRI_DIR=$LIBRI \ -DLIBCOMM_DIR=$LIBCOMM \ diff --git a/toolchain/build_abacus_gnu.sh b/toolchain/build_abacus_gnu.sh index 2a26780f3c9..505ae67268d 100755 --- a/toolchain/build_abacus_gnu.sh +++ b/toolchain/build_abacus_gnu.sh @@ -66,7 +66,7 @@ cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DENABLE_OPENMP=ON \ -DENABLE_ELPA=ON \ -DENABLE_DFTD4=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_LIBRI=ON \ -DLIBRI_DIR=$LIBRI \ -DLIBCOMM_DIR=$LIBCOMM \ diff --git a/toolchain/build_abacus_intel.sh b/toolchain/build_abacus_intel.sh index da043fdc80c..100da97d583 100755 --- a/toolchain/build_abacus_intel.sh +++ b/toolchain/build_abacus_intel.sh @@ -65,7 +65,7 @@ cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DENABLE_OPENMP=ON \ -DENABLE_ELPA=ON \ -DENABLE_DFTD4=ON \ - -DENABLE_RAPIDJSON=ON \ + -DENABLE_JSON=ON \ -DENABLE_LIBRI=ON \ -DLIBRI_DIR=$LIBRI \ -DLIBCOMM_DIR=$LIBCOMM \ diff --git a/toolchain/scripts/lib/config_manager.sh b/toolchain/scripts/lib/config_manager.sh index 80eacd51969..ce9345617c9 100644 --- a/toolchain/scripts/lib/config_manager.sh +++ b/toolchain/scripts/lib/config_manager.sh @@ -19,7 +19,7 @@ CONFIG_FILE_LOADED=false tool_list="gcc intel amd cmake" mpi_list="mpich openmpi intelmpi" math_list="mkl aocl openblas" -lib_list="fftw libxc scalapack elpa cereal rapidjson libtorch libnpy libri libcomm nep dftd4" +lib_list="fftw libxc scalapack elpa cereal json libtorch libnpy libri libcomm nep dftd4" package_list="${tool_list} ${mpi_list} ${math_list} ${lib_list}" # Configuration file paths for loading in advance @@ -367,7 +367,7 @@ config_set_defaults() { CONFIG_CACHE["with_scalapack"]="__INSTALL__" CONFIG_CACHE["with_elpa"]="__INSTALL__" CONFIG_CACHE["with_cereal"]="__INSTALL__" - CONFIG_CACHE["with_rapidjson"]="__INSTALL__" + CONFIG_CACHE["with_json"]="__INSTALL__" CONFIG_CACHE["with_libri"]="__INSTALL__" CONFIG_CACHE["with_libcomm"]="__INSTALL__" CONFIG_CACHE["with_nep"]="__DONTUSE__" diff --git a/toolchain/scripts/lib/package_manager.sh b/toolchain/scripts/lib/package_manager.sh index 71432578d81..0bdac523a06 100644 --- a/toolchain/scripts/lib/package_manager.sh +++ b/toolchain/scripts/lib/package_manager.sh @@ -62,7 +62,7 @@ package_manager_define_dependencies() { # Stage 4: Advanced Libraries PACKAGE_DEPENDENCIES["dftd4"]="cmake" PACKAGE_DEPENDENCIES["cereal"]="gcc cmake" - PACKAGE_DEPENDENCIES["rapidjson"]="gcc cmake" + PACKAGE_DEPENDENCIES["json"]="gcc cmake" PACKAGE_DEPENDENCIES["libtorch"]="gcc cmake" PACKAGE_DEPENDENCIES["libnpy"]="gcc cmake" PACKAGE_DEPENDENCIES["libri"]="gcc cmake" diff --git a/toolchain/scripts/lib/user_interface.sh b/toolchain/scripts/lib/user_interface.sh index 08a75f40450..3ef91c1731e 100644 --- a/toolchain/scripts/lib/user_interface.sh +++ b/toolchain/scripts/lib/user_interface.sh @@ -606,7 +606,7 @@ PACKAGE CONTROL OPTIONS: 📦 MPI Libraries: openmpi, mpich, intelmpi 📦 Math Libraries: mkl, aocl, openblas, scalapack 📦 Scientific: libxc, fftw, elpa - 📦 Advanced: cereal, rapidjson, libri, libcomm, libtorch, libnpy, nep + 📦 Advanced: cereal, json, libri, libcomm, libtorch, libnpy, nep ADVANCED OPTIONS: --enable-[=yes/no] Enable specific advanced features diff --git a/toolchain/scripts/package_versions.sh b/toolchain/scripts/package_versions.sh index 6cef74e63fd..30b1bc5efc3 100644 --- a/toolchain/scripts/package_versions.sh +++ b/toolchain/scripts/package_versions.sh @@ -113,8 +113,8 @@ libcomm_sha256="d7b991465d98d7b715b484d86880bf3525b9bf0cc62c3e5d38b0f6d140f6b9d4 libri_ver="b0eff7a" libri_sha256="bee9df54e0d827942524640897c3a57c0c738d2db6b2d73a09ab6c054bef94ea" -rapidjson_ver="24b5e7a" -rapidjson_sha256="dcb57b11036cb8fc6b2a57a6aded68d52e9cfe543811bf4fa8941087f84e72d0" +json_ver="3.12.0" +json_sha256="42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa" # NEP (Neural Evolution Potential) - CPU version nep_ver="629ec5d" @@ -260,9 +260,9 @@ load_package_vars() { libri_ver="${libri_ver}" libri_sha256="${libri_sha256}" ;; - "rapidjson") - rapidjson_ver="${rapidjson_ver}" - rapidjson_sha256="${rapidjson_sha256}" + "json") + json_ver="${json_ver}" + json_sha256="${json_sha256}" ;; "nep") nep_ver="${nep_ver}" diff --git a/toolchain/scripts/stage4/install_json.sh b/toolchain/scripts/stage4/install_json.sh new file mode 100755 index 00000000000..384e807ac3d --- /dev/null +++ b/toolchain/scripts/stage4/install_json.sh @@ -0,0 +1,67 @@ +#!/bin/bash -e + +SCRIPT_NAME="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(cd "$(dirname "${SCRIPT_NAME}")/.." && pwd -P)" + +source "${SCRIPT_DIR}/common_vars.sh" +source "${SCRIPT_DIR}/tool_kit.sh" +source "${SCRIPT_DIR}/signal_trap.sh" +source "${SCRIPT_DIR}/package_versions.sh" +load_package_vars "json" +source "${INSTALLDIR}/toolchain.conf" +source "${INSTALLDIR}/toolchain.env" + +rm -f "${BUILDDIR}/setup_json" +mkdir -p "${BUILDDIR}" +cd "${BUILDDIR}" +pkg_install_dir="" + +case "${with_json}" in + __INSTALL__) + echo "==================== Installing nlohmann-json ====================" + filename="json-${json_ver}.tar.xz" + url="https://github.com/nlohmann/json/releases/download/v${json_ver}/json.tar.xz" + pkg_install_dir="${INSTALLDIR}/json-${json_ver}" + install_lock_file="${pkg_install_dir}/install_successful" + if verify_checksums "${install_lock_file}"; then + echo "json-${json_ver} is already installed, skipping it." + else + retrieve_package "${json_sha256}" "${filename}" "${url}" + if [ "${PACK_RUN}" = "__TRUE__" ]; then + echo "--pack-run mode specified, skip installation" + exit 0 + fi + [ -d "json-${json_ver}" ] && rm -rf "json-${json_ver}" + tar -xJf "json-${json_ver}.tar.xz" + cd "json" + mkdir build && cd build + cmake .. \ + -DCMAKE_INSTALL_PREFIX="${pkg_install_dir}" \ + -DJSON_BuildTests=OFF \ + > cmake.log 2>&1 || tail -n "${LOG_LINES}" cmake.log + make install -j "$(get_nprocs)" > install.log 2>&1 || tail -n "${LOG_LINES}" install.log + write_checksums "${install_lock_file}" "${SCRIPT_DIR}/stage4/install_json.sh" + fi + ;; + __SYSTEM__) + # ABACUS resolves the installed configuration with CMake's find_package. + echo "Using system nlohmann-json (resolved by CMake)." + ;; + __DONTUSE__) ;; + *) + pkg_install_dir="${with_json}" + check_dir "${pkg_install_dir}" + ;; +esac + +if [ -n "${pkg_install_dir}" ]; then + cat > "${BUILDDIR}/setup_json" < cmake.log 2>&1 || tail -n ${LOG_LINES} cmake.log - make install -j $(get_nprocs) > make.log 2>&1 || tail -n ${LOG_LINES} make.log - write_checksums "${install_lock_file}" "${SCRIPT_DIR}/stage4/$(basename ${SCRIPT_NAME})" - fi - RAPIDJSON_CFLAGS="-I'${pkg_install_dir}/include'" - ;; - __SYSTEM__) - echo "==================== Finding RapidJSON from system paths ====================" - if [ "${PACK_RUN}" = "__TRUE__" ]; then - echo "--pack-run mode specified, skip system check" - exit 0 - fi - # Find rapidjson header file and derive package root directory - rapidjson_header_path="$(find_in_paths "rapidjson/rapidjson.h" $INCLUDE_PATHS)" - if [ "$rapidjson_header_path" != "__FALSE__" ]; then - # Derive pkg_install_dir from found header path - # rapidjson/rapidjson.h -> remove /rapidjson/rapidjson.h -> get include dir -> get parent dir - rapidjson_include_dir="$(dirname "$(dirname "$rapidjson_header_path")")" - pkg_install_dir="$(dirname "$rapidjson_include_dir")" - echo "Found rapidjson at: ${pkg_install_dir}" - RAPIDJSON_CFLAGS="-I'${rapidjson_include_dir}'" - else - report_error "Cannot find rapidjson/rapidjson.h in system paths" - exit 1 - fi - ;; - __DONTUSE__) ;; - - *) - echo "==================== Linking RapidJSON to user paths ====================" - pkg_install_dir="${with_rapidjson}" - check_dir "${pkg_install_dir}" - RAPIDJSON_CFLAGS="-I'${pkg_install_dir}/include'" - ;; -esac -if [ "$with_rapidjson" != "__DONTUSE__" ]; then - if [ "$with_rapidjson" != "__SYSTEM__" ]; then - cat << EOF > "${BUILDDIR}/setup_rapidjson" -prepend_path CMAKE_PREFIX_PATH "${pkg_install_dir}" -EOF - fi - cat << EOF >> "${BUILDDIR}/setup_rapidjson" -export RAPIDJSON_ROOT="${pkg_install_dir}" -EOF - filter_setup "${BUILDDIR}/setup_rapidjson" $SETUPFILE -fi - -load "${BUILDDIR}/setup_rapidjson" -write_toolchain_env "${INSTALLDIR}" - -cd "${ROOTDIR}" -report_timing "rapidjson" diff --git a/toolchain/scripts/stage4/install_stage4.sh b/toolchain/scripts/stage4/install_stage4.sh index 0980d809e5e..ffeefdcb761 100755 --- a/toolchain/scripts/stage4/install_stage4.sh +++ b/toolchain/scripts/stage4/install_stage4.sh @@ -5,7 +5,7 @@ ./scripts/stage4/install_dftd4.sh ./scripts/stage4/install_cereal.sh -./scripts/stage4/install_rapidjson.sh +./scripts/stage4/install_json.sh ./scripts/stage4/install_libtorch.sh ./scripts/stage4/install_libnpy.sh ./scripts/stage4/install_libri.sh diff --git a/toolchain/scripts/tool_kit.sh b/toolchain/scripts/tool_kit.sh index 361d915ea89..677bb07df64 100755 --- a/toolchain/scripts/tool_kit.sh +++ b/toolchain/scripts/tool_kit.sh @@ -126,7 +126,7 @@ Also. the --pack-run option can help in OFFLINE installation. You can manually install requirements packages via: 0. Download by 'wget $__url -O $__filename' manually 1. Download from www.cp2k.org/static/downloads (for OpenBLAS, OpenMPI and Others) -2. Download from github.com (especially for CEREAL, RapidJSON, libnpy, LibRI and other stage4 packages) +2. Download from github.com (especially for CEREAL, nlohmann-json, libnpy, LibRI and other stage4 packages) 3. for Intel-oneAPI and AMD AOCC/AOCL, please contact your server manager or visit their official website 4. For users in China, you can try Gitee mirror: git clone https://gitee.com/jamesmisaka/abacus_toolchain_build.git EOF diff --git a/toolchain/tests/test_rapidjson_cmake.sh b/toolchain/tests/test_rapidjson_cmake.sh deleted file mode 100755 index c7460257d82..00000000000 --- a/toolchain/tests/test_rapidjson_cmake.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -set -u - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" -FAILURES=0 - -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAILURES=$((FAILURES + 1)) -} - -write_target_package() { - local prefix="$1" - mkdir -p "${prefix}/include/rapidjson" "${prefix}/lib/cmake/RapidJSON" - printf '#pragma once\n' >"${prefix}/include/rapidjson/document.h" - cat >"${prefix}/lib/cmake/RapidJSON/RapidJSONConfig.cmake" <<'EOF' -message(STATUS "Loaded fake RapidJSON target package") -get_filename_component(RapidJSON_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -get_filename_component(_rapidjson_prefix "${RapidJSON_CMAKE_DIR}/../../.." ABSOLUTE) -if(NOT TARGET RapidJSON) - add_library(RapidJSON INTERFACE IMPORTED) - set_property(TARGET RapidJSON PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${_rapidjson_prefix}/include") -endif() -EOF -} - -write_fake_mkl() { - local prefix="$1" - mkdir -p "${prefix}/include" "${prefix}/lib" - printf '#pragma once\n' >"${prefix}/include/mkl_service.h" - : >"${prefix}/lib/libmkl_core.so" - : >"${prefix}/lib/libmkl_gf_lp64.so" - : >"${prefix}/lib/libmkl_gnu_thread.so" -} - -write_variable_only_package() { - local prefix="$1" - mkdir -p "${prefix}/include/rapidjson" "${prefix}/lib/cmake/RapidJSON" - printf '#pragma once\n' >"${prefix}/include/rapidjson/document.h" - cat >"${prefix}/lib/cmake/RapidJSON/RapidJSONConfig.cmake" <<'EOF' -get_filename_component(RapidJSON_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -get_filename_component(_rapidjson_prefix "${RapidJSON_CMAKE_DIR}/../../.." ABSOLUTE) -set(RapidJSON_INCLUDE_DIR "${_rapidjson_prefix}/include") -set(RapidJSON_INCLUDE_DIRS "${RapidJSON_INCLUDE_DIR}") -EOF -} - -run_top_level_configure() { - local build_dir="$1" - local prefix="$2" - local mkl_root="$3" - - cmake -S "$REPO_ROOT" -B "$build_dir" \ - -DENABLE_RAPIDJSON=ON \ - -DENABLE_LCAO=OFF \ - -DENABLE_MPI=OFF \ - -DENABLE_OPENMP=OFF \ - -DMKLROOT="$mkl_root" \ - -DCMAKE_PREFIX_PATH="$prefix" \ - >"${build_dir}.log" 2>&1 -} - -test_top_level_accepts_rapidjson_target_package() { - local tmpdir prefix mkl_root build_dir status - tmpdir="$(mktemp -d)" - prefix="${tmpdir}/prefix" - mkl_root="${tmpdir}/mkl" - build_dir="${tmpdir}/target-build" - - write_target_package "$prefix" - write_fake_mkl "$mkl_root" - run_top_level_configure "$build_dir" "$prefix" "$mkl_root" - status=$? - - if ! grep -Fq "Loaded fake RapidJSON target package" "${build_dir}.log"; then - cat "${build_dir}.log" >&2 - fail "top-level CMake did not load the fake RapidJSON target package" - elif grep -Fq "RapidJSON was found, but target RapidJSON is missing" "${build_dir}.log"; then - cat "${build_dir}.log" >&2 - fail "top-level CMake rejected RapidJSON target package as target-missing" - elif grep -Fq 'Could not find a package configuration file provided by "RapidJSON"' "${build_dir}.log"; then - cat "${build_dir}.log" >&2 - fail "top-level CMake did not find the fake RapidJSON target package" - elif [[ "$status" -ne 0 ]]; then - cat "${build_dir}.log" >&2 - fail "top-level CMake failed with a RapidJSON target package" - fi - - rm -rf "$tmpdir" -} - -test_top_level_rejects_variable_only_package() { - local tmpdir prefix mkl_root build_dir status - tmpdir="$(mktemp -d)" - prefix="${tmpdir}/prefix" - mkl_root="${tmpdir}/mkl" - build_dir="${tmpdir}/variable-build" - - write_variable_only_package "$prefix" - write_fake_mkl "$mkl_root" - run_top_level_configure "$build_dir" "$prefix" "$mkl_root" - status=$? - - if [[ "$status" -eq 0 ]]; then - cat "${build_dir}.log" >&2 - fail "top-level CMake configured with variable-only RapidJSON package; expected target-missing failure" - elif ! grep -Fq "RapidJSON was found, but target RapidJSON is missing." "${build_dir}.log"; then - cat "${build_dir}.log" >&2 - fail "top-level CMake failed for the wrong reason with variable-only RapidJSON package" - fi - - rm -rf "$tmpdir" -} - -test_top_level_accepts_rapidjson_target_package -test_top_level_rejects_variable_only_package - -if [[ "$FAILURES" -ne 0 ]]; then - printf '%s RapidJSON CMake test(s) failed\n' "$FAILURES" >&2 - exit 1 -fi - -printf 'RapidJSON CMake tests passed\n' diff --git a/toolchain/toolchain_aocc-aocl.sh b/toolchain/toolchain_aocc-aocl.sh index 83ad0a1c841..17e2cd1cfec 100755 --- a/toolchain/toolchain_aocc-aocl.sh +++ b/toolchain/toolchain_aocc-aocl.sh @@ -36,7 +36,7 @@ WITH_ELPA="install" # Utility Libraries WITH_CEREAL="install" -WITH_RAPIDJSON="install" +WITH_JSON="install" # Advanced Features (EXX calculations) WITH_LIBRI="install" @@ -101,7 +101,7 @@ run_toolchain_with_log compile.log ./install_abacus_toolchain_new.sh \ --with-fftw="$WITH_FFTW" \ --with-elpa="$WITH_ELPA" \ --with-cereal="$WITH_CEREAL" \ - --with-rapidjson="$WITH_RAPIDJSON" \ + --with-json="$WITH_JSON" \ --with-libtorch="$WITH_LIBTORCH" \ --with-nep="$WITH_NEP" \ --with-libnpy="$WITH_LIBNPY" \ diff --git a/toolchain/toolchain_gcc-aocl.sh b/toolchain/toolchain_gcc-aocl.sh index 1238defd361..6ef8789665d 100755 --- a/toolchain/toolchain_gcc-aocl.sh +++ b/toolchain/toolchain_gcc-aocl.sh @@ -36,7 +36,7 @@ WITH_ELPA="install" # Utility Libraries WITH_CEREAL="install" -WITH_RAPIDJSON="install" +WITH_JSON="install" # Advanced Features (EXX calculations) WITH_LIBRI="install" @@ -98,7 +98,7 @@ run_toolchain_with_log compile.log ./install_abacus_toolchain_new.sh \ --with-fftw="$WITH_FFTW" \ --with-elpa="$WITH_ELPA" \ --with-cereal="$WITH_CEREAL" \ - --with-rapidjson="$WITH_RAPIDJSON" \ + --with-json="$WITH_JSON" \ --with-libtorch="$WITH_LIBTORCH" \ --with-nep="$WITH_NEP" \ --with-libnpy="$WITH_LIBNPY" \ diff --git a/toolchain/toolchain_gcc-mkl.sh b/toolchain/toolchain_gcc-mkl.sh index df0fc04d108..a44bd5d710a 100755 --- a/toolchain/toolchain_gcc-mkl.sh +++ b/toolchain/toolchain_gcc-mkl.sh @@ -36,7 +36,7 @@ WITH_ELPA="install" # Utility Libraries WITH_CEREAL="install" -WITH_RAPIDJSON="install" +WITH_JSON="install" # Advanced Features (EXX calculations) WITH_LIBRI="install" @@ -101,7 +101,7 @@ run_toolchain_with_log compile.log ./install_abacus_toolchain_new.sh \ --with-elpa="$WITH_ELPA" \ --with-dftd4="$WITH_DFTD4" \ --with-cereal="$WITH_CEREAL" \ - --with-rapidjson="$WITH_RAPIDJSON" \ + --with-json="$WITH_JSON" \ --with-libtorch="$WITH_LIBTORCH" \ --with-nep="$WITH_NEP" \ --with-libnpy="$WITH_LIBNPY" \ diff --git a/toolchain/toolchain_gnu.sh b/toolchain/toolchain_gnu.sh index e297c0e453b..e4b6359afba 100755 --- a/toolchain/toolchain_gnu.sh +++ b/toolchain/toolchain_gnu.sh @@ -37,7 +37,7 @@ WITH_ELPA="install" # Utility Libraries WITH_CEREAL="install" -WITH_RAPIDJSON="install" +WITH_JSON="install" # Advanced Features (EXX calculations) WITH_LIBRI="install" @@ -104,7 +104,7 @@ run_toolchain_with_log compile.log ./install_abacus_toolchain_new.sh \ --with-elpa="$WITH_ELPA" \ --with-dftd4="$WITH_DFTD4" \ --with-cereal="$WITH_CEREAL" \ - --with-rapidjson="$WITH_RAPIDJSON" \ + --with-json="$WITH_JSON" \ --with-libtorch="$WITH_LIBTORCH" \ --with-nep="$WITH_NEP" \ --with-libnpy="$WITH_LIBNPY" \ diff --git a/toolchain/toolchain_intel.sh b/toolchain/toolchain_intel.sh index 9f854a0b4a1..4ef5166b55f 100755 --- a/toolchain/toolchain_intel.sh +++ b/toolchain/toolchain_intel.sh @@ -37,7 +37,7 @@ WITH_ELPA="install" # Utility Libraries WITH_CEREAL="install" -WITH_RAPIDJSON="install" +WITH_JSON="install" # Advanced Features (EXX calculations) WITH_LIBRI="install" @@ -122,7 +122,7 @@ run_toolchain_with_log compile.log ./install_abacus_toolchain_new.sh \ --with-elpa="$WITH_ELPA" \ --with-dftd4="$WITH_DFTD4" \ --with-cereal="$WITH_CEREAL" \ - --with-rapidjson="$WITH_RAPIDJSON" \ + --with-json="$WITH_JSON" \ --with-libtorch="$WITH_LIBTORCH" \ --with-nep="$WITH_NEP" \ --with-libnpy="$WITH_LIBNPY" \ diff --git a/tools/03_code_analysis/code_quality_score.py b/tools/03_code_analysis/code_quality_score.py old mode 100644 new mode 100755