Interactive Mode for Fast Geometry Optimization#

VASP’s interactive mode maintains a persistent process that reuses wavefunctions between ionic steps, providing up to 75% speedup for geometry optimizations.

How Interactive Mode Works#

Traditional:     Start → SCF(50) → Exit → Start → SCF(50) → Exit → ...
Interactive:     Start → SCF(50) → SCF(15) → SCF(10) → ... → Exit
                              ↑           ↑
                         Reuses wavefunction!
import numpy as np
from ase.build import bulk

from vasp.runners import InteractiveRunner

Example 1: Basic Interactive Session#

The InteractiveRunner manages a persistent VASP process.

# Create a copper bulk structure
cu = bulk('Cu', 'fcc', a=3.6)
print(f"Structure: {cu.get_chemical_formula()}")
print(f"Atoms: {len(cu)}")
print(f"Cell: {cu.cell.lengths()}")
Structure: Cu
Atoms: 1
Cell: [2.54558441 2.54558441 2.54558441]
# Create interactive runner
runner = InteractiveRunner(
    vasp_command='vasp_std',
    mpi_command='mpirun -np 4',  # Optional: MPI launcher
    timeout=3600,                 # Max seconds per step
)

print(f"Runner: {runner}")
print(f"Is running: {runner.is_running}")
Runner: InteractiveRunner(vasp_command='vasp_std', status=stopped, steps=0)
Is running: False

Starting a Session#

The start() method:

  1. Modifies INCAR to set INTERACTIVE = .TRUE.

  2. Launches VASP as a subprocess

  3. Returns initial energy and forces

# Note: This would run actual VASP
# results = runner.start('calc/', cu)
# print(f"Energy: {results.energy:.4f} eV")
# print(f"Forces shape: {results.forces.shape}")

# For demonstration, we'll simulate the API
print("API demonstration:")
print("  results = runner.start('calc/', atoms)")
print("  results.energy  → float (eV)")
print("  results.forces  → ndarray (N, 3) (eV/Å)")
API demonstration:
  results = runner.start('calc/', atoms)
  results.energy  → float (eV)
  results.forces  → ndarray (N, 3) (eV/Å)

Stepping Through Optimization#

The step() method sends new positions and receives updated forces:

# Manual optimization loop (conceptual)
def optimize_interactive(runner, atoms, directory, fmax=0.01, maxsteps=50):
    """Simple steepest descent with interactive VASP."""
    atoms = atoms.copy()

    # Start session
    results = runner.start(directory, atoms)

    for step in range(maxsteps):
        forces = results.forces
        max_force = np.max(np.abs(forces))

        print(f"Step {step}: E = {results.energy:.4f} eV, "
              f"max|F| = {max_force:.4f} eV/Å")

        if max_force < fmax:
            print("Converged!")
            break

        # Update positions (steepest descent)
        atoms.positions -= 0.1 * forces

        # Get new forces (reuses wavefunction!)
        results = runner.step(atoms)

    return atoms, results

print("Function defined: optimize_interactive(runner, atoms, directory)")
Function defined: optimize_interactive(runner, atoms, directory)

Cleanup with Context Manager#

Always close the session to allow VASP to write final output files:

# Using context manager (recommended)
print("Using context manager:")
print("""
with InteractiveRunner() as runner:
    results = runner.start('calc/', atoms)

    for step in range(100):
        if converged(results):
            break
        atoms = update_positions(atoms, results)
        results = runner.step(atoms)

# VASP automatically closed, OUTCAR written
""")
Using context manager:

with InteractiveRunner() as runner:
    results = runner.start('calc/', atoms)

    for step in range(100):
        if converged(results):
            break
        atoms = update_positions(atoms, results)
        results = runner.step(atoms)

# VASP automatically closed, OUTCAR written

Example 2: With ASE Optimizers#

Wrap the interactive runner to work with ASE’s optimization algorithms:

from ase.calculators.calculator import Calculator


class InteractiveVaspCalculator(Calculator):
    """ASE calculator wrapper for InteractiveRunner."""

    implemented_properties = ['energy', 'forces']

    def __init__(self, runner, directory, **kwargs):
        super().__init__(**kwargs)
        self.runner = runner
        self.directory = directory
        self._started = False

    def calculate(self, atoms=None, properties=None,
                  system_changes=None):
        if properties is None:
            properties = ['energy', 'forces']
        super().calculate(atoms, properties, system_changes)

        if not self._started:
            results = self.runner.start(self.directory, self.atoms)
            self._started = True
        else:
            results = self.runner.step(self.atoms)

        self.results = {
            'energy': results.energy,
            'forces': results.forces,
        }

print("InteractiveVaspCalculator defined")
InteractiveVaspCalculator defined
# Usage with ASE BFGS optimizer
print("Usage with ASE BFGS:")
print("""
from ase.optimize import BFGS

runner = InteractiveRunner(vasp_command='vasp_std')
calc = InteractiveVaspCalculator(runner, 'relax/')

atoms = bulk('Cu', 'fcc', a=3.6)
atoms.calc = calc

with runner:
    opt = BFGS(atoms, trajectory='opt.traj')
    opt.run(fmax=0.01)
""")
Usage with ASE BFGS:

from ase.optimize import BFGS

runner = InteractiveRunner(vasp_command='vasp_std')
calc = InteractiveVaspCalculator(runner, 'relax/')

atoms = bulk('Cu', 'fcc', a=3.6)
atoms.calc = calc

