guohanghui commited on
Commit
a2d1f3c
·
verified ·
1 Parent(s): fcf6a69

Update phonopy/mcp_output/mcp_plugin/mcp_service.py

Browse files
phonopy/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,131 +1,1153 @@
 
 
 
 
 
 
 
 
 
1
  from fastmcp import FastMCP
 
 
 
2
 
3
  # Create the FastMCP service application
4
  mcp = FastMCP("phonopy_service")
5
 
6
- @mcp.tool(name="calculate_phonon_band_structure", description="Calculate phonon band structure using Phonopy")
7
- def calculate_phonon_band_structure(cell: dict, supercell_matrix: list, band_paths: list) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
- Calculate the phonon band structure using Phonopy.
10
 
11
  Parameters:
12
- - cell: A dictionary representing the unit cell.
13
- - supercell_matrix: A list defining the supercell matrix.
14
- - band_paths: A list of paths in reciprocal space for band structure calculation.
 
 
 
 
15
 
16
  Returns:
17
- - dict: Phonon band structure data.
18
  """
19
  try:
20
- from phonopy import Phonopy
21
- from phonopy.structure.atoms import PhonopyAtoms
22
-
23
- unitcell = PhonopyAtoms(**cell)
24
- phonon = Phonopy(unitcell, supercell_matrix)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  phonon.generate_displacements()
26
- phonon.set_band_structure(band_paths)
27
- band_structure = phonon.get_band_structure()
28
-
29
  return {
30
  "success": True,
31
- "band_structure": band_structure
 
 
 
32
  }
33
  except Exception as e:
34
  return {"success": False, "error": str(e)}
35
 
36
- @mcp.tool(name="compute_thermal_properties", description="Compute thermal properties using Phonopy")
37
- def compute_thermal_properties(cell: dict, supercell_matrix: list, temperature_range: list) -> dict:
 
 
 
 
 
 
 
 
38
  """
39
- Compute thermal properties using Phonopy.
40
 
41
  Parameters:
42
- - cell: A dictionary representing the unit cell.
43
- - supercell_matrix: A list defining the supercell matrix.
44
- - temperature_range: A list of temperatures for thermal property calculation.
 
 
 
45
 
46
  Returns:
47
- - dict: Thermal properties data.
48
  """
49
  try:
50
- from phonopy import Phonopy
51
- from phonopy.structure.atoms import PhonopyAtoms
52
-
53
- unitcell = PhonopyAtoms(**cell)
54
- phonon = Phonopy(unitcell, supercell_matrix)
55
- phonon.generate_displacements()
56
- phonon.set_thermal_properties(temperature_range)
57
- thermal_properties = phonon.get_thermal_properties()
58
-
59
  return {
60
  "success": True,
61
- "thermal_properties": thermal_properties
 
 
 
62
  }
63
  except Exception as e:
64
  return {"success": False, "error": str(e)}
65
 
66
- @mcp.tool(name="generate_force_constants", description="Generate force constants using Phonopy")
67
- def generate_force_constants(cell: dict, supercell_matrix: list, force_sets: list) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
68
  """
69
- Generate force constants using Phonopy.
70
 
71
  Parameters:
72
- - cell: A dictionary representing the unit cell.
73
- - supercell_matrix: A list defining the supercell matrix.
74
- - force_sets: A list of force sets for the calculation.
 
 
 
75
 
76
  Returns:
77
- - dict: Force constants data.
78
  """
79
  try:
80
- from phonopy import Phonopy
81
- from phonopy.structure.atoms import PhonopyAtoms
82
-
83
- unitcell = PhonopyAtoms(**cell)
84
- phonon = Phonopy(unitcell, supercell_matrix)
85
- phonon.set_displacement_dataset(force_sets)
86
- phonon.produce_force_constants()
87
- force_constants = phonon.get_force_constants()
88
-
89
  return {
90
  "success": True,
91
- "force_constants": force_constants
 
 
 
 
92
  }
93
  except Exception as e:
94
  return {"success": False, "error": str(e)}
95
 
96
- @mcp.tool(name="plot_phonon_dos", description="Plot phonon density of states using Phonopy")
97
- def plot_phonon_dos(cell: dict, supercell_matrix: list, mesh: list) -> dict:
 
 
 
 
 
 
 
 
98
  """
99
- Plot phonon density of states using Phonopy.
100
 
101
  Parameters:
102
- - cell: A dictionary representing the unit cell.
103
- - supercell_matrix: A list defining the supercell matrix.
104
- - mesh: A list defining the mesh for DOS calculation.
 
 
 
105
 
106
  Returns:
107
- - dict: Phonon DOS plot data.
108
  """
109
  try:
110
- from phonopy import Phonopy
111
- from phonopy.structure.atoms import PhonopyAtoms
112
-
113
- unitcell = PhonopyAtoms(**cell)
114
- phonon = Phonopy(unitcell, supercell_matrix)
115
- phonon.set_mesh(mesh)
116
- phonon.set_total_DOS()
117
- dos = phonon.get_total_DOS()
118
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  return {
120
  "success": True,
121
- "dos": dos
 
 
122
  }
123
  except Exception as e:
124
  return {"success": False, "error": str(e)}
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  def create_app() -> FastMCP:
127
  """
128
- Create and return the FastMCP application instance.
129
 
130
  Returns:
131
  - FastMCP: The FastMCP application instance.
 
1
+ import os
2
+ import sys
3
+ from typing import List, Optional
4
+
5
+ # Add the local source directory to sys.path
6
+ source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
7
+ if source_path not in sys.path:
8
+ sys.path.insert(0, source_path)
9
+
10
  from fastmcp import FastMCP
11
+ import numpy as np
12
+ from phonopy.api_phonopy import Phonopy
13
+ from phonopy.structure.atoms import PhonopyAtoms
14
 
15
  # Create the FastMCP service application
16
  mcp = FastMCP("phonopy_service")
17
 
