Merge branch 'refactoring' into develop

This commit is contained in:
Atsushi Togo 2021-10-25 17:43:02 +09:00
commit ac7abb7e4b
369 changed files with 42095 additions and 35894 deletions

View File

@ -6,29 +6,28 @@ jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
max-parallel: 5
matrix:
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: ${{ matrix.python-version }}
- name: Add conda to system path
run: |
# $CONDA is an environment variable pointing to the root of the miniconda directory
echo $CONDA/bin >> $GITHUB_PATH
- name: Install dependencies
run: |
conda install --yes -c conda-forge python=3.8
conda install --yes -c conda-forge gcc_linux-64 gxx_linux-64 matplotlib-base pyyaml scipy numpy spglib h5py setuptools_scm conda-build pip pytest codecov pytest-cov pytest
conda install --yes -c conda-forge python=${{ matrix.python-version }}
conda install --yes -c conda-forge gcc_linux-64 gxx_linux-64 matplotlib-base pyyaml scipy numpy spglib h5py pip pytest codecov pytest-cov pytest
- name: Install cp2k-input-tools
run: |
pip install cp2k-input-tools
- name: Setup phonopy
run: |
./get_nanoversion.sh
cat __nanoversion__.txt
python setup.py build
pip install -e .
- name: Test with pytest

View File

@ -1,19 +1,24 @@
name: publish PyPI and TestPyPI
on: push
on:
push:
branches:
- master
- rc
jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
max-parallel: 5
matrix:
python-version: [3.9, ]
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: ${{ matrix.python-version }}
- name: Make sdist
run: |
./get_nanoversion.sh

53
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,53 @@
# See https://pre-commit.com for more informatio
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
exclude: ^conda/
- id: check-added-large-files
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
exclude: |
(?x)^(
test/phonon/test_irreps.py|
test/qha/test_electron.py|
phonopy/interface/cp2k.py
)$
args:
- "--max-line-length=88"
- "--ignore=E203,W503"
- id: flake8
files: |
(?x)^(
test/phonon/test_irreps.py|
test/qha/test_electron.py|
phonopy/interface/cp2k.py
)$
args:
- "--max-line-length=88"
- "--ignore=E203,W503,E501"
- repo: https://github.com/psf/black
rev: 21.9b0
hooks:
- id: black
args:
- --line-length=88
- repo: https://github.com/pycqa/pydocstyle
rev: 6.0.0
hooks:
- id: pydocstyle
- repo: https://github.com/pycqa/isort
rev: 5.8.0
hooks:
- id: isort
name: isort (python)

View File

@ -33,6 +33,13 @@ repository.
"python.linting.pycodestyleEnabled": false,
"python.linting.pydocstyleEnabled": true,
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length=88"],
"python.sortImports.args": ["--profile", "black"],
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
}
```
## Documentation

View File

@ -93,4 +93,3 @@ important part of the implementation is the symmetry handling. In
fropho, at first the symmetry finder in Abinit code was employed, but
later the symmetry finder was replaced by spglib
(http://spglib.sourceforge.net/).

View File

@ -1,3 +1,4 @@
"""Sphinx configuration of phonopy documentation."""
# -*- coding: utf-8 -*-
#
# phonopy documentation build configuration file, created by
@ -22,69 +23,69 @@ import sphinx_bootstrap_theme
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.mathjax']
extensions = ["sphinx.ext.mathjax"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
source_encoding = 'utf-8'
source_encoding = "utf-8"
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'phonopy'
copyright = u'2009, Atsushi Togo'
project = u"phonopy"
copyright = u"2009, Atsushi Togo"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.11'
version = "2.11"
# The full version, including alpha/beta/rc tags.
release = '2.11.0'
release = "2.11.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
exclude_trees = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
@ -93,20 +94,18 @@ pygments_style = 'sphinx'
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme = 'default'
# html_theme = 'nature'
html_theme = 'bootstrap'
html_theme = "bootstrap"
# html_theme = 'sphinxbootstrap4theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
html_theme_options = {
# Navigation bar title. (Default: ``project`` value)
'navbar_title': "Phonopy",
"navbar_title": "Phonopy",
# Tab name for entire site. (Default: "Site")
'navbar_site_name': "Site",
"navbar_site_name": "Site",
# A list of tuples containing pages or urls to link to.
# Valid tuples should be in the following forms:
# (name, page) # a link to a page
@ -122,17 +121,13 @@ html_theme_options = {
# ("Tags", "setting-tags"),
# ("Options", "command-options"),
# ],
# Render the next and previous page links in navbar. (Default: true)
'navbar_sidebarrel': True,
"navbar_sidebarrel": True,
# Render the current pages TOC in the navbar. (Default: true)
'navbar_pagenav': True,
"navbar_pagenav": True,
# Global TOC depth for "site" navbar tab. (Default: 1)
# Switching to -1 shows all levels.
'globaltoc_depth': 1,
"globaltoc_depth": 1,
# Include hidden TOCs in Site navbar?
#
# Note: If this is "false", you cannot have mixed ``:hidden:`` and
@ -140,32 +135,27 @@ html_theme_options = {
# will break.
#
# Values: "true" (default) or "false"
'globaltoc_includehidden': "true",
"globaltoc_includehidden": "true",
# HTML navbar class (Default: "navbar") to attach to <div> element.
# For black navbar, do "navbar navbar-inverse"
# 'navbar_class': "navbar navbar-inverse",
'navbar_class': "navbar",
"navbar_class": "navbar",
# Fix navigation bar to top of page?
# Values: "true" (default) or "false"
'navbar_fixed_top': "true",
"navbar_fixed_top": "true",
# Location of link to source.
# Options are "nav" (default), "footer" or anything else to exclude.
# 'source_link_position': "nav",
'source_link_position': "footer",
"source_link_position": "footer",
# Bootswatch (http://bootswatch.com/) theme.
#
# Options are nothing with "" (default) or the name of a valid theme
# such as "amelia" or "cosmo".
# 'bootswatch_theme': "united",
'bootswatch_theme': "cerulean",
"bootswatch_theme": "cerulean",
# Choose Bootstrap version.
# Values: "3" (default) or "2" (in quotes)
'bootstrap_version': "3",
"bootstrap_version": "3",
}
# Add any paths that contain custom themes here, relative to this directory.
@ -175,10 +165,10 @@ html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = 'Phonopy v.%s' % release
html_title = "Phonopy v.%s" % release
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sideba.
@ -187,80 +177,79 @@ html_title = 'Phonopy v.%s' % release
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'phonopydoc'
htmlhelp_basename = "phonopydoc"
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('documentation', 'phonopy.tex', u'phonopy manual',
u'Atsushi Togo', 'manual'),
("documentation", "phonopy.tex", u"phonopy manual", u"Atsushi Togo", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# latex_use_modindex = True

View File

@ -134,8 +134,7 @@ DFPT
Currently there are several many implementations such as `Abinit
<http://www.abinit.org/>`_, `Quantum espresso
<http://www.quantum-espresso.org/>`_, `Elk
<http://elk.sourceforge.net/>`_, `Fleur
<https://www.flapw.de/>`_, etc. VASP can calculate force constants
<http://elk.sourceforge.net/>`_, etc. VASP can calculate force constants
using DFPT however only at Gamma-point.
For the study of basics

View File

@ -121,4 +121,3 @@ parameter is not needed.
For further settings and command options, see the general Phonopy documentation
:ref:`setting_tags` and :ref:`command_options`, respectively, and
for examples, see :ref:`examples_link`.

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
"""Example of QHA calculation by Al."""
import numpy as np
import yaml
from yaml import CLoader as Loader
import numpy as np
from phonopy import PhonopyQHA
volumes = []
@ -15,24 +15,25 @@ for line in open("e-v.dat"):
entropy = []
cv = []
fe = []
for index in range(-5,6):
for index in range(-5, 6):
filename = "thermal_properties.yaml-%d" % index
print("Reading %s" % filename)
thermal_properties = yaml.load(open(filename),
Loader=Loader)['thermal_properties']
temperatures = [v['temperature'] for v in thermal_properties]
cv.append([v['heat_capacity'] for v in thermal_properties])
entropy.append([v['entropy'] for v in thermal_properties])
fe.append([v['free_energy'] for v in thermal_properties])
thermal_properties = yaml.load(open(filename), Loader=Loader)["thermal_properties"]
temperatures = [v["temperature"] for v in thermal_properties]
cv.append([v["heat_capacity"] for v in thermal_properties])
entropy.append([v["entropy"] for v in thermal_properties])
fe.append([v["free_energy"] for v in thermal_properties])
qha = PhonopyQHA(volumes,
qha = PhonopyQHA(
volumes,
energies,
temperatures=temperatures,
free_energy=np.transpose(fe),
cv=np.transpose(cv),
entropy=np.transpose(entropy),
t_max=400,
verbose=True)
verbose=True,
)
# qha.plot_helmholtz_volume().show()
# qha.plot_volume_temperature().show()

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -378.9467176
entropy: 289.4736650
heat_capacity: 99.5473036

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -366.7327306
entropy: 283.3949646
heat_capacity: 99.5190804

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -355.1311761
entropy: 277.6245626
heat_capacity: 99.4887894

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -343.8208732
entropy: 272.0025819
heat_capacity: 99.4557211

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -333.0629769
entropy: 266.6590471
heat_capacity: 99.4204298

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -391.4467304
entropy: 295.6979666
heat_capacity: 99.5729516

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -404.3758647
entropy: 302.1390626
heat_capacity: 99.5963768

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -417.8333877
entropy: 308.8463804
heat_capacity: 99.6177832

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -431.7193088
entropy: 315.7698942
heat_capacity: 99.6372002

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -446.4389062
entropy: 323.1118308
heat_capacity: 99.6550383

View File

@ -5015,4 +5015,3 @@ thermal_properties:
free_energy: -462.0680669
entropy: 330.9100940
heat_capacity: 99.6713363

View File

@ -1,9 +1,11 @@
import phonopy
"""Example by corundum Al2O3."""
import numpy as np
phonon = phonopy.load(unitcell_filename="POSCAR-unitcell",
supercell_matrix=[2, 2, 1],
log_level=1)
import phonopy
phonon = phonopy.load(
unitcell_filename="POSCAR-unitcell", supercell_matrix=[2, 2, 1], log_level=1
)
print("Space group: %s" % phonon.symmetry.get_international_table())
# Example to obtain dynamical matrix
@ -12,7 +14,7 @@ print(dmat)
# Example of band structure calculation
bands = []
q_start = np.array([1./3, 1./3, 0])
q_start = np.array([1.0 / 3, 1.0 / 3, 0])
q_end = np.array([0, 0, 0])
band = []
for i in range(51):
@ -20,30 +22,26 @@ for i in range(51):
bands.append(band)
q_start = np.array([0, 0, 0])
q_end = np.array([1./3, 1./3, 1./2])
q_end = np.array([1.0 / 3, 1.0 / 3, 1.0 / 2])
band = []
for i in range(51):
band.append(q_start + (q_end - q_start) / 50 * i)
bands.append(band)
print("\nPhonon dispersion:")
phonon.run_band_structure(bands,
with_eigenvectors=True,
labels=["X", r"$\Gamma$", "L"])
phonon.run_band_structure(bands, with_eigenvectors=True, labels=["X", r"$\Gamma$", "L"])
band_plot = phonon.plot_band_structure()
band_plot.show()
bs = phonon.get_band_structure_dict()
distances = bs['distances']
frequencies = bs['frequencies']
qpoints = bs['qpoints']
distances = bs["distances"]
frequencies = bs["frequencies"]
qpoints = bs["qpoints"]
for (qs_at_segments,
dists_at_segments,
freqs_at_segments) in zip(qpoints, distances, frequencies):
for (qs_at_segments, dists_at_segments, freqs_at_segments) in zip(
qpoints, distances, frequencies
):
for q, d, f in zip(qs_at_segments,
dists_at_segments,
freqs_at_segments):
for q, d, f in zip(qs_at_segments, dists_at_segments, freqs_at_segments):
print("# %f %f %f" % tuple(q))
print(("%s " + "%f " * len(f)) % ((d,) + tuple(f)))

View File

@ -24,4 +24,3 @@ SYMMETRY_TOL : 0.000001
kpoint_mp_grid : 8 8 8
kpoint_mp_offset : 0.00000000000000 0.00000000000000 0.00000000000000

View File

@ -120,4 +120,3 @@ SYMMETRY_TOL : 0.001000
kpoint_mp_grid : 8 8 8
kpoint_mp_offset : 0.500000000000000 0.500000000000000 0.500000000000000

View File

@ -39,4 +39,3 @@ AtomicCoordinatesFormat NotScaledCartesianAng
0.00000000000000 0.00000000000000 0.00000000000000 1 12.011000
1.27500000000000 0.73612159321677 0.00000000000000 1 12.011000
%endblock AtomicCoordinatesAndAtomicSpecies

View File

@ -1,12 +1,13 @@
import phonopy
"""Example of calculation of irreps of MgB2."""
import numpy as np
phonon = phonopy.load(unitcell_filename="POSCAR-unitcell",
supercell_matrix=[3, 3, 2])
import phonopy
phonon = phonopy.load(unitcell_filename="POSCAR-unitcell", supercell_matrix=[3, 3, 2])
print("Space group: %s" % phonon.symmetry.get_international_table())
# Character table
phonon.set_irreps([1./3, 1./3, 0], 1e-4)
phonon.set_irreps([1.0 / 3, 1.0 / 3, 0], 1e-4)
ct = phonon.get_irreps()
band_indices = ct.get_band_indices()
characters = np.rint(ct.get_characters()).real

View File

@ -23,4 +23,3 @@ SYMMETRY_TOL : 0.000001
Na ../Na.upf
Cl ../Cl.upf
%ENDBLOCK SPECIES_POT

View File

@ -1000,4 +1000,3 @@ SYMMETRY_TOL : 0.001000
kpoint_mp_grid : 6 6 6
kpoint_mp_offset : 0.500000000000000 0.500000000000000 0.500000000000000

View File

@ -1,35 +1,38 @@
"""Example of band structure and group velocity calculation by NaCl."""
from typing import List
import numpy as np
import phonopy
def append_band(bands, q_start, q_end):
def _append_band(bands, q_start, q_end):
band = []
nq = 51
for i in range(nq):
band.append(np.array(q_start) +
(np.array(q_end) - np.array(q_start)) / (nq - 1) * i)
band.append(
np.array(q_start) + (np.array(q_end) - np.array(q_start)) / (nq - 1) * i
)
bands.append(band)
phonon = phonopy.load(unitcell_filename="POSCAR-unitcell",
phonon = phonopy.load(
unitcell_filename="POSCAR-unitcell",
born_filename="BORN",
force_sets_filename="FORCE_SETS",
supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
)
bands = []
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.0, 0.0])
append_band(bands, [0.5, 0.0, 0.0], [0.5, 0.5, 0.0])
append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
bands = List[List]
_append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.0, 0.0])
_append_band(bands, [0.5, 0.0, 0.0], [0.5, 0.5, 0.0])
_append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
_append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
phonon.run_band_structure(bands, with_group_velocities=True)
band_dict = phonon.get_band_structure_dict()
frequencies = band_dict['frequencies']
group_velocities = band_dict['group_velocities']
frequencies = band_dict["frequencies"]
group_velocities = band_dict["group_velocities"]
print(len(frequencies))
print(frequencies[0].shape)
print(len(group_velocities))

View File

@ -1,16 +1,16 @@
"""NaCl band structure calculation example."""
import phonopy
from phonopy.phonon.band_structure import get_band_qpoints
phonon = phonopy.load(supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]],
phonon = phonopy.load(
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
unitcell_filename="POSCAR-unitcell",
force_sets_filename="FORCE_SETS",
born_filename="BORN")
born_filename="BORN",
)
points = get_band_qpoints(
[[[0.5, 0, 0.5], [0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.25, 0.75]]], 51)
phonon.run_band_structure(points, labels=['X', '$\Gamma$', 'L', 'W'])
[[[0.5, 0, 0.5], [0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.25, 0.75]]], 51
)
phonon.run_band_structure(points, labels=["X", r"$\Gamma$", "L", "W"])
phonon.plot_band_structure().show()

View File

@ -1,35 +1,37 @@
#!/usr/bin/env python
"""NaCl example to obtain dynamical matrix."""
import numpy as np
import yaml
import phonopy
import yaml
import numpy as np
phonon = phonopy.load(supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]],
phonon = phonopy.load(
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
unitcell_filename="POSCAR-unitcell",
force_sets_filename="FORCE_SETS",
born_filename="BORN")
born_filename="BORN",
)
q = [0.1, 0.1, 0.1]
dynmat = phonon.get_dynamical_matrix_at_q(q)
print(dynmat)
phonon.run_qpoints(q, with_dynamical_matrices=True)
print(phonon.get_qpoints_dict()['frequencies'][0])
print(phonon.get_qpoints_dict()["frequencies"][0])
phonon.write_yaml_qpoints_phonon()
data = yaml.load(open("qpoints.yaml"), Loader=yaml.FullLoader)
dynmat_from_yaml = []
dynmat_data = data['phonon'][0]['dynamical_matrix']
dynmat_data = data["phonon"][0]["dynamical_matrix"]
for row in dynmat_data:
vals = np.reshape(row, (-1, 2))
dynmat_from_yaml.append(vals[:, 0] + vals[:, 1] * 1j)
dynmat_from_yaml = np.array(dynmat_from_yaml)
print(dynmat_from_yaml)
eigvals, eigvecs, = np.linalg.eigh(dynmat)
(
eigvals,
eigvecs,
) = np.linalg.eigh(dynmat)
frequencies = np.sqrt(np.abs(eigvals.real)) * np.sign(eigvals.real)
conversion_factor_to_THz = 15.633302
print(frequencies * conversion_factor_to_THz)

View File

@ -1,23 +1,23 @@
import numpy as np
import phonopy
"""Group velocity example by NaCl."""
import matplotlib.pyplot as plt
import numpy as np
phonon = phonopy.load(unitcell_filename="POSCAR-unitcell",
import phonopy
phonon = phonopy.load(
unitcell_filename="POSCAR-unitcell",
born_filename="BORN",
force_sets_filename="FORCE_SETS",
supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
)
phonon.run_mesh([31, 31, 31], with_group_velocities=True)
phonon_mesh = phonon.get_mesh_dict()
frequencies = phonon_mesh['frequencies']
group_velocity = phonon_mesh['group_velocities']
frequencies = phonon_mesh["frequencies"]
group_velocity = phonon_mesh["group_velocities"]
gv_norm = np.sqrt((group_velocity ** 2).sum(axis=2))
for i, (f, g) in enumerate(zip(frequencies.T, gv_norm.T)):
plt.plot(f, g, 'o', label=('band%d' % (i + 1)))
plt.plot(f, g, "o", label=("band%d" % (i + 1)))
plt.legend()
plt.xlabel("Frequency (THz)")
plt.ylabel("|group-velocity| (THz.A)")

View File

@ -1,20 +1,17 @@
"""Example to read and write FORCE_CONSTANTS file."""
import phonopy
from phonopy.file_IO import parse_FORCE_CONSTANTS, write_FORCE_CONSTANTS
phonon = phonopy.load(supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]],
phonon = phonopy.load(
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
unitcell_filename="POSCAR-unitcell",
force_sets_filename="FORCE_SETS",
born_filename="BORN")
write_FORCE_CONSTANTS(phonon.get_force_constants(),
filename="FORCE_CONSTANTS")
born_filename="BORN",
)
write_FORCE_CONSTANTS(phonon.force_constants, filename="FORCE_CONSTANTS")
force_constants = parse_FORCE_CONSTANTS()
phonon.force_constants = force_constants
phonon.symmetrize_force_constants()
write_FORCE_CONSTANTS(phonon.force_constants,
filename="FORCE_CONSTANTS_NEW")
write_FORCE_CONSTANTS(phonon.force_constants, filename="FORCE_CONSTANTS_NEW")

View File

@ -1,16 +1,14 @@
"""Example to obtain PhonopyYaml instance."""
import phonopy
from phonopy.interface.phonopy_yaml import PhonopyYaml
phonon = phonopy.load(supercell_matrix=[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]],
phonon = phonopy.load(
supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
unitcell_filename="POSCAR-unitcell",
force_sets_filename="FORCE_SETS",
born_filename="BORN")
phpy_yaml = PhonopyYaml(calculator='vasp',
settings={'force_constants': True})
born_filename="BORN",
)
phpy_yaml = PhonopyYaml(calculator="vasp", settings={"force_constants": True})
phpy_yaml.set_phonon_info(phonon)
print(phpy_yaml)

View File

@ -1,14 +1,19 @@
import numpy as np
from phonopy import Phonopy
from phonopy.interface.vasp import read_vasp
from phonopy.file_IO import parse_FORCE_SETS, parse_BORN
from phonopy.structure.atoms import PhonopyAtoms
"""Example of NaCl calculation."""
from typing import List
def append_band(bands, q_start, q_end):
import numpy as np
from phonopy import Phonopy
from phonopy.file_IO import parse_BORN, parse_FORCE_SETS
from phonopy.interface.vasp import read_vasp
# from phonopy.structure.atoms import PhonopyAtoms
def _append_band(bands, q_start, q_end):
band = []
for i in range(51):
band.append(np.array(q_start) +
(np.array(q_end) - np.array(q_start)) / 50 * i)
band.append(np.array(q_start) + (np.array(q_end) - np.array(q_start)) / 50 * i)
bands.append(band)
@ -26,13 +31,11 @@ unitcell = read_vasp("POSCAR-unitcell")
# [0, 0.5, 0],
# [0, 0, 0.5]])
phonon = Phonopy(unitcell,
[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
phonon = Phonopy(
unitcell,
[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
)
symmetry = phonon.get_symmetry()
print("Space group: %s" % symmetry.get_international_table())
@ -61,34 +64,34 @@ nac_params = parse_BORN(primitive, filename="BORN")
phonon.nac_params = nac_params
# BAND = 0.0 0.0 0.0 0.5 0.0 0.0 0.5 0.5 0.0 0.0 0.0 0.0 0.5 0.5 0.5
bands = []
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.0, 0.0])
append_band(bands, [0.5, 0.0, 0.0], [0.5, 0.5, 0.0])
append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
bands = List[List]
_append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.0, 0.0])
_append_band(bands, [0.5, 0.0, 0.0], [0.5, 0.5, 0.0])
_append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
_append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
phonon.set_band_structure(bands)
band_dict = phonon.get_band_structure_dict()
q_points = band_dict['qpoints']
distances = band_dict['distances']
frequencies = band_dict['frequencies']
eigvecs = band_dict['eigenvectors']
q_points = band_dict["qpoints"]
distances = band_dict["distances"]
frequencies = band_dict["frequencies"]
eigvecs = band_dict["eigenvectors"]
for q_path, d_path, freq_path in zip(q_points, distances, frequencies):
for q, d, freq in zip(q_path, d_path, freq_path):
print(("%10.5f %5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((d, q[0], q[1], q[2]) + tuple(freq)))
print(
("%10.5f %5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((d, q[0], q[1], q[2]) + tuple(freq))
)
phonon.plot_band_structure().show()
# Mesh sampling 20x20x20
phonon.run_mesh(mesh=[20, 20, 20])
phonon.run_thermal_properties(t_step=10,
t_max=1000,
t_min=0)
phonon.run_thermal_properties(t_step=10, t_max=1000, t_min=0)
# DOS
phonon.run_total_dos(sigma=0.1)
dos_dict = phonon.get_total_dos_dict()
for omega, dos in zip(dos_dict['frequency_points'], dos_dict['total_dos']):
for omega, dos in zip(dos_dict["frequency_points"], dos_dict["total_dos"]):
print("%15.7f%15.7f" % (omega, dos))
phonon.plot_total_dos().show()
@ -96,21 +99,19 @@ phonon.plot_total_dos().show()
tprop_dict = phonon.get_thermal_properties_dict()
for t, free_energy, entropy, cv in zip(
tprop_dict['temperatures'],
tprop_dict['free_energy'],
tprop_dict['entropy'],
tprop_dict['heat_capacity']):
tprop_dict["temperatures"],
tprop_dict["free_energy"],
tprop_dict["entropy"],
tprop_dict["heat_capacity"],
):
print(("%12.3f " + "%15.7f" * 3) % (t, free_energy, entropy, cv))
phonon.plot_thermal_properties().show()
# PDOS
phonon.run_mesh(mesh=[10, 10, 10],
is_mesh_symmetry=False,
with_eigenvectors=True)
phonon.run_mesh(mesh=[10, 10, 10], is_mesh_symmetry=False, with_eigenvectors=True)
phonon.run_projected_dos(use_tetrahedron_method=True)
pdos_dict = phonon.get_projected_dos_dict()
omegas = pdos_dict['frequency_points']
pdos = pdos_dict['projected_dos']
omegas = pdos_dict["frequency_points"]
pdos = pdos_dict["projected_dos"]
pdos_indices = [[0], [1]]
phonon.plot_projected_dos(pdos_indices=pdos_indices,
legend=pdos_indices).show()
phonon.plot_projected_dos(pdos_indices=pdos_indices, legend=pdos_indices).show()

View File

@ -24,4 +24,3 @@ for the CRYSTAL interface, so, the -c crystal.o parameter is not needed
Create a formatted plot (here band.yaml is in cm^{-1) units):
phonopy-bandplot --fmin=0 --line --ylabel="Frequency (cm\$^{-1}\$)" --band-labels="`grep BAND_LABELS band.conf | cut -d= -f2-`" -o dispersion.pdf

View File

@ -1,34 +1,55 @@
from phonopy import Phonopy
from phonopy.interface.vasp import read_vasp
from phonopy.file_IO import parse_FORCE_SETS
"""QHA example of Si."""
import tarfile
import matplotlib.pyplot as plt
import numpy as np
def get_frequency(poscar_filename, force_sets_filename):
from phonopy import Phonopy
from phonopy.interface.vasp import Vasprun, read_vasp
def get_force_sets(index):
"""Parse vasprun.xml and return forces."""
with tarfile.open("vasprun_xmls.tar.lzma", "r:xz") as tr:
with tr.extractfile("vasprun_xmls/vasprun.xml-%d" % index) as fp:
vasprun = Vasprun(fp, use_expat=True)
forces = vasprun.read_forces()
return forces
def get_frequency(poscar_filename, force_sets):
"""Calculate phonons and return frequencies."""
unitcell = read_vasp(poscar_filename)
volume = unitcell.get_volume()
phonon = Phonopy(unitcell,
[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
force_sets = parse_FORCE_SETS(filename=force_sets_filename)
phonon.set_displacement_dataset(force_sets)
volume = unitcell.volume
phonon = Phonopy(
unitcell,
[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
)
disps = np.zeros_like(force_sets)
disps[0] = [0.01, 0, 0]
phonon.dataset = {
"natom": len(force_sets),
"first_atoms": [
{"number": 0, "displacement": [0.01, 0, 0], "forces": force_sets}
],
}
phonon.produce_force_constants()
return phonon.get_frequencies([0.5, 0.5, 0]), volume
frequencies = []
volumes = []
for i in range(-10, 6):
def main():
"""Run QHA."""
frequencies = []
volumes = []
for i in range(-5, 6):
poscar_filename = "POSCAR-%d" % i
force_sets_filename = "FORCE_SETS-%d" % i
fs, v = get_frequency(poscar_filename, force_sets_filename)
force_sets = get_force_sets(i)
fs, v = get_frequency(poscar_filename, force_sets)
frequencies.append(fs)
volumes.append(v)
import matplotlib.pyplot as plt
for freq_at_X in np.array(frequencies).T:
for freq_at_X in np.array(frequencies).T:
freq_squared = freq_at_X ** 2 * np.sign(freq_at_X)
# np.sign is used to treat imaginary mode, since
# imaginary frequency is returned as a negative value from phonopy.
@ -37,5 +58,9 @@ for freq_at_X in np.array(frequencies).T:
# print("%f %f" % (v, f2))
# print('')
# print('')
plt.title("Frequeny squared (THz^2) at X-point vs volume (A^3)")
plt.show()
plt.title("Frequeny squared (THz^2) at X-point vs volume (A^3)")
plt.show()
if __name__ == "__main__":
main()

View File

@ -35,4 +35,3 @@ PRIMITIVE_AXES is defined in band.conf to create the phonon dispersions for the
Create a formatted plot (here band.yaml is in cm^{-1) units):
phonopy-bandplot --fmin=0 --line --ylabel="Frequency (cm\$^{-1}\$)" --band-labels "`grep BAND_LABELS band.conf | cut -d= -f2-`" -o dispersion.pdf

View File

@ -8,4 +8,3 @@ $coord
5.17898184151 0.00000000000 5.17898184151 si
0.00000000000 5.17898184151 5.17898184151 si
$end

View File

@ -2002,4 +2002,3 @@
2019-03-24 12:48:27.712

View File

@ -1,50 +1,55 @@
import numpy as np
from phonopy import Phonopy, PhonopyGruneisen
from phonopy.interface.vasp import read_vasp
from phonopy.file_IO import parse_FORCE_SETS
"""Example to calculate mode Grueneisen parameters."""
from typing import List
def append_band(bands, q_start, q_end):
import numpy as np
from phonopy import Phonopy, PhonopyGruneisen
from phonopy.file_IO import parse_FORCE_SETS
from phonopy.interface.vasp import read_vasp
def _append_band(bands: List[List], q_start, q_end):
band = []
for i in range(51):
band.append(np.array(q_start) +
(np.array(q_end) - np.array(q_start)) / 50 * i)
points = np.array(q_start) + (np.array(q_end) - np.array(q_start)) / 50 * i
band.append(points.tolist())
bands.append(band)
phonons = {}
for vol in ("orig", "plus", "minus"):
unitcell = read_vasp("%s/POSCAR-unitcell" % vol)
phonon = Phonopy(unitcell,
[[2, 0, 0],
[0, 2, 0],
[0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
phonon = Phonopy(
unitcell,
[[2, 0, 0], [0, 2, 0], [0, 0, 2]],
primitive_matrix=[[0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]],
)
force_sets = parse_FORCE_SETS(filename="%s/FORCE_SETS" % vol)
phonon.set_displacement_dataset(force_sets)
phonon.produce_force_constants()
phonons[vol] = phonon
gruneisen = PhonopyGruneisen(phonons["orig"],
phonons["plus"],
phonons["minus"])
gruneisen = PhonopyGruneisen(phonons["orig"], phonons["plus"], phonons["minus"])
gruneisen.set_mesh([2, 2, 2])
q_points, _, frequencies, _, gammas = gruneisen.get_mesh()
for q, freq, g in zip(q_points, frequencies, gammas):
print(("%5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((q[0], q[1], q[2]) + tuple(freq)))
print(
("%5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((q[0], q[1], q[2]) + tuple(freq))
)
print(((" " * 18) + (" %7.3f" * len(g))) % tuple(g))
bands = []
append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
bands: List[List] = []
_append_band(bands, [0.5, 0.5, 0.0], [0.0, 0.0, 0.0])
_append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
gruneisen.set_band_structure(bands)
q_points, distances, frequencies, _, gammas = gruneisen.get_band_structure()
for q_path, d_path, freq_path, g_path in zip(q_points, distances,
frequencies, gammas):
for q_path, d_path, freq_path, g_path in zip(q_points, distances, frequencies, gammas):
for q, d, freq, g in zip(q_path, d_path, freq_path, g_path):
print(("%10.5f %5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((d, q[0], q[1], q[2]) + tuple(freq)))
print(
("%10.5f %5.2f %5.2f %5.2f " + (" %7.3f" * len(freq)))
% ((d, q[0], q[1], q[2]) + tuple(freq))
)
print(((" " * 30) + (" %7.3f" * len(g))) % tuple(g))

View File

@ -46,4 +46,3 @@ LatticeConstant 5.430 Ang
0.00 0.00 0.00 1 # Si 1
0.25 0.25 0.25 1 # Si 2
%endblock AtomicCoordinatesAndAtomicSpecies

View File

@ -3,4 +3,3 @@ Phonon calculation for TiO2-anatase. Supercell dimension is 4x4x2 and forces wer
Band structure is drawn with Seek-Path (pip install seekpath is needed) by
% phonopy band.conf -p --nac

View File

@ -1,98 +0,0 @@
from gpaw import GPAW, PW
from ase import Atoms
from phonopy import Phonopy
from phonopy.structure.atoms import PhonopyAtoms
import numpy as np
def get_gpaw(kpts_size=None):
if kpts_size is None:
calc = GPAW(mode=PW(300),
kpts={'size': (2, 2, 2)})
else:
calc = GPAW(mode=PW(300),
kpts={'size': kpts_size})
return calc
def get_crystal():
a = 5.404
cell = PhonopyAtoms(symbols=(['Si'] * 8),
cell=np.diag((a, a, a)),
scaled_positions=[(0, 0, 0),
(0, 0.5, 0.5),
(0.5, 0, 0.5),
(0.5, 0.5, 0),
(0.25, 0.25, 0.25),
(0.25, 0.75, 0.75),
(0.75, 0.25, 0.75),
(0.75, 0.75, 0.25)])
return cell
def phonopy_pre_process(cell, supercell_matrix=None):
if supercell_matrix is None:
smat = [[2,0,0], [0,2,0], [0,0,2]],
else:
smat = supercell_matrix
phonon = Phonopy(cell,
smat,
primitive_matrix=[[0, 0.5, 0.5],
[0.5, 0, 0.5],
[0.5, 0.5, 0]])
phonon.generate_displacements(distance=0.03)
print("[Phonopy] Atomic displacements:")
disps = phonon.get_displacements()
for d in disps:
print("[Phonopy] %d %s" % (d[0], d[1:]))
return phonon
def run_gpaw(calc, phonon):
supercells = phonon.get_supercells_with_displacements()
# Force calculations by calculator
set_of_forces = []
for scell in supercells:
cell = Atoms(symbols=scell.get_chemical_symbols(),
scaled_positions=scell.get_scaled_positions(),
cell=scell.get_cell(),
pbc=True)
cell.set_calculator(calc)
forces = cell.get_forces()
drift_force = forces.sum(axis=0)
print(("[Phonopy] Drift force:" + "%11.5f" * 3) % tuple(drift_force))
# Simple translational invariance
for force in forces:
force -= drift_force / forces.shape[0]
set_of_forces.append(forces)
return set_of_forces
def phonopy_post_process(phonon, set_of_forces):
phonon.produce_force_constants(forces=set_of_forces)
print('')
print("[Phonopy] Phonon frequencies at Gamma:")
for i, freq in enumerate(phonon.get_frequencies((0, 0, 0))):
print("[Phonopy] %3d: %10.5f THz" % (i + 1, freq)) # THz
# DOS
phonon.set_mesh([21, 21, 21])
phonon.set_total_DOS(tetrahedron_method=True)
print('')
print("[Phonopy] Phonon DOS:")
for omega, dos in np.array(phonon.get_total_DOS()).T:
print("%15.7f%15.7f" % (omega, dos))
def main():
cell = get_crystal()
# 1x1x1 supercell of conventional unit cell
calc = get_gpaw(kpts_size=(4, 4, 4))
phonon = phonopy_pre_process(cell, supercell_matrix=np.eye(3, dtype='intc'))
# # 2x2x2 supercell of conventional unit cell
# calc = get_gpaw(kpts_size=(2, 2, 2))
# phonon = phonopy_pre_process(cell,
# supercell_matrix=(np.eye(3, dtype='intc') * 2))
set_of_forces = run_gpaw(calc, phonon)
phonopy_post_process(phonon, set_of_forces)
if __name__ == "__main__":
main()

View File

@ -1,3 +1,4 @@
"""Phonon calculation code: Phonopy."""
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
@ -32,8 +33,8 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from phonopy.version import __version__
from phonopy.api_phonopy import Phonopy
from phonopy.api_gruneisen import PhonopyGruneisen
from phonopy.api_qha import PhonopyQHA
from phonopy.cui.load import load
from phonopy.api_gruneisen import PhonopyGruneisen # noqa F401
from phonopy.api_phonopy import Phonopy # noqa F401
from phonopy.api_qha import PhonopyQHA # noqa F401
from phonopy.cui.load import load # noqa F401
from phonopy.version import __version__ # noqa F401

View File

@ -1,3 +1,4 @@
"""API of mode Grueneisen parameter calculation."""
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
@ -32,17 +33,25 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from phonopy.gruneisen import GruneisenMesh
from phonopy.gruneisen import GruneisenBandStructure
from phonopy.gruneisen import GruneisenThermalProperties
from phonopy.gruneisen.band_structure import GruneisenBandStructure
from phonopy.gruneisen.mesh import GruneisenMesh
class PhonopyGruneisen(object):
def __init__(self,
phonon,
phonon_plus,
phonon_minus,
delta_strain=None):
class PhonopyGruneisen:
"""Class to calculate mode Grueneisen parameters."""
def __init__(self, phonon, phonon_plus, phonon_minus, delta_strain=None):
"""Init method.
Parameters
----------
phonon, phonon_plus, phonon_minus : Phonopy
Phonopy instances of the same crystal with differet volumes,
V_0, V_0 + dV, V_0 - dV.
delta_strain : float, optional
Default is None, which gives dV / V_0.
"""
self._phonon = phonon
self._phonon_plus = phonon_plus
self._phonon_minus = phonon_minus
@ -50,28 +59,31 @@ class PhonopyGruneisen(object):
self._mesh = None
self._band_structure = None
self._thermal_properties = None
def get_phonon(self):
"""Return Phonopy class instance at dV=0."""
return self._phonon
def set_mesh(self,
def set_mesh(
self,
mesh,
shift=None,
is_time_reversal=True,
is_gamma_center=False,
is_mesh_symmetry=True):
is_mesh_symmetry=True,
):
"""Set sampling mesh."""
for phonon in (self._phonon, self._phonon_plus, self._phonon_minus):
if phonon.get_dynamical_matrix() is None:
if phonon.dynamical_matrix is None:
print("Warning: Dynamical matrix has not yet built.")
return False
symmetry = phonon.get_primitive_symmetry()
rotations = symmetry.get_pointgroup_operations()
symmetry = phonon.primitive_symmetry
rotations = symmetry.pointgroup_operations
self._mesh = GruneisenMesh(
self._phonon.get_dynamical_matrix(),
self._phonon_plus.get_dynamical_matrix(),
self._phonon_minus.get_dynamical_matrix(),
self._phonon.dynamical_matrix,
self._phonon_plus.dynamical_matrix,
self._phonon_minus.dynamical_matrix,
mesh,
delta_strain=self._delta_strain,
shift=shift,
@ -79,94 +91,86 @@ class PhonopyGruneisen(object):
is_gamma_center=is_gamma_center,
is_mesh_symmetry=is_mesh_symmetry,
rotations=rotations,
factor=self._phonon.get_unit_conversion_factor())
factor=self._phonon.unit_conversion_factor,
)
return True
def get_mesh(self):
"""Return mode Grueneisen parameters calculated on sampling mesh."""
if self._mesh is None:
return None
else:
return (self._mesh.get_qpoints(),
return (
self._mesh.get_qpoints(),
self._mesh.get_weights(),
self._mesh.get_frequencies(),
self._mesh.get_eigenvectors(),
self._mesh.get_gruneisen())
self._mesh.get_gruneisen(),
)
def write_yaml_mesh(self):
"""Write mesh sampling calculation results to file in yaml."""
self._mesh.write_yaml()
def write_hdf5_mesh(self):
"""Write mesh sampling calculation results to file in hdf5."""
self._mesh.write_hdf5()
def plot_mesh(self,
cutoff_frequency=None,
color_scheme=None,
marker='o',
markersize=None):
def plot_mesh(
self, cutoff_frequency=None, color_scheme=None, marker="o", markersize=None
):
"""Return pyplot of mesh sampling calculation results."""
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.xaxis.set_ticks_position('both')
ax.yaxis.set_ticks_position('both')
ax.xaxis.set_tick_params(which='both', direction='in')
ax.yaxis.set_tick_params(which='both', direction='in')
self._mesh.plot(plt,
ax.xaxis.set_ticks_position("both")
ax.yaxis.set_ticks_position("both")
ax.xaxis.set_tick_params(which="both", direction="in")
ax.yaxis.set_tick_params(which="both", direction="in")
self._mesh.plot(
plt,
cutoff_frequency=cutoff_frequency,
color_scheme=color_scheme,
marker=marker,
markersize=markersize)
markersize=markersize,
)
return plt
def set_band_structure(self, bands):
"""Set band structure paths."""
self._band_structure = GruneisenBandStructure(
bands,
self._phonon.get_dynamical_matrix(),
self._phonon_plus.get_dynamical_matrix(),
self._phonon_minus.get_dynamical_matrix(),
self._phonon.dynamical_matrix,
self._phonon_plus.dynamical_matrix,
self._phonon_minus.dynamical_matrix,
delta_strain=self._delta_strain,
factor=self._phonon.get_unit_conversion_factor())
factor=self._phonon.unit_conversion_factor,
)
def get_band_structure(self):
"""Return band structure calculation results."""
band = self._band_structure
return (band.get_qpoints(),
return (
band.get_qpoints(),
band.get_distances(),
band.get_frequencies(),
band.get_eigenvectors(),
band.get_gruneisen())
band.get_gruneisen(),
)
def write_yaml_band_structure(self):
"""Write band structure calculation results to file in yaml."""
self._band_structure.write_yaml()
def plot_band_structure(self,
epsilon=1e-4,
color_scheme=None):
def plot_band_structure(self, epsilon=1e-4, color_scheme=None):
"""Return pyplot of band structure calculation results."""
import matplotlib.pyplot as plt
fig, axarr = plt.subplots(2, 1)
for ax in axarr:
ax.xaxis.set_ticks_position('both')
ax.yaxis.set_ticks_position('both')
ax.xaxis.set_tick_params(which='both', direction='in')
ax.yaxis.set_tick_params(which='both', direction='in')
self._band_structure.plot(axarr,
epsilon=epsilon,
color_scheme=color_scheme)
ax.xaxis.set_ticks_position("both")
ax.yaxis.set_ticks_position("both")
ax.xaxis.set_tick_params(which="both", direction="in")
ax.yaxis.set_tick_params(which="both", direction="in")
self._band_structure.plot(axarr, epsilon=epsilon, color_scheme=color_scheme)
return plt
def set_thermal_properties(self,
volumes,
t_step=2,
t_max=2004,
t_min=0,
cutoff_frequency=None):
self._thermal_properties = GruneisenThermalProperties(
self._mesh,
volumes,
t_step=t_step,
t_max=t_max,
t_min=t_min,
cutoff_frequency=cutoff_frequency)
def get_thermal_properties(self):
return self._thermal_properties
def write_yaml_thermal_properties(self, filename='thermal_properties'):
self._thermal_properties.write_yaml(filename=filename)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
"""API for QHA calculation."""
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
@ -33,23 +34,26 @@
# POSSIBILITY OF SUCH DAMAGE.
import warnings
from phonopy.qha import BulkModulus, QHA
from phonopy.qha.core import QHA, BulkModulus
class PhonopyQHA(object):
class PhonopyQHA:
"""PhonopyQHA API."""
def __init__(self,
def __init__(
self,
volumes=None,
electronic_energies=None,
temperatures=None,
free_energy=None,
cv=None,
entropy=None,
eos='vinet',
eos="vinet",
t_max=None,
energy_plot_factor=None,
verbose=False):
verbose=False,
):
"""Init method.
Notes
@ -98,12 +102,11 @@ class PhonopyQHA(object):
Show log or not.
"""
self._bulk_modulus = BulkModulus(volumes,
electronic_energies,
eos=eos)
self._bulk_modulus = BulkModulus(volumes, electronic_energies, eos=eos)
if temperatures is not None:
self._qha = QHA(volumes,
self._qha = QHA(
volumes,
electronic_energies,
temperatures,
cv,
@ -111,7 +114,8 @@ class PhonopyQHA(object):
free_energy,
eos=eos,
t_max=t_max,
energy_plot_factor=energy_plot_factor)
energy_plot_factor=energy_plot_factor,
)
self._qha.run(verbose=verbose)
@property
@ -250,179 +254,224 @@ class PhonopyQHA(object):
"""
return self._bulk_modulus.get_parameters()
def write_helmholtz_volume(self, filename='helmholtz-volume.dat'):
def write_helmholtz_volume(self, filename="helmholtz-volume.dat"):
"""Write Helmholtz free energy vs volume in file."""
self._qha.write_helmholtz_volume(filename=filename)
def write_helmholtz_volume_fitted(self,
thin_number,
filename='helmholtz-volume_fitted.dat'):
def write_helmholtz_volume_fitted(
self, thin_number, filename="helmholtz-volume_fitted.dat"
):
"""Write Helmholtz free energy (fitted) vs volume in file."""
self._qha.write_helmholtz_volume_fitted(thin_number, filename=filename)
def write_volume_temperature(self, filename='volume-temperature.dat'):
def write_volume_temperature(self, filename="volume-temperature.dat"):
"""Write volume vs temperature in file."""
self._qha.write_volume_temperature(filename=filename)
def write_thermal_expansion(self,
filename='thermal_expansion.dat'):
def write_thermal_expansion(self, filename="thermal_expansion.dat"):
"""Write thermal expansion vs temperature in file."""
self._qha.write_thermal_expansion(filename=filename)
def write_gibbs_temperature(self, filename='gibbs-temperature.dat'):
def write_gibbs_temperature(self, filename="gibbs-temperature.dat"):
"""Write Gibbs free energy vs temperature in file."""
self._qha.write_gibbs_temperature(filename=filename)
def write_bulk_modulus_temperature(self,
filename='bulk_modulus-temperature.dat'):
def write_bulk_modulus_temperature(self, filename="bulk_modulus-temperature.dat"):
"""Write bulk modulus vs temperature in file."""
self._qha.write_bulk_modulus_temperature(filename=filename)
def plot_bulk_modulus(self):
"""Returns matplotlib.pyplot of bulk modulus fitting curve"""
"""Return pyplot of bulk modulus fitting curve."""
return self._bulk_modulus.plot()
def plot_qha(self, thin_number=10, volume_temp_exp=None):
"""Returns matplotlib.pyplot of QHA fitting curves at temperatures"""
return self._qha.plot(thin_number=thin_number,
volume_temp_exp=volume_temp_exp)
"""Return pyplot of QHA fitting curves at temperatures."""
return self._qha.plot(thin_number=thin_number, volume_temp_exp=volume_temp_exp)
def plot_helmholtz_volume(self,
thin_number=10,
xlabel=r'Volume $(\AA^3)$',
ylabel='Free energy'):
return self._qha.plot_helmholtz_volume(thin_number=thin_number,
xlabel=xlabel,
ylabel=ylabel)
def plot_helmholtz_volume(
self, thin_number=10, xlabel=r"Volume $(\AA^3)$", ylabel="Free energy"
):
"""Return pyplot of Helmholtz free energes vs volume at temperatures."""
return self._qha.plot_helmholtz_volume(
thin_number=thin_number, xlabel=xlabel, ylabel=ylabel
)
def plot_pdf_helmholtz_volume(self,
thin_number=10,
filename='helmholtz-volume.pdf'):
self._qha.plot_pdf_helmholtz_volume(thin_number=thin_number,
filename=filename)
def plot_pdf_helmholtz_volume(
self, thin_number=10, filename="helmholtz-volume.pdf"
):
"""Plot Helmholtz free energes vs volume at temperatures in pdf."""
self._qha.plot_pdf_helmholtz_volume(thin_number=thin_number, filename=filename)
def plot_volume_temperature(self, exp_data=None):
"""Return pyplot of volume vs temperature."""
return self._qha.plot_volume_temperature(exp_data=exp_data)
def plot_pdf_volume_temperature(self,
exp_data=None,
filename='volume-temperature.pdf'):
self._qha.plot_pdf_volume_temperature(exp_data=exp_data,
filename=filename)
def plot_pdf_volume_temperature(
self, exp_data=None, filename="volume-temperature.pdf"
):
"""Plot volume vs temperature in pdf."""
self._qha.plot_pdf_volume_temperature(exp_data=exp_data, filename=filename)
def plot_thermal_expansion(self):
"""Return pyplot of thermal expansion vs temperature."""
return self._qha.plot_thermal_expansion()
def plot_pdf_thermal_expansion(self,
filename='thermal_expansion.pdf'):
def plot_pdf_thermal_expansion(self, filename="thermal_expansion.pdf"):
"""Plot thermal expansion vs temperature in pdf."""
self._qha.plot_pdf_thermal_expansion(filename=filename)
def plot_gibbs_temperature(self,
xlabel='Temperature (K)',
ylabel='Gibbs free energy'):
return self._qha.plot_gibbs_temperature(xlabel=xlabel,
ylabel=ylabel)
def plot_gibbs_temperature(
self, xlabel="Temperature (K)", ylabel="Gibbs free energy"
):
"""Return pyplot of Gibbs free energy vs temperature."""
return self._qha.plot_gibbs_temperature(xlabel=xlabel, ylabel=ylabel)
def plot_pdf_gibbs_temperature(self, filename='gibbs-temperature.pdf'):
def plot_pdf_gibbs_temperature(self, filename="gibbs-temperature.pdf"):
"""Plot Gibbs free energy vs temperature in pdf."""
self._qha.plot_pdf_gibbs_temperature(filename=filename)
def plot_bulk_modulus_temperature(self,
xlabel='Temperature (K)',
ylabel='Bulk modulus'):
return self._qha.plot_bulk_modulus_temperature(xlabel=xlabel,
ylabel=ylabel)
def plot_bulk_modulus_temperature(
self, xlabel="Temperature (K)", ylabel="Bulk modulus"
):
"""Return pyplot of bulk modulus vs temperature."""
return self._qha.plot_bulk_modulus_temperature(xlabel=xlabel, ylabel=ylabel)
def plot_pdf_bulk_modulus_temperature(self,
filename='bulk_modulus-temperature.pdf'):
def plot_pdf_bulk_modulus_temperature(
self, filename="bulk_modulus-temperature.pdf"
):
"""Plot bulk modulus vs temperature in pdf."""
self._qha.plot_pdf_bulk_modulus_temperature(filename=filename)
def plot_heat_capacity_P_numerical(self, Z=1, exp_data=None):
return self._qha.plot_heat_capacity_P_numerical(Z=Z,
exp_data=exp_data)
"""Return pyplot of C_P by numerical difference vs temperature."""
return self._qha.plot_heat_capacity_P_numerical(Z=Z, exp_data=exp_data)
def plot_pdf_heat_capacity_P_numerical(self,
exp_data=None,
filename='Cp-temperature.pdf'):
self._qha.plot_pdf_heat_capacity_P_numerical(exp_data=exp_data,
filename=filename)
def plot_pdf_heat_capacity_P_numerical(
self, exp_data=None, filename="Cp-temperature.pdf"
):
"""Plot C_P by numerical difference vs temperature in pdf."""
self._qha.plot_pdf_heat_capacity_P_numerical(
exp_data=exp_data, filename=filename
)
def write_heat_capacity_P_numerical(self, filename='Cp-temperature.dat'):
def write_heat_capacity_P_numerical(self, filename="Cp-temperature.dat"):
"""Write C_P by numerical difference vs temperature in file."""
self._qha.write_heat_capacity_P_numerical(filename=filename)
def plot_heat_capacity_P_polyfit(self, exp_data=None, Z=1):
return self._qha.plot_heat_capacity_P_polyfit(
Z=Z, exp_data=exp_data)
"""Return pyplot of C_P by fittings vs temperature."""
return self._qha.plot_heat_capacity_P_polyfit(Z=Z, exp_data=exp_data)
def plot_pdf_heat_capacity_P_polyfit(self,
exp_data=None,
filename='Cp-temperature_polyfit.pdf'):
self._qha.plot_pdf_heat_capacity_P_polyfit(exp_data=exp_data,
filename=filename)
def plot_pdf_heat_capacity_P_polyfit(
self, exp_data=None, filename="Cp-temperature_polyfit.pdf"
):
"""Plot C_P by fittings vs temperature in pdf."""
self._qha.plot_pdf_heat_capacity_P_polyfit(exp_data=exp_data, filename=filename)
def write_heat_capacity_P_polyfit(self,
filename='Cp-temperature_polyfit.dat',
filename_ev='entropy-volume.dat',
filename_cvv='Cv-volume.dat',
filename_dsdvt='dsdv-temperature.dat'):
self._qha.write_heat_capacity_P_polyfit(filename=filename,
def write_heat_capacity_P_polyfit(
self,
filename="Cp-temperature_polyfit.dat",
filename_ev="entropy-volume.dat",
filename_cvv="Cv-volume.dat",
filename_dsdvt="dsdv-temperature.dat",
):
"""Write C_P by fittings vs temperature in file."""
self._qha.write_heat_capacity_P_polyfit(
filename=filename,
filename_ev=filename_ev,
filename_cvv=filename_cvv,
filename_dsdvt=filename_dsdvt)
filename_dsdvt=filename_dsdvt,
)
def plot_gruneisen_temperature(self):
"""Return pyplot of Grueneisen parameter vs temperature."""
return self._qha.plot_gruneisen_temperature()
def plot_pdf_gruneisen_temperature(self,
filename='gruneisen-temperature.pdf'):
def plot_pdf_gruneisen_temperature(self, filename="gruneisen-temperature.pdf"):
"""Plot Grueneisen parameter vs temperature in pdf."""
self._qha.plot_pdf_gruneisen_temperature(filename=filename)
def write_gruneisen_temperature(self, filename='gruneisen-temperature.dat'):
def write_gruneisen_temperature(self, filename="gruneisen-temperature.dat"):
"""Write Grueneisen parameter vs temperature in file."""
self._qha.write_gruneisen_temperature(filename=filename)
def get_bulk_modulus(self):
warnings.warn("PhonopyQHA.get_bulk_modulus() is deprecated."
"""Return bulk moduli with no phonon contribution."""
warnings.warn(
"PhonopyQHA.get_bulk_modulus() is deprecated."
"Use bulk_modulus attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.bulk_modulus
def get_helmholtz_volume(self):
warnings.warn("PhonopyQHA.get_helmholtz_volume() is deprecated."
"""Return Helmholtz free energies at temperatures and volumes."""
warnings.warn(
"PhonopyQHA.get_helmholtz_volume() is deprecated."
"Use helmholtz_volume attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.helmholtz_volume
def get_volume_temperature(self):
warnings.warn("PhonopyQHA.get_volume_temperature() is deprecated."
"""Return equilibrium volumes at temperatures."""
warnings.warn(
"PhonopyQHA.get_volume_temperature() is deprecated."
"Use volume_temperature attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.volume_temperature
def get_thermal_expansion(self):
warnings.warn("PhonopyQHA.get_thermal_expansion() is deprecated."
"""Return thermal expansion coefficients at temperatures."""
warnings.warn(
"PhonopyQHA.get_thermal_expansion() is deprecated."
"Use thermal_expansion attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.thermal_expansion
def get_gibbs_temperature(self):
warnings.warn("PhonopyQHA.get_gibbs_temperature() is deprecated."
"""Return Gibbs free energies at temperatures."""
warnings.warn(
"PhonopyQHA.get_gibbs_temperature() is deprecated."
"Use gibbs_temperature attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.gibbs_temperature
def get_bulk_modulus_temperature(self):
warnings.warn("PhonopyQHA.get_bulk_modulus_temperature() is deprecated."
"""Return bulk moduli at temperatures."""
warnings.warn(
"PhonopyQHA.get_bulk_modulus_temperature() is deprecated."
"Use bulk_modulus_temperature attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.bulk_modulus_temperature
def get_heat_capacity_P_numerical(self):
warnings.warn("PhonopyQHA.get_heat_capacity_P_numerical() is deprecated."
"""Return C_P calculated by numerical differentiation."""
warnings.warn(
"PhonopyQHA.get_heat_capacity_P_numerical() is deprecated."
"Use heat_capacity_P_numerical attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.heat_capacity_P_numerical
def get_heat_capacity_P_polyfit(self):
warnings.warn("PhonopyQHA.get_heat_capacity_P_polyfit() is deprecated."
"""Return C_P calculated by fittings."""
warnings.warn(
"PhonopyQHA.get_heat_capacity_P_polyfit() is deprecated."
"Use heat_capacity_P_polyfit attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.heat_capacity_P_polyfit
def get_gruneisen_temperature(self):
warnings.warn("PhonopyQHA.get_gruneisen_temperature() is deprecated."
"""Return Grueneisen paramters at temperatures."""
warnings.warn(
"PhonopyQHA.get_gruneisen_temperature() is deprecated."
"Use gruneisen_temperature attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.gruneisen_temperature

View File

@ -0,0 +1,34 @@
"""Routines for command user interface."""
# Copyright (C) 2021 Atsushi Togo
# All rights reserved.
#
# This file is part of phonopy.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of the phonopy project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,3 +1,4 @@
"""Routines to collect crystal structure information."""
# Copyright (C) 2018 Atsushi Togo
# All rights reserved.
#
@ -33,13 +34,17 @@
# POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from phonopy.interface.calculator import (
read_crystal_structure, get_default_cell_filename)
from phonopy.interface.vasp import read_vasp
get_default_cell_filename,
read_crystal_structure,
)
from phonopy.interface.phonopy_yaml import PhonopyYaml
from phonopy.interface.vasp import read_vasp
def collect_cell_info(supercell_matrix=None,
def collect_cell_info(
supercell_matrix=None,
primitive_matrix=None,
interface_mode=None,
cell_filename=None,
@ -47,15 +52,16 @@ def collect_cell_info(supercell_matrix=None,
enforce_primitive_matrix_auto=False,
phonopy_yaml_cls=None,
symprec=1e-5,
return_dict=False):
return_dict=False,
):
"""Collect crystal structure information from inputs."""
# In some cases, interface mode falls back to phonopy_yaml mode.
fallback_reason = _fallback_to_phonopy_yaml(
supercell_matrix,
interface_mode,
cell_filename)
supercell_matrix, interface_mode, cell_filename
)
if fallback_reason:
_interface_mode = 'phonopy_yaml'
_interface_mode = "phonopy_yaml"
elif interface_mode is None:
_interface_mode = None
else:
@ -65,34 +71,40 @@ def collect_cell_info(supercell_matrix=None,
filename=cell_filename,
interface_mode=_interface_mode,
chemical_symbols=chemical_symbols,
phonopy_yaml_cls=phonopy_yaml_cls)
phonopy_yaml_cls=phonopy_yaml_cls,
)
# Error check
if unitcell is None:
err_msg = _get_error_message(optional_structure_info,
err_msg = _get_error_message(
optional_structure_info,
interface_mode,
fallback_reason,
cell_filename,
phonopy_yaml_cls)
phonopy_yaml_cls,
)
return err_msg
# Retrieve more information on cells
(interface_mode_out,
(
interface_mode_out,
supercell_matrix_out,
primitive_matrix_out) = _collect_cells_info(
primitive_matrix_out,
) = _collect_cells_info(
_interface_mode,
optional_structure_info,
interface_mode,
supercell_matrix,
primitive_matrix,
enforce_primitive_matrix_auto)
enforce_primitive_matrix_auto,
)
# Another error check
msg_list = ["Crystal structure was read from \"%s\"."
% optional_structure_info[0], ]
msg_list = [
'Crystal structure was read from "%s".' % optional_structure_info[0],
]
if supercell_matrix_out is None:
msg_list.append(
"Supercell matrix (DIM or --dim) information was not found.")
msg_list.append("Supercell matrix (DIM or --dim) information was not found.")
return "\n".join(msg_list)
if np.linalg.det(unitcell.get_cell()) < 0.0:
@ -100,27 +112,33 @@ def collect_cell_info(supercell_matrix=None,
return "\n".join(msg_list)
# Succeeded!
if _interface_mode == 'phonopy_yaml':
phpy_yaml = optional_structure_info[1]
if _interface_mode == "phonopy_yaml":
phpy_yaml: PhonopyYaml = optional_structure_info[1]
else:
phpy_yaml = None
if return_dict:
return {'unitcell': unitcell,
'supercell_matrix': supercell_matrix_out,
'primitive_matrix': primitive_matrix_out,
'optional_structure_info': optional_structure_info,
'interface_mode': interface_mode_out,
'phonopy_yaml': phpy_yaml}
return {
"unitcell": unitcell,
"supercell_matrix": supercell_matrix_out,
"primitive_matrix": primitive_matrix_out,
"optional_structure_info": optional_structure_info,
"interface_mode": interface_mode_out,
"phonopy_yaml": phpy_yaml,
}
else:
return (unitcell, supercell_matrix_out, primitive_matrix_out,
optional_structure_info, interface_mode_out, phpy_yaml)
return (
unitcell,
supercell_matrix_out,
primitive_matrix_out,
optional_structure_info,
interface_mode_out,
phpy_yaml,
)
def _fallback_to_phonopy_yaml(supercell_matrix,
interface_mode,
cell_filename):
"""Find possibility to fallback to phonopy.yaml mode
def _fallback_to_phonopy_yaml(supercell_matrix, interface_mode, cell_filename):
"""Find possibility to fallback to phonopy.yaml mode.
Fallback happens in any of the following cases.
@ -145,7 +163,6 @@ def _fallback_to_phonopy_yaml(supercell_matrix,
None means fallback to phonopy.yaml mode will not happen.
"""
fallback_reason = None
if interface_mode is None:
@ -159,7 +176,7 @@ def _fallback_to_phonopy_yaml(supercell_matrix,
def _poscar_failed(cell_filename):
"""Determine if fall back happens
"""Determine if fall back happens.
1) read_vasp (parsing POSCAR-style file) is failed. --> fallback
@ -181,11 +198,10 @@ def _poscar_failed(cell_filename):
handled properly (read_crystal_structure).
"""
fallback_reason = None
try:
if cell_filename is None:
read_vasp(get_default_cell_filename('vasp'))
read_vasp(get_default_cell_filename("vasp"))
else:
read_vasp(cell_filename)
except ValueError:
@ -201,17 +217,16 @@ def _poscar_failed(cell_filename):
return fallback_reason
def _collect_cells_info(_interface_mode,
def _collect_cells_info(
_interface_mode,
optional_structure_info,
interface_mode,
supercell_matrix,
primitive_matrix,
enforce_primitive_matrix_auto):
"""This is a method just to wrap up and exclude dirty stuffs."""
if (_interface_mode == 'phonopy_yaml' and
optional_structure_info[1] is not None):
phpy = optional_structure_info[1]
enforce_primitive_matrix_auto,
):
if _interface_mode == "phonopy_yaml" and optional_structure_info[1] is not None:
phpy: PhonopyYaml = optional_structure_info[1]
if phpy.calculator is None:
interface_mode_out = interface_mode
else:
@ -225,17 +240,17 @@ def _collect_cells_info(_interface_mode,
elif phpy.primitive_matrix is not None:
_primitive_matrix = phpy.primitive_matrix
else:
_primitive_matrix = 'auto'
_primitive_matrix = "auto"
else:
interface_mode_out = _interface_mode
_supercell_matrix = supercell_matrix
_primitive_matrix = primitive_matrix
if enforce_primitive_matrix_auto:
_primitive_matrix = 'auto'
_primitive_matrix = "auto"
if _supercell_matrix is None and _primitive_matrix == 'auto':
supercell_matrix_out = np.eye(3, dtype='intc')
if _supercell_matrix is None and _primitive_matrix == "auto":
supercell_matrix_out = np.eye(3, dtype="intc")
else:
supercell_matrix_out = _supercell_matrix
@ -244,11 +259,13 @@ def _collect_cells_info(_interface_mode,
return interface_mode_out, supercell_matrix_out, primitive_matrix_out
def _get_error_message(optional_structure_info,
def _get_error_message(
optional_structure_info,
interface_mode,
fallback_reason,
cell_filename,
phonopy_yaml_cls):
phonopy_yaml_cls,
):
final_cell_filename = optional_structure_info[0]
if phonopy_yaml_cls is None:
_phonopy_yaml_cls = PhonopyYaml
@ -258,10 +275,12 @@ def _get_error_message(optional_structure_info,
if fallback_reason is None:
msg_list = []
if cell_filename != final_cell_filename:
msg_list.append("Crystal structure file \"%s\" was not found."
% cell_filename)
msg_list.append("Crystal structure file \"%s\" was not found."
% final_cell_filename)
msg_list.append(
'Crystal structure file "%s" was not found.' % cell_filename
)
msg_list.append(
'Crystal structure file "%s" was not found.' % final_cell_filename
)
return "\n".join(msg_list)
####################################
@ -269,32 +288,32 @@ def _get_error_message(optional_structure_info,
####################################
msg_list = []
if fallback_reason in ["default file not found",
"read_vasp parsing failed"]:
if fallback_reason in ["default file not found", "read_vasp parsing failed"]:
if cell_filename:
vasp_filename = cell_filename
else:
vasp_filename = get_default_cell_filename('vasp')
vasp_filename = get_default_cell_filename("vasp")
if fallback_reason == "read_vasp parsing failed":
msg_list.append(
"Parsing crystal structure file of \"%s\" failed."
% vasp_filename)
'Parsing crystal structure file of "%s" failed.' % vasp_filename
)
else:
msg_list.append(
"Crystal structure file of \"%s\" was not found."
% vasp_filename)
'Crystal structure file of "%s" was not found.' % vasp_filename
)
elif fallback_reason == "no supercell matrix given":
msg_list.append("Supercell matrix (DIM or --dim) was not explicitly "
"specified.")
msg_list.append(
"Supercell matrix (DIM or --dim) was not explicitly " "specified."
)
msg_list.append("By this reason, %s_yaml mode was invoked."
% _phonopy_yaml_cls.command_name)
msg_list.append(
"By this reason, %s_yaml mode was invoked." % _phonopy_yaml_cls.command_name
)
if final_cell_filename is None: # No phonopy*.yaml file was found.
filenames = ["\"%s\"" % name
for name in _phonopy_yaml_cls.default_filenames]
filenames = ['"%s"' % name for name in _phonopy_yaml_cls.default_filenames]
if len(filenames) == 1:
text = filenames[0]
elif len(filenames) == 2:
@ -308,6 +327,6 @@ def _get_error_message(optional_structure_info,
phpy = optional_structure_info[1]
if phpy is None: # Failed to parse phonopy*.yaml.
msg_list.append("But parsing \"%s\" failed." % final_cell_filename)
msg_list.append('But parsing "%s" failed.' % final_cell_filename)
return "\n".join(msg_list)

View File

@ -1,3 +1,4 @@
"""Utilities to create force sets for main CUI script."""
# Copyright (C) 2020 Atsushi Togo
# All rights reserved.
#
@ -34,47 +35,56 @@
import numpy as np
from phonopy.interface.phonopy_yaml import PhonopyYaml
from phonopy.interface.calculator import get_force_sets, get_force_sets_wien2k
from phonopy.file_IO import parse_disp_yaml, write_FORCE_SETS
from phonopy.interface.calculator import get_force_sets, get_force_sets_wien2k
from phonopy.interface.phonopy_yaml import PhonopyYaml
def create_FORCE_SETS(interface_mode,
def create_FORCE_SETS(
interface_mode,
force_filenames,
symmetry_tolerance=None,
wien2k_P1_mode=False,
force_sets_zero_mode=False,
disp_filename='disp.yaml',
force_sets_filename='FORCE_SETS',
disp_filename="disp.yaml",
force_sets_filename="FORCE_SETS",
write_forcesets_yaml=False,
log_level=0):
log_level=0,
):
"""Create FORCE_SETS from phonopy_disp.yaml and calculator output files.
Reading disp.yaml instead of phonopy_disp.yaml is deprecated.
"""
if log_level > 0:
if interface_mode:
print("Calculator interface: %s" % interface_mode)
print("Displacements were read from \"%s\"." % disp_filename)
if disp_filename == 'disp.yaml':
print('')
print('Displacements were read from "%s".' % disp_filename)
if disp_filename == "disp.yaml":
print("")
print("NOTE:")
print(" From phonopy v2.0, displacements are written into "
"\"phonopy_disp.yaml\".")
print(" \"disp.yaml\" is still supported for reading except for "
"Wien2k interface, ")
print(
" From phonopy v2.0, displacements are written into "
'"phonopy_disp.yaml".'
)
print(
' "disp.yaml" is still supported for reading except for '
"Wien2k interface, "
)
print(" but is deprecated.")
print('')
print("")
if force_sets_zero_mode:
print("Forces in %s are subtracted from forces in all "
"other files." % force_filenames[0])
print(
"Forces in %s are subtracted from forces in all "
"other files." % force_filenames[0]
)
if disp_filename == 'disp.yaml':
if interface_mode == 'wien2k':
disp_dataset, supercell = parse_disp_yaml(filename=disp_filename,
return_cell=True)
if disp_filename == "disp.yaml":
if interface_mode == "wien2k":
disp_dataset, supercell = parse_disp_yaml(
filename=disp_filename, return_cell=True
)
else:
disp_dataset = parse_disp_yaml(filename=disp_filename)
else:
@ -83,80 +93,83 @@ def create_FORCE_SETS(interface_mode,
supercell = phpy_yaml.supercell
disp_dataset = phpy_yaml.dataset
if 'natom' in disp_dataset: # type-1 dataset
num_atoms = disp_dataset['natom']
num_displacements = len(disp_dataset['first_atoms'])
if "natom" in disp_dataset: # type-1 dataset
num_atoms = disp_dataset["natom"]
num_displacements = len(disp_dataset["first_atoms"])
dataset_type = 1
elif 'displacements' in disp_dataset: # type-2 dataset
num_atoms = disp_dataset['displacements'].shape[1]
num_displacements = disp_dataset['displacements'].shape[0]
elif "displacements" in disp_dataset: # type-2 dataset
num_atoms = disp_dataset["displacements"].shape[1]
num_displacements = disp_dataset["displacements"].shape[0]
dataset_type = 2
else:
raise RuntimeError("Number of atoms could not be retrieved from %s"
% disp_filename)
raise RuntimeError(
"Number of atoms could not be retrieved from %s" % disp_filename
)
if force_sets_zero_mode:
num_displacements += 1
if not check_number_of_force_files(num_displacements,
force_filenames,
disp_filename):
if not check_number_of_force_files(
num_displacements, force_filenames, disp_filename
):
force_sets = []
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
force_sets = get_force_sets_wien2k(
num_displacements,
force_filenames,
disp_filename,
supercell,
disp_dataset,
wien2k_P1_mode=wien2k_P1_mode,
symmetry_tolerance=symmetry_tolerance,
verbose=(log_level > 0))
verbose=(log_level > 0),
)
else:
force_sets = get_force_sets(interface_mode,
force_sets = get_force_sets(
interface_mode,
num_atoms,
num_displacements,
force_filenames,
disp_filename=disp_filename,
verbose=(log_level > 0))
verbose=(log_level > 0),
)
if force_sets:
if force_sets_zero_mode:
force_sets = _subtract_residual_forces(force_sets)
if dataset_type == 1:
for forces, disp in zip(force_sets, disp_dataset['first_atoms']):
disp['forces'] = forces
for forces, disp in zip(force_sets, disp_dataset["first_atoms"]):
disp["forces"] = forces
elif dataset_type == 2:
disp_dataset['forces'] = np.array(
force_sets, dtype='double', order='C')
disp_dataset["forces"] = np.array(force_sets, dtype="double", order="C")
else:
raise RuntimeError("FORCE_SETS could not be created.")
write_FORCE_SETS(disp_dataset, filename=force_sets_filename)
if log_level > 0:
print("\"%s\" has been created." % force_sets_filename)
print('"%s" has been created.' % force_sets_filename)
if disp_filename != 'disp.yaml' and write_forcesets_yaml:
if disp_filename != "disp.yaml" and write_forcesets_yaml:
phpy_yaml.dataset = disp_dataset
with open("phonopy_force_sets.yaml", 'w') as w:
with open("phonopy_force_sets.yaml", "w") as w:
w.write(str(phpy_yaml))
if log_level > 0:
print("\"%s\" has been created." % "phonopy_force_sets.yaml")
print('"%s" has been created.' % "phonopy_force_sets.yaml")
else:
if log_level > 0:
print("%s could not be created." % force_sets_filename)
def check_number_of_force_files(num_displacements,
force_filenames,
disp_filename):
def check_number_of_force_files(num_displacements, force_filenames, disp_filename):
"""Verify number of supercell force files.
This function is public because being used from phono3py.
"""
if num_displacements != len(force_filenames):
print('')
print("Number of files to be read (%d) don't match to" %
len(force_filenames))
print("the number of displacements (%d) in %s." %
(num_displacements, disp_filename))
print("")
print("Number of files to be read (%d) don't match to" % len(force_filenames))
print(
"the number of displacements (%d) in %s."
% (num_displacements, disp_filename)
)
return False
else:
return True

View File

@ -1,3 +1,4 @@
"""Phonopy loader."""
# Copyright (C) 2018 Atsushi Togo
# All rights reserved.
#
@ -33,14 +34,16 @@
# POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from phonopy.api_phonopy import Phonopy
from phonopy.interface.phonopy_yaml import PhonopyYaml
from phonopy.interface.calculator import get_default_physical_units
import phonopy.cui.load_helper as load_helper
from phonopy.api_phonopy import Phonopy
from phonopy.interface.calculator import get_default_physical_units
from phonopy.interface.phonopy_yaml import PhonopyYaml
from phonopy.structure.cells import get_primitive_matrix
def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
def load(
phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
supercell_matrix=None,
primitive_matrix=None,
is_nac=True,
@ -63,7 +66,8 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
is_compact_fc=True,
store_dense_svecs=False,
symprec=1e-5,
log_level=0):
log_level=0,
) -> Phonopy:
"""Create Phonopy instance from parameters and/or input files.
"phonopy_yaml"-like file is parsed unless crystal structure information
@ -198,11 +202,12 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
Verbosity control. Default is 0.
"""
if (supercell is not None or
supercell_filename is not None or
unitcell is not None or
unitcell_filename is not None): # noqa E129
if (
supercell is not None
or supercell_filename is not None
or unitcell is not None
or unitcell_filename is not None
): # noqa E129
cell, smat, pmat = load_helper.get_cell_settings(
supercell_matrix=supercell_matrix,
primitive_matrix=primitive_matrix,
@ -212,7 +217,8 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
supercell_filename=supercell_filename,
calculator=calculator,
symprec=symprec,
log_level=log_level)
log_level=log_level,
)
_calculator = calculator
_nac_params = nac_params
_dataset = None
@ -223,7 +229,7 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
cell = phpy_yaml.unitcell
smat = phpy_yaml.supercell_matrix
if smat is None:
smat = np.eye(3, dtype='intc', order='C')
smat = np.eye(3, dtype="intc", order="C")
if primitive_matrix is not None:
pmat = get_primitive_matrix(primitive_matrix, symprec=symprec)
else:
@ -241,20 +247,20 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
else:
_calculator = calculator
else:
msg = ("Cell information could not found. "
"Phonopy instance loading failed.")
msg = "Cell information could not found. " "Phonopy instance loading failed."
raise RuntimeError(msg)
if log_level and _calculator is not None:
print("Set \"%s\" mode." % _calculator)
print('Set "%s" mode.' % _calculator)
# units keywords: factor, nac_factor, distance_to_A
units = get_default_physical_units(_calculator)
if factor is None:
_factor = units['factor']
_factor = units["factor"]
else:
_factor = factor
phonon = Phonopy(cell,
phonon = Phonopy(
cell,
smat,
primitive_matrix=pmat,
factor=_factor,
@ -263,7 +269,8 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
is_symmetry=is_symmetry,
store_dense_svecs=store_dense_svecs,
calculator=_calculator,
log_level=log_level)
log_level=log_level,
)
# NAC params
if born_filename is not None or _nac_params is not None or is_nac:
@ -272,8 +279,9 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
nac_params=_nac_params,
born_filename=born_filename,
is_nac=is_nac,
nac_factor=units['nac_factor'],
log_level=log_level)
nac_factor=units["nac_factor"],
log_level=log_level,
)
if ret_nac_params is not None:
phonon.nac_params = ret_nac_params
@ -289,6 +297,7 @@ def load(phonopy_yaml=None, # phonopy.yaml-like must be the first argument.
produce_fc=produce_fc,
symmetrize_fc=symmetrize_fc,
is_compact_fc=is_compact_fc,
log_level=log_level)
log_level=log_level,
)
return phonon

View File

@ -34,17 +34,25 @@
# POSSIBILITY OF SUCH DAMAGE.
import os
import numpy as np
from phonopy.interface.calculator import read_crystal_structure
from phonopy.structure.cells import get_primitive_matrix
from phonopy.file_IO import (
parse_BORN, read_force_constants_hdf5, parse_FORCE_SETS,
parse_FORCE_CONSTANTS)
parse_BORN,
parse_FORCE_CONSTANTS,
parse_FORCE_SETS,
read_force_constants_hdf5,
)
from phonopy.interface.calculator import (
get_force_constant_conversion_factor,
read_crystal_structure,
)
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.interface.calculator import get_force_constant_conversion_factor
from phonopy.structure.cells import get_primitive_matrix
def get_cell_settings(supercell_matrix=None,
def get_cell_settings(
supercell_matrix=None,
primitive_matrix=None,
unitcell=None,
supercell=None,
@ -52,35 +60,41 @@ def get_cell_settings(supercell_matrix=None,
supercell_filename=None,
calculator=None,
symprec=1e-5,
log_level=0):
log_level=0,
):
"""Return crystal structures."""
optional_structure_info = None
if (primitive_matrix is None or
(type(primitive_matrix) is str and primitive_matrix == "auto")): # noqa E129
pmat = 'auto'
if primitive_matrix is None or (
type(primitive_matrix) is str and primitive_matrix == "auto"
): # noqa E129
pmat = "auto"
else:
pmat = primitive_matrix
if unitcell_filename is not None:
cell, optional_structure_info = _read_crystal_structure(
filename=unitcell_filename, interface_mode=calculator)
filename=unitcell_filename, interface_mode=calculator
)
smat = supercell_matrix
if log_level:
print("Unit cell structure was read from \"%s\"."
% optional_structure_info[0])
print(
'Unit cell structure was read from "%s".' % optional_structure_info[0]
)
elif supercell_filename is not None:
cell, optional_structure_info = read_crystal_structure(
filename=supercell_filename, interface_mode=calculator)
smat = np.eye(3, dtype='intc', order='C')
filename=supercell_filename, interface_mode=calculator
)
smat = np.eye(3, dtype="intc", order="C")
if log_level:
print("Supercell structure was read from \"%s\"."
% optional_structure_info[0])
print(
'Supercell structure was read from "%s".' % optional_structure_info[0]
)
elif unitcell is not None:
cell = PhonopyAtoms(atoms=unitcell)
smat = supercell_matrix
elif supercell is not None:
cell = PhonopyAtoms(atoms=supercell)
smat = np.eye(3, dtype='intc', order='C')
smat = np.eye(3, dtype="intc", order="C")
else:
raise RuntimeError("Cell has to be specified.")
@ -94,36 +108,38 @@ def get_cell_settings(supercell_matrix=None,
return cell, smat, pmat
def get_nac_params(primitive=None,
def get_nac_params(
primitive=None,
nac_params=None,
born_filename=None,
is_nac=True,
nac_factor=None,
log_level=0):
log_level=0,
):
"""Look for and return NAC parameters."""
if born_filename is not None:
_nac_params = parse_BORN(primitive, filename=born_filename)
if log_level:
print("NAC parameters were read from \"%s\"." % born_filename)
print('NAC parameters were read from "%s".' % born_filename)
elif nac_params is not None: # nac_params input or phonopy_yaml.nac_params
_nac_params = nac_params
elif is_nac and os.path.isfile("BORN"):
_nac_params = parse_BORN(primitive, filename="BORN")
if log_level:
print("NAC params were read from \"BORN\".")
print('NAC params were read from "BORN".')
else:
_nac_params = None
if _nac_params is not None:
if 'factor' not in _nac_params or _nac_params['factor'] is None:
_nac_params['factor'] = nac_factor
if "factor" not in _nac_params or _nac_params["factor"] is None:
_nac_params["factor"] = nac_factor
return _nac_params
def read_force_constants_from_hdf5(filename='force_constants.hdf5',
p2s_map=None,
calculator=None):
def read_force_constants_from_hdf5(
filename="force_constants.hdf5", p2s_map=None, calculator=None
):
"""Convert force constants physical unit.
Each calculator interface has own default force constants physical unit.
@ -136,9 +152,9 @@ def read_force_constants_from_hdf5(filename='force_constants.hdf5',
This method is also used from phonopy script.
"""
fc, fc_unit = read_force_constants_hdf5(filename=filename,
p2s_map=p2s_map,
return_physical_unit=True)
fc, fc_unit = read_force_constants_hdf5(
filename=filename, p2s_map=p2s_map, return_physical_unit=True
)
if fc_unit is None:
return fc
else:
@ -157,7 +173,8 @@ def set_dataset_and_force_constants(
produce_fc=True,
symmetrize_fc=True,
is_compact_fc=True,
log_level=0):
log_level=0,
):
"""Set displacement-force dataset and force constants."""
natom = len(phonon.supercell)
@ -190,49 +207,52 @@ def set_dataset_and_force_constants(
if _fc is not None:
phonon.force_constants = _fc
if log_level:
print("Force constants were read from \"%s\"."
% _force_constants_filename)
print('Force constants were read from "%s".' % _force_constants_filename)
if phonon.force_constants is None:
# Overwrite dataset
if _dataset is not None:
phonon.dataset = _dataset
if log_level:
print("Force sets were read from \"%s\"."
% _force_sets_filename)
print('Force sets were read from "%s".' % _force_sets_filename)
if produce_fc:
_produce_force_constants(phonon,
_produce_force_constants(
phonon,
fc_calculator,
fc_calculator_options,
symmetrize_fc,
is_compact_fc,
log_level)
log_level,
)
def _read_force_constants_file(phonon, force_constants_filename):
dot_split = force_constants_filename.split('.')
dot_split = force_constants_filename.split(".")
p2s_map = phonon.primitive.p2s_map
if len(dot_split) > 1 and dot_split[-1] == 'hdf5':
if len(dot_split) > 1 and dot_split[-1] == "hdf5":
_fc = read_force_constants_from_hdf5(
filename=force_constants_filename,
p2s_map=p2s_map,
calculator=phonon.calculator)
calculator=phonon.calculator,
)
else:
_fc = parse_FORCE_CONSTANTS(filename=force_constants_filename,
p2s_map=p2s_map)
_fc = parse_FORCE_CONSTANTS(filename=force_constants_filename, p2s_map=p2s_map)
return _fc
def _produce_force_constants(phonon,
def _produce_force_constants(
phonon,
fc_calculator,
fc_calculator_options,
symmetrize_fc,
is_compact_fc,
log_level):
log_level,
):
phonon.produce_force_constants(
calculate_full_force_constants=(not is_compact_fc),
fc_calculator=fc_calculator,
fc_calculator_options=fc_calculator_options)
fc_calculator_options=fc_calculator_options,
)
if symmetrize_fc:
phonon.symmetrize_force_constants(show_drift=(log_level > 0))
if log_level:
@ -241,17 +261,14 @@ def _produce_force_constants(phonon,
def _read_crystal_structure(filename=None, interface_mode=None):
try:
return read_crystal_structure(filename=filename,
interface_mode=interface_mode)
return read_crystal_structure(filename=filename, interface_mode=interface_mode)
except FileNotFoundError:
raise
except Exception:
msg = [
"============================ phonopy.load "
"============================",
"============================ phonopy.load " "============================",
" Reading crystal structure file failed in phonopy.load.",
" Maybe phonopy.load(..., calculator='<calculator name>') "
"expected?",
"============================ phonopy.load "
"============================"]
" Maybe phonopy.load(..., calculator='<calculator name>') " "expected?",
"============================ phonopy.load " "============================",
]
raise RuntimeError("\n".join(msg))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
"""Show symmetry information invoked by --symmetry command option."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -35,50 +36,56 @@
import numpy as np
import spglib
from spglib import get_pointgroup
from phonopy import Phonopy
from phonopy.interface.calculator import (
write_crystal_structure, get_default_cell_filename)
get_default_cell_filename,
write_crystal_structure,
)
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.cells import guess_primitive_matrix, get_primitive
from phonopy.structure.cells import get_primitive, guess_primitive_matrix
def check_symmetry(phonon, optional_structure_info):
def check_symmetry(phonon: Phonopy, optional_structure_info):
"""Show symmetry information and write refined crystals to files."""
# Assumed that primitive cell is the cell that user is interested in.
print(_get_symmetry_yaml(phonon.primitive,
phonon.primitive_symmetry,
phonon.version))
print(
_get_symmetry_yaml(phonon.primitive, phonon.primitive_symmetry, phonon.version)
)
if phonon.unitcell.magnetic_moments is None:
base_fname = get_default_cell_filename(phonon.calculator)
symprec = phonon.primitive_symmetry.get_symmetry_tolerance()
(bravais_lattice,
bravais_pos,
bravais_numbers) = spglib.refine_cell(phonon.primitive, symprec)
bravais = PhonopyAtoms(numbers=bravais_numbers,
scaled_positions=bravais_pos,
cell=bravais_lattice)
filename = 'B' + base_fname
print("# Symmetrized conventional unit cell is written into %s."
% filename)
(bravais_lattice, bravais_pos, bravais_numbers) = spglib.refine_cell(
phonon.primitive, symprec
)
bravais = PhonopyAtoms(
numbers=bravais_numbers, scaled_positions=bravais_pos, cell=bravais_lattice
)
filename = "B" + base_fname
print("# Symmetrized conventional unit cell is written into %s." % filename)
trans_mat = guess_primitive_matrix(bravais, symprec=symprec)
primitive = get_primitive(bravais, trans_mat, symprec=symprec)
write_crystal_structure(
filename,
bravais,
interface_mode=phonon.calculator,
optional_structure_info=optional_structure_info)
optional_structure_info=optional_structure_info,
)
filename = 'P' + base_fname
filename = "P" + base_fname
print("# Symmetrized primitive is written into %s." % filename)
write_crystal_structure(
filename,
primitive,
interface_mode=phonon.calculator,
optional_structure_info=optional_structure_info)
optional_structure_info=optional_structure_info,
)
def _get_symmetry_yaml(cell, symmetry, phonopy_version=None):
rotations = symmetry.get_symmetry_operations()['rotations']
translations = symmetry.get_symmetry_operations()['translations']
rotations = symmetry.get_symmetry_operations()["rotations"]
translations = symmetry.get_symmetry_operations()["translations"]
atom_sets = symmetry.get_map_atoms()
independent_atoms = symmetry.get_independent_atoms()
@ -91,7 +98,7 @@ def _get_symmetry_yaml(cell, symmetry, phonopy_version=None):
if cell.get_magnetic_moments() is None:
spg_symbol, spg_number = symmetry.get_international_table().split()
spg_number = int(spg_number.replace('(', '').replace(')', ''))
spg_number = int(spg_number.replace("(", "").replace(")", ""))
lines.append("space_group_type: '%s'" % spg_symbol)
lines.append("space_group_number: %d" % spg_number)
lines.append("point_group_type: '%s'" % symmetry.get_pointgroup())

View File

@ -1,3 +1,4 @@
"""File I/O related routines."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -33,58 +34,60 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import numpy as np
from io import StringIO
import numpy as np
import yaml
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.cui.settings import fracval
from phonopy.harmonic.force_constants import similarity_transformation
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.dataset import get_displacements_and_forces
from phonopy.structure.symmetry import Symmetry, elaborate_borns_and_epsilon
from phonopy.harmonic.force_constants import similarity_transformation
#
# FORCE_SETS
#
def write_FORCE_SETS(dataset, filename='FORCE_SETS'):
def write_FORCE_SETS(dataset, filename="FORCE_SETS"):
"""Write FORCE_SETS from dataset.
See more detail in ``get_FORCE_SETS_lines``.
"""
lines = get_FORCE_SETS_lines(dataset)
with open(filename, 'w') as w:
with open(filename, "w") as w:
w.write("\n".join(lines))
w.write("\n")
def get_FORCE_SETS_lines(dataset, forces=None):
"""Generate FORCE_SETS string
"""Generate FORCE_SETS string.
See the format of dataset in the docstring of
Phonopy.set_displacement_dataset. Optionally, sets of forces of supercells
Phonopy.dataset. Optionally, sets of forces of supercells
can be given. In this case, these forces are unnecessary to be stored
in the dataset.
"""
if 'first_atoms' in dataset:
if "first_atoms" in dataset:
return _get_FORCE_SETS_lines_type1(dataset, forces=forces)
elif 'displacements' in dataset:
elif "displacements" in dataset:
if forces is not None:
dataset['forces'] = forces
dataset["forces"] = forces
return _get_FORCE_SETS_lines_type2(dataset)
def _get_FORCE_SETS_lines_type1(dataset, forces=None):
num_atom = dataset['natom']
displacements = dataset['first_atoms']
num_atom = dataset["natom"]
displacements = dataset["first_atoms"]
if forces is None:
_forces = [x['forces'] for x in dataset['first_atoms']]
_forces = [x["forces"] for x in dataset["first_atoms"]]
else:
_forces = forces
@ -93,8 +96,8 @@ def _get_FORCE_SETS_lines_type1(dataset, forces=None):
lines.append("%-5d" % len(displacements))
for count, disp in enumerate(displacements):
lines.append("")
lines.append("%-5d" % (disp['number'] + 1))
lines.append("%20.16f %20.16f %20.16f" % tuple(disp['displacement']))
lines.append("%-5d" % (disp["number"] + 1))
lines.append("%20.16f %20.16f %20.16f" % tuple(disp["displacement"]))
for f in _forces[count]:
lines.append("%15.10f %15.10f %15.10f" % tuple(f))
@ -103,68 +106,59 @@ def _get_FORCE_SETS_lines_type1(dataset, forces=None):
def _get_FORCE_SETS_lines_type2(dataset):
lines = []
for displacements, forces in zip(dataset['displacements'],
dataset['forces']):
for displacements, forces in zip(dataset["displacements"], dataset["forces"]):
for d, f in zip(displacements, forces):
lines.append(("%15.8f" * 6) % (tuple(d) + tuple(f)))
return lines
def parse_FORCE_SETS(natom=None,
is_translational_invariance=False,
filename="FORCE_SETS",
to_type2=False):
"""
def parse_FORCE_SETS(natom=None, filename="FORCE_SETS", to_type2=False):
"""Parse FORCE_SETS from file.
to_type2 : bool
dataset of type2 is returned when True.
"""
Returns
-------
dataset : dict
Displacement dataset. See Phonopy.dataset.
with open(filename, 'r') as f:
"""
with open(filename, "r") as f:
return _get_dataset(
f,
natom=natom,
is_translational_invariance=is_translational_invariance,
to_type2=to_type2)
to_type2=to_type2,
)
def parse_FORCE_SETS_from_strings(strings,
natom=None,
is_translational_invariance=False,
to_type2=False):
return _get_dataset(
StringIO(strings),
natom=natom,
is_translational_invariance=is_translational_invariance,
to_type2=to_type2)
def parse_FORCE_SETS_from_strings(strings, natom=None, to_type2=False):
"""Parse FORCE_SETS from strings."""
return _get_dataset(StringIO(strings), natom=natom, to_type2=to_type2)
def _get_dataset(f,
natom=None,
is_translational_invariance=False,
to_type2=False):
def _get_dataset(f, natom=None, to_type2=False):
first_line_ary = _get_line_ignore_blank(f).split()
f.seek(0)
if len(first_line_ary) == 1:
if natom is None or int(first_line_ary[0]) == natom:
dataset = _get_dataset_type1(f, is_translational_invariance)
dataset = _get_dataset_type1(f)
else:
msg = "Number of forces is not consistent with supercell setting."
raise RuntimeError(msg)
if to_type2:
disps, forces = get_displacements_and_forces(dataset)
return {'displacements': disps, 'forces': forces}
return {"displacements": disps, "forces": forces}
else:
return dataset
elif len(first_line_ary) == 6:
return _get_dataset_type2(f, natom)
return get_dataset_type2(f, natom)
def _get_dataset_type1(f, is_translational_invariance):
def _get_dataset_type1(f):
set_of_forces = []
num_atom = int(_get_line_ignore_blank(f))
num_displacements = int(_get_line_ignore_blank(f))
@ -178,28 +172,22 @@ def _get_dataset_type1(f, is_translational_invariance):
for j in range(num_atom):
line = _get_line_ignore_blank(f).split()
forces_tmp.append(np.array([float(x) for x in line]))
forces_tmp = np.array(forces_tmp, dtype='double')
if is_translational_invariance:
forces_tmp -= np.sum(forces_tmp, axis=0) / len(forces_tmp)
forces = {'number': atom_number - 1,
'displacement': displacement,
'forces': forces_tmp}
forces_tmp = np.array(forces_tmp, dtype="double")
forces = {
"number": atom_number - 1,
"displacement": displacement,
"forces": forces_tmp,
}
set_of_forces.append(forces)
dataset = {'natom': num_atom,
'first_atoms': set_of_forces}
dataset = {"natom": num_atom, "first_atoms": set_of_forces}
return dataset
def get_dataset_type2(f, natom):
return _get_dataset_type2(f, natom)
def _get_dataset_type2(f, natom):
data = np.loadtxt(f, dtype='double')
"""Parse type2 FORCE_SETS text and return dataset."""
data = np.loadtxt(f, dtype="double")
if data.shape[1] != 6 or (natom and data.shape[0] % natom != 0):
msg = "Data shape of forces and displacements is incorrect."
raise RuntimeError(msg)
@ -210,27 +198,60 @@ def _get_dataset_type2(f, natom):
else:
displacements = data[:, :3]
forces = data[:, 3:]
dataset = {'displacements':
np.array(displacements, dtype='double', order='C'),
'forces': np.array(forces, dtype='double', order='C')}
dataset = {
"displacements": np.array(displacements, dtype="double", order="C"),
"forces": np.array(forces, dtype="double", order="C"),
}
return dataset
def _get_line_ignore_blank(f):
line = f.readline().strip()
if line == '':
if line == "":
line = _get_line_ignore_blank(f)
return line
def collect_forces(f, num_atom, hook, force_pos, word=None):
"""General function to collect forces from lines of a text file.
Parameters
----------
f :
Text file pointer such as that returned by ``open(filename)``.
num_atom : int
Number of atoms in cell. Quit parsing when number of forces reaches this
number.
hook : str
When this word is found at a line, parsing will start from the next line.
force_pos : list
Positions of force values in `line.split()`.
word : str, optional
Lines containing this word is only parsed. Default is None.
Example
-------
The following is the abinit output.
...
cartesian forces (hartree/bohr) at end:
1 -0.00093686935947 -0.00000000000000 -0.00000000000000
2 0.00015427277409 -0.00000000000000 -0.00000000000000
3 -0.00000200377550 -0.00000000000000 -0.00000000000000
4 0.00000619017547 -0.00000000000000 -0.00000000000000
...
hook = "cartesian forces (eV/Angstrom)"
force_pos = [1, 2, 3]
"""
for line in f:
if hook in line:
break
forces = []
for line in f:
if line.strip() == '':
if line.strip() == "":
continue
if word is not None:
if word not in line:
@ -252,12 +273,12 @@ def collect_forces(f, num_atom, hook, force_pos, word=None):
return forces
def iter_collect_forces(filename,
num_atom,
hook,
force_pos,
word=None,
max_iter=1000):
def iter_collect_forces(filename, num_atom, hook, force_pos, word=None, max_iter=1000):
"""Repeat ``collect_forces`` to get the last set of forces in the file.
Details of parameters are explained in ``collect_forces``.
"""
with open(filename) as f:
forces = []
prev_forces = []
@ -271,8 +292,7 @@ def iter_collect_forces(filename,
prev_forces = forces[:]
if i == max_iter - 1:
sys.stderr.write("Reached to max number of iterations (%d).\n" %
max_iter)
sys.stderr.write("Reached to max number of iterations (%d).\n" % max_iter)
return forces
@ -280,9 +300,7 @@ def iter_collect_forces(filename,
#
# FORCE_CONSTANTS, force_constants.hdf5
#
def write_FORCE_CONSTANTS(force_constants,
filename='FORCE_CONSTANTS',
p2s_map=None):
def write_FORCE_CONSTANTS(force_constants, filename="FORCE_CONSTANTS", p2s_map=None):
"""Write force constants in text file format.
Parameters
@ -298,17 +316,21 @@ def write_FORCE_CONSTANTS(force_constants,
dtype=intc
"""
lines = get_FORCE_CONSTANTS_lines(force_constants, p2s_map=p2s_map)
with open(filename, 'w') as w:
with open(filename, "w") as w:
w.write("\n".join(lines))
def get_FORCE_CONSTANTS_lines(force_constants, p2s_map=None):
"""Return text in FORCE_CONSTANTS format.
See also ``write_FORCE_CONSTANTS``.
"""
if p2s_map is not None and len(p2s_map) == force_constants.shape[0]:
indices = p2s_map
else:
indices = np.arange(force_constants.shape[0], dtype='intc')
indices = np.arange(force_constants.shape[0], dtype="intc")
lines = []
fc_shape = force_constants.shape
@ -322,11 +344,13 @@ def get_FORCE_CONSTANTS_lines(force_constants, p2s_map=None):
return lines
def write_force_constants_to_hdf5(force_constants,
filename='force_constants.hdf5',
def write_force_constants_to_hdf5(
force_constants,
filename="force_constants.hdf5",
p2s_map=None,
physical_unit=None,
compression=None):
compression=None,
):
"""Write force constants in hdf5 format.
Parameters
@ -349,25 +373,35 @@ def write_force_constants_to_hdf5(force_constants,
None.
"""
try:
import h5py
except ImportError:
raise ModuleNotFoundError("You need to install python-h5py.")
with h5py.File(filename, 'w') as w:
w.create_dataset('force_constants', data=force_constants,
compression=compression)
with h5py.File(filename, "w") as w:
w.create_dataset(
"force_constants", data=force_constants, compression=compression
)
if p2s_map is not None:
w.create_dataset('p2s_map', data=p2s_map)
w.create_dataset("p2s_map", data=p2s_map)
if physical_unit is not None:
dset = w.create_dataset('physical_unit', (1,),
dtype='S%d' % len(physical_unit))
dset = w.create_dataset(
"physical_unit", (1,), dtype="S%d" % len(physical_unit)
)
dset[0] = np.string_(physical_unit)
def parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS",
p2s_map=None):
def parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS", p2s_map=None):
"""Parse FORCE_CONSTANTS.
Parameters
----------
filename : str, optional
Filename.
p2s_map : ndarray, optional
Primitive.p2s_map. Supplied, this is used to check file format consistency.
"""
with open(filename) as fcfile:
idx1 = []
@ -375,7 +409,7 @@ def parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS",
idx = [int(x) for x in line.split()]
if len(idx) == 1:
idx = [idx[0], idx[0]]
force_constants = np.zeros((idx[0], idx[1], 3, 3), dtype='double')
force_constants = np.zeros((idx[0], idx[1], 3, 3), dtype="double")
for i in range(idx[0]):
for j in range(idx[1]):
s_i = int(fcfile.readline().split()[0]) - 1
@ -383,8 +417,7 @@ def parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS",
idx1.append(s_i)
tensor = []
for k in range(3):
tensor.append([float(x)
for x in fcfile.readline().split()])
tensor.append([float(x) for x in fcfile.readline().split()])
force_constants[i, j] = tensor
check_force_constants_indices(idx, idx1, p2s_map, filename)
@ -392,34 +425,45 @@ def parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS",
return force_constants
def read_force_constants_hdf5(filename="force_constants.hdf5",
p2s_map=None,
return_physical_unit=False):
def read_force_constants_hdf5(
filename="force_constants.hdf5", p2s_map=None, return_physical_unit=False
):
"""Parse force_constants.hdf5.
Parameters
----------
filename : str, optional
Filename.
p2s_map : ndarray, optional
Primitive.p2s_map. Supplied, this is used to check file format consistency.
return_physical_unit : bool, optional
When True and physical_unit is in file, physical unit is returned.
Default is False.
"""
try:
import h5py
except ImportError:
raise ModuleNotFoundError("You need to install python-h5py.")
with h5py.File(filename, 'r') as f:
if 'fc2' in f:
key = 'fc2'
elif 'force_constants' in f:
key = 'force_constants'
with h5py.File(filename, "r") as f:
if "fc2" in f:
key = "fc2"
elif "force_constants" in f:
key = "force_constants"
else:
raise RuntimeError("%s doesn't contain necessary information" %
filename)
raise RuntimeError("%s doesn't contain necessary information" % filename)
fc = f[key][:]
if 'p2s_map' in f:
p2s_map_in_file = f['p2s_map'][:]
check_force_constants_indices(fc.shape[:2],
p2s_map_in_file,
p2s_map,
filename)
if "p2s_map" in f:
p2s_map_in_file = f["p2s_map"][:]
check_force_constants_indices(
fc.shape[:2], p2s_map_in_file, p2s_map, filename
)
if return_physical_unit:
if 'physical_unit' in f:
physical_unit = f['physical_unit'][0].decode('utf-8')
if "physical_unit" in f:
physical_unit = f["physical_unit"][0].decode("utf-8")
else:
physical_unit = None
return fc, physical_unit
@ -428,15 +472,18 @@ def read_force_constants_hdf5(filename="force_constants.hdf5",
def check_force_constants_indices(shape, indices, p2s_map, filename):
"""Check consistency of force constants data type."""
if shape[0] != shape[1] and p2s_map is not None:
if len(p2s_map) != len(indices) or (p2s_map != indices).any():
text = ("%s file is inconsistent with the calculation setting. "
"PRIMITIVE_AXIS may not be set correctly.") % filename
text = (
"%s file is inconsistent with the calculation setting. "
"PRIMITIVE_AXIS may not be set correctly."
) % filename
raise RuntimeError(text)
def parse_disp_yaml(filename="disp.yaml", return_cell=False):
"""Read disp.yaml or phonopy_disp.yaml
"""Read disp.yaml or phonopy_disp.yaml.
This method was originally made for parsing disp.yaml. Later this
started to work for phonopy_disp.yaml, too. But now this method is not
@ -444,34 +491,32 @@ def parse_disp_yaml(filename="disp.yaml", return_cell=False):
class.
"""
with open(filename) as f:
new_dataset = {}
dataset = yaml.load(f, Loader=Loader)
if 'phonopy' in dataset and 'calculator' in dataset['phonopy']:
new_dataset['calculator'] = dataset['phonopy']['calculator']
if 'natom' in dataset:
natom = dataset['natom']
elif 'supercell' and 'points' in dataset['supercell']:
natom = len(dataset['supercell']['points'])
if "phonopy" in dataset and "calculator" in dataset["phonopy"]:
new_dataset["calculator"] = dataset["phonopy"]["calculator"]
if "natom" in dataset:
natom = dataset["natom"]
elif "supercell" and "points" in dataset["supercell"]:
natom = len(dataset["supercell"]["points"])
else:
raise RuntimeError("%s doesn't contain necessary information.")
new_dataset['natom'] = natom
new_dataset["natom"] = natom
new_first_atoms = []
try:
displacements = dataset['displacements']
displacements = dataset["displacements"]
except KeyError:
raise
if type(displacements[0]) is dict:
for first_atoms in displacements:
first_atoms['atom'] -= 1
atom1 = first_atoms['atom']
disp1 = first_atoms['displacement']
new_first_atoms.append({'number': atom1,
'displacement': disp1})
new_dataset['first_atoms'] = new_first_atoms
first_atoms["atom"] -= 1
atom1 = first_atoms["atom"]
disp1 = first_atoms["displacement"]
new_first_atoms.append({"number": atom1, "displacement": disp1})
new_dataset["first_atoms"] = new_first_atoms
if return_cell:
cell = get_cell_from_disp_yaml(dataset)
@ -480,23 +525,34 @@ def parse_disp_yaml(filename="disp.yaml", return_cell=False):
return new_dataset
def write_disp_yaml_from_dataset(dataset, supercell, filename='disp.yaml'):
displacements = [(d['number'],) + tuple(d['displacement'])
for d in dataset['first_atoms']]
def write_disp_yaml_from_dataset(dataset, supercell, filename="disp.yaml"):
"""Write disp.yaml from dataset.
This function is obsolete, because disp.yaml is obsolete.
"""
displacements = [
(d["number"],) + tuple(d["displacement"]) for d in dataset["first_atoms"]
]
write_disp_yaml(displacements, supercell, filename=filename)
def write_disp_yaml(displacements, supercell, filename='disp.yaml'):
def write_disp_yaml(displacements, supercell, filename="disp.yaml"):
"""Write disp.yaml from displacements.
This function is obsolete, because disp.yaml is obsolete.
"""
lines = []
lines.append("natom: %4d" % supercell.get_number_of_atoms())
lines += get_disp_yaml_lines(displacements, supercell)
lines += _get_disp_yaml_lines(displacements, supercell)
lines.append(str(supercell))
with open(filename, 'w') as w:
with open(filename, "w") as w:
w.write("\n".join(lines))
def get_disp_yaml_lines(displacements, supercell):
def _get_disp_yaml_lines(displacements, supercell):
lines = []
lines.append("displacements:")
for i, disp in enumerate(displacements):
@ -509,14 +565,20 @@ def get_disp_yaml_lines(displacements, supercell):
#
# DISP (old phonopy displacement format)
#
def parse_DISP(filename='DISP'):
def parse_DISP(filename="DISP"):
"""Parse DISP file.
This function is obsolete, because DISP is obsolete.
"""
with open(filename) as disp:
displacements = []
for line in disp:
if line.strip() != '':
if line.strip() != "":
a = line.split()
displacements.append(
[int(a[0])-1, float(a[1]), float(a[2]), float(a[3])])
[int(a[0]) - 1, float(a[1]), float(a[2]), float(a[3])]
)
return displacements
@ -524,14 +586,15 @@ def parse_DISP(filename='DISP'):
# Parse supercell in disp.yaml
#
def get_cell_from_disp_yaml(dataset):
if 'lattice' in dataset:
lattice = dataset['lattice']
if 'points' in dataset:
data_key = 'points'
pos_key = 'coordinates'
elif 'atoms' in dataset:
data_key = 'atoms'
pos_key = 'position'
"""Read cell from disp.yaml like file."""
if "lattice" in dataset:
lattice = dataset["lattice"]
if "points" in dataset:
data_key = "points"
pos_key = "coordinates"
elif "atoms" in dataset:
data_key = "atoms"
pos_key = "position"
else:
data_key = None
pos_key = None
@ -539,25 +602,27 @@ def get_cell_from_disp_yaml(dataset):
try:
positions = [x[pos_key] for x in dataset[data_key]]
except KeyError:
msg = ("\"disp.yaml\" format is too old. "
"Please re-create it as \"phonopy_disp.yaml\" to contain "
"supercell crystal structure information.")
msg = (
'"disp.yaml" format is too old. '
'Please re-create it as "phonopy_disp.yaml" to contain '
"supercell crystal structure information."
)
raise RuntimeError(msg)
symbols = [x['symbol'] for x in dataset[data_key]]
cell = PhonopyAtoms(cell=lattice,
scaled_positions=positions,
symbols=symbols,
pbc=True)
symbols = [x["symbol"] for x in dataset[data_key]]
cell = PhonopyAtoms(
cell=lattice, scaled_positions=positions, symbols=symbols, pbc=True
)
return cell
else:
return get_cell_from_disp_yaml(dataset['supercell'])
return get_cell_from_disp_yaml(dataset["supercell"])
#
# QPOINTS
#
def parse_QPOINTS(filename="QPOINTS"):
with open(filename, 'r') as f:
"""Read QPOINTS file."""
with open(filename, "r") as f:
num_qpoints = int(f.readline().strip())
qpoints = []
for i in range(num_qpoints):
@ -569,25 +634,37 @@ def parse_QPOINTS(filename="QPOINTS"):
# BORN
#
def write_BORN(primitive, borns, epsilon, filename="BORN"):
"""Write BORN from NAC paramters."""
lines = get_BORN_lines(primitive, borns, epsilon)
with open(filename, 'w') as w:
w.write('\n'.join(lines))
with open(filename, "w") as w:
w.write("\n".join(lines))
def get_BORN_lines(unitcell, borns, epsilon,
def get_BORN_lines(
unitcell,
borns,
epsilon,
factor=None,
primitive_matrix=None,
supercell_matrix=None,
symprec=1e-5):
symprec=1e-5,
):
"""Generate text of BORN file."""
borns, epsilon, atom_indices = elaborate_borns_and_epsilon(
unitcell, borns, epsilon, symmetrize_tensors=True,
unitcell,
borns,
epsilon,
symmetrize_tensors=True,
primitive_matrix=primitive_matrix,
supercell_matrix=supercell_matrix,
symprec=symprec)
symprec=symprec,
)
text = "# epsilon and Z* of atoms "
text += ' '.join(["%d" % n for n in atom_indices + 1])
lines = [text, ]
text += " ".join(["%d" % n for n in atom_indices + 1])
lines = [
text,
]
lines.append(("%13.8f " * 9) % tuple(epsilon.flatten()))
for z in borns:
lines.append(("%13.8f " * 9) % tuple(z.flatten()))
@ -595,12 +672,30 @@ def get_BORN_lines(unitcell, borns, epsilon,
def parse_BORN(primitive, symprec=1e-5, is_symmetry=True, filename="BORN"):
with open(filename, 'r') as f:
"""Parse BORN file.
Parameters
----------
primitive : Primitive
Primitive cell.
symprec : float, optional
Symmetry tolerance. Default is 1e-5.
is_symmetry : bool, optional
When True, parse values are symmetrized. Default is True.
filename : str, optional
Filename.
"""
with open(filename, "r") as f:
return _parse_BORN_from_file_object(f, primitive, symprec, is_symmetry)
def parse_BORN_from_strings(strings, primitive,
symprec=1e-5, is_symmetry=True):
def parse_BORN_from_strings(strings, primitive, symprec=1e-5, is_symmetry=True):
"""Parse BORN file text.
See `parse_BORN` for parameters.
"""
f = StringIO(strings)
return _parse_BORN_from_file_object(f, primitive, symprec, is_symmetry)
@ -611,6 +706,18 @@ def _parse_BORN_from_file_object(f, primitive, symprec, is_symmetry):
def get_born_parameters(f, primitive, prim_symmetry):
"""Parse BORN file text.
Parameters
----------
f :
File pointer of BORN file.
primitive : Primitive
Primitive cell.
prim_symmetry : Symmetry
Symmetry of primitive cell.
"""
line_arr = f.readline().split()
if len(line_arr) < 1:
print("BORN file format of line 1 is incorrect")
@ -645,8 +752,7 @@ def get_born_parameters(f, primitive, prim_symmetry):
# Read Born effective charge
independent_atoms = prim_symmetry.get_independent_atoms()
borns = np.zeros((primitive.get_number_of_atoms(), 3, 3),
dtype='double', order='C')
borns = np.zeros((primitive.get_number_of_atoms(), 3, 3), dtype="double", order="C")
for i in independent_atoms:
line = f.readline().split()
@ -661,42 +767,68 @@ def get_born_parameters(f, primitive, prim_symmetry):
# Check that the number of atoms in the BORN file was correct
line = f.readline().split()
if len(line) > 0:
print("Too many atoms in the BORN file (it should only contain "
"symmetry-independent atoms)")
print(
"Too many atoms in the BORN file (it should only contain "
"symmetry-independent atoms)"
)
return None
_expand_borns(borns, primitive, prim_symmetry)
non_anal = {'born': borns,
'factor': factor,
'dielectric': dielectric}
non_anal = {"born": borns, "factor": factor, "dielectric": dielectric}
if G_cutoff is not None:
non_anal['G_cutoff'] = G_cutoff
non_anal["G_cutoff"] = G_cutoff
if Lambda is not None:
non_anal['Lambda'] = Lambda
non_anal["Lambda"] = Lambda
return non_anal
def _expand_borns(borns, primitive, prim_symmetry):
def _expand_borns(borns, primitive: PhonopyAtoms, prim_symmetry: Symmetry):
# Expand Born effective charges to all atoms in the primitive cell
rotations = prim_symmetry.get_symmetry_operations()['rotations']
rotations = prim_symmetry.symmetry_operations["rotations"]
map_operations = prim_symmetry.get_map_operations()
map_atoms = prim_symmetry.get_map_atoms()
for i in range(primitive.get_number_of_atoms()):
for i in range(len(primitive)):
# R_cart = L R L^-1
rot_cartesian = similarity_transformation(
primitive.get_cell().transpose(), rotations[map_operations[i]])
primitive.cell.T, rotations[map_operations[i]]
)
# R_cart^T B R_cart^-T (inverse rotation is required to transform)
borns[i] = similarity_transformation(rot_cartesian.transpose(),
borns[map_atoms[i]])
borns[i] = similarity_transformation(rot_cartesian.T, borns[map_atoms[i]])
#
# phonopy.yaml
#
def is_file_phonopy_yaml(filename, keyword='phonopy'):
with open(filename, 'r') as f:
def is_file_phonopy_yaml(filename, keyword="phonopy"):
"""Check whether the file is phonopy.yaml like file or not.
Parameters
----------
filename : str
Filename.
keyword : str
When this keyword is found in dict keys returned by yaml loader,
this function return True.
Example
-------
The initial part of phonopy_disp.yaml is like below.
phonopy:
version: 2.7.0
frequency_unit_conversion_factor: 15.633302
symmetry_tolerance: 1.00000e-05
configuration:
cell_filename: "POSCAR-unitcell"
create_displacements: ".true."
primitive_axes: "auto"
dim: "2 2 2"
...
"""
with open(filename, "r") as f:
try:
data = yaml.load(f, Loader=Loader)
if data is None:
@ -713,58 +845,63 @@ def is_file_phonopy_yaml(filename, keyword='phonopy'):
# e-v.dat, thermal_properties.yaml
#
def read_thermal_properties_yaml(filenames):
"""Read thermal_properties.yaml."""
thermal_properties = []
num_modes = []
num_integrated_modes = []
for filename in filenames:
with open(filename) as f:
tp_yaml = yaml.load(f, Loader=Loader)
thermal_properties.append(tp_yaml['thermal_properties'])
if 'num_modes' in tp_yaml and 'num_integrated_modes' in tp_yaml:
num_modes.append(tp_yaml['num_modes'])
num_integrated_modes.append(tp_yaml['num_integrated_modes'])
thermal_properties.append(tp_yaml["thermal_properties"])
if "num_modes" in tp_yaml and "num_integrated_modes" in tp_yaml:
num_modes.append(tp_yaml["num_modes"])
num_integrated_modes.append(tp_yaml["num_integrated_modes"])
temperatures = [v['temperature'] for v in thermal_properties[0]]
temperatures = [v["temperature"] for v in thermal_properties[0]]
temp = []
cv = []
entropy = []
fe_phonon = []
for i, tp in enumerate(thermal_properties):
temp.append([v['temperature'] for v in tp])
temp.append([v["temperature"] for v in tp])
if not np.allclose(temperatures, temp):
msg = ['', ]
msg = [
"",
]
msg.append("Check your input files")
msg.append("Disagreement of temperature range or step")
for t, fname in zip(temp, filenames):
msg.append("%s: Range [ %d, %d ], Step %f" %
(fname, int(t[0]), int(t[-1]), t[1] - t[0]))
msg.append('')
msg.append(
"%s: Range [ %d, %d ], Step %f"
% (fname, int(t[0]), int(t[-1]), t[1] - t[0])
)
msg.append("")
msg.append("Stop phonopy-qha")
raise RuntimeError(msg)
cv.append([v['heat_capacity'] for v in tp])
entropy.append([v['entropy'] for v in tp])
fe_phonon.append([v['free_energy'] for v in tp])
cv.append([v["heat_capacity"] for v in tp])
entropy.append([v["entropy"] for v in tp])
fe_phonon.append([v["free_energy"] for v in tp])
# shape=(temperatures, volumes)
cv = np.array(cv).T
entropy = np.array(entropy).T
fe_phonon = np.array(fe_phonon).T
return (temperatures, cv, entropy, fe_phonon, num_modes,
num_integrated_modes)
return (temperatures, cv, entropy, fe_phonon, num_modes, num_integrated_modes)
def read_v_e(filename):
"""Read v-e.dat file."""
data = _parse_QHA_data(filename)
if data.shape[1] != 2:
msg = ("File format of %s is incorrect for reading e-v data." %
filename)
msg = "File format of %s is incorrect for reading e-v data." % filename
raise RuntimeError(msg)
volumes, electronic_energies = data.T
return volumes, electronic_energies
def read_efe(filename):
"""Read fe-v.dat (efe) file."""
data = _parse_QHA_data(filename)
temperatures = data[:, 0]
free_energies = data[:, 1:]
@ -775,10 +912,10 @@ def _parse_QHA_data(filename):
data = []
with open(filename) as f:
for line in f:
if line.strip() == '' or line.strip()[0] == '#':
if line.strip() == "" or line.strip()[0] == "#":
continue
if '#' in line:
data.append([float(x) for x in line.split('#')[0].split()])
if "#" in line:
data.append([float(x) for x in line.split("#")[0].split()])
else:
data.append([float(x) for x in line.split()])
return np.array(data)

View File

@ -1,3 +1,4 @@
"""Mode Grueneisen paramater calculation."""
# Copyright (C) 2017 Atsushi Togo
# All rights reserved.
#
@ -31,7 +32,3 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from .mesh import GruneisenMesh
from .band_structure import GruneisenBandStructure
from .thermal_properties import GruneisenThermalProperties

View File

@ -1,3 +1,4 @@
"""Mode Grueneisen parameter band structure calculation."""
# Copyright (C) 2012 Atsushi Togo
# All rights reserved.
#
@ -32,16 +33,21 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import sys
import gzip
import yaml
import sys
import numpy as np
from .core import GruneisenBase
import yaml
from phonopy.gruneisen.core import GruneisenBase
from phonopy.units import VaspToTHz
class GruneisenBandStructure(GruneisenBase):
def __init__(self,
"""Class to calculate mode Grueneisen parameter along band structure paths."""
def __init__(
self,
paths,
dynmat,
dynmat_plus,
@ -49,15 +55,18 @@ class GruneisenBandStructure(GruneisenBase):
delta_strain=None,
path_connections=None,
labels=None,
factor=VaspToTHz):
GruneisenBase.__init__(self,
factor=VaspToTHz,
):
"""Init method."""
super().__init__(
dynmat,
dynmat_plus,
dynmat_minus,
delta_strain=delta_strain,
is_band_connection=True)
self._cell = dynmat.get_primitive()
rec_lattice = np.linalg.inv(self._cell.get_cell())
is_band_connection=True,
)
self._cell = dynmat.primitive
rec_lattice = np.linalg.inv(self._cell.cell)
distance_shift = 0.0
self._paths = []
@ -66,7 +75,8 @@ class GruneisenBandStructure(GruneisenBase):
distances = np.zeros(len(qpoints))
delta_qpoints = qpoints[1:] - qpoints[:-1]
delta_distances = np.sqrt(
(np.dot(delta_qpoints, rec_lattice) ** 2).sum(axis=1))
(np.dot(delta_qpoints, rec_lattice) ** 2).sum(axis=1)
)
for i, dd in enumerate(delta_distances):
distances[i + 1] = distances[i] + dd
@ -75,67 +85,84 @@ class GruneisenBandStructure(GruneisenBase):
frequencies = np.sqrt(abs(eigenvalues)) * np.sign(eigenvalues) * factor
distances_with_shift = distances + distance_shift
self._paths.append([qpoints,
self._paths.append(
[
qpoints,
distances,
self._gruneisen,
eigenvalues,
self._eigenvectors,
frequencies,
distances_with_shift])
distances_with_shift,
]
)
distance_shift = distances_with_shift[-1]
self._labels = None
self._path_connections = None
if path_connections is None:
self._path_connections = [True, ] * len(self._paths)
self._path_connections = [
True,
] * len(self._paths)
self._path_connections[-1] = False
else:
self._path_connections = path_connections
if (labels is not None and
len(labels) == (2 - np.array(self._path_connections)).sum()):
if (
labels is not None
and len(labels) == (2 - np.array(self._path_connections)).sum()
):
self._labels = labels
def get_qpoints(self):
"""Return q-points."""
return [path[0] for path in self._paths]
def get_distances(self):
"""Return distances."""
return [path[6] for path in self._paths]
def get_gruneisen(self):
"""Return mode Gruneisen parameters."""
return [path[2] for path in self._paths]
def get_eigenvalues(self):
"""Return eigenvalues."""
return [path[3] for path in self._paths]
def get_eigenvectors(self):
"""Return eigenvectors."""
return [path[4] for path in self._paths]
def get_frequencies(self):
"""Return frequencies."""
return [path[5] for path in self._paths]
def write_yaml(self, comment=None, filename=None, compression=None):
"""Write results to file in yaml."""
if filename is not None:
_filename = filename
if compression is None:
if filename is None:
_filename = "gruneisen.yaml"
with open(_filename, 'w') as w:
with open(_filename, "w") as w:
self._write_yaml(w, comment)
elif compression == 'gzip':
elif compression == "gzip":
if filename is None:
_filename = "gruneisen.yaml.gz"
with gzip.open(_filename, 'wb') as w:
with gzip.open(_filename, "wb") as w:
self._write_yaml(w, comment, is_binary=True)
elif compression == 'lzma':
elif compression == "lzma":
try:
import lzma
except ImportError:
raise("Reading a lzma compressed file is not supported "
"by this python version.")
raise (
"Reading a lzma compressed file is not supported "
"by this python version."
)
if filename is None:
_filename = "gruneisen.yaml.xz"
with lzma.open(_filename, 'w') as w:
with lzma.open(_filename, "w") as w:
self._write_yaml(w, comment, is_binary=True)
def _write_yaml(self, w, comment, is_binary=False):
@ -155,41 +182,43 @@ class GruneisenBandStructure(GruneisenBase):
text.append("labels:")
if self._is_legacy_plot:
for i in range(len(self._paths)):
text.append("- [ \'%s\', \'%s\' ]" %
(self._labels[i], self._labels[i + 1]))
text.append(
"- [ '%s', '%s' ]" % (self._labels[i], self._labels[i + 1])
)
else:
i = 0
for c in self._path_connections:
text.append("- [ \'%s\', \'%s\' ]" %
(self._labels[i], self._labels[i + 1]))
text.append(
"- [ '%s', '%s' ]" % (self._labels[i], self._labels[i + 1])
)
if c:
i += 1
else:
i += 2
text.append("reciprocal_lattice:")
for vec, axis in zip(rec_lattice.T, ('a*', 'b*', 'c*')):
text.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" %
(tuple(vec) + (axis,)))
for vec, axis in zip(rec_lattice.T, ("a*", "b*", "c*")):
text.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" % (tuple(vec) + (axis,)))
text.append("natom: %-7d" % (natom))
text.append(str(self._cell))
text.append('')
text.append("")
text.append("path:")
text.append('')
text.append("")
for band_structure in self._paths:
(qpoints,
(
qpoints,
distances,
gamma,
eigenvalues,
_,
frequencies,
distances_with_shift) = band_structure
distances_with_shift,
) = band_structure
text.append("- nqpoint: %d" % len(qpoints))
text.append(" phonon:")
for q, d, gs, freqs in zip(qpoints, distances, gamma, frequencies):
text.append(" - q-position: [ %10.7f, %10.7f, %10.7f ]" %
tuple(q))
text.append(" - q-position: [ %10.7f, %10.7f, %10.7f ]" % tuple(q))
text.append(" distance: %10.7f" % d)
text.append(" band:")
for i, (g, freq) in enumerate(zip(gs, freqs)):
@ -206,25 +235,25 @@ class GruneisenBandStructure(GruneisenBase):
if sys.version_info < (3, 0):
w.write(bytes(text))
else:
w.write(bytes(text, 'utf8'))
w.write(bytes(text, "utf8"))
else:
w.write(text)
def plot(self,
axarr,
epsilon=None,
color_scheme=None):
def plot(self, axarr, epsilon=None, color_scheme=None):
"""Return pyplot of band structure calculation results."""
for band_structure in self._paths:
self._plot(axarr, band_structure, epsilon, color_scheme)
def _plot(self, axarr, band_structure, epsilon, color_scheme):
(qpoints,
(
qpoints,
distances,
gamma,
eigenvalues,
_,
frequencies,
distances_with_shift) = band_structure
distances_with_shift,
) = band_structure
n = len(gamma.T) - 1
ax1, ax2 = axarr
@ -254,25 +283,25 @@ class GruneisenBandStructure(GruneisenBase):
if abs(freqs[j]) < abs(max(freqs)) / 10:
curve[j] = curve[cutoff_index]
self._plot_a_band(ax1, curve, distances_with_shift, i, n,
color_scheme)
self._plot_a_band(ax1, curve, distances_with_shift, i, n, color_scheme)
ax1.set_xlim(0, distances_with_shift[-1])
for i, freqs in enumerate(frequencies.T):
self._plot_a_band(ax2, freqs, distances_with_shift, i, n,
color_scheme)
self._plot_a_band(ax2, freqs, distances_with_shift, i, n, color_scheme)
ax2.set_xlim(0, distances_with_shift[-1])
def _plot_a_band(self, ax, curve, distances_with_shift, i, n, color_scheme):
color = None
if color_scheme == 'RB':
color = (1. / n * i, 0, 1./ n * (n - i))
elif color_scheme == 'RG':
color = (1. / n * i, 1./ n * (n - i), 0)
elif color_scheme == 'RGB':
color = (max(2./ n * (i - n / 2.), 0),
min(2./ n * i, 2./ n * (n - i)),
max(2./ n * (n / 2. - i), 0))
if color_scheme == "RB":
color = (1.0 / n * i, 0, 1.0 / n * (n - i))
elif color_scheme == "RG":
color = (1.0 / n * i, 1.0 / n * (n - i), 0)
elif color_scheme == "RGB":
color = (
max(2.0 / n * (i - n / 2.0), 0),
min(2.0 / n * i, 2.0 / n * (n - i)),
max(2.0 / n * (n / 2.0 - i), 0),
)
if color:
ax.plot(distances_with_shift, curve, color=color)
else:

View File

@ -1,3 +1,4 @@
"""Mode Grueneisen parameter calculation."""
# Copyright (C) 2012 Atsushi Togo
# All rights reserved.
#
@ -32,26 +33,35 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from typing import Union
import numpy as np
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.phonon.band_structure import estimate_band_connection
from phonopy.phonon.degeneracy import rotate_eigenvectors
class GruneisenBase(object):
def __init__(self,
dynmat,
dynmat_plus,
dynmat_minus,
class GruneisenBase:
"""Base class of mode Grueneisen parameter calculation classes."""
def __init__(
self,
dynmat: Union[DynamicalMatrix, DynamicalMatrixNAC],
dynmat_plus: Union[DynamicalMatrix, DynamicalMatrixNAC],
dynmat_minus: Union[DynamicalMatrix, DynamicalMatrixNAC],
delta_strain=None,
qpoints=None,
is_band_connection=False):
is_band_connection=False,
):
"""Init method."""
self._dynmat = dynmat
self._dynmat_plus = dynmat_plus
self._dynmat_minus = dynmat_minus
if delta_strain is None:
volume = dynmat.get_primitive().get_volume()
volume_plus = dynmat_plus.get_primitive().get_volume()
volume_minus = dynmat_minus.get_primitive().get_volume()
volume = dynmat.primitive.volume
volume_plus = dynmat_plus.primitive.volume
volume_minus = dynmat_minus.primitive.volume
dV = volume_plus - volume_minus
self._delta_strain = dV / volume
else:
@ -60,25 +70,25 @@ class GruneisenBase(object):
self._qpoints = qpoints
self._gruneisen = None
self._gamma_prime = None
self._eigenvalues = None
if qpoints is not None:
self._set_gruneisen()
def set_qpoints(self, qpoints):
"""Set q-points."""
self._qpoints = qpoints
self._set_gruneisen()
def get_gruneisen(self):
"""Return mode Grueneisen parameters."""
return self._gruneisen
def get_gamma_prime(self):
return self._gamma_prime
def get_eigenvalues(self):
"""Return eigenvalues."""
return self._eigenvalues
def get_eigenvectors(self):
"""Return eigenvectors."""
return self._eigenvectors
def _set_gruneisen(self):
@ -96,7 +106,7 @@ class GruneisenBase(object):
else:
self._dynmat.run(q)
dm = self._dynmat.get_dynamical_matrix()
dm = self._dynmat.dynamical_matrix
evals, evecs = np.linalg.eigh(dm)
evals_at_q = evals.real
dD = self._get_dD(q, self._dynmat_minus, self._dynmat_plus)
@ -105,9 +115,8 @@ class GruneisenBase(object):
if self._is_band_connection:
if prev_eigvecs is not None:
band_order = estimate_band_connection(
prev_eigvecs,
evecs_at_q,
band_order)
prev_eigvecs, evecs_at_q, band_order
)
eigvals.append(evals_at_q[band_order])
eigvecs.append(evecs_at_q[:, band_order])
edDe.append(edDe_at_q[band_order])
@ -117,20 +126,26 @@ class GruneisenBase(object):
eigvecs.append(evecs_at_q)
edDe.append(edDe_at_q)
edDe = np.array(edDe, dtype='double', order='C')
self._eigenvalues = np.array(eigvals, dtype='double', order='C')
edDe = np.array(edDe, dtype="double", order="C")
self._eigenvalues = np.array(eigvals, dtype="double", order="C")
itemsize = self._eigenvalues.itemsize
self._eigenvectors = np.array(eigvecs,
dtype=("c%d" % (itemsize * 2)), order='C')
self._eigenvectors = np.array(
eigvecs, dtype=("c%d" % (itemsize * 2)), order="C"
)
self._gruneisen = -edDe / self._delta_strain / self._eigenvalues / 2
def _get_dD(self, q, d_a, d_b):
if (self._is_band_connection and d_a.is_nac() and d_b.is_nac()):
def _get_dD(
self,
q,
d_a: Union[DynamicalMatrix, DynamicalMatrixNAC],
d_b: Union[DynamicalMatrix, DynamicalMatrixNAC],
):
if self._is_band_connection and d_a.is_nac() and d_b.is_nac():
d_a.run(q, q_direction=self._q_direction)
d_b.run(q, q_direction=self._q_direction)
else:
d_a.run(q)
d_b.run(q)
dm_a = d_a.get_dynamical_matrix()
dm_b = d_b.get_dynamical_matrix()
return (dm_b - dm_a)
dm_a = d_a.dynamical_matrix
dm_b = d_b.dynamical_matrix
return dm_b - dm_a

View File

@ -1,3 +1,4 @@
"""Mode Grueneisen parameters calculation on sampling mesh."""
# Copyright (C) 2012 Atsushi Togo
# All rights reserved.
#
@ -32,18 +33,21 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import sys
import gzip
import yaml
import sys
import numpy as np
from .core import GruneisenBase
from phonopy.gruneisen.core import GruneisenBase
from phonopy.structure.grid_points import get_qpoints
from phonopy.phonon.thermal_properties import mode_cv
from phonopy.units import THzToEv, VaspToTHz
from phonopy.units import VaspToTHz
class GruneisenMesh(GruneisenBase):
def __init__(self,
"""Class to calculate mode Grueneisen parameters on sampling mesh."""
def __init__(
self,
dynmat,
dynmat_plus,
dynmat_minus,
@ -54,81 +58,82 @@ class GruneisenMesh(GruneisenBase):
is_gamma_center=False,
is_mesh_symmetry=True,
rotations=None, # Point group operations in real space
factor=VaspToTHz):
GruneisenBase.__init__(self,
dynmat,
dynmat_plus,
dynmat_minus,
delta_strain=delta_strain)
self._mesh = np.array(mesh, dtype='intc')
factor=VaspToTHz,
):
"""Init method."""
super().__init__(dynmat, dynmat_plus, dynmat_minus, delta_strain=delta_strain)
self._mesh = np.array(mesh, dtype="intc")
self._factor = factor
self._cell = dynmat.get_primitive()
self._cell = dynmat.primitive
self._qpoints, self._weights = get_qpoints(
self._mesh,
np.linalg.inv(self._cell.get_cell()),
np.linalg.inv(self._cell.cell),
q_mesh_shift=shift,
is_time_reversal=is_time_reversal,
is_gamma_center=is_gamma_center,
rotations=rotations,
is_mesh_symmetry=is_mesh_symmetry)
is_mesh_symmetry=is_mesh_symmetry,
)
self.set_qpoints(self._qpoints)
self._gamma = self._gruneisen
self._frequencies = np.sqrt(
abs(self._eigenvalues)) * np.sign(self._eigenvalues) * self._factor
self._frequencies = (
np.sqrt(abs(self._eigenvalues)) * np.sign(self._eigenvalues) * self._factor
)
def get_gruneisen(self):
"""Return mode Grueneisen parameters."""
return self._gamma
def get_gamma_prime(self):
return self._gamma_prime
def get_mesh_numbers(self):
"""Return mesh numbers."""
return self._mesh
def get_qpoints(self):
"""Return (irreducible) q-points."""
return self._qpoints
def get_weights(self):
"""Return weights of (irreducible) q-points."""
return self._weights
def get_eigenvalues(self):
"""Return eigenvalues of dynamical matrices."""
return self._eigenvalues
def get_eigenvectors(self):
"""Return phonon eigenvectors."""
return self._eigenvectors
def get_frequencies(self):
"""Return phonon frequencies."""
return self._frequencies
def get_eigenvectors(self):
"""
See the detail of array shape in phonopy.phonon.mesh.
"""
return self._eigenvectors
def write_yaml(self, comment=None, filename=None, compression=None):
"""Write results in yaml file."""
if filename is not None:
_filename = filename
if compression is None:
if filename is None:
_filename = "gruneisen.yaml"
with open(_filename, 'w') as w:
with open(_filename, "w") as w:
self._write_yaml(w, comment)
elif compression == 'gzip':
elif compression == "gzip":
if filename is None:
_filename = "gruneisen.yaml.gz"
with gzip.open(_filename, 'wb') as w:
with gzip.open(_filename, "wb") as w:
self._write_yaml(w, comment, is_binary=True)
elif compression == 'lzma':
elif compression == "lzma":
try:
import lzma
except ImportError:
raise("Reading a lzma compressed file is not supported "
"by this python version.")
raise (
"Reading a lzma compressed file is not supported "
"by this python version."
)
if filename is None:
_filename = "gruneisen.yaml.xz"
with lzma.open(_filename, 'w') as w:
with lzma.open(_filename, "w") as w:
self._write_yaml(w, comment, is_binary=True)
def _write_yaml(self, w, comment, is_binary=False):
@ -138,17 +143,15 @@ class GruneisenMesh(GruneisenBase):
text.append("mesh: [ %5d, %5d, %5d ]" % tuple(self._mesh))
text.append("nqpoint: %d" % len(self._qpoints))
text.append("reciprocal_lattice:")
for vec, axis in zip(rec_lattice.T, ('a*', 'b*', 'c*')):
text.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" %
(tuple(vec) + (axis,)))
for vec, axis in zip(rec_lattice.T, ("a*", "b*", "c*")):
text.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" % (tuple(vec) + (axis,)))
text.append("natom: %-7d" % natom)
text.append(str(self._cell))
text.append('')
text.append("")
text.append("phonon:")
for q, m, gs, freqs in zip(self._qpoints,
self._weights,
self._gamma,
self._frequencies):
for q, m, gs, freqs in zip(
self._qpoints, self._weights, self._gamma, self._frequencies
):
text.append("- q-position: [ %10.7f, %10.7f, %10.7f ]" % tuple(q))
text.append(" multiplicity: %d" % m)
text.append(" band:")
@ -166,54 +169,52 @@ class GruneisenMesh(GruneisenBase):
if sys.version_info < (3, 0):
w.write(bytes(text))
else:
w.write(bytes(text, 'utf8'))
w.write(bytes(text, "utf8"))
else:
w.write(text)
def write_hdf5(self, filename="gruneisen.hdf5"):
"""Write results in hdf5 file."""
import h5py
w = h5py.File(filename, 'w')
w.create_dataset('mesh', data=self._mesh)
w.create_dataset('gruneisen', data=self._gamma)
w.create_dataset('weight', data=self._weights)
w.create_dataset('frequency', data=self._frequencies)
w.create_dataset('qpoint', data=self._qpoints)
w = h5py.File(filename, "w")
w.create_dataset("mesh", data=self._mesh)
w.create_dataset("gruneisen", data=self._gamma)
w.create_dataset("weight", data=self._weights)
w.create_dataset("frequency", data=self._frequencies)
w.create_dataset("qpoint", data=self._qpoints)
w.close()
def plot(self,
plt,
cutoff_frequency=None,
color_scheme=None,
marker='o',
markersize=None):
def plot(
self, plt, cutoff_frequency=None, color_scheme=None, marker="o", markersize=None
):
"""Return pyplot of calculation results."""
n = len(self._gamma.T) - 1
for i, (g, freqs) in enumerate(zip(self._gamma.T,
self._frequencies.T)):
for i, (g, freqs) in enumerate(zip(self._gamma.T, self._frequencies.T)):
if cutoff_frequency:
g = np.extract(freqs > cutoff_frequency, g)
freqs = np.extract(freqs > cutoff_frequency, freqs)
if color_scheme == 'RB':
color = (1. / n * i, 0, 1./ n * (n - i))
if color_scheme == "RB":
color = (1.0 / n * i, 0, 1.0 / n * (n - i))
if markersize:
plt.plot(freqs, g, marker,
color=color, markersize=markersize)
plt.plot(freqs, g, marker, color=color, markersize=markersize)
else:
plt.plot(freqs, g, marker, color=color)
elif color_scheme == 'RG':
color = (1. / n * i, 1./ n * (n - i), 0)
elif color_scheme == "RG":
color = (1.0 / n * i, 1.0 / n * (n - i), 0)
if markersize:
plt.plot(freqs, g, marker,
color=color, markersize=markersize)
plt.plot(freqs, g, marker, color=color, markersize=markersize)
else:
plt.plot(freqs, g, marker, color=color)
elif color_scheme == 'RGB':
color = (max(2./ n * (i - n / 2.), 0),
min(2./ n * i, 2./ n * (n - i)),
max(2./ n * (n / 2. - i), 0))
elif color_scheme == "RGB":
color = (
max(2.0 / n * (i - n / 2.0), 0),
min(2.0 / n * i, 2.0 / n * (n - i)),
max(2.0 / n * (n / 2.0 - i), 0),
)
if markersize:
plt.plot(freqs, g, marker,
color=color, markersize=markersize)
plt.plot(freqs, g, marker, color=color, markersize=markersize)
else:
plt.plot(freqs, g, marker, color=color)
else:
@ -221,27 +222,3 @@ class GruneisenMesh(GruneisenBase):
plt.plot(freqs, g, marker, markersize=markersize)
else:
plt.plot(freqs, g, marker)
def get_thermodynamic_Gruneisen_parameter(gammas,
frequencies,
multiplicities,
t):
if t > 0:
conditions = (frequencies > 0)
freq_temp = np.where(conditions, x, 1)
cv_temp = mode_cv(t, frequencies * THzToEv)
cv = np.where(conditions, x, 0)
return (np.dot(multiplicities, cv * gammas).sum() /
np.dot(multiplicities, cv).sum())
else:
return 0.
def get_thermal_expansion_coefficient(gammas,
frequencies,
multiplicities,
t):
if t > 0:
return np.dot(multiplicities,
mode_cv(t, frequencies * THzToEv) * gammas).sum()
else:
return 0.

View File

@ -1,102 +0,0 @@
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
# This file is part of phonopy.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of the phonopy project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from phonopy.phonon.thermal_properties import ThermalProperties
class GruneisenThermalProperties(object):
def __init__(self,
gruneisen_mesh,
volumes,
t_step=2,
t_max=2004,
t_min=0,
cutoff_frequency=None):
phonon = gruneisen_mesh.get_phonon()
self._cutoff_frequency = cutoff_frequency
self._factor = phonon.get_unit_conversion_factor(),
self._V0 = phonon.get_primitive().get_volume()
self._gamma = gruneisen_mesh.get_gruneisen()
self._gamma_prime = gruneisen_mesh.get_gamma_prime()
self._weights = gruneisen_mesh.get_weights()
self._eigenvalues = gruneisen_mesh.get_eigenvalues()
self._frequencies = gruneisen_mesh.get_frequencies()
self._thermal_properties = []
for V in volumes:
tp = self._get_thermal_properties_at_V(V)
tp.set_temperature_range(t_min=t_min, t_max=t_max, t_step=t_step)
tp.run()
self._thermal_properties.append(tp)
def get_thermal_properties(self):
"""Return a set of phonopy.phonon::ThermalProperties object"""
return self._thermal_properties
def write_yaml(self, filename='thermal_properties'):
for i, tp in enumerate(self._thermal_properties):
tp.write_yaml(filename="%s-%02d.yaml" % (filename, i))
def _get_thermal_properties_at_V(self, V):
frequencies = self._get_frequencies_at_V(V)
tp = ThermalProperties(frequencies,
weights=self._weights,
cutoff_frequency=self._cutoff_frequency)
return tp
def _get_frequencies_at_V(self, V):
return self._get_frequencies_at_V_analytical_solution(V)
def _get_frequencies_at_V_analytical_solution(self, V):
eigvals = self._eigenvalues * np.exp(-2 * self._gamma *
np.log(V / self._V0))
return np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
def _get_frequencies_at_V_analytical_solution_with_1st_correction(self, V):
V0 = self._V0
g_prime = self._gamma_prime / V0
eigvals = self._eigenvalues * np.exp(
-2 * ((self._gamma - g_prime * V0) * np.log(V / V0)
+ g_prime * (V - V0)))
return np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
def _get_frequencies_at_V_Taylor_expansion_to_1st_order(self, V):
return self._frequencies * (
1.0
- self._gamma * (V - self._V0) / self._V0)
def _get_frequencies_at_V_Taylor_expansion_to_2nd_order(self, V):
return self._frequencies * (
1.0
- self._gamma * (V - self._V0) / self._V0
- self._gamma_prime * ((V - self._V0) / self._V0) ** 2 / 2)

View File

@ -0,0 +1,34 @@
"""Routines of pre-phonon calculations."""
# Copyright (C) 2021 Atsushi Togo
# All rights reserved.
#
# This file is part of phonopy.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of the phonopy project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

View File

@ -33,11 +33,16 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import warnings
from typing import Union
import numpy as np
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.structure.cells import sparse_to_dense_svecs
class DerivativeOfDynamicalMatrix(object):
class DerivativeOfDynamicalMatrix:
"""Compute analytical derivative of dynamical matrix.
This can be used dynamical matrix without NAC or with Wang-NAC.
@ -49,7 +54,7 @@ class DerivativeOfDynamicalMatrix(object):
"""
def __init__(self, dynamical_matrix):
def __init__(self, dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC]):
"""Init method.
Parameters
@ -63,13 +68,13 @@ class DerivativeOfDynamicalMatrix(object):
self._scell = self._dynmat.supercell
self._pcell = self._dynmat.primitive
dtype = 'int_'
dtype = "int_"
self._p2s_map = np.array(self._pcell.p2s_map, dtype=dtype)
self._s2p_map = np.array(self._pcell.s2p_map, dtype=dtype)
p2p_map = self._pcell.p2p_map
self._s2pp_map = np.array(
[p2p_map[self._s2p_map[i]] for i in range(len(self._s2p_map))],
dtype=dtype)
[p2p_map[self._s2p_map[i]] for i in range(len(self._s2p_map))], dtype=dtype
)
svecs, multi = self._pcell.get_smallest_vectors()
if self._pcell.store_dense_svecs:
@ -86,9 +91,9 @@ class DerivativeOfDynamicalMatrix(object):
# 2. Python implementation
self._derivative_order = None
def run(self, q, q_direction=None, lang='C'):
def run(self, q, q_direction=None, lang="C"):
"""Run at q."""
if self._derivative_order is not None or lang != 'C':
if self._derivative_order is not None or lang != "C":
self._run_py(q, q_direction=q_direction)
else:
self._run_c(q, q_direction=q_direction)
@ -116,15 +121,23 @@ class DerivativeOfDynamicalMatrix(object):
def get_derivative_of_dynamical_matrix(self):
"""Return derivative of dynamical matrix."""
warnings.warn(
"DerivativeOfDynamicalMatrix.get_derivative_of_dynamical_matrix() is "
"deprecated. Use d_dynamical_matrix attribute instead.",
DeprecationWarning,
)
return self.d_dynamical_matrix
def _run_c(self, q, q_direction=None):
import phonopy._phonopy as phonoc
num_patom = len(self._p2s_map)
fc = self._force_constants
ddm = np.zeros((3, num_patom * 3, num_patom * 3),
dtype=("c%d" % (np.dtype('double').itemsize * 2)))
ddm = np.zeros(
(3, num_patom * 3, num_patom * 3),
dtype=("c%d" % (np.dtype("double").itemsize * 2)),
)
if self._dynmat.is_nac():
born = self._dynmat.born
dielectric = self._dynmat.dielectric_constant
@ -132,7 +145,7 @@ class DerivativeOfDynamicalMatrix(object):
if q_direction is None:
q_dir = None
else:
q_dir = np.array(q_direction, dtype='double', order='C')
q_dir = np.array(q_direction, dtype="double", order="C")
else:
born = None
dielectric = None
@ -140,11 +153,11 @@ class DerivativeOfDynamicalMatrix(object):
q_dir = None
if fc.shape[0] == fc.shape[1]: # full fc
phonoc.derivative_dynmat(ddm.view(dtype='double'),
phonoc.derivative_dynmat(
ddm.view(dtype="double"),
fc,
np.array(q, dtype='double'),
np.array(self._pcell.cell.T,
dtype='double', order='C'),
np.array(q, dtype="double"),
np.array(self._pcell.cell.T, dtype="double", order="C"),
self._svecs,
self._multi,
self._pcell.masses,
@ -153,23 +166,24 @@ class DerivativeOfDynamicalMatrix(object):
nac_factor,
born,
dielectric,
q_dir)
q_dir,
)
else:
phonoc.derivative_dynmat(ddm.view(dtype='double'),
phonoc.derivative_dynmat(
ddm.view(dtype="double"),
fc,
np.array(q, dtype='double'),
np.array(self._pcell.cell.T,
dtype='double', order='C'),
np.array(q, dtype="double"),
np.array(self._pcell.cell.T, dtype="double", order="C"),
self._svecs,
self._multi,
self._pcell.masses,
self._s2pp_map,
np.arange(len(self._p2s_map),
dtype='int_'),
np.arange(len(self._p2s_map), dtype="int_"),
nac_factor,
born,
dielectric,
q_dir)
q_dir,
)
self._ddm = ddm
@ -193,16 +207,16 @@ class DerivativeOfDynamicalMatrix(object):
else:
num_elem = 3
itemsize = np.dtype('double').itemsize
ddm = np.zeros((num_elem, 3 * num_patom, 3 * num_patom),
dtype=("c%d" % (itemsize * 2)))
itemsize = np.dtype("double").itemsize
ddm = np.zeros(
(num_elem, 3 * num_patom, 3 * num_patom), dtype=("c%d" % (itemsize * 2))
)
for i, j in list(np.ndindex(num_patom, num_patom)):
s_i = self._p2s_map[i]
s_j = self._p2s_map[j]
mass = np.sqrt(self._pcell.masses[i] * self._pcell.masses[j])
ddm_local = np.zeros((num_elem, 3, 3),
dtype=("c%d" % (itemsize * 2)))
ddm_local = np.zeros((num_elem, 3, 3), dtype=("c%d" % (itemsize * 2)))
for k in range(num_satom):
if s_j != self._s2p_map[k]:
@ -210,14 +224,16 @@ class DerivativeOfDynamicalMatrix(object):
multi = multiplicity[k, i]
vecs_multi = vecs[k, i, :multi]
phase_multi = np.exp([np.vdot(vec, q) * 2j * np.pi
for vec in vecs_multi])
phase_multi = np.exp(
[np.vdot(vec, q) * 2j * np.pi for vec in vecs_multi]
)
vecs_multi_cart = np.dot(vecs_multi, self._pcell.cell)
coef_order1 = 2j * np.pi * vecs_multi_cart
if self._derivative_order == 2:
coef_order2 = [np.outer(co1, co1) for co1 in coef_order1]
coef = np.array([co2.ravel()[[0, 4, 8, 5, 2, 1]]
for co2 in coef_order2])
coef = np.array(
[co2.ravel()[[0, 4, 8, 5, 2, 1]] for co2 in coef_order2]
)
else:
coef = coef_order1
@ -228,22 +244,22 @@ class DerivativeOfDynamicalMatrix(object):
for ll in range(num_elem):
ddm_elem = fc_elem * (coef[:, ll] * phase_multi).sum()
if (self._dynmat.is_nac() and
not self._derivative_order == 2): # noqa E129
if (
self._dynmat.is_nac() and not self._derivative_order == 2
): # noqa E129
ddm_elem += d_nac[ll, i, j] * phase_multi.sum()
ddm_local[ll] += ddm_elem / mass / multi
ddm[:, (i * 3):(i * 3 + 3), (j * 3):(j * 3 + 3)] = ddm_local
ddm[:, (i * 3) : (i * 3 + 3), (j * 3) : (j * 3 + 3)] = ddm_local
# Impose Hermite condition
self._ddm = np.array([(ddm[i] + ddm[i].conj().T) / 2
for i in range(num_elem)])
self._ddm = np.array([(ddm[i] + ddm[i].conj().T) / 2 for i in range(num_elem)])
def _nac(self, q_direction):
"""nac_term = (A1 (x) A2) / B * coef."""
num_atom = self._pcell.get_number_of_atoms()
nac_q = np.zeros((num_atom, num_atom, 3, 3), dtype='double')
nac_q = np.zeros((num_atom, num_atom, 3, 3), dtype="double")
if (np.abs(q_direction) < 1e-5).all():
return nac_q
@ -267,7 +283,7 @@ class DerivativeOfDynamicalMatrix(object):
def _d_nac(self, q_direction):
num_atom = self._pcell.get_number_of_atoms()
d_nac_q = np.zeros((3, num_atom, num_atom, 3, 3), dtype='double')
d_nac_q = np.zeros((3, num_atom, num_atom, 3, 3), dtype="double")
if (np.abs(q_direction) < 1e-5).all():
return d_nac_q
@ -287,8 +303,8 @@ class DerivativeOfDynamicalMatrix(object):
A_j = self._A(q, Z, j)
dA_j = self._dA(Z, j, xyz)
d_nac_q[xyz, i, j] = (
(np.outer(dA_i, A_j) + np.outer(A_i, dA_j)) / B -
np.outer(A_i, A_j) * dB / B ** 2)
np.outer(dA_i, A_j) + np.outer(A_i, dA_j)
) / B - np.outer(A_i, A_j) * dB / B ** 2
num_satom = self._scell.get_number_of_atoms()
N = num_satom // num_atom

View File

@ -1,3 +1,4 @@
"""Routines to handle displacements in supercells."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -34,11 +35,11 @@
import numpy as np
directions_axis = np.array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
directions_axis = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
directions_diag = np.array([[1, 0, 0],
directions_diag = np.array(
[
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 1, 0],
@ -50,33 +51,34 @@ directions_diag = np.array([[1, 0, 0],
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
[-1, 1, 1]])
[-1, 1, 1],
]
)
def directions_to_displacement_dataset(displacement_directions,
distance,
supercell):
def directions_to_displacement_dataset(displacement_directions, distance, supercell):
"""Transform displacement directions to displacements in Cartesian coordinates."""
lattice = supercell.get_cell()
first_atoms = []
for disp in displacement_directions:
direction = disp[1:]
disp_cartesian = np.dot(direction, lattice)
disp_cartesian *= distance / np.linalg.norm(disp_cartesian)
first_atoms.append({'number': int(disp[0]),
'displacement': disp_cartesian.tolist()})
first_atoms.append(
{"number": int(disp[0]), "displacement": disp_cartesian.tolist()}
)
displacement_dataset = {
'natom': supercell.get_number_of_atoms(),
'first_atoms': first_atoms}
"natom": supercell.get_number_of_atoms(),
"first_atoms": first_atoms,
}
return displacement_dataset
def get_least_displacements(symmetry,
is_plusminus='auto',
is_diagonal=True,
is_trigonal=False,
log_level=0):
"""Return a set of displacements
def get_least_displacements(
symmetry, is_plusminus="auto", is_diagonal=True, is_trigonal=False, log_level=0
):
"""Return a set of displacements.
Returns
-------
@ -110,53 +112,44 @@ def get_least_displacements(symmetry,
for v in rot:
print("%2d %2d %2d" % tuple(v))
for disp in get_displacement(site_symmetry,
directions,
is_trigonal,
log_level):
displacements.append([atom_num,
disp[0], disp[1], disp[2]])
if is_plusminus == 'auto':
for disp in get_displacement(site_symmetry, directions, is_trigonal, log_level):
displacements.append([atom_num, disp[0], disp[1], disp[2]])
if is_plusminus == "auto":
if is_minus_displacement(disp, site_symmetry):
displacements.append([atom_num,
-disp[0], -disp[1], -disp[2]])
displacements.append([atom_num, -disp[0], -disp[1], -disp[2]])
elif is_plusminus is True:
displacements.append([atom_num,
-disp[0], -disp[1], -disp[2]])
displacements.append([atom_num, -disp[0], -disp[1], -disp[2]])
return displacements
def get_displacement(site_symmetry,
directions=directions_diag,
is_trigonal=False,
log_level=0):
def get_displacement(
site_symmetry, directions=directions_diag, is_trigonal=False, log_level=0
):
"""Return displacement directions for one atom."""
# One
sitesym_num, disp = get_displacement_one(site_symmetry,
directions)
sitesym_num, disp = _get_displacement_one(site_symmetry, directions)
if disp is not None:
if log_level > 2:
print("Site symmetry used to expand a direction %s" % disp[0])
print(site_symmetry[sitesym_num])
return disp
# Two
sitesym_num, disps = get_displacement_two(site_symmetry,
directions)
sitesym_num, disps = _get_displacement_two(site_symmetry, directions)
if disps is not None:
if log_level > 2:
print("Site symmetry used to expand directions %s %s" %
(disps[0], disps[1]))
print(
"Site symmetry used to expand directions %s %s" % (disps[0], disps[1])
)
print(site_symmetry[sitesym_num])
if is_trigonal:
disps_new = [disps[0]]
if is_trigonal_axis(site_symmetry[sitesym_num]):
if _is_trigonal_axis(site_symmetry[sitesym_num]):
if log_level > 2:
print("Trigonal axis is found.")
disps_new.append(np.dot(disps[0],
site_symmetry[sitesym_num].T))
disps_new.append(np.dot(disps_new[1],
site_symmetry[sitesym_num].T))
disps_new.append(np.dot(disps[0], site_symmetry[sitesym_num].T))
disps_new.append(np.dot(disps_new[1], site_symmetry[sitesym_num].T))
disps_new.append(disps[1])
return disps_new
else:
@ -165,25 +158,33 @@ def get_displacement(site_symmetry,
return [directions[0], directions[1], directions[2]]
def get_displacement_one(site_symmetry,
directions=directions_diag):
def _get_displacement_one(site_symmetry, directions=directions_diag):
"""Return one displacement.
This method tries to find three linearly independent displacements by
applying site symmetry to an input displacement.
"""
for direction in directions:
rot_directions = []
for r in site_symmetry:
rot_directions.append(np.dot(direction, r.T))
num_sitesym = len(site_symmetry)
for i in range(num_sitesym):
for j in range(i+1, num_sitesym):
det = determinant(direction,
rot_directions[i],
rot_directions[j])
for j in range(i + 1, num_sitesym):
det = _determinant(direction, rot_directions[i], rot_directions[j])
if det != 0:
return i, [direction]
return None, None
def get_displacement_two(site_symmetry,
directions=directions_diag):
def _get_displacement_two(site_symmetry, directions=directions_diag):
"""Return one displacement.
This method tries to find three linearly independent displacements by
applying site symmetry to two input displacements.
"""
for direction in directions:
rot_directions = []
for r in site_symmetry:
@ -191,15 +192,14 @@ def get_displacement_two(site_symmetry,
num_sitesym = len(site_symmetry)
for i in range(num_sitesym):
for second_direction in directions:
det = determinant(direction,
rot_directions[i],
second_direction)
det = _determinant(direction, rot_directions[i], second_direction)
if det != 0:
return i, [direction, second_direction]
return None, None
def is_minus_displacement(direction, site_symmetry):
"""Symmetrically check if minus displacement is necessary or not."""
is_minus = True
for r in site_symmetry:
rot_direction = np.dot(direction, r.T)
@ -211,7 +211,12 @@ def is_minus_displacement(direction, site_symmetry):
return is_minus
def is_trigonal_axis(r):
def _is_trigonal_axis(r):
"""Check three folded rotation.
True if r^3 = identity.
"""
r3 = np.dot(np.dot(r, r), r)
if (r3 == np.eye(3, dtype=int)).all():
return True
@ -219,51 +224,55 @@ def is_trigonal_axis(r):
return False
def determinant(a, b, c):
det = (a[0] * b[1] * c[2] - a[0] * b[2] * c[1]
+ a[1] * b[2] * c[0] - a[1] * b[0] * c[2]
+ a[2] * b[0] * c[1] - a[2] * b[1] * c[0])
def _determinant(a, b, c):
"""Return determinant of 3x3 matrix of [a, b, c]."""
det = (
a[0] * b[1] * c[2]
- a[0] * b[2] * c[1]
+ a[1] * b[2] * c[0]
- a[1] * b[0] * c[2]
+ a[2] * b[0] * c[1]
- a[2] * b[1] * c[0]
)
return det
def get_random_displacements_dataset(num_supercells,
distance,
num_atoms,
random_seed=None):
if (np.issubdtype(type(random_seed), np.integer) and
random_seed >= 0 and random_seed < 2 ** 32):
def get_random_displacements_dataset(
num_supercells, distance, num_atoms, random_seed=None
):
"""Return random displacements at constant displacement distance."""
if (
np.issubdtype(type(random_seed), np.integer)
and random_seed >= 0
and random_seed < 2 ** 32
):
seed = random_seed
else:
seed = None
disps = get_random_directions(num_atoms * num_supercells,
random_seed=random_seed) * distance
supercell_disps = np.array(disps.reshape(num_supercells, num_atoms, 3),
dtype='double', order='C')
dataset = {'displacements': supercell_disps}
disps = (
_get_random_directions(num_atoms * num_supercells, random_seed=random_seed)
* distance
)
supercell_disps = np.array(
disps.reshape(num_supercells, num_atoms, 3), dtype="double", order="C"
)
dataset = {"displacements": supercell_disps}
if seed is not None:
dataset['random_seed'] = seed
dataset["random_seed"] = seed
return dataset
def get_random_directions(num_atoms, random_seed=None):
"""Returns random directions in sphere with radius 1"""
if (np.issubdtype(type(random_seed), np.integer) and
random_seed >= 0 and random_seed < 2 ** 32):
def _get_random_directions(num_atoms, random_seed=None):
"""Return random directions in sphere with radius 1."""
if (
np.issubdtype(type(random_seed), np.integer)
and random_seed >= 0
and random_seed < 2 ** 32
):
np.random.seed(random_seed)
xy = np.random.randn(3, num_atoms)
r = np.sqrt((xy ** 2).sum(axis=0))
return (xy / r).T
def print_displacements(symmetry,
directions=directions_diag):
displacements = get_least_displacements(symmetry, directions)
print("Least displacements:")
print("Atom Directions")
print("----------------------------")
for key in displacements:
print("%4d %s" % (key + 1, displacements[key]))

View File

@ -36,58 +36,16 @@
import sys
import warnings
from phonopy.structure.cells import sparse_to_dense_svecs
from phonopy.harmonic.dynmat_to_fc import DynmatToForceConstants
from typing import Type, Union
import numpy as np
def get_dynamical_matrix(fc2,
supercell,
primitive,
nac_params=None,
frequency_scale_factor=None,
decimals=None,
symprec=1e-5,
log_level=0):
"""Return dynamical matrix.
The instance of a class inherited from DynamicalMatrix will be returned
depending on paramters.
"""
if frequency_scale_factor is None:
_fc2 = fc2
else:
_fc2 = fc2 * frequency_scale_factor ** 2
if nac_params is None:
dm = DynamicalMatrix(
supercell,
primitive,
_fc2,
decimals=decimals)
else:
if 'method' not in nac_params:
method = 'gonze'
else:
method = nac_params['method']
if method == 'wang':
DM_cls = DynamicalMatrixWang
else:
DM_cls = DynamicalMatrixGL
dm = DM_cls(
supercell,
primitive,
_fc2,
decimals=decimals,
symprec=symprec,
log_level=log_level)
dm.nac_params = nac_params
return dm
from phonopy.harmonic.dynmat_to_fc import DynmatToForceConstants
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.cells import Primitive, sparse_to_dense_svecs
class DynamicalMatrix(object):
class DynamicalMatrix:
"""Dynamical matrix base class.
When prmitive and supercell lattices are L_p and L_s, respectively,
@ -108,8 +66,8 @@ class DynamicalMatrix(object):
primitive: Primitive
Primitive cell instance. Note that Primitive is inherited from
PhonopyAtoms.
supercell: Supercell
Supercell instance. Note that Supercell is inherited from PhonopyAtoms.
supercell: PhonopyAtoms.
Supercell instance.
force_constants: ndarray
Supercell force constants. Full and compact shapes of arrays are
supported.
@ -126,16 +84,18 @@ class DynamicalMatrix(object):
# Non analytical term correction
_nac = False
def __init__(self,
supercell,
primitive,
def __init__(
self,
supercell: PhonopyAtoms,
primitive: Primitive,
force_constants,
decimals=None):
decimals=None,
):
"""Init method.
Parameters
----------
supercell : Supercell
supercell : PhonopyAtoms.
Supercell.
primitive : Primitive
Primitive cell.
@ -156,14 +116,14 @@ class DynamicalMatrix(object):
self._force_constants = None
self._set_force_constants(force_constants)
self._dtype_complex = ("c%d" % (np.dtype('double').itemsize * 2))
self._dtype_complex = "c%d" % (np.dtype("double").itemsize * 2)
self._p2s_map = np.array(self._pcell.p2s_map, dtype='int_')
self._s2p_map = np.array(self._pcell.s2p_map, dtype='int_')
self._p2s_map = np.array(self._pcell.p2s_map, dtype="int_")
self._s2p_map = np.array(self._pcell.s2p_map, dtype="int_")
p2p_map = self._pcell.p2p_map
self._s2pp_map = np.array(
[p2p_map[self._s2p_map[i]] for i in range(len(self._s2p_map))],
dtype='int_')
[p2p_map[self._s2p_map[i]] for i in range(len(self._s2p_map))], dtype="int_"
)
svecs, multi = self._pcell.get_smallest_vectors()
if self._pcell.store_dense_svecs:
self._svecs = svecs
@ -177,8 +137,9 @@ class DynamicalMatrix(object):
def get_dimension(self):
"""Return number of bands."""
warnings.warn("DynamicalMatrix.get_dimension() is deprecated.",
DeprecationWarning)
warnings.warn(
"DynamicalMatrix.get_dimension() is deprecated.", DeprecationWarning
)
return len(self._pcell) * 3
@property
@ -188,9 +149,11 @@ class DynamicalMatrix(object):
def get_decimals(self):
"""Return number of decimals of dynamical matrix values."""
warnings.warn("DynamicalMatrix.get_decimals() is deprecated."
warnings.warn(
"DynamicalMatrix.get_decimals() is deprecated."
"Use DynamicalMatrix.decimals attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.decimals
@property
@ -200,21 +163,25 @@ class DynamicalMatrix(object):
def get_supercell(self):
"""Return supercell."""
warnings.warn("DynamicalMatrix.get_supercell() is deprecated."
warnings.warn(
"DynamicalMatrix.get_supercell() is deprecated."
"Use DynamicalMatrix.supercell attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.supercell
@property
def primitive(self):
def primitive(self) -> Primitive:
"""Return primitive cell."""
return self._pcell
def get_primitive(self):
"""Return primitive cell."""
warnings.warn("DynamicalMatrix.get_primitive() is deprecated."
warnings.warn(
"DynamicalMatrix.get_primitive() is deprecated."
"Use DynamicalMatrix.primitive attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.primitive
@property
@ -224,9 +191,11 @@ class DynamicalMatrix(object):
def get_force_constants(self):
"""Return supercell force constants."""
warnings.warn("DynamicalMatrix.get_force_constants() is deprecated."
warnings.warn(
"DynamicalMatrix.get_force_constants() is deprecated."
"Use DynamicalMatrix.force_constants attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.force_constants
@property
@ -252,13 +221,15 @@ class DynamicalMatrix(object):
def get_dynamical_matrix(self):
"""Return dynamcial matrix calculated at q."""
warnings.warn("DynamicalMatrix.get_get_dynamical_matrix() is "
warnings.warn(
"DynamicalMatrix.get_get_dynamical_matrix() is "
"deprecated."
"Use DynamicalMatrix.get_dynamical_matrix attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.dynamical_matrix
def run(self, q, lang='C'):
def run(self, q, lang="C"):
"""Run dynamical matrix calculation at a q-point.
q : array_like
@ -270,27 +241,30 @@ class DynamicalMatrix(object):
def set_dynamical_matrix(self, q):
"""Run dynamical matrix calculation at a q-point."""
warnings.warn("DynamicalMatrix.set_dynamical_matrix() is deprecated."
warnings.warn(
"DynamicalMatrix.set_dynamical_matrix() is deprecated."
"Use DynamicalMatrix.run().",
DeprecationWarning)
DeprecationWarning,
)
self.run(q)
def _run(self, q, lang='C'):
if lang == 'C':
import phonopy._phonopy as phonoc # noqa F401
def _run(self, q, lang="C"):
if lang == "C":
self._run_c_dynamical_matrix(q)
else:
self._run_py_dynamical_matrix(q)
def _set_force_constants(self, fc):
if (type(fc) is np.ndarray and
fc.dtype is np.double and
fc.flags.aligned and
fc.flags.owndata and
fc.flags.c_contiguous): # noqa E129
if (
type(fc) is np.ndarray
and fc.dtype is np.double
and fc.flags.aligned
and fc.flags.owndata
and fc.flags.c_contiguous
): # noqa E129
self._force_constants = fc
else:
self._force_constants = np.array(fc, dtype='double', order='C')
self._force_constants = np.array(fc, dtype="double", order="C")
def _run_c_dynamical_matrix(self, q):
import phonopy._phonopy as phonoc
@ -298,24 +272,25 @@ class DynamicalMatrix(object):
fc = self._force_constants
mass = self._pcell.masses
size_prim = len(mass)
dm = np.zeros((size_prim * 3, size_prim * 3),
dtype=self._dtype_complex)
dm = np.zeros((size_prim * 3, size_prim * 3), dtype=self._dtype_complex)
if fc.shape[0] == fc.shape[1]: # full-fc
s2p_map = self._s2p_map
p2s_map = self._p2s_map
else: # compact-fc
s2p_map = self._s2pp_map
p2s_map = np.arange(len(self._p2s_map), dtype='int_')
p2s_map = np.arange(len(self._p2s_map), dtype="int_")
phonoc.dynamical_matrix(dm.view(dtype='double'),
phonoc.dynamical_matrix(
dm.view(dtype="double"),
fc,
np.array(q, dtype='double'),
np.array(q, dtype="double"),
self._svecs,
self._multi,
mass,
s2p_map,
p2s_map)
p2s_map,
)
# Data of dm array are stored in memory by the C order of
# (size_prim * 3, size_prim * 3, 2), where the last 2 means
@ -357,7 +332,7 @@ class DynamicalMatrix(object):
for k in range(len(self._scell)):
if s_j == self._s2p_map[k]:
m, adrs = multi[k][i]
svecs_at = svecs[adrs:adrs + m]
svecs_at = svecs[adrs : adrs + m]
phase = []
for ll in range(m):
vec = svecs_at[ll]
@ -365,7 +340,7 @@ class DynamicalMatrix(object):
phase_factor = np.exp(phase).sum()
dm_local += fc_elem[k] * phase_factor / sqrt_mm / m
dm[(i*3):(i*3+3), (j*3):(j*3+3)] += dm_local
dm[(i * 3) : (i * 3 + 3), (j * 3) : (j * 3 + 3)] += dm_local
# Impose Hermisian condition
self._dynamical_matrix = (dm + dm.conj().transpose()) / 2
@ -376,18 +351,20 @@ class DynamicalMatrixNAC(DynamicalMatrix):
_nac = True
def __init__(self,
supercell,
primitive,
def __init__(
self,
supercell: PhonopyAtoms,
primitive: Primitive,
force_constants,
symprec=1e-5,
decimals=None,
log_level=0):
log_level=0,
):
"""Init method.
Parameters
----------
supercell : Supercell
supercell : PhonopyAtoms
Supercell.
primitive : Primitive
Primitive cell.
@ -405,11 +382,7 @@ class DynamicalMatrixNAC(DynamicalMatrix):
Log level.
"""
super(DynamicalMatrixNAC, self).__init__(
supercell,
primitive,
force_constants,
decimals=decimals)
super().__init__(supercell, primitive, force_constants, decimals=decimals)
self._symprec = symprec
self._log_level = log_level
@ -448,7 +421,8 @@ class DynamicalMatrixNAC(DynamicalMatrix):
warnings.warn(
"DynamicalMatrixNAC.get_born_effective_charges() is deprecated."
"Use DynamicalMatrixNAC.born attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.born
@property
@ -458,9 +432,11 @@ class DynamicalMatrixNAC(DynamicalMatrix):
def get_nac_factor(self):
"""Return NAC unit conversion factor."""
warnings.warn("DynamicalMatrixNAC.get_nac_factor() is deprecated."
warnings.warn(
"DynamicalMatrixNAC.get_nac_factor() is deprecated."
"Use DynamicalMatrixNAC.nac_factor attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.nac_factor
@property
@ -473,7 +449,8 @@ class DynamicalMatrixNAC(DynamicalMatrix):
warnings.warn(
"DynamicalMatrixNAC.get_dielectric_constant() is deprecated."
"Use DynamicalMatrixNAC.dielectric_constant attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.dielectric_constant
@property
@ -483,17 +460,17 @@ class DynamicalMatrixNAC(DynamicalMatrix):
def get_nac_method(self):
"""Return NAC method name."""
warnings.warn("DynamicalMatrixNAC.get_nac_method() is deprecated."
warnings.warn(
"DynamicalMatrixNAC.get_nac_method() is deprecated."
"Use DynamicalMatrixNAC.nac_method attribute.",
DeprecationWarning)
DeprecationWarning,
)
return self.nac_method
@property
def nac_params(self):
"""Return NAC basic parameters."""
return {'born': self.born,
'factor': self.factor,
'dielectric': self.dielectric}
return {"born": self.born, "factor": self.factor, "dielectric": self.dielectric}
@nac_params.setter
def nac_params(self, nac_params):
@ -505,7 +482,8 @@ class DynamicalMatrixNAC(DynamicalMatrix):
warnings.warn(
"DynamicalMatrixNAC.set_nac_params() is deprecated."
"Use DynamicalMatrixNAC.nac_params attribute instead.",
DeprecationWarning)
DeprecationWarning,
)
self.nac_params = nac_params
@property
@ -523,21 +501,21 @@ class DynamicalMatrixNAC(DynamicalMatrix):
def _set_basic_nac_params(self, nac_params):
"""Set basic NAC parameters."""
self._born = np.array(nac_params['born'], dtype='double', order='C')
self._unit_conversion = nac_params['factor']
self._dielectric = np.array(nac_params['dielectric'],
dtype='double', order='C')
self._born = np.array(nac_params["born"], dtype="double", order="C")
self._unit_conversion = nac_params["factor"]
self._dielectric = np.array(nac_params["dielectric"], dtype="double", order="C")
def set_dynamical_matrix(self, q, q_direction=None):
"""Run dynamical matrix calculation at q-point."""
warnings.warn(
"DynamicalMatrixNAC.set_dynamical_matrix() is deprecated."
"Use DynamicalMatrixNAC.run().",
DeprecationWarning)
DeprecationWarning,
)
self.run(q, q_direction=q_direction)
def _get_charge_sum(self, num_atom, q, born):
nac_q = np.zeros((num_atom, num_atom, 3, 3), dtype='double', order='C')
nac_q = np.zeros((num_atom, num_atom, 3, 3), dtype="double", order="C")
A = np.dot(q, born)
for i in range(num_atom):
for j in range(num_atom):
@ -545,8 +523,9 @@ class DynamicalMatrixNAC(DynamicalMatrix):
return nac_q
def _get_constant_factor(self, q, dielectric, volume, unit_conversion):
return (unit_conversion * 4.0 * np.pi / volume /
np.dot(q.T, np.dot(dielectric, q)))
return (
unit_conversion * 4.0 * np.pi / volume / np.dot(q.T, np.dot(dielectric, q))
)
def _compute_dynamical_matrix(self, q_red, q_direction):
raise NotImplementedError()
@ -555,17 +534,19 @@ class DynamicalMatrixNAC(DynamicalMatrix):
class DynamicalMatrixGL(DynamicalMatrixNAC):
"""Non analytical term correction (NAC) by Gonze and Lee."""
_method = 'gonze'
_method = "gonze"
def __init__(self,
supercell,
primitive,
def __init__(
self,
supercell: PhonopyAtoms,
primitive: Primitive,
force_constants,
nac_params=None,
num_G_points=None, # For Gonze NAC
decimals=None,
symprec=1e-5,
log_level=0):
log_level=0,
):
"""Init method.
Parameters
@ -588,13 +569,14 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
Log level.
"""
super(DynamicalMatrixGL, self).__init__(
super().__init__(
supercell,
primitive,
force_constants,
symprec=symprec,
decimals=decimals,
log_level=log_level)
log_level=log_level,
)
# For the method by Gonze et al.
self._Gonze_force_constants = None
@ -613,18 +595,21 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
@property
def Gonze_nac_dataset(self):
"""Return Gonze-Lee NAC dataset."""
return (self._Gonze_force_constants,
return (
self._Gonze_force_constants,
self._dd_q0,
self._G_cutoff,
self._G_list,
self._Lambda)
self._Lambda,
)
def get_Gonze_nac_dataset(self):
"""Return Gonze-Lee NAC dataset."""
warnings.warn(
"DynamicalMatrixGL.get_Gonze_nac_dataset() is deprecated."
"Use DynamicalMatrixGL.Gonze_nac_dataset attribute instead.",
DeprecationWarning)
DeprecationWarning,
)
return self.Gonze_nac_dataset
def _set_nac_params(self, nac_params):
@ -634,18 +619,19 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
"""
self._set_basic_nac_params(nac_params)
if 'G_cutoff' in nac_params:
self._G_cutoff = nac_params['G_cutoff']
if "G_cutoff" in nac_params:
self._G_cutoff = nac_params["G_cutoff"]
else:
self._G_cutoff = (3 * self._num_G_points / (4 * np.pi) /
self._pcell.volume) ** (1.0 / 3)
self._G_cutoff = (
3 * self._num_G_points / (4 * np.pi) / self._pcell.volume
) ** (1.0 / 3)
self._G_list = self._get_G_list(self._G_cutoff)
if 'Lambda' in nac_params:
self._Lambda = nac_params['Lambda']
if "Lambda" in nac_params:
self._Lambda = nac_params["Lambda"]
else:
exp_cutoff = 1e-10
GeG = self._G_cutoff ** 2 * np.trace(self._dielectric) / 3
self._Lambda = np.sqrt(- GeG / 4 / np.log(exp_cutoff))
self._Lambda = np.sqrt(-GeG / 4 / np.log(exp_cutoff))
# self._H = self._get_H()
@ -658,16 +644,19 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
"""
try:
import phonopy._phonopy as phonoc # noqa F401
self._run_c_recip_dipole_dipole_q0()
except ImportError:
print("Python version of dipole-dipole calculation is not well "
"implemented.")
print(
"Python version of dipole-dipole calculation is not well "
"implemented."
)
sys.exit(1)
fc_shape = self._force_constants.shape
d2f = DynmatToForceConstants(self._pcell,
self._scell,
is_full_fc=(fc_shape[0] == fc_shape[1]))
d2f = DynmatToForceConstants(
self._pcell, self._scell, is_full_fc=(fc_shape[0] == fc_shape[1])
)
dynmat = []
num_q = len(d2f.commensurate_points)
for i, q_red in enumerate(d2f.commensurate_points):
@ -685,19 +674,22 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
def show_nac_message(self):
"""Show message on Gonze-Lee NAC method."""
print("Use NAC by Gonze et al. (no real space sum in current "
"implementation)")
print(
"Use NAC by Gonze et al. (no real space sum in current " "implementation)"
)
print(" PRB 50, 13035(R) (1994), PRB 55, 10355 (1997)")
print(" G-cutoff distance: %4.2f, Number of G-points: %d, "
"Lambda: %4.2f"
% (self._G_cutoff, len(self._G_list), self._Lambda))
print(
" G-cutoff distance: %4.2f, Number of G-points: %d, "
"Lambda: %4.2f" % (self._G_cutoff, len(self._G_list), self._Lambda)
)
def show_Gonze_nac_message(self):
"""Show message on Gonze-Lee NAC method."""
warnings.warn(
"DynamicalMatrixGL.show_Gonze_nac_message() is deprecated."
"Use DynamicalMatrixGL.show_nac_message instead.",
DeprecationWarning)
DeprecationWarning,
)
self.show_nac_message()
def _compute_dynamical_matrix(self, q_red, q_direction):
@ -716,19 +708,21 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
def _get_Gonze_dipole_dipole(self, q_red, q_direction):
rec_lat = np.linalg.inv(self._pcell.cell) # column vectors
q_cart = np.array(np.dot(q_red, rec_lat.T), dtype='double')
q_cart = np.array(np.dot(q_red, rec_lat.T), dtype="double")
if q_direction is None:
q_dir_cart = None
else:
q_dir_cart = np.array(np.dot(q_direction, rec_lat.T),
dtype='double')
q_dir_cart = np.array(np.dot(q_direction, rec_lat.T), dtype="double")
try:
import phonopy._phonopy as phonoc # noqa F401
C_recip = self._get_c_recip_dipole_dipole(q_cart, q_dir_cart)
except ImportError:
print("Python version of dipole-dipole calculation is not well "
"implemented.")
print(
"Python version of dipole-dipole calculation is not well "
"implemented."
)
sys.exit(1)
# Mass weighted
@ -757,21 +751,21 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
pos = self._pcell.positions
num_atom = len(pos)
volume = self._pcell.volume
dd = np.zeros((num_atom, 3, num_atom, 3),
dtype=self._dtype_complex, order='C')
dd = np.zeros((num_atom, 3, num_atom, 3), dtype=self._dtype_complex, order="C")
phonoc.recip_dipole_dipole(
dd.view(dtype='double'),
self._dd_q0.view(dtype='double'),
dd.view(dtype="double"),
self._dd_q0.view(dtype="double"),
self._G_list,
q_cart,
q_dir_cart,
self._born,
self._dielectric,
np.array(pos, dtype='double', order='C'),
np.array(pos, dtype="double", order="C"),
self._unit_conversion * 4.0 * np.pi / volume,
self._Lambda,
self._symprec)
self._symprec,
)
return dd
def _run_c_recip_dipole_dipole_q0(self):
@ -782,18 +776,18 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
"""
import phonopy._phonopy as phonoc
pos = self._pcell.get_positions()
self._dd_q0 = np.zeros((len(pos), 3, 3),
dtype=self._dtype_complex, order='C')
pos = self._pcell.positions
self._dd_q0 = np.zeros((len(pos), 3, 3), dtype=self._dtype_complex, order="C")
phonoc.recip_dipole_dipole_q0(
self._dd_q0.view(dtype='double'),
self._dd_q0.view(dtype="double"),
self._G_list,
self._born,
self._dielectric,
np.array(pos, dtype='double', order='C'),
np.array(pos, dtype="double", order="C"),
self._Lambda,
self._symprec)
self._symprec,
)
# Limiting contribution
# inv_eps = np.linalg.inv(self._dielectric)
@ -806,8 +800,7 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
pos = self._pcell.positions
num_atom = len(self._pcell)
volume = self._pcell.volume
C = np.zeros((num_atom, 3, num_atom, 3),
dtype=self._dtype_complex, order='C')
C = np.zeros((num_atom, 3, num_atom, 3), dtype=self._dtype_complex, order="C")
for q_K in K_list:
if np.linalg.norm(q_K) < self._symprec:
@ -818,13 +811,13 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
else:
dq_K = q_K
Z_mat = (self._get_charge_sum(num_atom, dq_K, self._born) *
self._get_constant_factor(dq_K,
self._dielectric,
volume,
self._unit_conversion))
Z_mat = self._get_charge_sum(
num_atom, dq_K, self._born
) * self._get_constant_factor(
dq_K, self._dielectric, volume, self._unit_conversion
)
for i in range(num_atom):
dpos = - pos + pos[i]
dpos = -pos + pos[i]
phase_factor = np.exp(2j * np.pi * np.dot(dpos, q_K))
for j in range(num_atom):
C[i, :, j, :] += Z_mat[i, j] * phase_factor[j]
@ -833,14 +826,14 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
q_G = q_K - q
if np.linalg.norm(q_G) < self._symprec:
continue
Z_mat = (self._get_charge_sum(num_atom, q_G, self._born) *
self._get_constant_factor(q_G,
self._dielectric,
volume,
self._unit_conversion))
Z_mat = self._get_charge_sum(
num_atom, q_G, self._born
) * self._get_constant_factor(
q_G, self._dielectric, volume, self._unit_conversion
)
for i in range(num_atom):
C_i = np.zeros((3, 3), dtype=self._dtype_complex, order='C')
dpos = - pos + pos[i]
C_i = np.zeros((3, 3), dtype=self._dtype_complex, order="C")
dpos = -pos + pos[i]
phase_factor = np.exp(2j * np.pi * np.dot(dpos, q_G))
for j in range(num_atom):
C_i += Z_mat[i, j] * phase_factor[j]
@ -853,8 +846,7 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
# g_rad must be greater than 0 for broadcasting.
G_vec_list = self._get_G_vec_list(g_rad, rec_lat)
G_norm2 = ((G_vec_list) ** 2).sum(axis=1)
return np.array(G_vec_list[G_norm2 < G_cutoff ** 2],
dtype='double', order='C')
return np.array(G_vec_list[G_norm2 < G_cutoff ** 2], dtype="double", order="C")
def _get_G_vec_list(self, g_rad, rec_lat):
pts = np.arange(-g_rad, g_rad + 1)
@ -877,22 +869,23 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
try:
from scipy.special import erfc
erfc_y = erfc(y)
except ImportError:
from math import erfc
erfc_y = np.zeros_like(y)
for i in np.ndindex(y.shape):
erfc_y[i] = erfc(y[i])
with np.errstate(divide='ignore', invalid='ignore'):
A = (3 * erfc_y / y3 + 2 / np.sqrt(np.pi)
* exp_y2 * (3 / y2 + 2)) / y2
with np.errstate(divide="ignore", invalid="ignore"):
A = (3 * erfc_y / y3 + 2 / np.sqrt(np.pi) * exp_y2 * (3 / y2 + 2)) / y2
A[A == np.inf] = 0
A = np.nan_to_num(A)
B = erfc_y / y3 + 2 / np.sqrt(np.pi) * exp_y2 / y2
B[B == np.inf] = 0
B = np.nan_to_num(B)
H = np.zeros((3, 3) + y.shape, dtype='double', order='C')
H = np.zeros((3, 3) + y.shape, dtype="double", order="C")
for i, j in np.ndindex((3, 3)):
H[i, j] = x[:, :, :, i] * x[:, :, :, j] * A - eps_inv[i, j] * B
return H
@ -901,21 +894,23 @@ class DynamicalMatrixGL(DynamicalMatrixNAC):
class DynamicalMatrixWang(DynamicalMatrixNAC):
"""Non analytical term correction (NAC) by Wang et al."""
_method = 'wang'
_method = "wang"
def __init__(self,
supercell,
primitive,
def __init__(
self,
supercell: PhonopyAtoms,
primitive: Primitive,
force_constants,
nac_params=None,
decimals=None,
symprec=1e-5,
log_level=0):
log_level=0,
):
"""Init method.
Parameters
----------
supercell : Supercell
supercell : PhonopyAtoms
Supercell.
primitive : Primitive
Primitive cell.
@ -933,13 +928,14 @@ class DynamicalMatrixWang(DynamicalMatrixNAC):
Log level.
"""
super(DynamicalMatrixWang, self).__init__(
super().__init__(
supercell,
primitive,
force_constants,
symprec=symprec,
decimals=decimals,
log_level=log_level)
log_level=log_level,
)
self._symprec = symprec
if nac_params is not None:
@ -948,8 +944,7 @@ class DynamicalMatrixWang(DynamicalMatrixNAC):
def show_nac_message(self):
"""Show Wang et al.'s paper reference."""
if self._log_level:
print("NAC by Wang et al., J. Phys. Condens. Matter 22, "
"202201 (2010)")
print("NAC by Wang et al., J. Phys. Condens. Matter 22, " "202201 (2010)")
def _set_nac_params(self, nac_params):
"""Set NAC parameters.
@ -967,12 +962,12 @@ class DynamicalMatrixWang(DynamicalMatrixNAC):
else:
q = np.dot(q_direction, rec_lat.T)
constant = self._get_constant_factor(q,
self._dielectric,
self._pcell.volume,
self._unit_conversion)
constant = self._get_constant_factor(
q, self._dielectric, self._pcell.volume, self._unit_conversion
)
try:
import phonopy._phonopy as phonoc # noqa F401
self._run_c_Wang_dynamical_matrix(q_red, q, constant)
except ImportError:
num_atom = len(self._pcell)
@ -988,39 +983,41 @@ class DynamicalMatrixWang(DynamicalMatrixNAC):
fc = self._force_constants
mass = self._pcell.masses
size_prim = len(mass)
dm = np.zeros((size_prim * 3, size_prim * 3),
dtype=self._dtype_complex)
dm = np.zeros((size_prim * 3, size_prim * 3), dtype=self._dtype_complex)
if fc.shape[0] == fc.shape[1]: # full fc
phonoc.nac_dynamical_matrix(dm.view(dtype='double'),
phonoc.nac_dynamical_matrix(
dm.view(dtype="double"),
fc,
np.array(q_red, dtype='double'),
np.array(q_red, dtype="double"),
self._svecs,
self._multi,
mass,
self._s2p_map,
self._p2s_map,
np.array(q, dtype='double'),
np.array(q, dtype="double"),
self._born,
factor)
factor,
)
else:
phonoc.nac_dynamical_matrix(dm.view(dtype='double'),
phonoc.nac_dynamical_matrix(
dm.view(dtype="double"),
fc,
np.array(q_red, dtype='double'),
np.array(q_red, dtype="double"),
self._svecs,
self._multi,
mass,
self._s2pp_map,
np.arange(len(self._p2s_map),
dtype='int_'),
np.array(q, dtype='double'),
np.arange(len(self._p2s_map), dtype="int_"),
np.array(q, dtype="double"),
self._born,
factor)
factor,
)
self._dynamical_matrix = dm
def _run_py_Wang_force_constants(self, fc, nac_q):
N = (len(self._scell) // len(self._pcell))
N = len(self._scell) // len(self._pcell)
for s1 in range(len(self._scell)):
# This if-statement is the trick.
# In contructing dynamical matrix in phonopy
@ -1032,3 +1029,49 @@ class DynamicalMatrixWang(DynamicalMatrixNAC):
for s2 in range(len(self._scell)):
p2 = self._s2pp_map[s2]
fc[s1, s2] += nac_q[p1, p2] / N
def get_dynamical_matrix(
fc2,
supercell: PhonopyAtoms,
primitive: Primitive,
nac_params=None,
frequency_scale_factor=None,
decimals=None,
symprec=1e-5,
log_level=0,
):
"""Return dynamical matrix.
The instance of a class inherited from DynamicalMatrix will be returned
depending on paramters.
"""
if frequency_scale_factor is None:
_fc2 = fc2
else:
_fc2 = fc2 * frequency_scale_factor ** 2
if nac_params is None:
dm = DynamicalMatrix(supercell, primitive, _fc2, decimals=decimals)
else:
if "method" not in nac_params:
method = "gonze"
else:
method = nac_params["method"]
DM_cls: Union[Type[DynamicalMatrixGL], Type[DynamicalMatrixWang]]
if method == "wang":
DM_cls = DynamicalMatrixWang
else:
DM_cls = DynamicalMatrixGL
dm = DM_cls(
supercell,
primitive,
_fc2,
decimals=decimals,
symprec=symprec,
log_level=log_level,
)
dm.nac_params = nac_params
return dm

View File

@ -34,14 +34,18 @@
# POSSIBILITY OF SUCH DAMAGE.
import warnings
import numpy as np
from phonopy.harmonic.force_constants import distribute_force_constants_by_translations
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.cells import (
get_supercell, get_primitive, shape_supercell_matrix,
sparse_to_dense_svecs)
get_primitive,
get_supercell,
shape_supercell_matrix,
sparse_to_dense_svecs,
)
from phonopy.structure.snf import SNF3x3
from phonopy.harmonic.force_constants import (
distribute_force_constants_by_translations)
def get_commensurate_points(supercell_matrix): # wrt primitive cell
@ -62,14 +66,14 @@ def get_commensurate_points(supercell_matrix): # wrt primitive cell
"""
smat = np.array(supercell_matrix, dtype=int)
rec_primitive = PhonopyAtoms(numbers=[1],
scaled_positions=[[0, 0, 0]],
cell=np.diag([1, 1, 1]),
pbc=True)
rec_primitive = PhonopyAtoms(
numbers=[1], scaled_positions=[[0, 0, 0]], cell=np.diag([1, 1, 1]), pbc=True
)
rec_supercell = get_supercell(rec_primitive, smat.T)
q_pos = rec_supercell.scaled_positions
return np.array(np.where(q_pos > 1 - 1e-15, q_pos - 1, q_pos),
dtype='double', order='C')
return np.array(
np.where(q_pos > 1 - 1e-15, q_pos - 1, q_pos), dtype="double", order="C"
)
def get_commensurate_points_in_integers(supercell_matrix):
@ -99,11 +103,13 @@ def get_commensurate_points_in_integers(supercell_matrix):
snf.run()
D = snf.D.diagonal()
b, c, a = np.meshgrid(range(D[1]), range(D[2]), range(D[0]))
lattice_points = np.dot(np.c_[a.ravel() * D[1] * D[2],
b.ravel() * D[0] * D[2],
c.ravel() * D[0] * D[1]], snf.Q.T)
lattice_points = np.array(lattice_points % np.prod(D),
dtype='intc', order='C')
lattice_points = np.dot(
np.c_[
a.ravel() * D[1] * D[2], b.ravel() * D[0] * D[2], c.ravel() * D[0] * D[1]
],
snf.Q.T,
)
lattice_points = np.array(lattice_points % np.prod(D), dtype="intc", order="C")
return lattice_points
@ -147,17 +153,17 @@ def ph2fc(ph_orig, supercell_matrix):
pcell = get_primitive(
scell,
np.dot(np.linalg.inv(smat), ph_orig.primitive_matrix),
positions_to_reorder=ph_orig.primitive.scaled_positions)
positions_to_reorder=ph_orig.primitive.scaled_positions,
)
d2f = DynmatToForceConstants(pcell, scell)
ph_orig.run_qpoints(d2f.commensurate_points,
with_dynamical_matrices=True)
ph_orig.run_qpoints(d2f.commensurate_points, with_dynamical_matrices=True)
ph_dict = ph_orig.get_qpoints_dict()
d2f.dynamical_matrices = ph_dict['dynamical_matrices']
d2f.dynamical_matrices = ph_dict["dynamical_matrices"]
d2f.run()
return d2f.force_constants
class DynmatToForceConstants(object):
class DynmatToForceConstants:
"""Transforms eigensolutions to force constants.
This is the inverse transform of force constants to eigensolutions.
@ -211,14 +217,16 @@ class DynmatToForceConstants(object):
"""
def __init__(self,
def __init__(
self,
primitive,
supercell,
eigenvalues=None,
eigenvectors=None,
dynamical_matrices=None,
commensurate_points=None,
is_full_fc=True):
is_full_fc=True,
):
"""Init method.
Parameters
@ -237,10 +245,9 @@ class DynmatToForceConstants(object):
self._pcell = primitive
self._scell = supercell
supercell_matrix = np.linalg.inv(self._pcell.primitive_matrix)
supercell_matrix = np.rint(supercell_matrix).astype('intc')
supercell_matrix = np.rint(supercell_matrix).astype("intc")
if commensurate_points is None:
self._commensurate_points = get_commensurate_points(
supercell_matrix)
self._commensurate_points = get_commensurate_points(supercell_matrix)
else:
self._commensurate_points = commensurate_points
@ -261,30 +268,34 @@ class DynmatToForceConstants(object):
else:
self._fc_shape = (n_p, n_s, 3, 3)
self._dtype_complex = ("c%d" % (np.dtype('double').itemsize * 2))
self._dtype_complex = "c%d" % (np.dtype("double").itemsize * 2)
if dynamical_matrices is not None or commensurate_points is not None:
warnings.warn("Instanciation init parameters of dynamical_matrices"
warnings.warn(
"Instanciation init parameters of dynamical_matrices"
" and commensurate_points are deprecated. Use "
"respecitve attributes.",
DeprecationWarning)
DeprecationWarning,
)
if eigenvalues is not None or eigenvectors is not None:
warnings.warn("Instanciation init parameters of eigenvalues and "
warnings.warn(
"Instanciation init parameters of eigenvalues and "
"eigenvectors are deprecated. Use "
"create_dynamical_matrices method.",
DeprecationWarning)
DeprecationWarning,
)
if eigenvalues is not None and eigenvectors is not None:
self.create_dynamical_matrices(
eigenvalues=eigenvalues,
eigenvectors=eigenvectors)
eigenvalues=eigenvalues, eigenvectors=eigenvectors
)
elif dynamical_matrices is not None:
self.dynamical_matrices = dynamical_matrices
def run(self, lang='C'):
def run(self, lang="C"):
"""Run."""
self._fc = np.zeros(self._fc_shape, dtype='double', order='C')
self._fc = np.zeros(self._fc_shape, dtype="double", order="C")
self._inverse_transformation(lang=lang)
@property
@ -315,13 +326,11 @@ class DynmatToForceConstants(object):
@commensurate_points.setter
def commensurate_points(self, comm_points):
self._commensurate_points = np.array(
comm_points, dtype='double', order='C')
self._commensurate_points = np.array(comm_points, dtype="double", order="C")
def get_commensurate_points(self):
"""Commensurate points in supercell with respect to primitive cell."""
warnings.warn("Use attribute, commensurate_points.",
DeprecationWarning)
warnings.warn("Use attribute, commensurate_points.", DeprecationWarning)
return self.commensurate_points
@property
@ -340,7 +349,7 @@ class DynmatToForceConstants(object):
@dynamical_matrices.setter
def dynamical_matrices(self, dynmat):
self._dynmat = np.array(dynmat, dtype=self._dtype_complex, order='C')
self._dynmat = np.array(dynmat, dtype=self._dtype_complex, order="C")
def get_dynamical_matrices(self):
"""Return numerical matrices of dynamical matrices."""
@ -373,42 +382,42 @@ class DynmatToForceConstants(object):
"""
dm = []
for eigvals, eigvecs in zip(eigenvalues, eigenvectors):
dm.append(np.dot(np.dot(eigvecs, np.diag(eigvals)),
eigvecs.T.conj()))
dm.append(np.dot(np.dot(eigvecs, np.diag(eigvals)), eigvecs.T.conj()))
self.dynamical_matrices = dm
def _inverse_transformation(self, lang='C'):
if lang == 'C':
import phonopy._phonopy as phonoc
def _inverse_transformation(self, lang="C"):
if lang == "C":
self._c_inverse_transformation()
else:
self._py_inverse_transformation()
if self._fc.shape[0] == self._fc.shape[1]:
distribute_force_constants_by_translations(self._fc,
self._pcell,
self._scell)
distribute_force_constants_by_translations(
self._fc, self._pcell, self._scell
)
def _c_inverse_transformation(self):
import phonopy._phonopy as phonoc
s2p = np.array(self._pcell.s2p_map, dtype='int_')
s2p = np.array(self._pcell.s2p_map, dtype="int_")
p2p = self._pcell.p2p_map
s2pp = np.array([p2p[i] for i in s2p], dtype='int_')
s2pp = np.array([p2p[i] for i in s2p], dtype="int_")
if self._fc.shape[0] == self._fc.shape[1]:
fc_index_map = np.array(self._pcell.p2s_map, dtype='int_')
fc_index_map = np.array(self._pcell.p2s_map, dtype="int_")
else:
fc_index_map = np.arange(self._fc.shape[0], dtype='int_')
fc_index_map = np.arange(self._fc.shape[0], dtype="int_")
phonoc.transform_dynmat_to_fc(self._fc,
self._dynmat.view(dtype='double'),
phonoc.transform_dynmat_to_fc(
self._fc,
self._dynmat.view(dtype="double"),
self._commensurate_points,
self._svecs,
self._multi,
self._pcell.masses,
s2pp,
fc_index_map)
fc_index_map,
)
def _py_inverse_transformation(self):
s2p = self._pcell.s2p_map
@ -429,12 +438,13 @@ class DynmatToForceConstants(object):
def _sum_q(self, p_i, s_j, p_j):
multi, adrs = self._multi[s_j, p_i]
pos = self._svecs[adrs:(adrs + multi)]
sum_q = np.zeros((3, 3), dtype=self._dtype_complex, order='C')
pos = self._svecs[adrs : (adrs + multi)]
sum_q = np.zeros((3, 3), dtype=self._dtype_complex, order="C")
phases = -2j * np.pi * np.dot(self._commensurate_points, pos.T)
phase_factors = np.exp(phases).sum(axis=1) / multi
for i, coef in enumerate(phase_factors):
sum_q += self._dynmat[i,
(p_i * 3):(p_i * 3 + 3),
(p_j * 3):(p_j * 3 + 3)] * coef
sum_q += (
self._dynmat[i, (p_i * 3) : (p_i * 3 + 3), (p_j * 3) : (p_j * 3 + 3)]
* coef
)
return sum_q.real

View File

@ -34,34 +34,34 @@
# POSSIBILITY OF SUCH DAMAGE.
import textwrap
import numpy as np
from phonopy.structure.cells import (get_smallest_vectors,
compute_permutation_for_rotation)
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.cells import (
compute_permutation_for_rotation,
get_smallest_vectors,
)
def get_force_constants(set_of_forces,
symmetry,
supercell,
atom_list=None,
decimals=None):
def get_force_constants(
set_of_forces, symmetry, supercell, atom_list=None, decimals=None
):
"""Calculate force constants from disp-force dataset."""
first_atoms = [{'number': x.get_atom_number(),
'displacement': x.get_displacement(),
'forces': x.get_forces()} for x in set_of_forces]
dataset = {'natom': supercell.get_number_of_atoms(),
'first_atoms': first_atoms}
force_constants = get_fc2(supercell,
symmetry,
dataset,
atom_list=atom_list)
first_atoms = [
{
"number": x.get_atom_number(),
"displacement": x.get_displacement(),
"forces": x.get_forces(),
}
for x in set_of_forces
]
dataset = {"natom": supercell.get_number_of_atoms(), "first_atoms": first_atoms}
force_constants = get_fc2(supercell, symmetry, dataset, atom_list=atom_list)
return force_constants
def get_fc2(supercell,
symmetry,
dataset,
atom_list=None,
decimals=None):
def get_fc2(supercell, symmetry, dataset, atom_list=None, decimals=None):
"""Force constants are computed.
Force constants, Phi, are calculated from sets for forces, F, and
@ -86,26 +86,26 @@ def get_fc2(supercell,
else:
fc_dim0 = len(atom_list)
force_constants = np.zeros((fc_dim0, len(supercell), 3, 3),
dtype='double', order='C')
force_constants = np.zeros(
(fc_dim0, len(supercell), 3, 3), dtype="double", order="C"
)
# Fill force_constants[ displaced_atoms, all_atoms_in_supercell ]
atom_list_done = _get_force_constants_disps(
force_constants,
supercell,
dataset,
symmetry,
atom_list=atom_list)
force_constants, supercell, dataset, symmetry, atom_list=atom_list
)
rotations = symmetry.get_symmetry_operations()['rotations']
lattice = np.array(supercell.cell.T, dtype='double', order='C')
rotations = symmetry.symmetry_operations["rotations"]
lattice = np.array(supercell.cell.T, dtype="double", order="C")
permutations = symmetry.atomic_permutations
distribute_force_constants(force_constants,
distribute_force_constants(
force_constants,
atom_list_done,
lattice,
rotations,
permutations,
atom_list=atom_list)
atom_list=atom_list,
)
if decimals:
force_constants = force_constants.round(decimals=decimals)
@ -115,22 +115,20 @@ def get_fc2(supercell,
def compact_fc_to_full_fc(phonon, compact_fc, log_level=0):
"""Transform compact fc to full fc."""
fc = np.zeros((compact_fc.shape[1], compact_fc.shape[1], 3, 3),
dtype='double', order='C')
fc = np.zeros(
(compact_fc.shape[1], compact_fc.shape[1], 3, 3), dtype="double", order="C"
)
fc[phonon.primitive.p2s_map] = compact_fc
distribute_force_constants_by_translations(
fc, phonon.primitive, phonon.supercell)
distribute_force_constants_by_translations(fc, phonon.primitive, phonon.supercell)
if log_level:
print("Force constants were expanded to full format.")
return fc
def cutoff_force_constants(force_constants,
supercell,
primitive,
cutoff_radius,
symprec=1e-5):
def cutoff_force_constants(
force_constants, supercell, primitive, cutoff_radius, symprec=1e-5
):
"""Set zero to force constants outside of cutoff distance.
Note
@ -146,7 +144,8 @@ def cutoff_force_constants(force_constants,
supercell.scaled_positions,
supercell.scaled_positions,
symprec=symprec,
store_dense_svecs=primitive.store_dense_svecs)
store_dense_svecs=primitive.store_dense_svecs,
)
lattice = supercell.cell
else:
svecs, multi = primitive.get_smallest_vectors()
@ -190,6 +189,7 @@ def symmetrize_force_constants(force_constants, level=1):
"""
try:
import phonopy._phonopy as phonoc
phonoc.perm_trans_symmetrize_fc(force_constants, level)
except ImportError:
for i in range(level):
@ -198,9 +198,7 @@ def symmetrize_force_constants(force_constants, level=1):
set_translational_invariance(force_constants)
def symmetrize_compact_force_constants(force_constants,
primitive,
level=1):
def symmetrize_compact_force_constants(force_constants, primitive, level=1):
"""Symmetry force constants by translational and permutation symmetries.
Parameters
@ -221,47 +219,52 @@ def symmetrize_compact_force_constants(force_constants,
p2s_map = primitive.p2s_map
p2p_map = primitive.p2p_map
permutations = primitive.atomic_permutations
s2pp_map, nsym_list = get_nsym_list_and_s2pp(s2p_map,
p2p_map,
permutations)
s2pp_map, nsym_list = get_nsym_list_and_s2pp(s2p_map, p2p_map, permutations)
try:
import phonopy._phonopy as phonoc
phonoc.perm_trans_symmetrize_compact_fc(force_constants,
permutations,
s2pp_map,
p2s_map,
nsym_list,
level)
phonoc.perm_trans_symmetrize_compact_fc(
force_constants, permutations, s2pp_map, p2s_map, nsym_list, level
)
except ImportError:
text = ("Import error at phonoc.perm_trans_symmetrize_compact_fc. "
"Corresponding pytono code is not implemented.")
text = (
"Import error at phonoc.perm_trans_symmetrize_compact_fc. "
"Corresponding pytono code is not implemented."
)
raise RuntimeError(text)
def distribute_force_constants(force_constants,
def distribute_force_constants(
force_constants,
atom_list_done,
lattice, # column vectors
rotations, # scaled (fractional)
permutations,
atom_list=None):
atom_list=None,
):
"""Fill force constants elements by symmetry."""
map_atoms, map_syms = _get_sym_mappings_from_permutations(
permutations, atom_list_done)
rots_cartesian = np.array([similarity_transformation(lattice, r)
for r in rotations],
dtype='double', order='C')
permutations, atom_list_done
)
rots_cartesian = np.array(
[similarity_transformation(lattice, r) for r in rotations],
dtype="double",
order="C",
)
if atom_list is None:
targets = np.arange(force_constants.shape[1], dtype='intc')
targets = np.arange(force_constants.shape[1], dtype="intc")
else:
targets = np.array(atom_list, dtype='intc')
targets = np.array(atom_list, dtype="intc")
import phonopy._phonopy as phonoc
phonoc.distribute_fc2(force_constants,
phonoc.distribute_fc2(
force_constants,
targets,
rots_cartesian,
permutations,
np.array(map_atoms, dtype='intc'),
np.array(map_syms, dtype='intc'))
np.array(map_atoms, dtype="intc"),
np.array(map_syms, dtype="intc"),
)
def distribute_force_constants_by_translations(fc, primitive, supercell):
@ -280,22 +283,24 @@ def distribute_force_constants_by_translations(fc, primitive, supercell):
positions = supercell.scaled_positions
lattice = supercell.cell.T
diff = positions - positions[p2s[0]]
trans = np.array(diff[np.where(s2p == p2s[0])[0]],
dtype='double', order='C')
rotations = np.array([np.eye(3, dtype='intc')] * len(trans),
dtype='intc', order='C')
trans = np.array(diff[np.where(s2p == p2s[0])[0]], dtype="double", order="C")
rotations = np.array(
[np.eye(3, dtype="intc")] * len(trans), dtype="intc", order="C"
)
permutations = primitive.atomic_permutations
distribute_force_constants(fc, p2s, lattice, rotations, permutations)
def solve_force_constants(force_constants,
def solve_force_constants(
force_constants,
disp_atom_number,
displacements,
sets_of_forces,
supercell,
site_symmetry,
symprec,
atom_list=None):
atom_list=None,
):
"""Calculate force constants elements of pairs from an atom."""
if atom_list is None:
fc_index = disp_atom_number
@ -311,14 +316,14 @@ def solve_force_constants(force_constants,
sets_of_forces,
supercell,
site_symmetry,
symprec)
symprec,
)
return None
def get_positions_sent_by_rot_inv(lattice, # column vectors
positions,
site_symmetry,
symprec):
def get_positions_sent_by_rot_inv(
lattice, positions, site_symmetry, symprec # column vectors
):
"""Return atom indices of positions sent by inverse site symmetries.
Rotated_positions[rot_map] == positions.
@ -330,13 +335,12 @@ def get_positions_sent_by_rot_inv(lattice, # column vectors
"""
rot_map_syms = []
for sym in site_symmetry:
rot_map = compute_permutation_for_rotation(np.dot(positions, sym.T),
positions,
lattice,
symprec)
rot_map = compute_permutation_for_rotation(
np.dot(positions, sym.T), positions, lattice, symprec
)
rot_map_syms.append(rot_map)
return np.array(rot_map_syms, dtype='intc', order='C')
return np.array(rot_map_syms, dtype="intc", order="C")
def get_rotated_displacement(displacements, site_sym_cart):
@ -350,93 +354,77 @@ def get_rotated_displacement(displacements, site_sym_cart):
rot_disps = []
for u in displacements:
rot_disps.append([np.dot(sym, u) for sym in site_sym_cart])
return np.array(np.reshape(rot_disps, (-1, 3)), dtype='double', order='C')
return np.array(np.reshape(rot_disps, (-1, 3)), dtype="double", order="C")
def set_tensor_symmetry(force_constants,
lattice, # column vectors
positions,
symmetry):
def set_tensor_symmetry(
force_constants, lattice, positions, symmetry # column vectors
):
"""Full force constants are symmetrized using crystal symmetry.
This method extracts symmetrically equivalent sets of atomic pairs and
take sum of their force constants and average the sum.
"""
rotations = symmetry.get_symmetry_operations()['rotations']
translations = symmetry.get_symmetry_operations()['translations']
rotations = symmetry.get_symmetry_operations()["rotations"]
translations = symmetry.get_symmetry_operations()["translations"]
map_atoms = symmetry.get_map_atoms()
symprec = symmetry.tolerance
cart_rot = np.array([similarity_transformation(lattice, rot)
for rot in rotations])
cart_rot = np.array([similarity_transformation(lattice, rot) for rot in rotations])
mapa = _get_atom_indices_by_symmetry(lattice,
positions,
rotations,
translations,
symprec)
mapa = _get_atom_indices_by_symmetry(
lattice, positions, rotations, translations, symprec
)
fc_new = np.zeros_like(force_constants)
indep_atoms = symmetry.get_independent_atoms()
for i in indep_atoms:
fc_combined = np.zeros(force_constants.shape[1:], dtype='double')
fc_combined = np.zeros(force_constants.shape[1:], dtype="double")
num_equiv_atoms = _combine_force_constants_equivalent_atoms(
fc_combined,
force_constants,
i,
cart_rot,
map_atoms,
mapa)
num_sitesym = _average_force_constants_by_sitesym(fc_new,
fc_combined,
i,
cart_rot,
mapa)
fc_combined, force_constants, i, cart_rot, map_atoms, mapa
)
num_sitesym = _average_force_constants_by_sitesym(
fc_new, fc_combined, i, cart_rot, mapa
)
assert num_equiv_atoms * num_sitesym == len(rotations)
permutations = symmetry.atomic_permutations
distribute_force_constants(fc_new,
indep_atoms,
lattice,
rotations,
permutations)
distribute_force_constants(fc_new, indep_atoms, lattice, rotations, permutations)
force_constants[:] = fc_new
def set_tensor_symmetry_PJ(force_constants,
lattice,
positions,
symmetry):
def set_tensor_symmetry_PJ(force_constants, lattice, positions, symmetry):
"""Full force constants are symmetrized using crystal symmetry.
This method extracts symmetrically equivalent sets of atomic pairs and
take sum of their force constants and average the sum.
"""
rotations = symmetry.get_symmetry_operations()['rotations']
translations = symmetry.get_symmetry_operations()['translations']
rotations = symmetry.get_symmetry_operations()["rotations"]
translations = symmetry.get_symmetry_operations()["translations"]
symprec = symmetry.tolerance
N = len(rotations)
mapa = _get_atom_indices_by_symmetry(lattice,
positions,
rotations,
translations,
symprec)
cart_rot = np.array([similarity_transformation(lattice, rot).T
for rot in rotations])
mapa = _get_atom_indices_by_symmetry(
lattice, positions, rotations, translations, symprec
)
cart_rot = np.array(
[similarity_transformation(lattice, rot).T for rot in rotations]
)
cart_rot_inv = np.array([np.linalg.inv(rot) for rot in cart_rot])
fcm = np.array([force_constants[mapa[n], :, :, :][:, mapa[n], :, :]
for n in range(N)])
s = np.transpose(np.array([np.dot(cart_rot[n],
np.dot(fcm[n], cart_rot_inv[n]))
for n in range(N)]), (0, 2, 3, 1, 4))
force_constants[:] = np.array(np.average(s, axis=0),
dtype='double',
order='C')
fcm = np.array(
[force_constants[mapa[n], :, :, :][:, mapa[n], :, :] for n in range(N)]
)
s = np.transpose(
np.array(
[np.dot(cart_rot[n], np.dot(fcm[n], cart_rot_inv[n])) for n in range(N)]
),
(0, 2, 3, 1, 4),
)
force_constants[:] = np.array(np.average(s, axis=0), dtype="double", order="C")
def set_translational_invariance(force_constants):
@ -457,11 +445,9 @@ def set_translational_invariance_per_index(fc2, index=0):
for i in range(fc2.shape[1 - index]):
for j, k in list(np.ndindex(3, 3)):
if index == 0:
fc2[:, i, j, k] -= np.sum(
fc2[:, i, j, k]) / fc2.shape[0]
fc2[:, i, j, k] -= np.sum(fc2[:, i, j, k]) / fc2.shape[0]
else:
fc2[i, :, j, k] -= np.sum(
fc2[i, :, j, k]) / fc2.shape[1]
fc2[i, :, j, k] -= np.sum(fc2[i, :, j, k]) / fc2.shape[1]
def set_permutation_symmetry(force_constants):
@ -482,8 +468,7 @@ def set_permutation_symmetry(force_constants):
fc_copy = force_constants.copy()
for i in range(force_constants.shape[0]):
for j in range(force_constants.shape[1]):
force_constants[i, j] = (force_constants[i, j] +
fc_copy[j, i].T) / 2
force_constants[i, j] = (force_constants[i, j] + fc_copy[j, i].T) / 2
def similarity_transformation(rot, mat):
@ -491,10 +476,9 @@ def similarity_transformation(rot, mat):
return np.dot(rot, np.dot(mat, np.linalg.inv(rot)))
def show_drift_force_constants(force_constants,
primitive=None,
name="force constants",
values_only=False):
def show_drift_force_constants(
force_constants, primitive=None, name="force constants", values_only=False
):
"""Show force constants drift."""
if force_constants.shape[0] == force_constants.shape[1]:
num_atom = force_constants.shape[0]
@ -516,42 +500,43 @@ def show_drift_force_constants(force_constants,
p2s_map = primitive.p2s_map
p2p_map = primitive.p2p_map
permutations = primitive.atomic_permutations
s2pp_map, nsym_list = get_nsym_list_and_s2pp(s2p_map,
p2p_map,
permutations)
s2pp_map, nsym_list = get_nsym_list_and_s2pp(s2p_map, p2p_map, permutations)
try:
import phonopy._phonopy as phonoc
phonoc.transpose_compact_fc(force_constants,
permutations,
s2pp_map,
p2s_map,
nsym_list)
phonoc.transpose_compact_fc(
force_constants, permutations, s2pp_map, p2s_map, nsym_list
)
maxval1, jk1 = _get_drift_per_index(force_constants)
phonoc.transpose_compact_fc(force_constants,
permutations,
s2pp_map,
p2s_map,
nsym_list)
phonoc.transpose_compact_fc(
force_constants, permutations, s2pp_map, p2s_map, nsym_list
)
maxval2, jk2 = _get_drift_per_index(force_constants)
except ImportError:
text = ("Import error at phonoc.tranpose_compact_fc. "
"Corresponding python code is not implemented.")
text = (
"Import error at phonoc.tranpose_compact_fc. "
"Corresponding python code is not implemented."
)
raise RuntimeError(text)
if values_only:
text = ""
else:
text = "Max drift of %s: " % name
text += "%f (%s%s) %f (%s%s)" % (maxval1, "xyz"[jk1[0]], "xyz"[jk1[1]],
maxval2, "xyz"[jk2[0]], "xyz"[jk2[1]])
text += "%f (%s%s) %f (%s%s)" % (
maxval1,
"xyz"[jk1[0]],
"xyz"[jk1[1]],
maxval2,
"xyz"[jk2[0]],
"xyz"[jk2[1]],
)
print(text)
def get_nsym_list_and_s2pp(s2p_map,
p2p_map,
permutations):
def get_nsym_list_and_s2pp(s2p_map, p2p_map, permutations):
"""Find lattice points corresponding to atoms in s2p_map.
Parameters
@ -578,9 +563,14 @@ def get_nsym_list_and_s2pp(s2p_map,
This method is public because of being used by phono3py.
"""
s2pp = np.array([p2p_map[i] for i in s2p_map], dtype='intc')
nsym_list = np.array([np.where(permutations[:, i] == target)[0][0]
for i, target in enumerate(s2p_map)], dtype='intc')
s2pp = np.array([p2p_map[i] for i in s2p_map], dtype="intc")
nsym_list = np.array(
[
np.where(permutations[:, i] == target)[0][0]
for i, target in enumerate(s2p_map)
],
dtype="intc",
)
return s2pp, nsym_list
@ -645,44 +635,41 @@ def _get_drift_per_index(force_constants):
return maxval, jk
def _solve_force_constants_svd(disp_atom_number,
def _solve_force_constants_svd(
disp_atom_number,
displacements,
sets_of_forces,
supercell,
supercell: PhonopyAtoms,
site_symmetry,
symprec):
lattice = supercell.get_cell().T
positions = supercell.get_scaled_positions()
symprec,
):
lattice = supercell.cell.T
positions = supercell.scaled_positions
pos_center = positions[disp_atom_number]
positions -= pos_center
rot_map_syms = get_positions_sent_by_rot_inv(lattice,
positions,
site_symmetry,
symprec)
site_sym_cart = [similarity_transformation(lattice, sym)
for sym in site_symmetry]
rot_map_syms = get_positions_sent_by_rot_inv(
lattice, positions, site_symmetry, symprec
)
site_sym_cart = [similarity_transformation(lattice, sym) for sym in site_symmetry]
rot_disps = get_rotated_displacement(displacements, site_sym_cart)
inv_displacements = np.linalg.pinv(rot_disps)
fc = np.zeros((supercell.get_number_of_atoms(), 3, 3),
dtype='double', order='C')
for i in range(supercell.get_number_of_atoms()):
fc = np.zeros((len(supercell), 3, 3), dtype="double", order="C")
for i in range(len(supercell)):
combined_forces = []
for forces in sets_of_forces:
combined_forces.append(
_get_rotated_forces(forces[rot_map_syms[:, i]],
site_sym_cart))
_get_rotated_forces(forces[rot_map_syms[:, i]], site_sym_cart)
)
combined_forces = np.reshape(combined_forces, (-1, 3))
fc[i] = -np.dot(inv_displacements, combined_forces)
return fc
def _get_force_constants_disps(force_constants,
supercell,
dataset,
symmetry,
atom_list=None):
def _get_force_constants_disps(
force_constants, supercell, dataset, symmetry, atom_list=None
):
"""Calculate force constants Phi = -F / d.
Force constants are obtained by one of the following algorithm.
@ -693,7 +680,7 @@ def _get_force_constants_disps(force_constants,
Force constants
shape=(len(atom_list),n_satom,3,3)
dtype=double
supercell: Supercell
supercell: PhonopyAtoms
Supercell
dataset: dict
Distplacement dataset. Forces are also stored.
@ -705,37 +692,36 @@ def _get_force_constants_disps(force_constants,
"""
symprec = symmetry.tolerance
disp_atom_list = np.unique([x['number'] for x in dataset['first_atoms']])
disp_atom_list = np.unique([x["number"] for x in dataset["first_atoms"]])
for disp_atom_number in disp_atom_list:
disps = []
sets_of_forces = []
for x in dataset['first_atoms']:
if x['number'] != disp_atom_number:
for x in dataset["first_atoms"]:
if x["number"] != disp_atom_number:
continue
disps.append(x['displacement'])
sets_of_forces.append(x['forces'])
disps.append(x["displacement"])
sets_of_forces.append(x["forces"])
site_symmetry = symmetry.get_site_symmetry(disp_atom_number)
solve_force_constants(force_constants,
solve_force_constants(
force_constants,
disp_atom_number,
disps,
sets_of_forces,
supercell,
site_symmetry,
symprec,
atom_list=atom_list)
atom_list=atom_list,
)
return disp_atom_list
def _combine_force_constants_equivalent_atoms(fc_combined,
force_constants,
i,
cart_rot,
map_atoms,
mapa):
def _combine_force_constants_equivalent_atoms(
fc_combined, force_constants, i, cart_rot, map_atoms, mapa
):
num_equiv_atoms = 0
for j, k in enumerate(map_atoms):
if k != i:
@ -745,18 +731,15 @@ def _combine_force_constants_equivalent_atoms(fc_combined,
r_i = (mapa[:, j] == i).nonzero()[0][0]
for k, l in enumerate(mapa[r_i]):
fc_combined[l] += similarity_transformation(
cart_rot[r_i], force_constants[j, k])
cart_rot[r_i], force_constants[j, k]
)
fc_combined /= num_equiv_atoms
return num_equiv_atoms
def _average_force_constants_by_sitesym(fc_new,
fc_i,
i,
cart_rot,
mapa):
def _average_force_constants_by_sitesym(fc_new, fc_i, i, cart_rot, mapa):
num_sitesym = 0
for r_i, mapa_r in enumerate(mapa):
if mapa_r[i] != i:
@ -764,18 +747,15 @@ def _average_force_constants_by_sitesym(fc_new,
num_sitesym += 1
for j in range(fc_i.shape[0]):
fc_new[i, j] += similarity_transformation(
cart_rot[r_i].T, fc_i[mapa[r_i, j]])
cart_rot[r_i].T, fc_i[mapa[r_i, j]]
)
fc_new[i] /= num_sitesym
return num_sitesym
def _get_atom_indices_by_symmetry(lattice,
positions,
rotations,
translations,
symprec):
def _get_atom_indices_by_symmetry(lattice, positions, rotations, translations, symprec):
# To understand this method, understanding numpy broadcasting is mandatory.
K = len(positions)
@ -791,9 +771,9 @@ def _get_atom_indices_by_symmetry(lattice,
diff -= np.rint(diff)
diff = np.dot(diff, lattice.T)
# m[N, K(1), K(2)]
m = (np.sqrt(np.sum(diff ** 2, axis=3)) < symprec)
m = np.sqrt(np.sum(diff ** 2, axis=3)) < symprec
# index_array[K(1), K(2)]
index_array = np.tile(np.arange(K, dtype='intc'), (K, 1))
index_array = np.tile(np.arange(K, dtype="intc"), (K, 1))
# Understanding numpy boolean array indexing (extract True elements)
# mapa[N, K(1)]
mapa = np.array([index_array[mr] for mr in m])
@ -803,9 +783,9 @@ def _get_atom_indices_by_symmetry(lattice,
def _get_shortest_distance_in_PBC(pos_i, pos_j, reduced_bases):
distances = []
for k in (-1, 0, 1):
for l in (-1, 0, 1):
for ll in (-1, 0, 1):
for m in (-1, 0, 1):
diff = pos_j + np.array([k, l, m]) - pos_i
diff = pos_j + np.array([k, ll, m]) - pos_i
distances.append(np.linalg.norm(np.dot(diff, reduced_bases)))
return np.min(distances)
@ -845,8 +825,8 @@ def _get_sym_mappings_from_permutations(permutations, atom_list_done):
num_pos = permutations.shape[1]
# filled with -1
map_atoms = np.zeros((num_pos,), dtype='intc') - 1
map_syms = np.zeros((num_pos,), dtype='intc') - 1
map_atoms = np.zeros((num_pos,), dtype="intc") - 1
map_syms = np.zeros((num_pos,), dtype="intc") - 1
atom_list_done = set(atom_list_done)
for atom_todo in range(num_pos):
@ -856,9 +836,11 @@ def _get_sym_mappings_from_permutations(permutations, atom_list_done):
map_syms[atom_todo] = sym_index
break
else:
text = ("Input forces are not enough to calculate force constants,"
text = (
"Input forces are not enough to calculate force constants,"
"or something wrong (e.g. crystal structure does not "
"match).")
"match)."
)
print(textwrap.fill(text))
raise ValueError

View File

@ -1,3 +1,4 @@
"""Routines for user and calculator interfaces to phonopy."""
# Copyright (C) 2019 Atsushi Togo
# All rights reserved.
#

View File

@ -1,3 +1,4 @@
"""Abinit calculator interface."""
# Copyright (C) 2014 Atsushi Togo
# All rights reserved.
#
@ -33,18 +34,23 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import (get_scaled_positions_lines, check_forces,
get_drift_forces)
from phonopy.units import Bohr
from phonopy.cui.settings import fracval
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import (
check_forces,
get_drift_forces,
get_scaled_positions_lines,
)
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.units import Bohr
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = 'cartesian forces (eV/Angstrom)'
"""Parse forces from output files."""
hook = "cartesian forces (eV/Angstrom)"
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):
@ -54,9 +60,9 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
f = open(filename)
abinit_forces = collect_forces(f, num_atoms, hook, [1, 2, 3])
if check_forces(abinit_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(abinit_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
abinit_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(abinit_forces) - drift_force)
else:
is_parsed = False
@ -68,51 +74,52 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
def read_abinit(filename):
"""Read crystal structure."""
with open(filename) as f:
abinit_in = AbinitIn(f.readlines())
tags = abinit_in.get_variables()
acell = tags['acell']
rprim = tags['rprim'].T
scalecart = tags['scalecart']
acell = tags["acell"]
rprim = tags["rprim"].T
scalecart = tags["scalecart"]
lattice = rprim * acell
if scalecart is not None:
for i in range(3):
lattice[i] *= scalecart[i]
if tags['xcart'] is not None:
pos_bohr = np.transpose(tags['xcart'])
if tags["xcart"] is not None:
pos_bohr = np.transpose(tags["xcart"])
positions = np.dot(np.linalg.inv(lattice), pos_bohr).T
elif tags['xangst'] is not None:
pos_bohr = np.transpose(tags['xangst']) / Bohr
elif tags["xangst"] is not None:
pos_bohr = np.transpose(tags["xangst"]) / Bohr
positions = np.dot(np.linalg.inv(lattice), pos_bohr).T
elif tags['xred'] is not None:
positions = tags['xred']
elif tags["xred"] is not None:
positions = tags["xred"]
numbers = [tags['znucl'][x - 1] for x in tags['typat']]
numbers = [tags["znucl"][x - 1] for x in tags["typat"]]
return Atoms(numbers=numbers,
cell=lattice.T,
scaled_positions=positions)
return Atoms(numbers=numbers, cell=lattice.T, scaled_positions=positions)
def write_abinit(filename, cell):
with open(filename, 'w') as f:
"""Write cell to file."""
with open(filename, "w") as f:
f.write(get_abinit_structure(cell))
def write_supercells_with_displacements(supercell,
cells_with_displacements,
ids,
pre_filename="supercell",
width=3):
def write_supercells_with_displacements(
supercell, cells_with_displacements, ids, pre_filename="supercell", width=3
):
"""Write supercells with displacements to files."""
write_abinit("%s.in" % pre_filename, supercell)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.in".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_abinit(filename, cell)
def get_abinit_structure(cell):
"""Return abinit structure in text."""
znucl = []
numbers = cell.get_atomic_numbers()
for n in numbers:
@ -137,40 +144,48 @@ def get_abinit_structure(cell):
return lines
class AbinitIn(object):
class AbinitIn:
"""Class to create Abinit input file."""
def __init__(self, lines):
self._set_methods = {'acell': self._set_acell,
'natom': self._set_natom,
'ntypat': self._set_ntypat,
'rprim': self._set_rprim,
'typat': self._set_typat,
'scalecart': self._set_scalecart,
'xangst': self._set_xangst,
'xcart': self._set_xcart,
'xred': self._set_xred,
'znucl': self._set_znucl}
self._tags = {'acell': None,
'natom': None,
'ntypat': None,
'rprim': None,
'typat': None,
'scalecart': None,
'xangst': None,
'xcart': None,
'xred': None,
'znucl': None}
"""Init method."""
self._set_methods = {
"acell": self._set_acell,
"natom": self._set_natom,
"ntypat": self._set_ntypat,
"rprim": self._set_rprim,
"typat": self._set_typat,
"scalecart": self._set_scalecart,
"xangst": self._set_xangst,
"xcart": self._set_xcart,
"xred": self._set_xred,
"znucl": self._set_znucl,
}
self._tags = {
"acell": None,
"natom": None,
"ntypat": None,
"rprim": None,
"typat": None,
"scalecart": None,
"xangst": None,
"xcart": None,
"xred": None,
"znucl": None,
}
self._values = None
self._collect(lines)
def get_variables(self):
"""Return tags."""
return self._tags
def _collect(self, lines):
elements = {}
tag = None
for line_tmp in lines:
line = line_tmp.replace('!', '#').split('#')[0]
line = line_tmp.replace("!", "#").split("#")[0]
for val in [x.lower() for x in line.split()]:
if val in self._set_methods:
tag = val
@ -178,32 +193,32 @@ class AbinitIn(object):
elif tag is not None:
elements[tag].append(val)
for tag in ['natom', 'ntypat']:
for tag in ["natom", "ntypat"]:
if tag not in elements:
print("%s is not found in the input file." % tag)
sys.exit(1)
for tag in elements:
self._values = elements[tag]
if tag == 'natom' or tag == 'ntypat':
if tag == "natom" or tag == "ntypat":
self._set_methods[tag]()
for tag in elements:
self._values = elements[tag]
if tag != 'natom' and tag != 'ntypat':
if tag != "natom" and tag != "ntypat":
self._set_methods[tag]()
def _get_numerical_values(self, char_string, num_type='float'):
def _get_numerical_values(self, char_string, num_type="float"):
m = 1
if '*' in char_string:
m = int(char_string.split('*')[0])
str_val = char_string.split('*')[1]
if "*" in char_string:
m = int(char_string.split("*")[0])
str_val = char_string.split("*")[1]
else:
m = 1
str_val = char_string
if num_type == 'float':
if num_type == "float":
a = fracval(str_val)
else:
a = int(str_val)
@ -215,20 +230,20 @@ class AbinitIn(object):
for val in self._values:
if len(acell) >= 3:
if len(val) >= 6:
if val[:6] == 'angstr':
if val[:6] == "angstr":
for i in range(3):
acell[i] /= Bohr
break
acell += self._get_numerical_values(val)
self._tags['acell'] = acell[:3]
self._tags["acell"] = acell[:3]
def _set_natom(self):
self._tags['natom'] = int(self._values[0])
self._tags["natom"] = int(self._values[0])
def _set_ntypat(self):
self._tags['ntypat'] = int(self._values[0])
self._tags["ntypat"] = int(self._values[0])
def _set_rprim(self):
rprim = []
@ -237,7 +252,7 @@ class AbinitIn(object):
if len(rprim) >= 9:
break
self._tags['rprim'] = np.reshape(rprim[:9], (3, 3))
self._tags["rprim"] = np.reshape(rprim[:9], (3, 3))
def _set_scalecart(self):
scalecart = []
@ -246,43 +261,43 @@ class AbinitIn(object):
if len(scalecart) >= 3:
break
self._tags['scalecart'] = np.array(scalecart[:3])
self._tags["scalecart"] = np.array(scalecart[:3])
def _set_typat(self):
typat = []
natom = self._tags['natom']
natom = self._tags["natom"]
for val in self._values:
typat += self._get_numerical_values(val, num_type='int')
typat += self._get_numerical_values(val, num_type="int")
if len(typat) >= natom:
break
self._tags['typat'] = typat[:natom]
self._tags["typat"] = typat[:natom]
def _set_xangst(self):
self._set_x_tags('xangst')
self._set_x_tags("xangst")
def _set_xcart(self):
self._set_x_tags('xcart')
self._set_x_tags("xcart")
def _set_xred(self):
self._set_x_tags('xred')
self._set_x_tags("xred")
def _set_x_tags(self, tagname):
xtag = []
natom = self._tags['natom']
natom = self._tags["natom"]
for val in self._values:
xtag += self._get_numerical_values(val)
if len(xtag) >= natom * 3:
break
self._tags[tagname] = np.reshape(xtag[:natom * 3], (-1, 3))
self._tags[tagname] = np.reshape(xtag[: natom * 3], (-1, 3))
def _set_znucl(self):
znucl = []
ntypat = self._tags['ntypat']
ntypat = self._tags["ntypat"]
for val in self._values:
znucl += self._get_numerical_values(val, num_type='int')
znucl += self._get_numerical_values(val, num_type="int")
if len(znucl) >= ntypat:
break
self._tags['znucl'] = znucl[:ntypat]
self._tags["znucl"] = znucl[:ntypat]

View File

@ -1,3 +1,4 @@
"""FHIaims calculator interface."""
# FHIaims.py - IO routines for phonopy-FHI-aims
# methods compatible with the corresponding ones from ase.io.aims
# only minimal subset of functionality required within phonopy context is implemented
@ -39,22 +40,26 @@
# Modified 2020 by Florian Knoop
import sys
import numpy as np
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
# FK 2018/07/19
def lmap(func, lis):
"""Python2/3 compatibility:
"""Python2/3 compatibility.
replace map(int, list) with lmap(int, list) that always returns a list
instead of an iterator. Otherwise conflicts with np.array in python3. """
instead of an iterator. Otherwise conflicts with np.array in python3.
"""
return list(map(func, lis))
def read_aims(filename):
"""Method to read FHI-aims geometry files in phonopy context."""
"""Read FHI-aims geometry files in phonopy context."""
lines = open(filename, "r").readlines()
cell = []
@ -80,16 +85,18 @@ def read_aims(filename):
positions.append(pos)
symbols.append(sym)
magmoms.append(None)
# implicitly assuming that initial_moments line adhere to FHI-aims geometry.in specification,
# i.e. two subsequent initial_moments lines do not occur
# if they do, the value specified in the last line is taken here - without any warning
# implicitly assuming that initial_moments line adhere to FHI-aims geometry.in
# specification, i.e. two subsequent initial_moments lines do not occur
# if they do, the value specified in the last line is taken here - without
# any warning
elif fields[0] == "initial_moment":
magmoms[-1] = float(fields[1])
for (n, frac) in enumerate(is_frac):
if frac:
pos = [
sum([positions[n][l] * cell[l][i] for l in range(3)]) for i in range(3)
sum([positions[n][ll] * cell[ll][i] for ll in range(3)])
for i in range(3)
]
positions[n] = pos
if None in magmoms:
@ -101,8 +108,7 @@ def read_aims(filename):
def write_aims(filename, atoms):
"""Method to write FHI-aims geometry files in phonopy context."""
"""Write FHI-aims geometry files in phonopy context."""
lines = ""
lines += "# geometry.in for FHI-aims \n"
lines += "# | generated by phonopy.FHIaims.write_aims() \n"
@ -130,29 +136,33 @@ def write_aims(filename, atoms):
class Atoms_with_forces(Atoms):
""" Hack to phonopy.atoms to maintain ASE compatibility also for forces."""
"""Hack to phonopy.atoms to maintain ASE compatibility also for forces."""
def get_forces(self):
"""Return forces."""
return self.forces
def read_aims_output(filename):
""" Read FHI-aims output and
return geometry, energy and forces from last self-consistency iteration"""
"""Read aims output.
Read FHI-aims output and return geometry, energy and forces
from last self-consistency iteration.
"""
lines = open(filename, "r").readlines()
l = 0
ll = 0
N = 0
while l < len(lines):
line = lines[l]
while ll < len(lines):
line = lines[ll]
if "| Number of atoms" in line:
N = int(line.split()[5])
elif "| Unit cell:" in line:
cell = []
for i in range(3):
l += 1
vec = lmap(float, lines[l].split()[1:4])
ll += 1
vec = lmap(float, lines[ll].split()[1:4])
cell.append(vec)
elif ("Atomic structure:" in line) or ("Updated atomic structure:" in line):
if "Atomic structure:" in line:
@ -163,12 +173,12 @@ def read_aims_output(filename):
i_sym = 4
i_pos_min = 1
i_pos_max = 4
l += 1
ll += 1
symbols = []
positions = []
for n in range(N):
l += 1
fields = lines[l].split()
ll += 1
fields = lines[ll].split()
sym = fields[i_sym]
pos = lmap(float, fields[i_pos_min:i_pos_max])
symbols.append(sym)
@ -176,10 +186,10 @@ def read_aims_output(filename):
elif "Total atomic forces" in line:
forces = []
for i in range(N):
l += 1
force = lmap(float, lines[l].split()[-3:])
ll += 1
force = lmap(float, lines[ll].split()[-3:])
forces.append(force)
l += 1
ll += 1
atoms = Atoms_with_forces(cell=cell, symbols=symbols, positions=positions)
atoms.forces = forces
@ -187,31 +197,30 @@ def read_aims_output(filename):
return atoms
def write_supercells_with_displacements(supercell,
cells_with_disps,
ids,
pre_filename="geometry.in",
width=3):
"""Writes perfect supercell and supercells with displacements
def write_supercells_with_displacements(
supercell, cells_with_disps, ids, pre_filename="geometry.in", width=3
):
"""Write perfect supercell and supercells with displacements.
Args:
supercell: perfect supercell
cells_with_disps: supercells with displaced atoms
filename: root-filename
"""
"""
# original cell
write_aims(pre_filename + ".supercell", supercell)
# displaced cells
for i, cell in zip(ids, cells_with_disps):
filename = "{pre_filename}-{0:0{width}}".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_aims(filename, cell)
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
"""parse the forces from output files in `forces_filenames`"""
"""Parse the forces from output files in ``forces_filenames``."""
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):

View File

@ -1,3 +1,4 @@
"""ALM force constants interface."""
# Copyright (C) 2018 Atsushi Togo
# All rights reserved.
#
@ -33,42 +34,50 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
def get_fc2(supercell,
def get_fc2(
supercell,
primitive,
displacements,
forces,
atom_list=None,
options=None,
log_level=0):
log_level=0,
):
"""Calculate fc2 using ALM."""
p2s_map = primitive.p2s_map
is_compact_fc = (atom_list is not None and
(atom_list == p2s_map).all())
fc2 = run_alm(supercell,
is_compact_fc = atom_list is not None and (atom_list == p2s_map).all()
fc2 = run_alm(
supercell,
primitive,
displacements,
forces,
1,
is_compact_fc=is_compact_fc,
options=options,
log_level=log_level)[0]
log_level=log_level,
)[0]
if not is_compact_fc and atom_list is not None:
fc2 = np.array(fc2[atom_list], dtype='double', order='C')
fc2 = np.array(fc2[atom_list], dtype="double", order="C")
return fc2
def run_alm(supercell,
def run_alm(
supercell,
primitive,
displacements,
forces,
maxorder,
is_compact_fc=False,
options=None,
log_level=0):
log_level=0,
):
"""Calculate force constants of arbitrary-order using ALM."""
fcs = None # This is returned.
lattice = supercell.cell
@ -81,41 +90,43 @@ def run_alm(supercell,
alm_options = _update_options(options)
num_elems = len(np.unique(numbers))
if log_level:
print("---------------------------------"
print(
"---------------------------------"
" ALM start "
"--------------------------------")
print("ALM is a non-trivial force constants calculator. "
"Please cite the paper:")
print("T. Tadano and S. Tsuneyuki, "
"J. Phys. Soc. Jpn. 87, 041015 (2018).")
print("ALM is developed at https://github.com/ttadano/ALM by T. "
"Tadano.")
"--------------------------------"
)
print(
"ALM is a non-trivial force constants calculator. " "Please cite the paper:"
)
print("T. Tadano and S. Tsuneyuki, " "J. Phys. Soc. Jpn. 87, 041015 (2018).")
print("ALM is developed at https://github.com/ttadano/ALM by T. " "Tadano.")
if log_level == 1:
print("Increase log-level to watch detailed ALM log.")
if 'norder' in alm_options:
_maxorder = alm_options['norder']
elif 'maxorder' in alm_options:
_maxorder = alm_options['maxorder']
if "norder" in alm_options:
_maxorder = alm_options["norder"]
elif "maxorder" in alm_options:
_maxorder = alm_options["maxorder"]
else:
_maxorder = maxorder
shape = (_maxorder, num_elems, num_elems)
cutoff_radii = -np.ones(shape, dtype='double')
if alm_options['cutoff'] is not None:
if len(alm_options['cutoff']) == 1:
cutoff_radii[:] = alm_options['cutoff'][0]
elif np.prod(shape) == len(alm_options['cutoff']):
cutoff_radii[:] = np.reshape(alm_options['cutoff'], shape)
cutoff_radii = -np.ones(shape, dtype="double")
if alm_options["cutoff"] is not None:
if len(alm_options["cutoff"]) == 1:
cutoff_radii[:] = alm_options["cutoff"][0]
elif np.prod(shape) == len(alm_options["cutoff"]):
cutoff_radii[:] = np.reshape(alm_options["cutoff"], shape)
else:
raise RuntimeError("Cutoff is not properly set.")
_disps, _forces, df_msg = _slice_displacements_and_forces(
displacements,
forces,
alm_options['ndata'],
alm_options['nstart'],
alm_options['nend'])
alm_options["ndata"],
alm_options["nstart"],
alm_options["nend"],
)
if log_level > 1:
print("")
@ -138,29 +149,30 @@ def run_alm(supercell,
with ALM(lattice, positions, numbers) as alm:
if log_level > 0:
if alm_options['cutoff'] is not None:
if alm_options["cutoff"] is not None:
for i in range(_maxorder):
if _maxorder > 1:
print("fc%d" % (i + 2))
print(("cutoff" + " %6s" * num_elems)
% tuple(alm.kind_names.values()))
print(
("cutoff" + " %6s" * num_elems) % tuple(alm.kind_names.values())
)
for r, kn in zip(cutoff_radii[i], alm.kind_names.values()):
print((" %-3s" + " %6.2f" * num_elems)
% ((kn, ) + tuple(r)))
print((" %-3s" + " %6.2f" * num_elems) % ((kn,) + tuple(r)))
if df_msg is not None:
print(df_msg)
if log_level > 1:
print("")
sys.stdout.flush()
alm.output_filename_prefix = alm_options['output_filename_prefix']
alm.output_filename_prefix = alm_options["output_filename_prefix"]
alm.verbosity = log_level_alm
alm.define(
_maxorder,
cutoff_radii=cutoff_radii,
nbody=alm_options['nbody'],
symmetrization_basis=alm_options['symmetrization_basis'])
nbody=alm_options["nbody"],
symmetrization_basis=alm_options["symmetrization_basis"],
)
alm.displacements = _disps
alm.forces = _forces
@ -171,32 +183,30 @@ def run_alm(supercell,
optcontrol[key] = alm_options[key]
if optcontrol:
alm.optimizer_control = optcontrol
if ('cross_validation' in optcontrol and
optcontrol['cross_validation'] > 0):
alm.optimize(solver=alm_options['solver'])
optcontrol['cross_validation'] = 0
optcontrol['l1_alpha'] = alm.cv_l1_alpha
if "cross_validation" in optcontrol and optcontrol["cross_validation"] > 0:
alm.optimize(solver=alm_options["solver"])
optcontrol["cross_validation"] = 0
optcontrol["l1_alpha"] = alm.cv_l1_alpha
alm.optimizer_control = optcontrol
alm.optimize(solver=alm_options['solver'])
alm.optimize(solver=alm_options["solver"])
fcs = _extract_fc_from_alm(alm,
natom,
maxorder,
is_compact_fc,
p2s_map=p2s_map,
p2p_map=p2p_map)
fcs = _extract_fc_from_alm(
alm, natom, maxorder, is_compact_fc, p2s_map=p2s_map, p2p_map=p2p_map
)
if log_level:
print("----------------------------------"
print(
"----------------------------------"
" ALM end "
"---------------------------------")
"---------------------------------"
)
return fcs
def _update_options(fc_calculator_options):
"""Set ALM options with appropriate data types
"""Set ALM options with appropriate data types.
fc_calculator_options : str
This string should be written such as follows:
@ -208,43 +218,46 @@ def _update_options(fc_calculator_options):
is cast to have its appropriate data type for ALM in this method.
"""
try:
from alm import optimizer_control_data_types
except ImportError:
raise ImportError("ALM python module was not found.")
# Default settings.
alm_options = {'solver': 'dense',
'ndata': None,
'nstart': None,
'nend': None,
'nbody': None,
'cutoff': None,
'symmetrization_basis': 'Lattice',
'output_filename_prefix': None}
alm_options = {
"solver": "dense",
"ndata": None,
"nstart": None,
"nend": None,
"nbody": None,
"cutoff": None,
"symmetrization_basis": "Lattice",
"output_filename_prefix": None,
}
if fc_calculator_options is not None:
alm_option_types = {'cutoff': np.double,
'maxorder': int,
'norder': int,
'ndata': int,
'nstart': int,
'nend': int,
'nbody': np.intc,
'output_filename_prefix': str,
'solver': str,
'symmetrization_basis': str}
alm_option_types = {
"cutoff": np.double,
"maxorder": int,
"norder": int,
"ndata": int,
"nstart": int,
"nend": int,
"nbody": np.intc,
"output_filename_prefix": str,
"solver": str,
"symmetrization_basis": str,
}
alm_option_types.update(optimizer_control_data_types)
for option_str in fc_calculator_options.split(","):
key, val = [x.strip() for x in option_str.split('=')[:2]]
key, val = [x.strip() for x in option_str.split("=")[:2]]
if key.lower() in alm_option_types:
if alm_option_types[key.lower()] is np.double:
option_value = np.array(
[float(x) for x in val.split()], dtype='double')
[float(x) for x in val.split()], dtype="double"
)
elif alm_option_types[key.lower()] is np.intc:
option_value = np.array(
[int(x) for x in val.split()], dtype='intc')
option_value = np.array([int(x) for x in val.split()], dtype="intc")
else:
option_value = alm_option_types[key.lower()](val)
alm_options[key] = option_value
@ -258,38 +271,40 @@ def _slice_displacements_and_forces(d, f, ndata, nstart, nend):
_f = f[:ndata]
msg = "Number of displacement supercells: %d" % ndata
elif nstart is not None and nend is not None:
_d = d[nstart - 1:nend]
_f = f[nstart - 1:nend]
_d = d[nstart - 1 : nend]
_f = f[nstart - 1 : nend]
msg = "Supercell index range: %d - %d" % (nstart, nend)
else:
return d, f, None
return (np.array(_d, dtype='double', order='C'),
np.array(_f, dtype='double', order='C'), msg)
return (
np.array(_d, dtype="double", order="C"),
np.array(_f, dtype="double", order="C"),
msg,
)
def _extract_fc_from_alm(alm,
natom,
maxorder,
is_compact_fc,
p2s_map=None,
p2p_map=None):
def _extract_fc_from_alm(
alm, natom, maxorder, is_compact_fc, p2s_map=None, p2p_map=None
):
# Harmonic: order=1, 3rd: order=2, ...
fcs = []
for order in range(1, maxorder + 1):
fc = None
p2s_map_alm = alm.getmap_primitive_to_supercell()[0]
if (is_compact_fc and
len(p2s_map_alm) == len(p2s_map) and
(p2s_map_alm == p2s_map).all()):
fc_shape = (
(len(p2s_map), ) + (natom, ) * order + (3, ) * (order + 1))
fc = np.zeros(fc_shape, dtype='double', order='C')
if (
is_compact_fc
and len(p2s_map_alm) == len(p2s_map)
and (p2s_map_alm == p2s_map).all()
):
fc_shape = (len(p2s_map),) + (natom,) * order + (3,) * (order + 1)
fc = np.zeros(fc_shape, dtype="double", order="C")
for fc_elem, indices in zip(
*alm.get_fc(order, mode='origin', permutation=1)):
*alm.get_fc(order, mode="origin", permutation=1)
):
v = indices // 3
c = indices % 3
selection = (p2p_map[v[0]], ) + tuple(v[1:]) + tuple(c)
selection = (p2p_map[v[0]],) + tuple(v[1:]) + tuple(c)
fc[selection] = fc_elem
if fc is None:
@ -297,16 +312,14 @@ def _extract_fc_from_alm(alm,
atom_list = p2s_map
else:
atom_list = np.arange(natom, dtype=int)
fc_shape = (
(len(atom_list), ) + (natom, ) * order + (3, ) * (order + 1))
fc = np.zeros(fc_shape, dtype='double', order='C')
for fc_elem, indices in zip(
*alm.get_fc(order, mode='all', permutation=1)):
fc_shape = (len(atom_list),) + (natom,) * order + (3,) * (order + 1)
fc = np.zeros(fc_shape, dtype="double", order="C")
for fc_elem, indices in zip(*alm.get_fc(order, mode="all", permutation=1)):
v = indices // 3
idx = np.where(atom_list == v[0])[0]
if len(idx) > 0:
c = indices % 3
selection = (idx[0], ) + tuple(v[1:]) + tuple(c)
selection = (idx[0],) + tuple(v[1:]) + tuple(c)
fc[selection] = fc_elem
fcs.append(fc)

View File

@ -1,3 +1,4 @@
"""Routines to handle various calculator interfaces."""
# Copyright (C) 2014 Atsushi Togo
# All rights reserved.
#
@ -33,60 +34,71 @@
# POSSIBILITY OF SUCH DAMAGE.
import os
import yaml
from argparse import ArgumentParser
import numpy as np
import yaml
from phonopy.interface.phonopy_yaml import PhonopyYaml
from phonopy.structure.dataset import get_displacements_and_forces
from phonopy.structure.cells import determinant
from phonopy.interface.vasp import sort_positions_by_symbols
from phonopy.structure.cells import determinant
from phonopy.structure.dataset import get_displacements_and_forces
from phonopy.units import (
AbinitToTHz,
Bohr,
CastepToTHz,
CP2KToTHz,
CrystalToTHz,
DftbpToTHz,
ElkToTHz,
FleurToTHz,
Hartree,
PwscfToTHz,
Rydberg,
SiestaToTHz,
TurbomoleToTHz,
VaspToTHz,
Wien2kToTHz,
)
calculator_info = {
'abinit': {'option': {'name': "--abinit",
'help': "Invoke Abinit mode"}},
'aims': {'option': {'name': "--aims",
'help': "Invoke FHI-aims mode"}},
'cp2k': {'option': {'name': "--cp2k",
'help': "Invoke CP2K mode"}},
'crystal': {'option': {'name': "--crystal",
'help': "Invoke CRYSTAL mode"}},
'dftbp': {'option': {'name': "--dftb+",
'help': "Invoke dftb+ mode"}},
'elk': {'option': {'name': "--elk",
'help': "Invoke elk mode"}},
'qe': {'option': {'name': "--qe",
'help': "Invoke Quantum espresso (QE) mode"}},
'siesta': {'option': {'name': "--siesta",
'help': "Invoke Siesta mode"}},
'turbomole': {'option': {'name': "--turbomole",
'help': "Invoke TURBOMOLE mode"}},
'vasp': {'option': {'name': "--vasp",
'help': "Invoke Vasp mode"}},
'wien2k': {'option': {'name': "--wien2k",
'help': "Invoke Wien2k mode"}},
'castep': {'option': {'name': "--castep",
'help': "Invoke CASTEP mode"}},
'fleur': {'option': {'name': "--fleur",
'help': "Invoke Fleur mode"}},
"abinit": {"option": {"name": "--abinit", "help": "Invoke Abinit mode"}},
"aims": {"option": {"name": "--aims", "help": "Invoke FHI-aims mode"}},
"cp2k": {"option": {"name": "--cp2k", "help": "Invoke CP2K mode"}},
"crystal": {"option": {"name": "--crystal", "help": "Invoke CRYSTAL mode"}},
"dftbp": {"option": {"name": "--dftb+", "help": "Invoke dftb+ mode"}},
"elk": {"option": {"name": "--elk", "help": "Invoke elk mode"}},
"qe": {"option": {"name": "--qe", "help": "Invoke Quantum espresso (QE) mode"}},
"siesta": {"option": {"name": "--siesta", "help": "Invoke Siesta mode"}},
"turbomole": {"option": {"name": "--turbomole", "help": "Invoke TURBOMOLE mode"}},
"vasp": {"option": {"name": "--vasp", "help": "Invoke Vasp mode"}},
"wien2k": {"option": {"name": "--wien2k", "help": "Invoke Wien2k mode"}},
"castep": {"option": {"name": "--castep", "help": "Invoke CASTEP mode"}},
"fleur": {"option": {"name": "--fleur", "help": "Invoke Fleur mode"}},
}
def add_arguments_of_calculators(parser, calculator_info):
def add_arguments_of_calculators(parser: ArgumentParser, calculator_info):
"""Add options of calculators to ArgumentParser class instance."""
for calculator in calculator_info:
option = calculator_info[calculator]['option']
option = calculator_info[calculator]["option"]
parser.add_argument(
option['name'], dest="%s_mode" % calculator, action="store_true",
default=False, help=option['help'])
option["name"],
dest="%s_mode" % calculator,
action="store_true",
default=False,
help=option["help"],
)
def get_interface_mode(args_dict):
"""Return calculator name
"""Return calculator name.
The calculator name is obtained from command option arguments where
argparse is used. The argument attribute name has to be
"{calculator}_mode". Then this method returns "{calculator}".
"""
for calculator in calculator_info:
mode = "%s_mode" % calculator
if mode in args_dict and args_dict[mode]:
@ -94,25 +106,20 @@ def get_interface_mode(args_dict):
return None
def convert_crystal_structure(filename_in,
interface_in,
filename_out,
interface_out):
cell, optional_structure_info = read_crystal_structure(
filename=filename_in,
interface_mode=interface_in)
def convert_crystal_structure(filename_in, interface_in, filename_out, interface_out):
"""Convert crystal structures between different calculator interfaces."""
cell, _ = read_crystal_structure(filename=filename_in, interface_mode=interface_in)
units_in = get_default_physical_units(interface_in)
units_out = get_default_physical_units(interface_out)
factor = units_in['distance_to_A'] / units_out['distance_to_A']
factor = units_in["distance_to_A"] / units_out["distance_to_A"]
cell.cell = cell.cell * factor
write_crystal_structure(filename_out, cell, interface_mode=interface_out)
def write_crystal_structure(filename,
cell,
interface_mode=None,
optional_structure_info=None):
"""Utility method to write out a crystal structure
def write_crystal_structure(
filename, cell, interface_mode=None, optional_structure_info=None
):
"""Write crystal structure to file in each calculator format.
filename : str, optional
File name to be used to write out the crystal structure.
@ -126,65 +133,79 @@ def write_crystal_structure(filename,
See the docstring. Default is None.
"""
if interface_mode is None or interface_mode == 'vasp':
if interface_mode is None or interface_mode == "vasp":
import phonopy.interface.vasp as vasp
vasp.write_vasp(filename, cell)
elif interface_mode == 'abinit':
elif interface_mode == "abinit":
import phonopy.interface.abinit as abinit
abinit.write_abinit(filename, cell)
elif interface_mode == 'qe':
elif interface_mode == "qe":
import phonopy.interface.qe as qe
pp_filenames = optional_structure_info[1]
qe.write_pwscf(filename, cell, pp_filenames)
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
import phonopy.interface.wien2k as wien2k
_, npts, r0s, rmts = optional_structure_info
wien2k.write_wein2k(filename, cell, npts, r0s, rmts)
elif interface_mode == 'elk':
elif interface_mode == "elk":
import phonopy.interface.elk as elk
sp_filenames = optional_structure_info[1]
elk.write_elk(filename, cell, sp_filenames)
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
import phonopy.interface.siesta as siesta
atypes = optional_structure_info[1]
siesta.write_siesta(filename, cell, atypes)
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
import phonopy.interface.cp2k as cp2k
_, tree = optional_structure_info
cp2k.write_cp2k_by_filename(filename, cell, tree)
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
import phonopy.interface.crystal as crystal
conv_numbers = optional_structure_info[1]
crystal.write_crystal(filename, cell, conv_numbers)
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
import phonopy.interface.dftbp as dftbp
dftbp.write_dftbp(filename, cell)
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
import phonopy.interface.turbomole as turbomole
turbomole.write_turbomole(filename, cell)
elif interface_mode == 'aims':
elif interface_mode == "aims":
import phonopy.interface.aims as aims
aims.write_aims(filename, cell)
elif interface_mode == 'castep':
elif interface_mode == "castep":
import phonopy.interface.castep as castep
castep.write_castep(filename, cell)
elif interface_mode == 'fleur':
elif interface_mode == "fleur":
import phonopy.interface.fleur as fleur
speci, restlines = optional_structure_info
fleur.write_fleur(filename, cell, speci, N, restlines)
fleur.write_fleur(filename, cell, speci, 1, restlines)
else:
raise RuntimeError("No calculator interface was found.")
def write_supercells_with_displacements(interface_mode,
def write_supercells_with_displacements(
interface_mode,
supercell,
cells_with_disps,
optional_structure_info,
displacement_ids=None,
zfill_width=3,
additional_info=None):
"""Utility method to write out supercell structures with displacements
additional_info=None,
):
"""Write supercell with displacements to files in each calculator format.
interface_mode : str
Calculator interface such as 'vasp', 'qe', ...
@ -207,80 +228,91 @@ def write_supercells_with_displacements(interface_mode,
Default is None.
"""
if displacement_ids is None:
ids = np.arange(len(cells_with_disps), dtype=int) + 1
else:
ids = displacement_ids
args = (supercell, cells_with_disps, ids)
kwargs = {'width': zfill_width}
if 'pre_filename' in additional_info:
kwargs['pre_filename'] = additional_info['pre_filename']
kwargs = {"width": zfill_width}
if "pre_filename" in additional_info:
kwargs["pre_filename"] = additional_info["pre_filename"]
if interface_mode is None or interface_mode == 'vasp':
if interface_mode is None or interface_mode == "vasp":
import phonopy.interface.vasp as vasp
vasp.write_supercells_with_displacements(*args, **kwargs)
write_magnetic_moments(supercell, sort_by_elements=True)
elif interface_mode == 'abinit':
elif interface_mode == "abinit":
import phonopy.interface.abinit as abinit
abinit.write_supercells_with_displacements(*args, **kwargs)
elif interface_mode == 'qe':
elif interface_mode == "qe":
import phonopy.interface.qe as qe
pp_filenames = optional_structure_info[1]
qe_args = args + (pp_filenames, )
qe_args = args + (pp_filenames,)
qe.write_supercells_with_displacements(*qe_args, **kwargs)
write_magnetic_moments(supercell, sort_by_elements=False)
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
import phonopy.interface.wien2k as wien2k
unitcell_filename, npts, r0s, rmts = optional_structure_info
N = abs(determinant(additional_info['supercell_matrix']))
N = abs(determinant(additional_info["supercell_matrix"]))
w2k_args = args + (npts, r0s, rmts, N)
if 'pre_filename' not in kwargs:
kwargs['pre_filename'] = unitcell_filename
if "pre_filename" not in kwargs:
kwargs["pre_filename"] = unitcell_filename
wien2k.write_supercells_with_displacements(*w2k_args, **kwargs)
elif interface_mode == 'elk':
elif interface_mode == "elk":
import phonopy.interface.elk as elk
sp_filenames = optional_structure_info[1]
elk_args = args + (sp_filenames, )
elk_args = args + (sp_filenames,)
elk.write_supercells_with_displacements(*elk_args, **kwargs)
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
import phonopy.interface.siesta as siesta
atypes = optional_structure_info[1]
sst_args = args + (atypes, )
sst_args = args + (atypes,)
siesta.write_supercells_with_displacements(*sst_args, **kwargs)
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
import phonopy.interface.cp2k as cp2k
cp2k_args = args + (optional_structure_info, )
cp2k_args = args + (optional_structure_info,)
cp2k.write_supercells_with_displacements(*cp2k_args, **kwargs)
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
import phonopy.interface.crystal as crystal
if additional_info is None:
kwargs['template_file'] = "TEMPLATE"
kwargs["template_file"] = "TEMPLATE"
else:
kwargs['template_file'] = additional_info.get('template_file',
"TEMPLATE")
kwargs["template_file"] = additional_info.get("template_file", "TEMPLATE")
conv_numbers = optional_structure_info[1]
N = abs(determinant(additional_info['supercell_matrix']))
N = abs(determinant(additional_info["supercell_matrix"]))
cst_args = args + (conv_numbers, N)
crystal.write_supercells_with_displacements(*cst_args, **kwargs)
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
import phonopy.interface.dftbp as dftbp
dftbp.write_supercells_with_displacements(*args, **kwargs)
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
import phonopy.interface.turbomole as turbomole
turbomole.write_supercells_with_displacements(*args, **kwargs)
elif interface_mode == 'aims':
elif interface_mode == "aims":
import phonopy.interface.aims as aims
aims.write_supercells_with_displacements(*args, **kwargs)
elif interface_mode == 'castep':
elif interface_mode == "castep":
import phonopy.interface.castep as castep
castep.write_supercells_with_displacements(*args, **kwargs)
elif interface_mode == 'fleur':
elif interface_mode == "fleur":
import phonopy.interface.fleur as fleur
speci = optional_structure_info[1]
restlines = optional_structure_info[2]
N = abs(determinant(additional_info['supercell_matrix']))
N = abs(determinant(additional_info["supercell_matrix"]))
fleur_args = args + (speci, N, restlines)
fleur.write_supercells_with_displacements(*fleur_args, **kwargs)
else:
@ -288,15 +320,17 @@ def write_supercells_with_displacements(interface_mode,
def write_magnetic_moments(cell, sort_by_elements=False):
"""Write MAGMOM."""
magmoms = cell.magnetic_moments
if magmoms is not None:
if sort_by_elements:
(_, _, _, sort_list) = sort_positions_by_symbols(
cell.symbols, cell.scaled_positions)
cell.symbols, cell.scaled_positions
)
else:
sort_list = range(cell.get_number_of_atoms())
with open("MAGMOM", 'w') as w:
with open("MAGMOM", "w") as w:
w.write(" MAGMOM = ")
for i in sort_list:
w.write("%f " % magmoms[i])
@ -304,11 +338,10 @@ def write_magnetic_moments(cell, sort_by_elements=False):
w.close()
def read_crystal_structure(filename=None,
interface_mode=None,
chemical_symbols=None,
phonopy_yaml_cls=None):
"""Returns crystal structure information
def read_crystal_structure(
filename=None, interface_mode=None, chemical_symbols=None, phonopy_yaml_cls=None
):
"""Return crystal structure from file in each calculator format.
Parameters
----------
@ -336,8 +369,7 @@ def read_crystal_structure(filename=None,
and the rest is dependent on calculator interface.
"""
if interface_mode == 'phonopy_yaml':
if interface_mode == "phonopy_yaml":
if phonopy_yaml_cls is None:
return _read_phonopy_yaml(filename, PhonopyYaml)
else:
@ -352,125 +384,148 @@ def read_crystal_structure(filename=None,
if not os.path.isfile(cell_filename):
return None, (cell_filename,)
if interface_mode is None or interface_mode == 'vasp':
if interface_mode is None or interface_mode == "vasp":
from phonopy.interface.vasp import read_vasp
if chemical_symbols is None:
unitcell = read_vasp(cell_filename)
else:
unitcell = read_vasp(cell_filename, symbols=chemical_symbols)
return unitcell, (cell_filename,)
elif interface_mode == 'abinit':
elif interface_mode == "abinit":
from phonopy.interface.abinit import read_abinit
unitcell = read_abinit(cell_filename)
return unitcell, (cell_filename,)
elif interface_mode == 'qe':
elif interface_mode == "qe":
from phonopy.interface.qe import read_pwscf
unitcell, pp_filenames = read_pwscf(cell_filename)
return unitcell, (cell_filename, pp_filenames)
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
from phonopy.interface.wien2k import parse_wien2k_struct
unitcell, npts, r0s, rmts = parse_wien2k_struct(cell_filename)
return unitcell, (cell_filename, npts, r0s, rmts)
elif interface_mode == 'elk':
elif interface_mode == "elk":
from phonopy.interface.elk import read_elk
unitcell, sp_filenames = read_elk(cell_filename)
return unitcell, (cell_filename, sp_filenames)
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
from phonopy.interface.siesta import read_siesta
unitcell, atypes = read_siesta(cell_filename)
return unitcell, (cell_filename, atypes)
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
from phonopy.interface.cp2k import read_cp2k
unitcell, config_tree = read_cp2k(cell_filename)
return unitcell, (cell_filename, config_tree)
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
from phonopy.interface.crystal import read_crystal
unitcell, conv_numbers = read_crystal(cell_filename)
return unitcell, (cell_filename, conv_numbers)
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
from phonopy.interface.dftbp import read_dftbp
unitcell = read_dftbp(cell_filename)
return unitcell, (cell_filename,)
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
from phonopy.interface.turbomole import read_turbomole
unitcell = read_turbomole(cell_filename)
return unitcell, (cell_filename,)
elif interface_mode == 'aims':
elif interface_mode == "aims":
from phonopy.interface.aims import read_aims
unitcell = read_aims(cell_filename)
return unitcell, (cell_filename,)
elif interface_mode == 'castep':
elif interface_mode == "castep":
from phonopy.interface.castep import read_castep
unitcell = read_castep(cell_filename)
return unitcell, (cell_filename,)
elif interface_mode == 'fleur':
elif interface_mode == "fleur":
from phonopy.interface.fleur import read_fleur
unitcell, speci, restlines = read_fleur(cell_filename)
return unitcell, (cell_filename,speci,restlines)
return unitcell, (cell_filename, speci, restlines)
else:
raise RuntimeError("No calculator interface was found.")
def get_default_cell_filename(interface_mode):
if interface_mode is None or interface_mode == 'vasp':
"""Return default filename of unit cell structure of each calculator."""
if interface_mode is None or interface_mode == "vasp":
return "POSCAR"
elif interface_mode in ('abinit', 'qe'):
elif interface_mode in ("abinit", "qe"):
return "unitcell.in"
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
return "case.struct"
elif interface_mode == 'elk':
elif interface_mode == "elk":
return "elk.in"
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
return "input.fdf"
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
return "unitcell.inp"
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
return "crystal.o"
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
return "geo.gen"
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
return "control"
elif interface_mode == 'aims':
elif interface_mode == "aims":
return "geometry.in"
elif interface_mode in ('castep'):
elif interface_mode in ("castep"):
return "unitcell.cell"
elif interface_mode == 'fleur':
elif interface_mode == "fleur":
return "fleur.in"
else:
return None
def get_default_supercell_filename(interface_mode):
if interface_mode == 'phonopy_yaml':
"""Return default filename of supercell structure of each calculator."""
if interface_mode == "phonopy_yaml":
return "phonopy_disp.yaml"
elif interface_mode is None or interface_mode == 'vasp':
elif interface_mode is None or interface_mode == "vasp":
return "SPOSCAR"
elif interface_mode in ('abinit', 'elk', 'qe', 'fleur'):
elif interface_mode in ("abinit", "elk", "qe", "fleur"):
return "supercell.in"
elif interface_mode == 'wien2k':
elif interface_mode == "wien2k":
return "case.structS"
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
return "supercell.fdf"
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
# CP2K interface generates filenames based on original project name
return None
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
return None # supercell.ext can not be parsed by crystal interface.
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
return "geo.genS"
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
return None # TURBOMOLE interface generates directories with inputs
elif interface_mode == 'aims':
elif interface_mode == "aims":
return "geometry.in.supercell"
elif interface_mode in ('castep'):
elif interface_mode in ("castep"):
return "supercell.cell"
else:
return None
def get_default_displacement_distance(interface_mode):
if interface_mode in ('wien2k', 'abinit', 'elk', 'qe', 'siesta',
'turbomole', 'fleur'):
"""Return default displacement distance of each calculator."""
if interface_mode in (
"wien2k",
"abinit",
"elk",
"qe",
"siesta",
"turbomole",
"fleur",
):
displacement_distance = 0.02
else: # default or vasp, crystal, cp2k
displacement_distance = 0.01
@ -478,7 +533,7 @@ def get_default_displacement_distance(interface_mode):
def get_default_physical_units(interface_mode=None):
"""Return physical units used for calculators
"""Return physical units of eachi calculator.
Physical units: energy, distance, atomic mass, force, force constants
vasp : eV, angstrom, AMU, eV/angstrom, eV/angstrom^2
@ -499,147 +554,150 @@ def get_default_physical_units(interface_mode=None):
the 'get_force_constant_conversion_factor' method.
"""
units = {
"factor": None,
"nac_factor": None,
"distance_to_A": None,
"force_to_eVperA": None,
"force_constants_unit": None,
"length_unit": None,
}
from phonopy.units import (Wien2kToTHz, AbinitToTHz, PwscfToTHz, ElkToTHz,
SiestaToTHz, VaspToTHz, CP2KToTHz, CrystalToTHz,
DftbpToTHz, TurbomoleToTHz, CastepToTHz, FleurToTHz,
Hartree, Bohr, Rydberg)
units = {'factor': None,
'nac_factor': None,
'distance_to_A': None,
'force_to_eVperA': None,
'force_constants_unit': None,
'length_unit': None}
if interface_mode is None or interface_mode in ('vasp', 'aims'):
units['factor'] = VaspToTHz
units['nac_factor'] = Hartree * Bohr
units['distance_to_A'] = 1.0
units['force_constants_unit'] = 'eV/angstrom^2'
units['length_unit'] = 'angstrom'
elif interface_mode == 'abinit':
units['factor'] = AbinitToTHz
units['nac_factor'] = Hartree / Bohr
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'eV/angstrom.au'
units['length_unit'] = 'au'
elif interface_mode == 'qe':
units['factor'] = PwscfToTHz
units['nac_factor'] = 2.0
units['distance_to_A'] = Bohr
units['force_to_eVperA'] = Rydberg / Bohr
units['force_constants_unit'] = 'Ry/au^2'
units['length_unit'] = 'au'
elif interface_mode == 'wien2k':
units['factor'] = Wien2kToTHz
units['nac_factor'] = 2000.0
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'mRy/au^2'
units['length_unit'] = 'au'
elif interface_mode == 'elk':
units['factor'] = ElkToTHz
units['nac_factor'] = 1.0
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'hartree/au^2'
units['length_unit'] = 'au'
elif interface_mode == 'siesta':
units['factor'] = SiestaToTHz
units['nac_factor'] = Hartree / Bohr
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'eV/angstrom.au'
units['length_unit'] = 'au'
elif interface_mode == 'cp2k':
units['factor'] = CP2KToTHz
units['nac_factor'] = None # not implemented
units['distance_to_A'] = 1.0
units['force_constants_unit'] = 'hartree/angstrom.au'
units['length_unit'] = 'angstrom'
elif interface_mode == 'crystal':
units['factor'] = CrystalToTHz
units['nac_factor'] = Hartree * Bohr
units['distance_to_A'] = 1.0
units['force_constants_unit'] = 'eV/angstrom^2'
units['length_unit'] = 'angstrom'
elif interface_mode == 'dftbp':
units['factor'] = DftbpToTHz
units['nac_factor'] = Hartree * Bohr
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'hartree/au^2'
units['length_unit'] = 'au'
elif interface_mode == 'turbomole':
units['factor'] = TurbomoleToTHz
units['nac_factor'] = 1.0
units['distance_to_A'] = Bohr
units['force_to_eVperA'] = Hartree / Bohr
units['force_constants_unit'] = 'hartree/au^2'
units['length_unit'] = 'au'
elif interface_mode == 'castep':
units['factor'] = CastepToTHz
units['nac_factor'] = Hartree * Bohr
units['distance_to_A'] = 1.0
units['force_constants_unit'] = 'eV/angstrom^2'
units['length_unit'] = 'angstrom'
elif interface_mode == 'fleur':
units['factor'] = FleurToTHz
units['nac_factor'] = 1.0
units['distance_to_A'] = Bohr
units['force_constants_unit'] = 'hartree/au^2'
units['length_unit'] = 'au'
if interface_mode is None or interface_mode in ("vasp", "aims"):
units["factor"] = VaspToTHz
units["nac_factor"] = Hartree * Bohr
units["distance_to_A"] = 1.0
units["force_constants_unit"] = "eV/angstrom^2"
units["length_unit"] = "angstrom"
elif interface_mode == "abinit":
units["factor"] = AbinitToTHz
units["nac_factor"] = Hartree / Bohr
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "eV/angstrom.au"
units["length_unit"] = "au"
elif interface_mode == "qe":
units["factor"] = PwscfToTHz
units["nac_factor"] = 2.0
units["distance_to_A"] = Bohr
units["force_to_eVperA"] = Rydberg / Bohr
units["force_constants_unit"] = "Ry/au^2"
units["length_unit"] = "au"
elif interface_mode == "wien2k":
units["factor"] = Wien2kToTHz
units["nac_factor"] = 2000.0
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "mRy/au^2"
units["length_unit"] = "au"
elif interface_mode == "elk":
units["factor"] = ElkToTHz
units["nac_factor"] = 1.0
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "hartree/au^2"
units["length_unit"] = "au"
elif interface_mode == "siesta":
units["factor"] = SiestaToTHz
units["nac_factor"] = Hartree / Bohr
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "eV/angstrom.au"
units["length_unit"] = "au"
elif interface_mode == "cp2k":
units["factor"] = CP2KToTHz
units["nac_factor"] = None # not implemented
units["distance_to_A"] = 1.0
units["force_constants_unit"] = "hartree/angstrom.au"
units["length_unit"] = "angstrom"
elif interface_mode == "crystal":
units["factor"] = CrystalToTHz
units["nac_factor"] = Hartree * Bohr
units["distance_to_A"] = 1.0
units["force_constants_unit"] = "eV/angstrom^2"
units["length_unit"] = "angstrom"
elif interface_mode == "dftbp":
units["factor"] = DftbpToTHz
units["nac_factor"] = Hartree * Bohr
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "hartree/au^2"
units["length_unit"] = "au"
elif interface_mode == "turbomole":
units["factor"] = TurbomoleToTHz
units["nac_factor"] = 1.0
units["distance_to_A"] = Bohr
units["force_to_eVperA"] = Hartree / Bohr
units["force_constants_unit"] = "hartree/au^2"
units["length_unit"] = "au"
elif interface_mode == "castep":
units["factor"] = CastepToTHz
units["nac_factor"] = Hartree * Bohr
units["distance_to_A"] = 1.0
units["force_constants_unit"] = "eV/angstrom^2"
units["length_unit"] = "angstrom"
elif interface_mode == "fleur":
units["factor"] = FleurToTHz
units["nac_factor"] = 1.0
units["distance_to_A"] = Bohr
units["force_constants_unit"] = "hartree/au^2"
units["length_unit"] = "au"
return units
def get_force_sets(interface_mode,
def get_force_sets(
interface_mode,
num_atoms,
num_displacements,
force_filenames,
disp_filename=None,
verbose=True):
if interface_mode is None or interface_mode == 'vasp':
verbose=True,
):
"""Read calculator output files and parse force sets.
Note
----
Wien2k output is treated by ``get_force_sets_wien2k``.
"""
if interface_mode is None or interface_mode == "vasp":
from phonopy.interface.vasp import parse_set_of_forces
elif interface_mode == 'abinit':
elif interface_mode == "abinit":
from phonopy.interface.abinit import parse_set_of_forces
elif interface_mode == 'qe':
elif interface_mode == "qe":
from phonopy.interface.qe import parse_set_of_forces
elif interface_mode == 'elk':
elif interface_mode == "elk":
from phonopy.interface.elk import parse_set_of_forces
elif interface_mode == 'siesta':
elif interface_mode == "siesta":
from phonopy.interface.siesta import parse_set_of_forces
elif interface_mode == 'cp2k':
elif interface_mode == "cp2k":
from phonopy.interface.cp2k import parse_set_of_forces
elif interface_mode == 'crystal':
elif interface_mode == "crystal":
from phonopy.interface.crystal import parse_set_of_forces
elif interface_mode == 'dftbp':
elif interface_mode == "dftbp":
from phonopy.interface.dftbp import parse_set_of_forces
elif interface_mode == 'turbomole':
elif interface_mode == "turbomole":
from phonopy.interface.turbomole import parse_set_of_forces
elif interface_mode == 'aims':
elif interface_mode == "aims":
from phonopy.interface.aims import parse_set_of_forces
elif interface_mode == 'castep':
elif interface_mode == "castep":
from phonopy.interface.castep import parse_set_of_forces
elif interface_mode == 'fleur':
elif interface_mode == "fleur":
from phonopy.interface.fleur import parse_set_of_forces
else:
return []
force_sets = parse_set_of_forces(num_atoms,
force_filenames,
verbose=verbose)
force_sets = parse_set_of_forces(num_atoms, force_filenames, verbose=verbose)
return force_sets
def get_force_sets_wien2k(num_displacements,
def get_force_sets_wien2k(
force_filenames,
disp_filename,
supercell,
disp_dataset,
wien2k_P1_mode=False,
symmetry_tolerance=None,
verbose=False):
verbose=False,
):
"""Read Wien2k output files and parse force sets."""
from phonopy.interface.wien2k import parse_set_of_forces
disps, _ = get_displacements_and_forces(disp_dataset)
force_sets = parse_set_of_forces(
disps,
@ -647,22 +705,24 @@ def get_force_sets_wien2k(num_displacements,
supercell,
wien2k_P1_mode=wien2k_P1_mode,
symmetry_tolerance=symmetry_tolerance,
verbose=verbose)
verbose=verbose,
)
return force_sets
def get_force_constant_conversion_factor(unit, interface_mode):
from phonopy.units import Bohr, Rydberg, Hartree
_unit = unit.replace('Angstrom', 'angstrom') # backward compatibility
"""Return unit conversion factor of force constants."""
_unit = unit.replace("Angstrom", "angstrom") # for backward compatibility
interface_default_units = get_default_physical_units(interface_mode)
default_unit = interface_default_units['force_constants_unit']
factor_to_eVperA2 = {'eV/angstrom^2': 1,
'eV/angstrom.au': 1 / Bohr,
'Ry/au^2': Rydberg / Bohr ** 2,
'mRy/au^2': Rydberg / Bohr ** 2 / 1000,
'hartree/au^2': Hartree / Bohr ** 2,
'hartree/angstrom.au': Hartree / Bohr}
default_unit = interface_default_units["force_constants_unit"]
factor_to_eVperA2 = {
"eV/angstrom^2": 1,
"eV/angstrom.au": 1 / Bohr,
"Ry/au^2": Rydberg / Bohr ** 2,
"mRy/au^2": Rydberg / Bohr ** 2 / 1000,
"hartree/au^2": Hartree / Bohr ** 2,
"hartree/angstrom.au": Hartree / Bohr,
}
if default_unit not in factor_to_eVperA2:
msg = "Force constant conversion for %s unit is not implemented."
raise NotImplementedError(msg)
@ -692,7 +752,7 @@ def _read_phonopy_yaml(filename, phonopy_yaml_cls):
def _get_cell_filename(filename, phonopy_yaml_cls):
cell_filename = None
for fname in ((filename, ) + phonopy_yaml_cls.default_filenames):
for fname in (filename,) + phonopy_yaml_cls.default_filenames:
if fname and os.path.isfile(fname):
cell_filename = fname
break

View File

@ -1,3 +1,4 @@
"""CASTEP calculator interface."""
# Copyright (C) 2014 Atsushi Togo
# All rights reserved.
#
@ -33,30 +34,31 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.interface.vasp import (get_scaled_positions_lines, check_forces,
get_drift_forces)
from phonopy.units import Bohr
from phonopy.cui.settings import fracval
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.symmetry import Symmetry
# Castep output contains blank lines surrounded by astericks which should be ignored.
# This is not implemented in collect_forces() function from file_IO module.
# Below is the reimplementation of the collect_forces() function for Castep with
# only one new variable skipafterhook. Setting this variable to zero (default value)
# is equivalent to original collect_forces() function.
def collect_forces_castep(f, num_atom, hook, force_pos, word=None, skipafterhook=0):
"""Collect forces from CASTEP output."""
for line in f:
if hook in line:
break
if (skipafterhook>0):
if skipafterhook > 0:
for i in range(skipafterhook):
f.readline()
forces = []
for line in f:
if line.strip() == '':
if line.strip() == "":
continue
if word is not None:
if word not in line:
@ -77,9 +79,11 @@ def collect_forces_castep(f, num_atom, hook, force_pos, word=None, skipafterhook
return forces
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = 'Cartesian components (eV/A)'
skipafterhook=3
"""Parse forces from output files."""
hook = "Cartesian components (eV/A)"
# skipafterhook = 3
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):
@ -87,12 +91,14 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
sys.stdout.write("%d. " % (i + 1))
f = open(filename)
castep_forces = collect_forces_castep(f, num_atoms, hook, [3, 4, 5], skipafterhook=3)
castep_forces = collect_forces_castep(
f, num_atoms, hook, [3, 4, 5], skipafterhook=3
)
if check_forces(castep_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(castep_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
castep_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(castep_forces) - drift_force)
else:
is_parsed = False
@ -102,129 +108,160 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
else:
return []
def read_castep(filename):
"""Read crystal structure."""
f_castep = open(filename)
castep_in = CastepIn(f_castep.readlines())
f_castep.close()
tags = castep_in.get_tags()
# 1st stage is to create Atoms object ignoring Spin polarization. General case.
cell = Atoms(cell=tags['lattice_vectors'],
symbols=tags['atomic_species'],
scaled_positions=tags['coordinates'])
# Analyse spin states and add data to Atoms instance "cell" if ones exist
magmoms = tags['magnetic_moments']
# 1st stage is to create Atoms object ignoring Spin polarization. General case.
cell = Atoms(
cell=tags["lattice_vectors"],
symbols=tags["atomic_species"],
scaled_positions=tags["coordinates"],
)
# Analyse spin states and add data to Atoms instance "cell" if ones exist
magmoms = tags["magnetic_moments"]
if magmoms is not None:
# Print out symmetry information for magnetic cases
# Original code from structure/symmetry.py
symmetry = Symmetry(cell, symprec=1e-5)
print("CASTEP-interface: Magnetic structure, number of operations without spin: %d" %
len(symmetry.get_symmetry_operations()['rotations']))
print("CASTEP-interface: Spacegroup without spin: %s" % symmetry.get_international_table())
print(
"CASTEP-interface: Magnetic structure, "
"number of operations without spin: %d"
% len(symmetry.get_symmetry_operations()["rotations"])
)
print(
"CASTEP-interface: Spacegroup without spin: %s"
% symmetry.get_international_table()
)
cell.set_magnetic_moments(magmoms)
symmetry = Symmetry(cell, symprec=1e-5)
print("CASTEP-interface: Magnetic structure, number of operations with spin: %d" %
len(symmetry.get_symmetry_operations()['rotations']))
print(
"CASTEP-interface: Magnetic structure, number of operations with spin: %d"
% len(symmetry.get_symmetry_operations()["rotations"])
)
print("")
return cell
def write_castep(filename, cell):
with open(filename, 'w') as f:
"""Write cell to file."""
with open(filename, "w") as f:
f.write(get_castep_structure(cell))
def write_supercells_with_displacements(supercell,
cells_with_displacements,
ids,
pre_filename="supercell",
width=3):
def write_supercells_with_displacements(
supercell, cells_with_displacements, ids, pre_filename="supercell", width=3
):
"""Write supercells with displacements to files."""
write_castep("%s.cell" % pre_filename, supercell)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.cell".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_castep(filename, cell)
def get_castep_structure(cell):
"""Return CASTEP structure in text."""
lines = ""
lines += '%BLOCK LATTICE_CART\n'
lines += "%BLOCK LATTICE_CART\n"
lines += ((" % 20.16f" * 3 + "\n") * 3) % tuple(cell.get_cell().ravel())
lines += '%ENDBLOCK LATTICE_CART\n\n'
lines += '%BLOCK POSITIONS_FRAC\n'
lines += "%ENDBLOCK LATTICE_CART\n\n"
lines += "%BLOCK POSITIONS_FRAC\n"
magmoms = cell.get_magnetic_moments()
for i in range(len(cell.get_chemical_symbols())):
atpos="".join("% 12.10f " % ap for ap in cell.get_scaled_positions()[i])
# Spin polarized case
atpos = "".join("% 12.10f " % ap for ap in cell.get_scaled_positions()[i])
# Spin polarized case
if (magmoms is not None) and (magmoms[i] != 0.0):
lines += "".join("%2s %s spin=% 5.2f\n" % (cell.get_chemical_symbols()[i], atpos, magmoms[i]))
# No spin ordering
lines += "".join(
"%2s %s spin=% 5.2f\n"
% (cell.get_chemical_symbols()[i], atpos, magmoms[i])
)
# No spin ordering
else:
lines += "".join("%2s %s\n" % (cell.get_chemical_symbols()[i], atpos))
lines += '%ENDBLOCK POSITIONS_FRAC\n\n'
lines += "%ENDBLOCK POSITIONS_FRAC\n\n"
return lines
class CastepIn:
"""Class to create CASTEP input file."""
def __init__(self, lines):
self._tags = {'lattice_vectors': None,
'atomic_species': None,
'coordinates': None,
'magnetic_moments': None}
"""Init method."""
self._tags = {
"lattice_vectors": None,
"atomic_species": None,
"coordinates": None,
"magnetic_moments": None,
}
self._values = None
self._collect(lines)
def get_tags(self):
"""Return tags."""
return self._tags
def _collect(self, lines):
magmoms = []
numspins = 0
# numspins = 0
units = 1.0 # Angstrom units
lattvecs = []
aspecies = []
coords = []
isSpinPol=0
# Read cell parameters
isSpinPol = 0
# Read cell parameters
for line in lines:
if ('%BLOCK LATTICE_CART' in line.upper()):
indx=lines.index(line)
for i in range(indx+1,len(lines)):
if ('ENDBLOCK' in lines[i].upper()):
if "%BLOCK LATTICE_CART" in line.upper():
indx = lines.index(line)
for i in range(indx + 1, len(lines)):
if "ENDBLOCK" in lines[i].upper():
break
if ('BOHR' in lines[i].upper()):
if "BOHR" in lines[i].upper():
# The lattice vector units is Bohr. Convertion needed.
units=0.529177211
elif (len(lines[i].split())>=3):
lattvecs.append([(float(lines[i].split()[j])*units) for j in range(3)])
units = 0.529177211
elif len(lines[i].split()) >= 3:
lattvecs.append(
[(float(lines[i].split()[j]) * units) for j in range(3)]
)
# Read atomic positions
# Read atomic positions
for line in lines:
if ('%BLOCK POSITIONS_FRAC' in line.upper()):
indx=lines.index(line)
for i in range(indx+1,len(lines)):
if ('ENDBLOCK' in lines[i].upper()):
if "%BLOCK POSITIONS_FRAC" in line.upper():
indx = lines.index(line)
for i in range(indx + 1, len(lines)):
if "ENDBLOCK" in lines[i].upper():
break
if (len(lines[i].split())>=3):
if len(lines[i].split()) >= 3:
aspecies.append(lines[i].split()[0])
coords.append([float(lines[i].split()[j]) for j in (1,2,3)])
coords.append([float(lines[i].split()[j]) for j in (1, 2, 3)])
# If there is magmetic spin
if (len(lines[i].split())>4) and ('SPIN' in lines[i].upper()):
magmoms.append(float(lines[i].upper().split('SPIN')[1].split('=')[1]))
isSpinPol=1
if (len(lines[i].split()) > 4) and ("SPIN" in lines[i].upper()):
magmoms.append(
float(lines[i].upper().split("SPIN")[1].split("=")[1])
)
isSpinPol = 1
else:
magmoms.append(0.0)
# Set magnetic tags if the structure with magnetic order
if (isSpinPol>0):
self._tags['magnetic_moments'] = magmoms
# Set magnetic tags if the structure with magnetic order
if isSpinPol > 0:
self._tags["magnetic_moments"] = magmoms
if len(lattvecs) == 3 and len(aspecies) > 0 and len(aspecies) == len(coords):
self._tags['lattice_vectors'] = lattvecs
self._tags['atomic_species'] = aspecies
self._tags['coordinates'] = coords
self._tags["lattice_vectors"] = lattvecs
self._tags["atomic_species"] = aspecies
self._tags["coordinates"] = coords
else:
print("CASTEP-interface: Error parsing CASTEP .cell file")
# ----------------------------------------------------------------------

View File

@ -1,3 +1,4 @@
"""Tests for CIF tools."""
# Copyright (C) 2016 Atsushi Togo
# All rights reserved.
#
@ -34,14 +35,18 @@
from phonopy.structure.cells import get_angles, get_cell_parameters
def write_cif_P1(cell, U_cif=None, filename=None):
"""Write P1 symmetry CIF file."""
if filename:
with open(filename, 'w') as w:
with open(filename, "w") as w:
w.write(get_cif_P1(cell, U_cif=U_cif))
def get_cif_P1(cell, U_cif=None):
a, b, c = get_cell_parameters(cell.get_cell())
alpha, beta, gamma = get_angles(cell.get_cell())
"""Return P1 symmetry CIF text."""
a, b, c = get_cell_parameters(cell.cell)
alpha, beta, gamma = get_angles(cell.cell)
cif = """data_crystal_structure_P1
@ -67,13 +72,26 @@ _atom_site_type_symbol
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
_atom_site_occupancy\n""" % (a, b, c, alpha, beta, gamma, cell.get_volume())
_atom_site_occupancy\n""" % (
a,
b,
c,
alpha,
beta,
gamma,
cell.get_volume(),
)
symbols = []
for s, p in zip(cell.get_chemical_symbols(), cell.get_scaled_positions()):
symbols.append(s)
cif += ("%-7s%2s %10.5f%10.5f%10.5f 1.00000\n" %
(s + "%d" % symbols.count(s), s, p[0], p[1], p[2]))
cif += "%-7s%2s %10.5f%10.5f%10.5f 1.00000\n" % (
s + "%d" % symbols.count(s),
s,
p[0],
p[1],
p[2],
)
if U_cif is not None:
@ -93,7 +111,8 @@ _atom_site_aniso_U_12\n"""
symbols.append(s)
m = U_cif[i]
vals = (m[0, 0], m[1, 1], m[2, 2], m[1, 2], m[0, 2], m[0, 1])
cif += ("%6s %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f\n" %
((s + "%d" % symbols.count(s),) + vals))
cif += "%6s %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f\n" % (
(s + "%d" % symbols.count(s),) + vals
)
return cif

View File

@ -1,3 +1,4 @@
"""CP2K calculator interface."""
# vim: set fileencoding=utf-8 :
# Copyright (C) 2017-2019 Tiziano Müller
# All rights reserved.
@ -38,21 +39,25 @@ from __future__ import print_function
import sys
from fractions import Fraction
from os import path
import numpy as np
from phonopy.file_IO import iter_collect_forces
from phonopy.interface.vasp import (check_forces, get_drift_forces)
from phonopy.structure.atoms import (PhonopyAtoms, symbol_map)
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms, symbol_map
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
"""Parse forces from output files."""
force_sets = []
for i, filename in enumerate(forces_filenames):
if verbose:
sys.stdout.write("%d. " % (i + 1))
forces = iter_collect_forces(filename, num_atoms, '# Atom Kind Element', [3, 4, 5])
forces = iter_collect_forces(
filename, num_atoms, "# Atom Kind Element", [3, 4, 5]
)
if not check_forces(forces, num_atoms, filename, verbose=verbose):
return [] # if one file is invalid, the whole thing is broken
@ -64,6 +69,7 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
def read_cp2k(filename):
"""Read crystal structure."""
from cp2k_input_tools.parser import CP2KInputParser
with open(filename) as fhandle:
@ -71,112 +77,143 @@ def read_cp2k(filename):
tree = parser.parse(fhandle)
try:
subsys = tree['+force_eval'][0]['+subsys']
cp2k_cell = subsys['+cell']
subsys = tree["+force_eval"][0]["+subsys"]
cp2k_cell = subsys["+cell"]
except IndexError:
raise RuntimeError("could not find a FORCE_EVAL/SUBSYS/CELL section in the given CP2K input file")
raise RuntimeError(
"could not find a FORCE_EVAL/SUBSYS/CELL section in the given "
"CP2K input file"
)
if len(tree['+force_eval']) > 1:
raise NotImplementedError("the given CP2K input file contains multiple FORCE_EVAL sections, which is not (yet) supported")
if len(tree["+force_eval"]) > 1:
raise NotImplementedError(
"the given CP2K input file contains multiple FORCE_EVAL sections, "
"which is not (yet) supported"
)
# CP2K can get its cell information in two ways:
# - A, B, C: cell vectors
# - ABC: scaling of cell vectors, ALPHA_BETA_GAMMA: angles between the cell vectors
# We'll parse either of them, but only write A, B, C
if 'a' in cp2k_cell:
if "a" in cp2k_cell:
# unit vectors given
unit_cell = np.array([
cp2k_cell['a'],
cp2k_cell['b'],
cp2k_cell['c'],
])
elif 'abc' in cp2k_cell:
unit_cell = np.array(
[
cp2k_cell["a"],
cp2k_cell["b"],
cp2k_cell["c"],
]
)
elif "abc" in cp2k_cell:
# length of unit vectors given
if 'alpha_beta_gamma' in cp2k_cell:
if "alpha_beta_gamma" in cp2k_cell:
# if we also have the angles, construct the cell
alpha, beta, gamma = cp2k_cell.pop('alpha_beta_gamma')
alpha, beta, gamma = cp2k_cell.pop("alpha_beta_gamma")
cos_alpha = np.cos(alpha)
cos_beta = np.cos(beta)
cos_gamma = np.cos(gamma)
sin_gamma = np.sin(gamma)
unit_cell = np.array([
[1., 0., 0.],
[cos_gamma, sin_gamma, 0.],
unit_cell = np.array(
[
[1.0, 0.0, 0.0],
[cos_gamma, sin_gamma, 0.0],
[
cos_beta,
(cos_alpha-cos_gamma*cos_beta)/sin_gamma,
np.sqrt(1.-cos_beta**2-((cos_alpha-cos_gamma*cos_beta)/sin_gamma)**2),
(cos_alpha - cos_gamma * cos_beta) / sin_gamma,
np.sqrt(
1.0
- cos_beta ** 2
- ((cos_alpha - cos_gamma * cos_beta) / sin_gamma) ** 2
),
],
])
]
)
else:
unit_cell = np.eye(3)
a, b, c = cp2k_cell.pop('abc') # remove them from the tree since we pass it along
a, b, c = cp2k_cell.pop(
"abc"
) # remove them from the tree since we pass it along
unit_cell[0,:] *= a
unit_cell[1,:] *= b
unit_cell[2,:] *= c
unit_cell[0, :] *= a
unit_cell[1, :] *= b
unit_cell[2, :] *= c
if '+cell_ref' in cp2k_cell:
if "+cell_ref" in cp2k_cell:
print("WARNING: the &CELL_REF section must be manually adjusted")
cp2k_coord = subsys['+coord']
cp2k_coord = subsys["+coord"]
numbers = []
positions = []
for coordline in cp2k_coord['*']:
for coordline in cp2k_coord["*"]:
# coordinates are a series of strings according to the CP2K schema
fields = coordline.split()
numbers += [symbol_map[fields[0]]]
# positions can also be fractions
positions += [[float(Fraction(f)) for f in fields[1:4]]]
if cp2k_coord.get('scaled', False): # the keyword can be unavailable, true or false
return (PhonopyAtoms(numbers=numbers, cell=unit_cell, scaled_positions=positions), tree)
if cp2k_coord.get("scaled", False): # the keyword can be unavailable, true or false
return (
PhonopyAtoms(numbers=numbers, cell=unit_cell, scaled_positions=positions),
tree,
)
else:
return (PhonopyAtoms(numbers=numbers, cell=unit_cell, positions=positions), tree)
return (
PhonopyAtoms(numbers=numbers, cell=unit_cell, positions=positions),
tree,
)
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
optional_structure_info,
pre_filename="supercell",
width=3):
width=3,
):
"""Write supercells with displacements to files."""
orig_fname, tree = optional_structure_info
fbase, fext = path.splitext(orig_fname)
pbase = tree["+global"]["project_name"]
supercell_ref_name = "{}-supercell{}".format(fbase, fext)
with open(supercell_ref_name, 'w') as fhandle:
fhandle.write("""\
with open(supercell_ref_name, "w") as fhandle:
fhandle.write(
"""\
# Generated by Phonopy, based on {fname}
# Original configuration with the generated supercell for comparison
""".format(fname=orig_fname))
""".format(
fname=orig_fname
)
)
write_cp2k(fhandle, "{}-{}".format(pbase, pre_filename), supercell, tree)
for i, cell in zip(ids, cells_with_displacements):
suffix = "{pre_filename}-{0:0{width}}".format(
i,
pre_filename=pre_filename,
width=width)
i, pre_filename=pre_filename, width=width
)
with open("{}-{}{}".format(fbase, suffix, fext), 'w') as fhandle:
fhandle.write("""\
with open("{}-{}{}".format(fbase, suffix, fext), "w") as fhandle:
fhandle.write(
"""\
# Generated by Phonopy, based on {fname}
# Merged configuration with displacements
""".format(fname=orig_fname))
""".format(
fname=orig_fname
)
)
write_cp2k(fhandle, "{}-{}".format(pbase, suffix), cell, tree)
def write_cp2k_by_filename(filename, cell, tree, header=None):
"""Simple wrapper of write_cp2k to write an arbitrary unit cell
"""Wrap write_cp2k to write an arbitrary unit cell.
Note
----
@ -186,7 +223,6 @@ def write_cp2k_by_filename(filename, cell, tree, header=None):
Please rewrite if somebody who knows well about cp2k input.
"""
fbase, fext = path.splitext(filename)
pbase = tree["+global"]["project_name"]
project_name = "{}-{}".format(pbase, fbase)
@ -196,55 +232,65 @@ def write_cp2k_by_filename(filename, cell, tree, header=None):
else:
_header = header
with open(filename, 'w') as w:
with open(filename, "w") as w:
w.write(_header)
write_cp2k(w, project_name, cell, tree)
def write_cp2k(fhandle, project_name, atoms, tree):
"""Merge the new the atoms structure with the configuration tree to a new CP2K input file
"""Merge the new the atoms structure with the configuration tree to a new CP2K input file.
:param fhandle: open file handle to which the routine will write to
:param project_name: the project name to use (CP2K uses that as prefix for generated files)i
:param atoms: the Atoms objects to use
:param tree: the configuration tree as returned from CP2KInputParser
"""
from cp2k_input_tools.generator import CP2KInputGenerator
generator = CP2KInputGenerator()
tree['+global']['run_type'] = 'ENERGY_FORCE'
tree['+global']['project_name'] = project_name
tree["+global"]["run_type"] = "ENERGY_FORCE"
tree["+global"]["project_name"] = project_name
force_eval = tree['+force_eval'][0]
subsys = force_eval['+subsys']
force_eval = tree["+force_eval"][0]
subsys = force_eval["+subsys"]
# if the original input contained scaled positions, continue with scaled positions
if subsys['+coord'].get('scaled', False):
if subsys["+coord"].get("scaled", False):
cp2k_coord = {
'scaled': True,
'*': ["{sym} {x} {y} {z}".format(sym=sym, x=coord[0], y=coord[1], z=coord[2])
for sym, coord in zip(atoms.get_chemical_symbols(), atoms.get_scaled_positions())],
"scaled": True,
"*": [
"{sym} {x} {y} {z}".format(sym=sym, x=coord[0], y=coord[1], z=coord[2])
for sym, coord in zip(
atoms.get_chemical_symbols(), atoms.get_scaled_positions()
)
],
}
# ... otherwise use absolute positions
else:
cp2k_coord = {
'*': ["{sym} {x} {y} {z}".format(sym=sym, x=coord[0], y=coord[1], z=coord[2])
for sym, coord in zip(atoms.get_chemical_symbols(), atoms.get_positions())],
"*": [
"{sym} {x} {y} {z}".format(sym=sym, x=coord[0], y=coord[1], z=coord[2])
for sym, coord in zip(
atoms.get_chemical_symbols(), atoms.get_positions()
)
],
}
subsys['+cell']['a'] = list(atoms.get_cell()[0])
subsys['+cell']['b'] = list(atoms.get_cell()[1])
subsys['+cell']['c'] = list(atoms.get_cell()[2])
subsys['+cell']['periodic'] = 'XYZ' # anything else does not make much sense
subsys["+cell"]["a"] = list(atoms.get_cell()[0])
subsys["+cell"]["b"] = list(atoms.get_cell()[1])
subsys["+cell"]["c"] = list(atoms.get_cell()[2])
subsys["+cell"]["periodic"] = "XYZ" # anything else does not make much sense
subsys['+coord'] = cp2k_coord # overwriting the coordinates
subsys["+coord"] = cp2k_coord # overwriting the coordinates
if not '+print' in force_eval:
force_eval['+print'] = {}
if not '+forces' in force_eval['+print']:
force_eval['+print']['+forces'] = {}
force_eval['+print']['+forces']['filename'] = "forces" # uses the project name as base with 'forces' as suffix
if "+print" not in force_eval:
force_eval["+print"] = {}
if "+forces" not in force_eval["+print"]:
force_eval["+print"]["+forces"] = {}
force_eval["+print"]["+forces"][
"filename"
] = "forces" # uses the project name as base with 'forces' as suffix
for line in generator.line_iter(tree):
fhandle.write("{line}\n".format(line=line))

View File

@ -1,3 +1,4 @@
"""CRYSTAL calculator interface."""
# Copyright (C) 2016 Antti J. Karttunen (antti.j.karttunen@iki.fi)
# All rights reserved.
#
@ -33,39 +34,37 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.file_IO import iter_collect_forces
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.atoms import symbol_map
from phonopy.structure.cells import get_cell_parameters, get_angles
from phonopy.units import Hartree, Bohr
from phonopy.structure.symmetry import Symmetry
from phonopy.units import Bohr, Hartree
def parse_set_of_forces(num_atoms,
forces_filenames,
verbose=True):
hook = 'ATOM X Y Z'
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
"""Parse forces from output files."""
hook = "ATOM X Y Z"
force_sets = []
for i, filename in enumerate(forces_filenames):
if verbose:
sys.stdout.write("%d. " % (i + 1))
crystal_forces = iter_collect_forces(filename,
num_atoms,
hook,
[2, 3, 4])
crystal_forces = iter_collect_forces(filename, num_atoms, hook, [2, 3, 4])
if check_forces(crystal_forces, num_atoms, filename, verbose=verbose):
is_parsed = True
drift_force = get_drift_forces(crystal_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
crystal_forces, filename=filename, verbose=verbose
)
# Convert forces Hartree / Bohr -> eV / Angstrom
# This avoids confusion with the units. CRYSTAL uses Angstroms for
# coordinates, but Hartree / Bohr for forces. This would lead in mixed
# units hartree / (Angstrom * Bohr) for force constants, requiring
# additional tweaks for unit conversions in other parts of the code
force_sets.append(np.multiply(np.array(crystal_forces) - drift_force, Hartree / Bohr))
force_sets.append(
np.multiply(np.array(crystal_forces) - drift_force, Hartree / Bohr)
)
else:
is_parsed = False
@ -74,37 +73,52 @@ def parse_set_of_forces(num_atoms,
else:
return []
def read_crystal(filename):
"""Read crystal structure."""
f_crystal = open(filename)
crystal_in = CrystalIn(f_crystal.readlines())
f_crystal.close()
tags = crystal_in.get_tags()
cell = Atoms(
cell=tags["lattice_vectors"],
symbols=tags["atomic_species"],
scaled_positions=tags["coordinates"],
)
cell = Atoms(cell=tags['lattice_vectors'],
symbols=tags['atomic_species'],
scaled_positions=tags['coordinates'])
magmoms = tags['magnetic_moments']
magmoms = tags["magnetic_moments"]
if magmoms is not None:
# Print out symmetry information for magnetic cases
# Original code from structure/symmetry.py
symmetry = Symmetry(cell, symprec=1e-5)
print("CRYSTAL-interface: Magnetic structure, number of operations without spin: %d" %
len(symmetry.get_symmetry_operations()['rotations']))
print("CRYSTAL-interface: Spacegroup without spin: %s" % symmetry.get_international_table())
print(
"CRYSTAL-interface: Magnetic structure, "
"number of operations without spin: %d"
% len(symmetry.get_symmetry_operations()["rotations"])
)
print(
"CRYSTAL-interface: Spacegroup without spin: %s"
% symmetry.get_international_table()
)
cell.set_magnetic_moments(magmoms)
symmetry = Symmetry(cell, symprec=1e-5)
print("CRYSTAL-interface: Magnetic structure, number of operations with spin: %d" %
len(symmetry.get_symmetry_operations()['rotations']))
print(
"CRYSTAL-interface: Magnetic structure, number of operations with spin: %d"
% len(symmetry.get_symmetry_operations()["rotations"])
)
print("")
return cell, tags['conv_numbers']
return cell, tags["conv_numbers"]
def write_crystal(filename, cell, conv_numbers, template_file="TEMPLATE", write_symmetry=False):
def write_crystal(
filename, cell, conv_numbers, template_file="TEMPLATE", write_symmetry=False
):
"""Write cell to file."""
# Write geometry in EXTERNAL file (fort.34)
f_ext = open(filename + '.ext', 'w')
f_ext = open(filename + ".ext", "w")
f_ext.write(get_crystal_structure(cell, conv_numbers, write_symmetry))
f_ext.close()
@ -130,26 +144,30 @@ def write_crystal(filename, cell, conv_numbers, template_file="TEMPLATE", write_
for i in range(0, len(magmoms)):
if magmoms[i] != 0:
N_spins += 1
atomspins += ("%d %d " % (i + 1, magmoms[i]))
atomspins += "%d %d " % (i + 1, magmoms[i])
lines += "ATOMSPIN\n"
lines += ("%d\n" % N_spins)
lines += "%d\n" % N_spins
lines += atomspins + "\n"
lines += "GRADCAL\n"
lines += "END\n"
# Write the input file
f_inputfile = open(filename + '.d12', 'w')
f_inputfile = open(filename + ".d12", "w")
f_inputfile.writelines(lines)
f_inputfile.close()
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
conv_numbers,
num_unitcells_in_supercell,
pre_filename="supercell",
width=3,
template_file="TEMPLATE"):
template_file="TEMPLATE",
):
"""Write supercells with displacements to files."""
convnum_super = []
for i in conv_numbers:
for j in range(num_unitcells_in_supercell):
@ -158,27 +176,36 @@ def write_supercells_with_displacements(supercell,
# Currently, symmetry is not used by default
# It can be turned on by creating a file called CRY_SYM
try:
f = open('CRY_SYM')
f = open("CRY_SYM")
use_symmetry = True
f.close()
except IOError:
use_symmetry = False
if use_symmetry:
print("CRYSTAL-interface: WARNING: Symmetry enabled in EXTERNAL files.\n" +
" Check the supercells very carefully, some spacegroups do not work (e.g. R-3m)\n" +
" Non-displaced supercell is always written without symmetry")
print(
"CRYSTAL-interface: WARNING: Symmetry enabled in EXTERNAL files.\n"
" Check the supercells very carefully, some spacegroups do not work "
"(e.g. R-3m)\n"
" Non-displaced supercell is always written without symmetry"
)
write_crystal(pre_filename, supercell, convnum_super, template_file, write_symmetry=False)
write_crystal(
pre_filename, supercell, convnum_super, template_file, write_symmetry=False
)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}".format(
i, pre_filename=pre_filename, width=width)
write_crystal(filename, cell, convnum_super, template_file, write_symmetry=use_symmetry)
i, pre_filename=pre_filename, width=width
)
write_crystal(
filename, cell, convnum_super, template_file, write_symmetry=use_symmetry
)
def get_crystal_structure(cell, conv_numbers, write_symmetry=False):
"""Return CRYSTAL structure in text."""
lattice = cell.get_cell()
positions = cell.get_positions()
numbers = cell.get_atomic_numbers()
# Create and EXTERNAL file (fort.34)
# Dimensionality, centring, crystal type
@ -190,8 +217,8 @@ def get_crystal_structure(cell, conv_numbers, write_symmetry=False):
# Symmetry operators
if write_symmetry:
symmetry = Symmetry(cell, symprec=1e-5)
rotations = symmetry.get_symmetry_operations()['rotations']
translations = symmetry.get_symmetry_operations()['translations']
rotations = symmetry.get_symmetry_operations()["rotations"]
translations = symmetry.get_symmetry_operations()["translations"]
N_symmops = 0
symmlines = ""
for i in range(0, len(rotations)):
@ -200,7 +227,7 @@ def get_crystal_structure(cell, conv_numbers, write_symmetry=False):
symmlines += (" %5.2f" * 3 + "\n") % tuple(rotations[i][j])
symmlines += (" %5.2f" * 3 + "\n") % tuple(translations[i])
lines += ("%d\n" % N_symmops)
lines += "%d\n" % N_symmops
lines += symmlines
else:
@ -214,80 +241,93 @@ def get_crystal_structure(cell, conv_numbers, write_symmetry=False):
lines += ("%d\n") % len(positions)
# Conventional atomic number and cartesian coordinates of the atoms
for i, pos in zip(conv_numbers, positions):
lines += (" %d " + "%16.12f"*3 + "\n") % (i, pos[0], pos[1], pos[2])
lines += (" %d " + "%16.12f" * 3 + "\n") % (i, pos[0], pos[1], pos[2])
return lines
class CrystalIn:
"""Class to create CRYSTAL input file."""
def __init__(self, lines):
# conv_numbers = CRYSTAL conventional atomic number mapping: 'Ge' -> 32 or 'Ge' -> 232
self._tags = {'lattice_vectors': None,
'atomic_species': None,
'coordinates': None,
'magnetic_moments': None,
'conv_numbers': None}
"""Init method."""
# conv_numbers = CRYSTAL conventional atomic number mapping:
# 'Ge' -> 32 or 'Ge' -> 232
self._tags = {
"lattice_vectors": None,
"atomic_species": None,
"coordinates": None,
"magnetic_moments": None,
"conv_numbers": None,
}
self._values = None
self._collect(lines)
def get_tags(self):
"""Return tags."""
return self._tags
def _collect(self, lines):
# Reads a CRYSTAL output file (lattice vectors, conventional atomic numbers,
# fractional atomic positions).
# - For optimization outputs, the final geometry in the file is read.
# - Dielectric tensor and effective Born charges can be read with script phonopy-crystal-born
# - Dielectric tensor and effective Born charges can be read with script
# phonopy-crystal-born
# - If ATOMSPIN keyword is present, magnetic moments are read from it
magmoms = []
atomspins = []
numspins = 0
l = 0
while l < len(lines):
line = lines[l]
if 'PRIMITIVE CELL - CENTRING CODE' in line:
ll = 0
while ll < len(lines):
line = lines[ll]
if "PRIMITIVE CELL - CENTRING CODE" in line:
aspecies = []
coords = []
convnum = []
l += 4
ll += 4
# ATOMS IN THE ASYMMETRIC UNIT 2 - ATOMS IN THE UNIT CELL: 6
N_asym_atoms = int(lines[l].split()[5])
N_atoms = int(lines[l].split()[12])
l += 3
# 1 T 22 TI 4.721218104494E-21 3.307446203077E-21 1.413771901417E-21
N_atoms = int(lines[ll].split()[12])
ll += 3
# 1 T 22 TI 4.721218104494E-21 3.307446203077E-21 1.413771901417E-21 # noqa E501
for atom in range(0, N_atoms):
atomdata = lines[l].split()
atomdata = lines[ll].split()
aspecies.append(atomdata[3].capitalize())
coords.append([float(x) for x in atomdata[4:7]])
convnum.append(int(atomdata[2]))
l += 1
elif 'DIRECT LATTICE VECTORS CARTESIAN COMPONENTS' in line:
ll += 1
elif "DIRECT LATTICE VECTORS CARTESIAN COMPONENTS" in line:
lattvecs = []
l += 2
ll += 2
# X Y Z
for lattvec in range(1, 4):
lattvecs.append([float(x) for x in lines[l].split()])
l += 1
elif 'ATOMSPIN' in line:
lattvecs.append([float(x) for x in lines[ll].split()])
ll += 1
elif "ATOMSPIN" in line:
# Read ATOMSPIN, and save the magnetic moments for later parsing
# (not all necessary information is available at this point)
# All spins must be entered on one line!
# ATOMSPIN
# 8
# 1 1 2 1 3 -1 4 -1 5 1 6 1 7 -1 8 -1
l += 1
numspins = int(lines[l])
l += 1
atomspins = [int(x) for x in lines[l].split()]
l += 1
ll += 1
numspins = int(lines[ll])
ll += 1
atomspins = [int(x) for x in lines[ll].split()]
ll += 1
l += 1 # while l < len(lines)
ll += 1 # while l < len(lines)
if len(lattvecs) == 3 and len(aspecies) > 0 and len(aspecies) == len(coords) and len(aspecies) == len(convnum):
self._tags['lattice_vectors'] = lattvecs
self._tags['atomic_species'] = aspecies
self._tags['coordinates'] = coords
self._tags['conv_numbers'] = convnum
if (
len(lattvecs) == 3
and len(aspecies) > 0
and len(aspecies) == len(coords)
and len(aspecies) == len(convnum)
):
self._tags["lattice_vectors"] = lattvecs
self._tags["atomic_species"] = aspecies
self._tags["coordinates"] = coords
self._tags["conv_numbers"] = convnum
else:
print("CRYSTAL-interface: Error parsing CRYSTAL output file")
@ -297,22 +337,26 @@ class CrystalIn:
magmoms = [0] * N_atoms
if numspins * 2 == len(atomspins):
for i in range(0, numspins):
atomnum = atomspins[i*2] - 1
magmom = atomspins[i*2 + 1]
atomnum = atomspins[i * 2] - 1
magmom = atomspins[i * 2 + 1]
magmoms[atomnum] = magmom
self._tags['magnetic_moments'] = magmoms
print("CRYSTAL-interface: Following magnetic moments have been read from ATOMSPIN entry:")
self._tags["magnetic_moments"] = magmoms
print(
"CRYSTAL-interface: Following magnetic moments "
"have been read from ATOMSPIN entry:"
)
print(magmoms)
else:
print("CRYSTAL-interface: Invalid ATOMSPIN entry, magnetic moments have not been set")
print(
"CRYSTAL-interface: Invalid ATOMSPIN entry, "
"magnetic moments have not been set"
)
else:
print("")
if __name__ == '__main__':
import sys
from phonopy.structure.symmetry import Symmetry
if __name__ == "__main__":
cell, conv_numbers = read_crystal(sys.argv[1])
symmetry = Symmetry(cell)
print("# %s" % symmetry.get_international_table())

View File

@ -1,3 +1,4 @@
"""DFTB+ calculator interface."""
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
@ -33,16 +34,18 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import Atoms
from phonopy.units import dftbpToBohr
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import (get_scaled_positions_lines, check_forces,
get_drift_forces)
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = 'forces :real:2:'
"""Parse forces from output files."""
hook = "forces :real:2:"
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):
@ -52,9 +55,9 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
f = open(filename)
dftbp_forces = collect_forces(f, num_atoms, hook, [0, 1, 2])
if check_forces(dftbp_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(dftbp_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
dftbp_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(dftbp_forces) - drift_force)
else:
is_parsed = False
@ -69,30 +72,33 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
# read dftbp-files
#
def read_dftbp(filename):
""" Reads DFTB+ structure files in gen format.
Args:
def read_dftbp(filename):
"""Read DFTB+ structure files in gen format.
Parameters
----------
filename: name of the gen-file to be read
Returns:
Returns
-------
atoms: an object of the phonopy.Atoms class, representing the structure
found in filename
"""
infile = open(filename, 'r')
"""
infile = open(filename, "r")
lines = infile.readlines()
# remove any comments
for ss in lines:
if ss.strip().startswith('#'):
if ss.strip().startswith("#"):
lines.remove(ss)
natoms = int(lines[0].split()[0])
symbols = lines[1].split()
if (lines[0].split()[1].lower() == 'f'):
if lines[0].split()[1].lower() == "f":
is_scaled = True
scale_pos = 1
scale_latvecs = dftbpToBohr
@ -105,49 +111,49 @@ def read_dftbp(filename):
positions = []
expaned_symbols = []
for ii in range(2, natoms+2):
for ii in range(2, natoms + 2):
lsplit = lines[ii].split()
expaned_symbols.append(symbols[int(lsplit[1]) - 1])
positions.append([float(ss)*scale_pos for ss in lsplit[2:5]])
positions.append([float(ss) * scale_pos for ss in lsplit[2:5]])
# origin is ignored, may be used in future
origin = [float(ss) for ss in lines[natoms+2].split()]
# origin = [float(ss) for ss in lines[natoms + 2].split()]
# assign coords of unitcell
cell = []
for ii in range(natoms+3, natoms+6):
for ii in range(natoms + 3, natoms + 6):
lsplit = lines[ii].split()
cell.append([float(ss)*scale_latvecs for ss in lsplit[:3]])
cell.append([float(ss) * scale_latvecs for ss in lsplit[:3]])
cell = np.array(cell)
if is_scaled:
atoms = Atoms(symbols=expaned_symbols,
cell=cell,
scaled_positions=positions)
atoms = Atoms(symbols=expaned_symbols, cell=cell, scaled_positions=positions)
else:
atoms = Atoms(symbols=expaned_symbols,
cell=cell,
positions=positions)
atoms = Atoms(symbols=expaned_symbols, cell=cell, positions=positions)
return atoms
#
# write dftb+ .gen-file
#
def get_reduced_symbols(symbols):
"""Reduces expanded list of symbols.
"""Reduce expanded list of symbols.
Args:
symbols: list containing any chemical symbols as often as
the atom appears in the structure
Parameters
----------
symbols:
list containing any chemical symbols as often as
the atom appears in the structure.
Returns
-------
reduced_symbols: any symbols appears only once.
Returns:
reduced_symbols: any symbols appears only once
"""
reduced_symbols = []
for ss in symbols:
@ -156,12 +162,15 @@ def get_reduced_symbols(symbols):
return reduced_symbols
def write_dftbp(filename, atoms):
"""Writes DFTB+ readable, gen-formatted structure files
Args:
def write_dftbp(filename, atoms):
"""Write DFTB+ readable, gen-formatted structure files.
Parameters
----------
filename: name of the gen-file to be written
atoms: object containing information about structure
"""
scale_pos = dftbpToBohr
@ -170,56 +179,59 @@ def write_dftbp(filename, atoms):
# 1. line, use absolute positions
natoms = atoms.get_number_of_atoms()
lines += str(natoms)
lines += ' S \n'
lines += " S \n"
# 2. line
expaned_symbols = atoms.get_chemical_symbols()
symbols = get_reduced_symbols(expaned_symbols)
lines += ' '.join(symbols) + '\n'
lines += " ".join(symbols) + "\n"
atom_numbers = []
for ss in expaned_symbols:
atom_numbers.append(symbols.index(ss) + 1)
positions = atoms.get_positions()/scale_pos
positions = atoms.get_positions() / scale_pos
for ii in range(natoms):
pos = positions[ii]
pos_str = "{:3d} {:3d} {:20.15f} {:20.15f} {:20.15f}\n".format(
ii + 1, atom_numbers[ii], pos[0], pos[1], pos[2])
ii + 1, atom_numbers[ii], pos[0], pos[1], pos[2]
)
lines += pos_str
# origin arbitrary
lines +='0.0 0.0 0.0\n'
lines += "0.0 0.0 0.0\n"
cell = atoms.get_cell()/scale_pos
cell = atoms.get_cell() / scale_pos
for ii in range(3):
cell_str = "{:20.15f} {:20.15f} {:20.15f}\n".format(
cell[ii][0], cell[ii][1], cell[ii][2])
cell[ii][0], cell[ii][1], cell[ii][2]
)
lines += cell_str
outfile = open(filename, 'w')
outfile = open(filename, "w")
outfile.write(lines)
def write_supercells_with_displacements(supercell,
cells_with_disps,
ids,
pre_filename="geo.gen",
width=3):
"""Writes perfect supercell and supercells with displacements
Args:
def write_supercells_with_displacements(
supercell, cells_with_disps, ids, pre_filename="geo.gen", width=3
):
"""Write perfect supercell and supercells with displacements.
Parameters
----------
supercell: perfect supercell
cells_with_disps: supercells with displaced atoms
filename: root-filename
"""
"""
# original cell
write_dftbp(pre_filename + "S", supercell)
# displaced cells
for i, cell in zip(ids, cells_with_disps):
filename = "{pre_filename}S-{0:0{width}}".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_dftbp(filename, cell)

View File

@ -1,3 +1,4 @@
"""Elk calculator interface."""
# Copyright (C) 2015 Atsushi Togo
# All rights reserved.
#
@ -33,19 +34,23 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import (get_scaled_positions_lines,
sort_positions_by_symbols,
from phonopy.interface.vasp import (
check_forces,
get_drift_forces)
get_drift_forces,
get_scaled_positions_lines,
sort_positions_by_symbols,
)
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.atoms import symbol_map
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = 'Forces :'
"""Parse forces from output files."""
hook = "Forces :"
is_parsed = True
force_sets = []
@ -53,15 +58,11 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
if verbose:
sys.stdout.write("%d. " % (i + 1))
f = open(filename)
elk_forces = collect_forces(f,
num_atoms,
hook,
[3, 4, 5],
word='total force')
elk_forces = collect_forces(f, num_atoms, hook, [3, 4, 5], word="total force")
if check_forces(elk_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(elk_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
elk_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(elk_forces) - drift_force)
else:
is_parsed = False
@ -73,11 +74,12 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
def read_elk(filename):
"""Read crystal structure."""
elk_in = ElkIn(open(filename).readlines())
tags = elk_in.get_variables()
avec = [tags['scale'][i] * np.array(tags['avec'][i]) for i in range(3)]
spfnames = tags['atoms']['spfnames']
symbols = [x.split('.')[0] for x in spfnames]
avec = [tags["scale"][i] * np.array(tags["avec"][i]) for i in range(3)]
spfnames = tags["atoms"]["spfnames"]
symbols = [x.split(".")[0] for x in spfnames]
numbers = []
for s in symbols:
if s in symbols:
@ -93,40 +95,42 @@ def read_elk(filename):
break
pos_all = []
num_all = []
for num, pos in zip(numbers, tags['atoms']['positions']):
for num, pos in zip(numbers, tags["atoms"]["positions"]):
pos_all += pos
num_all += [num] * len(pos)
return Atoms(numbers=num_all,
cell=avec,
scaled_positions=pos_all), spfnames
return Atoms(numbers=num_all, cell=avec, scaled_positions=pos_all), spfnames
def write_elk(filename, cell, sp_filenames):
f = open(filename, 'w')
"""Write cell to file."""
f = open(filename, "w")
f.write(get_elk_structure(cell, sp_filenames))
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
sp_filenames,
pre_filename="supercell",
width=3):
width=3,
):
"""Write supercells with displacements to files."""
write_elk("%s.in" % pre_filename, supercell, sp_filenames)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.in".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_elk(filename, cell, sp_filenames)
def get_elk_structure(cell, sp_filenames=None):
"""Return Elk structure in text."""
lattice = cell.get_cell()
(num_atoms,
symbols,
scaled_positions,
sort_list) = sort_positions_by_symbols(cell.get_chemical_symbols(),
cell.get_scaled_positions())
(num_atoms, symbols, scaled_positions, sort_list) = sort_positions_by_symbols(
cell.get_chemical_symbols(), cell.get_scaled_positions()
)
if sp_filenames is None:
spfnames = [s + ".in" for s in symbols]
@ -140,10 +144,9 @@ def get_elk_structure(cell, sp_filenames=None):
n_pos = 0
lines += " %d\n" % len(num_atoms)
for i, (n, s) in enumerate(zip(num_atoms, spfnames)):
lines += " \'%s\'\n" % s
lines += " '%s'\n" % s
lines += " %d\n" % n
lines += get_scaled_positions_lines(
scaled_positions[n_pos:(n_pos + n)])
lines += get_scaled_positions_lines(scaled_positions[n_pos : (n_pos + n)])
if i < len(num_atoms) - 1:
lines += "\n"
n_pos += n
@ -151,21 +154,25 @@ def get_elk_structure(cell, sp_filenames=None):
return lines
class ElkIn(object):
class ElkIn:
"""Class to create Elk input file."""
def __init__(self, lines):
self._set_methods = {'atoms': self._set_atoms,
'avec': self._set_avec,
'scale': self._set_scale,
'scale1': self._set_scale1,
'scale2': self._set_scale2,
'scale3': self._set_scale3}
self._tags = {'atoms': None,
'avec': None,
'scale': [1.0, 1.0, 1.0]}
"""Init method."""
self._set_methods = {
"atoms": self._set_atoms,
"avec": self._set_avec,
"scale": self._set_scale,
"scale1": self._set_scale1,
"scale2": self._set_scale2,
"scale3": self._set_scale3,
}
self._tags = {"atoms": None, "avec": None, "scale": [1.0, 1.0, 1.0]}
self._lines = lines[:]
self._collect()
def get_variables(self):
"""Return tags."""
return self._tags
def _collect(self):
@ -177,7 +184,7 @@ class ElkIn(object):
if len(line_str) == 0:
continue
if line_str[0] == '!':
if line_str[0] == "!":
continue
elems = line_str.split()
@ -189,40 +196,39 @@ class ElkIn(object):
spfnames = []
positions = []
for i in range(nspecies):
spfnames.append(self._lines.pop(0).split()[0].strip('\''))
spfnames.append(self._lines.pop(0).split()[0].strip("'"))
natoms = int(self._lines.pop(0).split()[0])
pos_sp = []
for j in range(natoms):
pos_sp.append(
[float(x) for x in self._lines.pop(0).split()[:3]])
pos_sp.append([float(x) for x in self._lines.pop(0).split()[:3]])
positions.append(pos_sp)
self._tags['atoms'] = {'spfnames': spfnames,
'positions': positions}
self._tags["atoms"] = {"spfnames": spfnames, "positions": positions}
def _set_avec(self):
avec = []
for i in range(3):
avec.append([float(x) for x in self._lines.pop(0).split()[:3]])
self._tags['avec'] = avec
self._tags["avec"] = avec
def _set_scale(self):
scale = float(self._lines.pop(0).split()[0])
for i in range(3):
self._tags['scale'][i] = scale
self._tags["scale"][i] = scale
def _set_scale1(self):
self._tags['scale'][0] = float(self._lines.pop(0).split()[0])
self._tags["scale"][0] = float(self._lines.pop(0).split()[0])
def _set_scale2(self):
self._tags['scale'][1] = float(self._lines.pop(0).split()[0])
self._tags["scale"][1] = float(self._lines.pop(0).split()[0])
def _set_scale3(self):
self._tags['scale'][2] = float(self._lines.pop(0).split()[0])
self._tags["scale"][2] = float(self._lines.pop(0).split()[0])
if __name__ == '__main__':
if __name__ == "__main__":
from phonopy.structure.symmetry import Symmetry
cell, sp_filenames = read_elk(sys.argv[1])
symmetry = Symmetry(cell)
print("# %s" % symmetry.get_international_table())

View File

@ -1,3 +1,4 @@
"""Force constants calculator interface."""
# Copyright (C) 2019 Atsushi Togo
# All rights reserved.
#
@ -34,12 +35,13 @@
# Each key has to be lowercase. {fc_calculator: name, ...}
# name is supporsed to be str and used for text output to stdout.
fc_calculator_names = {'alm': 'ALM', 'hiphive': 'hiPhive'}
fc_calculator_names = {"alm": "ALM", "hiphive": "hiPhive"}
# get_fc2 is called from
# phonopy.api_phonopy.Phonopy._run_force_constants_from_forces.
def get_fc2(supercell,
def get_fc2(
supercell,
primitive,
displacements,
forces,
@ -47,7 +49,8 @@ def get_fc2(supercell,
fc_calculator_options=None,
atom_list=None,
log_level=0,
symprec=None):
symprec=None,
):
"""Supercell 2nd order force constants (fc2) are calculated.
The expected shape of supercell fc2 to be returned is
@ -90,23 +93,28 @@ def get_fc2(supercell,
shape=(len(atom_list), num_atoms, 3, 3), dtype='double', order='C'.
"""
if fc_calculator == 'alm' or fc_calculator is None:
if fc_calculator == "alm" or fc_calculator is None:
from phonopy.interface.alm import get_fc2
return get_fc2(supercell,
primitive,
displacements,
forces,
atom_list=atom_list,
options=fc_calculator_options,
log_level=log_level)
if fc_calculator == 'hiphive':
from phonopy.interface.hiphive_interface import get_fc2
return get_fc2(supercell,
return get_fc2(
supercell,
primitive,
displacements,
forces,
atom_list=atom_list,
options=fc_calculator_options,
log_level=log_level,
symprec=symprec)
)
if fc_calculator == "hiphive":
from phonopy.interface.hiphive_interface import get_fc2
return get_fc2(
supercell,
primitive,
displacements,
forces,
atom_list=atom_list,
options=fc_calculator_options,
log_level=log_level,
symprec=symprec,
)

View File

@ -1,3 +1,4 @@
"""Fleur calculator interface."""
# Copyright (C) 2021 Alexander Neukirchen
# All rights reserved.
#
@ -32,22 +33,25 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
import math
import itertools
import math
import sys
import numpy as np
from phonopy.file_IO import collect_forces
from phonopy.interface.vasp import (get_scaled_positions_lines,
sort_positions_by_symbols,
from phonopy.interface.vasp import (
check_forces,
get_drift_forces)
get_drift_forces,
sort_positions_by_symbols,
)
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.atoms import symbol_map
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = '1 #'
"""Parse forces from output files."""
hook = "1 #"
is_parsed = True
force_sets = []
@ -55,15 +59,11 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
if verbose:
sys.stdout.write("%d. " % (i + 1))
f = open(filename)
fleur_forces = collect_forces(f,
num_atoms,
hook,
[0, 1, 2],
word='force')
fleur_forces = collect_forces(f, num_atoms, hook, [0, 1, 2], word="force")
if check_forces(fleur_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(fleur_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
fleur_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(fleur_forces) - drift_force)
else:
is_parsed = False
@ -75,11 +75,15 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
def read_fleur(filename):
"""Read crystal structure."""
fleur_in = FleurIn(open(filename).readlines())
tags = fleur_in.get_variables()
avec = [tags['avec'][i] for i in range(3)]
speci = tags['atoms']['speci']
symbols = [list(symbol_map.keys())[list(symbol_map.values()).index(math.floor(float(x)))] for x in speci]
avec = [tags["avec"][i] for i in range(3)]
speci = tags["atoms"]["speci"]
symbols = [
list(symbol_map.keys())[list(symbol_map.values()).index(math.floor(float(x)))]
for x in speci
]
numbers = [math.floor(float(x)) for x in speci]
for i, n in enumerate(numbers):
@ -90,78 +94,88 @@ def read_fleur(filename):
break
pos_all = []
num_all = []
for num, pos in zip(numbers, tags['atoms']['positions']):
for num, pos in zip(numbers, tags["atoms"]["positions"]):
pos_all += pos
num_all = [symbol_map[s] for s in symbols]
return Atoms(numbers=num_all,
cell=avec,
scaled_positions=pos_all), speci, fleur_in._restlines
return (
Atoms(numbers=num_all, cell=avec, scaled_positions=pos_all),
speci,
fleur_in._restlines,
)
def write_fleur(filename, cell, speci, N, restlines):
f = open(filename, 'w')
f.write(get_fleur_structure(cell,speci,N,restlines))
"""Write crystal structure to file."""
f = open(filename, "w")
f.write(get_fleur_structure(cell, speci, N, restlines))
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
speci,
N,
restlines,
pre_filename="supercell",
width=3):
width=3,
):
"""Write supercells with displacements to files."""
write_fleur("%s.in" % pre_filename, supercell, speci, N, restlines)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.in".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_fleur(filename, cell, speci, N, restlines)
def get_fleur_structure(cell,speci,N,restlines):
def get_fleur_structure(cell, speci, N, restlines):
"""Return Fleur structure in text."""
lattice = cell.get_cell()
(num_atoms,
symbols,
scaled_positions,
sort_list) = sort_positions_by_symbols(cell.get_chemical_symbols(),
cell.get_scaled_positions())
specilong=list(itertools.chain.from_iterable(itertools.repeat(x, N) for x in speci))
lines = restlines[0]+"\n"
(num_atoms, symbols, scaled_positions, sort_list) = sort_positions_by_symbols(
cell.get_chemical_symbols(), cell.get_scaled_positions()
)
specilong = list(
itertools.chain.from_iterable(itertools.repeat(x, N) for x in speci)
)
lines = restlines[0] + "\n"
lines += ((" %21.16f" * 3 + "\n") * 3) % tuple(lattice.ravel())
lines += '1.0 \n'
lines += '1.0 1.0 1.0 \n \n'
lines += str(sum(num_atoms)) + '\n'
lines += "1.0 \n"
lines += "1.0 1.0 1.0 \n \n"
lines += str(sum(num_atoms)) + "\n"
for i in range(sum(num_atoms)):
lines += specilong[i].ljust(6)
currentpos = str(scaled_positions[i]).replace("[", "")
currentpos = currentpos.replace("]", "").split()
for j in range(3):
lines += ' ' + "{:.10f}".format(float(currentpos[j]))
lines += " " + "{:.10f}".format(float(currentpos[j]))
if i < sum(num_atoms) - 1:
lines += "\n"
for x in range(1,len(restlines)):
for x in range(1, len(restlines)):
if len(restlines[x]) == 0:
lines += "\n"
continue
lines += restlines[x]
if x != len(restlines) -1:
if x != len(restlines) - 1:
lines += "\n"
return lines
class FleurIn(object):
class FleurIn:
"""Class to generate Fleur crystal structure."""
def __init__(self, lines):
self._set_methods = {'a1': self._set_avec,
'atoms': self._set_atoms}
self._tags = {'atoms': None,
'a1': None}
"""Init method."""
self._set_methods = {"a1": self._set_avec, "atoms": self._set_atoms}
self._tags = {"atoms": None, "a1": None}
self._lines = lines[:]
self._restlines = []
self._collect()
def get_variables(self):
"""Return variables."""
return self._tags
def _collect(self):
@ -172,18 +186,18 @@ class FleurIn(object):
except IndexError:
break
if firstline == True:
if firstline is True:
self._restlines.append(line_str)
firstline = False
continue
if len(line_str) == 0:
continue
if line_str[0] == '!':
if line_str[0] == "!":
continue
elems = line_str.split()
if elems[-1] in self._set_methods:
self._lines.insert(0,line_str)
self._lines.insert(0, line_str)
self._set_methods[elems[-1]]()
def _set_atoms(self):
@ -192,14 +206,39 @@ class FleurIn(object):
positions = []
positions1 = []
for i in range(natoms):
currentline=self._lines.pop(0).split()
currentline = self._lines.pop(0).split()
speci.append(currentline[0])
currentspeci=[float(x) for x in currentline[1:4]]
currentspeci = [float(x) for x in currentline[1:4]]
positions1.append(currentspeci)
positions.append(positions1)
self._tags['atoms'] = {'speci': speci,
'positions': positions}
factors = [1.0, 1.0, 1.0]
shifts = [0.0, 0.0, 0.0]
for count, line in enumerate(self._lines):
if "&factor" in line:
currentline = self._lines.pop(count).split()
factors = [
float(currentline[1]),
float(currentline[2]),
float(currentline[3]),
]
for count, line in enumerate(self._lines):
if "&shift" in line:
currentline = self._lines.pop(count).split()
shifts = [
float(currentline[1]),
float(currentline[2]),
float(currentline[3]),
]
for x in positions[0]:
x[0] = x[0] / factors[0] + shifts[0]
x[1] = x[1] / factors[1] + shifts[1]
x[2] = x[2] / factors[2] + shifts[2]
self._tags["atoms"] = {"speci": speci, "positions": positions}
for j in range(len(self._lines)):
self._restlines.append(self._lines[j])
@ -207,18 +246,22 @@ class FleurIn(object):
avec = []
for i in range(3):
avec.append([float(x) for x in self._lines.pop(0).split()[:3]])
lattcon=float(self._lines.pop(0).split()[0])
lattcon = float(self._lines.pop(0).split()[0])
scale = [float(x) for x in self._lines.pop(0).split()[:3]]
for j in range(3):
for k in range(3):
if scale[k]<0:
if scale[k] < 0:
scale[k] = np.sqrt(np.abs(scale[k]))
avec[j][k] = lattcon*avec[j][k]*scale[k]
self._tags['avec'] = avec
if scale[k] == 0.0:
scale[k] = 1.0
avec[j][k] = lattcon * avec[j][k] * scale[k]
self._tags["avec"] = avec
if __name__ == '__main__':
if __name__ == "__main__":
from phonopy.structure.symmetry import Symmetry
cell, speci, restlines = read_fleur(sys.argv[1])
symmetry = Symmetry(cell)
print("# %s" % symmetry.get_international_table())
print(get_fleur_structure(cell, speci, N, restlines))
print(get_fleur_structure(cell, speci, 1, restlines))

View File

@ -1,3 +1,4 @@
"""hiPhive force constants calculator interface."""
# Copyright (C) 2018 Atsushi Togo
# All rights reserved.
#
@ -33,30 +34,35 @@
# POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from ase.atoms import Atoms
from hiphive import ClusterSpace, StructureContainer, ForceConstantPotential
from hiphive.fitting import Optimizer
from hiphive.cutoffs import estimate_maximum_cutoff
from hiphive.input_output.logging_tools import set_config
set_config(level=30)
def phonopy_atoms_to_ase(atoms_phonopy):
ase_atoms = Atoms(cell=atoms_phonopy.cell,
"""Convert PhonopyAtoms to Atoms."""
try:
from ase.atoms import Atoms
except ImportError:
raise ImportError("ASE python module was not found.")
ase_atoms = Atoms(
cell=atoms_phonopy.cell,
scaled_positions=atoms_phonopy.scaled_positions,
numbers=atoms_phonopy.numbers,
pbc=True)
pbc=True,
)
return ase_atoms
def get_fc2(supercell,
def get_fc2(
supercell,
primitive,
displacements,
forces,
symprec,
atom_list=None,
options=None,
log_level=0):
log_level=0,
):
"""Calculate fc2 using hiPhive."""
if log_level:
msg = [
"-------------------------------"
@ -64,47 +70,45 @@ def get_fc2(supercell,
"------------------------------",
"hiPhive is a non-trivial force constants calculator. "
"Please cite the paper:",
"\"The Hiphive Package for the Extraction of HighOrder Force "
"Constants",
" by Machine Learning\"",
'"The Hiphive Package for the Extraction of HighOrder Force ' "Constants",
' by Machine Learning"',
"by Fredrik Eriksson, Erik Fransson, and Paul Erhart,",
"Advanced Theory and Simulations, DOI:10.1002/adts.201800184 "
"(2019)",
""]
"Advanced Theory and Simulations, DOI:10.1002/adts.201800184 " "(2019)",
"",
]
print("\n".join(msg))
fc2 = run_hiphive(supercell=supercell,
fc2 = run_hiphive(
supercell=supercell,
primitive=primitive,
displacements=displacements,
forces=forces,
options=options,
symprec=symprec,
log_level=log_level)
log_level=log_level,
)
p2s_map = primitive.p2s_map
is_compact_fc = (atom_list is not None and
(atom_list == p2s_map).all())
is_compact_fc = atom_list is not None and (atom_list == p2s_map).all()
if is_compact_fc:
fc2 = np.array(fc2[p2s_map], dtype='double', order='C')
fc2 = np.array(fc2[p2s_map], dtype="double", order="C")
elif atom_list is not None:
fc2 = np.array(fc2[atom_list], dtype='double', order='C')
fc2 = np.array(fc2[atom_list], dtype="double", order="C")
if log_level:
print("--------------------------------"
print(
"--------------------------------"
" hiPhive end "
"-------------------------------")
"-------------------------------"
)
return fc2
def run_hiphive(supercell,
primitive,
displacements,
forces,
options,
symprec,
log_level):
"""Run hiphive
def run_hiphive(
supercell, primitive, displacements, forces, options, symprec, log_level
):
"""Run hiphive.
supercell : Supercell
Perfect supercell.
@ -122,6 +126,15 @@ def run_hiphive(supercell,
Log control. 0: quiet, 1: normal, 2: verbose 3: debug
"""
try:
from hiphive import ClusterSpace, ForceConstantPotential, StructureContainer
from hiphive.cutoffs import estimate_maximum_cutoff
from hiphive.fitting import Optimizer
from hiphive.input_output.logging_tools import set_config
except ImportError:
raise ImportError("hiPhive python module was not found.")
set_config(level=30)
ase_supercell = phonopy_atoms_to_ase(supercell)
ase_prim = phonopy_atoms_to_ase(primitive)
@ -130,8 +143,8 @@ def run_hiphive(supercell,
structures = []
for d, f in zip(displacements, forces):
structure = ase_supercell.copy()
structure.new_array('displacements', d)
structure.new_array('forces', f)
structure.new_array("displacements", d)
structure.new_array("forces", f)
structures.append(structure)
# parse options
@ -142,13 +155,16 @@ def run_hiphive(supercell,
# select cutoff
max_cutoff = estimate_maximum_cutoff(ase_supercell) - 1e-5
if 'cutoff' in options_dict:
cutoff = options_dict['cutoff']
if "cutoff" in options_dict:
cutoff = options_dict["cutoff"]
if cutoff > max_cutoff:
raise ValueError('Cutoff {:.4f} is larger than maximum allowed '
'cutoff, {:.4f}, for the given supercell.'
'\nDecrease cutoff or provide larger supercells.'
.format(cutoff, max_cutoff))
raise ValueError(
"Cutoff {:.4f} is larger than maximum allowed "
"cutoff, {:.4f}, for the given supercell."
"\nDecrease cutoff or provide larger supercells.".format(
cutoff, max_cutoff
)
)
else:
cutoff = max_cutoff
@ -162,15 +178,17 @@ def run_hiphive(supercell,
sc.add_structure(structure)
n_rows, n_cols = sc.data_shape
if n_rows < n_cols:
raise ValueError('Fitting problem is under-determined.'
'\nProvide more structures or decrease cutoff.')
raise ValueError(
"Fitting problem is under-determined."
"\nProvide more structures or decrease cutoff."
)
# Estimate error
opt = Optimizer(sc.get_fit_data(), train_size=0.75)
opt.train()
print(opt)
print('RMSE train : {:.4f}'.format(opt.rmse_train))
print('RMSE test : {:.4f}'.format(opt.rmse_test))
print("RMSE train : {:.4f}".format(opt.rmse_train))
print("RMSE test : {:.4f}".format(opt.rmse_test))
# Final train
opt = Optimizer(sc.get_fit_data(), train_size=1.0)
@ -185,7 +203,7 @@ def run_hiphive(supercell,
def _decode_options(options):
"""This is an example to parse options given in str.
"""Parse options given in str.
When options = 'cutoff = 4.0', options is converted to {'cutoff': 4.0}.
@ -195,10 +213,9 @@ def _decode_options(options):
phonopy --hiphiveph --fc-calc-opt "cutoff = 4" ...
"""
option_dict = {}
for pair in options.split(','):
key, value = [x.strip() for x in pair.split('=')]
if key == 'cutoff':
for pair in options.split(","):
key, value = [x.strip() for x in pair.split("=")]
if key == "cutoff":
option_dict[key] = float(value)
return option_dict

View File

@ -34,36 +34,59 @@
# POSSIBILITY OF SUCH DAMAGE.
import os
from typing import TYPE_CHECKING
import numpy as np
import yaml
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
if TYPE_CHECKING:
from phonopy import Phonopy
from phonopy.structure.atoms import PhonopyAtoms
def read_cell_yaml(filename, cell_type='unitcell'):
"""Read crystal structure from a phonopy.yaml like file.
def read_cell_yaml(filename, cell_type="unitcell"):
"""Read crystal structure from a phonopy.yaml or PhonopyAtoms.__str__ like file.
This returns unit cell, primitive cell, or supercell. The first one found
in this order is returned.
phonopy.yaml like file can contain several different cells, e.g., unit cell,
primitive cell, or supercell. In this case, the default preference order of
the returned cell is unit cell > primitive cell > supercell. ``cell_type``
is used to specify to choose one of them.
When output of PhonopyAtoms.__str__ is given (like below), this file is
parsed and its cell is returned.
lattice:
- [ 0.000000000000000, 2.845150738087836, 2.845150738087836 ] # a
- [ 2.845150738087836, 0.000000000000000, 2.845150738087836 ] # b
- [ 2.845150738087836, 2.845150738087836, 0.000000000000000 ] # c
points:
- symbol: Na # 1
coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ]
mass: 22.989769
- symbol: Cl # 2
coordinates: [ 0.500000000000000, 0.500000000000000, 0.500000000000000 ]
mass: 35.453000
"""
ph_yaml = PhonopyYaml()
ph_yaml.read(filename)
if ph_yaml.unitcell and cell_type == 'unitcell':
if ph_yaml.unitcell and cell_type == "unitcell":
return ph_yaml.unitcell
elif ph_yaml.primitive and cell_type == 'primitive':
elif ph_yaml.primitive and cell_type == "primitive":
return ph_yaml.primitive
elif ph_yaml.supercell and cell_type == 'supercell':
elif ph_yaml.supercell and cell_type == "supercell":
return ph_yaml.supercell
else:
return None
class PhonopyYaml(object):
class PhonopyYaml:
"""PhonopyYaml is a container of phonopy setting.
This contains the writer (__str__) and reader (read) of phonopy.yaml type
@ -115,20 +138,18 @@ class PhonopyYaml(object):
"""
command_name = "phonopy"
default_filenames = ("phonopy_params.yaml",
"phonopy_disp.yaml",
"phonopy.yaml")
default_settings = {'force_sets': True,
'displacements': True,
'force_constants': False,
'born_effective_charge': True,
'dielectric_constant': True}
default_filenames = ("phonopy_params.yaml", "phonopy_disp.yaml", "phonopy.yaml")
default_settings = {
"force_sets": True,
"displacements": True,
"force_constants": False,
"born_effective_charge": True,
"dielectric_constant": True,
}
def __init__(self,
configuration=None,
calculator=None,
physical_units=None,
settings=None):
def __init__(
self, configuration=None, calculator=None, physical_units=None, settings=None
):
"""Init method.
Parameters
@ -140,7 +161,7 @@ class PhonopyYaml(object):
physical_units : dict
Physical units used for the calculation.
settings : dict
See Phonopy.save().
This controls amount of information in yaml output. See Phonopy.save().
"""
self.configuration = configuration
@ -174,7 +195,7 @@ class PhonopyYaml(object):
return "\n".join(self.get_yaml_lines())
def read(self, filename):
"""Read phonopy.yaml life file."""
"""Read phonopy.yaml like file."""
self.yaml_filename = filename
self._load(filename)
@ -189,6 +210,7 @@ class PhonopyYaml(object):
def parse(self):
"""Parse raw yaml data."""
self._parse_command_header()
self._parse_transformation_matrices()
self._parse_all_cells()
self._parse_force_constants()
@ -196,7 +218,7 @@ class PhonopyYaml(object):
self._parse_nac_params()
self._parse_calculator()
def set_phonon_info(self, phonopy):
def set_phonon_info(self, phonopy: "Phonopy"):
"""Collect data from Phonopy instance."""
self.unitcell = phonopy.unitcell
self.primitive = phonopy.primitive
@ -232,42 +254,44 @@ class PhonopyYaml(object):
lines.append("%s:" % self.command_name)
if self.version is None:
from phonopy.version import __version__
lines.append(" version: %s" % __version__)
else:
lines.append(" version: %s" % self.version)
if self.calculator:
lines.append(" calculator: %s" % self.calculator)
if self.frequency_unit_conversion_factor:
lines.append(" frequency_unit_conversion_factor: %f" %
self.frequency_unit_conversion_factor)
lines.append(
" frequency_unit_conversion_factor: %f"
% self.frequency_unit_conversion_factor
)
if self.symmetry:
lines.append(" symmetry_tolerance: %.5e" %
self.symmetry.tolerance)
lines.append(" symmetry_tolerance: %.5e" % self.symmetry.tolerance)
if self.nac_params:
lines.append(" nac_unit_conversion_factor: %f"
% self.nac_params['factor'])
lines.append(" nac_unit_conversion_factor: %f" % self.nac_params["factor"])
if self.configuration is not None:
lines.append(" configuration:")
for key in self.configuration:
val = self.configuration[key]
if type(val) is str:
val = val.replace('\\', '\\\\')
lines.append(" %s: \"%s\"" % (key, val))
val = val.replace("\\", "\\\\")
lines.append(' %s: "%s"' % (key, val))
lines.append("")
return lines
def _physical_units_yaml_lines(self):
lines = []
lines.append("physical_unit:")
lines.append(" atomic_mass: \"AMU\"")
lines.append(' atomic_mass: "AMU"')
units = self.physical_units
if units is not None:
if units['length_unit'] is not None:
lines.append(" length: \"%s\"" % units['length_unit'])
if (self.command_name == "phonopy" and
units['force_constants_unit'] is not None):
lines.append(" force_constants: \"%s\"" %
units['force_constants_unit'])
if units["length_unit"] is not None:
lines.append(' length: "%s"' % units["length_unit"])
if (
self.command_name == "phonopy"
and units["force_constants_unit"] is not None
):
lines.append(' force_constants: "%s"' % units["force_constants_unit"])
lines.append("")
return lines
@ -275,22 +299,22 @@ class PhonopyYaml(object):
lines = []
if self.symmetry is not None and self.symmetry.dataset is not None:
lines.append("space_group:")
lines.append(" type: \"%s\"" %
self.symmetry.get_dataset()['international'])
lines.append(" number: %d" %
self.symmetry.get_dataset()['number'])
hall_symbol = self.symmetry.get_dataset()['hall']
if "\"" in hall_symbol:
hall_symbol = hall_symbol.replace("\"", "\\\"")
lines.append(" Hall_symbol: \"%s\"" % hall_symbol)
lines.append(' type: "%s"' % self.symmetry.dataset["international"])
lines.append(" number: %d" % self.symmetry.dataset["number"])
hall_symbol = self.symmetry.dataset["hall"]
if '"' in hall_symbol:
hall_symbol = hall_symbol.replace('"', '\\"')
lines.append(' Hall_symbol: "%s"' % hall_symbol)
lines.append("")
return lines
def _cell_info_yaml_lines(self):
lines = self._primitive_matrix_yaml_lines(
self.primitive_matrix, "primitive_matrix")
self.primitive_matrix, "primitive_matrix"
)
lines += self._supercell_matrix_yaml_lines(
self.supercell_matrix, "supercell_matrix")
self.supercell_matrix, "supercell_matrix"
)
lines += self._primitive_yaml_lines(self.primitive, "primitive_cell")
lines += self._unitcell_yaml_lines()
lines += self._supercell_yaml_lines()
@ -317,30 +341,28 @@ class PhonopyYaml(object):
def _primitive_yaml_lines(self, primitive, name):
lines = []
if primitive is not None:
lines += self._cell_yaml_lines(
self.primitive, name, None)
lines += self._cell_yaml_lines(self.primitive, name, None)
lines.append(" reciprocal_lattice: # without 2pi")
rec_lat = np.linalg.inv(primitive.cell)
for v, a in zip(rec_lat.T, ('a*', 'b*', 'c*')):
lines.append(" - [ %21.15f, %21.15f, %21.15f ] # %s" %
(v[0], v[1], v[2], a))
for v, a in zip(rec_lat.T, ("a*", "b*", "c*")):
lines.append(
" - [ %21.15f, %21.15f, %21.15f ] # %s" % (v[0], v[1], v[2], a)
)
lines.append("")
return lines
def _unitcell_yaml_lines(self):
lines = []
if self.unitcell is not None:
lines += self._cell_yaml_lines(
self.unitcell, "unit_cell", self.u2p_map)
lines += self._cell_yaml_lines(self.unitcell, "unit_cell", self.u2p_map)
lines.append("")
return lines
def _supercell_yaml_lines(self):
lines = []
if self.supercell is not None:
s2p_map = getattr(self.primitive, 's2p_map', None)
lines += self._cell_yaml_lines(
self.supercell, "supercell", s2p_map)
s2p_map = getattr(self.primitive, "s2p_map", None)
lines += self._cell_yaml_lines(self.supercell, "supercell", s2p_map)
lines.append("")
return lines
@ -351,8 +373,7 @@ class PhonopyYaml(object):
for line in cell.get_yaml_lines():
lines.append(" " + line)
if map_to_primitive is not None and "mass" in line:
lines.append(" reduced_to: %d" %
(map_to_primitive[count] + 1))
lines.append(" reduced_to: %d" % (map_to_primitive[count] + 1))
count += 1
return lines
@ -365,116 +386,122 @@ class PhonopyYaml(object):
def _nac_yaml_lines_given_symbols(self, symbols):
lines = []
if self.nac_params is not None:
if self.settings['born_effective_charge']:
if self.settings["born_effective_charge"]:
lines.append("born_effective_charge:")
for i, z in enumerate(self.nac_params['born']):
for i, z in enumerate(self.nac_params["born"]):
text = "- # %d" % (i + 1)
if symbols:
text += " (%s)" % symbols[i]
lines.append(text)
for v in z:
lines.append(" - [ %18.15f, %18.15f, %18.15f ]" %
tuple(v))
lines.append(" - [ %18.15f, %18.15f, %18.15f ]" % tuple(v))
lines.append("")
if self.settings['dielectric_constant']:
if self.settings["dielectric_constant"]:
lines.append("dielectric_constant:")
for v in self.nac_params['dielectric']:
lines.append(
" - [ %18.15f, %18.15f, %18.15f ]" % tuple(v))
for v in self.nac_params["dielectric"]:
lines.append(" - [ %18.15f, %18.15f, %18.15f ]" % tuple(v))
lines.append("")
return lines
def _dataset_yaml_lines(self):
lines = []
if self.settings['force_sets'] or self.settings['displacements']:
if self.settings["force_sets"] or self.settings["displacements"]:
disp_yaml_lines = self._displacements_yaml_lines(
with_forces=self.settings['force_sets'])
with_forces=self.settings["force_sets"]
)
lines += disp_yaml_lines
return lines
def _displacements_yaml_lines(self, with_forces=False):
return self._displacements_yaml_lines_2types(
self.dataset, with_forces=with_forces)
self.dataset, with_forces=with_forces
)
def _displacements_yaml_lines_2types(self, dataset, with_forces=False):
if dataset is not None:
if 'first_atoms' in dataset:
if "first_atoms" in dataset:
return self._displacements_yaml_lines_type1(
dataset, with_forces=with_forces)
elif 'displacements' in dataset:
dataset, with_forces=with_forces
)
elif "displacements" in dataset:
return self._displacements_yaml_lines_type2(
dataset, with_forces=with_forces)
dataset, with_forces=with_forces
)
return []
def _displacements_yaml_lines_type1(self, dataset, with_forces=False):
lines = ["displacements:", ]
for i, d in enumerate(dataset['first_atoms']):
lines.append("- atom: %4d" % (d['number'] + 1))
lines = [
"displacements:",
]
for i, d in enumerate(dataset["first_atoms"]):
lines.append("- atom: %4d" % (d["number"] + 1))
lines.append(" displacement:")
lines.append(" [ %20.16f,%20.16f,%20.16f ]"
% tuple(d['displacement']))
if with_forces and 'forces' in d:
lines.append(" [ %20.16f,%20.16f,%20.16f ]" % tuple(d["displacement"]))
if with_forces and "forces" in d:
lines.append(" forces:")
for f in d['forces']:
for f in d["forces"]:
lines.append(" - [ %20.16f,%20.16f,%20.16f ]" % tuple(f))
lines.append("")
return lines
def _displacements_yaml_lines_type2(self, dataset, with_forces=False):
if 'random_seed' in dataset:
lines = ["random_seed: %d" % dataset['random_seed'],
"displacements:"]
if "random_seed" in dataset:
lines = ["random_seed: %d" % dataset["random_seed"], "displacements:"]
else:
lines = ["displacements:", ]
for i, dset in enumerate(dataset['displacements']):
lines = [
"displacements:",
]
for i, dset in enumerate(dataset["displacements"]):
lines.append("- # %4d" % (i + 1))
for j, d in enumerate(dset):
lines.append(" - displacement: # %d" % (j + 1))
lines.append(" [ %20.16f,%20.16f,%20.16f ]" % tuple(d))
if with_forces and 'forces' in dataset:
f = dataset['forces'][i][j]
if with_forces and "forces" in dataset:
f = dataset["forces"][i][j]
lines.append(" force:")
lines.append(" [ %20.16f,%20.16f,%20.16f ]"
% tuple(f))
lines.append(" [ %20.16f,%20.16f,%20.16f ]" % tuple(f))
lines.append("")
return lines
def _force_constants_yaml_lines(self):
lines = []
if (self.settings['force_constants'] and
self.force_constants is not None):
if self.settings["force_constants"] and self.force_constants is not None:
shape = self.force_constants.shape[:2]
lines = ["force_constants:", ]
lines = [
"force_constants:",
]
if shape[0] == shape[1]:
lines.append(" format: \"full\"")
lines.append(' format: "full"')
else:
lines.append(" format: \"compact\"")
lines.append(' format: "compact"')
lines.append(" shape: [ %d, %d ]" % shape)
lines.append(" elements:")
for (i, j) in list(np.ndindex(shape)):
lines.append(" - # (%d, %d)" % (i + 1, j + 1))
for v in self.force_constants[i, j]:
lines.append(" - [ %21.15f, %21.15f, %21.15f ]"
% tuple(v))
lines.append(" - [ %21.15f, %21.15f, %21.15f ]" % tuple(v))
return lines
def _load(self, filename):
_, ext = os.path.splitext(filename)
if ext == '.xz' or ext == '.lzma':
if ext == ".xz" or ext == ".lzma":
try:
import lzma
except ImportError:
raise("Reading a lzma compressed file is not supported "
"by this python version.")
raise (
"Reading a lzma compressed file is not supported "
"by this python version."
)
with lzma.open(filename) as f:
self._yaml = yaml.load(f, Loader=Loader)
elif ext == '.gz':
elif ext == ".gz":
import gzip
with gzip.open(filename) as f:
self._yaml = yaml.load(f, Loader=Loader)
else:
with open(filename, 'r') as f:
with open(filename, "r") as f:
self._yaml = yaml.load(f, Loader=Loader)
if type(self._yaml) is str:
@ -483,50 +510,58 @@ class PhonopyYaml(object):
self.parse()
def _parse_command_header(self):
if self.command_name in self._yaml:
header = self._yaml[self.command_name]
self.version = header["version"]
def _parse_transformation_matrices(self):
if 'supercell_matrix' in self._yaml:
self.supercell_matrix = np.array(self._yaml['supercell_matrix'],
dtype='intc', order='C')
if 'primitive_matrix' in self._yaml:
self.primitive_matrix = np.array(self._yaml['primitive_matrix'],
dtype='double', order='C')
if "supercell_matrix" in self._yaml:
self.supercell_matrix = np.array(
self._yaml["supercell_matrix"], dtype="intc", order="C"
)
if "primitive_matrix" in self._yaml:
self.primitive_matrix = np.array(
self._yaml["primitive_matrix"], dtype="double", order="C"
)
def _parse_all_cells(self):
if 'unit_cell' in self._yaml:
self.unitcell = self._parse_cell(self._yaml['unit_cell'])
if 'primitive_cell' in self._yaml:
self.primitive = self._parse_cell(self._yaml['primitive_cell'])
if 'supercell' in self._yaml:
self.supercell = self._parse_cell(self._yaml['supercell'])
if "unit_cell" in self._yaml:
self.unitcell = self._parse_cell(self._yaml["unit_cell"])
if "primitive_cell" in self._yaml:
self.primitive = self._parse_cell(self._yaml["primitive_cell"])
if "supercell" in self._yaml:
self.supercell = self._parse_cell(self._yaml["supercell"])
if self.unitcell is None:
if ('lattice' in self._yaml and
('points' in self._yaml or 'atoms' in self._yaml)):
if "lattice" in self._yaml and (
"points" in self._yaml or "atoms" in self._yaml
):
self.unitcell = self._parse_cell(self._yaml)
def _parse_cell(self, cell_yaml):
lattice = None
if 'lattice' in cell_yaml:
lattice = cell_yaml['lattice']
if "lattice" in cell_yaml:
lattice = cell_yaml["lattice"]
points = []
symbols = []
masses = []
if 'points' in cell_yaml:
for x in cell_yaml['points']:
if 'coordinates' in x:
points.append(x['coordinates'])
if 'symbol' in x:
symbols.append(x['symbol'])
if 'mass' in x:
masses.append(x['mass'])
if "points" in cell_yaml:
for x in cell_yaml["points"]:
if "coordinates" in x:
points.append(x["coordinates"])
if "symbol" in x:
symbols.append(x["symbol"])
if "mass" in x:
masses.append(x["mass"])
# For version < 1.10.9
elif 'atoms' in cell_yaml:
for x in cell_yaml['atoms']:
if 'coordinates' not in x and 'position' in x:
points.append(x['position'])
if 'symbol' in x:
symbols.append(x['symbol'])
if 'mass' in x:
masses.append(x['mass'])
elif "atoms" in cell_yaml:
for x in cell_yaml["atoms"]:
if "coordinates" not in x and "position" in x:
points.append(x["position"])
if "symbol" in x:
symbols.append(x["symbol"])
if "mass" in x:
masses.append(x["mass"])
return self._get_cell(lattice, points, symbols, masses=masses)
def _get_cell(self, lattice, points, symbols, masses=None):
@ -548,98 +583,106 @@ class PhonopyYaml(object):
_masses = None
if _lattice and _points and _symbols:
return PhonopyAtoms(symbols=_symbols,
return PhonopyAtoms(
symbols=_symbols,
cell=_lattice,
masses=_masses,
scaled_positions=_points)
scaled_positions=_points,
)
else:
return None
def _parse_force_constants(self):
if 'force_constants' in self._yaml:
shape = tuple(self._yaml['force_constants']['shape']) + (3, 3)
fc = np.reshape(self._yaml['force_constants']['elements'], shape)
self.force_constants = np.array(fc, dtype='double', order='C')
if "force_constants" in self._yaml:
shape = tuple(self._yaml["force_constants"]["shape"]) + (3, 3)
fc = np.reshape(self._yaml["force_constants"]["elements"], shape)
self.force_constants = np.array(fc, dtype="double", order="C")
def _parse_dataset(self):
self.dataset = self._get_dataset(self.supercell)
def _get_dataset(self, supercell):
dataset = None
if 'displacements' in self._yaml:
if "displacements" in self._yaml:
if supercell is not None:
natom = len(supercell)
else:
natom = None
disp = self._yaml['displacements'][0]
disp = self._yaml["displacements"][0]
if type(disp) is dict: # type1
dataset = self._parse_force_sets_type1(natom=natom)
elif type(disp) is list: # type2
if 'displacement' in disp[0]:
if "displacement" in disp[0]:
dataset = self._parse_force_sets_type2()
return dataset
def _parse_force_sets_type1(self, natom=None):
with_forces = False
if 'forces' in self._yaml['displacements'][0]:
if "forces" in self._yaml["displacements"][0]:
with_forces = True
dataset = {'natom': len(self._yaml['displacements'][0]['forces'])}
dataset = {"natom": len(self._yaml["displacements"][0]["forces"])}
elif natom is not None:
dataset = {'natom': natom}
elif 'natom' in self._yaml:
dataset = {'natom': self._yaml['natom']}
dataset = {"natom": natom}
elif "natom" in self._yaml:
dataset = {"natom": self._yaml["natom"]}
else:
raise RuntimeError(
"Number of atoms in supercell could not be found.")
raise RuntimeError("Number of atoms in supercell could not be found.")
first_atoms = []
for d in self._yaml['displacements']:
for d in self._yaml["displacements"]:
data = {
'number': d['atom'] - 1,
'displacement': np.array(d['displacement'], dtype='double')}
"number": d["atom"] - 1,
"displacement": np.array(d["displacement"], dtype="double"),
}
if with_forces:
data['forces'] = np.array(d['forces'],
dtype='double', order='C')
data["forces"] = np.array(d["forces"], dtype="double", order="C")
first_atoms.append(data)
dataset['first_atoms'] = first_atoms
dataset["first_atoms"] = first_atoms
return dataset
def _parse_force_sets_type2(self):
nsets = len(self._yaml['displacements'])
natom = len(self._yaml['displacements'][0])
if 'force' in self._yaml['displacements'][0][0]:
nsets = len(self._yaml["displacements"])
natom = len(self._yaml["displacements"][0])
if "force" in self._yaml["displacements"][0][0]:
with_forces = True
forces = np.zeros((nsets, natom, 3), dtype='double', order='C')
forces = np.zeros((nsets, natom, 3), dtype="double", order="C")
else:
with_forces = False
displacements = np.zeros((nsets, natom, 3), dtype='double', order='C')
for i, dfset in enumerate(self._yaml['displacements']):
displacements = np.zeros((nsets, natom, 3), dtype="double", order="C")
for i, dfset in enumerate(self._yaml["displacements"]):
for j, df in enumerate(dfset):
if with_forces:
forces[i, j] = df['force']
displacements[i, j] = df['displacement']
forces[i, j] = df["force"]
displacements[i, j] = df["displacement"]
if with_forces:
return {'forces': forces, 'displacements': displacements}
return {"forces": forces, "displacements": displacements}
else:
return {'displacements': displacements}
return {"displacements": displacements}
def _parse_nac_params(self):
nac_params = {}
if 'born_effective_charge' in self._yaml:
nac_params['born'] = np.array(self._yaml['born_effective_charge'],
dtype='double', order='C')
if 'dielectric_constant' in self._yaml:
nac_params['dielectric'] = np.array(
self._yaml['dielectric_constant'], dtype='double', order='C')
if (self.command_name in self._yaml and
'nac_unit_conversion_factor' in self._yaml[self.command_name]):
nac_params['factor'] = self._yaml[self.command_name][
'nac_unit_conversion_factor']
if 'born' in nac_params and 'dielectric' in nac_params:
if "born_effective_charge" in self._yaml:
nac_params["born"] = np.array(
self._yaml["born_effective_charge"], dtype="double", order="C"
)
if "dielectric_constant" in self._yaml:
nac_params["dielectric"] = np.array(
self._yaml["dielectric_constant"], dtype="double", order="C"
)
if (
self.command_name in self._yaml
and "nac_unit_conversion_factor" in self._yaml[self.command_name]
):
nac_params["factor"] = self._yaml[self.command_name][
"nac_unit_conversion_factor"
]
if "born" in nac_params and "dielectric" in nac_params:
self.nac_params = nac_params
def _parse_calculator(self):
if (self.command_name in self._yaml and
'calculator' in self._yaml[self.command_name]):
self.calculator = self._yaml[self.command_name]['calculator']
if (
self.command_name in self._yaml
and "calculator" in self._yaml[self.command_name]
):
self.calculator = self._yaml[self.command_name]["calculator"]

View File

@ -1,3 +1,4 @@
"""QE calculator interface."""
# Copyright (C) 2014 Atsushi Togo
# All rights reserved.
#
@ -33,41 +34,43 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from collections import OrderedDict
from phonopy.file_IO import (iter_collect_forces,
import numpy as np
from phonopy.file_IO import (
iter_collect_forces,
write_FORCE_CONSTANTS,
write_force_constants_to_hdf5,
write_FORCE_CONSTANTS)
)
from phonopy.harmonic.force_constants import distribute_force_constants_by_translations
from phonopy.interface.vasp import (
get_scaled_positions_lines, check_forces, get_drift_forces)
from phonopy.units import Bohr
check_forces,
get_drift_forces,
get_scaled_positions_lines,
)
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.atoms import symbol_map
from phonopy.structure.cells import get_supercell, get_primitive
from phonopy.harmonic.force_constants import (
distribute_force_constants_by_translations)
from phonopy.structure.cells import get_primitive, get_supercell
from phonopy.units import Bohr
def parse_set_of_forces(num_atoms,
forces_filenames,
verbose=True):
hook = 'Forces acting on atoms'
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
"""Parse forces from output files."""
hook = "Forces acting on atoms"
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):
if verbose:
sys.stdout.write("%d. " % (i + 1))
pwscf_forces = iter_collect_forces(filename,
num_atoms,
hook,
[6, 7, 8],
word='force')
pwscf_forces = iter_collect_forces(
filename, num_atoms, hook, [6, 7, 8], word="force"
)
if check_forces(pwscf_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(pwscf_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
pwscf_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(pwscf_forces) - drift_force)
else:
is_parsed = False
@ -79,15 +82,16 @@ def parse_set_of_forces(num_atoms,
def read_pwscf(filename):
"""Read crystal structure."""
with open(filename) as f:
pwscf_in = PwscfIn(f.readlines())
tags = pwscf_in.get_tags()
lattice = tags['cell_parameters']
positions = [pos[1] for pos in tags['atomic_positions']]
species = [pos[0] for pos in tags['atomic_positions']]
lattice = tags["cell_parameters"]
positions = [pos[1] for pos in tags["atomic_positions"]]
species = [pos[0] for pos in tags["atomic_positions"]]
mass_map = {}
pp_map = {}
for vals in tags['atomic_species']:
for vals in tags["atomic_species"]:
mass_map[vals[0]] = vals[1]
pp_map[vals[0]] = vals[2]
masses = [mass_map[x] for x in species]
@ -122,18 +126,15 @@ def read_pwscf(filename):
if n < 1:
numbers[i] = available_numbers[-n]
cell = Atoms(numbers=numbers,
masses=masses,
cell=lattice,
scaled_positions=positions)
cell = Atoms(
numbers=numbers, masses=masses, cell=lattice, scaled_positions=positions
)
else:
cell = Atoms(numbers=numbers,
cell=lattice,
scaled_positions=positions)
cell = Atoms(numbers=numbers, cell=lattice, scaled_positions=positions)
unique_symbols = []
pp_filenames = {}
for i, symbol in enumerate(cell.get_chemical_symbols()):
for i, symbol in enumerate(cell.symbols):
if symbol not in unique_symbols:
unique_symbols.append(symbol)
pp_filenames[symbol] = pp_all_filenames[i]
@ -142,28 +143,34 @@ def read_pwscf(filename):
def write_pwscf(filename, cell, pp_filenames):
f = open(filename, 'w')
"""Write cell to file."""
f = open(filename, "w")
f.write(get_pwscf_structure(cell, pp_filenames=pp_filenames))
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
pp_filenames,
pre_filename="supercell",
width=3):
width=3,
):
"""Write supercells with displacements to files."""
write_pwscf("%s.in" % pre_filename, supercell, pp_filenames)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.in".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_pwscf(filename, cell, pp_filenames)
def get_pwscf_structure(cell, pp_filenames=None):
lattice = cell.get_cell()
positions = cell.get_scaled_positions()
masses = cell.get_masses()
chemical_symbols = cell.get_chemical_symbols()
"""Return QE structure in text."""
lattice = cell.cell
positions = cell.scaled_positions
masses = cell.masses
chemical_symbols = cell.symbols
unique_symbols = []
atomic_species = []
for symbol, m in zip(chemical_symbols, masses):
@ -172,8 +179,10 @@ def get_pwscf_structure(cell, pp_filenames=None):
atomic_species.append((symbol, m))
lines = ""
lines += ("! ibrav = 0, nat = %d, ntyp = %d\n" %
(len(positions), len(unique_symbols)))
lines += "! ibrav = 0, nat = %d, ntyp = %d\n" % (
len(positions),
len(unique_symbols),
)
lines += "CELL_PARAMETERS bohr\n"
lines += ((" %21.16f" * 3 + "\n") * 3) % tuple(lattice.ravel())
lines += "ATOMIC_SPECIES\n"
@ -181,12 +190,11 @@ def get_pwscf_structure(cell, pp_filenames=None):
if pp_filenames is None:
lines += " %2s %10.5f %s_PP_filename\n" % (symbol, mass, symbol)
else:
lines += " %2s %10.5f %s\n" % (symbol, mass,
pp_filenames[symbol])
lines += " %2s %10.5f %s\n" % (symbol, mass, pp_filenames[symbol])
lines += "ATOMIC_POSITIONS crystal\n"
for i, (symbol, pos_line) in enumerate(zip(
chemical_symbols,
get_scaled_positions_lines(positions).split('\n'))):
for i, (symbol, pos_line) in enumerate(
zip(chemical_symbols, get_scaled_positions_lines(positions).split("\n"))
):
lines += (" %2s " % symbol) + pos_line
if i < len(chemical_symbols) - 1:
lines += "\n"
@ -194,23 +202,30 @@ def get_pwscf_structure(cell, pp_filenames=None):
return lines
class PwscfIn(object):
class PwscfIn:
"""Class to create QE input file."""
_set_methods = OrderedDict(
[('ibrav', '_set_ibrav'),
('celldm(1)', '_set_celldm1'),
('nat', '_set_nat'),
('ntyp', '_set_ntyp'),
('atomic_species', '_set_atom_types'),
('atomic_positions', '_set_positions'),
('cell_parameters', '_set_lattice')])
[
("ibrav", "_set_ibrav"),
("celldm(1)", "_set_celldm1"),
("nat", "_set_nat"),
("ntyp", "_set_ntyp"),
("atomic_species", "_set_atom_types"),
("atomic_positions", "_set_positions"),
("cell_parameters", "_set_lattice"),
]
)
def __init__(self, lines):
"""Init method."""
self._tags = {}
self._current_tag_name = None
self._values = None
self._collect(lines)
def get_tags(self):
"""Return tags."""
return self._tags
def _collect(self, lines):
@ -218,32 +233,36 @@ class PwscfIn(object):
tag_name = None
for line in lines:
_line = line.split('!')[0]
if ('atomic_positions' in _line.lower() or
'cell_parameters' in _line.lower()):
_line = line.split("!")[0]
if (
"atomic_positions" in _line.lower()
or "cell_parameters" in _line.lower()
):
if len(_line.split()) == 1:
raise RuntimeError(
"A unit has to be specified for %s." % _line.strip())
"A unit has to be specified for %s." % _line.strip()
)
else:
words = _line.split()[:2]
elif 'atomic_species' in _line.lower():
elif "atomic_species" in _line.lower():
words = _line.split()
else: # other tag names and values
line_replaced = _line.replace('=', ' ').replace(',', ' ')
line_replaced = _line.replace("=", " ").replace(",", " ")
words = line_replaced.split()
for val in words:
if val.lower() in self._set_methods: # tag name
tag_name = val.lower()
elements[tag_name] = [val, ]
elements[tag_name] = [
val,
]
elif tag_name is not None: # Ensure some tag name is set.
elements[tag_name].append(val)
# Check if some necessary tag_names exist in elements keys.
for tag_name in ['ibrav', 'nat', 'ntyp']:
for tag_name in ["ibrav", "nat", "ntyp"]:
if tag_name not in elements:
raise RuntimeError(
"%s is not found in the input file." % tag_name)
raise RuntimeError("%s is not found in the input file." % tag_name)
# Set values in self._tags[tag_name]
for tag_name in self._set_methods:
@ -256,19 +275,18 @@ class PwscfIn(object):
def _set_ibrav(self):
ibrav = int(self._values[0])
if ibrav != 0:
raise RuntimeError("Only %s = 0 is supported."
% self._current_tag_name)
raise RuntimeError("Only %s = 0 is supported." % self._current_tag_name)
self._tags['ibrav'] = ibrav
self._tags["ibrav"] = ibrav
def _set_celldm1(self):
self._tags['celldm(1)'] = float(self._values[0])
self._tags["celldm(1)"] = float(self._values[0])
def _set_nat(self):
self._tags['nat'] = int(self._values[0])
self._tags["nat"] = int(self._values[0])
def _set_ntyp(self):
self._tags['ntyp'] = int(self._values[0])
self._tags["ntyp"] = int(self._values[0])
def _set_lattice(self):
"""Calculate and set lattice parameters.
@ -276,35 +294,33 @@ class PwscfIn(object):
Invoked by CELL_PARAMETERS tag_name.
"""
unit = self._values[0].lower()
if unit == 'alat':
if not self._tags['celldm(1)']:
raise RuntimeError(
"celldm(1) has to be specified when using alat.")
if unit == "alat":
if not self._tags["celldm(1)"]:
raise RuntimeError("celldm(1) has to be specified when using alat.")
else:
factor = self._tags['celldm(1)'] # in Bohr
elif 'angstrom' in unit:
factor = self._tags["celldm(1)"] # in Bohr
elif "angstrom" in unit:
factor = 1.0 / Bohr
elif 'bohr' in unit:
elif "bohr" in unit:
factor = 1.0
else:
raise RuntimeError(
"As a unit, alat, angstrom, and bohr can be only used.")
raise RuntimeError("As a unit, alat, angstrom, and bohr can be only used.")
if len(self._values[1:]) < 9:
raise RuntimeError("%s is wrongly set." % self._current_tag_name)
lattice = np.reshape([float(x) for x in self._values[1:10]], (3, 3))
self._tags['cell_parameters'] = lattice * factor
self._tags["cell_parameters"] = lattice * factor
def _set_positions(self):
unit = self._values[0].lower()
if 'crystal' not in unit:
raise RuntimeError("Only ATOMIC_POSITIONS format with "
"crystal coordinates is supported.")
if "crystal" not in unit:
raise RuntimeError(
"Only ATOMIC_POSITIONS format with " "crystal coordinates is supported."
)
natom = self._tags['nat']
natom = self._tags["nat"]
pos_vals = self._values[1:]
if len(pos_vals) < natom * 4:
raise RuntimeError("ATOMIC_POSITIONS is wrongly set.")
@ -312,29 +328,34 @@ class PwscfIn(object):
positions = []
for i in range(natom):
positions.append(
[pos_vals[i * 4],
[float(x) for x in pos_vals[i * 4 + 1:i * 4 + 4]]])
[pos_vals[i * 4], [float(x) for x in pos_vals[i * 4 + 1 : i * 4 + 4]]]
)
self._tags['atomic_positions'] = positions
self._tags["atomic_positions"] = positions
def _set_atom_types(self):
num_types = self._tags['ntyp']
num_types = self._tags["ntyp"]
if len(self._values) < num_types * 3:
raise RuntimeError("%s is wrongly set." % self._current_tag_name)
species = []
for i in range(num_types):
species.append([self._values[i * 3],
species.append(
[
self._values[i * 3],
float(self._values[i * 3 + 1]),
self._values[i * 3 + 2]])
self._values[i * 3 + 2],
]
)
self._tags['atomic_species'] = species
self._tags["atomic_species"] = species
class PH_Q2R(object):
"""Parse QE/q2r output and create supercell force constants array
that is readable by phonopy. A simple usage is as follows:
class PH_Q2R:
"""Parse QE/q2r output and create supercell force constants array.
A simple usage is as follows:
---------
#!/usr/bin/env python
@ -397,6 +418,7 @@ class PH_Q2R(object):
"""
def __init__(self, filename, symprec=1e-5):
"""Init method."""
self.fc = None
self.dimension = None
self.epsilon = None
@ -407,7 +429,7 @@ class PH_Q2R(object):
self._filename = filename
def run(self, cell, is_full_fc=False, parse_fc=True):
"""Make supercell force constants readable for phonopy
"""Make supercell force constants readable for phonopy.
Note
----
@ -427,28 +449,26 @@ class PH_Q2R(object):
False may be used when expected to parse only epsilon and born.
"""
with open(self._filename) as f:
fc_dct = self._parse_q2r(f)
self.dimension = fc_dct['dimension']
self.epsilon = fc_dct['dielectric']
self.borns = fc_dct['born']
self.dimension = fc_dct["dimension"]
self.epsilon = fc_dct["dielectric"]
self.borns = fc_dct["born"]
if parse_fc:
(self.fc,
self.primitive,
self.supercell) = self._arrange_supercell_fc(
cell, fc_dct['fc'], is_full_fc=is_full_fc)
(self.fc, self.primitive, self.supercell) = self._arrange_supercell_fc(
cell, fc_dct["fc"], is_full_fc=is_full_fc
)
def write_force_constants(self, fc_format='hdf5'):
def write_force_constants(self, fc_format="hdf5"):
"""Write force constatns to file in hdf5."""
if self.fc is not None:
if fc_format == 'hdf5':
p2s_map = self.primitive.get_primitive_to_supercell_map()
write_force_constants_to_hdf5(self.fc, p2s_map=p2s_map)
if fc_format == "hdf5":
write_force_constants_to_hdf5(self.fc, p2s_map=self.primitive.p2s_map)
else:
write_FORCE_CONSTANTS(self.fc)
def _parse_q2r(self, f):
"""Parse q2r output file
"""Parse q2r output file.
The format of q2r output is described at the mailing list below:
http://www.democritos.it/pipermail/pw_forum/2005-April/002408.html
@ -457,12 +477,13 @@ class PH_Q2R(object):
https://www.mail-archive.com/pw_forum@pwscf.org/msg24388.html
"""
natom, dim, epsilon, borns = self._parse_parameters(f)
fc_dct = {'fc': self._parse_fc(f, natom, dim),
'dimension': dim,
'dielectric': epsilon,
'born': borns}
fc_dct = {
"fc": self._parse_fc(f, natom, dim),
"dimension": dim,
"dielectric": epsilon,
"born": borns,
}
return fc_dct
def _parse_parameters(self, f):
@ -474,19 +495,19 @@ class PH_Q2R(object):
for i in range(ntype + natom):
line = f.readline()
line = f.readline()
if line.strip() == 'T':
if line.strip() == "T":
epsilon, borns = self._parse_born(f, natom)
else:
epsilon = None
borns = None
line = f.readline()
dim = np.array([int(x) for x in line.split()], dtype='intc')
dim = np.array([int(x) for x in line.split()], dtype="intc")
return natom, dim, epsilon, borns
def _parse_born(self, f, natom):
epsilon = np.zeros((3, 3), dtype='double', order='C')
borns = np.zeros((natom, 3, 3), dtype='double', order='C')
epsilon = np.zeros((3, 3), dtype="double", order="C")
borns = np.zeros((natom, 3, 3), dtype="double", order="C")
for i in range(3):
line = f.readline()
epsilon[i, :] = [float(x) for x in line.split()]
@ -498,14 +519,13 @@ class PH_Q2R(object):
return epsilon, borns
def _parse_fc(self, f, natom, dim):
"""Parse force constants part
"""Parse force constants part.
Physical unit of force cosntants in the file is Ry/au^2.
"""
ndim = np.prod(dim)
fc = np.zeros((natom, natom * ndim, 3, 3), dtype='double', order='C')
fc = np.zeros((natom, natom * ndim, 3, 3), dtype="double", order="C")
for k, l, i, j in np.ndindex((3, 3, natom, natom)):
line = f.readline()
for i_dim in range(ndim):
@ -525,22 +545,20 @@ class PH_Q2R(object):
assert (np.abs(diff) < 1e-8).all()
assert scell.get_number_of_atoms() == len(q2r_spos)
site_map = self._get_site_mapping(scell.get_scaled_positions(),
q2r_spos,
scell.get_cell())
site_map = self._get_site_mapping(
scell.get_scaled_positions(), q2r_spos, scell.get_cell()
)
natom = pcell.get_number_of_atoms()
ndim = np.prod(dim)
natom_s = natom * ndim
if is_full_fc:
fc = np.zeros((natom_s, natom_s, 3, 3), dtype='double', order='C')
fc = np.zeros((natom_s, natom_s, 3, 3), dtype="double", order="C")
p2s = pcell.get_primitive_to_supercell_map()
fc[p2s, :] = q2r_fc[:, site_map]
distribute_force_constants_by_translations(fc,
pcell,
scell)
distribute_force_constants_by_translations(fc, pcell, scell)
else:
fc = np.zeros((natom, natom_s, 3, 3), dtype='double', order='C')
fc = np.zeros((natom, natom_s, 3, 3), dtype="double", order="C")
fc[:, :] = q2r_fc[:, site_map]
return fc, pcell, scell
@ -549,10 +567,10 @@ class PH_Q2R(object):
dim = self.dimension
natom = cell.get_number_of_atoms()
ndim = np.prod(dim)
spos = np.zeros((natom * np.prod(dim), 3), dtype='double', order='C')
spos = np.zeros((natom * np.prod(dim), 3), dtype="double", order="C")
trans = [x[::-1] for x in np.ndindex(tuple(dim[::-1]))]
for i, p in enumerate(cell.get_scaled_positions()):
spos[i * ndim:(i + 1) * ndim] = (trans + p) / dim
spos[i * ndim : (i + 1) * ndim] = (trans + p) / dim
return spos
def _get_site_mapping(self, spos, q2r_spos, lattice):

View File

@ -1,3 +1,4 @@
"""SIESTA calculator interface."""
# Copyright (C) 2015 Henrique Pereira Coutada Miranda
# All rights reserved.
#
@ -32,32 +33,32 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
import re
import sys
import numpy as np
from phonopy.file_IO import iter_collect_forces
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.units import Bohr
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.units import Bohr
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
hook = '' # Just for skipping the first line
"""Parse forces from output files."""
hook = "" # Just for skipping the first line
is_parsed = True
force_sets = []
for i, filename in enumerate(forces_filenames):
if verbose:
sys.stdout.write("%d. " % (i + 1))
siesta_forces = iter_collect_forces(filename,
num_atoms,
hook,
[1, 2, 3],
word='')
siesta_forces = iter_collect_forces(
filename, num_atoms, hook, [1, 2, 3], word=""
)
if check_forces(siesta_forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(siesta_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
siesta_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(siesta_forces) - drift_force)
else:
is_parsed = False
@ -69,6 +70,7 @@ def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
def read_siesta(filename):
"""Read crystal structure."""
siesta_in = SiestaIn(open(filename).read())
numbers = siesta_in._tags["atomicnumbers"]
alat = siesta_in._tags["latticeconstant"]
@ -87,32 +89,35 @@ def read_siesta(filename):
elif coordformat == "notscaledcartesianbohr" or coordformat == "bohr":
cell.set_positions(np.array(positions))
else:
print("The format %s for the AtomicCoordinatesFormat is not "
"implemented." % coordformat)
print(
"The format %s for the AtomicCoordinatesFormat is not "
"implemented." % coordformat
)
sys.exit(1)
return cell, atypes
def write_siesta(filename, cell, atypes):
with open(filename, 'w') as w:
"""Write cell to file."""
with open(filename, "w") as w:
w.write(get_siesta_structure(cell, atypes))
def write_supercells_with_displacements(supercell,
cells_with_displacements,
ids,
atypes,
pre_filename="supercell",
width=3):
def write_supercells_with_displacements(
supercell, cells_with_displacements, ids, atypes, pre_filename="supercell", width=3
):
"""Write supercells with displacements to files."""
write_siesta("%s.fdf" % pre_filename, supercell, atypes)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}.fdf".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_siesta(filename, cell, atypes)
def get_siesta_structure(cell,atypes):
def get_siesta_structure(cell, atypes):
"""Return SIESTA structure in text."""
lattice = cell.get_cell()
positions = cell.get_scaled_positions()
chemical_symbols = cell.get_chemical_symbols()
@ -131,100 +136,119 @@ def get_siesta_structure(cell,atypes):
lines += "%block AtomicCoordinatesAndAtomicSpecies\n"
for pos, i in zip(positions, chemical_symbols):
lines += ("%21.16lf"*3+" %d\n") % tuple(pos.tolist()+[atypes[i]])
lines += ("%21.16lf" * 3 + " %d\n") % tuple(pos.tolist() + [atypes[i]])
lines += "%endblock AtomicCoordinatesAndAtomicSpecies\n"
return lines
class SiestaIn(object):
_num_regex = '([+-]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?)'
_tags = { "latticeconstant": 1.0,
class SiestaIn:
"""Class to create SIESTA input file."""
_num_regex = r"([+-]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?)"
_tags = {
"latticeconstant": 1.0,
"latticeconstantunit": None,
"chemicalspecieslabel": None,
"atomiccoordinatesformat": None,
"atomicnumbers": None,
"atomicspecies": None,
"atomiccoordinates": None }
"atomiccoordinates": None,
}
def __init__(self, lines):
"""Init method."""
self._collect(lines)
def _collect(self, lines):
""" This routine reads the following from the Siesta file:
"""Collect values.
This routine reads the following from the Siesta file:
- atomic positions
- cell_parameters
- atomic_species
"""
for tag, value, unit in re.findall(
r'([\.A-Za-z]+)\s+%s\s+([A-Za-z]+)?' %
self._num_regex, lines):
r"([\.A-Za-z]+)\s+%s\s+([A-Za-z]+)?" % self._num_regex, lines
):
tag = tag.lower()
unit = unit.lower()
if tag == "latticeconstant":
self._tags['latticeconstantunit'] = unit.capitalize()
if unit == 'ang':
self._tags["latticeconstantunit"] = unit.capitalize()
if unit == "ang":
self._tags[tag] = float(value) / Bohr
elif unit == 'bohr':
elif unit == "bohr":
self._tags[tag] = float(value)
else:
raise ValueError(
'Unknown LatticeConstant unit: {}'.format(unit))
raise ValueError("Unknown LatticeConstant unit: {}".format(unit))
for tag, value in re.findall(r'([\.A-Za-z]+)[ \t]+([a-zA-Z]+)', lines):
tag = tag.replace('_', '').lower()
for tag, value in re.findall(r"([\.A-Za-z]+)[ \t]+([a-zA-Z]+)", lines):
tag = tag.replace("_", "").lower()
if tag == "atomiccoordinatesformat":
self._tags[tag] = value.strip().lower()
# check if the necessary tags are present
self.check_present('atomiccoordinatesformat')
acell = self._tags['latticeconstant']
self._check_present("atomiccoordinatesformat")
acell = self._tags["latticeconstant"]
# capture the blocks
blocks = re.findall(
r'%block\s+([A-Za-z_]+)\s*\n((?:.+\n)+?(?=(?:\s+)?%endblock))',
lines, re.MULTILINE)
r"%block\s+([A-Za-z_]+)\s*\n((?:.+\n)+?(?=(?:\s+)?%endblock))",
lines,
re.MULTILINE,
)
for tag, block in blocks:
tag = tag.replace('_','').lower()
tag = tag.replace("_", "").lower()
if tag == "chemicalspecieslabel":
block_array = block.split('\n')[:-1]
block_array = block.split("\n")[:-1]
self._tags["atomicnumbers"] = dict(
[map(int, species.split()[:2]) for species in block_array])
[map(int, species.split()[:2]) for species in block_array]
)
self._tags[tag] = dict(
[(lambda x: (x[2], int(x[0])))(species.split())
for species in block_array])
[
(lambda x: (x[2], int(x[0])))(species.split())
for species in block_array
]
)
elif tag == "latticevectors":
self._tags[tag] = [[float(v)*acell for v in vector.split()]
for vector in block.split('\n')[:3]]
self._tags[tag] = [
[float(v) * acell for v in vector.split()]
for vector in block.split("\n")[:3]
]
elif tag == "atomiccoordinatesandatomicspecies":
block_array = block.split('\n')[:-1]
block_array = block.split("\n")[:-1]
self._tags["atomiccoordinates"] = [
[float(x) for x in atom.split()[:3]] for atom in block_array]
self._tags["atomicspecies"] = [int(atom.split()[3])
for atom in block_array]
[float(x) for x in atom.split()[:3]] for atom in block_array
]
self._tags["atomicspecies"] = [
int(atom.split()[3]) for atom in block_array
]
# check if the block are present
self.check_present("atomicspecies")
self.check_present("atomiccoordinates")
self.check_present("latticevectors")
self.check_present("chemicalspecieslabel")
self._check_present("atomicspecies")
self._check_present("atomiccoordinates")
self._check_present("latticevectors")
self._check_present("chemicalspecieslabel")
# translate the atomicspecies to atomic numbers
self._tags["atomicnumbers"] = [
self._tags["atomicnumbers"][atype]
for atype in self._tags["atomicspecies"]]
self._tags["atomicnumbers"][atype] for atype in self._tags["atomicspecies"]
]
def check_present(self, tag):
def _check_present(self, tag):
if not self._tags[tag]:
print("%s not present" % tag)
sys.exit(1)
def __str__(self):
"""Return tags."""
return self._tags
if __name__ == '__main__':
if __name__ == "__main__":
from phonopy.structure.symmetry import Symmetry
cell, atypes = read_siesta(sys.argv[1])
symmetry = Symmetry(cell)
print("# %s" % symmetry.get_international_table())

View File

@ -1,3 +1,4 @@
"""CRYSTAL calculator interface."""
# Copyright (C) 2019 Antti J. Karttunen (antti.j.karttunen@iki.fi)
# All rights reserved.
#
@ -32,36 +33,36 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import sys
import os
import sys
import numpy as np
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
def parse_set_of_forces(num_atoms,
forces_filenames,
verbose=True):
def parse_set_of_forces(num_atoms, forces_filenames, verbose=True):
"""Parse forces from output files."""
# Filenames = subdirectories supercell-001, supercell-002, ...
force_sets = []
for i, filename in enumerate(forces_filenames):
if verbose:
sys.stdout.write("%d. " % (i + 1))
f_gradient = open(os.path.join(filename, 'gradient'))
f_gradient = open(os.path.join(filename, "gradient"))
lines = f_gradient.readlines()
f_gradient.close()
# Structure of the gradient file:
#$grad cartesian gradients
# $grad cartesian gradients
# cycle = 1 SCF energy = -578.5931883878 |dE/dxyz| = 0.000007
# coordinates (num_atoms lines)
# gradients (num_atoms lines)
#$end
# $end
turbomole_forces = []
for line in lines[2 + num_atoms : 2 + 2 * num_atoms]:
# Replace D with E in double precision floats
turbomole_forces.append([float(x.replace('D', 'E')) for x in line.split()])
turbomole_forces.append([float(x.replace("D", "E")) for x in line.split()])
# Change from gradient to force by inverting the sign
# Units: hartree / Bohr
@ -69,9 +70,9 @@ def parse_set_of_forces(num_atoms,
if check_forces(turbomole_forces, num_atoms, filename, verbose=verbose):
is_parsed = True
drift_force = get_drift_forces(turbomole_forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(
turbomole_forces, filename=filename, verbose=verbose
)
force_sets.append(np.array(turbomole_forces) - drift_force)
else:
is_parsed = False
@ -83,138 +84,155 @@ def parse_set_of_forces(num_atoms,
def read_turbomole(filename):
"""Read crystal structure."""
# filename is typically "control"
f_turbomole = open(filename)
turbomole_in = TurbomoleIn(f_turbomole.readlines())
f_turbomole.close()
tags = turbomole_in.get_tags()
cell = Atoms(cell=tags['lattice_vectors'],
symbols=tags['atomic_species'],
positions=tags['coordinates'])
cell = Atoms(
cell=tags["lattice_vectors"],
symbols=tags["atomic_species"],
positions=tags["coordinates"],
)
return cell
def write_turbomole(filename, cell):
"""Write cell to file."""
# Write geometry in a new directory
# Check if directory exists (directory supercell will already exist for phono3py)
if not os.path.exists(filename):
os.mkdir(filename)
# Create control file
lines = '$title ' + filename + '\n'
lines += '$symmetry c1\n'
lines += '$coord file=coord\n'
lines += '$periodic 3\n'
lines += '$kpoints\n'
lines += ' nkpoints KPOINTS_HERE\n'
lines += '$scfconv 10\n'
lines += '$lattice\n'
lines = "$title " + filename + "\n"
lines += "$symmetry c1\n"
lines += "$coord file=coord\n"
lines += "$periodic 3\n"
lines += "$kpoints\n"
lines += " nkpoints KPOINTS_HERE\n"
lines += "$scfconv 10\n"
lines += "$lattice\n"
lattice = cell.get_cell()
for lattvec in lattice:
lines += ("%12.8f" * 3 + "\n") % tuple(lattvec)
lines += '$end\n'
f_control = open(os.path.join(filename, 'control'), 'w')
lines += "$end\n"
f_control = open(os.path.join(filename, "control"), "w")
f_control.write(lines)
f_control.close()
# Create coord file
symbols = cell.get_chemical_symbols()
positions = cell.get_positions()
lines = '$coord\n'
lines = "$coord\n"
for atom, pos in zip(symbols, positions):
lines += ("%16.12f"*3 + " %s\n") % (pos[0], pos[1], pos[2], atom.lower())
lines += '$end\n'
f_coord = open(os.path.join(filename, 'coord'), 'w')
lines += ("%16.12f" * 3 + " %s\n") % (pos[0], pos[1], pos[2], atom.lower())
lines += "$end\n"
f_coord = open(os.path.join(filename, "coord"), "w")
f_coord.write(lines)
f_coord.close()
def write_supercells_with_displacements(supercell,
cells_with_displacements,
ids,
pre_filename="supercell",
width=3):
def write_supercells_with_displacements(
supercell, cells_with_displacements, ids, pre_filename="supercell", width=3
):
"""Write supercells with displacements to files."""
write_turbomole(pre_filename, supercell)
for i, cell in zip(ids, cells_with_displacements):
filename = "{pre_filename}-{0:0{width}}".format(
i, pre_filename=pre_filename, width=width)
i, pre_filename=pre_filename, width=width
)
write_turbomole(filename, cell)
class TurbomoleIn:
"""Class to create TURBOMOLE input file."""
def __init__(self, lines):
self._tags = {'lattice_vectors': None,
'atomic_species': None,
'coordinates': None}
"""Init method."""
self._tags = {
"lattice_vectors": None,
"atomic_species": None,
"coordinates": None,
}
self._values = None
self._collect(lines)
def get_tags(self):
"""Return tags."""
return self._tags
def _collect(self, lines):
# Reads TURBOMOLE control and coord files (lattice vectors, cartesian atomic positions).
l = 0
# Reads TURBOMOLE control and coord files
# (lattice vectors, cartesian atomic positions).
ll = 0
lattvecs = []
aspecies = []
coords = []
while l < len(lines):
line = lines[l]
while ll < len(lines):
line = lines[ll]
# Look for lattice vectors
# Only supports atomic units, $lattice angs not supported
if '$lattice' in line:
if "$lattice" in line:
# 0.00000000000 5.17898186576 5.17898186576
for lattvec in lines[l+1:l+4]:
for lattvec in lines[ll + 1 : ll + 4]:
lattvecs.append([float(x) for x in lattvec.split()])
l += 4
# Look for Cartesian coordinates. They can be in another file or embedded in control:
#1) $coord file=coord
#2) $coord
ll += 4
# Look for Cartesian coordinates.
# They can be in another file or embedded in control:
# 1) $coord file=coord
# 2) $coord
# 2.58949092075 2.58949092075 2.58949092075 si
elif '$coord' in line:
if line.strip() == '$coord':
elif "$coord" in line:
if line.strip() == "$coord":
# Embdedded coordinates.
l += 1
while l < len(lines):
atom = lines[l].split()
ll += 1
while ll < len(lines):
atom = lines[ll].split()
if len(atom) == 4:
coords.append([float(x) for x in atom[0:3]])
aspecies.append(atom[3].title()) # Convert si to Si, c to C, etc.
l += 1
aspecies.append(
atom[3].title()
) # Convert si to Si, c to C, etc.
ll += 1
else:
# End of $coord, go back to the main while loop to interpret the current line
# End of $coord, go back to the main while loop to interpret
# the current line
break
elif line.find('file=') > 6:
elif line.find("file=") > 6:
# Cross-reference to another file
coordfile = line.split('=')[1].strip()
coordfile = line.split("=")[1].strip()
f_coord = open(coordfile)
for coordline in f_coord:
# 2.58949092075 2.58949092075 2.58949092075 si
atom = coordline.split()
if len(atom) == 4:
coords.append([float(x) for x in atom[0:3]])
aspecies.append(atom[3].title()) # Convert si to Si, c to C, etc.
aspecies.append(
atom[3].title()
) # Convert si to Si, c to C, etc.
f_coord.close()
l += 1
ll += 1
else:
# $coordinateupdate or invalid $coord line
l += 1
ll += 1
else:
l += 1
ll += 1
if len(lattvecs) == 3 and len(aspecies) > 0 and len(aspecies) == len(coords):
self._tags['lattice_vectors'] = lattvecs
self._tags['atomic_species'] = aspecies
self._tags['coordinates'] = coords
self._tags["lattice_vectors"] = lattvecs
self._tags["atomic_species"] = aspecies
self._tags["coordinates"] = coords
else:
print("TURBOMOLE-interface: Error parsing TURBOMOLE output file")
if __name__ == '__main__':
if __name__ == "__main__":
from phonopy.structure.symmetry import Symmetry
cell = read_turbomole(sys.argv[1])
symmetry = Symmetry(cell)
print("# %s" % symmetry.get_international_table())

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
"""Wien2k calculator interface."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -33,20 +34,25 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import numpy as np
from phonopy.interface.vasp import get_drift_forces, check_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.symmetry import Symmetry
from phonopy.structure.cells import get_angles, get_cell_parameters
from phonopy.harmonic.force_constants import similarity_transformation
from phonopy.interface.vasp import check_forces, get_drift_forces
from phonopy.structure.atoms import PhonopyAtoms as Atoms
from phonopy.structure.cells import get_angles, get_cell_parameters
from phonopy.structure.symmetry import Symmetry
def parse_set_of_forces(disps,
def parse_set_of_forces(
disps,
forces_filenames,
supercell,
wien2k_P1_mode=False, # Only for the test
symmetry_tolerance=None,
verbose=True):
verbose=True,
):
"""Parse forces from output files."""
if symmetry_tolerance is None:
symprec = 1e-5
else:
@ -65,11 +71,8 @@ def parse_set_of_forces(disps,
wien2k_forces = _get_forces_wien2k(filename, lattice)
if not wien2k_P1_mode:
forces = _distribute_forces(
supercell,
disp,
wien2k_forces,
filename,
symprec)
supercell, disp, wien2k_forces, filename, symprec
)
else:
if num_atoms != len(wien2k_forces):
forces = []
@ -77,9 +80,7 @@ def parse_set_of_forces(disps,
forces = wien2k_forces
if check_forces(forces, num_atoms, filename, verbose=verbose):
drift_force = get_drift_forces(forces,
filename=filename,
verbose=verbose)
drift_force = get_drift_forces(forces, filename=filename, verbose=verbose)
force_sets.append(np.array(forces) - drift_force)
else:
is_parsed = False
@ -91,9 +92,10 @@ def parse_set_of_forces(disps,
def parse_wien2k_struct(filename):
"""Read crystal structure."""
with open(filename) as f:
# 1
title = f.readline().rstrip()
_ = f.readline().rstrip()
# 2
num_site = int(f.readline()[27:30])
@ -154,51 +156,50 @@ def parse_wien2k_struct(filename):
for j in range(3):
f.readline()
cell = Atoms(symbols=symbols,
scaled_positions=positions,
cell=lattice)
cell = Atoms(symbols=symbols, scaled_positions=positions, cell=lattice)
return cell, npts, r0s, rmts
def write_supercells_with_displacements(supercell,
def write_supercells_with_displacements(
supercell,
cells_with_displacements,
ids,
npts, r0s, rmts,
npts,
r0s,
rmts,
num_unitcells_in_supercell,
pre_filename="wien2k",
width=3):
width=3,
):
"""Write supercells with displacements to files."""
npts_super = []
r0s_super = []
rmts_super = []
for i, j, k in zip(npts, r0s, rmts):
for l in range(num_unitcells_in_supercell):
for _ in range(num_unitcells_in_supercell):
npts_super.append(i)
r0s_super.append(j)
rmts_super.append(k)
_pre_filename = pre_filename.split('/')[-1] + "S"
write_wein2k(_pre_filename,
supercell,
npts_super,
r0s_super,
rmts_super)
_pre_filename = pre_filename.split("/")[-1] + "S"
write_wein2k(_pre_filename, supercell, npts_super, r0s_super, rmts_super)
for i, cell in zip(ids, cells_with_displacements):
symmetry = Symmetry(cell)
filename = "{pre_filename}-{0:0{width}}.in".format(
i, pre_filename=_pre_filename, width=width)
print("Number of non-equivalent atoms in %s: %d" %
(filename, len(symmetry.get_independent_atoms())))
write_wein2k(filename,
cell,
npts_super,
r0s_super,
rmts_super)
i, pre_filename=_pre_filename, width=width
)
print(
"Number of non-equivalent atoms in %s: %d"
% (filename, len(symmetry.get_independent_atoms()))
)
write_wein2k(filename, cell, npts_super, r0s_super, rmts_super)
def write_wein2k(filename, cell, npts, r0s, rmts):
with open(filename, 'w') as w:
"""Write cell to file."""
with open(filename, "w") as w:
w.write(_get_wien2k_struct(cell, npts, r0s, rmts))
@ -223,8 +224,7 @@ def _get_wien2k_struct(cell, npts, r0s, rmts):
text += "%13s%4s\n" % ("MODE OF CALC=", "RELA")
# 4
text += "%10.6f%10.6f%10.6f%10.6f%10.6f%10.6f\n" % (
a, b, c, alpha, beta, gamma)
text += "%10.6f%10.6f%10.6f%10.6f%10.6f%10.6f\n" % (a, b, c, alpha, beta, gamma)
for i, pos in enumerate(positions):
for j in (0, 1, 2):
@ -235,7 +235,15 @@ def _get_wien2k_struct(cell, npts, r0s, rmts):
# 5 format (4X,I4,4X,F10.8,3X,F10.8,3X,F10.8)
text += "%4s%4d%4s%10.8f%3s%10.8f%3s%10.8f\n" % (
"ATOM", -(i + 1), ": X=", pos[0], " Y=", pos[1], " Z=", pos[2])
"ATOM",
-(i + 1),
": X=",
pos[0],
" Y=",
pos[1],
" Z=",
pos[2],
)
# 6 format (15X,I2,17X,I2)
text += "%15s%2d%17s%2d\n" % ("MULT=", 1, "ISPLIT=", 8)
@ -245,7 +253,16 @@ def _get_wien2k_struct(cell, npts, r0s, rmts):
r0 = r0s[i]
rmt = rmts[i]
text += "%-10s%5s%5d%5s%10.8f%5s%10.5f%5s%5.1f\n" % (
symbols[i], "NPT=", npt, "R0=", r0, "RMT=", rmt, "Z:", numbers[i])
symbols[i],
"NPT=",
npt,
"R0=",
r0,
"RMT=",
rmt,
"Z:",
numbers[i],
)
# 8 - 10 format (20X,3F10.7)
text += "%-20s%10.7f%10.7f%10.7f\n" % ("LOCAL ROT MATRIX:", 1, 0, 0)
@ -269,7 +286,7 @@ def _transform_axis(alpha, beta, gamma, a, b, c):
az = np.cos(beta) * a
ay = (np.cos(gamma) - np.cos(beta) * np.cos(alpha)) / np.sin(alpha) * a
ax = np.sqrt(a**2 - ay**2 - az**2)
ax = np.sqrt(a ** 2 - ay ** 2 - az ** 2)
return [ax, ay, az], [0, by, bz], [0, 0, cz]
@ -280,7 +297,7 @@ def _parse_core_param(file):
rmts = []
for line in file:
if line.strip()[0] == '#':
if line.strip()[0] == "#":
continue
vals = line.strip().split()
@ -300,7 +317,7 @@ def _get_forces_wien2k(filename, lattice):
num_atom = 0
for line in open(filename):
if line.count('total forces') > 0:
if line.count("total forces") > 0:
if line[:4] == ":FGL":
fx = float(line[29:45])
fy = float(line[45:61])
@ -316,16 +333,13 @@ def _distribute_forces(supercell, disp, forces, filename, symprec):
lattice = supercell.get_cell()
symbols = supercell.get_chemical_symbols()
positions = supercell.get_positions() + disp
cell = Atoms(cell=lattice,
positions=positions,
symbols=symbols,
pbc=True)
cell = Atoms(cell=lattice, positions=positions, symbols=symbols, pbc=True)
symmetry = Symmetry(cell, symprec)
independent_atoms = symmetry.get_independent_atoms()
# Rotation matrices in Cartesian
rotations = []
for r in symmetry.get_symmetry_operations()['rotations']:
for r in symmetry.get_symmetry_operations()["rotations"]:
rotations.append(similarity_transformation(lattice.T, r))
map_operations = symmetry.get_map_operations()
@ -335,18 +349,18 @@ def _distribute_forces(supercell, disp, forces, filename, symprec):
if len(forces) != len(atoms_in_dot_scf):
print("%s does not contain necessary information." % filename)
print("Plese check if there are \"FGL\" lines with")
print("\"total forces\" are required.")
print('Plese check if there are "FGL" lines with')
print('"total forces" are required.')
return False
if len(atoms_in_dot_scf) == natom:
print("It is assumed that there is no symmetrically-equivalent "
"atoms in ")
print("\'%s\' at wien2k calculation." % filename)
print("It is assumed that there is no symmetrically-equivalent " "atoms in ")
print("'%s' at wien2k calculation." % filename)
force_set = forces
elif len(forces) != len(independent_atoms):
print("Non-equivalent atoms of %s could not be recognized by phonopy."
% filename)
print(
"Non-equivalent atoms of %s could not be recognized by phonopy." % filename
)
return False
else:
# 1. Transform wien2k forces to those on independent atoms
@ -357,8 +371,7 @@ def _distribute_forces(supercell, disp, forces, filename, symprec):
diff = pos_wien2k - pos
diff -= np.rint(diff)
if (abs(diff) < symprec).all():
forces_remap.append(
np.dot(rotations[map_operations[j]], forces[i]))
forces_remap.append(np.dot(rotations[map_operations[j]], forces[i]))
indep_atoms_to_wien2k.append(map_atoms[j])
break
@ -372,8 +385,7 @@ def _distribute_forces(supercell, disp, forces, filename, symprec):
force_set = []
for i in range(natom):
j = indep_atoms_to_wien2k.index(map_atoms[i])
force_set.append(np.dot(
rotations[map_operations[i]].T, forces_remap[j]))
force_set.append(np.dot(rotations[map_operations[i]].T, forces_remap[j]))
return force_set
@ -396,11 +408,12 @@ def _get_independent_atoms_in_dot_scf(filename):
return np.array(positions)[-num_atom:]
if __name__ == '__main__':
if __name__ == "__main__":
from optparse import OptionParser
from phonopy.interface.vasp import write_vasp, read_vasp
def clean_scaled_positions(cell):
from phonopy.interface.vasp import read_vasp, write_vasp
def _clean_scaled_positions(cell):
positions = cell.get_scaled_positions()
for pos in positions:
for i in (0, 1, 2):
@ -411,12 +424,12 @@ if __name__ == '__main__':
parser = OptionParser()
parser.set_defaults(w2v=False, v2w=False)
parser.add_option("-w", dest="w2v",
action="store_true",
help="Convert WIEN2k to VASP")
parser.add_option("-v", dest="v2w",
action="store_true",
help="Convert VASP to WIEN2k")
parser.add_option(
"-w", dest="w2v", action="store_true", help="Convert WIEN2k to VASP"
)
parser.add_option(
"-v", dest="v2w", action="store_true", help="Convert VASP to WIEN2k"
)
(options, args) = parser.parse_args()
from phonopy.units import Bohr
@ -435,14 +448,12 @@ if __name__ == '__main__':
lattice = cell.get_cell() * Bohr
cell.set_cell(lattice)
cell.set_scaled_positions(positions)
clean_scaled_positions(cell)
_clean_scaled_positions(cell)
write_vasp("POSCAR.wien2k", cell, direct=True)
w = open("wien2k_core.dat", 'w')
w = open("wien2k_core.dat", "w")
w.write("# symbol npt r0 rmt\n")
for symbol, npt, r0, rmt in \
zip(cell.get_chemical_symbols(), npts, r0s, rmts):
w.write("%-10s %5d %10.8f %10.5f\n" %
(symbol, npt, r0, rmt))
for symbol, npt, r0, rmt in zip(cell.get_chemical_symbols(), npts, r0s, rmts):
w.write("%-10s %5d %10.8f %10.5f\n" % (symbol, npt, r0, rmt))
else:
print("You need to set -r or -w option.")

View File

@ -0,0 +1,34 @@
"""Routines of phonon and post-phonon calculations."""
# Copyright (C) 2021 Atsushi Togo
# All rights reserved.
#
# This file is part of phonopy.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of the phonopy project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,3 +1,4 @@
"""Convert phonon results to animation formats."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -33,88 +34,74 @@
# POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from phonopy.structure.cells import (get_angles, get_cell_parameters,
get_cell_matrix)
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.interface.vasp import write_vasp
from phonopy.structure.atoms import PhonopyAtoms
from phonopy.structure.cells import get_angles, get_cell_matrix, get_cell_parameters
from phonopy.units import VaspToTHz
def write_animation(dynamical_matrix,
def write_animation(
dynamical_matrix,
q_point=None,
anime_type='v_sim',
anime_type="v_sim",
band_index=None,
amplitude=None,
num_div=None,
shift=None,
factor=None,
filename=None):
filename=None,
):
"""Write atomic modulations in animation format."""
animation = Animation(q_point,
dynamical_matrix,
shift=shift)
animation = Animation(q_point, dynamical_matrix, shift=shift)
if anime_type == 'v_sim':
if anime_type == "v_sim":
if filename:
fname_out = animation.write_v_sim(amplitude=amplitude,
factor=factor,
filename=filename)
fname_out = animation.write_v_sim(
amplitude=amplitude, factor=factor, filename=filename
)
else:
fname_out = animation.write_v_sim(amplitude=amplitude,
factor=factor)
fname_out = animation.write_v_sim(amplitude=amplitude, factor=factor)
elif anime_type == 'arc' or anime_type is None:
elif anime_type == "arc" or anime_type is None:
if filename:
fname_out = animation.write_arc(band_index,
amplitude,
num_div,
filename=filename)
fname_out = animation.write_arc(
band_index, amplitude, num_div, filename=filename
)
else:
fname_out = animation.write_arc(band_index,
amplitude,
num_div)
elif anime_type == 'xyz':
fname_out = animation.write_arc(band_index, amplitude, num_div)
elif anime_type == "xyz":
if filename:
fname_out = animation.write_xyz(band_index,
amplitude,
num_div,
factor,
filename=filename)
fname_out = animation.write_xyz(
band_index, amplitude, num_div, factor, filename=filename
)
else:
fname_out = animation.write_xyz(band_index,
amplitude,
num_div,
factor)
elif anime_type == 'jmol':
fname_out = animation.write_xyz(band_index, amplitude, num_div, factor)
elif anime_type == "jmol":
if filename:
fname_out = animation.write_xyz_jmol(amplitude=amplitude,
factor=factor,
filename=filename)
fname_out = animation.write_xyz_jmol(
amplitude=amplitude, factor=factor, filename=filename
)
else:
fname_out = animation.write_xyz_jmol(amplitude=amplitude,
factor=factor)
elif anime_type == 'poscar':
fname_out = animation.write_xyz_jmol(amplitude=amplitude, factor=factor)
elif anime_type == "poscar":
if filename:
fname_out = animation.write_POSCAR(band_index,
amplitude,
num_div,
filename=filename)
fname_out = animation.write_POSCAR(
band_index, amplitude, num_div, filename=filename
)
else:
fname_out = animation.write_POSCAR(band_index,
amplitude,
num_div)
fname_out = animation.write_POSCAR(band_index, amplitude, num_div)
else:
raise RuntimeError(
"Animation format \'%s\' was not found." % anime_type)
raise RuntimeError("Animation format '%s' was not found." % anime_type)
return fname_out
class Animation(object):
def __init__(self,
qpoint,
dynamical_matrix,
shift=None):
class Animation:
"""Class to convert phonon results to animation formats."""
def __init__(self, qpoint, dynamical_matrix, shift=None):
"""Init method."""
if qpoint is None:
_qpoint = [0, 0, 0]
else:
@ -138,14 +125,15 @@ class Animation(object):
a, b, c = self._cell_params
alpha, beta, gamma = self._angles
self._lattice_oriented = get_cell_matrix(a, b, c, alpha, beta, gamma)
self._positions_oriented = \
self._get_oriented_displacements(np.dot(self._positions,
self._lattice))
self._positions_oriented = self._get_oriented_displacements(
np.dot(self._positions, self._lattice)
)
# For the orientation, see get_cell_matrix
def _get_oriented_displacements(self, vec_cartesian):
return np.dot(np.dot(vec_cartesian, np.linalg.inv(self._lattice)),
self._lattice_oriented)
return np.dot(
np.dot(vec_cartesian, np.linalg.inv(self._lattice)), self._lattice_oriented
)
def _set_displacements(self, band_index):
u = []
@ -154,18 +142,16 @@ class Animation(object):
self._displacements = np.array(u).reshape(-1, 3)
def write_v_sim(self,
amplitude=None,
factor=VaspToTHz,
filename="anime.ascii"):
def write_v_sim(self, amplitude=None, factor=VaspToTHz, filename="anime.ascii"):
"""Write to file in v_sim format."""
if amplitude is None:
_amplitude = 1.0
self._set_cell_oriented()
lat = self._lattice_oriented
q = self._qpoint
text = "# Phonopy generated file for v_sim 3.6\n"
text += "%15.9f%15.9f%15.9f\n" % (lat[0,0], lat[1,0], lat[1,1])
text += "%15.9f%15.9f%15.9f\n" % (lat[2,0], lat[2,1], lat[2,2])
text += "%15.9f%15.9f%15.9f\n" % (lat[0, 0], lat[1, 0], lat[1, 1])
text += "%15.9f%15.9f%15.9f\n" % (lat[2, 0], lat[2, 1], lat[2, 2])
for s, p in zip(self._symbols, self._positions_oriented):
text += "%15.9f%15.9f%15.9f %2s\n" % (p[0], p[1], p[2], s)
@ -176,24 +162,29 @@ class Animation(object):
omega = -np.sqrt(-val)
self._set_displacements(i)
text += "#metaData: qpt=[%f;%f;%f;%f \\\n" % (
q[0], q[1], q[2], omega * factor)
for u in (self._get_oriented_displacements(self._displacements) *
_amplitude):
q[0],
q[1],
q[2],
omega * factor,
)
for u in self._get_oriented_displacements(self._displacements) * _amplitude:
text += "#; %f; %f; %f; %f; %f; %f \\\n" % (
u[0].real, u[1].real, u[2].real,
u[0].imag, u[1].imag, u[2].imag)
u[0].real,
u[1].real,
u[2].real,
u[0].imag,
u[1].imag,
u[2].imag,
)
text += "# ]\n"
w = open(filename, 'w')
w = open(filename, "w")
w.write(text)
w.close()
return filename
def write_arc(self,
band_index,
amplitude=1,
num_div=20,
filename="anime.arc"):
def write_arc(self, band_index, amplitude=1, num_div=20, filename="anime.arc"):
"""Write to file in BIOSYM archive 3 format."""
self._set_cell_oriented()
self._set_displacements(band_index - 1)
displacements = self._get_oriented_displacements(self._displacements)
@ -206,32 +197,47 @@ class Animation(object):
text += "PBC=ON\n"
for i in range(num_div):
text += " 0.000000\n"
text += " 0.000000\n" # noqa E501
text += "!DATE\n"
text += "%-4s%10.4f%10.4f%10.4f%10.4f%10.4f%10.4f\n" % (
"PBC", a, b, c, alpha, beta, gamma)
positions = (self._positions_oriented +
(displacements *
np.exp(2j * np.pi / num_div * i)).imag * amplitude)
"PBC",
a,
b,
c,
alpha,
beta,
gamma,
)
positions = (
self._positions_oriented
+ (displacements * np.exp(2j * np.pi / num_div * i)).imag * amplitude
)
for j, p in enumerate(positions):
text += "%-5s%15.9f%15.9f%15.9f CORE" % (
self._symbols[j], p[0], p[1], p[2])
self._symbols[j],
p[0],
p[1],
p[2],
)
text += "%5s%3s%3s%9.4f%5s\n" % (
j + 1, self._symbols[j], self._symbols[j], 0.0, j + 1)
j + 1,
self._symbols[j],
self._symbols[j],
0.0,
j + 1,
)
text += "end\n"
text += "end\n"
w = open(filename, 'w')
w = open(filename, "w")
w.write(text)
w.close()
return filename
def write_xyz_jmol(self,
amplitude=10,
factor=VaspToTHz,
filename="anime.xyz_jmol"):
def write_xyz_jmol(self, amplitude=10, factor=VaspToTHz, filename="anime.xyz_jmol"):
"""Write to file in jmol xyz format."""
self._set_cell_oriented()
text = ""
for i, val in enumerate(self._eigenvalues):
@ -240,27 +246,30 @@ class Animation(object):
else:
freq = -np.sqrt(-val)
self._set_displacements(i)
displacements = self._get_oriented_displacements(
self._displacements) * amplitude
displacements = (
self._get_oriented_displacements(self._displacements) * amplitude
)
text += "%d\n" % len(self._symbols)
text += "q %s , b %d , f %f " % (str(self._qpoint), i+1, freq * factor)
text += "q %s , b %d , f %f " % (str(self._qpoint), i + 1, freq * factor)
text += "(generated by Phonopy)\n"
for s, p, u in zip(
self._symbols, self._positions_oriented, displacements):
for s, p, u in zip(self._symbols, self._positions_oriented, displacements):
text += "%-3s %22.15f %22.15f %22.15f " % (s, p[0], p[1], p[2])
text += "%15.9f %15.9f %15.9f\n" % (u[0].real, u[1].real, u[2].real)
w = open(filename, 'w')
w = open(filename, "w")
w.write(text)
w.close()
return filename
def write_xyz(self,
def write_xyz(
self,
band_index,
amplitude=1,
num_div=20,
factor=VaspToTHz,
filename="anime.xyz"):
filename="anime.xyz",
):
"""Write to file in xyz format."""
self._set_cell_oriented()
freq = self._eigenvalues[band_index - 1]
self._set_displacements(band_index - 1)
@ -269,36 +278,45 @@ class Animation(object):
for i in range(num_div):
text += "%d\n" % len(self._symbols)
text += "q %s , b %d , f %f , " % (
str(self._qpoint), band_index, freq * factor)
str(self._qpoint),
band_index,
freq * factor,
)
text += "div %d / %d " % (i, num_div)
text += "(generated by Phonopy)\n"
positions = (self._positions_oriented +
(displacements *
np.exp(2j * np.pi / num_div * i)).imag * amplitude)
positions = (
self._positions_oriented
+ (displacements * np.exp(2j * np.pi / num_div * i)).imag * amplitude
)
for j, p in enumerate(positions):
text += "%-3s %22.15f %22.15f %22.15f\n" % (
self._symbols[j], p[0], p[1], p[2])
w = open(filename, 'w')
self._symbols[j],
p[0],
p[1],
p[2],
)
w = open(filename, "w")
w.write(text)
w.close()
return filename
def write_POSCAR(self,
band_index,
amplitude=1,
num_div=20,
filename="APOSCAR"):
def write_POSCAR(self, band_index, amplitude=1, num_div=20, filename="APOSCAR"):
"""Write snapshots to files in VASP POSCAR format."""
self._set_displacements(band_index - 1)
for i in range(num_div):
positions = (np.dot(self._positions, self._lattice) +
(self._displacements *
np.exp(2j * np.pi / num_div * i)).imag * amplitude)
atoms = PhonopyAtoms(cell=self._lattice,
positions = (
np.dot(self._positions, self._lattice)
+ (self._displacements * np.exp(2j * np.pi / num_div * i)).imag
* amplitude
)
atoms = PhonopyAtoms(
cell=self._lattice,
positions=positions,
masses=self._masses,
symbols=self._symbols,
pbc=True)
write_vasp((filename+"-%03d") % i, atoms, direct=True)
pbc=True,
)
write_vasp((filename + "-%03d") % i, atoms, direct=True)
return filename

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
"""Utility routines to handle degeneracy."""
# Copyright (C) 2014 Atsushi Togo
# All rights reserved.
#
@ -32,10 +33,39 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from typing import Union
import numpy as np
from phonopy.harmonic.derivative_dynmat import DerivativeOfDynamicalMatrix
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
def degenerate_sets(freqs, cutoff=1e-4):
"""Find degenerate bands from frequencies.
Parameters
----------
freqs : ndarray
A list of values.
shape=(values,)
cutoff : float, optional
Equivalent of values is defined by this value, i.e.,
abs(val1 - val2) < cutoff
Default is 1e-4.
Returns
-------
indices : list of list
Indices of equivalent values are grouped as a list and those groups are
stored in a list.
Example
-------
In : degenerate_sets(np.array([1.5, 2.1, 2.1, 3.4, 8]))
Out: [[0], [1, 2], [3], [4]]
"""
indices = []
done = []
for i in range(len(freqs)):
@ -53,12 +83,15 @@ def degenerate_sets(freqs, cutoff=1e-4):
return indices
def get_eigenvectors(q,
dm,
ddm,
def get_eigenvectors(
q,
dm: Union[DynamicalMatrix, DynamicalMatrixNAC],
ddm: DerivativeOfDynamicalMatrix,
perturbation=None,
derivative_order=None,
nac_q_direction=None):
nac_q_direction=None,
):
"""Return degenerated eigenvalues and rotated eigenvalues."""
if nac_q_direction is not None and (np.abs(q) < 1e-5).all():
dm.run(q, q_direction=nac_q_direction)
else:
@ -77,6 +110,20 @@ def get_eigenvectors(q,
def rotate_eigenvectors(eigvals, eigvecs, dD):
"""Rotate eigenvectors among degenerated band.
Parameters
----------
eigvals :
Eigenvalues.
shape=(num_band, )
eigvecs :
Eigenvectors.
shape=(num_atom * 3, num_band)
dD :
q-point derivative of dynamical matrix.
"""
rot_eigvecs = np.zeros_like(eigvecs)
eigvals_dD = np.zeros_like(eigvals)
for deg in degenerate_sets(eigvals):
@ -86,10 +133,17 @@ def rotate_eigenvectors(eigvals, eigvecs, dD):
return rot_eigvecs, eigvals_dD
def _get_dD(q, ddm, perturbation):
def _get_dD(q, ddm: DerivativeOfDynamicalMatrix, perturbation):
"""Return q-vector derivative of dynamical matrix.
Returns
-------
shape=(3, num_band, num_band).
"""
ddm.run(q)
ddm_vals = ddm.get_derivative_of_dynamical_matrix()
dD = np.zeros(ddm_vals.shape[1:], dtype=ddm_vals.dtype, order='C')
dD = np.zeros(ddm_vals.shape[1:], dtype=ddm_vals.dtype, order="C")
if len(ddm_vals) == 3:
for i in range(3):
dD += perturbation[i] * ddm_vals[i]

View File

@ -1,3 +1,4 @@
"""Calculation of density of states."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -33,230 +34,93 @@
# POSSIBILITY OF SUCH DAMAGE.
import sys
import warnings
import numpy as np
from phonopy.phonon.mesh import Mesh
from phonopy.phonon.tetrahedron_mesh import TetrahedronMesh
from phonopy.structure.tetrahedron_method import TetrahedronMethod
def get_pdos_indices(symmetry):
mapping = symmetry.get_map_atoms()
return [list(np.where(mapping == i)[0])
for i in symmetry.get_independent_atoms()]
class NormalDistribution:
"""Class to represent normal distribution."""
def write_total_dos(frequency_points,
total_dos,
comment=None,
filename="total_dos.dat"):
with open(filename, 'w') as fp:
if comment is not None:
fp.write("# %s\n" % comment)
for freq, dos in zip(frequency_points, total_dos):
fp.write("%20.10f%20.10f\n" % (freq, dos))
def write_partial_dos(frequency_points,
partial_dos,
comment=None,
filename="partial_dos.dat"):
with open(filename, 'w') as fp:
if comment is not None:
fp.write("# %s\n" % comment)
for freq, pdos in zip(frequency_points, partial_dos.T):
fp.write("%20.10f" % freq)
fp.write(("%20.10f" * len(pdos)) % tuple(pdos))
fp.write("\n")
def plot_total_dos(ax,
frequency_points,
total_dos,
freq_Debye=None,
Debye_fit_coef=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False):
ax.xaxis.set_ticks_position('both')
ax.yaxis.set_ticks_position('both')
ax.xaxis.set_tick_params(which='both', direction='in')
ax.yaxis.set_tick_params(which='both', direction='in')
if freq_Debye is not None:
freq_pitch = frequency_points[1] - frequency_points[0]
num_points = int(freq_Debye / freq_pitch)
freqs = np.linspace(0, freq_Debye, num_points + 1)
if flip_xy:
ax.plot(total_dos, frequency_points, 'r-', linewidth=1)
if freq_Debye:
ax.plot(np.append(Debye_fit_coef * freqs**2, 0),
np.append(freqs, freq_Debye), 'b-', linewidth=1)
else:
ax.plot(frequency_points, total_dos, 'r-', linewidth=1)
if freq_Debye:
ax.plot(np.append(freqs, freq_Debye),
np.append(Debye_fit_coef * freqs**2, 0), 'b-', linewidth=1)
if xlabel:
ax.set_xlabel(xlabel)
if ylabel:
ax.set_ylabel(ylabel)
ax.grid(draw_grid)
def plot_partial_dos(ax,
frequency_points,
partial_dos,
indices=None,
legend=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False):
ax.xaxis.set_ticks_position('both')
ax.yaxis.set_ticks_position('both')
ax.xaxis.set_tick_params(which='both', direction='in')
ax.yaxis.set_tick_params(which='both', direction='in')
plots = []
num_pdos = len(partial_dos)
if indices is None:
indices = []
for i in range(num_pdos):
indices.append([i])
for set_for_sum in indices:
pdos_sum = np.zeros_like(frequency_points)
for i in set_for_sum:
if i > num_pdos - 1:
print("Index number \'%d\' is specified," % (i + 1))
print("but it is not allowed to be larger than the number of "
"atoms.")
raise ValueError
if i < 0:
print("Index number \'%d\' is specified, but it must be "
"positive." % (i + 1))
raise ValueError
pdos_sum += partial_dos[i]
if flip_xy:
plots.append(ax.plot(pdos_sum, frequency_points, linewidth=1))
else:
plots.append(ax.plot(frequency_points, pdos_sum, linewidth=1))
if legend is not None:
ax.legend(legend)
if xlabel:
ax.set_xlabel(xlabel)
if ylabel:
ax.set_ylabel(ylabel)
ax.grid(draw_grid)
class NormalDistribution(object):
def __init__(self, sigma):
"""Init method."""
self._sigma = sigma
def calc(self, x):
return 1.0 / np.sqrt(2 * np.pi) / self._sigma * \
np.exp(-x**2 / 2.0 / self._sigma**2)
"""Return normal distribution."""
return (
1.0
/ np.sqrt(2 * np.pi)
/ self._sigma
* np.exp(-(x ** 2) / 2.0 / self._sigma ** 2)
)
class CauchyDistribution(object):
class CauchyDistribution:
"""Class to represent Cauchy distribution."""
def __init__(self, gamma):
"""Init method."""
self._gamma = gamma
def calc(self, x):
return self._gamma / np.pi / (x**2 + self._gamma**2)
"""Return Cauchy distribution."""
return self._gamma / np.pi / (x ** 2 + self._gamma ** 2)
def run_tetrahedron_method_dos(mesh,
frequency_points,
frequencies,
grid_address,
grid_mapping_table,
relative_grid_address,
coef=None): # for each grid point
try:
import phonopy._phonopy as phonoc
except ImportError:
import sys
print("Phonopy C-extension has to be built properly.")
sys.exit(1)
class Dos:
"""Base class to calculate density of states."""
if coef is None:
_coef = np.ones((frequencies.shape[0], 1, frequencies.shape[1]),
dtype='double')
else:
_coef = np.array(coef, dtype='double', order='C')
arr_shape = frequencies.shape + (len(frequency_points), _coef.shape[1])
dos = np.zeros(arr_shape, dtype='double')
phonoc.tetrahedron_method_dos(
dos,
np.array(mesh, dtype='int_'),
frequency_points,
frequencies,
_coef,
np.array(grid_address, dtype='int_', order='C'),
np.array(grid_mapping_table, dtype='int_', order='C'),
relative_grid_address)
if coef is None:
return dos[:, :, :, 0].sum(axis=0).sum(axis=0) / np.prod(mesh)
else:
return dos.sum(axis=0).sum(axis=0) / np.prod(mesh)
class Dos(object):
def __init__(self, mesh_object, sigma=None, use_tetrahedron_method=False):
def __init__(self, mesh_object: Mesh, sigma=None, use_tetrahedron_method=False):
"""Init method."""
self._mesh_object = mesh_object
self._frequencies = mesh_object.frequencies
self._weights = mesh_object.weights
self._tetrahedron_mesh = None
if use_tetrahedron_method and sigma is None:
self._tetrahedron_mesh = TetrahedronMesh(
mesh_object.dynamical_matrix.primitive,
self._frequencies,
mesh_object.mesh_numbers,
np.array(mesh_object.grid_address, dtype='int_'),
np.array(mesh_object.grid_mapping_table, dtype='int_'),
mesh_object.ir_grid_points)
else:
self._tetrahedron_mesh = None
np.array(mesh_object.grid_address, dtype="int_"),
np.array(mesh_object.grid_mapping_table, dtype="int_"),
mesh_object.ir_grid_points,
)
self._frequency_points = None
self._sigma = sigma
self.set_draw_area()
self.set_smearing_function('Normal')
self.set_smearing_function("Normal")
@property
def frequency_points(self):
"""Return frequency points."""
return self._frequency_points
def set_smearing_function(self, function_name):
"""
function_name ==
"""Set function form for smearing method.
Parameters
----------
function_name : str
'Normal': smearing is done by normal distribution.
'Cauchy': smearing is done by Cauchy distribution.
"""
if function_name == 'Cauchy':
if function_name == "Cauchy":
self._smearing_function = CauchyDistribution(self._sigma)
else:
self._smearing_function = NormalDistribution(self._sigma)
def set_sigma(self, sigma):
"""Set sigma."""
self._sigma = sigma
def set_draw_area(self,
freq_min=None,
freq_max=None,
freq_pitch=None):
def set_draw_area(self, freq_min=None, freq_max=None, freq_pitch=None):
"""Set frequency points."""
f_min = self._frequencies.min()
f_max = self._frequencies.max()
@ -277,50 +141,67 @@ class Dos(object):
f_delta = (f_max - f_min) / 200.0
else:
f_delta = freq_pitch
self._frequency_points = np.arange(f_min,
f_max + f_delta * 0.1,
f_delta)
self._frequency_points = np.arange(f_min, f_max + f_delta * 0.1, f_delta)
class TotalDos(Dos):
def __init__(self, mesh_object, sigma=None, use_tetrahedron_method=False):
Dos.__init__(self,
"""Class to calculate total DOS."""
def __init__(self, mesh_object: Mesh, sigma=None, use_tetrahedron_method=False):
"""Init method."""
super().__init__(
mesh_object,
sigma=sigma,
use_tetrahedron_method=use_tetrahedron_method)
use_tetrahedron_method=use_tetrahedron_method,
)
self._dos = None
self._freq_Debye = None
self._Debye_fit_coef = None
self._openmp_thm = True
def run(self):
"""Calculate total DOS."""
if self._tetrahedron_mesh is None:
self._dos = np.array([self._get_density_of_states_at_freq(f)
for f in self._frequency_points])
self._dos = np.array(
[self._get_density_of_states_at_freq(f) for f in self._frequency_points]
)
else:
if self._openmp_thm:
self._run_tetrahedron_method_dos()
else:
self._dos = np.zeros_like(self._frequency_points)
thm = self._tetrahedron_mesh
thm.set(value='I', frequency_points=self._frequency_points)
thm.set(value="I", frequency_points=self._frequency_points)
for i, iw in enumerate(thm):
self._dos += np.sum(iw * self._weights[i], axis=1)
@property
def dos(self):
"""Return total DOS."""
return self._dos
def get_dos(self):
"""Return frequency points and total DOS.
Returns
-------
tuple
(frequency_points, total_dos)
"""
Return freqs and total dos
"""
warnings.warn(
"TotalDos.get_dos() is deprecated. "
"Use frequency_points and dos attributes instead.",
DeprecationWarning,
)
return self._frequency_points, self._dos
def get_Debye_frequency(self):
"""Return a kind of Debye frequency."""
return self._freq_Debye
def set_Debye_frequency(self, num_atoms, freq_max_fit=None):
"""Calculate a kind of Debye frequency."""
try:
from scipy.optimize import curve_fit
except ImportError:
@ -328,7 +209,7 @@ class TotalDos(Dos):
sys.exit(1)
def Debye_dos(freq, a):
return a * freq**2
return a * freq ** 2
freq_min = self._frequency_points.min()
freq_max = self._frequency_points.max()
@ -336,34 +217,32 @@ class TotalDos(Dos):
if freq_max_fit is None:
N_fit = int(len(self._frequency_points) / 4.0) # Hard coded
else:
N_fit = int(freq_max_fit / (freq_max - freq_min) *
len(self._frequency_points))
popt, pcov = curve_fit(Debye_dos,
self._frequency_points[0:N_fit],
self._dos[0:N_fit])
N_fit = int(
freq_max_fit / (freq_max - freq_min) * len(self._frequency_points)
)
popt, pcov = curve_fit(
Debye_dos, self._frequency_points[0:N_fit], self._dos[0:N_fit]
)
a2 = popt[0]
self._freq_Debye = (3 * 3 * num_atoms / a2)**(1.0 / 3)
self._freq_Debye = (3 * 3 * num_atoms / a2) ** (1.0 / 3)
self._Debye_fit_coef = a2
def plot(self,
ax,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False):
def plot(self, ax, xlabel=None, ylabel=None, draw_grid=True, flip_xy=False):
"""Plot total DOS."""
if flip_xy:
_xlabel = 'Density of states'
_ylabel = 'Frequency'
_xlabel = "Density of states"
_ylabel = "Frequency"
else:
_xlabel = 'Frequency'
_ylabel = 'Density of states'
_xlabel = "Frequency"
_ylabel = "Density of states"
if xlabel is not None:
_xlabel = xlabel
if ylabel is not None:
_ylabel = ylabel
plot_total_dos(ax,
plot_total_dos(
ax,
self._frequency_points,
self._dos,
freq_Debye=self._freq_Debye,
@ -371,23 +250,24 @@ class TotalDos(Dos):
xlabel=_xlabel,
ylabel=_ylabel,
draw_grid=draw_grid,
flip_xy=flip_xy)
flip_xy=flip_xy,
)
def write(self, filename="total_dos.dat"):
"""Write total DOS to total_dos.dat."""
if self._tetrahedron_mesh is None:
comment = "Sigma = %f" % self._sigma
else:
comment = "Tetrahedron method"
write_total_dos(self._frequency_points,
self._dos,
comment=comment,
filename=filename)
write_total_dos(
self._frequency_points, self._dos, comment=comment, filename=filename
)
def _run_tetrahedron_method_dos(self):
mesh_numbers = self._mesh_object.mesh_numbers
cell = self._mesh_object.dynamical_matrix.primitive
reciprocal_lattice = np.linalg.inv(cell.get_cell())
reciprocal_lattice = np.linalg.inv(cell.cell)
tm = TetrahedronMethod(reciprocal_lattice, mesh=mesh_numbers)
self._dos = run_tetrahedron_method_dos(
mesh_numbers,
@ -395,41 +275,48 @@ class TotalDos(Dos):
self._frequencies,
self._mesh_object.grid_address,
self._mesh_object.grid_mapping_table,
tm.get_tetrahedra())
tm.get_tetrahedra(),
)
def _get_density_of_states_at_freq(self, f):
return np.sum(np.dot(
self._weights, self._smearing_function.calc(self._frequencies - f))
return np.sum(
np.dot(self._weights, self._smearing_function.calc(self._frequencies - f))
) / np.sum(self._weights)
class PartialDos(Dos):
def __init__(self,
mesh_object,
class ProjectedDos(Dos):
"""Class to calculate projected DOS."""
def __init__(
self,
mesh_object: Mesh,
sigma=None,
use_tetrahedron_method=False,
direction=None,
xyz_projection=False):
Dos.__init__(self,
xyz_projection=False,
):
"""Init method."""
super().__init__(
mesh_object,
sigma=sigma,
use_tetrahedron_method=use_tetrahedron_method)
use_tetrahedron_method=use_tetrahedron_method,
)
self._eigenvectors = self._mesh_object.eigenvectors
self._partial_dos = None
self._projected_dos = None
if xyz_projection:
self._eigvecs2 = np.abs(self._eigenvectors) ** 2
else:
num_atom = self._frequencies.shape[1] // 3
i_x = np.arange(num_atom, dtype='int') * 3
i_y = np.arange(num_atom, dtype='int') * 3 + 1
i_z = np.arange(num_atom, dtype='int') * 3 + 2
i_x = np.arange(num_atom, dtype="int") * 3
i_y = np.arange(num_atom, dtype="int") * 3 + 1
i_z = np.arange(num_atom, dtype="int") * 3 + 2
if direction is None:
self._eigvecs2 = np.abs(self._eigenvectors[:, i_x, :]) ** 2
self._eigvecs2 += np.abs(self._eigenvectors[:, i_y, :]) ** 2
self._eigvecs2 += np.abs(self._eigenvectors[:, i_z, :]) ** 2
else:
d = np.array(direction, dtype='double')
d = np.array(direction, dtype="double")
d /= np.linalg.norm(direction)
proj_eigvecs = self._eigenvectors[:, i_x, :] * d[0]
proj_eigvecs += self._eigenvectors[:, i_y, :] * d[1]
@ -440,13 +327,21 @@ class PartialDos(Dos):
@property
def partial_dos(self):
return self._partial_dos
"""Return partial DOS."""
warnings.warn(
"PartialDos.partial_dos attribute is deprecated. "
"Use projected_dos attribute instead.",
DeprecationWarning,
)
return self._projected_dos
@property
def projected_dos(self):
return self._partial_dos
"""Return projected DOS."""
return self._projected_dos
def run(self):
"""Calculate projected DOS."""
if self._tetrahedron_mesh is None:
self._run_smearing_method()
else:
@ -456,79 +351,97 @@ class PartialDos(Dos):
self._run_tetrahedron_method()
def get_partial_dos(self):
"""
frequency_points: Sampling frequencies
partial_dos: [atom_index, frequency_points_index]
"""
return self._frequency_points, self._partial_dos
"""Return partial DOS.
def plot(self,
Returns
-------
tuple
frequency_points: Sampling frequencies
projected_dos: [atom_index, frequency_points_index]
"""
warnings.warn(
"ProjectedDos.get_partial_dos() is deprecated. "
"Use frequency_points and projected_dos attributes instead.",
DeprecationWarning,
)
return self._frequency_points, self._projected_dos
def plot(
self,
ax,
indices=None,
legend=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False):
flip_xy=False,
):
"""Plot projected DOS."""
if flip_xy:
_xlabel = 'Partial density of states'
_ylabel = 'Frequency'
_xlabel = "Partial density of states"
_ylabel = "Frequency"
else:
_xlabel = 'Frequency'
_ylabel = 'Partial density of states'
_xlabel = "Frequency"
_ylabel = "Partial density of states"
if xlabel is not None:
_xlabel = xlabel
if ylabel is not None:
_ylabel = ylabel
plot_partial_dos(ax,
plot_projected_dos(
ax,
self._frequency_points,
self._partial_dos,
self._projected_dos,
indices=indices,
legend=legend,
xlabel=_xlabel,
ylabel=_ylabel,
draw_grid=draw_grid,
flip_xy=flip_xy)
flip_xy=flip_xy,
)
def write(self, filename="partial_dos.dat"):
def write(self, filename="projected_dos.dat"):
"""Write projected DOS to projected_dos.dat."""
if self._tetrahedron_mesh is None:
comment = "Sigma = %f" % self._sigma
else:
comment = "Tetrahedron method"
write_partial_dos(self._frequency_points,
self._partial_dos,
write_projected_dos(
self._frequency_points,
self._projected_dos,
comment=comment,
filename=filename)
filename=filename,
)
def _run_smearing_method(self):
num_pdos = self._eigvecs2.shape[1]
num_freqs = len(self._frequency_points)
self._partial_dos = np.zeros((num_pdos, num_freqs), dtype='double')
self._projected_dos = np.zeros((num_pdos, num_freqs), dtype="double")
weights = self._weights / float(np.sum(self._weights))
for i, freq in enumerate(self._frequency_points):
amplitudes = self._smearing_function.calc(self._frequencies - freq)
for j in range(self._partial_dos.shape[0]):
self._partial_dos[j, i] = np.dot(
weights, self._eigvecs2[:, j, :] * amplitudes).sum()
for j in range(self._projected_dos.shape[0]):
self._projected_dos[j, i] = np.dot(
weights, self._eigvecs2[:, j, :] * amplitudes
).sum()
def _run_tetrahedron_method(self):
num_pdos = self._eigvecs2.shape[1]
num_freqs = len(self._frequency_points)
self._partial_dos = np.zeros((num_pdos, num_freqs), dtype='double')
self._projected_dos = np.zeros((num_pdos, num_freqs), dtype="double")
thm = self._tetrahedron_mesh
thm.set(value='I', frequency_points=self._frequency_points)
thm.set(value="I", frequency_points=self._frequency_points)
for i, iw in enumerate(thm):
w = self._weights[i]
self._partial_dos += np.dot(iw * w, self._eigvecs2[i].T).T
self._projected_dos += np.dot(iw * w, self._eigvecs2[i].T).T
def _run_tetrahedron_method_dos(self):
mesh_numbers = self._mesh_object.mesh_numbers
cell = self._mesh_object.dynamical_matrix.primitive
reciprocal_lattice = np.linalg.inv(cell.get_cell())
reciprocal_lattice = np.linalg.inv(cell.cell)
tm = TetrahedronMethod(reciprocal_lattice, mesh=mesh_numbers)
pdos = run_tetrahedron_method_dos(
mesh_numbers,
@ -537,5 +450,250 @@ class PartialDos(Dos):
self._mesh_object.grid_address,
self._mesh_object.grid_mapping_table,
tm.get_tetrahedra(),
coef=self._eigvecs2)
self._partial_dos = pdos.T
coef=self._eigvecs2,
)
self._projected_dos = pdos.T
class PartialDos(ProjectedDos):
"""Class to calculate partial DOS."""
def __init__(
self,
mesh_object: Mesh,
sigma=None,
use_tetrahedron_method=False,
direction=None,
xyz_projection=False,
):
"""Init method."""
warnings.warn(
"PartialDos class is deprecated. Use ProjectedDOS instead.",
DeprecationWarning,
)
super().__init__(
mesh_object,
sigma=sigma,
use_tetrahedron_method=use_tetrahedron_method,
direction=direction,
xyz_projection=xyz_projection,
)
def get_pdos_indices(symmetry):
"""Return atomic indieces grouped by symmetry."""
mapping = symmetry.get_map_atoms()
return [list(np.where(mapping == i)[0]) for i in symmetry.get_independent_atoms()]
def write_total_dos(
frequency_points, total_dos, comment=None, filename="total_dos.dat"
):
"""Write total_dos.dat."""
with open(filename, "w") as fp:
if comment is not None:
fp.write("# %s\n" % comment)
for freq, dos in zip(frequency_points, total_dos):
fp.write("%20.10f%20.10f\n" % (freq, dos))
def write_partial_dos(
frequency_points, partial_dos, comment=None, filename="partial_dos.dat"
):
"""Write partial_dos.dat."""
warnings.warn(
"write_partial_dos() is deprecated. Use write_projected_dos() instead.",
DeprecationWarning,
)
write_projected_dos(
frequency_points, partial_dos, comment=comment, filename=filename
)
def write_projected_dos(
frequency_points, projected_dos, comment=None, filename="projected_dos.dat"
):
"""Write projected_dos.dat."""
with open(filename, "w") as fp:
if comment is not None:
fp.write("# %s\n" % comment)
for freq, pdos in zip(frequency_points, projected_dos.T):
fp.write("%20.10f" % freq)
fp.write(("%20.10f" * len(pdos)) % tuple(pdos))
fp.write("\n")
def plot_total_dos(
ax,
frequency_points,
total_dos,
freq_Debye=None,
Debye_fit_coef=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False,
):
"""Plot total DOS."""
ax.xaxis.set_ticks_position("both")
ax.yaxis.set_ticks_position("both")
ax.xaxis.set_tick_params(which="both", direction="in")
ax.yaxis.set_tick_params(which="both", direction="in")
if freq_Debye is not None:
freq_pitch = frequency_points[1] - frequency_points[0]
num_points = int(freq_Debye / freq_pitch)
freqs = np.linspace(0, freq_Debye, num_points + 1)
if flip_xy:
ax.plot(total_dos, frequency_points, "r-", linewidth=1)
if freq_Debye:
ax.plot(
np.append(Debye_fit_coef * freqs ** 2, 0),
np.append(freqs, freq_Debye),
"b-",
linewidth=1,
)
else:
ax.plot(frequency_points, total_dos, "r-", linewidth=1)
if freq_Debye:
ax.plot(
np.append(freqs, freq_Debye),
np.append(Debye_fit_coef * freqs ** 2, 0),
"b-",
linewidth=1,
)
if xlabel:
ax.set_xlabel(xlabel)
if ylabel:
ax.set_ylabel(ylabel)
ax.grid(draw_grid)
def plot_partial_dos(
ax,
frequency_points,
partial_dos,
indices=None,
legend=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False,
):
"""Plot partial DOS."""
warnings.warn(
"plot_partial_dos() is deprecated. Use plot_projected_dos() instead.",
DeprecationWarning,
)
plot_projected_dos(
ax,
frequency_points,
partial_dos,
indices=indices,
legend=legend,
xlabel=xlabel,
ylabel=ylabel,
draw_grid=draw_grid,
flip_xy=flip_xy,
)
def plot_projected_dos(
ax,
frequency_points,
projected_dos,
indices=None,
legend=None,
xlabel=None,
ylabel=None,
draw_grid=True,
flip_xy=False,
):
"""Plot projected DOS."""
ax.xaxis.set_ticks_position("both")
ax.yaxis.set_ticks_position("both")
ax.xaxis.set_tick_params(which="both", direction="in")
ax.yaxis.set_tick_params(which="both", direction="in")
plots = []
num_pdos = len(projected_dos)
if indices is None:
indices = []
for i in range(num_pdos):
indices.append([i])
for set_for_sum in indices:
pdos_sum = np.zeros_like(frequency_points)
for i in set_for_sum:
if i > num_pdos - 1:
print("Index number '%d' is specified," % (i + 1))
print("but it is not allowed to be larger than the number of " "atoms.")
raise ValueError
if i < 0:
print(
"Index number '%d' is specified, but it must be "
"positive." % (i + 1)
)
raise ValueError
pdos_sum += projected_dos[i]
if flip_xy:
plots.append(ax.plot(pdos_sum, frequency_points, linewidth=1))
else:
plots.append(ax.plot(frequency_points, pdos_sum, linewidth=1))
if legend is not None:
ax.legend(legend)
if xlabel:
ax.set_xlabel(xlabel)
if ylabel:
ax.set_ylabel(ylabel)
ax.grid(draw_grid)
def run_tetrahedron_method_dos(
mesh,
frequency_points,
frequencies,
grid_address,
grid_mapping_table,
relative_grid_address,
coef=None,
):
"""Return (P)DOS calculated by tetrahedron method in C."""
try:
import phonopy._phonopy as phonoc
except ImportError:
import sys
print("Phonopy C-extension has to be built properly.")
sys.exit(1)
if coef is None:
_coef = np.ones((frequencies.shape[0], 1, frequencies.shape[1]), dtype="double")
else:
_coef = np.array(coef, dtype="double", order="C")
arr_shape = frequencies.shape + (len(frequency_points), _coef.shape[1])
dos = np.zeros(arr_shape, dtype="double")
phonoc.tetrahedron_method_dos(
dos,
np.array(mesh, dtype="int_"),
frequency_points,
frequencies,
_coef,
np.array(grid_address, dtype="int_", order="C"),
np.array(grid_mapping_table, dtype="int_", order="C"),
relative_grid_address,
)
if coef is None:
return dos[:, :, :, 0].sum(axis=0).sum(axis=0) / np.prod(mesh)
else:
return dos.sum(axis=0).sum(axis=0) / np.prod(mesh)

View File

@ -1,3 +1,4 @@
"""Group velocity calculation."""
# Copyright (C) 2013 Atsushi Togo
# All rights reserved.
#
@ -33,40 +34,20 @@
# POSSIBILITY OF SUCH DAMAGE.
import warnings
import textwrap
from typing import Optional, Union
import numpy as np
from phonopy.units import VaspToTHz
from phonopy.harmonic.derivative_dynmat import DerivativeOfDynamicalMatrix
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.harmonic.force_constants import similarity_transformation
from phonopy.phonon.degeneracy import degenerate_sets
from phonopy.structure.symmetry import Symmetry
from phonopy.units import VaspToTHz
def get_group_velocity(q, # q-point
dynamical_matrix,
q_length=None, # finite distance in q
symmetry=None,
frequency_factor_to_THz=VaspToTHz):
"""Returns group velocity at a q-point."""
gv = GroupVelocity(dynamical_matrix,
q_length=q_length,
symmetry=symmetry,
frequency_factor_to_THz=frequency_factor_to_THz)
gv.run([q])
return gv.group_velocity[0]
def delta_dynamical_matrix(q,
delta_q,
dynmat):
dynmat.run(q - delta_q)
dm1 = dynmat.dynamical_matrix
dynmat.run(q + delta_q)
dm2 = dynmat.dynamical_matrix
return dm2 - dm1
class GroupVelocity(object):
r"""Class to calculate group velocities of phonons
class GroupVelocity:
r"""Class to calculate group velocities of phonons.
d omega ----
------- = \ / omega
@ -82,21 +63,24 @@ class GroupVelocity(object):
----------
group_velocity : ndarray
Group velocities at q-points.
shape=(q-points, 3), dtype='double', order='C'
shape=(q-points, num_band, 3), dtype='double', order='C'
q_length : float
Distance in reciprocal space used to calculate finite difference of
dynamcial matrix.
"""
Default_q_length = 1e-5
def __init__(self,
dynamical_matrix,
def __init__(
self,
dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
q_length=None,
symmetry=None,
symmetry: Optional[Symmetry] = None,
frequency_factor_to_THz=VaspToTHz,
cutoff_frequency=1e-4):
"""
cutoff_frequency=1e-4,
):
"""Init method.
dynamical_matrix : DynamicalMatrix or DynamicalMatrixNAC
Dynamical matrix class instance.
@ -118,21 +102,23 @@ class GroupVelocity(object):
self._reciprocal_lattice_inv = primitive.cell
self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
self._q_length = q_length
if self._dynmat.is_nac() and self._dynmat.nac_method == 'gonze':
if self._dynmat.is_nac() and self._dynmat.nac_method == "gonze":
if self._q_length is None:
self._q_length = self.Default_q_length
self._ddm: Optional[DerivativeOfDynamicalMatrix]
if self._q_length is None:
self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
else:
self._ddm = None
self._symmetry = symmetry
self._factor = frequency_factor_to_THz
self._cutoff_frequency = cutoff_frequency
self._directions = np.array([[1, 2, 3],
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]], dtype='double')
self._directions = np.array(
[[1, 2, 3], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype="double"
)
self._directions[0] /= np.linalg.norm(self._directions[0])
self._q_points = None
@ -152,43 +138,57 @@ class GroupVelocity(object):
Direction in fractional coordinates of reciprocal space.
"""
self._q_points = q_points
self._perturbation = perturbation
if perturbation is None:
# Give an random direction to break symmetry
self._directions[0] = np.array([1, 2, 3])
else:
self._directions[0] = np.dot(
self._reciprocal_lattice, perturbation)
self._directions[0] = np.dot(self._reciprocal_lattice, perturbation)
self._directions[0] /= np.linalg.norm(self._directions[0])
gv = [self._calculate_group_velocity_at_q(q) for q in self._q_points]
self._group_velocities = np.array(gv, dtype='double', order='C')
self._group_velocities = np.array(gv, dtype="double", order="C")
@property
def q_length(self):
"""Setter an getter of q_length."""
return self._q_length
def get_q_length(self):
return self.q_length
@q_length.setter
def q_length(self, q_length):
self._q_length = q_length
def get_q_length(self):
"""Return q_length."""
warnings.warn(
"GroupVelocity.get_q_length() is deprecated. "
"Use q_length attribute instead.",
DeprecationWarning,
)
return self.q_length
def set_q_length(self, q_length):
"""Set q_length."""
warnings.warn(
"GroupVelocity.set_q_length() is deprecated. "
"Use q_length attribute instead.",
DeprecationWarning,
)
self.q_length = q_length
@property
def group_velocities(self):
"""Return group velocities."""
return self._group_velocities
def get_group_velocity(self):
"""Return group velocities."""
warnings.warn(
"GroupVelocity.get_group_velocity() is deprecated. "
"Use the attribute group_velocities.",
DeprecationWarning)
"Use group_velocities attribute instead.",
DeprecationWarning,
)
return self.group_velocities
def _calculate_group_velocity_at_q(self, q):
@ -197,13 +197,13 @@ class GroupVelocity(object):
eigvals, eigvecs = np.linalg.eigh(dm)
eigvals = eigvals.real
freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
gv = np.zeros((len(freqs), 3), dtype='double', order='C')
gv = np.zeros((len(freqs), 3), dtype="double", order="C")
deg_sets = degenerate_sets(freqs)
ddms = self._get_dD(np.array(q))
pos = 0
for deg in deg_sets:
gv[pos:pos+len(deg)] = self._perturb_D(ddms, eigvecs[:, deg])
gv[pos : pos + len(deg)] = self._perturb_D(ddms, eigvecs[:, deg])
pos += len(deg)
for i, f in enumerate(freqs):
@ -222,7 +222,6 @@ class GroupVelocity(object):
def _symmetrize_group_velocity(self, gv, q):
"""Symmetrize obtained group velocities using site symmetries."""
rotations = []
for r in self._symmetry.reciprocal_operations:
q_in_BZ = q - np.rint(q)
@ -238,38 +237,35 @@ class GroupVelocity(object):
return gv_sym / len(rotations)
def _get_dD(self, q):
"""Compute derivative or finite difference of dynamcial matrices"""
"""Compute derivative or finite difference of dynamcial matrices."""
if self._q_length is None:
return self._get_dD_analytical(q)
else:
return self._get_dD_FD(q)
def _get_dD_FD(self, q):
"""Compute finite difference of dynamcial matrices"""
"""Compute finite difference of dynamcial matrices."""
ddm = []
for dqc in self._directions * self._q_length:
dq = np.dot(self._reciprocal_lattice_inv, dqc)
ddm.append(delta_dynamical_matrix(q, dq, self._dynmat) /
self._q_length / 2)
ddm.append(
_delta_dynamical_matrix(q, dq, self._dynmat) / self._q_length / 2
)
return np.array(ddm)
def _get_dD_analytical(self, q):
"""Compute derivative of dynamcial matrices"""
"""Compute derivative of dynamcial matrices."""
self._ddm.run(q)
ddm = self._ddm.get_derivative_of_dynamical_matrix()
dtype = "c%d" % (np.dtype('double').itemsize * 2)
ddm_dirs = np.zeros((len(self._directions),) + ddm.shape[1:],
dtype=dtype)
dtype = "c%d" % (np.dtype("double").itemsize * 2)
ddm_dirs = np.zeros((len(self._directions),) + ddm.shape[1:], dtype=dtype)
for i, dq in enumerate(self._directions):
for j in range(3):
ddm_dirs[i] += dq[j] * ddm[j]
return ddm_dirs
def _perturb_D(self, ddms, eigsets):
"""Treat degeneracy
"""Treat degeneracy.
Group velocities are calculated using analytical continuation using
specified directions (self._directions) in reciprocal space.
@ -281,15 +277,39 @@ class GroupVelocity(object):
List of phonon eigenvectors of degenerate bands.
"""
eigvals, eigvecs = np.linalg.eigh(
np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))
_, eigvecs = np.linalg.eigh(np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))
gv = []
rot_eigsets = np.dot(eigsets, eigvecs)
for ddm in ddms[1:]:
gv.append(
np.diag(np.dot(rot_eigsets.T.conj(),
np.dot(ddm, rot_eigsets))).real)
np.diag(np.dot(rot_eigsets.T.conj(), np.dot(ddm, rot_eigsets))).real
)
return np.transpose(gv)
def get_group_velocity(
q, # q-point
dynamical_matrix,
q_length=None, # finite distance in q
symmetry=None,
frequency_factor_to_THz=VaspToTHz,
):
"""Return group velocity at a q-point."""
gv = GroupVelocity(
dynamical_matrix,
q_length=q_length,
symmetry=symmetry,
frequency_factor_to_THz=frequency_factor_to_THz,
)
gv.run([q])
return gv.group_velocity[0]
def _delta_dynamical_matrix(q, delta_q, dynmat):
dynmat.run(q - delta_q)
dm1 = dynmat.dynamical_matrix
dynmat.run(q + delta_q)
dm2 = dynmat.dynamical_matrix
return dm2 - dm1

View File

@ -1,3 +1,4 @@
"""Calculate irreducible representation from eigenvectors."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -32,31 +33,50 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from typing import Union
import numpy as np
from phonopy.structure.symmetry import Symmetry
from phonopy.harmonic.force_constants import similarity_transformation
from phonopy.phonon.degeneracy import degenerate_sets as get_degenerate_sets
from phonopy.units import VaspToTHz
from phonopy.harmonic.derivative_dynmat import DerivativeOfDynamicalMatrix
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.harmonic.force_constants import similarity_transformation
from phonopy.phonon.character_table import character_table
from phonopy.phonon.degeneracy import degenerate_sets as get_degenerate_sets
from phonopy.structure.symmetry import Symmetry
from phonopy.units import VaspToTHz
class IrReps(object):
def __init__(self,
dynamical_matrix,
class IrReps:
"""Class to calculate irreducible representations from eigenvectors.
Methods and terminologies used in this class may be easily found
in textbooks such as
- Group theory with applications in chemical physics by Patrick Jacobs
- Symmetry and condensed matter physics by M. El-Batanouny and F. Wooten
"""
def __init__(
self,
dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
q,
is_little_cogroup=False,
nac_q_direction=None,
factor=VaspToTHz,
symprec=1e-5,
degeneracy_tolerance=1e-5,
log_level=0):
degeneracy_tolerance=None,
log_level=0,
):
"""Init method."""
self._is_little_cogroup = is_little_cogroup
self._nac_q_direction = nac_q_direction
self._factor = factor
self._log_level = log_level
self._q = np.array(q)
if degeneracy_tolerance is None:
self._degeneracy_tolerance = 1e-5
else:
self._degeneracy_tolerance = degeneracy_tolerance
self._symprec = symprec
self._primitive = dynamical_matrix.primitive
@ -65,25 +85,30 @@ class IrReps(object):
self._character_table = None
def run(self):
"""Calculate irreps."""
self._set_eigenvectors(self._dynamical_matrix)
self._symmetry_dataset = Symmetry(self._primitive,
symprec=self._symprec).get_dataset()
self._symmetry_dataset = Symmetry(
self._primitive, symprec=self._symprec
).dataset
if not self._is_primitive_cell():
print('')
print("")
print("Non-primitve cell is used.")
print("Your unit cell may be transformed to a primitive cell "
"by PRIMITIVE_AXIS tag.")
print(
"Your unit cell may be transformed to a primitive cell "
"by PRIMITIVE_AXIS tag."
)
return False
(self._rotations_at_q,
self._translations_at_q) = self._get_rotations_at_q()
(self._rotations_at_q, self._translations_at_q) = self._get_rotations_at_q()
self._g = len(self._rotations_at_q)
(self._pointgroup_symbol,
(
self._pointgroup_symbol,
self._transformation_matrix,
self._conventional_rotations) = self._get_conventional_rotations()
self._conventional_rotations,
) = self._get_conventional_rotations()
self._ground_matrices = self._get_ground_matrix()
self._degenerate_sets = self._get_degenerate_sets()
@ -92,8 +117,10 @@ class IrReps(object):
self._ir_labels = None
if (self._pointgroup_symbol in character_table.keys() and
character_table[self._pointgroup_symbol] is not None):
if (
self._pointgroup_symbol in character_table.keys()
and character_table[self._pointgroup_symbol] is not None
):
self._rotation_symbols = self._get_rotation_symbols()
if (abs(self._q) < self._symprec).all() and self._rotation_symbols:
self._ir_labels = self._get_ir_labels()
@ -109,47 +136,61 @@ class IrReps(object):
return True
def _get_degenerate_sets(self):
deg_sets = get_degenerate_sets(self._freqs,
cutoff=self._degeneracy_tolerance)
deg_sets = get_degenerate_sets(self._freqs, cutoff=self._degeneracy_tolerance)
self._ddm.run(self._q)
return deg_sets
def get_band_indices(self):
"""Return band indices.
Returns
-------
See docstring of ``degenerate_sets``.
"""
return self._degenerate_sets
def get_characters(self):
"""Return characters of irreps."""
return self._characters
def get_eigenvectors(self):
"""Return eigenvectors."""
return self._eigvecs
def get_irreps(self):
"""Return irreps."""
return self._irreps
def get_ground_matrices(self):
"""Return ground matrices."""
return self._ground_matrices
def get_rotation_symbols(self):
"""Return symbols assigned to rotation matrices."""
return self._rotation_symbols
def get_rotations(self):
"""Return rotation matrices."""
return self._conventional_rotations
def get_projection_operators(self, idx_irrep, i=None, j=None):
"""Return projection operators."""
if i is None or j is None:
return self._get_character_projection_operators(idx_irrep)
else:
return self._get_projection_operators(idx_irrep, i, j)
def show(self, show_irreps=False):
"""Show irreps."""
self._show(show_irreps)
def write_yaml(self, show_irreps=False):
"""Write irreps in yaml file."""
self._write_yaml(show_irreps)
def _set_eigenvectors(self, dm):
if (self._nac_q_direction is not None and
(np.abs(self._q) < 1e-5).all()):
if self._nac_q_direction is not None and (np.abs(self._q) < 1e-5).all():
dm.run(self._q, q_direction=self._nac_q_direction)
else:
dm.run(self._q)
@ -159,8 +200,9 @@ class IrReps(object):
def _get_rotations_at_q(self):
rotations_at_q = []
trans_at_q = []
for r, t in zip(self._symmetry_dataset['rotations'],
self._symmetry_dataset['translations']):
for r, t in zip(
self._symmetry_dataset["rotations"], self._symmetry_dataset["translations"]
):
# Using r is used instead of np.linalg.inv(r)
diff = np.dot(self._q, r) - self._q
@ -176,14 +218,13 @@ class IrReps(object):
def _get_conventional_rotations(self):
rotations = self._rotations_at_q.copy()
pointgroup_symbol = self._symmetry_dataset['pointgroup']
transformation_matrix = self._symmetry_dataset['transformation_matrix']
pointgroup_symbol = self._symmetry_dataset["pointgroup"]
transformation_matrix = self._symmetry_dataset["transformation_matrix"]
conventional_rotations = self._transform_rotations(
transformation_matrix, rotations)
transformation_matrix, rotations
)
return (pointgroup_symbol,
transformation_matrix,
conventional_rotations)
return (pointgroup_symbol, transformation_matrix, conventional_rotations)
def _transform_rotations(self, tmat, rotations):
trans_rots = []
@ -197,10 +238,9 @@ class IrReps(object):
def _get_ground_matrix(self):
matrices = []
for (r, t) in zip(self._rotations_at_q,
self._translations_at_q):
for (r, t) in zip(self._rotations_at_q, self._translations_at_q):
lat = self._primitive.get_cell().T
lat = self._primitive.cell.T
r_cart = similarity_transformation(lat, r)
perm_mat = self._get_modified_permutation_matrix(r, t)
@ -217,8 +257,8 @@ class IrReps(object):
return np.array(characters), np.array(irrep_dims)
def _get_modified_permutation_matrix(self, r, t):
num_atom = self._primitive.get_number_of_atoms()
pos = self._primitive.get_scaled_positions()
num_atom = len(self._primitive)
pos = self._primitive.scaled_positions
matrix = np.zeros((num_atom, num_atom), dtype=complex)
for i, p1 in enumerate(pos):
p_rot = np.dot(r, p1) + t
@ -232,8 +272,7 @@ class IrReps(object):
# considering internal atomic positions, so
# the phase factors of eigenvectors are shifted in
# _get_irreps().
phase_factor = np.dot(
self._q, np.dot(np.linalg.inv(r), p2 - p_rot))
phase_factor = np.dot(self._q, np.dot(np.linalg.inv(r), p2 - p_rot))
# This phase factor comes from non-pure-translation of
# each symmetry opration.
@ -247,8 +286,12 @@ class IrReps(object):
def _get_irreps(self):
eigvecs = []
phases = np.kron(
[np.exp(2j * np.pi * np.dot(self._q, pos))
for pos in self._primitive.get_scaled_positions()], [1, 1, 1])
[
np.exp(2j * np.pi * np.dot(self._q, pos))
for pos in self._primitive.scaled_positions
],
[1, 1, 1],
)
for vec in self._eigvecs.T:
eigvecs.append(vec * phases)
@ -278,20 +321,33 @@ class IrReps(object):
def _get_character_projection_operators(self, idx_irrep):
dim = self._irrep_dims[idx_irrep]
chars = self._characters[idx_irrep]
return np.sum([mat * char.conj()
for mat, char in zip(self._ground_matrices, chars)],
axis=0) * dim / self._g
return (
np.sum(
[mat * char.conj() for mat, char in zip(self._ground_matrices, chars)],
axis=0,
)
* dim
/ self._g
)
def _get_projection_operators(self, idx_irrep, i, j):
dim = self._irrep_dims[idx_irrep]
return np.sum([mat * r[i, j].conj() for mat, r
in zip(self._ground_matrices, self._irreps[idx_irrep])],
axis=0) * dim / self._g
return (
np.sum(
[
mat * r[i, j].conj()
for mat, r in zip(self._ground_matrices, self._irreps[idx_irrep])
],
axis=0,
)
* dim
/ self._g
)
def _get_rotation_symbols(self):
ptg_symbol = self._pointgroup_symbol
for ct in character_table[ptg_symbol]:
mapping_table = ct['mapping_table']
mapping_table = ct["mapping_table"]
rotation_symbols = []
for r in self._conventional_rotations:
rotation_symbols.append(_get_rotation_symbol(r, mapping_table))
@ -307,21 +363,21 @@ class IrReps(object):
def _get_ir_labels(self):
ir_labels = []
rot_list = self._character_table['rotation_list']
char_table = self._character_table['character_table']
rot_list = self._character_table["rotation_list"]
char_table = self._character_table["character_table"]
for chars, deg_set in zip(self._characters, self._degenerate_sets):
chars_ordered = np.zeros(len(rot_list), dtype=complex)
for rs, ch in zip(self._rotation_symbols, chars):
chars_ordered[rot_list.index(rs)] += ch
for i, rl in enumerate(rot_list):
chars_ordered[i] /= len(
self._character_table['mapping_table'][rl])
chars_ordered[i] /= len(self._character_table["mapping_table"][rl])
found = False
for ct_label in char_table.keys():
if (abs(chars_ordered - np.array(char_table[ct_label])) <
self._symprec).all():
if (
abs(chars_ordered - np.array(char_table[ct_label])) < self._symprec
).all():
ir_labels.append(ct_label)
found = True
break
@ -342,8 +398,8 @@ class IrReps(object):
def _is_primitive_cell(self):
num_identity = 0
for r in self._symmetry_dataset['rotations']:
if (r - np.eye(3, dtype='intc') == 0).all():
for r in self._symmetry_dataset["rotations"]:
if (r - np.eye(3, dtype="intc") == 0).all():
num_identity += 1
if num_identity > 1:
return False
@ -351,39 +407,41 @@ class IrReps(object):
return True
def _show(self, show_irreps):
print('')
print("")
print("-------------------------------")
print(" Irreducible representations")
print("-------------------------------")
print("q-point: %s" % self._q)
print("Point group: %s" % self._pointgroup_symbol)
print('')
print("")
if (np.abs(self._q) < self._symprec).all():
width = 6
print("Original rotation matrices:")
print('')
print("")
_print_rotations(self._rotations_at_q, width=width)
else:
width = 4
print("Original symmetry operations:")
print('')
_print_rotations(self._rotations_at_q,
translations=self._translations_at_q,
width=width)
print("")
_print_rotations(
self._rotations_at_q, translations=self._translations_at_q, width=width
)
print("Transformation matrix:")
print('')
print("")
for v in self._transformation_matrix:
print("%6.3f %6.3f %6.3f" % tuple(v))
print('')
print("")
print("Rotation matrices by transformation matrix:")
print('')
_print_rotations(self._conventional_rotations,
print("")
_print_rotations(
self._conventional_rotations,
rotation_symbols=self._rotation_symbols,
width=width)
width=width,
)
print("Character table:")
print('')
print("")
for i, deg_set in enumerate(self._degenerate_sets):
text = "%3d (%8.3f): " % (deg_set[0] + 1, self._freqs[deg_set[0]])
if self._ir_labels is None:
@ -394,23 +452,24 @@ class IrReps(object):
else:
print("%s%s" % (text, self._ir_labels[i]))
_print_characters(self._characters[i])
print('')
print("")
if show_irreps:
self._show_irreps()
def _show_irreps(self):
print("IR representations:")
print('')
print("")
for i, (deg_set, irrep_Rs) in enumerate(zip(self._degenerate_sets,
self._irreps)):
for i, (deg_set, irrep_Rs) in enumerate(
zip(self._degenerate_sets, self._irreps)
):
print("%3d (%8.3f):" % (deg_set[0] + 1, self._freqs[deg_set[0]]))
print('')
print("")
for j, irrep_R in enumerate(irrep_Rs):
for k, irrep_Rk in enumerate(irrep_R):
text = " "
for l, irrep_Rkl in enumerate(irrep_Rk):
for ll, irrep_Rkl in enumerate(irrep_Rk):
if irrep_Rkl.real > 0:
sign_r = " "
else:
@ -426,21 +485,24 @@ class IrReps(object):
else:
str_index = " "
if l > 0:
str_index = ''
if ll > 0:
str_index = ""
text += "%s (%s%5.3f %s%5.3fi) " % (
str_index,
sign_r, abs(irrep_Rkl.real),
sign_i, abs(irrep_Rkl.imag))
sign_r,
abs(irrep_Rkl.real),
sign_i,
abs(irrep_Rkl.imag),
)
print(text)
if len(irrep_R) > 1:
print('')
print("")
if len(irrep_R) == 1:
print('')
print("")
def _write_yaml(self, show_irreps):
w = open("irreps.yaml", 'w')
w = open("irreps.yaml", "w")
w.write("q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(self._q))
w.write("point_group: %s\n" % self._pointgroup_symbol)
w.write("transformation_matrix:\n")
@ -487,30 +549,39 @@ class IrReps(object):
w.write("\n")
w.write("irreps:\n")
for i, (deg_set, irrep_Rs) in enumerate(
zip(self._degenerate_sets, self._irreps)):
zip(self._degenerate_sets, self._irreps)
):
w.write("- # %d\n" % (i + 1))
for j, irrep_R in enumerate(irrep_Rs):
if self._rotation_symbols:
symbol = self._rotation_symbols[j]
else:
symbol = ''
symbol = ""
if len(deg_set) > 1:
w.write(" - # %d %s\n" % (j + 1, symbol))
for k, v in enumerate(irrep_R):
w.write(" - [ ")
for x in v[:-1]:
w.write("%10.7f, %10.7f, " % (x.real, x.imag))
w.write("%10.7f, %10.7f ] # (" %
(v[-1].real, v[-1].imag))
w.write("%10.7f, %10.7f ] # (" % (v[-1].real, v[-1].imag))
w.write(("%5.0f" * len(v)) %
tuple((np.angle(v) / np.pi * 180) % 360))
w.write(
("%5.0f" * len(v))
% tuple((np.angle(v) / np.pi * 180) % 360)
)
w.write(")\n")
else:
x = irrep_R[0][0]
w.write(" - [ [ %10.7f, %10.7f ] ] # (%3.0f) %d %s\n" %
(x.real, x.imag,
(np.angle(x) / np.pi * 180) % 360, j + 1, symbol))
w.write(
" - [ [ %10.7f, %10.7f ] ] # (%3.0f) %d %s\n"
% (
x.real,
x.imag,
(np.angle(x) / np.pi * 180) % 360,
j + 1,
symbol,
)
)
pass
@ -543,20 +614,19 @@ def _print_characters(characters, width=6):
text = ""
def _get_rotation_text(rotations,
translations,
rotation_symbols,
width,
num_rest,
i):
def _get_rotation_text(rotations, translations, rotation_symbols, width, num_rest, i):
lines = []
if rotation_symbols is None:
if translations is None:
lines.append((" %2d " * num_rest) %
tuple(np.arange(i * width, i * width + num_rest) + 1))
lines.append(
(" %2d " * num_rest)
% tuple(np.arange(i * width, i * width + num_rest) + 1)
)
else:
lines.append((" %2d " * num_rest) %
tuple(np.arange(i * width, i * width + num_rest) + 1))
lines.append(
(" %2d " * num_rest)
% tuple(np.arange(i * width, i * width + num_rest) + 1)
)
else:
text = ""
for k in range(num_rest):
@ -588,29 +658,24 @@ def _get_rotation_text(rotations,
if translations is not None:
text += "%5.2f " % translations[i * width + k][j]
lines.append(text)
lines.append('')
lines.append("")
return "\n".join(lines)
def _print_rotations(rotations,
translations=None,
rotation_symbols=None,
width=6):
def _print_rotations(rotations, translations=None, rotation_symbols=None, width=6):
for i in range(len(rotations) // width):
print(_get_rotation_text(rotations,
translations,
rotation_symbols,
width,
width,
i))
print(
_get_rotation_text(
rotations, translations, rotation_symbols, width, width, i
)
)
num_rest = len(rotations) % width
if num_rest > 0:
i = len(rotations) // width
print(_get_rotation_text(rotations,
translations,
rotation_symbols,
width,
num_rest,
i))
print(
_get_rotation_text(
rotations, translations, rotation_symbols, width, num_rest, i
)
)

View File

@ -1,3 +1,4 @@
"""Phonon calculation on sampling mesh."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -32,13 +33,17 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import warnings
import numpy as np
from phonopy.units import VaspToTHz
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix
from phonopy.structure.grid_points import GridPoints
from phonopy.units import VaspToTHz
class MeshBase(object):
"""Base class of Mesh and IterMesh classes
class MeshBase:
"""Base class of Mesh and IterMesh classes.
Attributes
----------
@ -71,8 +76,9 @@ class MeshBase(object):
"""
def __init__(self,
dynamical_matrix,
def __init__(
self,
dynamical_matrix: DynamicalMatrix,
mesh,
shift=None,
is_time_reversal=True,
@ -80,21 +86,24 @@ class MeshBase(object):
with_eigenvectors=False,
is_gamma_center=False,
rotations=None, # Point group operations in real space
factor=VaspToTHz):
self._mesh = np.array(mesh, dtype='intc')
factor=VaspToTHz,
):
"""Init method."""
self._mesh = np.array(mesh, dtype="intc")
self._with_eigenvectors = with_eigenvectors
self._factor = factor
self._cell = dynamical_matrix.primitive
self._dynamical_matrix = dynamical_matrix
self._gp = GridPoints(self._mesh,
self._gp = GridPoints(
self._mesh,
np.linalg.inv(self._cell.cell),
q_mesh_shift=shift,
is_gamma_center=is_gamma_center,
is_time_reversal=(is_time_reversal and
is_mesh_symmetry),
is_time_reversal=(is_time_reversal and is_mesh_symmetry),
rotations=rotations,
is_mesh_symmetry=is_mesh_symmetry)
is_mesh_symmetry=is_mesh_symmetry,
)
self._qpoints = self._gp.qpoints
self._weights = self._gp.weights
@ -106,60 +115,106 @@ class MeshBase(object):
@property
def mesh_numbers(self):
"""Return mesh numbers."""
return self._mesh
def get_mesh_numbers(self):
"""Return mesh numbers."""
warnings.warn(
"MeshBase.get_mesh_numbers() is deprecated. Use mesh_numbers attribute.",
DeprecationWarning,
)
return self.mesh_numbers
@property
def qpoints(self):
"""Return (irreducible) q-points."""
return self._qpoints
def get_qpoints(self):
"""Return (irreducible) q-points."""
warnings.warn(
"MeshBase.get_qpoints() is deprecated. Use qpoints attribute.",
DeprecationWarning,
)
return self.qpoints
@property
def weights(self):
"""Return (irreducible) weights of q-points."""
return self._weights
def get_weights(self):
"""Return (irreducible) weights of q-points."""
warnings.warn(
"MeshBase.get_weights()) is deprecated. Use weights attribute.",
DeprecationWarning,
)
return self.weights
@property
def grid_address(self):
"""Return mesh grid addresses."""
return self._gp.grid_address
def get_grid_address(self):
"""Return mesh grid addresses."""
warnings.warn(
"MeshBase.get_grid_address()) is deprecated. Use grid_address attribute.",
DeprecationWarning,
)
return self.grid_address
@property
def ir_grid_points(self):
"""Return irreducible grid indices."""
return self._gp.ir_grid_points
def get_ir_grid_points(self):
"""Return irreducible grid indices."""
warnings.warn(
"MeshBase.get_ir_grid_points() is deprecated. "
"Use ir_grid_points attribute.",
DeprecationWarning,
)
return self.ir_grid_points
@property
def grid_mapping_table(self):
"""Return grid index mapping table."""
return self._gp.grid_mapping_table
def get_grid_mapping_table(self):
"""Return grid index mapping table."""
warnings.warn(
"MeshBase.get_grid_mapping_table() is deprecated. "
"Use grid_mapping_table attribute.",
DeprecationWarning,
)
return self.grid_mapping_table
@property
def dynamical_matrix(self):
"""Return dynamical matrix class instance."""
return self._dynamical_matrix
def get_dynamical_matrix(self):
"""Return dynamical matrix class instance."""
warnings.warn(
"MeshBase.get_dynamical_matrix() is deprecated. "
"Use dynamical_matrix attribute.",
DeprecationWarning,
)
return self.dynamical_matrix
@property
def with_eigenvectors(self):
"""Whether eigenvectors are calculated or not."""
return self._with_eigenvectors
class Mesh(MeshBase):
"""Class for phonons on mesh grid
"""Class for phonons on mesh grid.
Frequencies and eigenvectors can be also accessible by iterator
representation to be compatible with IterMesh.
@ -184,7 +239,9 @@ class Mesh(MeshBase):
More attributes from MeshBase should be watched.
"""
def __init__(self,
def __init__(
self,
dynamical_matrix,
mesh,
shift=None,
@ -195,8 +252,10 @@ class Mesh(MeshBase):
group_velocity=None,
rotations=None, # Point group operations in real space
factor=VaspToTHz,
use_lapack_solver=False):
MeshBase.__init__(self,
use_lapack_solver=False,
):
"""Init method."""
super().__init__(
dynamical_matrix,
mesh,
shift=shift,
@ -205,21 +264,27 @@ class Mesh(MeshBase):
with_eigenvectors=with_eigenvectors,
is_gamma_center=is_gamma_center,
rotations=rotations,
factor=factor)
factor=factor,
)
self._group_velocity = group_velocity
self._group_velocities = None
self._use_lapack_solver = use_lapack_solver
def __iter__(self):
"""Define iterator over q-points.
Initially, all phonons are computed and stored in arrays.
Then this is just used as an iterator to return exisiting results.
The purpose of this iterator is compatible use of IterMesh.
"""
if self._frequencies is None:
self.run()
return self
def next(self):
return self.__next__()
def __next__(self):
"""Return phonon frequencies and eigenvectors at each q-point."""
if self._q_count == len(self._qpoints):
self._q_count = 0
raise StopIteration
@ -232,22 +297,30 @@ class Mesh(MeshBase):
return self._frequencies[i], self._eigenvectors[i]
def run(self):
"""Calculate phonons at all required q-points."""
self._set_phonon()
if self._group_velocity is not None:
self._set_group_velocities(self._group_velocity)
@property
def frequencies(self):
"""Return phonon frequencies."""
if self._frequencies is None:
self.run()
return self._frequencies
def get_frequencies(self):
"""Return phonon frequencies."""
warnings.warn(
"Mesh.get_frequencies() is deprecated. " "Use frequencies attribute.",
DeprecationWarning,
)
return self.frequencies
@property
def eigenvectors(self):
"""
"""Return eigenvectors.
Eigenvectors is a numpy array of three dimension.
The first index runs through q-points.
In the second and third indices, eigenvectors obtained
@ -255,57 +328,70 @@ class Mesh(MeshBase):
The third index corresponds to the eigenvalue's index.
The second index is for atoms [x1, y1, z1, x2, y2, z2, ...].
"""
if self._frequencies is None:
self.run()
return self._eigenvectors
def get_eigenvectors(self):
"""Return eigenvectors."""
warnings.warn(
"Mesh.get_eigenvectors() is deprecated. " "Use eigenvectors attribute.",
DeprecationWarning,
)
return self.eigenvectors
@property
def group_velocities(self):
"""Return group velocities."""
if self._frequencies is None:
self.run()
return self._group_velocities
def get_group_velocities(self):
"""Return group velocities."""
warnings.warn(
"Mesh.get_group_velocities() is deprecated. "
"Use group_velocities attribute.",
DeprecationWarning,
)
return self.group_velocities
def write_hdf5(self):
def write_hdf5(self, filename="mesh.hdf5"):
"""Write results to hdf5 file."""
import h5py
with h5py.File('mesh.hdf5', 'w') as w:
w.create_dataset('mesh', data=self._mesh)
w.create_dataset('qpoint', data=self._qpoints)
w.create_dataset('weight', data=self._weights)
w.create_dataset('frequency', data=self._frequencies)
if self._eigenvectors is not None:
w.create_dataset('eigenvector', data=self._eigenvectors)
if self._group_velocities is not None:
w.create_dataset('group_velocity', data=self._group_velocities)
def write_yaml(self):
natom = self._cell.get_number_of_atoms()
rec_lattice = np.linalg.inv(self._cell.get_cell()) # column vectors
distances = np.sqrt(
np.sum(np.dot(self._qpoints, rec_lattice.T) ** 2, axis=1))
with h5py.File(filename, "w") as w:
w.create_dataset("mesh", data=self._mesh)
w.create_dataset("qpoint", data=self._qpoints)
w.create_dataset("weight", data=self._weights)
w.create_dataset("frequency", data=self._frequencies)
if self._eigenvectors is not None:
w.create_dataset("eigenvector", data=self._eigenvectors)
if self._group_velocities is not None:
w.create_dataset("group_velocity", data=self._group_velocities)
def write_yaml(self, filename="mesh.yaml"):
"""Write results to yaml file."""
natom = len(self._cell)
rec_lattice = np.linalg.inv(self._cell.cell) # column vectors
distances = np.sqrt(np.sum(np.dot(self._qpoints, rec_lattice.T) ** 2, axis=1))
lines = []
lines.append("mesh: [ %5d, %5d, %5d ]" % tuple(self._mesh))
lines.append("nqpoint: %-7d" % self._qpoints.shape[0])
lines.append("reciprocal_lattice:")
for vec, axis in zip(rec_lattice.T, ('a*', 'b*', 'c*')):
lines.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" %
(tuple(vec) + (axis,)))
for vec, axis in zip(rec_lattice.T, ("a*", "b*", "c*")):
lines.append("- [ %12.8f, %12.8f, %12.8f ] # %2s" % (tuple(vec) + (axis,)))
lines.append("natom: %-7d" % natom)
lines.append(str(self._cell))
lines.append("")
lines.append("phonon:")
for i, (q, d) in enumerate(zip(self._qpoints, distances)):
lines.append("- q-position: [ %12.7f, %12.7f, %12.7f ]"
% tuple(q))
lines.append("- q-position: [ %12.7f, %12.7f, %12.7f ]" % tuple(q))
lines.append(" distance_from_gamma: %12.9f" % d)
lines.append(" weight: %-5d" % self._weights[i])
lines.append(" band:")
@ -315,43 +401,58 @@ class Mesh(MeshBase):
lines.append(" frequency: %15.10f" % freq)
if self._group_velocities is not None:
lines.append(" group_velocity: "
"[ %13.7f, %13.7f, %13.7f ]" %
tuple(self._group_velocities[i, j]))
lines.append(
" group_velocity: "
"[ %13.7f, %13.7f, %13.7f ]"
% tuple(self._group_velocities[i, j])
)
if self._with_eigenvectors:
lines.append(" eigenvector:")
for k in range(natom):
lines.append(" - # atom %d" % (k+1))
for l in (0, 1, 2):
lines.append(" - # atom %d" % (k + 1))
for ll in (0, 1, 2):
lines.append(
" - [ %17.14f, %17.14f ]"
% (self._eigenvectors[i, k*3+l, j].real,
self._eigenvectors[i, k*3+l, j].imag))
% (
self._eigenvectors[i, k * 3 + ll, j].real,
self._eigenvectors[i, k * 3 + ll, j].imag,
)
)
lines.append("")
with open('mesh.yaml', 'w') as w:
with open(filename, "w") as w:
w.write("\n".join(lines))
def _set_phonon(self):
num_band = self._cell.get_number_of_atoms() * 3
num_band = len(self._cell) * 3
num_qpoints = len(self._qpoints)
self._frequencies = np.zeros((num_qpoints, num_band), dtype='double')
self._frequencies = np.zeros((num_qpoints, num_band), dtype="double")
if self._with_eigenvectors or self._use_lapack_solver:
dtype = "c%d" % (np.dtype('double').itemsize * 2)
dtype = "c%d" % (np.dtype("double").itemsize * 2)
self._eigenvectors = np.zeros(
(num_qpoints, num_band, num_band,), dtype=dtype, order='C')
(
num_qpoints,
num_band,
num_band,
),
dtype=dtype,
order="C",
)
if self._use_lapack_solver:
from phono3py.phonon.solver import get_phonons_at_qpoints
get_phonons_at_qpoints(self._frequencies,
get_phonons_at_qpoints(
self._frequencies,
self._eigenvectors,
self._dynamical_matrix,
self._qpoints,
self._factor,
nac_q_direction=None,
lapack_zheev_uplo='L')
lapack_zheev_uplo="L",
)
else:
for i, q in enumerate(self._qpoints):
self._dynamical_matrix.run(q)
@ -361,10 +462,14 @@ class Mesh(MeshBase):
eigenvalues = eigvals.real
else:
eigenvalues = np.linalg.eigvalsh(dm).real
self._frequencies[i] = np.array(np.sqrt(abs(eigenvalues)) *
np.sign(eigenvalues),
dtype='double',
order='C') * self._factor
self._frequencies[i] = (
np.array(
np.sqrt(abs(eigenvalues)) * np.sign(eigenvalues),
dtype="double",
order="C",
)
* self._factor
)
def _set_group_velocities(self, group_velocity):
group_velocity.run(self._qpoints)
@ -372,7 +477,7 @@ class Mesh(MeshBase):
class IterMesh(MeshBase):
"""Generator class for phonons on mesh grid
"""Generator class for phonons on mesh grid.
Not like as Mesh class, frequencies and eigenvectors are not
stored, instead generated by iterator. This may be used for
@ -383,7 +488,9 @@ class IterMesh(MeshBase):
Attributes from MeshBase should be watched.
"""
def __init__(self,
def __init__(
self,
dynamical_matrix,
mesh,
shift=None,
@ -392,8 +499,10 @@ class IterMesh(MeshBase):
with_eigenvectors=False,
is_gamma_center=False,
rotations=None, # Point group operations in real space
factor=VaspToTHz):
MeshBase.__init__(self,
factor=VaspToTHz,
):
"""Init method."""
super().__init__(
dynamical_matrix,
mesh,
shift=shift,
@ -402,15 +511,15 @@ class IterMesh(MeshBase):
with_eigenvectors=with_eigenvectors,
is_gamma_center=is_gamma_center,
rotations=rotations,
factor=factor)
factor=factor,
)
def __iter__(self):
"""Define iterator over q-points."""
return self
def next(self):
return self.__next__()
def __next__(self):
"""Calculate phonons at a q-point."""
if self._q_count == len(self._qpoints):
self._q_count = 0
raise StopIteration
@ -423,9 +532,13 @@ class IterMesh(MeshBase):
eigenvalues = eigvals.real
else:
eigenvalues = np.linalg.eigvalsh(dm).real
frequencies = np.array(np.sqrt(abs(eigenvalues)) *
np.sign(eigenvalues),
dtype='double',
order='C') * self._factor
frequencies = (
np.array(
np.sqrt(abs(eigenvalues)) * np.sign(eigenvalues),
dtype="double",
order="C",
)
* self._factor
)
self._q_count += 1
return frequencies, eigenvectors

View File

@ -1,3 +1,4 @@
"""Create atomic displacements."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -32,31 +33,34 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from typing import Union
import numpy as np
from phonopy.structure.cells import get_supercell
from phonopy.interface.vasp import write_vasp
from phonopy.units import VaspToTHz
from phonopy.phonon.degeneracy import get_eigenvectors
from phonopy.harmonic.derivative_dynmat import DerivativeOfDynamicalMatrix
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.interface.vasp import write_vasp
from phonopy.phonon.degeneracy import get_eigenvectors
from phonopy.structure.cells import get_supercell
from phonopy.units import VaspToTHz
class Modulation(object):
def __init__(self,
dynamical_matrix,
class Modulation:
"""Class to create atomic displacements."""
def __init__(
self,
dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
dimension,
phonon_modes,
delta_q=None,
derivative_order=None,
nac_q_direction=None,
factor=VaspToTHz):
"""Class describe atomic modulations
Atomic modulations corresponding to phonon modes are created.
"""
factor=VaspToTHz,
):
"""Init method."""
self._dm = dynamical_matrix
self._primitive = dynamical_matrix.get_primitive()
self._primitive = dynamical_matrix.primitive
self._phonon_modes = phonon_modes
self._dimension = np.array(dimension).ravel()
self._delta_q = delta_q # 1st/2nd order perturbation direction
@ -65,16 +69,22 @@ class Modulation(object):
self._derivative_order = derivative_order
self._factor = factor
self._u = []
self._eigvecs = []
self._eigvals = []
self._supercell = None
dim = self._get_dimension_3x3()
self._supercell = get_supercell(self._primitive, dim)
complex_dtype = "c%d" % (np.dtype("double").itemsize * 2)
self._u = np.zeros(
(len(self._phonon_modes), len(self._supercell), 3),
dtype=complex_dtype,
order="C",
)
self._eigvals = np.zeros(len(self._phonon_modes), dtype="double")
self._eigvecs = np.zeros(
(len(self._phonon_modes), len(self._primitive) * 3), dtype=complex_dtype
)
def run(self):
for ph_mode in self._phonon_modes:
"""Calculate modulations."""
for i, ph_mode in enumerate(self._phonon_modes):
q, band_index, amplitude, argument = ph_mode
eigvals, eigvecs = get_eigenvectors(
q,
@ -82,29 +92,30 @@ class Modulation(object):
self._ddm,
perturbation=self._delta_q,
derivative_order=self._derivative_order,
nac_q_direction=self._nac_q_direction)
u = self._get_displacements(eigvecs[:, band_index],
q,
amplitude,
argument)
self._u.append(u)
self._eigvecs.append(eigvecs[:, band_index])
self._eigvals.append(eigvals[band_index])
nac_q_direction=self._nac_q_direction,
)
u = self._get_displacements(eigvecs[:, band_index], q, amplitude, argument)
self._u[i] = u
self._eigvecs[i] = eigvecs[:, band_index]
self._eigvals[i] = eigvals[band_index]
def get_modulated_supercells(self):
"""Return modulations."""
modulations = []
for u in self._u:
modulations.append(self._get_cell_with_modulation(u))
return modulations
def get_modulations_and_supercell(self):
"""Return modulations and perfect supercell."""
return self._u, self._supercell
def write(self, filename="MPOSCAR"):
"""Write supercells with modulations to MPOSCARs."""
deltas = []
for i, u in enumerate(self._u):
cell = self._get_cell_with_modulation(u)
write_vasp((filename+"-%03d") % (i+1), cell, direct=True)
write_vasp((filename + "-%03d") % (i + 1), cell, direct=True)
deltas.append(u)
sum_of_deltas = np.sum(deltas, axis=0)
@ -112,20 +123,21 @@ class Modulation(object):
write_vasp(filename, cell, direct=True)
no_modulations = np.zeros(sum_of_deltas.shape, dtype=complex)
cell = self._get_cell_with_modulation(no_modulations)
write_vasp(filename+"-orig", cell, direct=True)
write_vasp(filename + "-orig", cell, direct=True)
def write_yaml(self):
self._write_yaml()
def write_yaml(self, filename="modulation.yaml"):
"""Write modulations to file in yaml."""
self._write_yaml(filename=filename)
def _get_cell_with_modulation(self, modulation):
lattice = self._supercell.get_cell()
positions = self._supercell.get_positions()
lattice = self._supercell.cell
positions = self._supercell.positions
positions += modulation.real
scaled_positions = np.dot(positions, np.linalg.inv(lattice))
for p in scaled_positions:
p -= np.floor(p)
cell = self._supercell.copy()
cell.set_scaled_positions(scaled_positions)
cell.scaled_positions = scaled_positions
return cell
@ -137,26 +149,25 @@ class Modulation(object):
else:
dim = np.array(self._dimension)
if dim.shape == (3, 3):
dim = np.array(dim, dtype='intc')
dim = np.array(dim, dtype="intc")
else:
print("Dimension is incorrectly set. Unit cell is used.")
dim = np.eye(3, dtype='intc')
dim = np.eye(3, dtype="intc")
return dim
def _get_displacements(self, eigvec, q, amplitude, argument):
m = self._supercell.get_masses()
s2u_map = self._supercell.get_supercell_to_unitcell_map()
u2u_map = self._supercell.get_unitcell_to_unitcell_map()
m = self._supercell.masses
s2u_map = self._supercell.s2u_map
u2u_map = self._supercell.u2u_map
s2uu_map = [u2u_map[x] for x in s2u_map]
spos = self._supercell.get_scaled_positions()
dim = self._supercell.get_supercell_matrix()
coefs = (np.exp(2j * np.pi * np.dot(np.dot(spos, dim.T), q))
/ np.sqrt(m))
spos = self._supercell.scaled_positions
dim = self._supercell.supercell_matrix
coefs = np.exp(2j * np.pi * np.dot(np.dot(spos, dim.T), q)) / np.sqrt(m)
u = []
for i, coef in enumerate(coefs):
eig_index = s2uu_map[i] * 3
u.append(eigvec[eig_index:eig_index + 3] * coef)
u.append(eigvec[eig_index : eig_index + 3] * coef)
u = np.array(u) / np.sqrt(len(m))
phase_factor = self._get_phase_factor(u, argument)
@ -177,10 +188,10 @@ class Modulation(object):
e = np.array(eigvals).real
return np.sqrt(np.abs(e)) * np.sign(e) * self._factor
def _write_yaml(self):
w = open('modulation.yaml', 'w')
primitive = self._dm.get_primitive()
num_atom = primitive.get_number_of_atoms()
def _write_yaml(self, filename="modulation.yaml"):
w = open(filename, "w")
primitive = self._dm.primitive
num_atom = len(primitive)
w.write("primitive_cell:\n")
self._write_cell_yaml(primitive, w)
@ -190,11 +201,10 @@ class Modulation(object):
for v in dim:
w.write(" - [ %d, %d, %d ]\n" % tuple(v))
self._write_cell_yaml(self._supercell, w)
inv_lattice = np.linalg.inv(self._supercell.get_cell().T)
inv_lattice = np.linalg.inv(self._supercell.cell.T)
w.write("modulations:\n")
for u, mode in zip(self._u,
self._phonon_modes):
for u, mode in zip(self._u, self._phonon_modes):
q = mode[0]
w.write("- q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(q))
w.write(" band: %d\n" % (mode[1] + 1))
@ -202,28 +212,34 @@ class Modulation(object):
w.write(" phase: %f\n" % mode[3])
w.write(" displacements:\n")
for i, p in enumerate(u):
w.write(" - [ %20.15f, %20.15f ] # %d x (%f)\n" %
(p[0].real, p[0].imag, i + 1, abs(p[0])))
w.write(" - [ %20.15f, %20.15f ] # %d y (%f)\n" %
(p[1].real, p[1].imag, i + 1, abs(p[1])))
w.write(" - [ %20.15f, %20.15f ] # %d z (%f)\n" %
(p[2].real, p[2].imag, i + 1, abs(p[2])))
w.write(
" - [ %20.15f, %20.15f ] # %d x (%f)\n"
% (p[0].real, p[0].imag, i + 1, abs(p[0]))
)
w.write(
" - [ %20.15f, %20.15f ] # %d y (%f)\n"
% (p[1].real, p[1].imag, i + 1, abs(p[1]))
)
w.write(
" - [ %20.15f, %20.15f ] # %d z (%f)\n"
% (p[2].real, p[2].imag, i + 1, abs(p[2]))
)
w.write(" fractional_displacements:\n")
for i, p in enumerate(np.dot(u, inv_lattice.T)):
w.write(" - [ %20.15f, %20.15f ] # %d a\n" %
(p[0].real, p[0].imag, i + 1))
w.write(" - [ %20.15f, %20.15f ] # %d b\n" %
(p[1].real, p[1].imag, i + 1))
w.write(" - [ %20.15f, %20.15f ] # %d c\n" %
(p[2].real, p[2].imag, i + 1))
w.write(
" - [ %20.15f, %20.15f ] # %d a\n" % (p[0].real, p[0].imag, i + 1)
)
w.write(
" - [ %20.15f, %20.15f ] # %d b\n" % (p[1].real, p[1].imag, i + 1)
)
w.write(
" - [ %20.15f, %20.15f ] # %d c\n" % (p[2].real, p[2].imag, i + 1)
)
w.write("phonon:\n")
freqs = self._eigvals_to_frequencies(self._eigvals)
for eigvec, freq, mode in zip(self._eigvecs,
freqs,
self._phonon_modes):
w.write("- q-position: [ %12.7f, %12.7f, %12.7f ]\n"
% tuple(mode[0]))
for eigvec, freq, mode in zip(self._eigvecs, freqs, self._phonon_modes):
w.write("- q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(mode[0]))
w.write(" band: %d\n" % (mode[1] + 1))
w.write(" amplitude: %f\n" % mode[2])
w.write(" phase: %f\n" % mode[3])
@ -233,8 +249,10 @@ class Modulation(object):
w.write(" - # atom %d\n" % (j + 1))
for k in (0, 1, 2):
val = eigvec[j * 3 + k]
w.write(" - [ %17.14f, %17.14f ] # %f\n" %
(val.real, val.imag, np.angle(val, deg=True)))
w.write(
" - [ %17.14f, %17.14f ] # %f\n"
% (val.real, val.imag, np.angle(val, deg=True))
)
def _write_cell_yaml(self, cell, w):
lattice = cell.get_cell()
@ -246,9 +264,8 @@ class Modulation(object):
w.write(" - { name: %2s, mass: %10.5f }\n" % (s, m))
w.write(" reciprocal_lattice:\n")
for vec, axis in zip(np.linalg.inv(lattice), ('a*', 'b*', 'c*')):
w.write(" - [ %12.8f, %12.8f, %12.8f ] # %2s\n" %
(tuple(vec) + (axis,)))
for vec, axis in zip(np.linalg.inv(lattice), ("a*", "b*", "c*")):
w.write(" - [ %12.8f, %12.8f, %12.8f ] # %2s\n" % (tuple(vec) + (axis,)))
w.write(" real_lattice:\n")
w.write(" - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[0])))
w.write(" - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[1])))

View File

@ -1,3 +1,4 @@
"""Calculate phonon state moments."""
# Copyright (C) 2016 Atsushi Togo
# All rights reserved.
#
@ -32,14 +33,24 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import warnings
import numpy as np
class PhononMoment(object):
def __init__(self,
frequencies,
weights,
eigenvectors=None):
class PhononMoment:
"""Calculate phonon state moments.
Attributes
----------
moment : float or ndarray
Phonon state moment of specified order (float) or
projected phonon state moment of specified order (ndarray).
"""
def __init__(self, frequencies, weights, eigenvectors=None):
"""Init method."""
self._frequencies = frequencies
self._eigenvectors = eigenvectors
self._weights = weights
@ -50,15 +61,20 @@ class PhononMoment(object):
@property
def moment(self):
"""Return phonon state moment."""
return self._moment
def get_moment(self):
"""Return phonon state moment."""
warnings.warn(
"PhononMoment.get_moment() is deprecated. "
"Use PhononMoment.moment attribute.",
DeprecationWarning,
)
return self.moment
def set_frequency_range(self,
freq_min=None,
freq_max=None,
tolerance=1e-8):
def set_frequency_range(self, freq_min=None, freq_max=None, tolerance=1e-8):
"""Set frequeny range where moment is computed."""
if freq_min is None:
self._fmin = tolerance
else:
@ -70,6 +86,7 @@ class PhononMoment(object):
self._fmax = freq_max + tolerance
def run(self, order=1):
"""Calculate phonon state moment of specified order."""
if self._eigenvectors is None:
self._get_moment(order)
else:
@ -80,20 +97,26 @@ class PhononMoment(object):
norm0 = 0
for i, w in enumerate(self._weights):
for freq in self._frequencies[i]:
if self._fmin < freq and freq < self._fmax :
if self._fmin < freq and freq < self._fmax:
norm0 += w
moment += freq ** order * w
self._moment = moment / norm0
def _get_projected_moment(self, order):
moment = np.zeros(self._frequencies.shape[1], dtype='double')
moment = np.zeros(self._frequencies.shape[1], dtype="double")
norm0 = np.zeros_like(moment)
for i, w in enumerate(self._weights):
for freq, eigvec in zip(self._frequencies[i],
self._eigenvectors[i].T):
if self._fmin < freq and freq < self._fmax :
for freq, eigvec in zip(self._frequencies[i], self._eigenvectors[i].T):
if self._fmin < freq and freq < self._fmax:
projection = np.abs(eigvec) ** 2
norm0 += w * projection
moment += freq ** order * w * projection
self._moment = np.array([np.sum((moment / norm0)[i * 3:(i + 1) * 3])
for i in range(len(moment) // 3)]) / 3
self._moment = (
np.array(
[
np.sum((moment / norm0)[i * 3 : (i + 1) * 3])
for i in range(len(moment) // 3)
]
)
/ 3
)

View File

@ -1,3 +1,4 @@
"""Phonon calculation at specific q-points."""
# Copyright (C) 2011 Atsushi Togo
# All rights reserved.
#
@ -33,12 +34,18 @@
# POSSIBILITY OF SUCH DAMAGE.
import warnings
from typing import Optional, Union
import numpy as np
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC
from phonopy.phonon.group_velocity import GroupVelocity
from phonopy.structure.cells import Primitive
from phonopy.units import VaspToTHz
class QpointsPhonon(object):
"""Calculate phonons at specified qpoints
class QpointsPhonon:
"""Calculate phonons at specified qpoints.
Attributes
----------
@ -62,15 +69,18 @@ class QpointsPhonon(object):
"""
def __init__(self,
def __init__(
self,
qpoints,
dynamical_matrix,
dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
nac_q_direction=None,
with_eigenvectors=False,
group_velocity=None,
with_dynamical_matrices=False,
factor=VaspToTHz):
primitive = dynamical_matrix.primitive
factor=VaspToTHz,
):
"""Init method."""
primitive: Primitive = dynamical_matrix.primitive
self._natom = len(primitive)
self._masses = primitive.masses
self._symbols = primitive.symbols
@ -81,7 +91,7 @@ class QpointsPhonon(object):
self._dynamical_matrix = dynamical_matrix
self._nac_q_direction = nac_q_direction
self._with_eigenvectors = with_eigenvectors
self._group_velocity = group_velocity
self._gv_obj: Optional[GroupVelocity] = group_velocity
self._with_dynamical_matrices = with_dynamical_matrices
self._factor = factor
@ -95,58 +105,77 @@ class QpointsPhonon(object):
@property
def frequencies(self):
"""Return frequencies."""
return self._frequencies
def get_frequencies(self):
warnings.warn("Use attribute, frequencies.", DeprecationWarning)
"""Return frequencies."""
warnings.warn(
"QpointsPhonon.get_frequencies() is deprecated. Use frequencies instead.",
DeprecationWarning,
)
return self.frequencies
@property
def eigenvalues(self):
"""Return eigenvalues."""
return self._eigenvalues
@property
def eigenvectors(self):
"""Return eigenvectors."""
return self._eigenvectors
def get_eigenvectors(self):
warnings.warn("Use attribute, eigenvectors.", DeprecationWarning)
"""Return eigenvectors."""
warnings.warn(
"QpointsPhonon.get_eigenvectors() is deprecated. Use eigenvectors instead.",
DeprecationWarning,
)
return self.eigenvectors
@property
def group_velocities(self):
"""Return group velocities."""
return self._group_velocities
def get_group_velocities(self):
warnings.warn("Use attribute, group_velocities.", DeprecationWarning)
"""Return group velocities."""
warnings.warn(
"QpointsPhonon.get_group_velocities() is deprecated. "
"Use group_velocities instead.",
DeprecationWarning,
)
return self.group_velocities
@property
def dynamical_matrices(self):
"""Return DynamicalMatrix class instance."""
return self._dynamical_matrices
def write_hdf5(self):
def write_hdf5(self, filename="qpoints.hdf5"):
"""Write results in hdf5."""
import h5py
with h5py.File('qpoints.hdf5', 'w') as w:
w.create_dataset('qpoint', data=self._qpoints)
w.create_dataset('frequency', data=self._frequencies)
if self._with_eigenvectors:
w.create_dataset('eigenvector', data=self._eigenvectors)
if self._group_velocities is not None:
w.create_dataset('group_velocity', data=self._group_velocities)
if self._with_dynamical_matrices:
w.create_dataset('dynamical_matrix',
data=self._dynamical_matrices)
def write_yaml(self):
w = open('qpoints.yaml', 'w')
with h5py.File(filename, "w") as w:
w.create_dataset("qpoint", data=self._qpoints)
w.create_dataset("frequency", data=self._frequencies)
if self._with_eigenvectors:
w.create_dataset("eigenvector", data=self._eigenvectors)
if self._group_velocities is not None:
w.create_dataset("group_velocity", data=self._group_velocities)
if self._with_dynamical_matrices:
w.create_dataset("dynamical_matrix", data=self._dynamical_matrices)
def write_yaml(self, filename="qpoints.yaml"):
"""Write results in yaml."""
w = open(filename, "w")
w.write("nqpoint: %-7d\n" % len(self._qpoints))
w.write("natom: %-7d\n" % self._natom)
rec_lattice = np.linalg.inv(self._lattice) # column vectors
w.write("reciprocal_lattice:\n")
for vec, axis in zip(rec_lattice.T, ('a*', 'b*', 'c*')):
w.write("- [ %12.8f, %12.8f, %12.8f ] # %2s\n" %
(tuple(vec) + (axis,)))
for vec, axis in zip(rec_lattice.T, ("a*", "b*", "c*")):
w.write("- [ %12.8f, %12.8f, %12.8f ] # %2s\n" % (tuple(vec) + (axis,)))
w.write("phonon:\n")
for i, q in enumerate(self._qpoints):
@ -168,24 +197,29 @@ class QpointsPhonon(object):
w.write(" frequency: %15.10f\n" % freq)
if self._group_velocities is not None:
w.write(" group_velocity: [ %13.7f, %13.7f, %13.7f ]\n"
% tuple(self._group_velocities[i, j]))
w.write(
" group_velocity: [ %13.7f, %13.7f, %13.7f ]\n"
% tuple(self._group_velocities[i, j])
)
if self._with_eigenvectors:
w.write(" eigenvector:\n")
for k in range(self._natom):
w.write(" - # atom %d\n" % (k + 1))
for l in (0, 1, 2):
w.write(" - [ %17.14f, %17.14f ]\n" %
(self._eigenvectors[i][k * 3 + l, j].real,
self._eigenvectors[i][k * 3 + l, j].imag))
for ll in (0, 1, 2):
w.write(
" - [ %17.14f, %17.14f ]\n"
% (
self._eigenvectors[i][k * 3 + ll, j].real,
self._eigenvectors[i][k * 3 + ll, j].imag,
)
)
w.write("\n")
def _run(self):
if self._group_velocity is not None:
self._group_velocity.run(
self._qpoints, perturbation=self._nac_q_direction)
self._group_velocities = self._group_velocity.group_velocities
if self._gv_obj is not None:
self._gv_obj.run(self._qpoints, perturbation=self._nac_q_direction)
self._group_velocities = self._gv_obj.group_velocities
if self._with_dynamical_matrices:
dynamical_matrices = []
@ -206,25 +240,27 @@ class QpointsPhonon(object):
eigvals = np.linalg.eigvalsh(dm)
eigvals = eigvals.real
eigenvalues.append(eigvals)
frequencies.append(np.sqrt(np.abs(eigvals)) *
np.sign(eigvals) * self._factor)
frequencies.append(
np.sqrt(np.abs(eigvals)) * np.sign(eigvals) * self._factor
)
self._eigenvalues = np.array(eigenvalues, dtype='double', order='C')
self._frequencies = np.array(frequencies, dtype='double', order='C')
dtype = "c%d" % (np.dtype('double').itemsize * 2)
self._eigenvalues = np.array(eigenvalues, dtype="double", order="C")
self._frequencies = np.array(frequencies, dtype="double", order="C")
dtype = "c%d" % (np.dtype("double").itemsize * 2)
if self._with_eigenvectors:
self._eigenvectors = np.array(eigenvectors,
dtype=dtype, order='C')
self._eigenvectors = np.array(eigenvectors, dtype=dtype, order="C")
if self._with_dynamical_matrices:
self._dynamical_matrices = np.array(dynamical_matrices,
dtype=dtype, order='C')
self._dynamical_matrices = np.array(
dynamical_matrices, dtype=dtype, order="C"
)
def _get_dynamical_matrix(self, q):
if (self._dynamical_matrix.is_nac() and
self._nac_q_direction is not None and
(np.abs(q) < 1e-5).all()):
self._dynamical_matrix.run(
q, q_direction=self._nac_q_direction)
if (
self._dynamical_matrix.is_nac()
and self._nac_q_direction is not None
and (np.abs(q) < 1e-5).all()
):
self._dynamical_matrix.run(q, q_direction=self._nac_q_direction)
else:
self._dynamical_matrix.run(q)
return self._dynamical_matrix.dynamical_matrix

Some files were not shown because too many files have changed in this diff Show More