├── .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 | [](https://zenodo.org/badge/latestdoi/133041124)
4 | [](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 |
--------------------------------------------------------------------------------