Skip to content

Commit

Permalink
Implement web JSON export
Browse files Browse the repository at this point in the history
Seems to be basically working!

The main limitation is that the seekpath band structures typically
include some non-contiguous sections, and the phonon website doesn't
handle those well. We might need to manipulate the distances a bit.
  • Loading branch information
ajjackson committed Oct 1, 2024
1 parent 92a71b8 commit 934934d
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 3 deletions.
17 changes: 15 additions & 2 deletions euphonic/cli/dispersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
def main(params: Optional[List[str]] = None) -> None:
args = get_args(get_parser(), params)

frequencies_only = not args.reorder # Need eigenvectors to reorder
# Need eigenvectors to reorder bands or write website JSON
frequencies_only = args.save_web_json is None and not args.reorder

data = load_data_from_file(args.filename, verbose=True,
frequencies_only=frequencies_only)
if not frequencies_only and type(data) is QpointFrequencies:
Expand All @@ -39,6 +41,9 @@ def main(params: Optional[List[str]] = None) -> None:
if args.reorder:
bands.reorder_frequencies()

if args.save_web_json is not None:
bands.write_phonon_website_json(output_file=args.save_web_json)

spectrum = bands.get_dispersion()

plot_label_kwargs = _plot_label_kwargs(
Expand All @@ -54,6 +59,7 @@ def main(params: Optional[List[str]] = None) -> None:

if args.save_json:
spectrum.to_json_file(args.save_json)

with matplotlib.style.context(style):
_ = plot_1d(spectra,
ymin=args.e_min,
Expand All @@ -63,7 +69,7 @@ def main(params: Optional[List[str]] = None) -> None:


def get_parser() -> ArgumentParser:
parser, _ = _get_cli_parser(features={'read-fc', 'read-modes', 'plotting',
parser, groups = _get_cli_parser(features={'read-fc', 'read-modes', 'plotting',
'q-e', 'btol'})
parser.description = (
'Plots a band structure from the file provided. If a force '
Expand All @@ -77,4 +83,11 @@ def get_parser() -> ArgumentParser:
action='store_true',
help=('Try to determine branch crossings from eigenvectors and'
' rearrange frequencies accordingly'))
groups["plotting"].add_argument(
'--save-web-json',
dest='save_web_json',
default=None,
help='Write JSON file for use with phonon website',
)

return parser
115 changes: 114 additions & 1 deletion euphonic/qpoint_phonon_modes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json

Check notice on line 1 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L1

Missing module docstring
import math
from typing import Dict, Optional, Union, TypeVar, Any, Type
from typing import Any, Dict, Optional, Union, Type, TypedDict, TypeVar
from collections.abc import Mapping

import numpy as np
Expand All @@ -14,6 +15,34 @@
StructureFactor, Spectrum1DCollection)


complex_pair = tuple[float, float]

Check notice on line 18 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L18

Class name "complex_pair" doesn't conform to PascalCase naming style


class PhononWebsiteData(TypedDict):
"""Data container for export to phonon visualisation website
Specification: https://henriquemiranda.github.io/phononwebsite/index.html
line_breaks are currently not implemented
"""

name: str
natoms: int
lattice: list[list[float]]
atom_types: list[str]
atom_numbers: list[int]
formula: str
repetitions: list[int]
atom_pos_car: list[list[float]]
atom_pos_red: list[list[float]]
highsym_qpts: list[tuple[int, str]]
qpoints: list[list[float]]
distances: list[float] # Cumulative distance from first q-point
eigenvalues: list[float] # in cm-1
vectors: list[list[list[tuple[complex_pair, complex_pair, complex_pair]]]]


class QpointPhononModes(QpointFrequencies):
"""
A class to read and store vibrational data from model (e.g. CASTEP)
Expand Down Expand Up @@ -716,3 +745,87 @@ def from_phonopy(cls: Type[T], path: str = '.',
path=path, phonon_name=phonon_name, phonon_format=phonon_format,
summary_name=summary_name)
return cls.from_dict(data)

def write_phonon_website_json(self,
output_file: str = 'phonons.json',
name: str = 'Euphonic export') -> None:
"""Dump to .json for use with phonon website visualiser
Use with javascript application at
https://henriquemiranda.github.io/phononwebsite
Parameters
----------
output_file
Path to output file
name
Set "name" metadata
"""

with open(output_file, 'w') as fd:

Check warning on line 766 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L766

Using open without explicitly specifying an encoding
json.dump(self._to_phonon_website_dict(name=name), fd)

@staticmethod
def _crystal_website_data(crystal: Crystal) -> dict[str, Any]:
elements = [
'_', 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na',
'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V',
'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se',
'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh',
'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba',
'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho',
'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt',
'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac',
'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm',
'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg',
'Cn', 'Nh', 'Fl', 'Mc', 'Lv', 'Ts', 'Og']

def get_z(symbol: str) -> int:
try:
return elements.index(symbol)
except ValueError: # Symbol not found
return 0

def symbols_to_formula(symbols: list[str]) -> str:
from toolz.recipes import countby

Check notice on line 791 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L791

Import outside toplevel (toolz.recipes.countby)
from toolz.functoolz import identity

Check notice on line 792 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L792

Import outside toplevel (toolz.functoolz.identity)

symbol_counts = countby(identity, symbols)
return "".join(f"{symbol}{count}"
for symbol, count in symbol_counts.items())

return dict(
natoms = len(crystal.atom_type),
lattice = crystal.cell_vectors.to("angstrom").magnitude.tolist(),
atom_types = crystal.atom_type.tolist(),
atom_numbers = list(map(get_z, crystal.atom_type)),
formula = symbols_to_formula(crystal.atom_type),
atom_pos_red = crystal.atom_r.tolist(),
atom_pos_car = (crystal.atom_r @ crystal.cell_vectors).to("angstrom").magnitude.tolist()
)

def _to_phonon_website_dict(self,
name: str = 'Euphonic export',
repetitions: tuple[int, int, int] = (2, 2, 2),
) -> PhononWebsiteData:
from euphonic.util import _calc_abscissa, get_qpoint_labels

Check notice on line 812 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L812

Import outside toplevel (euphonic.util._calc_abscissa, euphonic.util.get_qpoint_labels)

abscissa = _calc_abscissa(self.crystal.reciprocal_cell(), self.qpts)
x_tick_labels = get_qpoint_labels(self.qpts,
cell=self.crystal.to_spglib_cell())

dat = PhononWebsiteData(
name=name,
**self._crystal_website_data(self.crystal),
highsym_qpts=x_tick_labels,
distances=abscissa.magnitude.tolist(),
qpoints=self.qpts.tolist(),
eigenvalues=self.frequencies.to("1/cm").magnitude.tolist(),
vectors=self.eigenvectors.view(float).reshape(*self.eigenvectors.shape[:-1], 3, 2).tolist(),

Check notice on line 825 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L825

Line too long (104/100)
repetitions=repetitions,
)

print(dat)

return(dat)

Check notice on line 831 in euphonic/qpoint_phonon_modes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

euphonic/qpoint_phonon_modes.py#L831

Unnecessary parens after 'return' keyword

0 comments on commit 934934d

Please sign in to comment.