1# Copyright (C) 2018 Atsushi Togo
2# All rights reserved.
3#
4# This file is part of phonopy.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# * Redistributions of source code must retain the above copyright
11#   notice, this list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright
14#   notice, this list of conditions and the following disclaimer in
15#   the documentation and/or other materials provided with the
16#   distribution.
17#
18# * Neither the name of the phonopy project nor the names of its
19#   contributors may be used to endorse or promote products derived
20#   from this software without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33# POSSIBILITY OF SUCH DAMAGE.
34
35import numpy as np
36from phonopy.interface.calculator import (
37    read_crystal_structure, get_default_cell_filename)
38from phonopy.interface.vasp import read_vasp
39from phonopy.interface.phonopy_yaml import PhonopyYaml
40
41
42def collect_cell_info(supercell_matrix=None,
43                      primitive_matrix=None,
44                      interface_mode=None,
45                      cell_filename=None,
46                      chemical_symbols=None,
47                      enforce_primitive_matrix_auto=False,
48                      phonopy_yaml_cls=None,
49                      symprec=1e-5,
50                      return_dict=False):
51    # In some cases, interface mode falls back to phonopy_yaml mode.
52    fallback_reason = _fallback_to_phonopy_yaml(
53        supercell_matrix,
54        interface_mode,
55        cell_filename)
56
57    if fallback_reason:
58        _interface_mode = 'phonopy_yaml'
59    elif interface_mode is None:
60        _interface_mode = None
61    else:
62        _interface_mode = interface_mode.lower()
63
64    unitcell, optional_structure_info = read_crystal_structure(
65        filename=cell_filename,
66        interface_mode=_interface_mode,
67        chemical_symbols=chemical_symbols,
68        phonopy_yaml_cls=phonopy_yaml_cls)
69
70    # Error check
71    if unitcell is None:
72        err_msg = _get_error_message(optional_structure_info,
73                                     interface_mode,
74                                     fallback_reason,
75                                     cell_filename,
76                                     phonopy_yaml_cls)
77        return err_msg
78
79    # Retrieve more information on cells
80    (interface_mode_out,
81     supercell_matrix_out,
82     primitive_matrix_out) = _collect_cells_info(
83         _interface_mode,
84         optional_structure_info,
85         interface_mode,
86         supercell_matrix,
87         primitive_matrix,
88         enforce_primitive_matrix_auto)
89
90    # Another error check
91    msg_list = ["Crystal structure was read from \"%s\"."
92                % optional_structure_info[0], ]
93    if supercell_matrix_out is None:
94        msg_list.append(
95            "Supercell matrix (DIM or --dim) information was not found.")
96        return "\n".join(msg_list)
97
98    if np.linalg.det(unitcell.get_cell()) < 0.0:
99        msg_list.append("Lattice vectors have to follow the right-hand rule.")
100        return "\n".join(msg_list)
101
102    # Succeeded!
103    if _interface_mode == 'phonopy_yaml':
104        phpy_yaml = optional_structure_info[1]
105    else:
106        phpy_yaml = None
107
108    if return_dict:
109        return {'unitcell': unitcell,
110                'supercell_matrix': supercell_matrix_out,
111                'primitive_matrix': primitive_matrix_out,
112                'optional_structure_info': optional_structure_info,
113                'interface_mode': interface_mode_out,
114                'phonopy_yaml': phpy_yaml}
115    else:
116        return (unitcell, supercell_matrix_out, primitive_matrix_out,
117                optional_structure_info, interface_mode_out, phpy_yaml)
118
119
120def _fallback_to_phonopy_yaml(supercell_matrix,
121                              interface_mode,
122                              cell_filename):
123    """Find possibility to fallback to phonopy.yaml mode
124
125    Fallback happens in any of the following cases.
126
127    1. Parsing crystal structure file in the VASP POSCAR-style failed
128    2. Default VASP POSCAR-style file is not found.
129    3. supercell_matrix is not given along with (1) or (2).
130
131    Parameters
132    ----------
133    supercell_matrix : array_like or None
134        None is given when phonopy.yaml mode is expected.
135    interface_mode : str or None
136        None is the default mode, i.e., VASP like.
137    cell_filename : str or None
138        File name of VASP style crystal structure. None means the default
139        file name, "POSCAR".
140
141    Returns
142    -------
143    fallback_reason : str or None
144        This provides information how to handle after the fallback.
145        None means fallback to phonopy.yaml mode will not happen.
146
147    """
148
149    fallback_reason = None
150
151    if interface_mode is None:
152        fallback_reason = _poscar_failed(cell_filename)
153
154    if fallback_reason is not None:
155        if supercell_matrix is None:
156            fallback_reason = "no supercell matrix given"
157
158    return fallback_reason
159
160
161def _poscar_failed(cell_filename):
162    """Determine if fall back happens
163
164    1) read_vasp (parsing POSCAR-style file) is failed. --> fallback
165
166    ValueError is raised by read_vasp when the POSCAR-style format
167    is broken. By this way, we assume the input crystal structure
168    is not in the POSCAR-style format and is in the phonopy.yaml
169    type.
170
171    2) The file given by get_default_cell_filename('vasp') is not
172       found at the current directory.  --> fallback
173
174    This is the trigger to look for the phonopy.yaml type file.
175
176    3) The given file with cell_filename is not found.  --> not fallback
177
178    This case will not invoke phonopy.yaml mode and here nothing
179    is done, i.e., fallback_reason = None.
180    This error will be caught in the following part again be
181    handled properly (read_crystal_structure).
182
183    """
184
185    fallback_reason = None
186    try:
187        if cell_filename is None:
188            read_vasp(get_default_cell_filename('vasp'))
189        else:
190            read_vasp(cell_filename)
191    except ValueError:
192        # (1) see above
193        fallback_reason = "read_vasp parsing failed"
194    except FileNotFoundError:
195        if cell_filename is None:
196            # (2) see above
197            fallback_reason = "default file not found"
198        else:
199            # (3) see above
200            pass
201    return fallback_reason
202
203
204def _collect_cells_info(_interface_mode,
205                        optional_structure_info,
206                        interface_mode,
207                        supercell_matrix,
208                        primitive_matrix,
209                        enforce_primitive_matrix_auto):
210    """This is a method just to wrap up and exclude dirty stuffs."""
211
212    if (_interface_mode == 'phonopy_yaml' and
213        optional_structure_info[1] is not None):
214        phpy = optional_structure_info[1]
215        if phpy.calculator is None:
216            interface_mode_out = interface_mode
217        else:
218            interface_mode_out = phpy.calculator
219        if phpy.supercell_matrix is None:
220            _supercell_matrix = supercell_matrix
221        else:
222            _supercell_matrix = phpy.supercell_matrix
223        if primitive_matrix is not None:
224            _primitive_matrix = primitive_matrix
225        elif phpy.primitive_matrix is not None:
226            _primitive_matrix = phpy.primitive_matrix
227        else:
228            _primitive_matrix = 'auto'
229    else:
230        interface_mode_out = _interface_mode
231        _supercell_matrix = supercell_matrix
232        _primitive_matrix = primitive_matrix
233
234    if enforce_primitive_matrix_auto:
235        _primitive_matrix = 'auto'
236
237    if _supercell_matrix is None and _primitive_matrix == 'auto':
238        supercell_matrix_out = np.eye(3, dtype='intc')
239    else:
240        supercell_matrix_out = _supercell_matrix
241
242    primitive_matrix_out = _primitive_matrix
243
244    return interface_mode_out, supercell_matrix_out, primitive_matrix_out
245
246
247def _get_error_message(optional_structure_info,
248                       interface_mode,
249                       fallback_reason,
250                       cell_filename,
251                       phonopy_yaml_cls):
252    final_cell_filename = optional_structure_info[0]
253    if phonopy_yaml_cls is None:
254        _phonopy_yaml_cls = PhonopyYaml
255    else:
256        _phonopy_yaml_cls = phonopy_yaml_cls
257
258    if fallback_reason is None:
259        msg_list = []
260        if cell_filename != final_cell_filename:
261            msg_list.append("Crystal structure file \"%s\" was not found."
262                            % cell_filename)
263        msg_list.append("Crystal structure file \"%s\" was not found."
264                        % final_cell_filename)
265        return "\n".join(msg_list)
266
267    ####################################
268    # Must be phonopy_yaml mode below. #
269    ####################################
270
271    msg_list = []
272    if fallback_reason in ["default file not found",
273                           "read_vasp parsing failed"]:
274        if cell_filename:
275            vasp_filename = cell_filename
276        else:
277            vasp_filename = get_default_cell_filename('vasp')
278
279        if fallback_reason == "read_vasp parsing failed":
280            msg_list.append(
281                "Parsing crystal structure file of \"%s\" failed."
282                % vasp_filename)
283        else:
284            msg_list.append(
285                "Crystal structure file of \"%s\" was not found."
286                % vasp_filename)
287
288    elif fallback_reason == "no supercell matrix given":
289        msg_list.append("Supercell matrix (DIM or --dim) was not explicitly "
290                        "specified.")
291
292    msg_list.append("By this reason, %s_yaml mode was invoked."
293                    % _phonopy_yaml_cls.command_name)
294
295    if final_cell_filename is None:  # No phonopy*.yaml file was found.
296        filenames = ["\"%s\"" % name
297                     for name in _phonopy_yaml_cls.default_filenames]
298        if len(filenames) == 1:
299            text = filenames[0]
300        elif len(filenames) == 2:
301            text = " and ".join(filenames)
302        else:
303            tail = " and ".join(filenames[-2:])
304            head = ", ".join(filenames[:-2])
305            text = head + ", " + tail
306        msg_list.append("But %s could not be found." % text)
307        return "\n".join(msg_list)
308
309    phpy = optional_structure_info[1]
310    if phpy is None:  # Failed to parse phonopy*.yaml.
311        msg_list.append("But parsing \"%s\" failed." % final_cell_filename)
312
313    return "\n".join(msg_list)
314