Active Learning: Single-Molecule Setup and Run with QET

Set up a minimal Simple Active Learning workflow for finetuning the QET universal potential for a small organic molecule.

Initial imports

from scm.simple_active_learning import SimpleActiveLearningJob
import scm.plams as plams
from scm.base import ChemicalSystem

Input system

mol = plams.from_smiles("OCC=O", forcefield="uff")
for at in mol:
    at.properties = {}
plams.view(mol, direction="along_pca3", width=150, height=150)
image generated from notebook

Reference engine settings

For time reasons we use the UFF force field as the reference method. Typically you would instead train to DFT using ADF, BAND, or Quantum ESPRESSO.

ref_s = plams.Settings()
ref_s.input.ForceField.Type = "UFF"
ref_s.runscript.nproc = 1
print(plams.AMSJob(settings=ref_s).get_input())
Engine ForceField
  Type UFF
EndEngine

Molecular dynamics settings

Here, we use the convenient AMSNVTJob recipe to easily initialize sone MD settings.

md_s = plams.AMSNVTJob(temperature=300, timestep=0.5, nsteps=10000, thermostat="Berendsen", tau=50).settings
print(plams.AMSJob(settings=md_s).get_input())
MolecularDynamics
  BinLog
    DipoleMoment False
    PressureTensor False
    Time False
  End
  CalcPressure False
  Checkpoint
    Frequency 1000
  End
  InitialVelocities
    Temperature 300
    Type Random
  End
  NSteps 10000
  Thermostat
    Tau 50
    Temperature 300
    Type Berendsen
  End
  TimeStep 0.5
  Trajectory
    SamplingFreq 100
    WriteBonds True
    WriteCharges True
    WriteEngineGradients False
    WriteMolecules True
    WriteVelocities True
  End
End

Task MolecularDynamics

ParAMS ML Training settings

This example fine-tunes the MatGL QET-PBE-2025 foundation model. To use TensorNet, set it to TensorNet-PBE-M-2025. The corresponding r2SCAN models are selected in the same way. (Technical note: When using SimpleActiveLearningJob the ParAMS settings go under input.ams. When using ParAMSJob the settings instead simply go under input. See the ParAMS Python tutorials.)

ml_s = plams.Settings()
ml_s.input.ams.MachineLearning.Backend = "MatGL"
ml_s.input.ams.MachineLearning.CommitteeSize = 1
ml_s.input.ams.MachineLearning.MaxEpochs = 100
# fine-tuning MatGL (QET/TensorNet) requires AMS2027+
ml_s.input.ams.MachineLearning.MatGL.Model = "QET-PBE-2025"

#ml_s.input.ams.MachineLearning.MatGL.LearningRate = 0.0001
#ml_s.input.ams.MachineLearning.MatGL.LearningRateSchedule.Type = "Constant"

# early stopping requires AMS2027+
ml_s.input.ams.MachineLearning.EarlyStopping.Enabled = "Yes"
# use a low early-stopping patience since MaxEpochs is quite small in this example
ml_s.input.ams.MachineLearning.EarlyStopping.Patience = 40

print(SimpleActiveLearningJob(settings=ml_s).get_input())
MachineLearning
  Backend MatGL
  CommitteeSize 1
  EarlyStopping
    Enabled True
    Patience 40
  End
  MatGL
    Model QET-PBE-2025
  End
  MaxEpochs 100
End

Active learning settings

al_s = plams.Settings()
al_s.input.ams.ActiveLearning.Steps.Type = "Geometric"
al_s.input.ams.ActiveLearning.Steps.Geometric.Start = 10  # 10 MD frames
al_s.input.ams.ActiveLearning.Steps.Geometric.NumSteps = 5  # 10 AL steps
print(SimpleActiveLearningJob(settings=al_s).get_input())
ActiveLearning
  Steps
    Geometric
      NumSteps 5
      Start 10
    End
    Type Geometric
  End
End

Simple Active Learning Job

settings = ref_s + md_s + ml_s + al_s
job = SimpleActiveLearningJob(settings=settings, molecule=mol, name="sal")
print(job.get_input())
ActiveLearning
  Steps
    Geometric
      NumSteps 5
      Start 10
    End
    Type Geometric
  End
End
MachineLearning
  Backend MatGL
  CommitteeSize 1
  EarlyStopping
    Enabled True
    Patience 40
  End
  MatGL
    Model QET-PBE-2025
  End
  MaxEpochs 100
End
MolecularDynamics
  BinLog
    DipoleMoment False
    PressureTensor False
... output trimmed ....
EndEngine

System
  Atoms
              O       1.4148380879       1.2995032367       0.0552066866
              C       1.0045475756       0.1523546613       0.1288771758
              C      -0.3482031043      -0.2022667497      -0.4064331140
              O      -1.3422954055       0.5174377380       0.2698992152
              H       1.6276347522      -0.6127588237       0.5803290933
              H      -0.3900536454       0.0512247640      -1.4865055633
              H      -0.5220879195      -1.2970056162      -0.3025647644
              H      -1.4443803409       0.0915107896       1.1611912709
  End
  BondOrders
     1 2 2.0
     2 3 1.0
     3 4 1.0
     2 5 1.0
     3 6 1.0
     3 7 1.0
     4 8 1.0
  End
  Charge 0
End

Run the job