18
+
19
+ # ============================================================================
20
+ # Utility functions
21
+ # ============================================================================
22
+
23
+ def _create_phonopy_from_structure(
24
+ lattice: List[List[float]],
25
+ positions: List[List[float]],
26
+ numbers: List[int],
27
+ supercell_matrix: Optional[List[List[int]]] = None,
28
+ primitive_matrix: Optional[str] = None
29
+ ) -> Phonopy:
30
+ """Helper to create Phonopy instance from structure data."""
31
+ cell = PhonopyAtoms(
32
+ cell=np.array(lattice),
33
+ scaled_positions=np.array(positions),
34
+ numbers=np.array(numbers)
35
+ )
36
+ sc_matrix = supercell_matrix if supercell_matrix else [[2, 0, 0], [0, 2, 0], [0, 0, 2]]
37
+ p_matrix = primitive_matrix if primitive_matrix else "auto"
38
+ return Phonopy(cell, supercell_matrix=sc_matrix, primitive_matrix=p_matrix)
39
+
40
+
41
+ # ============================================================================
42
+ # Core Displacement and Force Constants Tools
43
+ # ============================================================================
44
+
45
+ @mcp.tool(name="generate_displacements", description="Generate atomic displacements for phonon calculations with customizable parameters.")
46
+ def generate_displacements(
47
+ lattice: List[List[float]],
48
+ positions: List[List[float]],
49
+ numbers: List[int],
50
+ supercell_matrix: Optional[List[List[int]]] = None,
51
+ distance: float = 0.01,
52
+ is_plusminus: str = "auto",
53
+ is_diagonal: bool = True
54
+ ) -> dict:
55
  """
56
+ Generate atomic displacements for phonon calculations.
57
 
58
  Parameters:
59
+ - lattice: 3x3 lattice vectors in Angstrom.
60
+ - positions: Fractional atomic positions.
61
+ - numbers: Atomic numbers.
62
+ - supercell_matrix: Supercell transformation matrix (default 2x2x2).
63
+ - distance: Displacement distance in Angstrom (default 0.01).
64
+ - is_plusminus: 'auto', 'True', or 'False' for displacement directions.
65
+ - is_diagonal: Whether to use diagonal displacements.
66
 
67
  Returns:
68
+ - dict: Displacement dataset information.
69
  """
70
  try:
71
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
72
+ phonon.generate_displacements(
73
+ distance=distance,
74
+ is_plusminus=is_plusminus,
75
+ is_diagonal=is_diagonal
76
+ )
77
+ dataset = phonon.dataset
78
+ result = {
79
+ "num_displacements": len(dataset.get("first_atoms", [])) if "first_atoms" in dataset else len(dataset.get("displacements", [])),
80
+ "supercell_atoms": len(phonon.supercell),
81
+ "primitive_atoms": len(phonon.primitive)
82
+ }
83
+ if "first_atoms" in dataset:
84
+ result["displacements"] = [
85
+ {"atom_index": d["number"], "displacement": d["displacement"].tolist()}
86
+ for d in dataset["first_atoms"]
87
+ ]
88
+ return {"success": True, "result": result}
89
+ except Exception as e:
90
+ return {"success": False, "error": str(e)}
91
+
92
+
93
+ @mcp.tool(name="calculate_force_constants", description="Calculate force constants from displacement-force datasets.")
94
+ def calculate_force_constants(
95
+ lattice: List[List[float]],
96
+ positions: List[List[float]],
97
+ numbers: List[int],
98
+ displacements: List[dict],
99
+ forces: List[List[List[float]]],
100
+ supercell_matrix: Optional[List[List[int]]] = None,
101
+ fc_calculator: Optional[str] = None
102
+ ) -> dict:
103
+ """
104
+ Calculate force constants from displacement-force datasets.
105
+
106
+ Parameters:
107
+ - lattice: 3x3 lattice vectors.
108
+ - positions: Fractional atomic positions.
109
+ - numbers: Atomic numbers.
110
+ - displacements: List of displacement data.
111
+ - forces: Forces on atoms for each displacement.
112
+ - supercell_matrix: Supercell transformation matrix.
113
+ - fc_calculator: External calculator ('symfc', 'alm', or None for traditional).
114
+
115
+ Returns:
116
+ - dict: Force constants calculation status and shape.
117
+ """
118
+ try:
119
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
120
  phonon.generate_displacements()
121
+ phonon.forces = np.array(forces)
122
+ phonon.produce_force_constants(fc_calculator=fc_calculator)
123
+ fc = phonon.force_constants
124
  return {
125
  "success": True,
126
+ "result": {
127
+ "force_constants_shape": list(fc.shape),
128
+ "message": "Force constants calculated successfully"
129
+ }
130
  }
131
  except Exception as e:
132
  return {"success": False, "error": str(e)}
133
 
134
+
135
+ @mcp.tool(name="symmetrize_force_constants", description="Symmetrize force constants by translational and permutation symmetry.")
136
+ def symmetrize_force_constants(
137
+ lattice: List[List[float]],
138
+ positions: List[List[float]],
139
+ numbers: List[int],
140
+ force_constants: List[List[List[List[float]]]],
141
+ supercell_matrix: Optional[List[List[int]]] = None,
142
+ level: int = 1
143
+ ) -> dict:
144
  """
145
+ Symmetrize force constants.
146
 
147
  Parameters:
148
+ - lattice: 3x3 lattice vectors.
149
+ - positions: Fractional atomic positions.
150
+ - numbers: Atomic numbers.
151
+ - force_constants: Force constants array.
152
+ - supercell_matrix: Supercell transformation matrix.
153
+ - level: Number of symmetrization iterations.
154
 
155
  Returns:
156
+ - dict: Symmetrized force constants shape.
157
  """
158
  try:
159
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
160
+ phonon.force_constants = np.array(force_constants)
161
+ phonon.symmetrize_force_constants(level=level)
 
 
 
 
 
 
162
  return {
163
  "success": True,
164
+ "result": {
165
+ "message": "Force constants symmetrized successfully",
166
+ "shape": list(phonon.force_constants.shape)
167
+ }
168
  }
169
  except Exception as e:
170
  return {"success": False, "error": str(e)}
171
 
172
+
173
+ # ============================================================================
174
+ # Phonon Frequency Calculation Tools
175
+ # ============================================================================
176
+
177
+ @mcp.tool(name="get_frequencies", description="Calculate phonon frequencies at a given q-point.")
178
+ def get_frequencies(
179
+ lattice: List[List[float]],
180
+ positions: List[List[float]],
181
+ numbers: List[int],
182
+ force_constants: List[List[List[List[float]]]],
183
+ q_point: List[float],
184
+ supercell_matrix: Optional[List[List[int]]] = None
185
+ ) -> dict:
186
  """
187
+ Calculate phonon frequencies at a given q-point.
188
 
189
  Parameters:
190
+ - lattice: 3x3 lattice vectors.
191
+ - positions: Fractional atomic positions.
192
+ - numbers: Atomic numbers.
193
+ - force_constants: Force constants array.
194
+ - q_point: Q-point in reduced coordinates [qx, qy, qz].
195
+ - supercell_matrix: Supercell transformation matrix.
196
 
197
  Returns:
198
+ - dict: Phonon frequencies at the q-point.
199
  """
200
  try:
