{ "cells": [ { "cell_type": "markdown", "id": "be6fbd9c-1268-4dab-9309-170c47ed85f0", "metadata": {}, "source": [ "# Modelforge ASE Calculator: Simple Walkthrough\n", "\n", "This notebook demonstrates how to use a potential trained with modelforge as a calculator in the Atomic Simulation Environment (ASE).\n", "It is written for first-time users of both modelforge and ASE.\n", "\n", "What you will do in this tutorial:\n", "1. Load a trained NNP model checkpoint.\n", "2. Wrap it with `ModelForgeCalculator`.\n", "3. Build a molecule from a SMILES string.\n", "4. Compute single-point energy and forces.\n", "5. Run geometry optimization and a short molecular dynamics simulation." ] }, { "cell_type": "code", "execution_count": 1, "id": "1c30380a-e276-4356-a3be-3601a8717b6e", "metadata": { "scrolled": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-04-20 15:24:41.993\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36mgenerate_potential\u001b[0m:\u001b[36m860\u001b[0m - \u001b[34m\u001b[1mtraining_parameter=None\u001b[0m\n", "\u001b[32m2026-04-20 15:24:41.994\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36mgenerate_potential\u001b[0m:\u001b[36m861\u001b[0m - \u001b[34m\u001b[1mpotential_parameter=SchNetParameters(potential_name='SchNet', only_unique_pairs=False, core_parameter=CoreParameter(number_of_radial_basis_functions=128, maximum_interaction_radius=0.49999999999999994, number_of_interaction_modules=9, number_of_filters=256, shared_interactions=True, activation_function_parameter=ActivationFunctionConfig(activation_function_name='ShiftedSoftplus', activation_function_arguments=None, activation_function=ShiftedSoftplus()), featurization=Featurization(properties_to_featurize=['atomic_number', 'per_system_total_charge'], atomic_number=AtomicNumber(maximum_atomic_number=101, number_of_per_atom_features=512), atomic_period=AtomicPeriod(maximum_period=8, number_of_per_period_features=32), atomic_group=AtomicGroup(maximum_group=18, number_of_per_group_features=32)), predicted_properties=['per_atom_energy', 'per_atom_charge'], predicted_dim=[1, 1]), postprocessing_parameter=PostProcessingParameter(properties_to_process=['per_atom_energy'], per_atom_energy=PerAtomEnergy(normalize=False, from_atom_to_system_reduction=True, keep_per_atom_property=True), per_atom_charge=PerAtomCharge(conserve=True, conserve_strategy='default'), per_system_electrostatic_energy=None, per_system_zbl_energy=None, per_system_vdw_energy=None, sum_per_system_energy=None, general_postprocessing_operation=GeneralPostProcessingOperation(calculate_molecular_self_energy=True, calculate_atomic_self_energy=False)), potential_seed=-1)\u001b[0m\n", "\u001b[32m2026-04-20 15:24:41.994\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36mgenerate_potential\u001b[0m:\u001b[36m862\u001b[0m - \u001b[34m\u001b[1mdataset_parameter=None\u001b[0m\n", "\u001b[32m2026-04-20 15:24:41.995\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36msetup_potential\u001b[0m:\u001b[36m705\u001b[0m - \u001b[34m\u001b[1mpotential_seed None\u001b[0m\n", "\u001b[32m2026-04-20 15:24:41.995\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.schnet\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m64\u001b[0m - \u001b[34m\u001b[1mInitializing the SchNet architecture.\u001b[0m\n", "\u001b[32m2026-04-20 15:24:42.008\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36msetup_potential\u001b[0m:\u001b[36m724\u001b[0m - \u001b[34m\u001b[1mOnly unique pairs: False\u001b[0m\n", "\u001b[32m2026-04-20 15:24:42.008\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36msetup_potential\u001b[0m:\u001b[36m758\u001b[0m - \u001b[34m\u001b[1mCutoffs: local_cutoff=0.49999999999999994, vdw_cutoff=-1, electrostatic_cutoff=-1\u001b[0m\n", "\u001b[32m2026-04-20 15:24:42.010\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mmodelforge.potential.neighbors\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m480\u001b[0m - \u001b[1mNeighborlistForInference initialized\u001b[0m\n", "\u001b[32m2026-04-20 15:24:42.103\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mmodelforge.potential.potential\u001b[0m:\u001b[36mload_state_dict\u001b[0m:\u001b[36m672\u001b[0m - \u001b[34m\u001b[1mRemoved prefixes: {'potential.'}\u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Loaded checkpoint: /Users/jenniferclark/bin/modelforge/modelforge-ase/modelforge/ase/tests/data/model.ckpt\n" ] } ], "source": [ "from modelforge.potential.potential import load_inference_model_from_checkpoint\n", "\n", "# Helper utilities to load the example model checkpoint bundled with modelforge.\n", "from modelforge.utils.io import get_path_string\n", "from modelforge.ase.tests import data\n", "\n", "checkpoint_file_path = f\"{get_path_string(data)}/model.ckpt\" # This is an example model used in testing \n", "potential = load_inference_model_from_checkpoint(checkpoint_file_path, jit=False)\n", "print(f\"Loaded checkpoint: {checkpoint_file_path}\")" ] }, { "cell_type": "markdown", "id": "f966aefd-ccf3-4a6f-bd91-0333c4e639af", "metadata": {}, "source": [ "To evaluate energies and forces in ASE, create a `ModelForgeCalculator` with the loaded potential and attach it to an ASE `Atoms` object." ] }, { "cell_type": "code", "execution_count": 2, "id": "6c1b834b-2e71-4100-abb4-dd34e541dd92", "metadata": {}, "outputs": [], "source": [ "# Install ASE\n", "from modelforge.ase import ModelForgeCalculator\n", "\n", "modelforge_calculator = ModelForgeCalculator(potential)\n" ] }, { "cell_type": "markdown", "id": "b8ce0fa1-d91a-46fa-ba98-62b4ea75108f", "metadata": {}, "source": [ "ASE includes tools for building molecules, and modelforge-ase also provides helper functions for a simple SMILES-based workflow.\n", "\n", "An ASE `Atoms` object stores the system definition: element identities, 3D positions, and optional simulation metadata (for example cell vectors and periodic boundary settings). By itself, `Atoms` is only a container for structure.\n", "\n", "To compute potential energy or forces, you must attach a calculator first (here, `ModelForgeCalculator`). Without a calculator, calls like `atoms.get_potential_energy()` and `atoms.get_forces()` cannot run." ] }, { "cell_type": "code", "execution_count": 3, "id": "b31a0d3b-e412-435e-adda-a554d705d7d2", "metadata": {}, "outputs": [], "source": [ "from modelforge.ase import smiles_to_ase, ase_to_rdkit\n", "\n", "# Set optimize=True to run an MMFF94 geometry optimization in RDKit before conversion.\n", "smiles = \"NCCCCCCO\"\n", "atoms = smiles_to_ase(smiles, optimize=False)\n", "\n", "# Attach a calculator before requesting energy/forces from ASE.\n", "atoms.calc = modelforge_calculator" ] }, { "cell_type": "markdown", "id": "7415213c-8e71-4e26-b4f8-e6c00acefdf5", "metadata": {}, "source": [ "You can view the 3D structure directly in ASE. (A separate RDKit view is also possible using the helper conversion utilities.)" ] }, { "cell_type": "code", "execution_count": 4, "id": "e0ba739d-dd83-494f-bdc1-e5dab0b84cfa", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "