5.8. ASE Calculator parametrization
***********************************

This tutorial shows you how you can

* parametrize a custom ASE calculator with ParAMS

Starting with AMS2023.1, AMS can use any custom ASE calculator as the
"engine". If you define the ASE calculator to be able to read the
ParAMS parameter_interface.yaml format, you can use ParAMS to optimize
the parameters used by the ASE calculator.

The files for this tutorial are located in
"$AMSHOME/scripting/scm/params/examples/CustomASECalculator". **Copy**
the files to a new **empty** directory.


5.8.1. Definition of the ASE calculator
=======================================

The file **calculator.py** defines an ASE calculator called
"MyCustomASECalculator". The "calculate()" method should populate the
energy (in eV) and ideally also the forces (in eV/ang) in
"self.results['energy']" and "self.results['forces']". For more
information about how to write ASE calculators, see the ASE website.

In this example, the energy is calculated by means of pair-wise
springs with spring constants "k" and "r0".

The function "get_calculator(params_path)" receives the path to the
parameter_interface.yaml file and returns an instance of the ASE
calculator. This function **must** be called "get_calculator".

   #!/usr/bin/env amspython
   from ase.calculators.calculator import Calculator
   from scm.params import ASEParameters
   from scm.plams import *


   class MyCustomASECalculator(Calculator):
       implemented_properties = ["energy"]

       def __init__(self, params_path=""):
           Calculator.__init__(self)
           self.parameter_interface = ASEParameters.yaml_load(params_path)

       def calculate(self, atoms=None, properties=None, system_changes=None):
           distance_matrix = atoms.get_all_distances(mic=True)
           energy = 0
           for i in range(len(atoms)):
               isym = atoms.symbols[i]
               energy += self.parameter_interface[f"ATM:{isym}:self_energy"].value
               for j in range(i):
                   jsym = atoms.symbols[j]
                   pname = f"{isym}.{jsym}" if isym < jsym else f"{jsym}.{isym}"
                   k = self.parameter_interface[f"BND:{pname}:k"].value
                   r0 = self.parameter_interface[f"BND:{pname}:r0"].value
                   energy += 0.5 * k * (distance_matrix[i, j] - r0) ** 2
           self.results["energy"] = energy

           # note: also populate self.results['forces'] for significant speedup, otherwise the forces are calculated numerically by the AMS Driver


   def get_calculator(params_path=""):
       """
       params_path: str
           Path to a parameter_interface.yaml file in the ParAMS format for an interface of type ASEParameters.
       """
       return MyCustomASECalculator(params_path=params_path)


5.8.2. Creation of the initial parameter interface
==================================================

The file **generate_parameter_interface.py**

* creates the "parameter_interface.yaml" file,

* modifies "job_collection.yaml" so that each job uses the "ParAMS"
  ExtraEngineID,

* modifies "job_collection_engines.yaml" so that the "ParAMS" engine
  has the **absolute** path to "calculator.py" specified in
  "ASE%File".

Note that the parameter names in "parameter_interface.yaml" can be
completely arbitrary, as long as the calculator in "calculator.py" can
interpret them.

   #!/usr/bin/env amspython
   from scm.plams import Settings
   from scm.params import ASEParameters, EngineCollection, JobCollection, Engine
   from scm.params.parameterinterfaces.base import Parameter
   import os

   """ 

   Copy all files to a new directory before running this file! It will overwrite
   parameter_interface.yaml, job_collection.yaml, and job_collection_engines.yaml

   In particular, the absolute path to calculator.py will be set inside
   job_collection_engines.yaml

   Run as $AMSBIN/amspython generate_parameter_interface.py
   """


   def main():
       parameters = [
           Parameter(name="ATM:O:self_energy", value=-80.0, range=(-200, 200), is_active=False),
           Parameter(name="ATM:H:self_energy", value=-20.0, range=(-200, 200), is_active=False),
           Parameter(name="BND:H.H:k", value=10.0, range=(0, 100), is_active=True),
           Parameter(name="BND:H.H:r0", value=1.5, range=(1.0, 3), is_active=True),
           Parameter(name="BND:H.O:k", value=50.0, range=(0, 100), is_active=True),
           Parameter(name="BND:H.O:r0", value=1.0, range=(0.5, 3), is_active=True),
           Parameter(name="BND:O.O:k", value=10.0, range=(0, 100), is_active=False),
           Parameter(name="BND:O.O:r0", value=3.0, range=(1, 8), is_active=False),
       ]

       interf = ASEParameters(parameters=parameters)
       interf.yaml_store("parameter_interface.yaml")

       s = Settings()
       s.input.ASE.Type = "File"

       # calculator.py contains a function get_calculator(params_path, **kwargs)
       # which returns the ASE calculator. You must set the absolute path to
       # calculator.py inside job_collection_engines.yaml on the machine where you run
       # params.

       s.input.ASE.File = os.path.abspath("calculator.py")

       # update the job collection so that
       # * all entries have ExtraEngineID ParAMS
       # * the ParAMS engine in job_collection_engines.yaml has the definition above

       jc = JobCollection("job_collection.yaml")
       jc.set_extra_engine(s)
       jc.store("job_collection.yaml")


   if __name__ == "__main__":
       main()

   Copy the example files to a new directory
   Run "$AMSBIN/amspython generate_parameter_interface.py".
   Verify that the correct absolute path to "calculator.py" is given in "job_collection_engines.yaml"

Note:

  You need to specify the absolute path to "calculator.py" on the
  machine where ParAMS runs. If you run on a remote machine, make sure
  that the path is correct for that machine!

Note:

  You cannot create new parameters in the GUI. The initial parameters
  must be created in a Python script as above.


5.8.3. Run the custom ASE parametrization
=========================================

If you ran "generate_parameter_interface.py" so that the path to
"calculator.py" is correctly given, then you can run the
parametrization as normal.

* Open "job_collection.yaml" in the ParAMS GUI, save the job and run
  it, *or*

* run "$AMSBIN/params" from the command-line.


5.8.4. Results of the custom ASE parametrization
================================================

The following figure can be produced by opening the job in the ParAMS
GUI (if not already done so) and setting:

* the first graph to "Loss:loss"

* the second graph to "Bondscan..."

* the third graph to "Anglescan..."

[image]