201
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
202
+ phonon.force_constants = np.array(force_constants)
203
+ frequencies = phonon.get_frequencies(q_point)
 
 
 
 
 
 
204
  return {
205
  "success": True,
206
+ "result": {
207
+ "q_point": q_point,
208
+ "frequencies_THz": frequencies.tolist(),
209
+ "num_bands": len(frequencies)
210
+ }
211
  }
212
  except Exception as e:
213
  return {"success": False, "error": str(e)}
214
 
215
+
216
+ @mcp.tool(name="get_frequencies_with_eigenvectors", description="Calculate phonon frequencies and eigenvectors at a q-point.")
217
+ def get_frequencies_with_eigenvectors(
218
+ lattice: List[List[float]],
219
+ positions: List[List[float]],
220
+ numbers: List[int],
221
+ force_constants: List[List[List[List[float]]]],
222
+ q_point: List[float],
223
+ supercell_matrix: Optional[List[List[int]]] = None
224
+ ) -> dict:
225
  """
226
+ Calculate phonon frequencies and eigenvectors at a q-point.
227
 
228
  Parameters:
229
+ - lattice: 3x3 lattice vectors.
230
+ - positions: Fractional atomic positions.
231
+ - numbers: Atomic numbers.
232
+ - force_constants: Force constants array.
233
+ - q_point: Q-point in reduced coordinates.
234
+ - supercell_matrix: Supercell transformation matrix.
235
 
236
  Returns:
237
+ - dict: Frequencies and eigenvector information.
238
  """
239
  try:
