conquest2a 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conquest2a-0.2.0/.gitignore +12 -0
- conquest2a-0.2.0/PKG-INFO +271 -0
- conquest2a-0.2.0/README.md +256 -0
- conquest2a-0.2.0/conquest2a/__init__.py +0 -0
- conquest2a-0.2.0/conquest2a/_types.py +14 -0
- conquest2a-0.2.0/conquest2a/algo/angles.py +30 -0
- conquest2a-0.2.0/conquest2a/algo/nn.py +53 -0
- conquest2a-0.2.0/conquest2a/band.py +55 -0
- conquest2a-0.2.0/conquest2a/chden.py +440 -0
- conquest2a-0.2.0/conquest2a/conquest.py +253 -0
- conquest2a-0.2.0/conquest2a/constants.py +36 -0
- conquest2a-0.2.0/conquest2a/eigenvalue.py +84 -0
- conquest2a-0.2.0/conquest2a/elements.txt +1 -0
- conquest2a-0.2.0/conquest2a/pdos.py +143 -0
- conquest2a-0.2.0/conquest2a/read/quantities.py +156 -0
- conquest2a-0.2.0/conquest2a/read/vesta.py +146 -0
- conquest2a-0.2.0/conquest2a/supercell.py +147 -0
- conquest2a-0.2.0/conquest2a/writers.py +260 -0
- conquest2a-0.2.0/pyproject.toml +78 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conquest2a
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A Python utility that deals with some pre- and post-processing functions for the linear scaling DFT program, CONQUEST.
|
|
5
|
+
Project-URL: Homepage, https://github.com/chpxu/CONQUEST_TO_VASP
|
|
6
|
+
Project-URL: Issues, https://github.com/chpxu/CONQUEST_TO_VASP/issues
|
|
7
|
+
Author-email: Chung <dev.chpxu@outlook.com>
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: ase
|
|
10
|
+
Requires-Dist: matplotlib
|
|
11
|
+
Requires-Dist: numpy>=2.0.0
|
|
12
|
+
Requires-Dist: scienceplots
|
|
13
|
+
Requires-Dist: scipy>=1.10.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# CONQUEST2a
|
|
17
|
+
|
|
18
|
+
A [CONQUEST](https://github.com/OrderN/CONQUEST-release/) post-processing tool written in Python to do multiple, useful things:
|
|
19
|
+
- Convert CONQUEST coordinates format into `.vasp` and `.(ext)xyz` formats for quick and easy visualisation, e.g. in [VESTA](https://jp-minerals.org/vesta/en/).
|
|
20
|
+
- Create `xsf` files using `AtomCharge.dat` to visualise net spins
|
|
21
|
+
- Create supercells (larger cells formed of repeats of a unit cell)
|
|
22
|
+
- Process and sort (p)DOS files into something easy to use for plotting via matplotlib
|
|
23
|
+
- Process and sort `BandStructure.dat` into something easy to use for plotting via matplotlib
|
|
24
|
+
- Nearest-neighbour searching
|
|
25
|
+
- Calculation of dihedral and planar angles
|
|
26
|
+
- Charge density post-processing
|
|
27
|
+
- VESTA to CONQUEST coordinates
|
|
28
|
+
## Installation From 0.2.0
|
|
29
|
+
Usage is simple. In your `venv`, simply
|
|
30
|
+
```
|
|
31
|
+
pip3 install numpy scipy ase matplotlib scienceplots conquest2a
|
|
32
|
+
```
|
|
33
|
+
If you are attempting to integrate this directly into your Nix devShell, you will have to manually build the package with `buildPythonPackage`. Support for this as a standalone package will come soon. The [devflake](https://github.com/chpxu/development-flake) in this repo automatically builds and adds it to the devshell environment.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
1. [Initialising your input](#initialising-your-input)
|
|
38
|
+
2. [Bandstructures](#bandstructures)
|
|
39
|
+
3. [pDOS](#pdos)
|
|
40
|
+
4. [kNN](#k-nearest-neighbours)
|
|
41
|
+
5. [Quantities](#quantities)
|
|
42
|
+
6. [Charge density](#charge-density)
|
|
43
|
+
7. [VESTA conversion](#vesta)
|
|
44
|
+
These steps assume you are already in the directory where `Conquest_input` and other relevant files sit. There is however, file path checking + absolute path resolution, for implementing when using in your own scripts, so relative paths _shouldn't_ be an issue.
|
|
45
|
+
|
|
46
|
+
### Initialising your input
|
|
47
|
+
|
|
48
|
+
First, import everything you might want to use:
|
|
49
|
+
```py
|
|
50
|
+
from conquest2a.conquest import * # necessary, (1)
|
|
51
|
+
from conquest2a.supercell import * # for supercell creation
|
|
52
|
+
from conquest2a.writers import * # to write output files to disk
|
|
53
|
+
from conquest2a.pdos import * # to process (p)DOS
|
|
54
|
+
from conquest2a.band import * # to process BandStructure.dat
|
|
55
|
+
from conquest2a.chden import * # to process cube files from CONQUEST
|
|
56
|
+
from conquest2a.read.quantities import * # to process static output files without ASE
|
|
57
|
+
from conquest2a.algo.nn import nearest_neighbours # for nearest-neighbour searching
|
|
58
|
+
```
|
|
59
|
+
Next, get the path to your Conquest coordinates file, and instantiate `(1)` as
|
|
60
|
+
```py
|
|
61
|
+
test_input = conquest_input({1: "Bi", 2: "Mn", 3: "O"}) # replace this dict with your dict
|
|
62
|
+
test_coords_proc = conquest_coordinates_processor("./tests/data/test.dat", test_input)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Your `dict` inside `conquest_input()` will represent be the Conquest species index to element label map. Note that the `dict` integers should match the ones specified in `Conquest_input` and the coordinates file. Please ensure that the element labels represent real elements - the code will error out if it isn't.
|
|
66
|
+
|
|
67
|
+
### k-Nearest Neighbours
|
|
68
|
+
|
|
69
|
+
Traditional nearest-neighbour methods involve searching all atoms and specifying an arbitrary cutoff which is expensive for ridiculously large systems (around tens or hundreds of thousands or more atoms).
|
|
70
|
+
|
|
71
|
+
CONQUEST2a gives each atom a number depending on their location in a Conquest coordinates file.
|
|
72
|
+
|
|
73
|
+
The algorithm used is a periodic KDTree, which automatically finds nearest neighbours using a binary tree. By specifying a number of neighbours $k$, you can then automatically get the $k$ closest neighbours, their interatomic distances, the element and their coordinates, assuming you initialised the Atoms correctly [above](#initialising-your-input). CONQUEST2a's implementation is a wrapper around [SciPy's KDTree](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.html) to interface with the `Atom` class.
|
|
74
|
+
|
|
75
|
+
```py
|
|
76
|
+
|
|
77
|
+
from conquest2a.algo.nn import nearest_neighbours
|
|
78
|
+
conquest_map = conquest_input({1: "O", 2: "Bi", 3: "Mn", 4: "Mn", 5: "Mn", 6: "Mn"})
|
|
79
|
+
path = "./tests/data/test_output_input_coords.in"
|
|
80
|
+
coordsproc = conquest_coordinates_processor(path, conquest_map)
|
|
81
|
+
nn = nearest_neighbours(
|
|
82
|
+
coordsproc, coordsproc.atoms[8]
|
|
83
|
+
)
|
|
84
|
+
nn.get_result(2) # Returns the interatomic distance in BOHR and the associated Atom
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**WARNING**: to make index mapping easier, the first element is ALWAYS the atom you passed in to search around. E.g., to search for the **first** nearest neighbour, ensure the integer passed in to `get_result()` is **2**.
|
|
88
|
+
|
|
89
|
+
### Bandstructures
|
|
90
|
+
|
|
91
|
+
First, ensure `conquest2a.band` is imported at the start of your file.
|
|
92
|
+
|
|
93
|
+
Get the path to your bandstructure file, and initialise the `bst_processor` class:
|
|
94
|
+
|
|
95
|
+
```py
|
|
96
|
+
from conquest2a.band import *
|
|
97
|
+
test_bst = bst_processor("./tests/data/test_BandStructure.dat")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Now, all the bands have been stored in a list of `band`, and can be accessed as `test_bst.bands`. The default CONQUEST BandStructure outputs all k-points as the "x-axis" for all bands and the specific options `Process.BandStrucAxis` may/may not work.
|
|
101
|
+
|
|
102
|
+
You can then just iterate through the `bands` list and plot, see `examples/plot_test_band.py`.
|
|
103
|
+
|
|
104
|
+
### pDOS
|
|
105
|
+
|
|
106
|
+
In CONQUEST, there are multiple types of density of states (DOS) output:
|
|
107
|
+
|
|
108
|
+
1. total DOS (TDOS)
|
|
109
|
+
2. partial DOS (PDOS), either $l$ (angular-momentum) resolved or $lm$-resolved for each atom
|
|
110
|
+
|
|
111
|
+
The relevant source is located in `conquest2a/pdos.py`.
|
|
112
|
+
|
|
113
|
+
To begin, first import the `pdos_processor` classes:
|
|
114
|
+
```py
|
|
115
|
+
|
|
116
|
+
from conquest2a.pdos import pdos_processor, pdos_l_processor, pdos_lm_processor
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If you care only about the total DOS and nothing else, use `pdos_processor` with `lm = "t"`, i.e.:
|
|
120
|
+
```py
|
|
121
|
+
tdos = pdos_processor(conquest_rundir="/yourpath", lm="t")
|
|
122
|
+
tdos.read_file(tdos.all_pdos_files[0]) # reads "DOS.dat"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This will search your directory, here `/yourpath`, for `DOS.dat` (if parameter `lm = "t"`, which is the default), and store the results in `self.blocks`, where each element of the list is a numpy array of pdos values, in order of the spin. In CONQUEST, spins are output in ascending order, so the first "block" is for Spin 1 etc.
|
|
126
|
+
|
|
127
|
+
Alternatively, you can get CONQUEST to output angular momentum-resolved DOS. In this case, you can either use `pdos_l_processor` or `pdos_lm_processor` depending on whether you have `AtomXXXXXXX_l.dat` or `AtomXXXXXXX_lm.dat` files respectively. Since they are also different objects, you may use both too. The valid filenames are then stored in `self.all_pdos_files` as a list of strings. To extract the data from a file, follow the same format
|
|
128
|
+
|
|
129
|
+
```py
|
|
130
|
+
lmpdos = pdos_lm_processor(conquest_rundir="/yourpath") # lm is set automatically
|
|
131
|
+
lmpdos.read_file(lmpdos.all_pdos_files[0]) # reads, e.g. "Atom00000001_lm.dat" if that exists in your directory.
|
|
132
|
+
atom1 = lmpdos.blocks # NOTE: this is a SHALLOW COPY. If you do another read, this will be OVERWRITTEN
|
|
133
|
+
# atom1 = copy.deepcopy(lmpdos.blocks) # you may prefer to do this instead, if you need to read and store all the pdos output separately
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Remark 1: in CONQUEST, you can choose to output pdos only for specific atoms, but nonetheless `self.all_pdos_files` will be in ascending order of atom number.
|
|
137
|
+
|
|
138
|
+
Remark 2: Reading pdos files automatically, and storing all of their data at once, is not implemented.
|
|
139
|
+
|
|
140
|
+
`pdos_lm_processor` and `pdos_l_processor` each have their own methods, `lm_map()` and `l_map()` respectively. This must be called after a read, e.g. as `lmpdos.lm_map()`. This groups each column of pdos files by their $(l,m)$ or $l$-value, thus forming a `dict` like
|
|
141
|
+
```py
|
|
142
|
+
{
|
|
143
|
+
"0,0": [np.array(...), np.array(...)], # [spin 1, spin 2,....]
|
|
144
|
+
"1,-1": [np.array(...), np.array(...)],
|
|
145
|
+
"1,0": [np.array(...), np.array(...)],
|
|
146
|
+
"1,1": [np.array(...), np.array(...)],
|
|
147
|
+
# etc
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
where again the numpy arrays are in ascending order of spins. These dicts can be accessed as `processor_instance.lm_dict` or `processor_instance.l_dict`.
|
|
151
|
+
|
|
152
|
+
See `examples/plot_test_pdos.py` for an example of plotting the data obtained from a pDOS file.
|
|
153
|
+
### Quantities
|
|
154
|
+
|
|
155
|
+
As detailed on the [CONQUEST docs](https://conquest.readthedocs.io/en/latest/ase-conquest.html), you can manage it with [ASE](https://ase-lib.org/) indirectly by setting the flag `IO.WriteOutToASEFile True` in your `Conquest_input` file. Sometimes, you just forget to add flags when you need to, and then proceed to do numerous calculations without ASE, and thus `conquest2a/read/quantities.py` was created.
|
|
156
|
+
|
|
157
|
+
By pointing to a file from a static run, this module will fetch the free energy, Harris-Foulkes energy, DFT total energy, forces on each atom (and assign them to the right `Atom` instances), max force and total stresses from near the end of the file.
|
|
158
|
+
|
|
159
|
+
First, load your species dictionary correctly, according to your coordinates file. Then,
|
|
160
|
+
|
|
161
|
+
```py
|
|
162
|
+
from conquest2a.conquest import *
|
|
163
|
+
from conquest2a.read.quantities import *
|
|
164
|
+
test_input = conquest_input({1: "Bi", 2: "Mn", 3: "O"})
|
|
165
|
+
test_coords_proc = conquest_coordinates_processor("./tests/data/test.dat", test_input)
|
|
166
|
+
output = read_static_file("tests/data/test_output.txt", test_coords_proc) # will do all the quantity fetching automatically
|
|
167
|
+
|
|
168
|
+
output.dft_energy
|
|
169
|
+
output.harris_foulkes_energy
|
|
170
|
+
#...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Forces can be accessed per atom.
|
|
174
|
+
|
|
175
|
+
### Charge density analysis
|
|
176
|
+
If you have a non-spin-polarised calculation, you will get a single `chden.cube` file by default from CONQUEST. If you do have a spin-polarised calculation, you will get both `chden_up.cube` and `chden_dn.cube` files if you haven't changed the string prefix in the input. These files contain volumetric data to allow you to perform charge density analysis. By adding the data, you will get the _total charge density_. By subtracting the spin down data from spin up, you will get spin density differences. Both calculations are supported in this package.
|
|
177
|
+
|
|
178
|
+
To use the class, simply
|
|
179
|
+
```py
|
|
180
|
+
from conquest2a.chden import chden, chden_plot
|
|
181
|
+
example_chden = chden(
|
|
182
|
+
np.array([1, 0, 0]),
|
|
183
|
+
0.0,
|
|
184
|
+
ch1="tests/data/chden_up.cube",
|
|
185
|
+
ch2="tests/data/chden_dn.cube",
|
|
186
|
+
mode="sum",
|
|
187
|
+
)
|
|
188
|
+
filename = None
|
|
189
|
+
chden_plot(example_chden, False).run(filename, log_scale=True)
|
|
190
|
+
```
|
|
191
|
+
The `chden` class reads in the data using ASE, performs the slicing and analysis of the charge density. The class `chden_plot` is a class which provides publication-ready plots using `matplotlib` and `scienceplots`.
|
|
192
|
+
#### Explanation of arguments
|
|
193
|
+
```py
|
|
194
|
+
chden(direction: np.ndarray, offset: float, ch1: str, ch2: str | None, mode: "sum" | "diff" | None)
|
|
195
|
+
```
|
|
196
|
+
- `direction`: length-3 NumPy array specifying the plane
|
|
197
|
+
- `offset`: fractional distance $[0,1]$ this plane is from the origin - the distance is measured from the **centre** of the plane
|
|
198
|
+
- `ch1`: path to a cube file. If you only have one chden file, use this parameter
|
|
199
|
+
- `ch2`: path to another cube file
|
|
200
|
+
- `mode`: set to `sum` or `diff` if you supply two chden files for sum or difference respectively. Will error out if this not `None` and you supply only one file.
|
|
201
|
+
|
|
202
|
+
```py
|
|
203
|
+
chden_plot(chden: chden, show_atoms: bool = False).run(
|
|
204
|
+
filename: str,
|
|
205
|
+
origin = "lower",
|
|
206
|
+
cmap = "viridis",
|
|
207
|
+
aspect = "equal",
|
|
208
|
+
interpolation: str = "lanczos",
|
|
209
|
+
log_scale: bool = False,
|
|
210
|
+
vmin: float,
|
|
211
|
+
vmax: float
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
- `show_atoms` will provide overlays on top of atomic positions with the element - useful for comparison with VESTA/other visualisations
|
|
215
|
+
- `log_scale` will apply `colors.LogNorm()` to get a logarithmic colour bar - useful for revealing details.
|
|
216
|
+
- `aspect` controls the aspect ratio. It defauls to `equals` to get square pixels
|
|
217
|
+
- `origin` sets the origin of the _plot_ in _matplotlib_ to the lower left (it does NOT affect the actual data)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
### VESTA
|
|
221
|
+
|
|
222
|
+
VESTA is a very useful tool to set spin patterns using `Edit > Vectors`. CONQUEST2a now supports reading VESTA files with vector informationn to produce CONQUEST coordinate files. As always, the output should be checked before using it to start any simulation.
|
|
223
|
+
|
|
224
|
+
Usage:
|
|
225
|
+
```py
|
|
226
|
+
from conquest2a.read.vesta import vesta_to_conquest
|
|
227
|
+
from conquest2a.conquest import conquest_input
|
|
228
|
+
species = {1: "O", 2: "Bi", 3: "Co", 4: "Co", 5: "Mn", 6: "Mn"}
|
|
229
|
+
conqin = conquest_input(species_dict=species)
|
|
230
|
+
|
|
231
|
+
vesta_to_conquest(
|
|
232
|
+
"tests/data/test_vesta_to_conquest.vesta",
|
|
233
|
+
"tests/data/test_vesta_to_conquest.coords",
|
|
234
|
+
conqin,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
In CONQUEST, to treat species with different spin (i.e. up/down, collinear spin only), the species entries must be duplicated inside the dictionary `species`. This library treats vectors $(0, 0, 1)$ as spin "up" and $(0, 0, -1)$ as spin "down" when inputting from VESTA. Additionally, it will set the lowest index corresponding to a species as spin up, and then spin down, so `5: "Mn", 6: "Mn"` will make species 5 be spin up and species 6 to be spin down, so make sure you check your `Conquest_input` correctly!
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
### 0.1.0 and older
|
|
241
|
+
Usage is simple and there are **no external library dependencies** (currently). Either use `main.py` from the Releases tab, or clone the repo and copy `main.py` to your desired location.
|
|
242
|
+
|
|
243
|
+
1. Define a `dict` mapping your CONQUEST species to elements. E.g., if `Conquest_input` has a block like this:
|
|
244
|
+
```
|
|
245
|
+
%block ChemicalSpeciesLabel
|
|
246
|
+
1 208.9800000 Bi_SpinUp
|
|
247
|
+
2 208.9800000 Bi_SpinDown
|
|
248
|
+
3 16.000000 O
|
|
249
|
+
%endblock
|
|
250
|
+
```
|
|
251
|
+
Your `dict` will be `{1: "Bi", 2: "Bi", 3: "O"}`. Note that the `dict` integers should match the ones specified in `Conquest_input` and are completely arbitrary. please ensure however, that the element labels represent real elements (see `main.py`, `CONQUEST_INPUT` class)!
|
|
252
|
+
|
|
253
|
+
2. Create an instance of `CONQUEST_INPUT`, e.g. `CONQUEST_INPUT({1: "Bi", 2: "Bi", 3: "O"})`
|
|
254
|
+
|
|
255
|
+
3. Create an instance of `CONQUEST_COORDINATES`, feeding in the coordinate file you want to post-process and the instance of `CONQUEST_INPUT` created in Step 2:
|
|
256
|
+
```py
|
|
257
|
+
conq = CONQUEST_COORDINATES(
|
|
258
|
+
"./test/test.dat", CONQUEST_input=CONQUEST_INPUT({1: "Bi", 2: "Bi", 3: "O"})
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
4. You may now call any of the writers with the path to the desination file, in this case, `test/test.ABC`:
|
|
262
|
+
1. `vasp_writer("test/test.vasp", data=conq)`
|
|
263
|
+
2. `xyz_writer("test/test.xyz", data=conq)`
|
|
264
|
+
3. `extxyz_writer("test/test.extxyz", data=conq)`
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## CONTRIBUTING
|
|
268
|
+
|
|
269
|
+
Edit `main.py` as necessary. Files may be refactored in the future.
|
|
270
|
+
|
|
271
|
+
Dependencies: tested and developed on Python 3.12. Expected to work on Python >= 3.10.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# CONQUEST2a
|
|
2
|
+
|
|
3
|
+
A [CONQUEST](https://github.com/OrderN/CONQUEST-release/) post-processing tool written in Python to do multiple, useful things:
|
|
4
|
+
- Convert CONQUEST coordinates format into `.vasp` and `.(ext)xyz` formats for quick and easy visualisation, e.g. in [VESTA](https://jp-minerals.org/vesta/en/).
|
|
5
|
+
- Create `xsf` files using `AtomCharge.dat` to visualise net spins
|
|
6
|
+
- Create supercells (larger cells formed of repeats of a unit cell)
|
|
7
|
+
- Process and sort (p)DOS files into something easy to use for plotting via matplotlib
|
|
8
|
+
- Process and sort `BandStructure.dat` into something easy to use for plotting via matplotlib
|
|
9
|
+
- Nearest-neighbour searching
|
|
10
|
+
- Calculation of dihedral and planar angles
|
|
11
|
+
- Charge density post-processing
|
|
12
|
+
- VESTA to CONQUEST coordinates
|
|
13
|
+
## Installation From 0.2.0
|
|
14
|
+
Usage is simple. In your `venv`, simply
|
|
15
|
+
```
|
|
16
|
+
pip3 install numpy scipy ase matplotlib scienceplots conquest2a
|
|
17
|
+
```
|
|
18
|
+
If you are attempting to integrate this directly into your Nix devShell, you will have to manually build the package with `buildPythonPackage`. Support for this as a standalone package will come soon. The [devflake](https://github.com/chpxu/development-flake) in this repo automatically builds and adds it to the devshell environment.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
1. [Initialising your input](#initialising-your-input)
|
|
23
|
+
2. [Bandstructures](#bandstructures)
|
|
24
|
+
3. [pDOS](#pdos)
|
|
25
|
+
4. [kNN](#k-nearest-neighbours)
|
|
26
|
+
5. [Quantities](#quantities)
|
|
27
|
+
6. [Charge density](#charge-density)
|
|
28
|
+
7. [VESTA conversion](#vesta)
|
|
29
|
+
These steps assume you are already in the directory where `Conquest_input` and other relevant files sit. There is however, file path checking + absolute path resolution, for implementing when using in your own scripts, so relative paths _shouldn't_ be an issue.
|
|
30
|
+
|
|
31
|
+
### Initialising your input
|
|
32
|
+
|
|
33
|
+
First, import everything you might want to use:
|
|
34
|
+
```py
|
|
35
|
+
from conquest2a.conquest import * # necessary, (1)
|
|
36
|
+
from conquest2a.supercell import * # for supercell creation
|
|
37
|
+
from conquest2a.writers import * # to write output files to disk
|
|
38
|
+
from conquest2a.pdos import * # to process (p)DOS
|
|
39
|
+
from conquest2a.band import * # to process BandStructure.dat
|
|
40
|
+
from conquest2a.chden import * # to process cube files from CONQUEST
|
|
41
|
+
from conquest2a.read.quantities import * # to process static output files without ASE
|
|
42
|
+
from conquest2a.algo.nn import nearest_neighbours # for nearest-neighbour searching
|
|
43
|
+
```
|
|
44
|
+
Next, get the path to your Conquest coordinates file, and instantiate `(1)` as
|
|
45
|
+
```py
|
|
46
|
+
test_input = conquest_input({1: "Bi", 2: "Mn", 3: "O"}) # replace this dict with your dict
|
|
47
|
+
test_coords_proc = conquest_coordinates_processor("./tests/data/test.dat", test_input)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Your `dict` inside `conquest_input()` will represent be the Conquest species index to element label map. Note that the `dict` integers should match the ones specified in `Conquest_input` and the coordinates file. Please ensure that the element labels represent real elements - the code will error out if it isn't.
|
|
51
|
+
|
|
52
|
+
### k-Nearest Neighbours
|
|
53
|
+
|
|
54
|
+
Traditional nearest-neighbour methods involve searching all atoms and specifying an arbitrary cutoff which is expensive for ridiculously large systems (around tens or hundreds of thousands or more atoms).
|
|
55
|
+
|
|
56
|
+
CONQUEST2a gives each atom a number depending on their location in a Conquest coordinates file.
|
|
57
|
+
|
|
58
|
+
The algorithm used is a periodic KDTree, which automatically finds nearest neighbours using a binary tree. By specifying a number of neighbours $k$, you can then automatically get the $k$ closest neighbours, their interatomic distances, the element and their coordinates, assuming you initialised the Atoms correctly [above](#initialising-your-input). CONQUEST2a's implementation is a wrapper around [SciPy's KDTree](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.html) to interface with the `Atom` class.
|
|
59
|
+
|
|
60
|
+
```py
|
|
61
|
+
|
|
62
|
+
from conquest2a.algo.nn import nearest_neighbours
|
|
63
|
+
conquest_map = conquest_input({1: "O", 2: "Bi", 3: "Mn", 4: "Mn", 5: "Mn", 6: "Mn"})
|
|
64
|
+
path = "./tests/data/test_output_input_coords.in"
|
|
65
|
+
coordsproc = conquest_coordinates_processor(path, conquest_map)
|
|
66
|
+
nn = nearest_neighbours(
|
|
67
|
+
coordsproc, coordsproc.atoms[8]
|
|
68
|
+
)
|
|
69
|
+
nn.get_result(2) # Returns the interatomic distance in BOHR and the associated Atom
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**WARNING**: to make index mapping easier, the first element is ALWAYS the atom you passed in to search around. E.g., to search for the **first** nearest neighbour, ensure the integer passed in to `get_result()` is **2**.
|
|
73
|
+
|
|
74
|
+
### Bandstructures
|
|
75
|
+
|
|
76
|
+
First, ensure `conquest2a.band` is imported at the start of your file.
|
|
77
|
+
|
|
78
|
+
Get the path to your bandstructure file, and initialise the `bst_processor` class:
|
|
79
|
+
|
|
80
|
+
```py
|
|
81
|
+
from conquest2a.band import *
|
|
82
|
+
test_bst = bst_processor("./tests/data/test_BandStructure.dat")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Now, all the bands have been stored in a list of `band`, and can be accessed as `test_bst.bands`. The default CONQUEST BandStructure outputs all k-points as the "x-axis" for all bands and the specific options `Process.BandStrucAxis` may/may not work.
|
|
86
|
+
|
|
87
|
+
You can then just iterate through the `bands` list and plot, see `examples/plot_test_band.py`.
|
|
88
|
+
|
|
89
|
+
### pDOS
|
|
90
|
+
|
|
91
|
+
In CONQUEST, there are multiple types of density of states (DOS) output:
|
|
92
|
+
|
|
93
|
+
1. total DOS (TDOS)
|
|
94
|
+
2. partial DOS (PDOS), either $l$ (angular-momentum) resolved or $lm$-resolved for each atom
|
|
95
|
+
|
|
96
|
+
The relevant source is located in `conquest2a/pdos.py`.
|
|
97
|
+
|
|
98
|
+
To begin, first import the `pdos_processor` classes:
|
|
99
|
+
```py
|
|
100
|
+
|
|
101
|
+
from conquest2a.pdos import pdos_processor, pdos_l_processor, pdos_lm_processor
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If you care only about the total DOS and nothing else, use `pdos_processor` with `lm = "t"`, i.e.:
|
|
105
|
+
```py
|
|
106
|
+
tdos = pdos_processor(conquest_rundir="/yourpath", lm="t")
|
|
107
|
+
tdos.read_file(tdos.all_pdos_files[0]) # reads "DOS.dat"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This will search your directory, here `/yourpath`, for `DOS.dat` (if parameter `lm = "t"`, which is the default), and store the results in `self.blocks`, where each element of the list is a numpy array of pdos values, in order of the spin. In CONQUEST, spins are output in ascending order, so the first "block" is for Spin 1 etc.
|
|
111
|
+
|
|
112
|
+
Alternatively, you can get CONQUEST to output angular momentum-resolved DOS. In this case, you can either use `pdos_l_processor` or `pdos_lm_processor` depending on whether you have `AtomXXXXXXX_l.dat` or `AtomXXXXXXX_lm.dat` files respectively. Since they are also different objects, you may use both too. The valid filenames are then stored in `self.all_pdos_files` as a list of strings. To extract the data from a file, follow the same format
|
|
113
|
+
|
|
114
|
+
```py
|
|
115
|
+
lmpdos = pdos_lm_processor(conquest_rundir="/yourpath") # lm is set automatically
|
|
116
|
+
lmpdos.read_file(lmpdos.all_pdos_files[0]) # reads, e.g. "Atom00000001_lm.dat" if that exists in your directory.
|
|
117
|
+
atom1 = lmpdos.blocks # NOTE: this is a SHALLOW COPY. If you do another read, this will be OVERWRITTEN
|
|
118
|
+
# atom1 = copy.deepcopy(lmpdos.blocks) # you may prefer to do this instead, if you need to read and store all the pdos output separately
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Remark 1: in CONQUEST, you can choose to output pdos only for specific atoms, but nonetheless `self.all_pdos_files` will be in ascending order of atom number.
|
|
122
|
+
|
|
123
|
+
Remark 2: Reading pdos files automatically, and storing all of their data at once, is not implemented.
|
|
124
|
+
|
|
125
|
+
`pdos_lm_processor` and `pdos_l_processor` each have their own methods, `lm_map()` and `l_map()` respectively. This must be called after a read, e.g. as `lmpdos.lm_map()`. This groups each column of pdos files by their $(l,m)$ or $l$-value, thus forming a `dict` like
|
|
126
|
+
```py
|
|
127
|
+
{
|
|
128
|
+
"0,0": [np.array(...), np.array(...)], # [spin 1, spin 2,....]
|
|
129
|
+
"1,-1": [np.array(...), np.array(...)],
|
|
130
|
+
"1,0": [np.array(...), np.array(...)],
|
|
131
|
+
"1,1": [np.array(...), np.array(...)],
|
|
132
|
+
# etc
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
where again the numpy arrays are in ascending order of spins. These dicts can be accessed as `processor_instance.lm_dict` or `processor_instance.l_dict`.
|
|
136
|
+
|
|
137
|
+
See `examples/plot_test_pdos.py` for an example of plotting the data obtained from a pDOS file.
|
|
138
|
+
### Quantities
|
|
139
|
+
|
|
140
|
+
As detailed on the [CONQUEST docs](https://conquest.readthedocs.io/en/latest/ase-conquest.html), you can manage it with [ASE](https://ase-lib.org/) indirectly by setting the flag `IO.WriteOutToASEFile True` in your `Conquest_input` file. Sometimes, you just forget to add flags when you need to, and then proceed to do numerous calculations without ASE, and thus `conquest2a/read/quantities.py` was created.
|
|
141
|
+
|
|
142
|
+
By pointing to a file from a static run, this module will fetch the free energy, Harris-Foulkes energy, DFT total energy, forces on each atom (and assign them to the right `Atom` instances), max force and total stresses from near the end of the file.
|
|
143
|
+
|
|
144
|
+
First, load your species dictionary correctly, according to your coordinates file. Then,
|
|
145
|
+
|
|
146
|
+
```py
|
|
147
|
+
from conquest2a.conquest import *
|
|
148
|
+
from conquest2a.read.quantities import *
|
|
149
|
+
test_input = conquest_input({1: "Bi", 2: "Mn", 3: "O"})
|
|
150
|
+
test_coords_proc = conquest_coordinates_processor("./tests/data/test.dat", test_input)
|
|
151
|
+
output = read_static_file("tests/data/test_output.txt", test_coords_proc) # will do all the quantity fetching automatically
|
|
152
|
+
|
|
153
|
+
output.dft_energy
|
|
154
|
+
output.harris_foulkes_energy
|
|
155
|
+
#...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Forces can be accessed per atom.
|
|
159
|
+
|
|
160
|
+
### Charge density analysis
|
|
161
|
+
If you have a non-spin-polarised calculation, you will get a single `chden.cube` file by default from CONQUEST. If you do have a spin-polarised calculation, you will get both `chden_up.cube` and `chden_dn.cube` files if you haven't changed the string prefix in the input. These files contain volumetric data to allow you to perform charge density analysis. By adding the data, you will get the _total charge density_. By subtracting the spin down data from spin up, you will get spin density differences. Both calculations are supported in this package.
|
|
162
|
+
|
|
163
|
+
To use the class, simply
|
|
164
|
+
```py
|
|
165
|
+
from conquest2a.chden import chden, chden_plot
|
|
166
|
+
example_chden = chden(
|
|
167
|
+
np.array([1, 0, 0]),
|
|
168
|
+
0.0,
|
|
169
|
+
ch1="tests/data/chden_up.cube",
|
|
170
|
+
ch2="tests/data/chden_dn.cube",
|
|
171
|
+
mode="sum",
|
|
172
|
+
)
|
|
173
|
+
filename = None
|
|
174
|
+
chden_plot(example_chden, False).run(filename, log_scale=True)
|
|
175
|
+
```
|
|
176
|
+
The `chden` class reads in the data using ASE, performs the slicing and analysis of the charge density. The class `chden_plot` is a class which provides publication-ready plots using `matplotlib` and `scienceplots`.
|
|
177
|
+
#### Explanation of arguments
|
|
178
|
+
```py
|
|
179
|
+
chden(direction: np.ndarray, offset: float, ch1: str, ch2: str | None, mode: "sum" | "diff" | None)
|
|
180
|
+
```
|
|
181
|
+
- `direction`: length-3 NumPy array specifying the plane
|
|
182
|
+
- `offset`: fractional distance $[0,1]$ this plane is from the origin - the distance is measured from the **centre** of the plane
|
|
183
|
+
- `ch1`: path to a cube file. If you only have one chden file, use this parameter
|
|
184
|
+
- `ch2`: path to another cube file
|
|
185
|
+
- `mode`: set to `sum` or `diff` if you supply two chden files for sum or difference respectively. Will error out if this not `None` and you supply only one file.
|
|
186
|
+
|
|
187
|
+
```py
|
|
188
|
+
chden_plot(chden: chden, show_atoms: bool = False).run(
|
|
189
|
+
filename: str,
|
|
190
|
+
origin = "lower",
|
|
191
|
+
cmap = "viridis",
|
|
192
|
+
aspect = "equal",
|
|
193
|
+
interpolation: str = "lanczos",
|
|
194
|
+
log_scale: bool = False,
|
|
195
|
+
vmin: float,
|
|
196
|
+
vmax: float
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
- `show_atoms` will provide overlays on top of atomic positions with the element - useful for comparison with VESTA/other visualisations
|
|
200
|
+
- `log_scale` will apply `colors.LogNorm()` to get a logarithmic colour bar - useful for revealing details.
|
|
201
|
+
- `aspect` controls the aspect ratio. It defauls to `equals` to get square pixels
|
|
202
|
+
- `origin` sets the origin of the _plot_ in _matplotlib_ to the lower left (it does NOT affect the actual data)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
### VESTA
|
|
206
|
+
|
|
207
|
+
VESTA is a very useful tool to set spin patterns using `Edit > Vectors`. CONQUEST2a now supports reading VESTA files with vector informationn to produce CONQUEST coordinate files. As always, the output should be checked before using it to start any simulation.
|
|
208
|
+
|
|
209
|
+
Usage:
|
|
210
|
+
```py
|
|
211
|
+
from conquest2a.read.vesta import vesta_to_conquest
|
|
212
|
+
from conquest2a.conquest import conquest_input
|
|
213
|
+
species = {1: "O", 2: "Bi", 3: "Co", 4: "Co", 5: "Mn", 6: "Mn"}
|
|
214
|
+
conqin = conquest_input(species_dict=species)
|
|
215
|
+
|
|
216
|
+
vesta_to_conquest(
|
|
217
|
+
"tests/data/test_vesta_to_conquest.vesta",
|
|
218
|
+
"tests/data/test_vesta_to_conquest.coords",
|
|
219
|
+
conqin,
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
In CONQUEST, to treat species with different spin (i.e. up/down, collinear spin only), the species entries must be duplicated inside the dictionary `species`. This library treats vectors $(0, 0, 1)$ as spin "up" and $(0, 0, -1)$ as spin "down" when inputting from VESTA. Additionally, it will set the lowest index corresponding to a species as spin up, and then spin down, so `5: "Mn", 6: "Mn"` will make species 5 be spin up and species 6 to be spin down, so make sure you check your `Conquest_input` correctly!
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
### 0.1.0 and older
|
|
226
|
+
Usage is simple and there are **no external library dependencies** (currently). Either use `main.py` from the Releases tab, or clone the repo and copy `main.py` to your desired location.
|
|
227
|
+
|
|
228
|
+
1. Define a `dict` mapping your CONQUEST species to elements. E.g., if `Conquest_input` has a block like this:
|
|
229
|
+
```
|
|
230
|
+
%block ChemicalSpeciesLabel
|
|
231
|
+
1 208.9800000 Bi_SpinUp
|
|
232
|
+
2 208.9800000 Bi_SpinDown
|
|
233
|
+
3 16.000000 O
|
|
234
|
+
%endblock
|
|
235
|
+
```
|
|
236
|
+
Your `dict` will be `{1: "Bi", 2: "Bi", 3: "O"}`. Note that the `dict` integers should match the ones specified in `Conquest_input` and are completely arbitrary. please ensure however, that the element labels represent real elements (see `main.py`, `CONQUEST_INPUT` class)!
|
|
237
|
+
|
|
238
|
+
2. Create an instance of `CONQUEST_INPUT`, e.g. `CONQUEST_INPUT({1: "Bi", 2: "Bi", 3: "O"})`
|
|
239
|
+
|
|
240
|
+
3. Create an instance of `CONQUEST_COORDINATES`, feeding in the coordinate file you want to post-process and the instance of `CONQUEST_INPUT` created in Step 2:
|
|
241
|
+
```py
|
|
242
|
+
conq = CONQUEST_COORDINATES(
|
|
243
|
+
"./test/test.dat", CONQUEST_input=CONQUEST_INPUT({1: "Bi", 2: "Bi", 3: "O"})
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
4. You may now call any of the writers with the path to the desination file, in this case, `test/test.ABC`:
|
|
247
|
+
1. `vasp_writer("test/test.vasp", data=conq)`
|
|
248
|
+
2. `xyz_writer("test/test.xyz", data=conq)`
|
|
249
|
+
3. `extxyz_writer("test/test.extxyz", data=conq)`
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
## CONTRIBUTING
|
|
253
|
+
|
|
254
|
+
Edit `main.py` as necessary. Files may be refactored in the future.
|
|
255
|
+
|
|
256
|
+
Dependencies: tested and developed on Python 3.12. Expected to work on Python >= 3.10.
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
INTEGER = int | np.int64
|
|
7
|
+
FLOAT = float | np.float64
|
|
8
|
+
REAL_NUMBER = INTEGER | FLOAT | np.floating[typing.Any]
|
|
9
|
+
NUMBER_ARRAY = npt.NDArray[np.number]
|
|
10
|
+
REAL_ARRAY = npt.NDArray[np.float64]
|
|
11
|
+
GENERIC_ARRAY = npt.NDArray[typing.Any]
|
|
12
|
+
INT_ARRAY = npt.NDArray[np.int64]
|
|
13
|
+
|
|
14
|
+
FILE_PATH = str | Path
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from conquest2a.conquest import conquest_coordinates_processor, Atom
|
|
3
|
+
import conquest2a._types as c2at
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class angles:
|
|
7
|
+
def __init__(self, conquest_coords_processor: conquest_coordinates_processor) -> None:
|
|
8
|
+
self.conquest_coords_processor = conquest_coords_processor
|
|
9
|
+
|
|
10
|
+
def find_unit_normal_of_three_points(self, atoms: tuple[Atom, Atom, Atom]) -> c2at.REAL_ARRAY:
|
|
11
|
+
d01 = atoms[0].coords - atoms[1].coords
|
|
12
|
+
d02 = atoms[2].coords - atoms[1].coords
|
|
13
|
+
normal_vector: c2at.REAL_ARRAY = np.cross(d01, d02).astype(np.float64)
|
|
14
|
+
# Coefficients of x ,y, z normalised
|
|
15
|
+
normal_unit_vector: c2at.REAL_ARRAY = normal_vector / np.linalg.norm(normal_vector)
|
|
16
|
+
return normal_unit_vector
|
|
17
|
+
|
|
18
|
+
def interior_angle(self, atoms: tuple[Atom, Atom, Atom]) -> c2at.REAL_NUMBER:
|
|
19
|
+
"""Find angle between 3 atoms, with the second atom at the centre"""
|
|
20
|
+
d01 = atoms[0].coords - atoms[1].coords
|
|
21
|
+
d02 = atoms[2].coords - atoms[1].coords
|
|
22
|
+
dot = np.dot(d01, d02)
|
|
23
|
+
mag01 = np.linalg.norm(d01)
|
|
24
|
+
mag02 = np.linalg.norm(d02)
|
|
25
|
+
return np.float64(np.arccos(dot / (mag01 * mag02)))
|
|
26
|
+
|
|
27
|
+
def dihedral_angle(
|
|
28
|
+
self, normal1: c2at.REAL_ARRAY, normal2: c2at.REAL_ARRAY
|
|
29
|
+
) -> c2at.REAL_NUMBER:
|
|
30
|
+
return np.float64(np.arccos(np.dot(normal1, normal2)))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any
|
|
3
|
+
from scipy.spatial import KDTree
|
|
4
|
+
from conquest2a.conquest import conquest_coordinates_processor, Atom
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class nearest_neighbours:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
conquest_coordinates_processor: conquest_coordinates_processor,
|
|
11
|
+
atom: Atom,
|
|
12
|
+
) -> None:
|
|
13
|
+
self.coords_proc = conquest_coordinates_processor
|
|
14
|
+
self.kdtree = self.build_kdtree()
|
|
15
|
+
self.atom_to_query = atom
|
|
16
|
+
|
|
17
|
+
def build_kdtree(self) -> KDTree:
|
|
18
|
+
return KDTree(
|
|
19
|
+
self.coords_proc.cart_position_vectors,
|
|
20
|
+
copy_data=True,
|
|
21
|
+
boxsize=[self.coords_proc.coords.lattice_vectors[i][i] for i in range(0, 3)],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _knn(self, atom_query: Atom, num_neighbours: int | Sequence[int]) -> tuple[Any, Any]:
|
|
25
|
+
"""Perform the KDTree query on number_of_neighbours around the specific Atom.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
query (Atom): The Atom to find nearest neighbours of
|
|
29
|
+
Returns:
|
|
30
|
+
list[Atom]: The list of nearest-neighbour Atoms
|
|
31
|
+
"""
|
|
32
|
+
atom_query_cart_coords = atom_query.coords @ self.coords_proc.coords.lattice_vectors.T
|
|
33
|
+
distances, indices = self.kdtree.query(
|
|
34
|
+
x=atom_query_cart_coords, k=num_neighbours, p=2, workers=-1 # type: ignore
|
|
35
|
+
) # type: ignore
|
|
36
|
+
return distances, indices
|
|
37
|
+
|
|
38
|
+
def get_result(self, num_neighbours: int | Sequence[int]) -> list[tuple[float, Atom]]:
|
|
39
|
+
distances, indices = self._knn(atom_query=self.atom_to_query, num_neighbours=num_neighbours)
|
|
40
|
+
# since k is int | Sequence[int], result is either squeezed or unsqueezed
|
|
41
|
+
# Will handles these separately for the purposes of finding the Atom,
|
|
42
|
+
# But should return the same results if the same parameters are entered
|
|
43
|
+
if isinstance(num_neighbours, int) and num_neighbours == 1:
|
|
44
|
+
# Single atom, 1 neighbour -> single float and single index
|
|
45
|
+
return [(distances, self.coords_proc.coords.atoms[indices])]
|
|
46
|
+
# if isinstance(self.num_neighbours, int):
|
|
47
|
+
# Here, k > 1, or k is a list of ints. Then expect lists
|
|
48
|
+
# Since we are querying for one atom only, do not need to handle lists of lists
|
|
49
|
+
all_atoms: list[Any] = []
|
|
50
|
+
for pair in zip(distances, indices):
|
|
51
|
+
assoc_atom = (pair[0], self.coords_proc.coords.atoms[pair[1]])
|
|
52
|
+
all_atoms.append(assoc_atom)
|
|
53
|
+
return all_atoms
|