forked from orest-d/p4vasp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Implement logic to extract magnetism * Implement basic magnetism reading from file * Implement reading the data into a dictionary * Make charges and moments accessible individually * Add magnetism to structure. * Implement plotting the moments along the z axis * Implement noncollinear magnetism * Implement charge-only case * Add documentation for new magnetism functionality
- Loading branch information
1 parent
baa199b
commit 5f03fc3
Showing
10 changed files
with
382 additions
and
6 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,142 @@ | ||
from py4vasp.data import _util | ||
import numpy as np | ||
import functools | ||
|
||
_step_parameter = """Parameters | ||
---------- | ||
steps : int or range | ||
If present specifies which steps of the simulation should be extracted. | ||
By default the data of all steps is read. | ||
""" | ||
|
||
_index_note = """Notes | ||
----- | ||
The index order is different compared to the raw data when noncollinear calculations | ||
are used. This routine returns the magnetic moments as (steps, atoms, orbitals, | ||
directions).""" | ||
|
||
_to_dict_doc = f""" Read the charges and magnetization data into a dictionary. | ||
{_step_parameter} | ||
Returns | ||
------- | ||
dict | ||
Contains the charges and magnetic moments generated by Vasp projected | ||
on atoms and orbitals. | ||
{_index_note} | ||
""" | ||
|
||
_charges_doc = f""" Read the charges of the selected steps. | ||
{_step_parameter} | ||
Returns | ||
------- | ||
np.ndarray | ||
Contains the charges for the selected steps projected on atoms and orbitals. | ||
""" | ||
|
||
_total_charges_doc = f""" Read the total charges of the selected steps. | ||
{_step_parameter} | ||
Returns | ||
------- | ||
np.ndarray | ||
Contains the total charges for the selected steps projected on atoms. This | ||
corresponds to the charges summed over the orbitals. | ||
""" | ||
|
||
_moments_doc = f""" Read the magnetic moments of the selected steps. | ||
{_step_parameter} | ||
Returns | ||
------- | ||
np.ndarray | ||
Contains the magnetic moments for the selected steps projected on atoms and | ||
orbitals. | ||
{_index_note} | ||
""" | ||
|
||
_total_moments_doc = f""" Read the total magnetic moments of the selected steps. | ||
{_step_parameter} | ||
Returns | ||
------- | ||
np.ndarray | ||
Contains the total magnetic moments for the selected steps projected on atoms. | ||
This corresponds to the magnetic moments summed over the orbitals. | ||
""" | ||
|
||
|
||
class Magnetism: | ||
""" The evolution of the magnetization over the simulation. | ||
This class gives access to the magnetic moments and charges projected on the | ||
different orbitals on every atom. | ||
Parameters | ||
---------- | ||
raw_magnetism | ||
Dataclass containing the charges and magnetic moments read from Vasp. | ||
""" | ||
|
||
def __init__(self, raw_magnetism): | ||
self._raw = raw_magnetism | ||
|
||
@classmethod | ||
@_util.add_doc(_util.from_file_doc("orbital-resolved magnetic moments")) | ||
def from_file(cls, file=None): | ||
return _util.from_file(cls, file, "magnetism") | ||
|
||
@_util.add_doc(_to_dict_doc) | ||
def to_dict(self, steps=None): | ||
return {"charges": self.charges(steps), "moments": self.moments(steps)} | ||
|
||
@functools.wraps(to_dict) | ||
def read(self, *args): | ||
return self.to_dict(*args) | ||
|
||
@_util.add_doc(_charges_doc) | ||
def charges(self, steps=None): | ||
steps = self._default_steps_if_none(steps) | ||
return self._raw.moments[steps, 0, :, :] | ||
|
||
@_util.add_doc(_moments_doc) | ||
def moments(self, steps=None): | ||
steps = self._default_steps_if_none(steps) | ||
if self._raw.moments.shape[1] == 1: | ||
return None | ||
elif self._raw.moments.shape[1] == 2: | ||
return self._raw.moments[steps, 1, :, :] | ||
else: | ||
moments = self._raw.moments[steps, 1:, :, :] | ||
direction_axis = 1 if moments.ndim == 4 else 0 | ||
return np.moveaxis(moments, direction_axis, -1) | ||
|
||
@_util.add_doc(_total_charges_doc) | ||
def total_charges(self, steps=None): | ||
return self._sum_over_orbitals(self.charges(steps)) | ||
|
||
@_util.add_doc(_total_moments_doc) | ||
def total_moments(self, steps=None): | ||
if self._raw.moments.shape[1] == 1: | ||
return None | ||
elif self._raw.moments.shape[1] == 2: | ||
return self._sum_over_orbitals(self.moments(steps)) | ||
else: | ||
steps = self._default_steps_if_none(steps) | ||
total_moments = self._sum_over_orbitals(self._raw.moments[steps, 1:, :, :]) | ||
direction_axis = 1 if total_moments.ndim == 3 else 0 | ||
return np.moveaxis(total_moments, direction_axis, -1) | ||
|
||
def _default_steps_if_none(self, steps): | ||
return steps if steps is not None else range(len(self._raw.moments)) | ||
|
||
def _sum_over_orbitals(self, quantity): | ||
return np.sum(quantity, axis=-1) |
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
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,93 @@ | ||
from py4vasp.data import Magnetism | ||
from .test_topology import raw_topology | ||
import py4vasp.raw as raw | ||
import numpy as np | ||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def raw_magnetism(raw_topology): | ||
number_steps = 4 | ||
number_atoms = len(raw_topology.elements) | ||
lmax = 3 | ||
number_components = 2 | ||
shape = (number_steps, number_components, number_atoms, lmax) | ||
magnetism = raw.Magnetism(moments=np.arange(np.prod(shape)).reshape(shape)) | ||
magnetism.charges = magnetism.moments[:, 0, :, :] | ||
magnetism.total_charges = np.sum(magnetism.charges, axis=2) | ||
magnetism.magnetic_moments = magnetism.moments[:, 1, :, :] | ||
magnetism.total_moments = np.sum(magnetism.magnetic_moments, axis=2) | ||
return magnetism | ||
|
||
|
||
@pytest.fixture | ||
def noncollinear_magnetism(raw_magnetism): | ||
shape = raw_magnetism.moments.shape | ||
shape = (shape[0] // 2, shape[1] * 2, shape[2], shape[3]) | ||
raw_magnetism.moments = raw_magnetism.moments.reshape(shape) | ||
return raw_magnetism | ||
|
||
|
||
def test_from_file(raw_magnetism, mock_file, check_read): | ||
with mock_file("magnetism", raw_magnetism) as mocks: | ||
check_read(Magnetism, mocks, raw_magnetism) | ||
|
||
|
||
def test_read(raw_magnetism, Assert): | ||
actual = Magnetism(raw_magnetism).read() | ||
Assert.allclose(actual["charges"], raw_magnetism.charges) | ||
Assert.allclose(actual["moments"], raw_magnetism.magnetic_moments) | ||
actual = Magnetism(raw_magnetism).read(-1) | ||
Assert.allclose(actual["charges"], raw_magnetism.charges[-1]) | ||
Assert.allclose(actual["moments"], raw_magnetism.magnetic_moments[-1]) | ||
|
||
|
||
def test_charges(raw_magnetism, Assert): | ||
actual = Magnetism(raw_magnetism).charges() | ||
Assert.allclose(actual, raw_magnetism.charges) | ||
|
||
|
||
def test_moments(raw_magnetism, Assert): | ||
actual = Magnetism(raw_magnetism).moments() | ||
Assert.allclose(actual, raw_magnetism.magnetic_moments) | ||
|
||
|
||
def test_total_charges(raw_magnetism, Assert): | ||
actual = Magnetism(raw_magnetism).total_charges() | ||
Assert.allclose(actual, raw_magnetism.total_charges) | ||
actual = Magnetism(raw_magnetism).total_charges(range(2)) | ||
Assert.allclose(actual, raw_magnetism.total_charges[0:2]) | ||
|
||
|
||
def test_total_moments(raw_magnetism, Assert): | ||
actual = Magnetism(raw_magnetism).total_moments() | ||
Assert.allclose(actual, raw_magnetism.total_moments) | ||
actual = Magnetism(raw_magnetism).total_moments(3) | ||
Assert.allclose(actual, raw_magnetism.total_moments[3]) | ||
|
||
|
||
def test_noncollinear(noncollinear_magnetism, Assert): | ||
actual = Magnetism(noncollinear_magnetism) | ||
Assert.allclose(actual.charges(), noncollinear_magnetism.moments[:, 0]) | ||
step = 0 | ||
moments = actual.moments(step) | ||
for new_order in np.ndindex(moments.shape): | ||
atom, orbital, component = new_order | ||
old_order = (step, component + 1, atom, orbital) # 0 component is charge | ||
Assert.allclose(moments[new_order], noncollinear_magnetism.moments[old_order]) | ||
total_moments = actual.total_moments() | ||
for new_order in np.ndindex(total_moments.shape): | ||
step, atom, component = new_order | ||
old_order = (step, component + 1, atom) # 0 component is charge | ||
expected_total_moment = np.sum(noncollinear_magnetism.moments[old_order]) | ||
Assert.allclose(total_moments[new_order], expected_total_moment) | ||
|
||
|
||
def test_charge_only(raw_magnetism, Assert): | ||
shape = raw_magnetism.moments.shape | ||
shape = (shape[0] * shape[1], 1, shape[2], shape[3]) | ||
raw_magnetism.moments = raw_magnetism.moments.reshape(shape) | ||
actual = Magnetism(raw_magnetism) | ||
Assert.allclose(actual.charges(), raw_magnetism.moments[:, 0]) | ||
assert actual.moments() is None | ||
assert actual.total_moments() is None |
Oops, something went wrong.