Examples

This page provides practical examples of using mlff_attack for various adversarial attack scenarios.

Basic FGSM Attack

The simplest way to perform an FGSM attack using the high-level API:

from ase.io import read
from mlff_attack.attacks import make_attack

# Load your structure
atoms = read('structure.cif')

# Perform FGSM attack
output_path, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=atoms,
    epsilon=0.1,
    target_energy=None,
    output_cif='perturbed_structure.cif',
    attack_type='fgsm'
)

print(f"Perturbed structure saved to: {output_path}")

PGD Attack with Multiple Steps

For iterative attacks with stronger perturbations:

from ase.io import read
from mlff_attack.attacks import make_attack

atoms = read('structure.cif')

# Perform iterative FGSM attack (I-FGSM) with 10 iterations
output_path, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=atoms,
    epsilon=0.05,
    target_energy=None,
    output_cif='perturbed_ifgsm.cif',
    attack_type='fgsm',
    n_steps=10,
    clip=False
)

Using the Class-Based API

For more control over the attack process:

from ase.io import read
from mlff_attack.grad_based.fgsm import FGSM_MACE
from mlff_attack.relaxation import setup_calculator

# Load structure and setup calculator
atoms = read('structure.cif')
atoms = setup_calculator(atoms, 'mace-mpa-0-medium.model', device='cuda')

# Create FGSM attack instance
fgsm_attack = FGSM_MACE(
    model=atoms.calc,
    epsilon=0.1,
    device='cuda',
    track_history=True
)

# Execute attack (returns perturbed atoms object)
perturbed_atoms = fgsm_attack.attack(atoms, n_steps=1, clip=True)

# Access attack history
print("Energies:", fgsm_attack.attack_history['energies'])
print("Max forces:", fgsm_attack.attack_history['max_forces'])

Targeted Energy Attack

Perform an attack aiming for a specific energy value:

from ase.io import read
from mlff_attack.attacks import make_attack
from mlff_attack.relaxation import setup_calculator

atoms = read('structure.cif')

# Get initial energy
atoms = setup_calculator(atoms, 'mace-mpa-0-medium.model', device='cuda')
initial_energy = atoms.get_potential_energy()
target_energy = initial_energy + 1.0  # Target 1 eV higher

# Perform targeted attack
output_path, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=atoms,
    epsilon=0.01,
    target_energy=target_energy,
    output_cif='targeted_attack.cif',
    attack_type='fgsm',
    n_steps=100
)

Tracking Attack Progress

Monitor the attack progress with detailed history tracking:

from ase.io import read
from mlff_attack.grad_based.fgsm import FGSM_MACE
from mlff_attack.relaxation import setup_calculator
import matplotlib.pyplot as plt

atoms = read('structure.cif')
atoms = setup_calculator(atoms, 'mace-mpa-0-medium.model', device='cuda')

# Create attack with history tracking
attack = FGSM_MACE(
    model=atoms.calc,
    epsilon=0.05,
    device='cuda',
    track_history=True
)

# Execute iterative attack
perturbed_atoms = attack.attack(atoms, n_steps=20, clip=False)

# Get attack summary
summary = attack.get_attack_summary()
print(f"Initial Energy: {summary['initial_energy']:.3f} eV")
print(f"Final Energy: {summary['final_energy']:.3f} eV")
print(f"Energy Change: {summary['energy_change']:.3f} eV")
print(f"Max Displacement: {summary['max_displacement']:.3f} Å")

# Plot energy evolution
plt.figure(figsize=(10, 6))
plt.plot(attack.attack_history['energies'])
plt.xlabel('Attack Step')
plt.ylabel('Energy (eV)')
plt.title('Energy Evolution During Attack')
plt.savefig('attack_energy.png')

Visualizing Perturbations

Visualize the perturbations applied to the structure:

from ase.io import read
from mlff_attack.attacks import make_attack, visualize_perturbation

# Perform attack
atoms = read('structure.cif')
output_path, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=atoms,
    epsilon=0.1,
    attack_type='fgsm',
    output_cif='perturbed.cif'
)

# Visualize the perturbation
fig = visualize_perturbation(
    atoms_before=atoms,
    atoms_after=perturbed_atoms,
    epsilon=0.1,
    outdir='./'
)

