Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:30:10 +08:00
commit f0bd18fb4e
824 changed files with 331919 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
#!/usr/bin/env python3
"""
Phase diagram generator using Materials Project data.
This script generates phase diagrams for chemical systems using data from the
Materials Project database via pymatgen's MPRester.
Usage:
python phase_diagram_generator.py chemical_system [options]
Examples:
python phase_diagram_generator.py Li-Fe-O
python phase_diagram_generator.py Li-Fe-O --output li_fe_o_pd.png
python phase_diagram_generator.py Fe-O --show
python phase_diagram_generator.py Li-Fe-O --analyze "LiFeO2"
"""
import argparse
import os
import sys
from pathlib import Path
try:
from pymatgen.core import Composition
from pymatgen.analysis.phase_diagram import PhaseDiagram, PDPlotter
except ImportError:
print("Error: pymatgen is not installed. Install with: pip install pymatgen")
sys.exit(1)
try:
from mp_api.client import MPRester
except ImportError:
print("Error: mp-api is not installed. Install with: pip install mp-api")
sys.exit(1)
def get_api_key() -> str:
"""Get Materials Project API key from environment."""
api_key = os.environ.get("MP_API_KEY")
if not api_key:
print("Error: MP_API_KEY environment variable not set.")
print("Get your API key from https://next-gen.materialsproject.org/")
print("Then set it with: export MP_API_KEY='your_key_here'")
sys.exit(1)
return api_key
def generate_phase_diagram(chemsys: str, args):
"""
Generate and analyze phase diagram for a chemical system.
Args:
chemsys: Chemical system (e.g., "Li-Fe-O")
args: Command line arguments
"""
api_key = get_api_key()
print(f"\n{'='*60}")
print(f"PHASE DIAGRAM: {chemsys}")
print(f"{'='*60}\n")
# Get entries from Materials Project
print("Fetching data from Materials Project...")
with MPRester(api_key) as mpr:
entries = mpr.get_entries_in_chemsys(chemsys)
print(f"✓ Retrieved {len(entries)} entries")
if len(entries) == 0:
print(f"Error: No entries found for chemical system {chemsys}")
sys.exit(1)
# Build phase diagram
print("Building phase diagram...")
pd = PhaseDiagram(entries)
# Get stable entries
stable_entries = pd.stable_entries
print(f"✓ Phase diagram constructed with {len(stable_entries)} stable phases")
# Print stable phases
print("\n--- STABLE PHASES ---")
for entry in stable_entries:
formula = entry.composition.reduced_formula
energy = entry.energy_per_atom
print(f" {formula:<20} E = {energy:.4f} eV/atom")
# Analyze specific composition if requested
if args.analyze:
print(f"\n--- STABILITY ANALYSIS: {args.analyze} ---")
try:
comp = Composition(args.analyze)
# Find closest entry
closest_entry = None
min_distance = float('inf')
for entry in entries:
if entry.composition.reduced_formula == comp.reduced_formula:
closest_entry = entry
break
if closest_entry:
# Calculate energy above hull
e_above_hull = pd.get_e_above_hull(closest_entry)
print(f"Energy above hull: {e_above_hull:.4f} eV/atom")
if e_above_hull < 0.001:
print(f"Status: STABLE (on convex hull)")
elif e_above_hull < 0.05:
print(f"Status: METASTABLE (nearly stable)")
else:
print(f"Status: UNSTABLE")
# Get decomposition
decomp = pd.get_decomposition(comp)
print(f"\nDecomposes to:")
for entry, fraction in decomp.items():
formula = entry.composition.reduced_formula
print(f" {fraction:.3f} × {formula}")
# Get reaction energy
rxn_energy = pd.get_equilibrium_reaction_energy(closest_entry)
print(f"\nDecomposition energy: {rxn_energy:.4f} eV/atom")
else:
print(f"No entry found for composition {args.analyze}")
print("Checking stability of hypothetical composition...")
# Analyze hypothetical composition
decomp = pd.get_decomposition(comp)
print(f"\nWould decompose to:")
for entry, fraction in decomp.items():
formula = entry.composition.reduced_formula
print(f" {fraction:.3f} × {formula}")
except Exception as e:
print(f"Error analyzing composition: {e}")
# Get chemical potentials
if args.chemical_potentials:
print("\n--- CHEMICAL POTENTIALS ---")
print("(at stability regions)")
try:
chempots = pd.get_all_chempots()
for element, potentials in chempots.items():
print(f"\n{element}:")
for potential in potentials[:5]: # Show first 5
print(f" {potential:.4f} eV")
except Exception as e:
print(f"Could not calculate chemical potentials: {e}")
# Plot phase diagram
print("\n--- GENERATING PLOT ---")
plotter = PDPlotter(pd, show_unstable=args.show_unstable)
if args.output:
output_path = Path(args.output)
plotter.write_image(str(output_path), image_format=output_path.suffix[1:])
print(f"✓ Phase diagram saved to {output_path}")
if args.show:
print("Opening interactive plot...")
plotter.show()
print(f"\n{'='*60}\n")
def main():
parser = argparse.ArgumentParser(
description="Generate phase diagrams using Materials Project data",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Requirements:
- Materials Project API key (set MP_API_KEY environment variable)
- mp-api package: pip install mp-api
Examples:
%(prog)s Li-Fe-O
%(prog)s Li-Fe-O --output li_fe_o_phase_diagram.png
%(prog)s Fe-O --show --analyze "Fe2O3"
%(prog)s Li-Fe-O --analyze "LiFeO2" --show-unstable
"""
)
parser.add_argument(
"chemsys",
help="Chemical system (e.g., Li-Fe-O, Fe-O)"
)
parser.add_argument(
"--output", "-o",
help="Output file for phase diagram plot (PNG, PDF, SVG)"
)
parser.add_argument(
"--show", "-s",
action="store_true",
help="Show interactive plot"
)
parser.add_argument(
"--analyze", "-a",
help="Analyze stability of specific composition (e.g., LiFeO2)"
)
parser.add_argument(
"--show-unstable",
action="store_true",
help="Include unstable phases in plot"
)
parser.add_argument(
"--chemical-potentials",
action="store_true",
help="Calculate chemical potentials"
)
args = parser.parse_args()
# Validate chemical system format
elements = args.chemsys.split("-")
if len(elements) < 2:
print("Error: Chemical system must contain at least 2 elements")
print("Example: Li-Fe-O")
sys.exit(1)
# Generate phase diagram
generate_phase_diagram(args.chemsys, args)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,266 @@
#!/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()

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""
Structure file format converter using pymatgen.
This script converts between different structure file formats supported by pymatgen.
Supports automatic format detection and batch conversion.
Usage:
python structure_converter.py input_file output_file
python structure_converter.py input_file --format cif
python structure_converter.py *.cif --output-dir ./converted --format poscar
Examples:
python structure_converter.py POSCAR structure.cif
python structure_converter.py structure.cif --format json
python structure_converter.py *.vasp --output-dir ./cif_files --format cif
"""
import argparse
import sys
from pathlib import Path
from typing import List
try:
from pymatgen.core import Structure
except ImportError:
print("Error: pymatgen is not installed. Install with: pip install pymatgen")
sys.exit(1)
def convert_structure(input_path: Path, output_path: Path = None, output_format: str = None) -> bool:
"""
Convert a structure file to a different format.
Args:
input_path: Path to input structure file
output_path: Path to output file (optional if output_format is specified)
output_format: Target format (e.g., 'cif', 'poscar', 'json', 'yaml')
Returns:
True if conversion succeeded, False otherwise
"""
try:
# Read structure with automatic format detection
struct = Structure.from_file(str(input_path))
print(f"✓ Read structure: {struct.composition.reduced_formula} from {input_path}")
# Determine output path
if output_path is None and output_format:
output_path = input_path.with_suffix(f".{output_format}")
elif output_path is None:
print("Error: Must specify either output_path or output_format")
return False
# Write structure
struct.to(filename=str(output_path))
print(f"✓ Wrote structure to {output_path}")
return True
except Exception as e:
print(f"✗ Error converting {input_path}: {e}")
return False
def batch_convert(input_files: List[Path], output_dir: Path, output_format: str) -> None:
"""
Convert multiple structure files to a common format.
Args:
input_files: List of input structure files
output_dir: Directory for output files
output_format: Target format for all files
"""
output_dir.mkdir(parents=True, exist_ok=True)
success_count = 0
for input_file in input_files:
output_file = output_dir / f"{input_file.stem}.{output_format}"
if convert_structure(input_file, output_file):
success_count += 1
print(f"\n{'='*60}")
print(f"Conversion complete: {success_count}/{len(input_files)} files converted successfully")
def main():
parser = argparse.ArgumentParser(
description="Convert structure files between different formats using pymatgen",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Supported formats:
Input: CIF, POSCAR, CONTCAR, XYZ, PDB, JSON, YAML, and many more
Output: CIF, POSCAR, XYZ, PDB, JSON, YAML, XSF, and many more
Examples:
%(prog)s POSCAR structure.cif
%(prog)s structure.cif --format json
%(prog)s *.cif --output-dir ./poscar_files --format poscar
"""
)
parser.add_argument(
"input",
nargs="+",
help="Input structure file(s). Supports wildcards for batch conversion."
)
parser.add_argument(
"output",
nargs="?",
help="Output structure file (ignored if --output-dir is used)"
)
parser.add_argument(
"--format", "-f",
help="Output format (e.g., cif, poscar, json, yaml, xyz)"
)
parser.add_argument(
"--output-dir", "-o",
type=Path,
help="Output directory for batch conversion"
)
args = parser.parse_args()
# Expand wildcards and convert to Path objects
input_files = []
for pattern in args.input:
matches = list(Path.cwd().glob(pattern))
if matches:
input_files.extend(matches)
else:
input_files.append(Path(pattern))
# Filter to files only
input_files = [f for f in input_files if f.is_file()]
if not input_files:
print("Error: No input files found")
sys.exit(1)
# Batch conversion mode
if args.output_dir or len(input_files) > 1:
if not args.format:
print("Error: --format is required for batch conversion")
sys.exit(1)
output_dir = args.output_dir or Path("./converted")
batch_convert(input_files, output_dir, args.format)
# Single file conversion
elif len(input_files) == 1:
input_file = input_files[0]
if args.output:
output_file = Path(args.output)
convert_structure(input_file, output_file)
elif args.format:
convert_structure(input_file, output_format=args.format)
else:
print("Error: Must specify output file or --format")
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()