How to Work with RES Files#
Learn how to read, write, and analyze AIRSS .res output files.
What are RES Files?#
RES files are the standard output format from AIRSS searches. They contain:
Structure information (atoms, cell, symmetry)
Computed properties (energy, volume, pressure)
Metadata (unique ID, space group)
Setup: Create Sample RES Files#
First, let’s create some sample RES files for demonstration:
from airsspy import SeedAtoms, save_airss_res
from ase.calculators.lj import LennardJones
from ase.optimize import BFGS
from ase.constraints import UnitCellFilter
import tempfile
import os
# Create temporary directory for examples
temp_dir = tempfile.gettempdir()
example_dir = os.path.join(temp_dir, 'res_examples')
os.makedirs(example_dir, exist_ok=True)
# Generate and save 3 example structures
seed = SeedAtoms('Al', cell=[2, 2, 2], pbc=True)
seed[0].num = 4
seed.gentags.minsep = 1.5
calc = LennardJones()
for i in range(3):
atoms = seed.build_random_atoms()
if atoms is not None:
atoms.set_calculator(calc)
opt = BFGS(UnitCellFilter(atoms), logfile=None)
opt.run(fmax=0.05)
info_dict = {
'uid': f'Al-example-{i+1}',
'P': 0.0,
'V': atoms.get_volume(),
'H': atoms.get_potential_energy(),
'nat': len(atoms),
'sym': 'P1'
}
res_path = os.path.join(example_dir, f'Al-example-{i+1}.res')
save_airss_res(atoms, info_dict, res_path, force_write=True)
print(f"Created 3 example RES files in {example_dir}")
Reading RES Files#
Using RESFile Class#
The most convenient way to work with RES files:
from airsspy import RESFile
# Load a RES file
res_path = os.path.join(example_dir, 'Al-example-1.res')
res = RESFile.from_file(res_path)
# Access properties
print(f"Formula: {res.formula}")
print(f"Enthalpy: {res.enthalpy:.4f} eV")
print(f"Volume: {res.volume:.2f} ų")
print(f"Pressure: {res.pressure:.2f} GPa")
print(f"Space group: {res.symm}")
print(f"Number of atoms: {res.natoms}")
Loading Multiple Files#
Process a directory of RES files:
from pathlib import Path
from airsspy import RESFile
# Load all example RES files
res_files = Path(example_dir).glob('*.res')
structures = []
for fpath in res_files:
res = RESFile.from_file(str(fpath))
structures.append(res)
# Sort by enthalpy
structures.sort(key=lambda r: r.enthalpy)
print("Lowest energy structures:")
for i, res in enumerate(structures, 1):
print(f" {i}. {res.label}: {res.enthalpy:.4f} eV, V={res.volume:.2f} ų")
Fast Loading (Metadata Only)#
Load only the TITL line without parsing structure:
res = RESFile.from_file(res_path, only_titl=True)
# Access metadata (faster)
print(f"Label: {res.label}")
print(f"Enthalpy: {res.enthalpy:.4f} eV")
print(f"Volume: {res.volume:.2f} ų")
# Note: structure will be None with only_titl=True
print(f"Structure loaded: {res.structure is not None}")
Using extract_res Function#
Extract metadata as a dictionary:
from airsspy import extract_res
info = extract_res(res_path)
print("Extracted metadata:")
print(f" UID: {info['uid']}")
print(f" Enthalpy: {info['H']:.4f} eV")
print(f" Volume: {info['V']:.2f} ų")
print(f" Pressure: {info['P']:.2f} GPa")
print(f" N atoms: {info['nat']}")
print(f" Space group: {info['sym']}")
Working with Structures#
Get ASE Atoms Object#
Convert RES structure to ASE Atoms:
from airsspy import RESFile
res = RESFile.from_file(res_path)
# Get as ASE Atoms
atoms = res.atoms
# Now use with ASE
print(f"Chemical formula: {atoms.get_chemical_formula()}")
print(f"Number of atoms: {len(atoms)}")
print(f"Cell volume: {atoms.get_volume():.2f} ų")
print(f"Positions shape: {atoms.get_positions().shape}")
Get Pymatgen Structure#
Get the pymatgen Structure object:
res = RESFile.from_file(res_path)
# Access pymatgen Structure
structure = res.structure
# Use pymatgen functionality
print(f"Composition: {structure.composition}")
print(f"Reduced formula: {structure.composition.reduced_formula}")
print(f"Density: {structure.density:.2f} g/cm³")
Writing RES Files#
Using save_airss_res#
Save an ASE Atoms object as a RES file:
from airsspy import save_airss_res, SeedAtoms
# Create a simple structure
seed = SeedAtoms('C', cell=[3, 3, 3], pbc=True)
seed[0].num = 4
seed.gentags.minsep = 1.5
atoms = seed.build_random_atoms()
if atoms is not None:
# Prepare metadata
info_dict = {
'uid': 'carbon-demo',
'P': 0.0, # Pressure (GPa)
'V': atoms.get_volume(), # Volume (ų)
'H': -20.5, # Enthalpy/energy (eV)
'nat': len(atoms), # Number of atoms
'sym': 'P1' # Space group
}
# Save to file
demo_path = os.path.join(example_dir, 'carbon-demo.res')
save_airss_res(atoms, info_dict, demo_path, force_write=True)
print(f"✓ Saved to {demo_path}")
# Read it back to verify
res = RESFile.from_file(demo_path)
print(f"✓ Verified: {res.label}, {res.natoms} atoms, H={res.enthalpy:.4f} eV")
Auto-naming#
Let the function generate the filename from uid:
if atoms is not None:
info_dict = {
'uid': 'auto-named-structure',
'P': 0.0,
'V': atoms.get_volume(),
'H': -15.0,
'nat': len(atoms),
'sym': 'P1'
}
# Change to example directory
import os
orig_dir = os.getcwd()
os.chdir(example_dir)
# Filename will be 'auto-named-structure.res'
save_airss_res(atoms, info_dict, force_write=True)
os.chdir(orig_dir)
print(f"Saved as auto-named-structure.res")
Batch Processing#
Analyze Multiple Results#
Process and rank all structures in a search:
from pathlib import Path
from airsspy import RESFile
# Load all RES files from example directory
results = []
for fpath in Path(example_dir).glob('*.res'):
try:
res = RESFile.from_file(str(fpath))
results.append({
'file': fpath.name,
'label': res.label,
'enthalpy': res.enthalpy,
'volume': res.volume,
'formula': res.formula,
'symm': res.symm
})
except Exception as e:
print(f"Failed to load {fpath.name}: {e}")
# Sort by enthalpy
results.sort(key=lambda x: x['enthalpy'])
# Print summary
print(f"\n{'Rank':<6} {'Label':<25} {'Enthalpy':<12} {'Volume':<10} {'Symm':<10}")
print("-" * 75)
for i, r in enumerate(results[:5], 1):
print(f"{i:<6} {r['label']:<25} {r['enthalpy']:<12.4f} {r['volume']:<10.2f} {r['symm']:<10}")
Complete Example: Post-Processing#
Here’s a complete example for analyzing AIRSS results:
from pathlib import Path
from airsspy import RESFile
def analyze_search_results(directory, top_n=5):
"""Analyze and summarize AIRSS search results"""
# Load all structures
structures = []
search_path = Path(directory)
for fpath in search_path.glob('*.res'):
try:
res = RESFile.from_file(str(fpath))
structures.append(res)
except Exception as e:
print(f"Warning: Could not load {fpath.name}: {e}")
if not structures:
print("No structures found!")
return
# Sort by enthalpy
structures.sort(key=lambda r: r.enthalpy)
print(f"\nAnalyzed {len(structures)} structures")
print(f"Energy range: {structures[-1].enthalpy - structures[0].enthalpy:.4f} eV")
print(f"\nTop {min(top_n, len(structures))} structures:\n")
# Print summary table
print(f"{'#':<4} {'Label':<25} {'Energy':<12} {'V/atom':<10} {'Symm':<12}")
print("-" * 75)
for i, res in enumerate(structures[:top_n], 1):
v_per_atom = res.volume / res.natoms
print(f"{i:<4} {res.label:<25} {res.enthalpy:<12.4f} {v_per_atom:<10.2f} {res.symm:<12}")
# Symmetry distribution
symmetries = [res.symm for res in structures]
print(f"\nSpace group distribution:")
unique_symms = sorted(set(symmetries))
for sg in unique_symms[:5]: # Show top 5
count = symmetries.count(sg)
print(f" {sg}: {count} structure(s)")
# Run analysis on our example directory
analyze_search_results(example_dir, top_n=5)
Troubleshooting#
File Format Errors#
If RESFile fails to load:
Check the file is a valid RES file
Ensure TITL line is present
Try
only_titl=Truefor corrupted structures
Missing spglib#
Some symmetry functions require spglib:
pip install spglib
See Also#
Quickstart Tutorial - Complete workflow example
Generating Structures - Creating structures to save
API: RESFile - Full RESFile documentation