├── .github └── workflows │ ├── ci.yml │ └── release_and_publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── cp2k_spm_tools ├── __init__.py ├── bader_wrapper.py ├── cli │ ├── __init__.py │ ├── bader_bond_order.py │ ├── crop_orbs_wfn.py │ ├── cube_from_wfn.py │ ├── cube_operations.py │ ├── cube_single_column.py │ ├── cube_split.py │ ├── hrstm_from_wfn.py │ ├── overlap_from_wfns.py │ ├── stm_sts_from_wfn.py │ └── stm_sts_plotter.py ├── common.py ├── cp2k_ftsts.py ├── cp2k_grid_orbitals.py ├── cp2k_overlap_matrix.py ├── cp2k_stm_sts.py ├── cp2k_utils.py ├── cp2k_wfn_file.py ├── cube.py ├── cube_utils.py ├── cycles.py ├── hrstm_tools │ ├── __init__.py │ ├── cp2k_grid_matrix.py │ ├── hrstm.py │ ├── hrstm_utils.py │ ├── interpolator.py │ ├── ppstm_grid_orbitals.py │ └── tip_coeffs.py ├── igor.py ├── postprocess │ ├── __init__.py │ └── overlap.py └── qe_utils.py ├── examples ├── benzene_cube_from_wfn │ └── run.sh ├── benzene_overlap │ └── run.sh ├── benzene_stm │ └── run.sh ├── c2h2_bader_bond_order │ ├── ref │ │ ├── neargrid_0.06.txt │ │ └── weight_0.06.txt │ └── run.sh ├── c2h2_bader_charge_wfn │ └── run.sh ├── clean_all_examples.sh ├── data │ ├── BASIS_MOLOPT │ ├── benzene_cp2k_scf │ │ ├── PROJ-RESTART.wfn │ │ ├── PROJ-WFN_00015_1-1_0.cube │ │ ├── PROJ-WFN_00016_1-1_0.cube │ │ ├── PROJ-v_hartree-1_0.cube │ │ ├── cp2k.inp │ │ ├── cp2k.out │ │ └── geom.xyz │ ├── c2h2_cp2k_scf │ │ ├── PROJ-RESTART.wfn │ │ ├── PROJ-WFN_00005_1-1_0.cube │ │ ├── PROJ-WFN_00006_1-1_0.cube │ │ ├── PROJ-v_hartree-1_0.cube │ │ ├── charge_density.cube │ │ ├── cp2k.inp │ │ ├── cp2k.out │ │ └── geom.xyz │ ├── o2_cp2k_scf │ │ ├── PROJ-RESTART.wfn │ │ ├── PROJ-WFN_00004_2-1_0.cube │ │ ├── PROJ-WFN_00005_2-1_0.cube │ │ ├── PROJ-WFN_00006_1-1_0.cube │ │ ├── PROJ-WFN_00006_2-1_0.cube │ │ ├── PROJ-WFN_00007_1-1_0.cube │ │ ├── PROJ-WFN_00007_2-1_0.cube │ │ ├── PROJ-WFN_00008_1-1_0.cube │ │ ├── PROJ-WFN_00009_1-1_0.cube │ │ ├── PROJ0-RESTART.wfn │ │ ├── aiida.coords.xyz │ │ ├── inp │ │ ├── inp0 │ │ ├── out │ │ ├── out0 │ │ ├── run │ │ └── run0 │ ├── polyphenylene_cp2k_scf │ │ ├── PROJ-HART-v_hartree-1_0.cube │ │ ├── PROJ-RESTART.wfn │ │ ├── cp2k.inp │ │ ├── cp2k.out │ │ └── ppp_12uc-opt.xyz │ ├── polyphenylene_qe_bands │ │ ├── bands.in │ │ ├── bands.xml │ │ ├── scf.in │ │ └── scf.xml │ └── tbau2_cp2k_scf │ │ ├── PROJ-RESTART.wfn │ │ ├── PROJ-WFN_00108_2-1_0.cube │ │ ├── PROJ-WFN_00109_1-1_0.cube │ │ ├── PROJ-WFN_00109_2-1_0.cube │ │ ├── PROJ-WFN_00110_1-1_0.cube │ │ ├── PROJ0-RESTART.wfn │ │ ├── aiida.coords.xyz │ │ ├── inp │ │ ├── inp0 │ │ ├── out │ │ ├── out0 │ │ ├── run │ │ └── run0 ├── example.png ├── o2_cube_from_wfn │ ├── check_cube.ipynb │ └── run.sh ├── o2_overlap │ └── run.sh ├── run_all_examples.sh └── tbau2_cube_from_wfn │ └── run.sh ├── hrstm_tips └── tip_coeffs.tar.gz ├── notebooks ├── ftsts_analysis.ipynb ├── overlap_viewer.ipynb ├── spherical_harmonics.nb └── stm_viewer.ipynb └── pyproject.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit check 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | PYTEST_ADDOPTS: "--color=yes" 11 | 12 | # Cancel running workflows when additional changes are pushed 13 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value 14 | concurrency: 15 | group: ${{ github.head_ref || github.run_id }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | pre-commit: 20 | name: install and run pre-commit 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Set up Python 3.10 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: "3.10" 30 | cache: "pip" 31 | cache-dependency-path: | 32 | pyproject.toml 33 | 34 | - name: Install pre-commit 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install pre-commit ruff 38 | 39 | - name: Run linters 40 | run: | 41 | pre-commit run --all-files 42 | -------------------------------------------------------------------------------- /.github/workflows/release_and_publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release on Github and publish on PyPI 3 | 4 | on: 5 | push: 6 | tags: 7 | # After vMajor.Minor.Patch _anything_ is allowed (without "/") ! 8 | - v[0-9]+.[0-9]+.[0-9]+* 9 | 10 | jobs: 11 | release_and_publish: 12 | runs-on: ubuntu-latest 13 | if: github.repository == 'nanotech-empa/cp2k-spm-tools' && startsWith(github.ref, 'refs/tags/v') 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Python 3.10 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "3.10" 23 | 24 | - name: Install MPICH 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install -y mpich libmpich-dev 28 | 29 | - name: Install pypa/build 30 | run: | 31 | python -m pip install build 32 | 33 | - name: Build source distribution 34 | run: python -m build . --wheel --sdist 35 | 36 | - name: Create release 37 | uses: softprops/action-gh-release@v2 38 | with: 39 | files: | 40 | dist/* 41 | generate_release_notes: true 42 | 43 | - name: Publish package to PyPI 44 | uses: pypa/gh-action-pypi-publish@release/v1 45 | with: 46 | user: __token__ 47 | password: ${{ secrets.PYPI_TOKEN }} 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | __pycache__/ 3 | .vscode 4 | dist/ 5 | *.egg-info/ 6 | .venv/ 7 | *venv/ 8 | .pypirc 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.9.4 4 | hooks: 5 | # Run the linter. 6 | - id: ruff 7 | args: [--fix] 8 | # Run the formatter. 9 | - id: ruff-format 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kristjan Eimre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CP2K Scanning Probe Microscopy tools 2 | 3 | [![DOI](https://zenodo.org/badge/133041124.svg)](https://zenodo.org/badge/latestdoi/133041124) 4 | [![PyPI - Version](https://img.shields.io/pypi/v/cp2k-spm-tools?color=4CC61E)](https://pypi.org/project/cp2k-spm-tools/) 5 | 6 | Library and scripts to perform scanning probe microscopy simulations based on a [CP2K](https://www.cp2k.org/) calculation. 7 | 8 | Features include: 9 | 10 | - Processing the various output files of [CP2K](https://www.cp2k.org/), including the `.wfn` file 11 | - Scanning Tunnelling Microscopy and Spectroscopy (STM/STS) analysis 12 | - Fourier-Transformed STS analysis for finite cutouts of periodic systems 13 | - Orbital hybridization analysis for adsorbed systems 14 | - High-resolution STM (HRSTM) simulations 15 | 16 | ### Installation 17 | 18 | The package requires an MPI implementation to be available on your system for `mpi4py`. One option is `mpich`, which you can install with: 19 | 20 | - On Linux: through your package manager (e.g., `apt install mpich` or `yum install mpich`) 21 | - On macOS: through Homebrew (`brew install mpich`) 22 | - Or via conda, but then it's recommended to install it together with `mpi4py`: `conda install -c conda-forge mpi4py mpich` 23 | 24 | Then install the package with pip: 25 | 26 | ```bash 27 | pip install cp2k-spm-tools 28 | ``` 29 | 30 | Or, for development: 31 | 32 | ```bash 33 | git clone https://github.com/nanotech-empa/cp2k-spm-tools.git 34 | cd cp2k-spm-tools 35 | pip install -e .[dev] 36 | ``` 37 | 38 | ### Command Line Tools 39 | 40 | The package provides several command-line tools, including: 41 | 42 | - `cp2k-stm-sts-wfn`: STM/STS analysis from wavefunction files 43 | - `cp2k-cube-from-wfn`: Create cube files from wavefunction files 44 | - `cp2k-bader-bond-order`: Bond order analysis based on Bader basins 45 | 46 | Use `--help` with each command to see its options. 47 | 48 | ### Example Usage 49 | 50 | When everything is set up correctly, the bash scripts in `examples/` folder can be executed without any further input and illustrate the usage of the various scripts. For example `example/benzene_stm/run_stm_sts_from_wfn.sh` evaluates the STM/STS signatures of isolated benzene at each orbital energy (`out/orb/`) as well as in an arbitrary energy range (`out/stm/`). The corresponding CP2K calculation is included in the repository. 51 | 52 | **NB: In all cases, the underlying DFT calculation has to be performed with the diagonalization algorithm rather than orbital transformation (OT).** 53 | 54 | ### Python API Example 55 | 56 | Most of the functionality of this library is built on top of the possibility to evaluate the Kohn-Sham orbitals encoded in the `.wfn` file on an arbitrarily defined grid. This is illustrated by the following script applied for a nanographene adsorbed on a Au(111) slab (total of 1252 atoms and 10512 electrons): 57 | 58 | ```python 59 | from cp2k_spm_tools.cp2k_grid_orbitals import Cp2kGridOrbitals 60 | 61 | ### Create the gridding object and load the cp2k data ### 62 | cgo = Cp2kGridOrbitals() 63 | cgo.read_cp2k_input("./cp2k.inp") 64 | cgo.read_xyz("./geom.xyz") 65 | cgo.read_basis_functions("./BASIS_MOLOPT") 66 | cgo.load_restart_wfn_file("./PROJ-RESTART.wfn", n_occ=2, n_virt=2) 67 | 68 | ### Evaluate the orbitals in the specific region ### 69 | cgo.calc_morbs_in_region( 70 | dr_guess = 0.15, # grid spacing, can change very slightly 71 | x_eval_region = None, # take whole cell in x 72 | y_eval_region = [0.0, cgo.cell_ang[1]/2], # half cell in y 73 | z_eval_region = [19.0, 24.0], # around the molecule in z 74 | ) 75 | 76 | cgo.write_cube("./homo.cube", orbital_nr=0) 77 | ``` 78 | 79 | Here's the resulting cube file, illustrating the constrained region of evaluation: 80 | 81 | 82 | 83 | ### For maintainers: 84 | 85 | In order to make a new release of the library and publish to PYPI, run 86 | 87 | ```shell 88 | bumpver update --major/--minor/--patch 89 | ``` 90 | 91 | This will 92 | 93 | - update version numbers, make a corresponding git commit and a git tag; 94 | - push this commit and tag to Github, which triggers the Github Action that makes a new Github Release and publishes the package to PYPI. 95 | -------------------------------------------------------------------------------- /cp2k_spm_tools/__init__.py: -------------------------------------------------------------------------------- 1 | """CP2K Scanning Probe Microscopy tools""" 2 | -------------------------------------------------------------------------------- /cp2k_spm_tools/bader_wrapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Routines to call the Henkelmann Bader program 3 | """ 4 | 5 | import os 6 | import subprocess 7 | 8 | ang_2_bohr = 1.0 / 0.52917721067 9 | hart_2_ev = 27.21138602 10 | 11 | 12 | def call_bader(folder, cube_file, method="neargrid", basin_atoms=[], ref_cube=None): 13 | cur_dir = os.getcwd() 14 | 15 | command = "bader -b %s" % method 16 | if len(basin_atoms) > 0: 17 | command += " -p sel_atom " + " ".join([str(e + 1) for e in basin_atoms]) 18 | 19 | if ref_cube is not None: 20 | command += " -ref %s" % ref_cube 21 | 22 | command += " %s > bader.log" % cube_file 23 | 24 | print(command) 25 | 26 | try: 27 | os.chdir(folder) 28 | subprocess.call(command, shell=True) 29 | except: 30 | print("Warning: Couldn't run Bader.") 31 | 32 | os.chdir(cur_dir) 33 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/cp2k_spm_tools/cli/__init__.py -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/bader_bond_order.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | 5 | from mpi4py import MPI 6 | 7 | from cp2k_spm_tools import common 8 | 9 | 10 | def main(): 11 | comm = MPI.COMM_WORLD 12 | mpi_rank = comm.Get_rank() 13 | mpi_size = comm.Get_size() 14 | 15 | parser = argparse.ArgumentParser(description="Runs bond order analysis based on Bader basins.") 16 | 17 | parser.add_argument( 18 | "--cp2k_input_file", metavar="FILENAME", required=True, help="CP2K input of the SCF calculation." 19 | ) 20 | parser.add_argument( 21 | "--basis_set_file", metavar="FILENAME", required=True, help="File containing the used basis sets." 22 | ) 23 | parser.add_argument("--xyz_file", metavar="FILENAME", required=True, help=".xyz file containing the geometry.") 24 | parser.add_argument( 25 | "--wfn_file", metavar="FILENAME", required=True, help="cp2k restart file containing the wavefunction." 26 | ) 27 | parser.add_argument( 28 | "--output_file", metavar="FILENAME", required=True, help="Output file containing the bond orders." 29 | ) 30 | parser.add_argument( 31 | "--bader_basins_dir", metavar="DIR", required=True, help="directory containing the Bader basin .cube files." 32 | ) 33 | parser.add_argument("--dx", type=float, metavar="DX", default=0.2, help="Spatial step for the grid (angstroms).") 34 | parser.add_argument( 35 | "--eval_cutoff", 36 | type=float, 37 | metavar="D", 38 | default=14.0, 39 | help=("Size of the region around the atom where each orbital is evaluated (only used for 'G' region)."), 40 | ) 41 | parser.add_argument( 42 | "--eval_region", 43 | type=str, 44 | nargs=6, 45 | metavar="X", 46 | required=False, 47 | default=["G", "G", "G", "G", "G", "G"], 48 | help=common.eval_region_description, 49 | ) 50 | 51 | # Rest of your existing code, unchanged 52 | time0 = time.time() 53 | 54 | ### ------------------------------------------------------ 55 | ### Parse args for only one rank to suppress duplicate stdio 56 | ### ------------------------------------------------------ 57 | 58 | args = None 59 | args_success = False 60 | try: 61 | if mpi_rank == 0: 62 | args = parser.parse_args() 63 | args_success = True 64 | finally: 65 | args_success = comm.bcast(args_success, root=0) 66 | 67 | if not args_success: 68 | print(mpi_rank, "exiting") 69 | exit(0) 70 | 71 | args = comm.bcast(args, root=0) 72 | 73 | # ... rest of your existing code ... 74 | 75 | print("R%d/%d finished, total time: %.2fs" % (mpi_rank, mpi_size, (time.time() - time0))) 76 | 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/crop_orbs_wfn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | 5 | import cp2k_spm_tools.cp2k_wfn_file as cwf 6 | 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser(description="Crops CP2K RESTART.wfn file.") 10 | 11 | parser.add_argument( 12 | "--wfn_file", metavar="FILENAME", required=True, help="cp2k restart file containing the wavefunction." 13 | ) 14 | parser.add_argument("--output_file", metavar="FILENAME", required=True, help="File where to save the output") 15 | parser.add_argument( 16 | "--emin", type=float, metavar="E", default=0.0, help="Lowest energy value for selecting orbitals (eV)." 17 | ) 18 | parser.add_argument( 19 | "--emax", type=float, metavar="E", default=0.0, help="Highest energy value for selecting orbitals (eV)." 20 | ) 21 | parser.add_argument("--n_homo", type=int, metavar="N", default=0, help="Number of HOMO orbitals to export.") 22 | parser.add_argument("--n_lumo", type=int, metavar="N", default=0, help="Number of LUMO orbitals to export.") 23 | 24 | time0 = time.time() 25 | 26 | args = parser.parse_args() 27 | 28 | cp2k_wfn_f = cwf.Cp2kWfnFile() 29 | 30 | if args.n_homo > 0 or args.n_lumo > 0: 31 | print("Number of orbitals specified, energy limits ignored.") 32 | cp2k_wfn_f.load_restart_wfn_file(args.wfn_file, n_homo=args.n_homo, n_lumo=args.n_lumo) 33 | else: 34 | cp2k_wfn_f.load_restart_wfn_file(args.wfn_file, emin=args.emin, emax=args.emax) 35 | 36 | print("Loaded wfn, %.2fs" % (time.time() - time0)) 37 | 38 | cp2k_wfn_f.write_fortran(args.output_file) 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/cube_from_wfn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | 5 | import numpy as np 6 | from mpi4py import MPI 7 | 8 | import cp2k_spm_tools.cp2k_grid_orbitals as cgo 9 | from cp2k_spm_tools import common 10 | 11 | 12 | def main(): 13 | comm = MPI.COMM_WORLD 14 | mpi_rank = comm.Get_rank() 15 | mpi_size = comm.Get_size() 16 | 17 | parser = argparse.ArgumentParser(description="Creates Gaussian cube files from cp2k .wfn file.") 18 | 19 | parser.add_argument( 20 | "--cp2k_input_file", 21 | metavar="FILENAME", 22 | required=True, 23 | help="CP2K input of the SCF calculation.", 24 | ) 25 | parser.add_argument( 26 | "--basis_set_file", 27 | metavar="FILENAME", 28 | required=True, 29 | help="File containing the used basis sets.", 30 | ) 31 | parser.add_argument( 32 | "--xyz_file", 33 | metavar="FILENAME", 34 | required=True, 35 | help=".xyz file containing the geometry.", 36 | ) 37 | parser.add_argument( 38 | "--wfn_file", 39 | metavar="FILENAME", 40 | required=True, 41 | help="cp2k restart file containing the wavefunction.", 42 | ) 43 | 44 | parser.add_argument( 45 | "--output_dir", 46 | metavar="DIR", 47 | required=True, 48 | help="directory where to output the cubes.", 49 | ) 50 | ### ----------------------------------------------------------- 51 | parser.add_argument( 52 | "--dx", 53 | type=float, 54 | metavar="DX", 55 | default=0.2, 56 | help="Spatial step for the grid (angstroms).", 57 | ) 58 | parser.add_argument( 59 | "--eval_cutoff", 60 | type=float, 61 | metavar="D", 62 | default=14.0, 63 | help=("Size of the region around the atom where each orbital is evaluated (only used for 'G' region)."), 64 | ) 65 | parser.add_argument( 66 | "--eval_region", 67 | type=str, 68 | nargs=6, 69 | metavar="X", 70 | required=False, 71 | default=["G", "G", "G", "G", "G", "G"], 72 | help=common.eval_region_description, 73 | ) 74 | parser.add_argument( 75 | "--pbc", 76 | type=int, 77 | nargs=3, 78 | metavar="X", 79 | required=False, 80 | default=[1, 1, 1], 81 | help="periodic boundary conditions in directions [x,y,z]. (1=on, 0=off)", 82 | ) 83 | ### ----------------------------------------------------------- 84 | parser.add_argument( 85 | "--n_homo", 86 | type=int, 87 | metavar="N", 88 | default=0, 89 | help="Number of HOMO orbitals to export.", 90 | ) 91 | parser.add_argument( 92 | "--n_lumo", 93 | type=int, 94 | metavar="N", 95 | default=0, 96 | help="Number of LUMO orbitals to export.", 97 | ) 98 | parser.add_argument( 99 | "--orb_square", 100 | action="store_true", 101 | help=("Additionally generate the square (RHO) for each MO."), 102 | ) 103 | ### ----------------------------------------------------------- 104 | parser.add_argument( 105 | "--charge_dens", 106 | action="store_true", 107 | help=("Calculate charge density (all occupied orbitals are evaluated)."), 108 | ) 109 | parser.add_argument( 110 | "--charge_dens_artif_core", 111 | action="store_true", 112 | help=("Calculate charge density with 'fake' artificial core (all occ orbitals are evaluated)."), 113 | ) 114 | parser.add_argument( 115 | "--spin_dens", 116 | action="store_true", 117 | help=("Calculate spin density (all occupied orbitals are evaluated)."), 118 | ) 119 | ### ----------------------------------------------------------- 120 | parser.add_argument("--do_not_center_atoms", action="store_true", help=("Center atoms to cell.")) 121 | ### ----------------------------------------------------------- 122 | 123 | time0 = time.time() 124 | 125 | ### ------------------------------------------------------ 126 | ### Parse args for only one rank to suppress duplicate stdio 127 | ### ------------------------------------------------------ 128 | 129 | args = None 130 | args_success = False 131 | try: 132 | if mpi_rank == 0: 133 | args = parser.parse_args() 134 | args_success = True 135 | finally: 136 | args_success = comm.bcast(args_success, root=0) 137 | 138 | if not args_success: 139 | print(mpi_rank, "exiting") 140 | exit(0) 141 | 142 | args = comm.bcast(args, root=0) 143 | 144 | output_dir = args.output_dir if args.output_dir[-1] == "/" else args.output_dir + "/" 145 | 146 | ### ------------------------------------------------------ 147 | ### Evaluate orbitals on the real-space grid 148 | ### ------------------------------------------------------ 149 | 150 | n_homo = args.n_homo 151 | n_lumo = args.n_lumo 152 | 153 | n_homo_range = n_homo 154 | if args.charge_dens or args.spin_dens: 155 | n_homo_range = None 156 | 157 | mol_grid_orb = cgo.Cp2kGridOrbitals(mpi_rank, mpi_size, comm, single_precision=False) 158 | mol_grid_orb.read_cp2k_input(args.cp2k_input_file) 159 | mol_grid_orb.read_xyz(args.xyz_file) 160 | if not args.do_not_center_atoms: 161 | mol_grid_orb.center_atoms_to_cell() 162 | mol_grid_orb.read_basis_functions(args.basis_set_file) 163 | mol_grid_orb.load_restart_wfn_file(args.wfn_file, n_occ=n_homo_range, n_virt=n_lumo) 164 | 165 | eval_reg = common.parse_eval_region_input(args.eval_region, mol_grid_orb.ase_atoms, mol_grid_orb.cell) 166 | 167 | mol_grid_orb.calc_morbs_in_region( 168 | args.dx, 169 | x_eval_region=eval_reg[0], 170 | y_eval_region=eval_reg[1], 171 | z_eval_region=eval_reg[2], 172 | pbc=np.array(args.pbc, dtype=bool), 173 | reserve_extrap=0.0, 174 | eval_cutoff=args.eval_cutoff, 175 | ) 176 | 177 | ### ------------------------------------------------------ 178 | ### Export the data 179 | ### ------------------------------------------------------ 180 | 181 | # ase_atoms = mol_grid_orb.ase_atoms 182 | # origin = mol_grid_orb.origin 183 | # cell = mol_grid_orb.eval_cell * np.eye(3) 184 | # vol_elem = np.prod(mol_grid_orb.dv) 185 | 186 | for imo in np.arange(n_homo + n_lumo): 187 | i_rel_homo = imo - n_homo + 1 188 | for ispin in range(mol_grid_orb.nspin): 189 | if imo >= len(mol_grid_orb.cwf.global_morb_indexes[ispin]): 190 | continue 191 | 192 | global_index = mol_grid_orb.cwf.global_morb_indexes[ispin][imo] 193 | 194 | if i_rel_homo < 0: 195 | hl_label = "HOMO%+d" % i_rel_homo 196 | elif i_rel_homo == 0: 197 | hl_label = "HOMO" 198 | elif i_rel_homo == 1: 199 | hl_label = "LUMO" 200 | else: 201 | hl_label = "LUMO%+d" % (i_rel_homo - 1) 202 | 203 | name = "S%d_%d_%s" % (ispin, global_index, hl_label) 204 | mol_grid_orb.write_cube(output_dir + name + ".cube", i_rel_homo, spin=ispin) 205 | 206 | if args.orb_square: 207 | mol_grid_orb.write_cube(output_dir + name + "_sq.cube", i_rel_homo, spin=ispin, square=True) 208 | 209 | if args.charge_dens: 210 | mol_grid_orb.calculate_and_save_charge_density(output_dir + "charge_density.cube") 211 | if args.charge_dens_artif_core: 212 | mol_grid_orb.calculate_and_save_charge_density(output_dir + "charge_density_artif.cube", artif_core=True) 213 | 214 | if args.spin_dens: 215 | mol_grid_orb.calculate_and_save_spin_density(output_dir + "spin_density.cube") 216 | 217 | print("R%d/%d: finished, total time: %.2fs" % (mpi_rank, mpi_size, (time.time() - time0))) 218 | 219 | 220 | if __name__ == "__main__": 221 | main() 222 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/cube_operations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import copy 4 | import time 5 | 6 | import numpy as np 7 | 8 | from cp2k_spm_tools import cube, cube_utils 9 | 10 | 11 | def main(): 12 | ang_2_bohr = 1.0 / 0.52917721067 13 | 14 | parser = argparse.ArgumentParser(description="Operations on gaussian cube files.") 15 | 16 | parser.add_argument("cubes", metavar="FILENAME", nargs="+", help="Gaussian cube files.") 17 | 18 | parser.add_argument( 19 | "operations", metavar="OP", type=str, help="Operations to apply to each cube. Enclose in quotation marks." 20 | ) 21 | 22 | parser.add_argument( 23 | "--proj_1d", 24 | metavar="IDs", 25 | type=str, 26 | default="no", 27 | help=("Projects to 'x', 'y' or 'z' dim, possibly averaging (e.g. 'z avg')."), 28 | ) 29 | parser.add_argument("--skip_result_cube", action="store_true", help=("Don't write the result cube.")) 30 | parser.add_argument( 31 | "--add_artif_core", 32 | action="store_true", 33 | help=("Adds artifical core charge to result cube (mainly for Bader analysis)."), 34 | ) 35 | 36 | time0 = time.time() 37 | 38 | args = parser.parse_args() 39 | 40 | result = None 41 | 42 | operations = args.operations.split() 43 | 44 | if len(operations) != len(args.cubes): 45 | print("Error: didn't find match between cubes and operations.") 46 | print("Did you forget to enclose operations in quotation marks?") 47 | exit(1) 48 | 49 | for i_c, cube_file in enumerate(args.cubes): 50 | time1 = time.time() 51 | c = cube.Cube() 52 | print("Reading %s..." % cube_file) 53 | c.read_cube_file(cube_file) 54 | 55 | if result is None: 56 | result = copy.deepcopy(c) 57 | result.data.fill(0) 58 | 59 | if np.any(np.abs(c.cell - result.cell) > 1e-4): 60 | print("Error: cube cell doesn't match: ", cube_file) 61 | exit(1) 62 | if np.any(c.data.shape != result.data.shape): 63 | print("Error: cube shape doesn't match: ", cube_file) 64 | exit(1) 65 | 66 | op = operations[i_c] 67 | 68 | if op == "+": 69 | result.data += c.data 70 | elif op == "-": 71 | result.data -= c.data 72 | elif op == "*": 73 | result.data *= c.data 74 | elif op == "/": 75 | result.data /= c.data 76 | 77 | print("%s done, time: %.2fs" % (cube_file, (time.time() - time1))) 78 | 79 | if args.add_artif_core: 80 | cube_utils.add_artif_core_charge(result) 81 | 82 | if not args.skip_result_cube: 83 | print("Writing result...") 84 | result.write_cube_file("./result.cube") 85 | 86 | proj_1d_ids = args.proj_1d.split() 87 | 88 | if "no" not in proj_1d_ids: 89 | avg = "avg" in proj_1d_ids 90 | proj_dims = [] 91 | if "x" in proj_1d_ids: 92 | proj_dims.append(0) 93 | if "y" in proj_1d_ids: 94 | proj_dims.append(1) 95 | if "z" in proj_1d_ids: 96 | proj_dims.append(2) 97 | 98 | for pd in proj_dims: 99 | if avg: 100 | data_1d = np.mean(result.data, axis=tuple({0, 1, 2} - {pd})) 101 | else: 102 | data_1d = np.sum(result.data, axis=tuple({0, 1, 2} - {pd})) 103 | x_arr = result.origin[pd] + np.linspace(0.0, result.cell[pd, pd], result.data.shape[pd]) 104 | x_arr /= ang_2_bohr 105 | save_arr = np.column_stack((x_arr, data_1d)) 106 | avg_str = "_avg" if avg else "" 107 | fname = "./proj_1d_%d" % pd + avg_str + ".txt" 108 | np.savetxt(fname, save_arr) 109 | 110 | print("Finished, total time: %.2fs" % (time.time() - time0)) 111 | 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/cube_single_column.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | 5 | from cp2k_spm_tools import cube 6 | 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser( 10 | description="Reformats cube in case it contains uneven columns (not supported by some software)." 11 | ) 12 | 13 | parser.add_argument("cube", metavar="FILENAME", help="Input cube file.") 14 | 15 | parser.add_argument("--output_cube", metavar="FILENAME", default="single_col.cube", help="Output cube file.") 16 | ### ----------------------------------------------------------- 17 | 18 | time0 = time.time() 19 | 20 | ### ------------------------------------------------------ 21 | ### Parse args for only one rank to suppress duplicate stdio 22 | ### ------------------------------------------------------ 23 | 24 | args = parser.parse_args() 25 | 26 | ### ------------------------------------------------------ 27 | ### Load the cube meta-data 28 | ### ------------------------------------------------------ 29 | 30 | inp_cube = cube.Cube() 31 | inp_cube.read_cube_file(args.cube, read_data=False) 32 | n_atoms = len(inp_cube.ase_atoms) 33 | 34 | n_metadata_lines = 6 + n_atoms 35 | 36 | column_digit_width = None 37 | num_decimals = 0 38 | 39 | with open(args.cube, "r") as in_f: 40 | with open(args.output_cube, "w") as out_f: 41 | i_line = 0 42 | for line in in_f: 43 | if i_line < n_metadata_lines: 44 | out_f.write(line) 45 | else: 46 | vals = line.split() 47 | for val in vals: 48 | val_fl = float(val) 49 | if column_digit_width is None: 50 | column_digit_width = len(val) 51 | if val_fl >= 0.0: 52 | column_digit_width += 1 53 | # determine num decimals 54 | after_p = val.split(".")[1] 55 | for c in after_p: 56 | if c.isdigit(): 57 | num_decimals += 1 58 | else: 59 | break 60 | 61 | out_f.write("{:{:d}.{:d}e}\n".format(val_fl, column_digit_width, num_decimals)) 62 | i_line += 1 63 | 64 | print("Finished, total time: %.2fs" % (time.time() - time0)) 65 | 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/cube_split.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import copy 4 | import os 5 | import time 6 | 7 | import ase 8 | import numpy as np 9 | from mpi4py import MPI 10 | 11 | from cp2k_spm_tools import bader_wrapper, cube, cube_utils 12 | 13 | 14 | def main(): 15 | ang_2_bohr = 1.0 / 0.52917721067 16 | 17 | comm = MPI.COMM_WORLD 18 | mpi_rank = comm.Get_rank() 19 | mpi_size = comm.Get_size() 20 | 21 | parser = argparse.ArgumentParser(description="Splits the cube file into smaller cubes centered around atoms.") 22 | 23 | parser.add_argument("cube", metavar="FILENAME", help="Input cube file.") 24 | parser.add_argument( 25 | "--atom_box_size", 26 | type=float, 27 | metavar="L", 28 | required=False, 29 | default=8.0, 30 | help="specify the evaluation box (L^3) size around each atom in [ang].", 31 | ) 32 | parser.add_argument("--output_dir", metavar="DIR", default=".", help="directory where to output the cubes.") 33 | ### ----------------------------------------------------------- 34 | 35 | time0 = time.time() 36 | 37 | ### ------------------------------------------------------ 38 | ### Parse args for only one rank to suppress duplicate stdio 39 | ### ------------------------------------------------------ 40 | 41 | args = None 42 | args_success = False 43 | try: 44 | if mpi_rank == 0: 45 | args = parser.parse_args() 46 | args_success = True 47 | finally: 48 | args_success = comm.bcast(args_success, root=0) 49 | 50 | if not args_success: 51 | print(mpi_rank, "exiting") 52 | exit(0) 53 | 54 | args = comm.bcast(args, root=0) 55 | 56 | output_dir = args.output_dir if args.output_dir[-1] == "/" else args.output_dir + "/" 57 | 58 | ### ------------------------------------------------------ 59 | ### Load the cube meta-data 60 | ### ------------------------------------------------------ 61 | 62 | inp_cube = cube.Cube() 63 | inp_cube.read_cube_file(args.cube, read_data=False) 64 | n_atoms = len(inp_cube.ase_atoms) 65 | 66 | dv = np.diag(inp_cube.dv) 67 | 68 | ### ------------------------------------------------------ 69 | ### Add periodic images of atoms that are close to the border 70 | ### ------------------------------------------------------ 71 | 72 | border_atom_images = ase.Atoms() 73 | 74 | d_cell = np.diag(inp_cube.cell) / ang_2_bohr 75 | 76 | inc_box = np.array( 77 | [ 78 | inp_cube.origin / ang_2_bohr - args.atom_box_size / 2, 79 | inp_cube.origin / ang_2_bohr + d_cell + args.atom_box_size / 2, 80 | ] 81 | ) 82 | 83 | def point_in_box(p, box): 84 | return box[0, 0] < p[0] < box[1, 0] and box[0, 1] < p[1] < box[1, 1] and box[0, 2] < p[2] < box[1, 2] 85 | 86 | for i_x in [-1, 0, 1]: 87 | for i_y in [-1, 0, 1]: 88 | for i_z in [-1, 0, 1]: 89 | if i_x == 0 and i_y == 0 and i_z == 0: 90 | continue 91 | pbc_vec = np.array([i_x, i_y, i_z]) * d_cell 92 | for atom in inp_cube.ase_atoms: 93 | pos = atom.position + pbc_vec 94 | if point_in_box(pos, inc_box): 95 | new_at = copy.deepcopy(atom) 96 | new_at.position = pos 97 | border_atom_images.append(new_at) 98 | 99 | ### ------------------------------------------------------ 100 | ### Analyze file memory layout 101 | ### ------------------------------------------------------ 102 | 103 | fhandle = open(args.cube, "r") 104 | n_metadata_lines = 6 + n_atoms 105 | # where does cube data start? 106 | data_start_offset = 0 107 | for i_l in range(n_metadata_lines): 108 | fhandle.readline() 109 | data_start_offset = fhandle.tell() 110 | 111 | # how is the cube data organized? 112 | # NB: THe following assumes that the number and width of columns 113 | # remains the same for the whole file 114 | ### NOT TRUE FOR CP2K OUTPUTS ### 115 | data_line = fhandle.readline() 116 | n_columns = len(data_line.split()) 117 | data_line_offset = fhandle.tell() - data_start_offset 118 | 119 | print("----------- data org: ", data_line, n_columns, data_line_offset) 120 | 121 | def get_nth_value(n): 122 | row = int(n / n_columns) 123 | col = n % n_columns 124 | fhandle.seek(data_start_offset + row * data_line_offset) 125 | return fhandle.readline().split()[col] 126 | 127 | ### ------------------------------------------------------ 128 | ### Divide the atoms between the mpi processes 129 | ### ------------------------------------------------------ 130 | 131 | base_atoms_per_rank = int(np.floor(n_atoms / mpi_size)) 132 | extra_atoms = n_atoms - base_atoms_per_rank * mpi_size 133 | if mpi_rank < extra_atoms: 134 | i_atom_start = mpi_rank * (base_atoms_per_rank + 1) 135 | i_atom_end = (mpi_rank + 1) * (base_atoms_per_rank + 1) 136 | else: 137 | i_atom_start = mpi_rank * (base_atoms_per_rank) + extra_atoms 138 | i_atom_end = (mpi_rank + 1) * (base_atoms_per_rank) + extra_atoms 139 | 140 | print("R%d/%d, atom indexes %d:%d " % (mpi_rank, mpi_size, i_atom_start, i_atom_end)) 141 | 142 | ### ------------------------------------------------------ 143 | ### Loop over atoms and extract local cubes 144 | ### ------------------------------------------------------ 145 | 146 | def parse_cube_data(extract_indexes): 147 | """ 148 | TOO SLOW TO BE USEFUL... 149 | Parse the whole cube file value-by-value (slow) 150 | by only taking the assigned memory... 151 | slow but memory usage is okay 152 | Also doesn't assume "fixed" column width in cube file 153 | """ 154 | cube_data = np.zeros(len(extract_indexes)) 155 | 156 | fhandle.seek(data_start_offset) 157 | cur_index = 0 158 | cube_i = 0 159 | for line in fhandle: 160 | vals = np.array(line.split(), dtype=float) 161 | 162 | for i_val, val in enumerate(vals): 163 | if cur_index + i_val in extract_indexes: 164 | cube_data[cube_i] = val 165 | cube_i += 1 166 | cur_index += len(vals) 167 | 168 | return cube_data 169 | 170 | for i_at in range(i_atom_start, i_atom_end): 171 | at_pos = inp_cube.ase_atoms[i_at].position 172 | 173 | cube_pos_1 = at_pos - 0.5 * np.array([1.0, 1.0, 1.0]) * args.atom_box_size 174 | cube_pos_2 = at_pos + 0.5 * np.array([1.0, 1.0, 1.0]) * args.atom_box_size 175 | 176 | cube_pos_1_i = np.round((cube_pos_1 - inp_cube.origin / ang_2_bohr) / dv).astype(int) 177 | cube_pos_2_i = np.round((cube_pos_2 - inp_cube.origin / ang_2_bohr) / dv).astype(int) 178 | 179 | x_inds = np.arange(cube_pos_1_i[0], cube_pos_2_i[0]) % inp_cube.cell_n[0] 180 | y_inds = np.arange(cube_pos_1_i[1], cube_pos_2_i[1]) % inp_cube.cell_n[1] 181 | z_inds = np.arange(cube_pos_1_i[2], cube_pos_2_i[2]) % inp_cube.cell_n[2] 182 | 183 | cube_data = np.zeros(len(x_inds) * len(y_inds) * len(z_inds)) 184 | 185 | # fastest index is z 186 | cube_i = 0 187 | for ix in x_inds: 188 | for iy in y_inds: 189 | for iz in z_inds: 190 | # extract_indexes.append(iz + iy * inp_cube.cell_n[2] + ix * inp_cube.cell_n[2] * inp_cube.cell_n[1]) 191 | ind = iz + iy * inp_cube.cell_n[2] + ix * inp_cube.cell_n[2] * inp_cube.cell_n[1] 192 | cube_data[cube_i] = get_nth_value(ind) 193 | cube_i += 1 194 | 195 | # Add only the atoms that fit in the box 196 | atoms_in_box = ase.Atoms() 197 | middle_at_i = 0 198 | for at in inp_cube.ase_atoms + border_atom_images: 199 | if point_in_box(at.position, np.array([cube_pos_1, cube_pos_2])): 200 | if np.allclose(at.position, at_pos): 201 | middle_at_i = len(atoms_in_box) 202 | atoms_in_box.append(copy.deepcopy(at)) 203 | 204 | # Save the new cube 205 | new_cube = cube.Cube( 206 | title="charge dens", 207 | comment="atom %d" % i_at, 208 | ase_atoms=atoms_in_box, 209 | origin=cube_pos_1_i * dv * ang_2_bohr + inp_cube.origin, 210 | cell=np.diag((cube_pos_2_i - cube_pos_1_i) * dv * ang_2_bohr), 211 | data=cube_data.reshape((len(x_inds), len(y_inds), len(z_inds))), 212 | ) 213 | 214 | local_dir = output_dir + "atom_%04d/" % i_at 215 | os.makedirs(local_dir, exist_ok=True) 216 | 217 | cube_name = "at_%04d.cube" % i_at 218 | new_cube.write_cube_file(local_dir + cube_name) 219 | cube_utils.add_artif_core_charge(new_cube) 220 | ref_cube_name = "at_%04d_artif.cube" % i_at 221 | new_cube.write_cube_file(local_dir + ref_cube_name) 222 | 223 | neargrid_dir = local_dir + "neargrid/" 224 | weight_dir = local_dir + "weight/" 225 | 226 | os.makedirs(neargrid_dir, exist_ok=True) 227 | os.makedirs(weight_dir, exist_ok=True) 228 | 229 | bader_wrapper.call_bader( 230 | neargrid_dir, 231 | "../" + cube_name, 232 | ref_cube="../" + ref_cube_name, 233 | basin_atoms=[middle_at_i], 234 | method="neargrid", 235 | ) 236 | bader_wrapper.call_bader( 237 | weight_dir, "../" + cube_name, ref_cube="../" + ref_cube_name, basin_atoms=[middle_at_i], method="weight" 238 | ) 239 | 240 | with open(local_dir + "info.txt", "w") as info_f: 241 | info_f.write("basin_atom_local_index: %d\n" % middle_at_i) 242 | 243 | print("R%d/%d finished, total time: %.2fs" % (mpi_rank, mpi_size, (time.time() - time0))) 244 | 245 | 246 | if __name__ == "__main__": 247 | main() 248 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/overlap_from_wfns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import sys 4 | import time 5 | 6 | import numpy as np 7 | from mpi4py import MPI 8 | 9 | import cp2k_spm_tools.cp2k_grid_orbitals as cgo 10 | from cp2k_spm_tools import common 11 | 12 | 13 | def main(): 14 | comm = MPI.COMM_WORLD 15 | mpi_rank = comm.Get_rank() 16 | mpi_size = comm.Get_size() 17 | 18 | parser = argparse.ArgumentParser(description="Puts the CP2K orbitals on grid and calculates scalar products.") 19 | # ---------------------------------- 20 | # First system: molecule on slab 21 | parser.add_argument( 22 | "--cp2k_input_file1", metavar="FILENAME", required=True, help="CP2K input of the SCF calculation." 23 | ) 24 | parser.add_argument( 25 | "--basis_set_file1", metavar="FILENAME", required=True, help="File containing the used basis sets." 26 | ) 27 | parser.add_argument("--xyz_file1", metavar="FILENAME", required=True, help=".xyz file containing the geometry.") 28 | parser.add_argument( 29 | "--wfn_file1", metavar="FILENAME", required=True, help="Restart file containing the final wavefunction." 30 | ) 31 | parser.add_argument( 32 | "--emin1", type=float, metavar="E", required=True, help="Lowest energy value for selecting orbitals (eV)." 33 | ) 34 | parser.add_argument( 35 | "--emax1", type=float, metavar="E", required=True, help="Highest energy value for selecting orbitals (eV)." 36 | ) 37 | 38 | # ---------------------------------- 39 | # Second system: only molecule 40 | parser.add_argument( 41 | "--cp2k_input_file2", metavar="FILENAME", required=True, help="CP2K input of the SCF calculation." 42 | ) 43 | parser.add_argument( 44 | "--basis_set_file2", metavar="FILENAME", required=True, help="File containing the used basis sets." 45 | ) 46 | parser.add_argument("--xyz_file2", metavar="FILENAME", required=True, help=".xyz file containing the geometry.") 47 | parser.add_argument( 48 | "--wfn_file2", metavar="FILENAME", required=True, help="Restart file containing the final wavefunction." 49 | ) 50 | parser.add_argument("--nhomo2", type=int, metavar="N", required=True, help="Number of homo orbitals.") 51 | parser.add_argument("--nlumo2", type=int, metavar="N", required=True, help="Number of lumo orbitals.") 52 | # ---------------------------------- 53 | 54 | parser.add_argument("--output_file", metavar="FILENAME", required=True, help="File, where to save the output") 55 | parser.add_argument( 56 | "--eval_region", type=str, nargs=6, metavar="X", required=True, help=common.eval_region_description 57 | ) 58 | parser.add_argument("--dx", type=float, metavar="DX", required=True, help="Spatial step for the grid (angstroms).") 59 | parser.add_argument( 60 | "--eval_cutoff", 61 | type=float, 62 | metavar="D", 63 | default=14.0, 64 | help=("Size of the region around the atom where each orbital is evaluated (only used for 'G' region)."), 65 | ) 66 | 67 | time0 = time.time() 68 | 69 | ### ------------------------------------------------------ 70 | ### Parse args for only one rank to suppress duplicate stdio 71 | ### ------------------------------------------------------ 72 | 73 | args = None 74 | args_success = False 75 | try: 76 | if mpi_rank == 0: 77 | args = parser.parse_args() 78 | args_success = True 79 | finally: 80 | args_success = comm.bcast(args_success, root=0) 81 | 82 | if not args_success: 83 | print(mpi_rank, "exiting") 84 | exit(0) 85 | 86 | args = comm.bcast(args, root=0) 87 | 88 | ### ------------------------------------------------------ 89 | ### Evaluate the same molecule orbitals on all mpi ranks 90 | ### ------------------------------------------------------ 91 | 92 | mol_grid_orb = cgo.Cp2kGridOrbitals(0, 1, single_precision=False) 93 | mol_grid_orb.read_cp2k_input(args.cp2k_input_file2) 94 | mol_grid_orb.read_xyz(args.xyz_file2) 95 | mol_grid_orb.read_basis_functions(args.basis_set_file2) 96 | mol_grid_orb.load_restart_wfn_file(args.wfn_file2, n_occ=args.nhomo2, n_virt=args.nlumo2) 97 | 98 | print("R%d/%d: loaded G2, %.2fs" % (mpi_rank, mpi_size, (time.time() - time0))) 99 | sys.stdout.flush() 100 | time1 = time.time() 101 | 102 | eval_reg = common.parse_eval_region_input(args.eval_region, mol_grid_orb.ase_atoms, mol_grid_orb.cell) 103 | 104 | mol_grid_orb.calc_morbs_in_region( 105 | args.dx, 106 | x_eval_region=eval_reg[0], 107 | y_eval_region=eval_reg[1], 108 | z_eval_region=eval_reg[2], 109 | reserve_extrap=0.0, 110 | eval_cutoff=args.eval_cutoff, 111 | ) 112 | 113 | print("R%d/%d: evaluated G2, %.2fs" % (mpi_rank, mpi_size, (time.time() - time1))) 114 | sys.stdout.flush() 115 | time1 = time.time() 116 | 117 | ### ------------------------------------------------------ 118 | ### Evaluate slab system orbitals 119 | ### ------------------------------------------------------ 120 | 121 | slab_grid_orb = cgo.Cp2kGridOrbitals(mpi_rank, mpi_size, mpi_comm=comm, single_precision=False) 122 | slab_grid_orb.read_cp2k_input(args.cp2k_input_file1) 123 | slab_grid_orb.read_xyz(args.xyz_file1) 124 | slab_grid_orb.read_basis_functions(args.basis_set_file1) 125 | slab_grid_orb.load_restart_wfn_file(args.wfn_file1, emin=args.emin1 - 0.05, emax=args.emax1 + 0.05) 126 | 127 | print("R%d/%d: loaded G1, %.2fs" % (mpi_rank, mpi_size, (time.time() - time1))) 128 | sys.stdout.flush() 129 | time1 = time.time() 130 | 131 | slab_grid_orb.calc_morbs_in_region( 132 | args.dx, 133 | x_eval_region=eval_reg[0], 134 | y_eval_region=eval_reg[1], 135 | z_eval_region=eval_reg[2], 136 | reserve_extrap=0.0, 137 | eval_cutoff=args.eval_cutoff, 138 | ) 139 | 140 | print("R%d/%d: evaluated G1, %.2fs" % (mpi_rank, mpi_size, (time.time() - time1))) 141 | sys.stdout.flush() 142 | time1 = time.time() 143 | 144 | ### ------------------------------------------------------ 145 | ### calculate overlap 146 | ### ------------------------------------------------------ 147 | 148 | ve = np.prod(slab_grid_orb.dv) 149 | 150 | output_dict = {} 151 | 152 | for i_spin_slab in range(slab_grid_orb.nspin): 153 | for i_spin_mol in range(mol_grid_orb.nspin): 154 | # The gas phase orbitals can be expressed in the basis of slab orbitals 155 | # |phi_i> = \sum_j |psi_j> 156 | # And the modulus is 157 | # = \sum_j ||^2 = 1 158 | # Therefore, the matrix of 159 | # ||^2 160 | # is a good description of the amount of gas phase orbitals in slab orbitals 161 | # (positive; integral between j1 to j2 gives the amount of |phi_i> in that region) 162 | overlap_matrix = ( 163 | np.einsum("iklm, jklm", slab_grid_orb.morb_grids[i_spin_slab], mol_grid_orb.morb_grids[i_spin_mol]) * ve 164 | ) ** 2 165 | 166 | print("R%d/%d: overlap finished, %.2fs" % (mpi_rank, mpi_size, (time.time() - time1))) 167 | sys.stdout.flush() 168 | 169 | overlap_matrix_rav = overlap_matrix.ravel() 170 | sendcounts = np.array(comm.gather(len(overlap_matrix_rav), 0)) 171 | 172 | if mpi_rank == 0: 173 | print("sendcounts: {}, total: {}".format(sendcounts, sum(sendcounts))) 174 | recvbuf = np.empty(sum(sendcounts), dtype=float) 175 | else: 176 | recvbuf = None 177 | 178 | comm.Gatherv(sendbuf=overlap_matrix_rav, recvbuf=[recvbuf, sendcounts], root=0) 179 | 180 | if mpi_rank == 0: 181 | overlap_matrix_collected = recvbuf.reshape( 182 | ( 183 | len(slab_grid_orb.global_morb_energies[i_spin_slab]), 184 | len(mol_grid_orb.global_morb_energies[i_spin_mol]), 185 | ) 186 | ) 187 | output_dict["overlap_matrix_s{}s{}".format(i_spin_slab, i_spin_mol)] = overlap_matrix_collected 188 | 189 | if mpi_rank == 0: 190 | output_dict["metadata"] = [ 191 | { 192 | "nspin_g1": slab_grid_orb.nspin, 193 | "nspin_g2": mol_grid_orb.nspin, 194 | "homo_i_g2": mol_grid_orb.i_homo_loc, 195 | } 196 | ] 197 | 198 | for i_spin_slab in range(slab_grid_orb.nspin): 199 | output_dict["energies_g1_s{}".format(i_spin_slab)] = slab_grid_orb.global_morb_energies[i_spin_slab] 200 | for i_spin_mol in range(mol_grid_orb.nspin): 201 | output_dict["energies_g2_s{}".format(i_spin_mol)] = mol_grid_orb.global_morb_energies[i_spin_mol] 202 | # NB: Count starts from 1! 203 | output_dict["orb_indexes_g2_s{}".format(i_spin_mol)] = mol_grid_orb.cwf.global_morb_indexes[i_spin_mol] 204 | 205 | np.savez(args.output_file, **output_dict) 206 | print("Finish! Total time: %.2fs" % (time.time() - time0)) 207 | 208 | 209 | if __name__ == "__main__": 210 | main() 211 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cli/stm_sts_plotter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import os 4 | 5 | import matplotlib 6 | import numpy as np 7 | 8 | matplotlib.use("Agg") 9 | import matplotlib.pyplot as plt 10 | from mpl_toolkits.axes_grid1 import make_axes_locatable 11 | 12 | from cp2k_spm_tools import igor 13 | 14 | FIG_Y = 4.0 15 | TITLE_FONT_SIZE = 14 16 | 17 | 18 | def make_plot( 19 | fig, 20 | ax, 21 | data, 22 | extent, 23 | title=None, 24 | title_size=None, 25 | center0=False, 26 | vmin=None, 27 | vmax=None, 28 | cmap="gist_heat", 29 | noadd=False, 30 | ): 31 | if center0: 32 | data_amax = np.max(np.abs(data)) 33 | im = ax.imshow( 34 | data.T, 35 | origin="lower", 36 | cmap=cmap, 37 | interpolation="bicubic", 38 | extent=extent, 39 | vmin=-data_amax, 40 | vmax=data_amax, 41 | ) 42 | else: 43 | im = ax.imshow( 44 | data.T, 45 | origin="lower", 46 | cmap=cmap, 47 | interpolation="bicubic", 48 | extent=extent, 49 | vmin=vmin, 50 | vmax=vmax, 51 | ) 52 | 53 | if noadd: 54 | ax.set_xticks([]) 55 | ax.set_yticks([]) 56 | else: 57 | ax.set_xlabel(r"x ($\AA$)") 58 | ax.set_ylabel(r"y ($\AA$)") 59 | divider = make_axes_locatable(ax) 60 | cax = divider.append_axes("right", size="5%", pad=0.05) 61 | cb = fig.colorbar(im, cax=cax) 62 | cb.formatter.set_powerlimits((-2, 2)) 63 | cb.update_ticks() 64 | ax.set_title(title, loc="left") 65 | if title_size: 66 | ax.title.set_fontsize(title_size) 67 | ax.axis("scaled") 68 | 69 | 70 | def make_series_label(info, i_spin=None): 71 | if info["type"] == "const-height sts": 72 | label = "p$_{tip}$=%.1f ch-sts fwhm=%.2f h=%.1f" % ( 73 | info["p_tip_ratio"], 74 | info["fwhm"], 75 | info["height"], 76 | ) 77 | elif info["type"] == "const-height stm": 78 | label = "p$_{tip}$=%.1f ch-stm fwhm=%.2f h=%.1f" % ( 79 | info["p_tip_ratio"], 80 | info["fwhm"], 81 | info["height"], 82 | ) 83 | elif info["type"] == "const-isovalue sts": 84 | label = "p$_{tip}$=%.1f cc-sts fwhm=%.2f iv=%.0e" % ( 85 | info["p_tip_ratio"], 86 | info["fwhm"], 87 | info["isovalue"], 88 | ) 89 | elif info["type"] == "const-isovalue stm": 90 | label = "p$_{tip}$=%.1f cc-stm fwhm=%.2f iv=%.0e" % ( 91 | info["p_tip_ratio"], 92 | info["fwhm"], 93 | info["isovalue"], 94 | ) 95 | 96 | elif info["type"] == "const-height orbital": 97 | label = "s%d ch-orb h=%.1f" % (i_spin, info["height"]) 98 | elif info["type"] == "const-height orbital sts": 99 | label = "s%d p$_{tip}$=%.1f ch-orb-sts h=%.1f" % ( 100 | i_spin, 101 | info["p_tip_ratio"], 102 | info["height"], 103 | ) 104 | elif info["type"] == "const-isovalue orbital sts": 105 | label = "s%d p$_{tip}$=%.1f cc-orb-sts iv=%.0e" % ( 106 | i_spin, 107 | info["p_tip_ratio"], 108 | info["isovalue"], 109 | ) 110 | else: 111 | print("No support for: " + str(info)) 112 | 113 | return label 114 | 115 | 116 | def make_orb_label(index, homo_index): 117 | i_rel_homo = index - homo_index 118 | 119 | if i_rel_homo < 0: 120 | hl_label = "HOMO%+d" % i_rel_homo 121 | elif i_rel_homo == 0: 122 | hl_label = "HOMO" 123 | elif i_rel_homo == 1: 124 | hl_label = "LUMO" 125 | else: 126 | hl_label = "LUMO%+d" % (i_rel_homo - 1) 127 | 128 | return "MO %d, " % index + hl_label 129 | 130 | 131 | def plot_series_and_export_igor(general_info, info, data, make_plot_args, plot_dir, itx_dir): 132 | e_arr = general_info["energies"] 133 | x_arr = general_info["x_arr"] * 0.529177 134 | y_arr = general_info["y_arr"] * 0.529177 135 | 136 | orb_indexes = general_info.get("orb_indexes") 137 | homo = general_info.get("homo") 138 | spin = general_info.get("spin") 139 | 140 | extent = [np.min(x_arr), np.max(x_arr), np.min(y_arr), np.max(y_arr)] 141 | 142 | figure_xy_ratio = (np.max(x_arr) - np.min(x_arr)) / (np.max(y_arr) - np.min(y_arr)) 143 | 144 | series_label = make_series_label(info, spin) 145 | 146 | for i_e, energy in enumerate(e_arr): 147 | # --------------------------------------------------- 148 | # Build labels, title and file name 149 | mo_label = None 150 | if orb_indexes is not None: 151 | mo_label = make_orb_label(orb_indexes[i_e], homo) 152 | 153 | title = "%s\n" % series_label 154 | if mo_label is not None: 155 | title += mo_label + " " 156 | title += "E=%.2f eV" % energy 157 | 158 | plot_name = ( 159 | series_label.lower() 160 | .replace(" ", "_") 161 | .replace("=", "") 162 | .replace("^", "") 163 | .replace(",", "") 164 | .replace("$_{tip}$", "") 165 | ) 166 | if mo_label is not None: 167 | plot_name += "_mo%03d_e%.2f" % (orb_indexes[i_e], energy) 168 | else: 169 | plot_name += "_%03d_e%.2f" % (i_e, energy) 170 | 171 | # --------------------------------------------------- 172 | # Make the plot 173 | fig = plt.figure(figsize=(FIG_Y * figure_xy_ratio, FIG_Y)) 174 | 175 | ax = plt.gca() 176 | make_plot( 177 | fig, 178 | ax, 179 | data[i_e, :, :], 180 | extent, 181 | title=title, 182 | title_size=TITLE_FONT_SIZE, 183 | noadd=False, 184 | **make_plot_args, 185 | ) 186 | 187 | plt.savefig(plot_dir + "/" + plot_name + ".png", dpi=200, bbox_inches="tight") 188 | plt.close() 189 | 190 | # --------------------------------------------------- 191 | # export IGOR format 192 | igorwave = igor.Wave2d( 193 | data=data[i_e, :, :], 194 | xmin=extent[0], 195 | xmax=extent[1], 196 | xlabel="x [Angstroms]", 197 | ymin=extent[2], 198 | ymax=extent[3], 199 | ylabel="y [Angstroms]", 200 | ) 201 | igorwave.write(itx_dir + "/" + plot_name + ".itx") 202 | # --------------------------------------------------- 203 | 204 | 205 | def plot_all_series(general_info, series_info, series_data, plot_dir, itx_dir): 206 | for info, data in zip(series_info, series_data): 207 | make_plot_args = {"cmap": "gist_heat", "center0": False} 208 | 209 | if info["type"] == "const-height sts": 210 | make_plot_args["cmap"] = "seismic" 211 | elif info["type"] == "const-height orbital sts": 212 | make_plot_args["cmap"] = "seismic" 213 | elif info["type"] == "const-isovalue orbital sts": 214 | make_plot_args["cmap"] = "seismic" 215 | elif info["type"] == "const-height orbital": 216 | make_plot_args["cmap"] = "seismic" 217 | make_plot_args["center0"] = True 218 | 219 | print("Plotting series: " + str(info)) 220 | plot_series_and_export_igor(general_info, info, data, make_plot_args, plot_dir, itx_dir) 221 | 222 | 223 | def main(): 224 | parser = argparse.ArgumentParser(description="Makes images from the STM .npz files.") 225 | 226 | ### ---------------------------------------------------------------------- 227 | ### Input and output files 228 | parser.add_argument("--stm_npz", metavar="FILENAME", default=None, help="File containing STM data.") 229 | parser.add_argument("--orb_npz", metavar="FILENAME", default=None, help="File containing ORB data.") 230 | parser.add_argument("--output_dir", metavar="DIR", default="./", help="Output directory.") 231 | ### ---------------------------------------------------------------------- 232 | 233 | args = parser.parse_args() 234 | 235 | ### ---------------------------------------------------------------------- 236 | ### STM.NPZ 237 | ### ---------------------------------------------------------------------- 238 | 239 | if args.stm_npz is not None: 240 | stm_dir = args.output_dir + "./stm" 241 | if not os.path.exists(stm_dir): 242 | os.makedirs(stm_dir) 243 | 244 | stm_itx_dir = args.output_dir + "./stm_itx" 245 | if not os.path.exists(stm_itx_dir): 246 | os.makedirs(stm_itx_dir) 247 | 248 | loaded_data = np.load(args.stm_npz, allow_pickle=True) 249 | 250 | stm_general_info = loaded_data["stm_general_info"][()] 251 | stm_series_info = loaded_data["stm_series_info"] 252 | stm_series_data = loaded_data["stm_series_data"] 253 | 254 | plot_all_series(stm_general_info, stm_series_info, stm_series_data, stm_dir, stm_itx_dir) 255 | 256 | ### ---------------------------------------------------------------------- 257 | ### ORB.NPZ 258 | ### ---------------------------------------------------------------------- 259 | 260 | if args.orb_npz is not None: 261 | orb_dir = args.output_dir + "./orb" 262 | if not os.path.exists(orb_dir): 263 | os.makedirs(orb_dir) 264 | 265 | orb_itx_dir = args.output_dir + "./orb_itx" 266 | if not os.path.exists(orb_itx_dir): 267 | os.makedirs(orb_itx_dir) 268 | 269 | loaded_data = np.load(args.orb_npz, allow_pickle=True) 270 | 271 | s0_orb_general_info = loaded_data["s0_orb_general_info"][()] 272 | s0_orb_series_info = loaded_data["s0_orb_series_info"] 273 | s0_orb_series_data = loaded_data["s0_orb_series_data"] 274 | 275 | plot_all_series( 276 | s0_orb_general_info, 277 | s0_orb_series_info, 278 | s0_orb_series_data, 279 | orb_dir, 280 | orb_itx_dir, 281 | ) 282 | 283 | if "s1_orb_general_info" in loaded_data.files: 284 | s1_orb_general_info = loaded_data["s1_orb_general_info"][()] 285 | s1_orb_series_info = loaded_data["s1_orb_series_info"] 286 | s1_orb_series_data = loaded_data["s1_orb_series_data"] 287 | 288 | plot_all_series( 289 | s1_orb_general_info, 290 | s1_orb_series_info, 291 | s1_orb_series_data, 292 | orb_dir, 293 | orb_itx_dir, 294 | ) 295 | 296 | 297 | if __name__ == "__main__": 298 | main() 299 | -------------------------------------------------------------------------------- /cp2k_spm_tools/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Useful tools for various situations 3 | """ 4 | 5 | import sys 6 | 7 | import numpy as np 8 | import scipy 9 | 10 | ang_2_bohr = 1.0 / 0.52917721067 11 | hart_2_ev = 27.21138602 12 | 13 | 14 | def is_number(s): 15 | try: 16 | float(s) 17 | return True 18 | except ValueError: 19 | return False 20 | 21 | 22 | eval_region_description = ( 23 | "Specify evaluation region limits [xmin xmax ymin ymax zmin zmax] (ang) as a string: " 24 | "'G' corresponds to global cell limit (also enables PBC if both of pair are 'G'); " 25 | "a number specifies absolute position wrt cell zero; p/n and number (e.g. 'p2.5') " 26 | "specifies distance [ang] from furthest-extending atom in positive (p) or negative (n) " 27 | "direction. Number with _element ('p2.5_C') correspondingly from furthest atom of " 28 | "elem. If xmin=xmax (within 1e-4), then only a plane is assumed." 29 | ) 30 | 31 | 32 | def parse_eval_region_input(eval_reg_inp, ase_atoms, cell): 33 | eval_regions_inp = [eval_reg_inp[0:2], eval_reg_inp[2:4], eval_reg_inp[4:6]] 34 | eval_regions = [[0, 0], [0, 0], [0, 0]] 35 | 36 | for i in range(3): 37 | if eval_regions_inp[i] == ["G", "G"]: 38 | eval_regions[i] = None 39 | continue 40 | for j in range(2): 41 | reg_str = eval_regions_inp[i][j] 42 | 43 | has_chem_el = False 44 | 45 | if "_" in reg_str: 46 | elem = reg_str.split("_")[1] 47 | reg_str = reg_str.split("_")[0] 48 | sel_positions = ase_atoms.positions[np.array(ase_atoms.get_chemical_symbols()) == elem] 49 | if len(sel_positions) == 0: 50 | print("Error: No element %s found. Exiting." % elem) 51 | sys.exit(1) 52 | has_chem_el = True 53 | else: 54 | sel_positions = ase_atoms.positions 55 | 56 | if reg_str == "G": 57 | eval_regions[i][j] = 0.0 if j == 0 else cell[i] 58 | elif is_number(reg_str): 59 | if has_chem_el: 60 | print("Unrecognized option ", eval_regions_inp[i][j]) 61 | sys.exit(1) 62 | eval_regions[i][j] = float(reg_str) 63 | 64 | else: 65 | ref_at_pos = reg_str[0] 66 | ref_shift_str = reg_str[1:] 67 | 68 | if ref_at_pos != "p" and ref_at_pos != "n": 69 | print("Error:", reg_str, "needs to start with a 'p' or 'n'") 70 | sys.exit(1) 71 | if not is_number(ref_shift_str): 72 | print("Error:", ref_shift_str, "needs to be a number") 73 | sys.exit(1) 74 | ref_shift_val = float(ref_shift_str) 75 | 76 | eval_regions[i][j] = ( 77 | np.min(sel_positions[:, i]) + ref_shift_val 78 | if ref_at_pos == "n" 79 | else np.max(sel_positions[:, i]) + ref_shift_val 80 | ) 81 | 82 | if np.abs(eval_regions[i][0] - eval_regions[i][1]) < 1e-3: 83 | eval_regions[i][0] = eval_regions[i][1] 84 | return eval_regions 85 | 86 | 87 | def resize_2d_arr_with_interpolation(array, new_shape): 88 | x_arr = np.linspace(0, 1, array.shape[0]) 89 | y_arr = np.linspace(0, 1, array.shape[1]) 90 | rgi = scipy.interpolate.RegularGridInterpolator(points=[x_arr, y_arr], values=array) 91 | 92 | x_arr_new = np.linspace(0, 1, new_shape[0]) 93 | y_arr_new = np.linspace(0, 1, new_shape[1]) 94 | x_coords = np.repeat(x_arr_new, len(y_arr_new)) 95 | y_coords = np.tile(y_arr_new, len(x_arr_new)) 96 | 97 | return rgi(np.array([x_coords, y_coords]).T).reshape(new_shape) 98 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cp2k_ftsts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tools to perform FT-STS analysis on orbitals evaluated on grid 3 | """ 4 | 5 | import numpy as np 6 | 7 | ang_2_bohr = 1.0 / 0.52917721067 8 | hart_2_ev = 27.21138602 9 | 10 | 11 | class FTSTS: 12 | """ 13 | Class to perform FT-STS analysis on gridded orbitals 14 | """ 15 | 16 | def __init__(self, cp2k_grid_orb): 17 | """ 18 | Convert all lengths from [au] to [ang] 19 | """ 20 | 21 | self.cp2k_grid_orb = cp2k_grid_orb 22 | self.nspin = cp2k_grid_orb.nspin 23 | self.mpi_rank = cp2k_grid_orb.mpi_rank 24 | self.mpi_size = cp2k_grid_orb.mpi_size 25 | self.cell_n = cp2k_grid_orb.eval_cell_n 26 | self.dv = cp2k_grid_orb.dv / ang_2_bohr 27 | self.origin = cp2k_grid_orb.origin / ang_2_bohr 28 | 29 | self.morbs_1d = None 30 | self.morb_fts = None 31 | self.k_arr = None 32 | self.dk = None 33 | 34 | self.ldos = None 35 | self.ftldos = None 36 | self.e_arr = None 37 | 38 | self.ldos_extent = None 39 | self.ftldos_extent = None 40 | 41 | def remove_row_average(self, ldos): 42 | ldos_no_avg = np.copy(ldos) 43 | for i in range(ldos.shape[1]): 44 | ldos_no_avg[:, i] -= np.mean(ldos[:, i]) 45 | return ldos_no_avg 46 | 47 | def add_padding(self, ldos, amount_factor): 48 | # assumes that first index is space 49 | pad_n = int(amount_factor * ldos.shape[0]) 50 | if pad_n == 0: 51 | return ldos 52 | padded_ldos = np.zeros((np.shape(ldos)[0] + 2 * pad_n, np.shape(ldos)[1])) 53 | padded_ldos[pad_n:-pad_n] = ldos 54 | return padded_ldos 55 | 56 | def crop_padding(self, ldos, tol=1e-6): 57 | # assumes that first index is space 58 | max_for_every_x = np.max(ldos, axis=1) 59 | i_crop_1 = np.argmax(max_for_every_x > tol) 60 | i_crop_2 = len(max_for_every_x) - np.argmax(max_for_every_x[::-1] > tol) 61 | 62 | return ldos[i_crop_1:i_crop_2], i_crop_1, i_crop_2 63 | 64 | def crop_edges(self, ldos, dist=10.0): 65 | crop_index = int(np.round(dist / self.dv[0])) 66 | return ldos[crop_index:-crop_index] 67 | 68 | def fourier_transform(self, ldos): 69 | ft = np.fft.rfft(ldos, axis=0) 70 | aft = np.abs(ft) 71 | 72 | # Corresponding k points 73 | k_arr = 2 * np.pi * np.fft.rfftfreq(len(ldos[:, 0]), self.dv[0]) 74 | # Note: Since we took the FT of the charge density, the wave vectors are 75 | # twice the ones of the underlying wave function. 76 | # k_arr = k_arr / 2 77 | 78 | # Brillouin zone boundary [1/angstroms] 79 | # bzboundary = np.pi / lattice_param 80 | # bzb_index = int(np.round(bzboundary/dk))+1 81 | 82 | dk = k_arr[1] 83 | 84 | return k_arr, aft, dk 85 | 86 | def gaussian(self, x, fwhm): 87 | sigma = fwhm / 2.3548 88 | return np.exp(-(x**2) / (2 * sigma**2)) / (sigma * np.sqrt(2 * np.pi)) 89 | 90 | def project_orbitals_1d(self, axis=0, gauss_pos=None, gauss_fwhm=2.0): 91 | self.morbs_1d = [] 92 | 93 | if axis != 0: 94 | dv = np.swapaxes(self.dv, axis, 0) 95 | else: 96 | dv = self.dv 97 | 98 | for ispin in range(self.nspin): 99 | self.morbs_1d.append(np.zeros((self.cell_n[0], len(self.cp2k_grid_orb.morb_grids[ispin])))) 100 | for i_mo, morb_grid in enumerate(self.cp2k_grid_orb.morb_grids[ispin]): 101 | if axis != 0: 102 | morb_grid = np.swapaxes(morb_grid, axis, 0) 103 | if gauss_pos is None: 104 | morb_1d = np.mean(morb_grid**2, axis=(1, 2)) 105 | else: 106 | ny = morb_grid.shape[1] 107 | y_arr = np.linspace(-dv[1] * ny / 2.0, dv[1] * ny / 2.0, ny) 108 | y_gaussian = self.gaussian(y_arr - gauss_pos, gauss_fwhm) 109 | morb_plane = np.mean(morb_grid**2, axis=2) 110 | morb_1d = np.dot(morb_plane, y_gaussian) 111 | 112 | self.morbs_1d[ispin][:, i_mo] = morb_1d 113 | 114 | def take_fts(self, crop_padding=True, crop_edges=0.0, remove_row_avg=True, padding=1.0): 115 | self.morb_fts = [] 116 | for ispin in range(self.nspin): 117 | tmp_morbs = self.morbs_1d[ispin] 118 | if crop_padding: 119 | tmp_morbs, i_crop_1, i_crop_2 = self.crop_padding(tmp_morbs) 120 | if crop_edges > 0.0: 121 | tmp_morbs = self.crop_edges(tmp_morbs, dist=crop_edges) 122 | if remove_row_avg: 123 | tmp_morbs = self.remove_row_average(tmp_morbs) 124 | if padding > 0.0: 125 | tmp_morbs = self.add_padding(tmp_morbs, padding) 126 | self.k_arr, m_fts, self.dk = self.fourier_transform(tmp_morbs) 127 | self.morb_fts.append(m_fts) 128 | borders = i_crop_1 * self.dv[0] + crop_edges, i_crop_2 * self.dv[0] - crop_edges 129 | return borders 130 | 131 | def make_ftldos(self, emin, emax, de, fwhm): 132 | self.e_arr = np.arange(emin, emax + de / 2, de) 133 | 134 | self.ldos = np.zeros((self.cell_n[0], len(self.e_arr))) 135 | self.ftldos = np.zeros((len(self.k_arr), len(self.e_arr))) 136 | 137 | self.ldos_extent = [0.0, self.cell_n[0] * self.dv[0], emin, emax] 138 | self.ftldos_extent = [0.0, self.k_arr[-1], emin, emax] 139 | 140 | for ispin in range(self.nspin): 141 | for i_mo, en_mo in enumerate(self.cp2k_grid_orb.morb_energies[ispin]): 142 | # Produce LDOS 143 | self.ldos += np.outer(self.morbs_1d[ispin][:, i_mo], self.gaussian(self.e_arr - en_mo, fwhm)) 144 | # Produce FTLDOS 145 | self.ftldos += np.outer(self.morb_fts[ispin][:, i_mo], self.gaussian(self.e_arr - en_mo, fwhm)) 146 | 147 | def get_ftldos_bz(self, nbz, lattice_param): 148 | """ 149 | Return part of previously calculated FTLDOS, which corresponds 150 | to the selected number of BZs (nbz) for specified lattice parameter (ang). 151 | """ 152 | # Brillouin zone boundary [1/angstroms] 153 | bzboundary = np.pi / lattice_param 154 | nbzb_index = int(np.round(nbz * bzboundary / self.dk)) + 1 155 | 156 | return self.ftldos[:nbzb_index, :], [0.0, nbz * bzboundary, self.ftldos_extent[2], self.ftldos_extent[3]] 157 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cp2k_overlap_matrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import scipy.io 4 | 5 | ang_2_bohr = 1.0 / 0.52917721067 6 | hart_2_ev = 27.21138602 7 | 8 | 9 | class Cp2kOverlapMatrix: 10 | """ 11 | Class to deal with the CP2K overlap matrix 12 | """ 13 | 14 | def __init__(self): 15 | self.sparse_mat = None 16 | 17 | def read_ascii_csr(self, file_name, n_basis_f): 18 | # might make more sense to store in dense format... 19 | 20 | csr_txt = np.loadtxt(file_name) 21 | 22 | sparse_mat = scipy.sparse.csr_matrix( 23 | (csr_txt[:, 2], (csr_txt[:, 0] - 1, csr_txt[:, 1] - 1)), shape=(n_basis_f, n_basis_f) 24 | ) 25 | 26 | # add also the lower triangular part 27 | sparse_mat += sparse_mat.T 28 | 29 | # diagonal got added by both triangular sides 30 | sparse_mat.setdiag(sparse_mat.diagonal() / 2) 31 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cp2k_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | CP2K utilities 3 | """ 4 | 5 | import re 6 | 7 | import numpy as np 8 | 9 | ang_2_bohr = 1.0 / 0.52917721067 10 | hart_2_ev = 27.21138602 11 | 12 | 13 | def is_float(s): 14 | try: 15 | float(s) 16 | return True 17 | except ValueError: 18 | return False 19 | 20 | 21 | def parse_cp2k_output(file_path): 22 | with open(file_path, "r") as f: 23 | lines = f.readlines() 24 | 25 | results = {} 26 | 27 | def add_res_list(name): 28 | if name not in results: 29 | results[name] = [] 30 | 31 | i_line = 0 32 | while i_line < len(lines): 33 | line = lines[i_line] 34 | 35 | # ---------------------------------------------------------------- 36 | # num electrons 37 | if "Number of electrons" in line: 38 | add_res_list("num_el") 39 | n = int(line.split()[-1]) 40 | spin_line = lines[i_line - 2] 41 | if "Spin " in spin_line: 42 | results["nspin"] = 2 43 | spin = int(spin_line.split()[-1]) - 1 44 | else: 45 | spin = 0 46 | results["nspin"] = 1 47 | 48 | if len(results["num_el"]) == spin: 49 | results["num_el"].append(n) 50 | else: 51 | print("Warning: something is wrong with num. el. parsing.") 52 | # ---------------------------------------------------------------- 53 | # Energy (overwrite so that we only have the last (converged) one) 54 | if "ENERGY| Total FORCE_EVAL ( QS ) energy (a.u.):" in line: 55 | results["energy"] = float(line.split()[-1]) * hart_2_ev 56 | # ---------------------------------------------------------------- 57 | # Occupied eigenvalues (normal SCF) 58 | if "Eigenvalues of the occupied" in line: 59 | add_res_list("evals") 60 | spin = int(line.split()[-1]) - 1 61 | results["evals"].append([]) 62 | i_line += 2 63 | while True: 64 | vals = lines[i_line].split() 65 | if len(vals) == 0 or not is_float(vals[0]): 66 | break 67 | else: 68 | results["evals"][spin] += [float(v) * hart_2_ev for v in vals] 69 | i_line += 1 70 | # ---------------------------------------------------------------- 71 | # Unoccupied eigenvalues (normal SCF) 72 | if "Lowest Eigenvalues of the unoccupied" in line: 73 | spin = int(line.split()[-1]) - 1 74 | i_line += 3 75 | while True: 76 | vals = lines[i_line].split() 77 | if len(vals) == 0 or not is_float(vals[0]): 78 | break 79 | else: 80 | results["evals"][spin] += [float(v) * hart_2_ev for v in vals] 81 | i_line += 1 82 | # ---------------------------------------------------------------- 83 | # GW output 84 | if "Sigx-vxc (eV)" in line and "E_GW (eV)" in line: 85 | add_res_list("mo") 86 | add_res_list("occ") 87 | add_res_list("gw_eval") 88 | add_res_list("g0w0_eval") 89 | add_res_list("g0w0_e_scf") 90 | 91 | i_line += 1 92 | 93 | gw_mo = [] 94 | gw_occ = [] 95 | gw_e_scf = [] 96 | gw_eval = [] 97 | 98 | while True: 99 | line_loc = lines[i_line] 100 | if "GW HOMO-LUMO gap" in line_loc: 101 | spin = 1 if "Beta" in line_loc else 0 102 | 103 | if len(results["mo"]) > spin: 104 | # we already have a set, overwrite with later iteration 105 | results["mo"][spin] = gw_mo 106 | results["occ"][spin] = gw_occ 107 | results["gw_eval"][spin] = gw_eval 108 | else: 109 | results["mo"].append(gw_mo) 110 | results["occ"].append(gw_occ) 111 | results["gw_eval"].append(gw_eval) 112 | results["g0w0_eval"].append(gw_eval) 113 | results["g0w0_e_scf"].append(gw_e_scf) 114 | 115 | break 116 | 117 | vals = line_loc.split() 118 | # header & example line: 119 | # Molecular orbital E_SCF (eV) Sigc (eV) Sigx-vxc (eV) E_GW (eV) 120 | # 1 ( occ ) -26.079 6.728 -10.116 -26.068 121 | if len(vals) == 8 and is_float(vals[0]): 122 | gw_mo.append(int(vals[0]) - 1) # start orb count from 0 123 | gw_occ.append(1 if vals[2] == "occ" else 0) 124 | gw_e_scf.append(float(vals[4])) 125 | gw_eval.append(float(vals[7])) 126 | i_line += 1 127 | # ---------------------------------------------------------------- 128 | # IC output 129 | if "E_n before ic corr" in line and "Delta E_ic" in line: 130 | add_res_list("mo") 131 | add_res_list("occ") 132 | add_res_list("ic_en") 133 | add_res_list("ic_delta") 134 | 135 | i_line += 1 136 | 137 | ic_mo = [] 138 | ic_occ = [] 139 | ic_en = [] 140 | ic_delta = [] 141 | 142 | while True: 143 | line_loc = lines[i_line] 144 | if "IC HOMO-LUMO gap" in line_loc: 145 | spin = 1 if "Beta" in line_loc else 0 146 | 147 | if len(results["mo"]) > spin: 148 | # we already have a set, overwrite with later iteration 149 | results["mo"][spin] = ic_mo 150 | results["occ"][spin] = ic_occ 151 | results["ic_en"][spin] = ic_en 152 | results["ic_delta"][spin] = ic_delta 153 | else: 154 | results["mo"].append(ic_mo) 155 | results["occ"].append(ic_occ) 156 | results["ic_en"].append(ic_en) 157 | results["ic_delta"].append(ic_delta) 158 | 159 | break 160 | 161 | vals = line_loc.split() 162 | # header & example line: 163 | # MO E_n before ic corr Delta E_ic E_n after ic corr 164 | # 70 ( occ ) -11.735 1.031 -10.705 165 | if len(vals) == 7 and is_float(vals[0]): 166 | ic_mo.append(int(vals[0]) - 1) # start orb count from 0 167 | ic_occ.append(1 if vals[2] == "occ" else 0) 168 | ic_en.append(float(vals[4])) 169 | ic_delta.append(float(vals[5])) 170 | i_line += 1 171 | 172 | # ---------------------------------------------------------------- 173 | i_line += 1 174 | 175 | # ---------------------------------------------------------------- 176 | # Determine HOMO indexes w.r.t. outputted eigenvalues 177 | results["homo"] = [] 178 | 179 | if "occ" in results: 180 | # In case of GW and IC, the MO count doesn't start from 0 181 | # so use the occupations 182 | for i_spin in range(results["nspin"]): 183 | results["homo"].append(results["occ"][i_spin].index(0) - 1) 184 | else: 185 | # In case of normal SCF, use the electron numbers 186 | for i_spin in range(results["nspin"]): 187 | if results["nspin"] == 1: 188 | results["homo"].append(int(results["num_el"][i_spin] / 2) - 1) 189 | else: 190 | results["homo"].append(results["num_el"][i_spin] - 1) 191 | # Also create 'mo' and 'occ' arrays 192 | add_res_list("occ") 193 | add_res_list("mo") 194 | occ = np.ones(len(results["evals"][i_spin])) 195 | occ[results["homo"][i_spin] + 1 :] = 0 196 | mo = np.arange(len(results["evals"][i_spin])) 197 | results["occ"].append(occ) 198 | results["mo"].append(mo) 199 | 200 | # ---------------------------------------------------------------- 201 | # convert "lowest level" to numpy arrays 202 | for key in results: 203 | if isinstance(results[key], list): 204 | for i in range(len(results[key])): 205 | if isinstance(results[key][i], list): 206 | results[key][i] = np.array(results[key][i]) 207 | 208 | return results 209 | 210 | 211 | def read_cp2k_pdos_file(file_path): 212 | header = open(file_path).readline() 213 | fermi = float(re.search("Fermi.* ([+-]?[0-9]*[.]?[0-9]+)", header).group(1)) 214 | try: 215 | kind = re.search(r"atomic kind.(\S+)", header).group(1) 216 | except: 217 | kind = None 218 | data = np.loadtxt(file_path) 219 | out_data = np.zeros((data.shape[0], 2)) 220 | out_data[:, 0] = (data[:, 1] - fermi) * hart_2_ev # energy 221 | 222 | out_data[:, 1] = np.sum(data[:, 3:], axis=1) # "contracted pdos" 223 | return out_data, kind 224 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cube.py: -------------------------------------------------------------------------------- 1 | """ 2 | Routines regarding gaussian cube files 3 | """ 4 | 5 | import ase 6 | import numpy as np 7 | 8 | ang_2_bohr = 1.0 / 0.52917721067 9 | hart_2_ev = 27.21138602 10 | 11 | 12 | class Cube: 13 | """ 14 | Gaussian cube 15 | """ 16 | 17 | def __init__( 18 | self, 19 | title=None, 20 | comment=None, 21 | ase_atoms=None, 22 | origin=np.array([0.0, 0.0, 0.0]), 23 | cell=None, 24 | cell_n=None, 25 | data=None, 26 | ): 27 | """ 28 | cell in [au] and (3x3) 29 | """ 30 | self.title = title 31 | self.comment = comment 32 | self.ase_atoms = ase_atoms 33 | self.origin = origin 34 | self.cell = cell 35 | self.data = data 36 | if data is not None: 37 | self.cell_n = data.shape 38 | else: 39 | self.cell_n = None 40 | 41 | def write_cube_file(self, filename): 42 | natoms = len(self.ase_atoms) 43 | 44 | f = open(filename, "w") 45 | 46 | if self.title is None: 47 | f.write(filename + "\n") 48 | else: 49 | f.write(self.title + "\n") 50 | 51 | if self.comment is None: 52 | f.write("cube\n") 53 | else: 54 | f.write(self.comment + "\n") 55 | 56 | dv_br = self.cell / self.data.shape 57 | 58 | f.write("%5d %12.6f %12.6f %12.6f\n" % (natoms, self.origin[0], self.origin[1], self.origin[2])) 59 | 60 | for i in range(3): 61 | f.write("%5d %12.6f %12.6f %12.6f\n" % (self.data.shape[i], dv_br[i][0], dv_br[i][1], dv_br[i][2])) 62 | 63 | if natoms > 0: 64 | positions = self.ase_atoms.positions * ang_2_bohr 65 | numbers = self.ase_atoms.get_atomic_numbers() 66 | for i in range(natoms): 67 | at_x, at_y, at_z = positions[i] 68 | f.write("%5d %12.6f %12.6f %12.6f %12.6f\n" % (numbers[i], 0.0, at_x, at_y, at_z)) 69 | 70 | self.data.tofile(f, sep="\n", format="%12.6e") 71 | 72 | f.close() 73 | 74 | def read_cube_file(self, filename, read_data=True): 75 | f = open(filename, "r") 76 | self.title = f.readline().rstrip() 77 | self.comment = f.readline().rstrip() 78 | 79 | line = f.readline().split() 80 | natoms = int(line[0]) 81 | 82 | section_headers = False 83 | if natoms < 0: 84 | # print("Warning: the cube %s has negative number of atoms") 85 | # print(" meaning that there could be multiple data sections") 86 | # print(" and each of those will have a header") 87 | natoms = -natoms 88 | section_headers = True 89 | 90 | self.origin = np.array(line[1:], dtype=float) 91 | 92 | self.cell_n = np.empty(3, dtype=int) 93 | self.cell = np.empty((3, 3)) 94 | for i in range(3): 95 | n, x, y, z = [float(s) for s in f.readline().split()] 96 | self.cell_n[i] = int(n) 97 | self.cell[i] = n * np.array([x, y, z]) 98 | 99 | numbers = np.empty(natoms, int) 100 | positions = np.empty((natoms, 3)) 101 | for i in range(natoms): 102 | line = f.readline().split() 103 | numbers[i] = int(line[0]) 104 | positions[i] = [float(s) for s in line[2:]] 105 | 106 | positions /= ang_2_bohr # convert from bohr to ang 107 | 108 | self.ase_atoms = ase.Atoms(numbers=numbers, positions=positions) 109 | 110 | if read_data: 111 | # Option 1: less memory usage but might be slower 112 | self.data = np.empty(self.cell_n[0] * self.cell_n[1] * self.cell_n[2], dtype=float) 113 | cursor = 0 114 | if section_headers: 115 | f.readline() 116 | 117 | for i, line in enumerate(f): 118 | ls = line.split() 119 | self.data[cursor : cursor + len(ls)] = ls 120 | cursor += len(ls) 121 | 122 | # Option 2: Takes much more memory (but may be faster) 123 | # data = np.array(f.read().split(), dtype=float) 124 | 125 | self.data = self.data.reshape(self.cell_n) 126 | 127 | f.close() 128 | 129 | def swapaxes(self, ax1, ax2): 130 | # Atomic positions: careful, the ase cell is not modified 131 | p = self.ase_atoms.positions 132 | p[:, ax1], p[:, ax2] = p[:, ax2], p[:, ax1].copy() 133 | 134 | self.origin[ax1], self.origin[ax2] = self.origin[ax2], self.origin[ax1].copy() 135 | 136 | self.cell[:, ax1], self.cell[:, ax2] = self.cell[:, ax2], self.cell[:, ax1].copy() 137 | self.cell[ax1, :], self.cell[ax2, :] = self.cell[ax2, :], self.cell[ax1, :].copy() 138 | 139 | self.data = np.swapaxes(self.data, ax1, ax2) 140 | 141 | self.cell_n = self.data.shape 142 | 143 | def get_plane_above_topmost_atom(self, height, axis=2): 144 | """ 145 | Returns the 2d plane above topmost atom in direction (default: z) 146 | height in [angstrom] 147 | """ 148 | topmost_atom_z = np.max(self.ase_atoms.positions[:, axis]) # Angstrom 149 | plane_z = (height + topmost_atom_z) * ang_2_bohr - self.origin[axis] 150 | 151 | plane_index = int(np.round(plane_z / self.cell[axis, axis] * np.shape(self.data)[axis] - 0.499)) 152 | 153 | if axis == 0: 154 | return self.data[plane_index, :, :] 155 | elif axis == 1: 156 | return self.data[:, plane_index, :] 157 | else: 158 | return self.data[:, :, plane_index] 159 | 160 | def get_x_index(self, x_ang): 161 | # returns the index value for a given x coordinate in angstrom 162 | return int(np.round((x_ang * ang_2_bohr - self.origin[0]) / self.cell[0, 0] * np.shape(self.data)[0])) 163 | 164 | def get_y_index(self, y_ang): 165 | # returns the index value for a given y coordinate in angstrom 166 | return int(np.round((y_ang * ang_2_bohr - self.origin[1]) / self.cell[1, 1] * np.shape(self.data)[1])) 167 | 168 | def get_z_index(self, z_ang): 169 | # returns the index value for a given z coordinate in angstrom 170 | return int(np.round((z_ang * ang_2_bohr - self.origin[2]) / self.cell[2, 2] * np.shape(self.data)[2])) 171 | 172 | @property 173 | def dv(self): 174 | """in [ang]""" 175 | return self.cell / self.cell_n / ang_2_bohr 176 | 177 | @property 178 | def dv_ang(self): 179 | """in [ang]""" 180 | return self.cell / self.cell_n / ang_2_bohr 181 | 182 | @property 183 | def dv_au(self): 184 | """in [au]""" 185 | return self.cell / self.cell_n 186 | 187 | @property 188 | def x_arr_au(self): 189 | """in [au]""" 190 | return np.arange(self.origin[0], self.origin[0] + (self.cell_n[0] - 0.5) * self.dv_au[0, 0], self.dv_au[0, 0]) 191 | 192 | @property 193 | def y_arr_au(self): 194 | """in [au]""" 195 | return np.arange(self.origin[1], self.origin[1] + (self.cell_n[1] - 0.5) * self.dv_au[1, 1], self.dv_au[1, 1]) 196 | 197 | @property 198 | def z_arr_au(self): 199 | """in [au]""" 200 | return np.arange(self.origin[2], self.origin[2] + (self.cell_n[2] - 0.5) * self.dv_au[2, 2], self.dv_au[2, 2]) 201 | 202 | @property 203 | def x_arr_ang(self): 204 | """in [ang]""" 205 | return self.x_arr_au / ang_2_bohr 206 | 207 | @property 208 | def y_arr_ang(self): 209 | """in [ang]""" 210 | return self.y_arr_au / ang_2_bohr 211 | 212 | @property 213 | def z_arr_ang(self): 214 | """in [ang]""" 215 | return self.z_arr_au / ang_2_bohr 216 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cube_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Routines regarding gaussian cube files 3 | """ 4 | 5 | import numpy as np 6 | 7 | ang_2_bohr = 1.0 / 0.52917721067 8 | hart_2_ev = 27.21138602 9 | 10 | 11 | def find_vacuum_level_naive(hartree_cube): 12 | """ 13 | In case of a slab, for accurate result, the dipole correction should be enabled. 14 | This case, however, is not handled. 15 | Only free molecules will have accurate result. 16 | 17 | Hartree cube in [Hrt] 18 | 19 | returns vacuum_level in [eV] 20 | """ 21 | 22 | # average the hartree pot in x and y directions and keep z 23 | hartree_1d = np.mean(hartree_cube.data, axis=(0, 1)) 24 | # take the maximum value of the averaged hartree potential 25 | # above the molecule (3 ang) until the end of the box 26 | z_ind_start = hartree_cube.get_z_index(np.max(hartree_cube.ase_atoms.positions[:, 2]) + 3.0) 27 | vacuum_level = np.max(hartree_1d[z_ind_start:]) 28 | 29 | return vacuum_level * hart_2_ev 30 | 31 | 32 | def add_artif_core_charge(charge_dens_cube): 33 | """ 34 | This function adds an artificial large core charge such that 35 | the Bader analysis is more robust. The total charge does not remain accurate. 36 | """ 37 | 38 | cell = np.diag(charge_dens_cube.cell) 39 | cell_n = charge_dens_cube.cell_n 40 | origin = charge_dens_cube.origin 41 | dv_au = cell / cell_n 42 | 43 | x = np.linspace(0.0, cell[0], cell_n[0]) + origin[0] 44 | y = np.linspace(0.0, cell[1], cell_n[1]) + origin[1] 45 | z = np.linspace(0.0, cell[2], cell_n[2]) + origin[2] 46 | 47 | for at in charge_dens_cube.ase_atoms: 48 | if at.number == 1: 49 | # No core density for H 50 | continue 51 | p = at.position * ang_2_bohr 52 | 53 | if ( 54 | p[0] < np.min(x) - 0.5 55 | or p[0] > np.max(x) + 0.5 56 | or p[1] < np.min(y) - 0.5 57 | or p[1] > np.max(y) + 0.5 58 | or p[2] < np.min(z) - 0.5 59 | or p[2] > np.max(z) + 0.5 60 | ): 61 | continue 62 | 63 | # Distance of the **Center** of each voxel to the atom 64 | x_grid, y_grid, z_grid = np.meshgrid( 65 | x - p[0] - dv_au[0] / 2, y - p[1] - dv_au[1] / 2, z - p[2] - dv_au[2] / 2, indexing="ij", copy=False 66 | ) 67 | r_grid = np.sqrt(x_grid**2 + y_grid**2 + z_grid**2) 68 | x_grid = None 69 | y_grid = None 70 | z_grid = None 71 | 72 | at.number - at.number % 8 # not exact... 73 | 74 | r_hat = 0.8 75 | h_hat = 20.0 76 | hat_func = h_hat - h_hat * r_grid / r_hat 77 | hat_func[r_grid > r_hat] = 0.0 78 | charge_dens_cube.data = np.maximum(hat_func, charge_dens_cube.data) 79 | -------------------------------------------------------------------------------- /cp2k_spm_tools/cycles.py: -------------------------------------------------------------------------------- 1 | import ase 2 | import ase.io 3 | import ase.neighborlist 4 | import ase.visualize 5 | import numpy as np 6 | 7 | 8 | def convert_neighbor_list(nl): 9 | new = {} 10 | n_vert = np.max(nl) + 1 11 | 12 | for i_v in range(n_vert): 13 | new[i_v] = [] 14 | 15 | for i_v, j_v in zip(nl[0], nl[1]): 16 | new[i_v].append(j_v) 17 | 18 | return new 19 | 20 | 21 | def find_cycles(i_vert, cnl, max_length, cur_path, passed_edges): 22 | if len(cur_path) - 1 == max_length: 23 | return [] 24 | 25 | acc_cycles = [] 26 | sort_cycles = [] 27 | 28 | neighbs = cnl[i_vert] 29 | 30 | # if we are connected to something that is not the end 31 | # then we crossed multiple cycles 32 | for n in neighbs: 33 | edge = (np.min([i_vert, n]), np.max([i_vert, n])) 34 | if edge not in passed_edges: 35 | if n in cur_path[1:]: 36 | # path went too close to itself... 37 | return [] 38 | 39 | # CHeck if we are at the end 40 | for n in neighbs: 41 | edge = (np.min([i_vert, n]), np.max([i_vert, n])) 42 | if edge not in passed_edges: 43 | if n == cur_path[0]: 44 | # found cycle 45 | return [cur_path] 46 | 47 | # Continue in all possible directions 48 | for n in neighbs: 49 | edge = (np.min([i_vert, n]), np.max([i_vert, n])) 50 | if edge not in passed_edges: 51 | cycs = find_cycles(n, cnl, max_length, cur_path + [n], passed_edges + [edge]) 52 | for cyc in cycs: 53 | sorted_cyc = tuple(sorted(cyc)) 54 | if sorted_cyc not in sort_cycles: 55 | sort_cycles.append(sorted_cyc) 56 | acc_cycles.append(cyc) 57 | 58 | return acc_cycles 59 | 60 | 61 | def dumb_cycle_detection(ase_atoms_no_h, max_length): 62 | neighbor_list = ase.neighborlist.neighbor_list("ij", ase_atoms_no_h, 2.0) 63 | 64 | cycles = [] 65 | sorted_cycles = [] 66 | n_vert = np.max(neighbor_list) + 1 67 | 68 | cnl = convert_neighbor_list(neighbor_list) 69 | 70 | for i_vert in range(n_vert): 71 | cycs = find_cycles(i_vert, cnl, max_length, [i_vert], []) 72 | for cyc in cycs: 73 | sorted_cyc = tuple(sorted(cyc)) 74 | if sorted_cyc not in sorted_cycles: 75 | sorted_cycles.append(sorted_cyc) 76 | cycles.append(cyc) 77 | 78 | return cycles 79 | 80 | 81 | def cycle_normal(cycle, h): 82 | cycle = np.array(cycle) 83 | centroid = np.mean(cycle, axis=0) 84 | 85 | points = cycle - centroid 86 | u, s, v = np.linalg.svd(points.T) 87 | normal = u[:, -1] 88 | normal /= np.linalg.norm(normal) 89 | if np.dot(normal, h * np.array([1, 1, 1])) < 0.0: 90 | normal *= -1.0 91 | return normal 92 | 93 | 94 | def find_cycle_centers_and_normals(ase_atoms_no_h, cycles, h=0.0): 95 | """ 96 | positive h means projection to z axis is positive and vice-versa 97 | """ 98 | if h == 0.0: 99 | h = 1.0 100 | normals = [] 101 | centers = [] 102 | for cyc in cycles: 103 | cyc_p = [] 104 | for i_at in cyc: 105 | cyc_p.append(ase_atoms_no_h[i_at].position) 106 | normals.append(cycle_normal(cyc_p, h)) 107 | centers.append(np.mean(cyc_p, axis=0)) 108 | return np.array(centers), np.array(normals) 109 | -------------------------------------------------------------------------------- /cp2k_spm_tools/hrstm_tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/cp2k_spm_tools/hrstm_tools/__init__.py -------------------------------------------------------------------------------- /cp2k_spm_tools/hrstm_tools/cp2k_grid_matrix.py: -------------------------------------------------------------------------------- 1 | # @author Hillebrand, Fabian 2 | # @date 2019 3 | 4 | import numpy as np 5 | 6 | from .interpolator import Interpolator 7 | 8 | ang2bohr = 1.88972612546 9 | ev2hartree = 0.03674930814 10 | 11 | 12 | class Cp2kGridMatrix: 13 | """ 14 | Class that provides a wrapper for Cp2kGridOrbtials such that they can be 15 | evaluated on an arbitrary grid using interpolation. Scaled derivatives are 16 | also accessible. 17 | 18 | This structure provides access to the wave functions via bracket operators. 19 | The following structure is provided: [itunnel,ispin,iene][derivative,x,y,z]. 20 | Note that this evaluation may be performed lazily. 21 | """ 22 | 23 | def __init__(self, cp2k_grid_orb, eval_region, tip_pos, norbs_tip, wn, mpi_rank=0, mpi_size=1, mpi_comm=None): 24 | self.mpi_rank = mpi_rank 25 | self.mpi_size = mpi_size 26 | self.mpi_comm = mpi_comm 27 | self._norbs_tip = norbs_tip 28 | self._decay = (2 * wn * ev2hartree) ** 0.5 29 | self._grids = tip_pos 30 | # Complete energy and wave function matrix when using no MPI 31 | self._ene = cp2k_grid_orb.morb_energies 32 | self._wfn_matrix = cp2k_grid_orb.morb_grids 33 | self._wfn_dim = np.shape(self.wfn_matrix[0])[1:] 34 | self._eval_region = eval_region 35 | self._eval_region_local = self.eval_region 36 | self._reg_grid = None 37 | self._ase_atoms = cp2k_grid_orb.ase_atoms 38 | self._dv = cp2k_grid_orb.dv / ang2bohr 39 | self._nspin = cp2k_grid_orb.nspin 40 | # Storage for computed wave function on evaluation grid 41 | self._wfn = None 42 | self._divide_flag = False 43 | 44 | def _get_slice(cls, wm, ids, axis): 45 | """ 46 | Retrieves a slice specified by a tuple (imin, imax) (inclusive) 47 | from an assumed-periodic array. The slice is along the specified 48 | axis. 49 | """ 50 | dim = np.shape(wm)[axis] 51 | slice_lower = [slice(None)] * wm.ndim 52 | slice_upper = [slice(None)] * wm.ndim 53 | if ids[0] < 0: 54 | slice_lower[axis] = slice(ids[0], None) 55 | if ids[1] >= dim: 56 | slice_upper[axis] = slice(None, ids[1] - dim + 1) 57 | # Case: Spill-over on both sides. 58 | return np.concatenate([wm[tuple(slice_lower)], wm, wm[tuple(slice_upper)]], axis=axis) 59 | else: 60 | slice_upper[axis] = slice(None, ids[1] + 1) 61 | # Case: Spill-over only on lower side. 62 | return np.concatenate([wm[tuple(slice_lower)], wm[tuple(slice_upper)]], axis=axis) 63 | elif ids[1] >= dim: 64 | slice_lower[axis] = slice(ids[0], None) 65 | slice_upper[axis] = slice(None, ids[1] - dim + 1) 66 | # Case: Spill-over only on upper side. 67 | return np.concatenate([wm[tuple(slice_lower)], wm[tuple(slice_upper)]], axis=axis) 68 | else: 69 | slice_lower[axis] = slice(ids[0], ids[1] + 1) 70 | # Case: No spill-over 71 | return wm[tuple(slice_lower)] 72 | 73 | def divide(self): 74 | """ 75 | Divides the grid obritals to the different MPI ranks along the 76 | x-direction rather than the energies. 77 | 78 | Note that this function overwrites the stored wave function matrix 79 | and must be called. 80 | """ 81 | if self._divide_flag: 82 | raise AssertionError("Tried to call Cp2kGridMatrix.divide() twice!") 83 | self._divide_flag = True 84 | # Index range needed by this rank (inclusive) 85 | isx = np.array( 86 | [ 87 | np.floor(min([np.min(pos[0] - self.eval_region[0][0]) for pos in self.grids]) / self._dv[0]), 88 | np.ceil(max([np.max(pos[0] - self.eval_region[0][0]) for pos in self.grids]) / self._dv[0]), 89 | ], 90 | dtype=int, 91 | ) 92 | isy = np.array( 93 | [ 94 | np.floor(min([np.min(pos[1] - self.eval_region[1][0]) for pos in self.grids]) / self._dv[1]), 95 | np.ceil(max([np.max(pos[1] - self.eval_region[1][0]) for pos in self.grids]) / self._dv[1]), 96 | ], 97 | dtype=int, 98 | ) 99 | if self.mpi_comm is None: 100 | wfn_matrix = [self._get_slice(self.wfn_matrix[ispin], isx, 1) for ispin in range(self.nspin)] 101 | else: 102 | ene = [] 103 | wfn_matrix = [] 104 | # Distribute and gather energies and wave functions on MPI ranks 105 | for ispin in range(self.nspin): 106 | # Gather energies 107 | ene_separated = self.mpi_comm.allgather(self.ene[ispin]) 108 | nene_by_rank = np.array([len(val) for val in ene_separated]) 109 | ene.append(np.hstack(ene_separated)) 110 | # Indices needed for the tip position on MPI rank 111 | isx_all = self.mpi_comm.allgather(isx) 112 | # Dimension of local grid for wave function matrix 113 | wfn_dim_local = (isx[1] - isx[0] + 1,) + self.wfn_dim[1:] 114 | npoints = np.product(wfn_dim_local) 115 | # Gather the necessary stuff 116 | for rank in range(self.mpi_size): 117 | if self.mpi_rank == rank: 118 | recvbuf = np.empty(len(ene[ispin]) * npoints) 119 | else: 120 | recvbuf = None 121 | sendbuf = np.array(self._get_slice(self.wfn_matrix[ispin], isx_all[rank], 1), order="C").ravel() 122 | self.mpi_comm.Gatherv(sendbuf=sendbuf, recvbuf=[recvbuf, nene_by_rank * npoints], root=rank) 123 | if self.mpi_rank == rank: 124 | wfn_matrix.append(recvbuf.reshape((len(ene[ispin]),) + wfn_dim_local)) 125 | self._ene = ene 126 | self._wfn_matrix = [] 127 | for ispin in range(self.nspin): 128 | self._wfn_matrix.append(self._get_slice(wfn_matrix[ispin], isy, 2)) 129 | # Set evaluation region for this MPI rank 130 | self._eval_region_local[0] = self.eval_region[0][0] + isx * self._dv[0] 131 | self._eval_region_local[1] = self.eval_region[1][0] + isy * self._dv[1] 132 | self._wfn_dim = np.shape(self.wfn_matrix[0])[1:] 133 | self._reg_grid = ( 134 | np.linspace(self.eval_region_local[0][0], self.eval_region_local[0][1], self.wfn_dim[0]), 135 | np.linspace(self.eval_region_local[1][0], self.eval_region_local[1][1], self.wfn_dim[1]), 136 | np.linspace(self.eval_region_local[2][0], self.eval_region_local[2][1], self.wfn_dim[2]), 137 | ) 138 | 139 | ### ------------------------------------------------------------------------ 140 | ### Access operators 141 | ### ------------------------------------------------------------------------ 142 | 143 | @property 144 | def ene(self): 145 | """List of energies per spin in eV.""" 146 | return self._ene 147 | 148 | @property 149 | def wfn_matrix(self): 150 | """Local underlying matrix defined on regular grid in atomic units.""" 151 | return self._wfn_matrix 152 | 153 | @property 154 | def eval_region_local(self): 155 | """Limits of evaluation grid for local non-relaxed tip scan in 156 | Angstrom. 157 | """ 158 | return self._eval_region_local 159 | 160 | @property 161 | def eval_region(self): 162 | """Limits of evaluation grid for complete non-relaxed tip scan in 163 | Angstrom. 164 | """ 165 | return self._eval_region 166 | 167 | @property 168 | def reg_grid(self): 169 | """Regular grid where local wave function matrix is defined on in 170 | Angstrom. 171 | """ 172 | return self._reg_grid 173 | 174 | @property 175 | def grids(self): 176 | """Local evaluation grids for tip positions in Angstrom.""" 177 | return self._grids 178 | 179 | @property 180 | def ase_atoms(self): 181 | """ASE atom object.""" 182 | return self._ase_atoms 183 | 184 | @property 185 | def wfn_dim(self): 186 | """Dimension of local grid for wave function matrix.""" 187 | return self._wfn_dim 188 | 189 | @property 190 | def grid_dim(self): 191 | """Dimension of local evaluation grids for tip positions.""" 192 | return np.shape(self._grids[0])[1:] 193 | 194 | @property 195 | def norbs_tip(self): 196 | """Number of tip orbitals (0 for s, 1 for p).""" 197 | return self._norbs_tip 198 | 199 | @property 200 | def decay(self): 201 | """Decay constant in atomic units.""" 202 | return self._decay 203 | 204 | @property 205 | def nspin(self): 206 | """Number of spins for sample.""" 207 | return self._nspin 208 | 209 | def __getitem__(self, itupel): 210 | igrid, ispin, iene = itupel 211 | # Check if already evaluated 212 | if self._wfn is not None and self._cgrid == igrid and self._cspin == ispin and self._cene == iene: 213 | return self._wfn 214 | # Storage container 215 | self._wfn = np.empty(((self.norbs_tip + 1) ** 2,) + self.grid_dim) 216 | # Create interpolator 217 | interp = Interpolator(self.reg_grid, self.wfn_matrix[ispin][iene]) 218 | self._wfn[0] = interp(*self.grids[igrid]) 219 | if self.norbs_tip: 220 | self._wfn[1] = interp.gradient(*self.grids[igrid], 2) / ang2bohr / self.decay 221 | self._wfn[2] = interp.gradient(*self.grids[igrid], 3) / ang2bohr / self.decay 222 | self._wfn[3] = interp.gradient(*self.grids[igrid], 1) / ang2bohr / self.decay 223 | self._cgrid, self._cspin, self._cene = itupel 224 | return self._wfn 225 | -------------------------------------------------------------------------------- /cp2k_spm_tools/hrstm_tools/hrstm.py: -------------------------------------------------------------------------------- 1 | # @author Hillebrand, Fabian 2 | # @date 2019 3 | 4 | import time 5 | 6 | import numpy as np 7 | import scipy as sp 8 | 9 | 10 | class Hrstm: 11 | """ 12 | Provides a relatively generic HR-STM simulator. 13 | 14 | Needs to be given an object for the wave function and the tip coefficients 15 | that provide certain information. 16 | The tip DOS can be energy-independent (i.e. "constant") or energy-dependent 17 | in which case it can be either broadened using Gaussians or left as Dirac 18 | functions depending on what the full-width at half maximum is set as. 19 | 20 | This class supports parallelism. However, the grids should be divided along 21 | x-axis only. 22 | """ 23 | 24 | def __init__(self, tip_coeffs, dim_pos, wfn_grid_matrix, sam_fwhm, tip_fwhm, mpi_rank=0, mpi_size=1, mpi_comm=None): 25 | self.mpi_rank = mpi_rank 26 | self.mpi_size = mpi_size 27 | self.mpi_comm = mpi_comm 28 | self._tc = tip_coeffs 29 | self._dim_pos = dim_pos 30 | self._gm = wfn_grid_matrix 31 | self._sigma = sam_fwhm / 2.35482 32 | self._variance = self._sigma 33 | if self._tc.type != "constant": 34 | self._tau = tip_fwhm / 2.35482 35 | # Dirac tip: 36 | if np.isclose(tip_fwhm, 0.0): 37 | self._check = self._check_dirac 38 | self._factor = self._dirac 39 | # Gaussian broadened tip: 40 | else: 41 | self._variance = self._sigma * self._tau / (self._sigma**2 + self._tau**2) ** 0.5 42 | self._check = self._check_gaussian 43 | self._factor = self._gaussian 44 | # Constant tip 45 | else: 46 | self._tau = None 47 | self._check = self._check_constant 48 | self._factor = self._constant 49 | 50 | ### ------------------------------------------------------------------------ 51 | 52 | def _check_constant(self, ene_sam, enes_tip, voltages): 53 | try: # Test if enes_tip is a container 54 | skip = np.array([True for ene in enes_tip]) 55 | except TypeError: 56 | skip = True 57 | try: # Test if voltages is a container 58 | for voltage in voltages: 59 | skip &= ~( 60 | -4.0 * self._sigma < ene_sam <= voltage + 4.0 * self._sigma 61 | or voltage - 4.0 * self._sigma < ene_sam <= 4.0 * self._sigma 62 | ) 63 | except TypeError: 64 | skip &= ~( 65 | -4.0 * self._sigma < ene_sam <= voltages + 4.0 * self._sigma 66 | or voltages - 4.0 * self._sigma < ene_sam <= 4.0 * self._sigma 67 | ) 68 | return ~skip 69 | 70 | def _constant(self, ene_sam, ene_tip, voltage): 71 | """Constant tip density and Gaussian density for sample.""" 72 | return 0.5 * ( 73 | sp.special.erf((voltage - ene_sam) / (2.0**0.5 * self._sigma)) 74 | - sp.special.erf((0.0 - ene_sam) / (2.0**0.5 * self._sigma)) 75 | ) 76 | 77 | ### ------------------------------------------------------------------------ 78 | 79 | def _check_gaussian(self, ene_sam, enes_tip, voltages): 80 | vals = ( 81 | (enes_tip * ene_sam > 0.0) | ((ene_sam <= 0.0) & (enes_tip == 0.0)) | ((ene_sam == 0.0) & (enes_tip <= 0.0)) 82 | ) 83 | skip = True 84 | try: 85 | for voltage in voltages: 86 | skip &= np.abs(voltage - ene_sam + enes_tip) >= 4.0 * (self._sigma + self._tau) 87 | except TypeError: 88 | skip &= np.abs(voltages - ene_sam + enes_tip) >= 4.0 * (self._sigma + self._tau) 89 | return ~(skip | vals) 90 | 91 | def _gaussian(self, ene_sam, ene_tip, voltage): 92 | """Gaussian density for tip and sample.""" 93 | # Product of two Gaussian is a Gaussian but don't forget pre-factor 94 | mean = (self._sigma**2 * (ene_tip + voltage) + self._tau**2 * ene_sam) / (self._sigma**2 + self._tau**2) 95 | sigma = self._variance 96 | correction = ( 97 | 1.0 98 | / (2.0 * np.pi * (self._sigma**2 + self._tau**2)) ** 0.5 99 | * np.exp(-0.5 * (ene_sam - ene_tip - voltage) ** 2 / (self._sigma**2 + self._tau**2)) 100 | ) 101 | return ( 102 | 0.5 103 | * correction 104 | * ( 105 | sp.special.erf((voltage - mean) / (2.0**0.5 * sigma)) 106 | - sp.special.erf((0.0 - mean) / (2.0**0.5 * sigma)) 107 | ) 108 | ) 109 | 110 | ### ------------------------------------------------------------------------ 111 | 112 | def _check_dirac(self, ene_sam, enes_tip, voltages): 113 | vals = ( 114 | (enes_tip * ene_sam > 0.0) | ((ene_sam <= 0.0) & (enes_tip == 0.0)) | ((ene_sam == 0.0) & (enes_tip <= 0.0)) 115 | ) 116 | skip = True 117 | try: 118 | for voltage in voltages: 119 | skip &= np.abs(voltage - ene_sam + enes_tip) >= 4.0 * self._sigma 120 | except TypeError: 121 | skip &= np.abs(voltages - ene_sam + enes_tip) >= 4.0 * self._sigma 122 | return ~(skip | vals) 123 | 124 | def _dirac(self, ene_sam, ene_tip, voltage): 125 | """ 126 | Gaussian density of states (integration with a Dirac function). 127 | 128 | Note: This is also the limit of self._gaussian as self._tau -> 0 129 | """ 130 | # Minus sign since voltage is added to tip energy: 131 | # Relevant range is then (0,-voltage] or (-voltage,0] 132 | if 0 < ene_tip <= -voltage or -voltage < ene_tip <= 0: 133 | return np.exp(-0.5 * ((ene_sam - ene_tip - voltage) / self._sigma) ** 2) / ( 134 | self._sigma * (2 * np.pi) ** 0.5 135 | ) 136 | return 0.0 137 | 138 | ### ------------------------------------------------------------------------ 139 | ### Store and collect 140 | ### ------------------------------------------------------------------------ 141 | 142 | def gather(self): 143 | """Gathers the current and returns it on rank 0.""" 144 | if self.mpi_comm is None: 145 | return self.local_current 146 | if self.mpi_rank == 0: 147 | current = np.empty(self._dim_pos + (len(self._voltages),)) 148 | else: 149 | current = None 150 | outputs = self.mpi_comm.allgather(len(self.local_current.ravel())) 151 | self.mpi_comm.Gatherv(self.local_current, [current, outputs], root=0) 152 | return current 153 | 154 | def write(self, filename): 155 | """ 156 | Writes the current to a file (*.npy). 157 | 158 | The file is written as a 1-dimensional array. The reconstruction has 159 | thus be done by hand. It can be reshaped into a 4-dimensional array in 160 | the form [zIdx,yIdx,xIdx,vIdx]. 161 | """ 162 | pass 163 | 164 | def write_compressed(self, filename, tol=1e-3): 165 | """ 166 | Writes the current compressed to a file (*.npz). 167 | 168 | The file is written as a 1-dimensional array similar to write(). 169 | Furthermore, in order to load the current use np.load()['arr_0']. 170 | 171 | Pay attention: This method invokes a gather! 172 | """ 173 | current = self.gather() 174 | if self.mpi_rank == 0: 175 | for iheight in range(self._dim_pos[-1]): 176 | for ivol in range(len(self._voltages)): 177 | max_val = np.max(np.abs(current[:, :, iheight, ivol])) 178 | current[:, :, iheight, ivol][np.abs(current[:, :, iheight, ivol]) < max_val * tol] = 0.0 179 | np.savez_compressed(filename, current.ravel()) 180 | 181 | ### ------------------------------------------------------------------------ 182 | ### Running HR-STM 183 | ### ------------------------------------------------------------------------ 184 | 185 | def run(self, voltages, info=True): 186 | """Performs the HR-STM simulation.""" 187 | self._voltages = np.array(voltages) 188 | self.local_current = np.zeros((len(self._voltages),) + self._tc.grid_dim) 189 | totTM = 0.0 190 | totVL = 0.0 191 | # Over each separate tunnel process (e.g. to O- or C-atom) 192 | for itunnel in range(self._tc.ntunnels): 193 | for ispin_sam in range(self._gm.nspin): 194 | for iene_sam, ene_sam in enumerate(self._gm.ene[ispin_sam]): 195 | for ispin_tip, enes_tip in enumerate(self._tc.ene): 196 | ienes_tip = np.arange(len(enes_tip)) 197 | for iene_tip in [ 198 | iene_tip for iene_tip in ienes_tip[self._check(ene_sam, enes_tip, self._voltages)] 199 | ]: 200 | # Current tip energy 201 | ene_tip = self._tc.ene[ispin_tip][iene_tip] 202 | start = time.time() 203 | tunnel_matrix_squared = ( 204 | np.einsum( 205 | "i...,i...->...", 206 | self._tc[itunnel, ispin_tip, iene_tip], 207 | self._gm[itunnel, ispin_sam, iene_sam], 208 | ) 209 | ) ** 2 210 | end = time.time() 211 | totTM += end - start 212 | start = time.time() 213 | for ivol, voltage in enumerate(self._voltages): 214 | if self._check(ene_sam, ene_tip, voltage): 215 | self.local_current[ivol] += ( 216 | self._factor(ene_sam, ene_tip, voltage) * tunnel_matrix_squared 217 | ) 218 | end = time.time() 219 | totVL += end - start 220 | # Copy to assure C-contiguous array 221 | self.local_current = self.local_current.transpose((1, 2, 3, 0)).copy() 222 | if info: 223 | print("Total time for tunneling matrix was {:} seconds.".format(totTM)) 224 | print("Total time for voltage loop was {:} seconds.".format(totVL)) 225 | -------------------------------------------------------------------------------- /cp2k_spm_tools/hrstm_tools/hrstm_utils.py: -------------------------------------------------------------------------------- 1 | # @author Hillebrand, Fabian 2 | # @date 2019 3 | 4 | import numpy as np 5 | from mpi4py import MPI 6 | 7 | 8 | def read_PPPos(filename): 9 | """ 10 | Loads the positions obtained from the probe particle model and returns 11 | them as a [3,X,Y,Z]-array together with the lVec (which defines the 12 | non-relaxed grid scan in terms of the lowest probe particle (oxygen)). 13 | The units are in Angstrom. 14 | """ 15 | disposX = np.transpose(np.load(filename + "_x.npy")).copy() 16 | disposY = np.transpose(np.load(filename + "_y.npy")).copy() 17 | disposZ = np.transpose(np.load(filename + "_z.npy")).copy() 18 | lvec = np.load(filename + "_vec.npy") 19 | # Stack arrays to form 4-dimensional array of size [3, noX, noY, noZ] 20 | dispos = (disposX, disposY, disposZ) 21 | return dispos, lvec 22 | 23 | 24 | def apply_bounds(grid, lVec): 25 | """ 26 | Assumes periodicity and restricts grid positions to a box in x- and 27 | y-direction. 28 | """ 29 | dx = lVec[1, 0] - lVec[0, 0] 30 | grid[0][grid[0] >= lVec[1, 0]] -= dx 31 | grid[0][grid[0] < lVec[0, 0]] += dx 32 | dy = lVec[2, 1] - lVec[0, 1] 33 | grid[1][grid[1] >= lVec[2, 1]] -= dy 34 | grid[1][grid[1] < lVec[0, 1]] += dy 35 | return grid 36 | 37 | 38 | def read_tip_positions(files, shift, dx, mpi_rank=0, mpi_size=1, mpi_comm=None): 39 | """ 40 | Reads the tip positions and determines the necessary grid orbital evaluation 41 | region for the sample via the tip positions. 42 | 43 | pos_local List with the tip positions for this rank. 44 | dim_pos Number of all tip positions along each axis. 45 | eval_region Limits of evaluation grid encompassing all tip positions. 46 | lVec 4x3 matrix defining non-relaxed tip positions 47 | (with respect to the oxygen). 48 | """ 49 | # Only reading on one rank, could be optimized but not the bottleneck 50 | if mpi_rank == 0: 51 | pos_all = [] 52 | for filename in files: 53 | positions, lVec = read_PPPos(filename) 54 | pos_all.append(positions) 55 | dim_pos = np.shape(pos_all[0])[1:] 56 | # Metal tip (needed only for rotation, no tunnelling considered) 57 | pos_all.insert( 58 | 0, 59 | np.mgrid[ 60 | lVec[0, 0] : lVec[0, 0] + lVec[1, 0] : dim_pos[0] * 1j, 61 | lVec[0, 1] : lVec[0, 1] + lVec[2, 1] : dim_pos[1] * 1j, 62 | lVec[0, 2] + shift : lVec[0, 2] + lVec[3, 2] + shift : dim_pos[2] * 1j, 63 | ], 64 | ) 65 | # Evaluation region for sample (x,y periodic) 66 | xmin = lVec[0, 0] 67 | xmax = lVec[0, 0] + lVec[1, 0] 68 | ymin = lVec[0, 1] 69 | ymax = lVec[0, 1] + lVec[2, 1] 70 | zmin = min([np.min(pos[2]) for pos in pos_all[1:]]) - dx / 2 71 | zmax = max([np.max(pos[2]) for pos in pos_all[1:]]) + dx / 2 72 | eval_region_wfn = np.array([[xmin, xmax], [ymin, ymax], [zmin, zmax]]) 73 | # No MPI 74 | if mpi_comm is None: 75 | return pos_all, dim_pos, eval_region_wfn, lVec 76 | else: 77 | pos_all = [[None] * 3] * (len(files) + 1) 78 | lVec = None 79 | dim_pos = None 80 | eval_region_wfn = None 81 | # Broadcast small things 82 | lVec = mpi_comm.bcast(lVec, root=0) 83 | dim_pos = mpi_comm.bcast(dim_pos, root=0) 84 | eval_region_wfn = mpi_comm.bcast(eval_region_wfn, root=0) 85 | # Divide up tip positions along x-axis 86 | all_x_ids = np.array_split(np.arange(dim_pos[0]), mpi_size) 87 | lengths = [len(all_x_ids[rank]) * np.product(dim_pos[1:]) for rank in range(mpi_size)] 88 | offsets = [all_x_ids[rank][0] * np.product(dim_pos[1:]) for rank in range(mpi_size)] 89 | # Prepare storage and then scatter grids 90 | pos_local = [ 91 | np.empty( 92 | ( 93 | 3, 94 | len(all_x_ids[mpi_rank]), 95 | ) 96 | + dim_pos[1:] 97 | ) 98 | for i in range(len(files) + 1) 99 | ] 100 | for gridIdx in range(len(files) + 1): 101 | for axis in range(3): 102 | mpi_comm.Scatterv([pos_all[gridIdx][axis], lengths, offsets, MPI.DOUBLE], pos_local[gridIdx][axis], root=0) 103 | return pos_local, dim_pos, eval_region_wfn, lVec 104 | 105 | 106 | def create_tip_positions(eval_region, dx, mpi_rank=0, mpi_size=1, mpi_comm=None): 107 | """ 108 | Creates uniform grids for tip positions. Due to the structure of the code, 109 | this returns a tuple with twice the same grid. Rotations are not supported. 110 | 111 | pos_local List with the tip positions for this rank. 112 | dim_pos Number of all tip positions along each axis. 113 | eval_region Limits of evaluation grid encompassing all tip positions. 114 | lVec 4x3 matrix defining non-relaxed tip positions 115 | (with respect to the oxygen). 116 | """ 117 | eval_region = np.reshape(eval_region, (3, 2)) 118 | lVec = np.zeros((4, 3)) 119 | lVec[0, 0] = eval_region[0, 0] 120 | lVec[1, 0] = eval_region[0, 1] - eval_region[0, 0] 121 | lVec[0, 1] = eval_region[1, 0] 122 | lVec[2, 1] = eval_region[1, 1] - eval_region[1, 0] 123 | lVec[0, 2] = eval_region[2, 0] 124 | lVec[3, 2] = eval_region[2, 1] - eval_region[2, 0] 125 | dim_pos = (int(lVec[1, 0] / dx + 1), int(lVec[2, 1] / dx + 1), int(lVec[3, 2] / dx + 1)) 126 | # True spacing 127 | dxyz = [lVec[i + 1, i] / (dim_pos[i] - 1) for i in range(3)] 128 | # Divide tip positions before building grids 129 | all_x_ids = np.array_split(np.arange(dim_pos[0]), mpi_size) 130 | start = lVec[0, 0] + dxyz[0] * all_x_ids[mpi_rank][0] 131 | end = lVec[0, 0] + dxyz[0] * all_x_ids[mpi_rank][-1] 132 | grid = np.mgrid[ 133 | start : end : len(all_x_ids[mpi_rank]) * 1j, 134 | lVec[0, 1] : lVec[0, 1] + lVec[2, 1] : dim_pos[1] * 1j, 135 | lVec[0, 2] : lVec[0, 2] + lVec[3, 2] : dim_pos[2] * 1j, 136 | ] 137 | # Increase eval_region to account for non-periodic axis 138 | eval_region[2, 0] -= dxyz[-1] / 2 139 | eval_region[2, 1] += dxyz[-1] / 2 140 | # Positions emulating apex atom + metal tip 141 | pos_local = [grid, grid] 142 | return pos_local, dim_pos, eval_region, lVec 143 | -------------------------------------------------------------------------------- /cp2k_spm_tools/hrstm_tools/interpolator.py: -------------------------------------------------------------------------------- 1 | # @author Hillebrand, Fabian 2 | # @date 2019 3 | 4 | import numpy as np 5 | 6 | 7 | class Interpolator: 8 | """ 9 | Provides an interpolator for a regular grid with consistent stepsize along 10 | an axis. 11 | 12 | The interpolation scheme used is currently piecewise linear polynomials. 13 | As such, the convergence rate is algebraic with a rate of 2. 14 | First derivatives are achieved using second order finite differences to not 15 | stump the convergence rate. 16 | 17 | Pay attention: No care is taken for periodicity or out of bound: Make sure 18 | all points to be interpolated are within the regular grid! 19 | """ 20 | 21 | def __init__(self, x, f): 22 | self.x = x 23 | self.f = f 24 | self.dx = x[0][1] - x[0][0] 25 | self.dy = x[1][1] - x[1][0] 26 | self.dz = x[2][1] - x[2][0] 27 | # For derivative 28 | self.derF = np.gradient(self.f, self.dx, self.dy, self.dz, edge_order=2, axis=(0, 1, 2)) 29 | 30 | ### ------------------------------------------------------------------------ 31 | ### Evaluation functions 32 | ### ------------------------------------------------------------------------ 33 | 34 | def __call__(self, x, y, z): 35 | """Evaluates interpolated value at given points.""" 36 | indX = ((x - self.x[0][0]) / self.dx).astype(int) 37 | indY = ((y - self.x[1][0]) / self.dy).astype(int) 38 | indZ = ((z - self.x[2][0]) / self.dz).astype(int) 39 | 40 | return ( 41 | (self.x[0][indX + 1] - x) 42 | * ( 43 | (self.x[1][indY + 1] - y) 44 | * ( 45 | (self.x[2][indZ + 1] - z) * self.f[indX, indY, indZ] 46 | + (z - self.x[2][indZ]) * self.f[indX, indY, indZ + 1] 47 | ) 48 | + (y - self.x[1][indY]) 49 | * ( 50 | (self.x[2][indZ + 1] - z) * self.f[indX, indY + 1, indZ] 51 | + (z - self.x[2][indZ]) * self.f[indX, indY + 1, indZ + 1] 52 | ) 53 | ) 54 | + (x - self.x[0][indX]) 55 | * ( 56 | (self.x[1][indY + 1] - y) 57 | * ( 58 | (self.x[2][indZ + 1] - z) * self.f[indX + 1, indY, indZ] 59 | + (z - self.x[2][indZ]) * self.f[indX + 1, indY, indZ + 1] 60 | ) 61 | + (y - self.x[1][indY]) 62 | * ( 63 | (self.x[2][indZ + 1] - z) * self.f[indX + 1, indY + 1, indZ] 64 | + (z - self.x[2][indZ]) * self.f[indX + 1, indY + 1, indZ + 1] 65 | ) 66 | ) 67 | ) / (self.dx * self.dy * self.dz) 68 | 69 | def gradient(self, x, y, z, direct): 70 | """ 71 | Evaluates gradient of interpolation in specified direction 72 | (x=1,y=2,z=3). 73 | 74 | Pay attention if units used for grid differ from units for 75 | function! 76 | """ 77 | try: 78 | tmp = self.derF[direct - 1] 79 | except IndexError: 80 | raise NotImplementedError("Gradient in direction {} is not available".format(direct)) 81 | 82 | indX = ((x - self.x[0][0]) / self.dx).astype(int) 83 | indY = ((y - self.x[1][0]) / self.dy).astype(int) 84 | indZ = ((z - self.x[2][0]) / self.dz).astype(int) 85 | 86 | return ( 87 | (self.x[0][indX + 1] - x) 88 | * ( 89 | (self.x[1][indY + 1] - y) 90 | * ( 91 | (self.x[2][indZ + 1] - z) * tmp[indX, indY, indZ] 92 | + (z - self.x[2][indZ]) * tmp[indX, indY, indZ + 1] 93 | ) 94 | + (y - self.x[1][indY]) 95 | * ( 96 | (self.x[2][indZ + 1] - z) * tmp[indX, indY + 1, indZ] 97 | + (z - self.x[2][indZ]) * tmp[indX, indY + 1, indZ + 1] 98 | ) 99 | ) 100 | + (x - self.x[0][indX]) 101 | * ( 102 | (self.x[1][indY + 1] - y) 103 | * ( 104 | (self.x[2][indZ + 1] - z) * tmp[indX + 1, indY, indZ] 105 | + (z - self.x[2][indZ]) * tmp[indX + 1, indY, indZ + 1] 106 | ) 107 | + (y - self.x[1][indY]) 108 | * ( 109 | (self.x[2][indZ + 1] - z) * tmp[indX + 1, indY + 1, indZ] 110 | + (z - self.x[2][indZ]) * tmp[indX + 1, indY + 1, indZ + 1] 111 | ) 112 | ) 113 | ) / (self.dx * self.dy * self.dz) 114 | -------------------------------------------------------------------------------- /cp2k_spm_tools/igor.py: -------------------------------------------------------------------------------- 1 | """Classes for use with IGOR Pro 2 | 3 | Code is based on asetk module by Leopold Talirz 4 | (https://github.com/ltalirz/asetk/blob/master/asetk/format/igor.py) 5 | 6 | -Kristjan Eimre 7 | """ 8 | 9 | import re 10 | 11 | import numpy as np 12 | 13 | 14 | def read_wave(lines): 15 | line = lines.pop(0) 16 | while not re.match("WAVES", line): 17 | if len(lines) == 0: 18 | return None 19 | line = lines.pop(0) 20 | # 1d or 2d? 21 | d2 = False 22 | if "N=" in line: 23 | d2 = True 24 | match = re.search(r"WAVES/N=\(([\d, ]+)\)", line) 25 | grid = match.group(1).split(",") 26 | grid = np.array(grid, dtype=int) 27 | name = line.split(")")[-1].strip() 28 | else: 29 | name = line.split()[-1] 30 | 31 | line = lines.pop(0).strip() 32 | if not line == "BEGIN": 33 | raise IOError("Missing 'BEGIN' statement of data block") 34 | 35 | # read data 36 | datastring = "" 37 | line = lines.pop(0) 38 | while not re.match("END", line): 39 | if len(lines) == 0: 40 | return None 41 | if line.startswith("X"): 42 | return None 43 | datastring += line 44 | line = lines.pop(0) 45 | data = np.array(datastring.split(), dtype=float) 46 | if d2: 47 | data = data.reshape(grid) 48 | 49 | # read axes 50 | axes = [] 51 | line = lines.pop(0) 52 | matches = re.findall("SetScale.+?(?:;|$)", line) 53 | for match in matches: 54 | ax = Axis(None, None, None, None) 55 | ax.read(match) 56 | axes.append(ax) 57 | 58 | if d2: 59 | # read also the second axis 60 | # is this necessary? can there be 2 lines with "SetScale" ? 61 | line = lines.pop(0) 62 | matches = re.findall("SetScale.+?(?:;|$)", line) 63 | for match in matches: 64 | ax = Axis(None, None, None, None) 65 | ax.read(match) 66 | axes.append(ax) 67 | return Wave2d(data, axes, name) 68 | else: 69 | return Wave1d(data, axes, name) 70 | 71 | 72 | def igor_wave_factory(fname): 73 | """ 74 | Returns either wave1d or wave2d, corresponding to the input file 75 | """ 76 | f = open(fname, "r") 77 | lines = f.readlines() 78 | f.close() 79 | 80 | # lines = content.split("\r") 81 | 82 | line = lines.pop(0).strip() 83 | if not line == "IGOR": 84 | raise IOError("Files does not begin with 'IGOR'") 85 | 86 | waves = [] 87 | while len(lines) != 0: 88 | try: 89 | wave = read_wave(lines) 90 | if wave is not None: 91 | waves.append(wave) 92 | except Exception as e: 93 | print(" Error: %.80s..." % str(e)) 94 | 95 | return waves 96 | 97 | 98 | class Axis(object): 99 | """Represents an axis of an IGOR wave""" 100 | 101 | def __init__(self, symbol, min_, delta, unit, wavename=None): 102 | self.symbol = symbol 103 | self.min = min_ 104 | self.delta = delta 105 | self.unit = unit 106 | self.wavename = wavename 107 | 108 | def __str__(self): 109 | """Prints axis in itx format 110 | Note: SetScale/P expects minimum value and step-size 111 | """ 112 | delta = 0 if self.delta is None else self.delta 113 | s = 'X SetScale/P {symb} {min},{delta}, "{unit}", {name};\n'.format( 114 | symb=self.symbol, min=self.min, delta=delta, unit=self.unit, name=self.wavename 115 | ) 116 | return s 117 | 118 | def read(self, string): 119 | """Read axis from string 120 | Format: 121 | X SetScale/P x 0,2.01342281879195e-11,"m", data_00381_Up; 122 | SetScale d 0,0,"V", data_00381_Up 123 | """ 124 | match = re.search('SetScale/?P? (.) ([+-\\.\\de]+),([+-\\.\\de]+),[ ]*"(.*)",\\s*(\\w+)', string) 125 | self.symbol = match.group(1) 126 | self.min = float(match.group(2)) 127 | self.delta = float(match.group(3)) 128 | self.unit = match.group(4) 129 | self.wavename = match.group(5) 130 | 131 | 132 | class Wave(object): 133 | """A class template for IGOR waves of generic dimension""" 134 | 135 | def __init__(self, data, axes, name=None): 136 | """Initialize IGOR wave of generic dimension""" 137 | self.data = data 138 | self.name = "PYTHON_IMPORT" if name is None else name 139 | self.axes = axes 140 | 141 | def __str__(self): 142 | """Print IGOR wave""" 143 | s = "" 144 | s += "IGOR\n" 145 | 146 | dimstring = "(" 147 | for i in range(len(self.data.shape)): 148 | dimstring += "{}, ".format(self.data.shape[i]) 149 | dimstring = dimstring[:-2] + ")" 150 | 151 | s += "WAVES/N={} {}\n".format(dimstring, self.name) 152 | s += "BEGIN\n" 153 | s += self.print_data() 154 | s += "END\n" 155 | for ax in self.axes: 156 | s += str(ax) 157 | return s 158 | 159 | @classmethod 160 | def read(cls, fname): 161 | """Read IGOR wave""" 162 | f = open(fname, "r") 163 | lines = f.readlines() 164 | f.close() 165 | 166 | # lines = content.split("\r") 167 | 168 | line = lines.pop(0).strip() 169 | if not line == "IGOR": 170 | raise IOError("Files does not begin with 'IGOR'") 171 | 172 | line = lines.pop(0) 173 | while not re.match("WAVES", line): 174 | line = lines.pop(0) 175 | # 1d or 2d? 176 | waves_str, name = line.split() 177 | d2 = False 178 | if "N=" in waves_str: 179 | d2 = True 180 | match = re.search(r"WAVES/N=\(([\d,]+)\)", waves_str) 181 | grid = match.group(1).split(",") 182 | grid = np.array(grid, dtype=int) 183 | 184 | line = lines.pop(0).strip() 185 | if not line == "BEGIN": 186 | raise IOError("Missing 'BEGIN' statement of data block") 187 | 188 | # read data 189 | datastring = "" 190 | line = lines.pop(0) 191 | while not re.match("END", line): 192 | datastring += line 193 | line = lines.pop(0) 194 | data = np.array(datastring.split(), dtype=float) 195 | if d2: 196 | data = data.reshape(grid) 197 | 198 | # read axes 199 | line = lines.pop(0) 200 | matches = re.findall("SetScale.+?(?:;|$)", line) 201 | axes = [] 202 | for match in matches: 203 | ax = Axis(None, None, None, None) 204 | ax.read(match) 205 | axes.append(ax) 206 | 207 | # the rest is discarded... 208 | 209 | return cls(data, axes, name) 210 | 211 | @property 212 | def extent(self): 213 | """Returns extent for plotting""" 214 | grid = self.data.shape 215 | extent = [] 216 | for i in range(len(grid)): 217 | ax = self.axes[i] 218 | extent.append(ax.min) 219 | extent.append(ax.min + ax.delta * grid[i]) 220 | 221 | return np.array(extent) 222 | 223 | def print_data(self): 224 | """Determines how to print the data block. 225 | 226 | To be implemented by subclasses.""" 227 | 228 | def write(self, fname): 229 | f = open(fname, "w") 230 | f.write(str(self)) 231 | f.close() 232 | 233 | def csv_header(self): 234 | header = "" 235 | shape = self.data.shape 236 | for i_ax in range(len(shape)): 237 | ax = self.axes[i_ax] 238 | if header != "": 239 | header += "\n" 240 | header += "axis %d: %s [unit: %s] [%.6e, %.6e], delta=%.6e, n=%d" % ( 241 | i_ax, 242 | ax.symbol, 243 | ax.unit, 244 | ax.min, 245 | ax.min + ax.delta * (shape[i_ax] - 1), 246 | ax.delta, 247 | shape[i_ax], 248 | ) 249 | return header 250 | 251 | def write_csv(self, fname, fmt="%.6e"): 252 | np.savetxt(fname, self.data, delimiter=",", header=self.csv_header(), fmt=fmt) 253 | 254 | 255 | class Wave1d(Wave): 256 | """1d Igor wave""" 257 | 258 | default_parameters = dict( 259 | xmin=0.0, 260 | xdelta=None, 261 | xlabel="x", 262 | ylabel="y", 263 | ) 264 | 265 | def __init__(self, data=None, axes=None, name="1d", **kwargs): 266 | """Initialize 1d IGOR wave""" 267 | super(Wave1d, self).__init__(data, axes, name) 268 | 269 | self.parameters = self.default_parameters 270 | for key, value in kwargs.items(): 271 | if key in self.parameters: 272 | self.parameters[key] = value 273 | else: 274 | raise KeyError("Unknown parameter {}".format(key)) 275 | 276 | if axes is None: 277 | p = self.parameters 278 | x = Axis(symbol="x", min_=p["xmin"], delta=p["xdelta"], unit=p["xlabel"], wavename=self.name) 279 | self.axes = [x] 280 | 281 | def print_data(self): 282 | s = "" 283 | for line in self.data: 284 | s += "{:12.6e}\n".format(float(line)) 285 | 286 | return s 287 | 288 | 289 | class Wave2d(Wave): 290 | """2d Igor wave""" 291 | 292 | default_parameters = dict( 293 | xmin=0.0, 294 | xdelta=None, 295 | xmax=None, 296 | xlabel="x", 297 | ymin=0.0, 298 | ydelta=None, 299 | ymax=None, 300 | ylabel="y", 301 | ) 302 | 303 | def __init__(self, data=None, axes=None, name=None, **kwargs): 304 | """Initialize 2d Igor wave 305 | Parameters 306 | ---------- 307 | 308 | * data 309 | * name 310 | * xmin, xdelta, xlabel 311 | * ymin, ydelta, ylabel 312 | """ 313 | super(Wave2d, self).__init__(data, axes=axes, name=name) 314 | 315 | self.parameters = self.default_parameters 316 | for key, value in kwargs.items(): 317 | if key in self.parameters: 318 | self.parameters[key] = value 319 | else: 320 | raise KeyError("Unknown parameter {}".format(key)) 321 | 322 | if axes is None: 323 | p = self.parameters 324 | 325 | nx, ny = self.data.shape 326 | if p["xmax"] is None: 327 | p["xmax"] = p["xdelta"] * nx 328 | elif p["xdelta"] is None: 329 | p["xdelta"] = p["xmax"] / nx 330 | 331 | if p["ymax"] is None: 332 | p["ymax"] = p["ydelta"] * ny 333 | elif p["ydelta"] is None: 334 | p["ydelta"] = p["ymax"] / ny 335 | 336 | x = Axis(symbol="x", min_=p["xmin"], delta=p["xdelta"], unit=p["xlabel"], wavename=self.name) 337 | y = Axis(symbol="y", min_=p["ymin"], delta=p["ydelta"], unit=p["ylabel"], wavename=self.name) 338 | self.axes = [x, y] 339 | 340 | def print_data(self): 341 | """Determines how to print the data block""" 342 | s = "" 343 | for line in self.data: 344 | for x in line: 345 | s += "{:12.6e} ".format(x) 346 | s += "\n" 347 | 348 | return s 349 | -------------------------------------------------------------------------------- /cp2k_spm_tools/postprocess/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/cp2k_spm_tools/postprocess/__init__.py -------------------------------------------------------------------------------- /cp2k_spm_tools/postprocess/overlap.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import os 3 | import re 4 | 5 | import numpy as np 6 | 7 | 8 | def read_and_process_pdos_file(pdos_path): 9 | header = open(pdos_path).readline() 10 | # fermi = float(re.search("Fermi.* ([+-]?[0-9]*[.]?[0-9]+)", header).group(1)) 11 | try: 12 | kind = re.search(r"atomic kind.(\S+)", header).group(1) 13 | except: 14 | kind = None 15 | data = np.loadtxt(pdos_path) 16 | 17 | # determine fermi by counting the number of electrons and 18 | # taking the middle of HOMO and LUMO. 19 | n_el = int(np.round(np.sum(data[:, 2]))) 20 | if data[0, 2] > 1.5: 21 | n_el = int(np.round(n_el / 2)) 22 | fermi = 0.5 * (data[n_el - 1, 1] + data[n_el, 1]) 23 | 24 | out_data = np.zeros((data.shape[0], 2)) 25 | out_data[:, 0] = (data[:, 1] - fermi) * 27.21138602 # energy 26 | out_data[:, 1] = np.sum(data[:, 3:], axis=1) # "contracted pdos" 27 | return out_data, kind 28 | 29 | 30 | def process_pdos_files(folder): 31 | nspin = 1 32 | for file in os.listdir(folder): 33 | if file.endswith(".pdos") and "BETA" in file: 34 | nspin = 2 35 | break 36 | 37 | dos = { 38 | "mol": [None] * nspin, 39 | } 40 | 41 | for file in os.listdir(folder): 42 | if file.endswith(".pdos"): 43 | path = os.path.join(folder, file) 44 | 45 | if "BETA" in file: 46 | i_spin = 1 47 | else: 48 | i_spin = 0 49 | 50 | pdos, kind = read_and_process_pdos_file(path) 51 | 52 | if "list1" in file: 53 | dos["mol"][i_spin] = pdos 54 | elif "list" in file: 55 | num = re.search("list(.*)-", file).group(1) 56 | label = f"sel_{num}" 57 | if label not in dos: 58 | dos[label] = [None] * nspin 59 | dos[label][i_spin] = pdos 60 | elif "k" in file: 61 | # remove any digits from kind 62 | kind = "".join([c for c in kind if not c.isdigit()]) 63 | label = f"kind_{kind}" 64 | if label not in dos: 65 | dos[label] = [None] * nspin 66 | if dos[label][i_spin] is not None: 67 | dos[label][i_spin][:, 1] += pdos[:, 1] 68 | else: 69 | dos[label][i_spin] = pdos 70 | 71 | tdos = None 72 | for k in dos: 73 | if k.startswith("kind"): 74 | if tdos is None: 75 | tdos = copy.deepcopy(dos[k]) 76 | else: 77 | for i_spin in range(nspin): 78 | tdos[i_spin][:, 1] += dos[k][i_spin][:, 1] 79 | dos["tdos"] = tdos 80 | return dos 81 | 82 | 83 | def create_series_w_broadening(x_values, y_values, x_arr, fwhm, shape="g"): 84 | spectrum = np.zeros(len(x_arr)) 85 | 86 | def lorentzian(x_): 87 | # factor = np.pi*fwhm/2 # to make maximum 1.0 88 | return 0.5 * fwhm / (np.pi * (x_**2 + (0.5 * fwhm) ** 2)) 89 | 90 | def gaussian(x_): 91 | sigma = fwhm / 2.3548 92 | return 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-(x_**2) / (2 * sigma**2)) 93 | 94 | for xv, yv in zip(x_values, y_values): 95 | if shape == "g": 96 | spectrum += yv * gaussian(x_arr - xv) 97 | else: 98 | spectrum += yv * lorentzian(x_arr - xv) 99 | return spectrum 100 | 101 | 102 | def load_overlap_npz(npz_path): 103 | loaded_data = np.load(npz_path, allow_pickle=True) 104 | 105 | metadata = loaded_data["metadata"][0] 106 | 107 | overlap_matrix = [] 108 | 109 | for i_spin_g1 in range(metadata["nspin_g1"]): 110 | overlap_matrix.append([]) 111 | for i_spin_g2 in range(metadata["nspin_g2"]): 112 | overlap_matrix[-1].append(loaded_data[f"overlap_matrix_s{i_spin_g1}s{i_spin_g2}"]) 113 | 114 | energies_g1 = [] 115 | for i_spin_g1 in range(metadata["nspin_g1"]): 116 | energies_g1.append(loaded_data[f"energies_g1_s{i_spin_g1}"]) 117 | 118 | energies_g2 = [] 119 | orb_indexes_g2 = [] 120 | for i_spin_g2 in range(metadata["nspin_g2"]): 121 | energies_g2.append(loaded_data[f"energies_g2_s{i_spin_g2}"]) 122 | orb_indexes_g2.append(loaded_data[f"orb_indexes_g2_s{i_spin_g2}"]) 123 | 124 | overlap_data = { 125 | "nspin_g1": metadata["nspin_g1"], 126 | "nspin_g2": metadata["nspin_g2"], 127 | "homo_i_g2": metadata["homo_i_g2"], 128 | "overlap_matrix": overlap_matrix, 129 | "energies_g1": energies_g1, 130 | "energies_g2": energies_g2, 131 | "orb_indexes_g2": orb_indexes_g2, 132 | } 133 | 134 | return overlap_data 135 | 136 | 137 | def match_and_reduce_spin_channels(om): 138 | # In principle, for high-spin states such as triplet, we could assume 139 | # that alpha is always the higher-populated spin. 140 | # However, for uks-1, we have to check both configurations and pick higher overlap, 141 | # so might as well do it in all cases 142 | 143 | # In rks case, just remove the 2nd spin index 144 | if len(om[0]) == 1: 145 | return [om[0][0]] 146 | # Uks case: 147 | # same spin channels 148 | same_contrib = 0 149 | for i_spin in range(2): 150 | same_contrib = np.sum(om[i_spin][i_spin]) 151 | # opposite spin channels 152 | oppo_contrib = 0 153 | for i_spin in range(2): 154 | oppo_contrib = np.sum(om[i_spin][(i_spin + 1) % 2]) 155 | 156 | if same_contrib >= oppo_contrib: 157 | return [om[i][i] for i in range(2)] 158 | else: 159 | return [om[i][(i + 1) % 2] for i in range(2)] 160 | 161 | 162 | def get_orbital_label(i_orb_wrt_homo): 163 | if i_orb_wrt_homo < 0: 164 | label = "HOMO%+d" % i_orb_wrt_homo 165 | elif i_orb_wrt_homo == 0: 166 | label = "HOMO" 167 | elif i_orb_wrt_homo == 1: 168 | label = "LUMO" 169 | elif i_orb_wrt_homo > 1: 170 | label = "LUMO%+d" % (i_orb_wrt_homo - 1) 171 | return label 172 | -------------------------------------------------------------------------------- /examples/benzene_cube_from_wfn/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | DATA_PATH="../data/benzene_cp2k_scf/" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir cubes 7 | 8 | cp2k-cube-from-wfn \ 9 | --cp2k_input_file $DATA_PATH/cp2k.inp \ 10 | --basis_set_file $BASIS_PATH \ 11 | --xyz_file $DATA_PATH/geom.xyz \ 12 | --wfn_file $DATA_PATH/PROJ-RESTART.wfn \ 13 | --output_dir ./cubes/ \ 14 | --n_homo 1 \ 15 | --n_lumo 1 \ 16 | --dx 0.8 \ 17 | --eval_cutoff 14.0 \ 18 | # --orb_square \ 19 | # --charge_dens \ 20 | # --spin_dens \ 21 | 22 | -------------------------------------------------------------------------------- /examples/benzene_overlap/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | # for demo, take slab and molecule to be the same calculation 4 | SLAB_FOLDER=../data/benzene_cp2k_scf 5 | MOL_FOLDER=../data/benzene_cp2k_scf 6 | BASIS_PATH="../data/BASIS_MOLOPT" 7 | 8 | mkdir out 9 | 10 | mpirun -n 2 cp2k-overlap-from-wfns \ 11 | --cp2k_input_file1 "$SLAB_FOLDER"/cp2k.inp \ 12 | --basis_set_file1 $BASIS_PATH \ 13 | --xyz_file1 "$SLAB_FOLDER"/geom.xyz \ 14 | --wfn_file1 "$SLAB_FOLDER"/PROJ-RESTART.wfn \ 15 | --emin1 -8.0 \ 16 | --emax1 8.0 \ 17 | --cp2k_input_file2 "$MOL_FOLDER"/cp2k.inp \ 18 | --basis_set_file2 $BASIS_PATH \ 19 | --xyz_file2 "$MOL_FOLDER"/geom.xyz \ 20 | --wfn_file2 "$MOL_FOLDER"/PROJ-RESTART.wfn \ 21 | --nhomo2 2 \ 22 | --nlumo2 2 \ 23 | --output_file "./out/overlap.npz" \ 24 | --eval_region "n-2.0_C" "p2.0_C" "n-2.0_C" "p2.0_C" "n-2.0_C" "p2.0_C" \ 25 | --dx 0.2 \ 26 | --eval_cutoff 14.0 \ 27 | 28 | -------------------------------------------------------------------------------- /examples/benzene_stm/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | FOLDER="../data/benzene_cp2k_scf" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir out 7 | 8 | mpirun -n 2 cp2k-stm-sts-wfn \ 9 | --cp2k_input_file "$FOLDER"/cp2k.inp \ 10 | --basis_set_file $BASIS_PATH \ 11 | --xyz_file "$FOLDER"/geom.xyz \ 12 | --wfn_file "$FOLDER"/PROJ-RESTART.wfn \ 13 | --hartree_file "$FOLDER"/PROJ-v_hartree-1_0.cube \ 14 | --output_file "./out/stm.npz" \ 15 | --orb_output_file "./out/orb.npz" \ 16 | \ 17 | --eval_region "G" "G" "G" "G" "n-1.5_C" "p3.5" \ 18 | --dx 0.15 \ 19 | --eval_cutoff 14.0 \ 20 | --extrap_extent 2.0 \ 21 | --p_tip_ratios 0.0 1.0 \ 22 | \ 23 | --n_homo 4 \ 24 | --n_lumo 4 \ 25 | --orb_heights 3.0 5.0 \ 26 | --orb_isovalues 1e-7 \ 27 | --orb_fwhms 0.1 \ 28 | \ 29 | --energy_range -3.0 3.0 1.0 \ 30 | --heights 4.5 \ 31 | --isovalues 1e-7 \ 32 | --fwhms 0.5 \ 33 | 34 | cd out 35 | cp2k-stm-sts-plot --orb_npz orb.npz --stm_npz stm.npz 36 | 37 | -------------------------------------------------------------------------------- /examples/c2h2_bader_bond_order/ref/neargrid_0.06.txt: -------------------------------------------------------------------------------- 1 | # 0 1 2 3 2 | 0.000000 2.844502 0.959836 0.079855 3 | 2.844502 0.000000 0.079807 0.959846 4 | 0.959836 0.079807 0.000000 0.004290 5 | 0.079855 0.959846 0.004290 0.000000 6 | -------------------------------------------------------------------------------- /examples/c2h2_bader_bond_order/ref/weight_0.06.txt: -------------------------------------------------------------------------------- 1 | # 0 1 2 3 2 | 0.000000 2.736713 1.046777 0.131732 3 | 2.736713 0.000000 0.131685 1.046790 4 | 1.046777 0.131685 0.000000 0.008324 5 | 0.131732 1.046790 0.008324 0.000000 6 | -------------------------------------------------------------------------------- /examples/c2h2_bader_bond_order/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | FOLDER="../data/c2h2_cp2k_scf" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir out 7 | 8 | echo "### 1: calculate charge density cube ###" 9 | 10 | mpirun -n 2 cp2k-cube-from-wfn \ 11 | --cp2k_input_file "$FOLDER"/cp2k.inp \ 12 | --basis_set_file $BASIS_PATH \ 13 | --xyz_file "$FOLDER"/geom.xyz \ 14 | --wfn_file "$FOLDER"/PROJ-RESTART.wfn \ 15 | --output_dir "./out/" \ 16 | \ 17 | --dx 0.1 \ 18 | --eval_cutoff 14.0 \ 19 | --eval_region "n-2.0_H" "p2.0_H" "n-3.0_C" "p3.0_C" "n-3.0_C" "p3.0_C" \ 20 | \ 21 | --n_homo 2 \ 22 | --n_lumo 2 \ 23 | --orb_square \ 24 | \ 25 | --charge_dens \ 26 | --charge_dens_artif_core \ 27 | --spin_dens \ 28 | 29 | echo "### 2: calculate Bader basins ###" 30 | 31 | cd out 32 | 33 | wget http://theory.cm.utexas.edu/henkelman/code/bader/download/bader_lnx_64.tar.gz 34 | 35 | tar -xvf bader_lnx_64.tar.gz 36 | 37 | # neargrid 38 | #./bader -p sel_atom 1 2 3 4 charge_density_artif.cube 39 | # weight 40 | ./bader -b weight -p sel_atom 1 2 3 4 charge_density_artif.cube 41 | 42 | cd .. 43 | 44 | echo "### 3: calculate bond order based on the Bader basins ###" 45 | 46 | mpirun -n 2 cp2k-bader-bond-order \ 47 | --cp2k_input_file "$FOLDER"/cp2k.inp \ 48 | --basis_set_file $BASIS_PATH \ 49 | --xyz_file "$FOLDER"/geom.xyz \ 50 | --wfn_file "$FOLDER"/PROJ-RESTART.wfn \ 51 | \ 52 | --output_file "./out/bond_order.txt" \ 53 | --bader_basins_dir "./out/" \ 54 | \ 55 | --dx 0.1 \ 56 | --eval_cutoff 14.0 \ 57 | --eval_region "n-2.0_H" "p2.0_H" "n-3.0_C" "p3.0_C" "n-3.0_C" "p3.0_C" \ 58 | # --eval_region "G" "G" "G" "G" "G" "G" 59 | 60 | -------------------------------------------------------------------------------- /examples/c2h2_bader_charge_wfn/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | FOLDER="../data/c2h2_cp2k_scf" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir out 7 | 8 | mpirun -n 2 cp2k-cube-from-wfn \ 9 | --cp2k_input_file "$FOLDER"/cp2k.inp \ 10 | --basis_set_file $BASIS_PATH \ 11 | --xyz_file "$FOLDER"/geom.xyz \ 12 | --wfn_file "$FOLDER"/PROJ-RESTART.wfn \ 13 | --output_dir "./out/" \ 14 | \ 15 | --dx 0.08 \ 16 | --eval_cutoff 14.0 \ 17 | --eval_region "G" "G" "G" "G" "G" "G" \ 18 | \ 19 | --charge_dens \ 20 | --charge_dens_artif_core \ 21 | 22 | cd out 23 | 24 | wget http://theory.cm.utexas.edu/henkelman/code/bader/download/bader_lnx_64.tar.gz 25 | 26 | tar -xvf bader_lnx_64.tar.gz 27 | 28 | ./bader -b weight -p all_atom charge_density.cube -ref charge_density_artif.cube 29 | 30 | -------------------------------------------------------------------------------- /examples/clean_all_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for dir in */; do 4 | # Check if the "out" folder exists and delete it 5 | if [ -d "${dir}out" ]; then 6 | echo "Deleting ${dir}out" 7 | rm -rf "${dir}out" 8 | fi 9 | # Check if the "cubes" folder exists and delete it 10 | if [ -d "${dir}cubes" ]; then 11 | echo "Deleting ${dir}cubes" 12 | rm -rf "${dir}cubes" 13 | fi 14 | done -------------------------------------------------------------------------------- /examples/data/benzene_cp2k_scf/PROJ-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/benzene_cp2k_scf/PROJ-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/benzene_cp2k_scf/cp2k.inp: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | METHOD Quickstep 3 | &DFT 4 | BASIS_SET_FILE_NAME BASIS_MOLOPT 5 | POTENTIAL_FILE_NAME POTENTIAL 6 | RESTART_FILE_NAME ./PROJ-RESTART.wfn 7 | &QS 8 | METHOD GPW 9 | EPS_DEFAULT 1.0E-14 10 | EXTRAPOLATION ASPC 11 | EXTRAPOLATION_ORDER 3 12 | &END QS 13 | &MGRID 14 | CUTOFF 600 15 | NGRIDS 5 16 | &END 17 | &SCF 18 | MAX_SCF 100 19 | SCF_GUESS RESTART 20 | 21 | EPS_SCF 1.0E-7 22 | ADDED_MOS 12 23 | CHOLESKY INVERSE 24 | &DIAGONALIZATION 25 | ALGORITHM STANDARD 26 | &END 27 | &MIXING 28 | METHOD BROYDEN_MIXING 29 | ALPHA 0.2 30 | BETA 1.5 31 | NBROYDEN 8 32 | &END 33 | 34 | &OUTER_SCF 35 | MAX_SCF 50 36 | EPS_SCF 1.0E-7 37 | &END 38 | &END SCF 39 | 40 | &POISSON 41 | PERIODIC NONE 42 | PSOLVER MT # cell size double charge density size + 4 ang 43 | &END 44 | 45 | &XC 46 | &XC_FUNCTIONAL PBE 47 | &END XC_FUNCTIONAL 48 | &END XC 49 | &PRINT 50 | &V_HARTREE_CUBE 51 | STRIDE 12 12 12 52 | &END 53 | &MO_CUBES 54 | NHOMO 1 55 | NLUMO 1 56 | STRIDE 12 12 12 57 | &END 58 | &END PRINT 59 | &END DFT 60 | &SUBSYS 61 | &CELL 62 | ABC 14.0 14.0 14.0 63 | &END CELL 64 | &TOPOLOGY 65 | COORD_FILE_NAME ./geom.xyz 66 | COORDINATE xyz 67 | &CENTER_COORDINATES 68 | &END CENTER_COORDINATES 69 | &END 70 | &KIND C 71 | BASIS_SET DZVP-MOLOPT-GTH 72 | POTENTIAL GTH-PBE 73 | &END KIND 74 | &KIND H 75 | BASIS_SET DZVP-MOLOPT-GTH 76 | POTENTIAL GTH-PBE 77 | &END KIND 78 | &END SUBSYS 79 | &END FORCE_EVAL 80 | 81 | &GLOBAL 82 | PRINT_LEVEL LOW 83 | PROJECT PROJ 84 | RUN_TYPE ENERGY 85 | WALLTIME 86000 86 | EXTENDED_FFT_LENGTHS 87 | &END GLOBAL 88 | -------------------------------------------------------------------------------- /examples/data/benzene_cp2k_scf/geom.xyz: -------------------------------------------------------------------------------- 1 | 12 2 | opt geom 3 | C -1.423440710611 0.491143055136 0.000000000000 4 | C -0.726481903054 -0.716025120606 0.000000000000 5 | C 0.667435807556 -0.716025175742 0.000000000000 6 | C 1.364394710611 0.491142944864 0.000000000000 7 | C 0.667435903054 1.698311120606 0.000000000000 8 | C -0.726481807556 1.698311175742 0.000000000000 9 | H -1.268690141798 2.637443644723 0.000000000000 10 | H 1.209644311590 2.637443546694 0.000000000000 11 | H 2.448811453388 0.491142901971 0.000000000000 12 | H 1.209644141798 -1.655157644723 0.000000000000 13 | H -1.268690311590 -1.655157546694 0.000000000000 14 | H -2.507857453388 0.491143098029 0.000000000000 15 | -------------------------------------------------------------------------------- /examples/data/c2h2_cp2k_scf/PROJ-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/c2h2_cp2k_scf/PROJ-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/c2h2_cp2k_scf/cp2k.inp: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | METHOD Quickstep 3 | &DFT 4 | BASIS_SET_FILE_NAME BASIS_MOLOPT 5 | POTENTIAL_FILE_NAME GTH_POTENTIALS 6 | RESTART_FILE_NAME ./PROJ-RESTART.wfn 7 | &QS 8 | METHOD GPW 9 | EPS_DEFAULT 1.0E-14 10 | EXTRAPOLATION ASPC 11 | EXTRAPOLATION_ORDER 3 12 | &END QS 13 | &MGRID 14 | CUTOFF 400 15 | NGRIDS 5 16 | &END 17 | &SCF 18 | 19 | SCF_GUESS ATOMIC 20 | EPS_SCF 1.0E-7 21 | 22 | MAX_SCF 100 23 | CHOLESKY INVERSE 24 | ADDED_MOS 10 25 | # &SMEAR ON 26 | # METHOD FERMI_DIRAC 27 | # ELECTRONIC_TEMPERATURE 100 28 | # &END 29 | &MIXING 30 | METHOD BROYDEN_MIXING 31 | ALPHA 0.1 32 | BETA 1.5 33 | NBROYDEN 8 34 | &END 35 | &DIAGONALIZATION 36 | ALGORITHM STANDARD 37 | &END 38 | 39 | &OUTER_SCF 40 | MAX_SCF 500 41 | EPS_SCF 1.0E-7 42 | &END 43 | &END SCF 44 | 45 | &XC 46 | &XC_FUNCTIONAL PBE 47 | &END XC_FUNCTIONAL 48 | &END XC 49 | &PRINT 50 | &V_HARTREE_CUBE 51 | STRIDE 6 6 6 52 | &END 53 | &MO_CUBES 54 | NHOMO 1 55 | NLUMO 1 56 | STRIDE 6 6 6 57 | &END 58 | &END PRINT 59 | &END DFT 60 | &SUBSYS 61 | &CELL 62 | ABC 10.0 10.0 10.0 63 | &END CELL 64 | &TOPOLOGY 65 | COORD_FILE_NAME ./c2h2-opt.xyz 66 | COORDINATE xyz 67 | # &CENTER_COORDINATES 68 | # &END 69 | &END 70 | &KIND C 71 | BASIS_SET TZV2P-MOLOPT-GTH 72 | POTENTIAL GTH-PBE-q4 73 | &END KIND 74 | &KIND H 75 | BASIS_SET TZV2P-MOLOPT-GTH 76 | POTENTIAL GTH-PBE-q1 77 | &END KIND 78 | &END SUBSYS 79 | &END FORCE_EVAL 80 | &GLOBAL 81 | PRINT_LEVEL LOW 82 | PROJECT PROJ 83 | RUN_TYPE ENERGY 84 | WALLTIME 86000 85 | EXTENDED_FFT_LENGTHS 86 | PREFERRED_DIAG_LIBRARY ELPA 87 | ELPA_KERNEL AUTO 88 | &DBCSR 89 | USE_MPI_ALLOCATOR .FALSE. 90 | &END 91 | &END GLOBAL 92 | 93 | -------------------------------------------------------------------------------- /examples/data/c2h2_cp2k_scf/geom.xyz: -------------------------------------------------------------------------------- 1 | 4 2 | i = 7, E = -12.4756658146 3 | C 4.3964520668 2.0000000000 2.0000000000 4 | C 5.6035480213 2.0000000000 2.0000000000 5 | H 3.3272884681 2.0000000000 2.0000000000 6 | H 6.6727114452 2.0000000000 2.0000000000 7 | -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/PROJ-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/o2_cp2k_scf/PROJ-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/PROJ0-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/o2_cp2k_scf/PROJ0-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/aiida.coords.xyz: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | O 5 5 5 4 | O 6.16 5 5 5 | -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/inp: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | &DFT 3 | BASIS_SET_FILE_NAME BASIS_MOLOPT 4 | CHARGE 0 5 | &MGRID 6 | CUTOFF 400 7 | NGRIDS 5 8 | &END MGRID 9 | MULTIPLICITY 3 10 | UKS .TRUE. 11 | &POISSON 12 | PERIODIC XYZ 13 | POISSON_SOLVER PERIODIC 14 | &END POISSON 15 | POTENTIAL_FILE_NAME POTENTIAL 16 | &PRINT 17 | &MO_CUBES 18 | ADD_LAST NUMERIC 19 | &EACH 20 | GEO_OPT 0 21 | QS_SCF 0 22 | &END EACH 23 | NHOMO 2 24 | NLUMO 2 25 | STRIDE 9 9 9 26 | &END MO_CUBES 27 | &END PRINT 28 | &QS 29 | EPS_DEFAULT 1e-14 30 | EXTRAPOLATION ASPC 31 | EXTRAPOLATION_ORDER 3 32 | METHOD GPW 33 | &END QS 34 | RESTART_FILE_NAME PROJ0-RESTART.wfn 35 | &SCF 36 | MAX_SCF 100 37 | SCF_GUESS RESTART 38 | 39 | EPS_SCF 1.0E-6 40 | ADDED_MOS 10 41 | CHOLESKY INVERSE 42 | &DIAGONALIZATION 43 | ALGORITHM STANDARD 44 | &END 45 | &MIXING 46 | ALPHA 0.1 47 | BETA 1.5 48 | METHOD BROYDEN_MIXING 49 | NBUFFER 8 50 | &END MIXING 51 | &OUTER_SCF 52 | MAX_SCF 50 53 | EPS_SCF 1.0E-6 54 | &END 55 | &END SCF 56 | &XC 57 | &XC_FUNCTIONAL PBE 58 | &END XC_FUNCTIONAL 59 | &END XC 60 | &END DFT 61 | METHOD Quickstep 62 | &SUBSYS 63 | &CELL 64 | A 10 0.0 0.0 65 | B 0.0 10 0.0 66 | C 0.0 0.0 10 67 | PERIODIC XYZ 68 | &END CELL 69 | &KIND O 70 | BASIS_SET TZVP-MOLOPT-GTH-q6 71 | ELEMENT O 72 | MAGNETIZATION 1 73 | POTENTIAL GTH-PBE-q6 74 | &END KIND 75 | &TOPOLOGY 76 | COORD_FILE_FORMAT XYZ 77 | COORD_FILE_NAME aiida.coords.xyz 78 | &END TOPOLOGY 79 | &END SUBSYS 80 | &END FORCE_EVAL 81 | &GLOBAL 82 | &DBCSR 83 | USE_MPI_ALLOCATOR .FALSE. 84 | &END DBCSR 85 | ELPA_KERNEL AUTO 86 | EXTENDED_FFT_LENGTHS 87 | PREFERRED_DIAG_LIBRARY ELPA 88 | PRINT_LEVEL MEDIUM 89 | PROJECT PROJ 90 | RUN_TYPE ENERGY 91 | WALLTIME 1700 92 | &END GLOBAL 93 | -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/inp0: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | &DFT 3 | BASIS_SET_FILE_NAME BASIS_MOLOPT 4 | CHARGE 0 5 | &MGRID 6 | CUTOFF 400 7 | NGRIDS 5 8 | &END MGRID 9 | MULTIPLICITY 3 10 | UKS .TRUE. 11 | &POISSON 12 | PERIODIC XYZ 13 | POISSON_SOLVER PERIODIC 14 | &END POISSON 15 | POTENTIAL_FILE_NAME POTENTIAL 16 | &PRINT 17 | &MO_CUBES 18 | ADD_LAST NUMERIC 19 | &EACH 20 | GEO_OPT 0 21 | QS_SCF 0 22 | &END EACH 23 | NHOMO 2 24 | NLUMO 2 25 | STRIDE 1 1 1 26 | &END MO_CUBES 27 | &END PRINT 28 | &QS 29 | EPS_DEFAULT 1e-14 30 | EXTRAPOLATION ASPC 31 | EXTRAPOLATION_ORDER 3 32 | METHOD GPW 33 | &END QS 34 | RESTART_FILE_NAME PROJ0-RESTART.wfn 35 | &SCF 36 | EPS_SCF 1e-06 37 | MAX_SCF 40 38 | &OT 39 | MINIMIZER CG 40 | PRECONDITIONER FULL_SINGLE_INVERSE 41 | &END OT 42 | &OUTER_SCF 43 | EPS_SCF 1e-06 44 | MAX_SCF 50 45 | &END OUTER_SCF 46 | &PRINT 47 | &RESTART 48 | ADD_LAST NUMERIC 49 | &EACH 50 | GEO_OPT 1 51 | QS_SCF 0 52 | &END EACH 53 | FILENAME RESTART 54 | &END RESTART 55 | &RESTART_HISTORY OFF 56 | BACKUP_COPIES 0 57 | &END RESTART_HISTORY 58 | &END PRINT 59 | SCF_GUESS RESTART 60 | &END SCF 61 | &XC 62 | &XC_FUNCTIONAL PBE 63 | &END XC_FUNCTIONAL 64 | &END XC 65 | &END DFT 66 | METHOD Quickstep 67 | &SUBSYS 68 | &CELL 69 | A 10 0.0 0.0 70 | B 0.0 10 0.0 71 | C 0.0 0.0 10 72 | PERIODIC XYZ 73 | &END CELL 74 | &KIND O 75 | BASIS_SET TZVP-MOLOPT-GTH-q6 76 | ELEMENT O 77 | MAGNETIZATION 1 78 | POTENTIAL GTH-PBE-q6 79 | &END KIND 80 | &TOPOLOGY 81 | COORD_FILE_FORMAT XYZ 82 | COORD_FILE_NAME aiida.coords.xyz 83 | &END TOPOLOGY 84 | &END SUBSYS 85 | &END FORCE_EVAL 86 | &GLOBAL 87 | &DBCSR 88 | USE_MPI_ALLOCATOR .FALSE. 89 | &END DBCSR 90 | ELPA_KERNEL AUTO 91 | EXTENDED_FFT_LENGTHS 92 | PREFERRED_DIAG_LIBRARY ELPA 93 | PRINT_LEVEL MEDIUM 94 | PROJECT PROJ0 95 | RUN_TYPE ENERGY 96 | WALLTIME 12000 97 | &END GLOBAL 98 | -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | #SBATCH --no-requeue 3 | #SBATCH --job-name="orb" 4 | #SBATCH --get-user-env 5 | #SBATCH --output=_scheduler-stdout.txt 6 | #SBATCH --error=_scheduler-stderr.txt 7 | #SBATCH --nodes=1 8 | #SBATCH --ntasks-per-node=4 9 | #SBATCH --cpus-per-task=2 10 | #SBATCH --time=00:30:00 11 | 12 | #SBATCH --partition=normal 13 | #SBATCH --account=s1141 14 | #SBATCH --constraint=gpu 15 | export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK:-1} 16 | source $MODULESHOME/init/bash 17 | export CRAY_CUDA_MPS=1 18 | ulimit -s unlimited 19 | 20 | 21 | module load daint-mc 22 | module load CP2K 23 | 24 | 25 | srun -n $SLURM_NTASKS --ntasks-per-node=$SLURM_NTASKS_PER_NODE -c $SLURM_CPUS_PER_TASK cp2k.psmp -i inp > out 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/data/o2_cp2k_scf/run0: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | #SBATCH --no-requeue 3 | #SBATCH --job-name="orb" 4 | #SBATCH --get-user-env 5 | #SBATCH --output=_scheduler-stdout.txt 6 | #SBATCH --error=_scheduler-stderr.txt 7 | #SBATCH --nodes=1 8 | #SBATCH --ntasks-per-node=4 9 | #SBATCH --cpus-per-task=2 10 | #SBATCH --time=00:30:00 11 | 12 | #SBATCH --partition=normal 13 | #SBATCH --account=s1141 14 | #SBATCH --constraint=gpu 15 | export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK:-1} 16 | source $MODULESHOME/init/bash 17 | export CRAY_CUDA_MPS=1 18 | ulimit -s unlimited 19 | 20 | 21 | module load daint-mc 22 | module load CP2K 23 | 24 | 25 | srun -n $SLURM_NTASKS --ntasks-per-node=$SLURM_NTASKS_PER_NODE -c $SLURM_CPUS_PER_TASK cp2k.psmp -i inp0 > out0 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/data/polyphenylene_cp2k_scf/PROJ-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/polyphenylene_cp2k_scf/PROJ-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/polyphenylene_cp2k_scf/cp2k.inp: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | METHOD Quickstep 3 | &DFT 4 | BASIS_SET_FILE_NAME ./BASIS_MOLOPT 5 | POTENTIAL_FILE_NAME ./POTENTIAL 6 | RESTART_FILE_NAME ./PROJ-RESTART.wfn 7 | &QS 8 | METHOD GPW 9 | EPS_DEFAULT 1.0E-14 10 | EXTRAPOLATION ASPC 11 | EXTRAPOLATION_ORDER 3 12 | &END QS 13 | 14 | &MGRID 15 | CUTOFF 600 16 | NGRIDS 5 17 | &END 18 | 19 | &SCF 20 | 21 | SCF_GUESS RESTART 22 | EPS_SCF 1.0E-7 23 | 24 | MAX_SCF 1000 25 | CHOLESKY INVERSE 26 | ADDED_MOS 20 27 | &MIXING 28 | METHOD BROYDEN_MIXING 29 | ALPHA 0.1 30 | BETA 1.5 31 | NBUFFER 8 32 | &END 33 | &DIAGONALIZATION 34 | ALGORITHM STANDARD 35 | &END 36 | 37 | &OUTER_SCF 38 | MAX_SCF 500 39 | EPS_SCF 1.0E-7 40 | &END 41 | &END SCF 42 | 43 | &XC 44 | &XC_FUNCTIONAL PBE 45 | &END XC_FUNCTIONAL 46 | &END XC 47 | 48 | &PRINT 49 | &V_HARTREE_CUBE 50 | FILENAME HART 51 | STRIDE 24 24 24 52 | &END 53 | &END PRINT 54 | 55 | &END DFT 56 | &SUBSYS 57 | &CELL 58 | ABC 64.0 16.0 16.0 59 | &END CELL 60 | &TOPOLOGY 61 | COORD_FILE_NAME ./ppp_12uc-opt.xyz 62 | COORDINATE xyz 63 | &END 64 | &KIND C 65 | BASIS_SET SZV-MOLOPT-GTH-q4 66 | POTENTIAL GTH-PBE-q4 67 | &END KIND 68 | &KIND H 69 | BASIS_SET SZV-MOLOPT-GTH-q1 70 | POTENTIAL GTH-PBE-q1 71 | &END KIND 72 | &END SUBSYS 73 | &END FORCE_EVAL 74 | 75 | &GLOBAL 76 | PRINT_LEVEL MEDIUM 77 | PROJECT PROJ 78 | RUN_TYPE ENERGY 79 | WALLTIME 86000 80 | EXTENDED_FFT_LENGTHS 81 | &END GLOBAL 82 | 83 | -------------------------------------------------------------------------------- /examples/data/polyphenylene_cp2k_scf/ppp_12uc-opt.xyz: -------------------------------------------------------------------------------- 1 | 122 2 | i = 27, E = -439.2467872427 3 | H 8.2510323796 10.4988554927 8.0000035380 4 | C 9.3415192477 10.5021122983 7.9999818845 5 | H 9.5097407009 12.6562401092 8.0000102804 6 | H 9.5219779891 8.3487004607 7.9999901570 7 | C 10.0542374859 9.3010475851 8.0000175219 8 | C 10.0476587707 11.7071284009 8.0000287820 9 | C 11.4481242685 9.3064306789 7.9999840647 10 | C 11.4417288334 11.7096451772 7.9999970718 11 | H 11.9653468277 8.3483404038 7.9999949828 12 | C 12.1836424993 10.5099920923 8.0000116766 13 | H 11.9536737935 12.6706788910 8.0000180029 14 | H 13.9097928543 12.6719883270 7.9999976419 15 | C 13.6700036616 10.5131226314 8.0000294629 16 | H 13.9160446869 8.3550244798 8.0000255705 17 | C 14.4166478423 11.7082327661 7.9999856951 18 | C 14.4201626535 9.3201719789 7.9999982272 19 | C 15.8098311577 9.3215161207 8.0000281980 20 | C 15.8065141021 11.7104242172 8.0000155179 21 | H 16.3168673909 8.3577638321 8.0000225051 22 | C 16.5566769651 10.5170069067 8.0000092831 23 | H 16.3113338909 12.6753323699 8.0000048796 24 | H 18.2775848102 12.6780005024 7.9999780301 25 | C 18.0384507421 10.5189792032 8.0000118873 26 | H 18.2834819333 8.3601869963 8.0000369806 27 | C 18.7854035619 11.7147108377 7.9999897336 28 | C 18.7883774111 9.3250583593 8.0000191042 29 | C 20.1776805329 9.3268923384 8.0000501720 30 | C 20.1748502150 11.7166224621 8.0000226725 31 | H 20.6853683682 8.3635192742 8.0000234108 32 | C 20.9247415415 10.5226612926 7.9999425363 33 | H 20.6798954043 12.6813870715 7.9999723927 34 | H 22.6471413232 12.6830077142 8.0000486114 35 | C 22.4060535790 10.5238861792 8.0000837516 36 | H 22.6491875068 8.3648547019 7.9999836518 37 | C 23.1540009322 11.7191942504 7.9999804240 38 | C 23.1549268023 9.3292552881 7.9999471376 39 | C 24.5441448763 9.3297801239 8.0000032219 40 | C 24.5432736161 11.7196559200 8.0000359069 41 | H 25.0509415673 8.3659381216 7.9999768631 42 | C 25.2921156709 10.5248957682 8.0000086181 43 | H 25.0494845886 12.6838688524 8.0000383844 44 | H 27.0156123671 12.6836980951 7.9999823555 45 | C 26.7731039993 10.5249113700 7.9999678093 46 | H 27.0147314843 8.3657495490 8.0000083369 47 | C 27.5218669087 11.7196141578 8.0000097924 48 | C 27.5212402145 9.3297550676 8.0000230057 49 | C 28.9104307676 9.3293462688 8.0000107701 50 | C 28.9111782392 11.7192763719 7.9999991788 51 | H 29.4164373719 8.3650944341 8.0000057673 52 | C 29.6592863834 10.5240723269 7.9999624796 53 | H 29.4175556997 12.6833450220 7.9999806927 54 | H 31.3837948108 12.6820170420 8.0000199833 55 | C 31.1404840132 10.5231633398 8.0000542043 56 | H 31.3811215749 8.3639998280 7.9999911021 57 | C 31.8896081045 11.7176373972 7.9999770898 58 | C 31.8882224619 9.3276951390 7.9999614849 59 | C 33.2774942359 9.3267055958 7.9999949122 60 | C 33.2788207887 11.7165596451 8.0000100474 61 | H 33.7831598398 8.3622200496 7.9999849354 62 | C 34.0266927135 10.5212323858 8.0000193874 63 | H 33.7859594218 12.6802215270 8.0000155163 64 | H 35.7488664014 12.6801494453 7.9999870342 65 | C 35.5079022549 10.5211383424 7.9999504119 66 | H 35.7512459992 8.3621134182 7.9999973497 67 | C 36.2558661834 11.7164153147 8.0000249459 68 | C 36.2570072170 9.3265466093 8.0000290113 69 | C 37.6463017878 9.3274371631 7.9999993008 70 | C 37.6451061160 11.7174018374 7.9999956396 71 | H 38.1532074878 8.3636503818 7.9999982214 72 | C 38.3941219743 10.5228537032 7.9999702210 73 | H 38.1509824263 12.6817608580 7.9999936382 74 | H 40.1172561839 12.6824961896 7.9999980540 75 | C 39.8752876149 10.5234249222 8.0000720716 76 | H 40.1174720382 8.3644433405 8.0000138780 77 | C 40.6237204667 11.7184225963 7.9999489788 78 | C 40.6238041990 9.3285135475 7.9999559523 79 | C 42.0129434325 9.3284517077 8.0000433886 80 | C 42.0130109523 11.7183099243 8.0000359183 81 | H 42.5192932597 8.3643775762 8.0000101183 82 | C 42.7614576097 10.5233395028 7.9999748420 83 | H 42.5196102771 12.6823058755 7.9999984818 84 | H 44.4848451894 12.6820005342 8.0000102206 85 | C 44.2425651739 10.5230906539 8.0000204010 86 | H 44.4842509240 8.3639286869 8.0000185231 87 | C 44.9911762873 11.7178940512 8.0000035055 88 | C 44.9907504657 9.3279399456 8.0000091224 89 | C 46.3799986582 9.3276911683 8.0000213515 90 | C 46.3804310610 11.7176128325 8.0000179324 91 | H 46.8861977092 8.3635159227 8.0000074396 92 | C 47.1286022245 10.5225354622 7.9999682821 93 | H 46.8870977305 12.6815392094 7.9999966348 94 | H 48.8515482579 12.6813088572 8.0000079340 95 | C 48.6097846187 10.5223952690 8.0000347483 96 | H 48.8518337478 8.3633724007 7.9999738559 97 | C 49.3581061146 11.7173254638 7.9999739927 98 | C 49.3581943210 9.3274705697 7.9999579074 99 | C 50.7474775546 9.3275769021 8.0000141737 100 | C 50.7474031401 11.7173271146 8.0000289899 101 | H 51.2539937430 8.3635622252 7.9999763548 102 | C 51.4957327663 10.5224993447 7.9999513344 103 | H 51.2539155800 12.6813428210 8.0000038878 104 | H 53.2194774347 12.6814142361 7.9999854541 105 | C 52.9773475384 10.5226557640 8.0000875524 106 | H 53.2199971451 8.3638705915 8.0000525111 107 | C 53.7256171952 11.7172073169 7.9999283779 108 | C 53.7258719036 9.3282282984 7.9999643153 109 | C 55.1155865555 9.3287796595 8.0000850530 110 | C 55.1153840899 11.7169756432 8.0000530052 111 | H 55.6210861150 8.3643312318 8.0000291048 112 | C 55.8637860640 10.5229695405 7.9999149416 113 | H 55.6208444656 12.6814466699 7.9999664830 114 | H 57.5739913411 12.6840873322 7.9999998651 115 | C 57.3500695387 10.5229974528 8.0000218833 116 | H 57.5741658886 8.3618403607 7.9999713047 117 | C 58.0887211145 11.7246137097 7.9999920986 118 | C 58.0887607129 9.3213896846 7.9999756356 119 | C 59.4826793817 9.3199803315 7.9999638341 120 | C 59.4826930356 11.7260848040 7.9999776308 121 | H 60.0177591995 8.3692201150 7.9999683812 122 | C 60.1921202234 10.5230267539 7.9999972369 123 | H 60.0177765176 12.6767940497 7.9999968116 124 | H 61.2825847009 10.5228723765 7.9999790173 125 | -------------------------------------------------------------------------------- /examples/data/polyphenylene_qe_bands/bands.in: -------------------------------------------------------------------------------- 1 | &CONTROL 2 | calculation = 'bands' 3 | forc_conv_thr = 1.0000000000d-04 4 | nstep = 500 5 | outdir = './out/' 6 | prefix = 'aiida' 7 | pseudo_dir = './pseudo/' 8 | verbosity = 'high' 9 | wf_collect = .true. 10 | / 11 | &SYSTEM 12 | degauss = 1.0000000000d-03 13 | ecutrho = 4.0000000000d+02 14 | ecutwfc = 5.0000000000d+01 15 | ibrav = 0 16 | nat = 10 17 | ntyp = 2 18 | occupations = 'smearing' 19 | / 20 | &ELECTRONS 21 | conv_thr = 1.0000000000d-08 22 | electron_maxstep = 50 23 | mixing_beta = 2.5000000000d-01 24 | scf_must_converge = .false. 25 | / 26 | ATOMIC_SPECIES 27 | C 12.011 C.pbe-n-kjpaw_psl.1.0.0.UPF 28 | H 1.008 H.pbe-kjpaw_psl.1.0.0.UPF 29 | ATOMIC_POSITIONS angstrom 30 | C 1.4882287748 9.9343530111 7.5000000000 31 | C 0.7403927550 8.7403446191 7.5000000000 32 | C 1.4882393474 7.5463384392 7.5000000000 33 | C 2.8771544707 7.5463352743 7.5000000000 34 | C 3.6249942816 8.7403419277 7.5000000000 35 | C 2.8771484655 9.9343510018 7.5000000000 36 | H 0.9816065183 10.8985988749 7.5000000000 37 | H 0.9816268197 6.5820898224 7.5000000000 38 | H 3.3837694459 6.5820830565 7.5000000000 39 | H 3.3837598655 10.8985994430 7.5000000000 40 | K_POINTS crystal 41 | 68 42 | 0.0000000000 0.0000000000 0.0000000000 1.0000000000 43 | 0.0074626866 0.0000000000 0.0000000000 1.0000000000 44 | 0.0149253731 0.0000000000 0.0000000000 1.0000000000 45 | 0.0223880597 0.0000000000 0.0000000000 1.0000000000 46 | 0.0298507463 0.0000000000 0.0000000000 1.0000000000 47 | 0.0373134328 0.0000000000 0.0000000000 1.0000000000 48 | 0.0447761194 0.0000000000 0.0000000000 1.0000000000 49 | 0.0522388060 0.0000000000 0.0000000000 1.0000000000 50 | 0.0597014925 0.0000000000 0.0000000000 1.0000000000 51 | 0.0671641791 0.0000000000 0.0000000000 1.0000000000 52 | 0.0746268657 0.0000000000 0.0000000000 1.0000000000 53 | 0.0820895522 0.0000000000 0.0000000000 1.0000000000 54 | 0.0895522388 0.0000000000 0.0000000000 1.0000000000 55 | 0.0970149254 0.0000000000 0.0000000000 1.0000000000 56 | 0.1044776119 0.0000000000 0.0000000000 1.0000000000 57 | 0.1119402985 0.0000000000 0.0000000000 1.0000000000 58 | 0.1194029851 0.0000000000 0.0000000000 1.0000000000 59 | 0.1268656716 0.0000000000 0.0000000000 1.0000000000 60 | 0.1343283582 0.0000000000 0.0000000000 1.0000000000 61 | 0.1417910448 0.0000000000 0.0000000000 1.0000000000 62 | 0.1492537313 0.0000000000 0.0000000000 1.0000000000 63 | 0.1567164179 0.0000000000 0.0000000000 1.0000000000 64 | 0.1641791045 0.0000000000 0.0000000000 1.0000000000 65 | 0.1716417910 0.0000000000 0.0000000000 1.0000000000 66 | 0.1791044776 0.0000000000 0.0000000000 1.0000000000 67 | 0.1865671642 0.0000000000 0.0000000000 1.0000000000 68 | 0.1940298507 0.0000000000 0.0000000000 1.0000000000 69 | 0.2014925373 0.0000000000 0.0000000000 1.0000000000 70 | 0.2089552239 0.0000000000 0.0000000000 1.0000000000 71 | 0.2164179104 0.0000000000 0.0000000000 1.0000000000 72 | 0.2238805970 0.0000000000 0.0000000000 1.0000000000 73 | 0.2313432836 0.0000000000 0.0000000000 1.0000000000 74 | 0.2388059701 0.0000000000 0.0000000000 1.0000000000 75 | 0.2462686567 0.0000000000 0.0000000000 1.0000000000 76 | 0.2537313433 0.0000000000 0.0000000000 1.0000000000 77 | 0.2611940299 0.0000000000 0.0000000000 1.0000000000 78 | 0.2686567164 0.0000000000 0.0000000000 1.0000000000 79 | 0.2761194030 0.0000000000 0.0000000000 1.0000000000 80 | 0.2835820896 0.0000000000 0.0000000000 1.0000000000 81 | 0.2910447761 0.0000000000 0.0000000000 1.0000000000 82 | 0.2985074627 0.0000000000 0.0000000000 1.0000000000 83 | 0.3059701493 0.0000000000 0.0000000000 1.0000000000 84 | 0.3134328358 0.0000000000 0.0000000000 1.0000000000 85 | 0.3208955224 0.0000000000 0.0000000000 1.0000000000 86 | 0.3283582090 0.0000000000 0.0000000000 1.0000000000 87 | 0.3358208955 0.0000000000 0.0000000000 1.0000000000 88 | 0.3432835821 0.0000000000 0.0000000000 1.0000000000 89 | 0.3507462687 0.0000000000 0.0000000000 1.0000000000 90 | 0.3582089552 0.0000000000 0.0000000000 1.0000000000 91 | 0.3656716418 0.0000000000 0.0000000000 1.0000000000 92 | 0.3731343284 0.0000000000 0.0000000000 1.0000000000 93 | 0.3805970149 0.0000000000 0.0000000000 1.0000000000 94 | 0.3880597015 0.0000000000 0.0000000000 1.0000000000 95 | 0.3955223881 0.0000000000 0.0000000000 1.0000000000 96 | 0.4029850746 0.0000000000 0.0000000000 1.0000000000 97 | 0.4104477612 0.0000000000 0.0000000000 1.0000000000 98 | 0.4179104478 0.0000000000 0.0000000000 1.0000000000 99 | 0.4253731343 0.0000000000 0.0000000000 1.0000000000 100 | 0.4328358209 0.0000000000 0.0000000000 1.0000000000 101 | 0.4402985075 0.0000000000 0.0000000000 1.0000000000 102 | 0.4477611940 0.0000000000 0.0000000000 1.0000000000 103 | 0.4552238806 0.0000000000 0.0000000000 1.0000000000 104 | 0.4626865672 0.0000000000 0.0000000000 1.0000000000 105 | 0.4701492537 0.0000000000 0.0000000000 1.0000000000 106 | 0.4776119403 0.0000000000 0.0000000000 1.0000000000 107 | 0.4850746269 0.0000000000 0.0000000000 1.0000000000 108 | 0.4925373134 0.0000000000 0.0000000000 1.0000000000 109 | 0.5000000000 0.0000000000 0.0000000000 1.0000000000 110 | CELL_PARAMETERS angstrom 111 | 4.3651102589 0.0000000000 0.0000000000 112 | 0.0000000000 17.4807780202 0.0000000000 113 | 0.0000000000 0.0000000000 15.0000000000 114 | -------------------------------------------------------------------------------- /examples/data/polyphenylene_qe_bands/scf.in: -------------------------------------------------------------------------------- 1 | &CONTROL 2 | calculation = 'scf' 3 | forc_conv_thr = 1.0000000000d-04 4 | nstep = 500 5 | outdir = './out/' 6 | prefix = 'aiida' 7 | pseudo_dir = './pseudo/' 8 | verbosity = 'high' 9 | wf_collect = .true. 10 | / 11 | &SYSTEM 12 | degauss = 1.0000000000d-03 13 | ecutrho = 4.0000000000d+02 14 | ecutwfc = 5.0000000000d+01 15 | ibrav = 0 16 | nat = 10 17 | ntyp = 2 18 | occupations = 'smearing' 19 | / 20 | &ELECTRONS 21 | conv_thr = 1.0000000000d-08 22 | electron_maxstep = 50 23 | mixing_beta = 2.5000000000d-01 24 | scf_must_converge = .false. 25 | / 26 | ATOMIC_SPECIES 27 | C 12.011 C.pbe-n-kjpaw_psl.1.0.0.UPF 28 | H 1.008 H.pbe-kjpaw_psl.1.0.0.UPF 29 | ATOMIC_POSITIONS angstrom 30 | C 1.4882287748 9.9343530111 7.5000000000 31 | C 0.7403927550 8.7403446191 7.5000000000 32 | C 1.4882393474 7.5463384392 7.5000000000 33 | C 2.8771544707 7.5463352743 7.5000000000 34 | C 3.6249942816 8.7403419277 7.5000000000 35 | C 2.8771484655 9.9343510018 7.5000000000 36 | H 0.9816065183 10.8985988749 7.5000000000 37 | H 0.9816268197 6.5820898224 7.5000000000 38 | H 3.3837694459 6.5820830565 7.5000000000 39 | H 3.3837598655 10.8985994430 7.5000000000 40 | K_POINTS automatic 41 | 51 1 1 0 0 0 42 | CELL_PARAMETERS angstrom 43 | 4.3651102589 0.0000000000 0.0000000000 44 | 0.0000000000 17.4807780202 0.0000000000 45 | 0.0000000000 0.0000000000 15.0000000000 46 | -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/PROJ-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/tbau2_cp2k_scf/PROJ-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/PROJ0-RESTART.wfn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/data/tbau2_cp2k_scf/PROJ0-RESTART.wfn -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/aiida.coords.xyz: -------------------------------------------------------------------------------- 1 | 19 2 | Lattice="18.0 0.0 0.0 0.0 18.0 0.0 0.0 0.0 12.0" Properties=species:S:1:pos:R:3:masses:R:1 pbc="T T T" 3 | Tb 8.99999500 8.62047500 7.33214000 158.92535000 4 | Au 6.05425500 8.61856500 7.14184000 196.96656900 5 | Au 7.52604500 6.06575500 7.13921000 196.96656900 6 | Au 10.47395500 6.06575500 7.13921000 196.96656900 7 | Au 11.94574500 8.61856500 7.14184000 196.96656900 8 | Au 4.57800500 7.71297500 4.66786000 196.96656900 9 | Au 6.03771500 5.19572500 4.67233000 196.96656900 10 | Au 7.54071500 7.74520500 4.67567000 196.96656900 11 | Au 8.99999500 5.14871500 4.67145000 196.96656900 12 | Au 10.45928500 7.74520500 4.67567000 196.96656900 13 | Au 11.96228500 5.19572500 4.67233000 196.96656900 14 | Au 7.52604500 11.17183500 7.13921000 196.96656900 15 | Au 10.47395500 11.17183500 7.13921000 196.96656900 16 | Au 6.03771500 10.30181500 4.67233000 196.96656900 17 | Au 7.54071500 12.85128500 4.67567000 196.96656900 18 | Au 8.99999500 10.25479500 4.67145000 196.96656900 19 | Au 10.45928500 12.85128500 4.67567000 196.96656900 20 | Au 11.96228500 10.30181500 4.67233000 196.96656900 21 | Au 13.42199500 7.71297500 4.66786000 196.96656900 22 | -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/inp: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | &DFT 3 | BASIS_SET_FILE_NAME BASIS_MOLOPT 4 | CHARGE 0 5 | &MGRID 6 | CUTOFF 1600 7 | NGRIDS 5 8 | &END MGRID 9 | MULTIPLICITY 2 10 | UKS .TRUE. 11 | &POISSON 12 | PERIODIC XYZ 13 | POISSON_SOLVER PERIODIC 14 | &END POISSON 15 | POTENTIAL_FILE_NAME POTENTIAL 16 | &PRINT 17 | &MO_CUBES 18 | ADD_LAST NUMERIC 19 | &EACH 20 | GEO_OPT 0 21 | QS_SCF 0 22 | &END EACH 23 | NHOMO 2 24 | NLUMO 2 25 | STRIDE 10 10 10 26 | &END MO_CUBES 27 | &END PRINT 28 | &QS 29 | EPS_DEFAULT 1e-14 30 | EXTRAPOLATION ASPC 31 | EXTRAPOLATION_ORDER 3 32 | METHOD GPW 33 | &END QS 34 | RESTART_FILE_NAME PROJ0-RESTART.wfn 35 | &SCF 36 | MAX_SCF 100 37 | SCF_GUESS RESTART 38 | 39 | EPS_SCF 1.0E-3 40 | ADDED_MOS 4 41 | CHOLESKY INVERSE 42 | &DIAGONALIZATION 43 | ALGORITHM STANDARD 44 | &END 45 | &MIXING 46 | ALPHA 0.1 47 | BETA 1.5 48 | METHOD BROYDEN_MIXING 49 | NBUFFER 8 50 | &END MIXING 51 | &OUTER_SCF 52 | MAX_SCF 50 53 | EPS_SCF 1.0E-3 54 | &END 55 | &END SCF 56 | &XC 57 | &XC_FUNCTIONAL PBE 58 | &END XC_FUNCTIONAL 59 | &END XC 60 | &END DFT 61 | METHOD Quickstep 62 | &SUBSYS 63 | &CELL 64 | A 18 0.0 0.0 65 | B 0.0 18 0.0 66 | C 0.0 0.0 18 67 | PERIODIC XYZ 68 | &END CELL 69 | &KIND Tb 70 | BASIS_SET DZVP-MOLOPT-SR-GTH-q19 71 | ELEMENT Tb 72 | MAGNETIZATION 1 73 | POTENTIAL GTH-PBE-q19 74 | &END KIND 75 | &KIND Tb1 76 | BASIS_SET DZVP-MOLOPT-SR-GTH-q19 77 | ELEMENT Tb 78 | MAGNETIZATION -1 79 | POTENTIAL GTH-PBE-q19 80 | &END KIND 81 | &KIND Au 82 | BASIS_SET DZVP-MOLOPT-SR-GTH-q11 83 | ELEMENT Au 84 | POTENTIAL GTH-PBE-q11 85 | &END KIND 86 | &TOPOLOGY 87 | COORD_FILE_FORMAT XYZ 88 | COORD_FILE_NAME aiida.coords.xyz 89 | &END TOPOLOGY 90 | &END SUBSYS 91 | &END FORCE_EVAL 92 | &GLOBAL 93 | &DBCSR 94 | USE_MPI_ALLOCATOR .FALSE. 95 | &END DBCSR 96 | ELPA_KERNEL AUTO 97 | EXTENDED_FFT_LENGTHS 98 | PREFERRED_DIAG_LIBRARY ELPA 99 | PRINT_LEVEL MEDIUM 100 | PROJECT PROJ 101 | RUN_TYPE ENERGY 102 | WALLTIME 1700 103 | &END GLOBAL 104 | -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/inp0: -------------------------------------------------------------------------------- 1 | &FORCE_EVAL 2 | &DFT 3 | BASIS_SET_FILE_NAME BASIS_MOLOPT 4 | CHARGE 0 5 | &MGRID 6 | CUTOFF 1600 7 | NGRIDS 5 8 | &END MGRID 9 | MULTIPLICITY 2 10 | UKS .TRUE. 11 | &POISSON 12 | PERIODIC XYZ 13 | POISSON_SOLVER PERIODIC 14 | &END POISSON 15 | POTENTIAL_FILE_NAME POTENTIAL 16 | &PRINT 17 | &MO_CUBES 18 | ADD_LAST NUMERIC 19 | &EACH 20 | GEO_OPT 0 21 | QS_SCF 0 22 | &END EACH 23 | NHOMO 1 24 | NLUMO 1 25 | STRIDE 1 1 1 26 | &END MO_CUBES 27 | &END PRINT 28 | &QS 29 | EPS_DEFAULT 1e-14 30 | EXTRAPOLATION ASPC 31 | EXTRAPOLATION_ORDER 3 32 | METHOD GPW 33 | &END QS 34 | RESTART_FILE_NAME PROJ0-RESTART.wfn 35 | &SCF 36 | EPS_SCF 1e-06 37 | MAX_SCF 40 38 | &OT 39 | MINIMIZER CG 40 | PRECONDITIONER FULL_SINGLE_INVERSE 41 | &END OT 42 | &OUTER_SCF 43 | EPS_SCF 1e-06 44 | MAX_SCF 50 45 | &END OUTER_SCF 46 | &PRINT 47 | &RESTART 48 | ADD_LAST NUMERIC 49 | &EACH 50 | GEO_OPT 1 51 | QS_SCF 0 52 | &END EACH 53 | FILENAME RESTART 54 | &END RESTART 55 | &RESTART_HISTORY OFF 56 | BACKUP_COPIES 0 57 | &END RESTART_HISTORY 58 | &END PRINT 59 | SCF_GUESS RESTART 60 | &END SCF 61 | &XC 62 | &XC_FUNCTIONAL PBE 63 | &END XC_FUNCTIONAL 64 | &END XC 65 | &END DFT 66 | METHOD Quickstep 67 | &SUBSYS 68 | &CELL 69 | A 18 0.0 0.0 70 | B 0.0 18 0.0 71 | C 0.0 0.0 12 72 | PERIODIC XYZ 73 | &END CELL 74 | &KIND Tb 75 | BASIS_SET DZVP-MOLOPT-SR-GTH-q19 76 | ELEMENT Tb 77 | MAGNETIZATION 1 78 | POTENTIAL GTH-PBE-q19 79 | &END KIND 80 | &KIND Tb1 81 | BASIS_SET DZVP-MOLOPT-SR-GTH-q19 82 | ELEMENT Tb 83 | MAGNETIZATION -1 84 | POTENTIAL GTH-PBE-q19 85 | &END KIND 86 | &KIND Au 87 | BASIS_SET DZVP-MOLOPT-SR-GTH-q11 88 | ELEMENT Au 89 | POTENTIAL GTH-PBE-q11 90 | &END KIND 91 | &TOPOLOGY 92 | COORD_FILE_FORMAT XYZ 93 | COORD_FILE_NAME aiida.coords.xyz 94 | &END TOPOLOGY 95 | &END SUBSYS 96 | &END FORCE_EVAL 97 | &GLOBAL 98 | &DBCSR 99 | USE_MPI_ALLOCATOR .FALSE. 100 | &END DBCSR 101 | ELPA_KERNEL AUTO 102 | EXTENDED_FFT_LENGTHS 103 | PREFERRED_DIAG_LIBRARY ELPA 104 | PRINT_LEVEL MEDIUM 105 | PROJECT PROJ0 106 | RUN_TYPE ENERGY 107 | WALLTIME 12000 108 | &END GLOBAL 109 | -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | #SBATCH --no-requeue 3 | #SBATCH --job-name="orb" 4 | #SBATCH --get-user-env 5 | #SBATCH --output=_scheduler-stdout.txt 6 | #SBATCH --error=_scheduler-stderr.txt 7 | #SBATCH --nodes=8 8 | #SBATCH --ntasks-per-node=12 9 | #SBATCH --cpus-per-task=1 10 | #SBATCH --time=00:30:00 11 | 12 | #SBATCH --partition=normal 13 | #SBATCH --account=s1141 14 | #SBATCH --constraint=gpu 15 | export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK:-1} 16 | source $MODULESHOME/init/bash 17 | export CRAY_CUDA_MPS=1 18 | ulimit -s unlimited 19 | 20 | 21 | module load daint-mc 22 | module load CP2K 23 | 24 | 25 | srun -n $SLURM_NTASKS --ntasks-per-node=$SLURM_NTASKS_PER_NODE -c $SLURM_CPUS_PER_TASK cp2k.psmp -i inp > out 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/data/tbau2_cp2k_scf/run0: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | #SBATCH --no-requeue 3 | #SBATCH --job-name="orb" 4 | #SBATCH --get-user-env 5 | #SBATCH --output=_scheduler-stdout.txt 6 | #SBATCH --error=_scheduler-stderr.txt 7 | #SBATCH --nodes=4 8 | #SBATCH --ntasks-per-node=12 9 | #SBATCH --cpus-per-task=2 10 | #SBATCH --time=03:30:00 11 | 12 | #SBATCH --partition=normal 13 | #SBATCH --account=s1141 14 | #SBATCH --constraint=gpu 15 | export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK:-1} 16 | source $MODULESHOME/init/bash 17 | export CRAY_CUDA_MPS=1 18 | ulimit -s unlimited 19 | 20 | 21 | module load daint-mc 22 | module load CP2K 23 | 24 | 25 | srun -n $SLURM_NTASKS --ntasks-per-node=$SLURM_NTASKS_PER_NODE -c $SLURM_CPUS_PER_TASK cp2k.psmp -i inp0 > out0 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/examples/example.png -------------------------------------------------------------------------------- /examples/o2_cube_from_wfn/check_cube.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "06c0b247", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "import os\n", 14 | "\n", 15 | "from cp2k_spm_tools import cube" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 6, 21 | "id": "8a8ba758", 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "wfn1=cube.Cube()\n", 26 | "wfn2=cube.Cube()\n", 27 | "cube1=\"o2_cp2k_scf/PROJ-WFN_00007_1-1_0.cube\"\n", 28 | "cube2=\"o2_cube_from_wfn/cubes/S0_7_HOMO.cube\"\n", 29 | "cube1=\"tbau2_cp2k_scf/PROJ-WFN_00109_1-1_0.cube\"\n", 30 | "cube2=\"tbau2_cube_from_wfn/cubes/S0_109_HOMO.cube\"\n", 31 | "wfn1.read_cube_file(cube1)\n", 32 | "wfn2.read_cube_file(cube2)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 7, 38 | "id": "9d1e1536", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "dV1=wfn1.dv_au[0][0]*wfn1.dv_au[1][1]*wfn1.dv_au[2][2]\n", 43 | "dV2=wfn2.dv_au[0][0]*wfn2.dv_au[1][1]*wfn2.dv_au[2][2]" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 8, 49 | "id": "d729c553", 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "0.9925371717719614\n", 57 | "0.9905386069533908\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "print(np.sum(wfn1.data *wfn1.data) * dV1)\n", 63 | "print(np.sum(wfn2.data *wfn2.data) * dV2)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 9, 69 | "id": "dab64f33", 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "data": { 74 | "text/plain": [ 75 | "0.0002507599999999992" 76 | ] 77 | }, 78 | "execution_count": 9, 79 | "metadata": {}, 80 | "output_type": "execute_result" 81 | } 82 | ], 83 | "source": [ 84 | "np.max(np.abs(wfn1.data)-np.abs(wfn2.data))" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "id": "612314b7", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [] 94 | } 95 | ], 96 | "metadata": { 97 | "kernelspec": { 98 | "display_name": "Python 3 (ipykernel)", 99 | "language": "python", 100 | "name": "python3" 101 | }, 102 | "language_info": { 103 | "codemirror_mode": { 104 | "name": "ipython", 105 | "version": 3 106 | }, 107 | "file_extension": ".py", 108 | "mimetype": "text/x-python", 109 | "name": "python", 110 | "nbconvert_exporter": "python", 111 | "pygments_lexer": "ipython3", 112 | "version": "3.11.4" 113 | } 114 | }, 115 | "nbformat": 4, 116 | "nbformat_minor": 5 117 | } 118 | -------------------------------------------------------------------------------- /examples/o2_cube_from_wfn/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | DIR="../data/o2_cp2k_scf" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir cubes 7 | 8 | cp2k-cube-from-wfn \ 9 | --cp2k_input_file $DIR/inp \ 10 | --basis_set_file $BASIS_PATH \ 11 | --xyz_file $DIR/aiida.coords.xyz \ 12 | --wfn_file $DIR/PROJ-RESTART.wfn \ 13 | --output_dir ./cubes/ \ 14 | --n_homo 3 \ 15 | --n_lumo 3 \ 16 | --dx .714283 \ 17 | --eval_cutoff 14.0 \ 18 | --do_not_center_atoms 19 | # --orb_square \ 20 | # --charge_dens \ 21 | # --spin_dens \ 22 | 23 | -------------------------------------------------------------------------------- /examples/o2_overlap/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | SLAB_FOLDER=../data/o2_cp2k_scf 4 | MOL_FOLDER=../data/o2_cp2k_scf 5 | BASIS_PATH="../data/BASIS_MOLOPT" 6 | 7 | mkdir out 8 | 9 | mpirun -n 2 cp2k-overlap-from-wfns \ 10 | --cp2k_input_file1 "$SLAB_FOLDER"/inp \ 11 | --basis_set_file1 $BASIS_PATH \ 12 | --xyz_file1 "$SLAB_FOLDER"/aiida.coords.xyz \ 13 | --wfn_file1 "$SLAB_FOLDER"/PROJ-RESTART.wfn \ 14 | --emin1 -18.0 \ 15 | --emax1 8.0 \ 16 | --cp2k_input_file2 "$MOL_FOLDER"/inp \ 17 | --basis_set_file2 $BASIS_PATH \ 18 | --xyz_file2 "$MOL_FOLDER"/aiida.coords.xyz \ 19 | --wfn_file2 "$MOL_FOLDER"/PROJ-RESTART.wfn \ 20 | --nhomo2 2 \ 21 | --nlumo2 2 \ 22 | --output_file "./out/overlap.npz" \ 23 | --eval_region "n-2.0_O" "p2.0_O" "n-2.0_O" "p2.0_O" "n-2.0_O" "p2.0_O" \ 24 | --dx 0.2 \ 25 | --eval_cutoff 14.0 \ 26 | 27 | -------------------------------------------------------------------------------- /examples/run_all_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Loop through all subdirectories 4 | for dir in */ ; do 5 | if [ -f "$dir/run.sh" ]; then 6 | echo "" 7 | echo "# ----" 8 | echo "# Running example in $dir" 9 | echo "# ----" 10 | (cd "$dir" && ./run.sh) 11 | fi 12 | done -------------------------------------------------------------------------------- /examples/tbau2_cube_from_wfn/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | 3 | DIR="../data/tbau2_cp2k_scf" 4 | BASIS_PATH="../data/BASIS_MOLOPT" 5 | 6 | mkdir cubes 7 | 8 | cp2k-cube-from-wfn \ 9 | --cp2k_input_file $DIR/inp \ 10 | --basis_set_file $BASIS_PATH \ 11 | --xyz_file $DIR/aiida.coords.xyz \ 12 | --wfn_file $DIR/PROJ-RESTART.wfn \ 13 | --output_dir ./cubes/ \ 14 | --n_homo 2 \ 15 | --n_lumo 2 \ 16 | --dx .40909086 \ 17 | --eval_cutoff 14.0 \ 18 | --do_not_center_atoms 19 | # --orb_square \ 20 | # --charge_dens \ 21 | # --spin_dens \ 22 | 23 | -------------------------------------------------------------------------------- /hrstm_tips/tip_coeffs.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanotech-empa/cp2k-spm-tools/c1cf95db23ff738fc4492d3ca445367eed7465a9/hrstm_tips/tip_coeffs.tar.gz -------------------------------------------------------------------------------- /notebooks/ftsts_analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "from cp2k_spm_tools import cp2k_grid_orbitals, cp2k_ftsts, qe_utils" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "lat_param = 4.37 # angstrom\n", 23 | "\n", 24 | "wfn_file = \"../examples/data/polyphenylene_cp2k_scf/PROJ-RESTART.wfn\"\n", 25 | "xyz_file = \"../examples/data/polyphenylene_cp2k_scf/ppp_12uc-opt.xyz\"\n", 26 | "cp2k_inp = \"../examples/data/polyphenylene_cp2k_scf/cp2k.inp\"\n", 27 | "basis_file = \"../examples/data/BASIS_MOLOPT\"" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "# define global energy limits (eV)\n", 37 | "emin = -3.5\n", 38 | "emax = 3.5" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "cp2k_grid_orb = cp2k_grid_orbitals.Cp2kGridOrbitals()\n", 48 | "cp2k_grid_orb.read_cp2k_input(cp2k_inp)\n", 49 | "cp2k_grid_orb.read_xyz(xyz_file)\n", 50 | "cp2k_grid_orb.ase_atoms.center()\n", 51 | "cp2k_grid_orb.read_basis_functions(basis_file)\n", 52 | "cp2k_grid_orb.load_restart_wfn_file(wfn_file, emin=emin, emax=emax)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "# define evaluation region (plane)\n", 62 | "\n", 63 | "plane_h = 3.5 # ang\n", 64 | "\n", 65 | "atoms_max_z = np.max(cp2k_grid_orb.ase_atoms.positions[:, 2]) # ang\n", 66 | "plane_z = atoms_max_z+plane_h\n", 67 | "\n", 68 | "eval_reg = [None, None, [plane_z, plane_z]]" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "cp2k_grid_orb.calc_morbs_in_region(0.10,\n", 78 | " x_eval_region = eval_reg[0],\n", 79 | " y_eval_region = eval_reg[1],\n", 80 | " z_eval_region = eval_reg[2],\n", 81 | " reserve_extrap = 0.0,\n", 82 | " pbc = (True, True, False),\n", 83 | " eval_cutoff = 12.0)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "# QE bands (optional)" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "qe_scf_xml = \"../examples/data/polyphenylene_qe_bands/scf.xml\"\n", 100 | "qe_bands_xml = \"../examples/data/polyphenylene_qe_bands/bands.xml\"\n", 101 | "\n", 102 | "qe_kpts = None\n", 103 | "qe_bands = None\n", 104 | "if qe_scf_xml is not None and qe_bands_xml is not None:\n", 105 | " qe_kpts, qe_bands, _ = qe_utils.read_band_data(qe_bands_xml)\n", 106 | " qe_fermi_en = qe_utils.read_scf_data(qe_scf_xml)\n", 107 | " qe_gap_middle = qe_utils.gap_middle(qe_bands[0], qe_fermi_en)\n", 108 | " qe_bands -= qe_gap_middle" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "# FT-STS" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "de = 0.01\n", 125 | "fwhm = 0.1" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "ftsts = cp2k_ftsts.FTSTS(cp2k_grid_orb)\n", 135 | "ftsts.project_orbitals_1d(gauss_pos=0.0, gauss_fwhm=3.0)\n", 136 | "borders = ftsts.take_fts(crop_padding=True, crop_edges=1.2, remove_row_avg=True, padding=3.0)\n", 137 | "ftsts.make_ftldos(emin, emax, de, fwhm)" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [ 146 | "imshow_kwargs = {'aspect': 'auto',\n", 147 | " 'origin': 'lower',\n", 148 | " #'cmap': 'jet',\n", 149 | " 'cmap': 'gist_ncar',\n", 150 | " }" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 12), gridspec_kw={'width_ratios': [3, 1]})\n", 160 | "\n", 161 | "# left side: LDOS\n", 162 | "ax1.imshow(ftsts.ldos.T, extent=ftsts.ldos_extent, vmax=1.0*np.max(ftsts.ldos), **imshow_kwargs)\n", 163 | "ax1.axvline(borders[0], color='r')\n", 164 | "ax1.axvline(borders[1], color='r')\n", 165 | "ax1.set_ylabel(\"Energy (eV)\")\n", 166 | "ax1.set_xlabel(\"x (Å)\")\n", 167 | "\n", 168 | "# right side: FT-LDOS\n", 169 | "ftldos, extent = ftsts.get_ftldos_bz(2, lat_param) # number of Brilliuin zones\n", 170 | "ax2.imshow(ftldos.T, extent=extent, vmax=1.0*np.max(ftldos), **imshow_kwargs)\n", 171 | "\n", 172 | "# add also QE bands\n", 173 | "if qe_bands is not None:\n", 174 | " for qe_band in qe_bands[0,]:\n", 175 | " plt.plot(2*qe_kpts[:, 0]*2*np.pi/lat_param, qe_band, '-', color='r', linewidth=2.0)\n", 176 | "\n", 177 | "ax2.set_ylim([emin, emax])\n", 178 | "ax2.set_xlabel(\"2k (Å$^{-1}$)\")\n", 179 | "\n", 180 | "plt.show()" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "# Plot individual orbitals" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [ 196 | "# select orbitals wrt to HOMO\n", 197 | "index_start = -5\n", 198 | "index_end = 6\n", 199 | "\n", 200 | "i_spin = 0" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "for i_mo_wrt_homo in range(index_end, index_start-1, -1):\n", 210 | "\n", 211 | " i_mo = cp2k_grid_orb.i_homo_glob[i_spin] + i_mo_wrt_homo\n", 212 | "\n", 213 | " global_i = cp2k_grid_orb.cwf.global_morb_indexes[i_spin][i_mo]\n", 214 | "\n", 215 | "\n", 216 | " print(\"%d HOMO%+d, E=%.3f eV\" % (global_i, i_mo_wrt_homo, cp2k_grid_orb.morb_energies[i_spin][i_mo]))\n", 217 | " morb = (cp2k_grid_orb.morb_grids[i_spin][i_mo, :, :, 0]).astype(np.float64)\n", 218 | " morb_amax = np.max(np.abs(morb))\n", 219 | "\n", 220 | "\n", 221 | " fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(16, 3))\n", 222 | " plt.subplots_adjust(wspace=0, hspace=0)\n", 223 | "\n", 224 | " ax1.imshow(morb.T, vmin=-morb_amax, vmax=morb_amax,origin='lower', cmap='seismic')\n", 225 | " ax1.set_xticks([])\n", 226 | " ax1.set_yticks([])\n", 227 | "\n", 228 | " ax2.imshow((morb**2).T,origin='lower', cmap='seismic')\n", 229 | " ax2.set_xticks([])\n", 230 | " ax2.set_yticks([])\n", 231 | "\n", 232 | " plt.show()" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": null, 238 | "metadata": {}, 239 | "outputs": [], 240 | "source": [] 241 | } 242 | ], 243 | "metadata": { 244 | "kernelspec": { 245 | "display_name": "cp2k-spm-tools", 246 | "language": "python", 247 | "name": "python3" 248 | }, 249 | "language_info": { 250 | "codemirror_mode": { 251 | "name": "ipython", 252 | "version": 3 253 | }, 254 | "file_extension": ".py", 255 | "mimetype": "text/x-python", 256 | "name": "python", 257 | "nbconvert_exporter": "python", 258 | "pygments_lexer": "ipython3", 259 | "version": "3.10.16" 260 | } 261 | }, 262 | "nbformat": 4, 263 | "nbformat_minor": 2 264 | } 265 | -------------------------------------------------------------------------------- /notebooks/overlap_viewer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "import cp2k_spm_tools.postprocess.overlap as pp\n", 12 | "\n", 13 | "# NOTE: Generate the output of the examples first!" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "# (optionally) load a CP2K PDOS calculation:\n", 23 | "pdos_folder = None\n", 24 | "dos = pp.process_pdos_files(pdos_folder)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "npz_path = \"../examples/o2_overlap/out/overlap.npz\"\n", 34 | "\n", 35 | "od = pp.load_overlap_npz(npz_path)\n", 36 | "om = pp.match_and_reduce_spin_channels(od['overlap_matrix'])" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "fwhm = 0.10\n", 46 | "de = np.min([fwhm/5, 0.005])\n", 47 | "energy_arr = np.arange(-3.0, 3.0, de)\n", 48 | "\n", 49 | "mpl_def_colors = plt.rcParams['axes.prop_cycle'].by_key()['color']\n", 50 | "\n", 51 | "pdos_series = [\n", 52 | " ['tdos', 'lightgray', 0.02, 'TDOS'],\n", 53 | " ['mol', 'black', 1.0, 'molecule PDOS'],\n", 54 | "]\n", 55 | "\n", 56 | "fig = plt.figure(figsize=(12, 6))\n", 57 | "\n", 58 | "ax1 = plt.gca()\n", 59 | "ylim = [None, None]\n", 60 | "\n", 61 | "mol_series = []\n", 62 | "\n", 63 | "### PDOS\n", 64 | "if dos['mol'][0] is not None:\n", 65 | " for pdos_ser in pdos_series:\n", 66 | " label = pdos_ser[3] if pdos_ser[2] == 1.0 else fr\"${pdos_ser[2]}\\cdot${pdos_ser[3]}\"\n", 67 | " d = dos[pdos_ser[0]]\n", 68 | " for i_spin in range(len(d)):\n", 69 | " series = pp.create_series_w_broadening(d[i_spin][:, 0], d[i_spin][:, 1], energy_arr, fwhm)\n", 70 | " series *= pdos_ser[2]\n", 71 | "\n", 72 | " kwargs = {}\n", 73 | " if i_spin == 0:\n", 74 | " kwargs['label'] = label\n", 75 | " if pdos_ser[0] == 'mol':\n", 76 | " kwargs['zorder'] = 300\n", 77 | " if i_spin == 0:\n", 78 | " ylim[1] = 1.2 * np.max(series)\n", 79 | " else:\n", 80 | " ylim[0] = 1.2 * np.min(-series)\n", 81 | "\n", 82 | " mol_series.append(series)\n", 83 | "\n", 84 | "\n", 85 | " ax1.plot(energy_arr, series * (-2* i_spin + 1), color=pdos_ser[1], **kwargs)\n", 86 | "\n", 87 | " ax1.fill_between(energy_arr, 0.0, series * (-2* i_spin + 1), color=pdos_ser[1], alpha=0.2)\n", 88 | "\n", 89 | "### Overlap\n", 90 | "for i_spin in range(od['nspin_g2']):\n", 91 | " cumulative = None\n", 92 | " for i_orb, energy in enumerate(od['energies_g2'][i_spin]):\n", 93 | " index = od['orb_indexes_g2'][i_spin][i_orb]\n", 94 | " i_wrt_homo = i_orb - od['homo_i_g2'][i_spin]\n", 95 | " label = pp.get_orbital_label(i_wrt_homo)\n", 96 | "\n", 97 | " spin_letter = \"\"\n", 98 | " if od['nspin_g2'] == 2:\n", 99 | " spin_letter = \"a-\" if i_spin == 0 else \"b-\"\n", 100 | "\n", 101 | " full_label = f'MO{index:2} {spin_letter}{label:6} (E={energy:5.2f})'\n", 102 | "\n", 103 | " series = pp.create_series_w_broadening(od['energies_g1'][i_spin], om[i_spin][:, i_orb], energy_arr, fwhm)\n", 104 | "\n", 105 | " if cumulative is None:\n", 106 | " cumulative = series\n", 107 | " else:\n", 108 | " cumulative += series\n", 109 | "\n", 110 | " # possibly due to numerical precision, the cumulative orbital makeup can slightly\n", 111 | " # surpass molecule PDOS. reduce it to the PDOS level\n", 112 | " if len(mol_series) != 0:\n", 113 | " surpass = cumulative > mol_series[i_spin]\n", 114 | " cumulative[surpass] = mol_series[i_spin][surpass]\n", 115 | "\n", 116 | " ax1.fill_between(energy_arr, 0.0, cumulative * (-2* i_spin + 1),\n", 117 | " facecolor=mpl_def_colors[i_orb], alpha=1.0, zorder=-i_orb+100, label=full_label)\n", 118 | "\n", 119 | " if i_spin == 0 and od['nspin_g2'] == 2:\n", 120 | " # add empty legend entries to align the spin channels\n", 121 | " for i in range(len(pdos_series)):\n", 122 | " ax1.fill_between([0.0], 0.0, [0.0], color='w', alpha=0, label=' ')\n", 123 | "\n", 124 | "plt.legend(ncol=od['nspin_g2'], loc='center left',bbox_to_anchor=(1.01, 0.5))\n", 125 | "\n", 126 | "plt.xlim([np.min(energy_arr), np.max(energy_arr)])\n", 127 | "\n", 128 | "if od['nspin_g2'] == 1:\n", 129 | " ylim[0] = 0.0\n", 130 | "plt.ylim(ylim)\n", 131 | "\n", 132 | "plt.axhline(0.0, color='k', lw=2.0, zorder=200)\n", 133 | "\n", 134 | "plt.ylabel(\"Density of States [a.u.]\")\n", 135 | "plt.xlabel(\"$E-E_F$ [eV]\")\n", 136 | "plt.show()" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "cp2k-spm-tools", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.10.16" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 2 168 | } 169 | -------------------------------------------------------------------------------- /notebooks/stm_viewer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "\n", 12 | "import os\n", 13 | "\n", 14 | "from cp2k_spm_tools import igor\n", 15 | "\n", 16 | "# NOTE: generate the example data first!\n", 17 | "\n", 18 | "stm_npz_path = \"../examples/benzene_stm/out/stm.npz\"\n", 19 | "orb_npz_path = \"../examples/benzene_stm/out/orb.npz\"" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import matplotlib\n", 29 | "\n", 30 | "class FormatScalarFormatter(matplotlib.ticker.ScalarFormatter):\n", 31 | " def __init__(self, fformat=\"%1.1f\", offset=True, mathText=True):\n", 32 | " self.fformat = fformat\n", 33 | " matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,\n", 34 | " useMathText=mathText)\n", 35 | " def _set_format(self, vmin, vmax):\n", 36 | " self.format = self.fformat\n", 37 | " if self._useMathText:\n", 38 | " self.format = '$%s$' % matplotlib.ticker._mathdefault(self.format)\n", 39 | "\n", 40 | "\n", 41 | "def make_plot(fig, ax, data, extent, title=None, title_size=None, center0=False, vmin=None, vmax=None, cmap='gist_heat', noadd=False):\n", 42 | " if center0:\n", 43 | " data_amax = np.max(np.abs(data))\n", 44 | " im = ax.imshow(data.T, origin='lower', cmap=cmap, interpolation='bicubic', extent=extent, vmin=-data_amax, vmax=data_amax)\n", 45 | " else:\n", 46 | " im = ax.imshow(data.T, origin='lower', cmap=cmap, interpolation='bicubic', extent=extent, vmin=vmin, vmax=vmax)\n", 47 | "\n", 48 | " if noadd:\n", 49 | " ax.set_xticks([])\n", 50 | " ax.set_yticks([])\n", 51 | " else:\n", 52 | " ax.set_xlabel(r\"x ($\\AA$)\")\n", 53 | " ax.set_ylabel(r\"y ($\\AA$)\")\n", 54 | " #if 1e-3 < np.max(data) < 1e3:\n", 55 | " # cb = fig.colorbar(im, ax=ax)\n", 56 | " #else:\n", 57 | " # cb = fig.colorbar(im, ax=ax, format=FormatScalarFormatter(\"%.1f\"))\n", 58 | " cb = fig.colorbar(im, ax=ax)\n", 59 | " cb.formatter.set_powerlimits((-2, 2))\n", 60 | " cb.update_ticks()\n", 61 | " if title:\n", 62 | " ax.set_title(title)\n", 63 | " if title_size:\n", 64 | " ax.title.set_fontsize(title_size)\n", 65 | " ax.axis('scaled')\n", 66 | "\n", 67 | "\n", 68 | "def make_series_plot(fig, data, voltages):\n", 69 | " for i_bias, bias in enumerate(voltages):\n", 70 | " ax = plt.subplot(1, len(voltages), i_bias+1)\n", 71 | " make_plot(fig, ax, data[:, :, i_bias], title=\"V=%.2f\"%bias, title_size=22, cmap='gist_heat', noadd=True)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "def make_label(info, index=None, homo_index=None):\n", 81 | " if info['type'] == 'const-height sts':\n", 82 | " label = 'ch-sts h=%.1f\\n fwhm=%.2f' % (info['height'], info['fwhm'])\n", 83 | " elif info['type'] == 'const-height stm':\n", 84 | " label = 'ch-stm h=%.1f\\n fwhm=%.2f' % (info['height'], info['fwhm'])\n", 85 | " elif info['type'] == 'const-isovalue sts':\n", 86 | " label = 'cc-sts isov=%.1e\\n fwhm=%.2f' % (info['isovalue'], info['fwhm'])\n", 87 | " elif info['type'] == 'const-isovalue stm':\n", 88 | " label = 'cc-stm isov=%.1e\\n fwhm=%.2f' % (info['isovalue'], info['fwhm'])\n", 89 | "\n", 90 | " elif info['type'] == 'const-height orbital':\n", 91 | " label = 'ch-orb h=%.1f' % info['height']\n", 92 | " elif info['type'] == 'const-isovalue orbital':\n", 93 | " label = 'cc-orb isov=%.1e' % info['isovalue']\n", 94 | "\n", 95 | " if index is not None and homo_index is not None:\n", 96 | "\n", 97 | " i_rel_homo = index - homo_index\n", 98 | "\n", 99 | " if i_rel_homo < 0:\n", 100 | " hl_label = \"HOMO%+d\" % i_rel_homo\n", 101 | " elif i_rel_homo == 0:\n", 102 | " hl_label = \"HOMO\"\n", 103 | " elif i_rel_homo == 1:\n", 104 | " hl_label = \"LUMO\"\n", 105 | " else:\n", 106 | " hl_label = \"LUMO%+d\" % (i_rel_homo-1)\n", 107 | "\n", 108 | " label += \"\\n\"\n", 109 | " label += \"MO %d, \" % index + hl_label\n", 110 | "\n", 111 | " return label" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "# View stm" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "loaded_data = np.load(stm_npz_path, allow_pickle=True)\n", 128 | "loaded_data.files" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "stm_general_info = loaded_data['stm_general_info'][()]\n", 138 | "stm_series_info = loaded_data['stm_series_info']\n", 139 | "stm_series_data = loaded_data['stm_series_data']\n", 140 | "\n", 141 | "e_arr = stm_general_info['energies']\n", 142 | "x_arr = stm_general_info['x_arr'] * 0.529177\n", 143 | "y_arr = stm_general_info['y_arr'] * 0.529177\n", 144 | "\n", 145 | "extent = [np.min(x_arr), np.max(x_arr), np.min(y_arr), np.max(y_arr)]" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "figsize = (12, 3*(extent[3] - extent[2])/(extent[1] - extent[0]))\n", 155 | "\n", 156 | "for i_e, e in enumerate(e_arr):\n", 157 | " fig, ax_arr = plt.subplots(1, 4, figsize=figsize)\n", 158 | " print(\"E = %.2f eV\" % e)\n", 159 | " for i_ax, ax in enumerate(ax_arr):\n", 160 | "\n", 161 | " data = stm_series_data[i_ax]\n", 162 | " info = stm_series_info[i_ax]\n", 163 | " label = make_label(info)\n", 164 | "\n", 165 | " make_plot(fig, ax, data[i_e], extent, title=label, noadd=True)\n", 166 | "\n", 167 | " plt.show()" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "# View orbitals" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "\n", 184 | "loaded_data = np.load(orb_npz_path, allow_pickle=True)" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [ 193 | "s0_orb_general_info = loaded_data['s0_orb_general_info'][()]\n", 194 | "s0_orb_series_info = loaded_data['s0_orb_series_info']\n", 195 | "s0_orb_series_data = loaded_data['s0_orb_series_data']\n", 196 | "\n", 197 | "e_arr = s0_orb_general_info['energies']\n", 198 | "x_arr = s0_orb_general_info['x_arr'] * 0.529177\n", 199 | "y_arr = s0_orb_general_info['y_arr'] * 0.529177\n", 200 | "\n", 201 | "extent = [np.min(x_arr), np.max(x_arr), np.min(y_arr), np.max(y_arr)]" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "for info, data in zip(s0_orb_series_info, s0_orb_series_data):\n", 211 | "\n", 212 | " if info['type'] == 'const-height orbital' and info['height'] == 3.0:\n", 213 | " for i_orb in range(len(s0_orb_general_info['orb_indexes'])):\n", 214 | "\n", 215 | " print(e_arr[i_orb])\n", 216 | "\n", 217 | " label = make_label(\n", 218 | " info,\n", 219 | " s0_orb_general_info['orb_indexes'][i_orb],\n", 220 | " s0_orb_general_info['homo']\n", 221 | " )\n", 222 | "\n", 223 | " fig = plt.figure(figsize=(3, 3))\n", 224 | " ax = plt.gca()\n", 225 | " make_plot(fig, ax, data[i_orb], extent, cmap='seismic', center0=True, title=label, noadd=True)\n", 226 | " plt.show()" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [ 235 | "s0_orb_series_data.shape" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "igorwave = igor.Wave2d(\n", 245 | " data=s0_orb_series_data[0, 0],\n", 246 | " xmin=x_arr[0],\n", 247 | " xmax=x_arr[-1],\n", 248 | " xlabel='x [Angstroms]',\n", 249 | " ymin=y_arr[0],\n", 250 | " ymax=y_arr[-1],\n", 251 | " ylabel='y [Angstroms]',\n", 252 | ")\n", 253 | "igorwave.write(\"orbital.itx\")" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "# Load and format .itx" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "igor_file_path = \"orbital.itx\"\n", 270 | "\n", 271 | "igor_wave = igor.igor_wave_factory(igor_file_path)" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": null, 277 | "metadata": {}, 278 | "outputs": [], 279 | "source": [ 280 | "first_wave = igor_wave[0]\n", 281 | "\n", 282 | "fig = plt.figure(figsize=(20, 4))\n", 283 | "ax = plt.gca()\n", 284 | "\n", 285 | "make_plot(fig, ax, first_wave.data, first_wave.extent,\n", 286 | " vmax=0.9*np.max(first_wave.data),\n", 287 | " vmin=0.0, cmap='gist_gray')\n", 288 | "\n", 289 | "plot_name = os.path.splitext(igor_file_path)[0]\n", 290 | "plt.savefig(plot_name + \".png\", dpi=300, bbox_inches='tight')\n", 291 | "plt.show()" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": null, 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [] 300 | } 301 | ], 302 | "metadata": { 303 | "kernelspec": { 304 | "display_name": "cp2k-spm-tools", 305 | "language": "python", 306 | "name": "python3" 307 | }, 308 | "language_info": { 309 | "codemirror_mode": { 310 | "name": "ipython", 311 | "version": 3 312 | }, 313 | "file_extension": ".py", 314 | "mimetype": "text/x-python", 315 | "name": "python", 316 | "nbconvert_exporter": "python", 317 | "pygments_lexer": "ipython3", 318 | "version": "3.10.16" 319 | } 320 | }, 321 | "nbformat": 4, 322 | "nbformat_minor": 2 323 | } 324 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cp2k-spm-tools" 3 | description = "CP2K Scanning Probe Microscopy tools." 4 | version = "1.5.0" 5 | authors = [ 6 | { name = "Kristjan Eimre", email = "kristjaneimre@gmail.com" }, 7 | { name = "Edoardo Baldi", email = "edoardo.baldi@empa.ch" }, 8 | ] 9 | readme = "README.md" 10 | license = { file = "LICENSE" } 11 | requires-python = ">=3.8" 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "License :: OSI Approved :: MIT License", 15 | "Operating System :: OS Independent", 16 | ] 17 | 18 | dependencies = [ 19 | "numpy >= 1.22, < 2", 20 | "scipy >= 1.10, < 2", 21 | "ase >= 3.15, < 4", 22 | "matplotlib >= 3.0, < 4", 23 | "mpi4py > 3.1", 24 | ] 25 | 26 | [project.urls] 27 | "Homepage" = "https://github.com/nanotech-empa/cp2k-spm-tools" 28 | "Bug Tracker" = "https://github.com/nanotech-empa/cp2k-spm-tools/issues" 29 | 30 | [project.optional-dependencies] 31 | dev = ["ruff", "pre-commit", "bumpver>=2023.1129"] 32 | 33 | [project.scripts] 34 | cp2k-bader-bond-order = "cp2k_spm_tools.cli.bader_bond_order:main" 35 | cp2k-crop-orbs-wfn = "cp2k_spm_tools.cli.crop_orbs_wfn:main" 36 | cp2k-cube-from-wfn = "cp2k_spm_tools.cli.cube_from_wfn:main" 37 | cp2k-cube-operations = "cp2k_spm_tools.cli.cube_operations:main" 38 | cp2k-cube-single-column = "cp2k_spm_tools.cli.cube_single_column:main" 39 | cp2k-cube-split = "cp2k_spm_tools.cli.cube_split:main" 40 | cp2k-hrstm-from-wfn = "cp2k_spm_tools.cli.hrstm_from_wfn:main" 41 | cp2k-overlap-from-wfns = "cp2k_spm_tools.cli.overlap_from_wfns:main" 42 | cp2k-stm-sts-wfn = "cp2k_spm_tools.cli.stm_sts_from_wfn:main" 43 | cp2k-stm-sts-plot = "cp2k_spm_tools.cli.stm_sts_plotter:main" 44 | 45 | [build-system] 46 | requires = ["setuptools>=61.0"] 47 | build-backend = "setuptools.build_meta" 48 | 49 | [tool.setuptools] 50 | packages = [ 51 | "cp2k_spm_tools", 52 | "cp2k_spm_tools.cli", 53 | "cp2k_spm_tools.hrstm_tools", 54 | ] 55 | 56 | [tool.ruff] 57 | line-length = 120 58 | exclude = ["*.ipynb"] 59 | 60 | [tool.ruff.lint] 61 | fixable = ["ALL"] 62 | select = ["E", "F", "I", "W"] 63 | ignore = ["E402", "E501", "E741", "E722"] 64 | 65 | [tool.bumpver] 66 | current_version = "v1.5.0" 67 | version_pattern = "vMAJOR.MINOR.PATCH[PYTAGNUM]" 68 | commit_message = "Bump version {old_version} -> {new_version}" 69 | commit = true 70 | tag = true 71 | push = true 72 | 73 | [tool.bumpver.file_patterns] 74 | "pyproject.toml" = [ 75 | 'version = "{pep440_version}"', 76 | 'current_version = "{version}"', 77 | ] 78 | --------------------------------------------------------------------------------