Saving and Loading Perturbations

Save perturbations for later analysis or reuse:

from ase.io import read
from mlff_attack.attacks import make_attack, save_perturbation, load_perturbation

# Perform attack
atoms = read('structure.cif')
output_path, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=atoms,
    epsilon=0.1,
    attack_type='fgsm',
    output_cif='perturbed.cif'
)

# Save perturbation with full metadata
save_perturbation(
    atoms_original=atoms,
    atoms_perturbed=perturbed_atoms,
    epsilon=0.1,
    energy_original=atoms.get_potential_energy(),
    energy_perturbed=perturbed_atoms.get_potential_energy(),
    gradients=attack_details['gradients'][-1],
    save_path='perturbation_data.npz'
)

# Later, load perturbation data
loaded_data = load_perturbation('perturbation_data.npz')
print(f"Energy change: {loaded_data['energy_change']:.4f} eV")
print(f"Displacement: {loaded_data['displacement']}")

Complete Workflow Example

A complete workflow from attack to relaxation analysis:

from ase.io import read, write
from mlff_attack.attacks import make_attack
from mlff_attack.relaxation import run_relaxation, load_structure, setup_calculator
from mlff_attack.visualization import create_visualization

# 1. Load original structure
original_atoms = read('structure.cif')

# 2. Perform adversarial attack
output_cif, perturbed_atoms, attack_details = make_attack(
    model_path='mace-mpa-0-medium.model',
    device='cuda',
    atoms=original_atoms,
    epsilon=0.1,
    attack_type='pgd',
    n_steps=10,
    output_cif='perturbed.cif'
)

# 3. Relax original structure
original_atoms = setup_calculator(original_atoms, 'mace-mpa-0-medium.model')
original_results = run_relaxation(
    atoms=original_atoms,
    fmax=0.01,
    max_steps=300,
    optimizer='LBFGS',
    trajectory_file='original_relaxed.traj'
)

# 4. Relax perturbed structure
perturbed_atoms = setup_calculator(perturbed_atoms, 'mace-mpa-0-medium.model')
perturbed_results = run_relaxation(
    atoms=perturbed_atoms,
    fmax=0.01,
    max_steps=300,
    optimizer='LBFGS',
    trajectory_file='perturbed_relaxed.traj'
)

# 5. Visualize and compare trajectories
create_visualization(
    'original_relaxed.traj',
    output_dir='analysis/',
    show_plots=False
)

create_visualization(
    'perturbed_relaxed.traj',
    output_dir='analysis/',
    show_plots=False
)

# 6. Compare final energies
print(f"Original final energy: {original_results['final_energy']:.3f} eV")
print(f"Perturbed final energy: {perturbed_results['final_energy']:.3f} eV")
print(f"Energy difference: {abs(perturbed_results['final_energy'] - original_results['final_energy']):.3f} eV")

Batch Processing Multiple Structures

Process multiple structures in a batch:

from pathlib import Path
from ase.io import read
from mlff_attack.attacks import make_attack

# Directory containing CIF files
input_dir = Path('initial_cifs/')
output_dir = Path('perturbed_cifs/')
output_dir.mkdir(exist_ok=True)

# Process each structure
for cif_file in input_dir.glob('*.cif'):
    print(f"Processing {cif_file.name}...")

    atoms = read(cif_file)
    output_path, perturbed_atoms, attack_details = make_attack(
        model_path='mace-mpa-0-medium.model',
        device='cuda',
        atoms=atoms,
        epsilon=0.1,
        attack_type='fgsm',
        output_cif=str(output_dir / f'perturbed_{cif_file.name}')
    )

    print(f"  Saved to {output_path}")

Using CLI Commands

Examples using the command-line interface:

FGSM Attack:

make-attack --type fgsm --input structure.cif --model mace-model.model \\
            --outdir perturbed.cif --epsilon 0.1

PGD Attack:

make-attack --type pgd --input structure.cif --model mace-model.model \\
            --outdir perturbed.cif --epsilon 0.05 --n-steps 10

MACE Relaxation:

mace-calc-single --input structure.cif --model mace-model.model \\
                 --outdir results/ --fmax 0.01 --max-steps 300

Trajectory Visualization:

visualize-traj --traj results/relaxed.traj --outdir results/ --show --format png