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

Update phonopy/mcp_output/mcp_plugin/mcp_service.py

Browse files
phonopy/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,1153 +1,131 @@
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.
 
 
 
 
 
 
 
 
 
 
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.