diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 83c986d168c..b99f4c6a35d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,6 +5,7 @@ - [ ] I have listed the exact verification commands run and their results. - [ ] I have described user-visible behavior changes, including INPUT parameter changes. - [ ] I have explained core-module impact for ESolver, HSolver, ElecState, Hamilt, Operator, Psi, or other `source/` changes. +- [ ] If this changes `interfaces/ASE_interface/`, `source/source_relax/socket_*`, or the socket metadata schema, I linked the abacuslite counterpart and updated the compatibility contract. - [ ] I have requested any needed governance exception below. ### Linked Issue diff --git a/.github/workflows/ase_plugin_test.yml b/.github/workflows/ase_plugin_test.yml index 822afcd957c..c9d5516b1db 100644 --- a/.github/workflows/ase_plugin_test.yml +++ b/.github/workflows/ase_plugin_test.yml @@ -1,7 +1,15 @@ -name: Atomic Simulation Environment (ASE) Plugin Test +name: Atomic Simulation Environment (ASE) Interface Test on: pull_request: + schedule: + - cron: '41 3 * * 1' + workflow_dispatch: + inputs: + plugin_ref: + description: Optional reviewed QuantumMisaka/abacuslite ref; empty uses published 1.2.2 + required: false + default: '' defaults: run: @@ -9,15 +17,29 @@ defaults: jobs: test: - name: abacuslite + name: abacuslite external runs-on: ubuntu-latest + timeout-minutes: 120 container: image: ghcr.io/deepmodeling/abacus-gnu + env: + ABACUS_COMMAND: mpirun -np 2 abacus + ASE_ABACUS_ASSETS: ${{ github.workspace }}/tests/PP_ORB + ASE_ABACUS_SOCKET: '1' steps: - - name: Checkout code + - name: Checkout engine PR uses: actions/checkout@v7.0.1 + - name: Checkout reviewed plugin candidate + if: github.event_name == 'workflow_dispatch' && inputs.plugin_ref != '' + uses: actions/checkout@v7.0.1 + with: + repository: QuantumMisaka/abacuslite + ref: ${{ inputs.plugin_ref }} + path: candidate-plugin + persist-credentials: false + - name: Set up Miniconda uses: conda-incubator/setup-miniconda@v4 with: @@ -25,17 +47,39 @@ jobs: activate-environment: abacuslite python-version: '3.10' - - name: Install dependencies + - name: Install Python dependencies shell: bash -l {0} run: | + set -euo pipefail conda install -y pip pip install numpy scipy matplotlib - name: Install abacuslite shell: bash -l {0} + env: + PLUGIN_REF: ${{ inputs.plugin_ref }} run: | - cd interfaces/ASE_interface - pip install . + set -euo pipefail + mkdir -p "$RUNNER_TEMP/plugin-wheel" + if [ -n "$PLUGIN_REF" ]; then + python -m pip install 'build>=1.2' + python -m build --wheel --outdir "$RUNNER_TEMP/plugin-wheel" candidate-plugin + git -C candidate-plugin rev-parse HEAD + else + python -m pip download --only-binary=:all: --no-deps \ + --dest "$RUNNER_TEMP/plugin-wheel" 'abacuslite==1.2.2' + fi + wheels=("$RUNNER_TEMP"/plugin-wheel/*.whl) + test "${#wheels[@]}" -eq 1 + sha256sum "${wheels[0]}" + if [ -z "$PLUGIN_REF" ]; then + actual="$(sha256sum "${wheels[0]}")" + test "${actual%% *}" = d4675aae2f70b99a6e18882e2c4ecf7f67f57eae10f5ba3e01622e879c6b8caf + fi + python -m pip install "${wheels[0]}" + python -m pip show abacuslite ase numpy seekpath + python interfaces/ASE_interface/tests/verify_external.py \ + --checkout-root "$GITHUB_WORKSPACE" - name: Install external tools from toolchain run: | @@ -45,46 +89,58 @@ jobs: ./scripts/stage4/install_stage4.sh cd .. - - name: Configure & Build ABACUS (GNU) + - name: Configure & build ABACUS (GNU) run: | - git config --global --add safe.directory `pwd` + git config --global --add safe.directory "$(pwd)" source toolchain/install/setup prepend_path CMAKE_PREFIX_PATH ${PEXSI32_ROOT}:${SUPERLU_DIST32_ROOT}:${PARMETIS32_ROOT}:${METIS32_ROOT}:${GKLIB_ROOT} rm -rf build cmake -B build -G Ninja cmake --build build -j2 - - - name: Install and Soft Link ABACUS + + - name: Install and attest the built ABACUS executable run: | + built="$GITHUB_WORKSPACE/build/abacus_basic_para" + test -x "$built" cmake --install build - if [ -f /usr/local/bin/abacus_2p ]; then - ln -sf /usr/local/bin/abacus_2p /usr/local/bin/abacus - fi + installed="$(command -v abacus)" + test -x "$installed" + built_sha="$(sha256sum "$built")" + installed_sha="$(sha256sum "$installed")" + test "${built_sha%% *}" = "${installed_sha%% *}" + printf 'Built ABACUS: %s %s\n' "$built" "${built_sha%% *}" + printf 'Installed ABACUS: %s %s\n' "$installed" "${installed_sha%% *}" + git rev-parse HEAD + abacus --version - - name: Run unit tests + - name: Run offline harness contracts shell: bash -l {0} run: | - cd interfaces/ASE_interface/abacuslite - chmod +x xtest.sh - ./xtest.sh + set -euo pipefail + cd interfaces/ASE_interface/tests + python -m unittest test_external_provenance test_harness_facts \ + test_native_md_fixture test_portable_runner -v - - name: Run integration tests + - name: Run engine-owned integration tests shell: bash -l {0} run: | - cd interfaces/ASE_interface/tests - chmod +x xtest.sh - ./xtest.sh + set -euo pipefail + python interfaces/ASE_interface/tests/run_engine_compatibility.py \ + --engine "$GITHUB_WORKSPACE/build/abacus_basic_para" \ + --mpi-command 'mpirun -np 2' \ + --assets "$GITHUB_WORKSPACE/tests/PP_ORB" \ + --format latest \ + --wheel "$RUNNER_TEMP"/plugin-wheel/*.whl \ + --report-dir "$RUNNER_TEMP/ase-engine-tests" \ + --engine-ref "$(git rev-parse HEAD)" \ + --python "$(command -v python)" - # When a test fails, every integration test script (scf/relax/md/band/ - # magnetic) preserves its whole ABACUS run directory under - # /tmp/abacus_ase_failure (see preserved_tmpdir in tests/common.py, - # overridable via ASE_ABACUS_KEEP_DIR). Upload it so remote-CI-only - # failures (e.g. issue #7794) can be root-caused without runner access. - name: Upload ASE failure artifacts - if: failure() + if: always() uses: actions/upload-artifact@v7 with: name: ase-failure-${{ github.run_id }}-${{ github.run_attempt }} - path: /tmp/abacus_ase_failure/** + path: | + ${{ runner.temp }}/ase-engine-tests/** if-no-files-found: ignore retention-days: 14 diff --git a/docs/advanced/interface/ase.md b/docs/advanced/interface/ase.md index b7b02d3a279..8dfb861f8c0 100644 --- a/docs/advanced/interface/ase.md +++ b/docs/advanced/interface/ase.md @@ -4,7 +4,11 @@ [ASE](https://wiki.fysik.dtu.dk/ase/) (Atomic Simulation Environment) performs as a powerful Pythonic platform for atomistic simulations, in which there are plenty of functionalties supported, such as various geometry optimization algorithms for finding both the minimum energy point and the transition states, including BFGS, BFGSLineSearch, FIRE, NEB, AUTO-NEB, etc, and various molecular dynamics techniques, including thermostats (Langevin, CSVR, Nose-Hoover Chain, etc) and metadynamics (via the interface with Plumed). -Due to the growing number of softwares and machine-learning forcefields, we turn to maintain the interface with ASE by our own, while a legacy version of ASE interface can still be found at [our GitLab repository of ase-abacus](https://gitlab.com/1041176461/ase-abacus ). +Due to the growing number of softwares and machine-learning forcefields, the +ABACUS ASE interface is maintained as the independent +[`abacuslite`](https://github.com/QuantumMisaka/abacuslite) package. A legacy +version of the ASE interface can still be found at +[our GitLab repository of ase-abacus](https://gitlab.com/1041176461/ase-abacus). ## Installation @@ -15,13 +19,23 @@ conda create -n abacus python=3.10 conda activate abacus ``` -Then, install the ASE interface by: +Then, install the independently maintained ASE plugin: ```bash -cd interfaces/ASE_interface -pip install . +python -m pip install abacuslite ``` +`abacuslite` owns the Python calculator, ABACUS input/output readers and user +examples. The ABACUS repository keeps only the engine-side compatibility +harness in +[`interfaces/ASE_interface/tests`](https://github.com/deepmodeling/abacus-develop/tree/develop/interfaces/ASE_interface/tests). +ABACUS CI installs the published plugin, or a reviewed candidate ref, and runs +that harness against the executable built from the current source. Manual CI +dispatch can select the candidate ref. + +The plugin is not vendored and is not a git submodule, so GitHub source +archives remain complete. + ## ABACUS Calculator Present calculator implementation requires a "profile" to act as an interface between the Python runtime and the file system. @@ -99,9 +113,34 @@ with tempfile.TemporaryDirectory() as tmpdir: ## Perform Calculations -In the new implementation, we limit the range of functionalties supported to mainly include the necessary ones, such as the SCF calculation, the energy and force/stress evaluation. The other features, such as starting the molecule dynamics directly in ABACUS from Python, is not supported anymore. Instead, it is encouraged to use the ASE tools to perform the molecule dynamics. - -Please read the examples in `interfaces/ASE_interface/examples/` for more details. +### Parser and API contract + +The [abacuslite public API contract](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/public-api.md) +defines supported calculator and I/O entry points, result units, atom ordering, +missing-value behavior and compatibility rules. Use the documented readers +for completed output and the calculator/ASE API for execution; no ABACUS-side +parser wrapper is required. Pure file I/O does not need an ABACUS executable. + +ABACUS owns its output formats and real-engine compatibility tests; abacuslite +owns parsing and ASE result assembly. An engine format change must be checked +against the installed plugin using independent raw-output assertions. +Internal parser helpers, template flags and ASE server process handles are +not external integration interfaces. + +In the new implementation, the supported surface is the one the external plugin +exposes: SCF calculations, energy and force/stress evaluation, ASE-driven +relaxation and molecular dynamics through the ASE optimizers and integrators, +and ABACUS native MD, in which the engine runs its own MD integrator and the +plugin reads the resulting trajectory. Both molecular-dynamics paths are +validated rather than assumed: the engine-side harness runs the ASE-driven +Langevin case (`md.py`) and the native-MD case (`native_md.py`) as required +compatibility cases in both the develop and the legacy output-format lanes. +Calculation types the plugin does not expose remain outside the supported +surface, and for simulations whose integrator you control the ASE tools remain +the recommended path. + +See the [abacuslite examples](https://github.com/QuantumMisaka/abacuslite/tree/main/examples) +for complete scripts. ### Socket I/O with ASE @@ -148,11 +187,10 @@ With the ABACUS toolchain workflow, build the normal ABACUS executable with LCAO For PW calculations on CUDA/ROCm with multiple MPI ranks, use a k-point layout compatible with ABACUS' GPU parallelization. In practice, make sure each k-point pool contains one MPI rank; for example, a 4-rank PW GPU socket calculation should use at least four k-points so the default GPU `kpar` adjustment can assign one rank per pool. A one-k-point PW GPU job with several MPI ranks can fail in the PW GPU transform path; reduce the rank count or use a denser k-point mesh such as a smaller `kspacing`. -The ASE interface can be installed from this repository with: +The ASE interface is installed from PyPI: ```bash -cd interfaces/ASE_interface -pip install . +python -m pip install abacuslite ``` A minimal socket calculator setup is: @@ -193,13 +231,28 @@ Calling `atoms.get_potential_energy()` does not force a force or stress calculat The ABACUS metadata extension is required to expose force/stress presence safely. If a legacy client returns an empty extras field, the adapter accepts only an energy-only response and refuses to infer forces or stress from the fixed-wire padding. Generic i-PI/ASE clients that ignore ABACUS extras cannot distinguish mandatory padding from a computed zero; use `AbacusSocketIO` or another metadata-aware client when requesting optional properties. When launching ABACUS with a generic client, explicitly set `cal_force=1` for force-driven workflows and `cal_stress=1` for stress evaluation; an omitted switch defaults to disabled. Such clients also need their own policy for unconverged SCF results. +#### Socket protocol compatibility + +The socket metadata extension uses the schema `abacus.socket.properties.v1`. +The `present` list distinguishes computed properties from protocol padding, and +`scf_converged` reports the SCF state for the returned frame. Additive metadata +fields may be added within v1; a breaking change requires a new schema version +and a transition period in both ABACUS and abacuslite. + +The ASE-side adapter contract is documented in the +[abacuslite socket contract](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/socket-contract.md). + +Engine and adapter changes that alter this contract should be linked and tested +together. + A socket calculator owns one ABACUS process initialized from one fixed `INPUT`/`STRU` setup. Reuse the same `AbacusSocketIO` instance only for position updates under the same electronic-structure settings and the same cell. Do not change `kpts`, `kspacing`, `nspin`, `basis_type`, `basissets`, pseudopotentials, species, atom count, cell, or other core `INPUT`/`STRU` parameters through an existing socket calculator; create a new `AbacusSocketIO` instance and a new ABACUS client process for those changes. `AbacusSocketIO` rejects cell changes before sending them to ABACUS, and the ABACUS socket driver also checks incoming POSDATA cells against the initial `STRU` cell and exits if they differ. In socket mode, ABACUS keeps one client process alive. All SCF evaluations produced by the same `AbacusSocketIO` instance are appended to the same `OUT.ABACUS/running_scf.log`, because the ABACUS calculation type remains `scf`. The authoritative per-step energy and force results are returned through the i-PI socket to ASE. Use ASE trajectory and optimizer log files, such as `BFGS(atoms, trajectory="opt.traj", logfile="opt.log")`, when each optimizer or MD step should be saved separately. Treat `running_scf.log` mainly as the ABACUS diagnostic log for the socket client, not as one independent FileIO result per structure. The i-PI protocol does not transmit element symbols. `AbacusSocketIO` therefore sorts the internal socket atoms with the same first-occurrence species grouping used when writing `STRU`, and maps returned forces back to the original ASE `Atoms` order. This avoids silent force/atom mismatches when structures are read from CIF, extxyz, POSCAR, or other formats whose atom order is not already grouped for ABACUS. Users should not manually reorder atoms for socket I/O; pass the physical ASE `Atoms` object directly to the calculator. -A complete fixed-cell validation and benchmark example is available in `interfaces/ASE_interface/examples/socketio.py`. +A complete fixed-cell validation example is available in +[`abacuslite/examples/socketio.py`](https://github.com/QuantumMisaka/abacuslite/blob/main/examples/socketio.py). ## SPAP Analysis diff --git a/interfaces/ASE_interface/DEVELOP.md b/interfaces/ASE_interface/DEVELOP.md deleted file mode 100644 index acb1f1384b4..00000000000 --- a/interfaces/ASE_interface/DEVELOP.md +++ /dev/null @@ -1,90 +0,0 @@ -# Developer Guide - -Abacuslite has the following file structure: -``` -. -├── __init__.py -├── core.py -├── io -│ ├── __init__.py -│ ├── generalio.py -│ ├── latestio.py -│ ├── legacyio.py -│ └── testfiles -├── utils -│ ├── __init__.py -│ └── ksampling.py -└── xtest.sh -``` - -## core.py - -This file contains the implementation of the Atomic Simulation Environment (ASE) calculator. - -## io - -This directory contains the input/output (I/O) functions for extracting information from ABACUS dumped files. A summary is here: - -### Long-term-support (LTS) version - -|Item|Regular expression|Example| -|----|----|----| -|esolver_type|'The esolver type has been set to : (\S+)'|`The esolver type has been set to : ksdft_pw`| -|nspin|'nspin\s+=\s+(\d+)'|`nspin = 4`| -|number of bands|'NBANDS\s+=\s+(\d+)'|`NBANDS = 40`| -|number of atoms|'TOTAL ATOM NUMBER\s*=\s*(\d+)'|`TOTAL ATOM NUMBER = 2`| -|lattice constant|'lattice constant \(Angstrom\)\s*=\s*(-?\d+(\.\d+)?)'|`lattice constant (Angstrom) = 0.529177`| -|lattice vectors|'^Lattice vectors: \(Cartesian coordinate: in unit of a_0\)$'|` Lattice vectors: (Cartesian coordinate: in unit of a_0)`| -|coordinate system|'^(DIRECT\|CARTESIAN) COORDINATES'|`DIRECT COORDINATES`| -|atomic positions|'^tau[c\|d]_([A-Z][a-z]?)\d+\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)*'|`taud_As1 0.2500000000 0.2500000000 0.2500000000 1.7321 0.0000000000 0.0000000000 0.0000000000`| -|eigenvalues|'\d+/\d+\s+kpoint\s+\(Cartesian\)\s+=\s+(-?\d(\.\d+)?(e-\d+)?)\s+(-?\d(\.\d+)?(e-\d+)?)\s+(-?\d(\.\d+)?(e-\d\|+)?)\s+\(\d+\s+pws\)'|` 1/1 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (230 pws)`| -|atomic forces|'\s\*TOTAL\-FORCE\s\*\(eV\s*/Angstrom\)'|` TOTAL-FORCE (eV/Angstrom) `| -|total stress|'\s\*TOTAL\-STRESS\s\*\(KBAR\)'|` TOTAL-STRESS (KBAR) `| -|kpoints and weights|'\s\*(IBZ\|KPOINTS)\s+(DIRECT\|CARTESIAN)_X\s+(DIRECT\|CARTESIAN)_Y\s+(DIRECT\|CARTESIAN)_Z\s+WEIGHT(\s+ibz2bz)?'|` KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT`| -|energy|'\s*ENERGY\s+Rydberg\s+eV'|` Energy Rydberg eV `| -|total magnetism|'\s\*Total\sMagnetism\s\(uB\)(\s+x\s+y\s+z)?\s\*'|` Total Magnetism (uB) `| - -### Latest version - -|Item|Regular expression|Example|Notes| -|----|----|----|----| -|esolver_type|'#ENERGY SOLVER#\s+(\S+)'|` #ENERGY SOLVER# ksdft_pw`|| -|nspin|'nspin\s+=\s+(\d+)'|`nspin = 4`|| -|number of bands|||has been removed| -|number of atoms|'TOTAL ATOM NUMBER\s*=\s*(\d+)'|`TOTAL ATOM NUMBER = 2`|| -|lattice constant|'lattice constant \(Angstrom\)\s*=\s*(-?\d+(\.\d+)?)'|`lattice constant (Angstrom) = 0.529177`|| -|lattice vectors|'^Lattice vectors: \(Cartesian coordinate: in unit of a_0\)$'|` Lattice vectors: (Cartesian coordinate: in unit of a_0)`|| -|coordinate system|'^(DIRECT\|CARTESIAN) COORDINATES'|`DIRECT COORDINATES`|| -|atomic positions|'atom\s+x\s+y\s+z\s+mag‘|` atom x y z mag`|"tauc/taud" suffix has been removed, therefore the way to read the coordinates changes to find the head of the table, then read the following `number of atoms` lines. On the other hand, if the `calculation` is set to `md`, ABACUS will not dump the atomic positions to running log anymore, instead, will read from the MD_dump file| -|eigenvalues|'spin=(\d)\s+k-point=(\d+)/(\d+)\s+Cartesian=\s*(-?\d(\.\d+)?(e-\d+)?)\s+(-?\d(\.\d+)?(e-\d+)?)\s+(-?\d(\.\d\|+)?(e-\d+)?)\s+\(\d+\s+plane wave\)'| spin=1 k-point=1/1 Cartesian=0.0000000 0.0000000 0.0000000 (1837 plane wave)|eigenvalues information has been removed from the running log, the file istate.info is renamed as eig_occ.txt, where the eigenvalues are read| -|atomic forces|'#\s\*TOTAL\-FORCE\s*\(eV\s*/Angstrom\)\s\*#'|` #TOTAL-FORCE (eV/Angstrom)#`|| -|total stress|'#\s\*TOTAL\-STRESS\s*\(kbar\)\s\*#'|` #TOTAL-STRESS (kbar)# `|| -|kpoints and weights|'\s*(IBZ\|KPOINTS)\s+(DIRECT\|CARTESIAN)_X\s+(DIRECT\|CARTESIAN)_Y\s+(DIRECT\|CARTESIAN)_Z\s+WEIGHT(\s+ibz2bz)?'|` KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT`|| -|energy|'\s*ENERGY\s+Rydberg\s+eV'|` Energy Rydberg eV `|| -|total magnetism|'\s\*Total\sMagnetism\s\(uB\)(\s+x\s+y\s+z)?\s\*'|` Total Magnetism (uB) `|| - -Please look at detailed implementations in the following files. - -### generalio.py - -This module contains the general I/O functions for ABACUS. - -### latestio.py - -This module contains the I/O functions for the latest version of ABACUS. - -### legacyio.py - -This module contains the I/O functions for the Long-Term-Support (LTS) version of ABACUS. - -## utils - -This directory contains the utility modules for Abacuslite. - -### ksampling.py - -This module contains the wrapper of k-point sampling functions and helper functions. - -## xtest.sh - -This script can run all the unittests that programmed in all Python source files. \ No newline at end of file diff --git a/interfaces/ASE_interface/README.md b/interfaces/ASE_interface/README.md index 543d4774836..992c1979569 100644 --- a/interfaces/ASE_interface/README.md +++ b/interfaces/ASE_interface/README.md @@ -1,61 +1,120 @@ -# abacuslite +# ABACUS ASE interface -## Introduction +[`abacuslite`](https://github.com/QuantumMisaka/abacuslite) is the +independently installed ASE calculator plugin for ABACUS. Install it from +PyPI: -abacuslite is a lightweight plugin for ABACUS (Atomic-orbital Based Ab-initio Computation at UStc), implementing the ASE (Atomic Simulation Environment) calculator interface. +```bash +python -m pip install abacuslite +``` -### Key Features +The plugin owns the Python calculator, ABACUS input/output readers, examples +and user documentation. This directory contains only the engine-side +compatibility harness used by ABACUS CI; it does not ship a second copy of the +plugin. -- **Lightweight Design**: Implemented as a plugin, no need to modify ASE core code -- **Version Compatibility**: Supports ASE versions satisfying the package requirement `ase>=3.22` -- **ASE Integration**: Uses ASE as the running platform, making ABACUS a callable calculator within it -- **Function Support**: Provides SCF-based energy, force, and stress evaluations through ASE. ASE can use these evaluations for relaxation, molecular dynamics, NEB, band-structure, and density-of-states workflows. -- **Socket Support**: `AbacusSocketIO` provides fixed-cell i-PI socket calculations, with energy always available and forces/stress enabled independently when requested. +## Public interface and parser ownership -## Installation +The plugin's [public API contract](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/public-api.md) +defines supported imports, result units/shapes, missing values and compatibility +rules. That is the canonical specification; this directory does not duplicate +it or add a forwarding parser. -Install the plugin from the ASE interface directory: +Use `Abacus`, `AbacusProfile`, `AbacusSocketIO` and the documented explicit I/O +functions. Tests use explicit curated pseudopotential/orbital filenames and +the socket's public `close()` lifecycle. They do not inspect template reader +flags, use asset-discovery heuristics, manipulate server process handles or +modify ASE's global magnetic-property definitions. -```bash -cd interfaces/ASE_interface -pip install . -``` +Report parser/calculator defects to abacuslite, engine/output defects to ABACUS, +and launcher/workflow policy defects to the consuming project. Link both sides +when a change affects an output format or socket protocol. The +[maintenance options](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/maintenance-options.md) +are a community discussion proposal; the repository hosting and maintenance +appointments remain undecided. -## Usage Examples +`abacuslite` is not vendored and is not a git submodule. Downloading the ABACUS +source archive therefore keeps its engine tree complete; the plugin is resolved +as a normal Python dependency. -Please refer to the example scripts in the `examples` folder. Recommended learning path: +## What this harness checks -1. **scf.py** - Basic SCF calculation example -2. **relax.py** - Atomic position relaxation calculation -3. **cellrelax.py** - Cell parameter relaxation calculation -4. **bandstructure.py** - Band structure calculation -5. **dos.py** - Density of states calculation -6. **md.py** - Molecular dynamics simulation -7. **constraintmd.py** - Constrained molecular dynamics simulation -8. **metadynamics.py** - Metadynamics simulation -9. **neb.py** - Nudged Elastic Band (NEB) calculation -10. **soc.py** - Noncollinear spin-orbit coupling calculation -11. **socketio.py** - Fixed-cell ASE optimization with `AbacusSocketIO`, running ABACUS as an i-PI socket client +The harness builds one explicit ABACUS executable and runs the installed +`abacuslite` package against it. Required checks cover SCF, ASE relaxation and +MD, native MD, band structure, magnetic moments and fixed-cell socket handoff. +The develop lane uses the latest output format; the same runner accepts a +legacy-format executable for LTS compatibility. -The regular `Abacus` calculator runs one ABACUS calculation for each ASE property evaluation. ASE controls the relaxation, molecular-dynamics, and other workflow steps. The socket calculator reuses one ABACUS process for position updates, while the cell and electronic-structure settings remain fixed for that calculator instance. +Every case runs in a separate Python process and preserves its raw ABACUS +output. The runner verifies that imports come from the installed distribution, +that runtime files match the supplied wheel, and that no required case was +skipped or omitted. -## Authors +The v2 report records the engine's public version and the requested oracle +format, without claiming to inspect an internal reader selection. The current +lanes accept ABACUS 3.10/LTS and 3.11/develop respectively. Actual parser +compatibility is demonstrated by the real calculations and independent raw-file +assertions. Socket close is included in the whole-case process-group timeout; +timeout/failure remains a failing case with retained raw output. -- Yuyang Ji -- Zhenxiong Shen -- Yike Huang -- Zhaoqing Liu +## Run locally -## Acknowledgments +Build ABACUS, install a wheel in the Python environment that will run the +harness, and pass both explicitly: -Thanks to the ABACUS development team for their support and contributions. +```bash +python -m pip install /path/to/abacuslite-*.whl + +python interfaces/ASE_interface/tests/run_engine_compatibility.py \ + --engine /path/to/abacus \ + --mpi-command 'mpirun -np 2' \ + --assets /path/to/abacus-develop/tests/PP_ORB \ + --format latest \ + --wheel /path/to/abacuslite-*.whl \ + --report-dir /tmp/abacus-ase-engine-tests \ + --engine-ref "$(git rev-parse HEAD)" +``` -## License +Use `--format legacy` with the supported 3.10.x executable. The LTS engine has +no socket driver; that is a declared capability difference, not a skipped +required case. -The applicable license terms are provided in the repository [LICENSE](../../LICENSE). +For the offline harness contracts: -## Contact +```bash +cd interfaces/ASE_interface/tests +python -m unittest test_external_provenance test_harness_facts \ + test_native_md_fixture test_portable_runner -v +``` -If you have any questions or suggestions, please contact us through: +## CI and cross-repository changes + +The ASE workflow installs the published, tested `abacuslite` wheel (currently +1.2.2) by default, with its published wheel SHA256 checked before installation. +A manual `workflow_dispatch` run can build a reviewed +`abacuslite` ref for a coordinated change. The `abacuslite` repository +separately builds pinned ABACUS develop/LTS snapshots and runs the same engine +harness on a schedule. + +Version 1.2.2 contains the noncollinear result-projection fix required to run +these tests without an ASE registry patch. Its +[release record](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/superpowers/plans/2026-10-20-1.2.2-release.md) +identifies the published artifacts and develop/LTS evidence. Future paired +changes can use `plugin_ref` after the candidate commit is publicly available; +update the stable version and wheel hash together after its reviewed release. + +ABACUS owns the engine, output formats and socket wire protocol. `abacuslite` +owns the Python adapter and parser. Parser or calculator fixes belong in +`abacuslite`; engine format changes belong here and require a linked adapter +change. + +The socket metadata schema is `abacus.socket.properties.v1`. Additive fields +may be added within v1. A breaking change requires a new schema version and a +transition period in both repositories. See +[the socket contract](https://github.com/QuantumMisaka/abacuslite/blob/main/docs/socket-contract.md) +and [ABACUS ASE documentation](../../docs/advanced/interface/ase.md). + +## License -- GitHub: [deepmodeling/abacus-develop](https://github.com/deepmodeling/abacus-develop) +The applicable license terms are provided in the repository +[LICENSE](../../LICENSE). diff --git a/interfaces/ASE_interface/abacuslite/__init__.py b/interfaces/ASE_interface/abacuslite/__init__.py deleted file mode 100644 index fe361f81bad..00000000000 --- a/interfaces/ASE_interface/abacuslite/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -''' -interfaces to Atomic-orbital Based Ab-initio Computation at UStc (ABACUS), -for more information about this DFT calculator, -please refer to the Github official repository: -https://github.com/deepmodeling/abacus-develop -and online-manual: -https://abacus.deepmodeling.com/en/latest/index.html - -For a more complete ABACUS pre-/post-processing workflow package, -please refer to the ABACUSTest package: -https://github.com/pxlxingliang/abacus-test -''' -from .core import * \ No newline at end of file diff --git a/interfaces/ASE_interface/abacuslite/core.py b/interfaces/ASE_interface/abacuslite/core.py deleted file mode 100644 index 9d23159ea25..00000000000 --- a/interfaces/ASE_interface/abacuslite/core.py +++ /dev/null @@ -1,1262 +0,0 @@ -# fmt: off - -''' -here the ase-abacus implementation is pasted and modified. -Source: -https://gitlab.com/1041176461/ase-abacus/-/blob/master/ase/calculators/abacus.py - -This module defines an ASE interface to ABACUS. -Created on Fri Jun 8 16:33:38 2018 - -ABACUS (Atomic-orbital Based Ab-initio Computation at UStc) is an open-source -package based on density functional theory (DFT). The package utilizes both plane -wave and numerical atomic basis sets with the usage of pseudopotentials to describe -the interactions between nuclear ions and valence electrons. ABACUS supports LDA, -GGA, meta-GGA, and hybrid functionals. Apart from single-point calculations, -the package allows geometry optimizations and ab-initio molecular dynamics with -various ensembles. The package also provides a variety of advanced functionalities -for simulating materials, including the DFT+U, VdW corrections, and implicit solvation -model, etc. In addition, ABACUS strives to provide a general infrastructure to -facilitate the developments and applications of novel machine-learning-assisted -DFT methods (DeePKS, DP-GEN, DeepH, DeePTB etc.) in molecular and material simulations. - -Modified on Wed Jun 20 15:00:00 2018 -@author: Shen Zhen-Xiong - -Modified on Wed Jun 03 23:00:00 2022 -@author: Ji Yu-yang - -Refactored from Sun Dec 07 21:41 2025 -@author: Huang Yi-ke -''' - -import json -import os -import re -import shutil -import tempfile -import unittest -from pathlib import Path -from typing import Dict, Optional, List - -import numpy as np -from ase.calculators.genericfileio import ( - BaseProfile, - CalculatorTemplate, - GenericFileIOCalculator, - read_stdout -) -from ase.calculators.socketio import SocketIOCalculator -from ase.atoms import Atoms -from ase.dft.kpoints import BandPath -from ase.io import read - -from abacuslite.io.generalio import ( - file_safe_backup, - read_input, - read_stru, - read_kpt, - species_group_indices, - write_input, - write_stru, - write_kpt -) - -__LEGACYIO__ = True -def switch_io_backend_version(version: str) -> bool: - '''determine if the i/o is in legacy format by the version number, - for detailed discussion, see issue #7260 - ''' - global __LEGACYIO__ - m = re.match(r'^v(\d+)\.(\d+)\.(\d+)(\.\d+|\-(alpha|beta|rc)\.\d+|\-(alpha|beta|rc)\d+)?$', version) - assert m, f'Invalid format of version number, please check file version.h' - assert int(m.group(1)) >= 3, f'ABACUS v2.x is not supported' - if int(m.group(2)) >= 11: - __LEGACYIO__ = False - elif int(m.group(2)) == 9: - if m.group(4) is not None: - # it is also possible to divide the version more carefully, - # but because 3.9.0.x are all on the develop branch, up to - # now, there is no user submit issue to request such a careful - # division - __LEGACYIO__ = False - else: - __LEGACYIO__ = True - return __LEGACYIO__ - -class AbacusProfile(BaseProfile): - '''AbacusProfile for interacting the ASE with ABACUS that installed in - the practical system''' - configvars = {'pseudo_dir', 'orbital_dir'} - - def __init__(self, - command: str, - pseudo_dir: Optional[str | Path] = None, - orbital_dir: Optional[str | Path] = None, - omp_num_threads: Optional[int] = None, - **kwargs): - '''Initialize ABACUS profile. - - Parameters - ---------- - command : str - The command to run ABACUS. NOTE: there may be the case for some - sophisticated ABACUS user they call ABACUS with command like - `OMP_NUM_THREADS=1 mpirun -np X abacus`. Here please do not set - the number of omp threads in `command`, instead, use `nomp=1`. - pseudo_dir : str or Path, optional - The directory containing pseudopotential files. - orbital_dir : str or Path, optional - The directory containing orbital basis files. This is only necessary - for an ABACUS-LCAO calculation - omp_num_threads : int, optional - The number of omp threads to use. - ''' - assert isinstance(command, str) - # further validation on the command will be in the __init__ of - # the base class - super().__init__(command, **kwargs) - self.pseudo_dir = pseudo_dir - self.orbital_dir = orbital_dir - - if omp_num_threads is not None: - # set the number of omp threads for the present process - assert isinstance(omp_num_threads, int) - os.environ['OMP_NUM_THREADS'] = str(omp_num_threads) - - @staticmethod - def parse_version(stdout) -> str: - # MPI launchers may add informational lines before ABACUS output. - match = re.search(r'ABACUS version (\S+)', stdout or '') - if match is None: - raise RuntimeError( - 'Could not parse ABACUS version from command output. ' - 'Expected a line like "ABACUS version vX.Y.Z".' - ) - return match.group(1) - - def get_calculator_command(self, inputfile) -> List[str]: - # because ABACUS run in the folder where there are INPUT files, so the - # additional inputfile argument is not used. - return [] - - def socketio_argv_inet(self, port: Optional[int] = None) -> List[str]: - port = 31415 if port is None else port - return [ - 'env', - f'ABACUS_SOCKET_ADDRESS=localhost:{port}', - *self._split_command, - ] - - def socketio_argv_unix(self, socket: str) -> List[str]: - return [ - 'env', - f'ABACUS_SOCKET_ADDRESS=/tmp/ipi_{socket}:UNIX', - *self._split_command, - ] - - def version(self) -> str: - '''get the abacus version information''' - cmd_ = [*self._split_command, '--version'] - return AbacusProfile.parse_version(read_stdout(cmd_)) - -class AbacusTemplate(CalculatorTemplate): - - implemented_properties = [ - 'energy', 'forces', 'stress', 'free_energy', 'magmom' - ] - _label = 'abacus' - - def __init__(self): - super().__init__( - 'abacus', - self.implemented_properties - ) - self.non_convergence_ok = False - # the redirect stdout and stderr - self.inputname = 'INPUT' # hard-coded - self.outputname = f'{self._label}.out' - self.errorname = f'{self._label}.err' - - # fix: inconsistent atoms order may induce bugs, here a list - # is kept to swap the order of atoms - self.atomorder = None - - '''because it may be not one-to-one mapping between the property - desired to calculate and the keywords used in the calculation, - in the following a series of functions for mapping the property - calculation to the keywords settings are implemented''' - @staticmethod - def get_energy_keywords(self) -> Dict[str, str]: - return {} - - @staticmethod - def get_forces_keywords(self) -> Dict[str, str]: - return {'cal_force': '1'} - - @staticmethod - def get_stress_keywords(self) -> Dict[str, str]: - return {'cal_stress': '1'} - - @staticmethod - def get_free_energy_keywords(self) -> Dict[str, str]: - return {} - - @staticmethod - def get_magmom_keywords(self) -> Dict[str, str]: - return {'nspin': '2'} - - def get_property_keywords(self, - parameters: Dict[str, str], - properties: List[str]) -> Dict[str, str]: - '''Connect the relationship between the properties calculation and - the ABACUS keywords. May be more complicated in the future, therefore - it is better to have a separate mapping function instead of - implementing in some other functions. - - Parameters - ---------- - parameters : dict - The parameters used to perform the calculation. - properties : list of str - The list of properties to calculate - ''' - def keyword_compare_value(value): - if isinstance(value, bool): - return '1' if value else '0' - if isinstance(value, (list, tuple, set)): - return ' '.join(str(i) for i in value) - return str(value) - - param_cache_ = { - key: keyword_compare_value(value) - for key, value in parameters.items() - if value is not None - } - - def counter(param_new: Dict[str, str]) -> Dict[str, str]: - info = 'desired properties or explicit parameters required contradictory keywords' - staged = {} - for k, v in param_new.items(): - if v is None: - continue - normalized_value = keyword_compare_value(v) - if k in param_cache_ and param_cache_[k] != normalized_value: - raise ValueError(f'{info}: {k}={v} (now), {param_cache_[k]} (before)') - staged[k] = normalized_value - param_cache_.update(staged) - return param_new - - # update the parameters with the keywords for the properties - for p in properties: - assert p in self.implemented_properties - parameters.update(counter(getattr(self, f'get_{p}_keywords')(parameters))) - - # from the parameters, get the file path - self.suffix = parameters.get('suffix', 'ABACUS') - self.calculation = parameters.get('calculation', 'scf') - # with the above two, the running log file can be positioned. - return parameters - - def write_input(self, - profile: AbacusProfile, - directory: Path | str, - atoms: Atoms, - parameters: Dict[str, str], - properties: List[str]) -> None: - '''Write the input files for the calculation. This function connects - the calculation in ASE language (atoms, properties, assisted by the - parameters) to the input files of ABACUS. - - Parameters - ---------- - profile : AbacusProfile - The profile used to perform the calculation. - directory : Path - The working directory to store the input files. - atoms : Atoms - The atoms object to perform the calculation on. Because - parameters: dict - The parameters used to perform the calculation. - properties: list of str - The list of properties to calculate - ''' - # directory - directory = Path(directory) - directory.mkdir(exist_ok=True, parents=True) - - # copy the `parameters` because later we will modify it - parameters = parameters.copy() - - # STRU - _ = file_safe_backup(directory / parameters.get('stru_file', 'STRU')) - # group atoms by first-occurrence species order. Keep the reverse map - # so that we will recover the order in function read_results() - ind = species_group_indices(atoms.get_chemical_symbols()) - self.atomorder = sorted(range(len(atoms)), key=lambda i: ind[i]) # revmap - # then we write - _ = write_stru(atoms[ind], - outdir=directory, - pp_file=parameters.get('pseudopotentials'), - orb_file=parameters.get('basissets'), - fname=parameters.get('stru_file', 'STRU')) - - # KPT, if needed - if 'kpts' in parameters: - _ = file_safe_backup(directory / parameters.get('kpoint_file', 'KPT')) - _ = write_kpt(parameters['kpts'], - directory / parameters.get('kpoint_file', 'KPT')) - # should this function be responsible for checking the integrity - # of information provided by the user? There may be the case that - # user provides incomplete information, such that the ABACUS cannot - # run with parameters. - - # INPUT - # after writing the KPT and STRU, delete them from the parameters - _ = parameters.pop('kpts', None) - - _ = parameters.pop('pseudopotentials', None) - parameters.update({'pseudo_dir': profile.pseudo_dir}) - - _ = parameters.pop('basissets', None) - parameters.update({'orbital_dir': profile.orbital_dir}) - # update the parameters respect to the properties desired - parameters = self.get_property_keywords(parameters, properties) - # postprocess on the parameters: convert the key and values - # from any to string. For the case where the value is a - # array, convert to the string spaced by whitespace - for k, v in parameters.items(): - # if the v is iterable, convert to the string spaced by whitespace - if isinstance(v, (list, tuple, set)): - parameters[k] = ' '.join(str(i) for i in v) - dst = directory / self.inputname - _ = file_safe_backup(dst) - # remove possible key-value pairs whose value is None - parameters = {k: v for k, v in parameters.items() if v is not None} - - # FIXME: only support the ksdft esolver_type presently - if parameters.get('esolver_type', 'ksdft') != 'ksdft': - raise NotImplementedError( - 'ABACUS Lite only supports the ksdft esolver_type presently, ' - 'which means the ABACUS should always be used as a DFT ' - 'calculator. For other forcefields that ABACUS supports ' - 'such as the LJ, DP, etc., please either use the ABACUS ' - 'directly, or the implementation of interfaces to ASE ' - 'directly.' - ) - - # write the INPUT file to the target directory - _ = write_input(parameters, dst) - - def execute(self, - directory: Path | str, - profile: AbacusProfile): - '''Execute the ABACUS Lite calculation. - - Parameters - ---------- - directory : Path or str - The working directory to store the input files. - profile : AbacusProfile - The profile used to perform the calculation. - - Raises - ------ - SubprocessError - If the ABACUS Lite calculation fails. - ''' - from subprocess import SubprocessError - try: - profile.run(directory=directory, - inputfile=None, - outputfile=self.outputname, - errorfile=self.errorname) - except SubprocessError: - # Surface only the tails of each log: abacus.out already contains - # the SCF iteration table, so full stdout/stderr plus 200 lines of - # running log would make the exception message unreadably long. - # The run dir is preserved separately by the test harness - # (ASE_ABACUS_KEEP_DIR) when post-mortem of the full logs is - # needed. - def tail(p: Path, n: int) -> str: - if not p.exists(): - return '' - lines = p.read_text(errors='replace').splitlines() - header = f'=== {p.name} (last {min(n, len(lines))} lines) ===' - return header + '\n' + '\n'.join(lines[-n:]) - - directory = Path(directory) - message = ['ABACUS Lite calculation failed'] - for fn, n in ((self.outputname, 200), - (self.errorname, 200)): - chunk = tail(directory / fn, n) - if chunk: - message.append(chunk) - # The tail of the SCF running log holds the per-step drho - # history, usually the key evidence for issues like #7794 - # ("Can't find even an electron"). - scf_log = (directory / f'OUT.{self.suffix}' - / f'running_{self.calculation}.log') - chunk = tail(scf_log, 100) - if chunk: - message.append(chunk) - raise SubprocessError('\n'.join(message)) - - def read_results(self, directory) -> Dict: - '''the function that returns the desired properties in dict''' - read_abacus_out = lambda fn: None - global __LEGACYIO__ - if __LEGACYIO__: - from abacuslite.io.legacyio import read_abacus_out - else: - from abacuslite.io.latestio import read_abacus_out - - outdir = directory / f'OUT.{self.suffix}' - # only the last frame - atoms: Optional[Atoms] = read_abacus_out( - outdir / f'running_{self.calculation}.log', - sort_atoms_with=self.atomorder)[-1] - assert atoms is not None - - return dict(atoms.calc.properties()) - - def load_profile(self, cfg, **kwargs): - return AbacusProfile.from_config(cfg, self.name, **kwargs) - -class Abacus(GenericFileIOCalculator): - def __init__(self, - profile=None, - directory='.', - **kwargs): - '''Construct the ABACUS calculator. - - The keyword arguments (kwargs) can be one of the ASE standard - keywords: 'xc', 'kpts' or any of ABACUS' - native keywords. - - Parameters - ---------- - profile: AbacusProfile - the interface that interacts with the ABACUS executable. - directory: str or Path - the working directory to store the input files. - pseudopotentials: dict - A mapping from the element to the pseudopotential file name, - e.g. ``{'O': 'O_ONCV_PBE-1.0.upf', 'H': 'H.upf'}``. - baisssets: dict, optional - A mapping from the element to the ABACUS numerical atomic - orbital file name. This is necessary only when it is an - ABACUS-LCAO (Linear-Combination-of-Atomic-Orbitals) calculation - e.g. ``{'O': 'O_gga_10au_100Ry_2s2p1d.orb', - 'H': 'H_gga_10au_100Ry_2s1p.orb'}``. - kpts: dict - The k-points sampling should be given as a dict. For there - are many modes of k-sampling supported, the content may differ - in cases. A `mode` key should be used to specify the ksampling, - allowed modes are: `mp-sampling`, `line` and `point`. For - `mp-sampling` mode, `gamma-centered`, `nk` and `kshift` should - present. `gamma-centered` is a boolean, `nk` and `kshift` should - be lists of three integers. ... TBD - inp: dict - parameters setting in INPUT of ABACUS. NOTE: if there are settings - on the `pseudo_dir` and `orbital_dir`, these will overwrite the - value in the profile. If you do not expect this, please only use - the profile, because the profile stands for interfacing with the - ASE calculator instance with the computational environment. - - **kwargs: - Other parameters to be passed to the ABACUS calculator. - ''' - # not recommended :( - profile = AbacusProfile('abacus') if profile is None else profile - - # to be compatible with both the legacy and latest format of i/o, the - # switch is needed. - _ = switch_io_backend_version(profile.version()) - - # because ABACUS run job in folders, based on the assumption that - # there is only one job in the folder. Therefore once there are already - # files in the folder, will try to create a new one...(seriously?) - inp = kwargs.pop('inp', {}) - - super().__init__( - template=AbacusTemplate(), - profile=profile, - parameters=kwargs | inp, - directory=directory, - ) - - def write_input(self, atoms, properties=None, system_changes=None): - if properties is None: - properties = self.template.implemented_properties - self.template.write_input( - profile=self.profile, - directory=Path(self.directory), - atoms=atoms, - parameters=self.parameters, - properties=properties, - ) - - @classmethod - def restart(cls, profile=None, directory='.', **kwargs): - '''instantiate one ABACUS calculator from an existing job directory, - optionally overwrite some keywords''' - directory = Path(directory) - inp_read = read_input(directory / 'INPUT') - - pporb_read = read_stru(directory / 'STRU')['species'] - pseudopotentials = kwargs.get( - 'pseudopotentials', - {pporb['symbol']: pporb['pp_file'] for pporb in pporb_read} - ) - if 'pseudopotentials' in kwargs: - del kwargs['pseudopotentials'] - - basissets = kwargs.get( - 'basissets', - {pporb['symbol']: pporb.get('orb_file') for pporb in pporb_read} - ) - if 'basissets' in kwargs: - del kwargs['basissets'] - if all([forb is None for forb in basissets.values()]): - basissets = {} - assert all([forb is not None for forb in basissets.values()]) - - kpts = kwargs.get('kpts', read_kpt(directory / inp_read.get('kpoint_file', 'KPT'))) - if 'kpts' in kwargs: - del kwargs['kpts'] - - inp = inp_read | kwargs.get('inp', {}) - if 'inp' in kwargs: - del kwargs['inp'] - - return cls(profile=profile, - directory=directory, - pseudopotentials=pseudopotentials, - basissets=basissets, - kpts=kpts, - inp=inp, - **kwargs) - - def fixed_density(self, - kpts: BandPath | Dict[str, str | int | List[float]], - symmetry: str = 'off', - profile=None, - **kwargs) -> 'Abacus': - '''spawn a new ABACUS calculator with fixed density, based on the present - instance. This funcionality is mostly only useful when perform the - non-self-consistent calculations like band structure. - This interface is referred from the ASE document at: - https://ase-lib.org/gettingstarted/tut04_bulk/bulk.html#band-structure - , however, we also note that it is from the implementation of the - GPAW python, not the ASE official. - To make less development burden as possible, we use the same interface - as the GPAW python. - - Parameters - ---------- - kpts : BandPath | Dict[str, str | int | List[float]] - The k-point path to be calculated. Can be either a BandPath object - or a dictionary that contains the k-point information. For the latter - case, see tbgen/calculators/abacus/generalio.py::write_kpt for more - details. - symmetry : str, optional - The symmetry mode to be used. Default is 'off'. Now only the `off` - mode is supported. - profile : AbacusProfile, optional - The profile to be used. Default is None. If None, the profile of - the present instance will be used. - **kwargs : dict - Other parameters to be passed to the ABACUS calculator. - - Returns - ------- - Abacus - The new ABACUS calculator instance that can perform the nscf calculation - tasks - ''' - # we should overwrite the 'calculation' to 'nscf', and 'init_chg' to 'file' - assert symmetry == 'off' - - kwargs.setdefault('inp', {}).update({'calculation': 'nscf', - 'init_chg': 'file', - 'symmetry': 0, - 'out_band': 1, - 'kspacing': 0.0, # overwrite - 'gamma_only': False, - 'read_file_dir': 'OUT.ABACUS'}) # overwrite - - profile = self.profile if profile is None else profile - - # get the kpoint coordinates - if isinstance(kpts, BandPath): - kwargs['kpts'] = { - 'mode': 'point', - 'nk': len(kpts.kpts), - 'nkinterpl': np.ones(len(kpts.kpts), dtype=int).tolist(), - 'coordinate': 'direct', - 'kpoints': kpts.kpts.tolist(), - } - else: - assert isinstance(kpts, dict) - kwargs['kpts'] = kpts - - # return - return Abacus.restart(profile=profile, - directory=self.directory, - **kwargs) - - def band_structure(self, efermi=None): - '''get the band structure from ABACUS. - (now not only GPAW can calculate the band structure ;) )''' - from ase.spectrum.band_structure import get_band_structure - return get_band_structure(calc=self, reference=efermi) - -class AbacusSocketIO(SocketIOCalculator): - """ASE socket I/O calculator that launches ABACUS as an i-PI client. - - A socket calculator owns one ABACUS process with one fixed INPUT/STRU - setup. The i-PI protocol can update positions, but electronic-structure - parameters such as k-points, spin, basis, pseudopotentials, and species - require a new calculator instance. Energy, forces, and stress are - independently controlled by ABACUS INPUT. The fixed-layout i-PI response - uses zero padding for absent fields and an extras metadata record so - padding is never exposed as a computed property. - """ - - def __init__(self, - profile=None, - directory='.', - port=None, - unixsocket=None, - timeout=None, - log=None, - **kwargs): - inp = dict(kwargs.pop('inp', {})) - self._property_constraints = {} - for keyword, property_name in (('cal_force', 'forces'), - ('cal_stress', 'stress')): - if keyword in inp: - self._property_constraints[property_name] = self._input_bool( - inp[keyword], keyword) - self.implemented_properties = [ - 'energy', 'free_energy', 'forces', 'stress'] - self._active_properties = None - self._last_socket_metadata = None - self.last_scf_converged = None - inp = self._socket_inp(inp) - self.abacus = Abacus( - profile=profile, - directory=directory, - inp=inp, - **kwargs, - ) - self._reference_cell = None - super().__init__( - port=port, - unixsocket=unixsocket, - timeout=timeout, - log=log, - launch_client=self._launch_client, - ) - - def calculate(self, atoms=None, properties=None, system_changes=None): - from ase.calculators.calculator import ( - PropertyNotImplementedError, - all_changes, - ) - from ase.stress import full_3x3_to_voigt_6_stress - - # A failed new request must not expose results or convergence from the - # previous geometry. Publish only a completely validated response. - self.results = {} - self.last_scf_converged = None - self._last_socket_metadata = None - if system_changes is None: - system_changes = all_changes - if atoms is None: - atoms = self.atoms - if atoms is None: - raise ValueError('AbacusSocketIO.calculate requires atoms') - - requested = self._normalize_socket_properties(properties) - self._check_requested_properties(requested) - - bad = [change for change in system_changes - if change not in self.supported_changes] - if self.atoms is not None and any(bad): - raise PropertyNotImplementedError( - 'Cannot change {} through IPI protocol. ' - 'Please create new socket calculator.' - .format(bad if len(bad) > 1 else bad[0])) - - desired = set(requested) - desired.discard('free_energy') - desired.add('energy') - for property_name, enabled in self._property_constraints.items(): - if enabled: - desired.add(property_name) - active = set(self._active_properties or ()) - if not active: - active.update(desired) - elif not desired.issubset(active): - active.update(desired) - if self.server is not None: - self._close_socket_session() - self._active_properties = tuple( - name for name in ('energy', 'forces', 'stress') if name in active) - - self._check_fixed_cell(atoms) - order = self._socket_sort_indices(atoms) - socket_atoms = atoms[order] - self.atoms = atoms.copy() - - if self.server is None: - self.server = self.launch_server() - proc = self.launch_client(socket_atoms, list(self._active_properties), - port=self._port, - unixsocket=self._unixsocket) - self.server.proc = proc - - raw_results = self.server.calculate(socket_atoms) - if not isinstance(raw_results, dict): - raise ValueError('ABACUS socket server returned a non-mapping result') - results = dict(raw_results) - metadata = self._decode_socket_metadata(results.pop('morebytes', None)) - if metadata is None: - if set(self._active_properties) != {'energy'}: - raise ValueError( - 'ABACUS socket response omitted property metadata; refusing ' - 'to infer forces or stress from fixed-wire padding') - present = {'energy'} - converged = None - else: - present = set(metadata['present']) - converged = metadata['scf_converged'] - - if 'energy' not in present or 'energy' not in results: - raise ValueError('ABACUS socket response did not provide energy') - energy = float(results['energy']) - if not np.isfinite(energy): - raise ValueError('ABACUS socket energy is not finite') - free_energy = float(results.get('free_energy', energy)) - if not np.isfinite(free_energy): - raise ValueError('ABACUS socket free energy is not finite') - current = {'energy': energy, 'free_energy': free_energy} - - if 'forces' in present: - if 'forces' not in results: - raise ValueError( - 'ABACUS socket metadata advertises forces, but wire response omitted them') - forces = np.asarray(results['forces'], dtype=np.float64) - expected_shape = (len(socket_atoms), 3) - if forces.shape != expected_shape or not np.all(np.isfinite(forces)): - raise ValueError('ABACUS socket forces have invalid shape or values') - current['forces'] = self._forces_to_input_order(forces, order) - - if 'stress' in present: - virial = results.get('virial') - if virial is None: - raise ValueError( - 'ABACUS socket metadata advertises stress, but wire response omitted virial') - if self.atoms.cell.rank != 3 or not any(self.atoms.pbc): - raise PropertyNotImplementedError( - 'ABACUS socket stress requires a periodic rank-3 cell') - virial = np.asarray(virial, dtype=np.float64) - if virial.shape != (3, 3) or not np.all(np.isfinite(virial)): - raise ValueError('ABACUS socket virial is not a finite 3x3 matrix') - vol = float(atoms.get_volume()) - if not np.isfinite(vol) or vol <= 0.0: - raise ValueError('ABACUS socket stress requires a positive cell volume') - current['stress'] = -full_3x3_to_voigt_6_stress(virial) / vol - - missing = [name for name in requested if name not in current] - if missing: - raise PropertyNotImplementedError( - 'ABACUS socket response did not provide requested {}'.format( - ', '.join(missing))) - self.results = current - self._last_socket_metadata = metadata - self.last_scf_converged = converged - - def _check_fixed_cell(self, atoms): - from ase.calculators.calculator import PropertyNotImplementedError - - cell = atoms.cell.array.copy() - if self._reference_cell is None: - self._reference_cell = cell - return - max_delta = np.max(np.abs(cell - self._reference_cell)) - if max_delta > 1.0e-10: - raise PropertyNotImplementedError( - 'AbacusSocketIO is fixed-cell only; create a new socket ' - 'calculator for a changed cell, or use the normal Abacus ' - 'FileIO calculator for variable-cell workflows.' - ) - - def set(self, **kwargs): - if kwargs: - raise ValueError( - 'AbacusSocketIO input parameters are fixed after construction; ' - 'create a new AbacusSocketIO calculator to change k-points, ' - 'spin, basis, pseudopotentials, species, or other INPUT/STRU ' - 'settings.' - ) - return super().set(**kwargs) - - def _check_requested_properties(self, requested): - from ase.calculators.calculator import PropertyNotImplementedError - - constraints = self._property_constraints - keywords = {'forces': 'cal_force', 'stress': 'cal_stress'} - for property_name, keyword in keywords.items(): - if property_name in requested and constraints.get(property_name) is False: - raise PropertyNotImplementedError( - '{}=0 disables requested {}'.format(keyword, property_name)) - - @staticmethod - def _normalize_socket_properties(properties): - from ase.calculators.calculator import PropertyNotImplementedError - - if properties is None: - names = ['energy'] - elif isinstance(properties, str): - names = [properties] - else: - names = list(properties) - if not names: - names = ['energy'] - allowed = {'energy', 'free_energy', 'forces', 'stress'} - unknown = [name for name in names if name not in allowed] - if unknown: - raise PropertyNotImplementedError( - 'ABACUS socket does not implement {}'.format(', '.join(unknown))) - return tuple(dict.fromkeys(names)) - - def _close_socket_session(self): - server = getattr(self, 'server', None) - if server is not None: - close = getattr(server, 'close', None) - if callable(close): - close() - self.server = None - self.results = {} - - @staticmethod - def _decode_socket_metadata(raw): - if raw is None: - return None - if isinstance(raw, str): - payload = raw.encode('utf-8') - elif isinstance(raw, (bytes, bytearray, memoryview)): - payload = bytes(raw) - else: - payload = np.asarray(raw, dtype=np.uint8).tobytes() - if not payload: - return None - try: - metadata = json.loads(payload.decode('utf-8')) - except (UnicodeDecodeError, ValueError) as error: - raise ValueError('ABACUS socket extras are not valid UTF-8 JSON') from error - if not isinstance(metadata, dict): - raise ValueError('ABACUS socket extras must be a JSON object') - if metadata.get('schema') != 'abacus.socket.properties.v1': - raise ValueError('unsupported ABACUS socket extras schema') - present = metadata.get('present') - if not isinstance(present, list): - raise ValueError('ABACUS socket extras present must be a list') - allowed = {'energy', 'forces', 'stress'} - if any(not isinstance(name, str) or name not in allowed for name in present): - raise ValueError('ABACUS socket extras contain an unknown property') - if 'energy' not in present: - raise ValueError('ABACUS socket extras must include energy') - scf_converged = metadata.get('scf_converged') - if not isinstance(scf_converged, bool): - raise ValueError('ABACUS socket extras scf_converged must be Boolean') - return { - 'present': tuple(dict.fromkeys(present)), - 'scf_converged': scf_converged, - } - - def _launch_client(self, atoms, properties=None, port=None, unixsocket=None): - from subprocess import Popen - - if properties is None: - properties = list(self._active_properties or ('energy',)) - properties = set(properties) - properties.discard('free_energy') - properties.add('energy') - # The i-PI response has fixed force/virial fields, but ABACUS must be - # told explicitly which expensive quantities to evaluate. Keep these - # switches synchronized with the session mask before writing INPUT. - self.abacus.parameters['cal_force'] = int('forces' in properties) - self.abacus.parameters['cal_stress'] = int('stress' in properties) - properties = [name for name in ('energy', 'forces', 'stress') - if name in properties] - - directory = Path(self.abacus.directory) - directory.mkdir(exist_ok=True, parents=True) - - if hasattr(self.abacus, 'write_inputfiles'): - self.abacus.write_inputfiles(atoms, properties) - else: - self.abacus.write_input(atoms, properties=properties) - - if unixsocket is not None: - argv = self.abacus.profile.socketio_argv_unix(socket=unixsocket) - else: - argv = self.abacus.profile.socketio_argv_inet(port=port) - - stdout = open(directory / self.abacus.template.outputname, 'w') - stderr = open(directory / self.abacus.template.errorname, 'w') - try: - return Popen(argv, cwd=directory, env=os.environ, - stdout=stdout, stderr=stderr) - finally: - stdout.close() - stderr.close() - - @staticmethod - def _socket_inp(inp): - inp = dict(inp) - calculation = inp.get('calculation', 'scf') - if calculation != 'scf': - raise ValueError('ABACUS socket I/O requires calculation="scf"') - for keyword in ('cal_force', 'cal_stress'): - if keyword in inp: - inp[keyword] = int(AbacusSocketIO._input_bool(inp[keyword], keyword)) - inp.update({ - 'calculation': 'scf', - 'socket_driver': 1, - }) - return inp - - @staticmethod - def _input_bool(value, name): - if isinstance(value, bool): - return value - if isinstance(value, int) and value in (0, 1): - return bool(value) - if isinstance(value, str): - normalized = value.strip().lower() - if normalized in ('true', '1'): - return True - if normalized in ('false', '0'): - return False - raise ValueError('{} must be one of true, false, 1, or 0'.format(name)) - - @staticmethod - def _socket_sort_indices(atoms): - return species_group_indices(atoms.get_chemical_symbols()) - - @staticmethod - def _forces_to_input_order(forces, order): - reordered = np.empty_like(forces) - for sorted_index, original_index in enumerate(order): - reordered[original_index] = forces[sorted_index] - return reordered - - -class TestAbacusCalculator(unittest.TestCase): - - here = Path(__file__).parent - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - - def test_socketio_species_order_mapping(self): - atoms = Atoms(symbols=['Si', 'O', 'C', 'Si', 'O', 'C']) - order = AbacusSocketIO._socket_sort_indices(atoms) - self.assertEqual(order, [0, 3, 1, 4, 2, 5]) - - socket_forces = np.arange(18).reshape(6, 3) - input_forces = AbacusSocketIO._forces_to_input_order( - socket_forces, order) - - expected = np.empty_like(socket_forces) - for sorted_index, original_index in enumerate(order): - expected[original_index] = socket_forces[sorted_index] - np.testing.assert_array_equal(input_forces, expected) - - def test_socketio_rejects_parameter_changes(self): - calc = object.__new__(AbacusSocketIO) - with self.assertRaisesRegex(ValueError, 'fixed after construction'): - calc.set(kpts={'mode': 'mp-sampling', 'nk': [2, 2, 2]}) - - def test_socketio_rejects_cell_changes(self): - from ase.calculators.calculator import PropertyNotImplementedError - - calc = object.__new__(AbacusSocketIO) - calc.atoms = Atoms('Si', cell=[5.0, 5.0, 5.0], pbc=True) - calc._reference_cell = calc.atoms.cell.array.copy() - - changed = calc.atoms.copy() - changed.cell[0, 0] = 5.1 - with self.assertRaisesRegex(PropertyNotImplementedError, 'fixed-cell'): - calc._check_fixed_cell(changed) - - def test_socketio_input_keeps_independent_property_switches(self): - self.assertEqual( - AbacusSocketIO._socket_inp({'cal_force': 0, 'cal_stress': 1}), - {'calculation': 'scf', 'socket_driver': 1, - 'cal_force': 0, 'cal_stress': 1}) - - def test_socketio_boolean_parser_rejects_ambiguous_values(self): - for value in ('yes', 'no', 'on', 'off', ''): - with self.assertRaisesRegex(ValueError, 'cal_force'): - AbacusSocketIO._input_bool(value, 'cal_force') - - def test_socketio_metadata_rejects_unknown_property(self): - metadata = json.dumps({ - 'schema': 'abacus.socket.properties.v1', - 'present': ['energy', 'charges'], - 'scf_converged': True, - }).encode('utf-8') - with self.assertRaisesRegex(ValueError, 'unknown property'): - AbacusSocketIO._decode_socket_metadata( - np.frombuffer(metadata, dtype=np.int8)) - - def test_socketio_metadata_marks_padding_absent(self): - metadata = json.dumps({ - 'schema': 'abacus.socket.properties.v1', - 'present': ['energy'], - 'scf_converged': False, - }).encode('utf-8') - decoded = AbacusSocketIO._decode_socket_metadata( - np.frombuffer(metadata, dtype=np.int8)) - self.assertEqual(decoded['present'], ('energy',)) - self.assertFalse(decoded['scf_converged']) - - def test_socketio_legacy_response_cannot_infer_force_from_padding(self): - class LegacyServer: - def calculate(self, atoms): - return { - 'energy': 1.0, - 'forces': np.zeros((len(atoms), 3)), - 'virial': np.zeros((3, 3)), - 'morebytes': b'', - } - - calc = object.__new__(AbacusSocketIO) - calc.variable_cell = False - calc._property_constraints = {} - calc._active_properties = ('energy', 'forces') - calc._reference_cell = None - calc.atoms = None - calc.server = LegacyServer() - atoms = Atoms('Si') - with self.assertRaisesRegex(ValueError, 'refusing to infer forces'): - calc.calculate(atoms=atoms, properties=('forces',), system_changes=()) - - def test_socketio_failed_response_clears_previous_frame(self): - class BrokenServer: - def calculate(self, atoms): - raise EOFError('incomplete frame') - - calc = object.__new__(AbacusSocketIO) - calc._property_constraints = {} - calc._active_properties = ('energy',) - calc._reference_cell = None - calc.atoms = None - calc.results = {'energy': 123.0} - calc.last_scf_converged = True - calc._last_socket_metadata = {'scf_converged': True} - calc.server = BrokenServer() - with self.assertRaises(EOFError): - calc.calculate(Atoms('Si'), properties=('energy',), system_changes=()) - self.assertEqual(calc.results, {}) - self.assertIsNone(calc.last_scf_converged) - self.assertIsNone(calc._last_socket_metadata) - - def test_socketio_requested_disabled_property_is_rejected(self): - calc = object.__new__(AbacusSocketIO) - calc._property_constraints = {'forces': False, 'stress': False} - from ase.calculators.calculator import PropertyNotImplementedError - with self.assertRaises(PropertyNotImplementedError): - calc._check_requested_properties(('forces',)) - - def test_parse_version_allows_launcher_noise(self): - stdout = 'launcher info\nABACUS version v3.11.0-beta6\n' - self.assertEqual(AbacusProfile.parse_version(stdout), 'v3.11.0-beta6') - - def test_parse_version_rejects_missing_version(self): - with self.assertRaisesRegex(RuntimeError, 'ABACUS version'): - AbacusProfile.parse_version('launcher failed before abacus started') - - def test_calculator_results(self): - from ase.build.bulk import bulk - silicon = bulk('Si', crystalstructure='diamond', a=5.43) - aprof = AbacusProfile( - command='mpirun -np 2 abacus', - pseudo_dir=self.pporb, - orbital_dir=self.pporb, - omp_num_threads=1 - ) - with tempfile.TemporaryDirectory() as tmpdir: - calculator = Abacus(aprof, - directory=tmpdir, - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_6au_100Ry_2s2p1d.orb'}, - inp={'calculation': 'scf', - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 40, - 'symmetry': 1, - 'nspin': 1, - 'gamma_only': True, - 'cal_force': 1, - 'cal_stress': 1}) - silicon.calc = calculator - e = silicon.get_potential_energy() - - # check! - self.assertAlmostEqual(e, -194.953050937) - self.assertIsNotNone(calculator.results) - self.assertIsInstance(calculator.results, dict) - for k in ['nspins', 'nkpts', 'nbands', 'eigenvalues', 'occupations', - 'fermi_level', 'kpoint_weights', 'ibz_kpoints', 'energy', - 'free_energy', 'natoms', 'forces', 'stress', 'magmoms']: - self.assertIn(k, calculator.results) - self.assertEqual(calculator.results['nspins'], 1) - self.assertEqual(calculator.results['nkpts'], 1) - self.assertEqual(calculator.results['nbands'], 14) - self.assertEqual(calculator.results['energy'], e) - self.assertEqual(calculator.results['free_energy'], e) - self.assertEqual(calculator.results['natoms'], 2) - - for k in ['eigenvalues', 'occupations', 'ibz_kpoints', 'forces', 'stress', 'magmoms']: - self.assertIsInstance(calculator.results[k], np.ndarray) - - self.assertEqual(calculator.results['eigenvalues'].shape, (1, 1, 14)) - ekb = [-4.82194, 7.62727, 7.62727, 7.62737, 10.2436 , 10.2436 , - 10.2436 , 10.9884 , 16.057 , 16.057 , 23.8353 , 25.421 , - 25.421 , 25.4212 ] - self.assertTrue(np.allclose(calculator.results['eigenvalues'][0, 0, :], np.array(ekb))) - - self.assertEqual(calculator.results['occupations'].shape, (1, 1, 14)) - occ = [2., 2., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.] - self.assertTrue(np.allclose(calculator.results['occupations'][0, 0, :], np.array(occ))) - - self.assertEqual(calculator.results['ibz_kpoints'].shape, (1, 3)) - self.assertTrue(np.allclose(calculator.results['ibz_kpoints'][0, :], np.array([0,0,0]))) - - self.assertEqual(calculator.results['forces'].shape, (2, 3)) - self.assertTrue(np.allclose(calculator.results['forces'], np.zeros((2, 3)))) - - self.assertEqual(calculator.results['stress'].shape, (6,)) - stress = [-0.19327923, -0.19327923, -0.19327923, -0. , 0. , 0. ] - self.assertTrue(np.allclose(calculator.results['stress'], np.array(stress))) - - self.assertEqual(calculator.results['magmoms'].shape, (2,)) - self.assertTrue(np.allclose(calculator.results['magmoms'], np.zeros(2))) - - def test_restart(self): - from ase.build.bulk import bulk - silicon = bulk('Si', crystalstructure='diamond', a=5.43) - aprof = AbacusProfile( - command='mpirun -np 2 abacus', - pseudo_dir=self.pporb, - orbital_dir=self.pporb, - omp_num_threads=1 - ) - with tempfile.TemporaryDirectory() as tmpdir: - calculator = Abacus(aprof, - directory=tmpdir, - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_6au_100Ry_2s2p1d.orb'}, - inp={'calculation': 'scf', - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 40, - 'symmetry': 1, - 'nspin': 1, - 'gamma_only': True, - 'cal_force': 1, - 'cal_stress': 1}) - silicon.calc = calculator - e = silicon.get_potential_energy() - - # restart - silicon.calc = Abacus.restart(aprof, directory=tmpdir) - e2 = silicon.get_potential_energy() - self.assertAlmostEqual(e2, e) - - def test_version_number_check(self): - # not a version number - with self.assertRaises(AssertionError): - switch_io_backend_version('not-a-version-number') - # too old version - with self.assertRaises(AssertionError): - switch_io_backend_version('v2.2.2') - self.assertTrue(switch_io_backend_version('v3.8.4')) - self.assertFalse(switch_io_backend_version('v3.9.0.25')) - self.assertTrue(switch_io_backend_version('v3.10.0')) - self.assertFalse(switch_io_backend_version('v3.11.0-beta.2')) - self.assertFalse(switch_io_backend_version('v3.11.0')) - - def test_property_keywords_reject_conflicting_user_parameters(self): - template = AbacusTemplate() - with self.assertRaises(ValueError): - template.get_property_keywords({'nspin': 1}, ['magmom']) - - parameters = template.get_property_keywords({'nspin': 2}, ['magmom']) - self.assertEqual(str(parameters['nspin']), '2') - - def test_property_keywords_accept_equivalent_boolean_user_parameters(self): - template = AbacusTemplate() - - parameters = template.get_property_keywords( - {'cal_force': True, 'cal_stress': True}, - ['forces', 'stress'] - ) - - self.assertEqual(str(parameters['cal_force']), '1') - self.assertEqual(str(parameters['cal_stress']), '1') - - def test_property_keywords_reject_conflicting_boolean_user_parameters(self): - template = AbacusTemplate() - - with self.assertRaises(ValueError): - template.get_property_keywords({'cal_force': False}, ['forces']) - - with self.assertRaises(ValueError): - template.get_property_keywords({'cal_stress': False}, ['stress']) - - def test_property_keywords_treat_string_values_as_scalars(self): - template = AbacusTemplate() - template.implemented_properties = ['probe'] - template.get_probe_keywords = lambda parameters: {'custom_switch': 'true'} - - parameters = template.get_property_keywords( - {'custom_switch': 'true'}, ['probe'] - ) - - self.assertEqual(parameters['custom_switch'], 'true') - - def test_property_keywords_compare_iterables_like_input_writer(self): - template = AbacusTemplate() - template.implemented_properties = ['probe'] - template.get_probe_keywords = lambda parameters: { - 'custom_vector': [1, 'true'] - } - - with self.assertRaises(ValueError): - template.get_property_keywords( - {'custom_vector': [True, 'true']}, ['probe'] - ) - - def test_property_keywords_reject_conflicting_properties(self): - template = AbacusTemplate() - template.implemented_properties = ['prop_a', 'prop_b'] - template.get_prop_a_keywords = lambda parameters: {'calculation': 'scf'} - template.get_prop_b_keywords = lambda parameters: {'calculation': 'md'} - - with self.assertRaises(ValueError): - template.get_property_keywords({}, ['prop_a', 'prop_b']) - - def test_dipole_property_is_not_implemented(self): - template = AbacusTemplate() - self.assertNotIn('dipole', template.implemented_properties) - with self.assertRaises(AssertionError): - template.get_property_keywords({}, ['dipole']) - -if __name__ == '__main__': - unittest.main() diff --git a/interfaces/ASE_interface/abacuslite/io/__init__.py b/interfaces/ASE_interface/abacuslite/io/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/interfaces/ASE_interface/abacuslite/io/generalio.py b/interfaces/ASE_interface/abacuslite/io/generalio.py deleted file mode 100644 index c690964a5bb..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/generalio.py +++ /dev/null @@ -1,941 +0,0 @@ -'''public functions that can be used by both the LTS and the latest -version of ABACUS''' -import re -import os -import tempfile -import unittest -from pathlib import Path -from io import TextIOWrapper -from typing import Optional, Dict, List, Any - -import numpy as np -from ase.atoms import Atoms -from ase.build import bulk -from ase.constraints import FixAtoms, FixCartesian -from ase.data import chemical_symbols, atomic_masses -ATOM_MASS = dict(zip(chemical_symbols, atomic_masses.tolist())) - -def load_pseudo(pseudo_dir: str) -> Dict[str, str]: - '''load the pseudopotential mapping from the pseudo_dir''' - pseudo_dir = Path(pseudo_dir) - UPFPAT_ = r'^([A-Z][a-z]?)([^a-zA-Z]+\S+)?\.(upf|UPF)$' - # the file name is assumed to have the pattern: - # [element symbol][delimeter][other things...][.upf|UPF] - return {re.match(UPFPAT_, f.name).group(1): f.name - for f in pseudo_dir.glob('*') - if re.match(UPFPAT_, f.name)} - -def load_orbital(orbital_dir: str, efficiency: bool=True) -> Dict[str, str]: - '''load the ABACUS numerical atomic orbital mapping from the - orbital dir. There may be the case that there is not only one - orbitals find for one element, in this case, the second parameter - `efficiency` will take its effect: if true, use the "smaller" - one, otherwise, use the "larger" one - - Parameters - ---------- - orbital_dir : str - The directory containing the orbital basis files. - efficiency : bool, optional - Whether to use the "smaller" one, default is True. - - Returns - ------- - Dict[str, str] - The mapping from element symbol to orbital basis file name. - ''' - # a small helper function - def _basis_size(basis: List[str]) -> int: - SPECTRUM = 'spdfghi' - num, symb = zip(*[re.match(r'(\d+)([spdfghi])', b).groups() for b in basis]) - return sum((2*SPECTRUM.index(s) + 1) * int(n) for n, s in zip(num, symb)) - - orbital_dir = Path(orbital_dir) - ORBPAT_ = r'^([A-Z][a-z]?)_gga_(\d+)au_(\d+(\.\d+)?)Ry_(\w+)\.orb$' - # a correct parse would yield: - # elem, rcut, ecut, components = re.match(ORBPAT_, f.name) - tosort = {} - for f in orbital_dir.glob('*'): - m = re.match(ORBPAT_, f.name) - if m: - elem, rcut, ecut, _, components = m.groups() - components = re.findall(r'\d+[spdfghi]', components) - tosort.setdefault(elem, []).append( - (f.name, int(rcut), float(ecut), _basis_size(components)) - ) - # sort the basis files by their size: first by components, then by rcut, then by ecut - ib = 0 if efficiency else -1 - return {elem: sorted(basis, key=lambda b: (b[3], b[1], b[2]))[ib][0] - for elem, basis in tosort.items()} - -def file_safe_backup(fn: Path, suffix: str = 'bak'): - '''for the case where there are already files with the same name, - add a suffix to the file name, like `STRU.bak.0`. If there are - already `STRU.bak.0`, rename the elder to `STRU.bak.1` and let - the latest one be `STRU.bak.0`. - - Parameters - ---------- - fn : Path - The path to the file to backup. Note: it must be provided - as the Path object so that its folder is accessible by this - function. - suffix : str, optional - The suffix to add to the file name. Default is 'bak' - ''' - assert isinstance(fn, Path) - where = fn.parent - prefix = f'{fn.name}.{suffix}.' - indexed_backups = [] - - for backup in where.glob(f'{fn.name}.{suffix}.*'): - index_text = backup.name.removeprefix(prefix) - if not re.fullmatch(r'0|[1-9][0-9]*', index_text): - continue - indexed_backups.append((int(index_text), backup)) - - for backup_index, backup in sorted(indexed_backups, key=lambda item: item[0], reverse=True): - backup.rename(backup.parent / f'{fn.name}.{suffix}.{backup_index + 1}') - - # backup the latest file, if there is one - if fn.exists(): - fn.rename(fn.parent / f'{fn.name}.{suffix}.0') - -def _write_stru(job_dir, stru, fname='STRU'): - ''' - Generates a ABACUS STRU file from a STRU dict. - - Parameters - ---------- - job_dir: str - Directory in which the STRU file is generated. - stru : dict - Parameters to generate the STRU file. - See the docstring at the beginning of this script for details. - fname : str - Name of the STRU file. - - ''' - with open(Path(job_dir) / fname, 'w') as f: - - #============ ATOMIC_SPECIES ============ - f.write('ATOMIC_SPECIES\n') - width = {key + '_w' : max([len(str(s[key])) for s in stru['species']]) - for key in ['symbol', 'mass', 'pp_file']} - for s in stru['species']: - f.write('{symbol:<{symbol_w}} {mass:>{mass_w}} {pp_file:>{pp_file_w}}'.format(**s, **width)) - if 'pp_type' in s: - f.write(f" {s['pp_type']}") - f.write('\n') - - #============ NUMERICAL_ORBITAL ============ - if 'orb_file' in stru['species'][0]: - f.write('\nNUMERICAL_ORBITAL\n') - for s in stru['species']: - f.write(f"{s['orb_file']}\n") - - #============ LATTICE_CONSTANT/PARAMETER/VECTORS ============ - f.write('\nLATTICE_CONSTANT\n') - f.write(f"{stru['lat']['const']}\n") - - if 'vec' in stru['lat']: - f.write('\nLATTICE_VECTORS\n') - for v in stru['lat']['vec']: - f.write(f'{v[0]} {v[1]} {v[2]}\n') - - if 'param' in stru['lat']: - f.write('\nLATTICE_PARAMETER\n') - for param in stru['lat']['param']: - f.write(f'{param} ') - f.write('\n') - - #============ ATOMIC_POSITIONS ============ - f.write('\nATOMIC_POSITIONS\n') - f.write(f"{stru['coord_type']}\n") - - for s in stru['species']: - f.write(f"\n{s['symbol']}\n") - f.write(f"{s['mag_each']}\n") - f.write(f"{s['natom']}\n") - - for atom in s['atom']: - f.write(' '.join(f'{x}' for x in atom['coord'])) - - for key in ['m', 'v']: # frozen atom / initial velocity - if key in atom: - f.write(f' {key}' + - ''.join(f' {x}' for x in atom[key])) - - if 'mag' in atom: - if not isinstance(atom['mag'], tuple): # collinear - f.write(f" mag {atom['mag']}") - else: # non-collinear - mag_coord_type, mag = atom['mag'] - assert mag_coord_type in ['Cartesian', 'Spherical'] - if mag_coord_type == 'Cartesian': - f.write(f' mag {mag[0]} {mag[1]} {mag[2]}') - else: - f.write(f' mag {mag[0]} angle1 {mag[1]} angle2 {mag[2]}') - - f.write('\n') - -def species_group_indices(symbols: List[str]) -> List[int]: - """Return indices grouped by first-occurrence species order.""" - species_order = list(dict.fromkeys(symbols)) - return [i for species in species_order for i, symbol in enumerate(symbols) if symbol == species] - - -def _constraint_mobility(atoms: Atoms) -> np.ndarray: - """Return ABACUS mobility flags derived from ASE constraints.""" - mobility = np.ones((len(atoms), 3), dtype=int) - for constraint in atoms.constraints: - if isinstance(constraint, FixAtoms): - mobility[constraint.get_indices()] = 0 - elif isinstance(constraint, FixCartesian): - indices = np.asarray(constraint.get_indices(), dtype=int) - mask = np.asarray(constraint.mask, dtype=bool) - if mask.ndim == 1: - mobility[np.ix_(indices, np.where(mask)[0])] = 0 - else: - for atom_index, atom_mask in zip(indices, mask): - mobility[atom_index, atom_mask] = 0 - return mobility - - -def write_stru(stru: Atoms, - outdir: str, - pp_file: Optional[Dict[str, str]], - orb_file: Optional[Dict[str, str]] = None, - fname: Optional[str] = 'STRU') -> str: - ''' - write the ABACUS STRU file from an Atoms object into outdir. - - Parameters - ---------- - stru : Atoms - The Atoms object to write. - outdir : str - The directory to write the STRU file. - pp_file : dict - A dictionary mapping element symbols to pseudopotential file names. - The keys are the element symbols, and the values are the paths to - the pseudopotential files. - orb_file : dict, optional - A dictionary mapping element symbols to orbital file names. - The keys are the element symbols, and the values are the paths to - the orbital files. If None, no orbital files will be written. - fname : str, optional - The name of the STRU file to write. Default is 'STRU'. - - Returns - ------- - str - The path to the written STRU file. - ''' - from ase.units import ( - Bohr as __BOHR__, - Angstrom as __ANGSTROM__ - ) - assert isinstance(stru, Atoms) - pp_file = pp_file or {} # can be None, for those non-ESolver_KS cases - - elem = stru.get_chemical_symbols() - # ABACUS requires the atoms ranged species-by-species, therefore - # we need to group atoms by species. Preserve first-occurrence species - # order from ASE instead of forcing alphabetical order. - ind = species_group_indices(elem) - coords = stru.get_positions()[ind] - mobility = _constraint_mobility(stru)[ind] - elem = [elem[i] for i in ind] - - # handle the atomic magnetic moment (issue #6516) - magmoms = np.array([stru[i].magmom for i in ind]).reshape(len(stru), -1) # ncol in [1, 3] - magmoms = [{} if abs(np.linalg.norm(m)) <= 1e-10 - else {'mag': m[0] if len(m) == 1 else ('Cartesian', m.tolist())} - for m in magmoms] - elem_uniq = list(dict.fromkeys(elem)) - nat = np.array([elem.count(e) for e in elem_uniq]) - stru_dict = { - 'coord_type': 'Cartesian', - 'lat': { - 'const': __ANGSTROM__ / __BOHR__, - 'vec': np.array(stru.get_cell()).tolist() - }, - 'species': [ - { - 'symbol': e, - 'mass': ATOM_MASS[e], - 'pp_file': pp_file.get(e, ''), - 'pp_type': '', - 'natom': n, - 'mag_each': 0.0, - 'atom': [ - magmoms[j] | { - 'coord': coords[j].tolist(), # coordinate - 'm': mobility[j].tolist(), # mobility - 'v': [0.0, 0.0, 0.0], # velocity - } for j in range(np.sum(nat[:i]), np.sum(nat[:i+1])) - ] - } - for i, (n, e) in enumerate(zip(nat, elem_uniq)) - ] - } - if orb_file is not None: - for s in stru_dict['species']: - s['orb_file'] = orb_file[s['symbol']] - - _write_stru(outdir, stru_dict, fname) - - return (Path(outdir) / fname).resolve().as_posix() - -def write_input(data: Dict[str, Any], fn: str) -> str: - ''' - write the ABACUS INPUT file from a Dict[str, Any] object - - Parameters - ---------- - data : dict - The data to write to the INPUT file. - The keys are the parameters and the values are the values of the parameters. - fn : str - The path to the ABACUS INPUT file. - - Returns - ------- - str - the absolute path of the written INPUT file - ''' - with open(fn, 'w') as f: - f.write('INPUT_PARAMETERS\n') - for key, value in data.items(): - f.write(f'{key} {value}\n') - f.write('\n') # add a newline at the end - - return str(Path(fn).resolve()) - -def _write_kline(data: Dict[str, Any], f: TextIOWrapper): - ''' - write the k-point file whose mode is "Line" - ''' - f.write('K_POINTS\n') - f.write(f"{data['nk']}\n") - f.write('Line_Cartesian\n' if data['coordinate'].lower() == 'cartesian' - else 'Line\n') - for k, nk in zip(data['kpoints'], data['nkinterpl']): - f.write(f"{k[0]} {k[1]} {k[2]} {nk}\n") - -def _write_ksampl_mp(data: Dict[str, Any], f: TextIOWrapper): - ''' - write the k-point file whose mode is automatically sampling with Monkhorst-Pack - ''' - f.write('K_POINTS\n') - f.write('0\n') - f.write("Gamma\n" if data['gamma-centered'] else "MP\n") - f.write(f"{data['nk'][0]} {data['nk'][1]} {data['nk'][2]} " - f"{data['kshift'][0]} {data['kshift'][1]} {data['kshift'][2]}\n") - -def _write_kpoint(data: Dict[str, Any], f: TextIOWrapper): - ''' - write the k-point file whose mode is specifying kpoints one-by-one - ''' - f.write('K_POINTS\n') - f.write(f"{data['nk']}\n") - f.write(f"{data['coordinate'].capitalize()}\n") - nkinterpl = data.get('nkinterpl', [1 for _ in data['kpoints']]) - for k, nk in zip(data['kpoints'], data.get('nkinterpl', nkinterpl)): - f.write(f"{k[0]} {k[1]} {k[2]} {nk}\n") - -def read_kpoint_from_running_log(fn: str) -> Dict[str, Any]: - '''Read the k-point information from the ABACUS running log file.''' - raise NotImplementedError('read_kpoint_from_running_log is not implemented yet.') - -def write_kpt(data: Dict[str, Any], fn: str) -> str: - ''' - write the ABACUS KPT file from a Dict[str, Any] object - - Parameters - ---------- - data : dict - The data to write to the KPT file. - The keys are the parameters and the values are the values of the parameters. - fn : str - The path to the ABACUS KPT file. - - Returns - ------- - str - the absolute path of the written KPT file - ''' - assert 'mode' in data, 'KPT data must contain "mode" key' - mywriter = { - # 'line': _write_kline, - 'mp-sampling': _write_ksampl_mp, - 'point': _write_kpoint - } - assert data['mode'] in mywriter, \ - f'Invalid KPT mode {data["mode"]}, must be one of ' \ - f'{list(mywriter.keys())}' - with open(fn, 'w') as f: - mywriter[data['mode']](data, f) - - return str(Path(fn).resolve()) - -def _parse_coord_line(line): - ''' - Parses a coordinate line (which may include extra parameters) - in the ATOMIC_POSITIONS block. - - A coordinate line always contains the x, y, z coordinates of an atom, - and may also include - - whether an atom is frozen in MD or relaxation - - initial velocity of an atom in MD or relaxation - - magnetic moment of an atom - - Reference - --------- - https://abacus.deepmodeling.com/en/latest/advanced/input_files/stru.html - - (see section "More Key Words" for details) - - ''' - fields = line.split() - result = { 'coord' : [float(x) for x in fields[0:3]] } - - idx = 3 - while idx < len(fields): - if fields[idx].isdigit(): # no keyword, 0/1 -> frozen atom - result['m'] = [int(x) for x in fields[idx:idx+3]] - idx += 3 - elif fields[idx] == 'm': # frozen atom - result['m'] = [int(x) for x in fields[idx+1:idx+4]] - idx += 4 - elif fields[idx] in ['v', 'vel', 'velocity']: # initial velocity - result['v'] = [float(x) for x in fields[idx+1:idx+4]] - idx += 4 - elif fields[idx] in ['mag', 'magmom']: - ''' - here we assume that frozen atom info cannot be placed after - a collinear mag info without a keyword - i.e., the following coordinate line - 0.0 0.0 0.0 mag 1.0 0 0 0 - is not allowed; one must explicitly specify 'm' in this case: - 0.0 0.0 0.0 mag 1.0 m 0 0 0 - - ''' - if idx + 2 < len(fields) and fields[idx+2] == 'angle1': - result['mag'] = ('Spherical', - list(map(float, fields[idx+1:idx+6:2]))) - idx += 6 - elif idx + 2 < len(fields) and fields[idx+2][0].isdigit(): - result['mag'] = ('Cartesian', - list(map(float, fields[idx+1:idx+4]))) - idx += 4 - else: # collinear - result['mag'] = float(fields[idx+1]) - idx += 2 - else: - raise ValueError('Error: unknown keyword %s'%fields[idx]) - - return result - -def _atomic_positions_gen(lines): - ''' - Iteratively generates info per species from the ATOMIC_POSITIONS block. - - ''' - natom = int(lines[2]) - yield {'symbol': lines[0], 'mag_each': float(lines[1]), 'natom': natom, - 'atom': [_parse_coord_line(line) for line in lines[3:3+natom]]} - if len(lines) > 3 + natom: - yield from _atomic_positions_gen(lines[3+natom:]) - -def read_stru(fn: str) -> Dict[str, Any]: - ''' - read the ABACUS STRU file and return a comprehensive Dict[str, Any] - object. This function is implemented by @jinzx10 in ABACUS-CSW-NAO - project. - - Parameters - ---------- - fn : str - The path to the ABACUS STRU file. - - Returns - ------- - A dict containing the following keys-value pairs: - 'species' : list of dict - List of atomic species. Each dict contains 'symbol', 'mass', - 'pp_file', and optionally 'pp_type'. - ''' - block_title = ['ATOMIC_SPECIES', - 'NUMERICAL_ORBITAL', - 'LATTICE_CONSTANT', - 'LATTICE_PARAMETER', - 'LATTICE_VECTORS', - 'ATOMIC_POSITIONS'] - - def _trim(line): - return line.split('#')[0].split('//')[0].strip(' \t\n') - - with open(fn, 'r') as f: - lines = [_trim(line).replace('\t', ' ') - for line in f.readlines() if len(_trim(line)) > 0] - - # break the content into blocks - delim = [i for i, line in enumerate(lines) if line in block_title] \ - + [len(lines)] - blocks = {lines[delim[i]] : lines[delim[i]+1:delim[i+1]] - for i in range(len(delim) - 1)} - - stru = {} - #============ LATTICE_CONSTANT/PARAMETER/VECTORS ============ - stru['lat'] = {'const': float(blocks['LATTICE_CONSTANT'][0])} - if 'LATTICE_VECTORS' in blocks: - stru['lat']['vec'] = [[float(x) for x in line.split()] - for line in blocks['LATTICE_VECTORS']] - elif 'LATTICE_PARAMETER' in blocks: - stru['lat']['param'] = [float(x) - for x in blocks['LATTICE_PARAMETER'][0].split()] - - #============ ATOMIC_SPECIES ============ - stru['species'] = [dict(zip(['symbol', 'mass', 'pp_file', 'pp_type'], - line.split())) - for line in blocks['ATOMIC_SPECIES']] - for s in stru['species']: - s['mass'] = float(s['mass']) - - #============ NUMERICAL_ORBITAL ============ - if 'NUMERICAL_ORBITAL' in blocks: - for i, s in enumerate(stru['species']): - s['orb_file'] = blocks['NUMERICAL_ORBITAL'][i] - - #============ ATOMIC_POSITIONS ============ - stru['coord_type'] = blocks['ATOMIC_POSITIONS'][0] - index = { s['symbol'] : i for i, s in enumerate(stru['species']) } - for ap in _atomic_positions_gen(blocks['ATOMIC_POSITIONS'][1:]): - stru['species'][index[ap['symbol']]].update(ap) - - return stru - -def read_input(fn: str) -> Dict[str, Any]: - ''' - read the ABACUS INPUT file and return a comprehensive Dict[str, Any] - - Parameters - ---------- - fn : str - The path to the ABACUS INPUT file. - - Returns - ------- - dict - ''' - with open(fn) as f: - raw = [l.strip() for l in f.readlines()] - raw = [l for l in raw - if l and not re.match(r'^(INPUT_PARAMETERS|#|//|!)', l)] - raw = [re.split(r'#|//|!', l)[0].strip() for l in raw] - raw = [re.split(r'\s+', l) for l in raw] - - return dict([(l[0], ' '.join(l[1:])) for l in raw if len(l) > 0]) - -def _read_kline(raw: List[str]) -> Dict[str, Any]: - ''' - read the k-point file whose mode is "Line", and return the parsed result - ''' - assert len(raw) >= 4, \ - f'Invalid KPT file, expected at least 4 lines, got {len(raw)}' - assert re.match(r'^\d+$', raw[1]), \ - f'Invalid KPT file, expected the second line to be an integer, ' \ - f'got {raw[1]}' - assert int(raw[1]) == len(raw) - 3, \ - f'Invalid KPT file, expected the second line to be the number of ' \ - f'k-points, got {raw[1]}, but found {len(raw)} k-points' - assert raw[2].lower().startswith('line'), \ - f'Invalid KPT file, expected "Line" in the third line, got {raw[2]}' - mymatch = [re.match(r'^(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+' - r'(\d+).*', l) - for l in raw[3:]] - assert all(m for m in mymatch), \ - 'Invalid KPT file, expected the k-points to be in the format ' \ - '"x y z n # comment"' - return { - 'mode': 'line', - 'coordinate': 'Cartesian' if raw[2].lower().endswith('cartesian') else 'Direct', - 'nk': int(raw[1]), - 'kpoints': [tuple([float(x) for x in m.groups()[:3]]) for m in mymatch], - 'nkinterpl': [int(m.groups()[6]) for m in mymatch] - } - -def _read_ksampl_mp(raw: List[str]) -> Dict[str, Any]: - ''' - read the k-point file whose mode is automatically sampling with Monkhorst-Pack - scheme, and return the parsed result - ''' - assert len(raw) == 4, \ - f'Invalid KPT file, expected 4 lines, got {len(raw)}' - assert raw[2].lower() in ['gamma', 'mp'], \ - f'Invalid KPT file, expected "Gamma" or "MP" in the third line, ' \ - f'got {raw[2]}' - # the following may raise the ValueError if unpacking is not correct - nk1, nk2, nk3, kshift1, kshift2, kshift3 = map(int, raw[3].split()) - return { - 'mode': 'mp-sampling', - 'gamma-centered': bool(raw[2].lower() == 'gamma'), - 'nk': (nk1, nk2, nk3), - 'kshift': (kshift1, kshift2, kshift3) - } - -def _read_kpoint(raw: List[str]) -> Dict[str, Any]: - ''' - read the k-point file whose mode is specifying kpoints one-by-one, return - the parsed result. - ''' - assert len(raw) >= 4, \ - f'Invalid KPT file, expected at least 4 lines, got {len(raw)}' - assert re.match(r'^\d+$', raw[1]), \ - f'Invalid KPT file, expected the second line to be an integer, ' \ - f'got {raw[1]}' - assert int(raw[1]) == len(raw) - 3, \ - f'Invalid KPT file, expected the second line to be the number of ' \ - f'k-points, got {raw[1]}, but found {len(raw)} k-points' - assert raw[2].lower() in ['direct', 'cartesian'], \ - f'Invalid KPT file, expected "Direct" or "Cartesian" in the third line, ' \ - f'got {raw[2]}' - mymatch = [re.match(r'^(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s*' - r'(\d+).*', l) - for l in raw[3:]] - assert all(m for m in mymatch), \ - 'Invalid KPT file, expected the k-points to be in the format ' \ - '"x y z n # comment"' - - return { - 'mode': 'point', - 'coordinate': 'Cartesian' if raw[2].lower() == 'cartesian' else 'Direct', - 'nk': int(raw[1]), - 'kpoints': [tuple(map(float, m.groups()[:3])) for m in mymatch], - 'weights': [int(m.groups()[6]) for m in mymatch] - } - -def read_kpt(fn: str) -> Dict[str, Any]: - ''' - read the ABACUS KPT file and return a comprehensive Dict[str, Any] - - Parameters - ---------- - fn : str - The path to the ABACUS KPT file. - - Returns - ------- - dict - A dictionary containing the k-point information. - ''' - with open(fn) as f: - raw = [l.strip() for l in f.readlines()] - raw = [l for l in raw if l] - parser_switch = {'line': _read_kline, - 'gamma': _read_ksampl_mp, 'mp': _read_ksampl_mp, - 'direct': _read_kpoint, 'cartesian': _read_kpoint} - assert raw[0] == 'K_POINTS', \ - f'Invalid KPT file {fn}, first line must be "K_POINTS"' - # because there are modes like "Line_Cartesian", we need to - # split the second line by '_' - return parser_switch[raw[2].lower().split('_')[0]](raw) - -class TestAbacusCalculatorIOUtil(unittest.TestCase): - - def setUp(self): - here = Path(__file__).parent - self.testfiles = here / 'testfiles' - - def test_input_io(self): - fn = self.testfiles / 'input-script' - data = read_input(fn) - with tempfile.NamedTemporaryFile(mode='w') as f: - fn_ = Path(f.name) - write_input(data, fn_.as_posix()) - data_ = read_input(fn_.as_posix()) - self.assertDictEqual(data, data_) - # will automatically delete the file after the context manager - - def test_file_safe_backup_rotates_numbered_backups(self): - with tempfile.TemporaryDirectory() as tmpdir: - workdir = Path(tmpdir) - live = workdir / 'STRU' - live.write_text('live') - (workdir / 'STRU.bak.0').write_text('bak0') - (workdir / 'STRU.bak.1').write_text('bak1') - (workdir / 'STRU.bak.01').write_text('bak01') - (workdir / 'STRU.bak.note').write_text('note') - - file_safe_backup(live) - - self.assertFalse(live.exists()) - self.assertEqual((workdir / 'STRU.bak.0').read_text(), 'live') - self.assertEqual((workdir / 'STRU.bak.1').read_text(), 'bak0') - self.assertEqual((workdir / 'STRU.bak.2').read_text(), 'bak1') - self.assertEqual((workdir / 'STRU.bak.01').read_text(), 'bak01') - self.assertEqual((workdir / 'STRU.bak.note').read_text(), 'note') - - def test_stru_io(self): - from ase.units import Bohr, Angstrom - nacl = bulk('NaCl', 'rocksalt', a=5.64) - write_stru( - nacl, - outdir=self.testfiles, - pp_file={ - 'Na': 'Na.pz-bhs.UPF', - 'Cl': 'Cl.pz-bhs.UPF' - }, - orb_file={ - 'Na': 'Na_gga_6au_100Ry_2s2p1d.orb', - 'Cl': 'Cl_gga_6au_100Ry_2s2p1d.orb', - }, - ) - stru_ = read_stru(self.testfiles / 'STRU') - (self.testfiles / 'STRU').unlink() - - self.assertIsInstance(stru_, dict) - for k in ['lat', 'species', 'coord_type']: - self.assertIn(k, stru_) - for k in ['const', 'vec']: - self.assertIn(k, stru_['lat']) - for k in ['symbol', 'mass', 'pp_file', 'orb_file', 'mag_each', 'natom', 'atom']: - for s in stru_['species']: - self.assertIn(k, s) - self.assertEqual(stru_['coord_type'], 'Cartesian') - - self.assertEqual(stru_['lat']['const'], Angstrom / Bohr) - self.assertTrue(np.allclose(stru_['lat']['vec'], np.array(nacl.get_cell()))) - self.assertIsInstance(stru_['species'], list) - - for s in stru_['species']: - self.assertIsInstance(s, dict) - self.assertEqual(s['natom'], 1) - self.assertIsInstance(s['atom'], list) - self.assertEqual(s['natom'], len(s['atom'])) - for a in s['atom']: - self.assertIsInstance(a, dict) - for k in ['coord', 'm', 'v']: - self.assertIn(k, a) - self.assertEqual(a['m'], [1, 1, 1]) - self.assertEqual(a['v'], [0.0, 0.0, 0.0]) - - self.assertEqual(stru_['species'][0]['symbol'], 'Na') - self.assertEqual(stru_['species'][1]['symbol'], 'Cl') - self.assertEqual(stru_['species'][0]['mass'], ATOM_MASS['Na']) - self.assertEqual(stru_['species'][1]['mass'], ATOM_MASS['Cl']) - self.assertEqual(stru_['species'][0]['pp_file'], 'Na.pz-bhs.UPF') - self.assertEqual(stru_['species'][1]['pp_file'], 'Cl.pz-bhs.UPF') - self.assertEqual(stru_['species'][0]['orb_file'], 'Na_gga_6au_100Ry_2s2p1d.orb') - self.assertEqual(stru_['species'][1]['orb_file'], 'Cl_gga_6au_100Ry_2s2p1d.orb') - - def test_read_stru_with_lattice_parameter(self): - # STRU that uses LATTICE_PARAMETER instead of LATTICE_VECTORS - # (issue #7555: previously raised KeyError on 'LATTICE_PARAMETERS') - content = ( - "ATOMIC_SPECIES\n" - "Si 28.085 Si.upf\n" - "\n" - "LATTICE_CONSTANT\n" - "10.2\n" - "\n" - "LATTICE_PARAMETER\n" - "5.43 5.43 5.43 90 90 90\n" - "\n" - "ATOMIC_POSITIONS\n" - "Direct\n" - "\n" - "Si\n" - "0.0\n" - "2\n" - "0.0 0.0 0.0 1 1 1\n" - "0.25 0.25 0.25 1 1 1\n" - ) - with tempfile.TemporaryDirectory() as tmpdir: - fn = Path(tmpdir) / 'STRU' - fn.write_text(content) - stru = read_stru(fn) - self.assertEqual(stru['lat']['const'], 10.2) - self.assertNotIn('vec', stru['lat']) - self.assertEqual(stru['lat']['param'], [5.43, 5.43, 5.43, 90.0, 90.0, 90.0]) - self.assertEqual(stru['species'][0]['symbol'], 'Si') - self.assertEqual(stru['species'][0]['natom'], 2) - - def test_write_stru_preserves_first_occurrence_species_order(self): - atoms = Atoms( - symbols=['C', 'C', 'Pt', 'H', 'H'], - positions=np.zeros((5, 3)), - cell=np.eye(3), - ) - - with tempfile.TemporaryDirectory() as tmpdir: - write_stru( - atoms, - outdir=tmpdir, - pp_file={'C': 'C.upf', 'Pt': 'Pt.upf', 'H': 'H.upf'}, - ) - stru = read_stru(Path(tmpdir) / 'STRU') - - self.assertEqual([s['symbol'] for s in stru['species']], ['C', 'Pt', 'H']) - self.assertEqual([s['natom'] for s in stru['species']], [2, 1, 2]) - - def test_write_stru_uses_ase_constraints_for_mobility(self): - atoms = Atoms( - symbols=['C', 'C', 'Pt', 'H'], - positions=np.zeros((4, 3)), - cell=np.eye(3), - ) - atoms.set_constraint([ - FixAtoms(indices=[0]), - FixCartesian(2, mask=[True, False, True]), - ]) - - with tempfile.TemporaryDirectory() as tmpdir: - write_stru( - atoms, - outdir=tmpdir, - pp_file={'C': 'C.upf', 'Pt': 'Pt.upf', 'H': 'H.upf'}, - ) - stru = read_stru(Path(tmpdir) / 'STRU') - - self.assertEqual(stru['species'][0]['atom'][0]['m'], [0, 0, 0]) - self.assertEqual(stru['species'][0]['atom'][1]['m'], [1, 1, 1]) - self.assertEqual(stru['species'][1]['atom'][0]['m'], [0, 1, 0]) - self.assertEqual(stru['species'][2]['atom'][0]['m'], [1, 1, 1]) - - def test_kpt_io(self): - kpt = { - 'mode': 'point', - 'coordinate': 'Cartesian', - 'nk': 1, - 'kpoints': [(0.0, 0.0, 0.0)], - 'weights': [1] - } - with tempfile.NamedTemporaryFile(mode='w') as f: - fn_ = Path(f.name) - write_kpt(kpt, fn_.as_posix()) - self.assertDictEqual(kpt, read_kpt(fn_.as_posix())) - - kpt = { - 'mode': 'mp-sampling', - 'nk': (5, 5, 5), - 'kshift': (0, 0, 0), - 'gamma-centered': True, - } - with tempfile.NamedTemporaryFile(mode='w') as f: - fn_ = Path(f.name) - write_kpt(kpt, fn_.as_posix()) - self.assertDictEqual(kpt, read_kpt(fn_.as_posix())) - - # kpt = { - # 'mode': 'line', - # 'nk': 6, - # 'kpoints': [ - # (0.0, 0.0, 0.0), - # (0.5, 0.5, 0.5), - # (0.0, 0.5, 0.5), - # (0.5, 0.5, 0.0), - # (0.5, 0.0, 0.5), - # (0.0, 0.0, 0.5), - # ], - # 'nkinterpl': [10, 10, 10, 10, 10, 1], - # 'coordinate': 'Cartesian', - # } - # with tempfile.NamedTemporaryFile(mode='w') as f: - # fn_ = Path(f.name) - # write_kpt(kpt, fn_.as_posix()) - # self.assertDictEqual(kpt, read_kpt(fn_.as_posix())) - - @unittest.skip('') - def test_load_pseudo(self): - pseudo_dir = self.testfiles / 'pporb' - pseudo_map = load_pseudo(pseudo_dir) - self.assertEqual(pseudo_map['O'], 'O.upf') - self.assertEqual(pseudo_map['Si'], 'Si.upf') - self.assertEqual(pseudo_map['In'], 'In.upf') - self.assertEqual(pseudo_map['As'], 'As.pbe-n-rrkjus_psl.0.2.UPF') - self.assertEqual(pseudo_map['Ga'], 'Ga.pbe-dn-kjpaw_psl.1.0.0.UPF') - - @unittest.skip('') - def test_load_orbital(self): - orbital_dir = self.testfiles / 'pporb' - orbital_map = load_orbital(orbital_dir) - self.assertEqual(orbital_map['O'], 'O_gga_6au_100Ry_2s2p1d.orb') - self.assertEqual(orbital_map['Si'], 'Si_gga_7au_100Ry_2s2p1d.orb') - self.assertEqual(orbital_map['In'], 'In_gga_8au_100Ry_1s1p1d.orb') - - orbital_map = load_orbital(orbital_dir, efficiency=False) - self.assertEqual(orbital_map['O'], 'O_gga_6au_100Ry_2s2p1d.orb') - self.assertEqual(orbital_map['Si'], 'Si_gga_7au_100Ry_2s2p1d.orb') - self.assertEqual(orbital_map['In'], 'In_gga_8au_100Ry_2s2p2d1f.orb') - - def test_write_stru_with_magmom(self): - # from issue #6516: https://github.com/deepmodeling/abacus-develop/issues/6516 - atoms = Atoms( - symbols=['Co', 'Cr', 'Ni', 'Co'], - cell=np.eye(3), - magmoms=[1, 2, 3, 1] - ) - with tempfile.NamedTemporaryFile(mode='w') as f: - write_stru( - stru=atoms, - outdir=self.testfiles, - pp_file={ # as if we have some pseudopotentials - 'Co': 'Co.upf', 'Cr': 'Cr.upf', 'Ni': 'Ni.upf', 'Co': 'Co.upf', - }, - orb_file={ # as if we have some orbitals - 'Co': 'Co.orb', 'Cr': 'Cr.orb', 'Ni': 'Ni.orb', 'Co': 'Co.orb', - }, - fname=f.name, - ) - stru = read_stru(f.name) - # Co - self.assertEqual(stru['species'][0]['mag_each'], 0.0) - self.assertEqual(stru['species'][0]['atom'][0]['mag'], 1.0) - self.assertEqual(stru['species'][0]['atom'][1]['mag'], 1.0) - # Cr - self.assertEqual(stru['species'][1]['mag_each'], 0.0) - self.assertEqual(stru['species'][1]['atom'][0]['mag'], 2.0) - # Ni - self.assertEqual(stru['species'][2]['mag_each'], 0.0) - self.assertEqual(stru['species'][2]['atom'][0]['mag'], 3.0) - - # then the non-colinear case - atoms = Atoms( - symbols=['Co', 'Cr', 'Ni', 'Co'], - cell=np.eye(3), - magmoms=np.array([ - [1, 0, 0], - [0, 2, 0], - [0, 0, 3], - [1, 0, 0], - ]) - ) - with tempfile.NamedTemporaryFile(mode='w') as f: - write_stru( - stru=atoms, - outdir=self.testfiles, - pp_file={ # as if we have some pseudopotentials - 'Co': 'Co.upf', 'Cr': 'Cr.upf', 'Ni': 'Ni.upf', 'Co': 'Co.upf', - }, - orb_file={ # as if we have some orbitals - 'Co': 'Co.orb', 'Cr': 'Cr.orb', 'Ni': 'Ni.orb', 'Co': 'Co.orb', - }, - fname=f.name, - ) - stru = read_stru(f.name) - # Co - self.assertEqual(stru['species'][0]['mag_each'], 0.0) - self.assertEqual(stru['species'][0]['atom'][0]['mag'], ('Cartesian', [1.0, 0.0, 0.0])) - self.assertEqual(stru['species'][0]['atom'][1]['mag'], ('Cartesian', [1.0, 0.0, 0.0])) - # Cr - self.assertEqual(stru['species'][1]['mag_each'], 0.0) - self.assertEqual(stru['species'][1]['atom'][0]['mag'], ('Cartesian', [0.0, 2.0, 0.0])) - # Ni - self.assertEqual(stru['species'][2]['mag_each'], 0.0) - self.assertEqual(stru['species'][2]['atom'][0]['mag'], ('Cartesian', [0.0, 0.0, 3.0])) - -if __name__ == '__main__': - unittest.main() diff --git a/interfaces/ASE_interface/abacuslite/io/latestio.py b/interfaces/ASE_interface/abacuslite/io/latestio.py deleted file mode 100644 index 2b5e4ac39a9..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/latestio.py +++ /dev/null @@ -1,712 +0,0 @@ -'''implements the parser for the latest version of ABACUS''' -import re -import shutil -import unittest -from pathlib import Path -from typing import Dict, List, Optional, Tuple - -import numpy as np -from ase.atoms import Atoms -from ase.calculators.singlepoint import ( - SinglePointKPoint, - SinglePointDFTCalculator -) -from ase.units import GPa -from ase.stress import full_3x3_to_voigt_6_stress - -# some output formats are not updated, -# for these cases, we import from the legacyio module -from abacuslite.io.legacyio import ( - read_kpoints_from_running_log, - read_energies_from_running_log, - read_traj_from_md_dump, - read_magmom_from_running_log, - is_invalid_arr, - find_final_info_with_iter_header -) - -def read_esolver_type_from_running_log(src: str | Path | List[str]) \ - -> str: - ''' - read the esolver type from the ABACUS running log file. - - Parameters - ---------- - fn : str - The path to the ABACUS running log file. - - Returns - ------- - str - The esolver type used in the ABACUS calculation. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the line with information like: - # "#ENERGY SOLVER# ksdft_lcao" - lines = [ - re.match(r'#ENERGY SOLVER#\s+(\S+)', l) - for l in raw - ] - eslvtyp = [m.group(1) for m in lines if m is not None] - assert len(set(eslvtyp)) == 1, \ - f'Inconsistent esolver type: {set(eslvtyp)}' - return eslvtyp[0] - -def read_band_from_running_log(src: str | Path | List[str]): - '''in the latest branch, the band information is removed from running log. - This function is only for backward compatibility. To get the band information, - please call the "read_band_from_eig_occ" function.''' - return None - -def read_band_from_eig_occ(src: str | Path | List[str]) \ - -> List[Dict[str, np.ndarray]]: - '''read the OUT.${suffix}/eig_occ.txt - - Parameters - ---------- - src : str or Path or list of str - The path to the ABACUS running log file or the return of the readlines() method. - - Returns - ------- - list of dict - A list of dictionaries containing the k-points and band energies. - Each dictionary has keys 'k' and 'e', where 'k' is a list of k-point - coordinates and 'e' is a numpy array of band energies. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - nframe = len([l for l in raw if l.endswith('# ionic step')]) - ekb_leading_pat = r'spin=(\d)\s+k-point=(\d+)/(\d+)\s+Cartesian=\s*' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'\(\d+\s+plane wave\)' - # from the ekb leading line, there are nspin, nk information, also the - # coordinate of kpoints - iekb = [i for i, l in enumerate(raw) if re.match(ekb_leading_pat, l)] - assert len(iekb) > 0, f'No k-point found' - - k_raw = [re.match(ekb_leading_pat, raw[i]).groups() for i in iekb] - - # spin - ispin = [int(g[0]) for g in k_raw] - assert all(i in [1, 2] for i in ispin) # what about nspin 4? - nspin = len(set(ispin)) - assert nspin in [1, 2] - - # k-points - nk = [int(g[2]) for g in k_raw] - nk = set(nk) - assert len(nk) == 1, f'Sampling on k-points changed during the calculation: {nk}' - nk = nk.pop() - ik = [int(g[1]) for g in k_raw] - assert all(i-1 in range(nk) for i in ik), f'k-point index out of range: {ik}' - assert len(ik) == nframe * nspin * nk - k = np.array([[float(g[i]) for i in [3, 6, 9]] for g in k_raw]) - assert k.shape == (nframe * nspin * nk, 3) - k = k.reshape((nframe, nspin, nk, 3)) - - ekbpat = r'\d+\s+' - ekbpat += r'(-?\d+(\.\d+)?(e[+-]\d+)?)\s+' - ekbpat += r'(\d+(\.\d+)?(e[+-]\d+)?)' - iekb = [i for i, l in enumerate(raw) if re.match(ekbpat, l)] - assert len(iekb) > 0, f'No band energy found' - assert len(iekb) % (nspin*nk) == 0 - nb = len(iekb) // (nframe * nspin * nk) - ekb_raw = np.array([list(map(float, raw[i].split()[1:])) for i in iekb]) - assert ekb_raw.shape == (nframe * nspin * nk * nb, 2) - ekb_raw = ekb_raw.reshape(nframe, nspin, nk, nb, 2) - ekb, occ = ekb_raw[:, :, :, :, 0], ekb_raw[:, :, :, :, 1] - - return [{'k': ki, 'e': eki, 'occ': occi} - for ki, eki, occi in zip(k, ekb, occ)] - -def read_traj_from_running_log(src: str | Path | List[str]) \ - -> List[Dict[str, np.ndarray|str]]: - ''' - read the trajectory from the ABACUS running log file. - NOTE: in MD runs, the trajectory is not recorded in the running log file. - - Parameters - ---------- - fn : str - The path to the ABACUS running log file. - - Returns - ------- - list of dict - A list of dictionaries containing the coordinate system, cell, elements - and the coordinates of the atoms. Each dictionary has keys 'coordinate', - 'cell', 'cell_unit', 'alat_in_angstrom', 'elem', and 'coords'. - The values are numpy arrays or strings. - - 'coordinate': string, the coordinate system, e.g., 'Cartesian' or 'Direct' - - 'cell': numpy array of shape (3, 3) - - 'cell_unit': string, the unit of the cell, e.g., 'Angstrom' - - 'alat_in_angstrom': float, the lattice constant in Angstrom - - 'elem': list of strings, the chemical symbols of the elements - - 'coords': numpy array of shape (natoms, 3), the coordinates of the atoms - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the total number of atoms - # r'TOTAL ATOM NUMBER = (\d+)' - natoms = [re.search(r'TOTAL ATOM NUMBER\s*=\s*(\d+)', l) for l in raw] - natoms = [int(n.group(1)) for n in natoms if n] - assert len(set(natoms)) == 1, \ - f'Inconsistent number of atoms: {set(natoms)}' - natoms = natoms[0] - - # search for the coordinate system - # r'^([DIRECT|CARTESIAN]) COORDINATES$' - coordinate = [re.match(r'^(DIRECT|CARTESIAN) COORDINATES', l) for l in raw] - coordinate = [l.group(1).lower().capitalize() for l in coordinate if l] - assert len(set(coordinate)) == 1, \ - f'Inconsistent coordinate system: {set(coordinate)}' - coordinate = coordinate[0] - - # search for the cell, but first get the "a0": lattice constant - # r'lattice constant (Angstrom) = (-?\d+(\.\d+)?)' - a0 = [re.search(r'lattice constant \(Angstrom\)\s*=\s*(-?\d+(\.\d+)?)', l, - re.IGNORECASE) for l in raw] - a0 = [float(n.group(1)) for n in a0 if n] - assert len(set(a0)) == 1, f'Inconsistent lattice constant: {set(a0)}' - a0 = a0[0] - # then the cell - # r'^Lattice vectors: \(Cartesian coordinate: in unit of a\_0\)$' - icell = [i for i, l in enumerate(raw) - if re.match(r'^Lattice vectors: \(Cartesian coordinate: in unit of a_0\)$', l)] - assert len(icell) > 0, f'No cell found' - # nframe = len(icell) # will be 1 for cell-invariant MD - # assert nframe > 0, f'Invalid trajectory with length {nframe}') - cell_raw = [raw[i+1:i+1+3] for i in icell] - cell = [np.array([list(map(float, l.split())) for l in c]) * a0 - for c in cell_raw] # convert to Angstrom - assert all(c.shape == (3, 3) for c in cell), \ - f'Unexpected shape of cell: {[c.shape for c in cell]}' - - # search for the elements and coordinates - coord_leading_pat = r'atom\s+x\s+y\s+z\s+mag' # what about nspin 4? - icoord = [i for i, l in enumerate(raw) if re.match(coord_leading_pat, l)] - nframe = len(icoord) - coord_raw = np.array([l.split() for i in icoord for l in raw[i+1:i+1+natoms]]) - elem = coord_raw[:, 0].astype(str).reshape((nframe, natoms)) - coords = coord_raw[:, 1:4].astype(float).reshape((nframe, natoms, 3)) - mag = coord_raw[:, 4].astype(float).reshape((nframe, natoms, )) - - cell = np.array(cell).reshape(-1, 3, 3) - if cell.shape[0] == 1: - cell = [cell[0] for _ in range(nframe)] - assert len(cell) == nframe, \ - f'Unexpected number of cells: {len(cell)}, ' \ - f'expected {nframe}' - return [{ - 'coordinate': coordinate, - 'cell': c, - 'cell_unit': 'Angstrom', - 'alat_in_angstrom': a0, - 'elem': e, - 'coords': co - } for c, e, co in zip(cell, elem, coords)] - -def read_forces_from_running_log(src: str | Path | List[str]) \ - -> List[np.ndarray]: - ''' - read the forces from the ABACUS running log file. - - Parameters - ---------- - src : str or Path or list of str - The path to the ABACUS running log file or the return of the readlines() method. - - Returns - ------- - list of numpy array - A list of numpy arrays containing the forces on each atom. - Each array has shape (natoms, 3). - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # iteratively search for the forces, which led by the title `#TOTAL-FORCE (eV/Angstrom)#` - forces, istart = [], 0 - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'#\s*TOTAL\-FORCE\s*\(eV\s*/Angstrom\)\s*#', l, re.IGNORECASE): - ith = i - break - if ith is None: # no forces found - break - # otherwise - # search for the first line that matches the pattern - FORCEPAT_ = r'\s*([A-Z][a-z]?\d+)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(FORCEPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = None # index of the last line of the table body - for j, l in enumerate(raw[istart+ith+1+itb:]): - if not re.match(FORCEPAT_, l): - jtb = j - break - if jtb is None: # no content found - break - - # truncate the force table and append - force_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - force = np.array([list(map(float, l.split()[1:])) for l in force_raw]) - forces.append(force) - - # update the istart - istart += ith + itb + jtb + 1 - - return forces - -def read_stress_from_running_log(src: str | Path | List[str]) \ - -> List[np.ndarray]: - ''' - read the stress from the ABACUS running log file. - - Parameters - ---------- - src : str or Path or list of str - The path to the ABACUS running log file or the return of the readlines() method. - - Returns - ------- - list of numpy array - A list of numpy arrays containing the stress on each atom. - Each array has shape (3, 3). - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # iteratively search for the stress, which led by the title `#TOTAL-STRESS (kbar)#` - stresses, istart = [], 0 - - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'#\s*TOTAL\-STRESS\s*\(kbar\)\s*#', l, re.IGNORECASE): - ith = i - break - if ith is None: # no stress found - break - # otherwise - # search for the first line that matches the pattern - STRESSPAT_ = r'\s*(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(STRESSPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = 3 # because the stress tensor would be a (3, 3)-matrix - - # truncate the stress table and append - stress_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - stress = np.array([list(map(float, l.split())) for l in stress_raw]).reshape(3, 3) - # unit: kbar -> GPa - stresses.append(-0.1 * stress * GPa) - - # update the istart - istart += ith + itb + jtb + 1 - - return stresses - -def read_iter_header_from_running_log(src: str | Path | List[str]) \ - -> List[Tuple[int, int]]: - ''' - read the "iteration header" from the running log, useful for determining - which is the final iteration. The "iteration header" is defined as: - ``` - --> #ION MOVE# 1 #ELEC ITER# 3 - ``` - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - HEADER_PAT = r'-->\s+#ION MOVE#\s+(\d+)\s+#ELEC ITER#\s+(\d+)' - res = [re.findall(HEADER_PAT, l) for l in raw] - - return [(int(pack[0][0]), int(pack[0][1])) for pack in res if pack] - -def read_abacus_out(fileobj, - index=slice(None), - results_required=True, - sort_atoms_with: Optional[List[int]] = None) -> Atoms | List[Atoms]: - '''Reads the ABACUS output files. This function would be called by - the AbacusTemplate.read_results() function. The detailed call stack - is as follows: - get_potential_energy() - -> get_property() - -> calculate() - -> read_results() - -> read_abacus_out() *here* - - To use this function as a standalone one, the fileobj should be - the return of the open() function, which is a TextIOWrapper object: - >>> with open(fn) as fileobj: - ... read_abacus_out(fileobj) - - Parameters - ---------- - fileobj : str | Path | TextIOWrapper - The file object to read. - index : slice - The index of the frames to read. - results_required : bool - Whether the results are required. If True, the results will be - returned. If False, the results will not be returned. This parameter - is not used. - sort_atoms_with: Optional[List[int]] - The sort order of the atoms. If not None, the atoms will be sorted - according to the order in the list. - - Returns - ------- - atoms : Atoms | List[Atoms] - The atoms object, whose calculator is the `SinglePointDFTCalculator`. - ''' - assert isinstance(fileobj, Path) - # it is required that the running log file is passed as the Path object - # so that the band structure file ``eig_occ.txt`` can be located as the - # one in the same folder, otherwise the reading of elecstate will be - # invalid and cause the failure - with open(fileobj) as f: - abacus_lines = f.readlines() - - # read the esolver type - eslvtyp = read_esolver_type_from_running_log(abacus_lines) - # FIXME: implement read_ksdft_esolver_out instead of read_abacus_out to - # make flexible esolver support - - # read the structure, with the cell, elem, etc. (nframe) - # if it is MD run, the trajectories will be in the file MD_dump, instead - # of the running log - trajectory = read_traj_from_running_log(abacus_lines) \ - if fileobj.name != 'running_md.log' else \ - read_traj_from_md_dump(fileobj.parent / 'MD_dump') - - # read the eigenvalues (nframe, nk, nbnd) - elecstate = read_band_from_eig_occ(fileobj.parent / 'eig_occ.txt') - - # read the atomic forces (nframe, nat, 3) - forces = read_forces_from_running_log(abacus_lines) - - # read the stress (nframe, 3, 3), but may be None - stress = read_stress_from_running_log(abacus_lines) - - # read all kpoints tables (but only want the first, spinless, with ibz2bz) - k, _, _, _, _ = read_kpoints_from_running_log(abacus_lines) - # unpack the kpoints information - kvecd, wk, _ = k - - # FIXME: in principle, the two spin channels share the same set of - # the kpoints, so it is not needed to have two sets of kpoints - # and it is assumed that the sampling of kpoints won't change during - # the simulation, which is, not exactly to be true for the NPT-MD - # runs. - - # only keep the energies of the final iteration for each ion step - energies = find_final_info_with_iter_header( - read_energies_from_running_log(abacus_lines)[1], - read_iter_header_from_running_log(abacus_lines) - ) - - # read the magmom - magmom = read_magmom_from_running_log(abacus_lines) - - # roughly check the integrity of data from their length consistency - assert len(trajectory) == len(energies), \ - f'Inconsistent length: {len(trajectory)} != {len(energies)}' - assert len(trajectory) == len(elecstate), \ - f'Inconsistent length: {len(trajectory)} != {len(elecstate)}' - if len(forces) == 0: - forces = [None] * len(trajectory) - assert len(trajectory) == len(forces), \ - f'Inconsistent length: {len(trajectory)} != {len(forces)}' - if len(stress) == 0: - stress = [None] * len(trajectory) - assert len(trajectory) == len(stress), \ - f'Inconsistent length: {len(trajectory)} != {len(stress)}' - if len(magmom) == 0: - magmom = [np.zeros(shape=(len(trajectory[0]['elem'])))] * len(trajectory) - - # loop over the frame... - images, ind = [], sort_atoms_with - for frame, estat, mag, frs, strs, ener in zip( - trajectory, elecstate, magmom, forces, stress, energies): - # for each frame, a structure can be defined - ind = ind or list(range(len(frame['elem']))) - atoms = Atoms(symbols=np.array(frame['elem'])[ind].tolist(), - positions=frame['coords'][ind], - cell=frame['cell'], - magmoms=mag[ind]) - # from result, a calculator can be assembled - # however, sometimes the force and stress is not calculated - # in this case, we set them to None - frs = None if is_invalid_arr(frs) else frs[ind] - strs = None if is_invalid_arr(strs) else full_3x3_to_voigt_6_stress(strs) - calc = SinglePointDFTCalculator(atoms=atoms, energy=ener['E_KohnSham'], - free_energy=ener['E_KohnSham'], - forces=frs, stress=strs, - magmoms=mag[ind], efermi=ener['E_Fermi'], - ibzkpts=kvecd, dipole=None) - # import the eigenvalues and occupations kpoint-by-kpoint - calc.kpts = [] - for ispn, (ekb, occ) in enumerate(zip(estat['e'], estat['occ'])): # loop over the spin - calc.kpts += [SinglePointKPoint(weight=wk[ik], - s=ispn, - k=kvecd[ik], - eps_n=ekb[ik,:], - f_n=occ[ik,:]) - for ik in range(len(kvecd))] - # attach the calculator to the atoms - atoms.calc = calc - images.append(atoms) - - return images[index] - -class TestLatestIO(unittest.TestCase): - ''' - test the validities of functions for the I/O of the latest version of ABACUS - ''' - here = Path(__file__).parent - testfiles = here / 'testfiles' - - def test_read_abacus_out_reorders_calculator_magmoms(self): - import tempfile - from unittest.mock import patch - - frame = { - 'elem': ['Na', 'Na', 'Cl'], - 'coords': np.array([[0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [2.0, 0.0, 0.0]]), - 'cell': np.eye(3), - } - elecstate = [{ - 'k': np.zeros((1, 1, 3)), - 'e': np.zeros((1, 1, 1)), - 'occ': np.ones((1, 1, 1)), - }] - energies = [{'E_KohnSham': -1.0, 'E_Fermi': 0.0}] - kpoints = ((np.zeros((1, 3)), np.ones(1), None), None, None, None, None) - - with tempfile.TemporaryDirectory() as tmpdir: - running_log = Path(tmpdir) / 'running_scf.log' - running_log.write_text('') - (Path(tmpdir) / 'eig_occ.txt').write_text('') - with patch(__name__ + '.read_esolver_type_from_running_log', return_value='ksdft'), \ - patch(__name__ + '.read_traj_from_running_log', return_value=[frame]), \ - patch(__name__ + '.read_band_from_eig_occ', return_value=elecstate), \ - patch(__name__ + '.read_forces_from_running_log', return_value=[]), \ - patch(__name__ + '.read_stress_from_running_log', return_value=[]), \ - patch(__name__ + '.read_kpoints_from_running_log', return_value=kpoints), \ - patch(__name__ + '.read_energies_from_running_log', return_value=([], [])), \ - patch(__name__ + '.read_iter_header_from_running_log', return_value=[]), \ - patch(__name__ + '.find_final_info_with_iter_header', return_value=energies), \ - patch(__name__ + '.read_magmom_from_running_log', return_value=[np.array([10.0, 20.0, 30.0])]): - atoms = read_abacus_out(running_log, sort_atoms_with=[0, 2, 1])[0] - - self.assertEqual(atoms.get_chemical_symbols(), ['Na', 'Cl', 'Na']) - self.assertTrue(np.allclose(atoms.calc.results['magmoms'], [10.0, 30.0, 20.0])) - - def test_read_esolver_type_from_running_log(self): - self.assertEqual( - read_esolver_type_from_running_log( - self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax'), - 'ksdft_lcao' - ) - self.assertEqual( - read_esolver_type_from_running_log( - self.testfiles / 'lcao-symm0-nspin2-multik-relax'), - 'ksdft_lcao' - ) - self.assertEqual( - read_esolver_type_from_running_log( - self.testfiles / 'lcao-symm1-nspin1-multik-scf'), - 'ksdft_lcao' - ) - self.assertEqual( - read_esolver_type_from_running_log( - self.testfiles / 'pw-symm0-nspin4-gamma-md'), - 'ksdft_pw' - ) - - def test_read_band_from_running_log(self): - self.assertIsNone( - read_band_from_running_log(self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax') - ) - - def test_read_band_from_eig_occ(self): - fn = self.testfiles / 'nspin4-gamma-eigocc' - elecstate = read_band_from_eig_occ(fn) - # 2 frames, 1 spin channel (nspin 4), 1 kpoint, 35 bands - self.assertEqual(len(elecstate), 2) - for es in elecstate: # loop over frames - self.assertIn('k', es) - self.assertEqual(es['k'].shape, (1, 1, 3)) # ispin, ik, 3 - self.assertIn('e', es) - self.assertEqual(es['e'].shape, (1, 1, 35)) # ispin, ik, nbnd - self.assertIn('occ', es) - self.assertEqual(es['occ'].shape, (1, 1, 35)) # ispin, ik, nbnd - - def test_read_traj_from_running_log(self): - traj = read_traj_from_running_log(self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax') - self.assertEqual(len(traj), 3) - - reference = [ - np.array([[4.17203 , 2.086015, 2.086015], - [2.086015, 4.17203 , 2.086015], - [2.086015, 2.086015, 4.17203 ]]), - np.array([[4.17550947, 2.05376104, 2.05376104], - [2.05811246, 4.12078078, 2.09979522], - [2.05811246, 2.09979522, 4.12078078]]), - np.array([[4.17667347, 2.04296382, 2.04296382], - [2.04876712, 4.10361705, 2.10440948], - [2.04876712, 2.10440948, 4.10361705]]) - ] - - taud = np.array([[0. , 1. , 1. ], - [0.5 , 0.5 , 0.5 ], - [0.25, 0.25, 0.25], - [0.75, 0.75, 0.75]]) - for t, c in zip(traj, reference): - self.assertEqual(t['coordinate'], 'Direct') - self.assertEqual(t['cell_unit'], 'Angstrom'), - self.assertEqual(t['alat_in_angstrom'], 4.17203) - self.assertTrue(all(e1 == e2 for e1, e2 in zip(t['elem'], np.array(['Ni1', 'Ni2', 'O', 'O'])))) - self.assertTrue(np.allclose(t['cell'], c)) - self.assertTrue(np.allclose(t['coords'] % 1, taud % 1)) - - def test_read_forces_from_running_log(self): - forces = read_forces_from_running_log(self.testfiles / 'lcao-symm0-nspin2-multik-relax') - self.assertEqual(len(forces), 2) # two frames - reference = [ - np.array([ - [-1.2994798821, -0.7502719805, 0.0000000000], - [ 1.2994798821, 0.7502719805, 0.0000000000], - ]), - np.array([ - [-1.6814741322, -0.9708025265, 0.0000000000], - [ 1.6814741322, 0.9708025265, 0.0000000000], - ]), - ] - for f, ref in zip(forces, reference): - self.assertTrue(np.allclose(f, ref)) - - def test_read_stress_from_running_log(self): - stresses = read_stress_from_running_log( - self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax') - self.assertEqual(len(stresses), 2) # two frames - reference = [ - np.array([ - [ 28.0257165781, -25.0814687477, -25.0814687477], - [-25.0814687477, -57.2309475134, 52.8147857580], - [-25.0814687477, 52.8147857580, -57.2309475134], - ]), - np.array([ - [ 10.5133147463,-28.8166870580, -28.8166870559], - [-28.8166870580, 0.1640200010, 7.6016822662], - [-28.8166870559, 7.6016822662, 0.1640199977], - ]), - ] - for s, ref in zip(stresses, reference): - self.assertTrue(np.allclose(s, -0.1 * GPa * ref)) - - def test_read_abacus_out_string(self): - fn = self.testfiles / 'pw-symm0-nspin4-gamma-md' - # the case that does not give the instance of Path - with self.assertRaises(AssertionError): - read_abacus_out(str(fn)) - - def test_read_pw_symm0_nspin4_gamma_md(self): - # make files ready - shutil.copy(self.testfiles / 'pw-symm0-nspin4-gamma-md', - self.testfiles / 'running_md.log') - shutil.copy(self.testfiles / 'nspin4-gamma-eigocc', - self.testfiles / 'eig_occ.txt') - shutil.copy(self.testfiles / 'nspin4-gamma-mddump', - self.testfiles / 'MD_dump') - - res = read_abacus_out(self.testfiles / 'running_md.log') - self.assertIsNotNone(res) - self.assertEqual(len(res), 2) # two frames - - for atoms in res: - self.assertIsInstance(atoms, Atoms) - self.assertIsInstance(atoms.calc, SinglePointDFTCalculator) - self.assertGreater(len(atoms.calc.kpts), 0) - # Gamma point calculation - for k in atoms.calc.kpts: - self.assertIsInstance(k, SinglePointKPoint) - - # remove the files - (self.testfiles / 'running_md.log').unlink() - (self.testfiles / 'eig_occ.txt').unlink() - (self.testfiles / 'MD_dump').unlink() - - def test_read_iter_header_from_running_log(self): - header = read_iter_header_from_running_log( - self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax') - self.assertTupleEqual(header[0], (1, 1)) - self.assertTupleEqual(header[1], (1, 2)) - self.assertTupleEqual(header[2], (2, 1)) - -if __name__ == '__main__': - unittest.main() diff --git a/interfaces/ASE_interface/abacuslite/io/legacyio.py b/interfaces/ASE_interface/abacuslite/io/legacyio.py deleted file mode 100644 index 223a31b52d1..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/legacyio.py +++ /dev/null @@ -1,1261 +0,0 @@ -import re -import shutil -import unittest -from io import TextIOWrapper -from pathlib import Path -from typing import Dict, List, Tuple, Optional, Any - -import numpy as np -from ase.atoms import Atoms -from ase.calculators.singlepoint import ( - SinglePointKPoint, - SinglePointDFTCalculator -) -from ase.units import Ry, eV, GPa, bar -from ase.stress import full_3x3_to_voigt_6_stress -# from ase.utils import reader - -__all__ = ['read_kpoints_from_running_log', - 'read_band_from_running_log', 'read_traj_from_running_log', - 'read_traj_from_md_dump', 'read_forces_from_running_log', - 'read_stress_from_running_log', 'read_energies_from_running_log', - 'read_abacus_out'] - -def parse_kpoints_table(raw: List[str]) \ - -> Tuple[np.ndarray, np.ndarray, List[int] | None]: - '''parse the lines that are the kpoints table - - Parameters - ---------- - raw : list of str - The lines of the kpoints table. - - Returns - ------- - tuple - A tuple containing the kpoints coordinates, weights and ibz2bz indexing, - or without the indexing. - ''' - # the pattern with the ibz2bz indexing - W_PAT_ = r'\s*(\d+)' - W_PAT_ += r'\s+(-?\d\.\d+)\s+(-?\d\.\d+)\s+(-?\d\.\d+)' - W_PAT_ += r'\s+(\d\.\d+)\s+(\d+)' - # the pattern without the ibz2bz indexing - WOPAT_ = r'\s*(\d+)' - WOPAT_ += r'\s+(-?\d\.\d+)\s+(-?\d\.\d+)\s+(-?\d\.\d+)' - WOPAT_ += r'\s+(\d\.\d+)' - # for each line, must match either the first or the second - - with_ibz = all(re.match(W_PAT_, l) for l in raw) - assert with_ibz or all(re.match(WOPAT_, l) for l in raw), \ - f'Unexpected format of kpoints table: {raw}' - if with_ibz: - data = [re.match(W_PAT_, l).groups() for l in raw] - k = np.array([list(map(float, ki[1:4])) for ki in data]) - w = np.array([float(ki[4]) for ki in data]) - ibz2bz = [int(ki[5]) for ki in data] - return k, w, ibz2bz - # otherwise, it must be the case without the ibz2bz - data = [re.match(WOPAT_, l).groups() for l in raw] - k = np.array([list(map(float, ki[1:4])) for ki in data]) - w = np.array([float(ki[4]) for ki in data]) - return k, w, None - -def read_kpoints_from_running_log(src: str | Path | List[str]) \ - -> Tuple[Tuple[np.ndarray, np.ndarray, List[int]], - Tuple[np.ndarray, np.ndarray], - List[Tuple[np.ndarray, np.ndarray]], - List[Tuple[np.ndarray, np.ndarray]], - List[Tuple[np.ndarray, np.ndarray]]]: - ''' - read the kpoint coordinates and weights from the running log file. Up to now, - the ABACUS version 3.10.1 LTS, the ABACUS will print 4 tables of kpoints at - first, including the one with the ibz2bz indexing, one of spinless direct - coordinates, then two spin cartesian and direct coordinates of kpoints. If - the cell would change during the calculation, then after each cell-relax step, - there will be 3 tables, including direct, cartesian and then direct (again, - but i don't know why!) kpoint coordinates. - - Parameters - ---------- - fn : str - The path to the ABACUS running log file. - - Returns - ------- - tuple - A tuple contains the first table (with ibz2bz indexing), the second table - (spinless direct coordinates), the trajectories of the third (spin-direct), - fourth (spin-cartesian) and fifth (spin-direct) tables. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw if l.strip()] - raw = [l for l in raw if l] # remove empty lines - - # the pattern of table header - THPAT_ = r'\s*(IBZ|KPOINTS)' - THPAT_ += r'\s+(DIRECT|CARTESIAN)_X\s+(DIRECT|CARTESIAN)_Y\s+(DIRECT|CARTESIAN)_Z' - THPAT_ += r'\s+WEIGHT(\s+ibz2bz)?' - # the pattern of table content, optionally with the ibz2bz data - TBPAT_ = r'\s*\d+' - TBPAT_ += r'\s+(-?\d(\.\d+)?)\s+(-?\d(\.\d+)?)\s+(-?\d(\.\d+)?)' - TBPAT_ += r'\s+(\d(\.\d+)?)(\s+\d+)?' - - # search for the kpoints table - tables, istart = [], 0 - while istart < len(raw): - # search for the table header... - ith = None - for i, l in enumerate(raw[istart:]): - if re.match(THPAT_, l): # find a table header - ith = i - break - if ith is None: # no more table - break - itb = ith + 1 # we assume there is no more header before the data - assert re.match(TBPAT_, raw[istart+itb]) # really? - - # search for the end of the table... - jtb = None - for i, l in enumerate(raw[istart+itb:]): - if not re.match(TBPAT_, l): # the next line is not a table content - jtb = i - break - if jtb is None: # no more table - break - # parse the table - ktab_raw = raw[istart+itb:istart+itb+jtb] - tables.append(parse_kpoints_table(ktab_raw)) - - # update the starting point to search for the next table - istart += itb + jtb + 1 - - # sort tables... - kibz = tables.pop(0) - # get the number of kpoints, then will use in reshaping the kpoints trajectories - nk, _ = kibz[0].shape - kspnls = (tables[0][0], tables[0][1]) # spinless direct, drop the None in [2] - kd1traj = [(t[0].reshape(-1, nk, 3), t[1].reshape(-1, nk)) # drop the [2] because it is None - for i, t in enumerate(tables) if i % 3 == 0] - kctraj = [(t[0].reshape(-1, nk, 3), t[1].reshape(-1, nk)) # drop the [2] because it is None - for i, t in enumerate(tables) if i % 3 == 1] - kd2traj = [(t[0].reshape(-1, nk, 3), t[1].reshape(-1, nk)) # drop the [2] because it is None - for i, t in enumerate(tables) if i % 3 == 2] - return kibz, kspnls, kd1traj, kctraj, kd2traj - -def read_esolver_type_from_running_log(src: str | Path | List[str]) \ - -> str: - ''' - read the esolver type from the ABACUS running log file. - - Parameters - ---------- - fn : str - The path to the ABACUS running log file. - - Returns - ------- - str - The esolver type used in the ABACUS calculation. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the line with information like: - # "The esolver type has been set to : lj_pot" - lines = [ - re.match(r'The esolver type has been set to : (\S+)', - l) for l in raw - ] - eslvtyp = [m.group(1) for m in lines if m is not None] - assert len(set(eslvtyp)) == 1, \ - f'Inconsistent esolver type: {set(eslvtyp)}' - return eslvtyp[0] - -def read_band_from_running_log(src: str | Path | List[str]) \ - -> List[Dict[str, np.ndarray]]: - ''' - read the band structure from the ABACUS running log file. This would be - helpful for MD case. - - Parameters - ---------- - src : str or Path or list of str - The path to the ABACUS running log file or the return of the readlines() method. - - Returns - ------- - list of dict - A list of dictionaries containing the k-points and band energies. - Each dictionary has keys 'k' and 'e', where 'k' is a list of k-point - coordinates and 'e' is a numpy array of band energies. - ''' - masknspin = lambda n: 1 if n in [1, 4] else n - - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the number of spin channels, bands and kpoints first - # r'nspin\s+=\s+(\d+)' - nspin = [re.search(r'nspin\s+=\s+(\d+)', l) for l in raw] - nspin = [int(n.group(1)) for n in nspin if n] - assert len(set(nspin)) == 1, \ - f'Inconsistent number of spins: {set(nspin)}' - nspin = nspin[0] - # r'NBANDS\s+=\s+(\d+)' - nbnd = [re.search(r'NBANDS\s+=\s+(\d+)', l) for l in raw] - nbnd = [int(n.group(1)) for n in nbnd if n] - assert len(set(nbnd)) == 1, \ - f'Inconsistent number of bands: {set(nbnd)}' - nbnd = nbnd[0] - # r'nkstot\snow\s=\s(\d+)' - # (there may be not the nkstot_ibz for symmetry 0 case) - # symmetry 1 - nkibz = [re.search(r'nkstot_ibz\s+=\s+(\d+)', l) for l in raw] - nkibz = [int(n.group(1)) for n in nkibz if n] - # symmetry 0, -1 - nktot = [re.search(r'nkstot\s+=\s+(\d+)', l) for l in raw] - nktot = [int(n.group(1)) for n in nktot if n] - nk = nktot if len(nkibz) == 0 else nkibz - assert len(set(nk)) == 1, \ - f'Inconsistent number of k-points: {set(nk)}' - nk = nk[0] - - # extract the band, first find the leading line - # 333/511 kpoint (Cartesian) = 0.13022 0.052050 -6.5974e-10 (190 pws) - ekb_leading_pat = r'\d+/\d+\s+kpoint\s+\(Cartesian\)\s+=\s+' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'(-?\d(\.\d+)?(e-\d+)?)\s+' \ - + r'\(\d+\s+pws\)' - iekb = [i for i, l in enumerate(raw) if re.match(ekb_leading_pat, l)] - assert len(iekb) > 0, f'No k-point found' - assert len(iekb) % (masknspin(nspin)*nk) == 0, \ - f'Inconsistent number of k-points: {len(iekb)} vs {nk}' - k_raw = [re.match(ekb_leading_pat, raw[i]).groups() for i in iekb] - k = np.array([list(map(float, ki[::3])) for ki in k_raw]) - assert k.shape == (len(iekb), 3), \ - f'Unexpected shape of k-points: {k.shape}, expected ({len(iekb)}, 3)' - - nframe = len(iekb) // (masknspin(nspin)*nk) # number of frames, for MD or relax tasks - ekb_raw = [] - for i in iekb: - rows = [] - j = i + 1 - while j < len(raw) and len(rows) < nbnd: - if re.match(ekb_leading_pat, raw[j]): - break - parts = raw[j].strip().split() - if len(parts) >= 3 and parts[0].isdigit(): - try: - band_index = int(parts[0]) - if band_index == len(rows) + 1: - rows.append([float(parts[0]), float(parts[1]), float(parts[2])]) - except ValueError: - pass - j += 1 - assert len(rows) == nbnd, \ - f'Unexpected number of band rows: {len(rows)} vs {nbnd}' - ekb_raw.extend(rows) - ekb_raw = np.array(ekb_raw) - assert ekb_raw.shape == (nframe * masknspin(nspin) * nk * nbnd, 3), \ - f'Unexpected shape of band energies: {ekb_raw.shape}. ' \ - f'Expected ({nframe * masknspin(nspin) * nk * nbnd}, 3), in which ' \ - f'nframe={nframe}, nspin={nspin}, nk={nk}, nbnd={nbnd}' - ekb = ekb_raw[:, 1].reshape(nframe, masknspin(nspin), nk, nbnd) - occ = ekb_raw[:, 2].reshape(nframe, masknspin(nspin), nk, nbnd) - # reshape the k-points to (nframe, nspin, nk, 3) - k = k.reshape(nframe, masknspin(nspin), nk, 3) - - return [{'k': ki, 'e': eki, 'occ': occi} - for ki, eki, occi in zip(k, ekb, occ)] - -def read_traj_from_running_log(src: str | Path | List[str]) \ - -> List[Dict[str, np.ndarray|str]]: - ''' - read the trajectory from the ABACUS running log file. This would be - helpful for MD case. - - Parameters - ---------- - fn : str - The path to the ABACUS running log file. - - Returns - ------- - list of dict - A list of dictionaries containing the coordinate system, cell, elements - and the coordinates of the atoms. Each dictionary has keys 'coordinate', - 'cell', 'cell_unit', 'alat_in_angstrom', 'elem', and 'coords'. - The values are numpy arrays or strings. - - 'coordinate': string, the coordinate system, e.g., 'Cartesian' or 'Direct' - - 'cell': numpy array of shape (3, 3) - - 'cell_unit': string, the unit of the cell, e.g., 'Angstrom' - - 'alat_in_angstrom': float, the lattice constant in Angstrom - - 'elem': list of strings, the chemical symbols of the elements - - 'coords': numpy array of shape (natoms, 3), the coordinates of the atoms - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the total number of atoms - # r'TOTAL ATOM NUMBER = (\d+)' - natoms = [re.search(r'TOTAL ATOM NUMBER\s*=\s*(\d+)', l) for l in raw] - natoms = [int(n.group(1)) for n in natoms if n] - assert len(set(natoms)) == 1, \ - f'Inconsistent number of atoms: {set(natoms)}' - natoms = natoms[0] - - # search for the coordinate system - # r'^([DIRECT|CARTESIAN]) COORDINATES$' - coordinate = [re.match(r'^(DIRECT|CARTESIAN) COORDINATES', l) for l in raw] - coordinate = [l.group(1).lower().capitalize() for l in coordinate if l] - assert len(set(coordinate)) == 1, \ - f'Inconsistent coordinate system: {set(coordinate)}' - coordinate = coordinate[0] - - # search for the cell, but first get the "a0": lattice constant - # r'lattice constant (Angstrom) = (-?\d+(\.\d+)?)' - a0 = [re.search(r'lattice constant \(Angstrom\)\s*=\s*(-?\d+(\.\d+)?)', l, - re.IGNORECASE) for l in raw] - a0 = [float(n.group(1)) for n in a0 if n] - assert len(set(a0)) == 1, f'Inconsistent lattice constant: {set(a0)}' - a0 = a0[0] - # then the cell - # r'^Lattice vectors: \(Cartesian coordinate: in unit of a\_0\)$' - icell = [i for i, l in enumerate(raw) - if re.match(r'^Lattice vectors: \(Cartesian coordinate: in unit of a_0\)$', l)] - assert len(icell) > 0, f'No cell found' - # nframe = len(icell) # will be 1 for NVT MD - # assert nframe > 0, f'Invalid trajectory with length {nframe}') - cell_raw = [raw[i+1:i+1+3] for i in icell] - cell = [np.array([list(map(float, l.split())) for l in c]) * a0 - for c in cell_raw] # convert to Angstrom - assert all(c.shape == (3, 3) for c in cell), \ - f'Unexpected shape of cell: {[c.shape for c in cell]}' - - # search for the elements and coordinates - # r'^tau[c|d]_([A-Z][a-z]?)\d+\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)*' - m_tau = [re.match(r'^tau[c|d]_([A-Z][a-z]?)\d+' - r'\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)', l) - for l in raw] - m_tau = [m for m in m_tau if m] - assert len(m_tau) > 0, f'No atoms found' - nframe = len(m_tau) // natoms - assert nframe > 0, f'Invalid trajectory with length {nframe}' - elem = [m.group(1) for m in m_tau] - coords = np.array([[float(m.group(2)), float(m.group(4)), float(m.group(6))] - for m in m_tau]).reshape(-1, natoms, 3) - assert coords.shape == (nframe, natoms, 3), \ - f'Unexpected shape of coordinates: {coords.shape}, ' \ - f'expected ({nframe}, {natoms}, 3)' - assert len(elem) == natoms * nframe, \ - f'Unexpected number of elements: {len(elem)}, ' \ - f'expected {natoms * nframe}' - elem = [elem[i:i+natoms] for i in range(0, len(elem), natoms)] - - # final: for volume-constant run, the cell information will only be printed for once - cell = np.array(cell).reshape(-1, 3, 3) - if cell.shape[0] == 1: - cell = [cell[0] for _ in range(nframe)] - assert len(cell) == nframe, \ - f'Unexpected number of cells: {len(cell)}, ' \ - f'expected {nframe}' - return [{ - 'coordinate': coordinate, - 'cell': c, - 'cell_unit': 'Angstrom', - 'alat_in_angstrom': a0, - 'elem': e, - 'coords': co - } for c, e, co in zip(cell, elem, coords)] - -def read_traj_from_md_dump(src: str | Path | List[str]) \ - -> List[Dict[str, np.ndarray]]: - ''' - read the trajectory from the ABACUS MD dump file - - Parameters - ---------- - fn : str - The path to the ABACUS MD dump file. - - Returns - ------- - list of dict - A list of dictionaries containing the coordinate system, cell, elements - and the coordinates of the atoms. Each dictionary has keys 'coordinate', - 'cell', 'alat_in_angstrom', 'elem', and 'coords'. - The values are numpy arrays or strings. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # search for the lattice constant - # r'LATTICE_CONSTANT:\s+(-?\d+(\.\d+)?) Angstrom' - a0 = [re.search(r'LATTICE_CONSTANT:\s+(-?\d+(\.\d+)?) Angstrom', l) for l in raw] - a0 = [float(n.group(1)) for n in a0 if n] - assert len(set(a0)) == 1, \ - f'Inconsistent lattice constant: {set(a0)}' - a0 = a0[0] - # search for the cell - # r'^LATTICE_VECTORS$' - icell = [i for i, l in enumerate(raw) if re.match(r'^LATTICE_VECTORS$', l)] - assert len(icell) > 0, f'No cell found in file' - cell_raw = [raw[i+1:i+1+3] for i in icell] - cell = [np.array([list(map(float, l.split())) for l in c]) - for c in cell_raw] - assert all(c.shape == (3, 3) for c in cell), \ - f'Unexpected shape of cell: {[c.shape for c in cell]}' - nframe = len(cell) - - # search for the elements and coordinates - # r'^\d+\s+([A-Z][a-z]?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)*' - m_tau = [re.match(r'^\d+\s+([A-Z][a-z]?)\s+' - r'(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)', l) - for l in raw] - m_tau = [m for m in m_tau if m] - assert len(m_tau) > 0, f'No atoms found' - natoms = len(m_tau) // nframe - assert natoms > 0, f'Invalid trajectory with length {nframe}.' - elem = [m.group(1) for m in m_tau] - coords = np.array([[float(m.group(2)), float(m.group(4)), float(m.group(6))] - for m in m_tau]).reshape(nframe, natoms, 3) - assert coords.shape == (nframe, natoms, 3), \ - f'Unexpected shape of coordinates: {coords.shape}, ' \ - f'expected ({nframe}, {natoms}, 3)' - assert len(elem) == natoms * nframe, \ - f'Unexpected number of elements: {len(elem)}, ' \ - f'expected {natoms * nframe}' - elem = [elem[i:i+natoms] for i in range(0, len(elem), natoms)] - assert len(elem) == nframe, \ - f'Unexpected number of elements: {len(elem)}, ' \ - f'expected {nframe}' - - return [{ - 'coordinate': 'Cartesian', - 'cell': c, - 'alat_in_angstrom': a0, - 'elem': e, - 'coords': co - } for c, e, co in zip(cell, elem, coords)] - -def read_forces_from_running_log(src: str | Path | List[str]) \ - -> List[np.ndarray]: - '''''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # iteratively search for the forces, which led by the title `TOTAL-FORCE` - forces, istart = [], 0 - - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'\s*TOTAL\-FORCE\s*\(eV\s*/Angstrom\)', l, re.IGNORECASE): - ith = i - break - if ith is None: # no forces found - break - # otherwise - # search for the first line that matches the pattern - FORCEPAT_ = r'\s*([A-Z][a-z]?\d+)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(FORCEPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = None # index of the last line of the table body - for j, l in enumerate(raw[istart+ith+1+itb:]): - if not re.match(FORCEPAT_, l): - jtb = j - break - if jtb is None: # no content found - break - - # truncate the force table and append - force_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - force = np.array([list(map(float, l.split()[1:])) for l in force_raw]) - forces.append(force) - - # update the istart - istart += ith + itb + jtb + 1 - - return forces - -def read_stress_from_running_log(src: str | Path | List[str]) \ - -> List[np.ndarray]: - '''''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - # iteratively search for the stress, which led by the title `TOTAL-STRESS` - stresses, istart = [], 0 - - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'\s*TOTAL\-STRESS\s*\(KBAR\)', l, re.IGNORECASE): - ith = i - break - if ith is None: # no stress found - break - # otherwise - # search for the first line that matches the pattern - STRESSPAT_ = r'\s*(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(STRESSPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = 3 # because the stress tensor would be a (3, 3)-matrix - - # truncate the stress table and append - stress_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - stress = np.array([list(map(float, l.split())) for l in stress_raw]).reshape(3, 3) - # unit: kbar -> GPa - stresses.append(-0.1 * stress * GPa) - - # update the istart - istart += ith + itb + jtb + 1 - - return stresses - -def read_energies_from_running_log(src: str | Path | List[str]) \ - -> List[Dict[str, float]]: - '''''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - energies_ry, energies_ev, istart = [], [], 0 - - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'\s*ENERGY\s+Rydberg\s+eV', l, re.IGNORECASE): - ith = i - break - if ith is None: # no energies found - break - # otherwise - # search for the first line that matches the pattern - ENERGYPAT_ = r'\s*E_(\S+)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(ENERGYPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = None - for j, l in enumerate(raw[istart+ith+1+itb:]): - if not re.match(ENERGYPAT_, l): - jtb = j - break - if jtb is None: # no content found - break - - # truncate the energy table and append - tb_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - # first item is the name of the energy component - # the second and third items are the ry and ev values - name, e_ry, e_ev = zip(*[l.split() for l in tb_raw]) - energies_ry.append(dict(zip(name, list(map(float, e_ry))))) - energies_ev.append(dict(zip(name, list(map(float, e_ev))))) - - # update the istart - istart += ith + itb + jtb + 1 - - return energies_ry, energies_ev - -def read_magmom_from_running_log(src: str | Path | List[str]) \ - -> List[np.ndarray]: - ''' - Read the magnetic momentum from ABACUS running log. Note: - this function returns the list of np.ndarray which has dimension of - (nat, 3), where nat is the number of atoms, and 3 stands for - the x, y and z components. - There will be problem if the output directly imported to the - ASE SinglePointDFTCalculator, because the magmom required by - ASE-side is the (N,) array, so another operation `np.linalg.norm - (magmom, axis=1)` should be called to postprocess the magmom. - - Parameters - ---------- - src : str | Path | List[str] - The source of the running log. If it is a string, it will be - treated as the path to the running log. If it is a list of - strings, it will be treated as the return of the readlines() - function. - - Returns - ------- - magmom : List[np.ndarray] - The trajectories of the magnetic moment of each atom. - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - magmom, istart = [], 0 - while istart < len(raw): - ith = None # index of the table header - for i, l in enumerate(raw[istart:]): - if re.match(r'\s*Total\sMagnetism\s\(uB\)(\s+x\s+y\s+z)?\s*$', l, - re.IGNORECASE): - ith = i - break - if ith is None: # no magmom found - break - # otherwise - # search for the first line that matches the pattern - MAGMOMPAT_ = r'\s*([A-Z][a-z]?\d+)' - MAGMOMPAT_ += r'\s+([-+]?\d+\.\d+)' - MAGMOMPAT_ += r'(\s+([-+]?\d+\.\d+)\s+([-+]?\d+\.\d+))?\s*' - itb = None # index of the first line of the table body - for i, l in enumerate(raw[istart+ith+1:]): - if re.match(MAGMOMPAT_, l): - itb = i - break - if itb is None: # no content found - break - # otherwise - jtb = None - for j, l in enumerate(raw[istart+ith+1+itb:]): - if not re.match(MAGMOMPAT_, l): - jtb = j - break - if jtb is None: # no content found - break - - # truncate the magmom table and append - tb_raw = raw[istart+ith+1+itb:istart+ith+1+itb+jtb] - # first item is the name of the magmom component - # the second to fourth items are the x, y, z components - res = list(zip(*[l.split() for l in tb_raw]))[1:] - assert len(res) in [1, 3] # colinear or non-colinear case - if len(res) == 1: # colinear case - magmom.append(np.array([list(map(float, res[-1]))]).flatten()) - else: # non-colinear case - mx, my, mz = res - magmom.append(np.array([list(map(float, (mx, my, mz))) - for mx, my, mz in zip(mx, my, mz)])) - # update the istart - istart += ith + itb + jtb + 1 - - return magmom - -def read_iter_header_from_running_log(src: str | Path | List[str]) \ - -> List[Tuple[int, int]]: - ''' - read the "iteration header" from the running log, useful for determining - which is the final iteration. The "iteration header" is defined as: - ``` - LCAO ALGORITHM --------------- ION= 1 ELEC= 100--------------------- - ``` - or for PW: - ``` - PW ALGORITHM --------------- ION=2 ELEC=1 ----------------------- - ``` - ''' - if isinstance(src, (str, Path)): - with open(src) as f: - raw = f.readlines() - else: # assume the src is the return of the readlines() - raw = src - # with open(fn) as f: - # raw = f.readlines() - raw = [l.strip() for l in raw] - raw = [l for l in raw if l] # remove empty lines - - HEADER_PAT = r'(LCAO|PW)\s+ALGORITHM\s+-+\s+ION=\s+([0-9]+)\s+ELEC=\s+([0-9]+)' - res = [re.findall(HEADER_PAT, l) for l in raw] - - return [(int(pack[0][1]), int(pack[0][2])) for pack in res if pack] - -def find_final_info_with_iter_header( - info: List[Any], - headers: List[Tuple[int, int]] - ) -> List[Any]: - '''find the energies of the final iteration - - Parameters - ---------- - info: List[Dict[str, float]] - The information that is printed after each iteration. - headers: List[Tuple[int, int]] - The "iteration header" returned from the function - `read_iter_header_from_running_log` - - Returns - ------- - List[Any] - The information of the final iteration. - ''' - headers = np.array(headers) # because indices should start from 0 - assert headers.ndim == 2 - assert headers.shape[1] == 2 - - ion = headers[:, 0].flatten() - ion, nelec = np.unique(ion, return_counts=True) - ielec = np.cumsum(nelec) - 1 - - return [info[i] for i in ielec] - -def is_invalid_arr(arr) -> bool: - '''Check if the array is invalid, including the cases of None, - empty array, and array with NaN values. - - Parameters - ---------- - arr : np.ndarray - The array to check. - - Returns - ------- - is_invalid : bool - Whether the array is invalid. - ''' - if arr is None: - return True - if isinstance(arr, list): - if len(arr) == 0: - return True - if isinstance(arr, np.ndarray): - if len(arr) == 0: - return True - if np.isnan(arr).any(): - return True - return False - -# @reader -def read_abacus_out(fileobj, - index=slice(None), - results_required=True, - sort_atoms_with: Optional[List[int]] = None) -> Atoms | List[Atoms]: - '''Reads the ABACUS output files. This function would be called by - the AbacusTemplate.read_results() function. The detailed call stack - is as follows: - get_potential_energy() - -> get_property() - -> calculate() - -> read_results() - -> read_abacus_out() *here* - - To use this function as a standalone one, the fileobj should be - the return of the open() function, which is a TextIOWrapper object: - >>> with open(fn) as fileobj: - ... read_abacus_out(fileobj) - - Parameters - ---------- - fileobj : str | Path | TextIOWrapper - The file object to read. - index : slice - The index of the frames to read. - results_required : bool - Whether the results are required. If True, the results will be - returned. If False, the results will not be returned. This parameter - is not used. - sort_atoms_with : Optional[List[int]] - The index of the atoms to sort. If None, the atoms will not be sorted. - - Returns - ------- - atoms : Atoms | List[Atoms] - The atoms object, whose calculator is the `SinglePointDFTCalculator`. - ''' - if isinstance(fileobj, (str, Path)): - with open(fileobj) as f: - abacus_lines = f.readlines() - else: # from the `with open(fn) as fileobj:` context - assert isinstance(fileobj, TextIOWrapper) - abacus_lines = fileobj.readlines() - - # read the esolver type - eslvtyp = read_esolver_type_from_running_log(abacus_lines) - - # read the structure, with the cell, elem, etc. (nframe) - trajectory = read_traj_from_running_log(abacus_lines) - # read the eigenvalues (nframe, nk, nbnd) - elecstate = read_band_from_running_log(abacus_lines) - # read the atomic forces (nframe, nat, 3) - forces = read_forces_from_running_log(abacus_lines) - # read the stress (nframe, 3, 3), but may be None - stress = read_stress_from_running_log(abacus_lines) - # read all kpoints tables (but only want the first, spinless, with ibz2bz) - k, _, _, _, _ = read_kpoints_from_running_log(abacus_lines) - # unpack the kpoints information - kvecd, wk, _ = k - # FIXME: in principle, the two spin channels share the same set of - # the kpoints, so it is not needed to have two sets of kpoints - # and it is assumed that the sampling of kpoints won't change during - # the simulation, which is, not exactly to be true for the NPT-MD - # runs. - - # only keep the energies of the final iteration for each ion step - energies = find_final_info_with_iter_header( - read_energies_from_running_log(abacus_lines)[1], - read_iter_header_from_running_log(abacus_lines) - ) - - # read the magmom - magmom = read_magmom_from_running_log(abacus_lines) - - # roughly check the integrity of data from their length consistency - assert len(trajectory) == len(energies), \ - f'Inconsistent length: {len(trajectory)} != {len(energies)}' - assert len(trajectory) == len(elecstate), \ - f'Inconsistent length: {len(trajectory)} != {len(elecstate)}' - if len(forces) == 0: - forces = [None] * len(trajectory) - assert len(trajectory) == len(forces), \ - f'Inconsistent length: {len(trajectory)} != {len(forces)}' - if len(stress) == 0: - stress = [None] * len(trajectory) - assert len(trajectory) == len(stress), \ - f'Inconsistent length: {len(trajectory)} != {len(stress)}' - if len(magmom) == 0: - magmom = [np.zeros(shape=(len(trajectory[0]['elem'])))] * len(trajectory) - - # loop over the frame... - images, ind = [], sort_atoms_with - for frame, estat, mag, frs, strs, ener in zip( - trajectory, elecstate, magmom, forces, stress, energies): - # for each frame, a structure can be defined - ind = ind or list(range(len(frame['elem']))) - atoms = Atoms(symbols=np.array(frame['elem'])[ind].tolist(), - positions=frame['coords'][ind], - cell=frame['cell'], - magmoms=mag[ind]) - - # from result, a calculator can be assembled - # however, sometimes the force and stress is not calculated - # in this case, we set them to None - frs = None if is_invalid_arr(frs) else frs[ind] - strs = None if is_invalid_arr(strs) else full_3x3_to_voigt_6_stress(strs) - calc = SinglePointDFTCalculator(atoms=atoms, energy=ener['E_KohnSham'], - free_energy=ener['E_KohnSham'], - forces=frs, stress=strs, - magmoms=mag[ind], efermi=ener['E_Fermi'], - ibzkpts=kvecd, dipole=None) - # import the eigenvalues and occupations kpoint-by-kpoint - calc.kpts = [] - for ispn, (ekb, occ) in enumerate(zip(estat['e'], estat['occ'])): # loop over the spin - calc.kpts += [SinglePointKPoint(weight=wk[ik], - s=ispn, - k=kvecd[ik], - eps_n=ekb[ik,:], - f_n=occ[ik,:]) - for ik in range(len(kvecd))] - # attach the calculator to the atoms - atoms.calc = calc - images.append(atoms) - - return images[index] - -class TestLegacyIO(unittest.TestCase): - - here = Path(__file__).parent - testfiles = here / 'testfiles' - - def test_read_band_from_running_log(self): - # nspin1 - fn = self.testfiles / 'lcao-symm1-nspin1-multik-scf_' - data = read_band_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertGreater(len(data), 0) - self.assertTrue(all('k' in d and 'e' in d and 'occ' in d for d in data)) - nspin, nk, nband = data[0]['e'].shape - self.assertEqual(nspin, 1) - self.assertEqual(nband, 24) - self.assertEqual(nk, 2) - for d in data: # for each frame - self.assertTrue(d['k'].shape == (nspin, nk, 3)) - self.assertTrue(d['e'].shape == (nspin, nk, nband)) - self.assertTrue(d['occ'].shape == (nspin, nk, nband)) - - # nspin2 - fn = self.testfiles / 'lcao-symm0-nspin2-multik-relax_' - data = read_band_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertGreater(len(data), 0) - self.assertTrue(all('k' in d and 'e' in d and 'occ' in d for d in data)) - nspin, nk, nband = data[0]['e'].shape - self.assertEqual(nspin, 2) - self.assertEqual(nband, 24) - self.assertEqual(nk, 2) - for d in data: # for each frame - self.assertTrue(d['k'].shape == (nspin, nk, 3)) - self.assertTrue(d['e'].shape == (nspin, nk, nband)) - self.assertTrue(d['occ'].shape == (nspin, nk, nband)) - - # nspin 2, cell-relax (multi-frames) - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - data = read_band_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 2) # two frames - self.assertTrue(all('k' in d and 'e' in d and 'occ' in d for d in data)) - nspin, nk, nband = data[0]['e'].shape - self.assertEqual(nspin, 2) - self.assertEqual(nband, 40) - self.assertEqual(nk, 2) - for d in data: # for each frame - self.assertTrue(d['k'].shape == (nspin, nk, 3)) - self.assertTrue(d['e'].shape == (nspin, nk, nband)) - self.assertTrue(d['occ'].shape == (nspin, nk, nband)) - - # nspin 2, MD (multi-frames) - fn = self.testfiles / 'pw-symm0-nspin4-gamma-md_' - data = read_band_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 3) - self.assertTrue(all('k' in d and 'e' in d and 'occ' in d for d in data)) - nspin, nk, nband = data[0]['e'].shape - self.assertEqual(nspin, 1) - self.assertEqual(nband, 35) - self.assertEqual(nk, 1) - for d in data: # for each frame - self.assertTrue(d['k'].shape == (nspin, nk, 3)) - self.assertTrue(d['e'].shape == (nspin, nk, nband)) - self.assertTrue(d['occ'].shape == (nspin, nk, nband)) - - def test_read_band_from_running_log_skips_non_band_rows(self): - data = read_band_from_running_log([ - 'nspin = 1', - 'NBANDS = 2', - 'nkstot = 1', - '1/1 kpoint (Cartesian) = 0.0 0.0 0.0 (1 pws)', - 'BAND ENERGY OCCUPATION', - '1 -1.0 1.0', - '2 0.5 0.0', - ]) - - self.assertEqual(len(data), 1) - self.assertEqual(data[0]['e'].shape, (1, 1, 2)) - self.assertTrue(np.allclose(data[0]['e'][0, 0], [-1.0, 0.5])) - self.assertTrue(np.allclose(data[0]['occ'][0, 0], [1.0, 0.0])) - - def test_read_band_from_running_log_does_not_cross_kpoint_blocks(self): - with self.assertRaises(AssertionError): - read_band_from_running_log([ - 'nspin = 1', - 'NBANDS = 2', - 'nkstot = 2', - '1/2 kpoint (Cartesian) = 0.0 0.0 0.0 (1 pws)', - '1 -1.0 1.0', - '2/2 kpoint (Cartesian) = 0.5 0.0 0.0 (1 pws)', - '1 -0.5 1.0', - '2 0.5 0.0', - ]) - - def test_read_abacus_out_reorders_calculator_magmoms(self): - import tempfile - from unittest.mock import patch - - frame = { - 'elem': ['Na', 'Na', 'Cl'], - 'coords': np.array([[0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [2.0, 0.0, 0.0]]), - 'cell': np.eye(3), - } - elecstate = [{ - 'k': np.zeros((1, 1, 3)), - 'e': np.zeros((1, 1, 1)), - 'occ': np.ones((1, 1, 1)), - }] - energies = [{'E_KohnSham': -1.0, 'E_Fermi': 0.0}] - kpoints = ((np.zeros((1, 3)), np.ones(1), None), None, None, None, None) - - with tempfile.NamedTemporaryFile(mode='w') as f: - with patch(__name__ + '.read_esolver_type_from_running_log', return_value='ksdft'), \ - patch(__name__ + '.read_traj_from_running_log', return_value=[frame]), \ - patch(__name__ + '.read_band_from_running_log', return_value=elecstate), \ - patch(__name__ + '.read_forces_from_running_log', return_value=[]), \ - patch(__name__ + '.read_stress_from_running_log', return_value=[]), \ - patch(__name__ + '.read_kpoints_from_running_log', return_value=kpoints), \ - patch(__name__ + '.read_energies_from_running_log', return_value=([], [])), \ - patch(__name__ + '.read_iter_header_from_running_log', return_value=[]), \ - patch(__name__ + '.find_final_info_with_iter_header', return_value=energies), \ - patch(__name__ + '.read_magmom_from_running_log', return_value=[np.array([10.0, 20.0, 30.0])]): - atoms = read_abacus_out(f.name, sort_atoms_with=[0, 2, 1])[0] - - self.assertEqual(atoms.get_chemical_symbols(), ['Na', 'Cl', 'Na']) - self.assertTrue(np.allclose(atoms.calc.results['magmoms'], [10.0, 30.0, 20.0])) - - def test_read_traj_from_running_log(self): - fn = self.testfiles / 'lcao-symm1-nspin1-multik-scf_' - data = read_traj_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 1) # it is scf run, only one frame - self.assertTrue(all('coordinate' in d and 'cell' in d and - 'elem' in d and 'coords' in d for d in data)) - for d in data: - self.assertIn(d['coordinate'], ['Cartesian', 'Direct']) - self.assertEqual(d['cell'].shape, (3, 3)) - self.assertIsInstance(d['elem'], list) - self.assertEqual(len(d['elem']), 2) - - # relax - fn = self.testfiles / 'lcao-symm0-nspin2-multik-relax_' - data = read_traj_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 1) - # relax task will only print the coordinate at the first run - # but band structure will be printed for multiple times... - self.assertTrue(all('coordinate' in d and 'cell' in d and - 'elem' in d and 'coords' in d for d in data)) - for d in data: - self.assertIn(d['coordinate'], ['Cartesian', 'Direct']) - self.assertEqual(d['cell'].shape, (3, 3)) - self.assertIsInstance(d['elem'], list) - self.assertEqual(len(d['elem']), 2) - self.assertEqual(d['coords'].shape, (2, 3)) - - # cell-relax - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - data = read_traj_from_running_log(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 3) # print each time - self.assertTrue(all('coordinate' in d and 'cell' in d and - 'elem' in d and 'coords' in d for d in data)) - for d in data: - self.assertIn(d['coordinate'], ['Cartesian', 'Direct']) - self.assertEqual(d['cell'].shape, (3, 3)) - self.assertIsInstance(d['elem'], list) - self.assertEqual(len(d['elem']), 4) - self.assertEqual(d['coords'].shape, (4, 3)) - - def test_read_traj_from_md_dump(self): - fn = self.testfiles / 'nspin4-gamma-mddump' - data = read_traj_from_md_dump(fn) - self.assertIsInstance(data, list) - self.assertEqual(len(data), 2) - self.assertTrue(all('coordinate' in d and 'cell' in d and - 'alat_in_angstrom' in d and 'elem' in d and - 'coords' in d for d in data)) - for d in data: - self.assertEqual(d['coordinate'], 'Cartesian') - self.assertEqual(d['cell'].shape, (3, 3)) - self.assertEqual(d['alat_in_angstrom'], 0.529177000000) - self.assertIsInstance(d['elem'], list) - self.assertEqual(len(d['elem']), 2) - self.assertEqual(d['coords'].shape, (2, 3)) - - def test_read_forces_from_running_log(self): - fn = self.testfiles / 'pw-symm0-nspin4-gamma-md_' - forces = read_forces_from_running_log(fn) - self.assertEqual(len(forces), 3) # 3 frames - self.assertTrue(all(f.shape == (2, 3) for f in forces)) # 2 atoms, 3 components - - def test_read_stress_from_running_log(self): - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - stress = read_stress_from_running_log(fn) - self.assertEqual(len(stress), 2) - self.assertTrue(all(s.shape == (3, 3) for s in stress)) # 3x3 matrix - - reference = np.array([ - [ 28.0257165780, -25.0814687477, -25.0814687478], - [-25.0814687477, -57.2309475134, 52.8147857580], - [-25.0814687478, 52.8147857580, -57.2309475133], - [ 7.3061116295, -27.6053387696, -27.6053387674], - [-27.6053387696, 0.8540980637, 7.6367729837], - [-27.6053387674, 7.6367729837, 0.8540980603], - ]).reshape(-1, 3, 3) - for s, sref in zip(stress, reference): - self.assertTrue(np.allclose(s, -0.1 * GPa * sref)) - - def test_read_energies_from_running_log(self): - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - energies_ry, energies_ev = read_energies_from_running_log(fn) - self.assertIsInstance(energies_ry, list) - self.assertIsInstance(energies_ev, list) - self.assertEqual(len(energies_ry), 3) - self.assertEqual(len(energies_ev), 3) - self.assertTrue(all(isinstance(e, dict) for e in energies_ry)) # dict of energies - self.assertTrue(all(isinstance(e, dict) for e in energies_ev)) - for e_ev, e_ry in zip(energies_ev, energies_ry): - # take energies of one SCF step, there are still many energy terms - self.assertEqual(len(e_ev), len(e_ry)) - for k, ei_ev in e_ev.items(): - self.assertIn(k, e_ry) - self.assertAlmostEqual(ei_ev, e_ry[k] * Ry / eV, delta=1e-2) - - def test_read_kpoints_from_running_log(self): - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - kpoints = read_kpoints_from_running_log(fn) - self.assertIsInstance(kpoints, tuple) - self.assertEqual(len(kpoints), 5) - # thus we unpack - kibz, kdspnls, kd1traj, kctraj, kd2traj = kpoints - - # kibz - self.assertIsInstance(kibz, tuple) - self.assertEqual(len(kibz), 3) - # thus we unpack - kibz, wk, ibz2bz = kibz - self.assertIsInstance(kibz, np.ndarray) - self.assertEqual(kibz.shape, (2, 3)) - self.assertIsInstance(wk, np.ndarray) - self.assertEqual(wk.shape, (2,)) - self.assertAlmostEqual(wk.sum(), 1.0, delta=1e-3) - self.assertIsInstance(ibz2bz, list) - self.assertEqual(len(ibz2bz), 2) - - # kdspnls - self.assertIsInstance(kdspnls, tuple) - self.assertEqual(len(kdspnls), 2) - # thus we unpack - kdspnls, wk = kdspnls - self.assertIsInstance(kdspnls, np.ndarray) - self.assertEqual(kdspnls.shape, (2, 3)) - self.assertIsInstance(wk, np.ndarray) - self.assertEqual(wk.shape, (2,)) - self.assertAlmostEqual(wk.sum(), 1.0, delta=1e-3) - - # kd1traj - self.assertIsInstance(kd1traj, list) - self.assertEqual(len(kd1traj), 2) # 2 cell-relax steps - for i, kd1 in enumerate(kd1traj): - # because the i=0 corresponds to the spinless case, - # it has 29 instead of 58 kpoints like the others - nk = 2 - nspin = 1 if i == 0 else 2 - wktot = 1 if i == 0 else 2 - self.assertIsInstance(kd1, tuple) - self.assertEqual(len(kd1), 2) - # thus we unpack - kd1, wk = kd1 - self.assertIsInstance(kd1, np.ndarray) - self.assertEqual(kd1.shape, (nspin, nk, 3)) - self.assertIsInstance(wk, np.ndarray) - self.assertEqual(wk.shape, (nspin, nk)) - self.assertAlmostEqual(wk.sum(), wktot, delta=1e-3) - - # kctraj and kd2traj - for ktraj in [kctraj, kd2traj]: - self.assertIsInstance(ktraj, list) - self.assertEqual(len(ktraj), 2) # 2 cell-relax steps - for i, k in enumerate(ktraj): - self.assertIsInstance(k, tuple) - self.assertEqual(len(k), 2) - # thus we unpack - k, wk = k - self.assertIsInstance(k, np.ndarray) - self.assertEqual(k.shape, (2, 2, 3)) - self.assertIsInstance(wk, np.ndarray) - self.assertEqual(wk.shape, (2, 2)) - self.assertAlmostEqual(wk.sum(), 2.0, delta=1e-3) - - def test_read_magmom_from_running_log(self): - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - magmoms = read_magmom_from_running_log(fn) - self.assertIsInstance(magmoms, list) - self.assertEqual(len(magmoms), 2) # 2 cell-relax steps - for i, magmom in enumerate(magmoms): - self.assertIsInstance(magmom, np.ndarray) - self.assertEqual(magmom.shape, (4,)) - self.assertAlmostEqual(magmom.sum(), 0.0, delta=1e-3) # AFM - - # non-colinear case - fn = self.testfiles / 'pw-symm0-nspin4-gamma-md_' - magmoms = read_magmom_from_running_log(fn) - self.assertIsInstance(magmoms, list) - self.assertEqual(len(magmoms), 2) - for magmom in magmoms: - self.assertTrue( - np.allclose(magmom, - np.array([[0. , 0. , 3.62032142], - [0. , 0. , 3.62032142]]))) - - def test_read_iter_header_from_running_log(self): - fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_' - header = read_iter_header_from_running_log(fn) - self.assertEqual(header[0], (1,1)) - self.assertEqual(header[1], (1,2)) - self.assertEqual(header[2], (2,1)) - fn = self.testfiles / 'pw-symm0-nspin4-gamma-md_' - header = read_iter_header_from_running_log(fn) - self.assertEqual(header[0], (1,1)) - self.assertEqual(header[1], (1,2)) - self.assertEqual(header[2], (1,3)) - self.assertEqual(header[3], (3,2)) - self.assertEqual(header[4], (3,3)) - - def test_find_final_info_with_iter_header(self): - info = [1, 2, 3, 4, 5, 6] - header = [[1,1], [1,2], [2,1], [3,1], [3,2], [3,3]] - final_info = find_final_info_with_iter_header(info, header) - self.assertListEqual(final_info, [info[1], info[2], info[5]]) - -if __name__ == '__main__': - unittest.main() diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/input-script b/interfaces/ASE_interface/abacuslite/io/testfiles/input-script deleted file mode 100644 index 1ecbd7e13a3..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/input-script +++ /dev/null @@ -1,34 +0,0 @@ -INPUT_PARAMETERS -#Parameters (1.General) -suffix autotest -calculation scf -device gpu -gamma_only 1 # GPU acceleration currently only support gamma_only set to 1. ### Abacus will generate/overwrite a KPT file when gamma_only is set to 1. -ks_solver cusolver # if not set, the default ks_solver is cusolver, - # you can also choose genelpa or scalapack_gvx. - -#nbands 8 -symmetry 1 - -#Parameters (2.Iteration) -ecutwfc 100 ###Energy cutoff needs to be tested to ensure your calculation is reliable.[1] -scf_thr 1e-6 -scf_nmax 100 -cal_force 1 -cal_stress 1 - -#Parameters (3.Basis) -basis_type lcao - -#Parameters (4.Smearing) -smearing_method gauss -smearing_sigma 0.002 - -#Parameters (5.Mixing) -mixing_type broyden -mixing_beta 0.3 - - -### [1] Energy cutoff determines the quality of numerical quadratures in your calculations. -### So it is strongly recommended to test whether your result (such as converged SCF energies) is -### converged with respect to the energy cutoff. diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax deleted file mode 100644 index dca11488a45..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax +++ /dev/null @@ -1,315 +0,0 @@ - READING UNITCELL INFORMATION - Number of elements = 3 - Lattice constant (Bohr) = 7.884 - Lattice constant (Angstrom) = 4.17203 - - TOTAL ATOM NUMBER = 4 - - DIRECT COORDINATES - atom x y z mag - Ni1 0.000000000000 0.000000000000 0.000000000000 2.0000 - Ni2 0.500000000000 0.500000000000 0.500000000000 -2.0000 - O 0.250000000000 0.250000000000 0.250000000000 0.0000 - O 0.750000000000 0.750000000000 0.750000000000 0.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Ni1 0 0 0 - Ni2 0 0 0 - O 0 0 0 - O 0 0 0 - - Cell volume (Bohr^3) = 245.025 - Cell volume (A^3) = 36.3089 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0.5 +0.5 - +0.5 +1 +0.5 - +0.5 +0.5 +1 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.5 -0.5 -0.5 - -0.5 +1.5 -0.5 - -0.5 -0.5 +1.5 - - #ENERGY SOLVER# ksdft_lcao - - SETUP K-POINTS - nspin = 2 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 2 - Number of irreducible k-points = 2 - - K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.5000 0 - 2 0.50000000 0.00000000 0.00000000 0.5000 1 - - nkstot now = 2 - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - - - Number of k-points in this process = 2 - Minimum distributed k-point number = 2 - nks(nspin=2) = 4 - nkstot(nspin=2) = 4 - - K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.75000000 -0.25000000 -0.25000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.75000000 -0.25000000 -0.25000000 0.5000 - - - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - ================================================================ - RELAX STEP: 1 (CELL_CHANGE# 1 IONS_CHANGE# 1) - ================================================================ - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 1 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Electron density deviation 0.095868 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -680.0824614910 -9252.9965861430 - E_Harris -681.2945326460 -9269.4876602324 - E_Fermi 1.1216473600 15.2607952433 - E_gap(k) 0.0497750855 0.6772247815 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 2 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - Total magnetism (Bohr mag/cell) = 0.00000000 - Absolute magnetism (Bohr mag/cell) = 2.80151196 - Electron number for spin up = 24.00000000 - Electron number for spin down = 24.00000000 - Electron density deviation 0.044770 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -679.9255100509 -9250.8611522481 - E_KS(sigma->0) -679.9255100509 -9250.8611522481 - E_Harris -676.7523312621 -9207.6878399479 - E_band -48.6414989983 -661.8015456379 - E_one_elec -311.2597632447 -4234.9063382588 - E_Hartree 181.7820867842 2473.2721745958 - E_xc -74.7512281470 -1017.0426352976 - E_Ewald -478.4202561664 -6509.2415224829 - E_entropy(-TS) -0.0000000000 -0.0000000000 - E_descf 0.0000000000 0.0000000000 - E_localpp -401.8063652052 -5466.8560594599 - E_plusU 2.7236507231 37.0571691954 - E_exx 0.0000000000 0.0000000000 - E_Fermi 1.1111466448 15.1179256829 - E_gap(k) 0.1931559722 2.6280218248 - ---------------------------------------------------------- - - #SCF IS CONVERGED# - #TOTAL ENERGY# -9250.86115224814 eV - Write eigenvalues and occupations to file: OUT.NiO/eig_occ.txt -------------------------------------------------- - Total Magnetism (uB) -------------------------------------------------- - Ni11 1.2715948597 - Ni21 -1.2715948597 - O1 0.0000000000 - O2 0.0000000000 -------------------------------------------------- - - - #TOTAL-FORCE (eV/Angstrom)# - ------------------------------------------------------------------------- - Atoms Force_x Force_y Force_z - ------------------------------------------------------------------------- - Ni11 0.0000000000 0.0000000000 0.0000000000 - Ni21 0.0000000000 0.0000000000 0.0000000000 - O1 0.0000000000 0.0000000000 0.0000000000 - O2 0.0000000000 0.0000000000 0.0000000000 - ------------------------------------------------------------------------- - - #TOTAL-STRESS (kbar)# - ---------------------------------------------------------------- - Stress_x Stress_y Stress_z - ---------------------------------------------------------------- - 28.0257165781 -25.0814687477 -25.0814687477 - -25.0814687477 -57.2309475134 52.8147857580 - -25.0814687477 52.8147857580 -57.2309475134 - ---------------------------------------------------------------- - #TOTAL-PRESSURE# (EXCLUDE KINETIC PART OF IONS): -28.812059 kbar - - Largest force is 0.000000 eV/Angstrom while threshold is 0.025711 eV/Angstrom - Largest stress is 57.230948 kbar while threshold is 0.500000 kbar - - Relaxation is not converged yet! - DIRECT COORDINATES - atom x y z mag - Ni1 0.000000000000 1.000000000000 1.000000000000 2.0000 - Ni2 0.500000000000 0.500000000000 0.500000000000 -2.0000 - O 0.250000000000 0.250000000000 0.250000000000 0.0000 - O 0.750000000000 0.750000000000 0.750000000000 0.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Ni1 0.000000000000 0.000000000000 0.000000000000 - Ni2 0.000000000000 0.000000000000 0.000000000000 - O 0.000000000000 0.000000000000 0.000000000000 - O 0.000000000000 0.000000000000 0.000000000000 - - Cell volume (Bohr^3) = 238.947741 - Cell volume (A^3) = 35.408360 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1.000834 +0.492269 +0.492269 - +0.493312 +0.987716 +0.503303 - +0.493312 +0.503303 +0.987716 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.481276 -0.490089 -0.490089 - -0.489053 +1.529324 -0.535029 - -0.489053 -0.535029 +1.529324 - DONE : SETUP UNITCELL Time : 34.044430 (SEC) - - ================================================================ - RELAX STEP: 2 (CELL_CHANGE# 2 IONS_CHANGE# 1) - ================================================================ - DONE : LOCAL POTENTIAL Time : 34.155598 (SEC) - - - SETUP K-POINTS - nspin = 2 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - - K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.74063794 -0.24504430 -0.24504430 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.74063794 -0.24504430 -0.24504430 0.5000 - - - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - DONE : INIT K-POINTS Time : 34.155733 (SEC) - - NEW-OLD atomic charge density approx. for the potential ! - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 2 #ELEC ITER# 1 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = -0.00000000 - Absolute magnetism (Bohr mag/cell) = 3.21518097 - Electron number for spin up = 24.00000000 - Electron number for spin down = 24.00000000 - Electron density deviation 0.025755 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -679.8533719985 -9249.8796636937 - E_KS(sigma->0) -679.8533719985 -9249.8796636937 - E_Harris -684.2300352486 -9309.4272221222 - E_band -43.6339455623 -593.6702858688 - E_one_elec -300.8081834810 -4092.7053003714 - E_Hartree 175.1973991234 2383.6829028589 - E_xc -74.7285731514 -1016.7343982682 - E_Ewald -482.3768468830 -6563.0737008819 - E_entropy(-TS) -0.0000000000 -0.0000000000 - E_descf 0.0000000000 0.0000000000 - E_localpp -392.5829798906 -5341.3654643309 - E_plusU 2.8628323934 38.9508329690 - E_exx 0.0000000000 0.0000000000 - E_Fermi 1.1742844955 15.9769602119 - E_gap(k) 0.2577258748 3.5065404191 - ---------------------------------------------------------- - - #SCF IS CONVERGED# - #TOTAL ENERGY# -9249.87966369367 eV - Write eigenvalues and occupations to file: OUT.NiO/eig_occ.txt -------------------------------------------------- - Total Magnetism (uB) -------------------------------------------------- - Ni11 1.5398722566 - Ni21 -1.5398722566 - O1 0.0000000000 - O2 -0.0000000000 -------------------------------------------------- - - - #TOTAL-FORCE (eV/Angstrom)# - ------------------------------------------------------------------------- - Atoms Force_x Force_y Force_z - ------------------------------------------------------------------------- - Ni11 0.0000000000 0.0000000000 0.0000000000 - Ni21 0.0000000000 0.0000000000 0.0000000000 - O1 0.0000000000 0.0000000000 0.0000000000 - O2 0.0000000000 0.0000000000 0.0000000000 - ------------------------------------------------------------------------- - - #TOTAL-STRESS (kbar)# - ---------------------------------------------------------------- - Stress_x Stress_y Stress_z - ---------------------------------------------------------------- - 10.5133147463 -28.8166870580 -28.8166870559 - -28.8166870580 0.1640200010 7.6016822662 - -28.8166870559 7.6016822662 0.1640199977 - ---------------------------------------------------------------- - #TOTAL-PRESSURE# (EXCLUDE KINETIC PART OF IONS): 3.613785 kbar - - Largest force is 0.000000 eV/Angstrom while threshold is 0.025711 eV/Angstrom - Largest stress is 28.816687 kbar while threshold is 0.500000 kbar - - Relaxation is not converged yet! - DIRECT COORDINATES - atom x y z mag - Ni1 0.000000000000 1.000000000000 1.000000000000 2.0000 - Ni2 0.500000000000 0.500000000000 0.500000000000 -2.0000 - O 0.250000000000 0.250000000000 0.250000000000 0.0000 - O 0.750000000000 0.750000000000 0.750000000000 0.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Ni1 0.000000000000 0.000000000000 0.000000000000 - Ni2 0.000000000000 0.000000000000 0.000000000000 - O 0.000000000000 0.000000000000 0.000000000000 - O 0.000000000000 0.000000000000 0.000000000000 - - Cell volume (Bohr^3) = 236.878456 - Cell volume (A^3) = 35.101724 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1.001113 +0.489681 +0.489681 - +0.491072 +0.983602 +0.504409 - +0.491072 +0.504409 +0.983602 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.475132 -0.486822 -0.486822 - -0.485442 +1.539645 -0.547196 - -0.485442 -0.547196 +1.539645 - DONE : SETUP UNITCELL Time : 63.323510 (SEC) - - - -------------------------------------------- - !FINAL_ETOT_IS -9249.8796636936658615 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax_ b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax_ deleted file mode 100644 index 93870d85621..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-cellrelax_ +++ /dev/null @@ -1,648 +0,0 @@ -NBANDS = 40 - - READING UNITCELL INFORMATION - ntype = 3 - lattice constant (Bohr) = 7.884 - lattice constant (Angstrom) = 4.17203 - - TOTAL ATOM NUMBER = 4 -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_Ni11 0.0000000000 0.0000000000 0.0000000000 2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_Ni21 0.5000000000 0.5000000000 0.5000000000 -2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O1 0.2500000000 0.2500000000 0.2500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O2 0.7500000000 0.7500000000 0.7500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 245.025 - Volume (A^3) = 36.3089 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0.5 +0.5 - +0.5 +1 +0.5 - +0.5 +0.5 +1 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.5 -0.5 -0.5 - -0.5 +1.5 -0.5 - -0.5 -0.5 +1.5 - The esolver type has been set to : ksdft_lcao - - SETUP K-POINTS - nspin = 2 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 2 - nkstot_ibz = 2 -K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.5000 0 - 2 0.50000000 0.00000000 0.00000000 0.5000 1 - - nkstot now = 2 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - - - k-point number in this process = 2 - minimum distributed K point number = 2 - nks(nspin=2) = 4 - nkstot(nspin=2) = 4 - -K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.75000000 -0.25000000 -0.25000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.75000000 -0.25000000 -0.25000000 0.5000 - - -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - DONE : INIT K-POINTS Time : 0.0449331 (SEC) - - LCAO ALGORITHM --------------- ION= 1 ELEC= 1-------------------------------- - total magnetism (Bohr mag/cell) = 0.00000000 - absolute magnetism (Bohr mag/cell) = 2.61029538 - nelec for spin up = 24.00000000 - nelec for spin down = 24.00000000 - - Density error is 0.095867875873 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -680.0824614910 -9252.9965861430 - E_Harris -681.2945326460 -9269.4876602324 - E_Fermi 1.1216473600 15.2607952433 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 2-------------------------------- - total magnetism (Bohr mag/cell) = 0.00000000 - absolute magnetism (Bohr mag/cell) = 2.80151196 - nelec for spin up = 24.00000000 - nelec for spin down = 24.00000000 - - Density error is 0.044770387430 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -679.9255100509 -9250.8611522481 - E_KS(sigma->0) -679.9255100509 -9250.8611522481 - E_Harris -676.7523312621 -9207.6878399479 - E_band -48.6414989983 -661.8015456379 - E_one_elec -311.2597632447 -4234.9063382588 - E_Hartree 181.7820867842 2473.2721745958 - E_xc -74.7512281470 -1017.0426352976 - E_Ewald -478.4202561664 -6509.2415224829 - E_entropy(-TS) -0.0000000000 -0.0000000000 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 1.1111466448 15.1179256829 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -9250.86115224814 eV - EFERMI = 15.11792568287 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 2 -SPIN UP : - 1/2 kpoint (Cartesian) = 0.00000 0.00000 0.00000 (522 pws) - 1 -89.404874 0.500000 - 2 -89.125942 0.500000 - 3 -50.116722 0.500000 - 4 -50.012641 0.500000 - 5 -49.770942 0.500000 - 6 -49.167045 0.500000 - 7 -49.133084 0.500000 - 8 -49.072023 0.500000 - 9 -4.305764 0.500000 - 10 -2.828539 0.500000 - 11 7.064136 0.500000 - 12 8.571683 0.500000 - 13 8.610007 0.500000 - 14 10.099916 0.500000 - 15 10.229316 0.500000 - 16 10.273838 0.500000 - 17 10.620054 0.500000 - 18 10.883396 0.500000 - 19 12.026525 0.500000 - 20 12.870909 0.500000 - 21 12.951267 0.500000 - 22 13.103917 0.500000 - 23 13.160275 0.500000 - 24 13.160406 0.500000 - 25 16.848597 0.000000 - 26 16.983083 0.000000 - 27 17.314452 0.000000 - 28 21.985231 0.000000 - 29 25.988979 0.000000 - 30 31.586032 0.000000 - 31 31.633203 0.000000 - 32 40.413880 0.000000 - 33 40.422562 0.000000 - 34 40.463585 0.000000 - 35 40.901535 0.000000 - 36 41.381761 0.000000 - 37 46.434114 0.000000 - 38 46.444013 0.000000 - 39 46.469222 0.000000 - 40 49.052274 0.000000 - - 2/2 kpoint (Cartesian) = 0.75000 -0.25000 -0.25000 (525 pws) - 1 -89.403068 0.500000 - 2 -89.124281 0.500000 - 3 -50.122332 0.500000 - 4 -50.014725 0.500000 - 5 -49.789066 0.500000 - 6 -49.185597 0.500000 - 7 -49.139283 0.500000 - 8 -49.076151 0.500000 - 9 -2.873734 0.500000 - 10 -2.767857 0.500000 - 11 7.978080 0.500000 - 12 8.219411 0.500000 - 13 8.548029 0.500000 - 14 8.669009 0.500000 - 15 9.836748 0.500000 - 16 10.504337 0.500000 - 17 10.667347 0.500000 - 18 10.804437 0.500000 - 19 11.314523 0.500000 - 20 11.738650 0.500000 - 21 11.747180 0.500000 - 22 12.040081 0.500000 - 23 13.198973 0.500000 - 24 13.739675 0.500000 - 25 16.367697 0.000000 - 26 16.550384 0.000000 - 27 23.386516 0.000000 - 28 23.511720 0.000000 - 29 26.159604 0.000000 - 30 26.291125 0.000000 - 31 28.634180 0.000000 - 32 28.670785 0.000000 - 33 32.915320 0.000000 - 34 32.996909 0.000000 - 35 40.504483 0.000000 - 36 40.630715 0.000000 - 37 50.312984 0.000000 - 38 50.426416 0.000000 - 39 51.859669 0.000000 - 40 51.907506 0.000000 - -SPIN DOWN : - 1/2 kpoint (Cartesian) = 0.00000 0.00000 0.00000 (522 pws) - 1 -89.404874 0.500000 - 2 -89.125942 0.500000 - 3 -50.116722 0.500000 - 4 -50.012641 0.500000 - 5 -49.770942 0.500000 - 6 -49.167045 0.500000 - 7 -49.133084 0.500000 - 8 -49.072023 0.500000 - 9 -4.305764 0.500000 - 10 -2.828539 0.500000 - 11 7.064136 0.500000 - 12 8.571683 0.500000 - 13 8.610007 0.500000 - 14 10.099916 0.500000 - 15 10.229316 0.500000 - 16 10.273838 0.500000 - 17 10.620054 0.500000 - 18 10.883396 0.500000 - 19 12.026525 0.500000 - 20 12.870909 0.500000 - 21 12.951267 0.500000 - 22 13.103917 0.500000 - 23 13.160275 0.500000 - 24 13.160406 0.500000 - 25 16.848597 0.000000 - 26 16.983083 0.000000 - 27 17.314452 0.000000 - 28 21.985231 0.000000 - 29 25.988979 0.000000 - 30 31.586032 0.000000 - 31 31.633203 0.000000 - 32 40.413880 0.000000 - 33 40.422562 0.000000 - 34 40.463585 0.000000 - 35 40.901535 0.000000 - 36 41.381761 0.000000 - 37 46.434114 0.000000 - 38 46.444013 0.000000 - 39 46.469222 0.000000 - 40 49.052274 0.000000 - - 2/2 kpoint (Cartesian) = 0.75000 -0.25000 -0.25000 (525 pws) - 1 -89.403068 0.500000 - 2 -89.124281 0.500000 - 3 -50.122332 0.500000 - 4 -50.014725 0.500000 - 5 -49.789066 0.500000 - 6 -49.185597 0.500000 - 7 -49.139283 0.500000 - 8 -49.076151 0.500000 - 9 -2.873734 0.500000 - 10 -2.767857 0.500000 - 11 7.978080 0.500000 - 12 8.219411 0.500000 - 13 8.548029 0.500000 - 14 8.669009 0.500000 - 15 9.836748 0.500000 - 16 10.504337 0.500000 - 17 10.667347 0.500000 - 18 10.804437 0.500000 - 19 11.314523 0.500000 - 20 11.738650 0.500000 - 21 11.747180 0.500000 - 22 12.040081 0.500000 - 23 13.198973 0.500000 - 24 13.739675 0.500000 - 25 16.367697 0.000000 - 26 16.550384 0.000000 - 27 23.386516 0.000000 - 28 23.511720 0.000000 - 29 26.159604 0.000000 - 30 26.291125 0.000000 - 31 28.634180 0.000000 - 32 28.670785 0.000000 - 33 32.915320 0.000000 - 34 32.996909 0.000000 - 35 40.504483 0.000000 - 36 40.630715 0.000000 - 37 50.312984 0.000000 - 38 50.426416 0.000000 - 39 51.859669 0.000000 - 40 51.907506 0.000000 - -------------------------------------------------- - Total Magnetism (uB) -------------------------------------------------- - Ni11 1.2715948597 - Ni21 -1.2715948597 - O1 0.0000000000 - O2 0.0000000000 -------------------------------------------------- - - - Warning_Memory_Consuming allocated: Stress::dSR 5.079346 MB - correction force for each atom along direction 1 is 0.000000 - correction force for each atom along direction 2 is 0.000000 - correction force for each atom along direction 3 is 0.000000 ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - Ni11 0.0000000000 0.0000000000 0.0000000000 - Ni21 0.0000000000 0.0000000000 0.0000000000 - O1 0.0000000000 0.0000000000 0.0000000000 - O2 0.0000000000 0.0000000000 0.0000000000 ------------------------------------------------------------------------------------------- - ----------------------------------------------------------------- - TOTAL-STRESS (KBAR) ----------------------------------------------------------------- - 28.0257165780 -25.0814687477 -25.0814687478 - -25.0814687477 -57.2309475134 52.8147857580 - -25.0814687478 52.8147857580 -57.2309475133 ----------------------------------------------------------------- - TOTAL-PRESSURE: -28.812059 KBAR - - - Largest gradient in force is 0.000000 eV/A. - Threshold is 0.025711 eV/A. - - Largest gradient in stress is 57.230948 kbar. - Threshold is 0.500000 kbar. - - Relaxation is not converged yet! -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_Ni11 0.0000000000 0.0000000000 0.0000000000 2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_Ni21 0.5000000000 0.5000000000 0.5000000000 -2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O1 0.2500000000 0.2500000000 0.2500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O2 0.7500000000 0.7500000000 0.7500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 238.947741 - Volume (A^3) = 35.408360 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1.000834 +0.492269 +0.492269 - +0.493312 +0.987716 +0.503303 - +0.493312 +0.503303 +0.987716 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.481276 -0.490089 -0.490089 - -0.489053 +1.529324 -0.535029 - -0.489053 -0.535029 +1.529324 - DONE : SETUP UNITCELL Time : 35.144061 (SEC) - - - ------------------------------------------- - STEP OF RELAXATION : 2 - ------------------------------------------- - DONE : LOCAL POTENTIAL Time : 35.274813 (SEC) - - - SETUP K-POINTS - nspin = 2 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - -K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.74063794 -0.24504430 -0.24504430 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.74063794 -0.24504430 -0.24504430 0.5000 - - -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - DONE : INIT K-POINTS Time : 35.274964 (SEC) - - NEW-OLD atomic charge density approx. for the potential ! - - LCAO ALGORITHM --------------- ION= 2 ELEC= 1-------------------------------- - total magnetism (Bohr mag/cell) = 0.00000000 - absolute magnetism (Bohr mag/cell) = 3.21425392 - nelec for spin up = 24.00000000 - nelec for spin down = 24.00000000 - - Density error is 0.026004815939 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -679.8473275556 -9249.7974248288 - E_KS(sigma->0) -679.8473275556 -9249.7974248288 - E_Harris -684.3263638890 -9310.7378405121 - E_band -43.4930687779 -591.7535588848 - E_one_elec -300.5649336133 -4089.3957161326 - E_Hartree 175.0901338700 2382.2234842145 - E_xc -74.7193316524 -1016.6086612241 - E_Ewald -482.3768468830 -6563.0737008819 - E_entropy(-TS) -0.0000000000 -0.0000000000 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 1.1742850813 15.9769681825 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -9249.79742482878 eV - EFERMI = 15.97696818250 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 2 -SPIN UP : - 1/2 kpoint (Cartesian) = 0.00000 0.00000 0.00000 (522 pws) - 1 -87.313319 0.500000 - 2 -86.299919 0.500000 - 3 -48.144915 0.500000 - 4 -47.852774 0.500000 - 5 -47.594179 0.500000 - 6 -46.643110 0.500000 - 7 -46.469430 0.500000 - 8 -46.356732 0.500000 - 9 -4.441729 0.500000 - 10 -3.006060 0.500000 - 11 7.554887 0.500000 - 12 9.603175 0.500000 - 13 9.791693 0.500000 - 14 11.910486 0.500000 - 15 12.024017 0.500000 - 16 12.122462 0.500000 - 17 12.800279 0.500000 - 18 12.812330 0.500000 - 19 12.962308 0.500000 - 20 13.085032 0.500000 - 21 13.093740 0.500000 - 22 13.606898 0.500000 - 23 14.163995 0.500000 - 24 14.210286 0.500000 - 25 18.126647 0.000000 - 26 18.317661 0.000000 - 27 18.411251 0.000000 - 28 22.810951 0.000000 - 29 26.717376 0.000000 - 30 32.381051 0.000000 - 31 33.327582 0.000000 - 32 40.974339 0.000000 - 33 42.076111 0.000000 - 34 42.512684 0.000000 - 35 42.741748 0.000000 - 36 42.813320 0.000000 - 37 46.702895 0.000000 - 38 48.044296 0.000000 - 39 48.397388 0.000000 - 40 49.449904 0.000000 - - 2/2 kpoint (Cartesian) = 0.74064 -0.24504 -0.24504 (525 pws) - 1 -87.311541 0.500000 - 2 -86.298007 0.500000 - 3 -48.150190 0.500000 - 4 -47.853056 0.500000 - 5 -47.615448 0.500000 - 6 -46.649045 0.500000 - 7 -46.467146 0.500000 - 8 -46.383080 0.500000 - 9 -3.088880 0.500000 - 10 -3.004681 0.500000 - 11 8.440016 0.500000 - 12 8.606555 0.500000 - 13 9.402022 0.500000 - 14 9.896985 0.500000 - 15 10.486907 0.500000 - 16 11.502839 0.500000 - 17 12.152707 0.500000 - 18 12.604010 0.500000 - 19 12.956976 0.500000 - 20 13.237777 0.500000 - 21 13.559188 0.500000 - 22 14.001685 0.500000 - 23 14.085813 0.500000 - 24 14.514613 0.500000 - 25 18.021728 0.000000 - 26 18.254714 0.000000 - 27 24.223340 0.000000 - 28 24.392854 0.000000 - 29 27.003980 0.000000 - 30 27.096256 0.000000 - 31 29.660858 0.000000 - 32 29.728710 0.000000 - 33 34.805284 0.000000 - 34 35.022252 0.000000 - 35 42.072506 0.000000 - 36 42.132637 0.000000 - 37 51.290113 0.000000 - 38 51.325899 0.000000 - 39 52.955352 0.000000 - 40 53.076750 0.000000 - -SPIN DOWN : - 1/2 kpoint (Cartesian) = 0.00000 0.00000 0.00000 (522 pws) - 1 -87.313319 0.500000 - 2 -86.299919 0.500000 - 3 -48.144915 0.500000 - 4 -47.852774 0.500000 - 5 -47.594179 0.500000 - 6 -46.643110 0.500000 - 7 -46.469430 0.500000 - 8 -46.356732 0.500000 - 9 -4.441729 0.500000 - 10 -3.006060 0.500000 - 11 7.554887 0.500000 - 12 9.603175 0.500000 - 13 9.791693 0.500000 - 14 11.910486 0.500000 - 15 12.024017 0.500000 - 16 12.122462 0.500000 - 17 12.800279 0.500000 - 18 12.812330 0.500000 - 19 12.962308 0.500000 - 20 13.085032 0.500000 - 21 13.093740 0.500000 - 22 13.606898 0.500000 - 23 14.163995 0.500000 - 24 14.210286 0.500000 - 25 18.126647 0.000000 - 26 18.317661 0.000000 - 27 18.411251 0.000000 - 28 22.810951 0.000000 - 29 26.717376 0.000000 - 30 32.381051 0.000000 - 31 33.327582 0.000000 - 32 40.974339 0.000000 - 33 42.076111 0.000000 - 34 42.512684 0.000000 - 35 42.741748 0.000000 - 36 42.813320 0.000000 - 37 46.702895 0.000000 - 38 48.044296 0.000000 - 39 48.397388 0.000000 - 40 49.449904 0.000000 - - 2/2 kpoint (Cartesian) = 0.74064 -0.24504 -0.24504 (525 pws) - 1 -87.311541 0.500000 - 2 -86.298007 0.500000 - 3 -48.150190 0.500000 - 4 -47.853056 0.500000 - 5 -47.615448 0.500000 - 6 -46.649045 0.500000 - 7 -46.467146 0.500000 - 8 -46.383080 0.500000 - 9 -3.088880 0.500000 - 10 -3.004681 0.500000 - 11 8.440016 0.500000 - 12 8.606555 0.500000 - 13 9.402022 0.500000 - 14 9.896985 0.500000 - 15 10.486907 0.500000 - 16 11.502839 0.500000 - 17 12.152707 0.500000 - 18 12.604010 0.500000 - 19 12.956976 0.500000 - 20 13.237777 0.500000 - 21 13.559188 0.500000 - 22 14.001685 0.500000 - 23 14.085813 0.500000 - 24 14.514613 0.500000 - 25 18.021728 0.000000 - 26 18.254714 0.000000 - 27 24.223340 0.000000 - 28 24.392854 0.000000 - 29 27.003980 0.000000 - 30 27.096256 0.000000 - 31 29.660858 0.000000 - 32 29.728710 0.000000 - 33 34.805284 0.000000 - 34 35.022252 0.000000 - 35 42.072506 0.000000 - 36 42.132637 0.000000 - 37 51.290113 0.000000 - 38 51.325899 0.000000 - 39 52.955352 0.000000 - 40 53.076750 0.000000 - -------------------------------------------------- - Total Magnetism (uB) -------------------------------------------------- - Ni11 1.5398231110 - Ni21 -1.5398231110 - O1 -0.0000000000 - O2 -0.0000000000 -------------------------------------------------- - - correction force for each atom along direction 1 is 0.000000 - correction force for each atom along direction 2 is -0.000000 - correction force for each atom along direction 3 is -0.000000 ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - Ni11 0.0000000000 0.0000000000 0.0000000000 - Ni21 0.0000000000 0.0000000000 0.0000000000 - O1 0.0000000000 0.0000000000 0.0000000000 - O2 0.0000000000 0.0000000000 0.0000000000 ------------------------------------------------------------------------------------------- - ----------------------------------------------------------------- - TOTAL-STRESS (KBAR) ----------------------------------------------------------------- - 7.3061116295 -27.6053387696 -27.6053387674 - -27.6053387696 0.8540980637 7.6367729837 - -27.6053387674 7.6367729837 0.8540980603 ----------------------------------------------------------------- - TOTAL-PRESSURE: 3.004769 KBAR - - - Largest gradient in force is 0.000000 eV/A. - Threshold is 0.025711 eV/A. - - Largest gradient in stress is 27.605339 kbar. - Threshold is 0.500000 kbar. - - Relaxation is not converged yet! -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_Ni11 0.0000000000 0.0000000000 0.0000000000 2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_Ni21 0.5000000000 0.5000000000 0.5000000000 -2.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O1 0.2500000000 0.2500000000 0.2500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O2 0.7500000000 0.7500000000 0.7500000000 0.0000 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 237.073950 - Volume (A^3) = 35.130693 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1.001087 +0.489924 +0.489924 - +0.491283 +0.983989 +0.504305 - +0.491283 +0.504305 +0.983989 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1.475708 -0.487128 -0.487128 - -0.485781 +1.538662 -0.546041 - -0.485781 -0.546041 +1.538662 - DONE : SETUP UNITCELL Time : 58.301245 (SEC) - - - - -------------------------------------------- - !FINAL_ETOT_IS -9249.7974248287773662 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax deleted file mode 100644 index 32c1b22a053..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax +++ /dev/null @@ -1,254 +0,0 @@ - READING UNITCELL INFORMATION - Number of elements = 2 - Lattice constant (Bohr) = 6.1416 - Lattice constant (Angstrom) = 3.24999 - - TOTAL ATOM NUMBER = 2 - - DIRECT COORDINATES - atom x y z mag - Zn 0.000000000000 0.000000000000 0.000000000000 1.0000 - O 0.333329999999 0.666670000001 0.500000000000 1.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Zn 0 0 0 - O 0 0 0 - - Cell volume (Bohr^3) = 320.983 - Cell volume (A^3) = 47.5648 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0 +0 - -0.5 +0.866 +0 - +0 +0 +1.6 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1 +0.577367 -0 - -0 +1.15473 -0 - +0 -0 +0.625 - - #ENERGY SOLVER# ksdft_lcao - - SETUP K-POINTS - nspin = 2 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 2 - Number of irreducible k-points = 2 - - K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.5000 0 - 2 0.50000000 0.00000000 0.00000000 0.5000 1 - - nkstot now = 2 - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - - - Number of k-points in this process = 2 - Minimum distributed k-point number = 2 - nks(nspin=2) = 4 - nkstot(nspin=2) = 4 - - K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.28868360 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.28868360 0.00000000 0.5000 - - - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - 3 0.00000000 0.00000000 0.00000000 0.5000 - 4 0.50000000 0.00000000 0.00000000 0.5000 - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 1 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1 - Absolute magnetism (Bohr mag/cell) = 1.12149 - Electron number for spin up = 9.5 - Electron number for spin down = 8.5 - Electron density deviation 0.125277 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.2770827085 -1935.7790196528 - E_Harris -143.4665603429 -1951.9626931241 - E_Fermi -0.0403816764 -0.5494208938 - E_gap(k) 0.0210767116 0.2867633735 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 2 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1.47102 - Absolute magnetism (Bohr mag/cell) = 1.73431 - Electron number for spin up = 9.73551 - Electron number for spin down = 8.26449 - Electron density deviation 0.0831681 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3304483127 -1936.5050959474 - E_Harris -143.3480046582 -1950.3496602818 - E_Fermi 0.1727758798 2.3507364425 - E_gap(k) 0.0829435366 1.1285047105 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 3 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1.12003 - Absolute magnetism (Bohr mag/cell) = 1.31163 - Electron number for spin up = 9.56002 - Electron number for spin down = 8.43998 - Electron density deviation 0.0509266 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3384608538 -1936.6141121617 - E_Harris -142.9615903984 -1945.0922245608 - E_Fermi 0.1548551093 2.1069118503 - E_gap(k) 0.0983427230 1.3380213902 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 4 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1.53777 - Absolute magnetism (Bohr mag/cell) = 1.6108 - Electron number for spin up = 9.76889 - Electron number for spin down = 8.23111 - Electron density deviation 0.0330854 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3352576013 -1936.5705296758 - E_KS(sigma->0) -142.3323774224 -1936.5313428317 - E_Harris -142.7863078681 -1942.7073833888 - E_band -6.1860559614 -84.1656092218 - E_one_elec -82.8067447602 -1126.6435615698 - E_Hartree 52.7505694558 717.7083173431 - E_xc -22.3915899098 -304.6532100528 - E_Ewald -89.8817320294 -1222.9037017083 - E_entropy(-TS) -0.0057603578 -0.0783736881 - E_descf 0.0000000000 0.0000000000 - E_localpp -63.0117129308 -857.3183365997 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.1563671777 2.1274845966 - E_gap(k) 0.0088059230 0.1198107291 - ---------------------------------------------------------- - - #SCF IS CONVERGED# - #TOTAL ENERGY# -1936.5705297 eV - Write eigenvalues and occupations to file: OUT.ABACUS/eig_occ.txt - - #TOTAL-FORCE (eV/Angstrom)# - ------------------------------------------------------------------------- - Atoms Force_x Force_y Force_z - ------------------------------------------------------------------------- - Zn1 -1.2994798821 -0.7502719805 0.0000000000 - O1 1.2994798821 0.7502719805 0.0000000000 - ------------------------------------------------------------------------- - DIRECT COORDINATES - atom x y z mag - Zn 0.992383886577 0.996191795094 1.000000000000 1.0000 - O 0.340946113423 0.670478204907 0.500000000000 1.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Zn 0 0 0 - O 0 0 0 - - ================================================================ - RELAX STEP: 2 - ================================================================ - NEW-OLD atomic charge density approx. for the potential ! - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 2 #ELEC ITER# 1 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1 - Absolute magnetism (Bohr mag/cell) = 1.22086 - Electron number for spin up = 9.5 - Electron number for spin down = 8.5 - Electron density deviation 0.059569 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.2850208668 -1935.8870238374 - E_Harris -142.0296340730 -1932.4123082471 - E_Fermi 0.0811057930 1.1035009262 - E_gap(k) 0.0049642264 0.0675417656 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 2 #ELEC ITER# 2 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1.58366 - Absolute magnetism (Bohr mag/cell) = 1.79488 - Electron number for spin up = 9.79183 - Electron number for spin down = 8.20817 - Electron density deviation 0.0753194 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3289737580 -1936.4850336018 - E_Harris -142.6117578899 -1940.3325090994 - E_Fermi 0.1443002225 1.9633052486 - E_gap(k) 0.0030396611 0.0413567116 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 2 #ELEC ITER# 3 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Total magnetism (Bohr mag/cell) = 1.72786 - Absolute magnetism (Bohr mag/cell) = 1.80974 - Electron number for spin up = 9.86393 - Electron number for spin down = 8.13607 - Electron density deviation 0.0255995 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3181389505 -1936.3376184830 - E_KS(sigma->0) -142.3160566944 -1936.3092879352 - E_Harris -142.1804183322 -1934.4638333420 - E_band -6.5047886054 -88.5021893191 - E_one_elec -83.0970862127 -1130.5938596904 - E_Hartree 53.0543074453 721.8408847003 - E_xc -22.3904168680 -304.6372499998 - E_Ewald -89.8807788029 -1222.8907323974 - E_entropy(-TS) -0.0041645122 -0.0566610957 - E_descf 0.0000000000 0.0000000000 - E_localpp -63.1799315004 -859.6070676546 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.1490975189 2.0285758150 - E_gap(k) 0.0166283571 0.2262404048 - ---------------------------------------------------------- - - #SCF IS CONVERGED# - #TOTAL ENERGY# -1936.3376185 eV - Write eigenvalues and occupations to file: OUT.ABACUS/eig_occ.txt - - #TOTAL-FORCE (eV/Angstrom)# - ------------------------------------------------------------------------- - Atoms Force_x Force_y Force_z - ------------------------------------------------------------------------- - Zn1 -1.6814741322 -0.9708025265 0.0000000000 - O1 1.6814741322 0.9708025265 0.0000000000 - ------------------------------------------------------------------------- - - -------------------------------------------- - !FINAL_ETOT_IS -1936.337618483019 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax_ b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax_ deleted file mode 100644 index b2767839183..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm0-nspin2-multik-relax_ +++ /dev/null @@ -1,542 +0,0 @@ -NBANDS = 24 - READING UNITCELL INFORMATION - ntype = 2 - lattice constant (Bohr) = 6.1416 - lattice constant (Angstrom) = 3.24999 - - TOTAL ATOM NUMBER = 2 -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_Zn1 0.0000000000 0.0000000000 0.0000000000 1.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O1 0.3333300000 0.6666700000 0.5000000000 1.0000 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 320.983 - Volume (A^3) = 47.5648 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0 +0 - -0.5 +0.866 +0 - +0 +0 +1.6 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1 +0.577367 -0 - -0 +1.15473 -0 - +0 -0 +0.625 - The esolver type has been set to : ksdft_lcao - - SETUP K-POINTS - nspin = 2 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 3 - nkstot_ibz = 2 -K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.3333 0 - 2 0.33333333 0.00000000 0.00000000 0.6667 1 - - nkstot now = 2 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.3333 - 2 0.33333333 0.00000000 0.00000000 0.6667 - - - k-point number in this process = 2 - minimum distributed K point number = 2 - nks(nspin=2) = 4 - nkstot(nspin=2) = 4 - -K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.3333 - 2 0.33333333 0.19245574 0.00000000 0.6667 - 3 0.00000000 0.00000000 0.00000000 0.3333 - 4 0.33333333 0.19245574 0.00000000 0.6667 - - -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.3333 - 2 0.33333333 0.00000000 0.00000000 0.6667 - 3 0.00000000 0.00000000 0.00000000 0.3333 - 4 0.33333333 0.00000000 0.00000000 0.6667 - - DONE : INIT K-POINTS Time : 0.158631 (SEC) - - LCAO ALGORITHM --------------- ION= 1 ELEC= 1-------------------------------- - total magnetism (Bohr mag/cell) = 0.666667 - absolute magnetism (Bohr mag/cell) = 0.859038 - nelec for spin up = 9.33333 - nelec for spin down = 8.66667 - - Density error is 0.148375838039 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.1187686707 -1933.6250466660 - E_Harris -143.4705730938 -1952.0172894008 - E_Fermi -0.0353855376 -0.4814449377 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 2-------------------------------- - total magnetism (Bohr mag/cell) = 1.07915 - absolute magnetism (Bohr mag/cell) = 1.12107 - nelec for spin up = 9.53958 - nelec for spin down = 8.46042 - - Density error is 0.124524909173 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3211355503 -1936.3783893140 - E_Harris -143.6941669355 -1955.0594396866 - E_Fermi 0.1465181279 1.9934813994 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 3-------------------------------- - total magnetism (Bohr mag/cell) = 1.99248 - absolute magnetism (Bohr mag/cell) = 1.99742 - nelec for spin up = 9.99624 - nelec for spin down = 8.00376 - - Density error is 0.0706868918851 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3807869186 -1937.1899878166 - E_Harris -142.7640217764 -1942.4041655545 - E_Fermi 0.0369440819 0.5026500219 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 4-------------------------------- - Energy (eV) & Occupations for spin=1 K-point=1 - 1 -15.5220 0.333333 - 2 -5.15869 0.333333 - 3 -5.09633 0.333333 - 4 -5.09579 0.333333 - 5 -4.91098 0.333333 - 6 -4.88255 0.333333 - 7 -2.41462 0.333333 - 8 -1.17166 0.333333 - 9 -0.444834 0.333333 - 10 -0.0995765 0.333331 - 11 6.45991 0.00000 - 12 11.4422 0.00000 - 13 15.0234 0.00000 - 14 15.0596 0.00000 - 15 23.2513 0.00000 - 16 23.2764 0.00000 - 17 25.4767 0.00000 - 18 26.9713 0.00000 - 19 31.1215 0.00000 - 20 31.1309 0.00000 - 21 35.3447 0.00000 - 22 35.5400 0.00000 - 23 44.3560 0.00000 - 24 44.3653 0.00000 - Energy (eV) & Occupations for spin=1 K-point=2 - 1 -15.3130 0.666667 - 2 -5.19707 0.666667 - 3 -5.04322 0.666667 - 4 -4.95407 0.666667 - 5 -4.92694 0.666667 - 6 -4.90614 0.666667 - 7 -1.60476 0.666667 - 8 -0.647511 0.666667 - 9 -0.597491 0.666667 - 10 0.0977501 0.665705 - 11 6.63089 0.00000 - 12 7.81903 0.00000 - 13 12.0662 0.00000 - 14 13.7495 0.00000 - 15 17.0352 0.00000 - 16 18.4561 0.00000 - 17 23.0144 0.00000 - 18 27.6550 0.00000 - 19 29.0735 0.00000 - 20 32.7571 0.00000 - 21 34.0272 0.00000 - 22 34.4864 0.00000 - 23 45.8436 0.00000 - 24 47.8824 0.00000 - Energy (eV) & Occupations for spin=2 K-point=3 - 1 -13.9098 0.333333 - 2 -5.28447 0.333333 - 3 -5.23229 0.333333 - 4 -5.23152 0.333333 - 5 -5.04766 0.333333 - 6 -5.02138 0.333333 - 7 -2.41296 0.333333 - 8 -0.465692 0.333333 - 9 1.52893 6.64394e-13 - 10 1.91854 0.00000 - 11 6.73202 0.00000 - 12 11.8411 0.00000 - 13 14.9947 0.00000 - 14 15.0247 0.00000 - 15 23.2999 0.00000 - 16 23.3150 0.00000 - 17 25.6846 0.00000 - 18 27.1076 0.00000 - 19 31.1088 0.00000 - 20 31.1333 0.00000 - 21 36.4707 0.00000 - 22 36.7183 0.00000 - 23 45.7329 0.00000 - 24 45.7353 0.00000 - Energy (eV) & Occupations for spin=2 K-point=4 - 1 -13.6624 0.666667 - 2 -5.32995 0.666667 - 3 -5.17483 0.666667 - 4 -5.08486 0.666667 - 5 -5.06576 0.666667 - 6 -5.04464 0.666667 - 7 -0.935533 0.666667 - 8 0.232424 0.653097 - 9 0.818953 0.0145341 - 10 1.65447 1.92439e-15 - 11 6.66257 0.00000 - 12 7.91394 0.00000 - 13 12.0744 0.00000 - 14 14.3168 0.00000 - 15 17.1145 0.00000 - 16 18.4599 0.00000 - 17 23.6133 0.00000 - 18 27.6693 0.00000 - 19 30.2686 0.00000 - 20 33.0577 0.00000 - 21 34.2411 0.00000 - 22 34.5673 0.00000 - 23 46.5926 0.00000 - 24 48.6064 0.00000 - total magnetism (Bohr mag/cell) = 1.99807 - absolute magnetism (Bohr mag/cell) = 2.00454 - nelec for spin up = 9.99904 - nelec for spin down = 8.00096 - - Density error is 0.0656436757814 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3712279182 -1937.0599309441 - E_KS(sigma->0) -142.3708533084 -1937.0548341158 - E_Harris -142.7308078006 -1941.9522662316 - E_band -6.2153589294 -84.5642965550 - E_one_elec -82.9844461107 -1129.0613124793 - E_Hartree 53.1237310038 722.7854406707 - E_xc -22.4953290567 -306.0646535560 - E_Ewald -89.8817320293 -1222.9037017083 - E_entropy(-TS) -0.0007492197 -0.0101936567 - E_descf -0.1327025056 -1.8055102146 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.0387889151 0.5277502650 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 5-------------------------------- - total magnetism (Bohr mag/cell) = 1.99993 - absolute magnetism (Bohr mag/cell) = 2.03979 - nelec for spin up = 9.99997 - nelec for spin down = 8.00003 - - Density error is 0.0248102508790 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3828162660 -1937.2175985049 - E_KS(sigma->0) -142.3828138132 -1937.2175651321 - E_Harris -142.3278409962 -1936.4696215860 - E_band -6.4176731232 -87.3169223772 - E_one_elec -83.1842752559 -1131.7801274806 - E_Hartree 53.1444543678 723.0673965037 - E_xc -22.4612584429 -305.6010990741 - E_Ewald -89.8817320293 -1222.9037017083 - E_entropy(-TS) -0.0000049057 -0.0000667456 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.0397561785 0.5409105583 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -1937.2175985 eV - EFERMI = 0.54091055829 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 2 -SPIN UP : - 1/2 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (893 pws) - 1 -17.3578 0.333333 - 2 -4.81961 0.333333 - 3 -4.73286 0.333333 - 4 -4.72898 0.333333 - 5 -4.55927 0.333333 - 6 -4.54757 0.333333 - 7 -2.39449 0.333333 - 8 -2.32744 0.333333 - 9 -2.25455 0.333333 - 10 -2.23106 0.333333 - 11 6.19588 0.00000 - 12 10.9105 0.00000 - 13 15.2835 0.00000 - 14 15.3348 0.00000 - 15 23.4019 0.00000 - 16 23.4194 0.00000 - 17 25.2091 0.00000 - 18 26.8842 0.00000 - 19 31.1933 0.00000 - 20 31.2197 0.00000 - 21 34.2734 0.00000 - 22 34.3051 0.00000 - 23 42.9212 0.00000 - 24 42.9350 0.00000 - - 2/2 kpoint (Cartesian) = 0.33333 0.19246 0.0000 (885 pws) - 1 -17.1796 0.666667 - 2 -4.84064 0.666667 - 3 -4.70446 0.666667 - 4 -4.62781 0.666667 - 5 -4.57157 0.666667 - 6 -4.55825 0.666667 - 7 -3.28584 0.666667 - 8 -2.56381 0.666667 - 9 -1.80637 0.666667 - 10 -0.0190494 0.666632 - 11 6.80206 0.00000 - 12 7.76382 0.00000 - 13 12.3030 0.00000 - 14 12.9847 0.00000 - 15 16.9929 0.00000 - 16 18.6240 0.00000 - 17 22.2820 0.00000 - 18 27.7369 0.00000 - 19 28.0657 0.00000 - 20 32.3697 0.00000 - 21 33.8334 0.00000 - 22 34.6594 0.00000 - 23 44.8974 0.00000 - 24 46.3639 0.00000 - -SPIN DOWN : - 1/2 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (893 pws) - 1 -14.0445 0.333333 - 2 -4.79338 0.333333 - 3 -4.73524 0.333333 - 4 -4.72894 0.333333 - 5 -4.55673 0.333333 - 6 -4.55287 0.333333 - 7 -2.01856 0.333333 - 8 -0.690145 0.333333 - 9 1.71782 5.55112e-17 - 10 1.71906 5.55112e-17 - 11 6.79442 0.00000 - 12 11.7239 0.00000 - 13 15.4745 0.00000 - 14 15.4804 0.00000 - 15 23.7304 0.00000 - 16 23.7520 0.00000 - 17 25.6891 0.00000 - 18 27.2645 0.00000 - 19 31.3363 0.00000 - 20 31.3391 0.00000 - 21 36.5216 0.00000 - 22 36.5324 0.00000 - 23 45.5784 0.00000 - 24 45.5799 0.00000 - - 2/2 kpoint (Cartesian) = 0.33333 0.19246 0.0000 (885 pws) - 1 -13.7943 0.666667 - 2 -4.83973 0.666667 - 3 -4.69011 0.666667 - 4 -4.60271 0.666667 - 5 -4.57650 0.666667 - 6 -4.56292 0.666667 - 7 -0.679818 0.666667 - 8 -0.0349660 0.666645 - 9 1.08344 5.67566e-05 - 10 1.45183 9.16874e-11 - 11 7.10261 0.00000 - 12 8.05898 0.00000 - 13 12.5265 0.00000 - 14 14.2714 0.00000 - 15 17.2717 0.00000 - 16 18.8507 0.00000 - 17 23.5800 0.00000 - 18 27.8847 0.00000 - 19 30.1119 0.00000 - 20 33.0443 0.00000 - 21 34.4305 0.00000 - 22 34.9634 0.00000 - 23 46.4765 0.00000 - 24 48.4894 0.00000 - - correction force for each atom along direction 1 is 0.00411709 - correction force for each atom along direction 2 is 0.00237660 - correction force for each atom along direction 3 is -1.26516e-14 ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - Zn1 0.1954664241 0.1128694317 0.0000000000 - O1 -0.1954664241 -0.1128694317 0.0000000000 ------------------------------------------------------------------------------------------- - - - ------------------------------------------- - STEP OF ION RELAXATION : 2 - ------------------------------------------- - NEW-OLD atomic charge density approx. for the potential ! - - LCAO ALGORITHM --------------- ION= 2 ELEC= 1-------------------------------- - total magnetism (Bohr mag/cell) = 1.98397 - absolute magnetism (Bohr mag/cell) = 2.01565 - nelec for spin up = 9.99199 - nelec for spin down = 8.00801 - - Density error is 0.0228305178505 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3657663672 -1936.9856227301 - E_KS(sigma->0) -142.3655346066 -1936.9824694665 - E_Harris -142.4529533528 -1938.1718625268 - E_band -6.3921548433 -86.9697283672 - E_one_elec -83.1041185049 -1130.6895389336 - E_Hartree 53.1961932590 723.7713402320 - E_xc -22.5756668328 -307.1577050760 - E_Ewald -89.8817107675 -1222.9034124254 - E_entropy(-TS) -0.0004635210 -0.0063065271 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.0342773968 0.4663679095 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -1936.9856227 eV - EFERMI = 0.46636790945 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 2 -SPIN UP : - 1/2 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (893 pws) - 1 -17.7025 0.333333 - 2 -4.59882 0.333333 - 3 -4.48702 0.333333 - 4 -4.48147 0.333333 - 5 -4.33746 0.333333 - 6 -4.33126 0.333333 - 7 -2.96724 0.333333 - 8 -2.55522 0.333333 - 9 -2.43500 0.333333 - 10 -2.06949 0.333333 - 11 6.19551 0.00000 - 12 10.7575 0.00000 - 13 15.5015 0.00000 - 14 15.5549 0.00000 - 15 23.5757 0.00000 - 16 23.5948 0.00000 - 17 25.1721 0.00000 - 18 26.8978 0.00000 - 19 31.2662 0.00000 - 20 31.2667 0.00000 - 21 33.9239 0.00000 - 22 34.1543 0.00000 - 23 42.5429 0.00000 - 24 42.5431 0.00000 - - 2/2 kpoint (Cartesian) = 0.33333 0.19246 0.0000 (885 pws) - 1 -17.5271 0.666667 - 2 -4.60169 0.666667 - 3 -4.48965 0.666667 - 4 -4.42403 0.666667 - 5 -4.35548 0.666667 - 6 -4.31050 0.666667 - 7 -3.48370 0.666667 - 8 -3.13075 0.666667 - 9 -1.99545 0.666667 - 10 0.140729 0.658654 - 11 6.98624 0.00000 - 12 7.79735 0.00000 - 13 12.5109 0.00000 - 14 12.8264 0.00000 - 15 17.0254 0.00000 - 16 18.7928 0.00000 - 17 22.1240 0.00000 - 18 27.7362 0.00000 - 19 27.7983 0.00000 - 20 32.2838 0.00000 - 21 33.8717 0.00000 - 22 34.8442 0.00000 - 23 44.6873 0.00000 - 24 45.9661 0.00000 - -SPIN DOWN : - 1/2 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (893 pws) - 1 -14.6585 0.333333 - 2 -4.52650 0.333333 - 3 -4.44075 0.333333 - 4 -4.43523 0.333333 - 5 -4.29202 0.333333 - 6 -4.28421 0.333333 - 7 -1.78718 0.333333 - 8 -0.947410 0.333333 - 9 0.870330 0.000853653 - 10 1.06811 5.08182e-06 - 11 6.79068 0.00000 - 12 11.5147 0.00000 - 13 15.7693 0.00000 - 14 15.7810 0.00000 - 15 23.9664 0.00000 - 16 24.0061 0.00000 - 17 25.6603 0.00000 - 18 27.3078 0.00000 - 19 31.4681 0.00000 - 20 31.4747 0.00000 - 21 35.9897 0.00000 - 22 36.1264 0.00000 - 23 44.9479 0.00000 - 24 44.9594 0.00000 - - 2/2 kpoint (Cartesian) = 0.33333 0.19246 0.0000 (885 pws) - 1 -14.4180 0.666667 - 2 -4.55358 0.666667 - 3 -4.42144 0.666667 - 4 -4.34051 0.666667 - 5 -4.31573 0.666667 - 6 -4.26983 0.666667 - 7 -0.959860 0.666667 - 8 -0.337689 0.666667 - 9 0.813142 0.00542077 - 10 0.869628 0.00173321 - 11 7.33824 0.00000 - 12 8.13795 0.00000 - 13 12.8109 0.00000 - 14 13.9302 0.00000 - 15 17.3526 0.00000 - 16 19.0843 0.00000 - 17 23.2854 0.00000 - 18 28.0244 0.00000 - 19 29.7367 0.00000 - 20 32.9427 0.00000 - 21 34.4375 0.00000 - 22 35.1991 0.00000 - 23 46.1806 0.00000 - 24 48.1360 0.00000 - - correction force for each atom along direction 1 is -0.00291807 - correction force for each atom along direction 2 is -0.00168508 - correction force for each atom along direction 3 is -1.81361e-12 ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - Zn1 -0.2709519327 -0.1564259419 0.0000000000 - O1 0.2709519327 0.1564259419 0.0000000000 ------------------------------------------------------------------------------------------- - - - - -------------------------------------------- - !FINAL_ETOT_IS -1936.985622730090 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf deleted file mode 100644 index c3ae15baf53..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf +++ /dev/null @@ -1,135 +0,0 @@ - READING UNITCELL INFORMATION - Number of elements = 2 - Lattice constant (Bohr) = 6.1416 - Lattice constant (Angstrom) = 3.24999 - - TOTAL ATOM NUMBER = 2 - - DIRECT COORDINATES - atom x y z mag - Zn 0.000000000000 0.000000000000 0.000000000000 0.0000 - O 0.333329999999 0.666670000001 0.500000000000 0.0000 - - ATOMIC VELOCITIES - atom vx vy vz - Zn 0 0 0 - O 0 0 0 - - Cell volume (Bohr^3) = 320.983 - Cell volume (A^3) = 47.5648 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0 +0 - -0.5 +0.866 +0 - +0 +0 +1.6 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1 +0.577367 -0 - -0 +1.15473 -0 - +0 -0 +0.625 - - #ENERGY SOLVER# ksdft_lcao - - SETUP K-POINTS - nspin = 1 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 2 - Right-handed lattice = 1 - - For reciprocal-space lattice - Bravais lattice type = 11 - Bravais lattice name = 11. Orthorhombic C (base-centered) - Right-handed lattice = 1 - - For k-vectors - Bravais lattice type = 12 - Bravais lattice name = 12. Monoclinic P (simple) - Right-handed lattice = 1 - Number of rotation matrices = 8 - Number of irreducible k-points = 2 - - K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.5000 0 - 2 0.50000000 0.00000000 0.00000000 0.5000 1 - - nkstot now = 2 - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.5000 - 2 0.50000000 0.00000000 0.00000000 0.5000 - - - Number of k-points in this process = 2 - Minimum distributed k-point number = 2 - - K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 1.0000 - 2 0.50000000 0.28868360 0.00000000 1.0000 - - - K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 1.0000 - 2 0.50000000 0.00000000 0.00000000 1.0000 - - DONE : INIT K-POINTS Time : 0.172973 (SEC) - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 1 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Electron density deviation 0.0893377 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.2578934767 -1935.5179367603 - E_Harris -143.4816580020 -1952.1681073148 - E_Fermi -0.0852280930 -1.1595876945 - E_gap(k) 0.3010005903 4.0953231295 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 2 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Electron density deviation 0.0527619 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.3016957759 -1936.1138976154 - E_Harris -143.6433224427 -1954.3676648718 - E_Fermi 0.1242110447 1.6899779627 - E_gap(k) 0.0037426876 0.0509218768 - ---------------------------------------------------------- - - - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - --> #ION MOVE# 1 #ELEC ITER# 3 - ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Electron density deviation 0.0364934 - ---------------------------------------------------------- - Energy Rydberg eV - ---------------------------------------------------------- - E_KohnSham -142.2855446344 -1935.8941500616 - E_KS(sigma->0) -142.2812283423 -1935.8354238938 - E_Harris -143.1222058877 -1947.2775104019 - E_band -6.0716994722 -82.6097093654 - E_one_elec -82.4655761013 -1122.0017238299 - E_Hartree 52.4529512574 713.6590140163 - E_xc -22.3825551768 -304.5302862042 - E_Ewald -89.8817320294 -1222.9037017083 - E_entropy(-TS) -0.0086325843 -0.1174523356 - E_descf 0.0000000000 0.0000000000 - E_localpp -62.8392385880 -854.9717027777 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.1211730503 1.6486439277 - E_gap(k) 0.0066246245 0.0901326401 - ---------------------------------------------------------- - - #SCF IS CONVERGED# - #TOTAL ENERGY# -1935.8941501 eV - Write eigenvalues and occupations to file: OUT.ABACUS/eig_occ.txt - - -------------------------------------------- - !FINAL_ETOT_IS -1935.894150061628 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf_ b/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf_ deleted file mode 100644 index 264f7a2790c..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/lcao-symm1-nspin1-multik-scf_ +++ /dev/null @@ -1,259 +0,0 @@ -NBANDS = 24 - READING UNITCELL INFORMATION - ntype = 2 - lattice constant (Bohr) = 6.1416 - lattice constant (Angstrom) = 3.24999 - - TOTAL ATOM NUMBER = 2 -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_Zn1 0.0000000000 0.0000000000 0.0000000000 0.0000 0.0000000000 0.0000000000 0.0000000000 -taud_O1 0.3333300000 0.6666700000 0.5000000000 0.0000 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 320.983 - Volume (A^3) = 47.5648 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +1 +0 +0 - -0.5 +0.866 +0 - +0 +0 +1.6 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +1 +0.577367 -0 - -0 +1.15473 -0 - +0 -0 +0.625 - The esolver type has been set to : ksdft_lcao - - SETUP K-POINTS - nspin = 1 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 3 - right hand lattice = 1 -(for reciprocal lattice: ) - BRAVAIS TYPE = 11 - BRAVAIS LATTICE NAME = 11. Orthorhombic C (base-centered) - ibrav = 11 - right hand lattice = 1 -(for k-lattice: ) - BRAVAIS TYPE = 12 - BRAVAIS LATTICE NAME = 12. Monoclinic P (simple) - ibrav = 12 - right hand lattice = 1 - ROTATION MATRICES = 8 - nkstot_ibz = 2 -K-POINTS REDUCTION ACCORDING TO SYMMETRY - IBZ DIRECT_X DIRECT_Y DIRECT_Z WEIGHT ibz2bz - 1 0.00000000 0.00000000 0.00000000 0.3333 0 - 2 0.33333333 0.00000000 0.00000000 0.6667 1 - - nkstot now = 2 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.3333 - 2 0.33333333 0.00000000 0.00000000 0.6667 - - - k-point number in this process = 2 - minimum distributed K point number = 2 - -K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.6667 - 2 0.33333333 0.19245574 0.00000000 1.3333 - - -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 0.6667 - 2 0.33333333 0.00000000 0.00000000 1.3333 - - DONE : INIT K-POINTS Time : 0.175334 (SEC) - - LCAO ALGORITHM --------------- ION= 1 ELEC= 1-------------------------------- - - Density error is 0.116736119788 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.1079725377 -1933.4781577397 - E_Harris -143.5030834324 -1952.4596152498 - E_Fermi -0.0839876907 -1.1427111550 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 2-------------------------------- - - Density error is 0.109138222131 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3154389289 -1936.3008828043 - E_Harris -143.8122525428 -1956.6660767972 - E_Fermi 0.1798485039 2.4469644292 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 3-------------------------------- - - Density error is 0.0699576059847 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.3463092814 -1936.7208954978 - E_Harris -142.9090540038 -1944.3774302413 - E_Fermi 0.0604391077 0.8223162464 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 4-------------------------------- - Energy (eV) & Occupations for spin=1 K-point=1 - 1 -14.1855 0.666667 - 2 -5.40800 0.666667 - 3 -5.35442 0.666667 - 4 -5.35098 0.666667 - 5 -5.16667 0.666667 - 6 -5.13296 0.666667 - 7 -2.53068 0.666667 - 8 -0.450756 0.666667 - 9 1.25776 0.0262682 - 10 1.27497 0.0201777 - 11 6.70135 0.00000 - 12 11.8000 0.00000 - 13 14.8582 0.00000 - 14 14.8935 0.00000 - 15 23.1672 0.00000 - 16 23.1756 0.00000 - 17 25.6709 0.00000 - 18 27.0330 0.00000 - 19 31.0530 0.00000 - 20 31.0704 0.00000 - 21 36.3368 0.00000 - 22 36.3404 0.00000 - 23 45.4171 0.00000 - 24 45.4249 0.00000 - Energy (eV) & Occupations for spin=1 K-point=2 - 1 -13.9469 1.33333 - 2 -5.45027 1.33333 - 3 -5.29277 1.33333 - 4 -5.20247 1.33333 - 5 -5.18723 1.33333 - 6 -5.15617 1.33333 - 7 -1.06442 1.33333 - 8 0.249999 1.33333 - 9 0.623862 1.32772 - 10 1.01520 0.625831 - 11 6.53028 0.00000 - 12 7.87336 0.00000 - 13 11.9322 0.00000 - 14 14.2512 0.00000 - 15 17.0724 0.00000 - 16 18.3473 0.00000 - 17 23.5221 0.00000 - 18 27.6028 0.00000 - 19 29.9496 0.00000 - 20 33.0442 0.00000 - 21 34.1542 0.00000 - 22 34.4176 0.00000 - 23 46.5051 0.00000 - 24 48.5929 0.00000 - - Density error is 0.0750606787978 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.2614224469 -1935.5659508624 - E_KS(sigma->0) -142.2579788666 -1935.5190985488 - E_Harris -142.8938709499 -1944.1708541959 - E_band -6.0463316667 -82.2645626649 - E_one_elec -82.7951856427 -1126.4862917089 - E_Hartree 53.2371174712 724.3281427039 - E_xc -22.3749457121 -304.4267541254 - E_Ewald -89.8817320293 -1222.9037017083 - E_entropy(-TS) -0.0068871606 -0.0937046273 - E_descf -0.4397893733 -5.9836413965 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.0738009554 1.0041135109 ----------------------------------------------------------- - - - LCAO ALGORITHM --------------- ION= 1 ELEC= 5-------------------------------- - - Density error is 0.0745845715339 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.2386028344 -1935.2554741064 - E_KS(sigma->0) -142.2353299761 -1935.2109445858 - E_Harris -142.9041261418 -1944.3103832391 - E_band -6.1936955078 -84.2695505825 - E_one_elec -82.6362478781 -1124.3238324822 - E_Hartree 52.8462564148 719.0102052107 - E_xc -22.3898719929 -304.6298365935 - E_Ewald -89.8817320293 -1222.9037017083 - E_entropy(-TS) -0.0065457165 -0.0890590413 - E_descf -0.1704616325 -2.3192494919 - E_exx 0.0000000000 0.0000000000 - E_Fermi -0.0124380091 -0.1692277961 ----------------------------------------------------------- - - !! convergence has not been achieved @_@ - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 1 - 1/2 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (893 pws) - 1 -15.5135 0.666667 - 2 -4.89219 0.666667 - 3 -4.81916 0.666667 - 4 -4.81485 0.666667 - 5 -4.64692 0.666667 - 6 -4.63262 0.666667 - 7 -2.19547 0.666667 - 8 -1.24988 0.666667 - 9 -0.220439 0.425770 - 10 -0.142710 0.284736 - 11 6.51116 0.00000 - 12 11.3767 0.00000 - 13 15.2976 0.00000 - 14 15.3258 0.00000 - 15 23.5074 0.00000 - 16 23.5115 0.00000 - 17 25.4888 0.00000 - 18 27.0509 0.00000 - 19 31.2293 0.00000 - 20 31.2429 0.00000 - 21 35.4387 0.00000 - 22 35.4738 0.00000 - 23 44.3435 0.00000 - 24 44.3518 0.00000 - - 2/2 kpoint (Cartesian) = 0.33333 0.19246 0.0000 (885 pws) - 1 -15.3000 1.33333 - 2 -4.92301 1.33333 - 3 -4.78276 1.33333 - 4 -4.69716 1.33333 - 5 -4.65638 1.33333 - 6 -4.64658 1.33333 - 7 -1.61044 1.33333 - 8 -0.684688 1.33310 - 9 -0.432982 1.28827 - 10 0.272787 0.00146112 - 11 6.87354 0.00000 - 12 7.89763 0.00000 - 13 12.3328 0.00000 - 14 13.6686 0.00000 - 15 17.1208 0.00000 - 16 18.6712 0.00000 - 17 22.9805 0.00000 - 18 27.7747 0.00000 - 19 29.1320 0.00000 - 20 32.7679 0.00000 - 21 34.1113 0.00000 - 22 34.7323 0.00000 - 23 45.8107 0.00000 - 24 47.8444 0.00000 - - - - -------------------------------------------- - !FINAL_ETOT_IS -1935.255474106441 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/pw-symm0-nspin4-gamma-md_ b/interfaces/ASE_interface/abacuslite/io/testfiles/pw-symm0-nspin4-gamma-md_ deleted file mode 100644 index 45143f8269c..00000000000 --- a/interfaces/ASE_interface/abacuslite/io/testfiles/pw-symm0-nspin4-gamma-md_ +++ /dev/null @@ -1,409 +0,0 @@ -NBANDS = 35 - READING UNITCELL INFORMATION - ntype = 2 - lattice constant (Bohr) = 1 - lattice constant (Angstrom) = 0.529177 - - TOTAL ATOM NUMBER = 2 -DIRECT COORDINATES - atom x y z mag vx vy vz -taud_As1 0.2500000000 0.2500000000 0.2500000000 1.7321 0.0000000000 0.0000000000 0.0000000000 -taud_Ga1 0.0000000000 0.0000000000 0.0000000000 1.7321 0.0000000000 0.0000000000 0.0000000000 - - - - Volume (Bohr^3) = 304.884 - Volume (A^3) = 45.1791 - - Lattice vectors: (Cartesian coordinate: in unit of a_0) - +5.34197 +5.34197 +0 - +0 +5.34197 +5.34197 - +5.34197 +0 +5.34197 - Reciprocal vectors: (Cartesian coordinate: in unit of 2 pi/a_0) - +0.0935984 +0.0935984 -0.0935984 - -0.0935984 +0.0935984 +0.0935984 - +0.0935984 -0.0935984 +0.0935984 - The esolver type has been set to : ksdft_pw - - SETUP K-POINTS - nspin = 4 - Input type of k points = Monkhorst-Pack(Gamma) - nkstot = 1 -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 1.0000 - - - k-point number in this process = 1 - minimum distributed K point number = 1 - -K-POINTS CARTESIAN COORDINATES - KPOINTS CARTESIAN_X CARTESIAN_Y CARTESIAN_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 1.0000 - - -K-POINTS DIRECT COORDINATES - KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT - 1 0.00000000 0.00000000 0.00000000 1.0000 - - DONE : INIT K-POINTS Time : 0.062681 (SEC) - - ------------------------------------------- - STEP OF MOLECULAR DYNAMICS : 0 - ------------------------------------------- - init_chg = atomic - DONE : INIT SCF Time : 0.344263 (SEC) - - - PW ALGORITHM --------------- ION= 1 ELEC= 1-------------------------------- -total magnetism (Bohr mag/cell) 0 0 1.43092 - absolute magnetism (Bohr mag/cell) = 1.46002 - - Density error is 0.425536171685 - Error Threshold = 0.01 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.5414819203 -1939.3763554796 - E_Harris -145.5088261529 -1979.7491449706 - E_Fermi 0.5470939917 7.4435956292 ----------------------------------------------------------- - - - PW ALGORITHM --------------- ION= 1 ELEC= 2-------------------------------- -total magnetism (Bohr mag/cell) 0 0 1.29536921409 - absolute magnetism (Bohr mag/cell) = 1.47327692817 - - Density error is 0.147809332734 - Error Threshold = 0.00236408984269 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.6162210470 -1940.3932334666 - E_Harris -143.6867081318 -1954.9579574558 - E_Fermi 0.5738478660 7.8076007634 ----------------------------------------------------------- - - - PW ALGORITHM --------------- ION= 1 ELEC= 3-------------------------------- -total magnetism (Bohr mag/cell) 0 0 1.90445147148 - absolute magnetism (Bohr mag/cell) = 1.92389538407 - - Density error is 0.044016528552 - Error Threshold = 0.000821162959636 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.6278099556 -1940.5509086572 - E_KS(sigma->0) -142.6217168267 -1940.4680073850 - E_Harris -142.2884165619 -1935.9332246392 - E_band -3.4602160123 -47.0786540781 - E_one_elec -81.1361343512 -1103.9137408700 - E_Hartree 53.0833187552 722.2356038215 - E_xc -21.5526527706 -293.2388846958 - E_Ewald -93.0101553311 -1265.4680843686 - E_entropy(-TS) -0.0121862579 -0.1658025443 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.6044100604 8.2234207503 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -1940.5509087 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 4 - 1/1 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (230 pws) - 1 -8.75727 1.00000 - 2 -8.71932 1.00000 - 3 -8.58843 1.00000 - 4 -8.56405 1.00000 - 5 -8.31527 1.00000 - 6 -8.08147 1.00000 - 7 -7.99239 1.00000 - 8 -7.87334 1.00000 - 9 -7.83653 1.00000 - 10 -7.77801 1.00000 - 11 -5.45404 1.00000 - 12 -4.60934 1.00000 - 13 6.99458 1.00000 - 14 7.16826 1.00000 - 15 7.42743 0.999982 - 16 7.81508 0.983090 - 17 7.88204 0.961984 - 18 8.12687 0.692099 - 19 8.29292 0.358984 - 20 8.73601 0.00386068 - 21 10.5767 0.00000 - 22 10.6695 0.00000 - 23 10.7736 0.00000 - 24 11.2484 0.00000 - 25 11.3392 0.00000 - 26 11.4112 0.00000 - 27 14.2930 0.00000 - 28 14.7540 0.00000 - 29 17.3699 0.00000 - 30 17.4624 0.00000 - 31 17.8797 0.00000 - 32 17.9894 0.00000 - 33 18.4671 0.00000 - 34 18.4688 0.00000 - 35 18.5138 0.00000 - - - ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - As1 1.0712942948 2.1781413019 1.2152201133 - Ga1 -1.0712942948 -2.1781413019 -1.2152201133 ------------------------------------------------------------------------------------------- - -------------------------------------------------------------------------------------------- - Total Magnetism (uB) x y z -------------------------------------------------------------------------------------------- - As1 0.0000000000 0.0000000000 3.6203214193 - Ga1 0.0000000000 0.0000000000 3.6203214193 -------------------------------------------------------------------------------------------- - -------------------------------------------------------------------------------------------- - Total Magnetism (uB) Magnitude (uB) Polar (degree) Azimuth (degree) -------------------------------------------------------------------------------------------- - As1 3.6203214193 0.0000000000 0.0000000000 - Ga1 3.6203214193 0.0000000000 0.0000000000 -------------------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------------------------ - Energy (Ry) Potential (Ry) Kinetic (Ry) Temperature (K) - -142.62496 -142.62781 0.0028501339 300.00000 - ------------------------------------------------------------------------------------------------ - - - - ------------------------------------------- - STEP OF MOLECULAR DYNAMICS : 1 - ------------------------------------------- - NEW-OLD atomic charge density approx. for the potential ! - DONE : INIT SCF Time : 1.27344 (SEC) - - - PW ALGORITHM --------------- ION=2 ELEC=1 -------------------------------- - Notice: Threshold on eigenvalues was too large. - hsover_error=0.180000 > DRHO=0.0154165 - Origin diag ethr = 0.0100000 - New diag ethr = 8.56474e-05 -total magnetism (Bohr mag/cell) 0.00000 0.00000 1.92056 - absolute magnetism (Bohr mag/cell) = 1.92336 - - Density error is 0.0450726236251 - Error Threshold = 8.56473592548e-05 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.6244218927 -1940.5048116970 - E_KS(sigma->0) -142.6165258278 -1940.3973802229 - E_Harris -141.7382337402 -1928.4476033223 - E_band -2.9490141284 -40.1233956284 - E_one_elec -82.0470893599 -1116.3079196092 - E_Hartree 54.0249296563 735.0468773755 - E_xc -21.5799670555 -293.6105146070 - E_Ewald -93.0065030040 -1265.4183919081 - E_entropy(-TS) -0.0157921297 -0.2148629482 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.6723961422 9.1484188472 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -1940.5048117 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 4 - 1/1 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (230 pws) - 1 -8.54134 1.00000 - 2 -8.49986 1.00000 - 3 -8.39548 1.00000 - 4 -8.38225 1.00000 - 5 -8.10904 1.00000 - 6 -8.09947 1.00000 - 7 -8.01762 1.00000 - 8 -7.94956 1.00000 - 9 -7.93862 1.00000 - 10 -7.87801 1.00000 - 11 -4.80928 1.00000 - 12 -3.75351 1.00000 - 13 7.57656 1.00000 - 14 7.75964 1.00000 - 15 8.11289 1.00000 - 16 8.69241 0.991104 - 17 8.97032 0.822677 - 18 9.04463 0.705205 - 19 9.17915 0.436556 - 20 9.47574 0.0444576 - 21 10.8453 0.00000 - 22 10.9566 0.00000 - 23 10.9829 0.00000 - 24 11.6632 0.00000 - 25 11.7908 0.00000 - 26 11.8574 0.00000 - 27 14.5416 0.00000 - 28 15.1202 0.00000 - 29 17.6699 0.00000 - 30 17.7719 0.00000 - 31 18.3448 0.00000 - 32 18.4351 0.00000 - 33 18.5704 0.00000 - 34 18.6714 0.00000 - 35 18.7804 0.00000 - - - ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - As1 -5.8703800606 -4.7946411869 -6.0982122022 - Ga1 5.8703800606 4.7946411869 6.0982122022 ------------------------------------------------------------------------------------------- - - - - ------------------------------------------------------------------------------------------------ - Energy (Ry) Potential (Ry) Kinetic (Ry) Temperature (K) - -142.62247 -142.62442 0.0019474418 204.98424 - ------------------------------------------------------------------------------------------------ - - - - ------------------------------------------- - STEP OF MOLECULAR DYNAMICS : 2 - ------------------------------------------- - first order charge density extrapolation ! - DONE : INIT SCF Time : 1.71701 (SEC) - - - PW ALGORITHM --------------- ION=3 ELEC=1 -------------------------------- -total magnetism (Bohr mag/cell) 0.00000 0.00000 1.95640 - absolute magnetism (Bohr mag/cell) = 1.95962 - - Density error is 0.477550291489 - Error Threshold = 0.0100000000000 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.6498507178 -1940.8507886117 - E_Harris -146.4491358068 -1992.5427141488 - E_Fermi 0.6192799018 8.4257353211 ----------------------------------------------------------- - - - PW ALGORITHM --------------- ION= 3 ELEC= 2-------------------------------- -total magnetism (Bohr mag/cell) 0.00000000000 0.00000000000 1.98933318075 - absolute magnetism (Bohr mag/cell) = 2.03111991516 - - Density error is 0.175985640789 - Error Threshold = 0.00265305717494 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.5965805196 -1940.1260103830 - E_Harris -144.3777550999 -1964.3601338067 - E_Fermi 0.6461984287 8.7919806691 ----------------------------------------------------------- - - - PW ALGORITHM --------------- ION= 3 ELEC= 3-------------------------------- -total magnetism (Bohr mag/cell) 0.00000000000 0.00000000000 1.97493083463 - absolute magnetism (Bohr mag/cell) = 2.02445029910 - - Density error is 0.0131512819145 - Error Threshold = 0.000977698004383 ----------------------------------------------------------- - Energy Rydberg eV ----------------------------------------------------------- - E_KohnSham -142.6376231476 -1940.6844239839 - E_KS(sigma->0) -142.6342116592 -1940.6380083031 - E_Harris -142.3168424376 -1936.3199785192 - E_band -2.9205568119 -39.7362139749 - E_one_elec -81.5182312080 -1109.1124353098 - E_Hartree 53.4665656534 727.4499453771 - E_xc -21.5755011497 -293.5497528418 - E_Ewald -93.0036334665 -1265.3793498479 - E_entropy(-TS) -0.0068229768 -0.0928313616 - E_descf 0.0000000000 0.0000000000 - E_exx 0.0000000000 0.0000000000 - E_Fermi 0.6632047358 9.0233633480 ----------------------------------------------------------- - - - charge density convergence is achieved - final etot is -1940.6844240 eV - - STATE ENERGY(eV) AND OCCUPATIONS NSPIN == 4 - 1/1 kpoint (Cartesian) = 0.0000 0.0000 0.0000 (230 pws) - 1 -8.31421 1.00000 - 2 -8.20430 1.00000 - 3 -8.17182 1.00000 - 4 -8.14404 1.00000 - 5 -7.85336 1.00000 - 6 -7.76073 1.00000 - 7 -7.71833 1.00000 - 8 -7.68256 1.00000 - 9 -7.64568 1.00000 - 10 -7.60154 1.00000 - 11 -5.11512 1.00000 - 12 -3.87763 1.00000 - 13 7.03852 1.00000 - 14 7.57655 1.00000 - 15 7.83884 1.00000 - 16 8.41377 0.999233 - 17 8.62549 0.980671 - 18 8.79132 0.886086 - 19 9.25378 0.115555 - 20 9.42488 0.0184549 - 21 10.6927 0.00000 - 22 10.9099 0.00000 - 23 11.0110 0.00000 - 24 11.5312 0.00000 - 25 11.7823 0.00000 - 26 11.8581 0.00000 - 27 14.4850 0.00000 - 28 15.1448 0.00000 - 29 17.4644 0.00000 - 30 17.6904 0.00000 - 31 18.2557 0.00000 - 32 18.5078 0.00000 - 33 18.5427 0.00000 - 34 18.6574 0.00000 - 35 18.7331 0.00000 - - - ------------------------------------------------------------------------------------------- - TOTAL-FORCE (eV/Angstrom) ------------------------------------------------------------------------------------------- - As1 -10.1802174084 -5.5311564523 -6.8477853698 - Ga1 10.1802174084 5.5311564523 6.8477853698 ------------------------------------------------------------------------------------------- - -------------------------------------------------------------------------------------------- - Total Magnetism (uB) x y z -------------------------------------------------------------------------------------------- - As1 0.0000000000 0.0000000000 3.6203214193 - Ga1 0.0000000000 0.0000000000 3.6203214193 -------------------------------------------------------------------------------------------- - -------------------------------------------------------------------------------------------- - Total Magnetism (uB) Magnitude (uB) Polar (degree) Azimuth (degree) -------------------------------------------------------------------------------------------- - As1 3.6203214193 0.0000000000 0.0000000000 - Ga1 3.6203214193 0.0000000000 0.0000000000 -------------------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------------------------ - Energy (Ry) Potential (Ry) Kinetic (Ry) Temperature (K) - -142.57602 -142.63762 0.061605080 6484.4407 - ------------------------------------------------------------------------------------------------ - - -------------------------------------------- - !FINAL_ETOT_IS -1940.684423983896 eV - -------------------------------------------- diff --git a/interfaces/ASE_interface/abacuslite/utils/__init__.py b/interfaces/ASE_interface/abacuslite/utils/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/interfaces/ASE_interface/abacuslite/utils/ksampling.py b/interfaces/ASE_interface/abacuslite/utils/ksampling.py deleted file mode 100644 index 328973e9558..00000000000 --- a/interfaces/ASE_interface/abacuslite/utils/ksampling.py +++ /dev/null @@ -1,260 +0,0 @@ -'''this module provides some useful tools for k-sampling''' -import seekpath -import unittest -from typing import Optional, Tuple, List, Dict - -import numpy as np -from ase.atoms import Atoms - -def convert_kspacing_to_kpts(cell: np.ndarray, - kspacing: float | Tuple[float, float, float])\ - -> Tuple[int, int, int]: - ''' - convert the kspacing to kpts, according to the given cell in Angstrom. - This function would be helpful for those calculators that only support - kpts, but not kspacing. A negative value in kspacing would yield nk=1 - for that direction. - - Parameters - ---------- - cell : np.ndarray - The cell in Angstrom, in shape (3, 3). - kspacing : float or tuple of float - The kspacing in Angstrom. If a tuple of float is given, it would be - interpreted as the kspacing in each direction. - - Returns - ------- - tuple of int - The kpts in each direction. - ''' - # get reciprocal cell vectors... - bvec = 2*np.pi * np.linalg.solve(cell.T, np.eye(3)) - bnorm = np.linalg.norm(bvec, axis=1).tolist() - - kspacing = (kspacing, kspacing, kspacing) if isinstance(kspacing, float) else kspacing - assert len(kspacing) == 3 - nk = [int(norm / kspac) if kspac > 0 else 1 for kspac, norm in zip(kspacing, bnorm)] - return tuple(map(lambda x: max(1, x+1), nk)) - -def get_kpath(atoms: Atoms, n_interpl: int = 10) -> Tuple[np.ndarray, np.ndarray, List[str]]: - '''''' - from seekpath import get_path as _corefunc_seekpath - seeked = _corefunc_seekpath( - structure=(atoms.get_cell().tolist(), - atoms.get_scaled_positions(), - atoms.get_atomic_numbers())) - - # get the most useful informaiton - k = np.array(list(seeked['point_coords'].values())) - assert k.ndim == 2 - nk, dim = k.shape - assert dim == 3 - - # interpolate... - kvec = np.vstack([np.linspace(k[i, :], k[i + 1, :], - num=n_interpl+int(i == nk - 2), - endpoint=bool(i == nk - 2)) - for i in range(nk - 1)]) - - # for easy-drawing - kdist = np.linalg.norm(kvec[1:] - kvec[:-1], axis=1) - kdist = np.cumsum(kdist) - kdist = np.hstack([[0], kdist]) - - kname = list(seeked['point_coords'].keys()) - kname = [[kn] + [''] * (n_interpl-1) for kn in kname[:-1]] + [[kname[-1]]] - kname = [n for kn in kname for n in kn] - - # finally, we assert the consistency of the data - assert len(kvec) == len(kdist) == len(kname), f'{len(kvec)}, {len(kdist)}, {len(kname)}' - return kvec, kdist, kname - -def interpolate_kpath(knodes: np.ndarray, - knames: List[str], - n_interpl: int | List[int] = 10) -> Tuple[np.ndarray, np.ndarray, List[str]]: - ''' - interpolate between the given k-points (nodes, as they are nodes along the path). - The number of interpolated points is given by `n_interpl`, in which the last - number will be ignored. - ''' - if isinstance(n_interpl, int): - n_interpl = [n_interpl] * (len(knodes) - 1) + [1] - assert isinstance(knodes, np.ndarray) - assert knodes.ndim == 2 - nk, dim = knodes.shape - - assert dim == 3 - assert len(n_interpl) == len(knames) == nk - - kvec = np.vstack([np.linspace(knodes[i, :], knodes[i + 1, :], - num=n_interpl[i]+int(i == nk - 2), - endpoint=bool(i == nk - 2)) - for i in range(nk - 1)]) - - # for easy-drawing - kdist = np.linalg.norm(kvec[1:] - kvec[:-1], axis=1) - kdist = np.cumsum(kdist) - kdist = np.hstack([[0], kdist]) - - # padding the knames to have the same length as kvec - kname = [[kn] + [''] * (n_interpl[i]-1) for i, kn in enumerate(knames[:-1])] + [[knames[-1]]] - kname = [n for kn in kname for n in kn] - - # finally, we assert the consistency of the data - assert len(kvec) == len(kdist) == len(kname), f'{len(kvec)}, {len(kdist)}, {len(kname)}' - return kvec, kdist, kname - -def merge_ksgm(segments) -> Tuple[List[str], List[bool]]: - ''' - SeeKpath generates the path of kpoints in the format of list of 2-element tuples. - This is good but not quite compatible with some DFT softwares like ABACUS. This - function will return a list of special kpoint labels accompanied with a boolean - list that helps distinguish the breakpoint of path. - - Parameters - ---------- - segments : List[Tuple[str, str]] - The list of kpath segments. Each segment is a tuple of two strings, - where the first string is the starting point of the segment and the - second string is the ending point of the segment, like [(Gamma, M), - (M, K), (K, Gamma)] - - Returns - ------- - Tuple[List[str], List[bool]] - The first element is the list of special kpoint labels. The second element - is the boolean list that helps distinguish the breakpoint of path. - ''' - klabels, is_brkpt = [segments[0][0]], [False] - for i, (start, end) in enumerate(segments): - if start != klabels[-1]: - is_brkpt[-1] = True - klabels.append(start) - is_brkpt.append(False) - - klabels.append(end) - is_brkpt.append(i == len(segments) - 1) - - return klabels, is_brkpt - -def make_kstring(klabels: List[str], - is_brkpt: List[bool]) -> str: - ''' - Make the kpoint path in the str. For path in which there are break point, - will use the comma to distinguish - - Parameters - ---------- - klabels : List[str] - The list of special kpoint labels. - is_brkpt : List[bool] - The boolean list that helps distinguish the breakpoint of path. - - Returns - ------- - str - The kstring that ABACUS uses to represent the kpath. - ''' - out = '' - for lbl, brkpt in zip(klabels, is_brkpt): - out += lbl - if brkpt: - out += ',' - return out[:-1] # remove the last comma - -def make_klines(kpts, - is_brkpt, - n_interpl, - klabels) -> List[Dict[str, np.ndarray | str | int]]: - ''' - gather the information and output the list of dicts that organizes the - information easy for exporting the ABAUCS KLINES file. Each dictionary - contains the keys `coord`, `label`, `n`, where `coord` is the coordinate - of kpoint, `label` is the label of kpoint, and `n` is the number of - interpolation points between the next kpoint and itself (the next kpoint - included). - - Parameters - ---------- - kpts : np.ndarray - The array of kpoints. - is_brkpt : List[bool] - The boolean list that helps distinguish the breakpoint of path. - n_interpl : int - The number of interpolation points between the kpoints. - klabels : List[str] - The list of special kpoint labels. - - Returns - ------- - List[Dict[str, np.ndarray | str | int]] - The list of dictionaries that organizes the information easy for - exporting the ABAUCS KLINES file. - ''' - def fspawnk(c: np.ndarray, n: int, label: str) -> Dict[str, np.ndarray | str | int]: - return {'coord': c, 'label': label, 'n': n} - - return [fspawnk(c, 1 if is_brkpt[i] else n_interpl, klbl) - for i, (c, klbl) in enumerate(zip(kpts, klabels))] - -def kpathgen(atoms: Atoms) -> Tuple[str, Dict[str, List[float]]]: - ''' - Generate the k-path by default SeeK-Path flavor in the format - compatible with ase bandpath module - ''' - kpathseen = seekpath.get_path( - structure=(np.array(atoms.get_cell()), - atoms.get_scaled_positions(), - atoms.get_atomic_numbers()), - with_time_reversal=True - ) - - # convert the kpoint path to the format that is acceptable by ASE - kpathstr, is_brkpt = merge_ksgm(kpathseen['path']) - fklblfilter = lambda lbl: lbl if lbl != 'GAMMA' else 'G' - kpathstr = make_kstring([fklblfilter(lbl) for lbl in kpathstr], is_brkpt) - kpathstr = ''.join([k if k != 'GAMMA' else 'G' for k in kpathstr]) - # seekpath use 'GAMMA' as the gamma point symbol, while the ASE use 'G' - kspecial = {k: v for k, v in kpathseen['point_coords'].items() if k != 'GAMMA'} - kspecial['G'] = [0.0, 0.0, 0.0] - return kpathstr, kspecial - -# the following are for quick test on the silicon case -SILICON_KNODES = np.array([[0.0000000000, 0.0000000000, 0.0000000000], - [0.5000000000, 0.0000000000, 0.5000000000], - [0.6250000000, 0.2500000000, 0.6250000000], - [0.3750000000, 0.3750000000, 0.7500000000], - [0.0000000000, 0.0000000000, 0.0000000000], - [0.5000000000, 0.5000000000, 0.5000000000], - [0.5000000000, 0.2500000000, 0.7500000000], - [0.5000000000, 0.0000000000, 0.5000000000]]) -SILICON_KNAMES = ["G", "X", "X/U", "K", "G", "L", "W", "X"] -SILICON_NINTPL = [50, 50, 1, 50, 50, 50, 50, 1] # from the DeePTB example :) - -class TestKsampling(unittest.TestCase): - - def test_convert_kspacing(self): - # test-reference: ABACUS 3.8.4 implementation - from ase.geometry import cellpar_to_cell - - cell = cellpar_to_cell([4.22798145, 4.22798145, 4.22798145, 60, 60, 60]) - kpts = convert_kspacing_to_kpts(cell, 0.03*1.889725989) - # kspacing 0.03 Bohr-1, multiply 1.889725989 to convert to Angstrom-1 - self.assertEqual(kpts, (33, 33, 33)) - - def test_merge_ksgm(self): - klables, is_brkpt = merge_ksgm([('GAMMA', 'X'), ('X', 'U'), ('K', 'GAMMA'), - ('GAMMA', 'L'), ('L', 'W'), ('W', 'X')]) - self.assertEqual(klables, ['GAMMA', 'X', 'U', 'K','GAMMA', 'L', 'W', 'X']) - self.assertEqual(is_brkpt, [False, False, True, False, False, False, False, True]) - - def test_make_kstring(self): - fklblfilter = lambda lbl: lbl if lbl != 'GAMMA' else 'G' - klabels = ['GAMMA', 'X', 'U', 'K','GAMMA', 'L', 'W', 'X'] - klabels = [fklblfilter(lbl) for lbl in klabels] - is_brkpt = [False, False, True, False, False, False, False, True] - self.assertEqual(make_kstring(klabels, is_brkpt), 'GXU,KGLWX') - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/interfaces/ASE_interface/abacuslite/xtest.sh b/interfaces/ASE_interface/abacuslite/xtest.sh deleted file mode 100755 index 47640cfc28b..00000000000 --- a/interfaces/ASE_interface/abacuslite/xtest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -e # Exit on error - -python3 core.py -python3 ./io/generalio.py -v -python3 ./io/legacyio.py -v -python3 ./io/latestio.py -v -python3 ./utils/ksampling.py -v - diff --git a/interfaces/ASE_interface/examples/bandstructure.py b/interfaces/ASE_interface/examples/bandstructure.py deleted file mode 100644 index 626638e21e9..00000000000 --- a/interfaces/ASE_interface/examples/bandstructure.py +++ /dev/null @@ -1,82 +0,0 @@ -''' -This example shows how to run a SCF calculation with ABACUS -of Si diamond structure. - -To run this example, please install the SeeK-path package: -``` -pip install seekpath -``` -. The SeeK-path package recommands you cite by the way posted here: -https://seekpath.materialscloud.io/ -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -from ase.build import bulk -from abacuslite import Abacus, AbacusProfile -from abacuslite.utils.ksampling import kpathgen - -# AbacusProfile: the interface connecting the Abacus calculator instance -# with the file system and the enviroment -aprof = AbacusProfile( - command='mpirun -np 4 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -# Abacus: the calculator instance -jobdir = here / 'bandstructure' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.1 - } -) - -# get the structure, can also from the -# ``` -# from ase.io import read -# atoms = read(...) -# ``` -atoms = bulk('Si', 'diamond', a=5.43) - -# bind the atoms with the abacus -atoms.calc = abacus - -# perform the SCF calculation to get the converged wavefunction -print('SCF calculation get energy:', atoms.get_potential_energy()) - -kpathstr, kspecial = kpathgen(atoms) -# instantiate the bandpath -bandpath = atoms.cell.bandpath(path=kpathstr, - npoints=50, - special_points=kspecial) - -# derive the band structure calculator from SCF calculator -bscalc = atoms.calc.fixed_density(bandpath) -atoms.calc = bscalc -_ = atoms.get_potential_energy() # NSCF calculation will be performed - -bs = bscalc.band_structure() -bs.write('bandstructure.json') -# you can use the ase-cli to plot the JSON file later by: -# ``` -# ase band-structure bandstructure.json -r -10 15 -# ``` -bs.plot(emin=-10, emax=15, filename='bandstructure.png') - -shutil.rmtree(jobdir) \ No newline at end of file diff --git a/interfaces/ASE_interface/examples/cellrelax.py b/interfaces/ASE_interface/examples/cellrelax.py deleted file mode 100644 index a6b5cc06c5f..00000000000 --- a/interfaces/ASE_interface/examples/cellrelax.py +++ /dev/null @@ -1,76 +0,0 @@ -''' -This example shows how to perform a cell-ion joint relaxation with ABACUS -of Si diamond structure. -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -from ase.build import bulk -from ase.filters import FrechetCellFilter -from ase.optimize import BFGS -from abacuslite import Abacus, AbacusProfile - -# AbacusProfile: the interface connecting the Abacus calculator instance -# with the file system and the enviroment -aprof = AbacusProfile( - command='mpirun -np 4 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -# Abacus: the calculator instance -jobdir = here / 'scf' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.1, - 'cal_force': 1, # let ABACUS calculate the forces - 'cal_stress': 1 # let ABACUS calculate the stress - } -) - -# get the structure, can also from the -# ``` -# from ase.io import read -# atoms = read(...) -# ``` -atoms = bulk('Si', 'diamond', a=5.43) - -# bind the atoms with the abacus -atoms.calc = abacus - -# print the energy before relaxation -print(f'Before: {atoms.get_potential_energy()}') -print(f'Cell: {atoms.get_cell()}') - -# perform the relaxation calculation -dyn = BFGS(FrechetCellFilter(atoms), logfile='-') -# or a fixed alpha, beta, gamma relaxation, if you needed -# dyn = BFGS(FrechetCellFilter( -# atoms, -# mask=[True, True, True, False, False, False] -# ), logfile='-') -# the first three mask elements are for the strain, -# the last three are for the shear -dyn.run(fmax=0.05) - -# print the energy after relaxation -print(f'After: {atoms.get_potential_energy()}') -print(f'Cell: {atoms.get_cell()}') - -# remove the temporary job directory (including all files inside) -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/constraintmd.py b/interfaces/ASE_interface/examples/constraintmd.py deleted file mode 100644 index b28a781490c..00000000000 --- a/interfaces/ASE_interface/examples/constraintmd.py +++ /dev/null @@ -1,101 +0,0 @@ -import shutil -import tempfile -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -import numpy as np -import matplotlib.pyplot as plt -from ase.io import read, Trajectory -from ase.md import Langevin -from ase.md.velocitydistribution import MaxwellBoltzmannDistribution -from ase.units import fs -from ase.constraints import FixCartesian -from abacuslite import AbacusProfile, Abacus - -aprof = AbacusProfile( - command='mpirun -np 16 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) -jobdir = here / 'constraintmd' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={ - 'C': 'C_ONCV_PBE-1.0.upf', - 'H': 'H_ONCV_PBE-1.0.upf', - 'F': 'F_ONCV_PBE-1.0.upf', - }, - basissets={ - 'C': 'C_gga_8au_100Ry_2s2p1d.orb', - 'H': 'H_gga_8au_100Ry_2s1p.orb', - 'F': 'F_gga_7au_100Ry_2s2p1d.orb', - }, - inp={ - 'calculation': 'scf', - 'nspin': 2, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 40, - 'scf_thr': 1e-6, - 'symmetry': 1, - 'gamma_only': True, - 'init_chg': 'auto' # small trick, use the previous charge density - } -) - -mol = ''' 6 -CH4-F - C -0.00000000 -0.00000000 -1.50074532 - H 0.00000000 -1.01539888 -1.16330635 - H -0.87936122 0.50769944 -1.16330635 - H 0.87936122 0.50769944 -1.16330635 - H -0.00000000 -0.00000000 -2.57055225 - F 0.00000000 -0.00000000 0.88279136 -''' -with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz') as f: - f.write(mol) - f.flush() - atoms = read(f.name) - -atoms.center(vacuum=5.0) # to reduce the computational cost -# view(atoms) - -# constraint the No.1, 5, 6 atoms' X and Y coordiantes so that -# they can only move along the z-axis -constraint = FixCartesian(a=[0, 4, 5], mask=(True, True, False)) -# apply -atoms.set_constraint(constraint) -MaxwellBoltzmannDistribution(atoms, temperature_K=300) - -atoms.calc = abacus -dyn = Langevin(atoms, - timestep=1.0 * fs, - temperature_K=300, - friction=0.004, - logfile='-', - trajectory='constraintmd.traj') -dyn.run(5) - -# let's see if the X, Y coordinates of No.1, 5, and 6 atoms are really -# fixed -with Trajectory('constraintmd.traj') as traj: - traj = np.array([atoms.get_positions() for atoms in traj]) - -# transpose from (nframe, natom, 3) to (natom, nframe, 3) -traj = traj.transpose(1, 0, 2) - -# plot the trajectory of No.1, 5, and 6 atoms -plt.plot(traj[0, :, 2], label='C') -plt.plot(traj[4, :, 2], label='H') -plt.plot(traj[5, :, 2], label='F') -plt.xlabel('Step') -plt.ylabel('Z (Å)') -plt.legend() -plt.show() - -shutil.rmtree(jobdir) \ No newline at end of file diff --git a/interfaces/ASE_interface/examples/dos.py b/interfaces/ASE_interface/examples/dos.py deleted file mode 100644 index 39a5f12fa84..00000000000 --- a/interfaces/ASE_interface/examples/dos.py +++ /dev/null @@ -1,65 +0,0 @@ -''' -This example shows how to run a SCF calculation with ABACUS -of Si diamond structure. -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -import matplotlib.pyplot as plt -from ase.build import bulk -from ase.dft import DOS -from abacuslite import Abacus, AbacusProfile - -aprof = AbacusProfile( - command='mpirun -np 4 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -jobdir = here / 'scf' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.1 - } -) - -# get the structure, can also from the -# ``` -# from ase.io import read -# atoms = read(...) -# ``` -atoms = bulk('Si', 'diamond', a=5.43) - -# bind the atoms with the abacus -atoms.calc = abacus - -# calculate! -print(atoms.get_potential_energy()) - -doscalc = DOS(atoms.calc, width=0.1) -e, dos = doscalc.get_energies(), doscalc.get_dos() - -plt.plot(e, dos) -plt.xlim(-5, 5) -plt.xlabel('E - E_f (eV)') -plt.ylabel('DOS') -plt.title('DOS of Si Diamond') -plt.show() - -# remove the temporary job directory (including all files inside) -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/md.py b/interfaces/ASE_interface/examples/md.py deleted file mode 100644 index 2fc2bbefbb8..00000000000 --- a/interfaces/ASE_interface/examples/md.py +++ /dev/null @@ -1,72 +0,0 @@ -'''this example recap the example of MD in abacus example -directory, but there the CSVR thermostat is used instead -of those implemented in ABACUS. - -In ASE, the CSVR thermostat is named as the Bussi -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -import numpy as np -from ase.atoms import Atoms -from ase.md import Bussi -from ase.md.velocitydistribution import MaxwellBoltzmannDistribution -from ase.units import fs -from abacuslite import Abacus, AbacusProfile - -cell = np.eye(3) * 5.43090251 -taud = [ - [0.00000000, 0.00000000, 0.00000000], - [0.00000000, 0.50000000, 0.50000000], - [0.50000000, 0.00000000, 0.50000000], - [0.50000000, 0.50000000, 0.00000000], - [0.25000000, 0.25000000, 0.25000000], - [0.25000000, 0.75000000, 0.75000000], - [0.75000000, 0.25000000, 0.75000000], - [0.75000000, 0.75000000, 0.25000000], -] -atoms = Atoms(symbols=['Si' for _ in range(8)], - scaled_positions=taud, - cell=cell, - pbc=True) - -aprof = AbacusProfile( - command='mpirun -np 4 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -jobdir = here / 'md' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', # still use SCF here because the MD is driven by ASE - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.25 # highly unconverged, just for demo - } -) - -atoms.calc = abacus -# initialize the velocities, necessary for CSVR -MaxwellBoltzmannDistribution(atoms, temperature_K=300) - -dyn = Bussi(atoms, - timestep=1*fs, - temperature_K=300, - taut=10*fs, - logfile='-') # let's see the trajectory -dyn.run(2) - -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/metadynamics.py b/interfaces/ASE_interface/examples/metadynamics.py deleted file mode 100644 index 5595d131436..00000000000 --- a/interfaces/ASE_interface/examples/metadynamics.py +++ /dev/null @@ -1,146 +0,0 @@ -''' -To run the metadynamics, you need to configure the plumed correctly: - -conda install -c conda-forge plumed=2.8.2=mpi_openmpi_hb0545ae_0 -conda install -c conda-forge py-plumed - -You may be also interested in a newer version of plumed, a possible solution is -to search `plumed` at conda website: -https://anaconda.org/channels/conda-forge/packages/plumed/files -in which, up to 2026/02/25, the latest version is -``` -linux-64/plumed-2.9.2-mpi_openmpi_h02da92d_0.conda, -``` -you can install it with: -conda install -c conda-forge plumed=2.9.2=mpi_openmpi_h02da92d_0 - -we do not recommend the default version of plumed installed by conda, which -is nompi-labelled, may cause segmentation fault error during the MTD run. - -In this example, we will run a metadynamics simulation to explore the -substitution reaction, during which the famous Walden inversion -(https://en.wikipedia.org/wiki/Walden_inversion) happens: -``` -CH4 + F -> CH3F + H -``` -. Because the F is a radical, we set nspin 2 throughout the simulation. -The structure is like - H H - \ / - H - C F· - | - H -, thus the best Collective Variable (CV) is the difference between the -H-C bond length and the C-F bond length. - -CSVR thermostat is used to maintain the temperature during the simulation. -''' - -import shutil -import tempfile -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -from ase.io import read -from ase.md import Bussi -from ase.md.velocitydistribution import MaxwellBoltzmannDistribution -from ase.units import ( - fs, - kJ as _kJ, - mol as _mol, -) -_ps = 1000 * fs -from ase.constraints import FixCartesian -from ase.calculators.plumed import Plumed -from abacuslite import AbacusProfile, Abacus - -aprof = AbacusProfile( - command='mpirun -np 16 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) -jobdir = here / 'constraintmd' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={ - 'C': 'C_ONCV_PBE-1.0.upf', - 'H': 'H_ONCV_PBE-1.0.upf', - 'F': 'F_ONCV_PBE-1.0.upf', - }, - basissets={ - 'C': 'C_gga_8au_100Ry_2s2p1d.orb', - 'H': 'H_gga_8au_100Ry_2s1p.orb', - 'F': 'F_gga_7au_100Ry_2s2p1d.orb', - }, - inp={ - 'calculation': 'scf', - 'nspin': 2, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 40, - 'scf_thr': 1e-6, - 'symmetry': 1, - 'gamma_only': True, - 'init_chg': 'auto' # small trick, use the previous charge density - } -) - -ch4f = ''' 6 -ABACUS ASE Plugin Metadynamics example structure - C -0.00000000 -0.00000000 -1.50074532 - F 0.00000000 -0.00000000 0.88279136 - H 0.00000000 -1.01539888 -1.16330635 - H -0.87936122 0.50769944 -1.16330635 - H 0.87936122 0.50769944 -1.16330635 - H -0.00000000 -0.00000000 -2.57055225 -''' -with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz') as f: - f.write(ch4f) - f.flush() - atoms = read(f.name) - -atoms.center(vacuum=5.0) # to reduce the computational cost - -# constraint the No.5, 6 atoms' X and Y coordiantes so that -# they can only move along the z-axis, also fix the atom C's -# all components -atoms.set_constraint([FixCartesian(a=[4, 5], mask=(True, True, False)), - FixCartesian(a=[0])]) -MaxwellBoltzmannDistribution(atoms, temperature_K=300) - -setup = [# define the unit within the PLUMED runtime - f'UNITS LENGTH=A TIME={1/_ps} ENERGY={_mol/_kJ}', - # define the two bond lengths - 'd1: DISTANCE ATOMS=1,5', - 'd2: DISTANCE ATOMS=1,6', - # define the CV as the difference between the two bond lengths - 'c1: MATHEVAL ARG=d1,d2 VAR=a,b FUNC=a-b PERIODIC=NO', - # add walls to confine the position of H and F atoms - # such that the C-H bond will have length between 0.5 and 2.0, - # and the C-F bond will have length between 1.0 and 3.0 - 'lwall: LOWER_WALLS ARG=d1,d2 AT=0.5,1.0 KAPPA=150.0,150.0 EXP=2,2', - 'uwall: UPPER_WALLS ARG=d1,d2 AT=2.0,3.0 KAPPA=150.0,150.0 EXP=2,2', - # setup the metadynamics simulation - 'metad: METAD ARG=c1 PACE=5 HEIGHT=0.2 SIGMA=0.05 FILE=HILLS TEMP=300', - 'PRINT STRIDE=1 ARG=d1,d2,c1 FILE=COLVAR'] - -atoms.calc = Plumed(calc=abacus, - input=setup, - timestep=1.0 * fs, - atoms=atoms, - kT=0.1) - -dyn = Bussi(atoms, - timestep=1.0 * fs, - temperature_K=300, - taut=10.0 * fs, - trajectory='metadynamics.traj', - logfile='-') - -dyn.run(20) -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/neb.py b/interfaces/ASE_interface/examples/neb.py deleted file mode 100644 index 1118fcadc19..00000000000 --- a/interfaces/ASE_interface/examples/neb.py +++ /dev/null @@ -1,111 +0,0 @@ -''' -PbTiO3 ferroelectric inversion energy barrier - -Learn how to use the NEB module in ASE, please refer to the online manual at: -https://ase-lib.org/examples_generated/tutorials/neb_idpp.html -''' -from pathlib import Path -here = Path(__file__).parent - -import numpy as np -from ase.io import Trajectory -from ase.atoms import Atoms -from ase.optimize import FIRE -from ase.mep import NEB -import matplotlib.pyplot as plt -from abacuslite import Abacus, AbacusProfile - -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -elem = ['Ti', 'Pb', 'O', 'O', 'O'] -taud = np.array([ - [0.5, 0.5, 0.5948316037314115], - [0.0, 0.0, 0.1235879499999999], - [0.0, 0.5, 0.5094847864489368], - [0.5, 0.0, 0.5094847864489368], - [0.5, 0.5, 0.0088672395150394], -]) -cell = np.array([ - [3.8795519, 0.0000000, 0.00000000], - [0.0000000, 3.8795519, 0.00000000], - [0.0000000, 0.0000000, 4.28588762], -]) - -# we have relaxed with the parameters above :) -up = Atoms(elem, cell=cell, scaled_positions=taud) - -# get the polarisation inversed by inversing the Ti atoms -taud = np.array([ - [0.5, 0.5, 0.6508136593687969], - [0.0, 0.0, 0.1235879499999999], - [0.0, 0.5, 0.7348401327639794], - [0.5, 0.0, 0.7348401327639794], - [0.5, 0.5, 0.2364165087650052], -]) -dw = Atoms(elem, cell=cell, scaled_positions=taud) - -aprof = AbacusProfile( - command='mpirun -np 8 abacus_2p', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1 -) -pseudopotentials = { - 'Ti': 'Ti_ONCV_PBE-1.0.upf', - 'Pb': 'Pb_ONCV_PBE-1.0.upf', - 'O' : 'O_ONCV_PBE-1.0.upf', -} -basissets = { - 'Ti': 'Ti_gga_8au_100Ry_4s2p2d1f.orb', - 'Pb': 'Pb_gga_7au_100Ry_2s2p2d1f.orb', - 'O' : 'O_gga_7au_100Ry_2s2p1d.orb', -} -inp = { - 'profile': aprof, - 'pseudopotentials': pseudopotentials, - 'basissets': basissets, - 'inp': { - 'basis_type': 'lcao', - 'symmetry': 1, - 'kspacing': 0.25, # Oops! - 'init_chg': 'auto', - 'cal_force': 1, - } -} - -n_replica = 7 # the ini and fin images included. 7 is acceptable for production -replica = [] -for irep in range(n_replica): - image = up.copy() if irep <= (n_replica // 2) else dw.copy() - # attach the calculator to each image, so that we can run the optimization - image.calc = Abacus(**inp, directory=here / f'neb-{irep}') - replica.append(image) - -neb = NEB(replica, - k=0.05, # too high value is hard to converge - climb=False, # use True in production run, though CI-NEB is harder to converge - parallel=True) -neb.interpolate('idpp') - -qn = FIRE(neb, trajectory=here / 'neb.traj') -qn.run(fmax=0.05) - -energies = [] -# get the energy profile along the reaction path -with Trajectory(here / 'neb.traj') as traj: - replica = traj[-7:] # the last NEB frames - for i, rep in enumerate(replica): - rep: Atoms # type hint - # the energies of the initial and the final state - # are not calculated, here we calculate them - rep.calc = Abacus(**inp, directory=here / f'neb-{i}') - energies.append(rep.get_potential_energy()) - -energies = np.array(energies) -# plot the energy profile -plt.plot(energies - energies[0], 'o-') -plt.xlabel('NEB image index') -plt.ylabel('Total energies (eV)') -plt.title('Energy profile along the reaction path') -plt.savefig(here / 'energy_profile.png', dpi=300) -plt.close() diff --git a/interfaces/ASE_interface/examples/relax.py b/interfaces/ASE_interface/examples/relax.py deleted file mode 100644 index ac769055a40..00000000000 --- a/interfaces/ASE_interface/examples/relax.py +++ /dev/null @@ -1,67 +0,0 @@ -''' -This example shows how to perform a ion relaxation with ABACUS -of Si diamond structure. -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -from ase.build import bulk -from ase.optimize import BFGS -from abacuslite import Abacus, AbacusProfile - -# AbacusProfile: the interface connecting the Abacus calculator instance -# with the file system and the enviroment -aprof = AbacusProfile( - command='mpirun -np 8 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -# Abacus: the calculator instance -jobdir = here / 'relax' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.1, - 'cal_force': 1 # let ABACUS calculate the forces - } -) - -# get the structure, can also from the -# ``` -# from ase.io import read -# atoms = read(...) -# ``` -atoms = bulk('Si', 'diamond', a=5.43) -# displacement the atoms a little bit -atoms.rattle(stdev=0.1) - -# bind the atoms with the abacus -atoms.calc = abacus - -# print the energy before relaxation -print(f'Before: {atoms.get_potential_energy()}') - -# perform the relaxation calculation -dyn = BFGS(atoms, logfile='-') # let's print to screen, observe the trajectory -dyn.run(fmax=0.05) - -# print the energy after relaxation -print(f'After: {atoms.get_potential_energy()}') - -# remove the temporary job directory (including all files inside) -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/scf.py b/interfaces/ASE_interface/examples/scf.py deleted file mode 100644 index 9e3fdc4f228..00000000000 --- a/interfaces/ASE_interface/examples/scf.py +++ /dev/null @@ -1,56 +0,0 @@ -''' -This example shows how to run a SCF calculation with ABACUS -of Si diamond structure. -''' -import shutil -from pathlib import Path # a more Pythonic alternative to the os.path -here = Path(__file__).parent -# to the directory where the pseudopotential and orbital files are stored -# In your case you change to the appropriate one -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -from ase.build import bulk -from abacuslite import Abacus, AbacusProfile - -# AbacusProfile: the interface connecting the Abacus calculator instance -# with the file system and the enviroment -aprof = AbacusProfile( - command='mpirun -np 4 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -# Abacus: the calculator instance -jobdir = here / 'scf' -abacus = Abacus( - profile=aprof, - directory=str(jobdir), - pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, - basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - inp={ - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'genelpa', - 'ecutwfc': 100, - 'symmetry': 1, - 'kspacing': 0.1 - } -) - -# get the structure, can also from the -# ``` -# from ase.io import read -# atoms = read(...) -# ``` -atoms = bulk('Si', 'diamond', a=5.43) - -# bind the atoms with the abacus -atoms.calc = abacus - -# calculate! -print(atoms.get_potential_energy()) - -# remove the temporary job directory (including all files inside) -shutil.rmtree(jobdir) diff --git a/interfaces/ASE_interface/examples/soc.py b/interfaces/ASE_interface/examples/soc.py deleted file mode 100644 index cdbbd04d6e4..00000000000 --- a/interfaces/ASE_interface/examples/soc.py +++ /dev/null @@ -1,63 +0,0 @@ -'''this example shows how to perform the noncolinear -spin-orbit coupling calculation''' -import shutil -from pathlib import Path -here = Path(__file__).parent - -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -import numpy as np -from ase.atoms import Atoms -from abacuslite import Abacus, AbacusProfile - -'''SPECIAL: ase does not support the noncolinear spin yet -till 2026/3/24, see ase/outputs.py:L154-155, in which the -magmom cannot be set as the vector, so we release the -datatype of magmom and magmoms by ourself''' -from ase.outputs import _defineprop, all_outputs -del all_outputs['magmom'] -del all_outputs['magmoms'] -_defineprop('magmom', float, shape=3) # re-define the magmom can be set as the vector -_defineprop('magmoms', float, shape=('natoms', 3)) - -fe = Atoms(symbols=['Fe'] * 2, - scaled_positions=[[0., 0., 0.], [0.5, 0.5, 0.5]], - magmoms=np.array([[0, 0, 1], [0, 0, 1]]), - cell=np.eye(3) * 5.2, - pbc=True) - -aprof = AbacusProfile( - command='mpirun -np 2 abacus', - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1 -) - -jobdir = here / 'soc' -abacus = Abacus( - profile=aprof, - directory=jobdir, - pseudopotentials={'Fe': 'Fe.upf'}, - basissets={'Fe': 'Fe_gga_6au_100Ry_4s2p2d1f.orb'}, - inp={ - 'basis_type': 'lcao', - 'nspin': 4, - 'lspinorb': True, - 'noncolin': True, - 'out_mul': True, - 'scf_nmax': 10 # not serious setting! - }, - kpts={ - 'mode': 'mp-sampling', - 'gamma-centered': True, - 'nk': (2, 2, 2), - 'kshift': (0, 0, 0) - } -) - -fe.calc = abacus -print(f'Fe (non-colinear): {fe.get_potential_energy()} eV') -for ife, m in enumerate(fe.calc.results['magmoms']): - print(f'Fe{ife}: ({",".join([f"{mi:10.6f}" for mi in m])}) uB') - -shutil.rmtree(jobdir) \ No newline at end of file diff --git a/interfaces/ASE_interface/examples/socketio.py b/interfaces/ASE_interface/examples/socketio.py deleted file mode 100644 index e3087453ffc..00000000000 --- a/interfaces/ASE_interface/examples/socketio.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -This example validates and benchmarks ABACUS socket I/O from ASE. - -ASE runs as the i-PI socket server and ABACUS runs as the socket client. -ABACUS keeps calculation=scf and enables socket_driver internally. - -The script checks two PR-review relevant points: -1. socket SCF gives the same energy and forces as a normal non-socket SCF; -2. repeated socket calculations avoid relaunching ABACUS and are faster than - the normal FileIO calculator for a sequence of SCF force evaluations. - -The i-PI protocol does not carry element symbols. AbacusSocketIO handles -the required STRU/socket atom-order alignment internally and returns forces -in the original ASE Atoms order. -""" -import os -import shutil -import time -from pathlib import Path - -import numpy as np -from ase import Atoms -from abacuslite import Abacus, AbacusProfile, AbacusSocketIO - -here = Path(__file__).parent -pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' - -aprof = AbacusProfile( - command=os.environ.get('ABACUS_COMMAND', 'mpirun -np 4 abacus'), - pseudo_dir=pporb, - orbital_dir=pporb, - omp_num_threads=1, -) - -common_kwargs = { - 'pseudopotentials': {'Si': 'Si_ONCV_PBE-1.0.upf'}, - 'basissets': {'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, - 'inp': { - 'calculation': 'scf', - 'nspin': 1, - 'basis_type': 'lcao', - 'ks_solver': 'scalapack_gvx', - 'ecutwfc': 30, - 'symmetry': 0, - 'kspacing': 0.5, - 'scf_thr': 1e-8, - 'scf_nmax': 40, - 'chg_extrap': 'atomic', - 'cal_force': 1, - }, -} - -base_atoms = Atoms( - 'Si2', - positions=[[0.0, 0.0, 0.0], [1.25, 1.25, 1.25]], - cell=[5.43, 5.43, 5.43], - pbc=True, -) - - -def clean(directory): - shutil.rmtree(directory, ignore_errors=True) - - -def run_fileio(atoms, directory): - clean(directory) - calc = Abacus(profile=aprof, directory=str(directory), **common_kwargs) - atoms = atoms.copy() - atoms.calc = calc - forces = atoms.get_forces() - energy = atoms.get_potential_energy() - return energy, forces - - -def run_socketio(atoms, directory, socket_name): - clean(directory) - calc = AbacusSocketIO( - profile=aprof, - directory=str(directory), - unixsocket=socket_name, - timeout=120, - **common_kwargs, - ) - atoms = atoms.copy() - with calc: - atoms.calc = calc - energy = atoms.get_potential_energy() - forces = atoms.get_forces() - return energy, forces - - -def displaced_structures(): - structures = [] - for scale in (0.00, 0.03, -0.02, 0.05): - atoms = base_atoms.copy() - atoms.positions[1] += scale - structures.append(atoms) - return structures - - -fileio_dir = here / 'socketio_fileio_scf' -socket_dir = here / 'socketio_socket_scf' -bench_fileio_dir = here / 'socketio_bench_fileio' -bench_socket_dir = here / 'socketio_bench_socket' - -try: - reference_energy, reference_forces = run_fileio(base_atoms, fileio_dir) - socket_energy, socket_forces = run_socketio( - base_atoms, socket_dir, 'abacus_si_check') - - energy_diff = abs(socket_energy - reference_energy) - force_diff = np.max(np.abs(socket_forces - reference_forces)) - print(f'FileIO SCF energy: {reference_energy:.12f} eV') - print(f'Socket SCF energy: {socket_energy:.12f} eV') - print(f'|dE|: {energy_diff:.3e} eV') - print(f'max |dF|: {force_diff:.3e} eV/Angstrom') - assert energy_diff < 1e-4 - assert force_diff < 1e-5 - - structures = displaced_structures() - - clean(bench_fileio_dir) - fileio_calc = Abacus( - profile=aprof, - directory=str(bench_fileio_dir), - **common_kwargs, - ) - t0 = time.perf_counter() - for atoms in structures: - atoms = atoms.copy() - atoms.calc = fileio_calc - atoms.get_forces() - fileio_seconds = time.perf_counter() - t0 - - clean(bench_socket_dir) - socket_calc = AbacusSocketIO( - profile=aprof, - directory=str(bench_socket_dir), - unixsocket='abacus_si_bench', - timeout=120, - **common_kwargs, - ) - t0 = time.perf_counter() - with socket_calc as calc: - for atoms in structures: - atoms = atoms.copy() - atoms.calc = calc - atoms.get_forces() - socket_seconds = time.perf_counter() - t0 - - speedup = fileio_seconds / socket_seconds - print(f'FileIO repeated SCF force time: {fileio_seconds:.2f} s') - print(f'Socket repeated SCF force time: {socket_seconds:.2f} s') - print(f'Socket speedup vs FileIO: {speedup:.2f}x') -finally: - for directory in (fileio_dir, socket_dir, bench_fileio_dir, bench_socket_dir): - clean(directory) diff --git a/interfaces/ASE_interface/pyproject.toml b/interfaces/ASE_interface/pyproject.toml deleted file mode 100644 index 8f84373e0af..00000000000 --- a/interfaces/ASE_interface/pyproject.toml +++ /dev/null @@ -1,24 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "abacuslite" -version = "1.0.0" -description = "An ABACUS light-weighted plugin implementation of ase calculator" -readme = "README.md" -authors = [ - { name = "Yuyang Ji" }, - { name = "Zhenxiong Shen" }, - { name = "Yike Huang" }, - { name = "Zhaoqing Liu" } -] -requires-python = ">=3.10" -dependencies = [ - "ase>=3.22.0", - "seekpath" -] - -[tool.setuptools.packages.find] -where = ["."] -include = ["abacuslite*"] \ No newline at end of file diff --git a/interfaces/ASE_interface/tests/band.py b/interfaces/ASE_interface/tests/band.py index 1ba7799c83e..f186f32ff3e 100644 --- a/interfaces/ASE_interface/tests/band.py +++ b/interfaces/ASE_interface/tests/band.py @@ -1,11 +1,13 @@ +import os import unittest +import numpy as np from pathlib import Path here = Path(__file__).parent from ase.build import bulk -from abacuslite.io.generalio import load_pseudo, load_orbital from abacuslite import AbacusProfile, Abacus from abacuslite.utils.ksampling import kpathgen -from common import preserved_tmpdir +from common import (preserved_tmpdir, test_profile_paths, assert_scf_facts, + assert_band_facts) class TestSCFFollowedNSCF(unittest.TestCase): @@ -18,11 +20,11 @@ def setUp(self): return super().setUp() def test(self): - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' + command, pporb = test_profile_paths(here) silicon = bulk('Si', 'diamond', a=5.43) aprof = AbacusProfile( - command='mpirun -np 2 abacus', + command=command, pseudo_dir=pporb, orbital_dir=pporb, omp_num_threads=1, @@ -32,8 +34,8 @@ def test(self): abacus = Abacus( profile=aprof, directory=tmpdir, - pseudopotentials=load_pseudo(pporb), - basissets=load_orbital(pporb, efficiency=True), + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, inp={ 'basis_type': 'lcao', 'scf_thr': 1e-3, # fast for test, wrong for production, @@ -43,6 +45,7 @@ def test(self): silicon.calc = abacus _ = silicon.get_potential_energy() + assert_scf_facts(self, abacus, tmpdir, len(silicon)) # ============================================================= # @@ -54,8 +57,18 @@ def test(self): silicon.calc = bscalc _ = silicon.get_potential_energy() - bscalc.band_structure().write(Path(tmpdir) / 'bandstructure.json') + bands = bscalc.band_structure() + values = np.asarray(bands.energies) + self.assertEqual(values.ndim, 3) + self.assertEqual(values.shape[1], len(bandpath.kpts)) + self.assertGreater(values.shape[2], 0) + self.assertTrue(np.all(np.isfinite(values))) + output = Path(tmpdir) / 'OUT.ABACUS' + raw_bands = output / ('running_nscf.log' if os.environ.get('ASE_ABACUS_FORMAT') == 'legacy' + else 'eig_occ.txt') + assert_band_facts(self, bands, raw_bands) + bands.write(Path(tmpdir) / 'bandstructure.json') self.assertTrue((Path(tmpdir) / 'bandstructure.json').exists()) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/interfaces/ASE_interface/tests/common.py b/interfaces/ASE_interface/tests/common.py index dab62b072ab..d3d42707a90 100644 --- a/interfaces/ASE_interface/tests/common.py +++ b/interfaces/ASE_interface/tests/common.py @@ -2,10 +2,215 @@ import contextlib import os +import re import shutil import tempfile +import warnings from pathlib import Path +import numpy as np +from ase.units import GPa, fs +from ase.stress import full_3x3_to_voigt_6_stress + + +def test_profile_paths(here): + """Return the engine command and curated assets for a real ASE run.""" + assets = Path(os.environ.get('ASE_ABACUS_ASSETS', here.parent.parent.parent / 'tests' / 'PP_ORB')).resolve() + if not assets.is_dir(): + raise FileNotFoundError(f'ASE_ABACUS_ASSETS is not a directory: {assets}') + return os.environ.get('ABACUS_COMMAND', 'mpirun -np 2 abacus'), assets + + +def calculate_socket_frame(calc, atoms): + """Request all three facts while passing ASE's actual structure changes.""" + changes = calc.check_state(atoms) + calc.calculate(atoms, properties=['energy', 'forces', 'stress'], + system_changes=changes) + + +def close_socket_checked(calc): + """Close through ASE's public lifecycle and reject client-exit warnings.""" + with warnings.catch_warnings(): + warnings.filterwarnings('error', message=r'Subprocess exited with status .*', + category=UserWarning) + calc.close() + + +def assert_band_facts(testcase, bands, eig_occ_path): + """Check ASE bands against the engine's raw eig_occ rows in eV.""" + if os.environ.get('ASE_ABACUS_FORMAT', 'latest') == 'legacy': + from legacy_facts import assert_band_facts as assert_legacy_band_facts + return assert_legacy_band_facts(testcase, bands, eig_occ_path) + values = np.asarray(bands.energies) + testcase.assertEqual(values.ndim, 3) + nspin, nk, nbands = values.shape + testcase.assertGreater(nbands, 0) + frames = [] + frame = {} + key = None + for line in Path(eig_occ_path).read_text().splitlines(): + if line.strip().endswith('# ionic step'): + if frame: + frames.append(frame) + frame = {} + key = None + continue + header = re.match(r'\s*spin=(\d+)\s+k-point=(\d+)/(\d+)\b', line) + if header: + spin, point, total = map(int, header.groups()) + testcase.assertEqual(total, nk) + key = (spin, point) + testcase.assertNotIn(key, frame) + frame[key] = [] + continue + row = re.fullmatch(r'\s*(\d+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*', line) + if row and key is not None: + testcase.assertEqual(int(row.group(1)), len(frame[key]) + 1) + frame[key].append(float(row.group(2))) + if frame: + frames.append(frame) + testcase.assertTrue(frames, f'no raw bands in {eig_occ_path}') + raw = frames[-1] + testcase.assertEqual(set(raw), {(spin, point) + for spin in range(1, nspin + 1) + for point in range(1, nk + 1)}) + for spin in range(nspin): + for point in range(nk): + row = raw[(spin + 1, point + 1)] + testcase.assertEqual(len(row), nbands) + testcase.assertTrue(np.allclose(values[spin, point], row, + rtol=0, atol=1e-5)) + + +def assert_collinear_magmoms(testcase, calc, log_path, natoms): + """Compare moments in atom order with the last raw ABACUS table (uB).""" + lines = Path(log_path).read_text().splitlines() + starts = [index for index, line in enumerate(lines) + if re.fullmatch(r'\s*Total Magnetism \(uB\)\s*', line, re.I)] + testcase.assertTrue(starts, f'no per-atom magnetic table in {log_path}') + rows = [] + for line in lines[starts[-1] + 1:]: + match = re.fullmatch(r'\s*([A-Z][a-z]?\d+)\s+([-+\d.Ee]+)\s*', line) + if match: + rows.append((match.group(1), float(match.group(2)))) + elif rows: + break + testcase.assertEqual(len(rows), natoms) + testcase.assertEqual(len({label for label, _ in rows}), natoms) + moments = np.asarray(calc.results['magmoms']) + testcase.assertEqual(moments.shape, (natoms,)) + testcase.assertTrue(np.allclose(moments, [value for _, value in rows], + rtol=0, atol=1e-4)) + + +def assert_native_md_facts(testcase, frames, dump_path, *, expected_frames): + """Compare ASE frames with the raw MD_dump lattice and Cartesian rows.""" + raw_energies = {} + ion = None + for line in Path(dump_path).with_name('running_md.log').read_text().splitlines(): + header = (re.search(r'\bION=\s*(\d+)\b', line) or + re.search(r'#ION MOVE#\s*(\d+)\b', line)) + if header: + ion = int(header.group(1)) + energy = re.match(r'\s*E_KohnSham\s+[-+\d.Ee]+\s+([-+\d.Ee]+)', line) + if ion is not None and energy: + raw_energies[ion] = float(energy.group(1)) + testcase.assertEqual(set(raw_energies), set(range(1, expected_frames + 1))) + sections = re.split(r'(?=^MDSTEP:\s*\d+\s*$)', + Path(dump_path).read_text(), flags=re.M) + sections = [section for section in sections if section.startswith('MDSTEP:')] + testcase.assertEqual(len(sections), expected_frames) + testcase.assertEqual(len(frames), expected_frames) + raw_velocities = [] + for step, (atoms, section) in enumerate(zip(frames, sections), start=1): + lines = section.splitlines() + alat = re.search(r'^LATTICE_CONSTANT:\s*([-+\d.Ee]+) Angstrom$', + section, re.M) + testcase.assertIsNotNone(alat) + lattice = lines.index('LATTICE_VECTORS') + raw_cell = np.array([[float(value) for value in row.split()] + for row in lines[lattice + 1:lattice + 4]]) + rows = [row.split() for row in lines if re.match(r'^\s*\d+\s+[A-Z][a-z]?\s+', row)] + testcase.assertIn('VELOCITY (Angstrom/fs)', section) + raw_velocities.append(np.asarray([[float(value) for value in row[8:11]] + for row in rows])) + testcase.assertEqual(len(rows), len(atoms)) + testcase.assertEqual(atoms.get_chemical_symbols(), [row[1] for row in rows]) + testcase.assertTrue(np.allclose(atoms.cell.array, raw_cell * float(alat.group(1)), + rtol=0, atol=1e-8)) + testcase.assertTrue(np.all(atoms.pbc)) + testcase.assertTrue(np.allclose(atoms.positions, + [[float(value) for value in row[2:5]] for row in rows], + rtol=0, atol=1e-8)) + testcase.assertTrue(np.isfinite(atoms.get_potential_energy())) + testcase.assertAlmostEqual(atoms.get_potential_energy(), raw_energies[step], + delta=1e-4) + forces = atoms.get_forces() + testcase.assertEqual(forces.shape, (len(atoms), 3)) + testcase.assertTrue(np.all(np.isfinite(forces))) + testcase.assertTrue(np.allclose(forces, + [[float(value) for value in row[5:8]] for row in rows], + rtol=0, atol=1e-4)) + testcase.assertTrue(np.all(np.isfinite(raw_velocities))) + testcase.assertGreater(np.max(np.abs(raw_velocities[0])), 1e-8, + 'MD starts with zero velocity') + cell = frames[0].cell.array + delta = frames[-1].positions - frames[0].positions + fractional = np.linalg.solve(cell.T, delta.T).T + fractional -= np.rint(fractional) + testcase.assertGreater(np.max(np.linalg.norm(fractional @ cell, axis=1)), 1e-6, + 'MD trajectory is static') + + +def assert_stru_velocity_facts(testcase, stru_path, ase_velocities): + """Check ASE Angstrom/ASE-time velocities converted to Bohr/atomic-time.""" + text = Path(stru_path).read_text() + tokens = re.findall(r'\bv\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)', text) + actual = np.asarray(tokens, dtype=float) + testcase.assertEqual(actual.shape, np.asarray(ase_velocities).shape) + expected = np.asarray(ase_velocities) * fs * 1.8897270 * 0.02418884326505 + testcase.assertGreater(np.max(np.abs(expected)), 1e-8) + testcase.assertTrue(np.allclose(actual, expected, rtol=2e-6, atol=1e-12), + f'STRU v unit mismatch: actual={actual}, expected={expected}') + + +def assert_scf_facts(testcase, calc, directory, natoms, *, forces=False, stress=False): + """Compare calculator facts with ABACUS' independently written SCF log.""" + if os.environ.get('ASE_ABACUS_FORMAT', 'latest') == 'legacy': + from legacy_facts import assert_scf_facts as assert_legacy_scf_facts + return assert_legacy_scf_facts(testcase, calc, directory, natoms, + forces=forces, stress=stress) + log = Path(directory) / 'OUT.ABACUS' / 'running_scf.log' + content = log.read_text() + energies = re.findall(r'!FINAL_ETOT_IS\s+([-+\d.Ee]+)\s+eV', content) + testcase.assertTrue(energies, f'no final energy in {log}') + energy = float(calc.results['energy']) + testcase.assertTrue(np.isfinite(energy)) + testcase.assertAlmostEqual(energy, float(energies[-1]), delta=1e-4) + fermi = re.findall(r'^\s*E_Fermi\s+[-+\d.Ee]+\s+([-+\d.Ee]+)', content, re.M) + if fermi: + testcase.assertIn('fermi_level', calc.results) + testcase.assertAlmostEqual(float(calc.results['fermi_level']), float(fermi[-1]), delta=1e-4) + if forces: + values = np.asarray(calc.results['forces']) + testcase.assertEqual(values.shape, (natoms, 3)) + testcase.assertTrue(np.all(np.isfinite(values))) + blocks = re.findall(r'#TOTAL-FORCE \(eV/Angstrom\)#(.*?)(?=\n\s*#|\Z)', content, re.S) + testcase.assertTrue(blocks, f'no raw force table in {log}') + rows = re.findall(r'^\s*[A-Z][a-z]?\d+\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*$', blocks[-1], re.M) + testcase.assertEqual(len(rows), natoms) + testcase.assertTrue(np.allclose(values, np.asarray(rows, dtype=float), rtol=0, atol=1e-4)) + if stress: + values = np.asarray(calc.results['stress']) + testcase.assertEqual(values.shape, (6,)) + testcase.assertTrue(np.all(np.isfinite(values))) + blocks = re.findall(r'#TOTAL-STRESS \(kbar\)#(.*?)(?=\n\s*#|\Z)', content, re.S) + testcase.assertTrue(blocks, f'no raw stress table in {log}') + rows = re.findall(r'^\s*([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*$', blocks[-1], re.M) + testcase.assertGreaterEqual(len(rows), 3) + tensor = -0.1 * np.asarray(rows[:3], dtype=float) * GPa + testcase.assertTrue(np.allclose(values, full_3x3_to_voigt_6_stress(tensor), rtol=0, atol=1e-4)) + @contextlib.contextmanager def preserved_tmpdir(prefix): @@ -16,7 +221,10 @@ def preserved_tmpdir(prefix): /tmp/abacus_ase_failure) so remote-CI-only failures (e.g. issue #7794) can be root-caused from the uploaded artifact. """ - tmpdir = tempfile.mkdtemp(prefix=prefix) + raw_root = os.environ.get('ASE_ABACUS_RAW_DIR') + if raw_root: + Path(raw_root).mkdir(parents=True, exist_ok=True) + tmpdir = tempfile.mkdtemp(prefix=prefix, dir=raw_root) try: yield tmpdir except Exception: @@ -28,4 +236,5 @@ def preserved_tmpdir(prefix): print(f'[ase-test] failure, run dir preserved at {keep}') raise finally: - shutil.rmtree(tmpdir, ignore_errors=True) + if not raw_root: + shutil.rmtree(tmpdir, ignore_errors=True) diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/nspin4-gamma-mddump b/interfaces/ASE_interface/tests/fixtures/native_md/MD_dump similarity index 100% rename from interfaces/ASE_interface/abacuslite/io/testfiles/nspin4-gamma-mddump rename to interfaces/ASE_interface/tests/fixtures/native_md/MD_dump diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/nspin4-gamma-eigocc b/interfaces/ASE_interface/tests/fixtures/native_md/eig_occ.txt similarity index 100% rename from interfaces/ASE_interface/abacuslite/io/testfiles/nspin4-gamma-eigocc rename to interfaces/ASE_interface/tests/fixtures/native_md/eig_occ.txt diff --git a/interfaces/ASE_interface/abacuslite/io/testfiles/pw-symm0-nspin4-gamma-md b/interfaces/ASE_interface/tests/fixtures/native_md/running_md.log.txt similarity index 100% rename from interfaces/ASE_interface/abacuslite/io/testfiles/pw-symm0-nspin4-gamma-md rename to interfaces/ASE_interface/tests/fixtures/native_md/running_md.log.txt diff --git a/interfaces/ASE_interface/tests/legacy_facts.py b/interfaces/ASE_interface/tests/legacy_facts.py new file mode 100644 index 00000000000..d15c3f65c64 --- /dev/null +++ b/interfaces/ASE_interface/tests/legacy_facts.py @@ -0,0 +1,96 @@ +"""Small raw-output oracles for ABACUS v3.10/LTS tables.""" + +import re +from pathlib import Path + +import numpy as np +from ase.units import GPa +from ase.stress import full_3x3_to_voigt_6_stress + + +def assert_band_facts(testcase, bands, running_nscf_path): + """Compare ASE bands with literal LTS running_nscf.log rows.""" + values = np.asarray(bands.energies) + testcase.assertEqual(values.ndim, 3) + nspin, nk, nbands = values.shape + testcase.assertGreater(nbands, 0) + testcase.assertEqual(nspin, 1, 'this silicon LTS band smoke is spin-unpolarized') + lines = Path(running_nscf_path).read_text().splitlines() + testcase.assertTrue(lines, running_nscf_path) + headings = [] + for i, line in enumerate(lines): + match = re.match(r'\s*(\d+)/(\d+)\s+kpoint\s+\(Cartesian\)\s*=.*\(\d+\s+pws\)', line) + if match: + headings.append((i, int(match.group(1)), int(match.group(2)))) + testcase.assertEqual(len(headings), nk) + raw = [] + for i, point, total in headings: + testcase.assertEqual(total, nk) + testcase.assertEqual(point, len(raw) + 1) + energies = [] + for line in lines[i + 1:]: + match = re.fullmatch(r'\s*(\d+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*', line) + if match: + testcase.assertEqual(int(match.group(1)), len(energies) + 1) + energies.append(float(match.group(2))) + if len(energies) == nbands: + break + elif energies: + break + testcase.assertEqual(len(energies), nbands) + raw.append(energies) + testcase.assertTrue(np.allclose(values[0], np.asarray(raw), rtol=0, atol=1e-5)) + + +def assert_scf_facts(testcase, calc, directory, natoms, *, forces=False, stress=False): + """Compare energy, optional EF, forces and stress with literal LTS log.""" + log = Path(directory) / 'OUT.ABACUS' / 'running_scf.log' + content = log.read_text() + energies = re.findall(r'!FINAL_ETOT_IS\s+([-+\d.Ee]+)\s+eV', content) + testcase.assertTrue(energies, f'no final energy in {log}') + energy = float(calc.results['energy']) + testcase.assertTrue(np.isfinite(energy)) + testcase.assertAlmostEqual(energy, float(energies[-1]), delta=1e-4) + fermi = re.findall(r'^\s*E_Fermi\s+[-+\d.Ee]+\s+([-+\d.Ee]+)', content, re.M) + if fermi: + testcase.assertIn('fermi_level', calc.results) + testcase.assertAlmostEqual(float(calc.results['fermi_level']), float(fermi[-1]), delta=1e-4) + lines = content.splitlines() + if forces: + values = np.asarray(calc.results['forces']) + testcase.assertEqual(values.shape, (natoms, 3)) + testcase.assertTrue(np.all(np.isfinite(values))) + starts = [i for i, line in enumerate(lines) if re.match( + r'^\s*TOTAL-FORCE\s*\(eV/Angstrom\)', line, re.I)] + testcase.assertTrue(starts, f'no LTS force table in {log}') + rows = [] + for line in lines[starts[-1] + 1:]: + match = re.fullmatch(r'\s*[A-Z][a-z]?\d+\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*', line) + if match: + rows.append([float(value) for value in match.groups()]) + if len(rows) == natoms: + break + elif rows: + break + testcase.assertEqual(len(rows), natoms) + testcase.assertTrue(np.allclose(values, rows, rtol=0, atol=1e-4)) + if stress: + values = np.asarray(calc.results['stress']) + testcase.assertEqual(values.shape, (6,)) + testcase.assertTrue(np.all(np.isfinite(values))) + starts = [i for i, line in enumerate(lines) if re.match( + r'^\s*TOTAL-STRESS\s*\(KBAR\)', line, re.I)] + testcase.assertTrue(starts, f'no LTS stress table in {log}') + rows = [] + for line in lines[starts[-1] + 1:]: + match = re.fullmatch(r'\s*([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s*', line) + if match: + rows.append([float(value) for value in match.groups()]) + if len(rows) == 3: + break + elif rows: + break + testcase.assertEqual(len(rows), 3) + tensor = -0.1 * np.asarray(rows, dtype=float) * GPa + testcase.assertTrue(np.allclose(values, full_3x3_to_voigt_6_stress(tensor), + rtol=0, atol=1e-4)) diff --git a/interfaces/ASE_interface/tests/magnetic.py b/interfaces/ASE_interface/tests/magnetic.py index 080961c3e2e..3ec34c2dac3 100644 --- a/interfaces/ASE_interface/tests/magnetic.py +++ b/interfaces/ASE_interface/tests/magnetic.py @@ -8,14 +8,15 @@ here = Path(__file__).parent from ase.atoms import Atoms from abacuslite import AbacusProfile, Abacus -from common import preserved_tmpdir +from common import (preserved_tmpdir, test_profile_paths, assert_scf_facts, + assert_collinear_magmoms) class TestMagneticSCF(unittest.TestCase): def setUp(self): - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' + command, pporb = test_profile_paths(here) self.aprof = AbacusProfile( - command='mpirun -np 2 abacus', + command=command, pseudo_dir=pporb, orbital_dir=pporb, omp_num_threads=1, @@ -45,10 +46,16 @@ def test_antiferromagnetic(self): fe.calc = abacus print(f'Fe (AFM): {fe.get_potential_energy()} eV') + assert_scf_facts(self, abacus, tmpdir, len(fe)) + self.assertEqual(np.asarray(abacus.results['magmoms']).shape, (2,)) + assert_collinear_magmoms(self, abacus, + Path(tmpdir) / 'OUT.ABACUS' / 'running_scf.log', + len(fe)) for ife, m in enumerate(fe.calc.results['magmoms']): print(f'Fe{ife}: {m:10.6f} uB') self.assertGreater(abs(m), 1e-3) self.assertTrue(np.allclose(np.sum(fe.calc.results['magmoms']), 0.0, atol=1e-6)) + self.assertLess(np.prod(fe.calc.results['magmoms']), 0.0) def test_ferromagnetic(self): @@ -75,21 +82,17 @@ def test_ferromagnetic(self): fe.calc = abacus print(f'Fe (FM): {fe.get_potential_energy()} eV') + assert_scf_facts(self, abacus, tmpdir, len(fe)) + self.assertEqual(np.asarray(abacus.results['magmoms']).shape, (2,)) for ife, m in enumerate(fe.calc.results['magmoms']): print(f'Fe{ife}: {m:10.6f} uB') self.assertGreater(abs(m), 1e-3) self.assertGreater(abs(np.sum(fe.calc.results['magmoms'])), 0.0) + self.assertGreater(np.prod(fe.calc.results['magmoms']), 0.0) - # @unittest.skip('see ase/outputs.py:L154-155, the magmom cannot be set as the vector, so ' - # 'we skip this test for now') def test_noncolinear(self): - from ase.outputs import _defineprop, all_outputs - # refresh the definition of magmom and magmoms - del all_outputs['magmom'] - del all_outputs['magmoms'] - _defineprop('magmom', float, shape=3) - _defineprop('magmoms', float, shape=('natoms', 3)) - + # Vector moments must survive the public calculator handoff without + # changing ASE's global property definitions in the caller. fe = Atoms(symbols=['Fe'] * 2, scaled_positions=[[0., 0., 0.], [0.5, 0.5, 0.5]], magmoms=np.array([[1, 0, 0], [0, 1, 0]]), @@ -121,9 +124,11 @@ def test_noncolinear(self): fe.calc = abacus print(f'Fe (non-colinear): {fe.get_potential_energy()} eV') + assert_scf_facts(self, abacus, tmpdir, len(fe)) + self.assertEqual(np.asarray(abacus.results['magmoms']).shape, (2, 3)) for ife, m in enumerate(fe.calc.results['magmoms']): print(f'Fe{ife}: ({",".join([f"{mi:10.6f}" for mi in m])}) uB') self.assertGreater(np.linalg.norm(m), 1e-3) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/interfaces/ASE_interface/tests/md.py b/interfaces/ASE_interface/tests/md.py index f00b9e3c965..0be22135e55 100644 --- a/interfaces/ASE_interface/tests/md.py +++ b/interfaces/ASE_interface/tests/md.py @@ -1,22 +1,22 @@ import unittest +import numpy as np from pathlib import Path here = Path(__file__).parent from ase.build import bulk from ase.units import fs from ase.md import Langevin from ase.md.velocitydistribution import MaxwellBoltzmannDistribution -from abacuslite.io.generalio import load_pseudo, load_orbital from abacuslite import AbacusProfile, Abacus -from common import preserved_tmpdir +from common import preserved_tmpdir, test_profile_paths, assert_scf_facts class TestLangevinMolecularDynamics(unittest.TestCase): def test(self): - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' + command, pporb = test_profile_paths(here) silicon = bulk('Si', 'diamond', a=5.43) aprof = AbacusProfile( - command='mpirun -np 2 abacus', + command=command, pseudo_dir=pporb, orbital_dir=pporb, omp_num_threads=1, @@ -26,8 +26,8 @@ def test(self): abacus = Abacus( profile=aprof, directory=tmpdir, - pseudopotentials=load_pseudo(pporb), - basissets=load_orbital(pporb, efficiency=True), + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, inp={ 'basis_type': 'lcao', 'gamma_only': True, @@ -38,13 +38,24 @@ def test(self): } ) - silicon.calc = abacus - MaxwellBoltzmannDistribution(silicon, temperature_K=300) - dyn = Langevin(silicon, - timestep=1.0 * fs, - temperature_K=300, - friction=0.01) - dyn.run(2) + silicon.calc = abacus + MaxwellBoltzmannDistribution(silicon, temperature_K=300) + positions = silicon.positions.copy() + frames = [] + dyn = Langevin(silicon, + timestep=1.0 * fs, + temperature_K=300, + friction=0.01) + dyn.attach(lambda: frames.append((silicon.positions.copy(), silicon.get_forces().copy()))) + dyn.run(2) + self.assertEqual(dyn.nsteps, 2) + self.assertGreaterEqual(len(frames), 3) + self.assertFalse(np.allclose(silicon.positions, positions)) + for frame_positions, frame_forces in frames: + self.assertEqual(frame_forces.shape, (len(silicon), 3)) + self.assertTrue(np.all(np.isfinite(frame_positions))) + self.assertTrue(np.all(np.isfinite(frame_forces))) + assert_scf_facts(self, abacus, tmpdir, len(silicon), forces=True, stress=True) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/interfaces/ASE_interface/tests/native_md.py b/interfaces/ASE_interface/tests/native_md.py new file mode 100644 index 00000000000..552c17d948a --- /dev/null +++ b/interfaces/ASE_interface/tests/native_md.py @@ -0,0 +1,58 @@ +"""Run short ABACUS-native MD and check its ASE trajectory against MD_dump.""" + +import os +import unittest +from pathlib import Path + +import numpy as np +from ase.build import bulk + +from abacuslite import Abacus, AbacusProfile +from common import (assert_native_md_facts, assert_stru_velocity_facts, + preserved_tmpdir, test_profile_paths) + + +class TestNativeMD(unittest.TestCase): + def test_two_steps(self): + command, pporb = test_profile_paths(Path(__file__).parent) + silicon = bulk('Si', 'diamond', a=5.43) + silicon.set_velocities(np.array([[0.01, 0.0, 0.0], + [-0.01, 0.0, 0.0]])) + initial_velocities = silicon.get_velocities().copy() + legacy = os.environ.get('ASE_ABACUS_FORMAT', 'latest') == 'legacy' + if legacy: + from abacuslite.io.legacyio import read_abacus_out + else: + from abacuslite.io.latestio import read_abacus_out + profile = AbacusProfile(command=command, pseudo_dir=pporb, + orbital_dir=pporb, omp_num_threads=1) + with preserved_tmpdir('abacus_ase_native_md_') as run: + calculator = Abacus( + profile=profile, + directory=run, + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, + inp={ + 'calculation': 'md', + 'basis_type': 'lcao', + 'gamma_only': True, + 'scf_thr': 1e-3, + 'cal_force': True, + 'md_nstep': 2, + 'md_tchain': 4, + 'md_tfirst': 10, + 'md_tfreq': 0.1, + 'init_vel': 1, + }, + ) + silicon.calc = calculator + self.assertTrue(np.isfinite(silicon.get_potential_energy())) + assert_stru_velocity_facts(self, Path(run) / 'STRU', initial_velocities) + output = Path(run) / 'OUT.ABACUS' + frames = read_abacus_out(output / 'running_md.log') + assert_native_md_facts(self, frames, output / 'MD_dump', + expected_frames=3 if legacy else 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/interfaces/ASE_interface/tests/native_md_generated.py b/interfaces/ASE_interface/tests/native_md_generated.py new file mode 100644 index 00000000000..1de914a033c --- /dev/null +++ b/interfaces/ASE_interface/tests/native_md_generated.py @@ -0,0 +1,59 @@ +"""Run short ABACUS-native MD and check its ASE trajectory against MD_dump.""" + +import unittest +import re +from pathlib import Path + +import numpy as np +from ase.build import bulk + +from abacuslite import Abacus, AbacusProfile +from abacuslite.io.legacyio import read_abacus_out +from common import assert_native_md_facts, preserved_tmpdir, test_profile_paths + + +class TestNativeMD(unittest.TestCase): + def test_two_steps(self): + command, pporb = test_profile_paths(Path(__file__).parent) + silicon = bulk('Si', 'diamond', a=5.43) + # init_vel=0 must generate seeded engine velocities even though the + # ASE structure carries no initial momenta (STRU v remains zero). + self.assertFalse(silicon.has('momenta')) + profile = AbacusProfile(command=command, pseudo_dir=pporb, + orbital_dir=pporb, omp_num_threads=1) + with preserved_tmpdir('abacus_ase_native_md_') as run: + calculator = Abacus( + profile=profile, + directory=run, + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, + inp={ + 'calculation': 'md', + 'basis_type': 'lcao', + 'gamma_only': True, + 'scf_thr': 1e-3, + 'cal_force': True, + 'md_nstep': 2, + 'md_tchain': 4, + 'md_tfirst': 10, + 'md_tfreq': 0.1, + 'init_vel': 0, + 'md_seed': 1729, + }, + ) + silicon.calc = calculator + self.assertTrue(np.isfinite(silicon.get_potential_energy())) + stru = (Path(run) / 'STRU').read_text() + raw_velocities = np.asarray(re.findall( + r'\bv\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)\s+([-+\d.Ee]+)', stru), + dtype=float) + self.assertEqual(raw_velocities.shape, (len(silicon), 3)) + self.assertTrue(np.all(raw_velocities == 0)) + output = Path(run) / 'OUT.ABACUS' + frames = read_abacus_out(output / 'running_md.log') + # LTS run_md.cpp records MDSTEP 0, 1 and 2 for md_nstep=2. + assert_native_md_facts(self, frames, output / 'MD_dump', expected_frames=3) + + +if __name__ == '__main__': + unittest.main() diff --git a/interfaces/ASE_interface/tests/relax.py b/interfaces/ASE_interface/tests/relax.py index 1b3c2a98751..0901e14512b 100644 --- a/interfaces/ASE_interface/tests/relax.py +++ b/interfaces/ASE_interface/tests/relax.py @@ -1,20 +1,21 @@ import unittest +import numpy as np from pathlib import Path here = Path(__file__).parent from ase.build import bulk from ase.optimize import BFGS -from abacuslite.io.generalio import load_pseudo, load_orbital from abacuslite import AbacusProfile, Abacus -from common import preserved_tmpdir +from common import preserved_tmpdir, test_profile_paths, assert_scf_facts class TestIonicRelaxationWithStress(unittest.TestCase): def test(self): - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' + command, pporb = test_profile_paths(here) silicon = bulk('Si', 'diamond', a=5.43) + silicon.positions[1, 0] += 0.1 aprof = AbacusProfile( - command='mpirun -np 2 abacus', + command=command, pseudo_dir=pporb, orbital_dir=pporb, omp_num_threads=1, @@ -24,8 +25,8 @@ def test(self): abacus = Abacus( profile=aprof, directory=tmpdir, - pseudopotentials=load_pseudo(pporb), - basissets=load_orbital(pporb, efficiency=True), + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, inp={ 'basis_type': 'lcao', 'gamma_only': True, @@ -35,13 +36,21 @@ def test(self): } ) - silicon.calc = abacus - e = silicon.get_potential_energy() - opt = BFGS(silicon) - opt.run(fmax=0.05, steps=1) - self.assertLessEqual(silicon.get_potential_energy(), e) - self.assertIsNotNone(silicon.calc.results['forces']) - self.assertIsNotNone(silicon.calc.results['stress']) + silicon.calc = abacus + initial_positions = silicon.positions.copy() + e = silicon.get_potential_energy() + initial_forces = silicon.get_forces() + self.assertTrue(np.isfinite(e)) + self.assertEqual(initial_forces.shape, (len(silicon), 3)) + self.assertGreater(np.max(np.linalg.norm(initial_forces, axis=1)), 0.05) + opt = BFGS(silicon, logfile=None) + opt.run(fmax=0.05, steps=1) + final_energy = silicon.get_potential_energy() + self.assertTrue(np.isfinite(final_energy)) + self.assertLessEqual(final_energy, e + 1e-4) + self.assertEqual(opt.nsteps, 1) + self.assertFalse(np.allclose(silicon.positions, initial_positions)) + assert_scf_facts(self, abacus, tmpdir, len(silicon), forces=True, stress=True) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/interfaces/ASE_interface/tests/run_engine_compatibility.py b/interfaces/ASE_interface/tests/run_engine_compatibility.py new file mode 100644 index 00000000000..49355a62ba4 --- /dev/null +++ b/interfaces/ASE_interface/tests/run_engine_compatibility.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +"""Run the installed abacuslite wheel against one explicit ABACUS executable. + +The runner copies only this interface harness into a report directory outside +the ABACUS checkout. Each real-engine script then gets its own Python process. +""" + +import argparse +import hashlib +import json +import os +import re +import shlex +import shutil +import signal +import subprocess +import sys +import time +import zipfile +from email.parser import BytesParser +from pathlib import Path + + +HERE = Path(__file__).resolve().parent +REPO = HERE.parents[2] +CASES = { + 'latest': {'scf.py': 1, 'relax.py': 1, 'md.py': 1, 'native_md.py': 1, + 'band.py': 1, 'magnetic.py': 3, 'socket_handoff.py': 1}, + 'legacy': {'scf.py': 1, 'relax.py': 1, 'md.py': 1, 'native_md.py': 1, + 'band.py': 1, 'magnetic.py': 3, 'native_md_generated.py': 1}, +} +ASSETS = ('Si_ONCV_PBE-1.0.upf', 'Si_gga_8au_100Ry_2s2p1d.orb', + 'Fe_ONCV_PBE-1.0.upf', 'Fe.upf', 'Fe_gga_6au_100Ry_4s2p2d1f.orb') +PROBE = r'''import json, sys +from importlib import metadata +from pathlib import Path +import abacuslite +from abacuslite import AbacusProfile +d = metadata.distribution('abacuslite') +p = AbacusProfile(command=sys.argv[1]) +print(json.dumps({'python': sys.executable, 'prefix': sys.prefix, + 'module': str(Path(abacuslite.__file__).resolve()), + 'installed': str(Path(d.locate_file('abacuslite/__init__.py')).resolve()), + 'version': d.version, 'engine_version': p.version()}))''' + + +def validate_engine_format(version, fmt): + """Keep the tested 3.10/LTS and 3.11/develop oracle lanes explicit.""" + match = re.match(r'^v3\.(10|11)\.\d+(?:[.+-].*)?$', version) + expected_minor = '10' if fmt == 'legacy' else '11' + if match is None or match.group(1) != expected_minor: + raise ValueError(f'engine version {version!r} does not match {fmt} lane') + + +def sha256(path): + digest = hashlib.sha256() + with Path(path).open('rb') as stream: + for block in iter(lambda: stream.read(1024 * 1024), b''): + digest.update(block) + return digest.hexdigest() + + +def validate_cases(fmt, results): + """Reject omitted, skipped or misreported required scripts.""" + expected = CASES[fmt] + if [item['script'] for item in results] != list(expected): + raise ValueError(f'incomplete {fmt} lane: expected {list(expected)}') + for item in results: + if (item['returncode'] != 0 or item['timed_out'] or + item['tests_reported'] != expected[item['script']] or + item['skipped']): + raise ValueError(f"required case failed: {item['script']}: {item}") + + +def group_members_alive(pid): + """Report whether the isolated process group still has live members.""" + try: + os.killpg(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + return True + return True + + +def signal_group(pid, sig): + """Signal the whole isolated group; missing groups are not an error.""" + try: + os.killpg(pid, sig) + except ProcessLookupError: + return False + return True + + +def capture(command, *, cwd, env, log, timeout): + start = time.monotonic() + timed_out = False + with Path(log).open('w') as stream: + process = subprocess.Popen(command, cwd=cwd, env=env, stdout=stream, + stderr=subprocess.STDOUT, start_new_session=True) + try: + process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + timed_out = True + signal_group(process.pid, signal.SIGTERM) + try: + process.wait(timeout=10) + except subprocess.TimeoutExpired: + pass + # The leader can exit normally, with a nonzero status, or on SIGTERM + # while a descendant still runs in the isolated group. Reclaim the + # whole group whenever members remain, not only when the leader timed + # out; otherwise the next case would start alongside the leftover. + if group_members_alive(process.pid): + signal_group(process.pid, signal.SIGTERM) + try: + process.wait(timeout=10) + except subprocess.TimeoutExpired: + pass + # A child that ignores SIGTERM keeps the group alive after the + # leader is gone, so finish with SIGKILL instead of treating + # leader exit as group shutdown. + if group_members_alive(process.pid): + signal_group(process.pid, signal.SIGKILL) + process.wait(timeout=10) + return {'returncode': process.returncode, 'timed_out': timed_out, + 'seconds': round(time.monotonic() - start, 3)} + + +def wheel_runtime(wheel): + """Read wheel version and package runtime hashes without importing it.""" + with zipfile.ZipFile(wheel) as archive: + metadata_names = [name for name in archive.namelist() + if name.endswith('.dist-info/METADATA')] + if len(metadata_names) != 1: + raise ValueError('wheel has no unique METADATA') + metadata = BytesParser().parsebytes(archive.read(metadata_names[0])) + if metadata.get('Name', '').lower() != 'abacuslite': + raise ValueError('wheel is not abacuslite') + runtime = {name: hashlib.sha256(archive.read(name)).hexdigest() + for name in archive.namelist() + if name.startswith('abacuslite/') and name.endswith('.py')} + if 'abacuslite/__init__.py' not in runtime: + raise ValueError('wheel lacks abacuslite runtime') + return metadata['Version'], runtime + + +def harness_revision(repo): + """Record checkout identity separately from copied script hashes.""" + revision = subprocess.run(['git', '-C', str(repo), 'rev-parse', 'HEAD'], + capture_output=True, text=True, check=False) + status = subprocess.run(['git', '-C', str(repo), 'status', '--porcelain'], + capture_output=True, text=True, check=False) + return {'ref': revision.stdout.strip() if revision.returncode == 0 else None, + 'dirty': bool(status.stdout.strip()) if status.returncode == 0 else None} + + +def attest_install(probe, wheel, repo): + """Require every wheel runtime module to match installed site-packages.""" + version, runtime = wheel_runtime(wheel) + imported = Path(probe['module']).resolve() + installed = Path(probe['installed']).resolve() + prefix = Path(probe['prefix']).resolve() + if probe['version'] != version or imported != installed: + raise ValueError(f'installed wheel identity mismatch: {probe}') + if not imported.is_relative_to(prefix) or 'site-packages' not in imported.parts: + raise ValueError(f'not installed in interpreter site-packages: {imported}') + if imported.is_relative_to(repo): + raise ValueError(f'ABACUS checkout shadowed installed wheel: {imported}') + package = imported.parent.parent + for name, digest in runtime.items(): + path = package / name + if not path.is_file() or sha256(path) != digest: + raise ValueError(f'installed runtime differs from supplied wheel: {name}') + return {'version': version, 'runtime_sha256': runtime} + + +def required_path(value, *, file=False, directory=False, follow_symlinks=True): + path = Path(value) + if not path.is_absolute(): + raise ValueError(f'path must be absolute: {value}') + path = path.resolve() if follow_symlinks else path.absolute() + if file and not path.is_file(): + raise FileNotFoundError(path) + if directory and not path.is_dir(): + raise FileNotFoundError(path) + return path + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--engine', required=True) + parser.add_argument('--mpi-command', required=True) + parser.add_argument('--assets', required=True) + parser.add_argument('--format', choices=CASES, required=True) + parser.add_argument('--wheel', required=True) + parser.add_argument('--report-dir', required=True) + parser.add_argument('--engine-ref', required=True) + parser.add_argument('--python', default=sys.executable) + args = parser.parse_args(argv) + if not re.fullmatch(r'[0-9a-f]{40}', args.engine_ref): + parser.error('--engine-ref must be a full lowercase commit SHA') + engine = required_path(args.engine, file=True) + # Resolving a venv's python symlink would select its base interpreter. + python = required_path(args.python, file=True, follow_symlinks=False) + assets = required_path(args.assets, directory=True) + wheel = required_path(args.wheel, file=True) + report = required_path(args.report_dir) + if report.is_relative_to(REPO) or REPO.is_relative_to(report): + raise ValueError('report directory must be outside ABACUS checkout') + if report.exists() and any(report.iterdir()): + raise ValueError(f'report directory is not empty: {report}') + missing = [name for name in ASSETS if not (assets / name).is_file()] + if missing: + raise FileNotFoundError(f'missing curated assets: {missing}') + mpi = shlex.split(args.mpi_command) + if not mpi: + raise ValueError('empty MPI command') + report.mkdir(parents=True, exist_ok=True) + harness = report / 'harness' + harness.mkdir() + scripts = set(CASES[args.format]) | {'common.py', 'legacy_facts.py'} + for name in scripts: + shutil.copy2(HERE / name, harness / name) + env = os.environ.copy() + for key in ('PYTHONPATH', 'PYTHONHOME', 'ASE_ABACUS_SOCKET', 'ASE_ABACUS_RAW_DIR'): + env.pop(key, None) + env.update(ABACUS_COMMAND=shlex.join(mpi + [str(engine)]), + ASE_ABACUS_ASSETS=str(assets), ASE_ABACUS_FORMAT=args.format, + ASE_ABACUS_KEEP_DIR=str(report / 'failed-runs'), + OMP_NUM_THREADS='1', OPENBLAS_NUM_THREADS='1') + record = {'schema': 'abacuslite-engine-compatibility-v2', 'format': args.format, + 'engine': str(engine), 'engine_ref_asserted': args.engine_ref, + 'engine_sha256': sha256(engine), 'mpi_command': mpi, + 'harness_checkout': harness_revision(REPO), + 'assets': {name: sha256(assets / name) for name in ASSETS}, + 'wheel': str(wheel), 'wheel_sha256': sha256(wheel), + 'python': str(python), 'harness_sha256': sha256(__file__), + 'scripts_sha256': {name: sha256(harness / name) for name in sorted(scripts)}, + 'expected_cases': CASES[args.format], 'cases': [], 'all_pass': False} + output = report / 'evidence.json' + + def save(): + output.write_text(json.dumps(record, indent=2, sort_keys=True) + '\n') + + save() + try: + probe_result = capture([str(engine), '--version'], cwd=harness, env=env, + log=report / 'engine-version.log', timeout=30) + record['engine_probe'] = probe_result + record['engine_version_output'] = (report / 'engine-version.log').read_text() + save() + if probe_result['returncode'] != 0 or probe_result['timed_out']: + raise RuntimeError('engine --version failed') + probe_result = capture([str(python), '-c', PROBE, str(engine)], cwd=harness, + env=env, log=report / 'package-probe.log', timeout=45) + record['package_probe_result'] = probe_result + if probe_result['returncode'] != 0 or probe_result['timed_out']: + raise RuntimeError('installed package probe failed') + probe = json.loads((report / 'package-probe.log').read_text().splitlines()[-1]) + record['package_probe'] = probe + record['wheel_runtime'] = attest_install(probe, wheel, REPO) + validate_engine_format(probe['engine_version'], args.format) + save() + for script, expected in CASES[args.format].items(): + name = script.removesuffix('.py') + raw = report / 'raw' / name + raw.mkdir(parents=True) + case_env = dict(env, ASE_ABACUS_RAW_DIR=str(raw)) + log = report / f'{name}.log' + result = capture([str(python), str(harness / script), '-v'], cwd=harness, + env=case_env, log=log, timeout=240) + content = log.read_text() + counts = [int(value) for value in re.findall(r'Ran (\d+) tests?', content)] + result.update(script=script, expected_tests=expected, + tests_reported=sum(counts), + skipped=bool(re.search(r'\bskipped=\d+\b', content)), + log=log.name) + record['cases'].append(result) + save() + print(f'{script}: {result}', flush=True) + validate_cases(args.format, record['cases']) + record['all_pass'] = True + except Exception as exc: + record['error'] = f'{type(exc).__name__}: {exc}' + raise + finally: + record['raw_sha256'] = {str(path.relative_to(report)): sha256(path) + for path in sorted((report / 'raw').rglob('*')) + if path.is_file()} if (report / 'raw').exists() else {} + record['dependency_freeze'] = None + freeze = capture([str(python), '-m', 'pip', 'freeze'], cwd=harness, env=env, + log=report / 'pip-freeze.log', timeout=45) + record['dependency_freeze'] = freeze + save() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/interfaces/ASE_interface/tests/scf.py b/interfaces/ASE_interface/tests/scf.py index d9f3e52e150..3d909bf915f 100644 --- a/interfaces/ASE_interface/tests/scf.py +++ b/interfaces/ASE_interface/tests/scf.py @@ -1,19 +1,19 @@ import unittest +import numpy as np from pathlib import Path here = Path(__file__).parent from ase.build import bulk -from abacuslite.io.generalio import load_pseudo, load_orbital from abacuslite import AbacusProfile, Abacus -from common import preserved_tmpdir +from common import preserved_tmpdir, test_profile_paths, assert_scf_facts class TestSCF(unittest.TestCase): def test(self): - pporb = here.parent.parent.parent / 'tests' / 'PP_ORB' + command, pporb = test_profile_paths(here) silicon = bulk('Si', 'diamond', a=5.43) aprof = AbacusProfile( - command='mpirun -np 2 abacus', + command=command, pseudo_dir=pporb, orbital_dir=pporb, omp_num_threads=1, @@ -25,17 +25,24 @@ def test(self): abacus = Abacus( profile=aprof, directory=tmpdir, - pseudopotentials=load_pseudo(pporb), - basissets=load_orbital(pporb, efficiency=True), + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, inp={ 'basis_type': 'lcao', 'gamma_only': True, 'scf_thr': 1e-3, # fast for test, wrong for production + 'cal_force': True, + 'cal_stress': True, } ) silicon.calc = abacus - print('Silicon :', silicon.get_potential_energy()) + energy = silicon.get_potential_energy() + print('Silicon :', energy) + self.assertTrue(np.isfinite(energy)) + assert_scf_facts(self, abacus, tmpdir, len(silicon), + forces=True, stress=True) + self.assertEqual(abacus.results['natoms'], len(silicon)) if __name__ == '__main__': unittest.main() diff --git a/interfaces/ASE_interface/tests/socket_handoff.py b/interfaces/ASE_interface/tests/socket_handoff.py new file mode 100644 index 00000000000..1d1b341fcb5 --- /dev/null +++ b/interfaces/ASE_interface/tests/socket_handoff.py @@ -0,0 +1,77 @@ +"""Bounded real fixed-cell socket handoff against raw FileIO calculations.""" + +import os +import signal +import unittest +from pathlib import Path + +import numpy as np +from ase import Atoms +from abacuslite import Abacus, AbacusProfile, AbacusSocketIO + +from common import (assert_scf_facts, calculate_socket_frame, close_socket_checked, + preserved_tmpdir, test_profile_paths) + + +class TestSocketHandoff(unittest.TestCase): + def test_two_positions(self): + command, assets = test_profile_paths(Path(__file__).parent) + profile = AbacusProfile(command=command, pseudo_dir=assets, + orbital_dir=assets, omp_num_threads=1) + inputs = dict( + pseudopotentials={'Si': 'Si_ONCV_PBE-1.0.upf'}, + basissets={'Si': 'Si_gga_8au_100Ry_2s2p1d.orb'}, + inp={'calculation': 'scf', 'basis_type': 'lcao', 'gamma_only': True, + 'scf_thr': 1e-6, 'scf_nmax': 40, 'cal_force': 1, 'cal_stress': 1}, + ) + atoms = Atoms('Si2', positions=[[0, 0, 0], [1.25, 1.25, 1.25]], + cell=[5.43, 5.43, 5.43], pbc=True) + with preserved_tmpdir('abacus_ase_socket_') as tmpdir: + previous_handler = signal.getsignal(signal.SIGALRM) + def timed_out(*_): + raise TimeoutError('socket test exceeded 360 s') + signal.signal(signal.SIGALRM, timed_out) + signal.alarm(360) + try: + self._run_handoff(profile, inputs, atoms, tmpdir) + finally: + signal.alarm(0) + signal.signal(signal.SIGALRM, previous_handler) + + def _run_handoff(self, profile, inputs, atoms, tmpdir): + root = Path(tmpdir) + socket = AbacusSocketIO(profile=profile, directory=root / 'socket', + unixsocket=f'abacus_ci_{os.getpid()}', + timeout=120, **inputs) + try: + atoms.calc = socket + for index, displacement in enumerate((0.0, 0.03)): + atoms.positions[1] = [1.25 + displacement] * 3 + calculate_socket_frame(socket, atoms) + energy = atoms.get_potential_energy() + forces = atoms.get_forces().copy() + stress = atoms.get_stress().copy() + self.assertTrue(np.isfinite(energy)) + self.assertEqual(forces.shape, (2, 3)) + self.assertEqual(stress.shape, (6,)) + self.assertTrue(np.all(np.isfinite(forces))) + self.assertTrue(np.all(np.isfinite(stress))) + + reference_dir = root / f'fileio_{index}' + reference = Abacus(profile=profile, directory=reference_dir, **inputs) + check_atoms = atoms.copy() + check_atoms.calc = reference + self.assertAlmostEqual(energy, check_atoms.get_potential_energy(), delta=1e-4) + self.assertTrue(np.allclose(forces, check_atoms.get_forces(), atol=1e-4)) + self.assertTrue(np.allclose(stress, check_atoms.get_stress(), atol=1e-4)) + assert_scf_facts(self, reference, reference_dir, 2, + forces=True, stress=True) + finally: + # Use the public lifecycle API. The outer runner bounds the whole + # case (including close), kills its process group on failure and + # retains raw files; no ASE server/process internals are inspected. + close_socket_checked(socket) + + +if __name__ == '__main__': + unittest.main() diff --git a/interfaces/ASE_interface/tests/test_external_provenance.py b/interfaces/ASE_interface/tests/test_external_provenance.py new file mode 100644 index 00000000000..28d0237da4e --- /dev/null +++ b/interfaces/ASE_interface/tests/test_external_provenance.py @@ -0,0 +1,42 @@ +import tempfile +import unittest +from pathlib import Path + +from verify_external import verify_locations + + +class ExternalProvenanceTest(unittest.TestCase): + def test_installed_wheel_location(self): + with tempfile.TemporaryDirectory() as root: + root = Path(root) + site = root / 'venv' / 'site-packages' / 'abacuslite' / '__init__.py' + source = root / 'checkout' / 'interfaces' / 'ASE_interface' + site.parent.mkdir(parents=True) + source.mkdir(parents=True) + site.touch() + verify_locations(site, site, source) + + def test_source_shadow_is_rejected(self): + with tempfile.TemporaryDirectory() as root: + root = Path(root) + source = root / 'checkout' / 'interfaces' / 'ASE_interface' + shadow = source / 'abacuslite' / '__init__.py' + installed = root / 'venv' / 'site-packages' / 'abacuslite' / '__init__.py' + shadow.parent.mkdir(parents=True) + installed.parent.mkdir(parents=True) + shadow.touch() + installed.touch() + with self.assertRaisesRegex(AssertionError, 'source shadow'): + verify_locations(shadow, installed, root / 'checkout') + + def test_wrong_distribution_is_rejected(self): + with tempfile.TemporaryDirectory() as root: + root = Path(root) + imported = root / 'one' / 'abacuslite' / '__init__.py' + installed = root / 'two' / 'abacuslite' / '__init__.py' + imported.parent.mkdir(parents=True) + installed.parent.mkdir(parents=True) + imported.touch() + installed.touch() + with self.assertRaisesRegex(AssertionError, 'installed distribution'): + verify_locations(imported, installed, root / 'checkout') diff --git a/interfaces/ASE_interface/tests/test_harness_facts.py b/interfaces/ASE_interface/tests/test_harness_facts.py new file mode 100644 index 00000000000..60f42680443 --- /dev/null +++ b/interfaces/ASE_interface/tests/test_harness_facts.py @@ -0,0 +1,145 @@ +"""Offline checks for independent engine facts and bounded socket cleanup.""" + +import subprocess +import sys +import tempfile +import unittest +import warnings +from pathlib import Path +from types import SimpleNamespace + +import numpy as np +from ase import Atoms +from ase.calculators.calculator import Calculator, PropertyNotImplementedError + +from common import (assert_band_facts, assert_collinear_magmoms, + calculate_socket_frame, close_socket_checked) +from run_engine_compatibility import capture +from legacy_facts import assert_band_facts as assert_legacy_band_facts + + +class HarnessFactsTest(unittest.TestCase): + def test_public_socket_close_rejects_nonzero_client_exit_warning(self): + class PublicSocket: + def close(self): + warnings.warn('Subprocess exited with status 7', UserWarning) + + with self.assertRaisesRegex(UserWarning, 'status 7'): + close_socket_checked(PublicSocket()) + + def test_public_socket_close_accepts_clean_shutdown(self): + closed = [] + socket = SimpleNamespace(close=lambda: closed.append(True)) + close_socket_checked(socket) + self.assertEqual(closed, [True]) + + def test_socket_requests_only_actual_changes_after_first_frame(self): + class RecordingCalculator(Calculator): + implemented_properties = ['energy', 'forces', 'stress'] + + def __init__(self): + super().__init__() + self.changes = [] + + def calculate(self, atoms=None, properties=None, system_changes=None): + if self.atoms is not None and any( + name not in ('positions', 'cell') for name in system_changes): + raise PropertyNotImplementedError('immutable socket input changed') + super().calculate(atoms, properties, system_changes) + self.changes.append(list(system_changes)) + self.results = {'energy': 0.0, 'forces': np.zeros((2, 3)), + 'stress': np.zeros(6)} + + atoms = Atoms('Si2', positions=[[0, 0, 0], [1, 1, 1]], + cell=[5, 5, 5], pbc=True) + calc = RecordingCalculator() + calculate_socket_frame(calc, atoms) + atoms.positions[1, 0] += 0.03 + calculate_socket_frame(calc, atoms) + self.assertIn('numbers', calc.changes[0]) + self.assertEqual(calc.changes[1], ['positions']) + + def test_band_eigenvalues_reject_mutated_reader_result(self): + raw = '''1 # ionic step +spin=1 k-point=1/2 Cartesian=0.0 0.0 0.0 (1 plane wave) +1 -2.5000 1.0 +2 1.2500 0.0 +spin=1 k-point=2/2 Cartesian=0.5 0.0 0.0 (1 plane wave) +1 -2.2500 1.0 +2 1.5000 0.0 +''' + with tempfile.TemporaryDirectory() as root: + path = Path(root) / 'eig_occ.txt' + path.write_text(raw) + bands = SimpleNamespace(energies=np.array([[[-2.5, 1.25], [-2.25, 1.5]]])) + assert_band_facts(self, bands, path) + bands.energies[0, 1, 1] += 0.1 + with self.assertRaises(AssertionError): + assert_band_facts(self, bands, path) + + def test_legacy_band_oracle_rejects_mutated_reader_result(self): + raw = '''1/2 kpoint (Cartesian) = 0.0 0.0 0.0 (1 pws) +1 -2.5000 1.0 +2 1.2500 0.0 +2/2 kpoint (Cartesian) = 0.5 0.0 0.0 (1 pws) +1 -2.2500 1.0 +2 1.5000 0.0 +''' + with tempfile.TemporaryDirectory() as root: + path = Path(root) / 'running_nscf.log' + path.write_text(raw) + bands = SimpleNamespace(energies=np.array([[[-2.5, 1.25], [-2.25, 1.5]]])) + assert_legacy_band_facts(self, bands, path) + bands.energies[0, 1, 1] += 0.1 + with self.assertRaises(AssertionError): + assert_legacy_band_facts(self, bands, path) + + def test_per_atom_magnetic_moment_rejects_swap(self): + raw = '''Total Magnetism (uB) +-------------------- + Fe1 1.2715948597 + Fe2 -1.2715948597 +-------------------- +''' + with tempfile.TemporaryDirectory() as root: + path = Path(root) / 'running_scf.log' + path.write_text(raw) + calc = SimpleNamespace(results={'magmoms': np.array([1.2715948597, -1.2715948597])}) + assert_collinear_magmoms(self, calc, path, 2) + calc.results['magmoms'] = calc.results['magmoms'][::-1] + with self.assertRaises(AssertionError): + assert_collinear_magmoms(self, calc, path, 2) + + def test_hung_public_close_is_stopped_without_touching_unrelated_process(self): + # Model a client's public close() waiting forever. The runner owns the + # process group; it must retain diagnostics and stop that group only. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + script = root / 'hung_close.py' + script.write_text("""import subprocess, sys +from pathlib import Path +class Socket: + def __init__(self): + self.client = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)']) + Path('client.pid').write_text(str(self.client.pid)) + def close(self): + self.client.wait() +Path('raw-output.txt').write_text('retained engine output') +Socket().close() +""") + unrelated = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)']) + try: + result = capture([sys.executable, str(script)], cwd=root, + env=None, log=root / 'case.log', timeout=1) + self.assertTrue(result['timed_out']) + self.assertNotEqual(result['returncode'], 0) + self.assertLess(result['seconds'], 4) + self.assertIsNone(unrelated.poll()) + self.assertEqual((root / 'raw-output.txt').read_text(), 'retained engine output') + child = int((root / 'client.pid').read_text()) + stat = Path(f'/proc/{child}/stat') + if stat.exists(): + self.assertEqual(stat.read_text().split()[2], 'Z') + finally: + unrelated.terminate() + unrelated.wait(timeout=3) diff --git a/interfaces/ASE_interface/tests/test_native_md_fixture.py b/interfaces/ASE_interface/tests/test_native_md_fixture.py new file mode 100644 index 00000000000..1058476e11b --- /dev/null +++ b/interfaces/ASE_interface/tests/test_native_md_fixture.py @@ -0,0 +1,46 @@ +"""Offline regression for the real, two-frame latest-format ABACUS MD output.""" + +import unittest +import shutil +import tempfile +from pathlib import Path + +from abacuslite.io.latestio import read_abacus_out +from common import assert_native_md_facts + + +class NativeMDOutputTest(unittest.TestCase): + def setUp(self): + # Engine-owned copy of a two-frame raw output fixture. + source = Path(__file__).resolve().parent / 'fixtures' / 'native_md' + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.fixture = Path(temporary.name) + for name, target in ( + ('running_md.log.txt', 'running_md.log'), + ('eig_occ.txt', 'eig_occ.txt'), + ('MD_dump', 'MD_dump'), + ): + shutil.copyfile(source / name, self.fixture / target) + + def test_real_dump_cell_and_positions(self): + frames = read_abacus_out(self.fixture / 'running_md.log') + assert_native_md_facts(self, frames, self.fixture / 'MD_dump', expected_frames=2) + + def test_raw_dump_oracle_rejects_mutated_cell_pbc_and_position(self): + for mutation in ('cell', 'pbc', 'position'): + with self.subTest(mutation=mutation): + frames = read_abacus_out(self.fixture / 'running_md.log') + if mutation == 'cell': + frames[0].cell[0, 0] += 0.1 + elif mutation == 'pbc': + frames[0].pbc[0] = False + else: + frames[0].positions[0, 0] += 0.1 + with self.assertRaises(AssertionError): + assert_native_md_facts(self, frames, self.fixture / 'MD_dump', + expected_frames=2) + + +if __name__ == '__main__': + unittest.main() diff --git a/interfaces/ASE_interface/tests/test_portable_runner.py b/interfaces/ASE_interface/tests/test_portable_runner.py new file mode 100644 index 00000000000..74bb23f1ffb --- /dev/null +++ b/interfaces/ASE_interface/tests/test_portable_runner.py @@ -0,0 +1,212 @@ +"""Offline contract checks for the portable real-engine lane.""" + +import os +import io +import json +import signal +import sys +import tempfile +import time +import unittest +import zipfile +from contextlib import redirect_stdout +from pathlib import Path +from types import ModuleType, SimpleNamespace +from unittest.mock import patch + +from run_engine_compatibility import (CASES, PROBE, attest_install, capture, + required_path, validate_cases, + validate_engine_format) + + +class PortableRunnerTest(unittest.TestCase): + def test_mismatched_or_untested_engine_version_cannot_select_an_oracle_lane(self): + validate_engine_format('v3.10.1', 'legacy') + validate_engine_format('v3.11.0-beta11', 'latest') + for version, fmt in [('v3.10.1', 'latest'), ('v3.11.0', 'legacy'), + ('v4.0.0', 'latest'), ('garbage', 'legacy')]: + with self.subTest(version=version, fmt=fmt): + with self.assertRaisesRegex(ValueError, 'does not match'): + validate_engine_format(version, fmt) + + def test_package_probe_accepts_public_profile_without_core_internals(self): + package = ModuleType('abacuslite') + package.__path__ = [] + package.__file__ = '/venv/site-packages/abacuslite/__init__.py' + + class PublicProfile: + def __init__(self, command): + self.command = command + + def version(self): + self_test.assertEqual(self.command, '/engine/abacus') + return 'v3.10.1' + + self_test = self + package.AbacusProfile = PublicProfile + distribution = SimpleNamespace( + version='1.2.1', locate_file=lambda name: Path('/venv/site-packages') / name) + output = io.StringIO() + # A legitimate implementation may have no core module or template. + # The runner only needs the public version probe and package identity. + with patch.dict(sys.modules, {'abacuslite': package, 'abacuslite.core': None}), \ + patch('importlib.metadata.distribution', return_value=distribution), \ + patch.object(sys, 'argv', ['probe', '/engine/abacus']), \ + redirect_stdout(output): + exec(PROBE, {}) + self.assertEqual(json.loads(output.getvalue())['engine_version'], 'v3.10.1') + + def test_venv_python_symlink_is_not_resolved_to_base_interpreter(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + binary = root / 'base-python' + binary.touch() + venv_python = root / 'venv-python' + venv_python.symlink_to(binary) + self.assertEqual(required_path(str(venv_python), file=True, + follow_symlinks=False), venv_python) + + def test_required_case_set_rejects_missing_failure_skip_and_wrong_count(self): + cases = [{'script': name, 'returncode': 0, 'timed_out': False, + 'tests_reported': count, 'skipped': False} + for name, count in CASES['legacy'].items()] + validate_cases('legacy', cases) + for changed in (cases[:-1],): + with self.assertRaisesRegex(ValueError, 'incomplete'): + validate_cases('legacy', changed) + for change in ({'returncode': 1}, {'timed_out': True}, + {'tests_reported': 0}, {'skipped': True}): + altered = [dict(item) for item in cases] + altered[0].update(change) + with self.assertRaisesRegex(ValueError, 'required case failed'): + validate_cases('legacy', altered) + + def test_installed_runtime_bytes_must_match_supplied_wheel(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + site = root / 'venv' / 'lib' / 'python3.10' / 'site-packages' + package = site / 'abacuslite' + package.mkdir(parents=True) + imported = package / '__init__.py' + imported.write_text('original') + wheel = root / 'abacuslite-1.1.0-py3-none-any.whl' + with zipfile.ZipFile(wheel, 'w') as archive: + archive.writestr('abacuslite/__init__.py', 'original') + archive.writestr('abacuslite-1.1.0.dist-info/METADATA', + 'Metadata-Version: 2.1\nName: abacuslite\nVersion: 1.1.0\n') + probe = {'module': str(imported), 'installed': str(imported), + 'prefix': str(root / 'venv'), 'version': '1.1.0'} + attest_install(probe, wheel, root / 'checkout') + imported.write_text('mutated') + with self.assertRaisesRegex(ValueError, 'runtime differs'): + attest_install(probe, wheel, root / 'checkout') + imported.write_text('original') + with self.assertRaisesRegex(ValueError, 'shadowed'): + attest_install(probe, wheel, root) + + def test_hung_case_has_bounded_process_group_timeout(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + result = capture(['python3', '-c', 'import time; time.sleep(60)'], + cwd=root, env=None, log=root / 'hung.log', timeout=0.1) + self.assertTrue(result['timed_out']) + self.assertNotEqual(result['returncode'], 0) + self.assertLess(result['seconds'], 3) + + def test_timeout_kills_sigterm_ignoring_child_after_leader_exits(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + pid_file = root / 'child.pid' + child_code = ('import os,signal,sys,time; ' + 'signal.signal(signal.SIGTERM, signal.SIG_IGN); ' + 'open(sys.argv[1], "w").write(str(os.getpid())); ' + 'time.sleep(60)') + leader_code = ('import subprocess,sys,time; ' + 'subprocess.Popen([sys.executable, "-c", ' + 'sys.argv[1], sys.argv[2]]); time.sleep(60)') + child_pid = None + try: + result = capture([sys.executable, '-c', leader_code, + child_code, str(pid_file)], + cwd=root, env=None, log=root / 'hung-group.log', + timeout=1) + self.assertTrue(pid_file.is_file(), 'child probe did not start') + child_pid = int(pid_file.read_text()) + self.assertTrue(result['timed_out']) + self.assertLess(result['seconds'], 4) + for _ in range(50): + try: + state = Path(f'/proc/{child_pid}/stat').read_text().split()[2] + except FileNotFoundError: + break + if state == 'Z': + break + time.sleep(0.02) + else: + self.fail(f'timeout left SIGTERM-ignoring child {child_pid} alive') + finally: + if child_pid is None and pid_file.is_file(): + child_pid = int(pid_file.read_text()) + if child_pid is not None: + try: + child_group = os.getpgid(child_pid) + if child_group != os.getpgrp(): + os.killpg(child_group, signal.SIGKILL) + else: + os.kill(child_pid, signal.SIGKILL) + except ProcessLookupError: + pass + + def test_nonzero_leader_exit_reclaims_remaining_child(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + pid_file = root / 'child.pid' + child_code = ('import os,signal,sys,time; ' + 'signal.signal(signal.SIGTERM, signal.SIG_IGN); ' + 'open(sys.argv[1], "w").write(str(os.getpid())); ' + 'time.sleep(60)') + leader_code = ('import os,sys,time,subprocess; ' + 'subprocess.Popen([sys.executable, "-c", ' + 'sys.argv[1], sys.argv[2]]); ' + 'deadline=time.time()+10\n' + 'while not os.path.exists(sys.argv[2]) and ' + 'time.time() ABACUS calculation -> post-analysis) - Standardize integration methods to ensure reproducibility and ease of use +The ASE calculator is maintained externally as the +[`abacuslite`](https://github.com/QuantumMisaka/abacuslite) package. +`ASE_interface/` in this repository contains only the engine-side +compatibility harness used by ABACUS CI. + ## Usage Guidelines - For each software interface, refer to the dedicated README file in the diff --git a/source/source_relax/socket_utils.cpp b/source/source_relax/socket_utils.cpp index 8f41d005460..d4b61df17b1 100644 --- a/source/source_relax/socket_utils.cpp +++ b/source/source_relax/socket_utils.cpp @@ -68,7 +68,7 @@ void throw_if_any_rank_failed(int local_failed, std::string local_message) std::string properties_extra(const ComputedFrame& frame) { std::ostringstream extra; - extra << "{\"schema\":\"abacus.socket.properties.v1\",\"present\":[\"energy\""; + extra << "{\"schema\":\"" << kPropertiesSchema << "\",\"present\":[\"energy\""; if (frame.forces_present) { extra << ",\"forces\""; diff --git a/source/source_relax/socket_utils.h b/source/source_relax/socket_utils.h index 4314d165302..ce90a24269b 100644 --- a/source/source_relax/socket_utils.h +++ b/source/source_relax/socket_utils.h @@ -21,6 +21,7 @@ constexpr double kInverseRelativeTolerance = 64.0; constexpr double kStressAbsoluteTolerance = 1.0e-10; constexpr double kStressRelativeTolerance = 1.0e-8; constexpr std::int32_t kMaxInitBytes = static_cast(1048576); +constexpr const char* kPropertiesSchema = "abacus.socket.properties.v1"; enum class DriverState { diff --git a/source/source_relax/unittests/test_socket_driver.cpp b/source/source_relax/unittests/test_socket_driver.cpp index 50e5fe25597..d8a3acc9f07 100644 --- a/source/source_relax/unittests/test_socket_driver.cpp +++ b/source/source_relax/unittests/test_socket_driver.cpp @@ -4,6 +4,7 @@ #include "source_esolver/esolver.h" #include "source_io/module_parameter/input_parameter.h" #include "source_relax/socket_driver.h" +#include "source_relax/socket_utils.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -482,6 +483,17 @@ TEST(SocketDriverTest, NonconvergedFrameIsPublishedWithMetadata) EXPECT_THAT(response.extra, testing::HasSubstr("\"scf_converged\":false")); } +TEST(SocketDriverTest, PropertiesMetadataDeclaresVersionedSchema) +{ + SocketUtils::ComputedFrame frame; + const std::string extra = SocketUtils::properties_extra(frame); + + // A breaking schema change must introduce a new version in both ABACUS and + // abacuslite; the adapter rejects unknown schemas. + EXPECT_THAT(extra, testing::HasSubstr( + "\"schema\":\"abacus.socket.properties.v1\"")); +} + TEST(SocketDriverTest, EnergyOnlyFrameMarksForceAndStressAbsent) { ForceResponse response;