Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,51 @@ This CLA is an adapted industry-standard CLA (Apache CLA) with minor modificatio
- [Individual CLA (ICLA) for personal contributions](CLA.md),
- Corporate CLA (CCLA) for contributions made on behalf of an employer (available upon request to info@faccts.de).

## AIMNet2 / MLIP integration

OPI already supports ORCA external methods through `! extopt` and `%method ProgExt ... end`.
This local patch adds a first-class AIMNetCentral helper so OPI can target maintained AIMNet2 models instead of the deprecated `aimnet2calc` / archived `AIMNet2` path.

Local usage after this patch:

```python
from opi.core import Calculator
from opi.external_methods import AimnetCentralConfig, create_aimnetcentral_extopt
from opi.input.simple_keywords import Task
from opi.input.structures import Structure

calc = Calculator(basename="job", working_dir="RUN", version_check=False)
calc.structure = Structure.from_xyz("inp.xyz")

extopt_kw, aimnet_block = create_aimnetcentral_extopt(
AimnetCentralConfig(
model="aimnet2_2025",
device="cuda",
compile_model=True,
coulomb_method="dsf",
coulomb_cutoff=15.0,
dftd3_cutoff=15.0,
)
)

calc.input.add_simple_keywords(extopt_kw, Task.OPT)
calc.input.add_blocks(aimnet_block)
calc.write_input()
```

What this wrapper exposes from AIMNetCentral:
- latest maintained model aliases such as `aimnet2`, `aimnet2_2025`, `aimnet2nse`, and `aimnet2pd`
- optional `torch.compile` acceleration
- adaptive neighbor lists / modern batching through the upstream `AIMNet2Calculator`
- configurable long-range Coulomb modes (`simple`, `dsf`, `ewald`)
- configurable external DFT-D3 cutoff / smoothing
- Hugging Face or local model loading through the AIMNetCentral calculator API

Current limitations of this local patch:
- the ORCA ExtOpt text interface is single-structure, so AIMNetCentral batch inference is used internally for neighbor handling but not yet for multi-geometry ORCA-side dispatch
- point charges from ORCA's optional external file are parsed by OPI but are not yet forwarded into AIMNetCentral
- periodic cell reconstruction from ORCA ExtOpt files is not implemented yet; the wrapper currently targets molecular optimization / gradient workflows

## Citation

If you use OPI in your research, please consider citing the following:
Expand Down
35 changes: 35 additions & 0 deletions examples/exmp054_aimnetcentral/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3

from pathlib import Path

from opi.core import Calculator
from opi.external_methods import AimnetCentralConfig, create_aimnetcentral_extopt
from opi.input.simple_keywords import Task
from opi.input.structures import Structure


def main() -> None:
working_dir = Path("RUN")
working_dir.mkdir(exist_ok=True)

calc = Calculator(basename="job", working_dir=working_dir, version_check=False)
calc.structure = Structure.from_xyz("inp.xyz")

extopt_kw, aimnet_block = create_aimnetcentral_extopt(
AimnetCentralConfig(
model="aimnet2_2025",
device="cuda",
compile_model=True,
coulomb_method="dsf",
coulomb_cutoff=15.0,
dftd3_cutoff=15.0,
)
)

calc.input.add_simple_keywords(extopt_kw, Task.OPT)
calc.input.add_blocks(aimnet_block)
calc.write_input()


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions examples/exmp055_aimnetcentral_ts/inp.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
2

H 0.0 0.0 0.0
H 0.74 0.0 0.0
94 changes: 94 additions & 0 deletions examples/exmp055_aimnetcentral_ts/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3

"""
Example exmp055: Transition State Optimization with AIMNetCentral

This example demonstrates how to perform a transition state (TS) optimization
using ORCA's external methods interface with AIMNetCentral as the calculator.

For TS optimization, ORCA uses:
- `! extopt optts` keyword combination
- The external method wrapper computes gradients (forces) for the TS optimizer
- TS requires at least one imaginary frequency in the final Hessian

Notes
-----
- The wrapper computes gradients which ORCA's TS optimizer uses internally
- Make sure your initial guess has the correct symmetry/constraints for the TS
- After optimization, verify the TS has exactly one imaginary frequency
"""

from pathlib import Path

from opi.core import Calculator
from opi.external_methods import AimnetCentralConfig, create_aimnetcentral_extopt
from opi.input.blocks import BlockGeom
from opi.input.simple_keywords import Task, Opt
from opi.input.structures import Structure


