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.filters import UnitCellFilter
from ase.optimize import BFGS
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}")

Hide code cell output

/tmp/ipykernel_2619/3590643523.py:23: FutureWarning: Please use atoms.calc = calc
  atoms.set_calculator(calc)
/tmp/ipykernel_2619/3590643523.py:23: FutureWarning: Please use atoms.calc = calc
  atoms.set_calculator(calc)
/tmp/ipykernel_2619/3590643523.py:23: FutureWarning: Please use atoms.calc = calc
  atoms.set_calculator(calc)
Created 3 example RES files in /tmp/res_examples

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}")
Formula: Al4
Enthalpy: -31.7516 eV
Volume: 3.70 ų
Pressure: 0.00 GPa
Space group: (P1)
Number of atoms: 4

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} ų")
Lowest energy structures:
  1. Al-example-3: -31.7644 eV, V=3.70 ų
  2. Al-example-2: -31.7516 eV, V=3.70 ų
  3. Al-example-1: -31.7516 eV, V=3.70 ų

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}")
Label: Al-example-1
Enthalpy: -31.7516 eV
Volume: 3.70 ų
Structure loaded: False

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']}")
Extracted metadata:
  UID: Al-example-1
  Enthalpy: -31.7516 eV
  Volume: 3.70 ų
  Pressure: 0.00 GPa
  N atoms: 4
  Space group: (P1)

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}")
Chemical formula: Al4
Number of atoms: 4
Cell volume: 3.70 ų
Positions shape: (4, 3)

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³")
Composition: Al4
Reduced formula: Al
Density: 48.48 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")
✓ Saved to /tmp/res_examples/carbon-demo.res
✓ Verified: carbon-demo, 4 atoms, H=-20.5000 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")

Hide code cell output

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}")
Rank   Label                     Enthalpy     Volume     Symm      
---------------------------------------------------------------------------
1      Al-example-3              -31.7644     3.70       (P1)      
2      Al-example-2              -31.7516     3.70       (P1)      
3      Al-example-1              -31.7516     3.70       (P1)      
4      carbon-demo               -20.5000     102.87     (P1)      
5      auto-named-structure      -15.0000     102.87     (P1)      

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)
Analyzed 5 structures
Energy range: 16.7644 eV

Top 5 structures:

#    Label                     Energy       V/atom     Symm        
---------------------------------------------------------------------------
1    Al-example-3              -31.7644     0.92       (P1)        
2    Al-example-2              -31.7516     0.92       (P1)        
3    Al-example-1              -31.7516     0.92       (P1)        
4    carbon-demo               -20.5000     25.72      (P1)        
5    auto-named-structure      -15.0000     25.72      (P1)        

Space group distribution:
  (P1): 5 structure(s)

Troubleshooting#

File Format Errors#

If RESFile fails to load:

  1. Check the file is a valid RES file

  2. Ensure TITL line is present

  3. Try only_titl=True for corrupted structures

Missing spglib#

Some symmetry functions require spglib:

pip install spglib

See Also#