Files
2025-11-30 08:30:10 +08:00

267 lines
8.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Structure analysis tool using pymatgen.
Analyzes crystal structures and provides comprehensive information including:
- Composition and formula
- Space group and symmetry
- Lattice parameters
- Density
- Coordination environment
- Bond lengths and angles
Usage:
python structure_analyzer.py structure_file [options]
Examples:
python structure_analyzer.py POSCAR
python structure_analyzer.py structure.cif --symmetry --neighbors
python structure_analyzer.py POSCAR --export json
"""
import argparse
import json
import sys
from pathlib import Path
try:
from pymatgen.core import Structure
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.analysis.local_env import CrystalNN
except ImportError:
print("Error: pymatgen is not installed. Install with: pip install pymatgen")
sys.exit(1)
def analyze_structure(struct: Structure, args) -> dict:
"""
Perform comprehensive structure analysis.
Args:
struct: Pymatgen Structure object
args: Command line arguments
Returns:
Dictionary containing analysis results
"""
results = {}
# Basic information
print("\n" + "="*60)
print("STRUCTURE ANALYSIS")
print("="*60)
print("\n--- COMPOSITION ---")
print(f"Formula (reduced): {struct.composition.reduced_formula}")
print(f"Formula (full): {struct.composition.formula}")
print(f"Formula (Hill): {struct.composition.hill_formula}")
print(f"Chemical system: {struct.composition.chemical_system}")
print(f"Number of sites: {len(struct)}")
print(f"Number of species: {len(struct.composition.elements)}")
print(f"Molecular weight: {struct.composition.weight:.2f} amu")
results['composition'] = {
'reduced_formula': struct.composition.reduced_formula,
'formula': struct.composition.formula,
'hill_formula': struct.composition.hill_formula,
'chemical_system': struct.composition.chemical_system,
'num_sites': len(struct),
'molecular_weight': struct.composition.weight,
}
# Lattice information
print("\n--- LATTICE ---")
print(f"a = {struct.lattice.a:.4f} Å")
print(f"b = {struct.lattice.b:.4f} Å")
print(f"c = {struct.lattice.c:.4f} Å")
print(f"α = {struct.lattice.alpha:.2f}°")
print(f"β = {struct.lattice.beta:.2f}°")
print(f"γ = {struct.lattice.gamma:.2f}°")
print(f"Volume: {struct.volume:.2f} ų")
print(f"Density: {struct.density:.3f} g/cm³")
results['lattice'] = {
'a': struct.lattice.a,
'b': struct.lattice.b,
'c': struct.lattice.c,
'alpha': struct.lattice.alpha,
'beta': struct.lattice.beta,
'gamma': struct.lattice.gamma,
'volume': struct.volume,
'density': struct.density,
}
# Symmetry analysis
if args.symmetry:
print("\n--- SYMMETRY ---")
try:
sga = SpacegroupAnalyzer(struct)
spacegroup_symbol = sga.get_space_group_symbol()
spacegroup_number = sga.get_space_group_number()
crystal_system = sga.get_crystal_system()
point_group = sga.get_point_group_symbol()
print(f"Space group: {spacegroup_symbol} (#{spacegroup_number})")
print(f"Crystal system: {crystal_system}")
print(f"Point group: {point_group}")
# Get symmetry operations
symm_ops = sga.get_symmetry_operations()
print(f"Symmetry operations: {len(symm_ops)}")
results['symmetry'] = {
'spacegroup_symbol': spacegroup_symbol,
'spacegroup_number': spacegroup_number,
'crystal_system': crystal_system,
'point_group': point_group,
'num_symmetry_ops': len(symm_ops),
}
# Show equivalent sites
sym_struct = sga.get_symmetrized_structure()
print(f"Symmetry-equivalent site groups: {len(sym_struct.equivalent_sites)}")
except Exception as e:
print(f"Could not determine symmetry: {e}")
# Site information
print("\n--- SITES ---")
print(f"{'Index':<6} {'Species':<10} {'Wyckoff':<10} {'Frac Coords':<30}")
print("-" * 60)
for i, site in enumerate(struct):
coords_str = f"[{site.frac_coords[0]:.4f}, {site.frac_coords[1]:.4f}, {site.frac_coords[2]:.4f}]"
wyckoff = "N/A"
if args.symmetry:
try:
sga = SpacegroupAnalyzer(struct)
sym_struct = sga.get_symmetrized_structure()
wyckoff = sym_struct.equivalent_sites[0][0].species_string # Simplified
except:
pass
print(f"{i:<6} {site.species_string:<10} {wyckoff:<10} {coords_str:<30}")
# Neighbor analysis
if args.neighbors:
print("\n--- COORDINATION ENVIRONMENT ---")
try:
cnn = CrystalNN()
for i, site in enumerate(struct):
neighbors = cnn.get_nn_info(struct, i)
print(f"\nSite {i} ({site.species_string}):")
print(f" Coordination number: {len(neighbors)}")
if len(neighbors) > 0 and len(neighbors) <= 12:
print(f" Neighbors:")
for j, neighbor in enumerate(neighbors):
neighbor_site = struct[neighbor['site_index']]
distance = site.distance(neighbor_site)
print(f" {neighbor_site.species_string} at {distance:.3f} Å")
except Exception as e:
print(f"Could not analyze coordination: {e}")
# Distance matrix (for small structures)
if args.distances and len(struct) <= 20:
print("\n--- DISTANCE MATRIX (Å) ---")
distance_matrix = struct.distance_matrix
# Print header
print(f"{'':>4}", end="")
for i in range(len(struct)):
print(f"{i:>8}", end="")
print()
# Print matrix
for i in range(len(struct)):
print(f"{i:>4}", end="")
for j in range(len(struct)):
if i == j:
print(f"{'---':>8}", end="")
else:
print(f"{distance_matrix[i][j]:>8.3f}", end="")
print()
print("\n" + "="*60)
return results
def main():
parser = argparse.ArgumentParser(
description="Analyze crystal structures using pymatgen",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"structure_file",
help="Structure file to analyze (CIF, POSCAR, etc.)"
)
parser.add_argument(
"--symmetry", "-s",
action="store_true",
help="Perform symmetry analysis"
)
parser.add_argument(
"--neighbors", "-n",
action="store_true",
help="Analyze coordination environment"
)
parser.add_argument(
"--distances", "-d",
action="store_true",
help="Show distance matrix (for structures with ≤20 atoms)"
)
parser.add_argument(
"--export", "-e",
choices=["json", "yaml"],
help="Export analysis results to file"
)
parser.add_argument(
"--output", "-o",
help="Output file for exported results"
)
args = parser.parse_args()
# Read structure
try:
struct = Structure.from_file(args.structure_file)
except Exception as e:
print(f"Error reading structure file: {e}")
sys.exit(1)
# Analyze structure
results = analyze_structure(struct, args)
# Export results
if args.export:
output_file = args.output or f"analysis.{args.export}"
if args.export == "json":
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
print(f"\n✓ Analysis exported to {output_file}")
elif args.export == "yaml":
try:
import yaml
with open(output_file, "w") as f:
yaml.dump(results, f, default_flow_style=False)
print(f"\n✓ Analysis exported to {output_file}")
except ImportError:
print("Error: PyYAML is not installed. Install with: pip install pyyaml")
if __name__ == "__main__":
main()