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:
Modifies INCAR to set
INTERACTIVE = .TRUE.Launches VASP as a subprocess
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#
Extrapolation: VASP extrapolates wavefunctions between steps. Small position changes → fast convergence.
ALGO: Use
ALGO = FastorALGO = VeryFastfor interactive mode.Step size: Keep geometry changes small (< 0.1 Å) for best wavefunction reuse.
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 |
|---|---|---|
|
Local optimization |
2-4x |
|
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