267 lines
8.3 KiB
Python
267 lines
8.3 KiB
Python
#!/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()
|