job.run(watch=True);
[12.08|12:24:29] JOB sal STARTED
[12.08|12:24:29] JOB sal RUNNING
[12.08|12:24:30] Simple Active Learning 2026.201,  Nodes: 1, Procs: 1
[12.08|12:24:31] Composition of main system: C2H4O2
[12.08|12:24:31] All REFERENCE calculations will be performed with the following ForceField engine:
[12.08|12:24:31]

Engine ForceField
  Type UFF
EndEngine


[12.08|12:24:31] The following are the settings for the to-be-trained MACHINE LEARNING model:
[12.08|12:24:31]
MachineLearning
  Backend MatGL
  CommitteeSize 1
  EarlyStopping
    Enabled True
    Patience 40
  End
  MatGL
    Model QET-PBE-2025
  End
  MaxEpochs 100
... output trimmed ....
[12.08|12:38:29]     2        3 SUCCESS  Accurate                                         56                         0.3735
[12.08|12:38:29]     3        1 SUCCESS  Accurate                                        316                         0.4624
[12.08|12:38:29]     4        1 FAILED   Inaccurate                                     1778                         0.8213
[12.08|12:38:29]     4        2 FAILED   Inaccurate                                     1778                         0.6959
[12.08|12:38:29]     4        3 SUCCESS  Accurate                                       1778                         0.2892
[12.08|12:38:29]     5        1 SUCCESS  Accurate                                      10000                         0.5062
[12.08|12:38:29] --- End summary ---
[12.08|12:38:29]
[12.08|12:38:29] The engine settings for the final trained ML engine are:
[12.08|12:38:29]

Engine MLPotential
  Backend MatGL
  Model Custom
  ParameterDir /path/to/plams_workdir.006/sal/step4_attempt2_training/results/optimization/matgl/matgl
EndEngine



[12.08|12:38:29] Active learning finished!
[12.08|12:38:29] Rerunning the simulation with the final parameters...
[12.08|12:42:11] Copying final_production_simulation/ams.rkf to ams.rkf
[12.08|12:42:11] Goodbye!
[12.08|12:42:11] JOB sal FINISHED
[12.08|12:42:11] JOB sal SUCCESSFUL

See also

Python Script

#!/usr/bin/env python
# coding: utf-8

# ## Initial imports

from scm.simple_active_learning import SimpleActiveLearningJob
import scm.plams as plams
from scm.base import ChemicalSystem


# ## Input system

mol = plams.from_smiles("OCC=O", forcefield="uff")
for at in mol:
    at.properties = {}
plams.view(mol, direction="along_pca3", width=150, height=150, picture_path="picture1.png")


# ## Reference engine settings
# For time reasons we use the UFF force field as the reference method. Typically you would instead train to DFT using ADF, BAND, or Quantum ESPRESSO.

ref_s = plams.Settings()
ref_s.input.ForceField.Type = "UFF"
ref_s.runscript.nproc = 1


print(plams.AMSJob(settings=ref_s).get_input())


# ## Molecular dynamics settings
# Here, we use the convenient ``AMSNVTJob`` recipe to easily initialize sone MD settings.

md_s = plams.AMSNVTJob(temperature=300, timestep=0.5, nsteps=10000, thermostat="Berendsen", tau=50).settings


print(plams.AMSJob(settings=md_s).get_input())


# ## ParAMS ML Training settings
# 
# This example fine-tunes the MatGL ``QET-PBE-2025`` foundation model. To use TensorNet, set it to ``TensorNet-PBE-M-2025``. The corresponding r2SCAN models are selected in the same way.
# (Technical note: When using ``SimpleActiveLearningJob`` the ParAMS settings go under ``input.ams``. When using ``ParAMSJob`` the settings instead simply go under ``input``. See the ParAMS Python tutorials.)

ml_s = plams.Settings()
ml_s.input.ams.MachineLearning.Backend = "MatGL"
ml_s.input.ams.MachineLearning.CommitteeSize = 1
ml_s.input.ams.MachineLearning.MaxEpochs = 100
# fine-tuning MatGL (QET/TensorNet) requires AMS2027+
ml_s.input.ams.MachineLearning.MatGL.Model = "QET-PBE-2025"

#ml_s.input.ams.MachineLearning.MatGL.LearningRate = 0.0001
#ml_s.input.ams.MachineLearning.MatGL.LearningRateSchedule.Type = "Constant"

# early stopping requires AMS2027+
ml_s.input.ams.MachineLearning.EarlyStopping.Enabled = "Yes"
# use a low early-stopping patience since MaxEpochs is quite small in this example
ml_s.input.ams.MachineLearning.EarlyStopping.Patience = 40 

print(SimpleActiveLearningJob(settings=ml_s).get_input())


# ## Active learning settings

al_s = plams.Settings()
al_s.input.ams.ActiveLearning.Steps.Type = "Geometric"
al_s.input.ams.ActiveLearning.Steps.Geometric.Start = 10  # 10 MD frames
al_s.input.ams.ActiveLearning.Steps.Geometric.NumSteps = 5  # 10 AL steps
print(SimpleActiveLearningJob(settings=al_s).get_input())


# ## Simple Active Learning Job

settings = ref_s + md_s + ml_s + al_s
job = SimpleActiveLearningJob(settings=settings, molecule=mol, name="sal")
print(job.get_input())


# ## Run the job

job.run(watch=True);