240
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
241
+ phonon.force_constants = np.array(force_constants)
242
+ frequencies, eigenvectors = phonon.get_frequencies_with_eigenvectors(q_point)
243
+ return {
244
+ "success": True,
245
+ "result": {
246
+ "q_point": q_point,
247
+ "frequencies_THz": frequencies.tolist(),
248
+ "eigenvectors_shape": list(eigenvectors.shape),
249
+ "num_bands": len(frequencies)
250
+ }
251
+ }
252
+ except Exception as e:
253
+ return {"success": False, "error": str(e)}
254
+
255
+
256
+ @mcp.tool(name="get_dynamical_matrix_at_q", description="Calculate dynamical matrix at a given q-point.")
257
+ def get_dynamical_matrix_at_q(
258
+ lattice: List[List[float]],
259
+ positions: List[List[float]],
260
+ numbers: List[int],
261
+ force_constants: List[List[List[List[float]]]],
262
+ q_point: List[float],
263
+ supercell_matrix: Optional[List[List[int]]] = None
264
+ ) -> dict:
265
+ """
266
+ Calculate dynamical matrix at a given q-point.
267
+
268
+ Parameters:
269
+ - lattice: 3x3 lattice vectors.
270
+ - positions: Fractional atomic positions.
271
+ - numbers: Atomic numbers.
272
+ - force_constants: Force constants array.
273
+ - q_point: Q-point in reduced coordinates.
274
+ - supercell_matrix: Supercell transformation matrix.
275
+
276
+ Returns:
277
+ - dict: Dynamical matrix shape and properties.
278
+ """
279
+ try:
280
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
281
+ phonon.force_constants = np.array(force_constants)
282
+ dm = phonon.get_dynamical_matrix_at_q(q_point)
283
+ return {
284
+ "success": True,
285
+ "result": {
286
+ "q_point": q_point,
287
+ "dynamical_matrix_shape": list(dm.shape),
288
+ "is_hermitian": np.allclose(dm, dm.conj().T)
289
+ }
290
+ }
291
+ except Exception as e:
292
+ return {"success": False, "error": str(e)}
293
+
294
+
295
+ # ============================================================================
296
+ # Band Structure Tools
297
+ # ============================================================================
298
+
299
+ @mcp.tool(name="run_band_structure", description="Run phonon band structure calculation along specified paths.")
300
+ def run_band_structure(
301
+ lattice: List[List[float]],
302
+ positions: List[List[float]],
303
+ numbers: List[int],
304
+ force_constants: List[List[List[List[float]]]],
305
+ paths: List[List[List[float]]],
306
+ supercell_matrix: Optional[List[List[int]]] = None,
307
+ with_eigenvectors: bool = False,
308
+ with_group_velocities: bool = False,
309
+ labels: Optional[List[str]] = None
310
+ ) -> dict:
311
+ """
312
+ Run phonon band structure calculation.
313
+
314
+ Parameters:
315
+ - lattice: 3x3 lattice vectors.
316
+ - positions: Fractional atomic positions.
317
+ - numbers: Atomic numbers.
318
+ - force_constants: Force constants array.
319
+ - paths: List of q-point paths, each path is a list of q-points.
320
+ - supercell_matrix: Supercell transformation matrix.
321
+ - with_eigenvectors: Calculate eigenvectors.
322
+ - with_group_velocities: Calculate group velocities.
323
+ - labels: Labels for special points.
324
+
325
+ Returns:
326
+ - dict: Band structure calculation results.
327
+ """
328
+ try:
329
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
330
+ phonon.force_constants = np.array(force_constants)
331
+ phonon.run_band_structure(
332
+ paths,
333
+ with_eigenvectors=with_eigenvectors,
334
+ with_group_velocities=with_group_velocities,
335
+ labels=labels
336
+ )
337
+ bs_dict = phonon.get_band_structure_dict()
338
+ result = {
339
+ "num_paths": len(bs_dict["qpoints"]),
340
+ "frequencies_ranges": [
341
+ {"min": float(np.min(f)), "max": float(np.max(f))}
342
+ for f in bs_dict["frequencies"]
343
+ ],
344
+ "has_eigenvectors": bs_dict["eigenvectors"] is not None,
345
+ "has_group_velocities": bs_dict["group_velocities"] is not None
346
+ }
347
+ return {"success": True, "result": result}
348
+ except Exception as e:
349
+ return {"success": False, "error": str(e)}
350
+
351
+
352
+ @mcp.tool(name="auto_band_structure", description="Automatically calculate band structure using standard high-symmetry paths.")
353
+ def auto_band_structure(
354
+ lattice: List[List[float]],
355
+ positions: List[List[float]],
356
+ numbers: List[int],
357
+ force_constants: List[List[List[List[float]]]],
358
+ supercell_matrix: Optional[List[List[int]]] = None,
359
+ npoints: int = 101
360
+ ) -> dict:
361
+ """
362
+ Automatically calculate band structure using seekpath.
363
+
364
+ Parameters:
365
+ - lattice: 3x3 lattice vectors.
366
+ - positions: Fractional atomic positions.
367
+ - numbers: Atomic numbers.
368
+ - force_constants: Force constants array.
369
+ - supercell_matrix: Supercell transformation matrix.
370
+ - npoints: Number of points per path segment.
371
+
372
+ Returns:
373
+ - dict: Auto band structure results with high-symmetry path labels.
374
+ """
375
+ try:
376
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
377
+ phonon.force_constants = np.array(force_constants)
378
+ phonon.auto_band_structure(npoints=npoints)
379
+ bs_dict = phonon.get_band_structure_dict()
380
+ result = {
381
+ "num_paths": len(bs_dict["qpoints"]),
382
+ "total_qpoints": sum(len(q) for q in bs_dict["qpoints"]),
383
+ "frequency_range": {
384
+ "min_THz": float(min(np.min(f) for f in bs_dict["frequencies"])),
385
+ "max_THz": float(max(np.max(f) for f in bs_dict["frequencies"]))
386
+ }
387
+ }
388
+ return {"success": True, "result": result}
389
+ except Exception as e:
390
+ return {"success": False, "error": str(e)}
391
+
392
+
393
+ # ============================================================================
394
+ # Mesh Sampling Tools
395
+ # ============================================================================
396
+
397
+ @mcp.tool(name="run_mesh", description="Run phonon calculation on a mesh grid in reciprocal space.")
398
+ def run_mesh(
399
+ lattice: List[List[float]],
400
+ positions: List[List[float]],
401
+ numbers: List[int],
402
+ force_constants: List[List[List[List[float]]]],
403
+ mesh: List[int],
404
+ supercell_matrix: Optional[List[List[int]]] = None,
405
+ shift: Optional[List[float]] = None,
406
+ is_mesh_symmetry: bool = True,
407
+ with_eigenvectors: bool = False,
408
+ with_group_velocities: bool = False,
409
+ is_gamma_center: bool = False
410
+ ) -> dict:
411
+ """
412
+ Run mesh sampling phonon calculation.
413
+
414
+ Parameters:
415
+ - lattice: 3x3 lattice vectors.
416
+ - positions: Fractional atomic positions.
417
+ - numbers: Atomic numbers.
418
+ - force_constants: Force constants array.
419
+ - mesh: Mesh grid numbers [n1, n2, n3].
420
+ - supercell_matrix: Supercell transformation matrix.
421
+ - shift: Mesh shift.
422
+ - is_mesh_symmetry: Use symmetry to reduce mesh points.
423
+ - with_eigenvectors: Store eigenvectors.
424
+ - with_group_velocities: Calculate group velocities.
425
+ - is_gamma_center: Use gamma-centered mesh.
426
+
427
+ Returns:
428
+ - dict: Mesh sampling results.
429
+ """
430
+ try:
431
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
432
+ phonon.force_constants = np.array(force_constants)
433
+ phonon.run_mesh(
434
+ mesh=mesh,
435
+ shift=shift,
436
+ is_mesh_symmetry=is_mesh_symmetry,
437
+ with_eigenvectors=with_eigenvectors,
438
+ with_group_velocities=with_group_velocities,
439
+ is_gamma_center=is_gamma_center
440
+ )
441
+ mesh_dict = phonon.get_mesh_dict()
442
+ result = {
443
+ "mesh": mesh,
444
+ "num_irreducible_qpoints": len(mesh_dict["qpoints"]),
445
+ "total_mesh_points": int(np.prod(mesh)),
446
+ "frequency_range": {
447
+ "min_THz": float(np.min(mesh_dict["frequencies"])),
448
+ "max_THz": float(np.max(mesh_dict["frequencies"]))
449
+ },
450
+ "has_eigenvectors": mesh_dict["eigenvectors"] is not None,
451
+ "has_group_velocities": mesh_dict["group_velocities"] is not None
452
+ }
453
+ return {"success": True, "result": result}
454
+ except Exception as e:
455
+ return {"success": False, "error": str(e)}
456
+
457
+
458
+ # ============================================================================
459
+ # DOS (Density of States) Tools
460
+ # ============================================================================
461
+
462
+ @mcp.tool(name="run_total_dos", description="Calculate total phonon density of states.")
463
+ def run_total_dos(
464
+ lattice: List[List[float]],
465
+ positions: List[List[float]],
466
+ numbers: List[int],
467
+ force_constants: List[List[List[List[float]]]],
468
+ mesh: List[int],
469
+ supercell_matrix: Optional[List[List[int]]] = None,
470
+ sigma: Optional[float] = None,
471
+ freq_min: Optional[float] = None,
472
+ freq_max: Optional[float] = None,
473
+ freq_pitch: Optional[float] = None,
474
+ use_tetrahedron_method: bool = True
475
+ ) -> dict:
476
+ """
477
+ Calculate total phonon density of states.
478
+
479
+ Parameters:
480
+ - lattice: 3x3 lattice vectors.
481
+ - positions: Fractional atomic positions.
482
+ - numbers: Atomic numbers.
483
+ - force_constants: Force constants array.
484
+ - mesh: Mesh grid numbers for sampling.
485
+ - supercell_matrix: Supercell transformation matrix.
486
+ - sigma: Smearing width (None for tetrahedron method).
487
+ - freq_min/freq_max/freq_pitch: Frequency range and step.
488
+ - use_tetrahedron_method: Use tetrahedron method.
489
+
490
+ Returns:
491
+ - dict: DOS results with frequency points and DOS values.
492
+ """
493
+ try:
494
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
495
+ phonon.force_constants = np.array(force_constants)
496
+ phonon.run_mesh(mesh=mesh)
497
+ phonon.run_total_dos(
498
+ sigma=sigma,
499
+ freq_min=freq_min,
500
+ freq_max=freq_max,
501
+ freq_pitch=freq_pitch,
502
+ use_tetrahedron_method=use_tetrahedron_method
503
+ )
504
+ dos_dict = phonon.get_total_dos_dict()
505
+ result = {
506
+ "frequency_range_THz": {
507
+ "min": float(dos_dict["frequency_points"][0]),
508
+ "max": float(dos_dict["frequency_points"][-1])
509
+ },
510
+ "num_frequency_points": len(dos_dict["frequency_points"]),
511
+ "dos_max": float(np.max(dos_dict["total_dos"])),
512
+ "integrated_dos": float(np.trapezoid(dos_dict["total_dos"], dos_dict["frequency_points"]))
513
+ }
514
+ return {"success": True, "result": result}
515
+ except Exception as e:
516
+ return {"success": False, "error": str(e)}
517
+
518
+
519
+ @mcp.tool(name="run_projected_dos", description="Calculate projected phonon density of states.")
520
+ def run_projected_dos(
521
+ lattice: List[List[float]],
522
+ positions: List[List[float]],
523
+ numbers: List[int],
524
+ force_constants: List[List[List[List[float]]]],
525
+ mesh: List[int],
526
+ supercell_matrix: Optional[List[List[int]]] = None,
527
+ sigma: Optional[float] = None,
528
+ use_tetrahedron_method: bool = True,
529
+ xyz_projection: bool = False
530
+ ) -> dict:
531
+ """
532
+ Calculate projected phonon density of states (PDOS).
533
+
534
+ Parameters:
535
+ - lattice: 3x3 lattice vectors.
536
+ - positions: Fractional atomic positions.
537
+ - numbers: Atomic numbers.
538
+ - force_constants: Force constants array.
539
+ - mesh: Mesh grid numbers.
540
+ - supercell_matrix: Supercell transformation matrix.
541
+ - sigma: Smearing width.
542
+ - use_tetrahedron_method: Use tetrahedron method.
543
+ - xyz_projection: Project along Cartesian directions.
544
+
545
+ Returns:
546
+ - dict: PDOS results.
547
+ """
548
+ try:
549
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
550
+ phonon.force_constants = np.array(force_constants)
551
+ phonon.run_mesh(mesh=mesh, is_mesh_symmetry=False, with_eigenvectors=True)
552
+ phonon.run_projected_dos(
553
+ sigma=sigma,
554
+ use_tetrahedron_method=use_tetrahedron_method,
555
+ xyz_projection=xyz_projection
556
+ )
557
+ pdos_dict = phonon.get_projected_dos_dict()
558
+ result = {
559
+ "num_projections": len(pdos_dict["projected_dos"]),
560
+ "num_frequency_points": len(pdos_dict["frequency_points"]),
561
+ "frequency_range_THz": {
562
+ "min": float(pdos_dict["frequency_points"][0]),
563
+ "max": float(pdos_dict["frequency_points"][-1])
564
+ }
565
+ }
566
+ return {"success": True, "result": result}
567
+ except Exception as e:
568
+ return {"success": False, "error": str(e)}
569
+
570
+
571
+ @mcp.tool(name="get_debye_frequency", description="Calculate Debye frequency from total DOS.")
572
+ def get_debye_frequency(
573
+ lattice: List[List[float]],
574
+ positions: List[List[float]],
575
+ numbers: List[int],
576
+ force_constants: List[List[List[List[float]]]],
577
+ mesh: List[int],
578
+ supercell_matrix: Optional[List[List[int]]] = None,
579
+ freq_max_fit: Optional[float] = None
580
+ ) -> dict:
581
+ """
582
+ Calculate Debye frequency from total DOS.
583
+
584
+ Parameters:
585
+ - lattice: 3x3 lattice vectors.
586
+ - positions: Fractional atomic positions.
587
+ - numbers: Atomic numbers.
588
+ - force_constants: Force constants array.
589
+ - mesh: Mesh grid numbers.
590
+ - supercell_matrix: Supercell transformation matrix.
591
+ - freq_max_fit: Maximum frequency for fitting.
592
+
593
+ Returns:
594
+ - dict: Debye frequency in THz.
595
+ """
596
+ try:
597
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
598
+ phonon.force_constants = np.array(force_constants)
599
+ phonon.run_mesh(mesh=mesh)
600
+ phonon.run_total_dos()
601
+ phonon.set_Debye_frequency(freq_max_fit=freq_max_fit)
602
+ debye_freq = phonon.get_Debye_frequency()
603
  return {
604
  "success": True,
605
+ "result": {
606
+ "debye_frequency_THz": float(debye_freq) if debye_freq else None
607
+ }
608
  }
