-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Read and plot exciton charge density * Shift the cell if view.center=True * Add tests for new features --------- Co-authored-by: Alexey Tal <[email protected]>
- Loading branch information
1 parent
7cb5bf7
commit bca35fd
Showing
8 changed files
with
436 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# Copyright © VASP Software GmbH, | ||
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) | ||
import numpy as np | ||
|
||
from py4vasp import _config, exception | ||
from py4vasp._calculation import _stoichiometry, base, structure | ||
from py4vasp._third_party import view | ||
from py4vasp._util import import_, index, select | ||
|
||
pretty = import_.optional("IPython.lib.pretty") | ||
|
||
|
||
_DEFAULT_SELECTION = "1" | ||
|
||
|
||
class ExcitonDensity(base.Refinery, structure.Mixin, view.Mixin): | ||
"""This class accesses exciton charge densities of VASP. | ||
The exciton charge densities can be calculated via the BSE/TDHF algorithm in | ||
VASP. With this class you can extract these charge densities. | ||
""" | ||
|
||
@base.data_access | ||
def __str__(self): | ||
_raise_error_if_no_data(self._raw_data.exciton_charge) | ||
grid = self._raw_data.exciton_charge.shape[1:] | ||
raw_stoichiometry = self._raw_data.structure.stoichiometry | ||
stoichiometry = _stoichiometry.Stoichiometry.from_data(raw_stoichiometry) | ||
return f"""exciton charge density: | ||
structure: {pretty.pretty(stoichiometry)} | ||
grid: {grid[2]}, {grid[1]}, {grid[0]} | ||
excitons: {len(self._raw_data.exciton_charge)}""" | ||
|
||
@base.data_access | ||
def to_dict(self): | ||
"""Read the exciton density into a dictionary. | ||
Returns | ||
------- | ||
dict | ||
Contains the supercell structure information as well as the exciton | ||
charge density represented on a grid in the supercell. | ||
""" | ||
_raise_error_if_no_data(self._raw_data.exciton_charge) | ||
result = {"structure": self._structure.read()} | ||
result.update({"charge": self.to_numpy()}) | ||
return result | ||
|
||
@base.data_access | ||
def to_numpy(self): | ||
"""Convert the exciton charge density to a numpy array. | ||
Returns | ||
------- | ||
np.ndarray | ||
Charge density of all excitons. | ||
""" | ||
return np.moveaxis(self._raw_data.exciton_charge, 0, -1).T | ||
|
||
@base.data_access | ||
def to_view(self, selection=None, supercell=None, center=False, **user_options): | ||
"""Plot the selected exciton density as a 3d isosurface within the structure. | ||
Parameters | ||
---------- | ||
selection : str | ||
Can be exciton index or a combination, i.e., "1" or "1+2+3" | ||
supercell : int or np.ndarray | ||
If present the data is replicated the specified number of times along each | ||
direction. | ||
center : bool | ||
Shift the origin of the unit cell to the center. This is helpful if you | ||
the exciton is at the corner of the cell. | ||
user_options | ||
Further arguments with keyword that get directly passed on to the | ||
visualizer. Most importantly, you can set isolevel to adjust the | ||
value at which the isosurface is drawn. | ||
Returns | ||
------- | ||
View | ||
Visualize an isosurface of the exciton density within the 3d structure. | ||
Examples | ||
-------- | ||
>>> calc = py4vasp.Calculation.from_path(".") | ||
Plot an isosurface of the first exciton charge density | ||
>>> calc.exciton.density.plot() | ||
Plot an isosurface of the third exciton charge density | ||
>>> calc.exciton.density.plot("3") | ||
Plot an isosurface of the sum of first and second exciton charge | ||
densities | ||
>>> calc.exciton.density.plot("1+2") | ||
""" | ||
_raise_error_if_no_data(self._raw_data.exciton_charge) | ||
selection = selection or _DEFAULT_SELECTION | ||
viewer = self._structure.plot(supercell) | ||
map_ = self._create_map() | ||
selector = index.Selector({0: map_}, self._raw_data.exciton_charge) | ||
tree = select.Tree.from_selection(selection) | ||
viewer.grid_scalars = [ | ||
self._grid_quantity(selector, selection, map_, user_options) | ||
for selection in tree.selections() | ||
] | ||
if center: | ||
viewer.shift = (0.5, 0.5, 0.5) | ||
return viewer | ||
|
||
def _create_map(self): | ||
num_excitons = self._raw_data.exciton_charge.shape[0] | ||
return {str(choice + 1): choice for choice in range(num_excitons)} | ||
|
||
def _grid_quantity(self, selector, selection, map_, user_options): | ||
return view.GridQuantity( | ||
quantity=(selector[selection].T)[np.newaxis], | ||
label=selector.label(selection), | ||
isosurfaces=self._isosurfaces(**user_options), | ||
) | ||
|
||
def _isosurfaces(self, isolevel=0.8, color=None, opacity=0.6): | ||
color = color or _config.VASP_COLORS["cyan"] | ||
return [view.Isosurface(isolevel, color, opacity)] | ||
|
||
|
||
def _raise_error_if_no_data(data): | ||
if data.is_none(): | ||
raise exception.NoData( | ||
"Exciton charge density was not found. Note that in order to calculate the" | ||
"exciton charge density the number of eigenvectors has to be selected with" | ||
"the tag NBSEEIG and the position of the hole or the electron has to be" | ||
"provided with the tag BSEHOLE or BSEELECTRON, correspondingly. The exciton" | ||
"density is written to vaspout.h5 if the tags LCHARGH5=T or LH5=T are set" | ||
"in the INCAR file, otherwise the charge density is written to CHG.XXX files." | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# Copyright © VASP Software GmbH, | ||
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) | ||
import types | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from py4vasp import _config, exception, raw | ||
from py4vasp._calculation.exciton_density import ExcitonDensity | ||
from py4vasp._calculation.structure import Structure | ||
|
||
|
||
@pytest.fixture | ||
def exciton_density(raw_data): | ||
raw_density = raw_data.exciton_density() | ||
density = ExcitonDensity.from_data(raw_density) | ||
density.ref = types.SimpleNamespace() | ||
density.ref.structure = Structure.from_data(raw_density.structure) | ||
expected_charge = [component.T for component in raw_density.exciton_charge] | ||
density.ref.density = np.array(expected_charge) | ||
print(density.ref.density.shape) | ||
return density | ||
|
||
|
||
@pytest.fixture | ||
def empty_density(raw_data): | ||
raw_density = raw.ExcitonDensity( | ||
raw_data.structure("Sr2TiO4"), exciton_charge=raw.VaspData(None) | ||
) | ||
return ExcitonDensity.from_data(raw_density) | ||
|
||
|
||
def test_read(exciton_density, Assert): | ||
actual = exciton_density.read() | ||
actual_structure = actual.pop("structure") | ||
Assert.same_structure(actual_structure, exciton_density.ref.structure.read()) | ||
Assert.allclose(actual["charge"], exciton_density.ref.density) | ||
|
||
|
||
def test_missing_data(empty_density): | ||
with pytest.raises(exception.NoData): | ||
empty_density.read() | ||
|
||
|
||
def test_to_numpy(exciton_density, Assert): | ||
actual = exciton_density.to_numpy() | ||
Assert.allclose(actual, exciton_density.ref.density) | ||
|
||
|
||
@pytest.mark.parametrize("selection, indices", [(None, 0), ("2", 1), ("1, 3", (0, 2))]) | ||
def test_plot_selection(exciton_density, selection, indices, Assert): | ||
indices = np.atleast_1d(indices) | ||
if selection is None: | ||
view = exciton_density.plot() | ||
else: | ||
view = exciton_density.plot(selection) | ||
Assert.same_structure_view(view, exciton_density.ref.structure.plot()) | ||
assert len(view.grid_scalars) == len(indices) | ||
for grid_scalar, index in zip(view.grid_scalars, indices): | ||
selected_exciton = exciton_density.ref.density[index] | ||
assert grid_scalar.label == str(index + 1) | ||
assert grid_scalar.quantity.ndim == 4 | ||
Assert.allclose(grid_scalar.quantity, selected_exciton) | ||
assert len(grid_scalar.isosurfaces) == 1 | ||
isosurface = grid_scalar.isosurfaces[0] | ||
assert isosurface.isolevel == 0.8 | ||
assert isosurface.color == _config.VASP_COLORS["cyan"] | ||
assert isosurface.opacity == 0.6 | ||
|
||
|
||
def test_plot_addition(exciton_density, Assert): | ||
view = exciton_density.plot("1 + 3") | ||
assert len(view.grid_scalars) == 1 | ||
grid_scalar = view.grid_scalars[0] | ||
selected_exciton = exciton_density.ref.density[0] + exciton_density.ref.density[2] | ||
assert grid_scalar.label == "1 + 3" | ||
Assert.allclose(grid_scalar.quantity, selected_exciton) | ||
|
||
|
||
def test_plot_centered(exciton_density, Assert): | ||
view = exciton_density.plot() | ||
assert view.shift is None | ||
view = exciton_density.plot(center=True) | ||
Assert.allclose(view.shift, 0.5) | ||
|
||
|
||
@pytest.mark.parametrize("supercell", (2, (3, 1, 2))) | ||
def test_plot_supercell(exciton_density, supercell, Assert): | ||
view = exciton_density.plot(supercell=supercell) | ||
Assert.allclose(view.supercell, supercell) | ||
|
||
|
||
def test_plot_user_options(exciton_density): | ||
view = exciton_density.plot(isolevel=0.4, color="red", opacity=0.5) | ||
assert len(view.grid_scalars) == 1 | ||
grid_scalar = view.grid_scalars[0] | ||
assert len(grid_scalar.isosurfaces) == 1 | ||
isosurface = grid_scalar.isosurfaces[0] | ||
assert isosurface.isolevel == 0.4 | ||
assert isosurface.color == "red" | ||
assert isosurface.opacity == 0.5 | ||
|
||
|
||
def test_print(exciton_density, format_): | ||
actual, _ = format_(exciton_density) | ||
expected_text = """\ | ||
exciton charge density: | ||
structure: Sr2TiO4 | ||
grid: 10, 12, 14 | ||
excitons: 3""" | ||
assert actual == {"text/plain": expected_text} | ||
|
||
|
||
def test_factory_methods(raw_data, check_factory_methods): | ||
data = raw_data.exciton_density() | ||
check_factory_methods(ExcitonDensity, data) |
Oops, something went wrong.