with runner:
    opt = BFGS(atoms, trajectory='opt.traj')
    opt.run(fmax=0.01)

Example 3: Socket-Based Remote Execution#

For HPC clusters where VASP runs on compute nodes but you want to drive from login node:

from vasp.runners import SocketConfig

# Configuration
config = SocketConfig(
    host='localhost',  # Or compute node hostname
    port=31415,
    timeout=3600,
)

print(f"Socket config: {config}")
Socket config: SocketConfig(host='localhost', port=31415, unix_socket=None, timeout=3600)
# Driver script (runs on login node)
driver_script = '''
from vasp.runners import SocketServer, SocketConfig
from ase.build import bulk
import numpy as np

config = SocketConfig(port=31415)
atoms = bulk('Cu', 'fcc', a=3.6)

with SocketServer(config) as server:
    print("Waiting for VASP client...")
    server.wait_for_client()
    print("Client connected!")

    for step in range(50):
        results = server.calculate(atoms)

        energy = results['energy']
        forces = results['forces']
        max_force = np.max(np.abs(forces))

        print(f"Step {step}: E={energy:.4f}, max|F|={max_force:.4f}")

        if max_force < 0.01:
            break

        atoms.positions -= 0.1 * forces
'''
print("Driver script (login node):")
print(driver_script)
Driver script (login node):

from vasp.runners import SocketServer, SocketConfig
from ase.build import bulk
import numpy as np

config = SocketConfig(port=31415)
atoms = bulk('Cu', 'fcc', a=3.6)

with SocketServer(config) as server:
    print("Waiting for VASP client...")
    server.wait_for_client()
    print("Client connected!")

    for step in range(50):
        results = server.calculate(atoms)

        energy = results['energy']
        forces = results['forces']
        max_force = np.max(np.abs(forces))

        print(f"Step {step}: E={energy:.4f}, max|F|={max_force:.4f}")

        if max_force < 0.01:
            break

        atoms.positions -= 0.1 * forces
# VASP client script (runs on compute node)
client_script = '''
from vasp.runners import SocketClient, SocketConfig, InteractiveRunner
from ase.build import bulk

config = SocketConfig(host='login-node', port=31415)
runner = InteractiveRunner(vasp_command='vasp_std')

# Template atoms (just for element types)
atoms = bulk('Cu', 'fcc', a=3.6)

client = SocketClient(config, runner)
client.connect()
client.run(atoms, 'calc/')  # Blocks until driver sends EXIT
'''
print("Client script (compute node):")
print(client_script)
Client script (compute node):

from vasp.runners import SocketClient, SocketConfig, InteractiveRunner
from ase.build import bulk

config = SocketConfig(host='login-node', port=31415)
runner = InteractiveRunner(vasp_command='vasp_std')

# Template atoms (just for element types)
atoms = bulk('Cu', 'fcc', a=3.6)

client = SocketClient(config, runner)
client.connect()
client.run(atoms, 'calc/')  # Blocks until driver sends EXIT

INCAR Modifications#

The InteractiveRunner automatically modifies your INCAR:

print("Original INCAR:")
print("""
ENCUT = 400
ISMEAR = 0
SIGMA = 0.1
NSW = 100      # Will be removed
IBRION = 2     # Will be removed
""")

print("Modified INCAR (by InteractiveRunner):")
print("""
INTERACTIVE = .TRUE.   # Added
NSW = 0                # Set to 0
IBRION = -1            # Set to -1 (no internal opt)
ENCUT = 400
ISMEAR = 0
SIGMA = 0.1
""")
Original INCAR:

ENCUT = 400
ISMEAR = 0
SIGMA = 0.1
NSW = 100      # Will be removed
IBRION = 2     # Will be removed

Modified INCAR (by InteractiveRunner):

INTERACTIVE = .TRUE.   # Added
NSW = 0                # Set to 0
IBRION = -1            # Set to -1 (no internal opt)
ENCUT = 400
ISMEAR = 0
SIGMA = 0.1

Performance Tips#

  1. Extrapolation: VASP extrapolates wavefunctions between steps. Small position changes → fast convergence.

  2. ALGO: Use ALGO = Fast or ALGO = VeryFast for interactive mode.

  3. Step size: Keep geometry changes small (< 0.1 Å) for best wavefunction reuse.

  4. Memory: Interactive mode keeps wavefunctions in memory. Ensure sufficient RAM.

# Recommended INCAR settings for interactive mode
recommended_settings = {
    'ALGO': 'Fast',      # Davidson + RMM-DIIS
    'NELM': 100,         # Max electronic steps
    'NELMIN': 4,         # Min electronic steps
    'EDIFF': 1e-6,       # Electronic convergence
    'LREAL': 'Auto',     # Real-space projection for large systems
    'LWAVE': False,      # Don't write WAVECAR (in memory)
    'LCHARG': False,     # Don't write CHGCAR
}

print("Recommended INCAR settings:")
for key, value in recommended_settings.items():
    print(f"  {key} = {value}")
Recommended INCAR settings:
  ALGO = Fast
  NELM = 100
  NELMIN = 4
  EDIFF = 1e-06
  LREAL = Auto
  LWAVE = False
  LCHARG = False

Summary#

Mode

Use Case

Speedup

InteractiveRunner

Local optimization

2-4x

SocketServer/Client

Remote/distributed

2-4x

Interactive mode is most beneficial for:

  • Geometry optimizations with many steps

  • NEB calculations

  • Molecular dynamics

  • ML potential training with DFT validation