Skip to content

Commit

Permalink
implemented ARTModel
Browse files Browse the repository at this point in the history
  • Loading branch information
wolearyc committed Aug 9, 2024
1 parent 231f668 commit 8c074a0
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/coverage-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs/source/generated/ramannoodle.polarizability.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ ramannoodle.polarizability package
Submodules
----------

ramannoodle.polarizability.art module
-------------------------------------

.. automodule:: ramannoodle.polarizability.art
:members:
:undoc-members:
:show-inheritance:

ramannoodle.polarizability.interpolation module
-----------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/tests-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
191 changes: 191 additions & 0 deletions ramannoodle/polarizability/art.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""Polarizability model based on atomic Raman tensors (ARTs)."""

from pathlib import Path

import numpy as np
from numpy.typing import NDArray

from .interpolation import InterpolationModel
from ..exceptions import (
get_type_error,
get_shape_error,
verify_ndarray_shape,
InvalidDOFException,
)


class ARTModel(InterpolationModel):
"""Polarizability model based on atomic Raman tensors (ARTs).
This model is a flavor of InterpolationModel with added restrictions. The degrees
of freedom (DOF) in ARTModel are displacements of individual atoms. 1D
interpolations are used, with only two amplitudes allowed per DOF. Atomic
displacements can be in any direction, so long as all three directions for a given
atom are orthogonal.
All assumptions from InterpolationModel apply.
.. note::
Degrees of freedom cannot (and should not) be added using `add_dof` and
`add_dof_from_files`. Instead, use `add_art` and `add_art_from_files`.
.. warning::
Due to the way InterpolationModel works, an ARTModel's predicted polarizability
tensor of the equilibrium structure may be offset slightly. This is a fixed
offset and therefore has no influence on Raman spectra calculations.
Parameters
----------
structural_symmetry
equilibrium_polarizability
2D array with shape (3,3) giving polarizability of system at equilibrium. This
would usually correspond to the minimum energy structure.
"""

def add_dof( # pylint: disable=too-many-arguments
self,
displacement: NDArray[np.float64],
amplitudes: NDArray[np.float64],
polarizabilities: NDArray[np.float64],
interpolation_order: int,
include_equilibrium_polarizability: bool = True,
) -> None:
"""Disable add_dof.
:meta private:
"""
raise AttributeError("'ARTModel' object has no attribute 'add_dof'")

def add_dof_from_files(
self,
filepaths: str | Path | list[str] | list[Path],
file_format: str,
interpolation_order: int,
) -> None:
"""Disable add_dof_from_files.
:meta private:
"""
raise AttributeError("'ARTModel' object has no attribute 'add_dof_from_files'")

def add_art(
self,
atom_index: int,
direction: NDArray[np.float64],
amplitudes: NDArray[np.float64],
polarizabilities: NDArray[np.float64],
) -> None:
"""Add an atomic Raman tensor (ART).
Specification of an ART requires an atom index, displacement direction,
displacement amplitudes, and corresponding known polarizabilities for each
amplitude. Alongside ART's related to the specified ART by symmetry are added
automatically.
Parameters
----------
atom_index
Index of atom, consistent with the StructuralSymmetry used to initialize
the model.
direction
1D array with shape (3,). Must be orthogonal to all previously added ARTs
belonging to specified atom.
amplitudes
1D array of length 1 or 2 containing amplitudes in angstroms. Duplicate
amplitudes are not allowed, including symmetrically equivalent
amplitudes.
polarizabilities
3D array with shape (1,3,3) or (2,3,3) containing known polarizabilities for
each amplitude.
Raises
------
InvalidDOFException
Provided ART was invalid.
"""
if not isinstance(atom_index, int):
raise get_type_error("atom_index", atom_index, "int")
try:
if amplitudes.shape not in [(1,), (2,)]:
raise get_shape_error("amplitudes", amplitudes, "(1,) or (2,)")
except AttributeError as exc:
raise get_type_error("amplitudes", amplitudes, "ndarray") from exc
verify_ndarray_shape(
"polarizabilities", polarizabilities, (amplitudes.size, 3, 3)
)

displacement = self._structural_symmetry.get_fractional_positions() * 0
try:
displacement[atom_index] = direction / np.linalg.norm(direction * 10.0)
except TypeError as exc:
raise get_type_error("direction", direction, "ndarray") from exc
except ValueError as exc:
raise get_shape_error("direction", direction, "(3,)") from exc

super().add_dof(
displacement,
amplitudes,
polarizabilities,
1,
include_equilibrium_polarizability=False,
)

def add_art_from_files(
self,
filepaths: str | Path | list[str] | list[Path],
file_format: str,
) -> None:
"""Add a atomic Raman tensor (ART) from file(s).
Required directions, amplitudes, and polarizabilities are automatically
determined from provided files. Files should be chosen wisely such that the
resulting ARTs are valid under the restrictions set by `add_art`.
Parameters
----------
filepaths
file_format
supports: "outcar"
Raises
------
FileNotFoundError
File could not be found.
InvalidDOFException
ART assembled from supplied files was invalid (see get_art)
"""
displacements, amplitudes, polarizabilities = super()._read_dof(
filepaths, file_format
)
# Check whether only one atom is displaced.
_displacement = np.copy(displacements[0])
atom_index = int(np.argmax(np.sum(_displacement**2, axis=1)))
_displacement[atom_index] = np.zeros(3)
if not np.isclose(_displacement, 0.0, atol=1e-6).all():
raise InvalidDOFException("multiple atoms displaced simultaneously")

basis_vectors_to_add, interpolation_xs, interpolation_ys = super()._get_dof(
displacements[0], amplitudes, polarizabilities, False
)

num_amplitudes = len(interpolation_xs[0])
if num_amplitudes != 2:
raise InvalidDOFException(
f"wrong number of amplitudes: {num_amplitudes} != 2"
)

super()._construct_and_add_interpolations(
basis_vectors_to_add, interpolation_xs, interpolation_ys, 1
)

def get_status_dict(self) -> dict[str, int]:
"""Return information on model status.
Relevant information includes which ARTs are required for each atom and whether
or not these ARTs have been specified.
"""
raise NotImplementedError
Binary file added test/data/TiO2/known_art_spectrum.npz
Binary file not shown.
Loading

0 comments on commit 8c074a0

Please sign in to comment.