def main() -> None:
working_dir = Path("RUN_TS")
working_dir.mkdir(exist_ok=True)

# > Create calculator
calc = Calculator(basename="ts_opt", working_dir=working_dir, version_check=False)

# > Read structure from inp.xyz (should be an initial guess near the TS)
calc.structure = Structure.from_xyz("inp.xyz")

# > For TS optimization, we need to specify opt_type="optts"
extopt_kw, aimnet_block = create_aimnetcentral_extopt(
AimnetCentralConfig(
model="aimnet2_2025",
device="cuda",
opt_type="optts", # Important: set to "optts" for transition state
),
opt_type="optts", # Also set via create_aimnetcentral_extopt parameter
)

# > For TS optimization, use Task.OPT with Opt.OPTTS keyword
# > ORCA will use %geom ts_search ef for Eigenvector Follow
calc.input.add_simple_keywords(extopt_kw, Opt.OPTTS)

# > Optional: Add %geom block for TS-specific settings
# > These can help the TS optimizer converge
calc.input.add_blocks(
BlockGeom(
ts_search="ef", # Eigenvector Follow for TS search
maxiter=100,
trust=0.1, # Smaller trust radius for TS
step="rfo", # Rational Function Optimization
)
)

# > Set number of cores
calc.input.ncores = 4

# > Write input and run
calc.write_input()
print(f"ORCA input written to {working_dir / 'ts_opt.inp'}")

# > To run the actual calculation, uncomment the following:
# > calc.run()

print("\nExample input for TS optimization:")
print("=" * 60)
print(f"! extopt optts")
print(f"%method")
print(f" ProgExt {aimnet_block.ProgExt}")
print(f" Ext_Params {aimnet_block.Ext_Params}")
print(f"end")
print(f"%geom")
print(f" ts_search ef")
print(f" maxiter 100")
print(f" trust 0.1")
print(f" step rfo")
print(f"end")
print("=" * 60)
print("\nAfter running, verify TS by checking for exactly one imaginary frequency.")
print("Run: orca_job.out for frequency analysis or use ORCA's freq module.")


if __name__ == "__main__":
main()
97 changes: 97 additions & 0 deletions examples/exmp056_aimnetcentral_nse/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# exmp056_aimnetcentral_nse

This example demonstrates AIMNetCentral with NSE (Neural Spin Equilibration) for open-shell systems.

## NSE Model Requirements

The AIMNet2-NSE model (`aimnet2nse`) supports open-shell chemistry with spin-polarized charges:
- Requires `num_charge_channels=2` (vs. 1 for closed-shell models)
- Needs both `charge` and `mult` (multiplicity) inputs
- Outputs `spin_charges` in addition to regular `charges`

## Usage

### Command line (direct)

```bash
# Set multiplicity via command line
python3 run_aimnetcentral_extopt.py --model aimnet2nse --mult 2.0

# Or via config file
python3 -m opi.external_methods.aimnetcentral.run_aimnetcentral_extopt --model aimnet2nse --mult 2.0
```

### With ORCA ExtOpt (recommended)

```python
from opi.external_methods import AimnetCentralConfig, create_aimnetcentral_extopt

config = AimnetCentralConfig(
model="aimnet2nse",
charge=0.0, # Molecular charge
mult=2.0, # Spin multiplicity (2S+1)
)

extopt_kw, aimnet_block = create_aimnetcentral_extopt(config)
```

### ORCA input file

Add to your ORCA input:
```
! extopt
%method
ProgExt "python3"
Ext_Params "path/to/run_aimnetcentral_extopt.py --model aimnet2nse --mult 2.0"
end
```

## NSE Model Selection

| Model | `num_charge_channels` | Use case |
|-------|----------------------|----------|
| `aimnet2`, `aimnet2_2025` | 1 | Closed-shell systems |
| `aimnet2nse` | 2 | Open-shell systems (radicals, diradicals) |
| `aimnet2pd` | 1 | Protein-ligand binding |

## Spin Multiplicity Guide

| Multiplicity | Spin (S) | Electrons | Example |
|--------------|----------|-----------|---------|
| 1 | 0 | Even | Closed-shell, singlet |
| 2 | 1/2 | Odd | Doublet, radicals |
| 3 | 1 | Even | Triplet, diradicals |
| 4 | 3/2 | Odd | Quartet, radicals |

For radicals: `multiplicity = number_of_unpaired_electrons + 1`