609
  except Exception as e:
610
  return {"success": False, "error": str(e)}
611
 
612
+
613
+ # ============================================================================
614
+ # Thermal Properties Tools
615
+ # ============================================================================
616
+
617
+ @mcp.tool(name="run_thermal_properties", description="Calculate thermal properties at constant volume.")
618
+ def run_thermal_properties(
619
+ lattice: List[List[float]],
620
+ positions: List[List[float]],
621
+ numbers: List[int],
622
+ force_constants: List[List[List[List[float]]]],
623
+ mesh: List[int],
624
+ supercell_matrix: Optional[List[List[int]]] = None,
625
+ t_min: float = 0,
626
+ t_max: float = 1000,
627
+ t_step: float = 10,
628
+ cutoff_frequency: Optional[float] = None,
629
+ classical: bool = False
630
+ ) -> dict:
631
+ """
632
+ Calculate thermal properties (free energy, entropy, heat capacity).
633
+
634
+ Parameters:
635
+ - lattice: 3x3 lattice vectors.
636
+ - positions: Fractional atomic positions.
637
+ - numbers: Atomic numbers.
638
+ - force_constants: Force constants array.
639
+ - mesh: Mesh grid numbers.
640
+ - supercell_matrix: Supercell transformation matrix.
641
+ - t_min/t_max/t_step: Temperature range and step (K).
642
+ - cutoff_frequency: Cutoff for imaginary frequencies.
643
+ - classical: Use classical statistics.
644
+
645
+ Returns:
646
+ - dict: Thermal properties at temperature points.
647
+ """
648
+ try:
649
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
650
+ phonon.force_constants = np.array(force_constants)
651
+ phonon.run_mesh(mesh=mesh)
652
+ phonon.run_thermal_properties(
653
+ t_min=t_min,
654
+ t_max=t_max,
655
+ t_step=t_step,
656
+ cutoff_frequency=cutoff_frequency,
657
+ classical=classical
658
+ )
659
+ tp_dict = phonon.get_thermal_properties_dict()
660
+ result = {
661
+ "temperature_range_K": {"min": t_min, "max": t_max, "step": t_step},
662
+ "num_temperature_points": len(tp_dict["temperatures"]),
663
+ "free_energy_range_kJ_mol": {
664
+ "min": float(np.min(tp_dict["free_energy"])),
665
+ "max": float(np.max(tp_dict["free_energy"]))
666
+ },
667
+ "entropy_range_J_K_mol": {
668
+ "min": float(np.min(tp_dict["entropy"])),
669
+ "max": float(np.max(tp_dict["entropy"]))
670
+ },
671
+ "heat_capacity_range_J_K_mol": {
672
+ "min": float(np.min(tp_dict["heat_capacity"])),
673
+ "max": float(np.max(tp_dict["heat_capacity"]))
674
+ },
675
+ "sample_values_at_300K": None
676
+ }
677
+ # Find values at 300K if available
678
+ temps = tp_dict["temperatures"]
679
+ idx_300 = np.argmin(np.abs(temps - 300))
680
+ if abs(temps[idx_300] - 300) < t_step:
681
+ result["sample_values_at_300K"] = {
682
+ "temperature_K": float(temps[idx_300]),
683
+ "free_energy_kJ_mol": float(tp_dict["free_energy"][idx_300]),
684
+ "entropy_J_K_mol": float(tp_dict["entropy"][idx_300]),
685
+ "heat_capacity_J_K_mol": float(tp_dict["heat_capacity"][idx_300])
686
+ }
687
+ return {"success": True, "result": result}
688
+ except Exception as e:
689
+ return {"success": False, "error": str(e)}
690
+
691
+
692
+ @mcp.tool(name="run_thermal_displacements", description="Calculate thermal displacements of atoms.")
693
+ def run_thermal_displacements(
694
+ lattice: List[List[float]],
695
+ positions: List[List[float]],
696
+ numbers: List[int],
697
+ force_constants: List[List[List[List[float]]]],
698
+ mesh: List[int],
699
+ supercell_matrix: Optional[List[List[int]]] = None,
700
+ t_min: float = 0,
701
+ t_max: float = 1000,
702
+ t_step: float = 10,
703
+ direction: Optional[List[float]] = None
704
+ ) -> dict:
705
+ """
706
+ Calculate thermal displacements of atoms.
707
+
708
+ Parameters:
709
+ - lattice: 3x3 lattice vectors.
710
+ - positions: Fractional atomic positions.
711
+ - numbers: Atomic numbers.
712
+ - force_constants: Force constants array.
713
+ - mesh: Mesh grid numbers.
714
+ - supercell_matrix: Supercell transformation matrix.
715
+ - t_min/t_max/t_step: Temperature range and step.
716
+ - direction: Projection direction in reduced coordinates.
717
+
718
+ Returns:
719
+ - dict: Thermal displacement results.
720
+ """
721
+ try:
722
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
723
+ phonon.force_constants = np.array(force_constants)
724
+ phonon.run_mesh(mesh=mesh, is_mesh_symmetry=False, with_eigenvectors=True)
725
+ phonon.run_thermal_displacements(
726
+ t_min=t_min,
727
+ t_max=t_max,
728
+ t_step=t_step,
729
+ direction=direction
730
+ )
731
+ td_dict = phonon.get_thermal_displacements_dict()
732
+ result = {
733
+ "num_atoms": len(td_dict["thermal_displacements"][0]) if len(td_dict["thermal_displacements"]) > 0 else 0,
734
+ "num_temperature_points": len(td_dict["temperatures"]),
735
+ "temperature_range_K": {"min": t_min, "max": t_max}
736
+ }
737
+ return {"success": True, "result": result}
738
+ except Exception as e:
739
+ return {"success": False, "error": str(e)}
740
+
741
+
742
+ @mcp.tool(name="run_thermal_displacement_matrices", description="Calculate thermal displacement matrices (U tensors).")
743
+ def run_thermal_displacement_matrices(
744
+ lattice: List[List[float]],
745
+ positions: List[List[float]],
746
+ numbers: List[int],
747
+ force_constants: List[List[List[List[float]]]],
748
+ mesh: List[int],
749
+ supercell_matrix: Optional[List[List[int]]] = None,
750
+ t_min: float = 0,
751
+ t_max: float = 1000,
752
+ t_step: float = 10
753
+ ) -> dict:
754
+ """
755
+ Calculate thermal displacement matrices for CIF output.
756
+
757
+ Parameters:
758
+ - lattice: 3x3 lattice vectors.
759
+ - positions: Fractional atomic positions.
760
+ - numbers: Atomic numbers.
761
+ - force_constants: Force constants array.
762
+ - mesh: Mesh grid numbers.
763
+ - supercell_matrix: Supercell transformation matrix.
764
+ - t_min/t_max/t_step: Temperature range and step.
765
+
766
+ Returns:
767
+ - dict: Thermal displacement matrix results.
768
+ """
769
+ try:
770
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
771
+ phonon.force_constants = np.array(force_constants)
772
+ phonon.run_mesh(mesh=mesh, is_mesh_symmetry=False, with_eigenvectors=True)
773
+ phonon.run_thermal_displacement_matrices(t_min=t_min, t_max=t_max, t_step=t_step)
774
+ tdm_dict = phonon.get_thermal_displacement_matrices_dict()
775
+ result = {
776
+ "num_temperature_points": len(tdm_dict["temperatures"]),
777
+ "matrix_shape": list(tdm_dict["thermal_displacement_matrices"][0].shape) if len(tdm_dict["thermal_displacement_matrices"]) > 0 else [],
778
+ "has_cif_format": tdm_dict["thermal_displacement_matrices_cif"] is not None
779
+ }
780
+ return {"success": True, "result": result}
781
+ except Exception as e:
782
+ return {"success": False, "error": str(e)}
783
+
784
+
785
+ # ============================================================================
786
+ # Q-points Calculation Tools
787
+ # ============================================================================
788
+
789
+ @mcp.tool(name="run_qpoints", description="Calculate phonon properties at specific q-points.")
790
+ def run_qpoints(
791
+ lattice: List[List[float]],
792
+ positions: List[List[float]],
793
+ numbers: List[int],
794
+ force_constants: List[List[List[List[float]]]],
795
+ q_points: List[List[float]],
796
+ supercell_matrix: Optional[List[List[int]]] = None,
797
+ with_eigenvectors: bool = False,
798
+ with_group_velocities: bool = False,
799
+ with_dynamical_matrices: bool = False
800
+ ) -> dict:
801
+ """
802
+ Calculate phonon properties at specified q-points.
803
+
804
+ Parameters:
805
+ - lattice: 3x3 lattice vectors.
806
+ - positions: Fractional atomic positions.
807
+ - numbers: Atomic numbers.
808
+ - force_constants: Force constants array.
809
+ - q_points: List of q-points in reduced coordinates.
810
+ - supercell_matrix: Supercell transformation matrix.
811
+ - with_eigenvectors: Store eigenvectors.
812
+ - with_group_velocities: Calculate group velocities.
813
+ - with_dynamical_matrices: Store dynamical matrices.
814
+
815
+ Returns:
816
+ - dict: Phonon properties at q-points.
817
+ """
818
+ try:
819
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
820
+ phonon.force_constants = np.array(force_constants)
821
+ phonon.run_qpoints(
822
+ q_points,
823
+ with_eigenvectors=with_eigenvectors,
824
+ with_group_velocities=with_group_velocities,
825
+ with_dynamical_matrices=with_dynamical_matrices
826
+ )
827
+ qp_dict = phonon.get_qpoints_dict()
828
+ result = {
829
+ "num_qpoints": len(q_points),
830
+ "frequencies_shape": list(qp_dict["frequencies"].shape),
831
+ "has_eigenvectors": qp_dict["eigenvectors"] is not None,
832
+ "has_group_velocities": qp_dict["group_velocities"] is not None,
833
+ "has_dynamical_matrices": qp_dict["dynamical_matrices"] is not None,
834
+ "frequency_summary": [
835
+ {"q_point": q_points[i], "frequencies_THz": qp_dict["frequencies"][i].tolist()}
836
+ for i in range(min(3, len(q_points)))
837
+ ]
838
+ }
839
+ return {"success": True, "result": result}
840
+ except Exception as e:
841
+ return {"success": False, "error": str(e)}
842
+
843
+
844
+ @mcp.tool(name="get_group_velocity_at_q", description="Calculate phonon group velocity at a q-point.")
845
+ def get_group_velocity_at_q(
846
+ lattice: List[List[float]],
847
+ positions: List[List[float]],
848
+ numbers: List[int],
849
+ force_constants: List[List[List[List[float]]]],
850
+ q_point: List[float],
851
+ supercell_matrix: Optional[List[List[int]]] = None
852
+ ) -> dict:
853
+ """
854
+ Calculate phonon group velocity at a q-point.
855
+
856
+ Parameters:
857
+ - lattice: 3x3 lattice vectors.
858
+ - positions: Fractional atomic positions.
859
+ - numbers: Atomic numbers.
860
+ - force_constants: Force constants array.
861
+ - q_point: Q-point in reduced coordinates.
862
+ - supercell_matrix: Supercell transformation matrix.
863
+
864
+ Returns:
865
+ - dict: Group velocities for all bands at the q-point.
866
+ """
867
+ try:
868
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
869
+ phonon.force_constants = np.array(force_constants)
870
+ gv = phonon.get_group_velocity_at_q(q_point)
871
+ result = {
872
+ "q_point": q_point,
873
+ "group_velocities": gv.tolist(),
874
+ "num_bands": len(gv),
875
+ "velocity_magnitudes": np.linalg.norm(gv, axis=1).tolist()
876
+ }
877
+ return {"success": True, "result": result}
878
+ except Exception as e:
879
+ return {"success": False, "error": str(e)}
880
+
881
+
882
+ # ============================================================================
883
+ # Modulation and Animation Tools
884
+ # ============================================================================
885
+
886
+ @mcp.tool(name="run_modulations", description="Generate atomic modulations for phonon modes.")
887
+ def run_modulations(
888
+ lattice: List[List[float]],
889
+ positions: List[List[float]],
890
+ numbers: List[int],
891
+ force_constants: List[List[List[List[float]]]],
892
+ dimension: List[int],
893
+ phonon_modes: List[dict],
894
+ supercell_matrix: Optional[List[List[int]]] = None
895
+ ) -> dict:
896
+ """
897
+ Generate atomic modulations for specified phonon modes.
898
+
899
+ Parameters:
900
+ - lattice: 3x3 lattice vectors.
901
+ - positions: Fractional atomic positions.
902
+ - numbers: Atomic numbers.
903
+ - force_constants: Force constants array.
904
+ - dimension: Supercell dimension [n1, n2, n3].
905
+ - phonon_modes: List of mode specifications. Each has:
906
+ - q_point: [qx, qy, qz]
907
+ - band_index: Integer band index (0-based)
908
+ - amplitude: Float amplitude
909
+ - phase: Float phase factor
910
+ - supercell_matrix: Supercell transformation matrix.
911
+
912
+ Returns:
913
+ - dict: Modulation results.
914
+ """
915
+ try:
916
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
917
+ phonon.force_constants = np.array(force_constants)
918
+ modes = [
919
+ [m["q_point"], m["band_index"], m["amplitude"], m.get("phase", 0)]
920
+ for m in phonon_modes
921
+ ]
922
+ phonon.run_modulations(dimension, modes)
923
+ modulated_cells = phonon.get_modulated_supercells()
924
+ result = {
925
+ "num_modulated_cells": len(modulated_cells),
926
+ "supercell_dimension": dimension,
927
+ "atoms_per_cell": len(modulated_cells[0]) if modulated_cells else 0
928
+ }
929
+ return {"success": True, "result": result}
930
+ except Exception as e:
931
+ return {"success": False, "error": str(e)}
932
+
933
+
934
+ # ============================================================================
935
+ # Irreducible Representations Tools
936
+ # ============================================================================
937
+
938
+ @mcp.tool(name="set_irreps", description="Calculate irreducible representations at a q-point.")
939
+ def set_irreps(
940
+ lattice: List[List[float]],
941
+ positions: List[List[float]],
942
+ numbers: List[int],
943
+ force_constants: List[List[List[List[float]]]],
944
+ q_point: List[float],
945
+ supercell_matrix: Optional[List[List[int]]] = None,
946
+ is_little_cogroup: bool = False
947
+ ) -> dict:
948
+ """
949
+ Calculate irreducible representations of phonon modes.
950
+
951
+ Parameters:
952
+ - lattice: 3x3 lattice vectors.
953
+ - positions: Fractional atomic positions.
954
+ - numbers: Atomic numbers.
955
+ - force_constants: Force constants array.
956
+ - q_point: Q-point in reduced coordinates.
957
+ - supercell_matrix: Supercell transformation matrix.
958
+ - is_little_cogroup: Use little co-group.
959
+
960
+ Returns:
961
+ - dict: Irreducible representation information.
962
+ """
963
+ try:
964
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
965
+ phonon.force_constants = np.array(force_constants)
966
+ phonon.set_irreps(q_point, is_little_cogroup=is_little_cogroup)
967
+ result = {
968
+ "q_point": q_point,
969
+ "irreps_calculated": True,
970
+ "is_little_cogroup": is_little_cogroup
971
+ }
972
+ return {"success": True, "result": result}
973
+ except Exception as e:
974
+ return {"success": False, "error": str(e)}
975
+
976
+
977
+ # ============================================================================
978
+ # Moment Calculation Tools
979
+ # ============================================================================
980
+
981
+ @mcp.tool(name="run_moment", description="Calculate phonon frequency moment.")
982
+ def run_moment(
983
+ lattice: List[List[float]],
984
+ positions: List[List[float]],
985
+ numbers: List[int],
986
+ force_constants: List[List[List[List[float]]]],
987
+ mesh: List[int],
988
+ supercell_matrix: Optional[List[List[int]]] = None,
989
+ order: int = 1,
990
+ freq_min: Optional[float] = None,
991
+ freq_max: Optional[float] = None
992
+ ) -> dict:
993
+ """
994
+ Calculate phonon frequency moment.
995
+
996
+ Parameters:
997
+ - lattice: 3x3 lattice vectors.
998
+ - positions: Fractional atomic positions.
999
+ - numbers: Atomic numbers.
1000
+ - force_constants: Force constants array.
1001
+ - mesh: Mesh grid numbers.
1002
+ - supercell_matrix: Supercell transformation matrix.
1003
+ - order: Moment order (1, 2, etc.).
1004
+ - freq_min/freq_max: Frequency range.
1005
+
1006
+ Returns:
1007
+ - dict: Moment calculation result.
1008
+ """
1009
+ try:
1010
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
1011
+ phonon.force_constants = np.array(force_constants)
1012
+ phonon.run_mesh(mesh=mesh)
1013
+ phonon.run_moment(order=order, freq_min=freq_min, freq_max=freq_max)
1014
+ moment = phonon.get_moment()
1015
+ result = {
1016
+ "order": order,
1017
+ "moment": float(moment) if isinstance(moment, (int, float, np.floating)) else moment.tolist() if moment is not None else None
1018
+ }
1019
+ return {"success": True, "result": result}
1020
+ except Exception as e:
1021
+ return {"success": False, "error": str(e)}
1022
+
1023
+
1024
+ # ============================================================================
1025
+ # Structure Information Tools
1026
+ # ============================================================================
1027
+
1028
+ @mcp.tool(name="get_structure_info", description="Get structure information from phonopy setup.")
1029
+ def get_structure_info(
1030
+ lattice: List[List[float]],
1031
+ positions: List[List[float]],
1032
+ numbers: List[int],
1033
+ supercell_matrix: Optional[List[List[int]]] = None
1034
+ ) -> dict:
1035
+ """
1036
+ Get structure information including symmetry.
1037
+
1038
+ Parameters:
1039
+ - lattice: 3x3 lattice vectors.
1040
+ - positions: Fractional atomic positions.
1041
+ - numbers: Atomic numbers.
1042
+ - supercell_matrix: Supercell transformation matrix.
1043
+
1044
+ Returns:
1045
+ - dict: Structure and symmetry information.
1046
+ """
1047
+ try:
1048
+ phonon = _create_phonopy_from_structure(lattice, positions, numbers, supercell_matrix)
1049
+ unitcell = phonon.unitcell
1050
+ supercell = phonon.supercell
1051
+ primitive = phonon.primitive
1052
+ symmetry = phonon.symmetry
1053
+ result = {
1054
+ "unitcell": {
1055
+ "num_atoms": len(unitcell),
1056
+ "lattice_parameters": {
1057
+ "a": float(np.linalg.norm(unitcell.cell[0])),
1058
+ "b": float(np.linalg.norm(unitcell.cell[1])),
1059
+ "c": float(np.linalg.norm(unitcell.cell[2]))
1060
+ },
1061
+ "volume_angstrom3": float(np.linalg.det(unitcell.cell))
1062
+ },
1063
+ "supercell": {
1064
+ "num_atoms": len(supercell),
1065
+ "volume_angstrom3": float(np.linalg.det(supercell.cell))
1066
+ },
1067
+ "primitive": {
1068
+ "num_atoms": len(primitive),
1069
+ "volume_angstrom3": float(np.linalg.det(primitive.cell))
1070
+ },
1071
+ "symmetry": {
1072
+ "num_operations": len(symmetry.symmetry_operations["rotations"]),
1073
+ "international_symbol": symmetry.international_symbol if hasattr(symmetry, 'international_symbol') else None
1074
+ }
1075
+ }
1076
+ return {"success": True, "result": result}
1077
+ except Exception as e:
1078
+ return {"success": False, "error": str(e)}
1079
+
1080
+
1081
+ @mcp.tool(name="get_unit_conversion_factor", description="Get phonopy unit conversion factor.")
1082
+ def get_unit_conversion_factor(calculator: Optional[str] = None) -> dict:
1083
+ """
1084
+ Get phonopy unit conversion factor.
1085
+
1086
+ Parameters:
1087
+ - calculator: Calculator name ('vasp', 'qe', etc.) or None for default.
1088
+
1089
+ Returns:
1090
+ - dict: Unit conversion factor information.
1091
+ """
1092
+ try:
1093
+ from phonopy.interface.calculator import get_calculator_physical_units
1094
+ from phonopy.physical_units import get_physical_units
1095
+
1096
+ if calculator:
1097
+ units = get_calculator_physical_units(interface_mode=calculator)
1098
+ factor = units.get("factor", None)
1099
+ else:
1100
+ factor = get_physical_units().DefaultToTHz
1101
+
1102
+ result = {
1103
+ "calculator": calculator if calculator else "default",
1104
+ "factor_to_THz": float(factor) if factor else None,
1105
+ "description": "Converts sqrt(eV/Angstrom^2/AMU) to THz"
1106
+ }
1107
+ return {"success": True, "result": result}
1108
+ except Exception as e:
1109
+ return {"success": False, "error": str(e)}
1110
+
1111
+
1112
+ @mcp.tool(name="get_phonopy_info", description="Get phonopy version and capability information.")
1113
+ def get_phonopy_info() -> dict:
1114
+ """
1115
+ Get phonopy version and available features.
1116
+
1117
+ Returns:
1118
+ - dict: Version and capability information.
1119
+ """
1120
+ try:
1121
+ from phonopy.version import __version__
1122
+
1123
+ result = {
1124
+ "version": __version__,
1125
+ "capabilities": [
1126
+ "Phonon band structure calculation",
1127
+ "Density of states (total and projected)",
1128
+ "Thermal properties (free energy, entropy, heat capacity)",
1129
+ "Thermal displacements",
1130
+ "Group velocity calculation",
1131
+ "Irreducible representations",
1132
+ "Modulation and animation",
1133
+ "Force constants symmetrization",
1134
+ "Mesh sampling",
1135
+ "Q-point calculations"
1136
+ ],
1137
+ "supported_calculators": [
1138
+ "vasp", "qe", "abinit", "siesta", "elk", "wien2k",
1139
+ "crystal", "turbomole", "cp2k", "dftbp", "lammps"
1140
+ ],
1141
+ "fc_calculators": ["traditional", "symfc", "alm"]
1142
+ }
1143
+ return {"success": True, "result": result}
1144
+ except Exception as e:
1145
+ return {"success": False, "error": str(e)}
1146
+
1147
+
1148
  def create_app() -> FastMCP:
1149
  """
1150
+ Creates and returns the FastMCP application instance.
1151
 
1152
  Returns:
1153
  - FastMCP: The FastMCP application instance.