## Verifying Spin Charge Output

After calculation, check:
- `spin_charges`: Spin-polarized atomic charges (sum should equal `mult - 1`)
- `charges`: Total atomic charges
- For doublet (`mult=2`): `sum(spin_charges) ≈ 1.0`

## Python API

```python
from aimnet.calculators import AIMNet2Calculator, AIMNet2ASE

# Direct calculator
calc = AIMNet2Calculator("aimnet2nse")
data = {
"coord": coord,
"numbers": numbers,
"charge": 0.0,
"mult": 2.0,
}
results = calc(data)
print(results["spin_charges"]) # Spin-polarized charges

# ASE calculator
from ase import Atoms
atoms = Atoms("CH3", positions=...)
atoms.calc = AIMNet2ASE("aimnet2nse", charge=0, mult=2)
atoms.get_potential_energy()
spin_charges = atoms.calc.get_spin_charges()
```
71 changes: 71 additions & 0 deletions examples/exmp056_aimnetcentral_nse/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Example: AIMNetCentral with NSE (Neural Spin Equilibration) for open-shell systems.

This example demonstrates how to use AIMNet2-NSE model with ORCA ExtOpt interface
for calculating properties of open-shell (radical) systems.

The NSE model supports spin-polarized charges with num_charge_channels=2,
requiring both charge and multiplicity (or spin) to be passed correctly.
"""

from pathlib import Path

# This example shows how to write ORCA input files for AIMNetCentral NSE calculations
# To run actual AIMNetCentral calculations, use the command-line interface:
# python -m opi.external_methods.aimnetcentral.run_aimnetcentral_extopt --model aimnet2nse --mult 2.0

def main() -> None:
"""Write ORCA input files for NSE calculation on a radical system (methyl radical)."""
working_dir = Path("RUN")
working_dir.mkdir(exist_ok=True)

# Create a simple XYZ file for methyl radical (CH3•)
# Open-shell system with 7 valence electrons (odd count)
# Charge = 0, Multiplicity = 2 (doublet, S = 1/2)
xyz_content = """4

C 0.000000 0.000000 0.000000
H 0.000000 0.000000 1.090000
H 1.026739 0.000000 -0.363333
H -0.513370 -0.889181 -0.363333
"""
xyz_path = working_dir / "inp.xyz"
xyz_path.write_text(xyz_content)

# Create ORCA input file
orca_input = """! extopt pbe def2-svp def2-svpjk

%scf
maxiter 200
end

%method
ProgExt "python3"
Ext_Params "run_aimnetcentral_extopt.py --model aimnet2nse --mult 2.0"
end

* xyz 0 2
C 0.000000 0.000000 0.000000
H 0.000000 0.000000 1.090000
H 1.026739 0.000000 -0.363333
H -0.513370 -0.889181 -0.363333
*
"""
orca_path = working_dir / "job.inp"
orca_path.write_text(orca_input)

print(f"Input files written to {working_dir}")
print(f"Created: {xyz_path}")
print(f"Created: {orca_path}")
print()
print("To run the calculation:")
print(" cd RUN && orca job.inp")
print()
print("For NSE models (aimnet2nse):")
print(" - Set 'Mult 2' in ORCA input (or via Ext_Params)")
print(" - Both charge and multiplicity must be specified correctly")
print(" - Outputs spin_charges in addition to charges")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"pydantic>=2.11.5,<3",
"rdkit>=2025.3.2,<2026",
"semantic-version>=2.10.0,<3",
"aimnet>=0.1",
]
requires-python = ">=3.11"
readme = "README.md"
Expand Down
14 changes: 14 additions & 0 deletions src/opi/external_methods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@
* `interface`: Module for reading/writing ORCA output/input meant for the external-tools
"""

from opi.external_methods.aimnetcentral import (
AimnetCentralConfig,
create_aimnetcentral_extopt,
get_aimnetcentral_wrapper_path,
)
from opi.external_methods.interface import ExtoptInterface
from opi.external_methods.process import Process
from opi.external_methods.server import CalcServer, OpiServer

__all__ = [
"AIMNetCentralClient",
"AimnetCentralConfig",
"CalcServer",
"ExtoptInterface",
"OpiServer",
"Process",
"create_aimnetcentral_extopt",
"get_aimnetcentral_wrapper_path",
"make_single_point_request",
"run_with_server",
"run_with_server_batch",
"start_server",
"shutdown_server",
]
Loading