Constrained Geometry Optimization with AMSWorker
************************************************

ForceField Geometry Optimization Technical

Reuse a single AMS worker process for repeated constrained geometry
optimizations and inspect how constraint settings affect the result.

**Requires:** AMS2026 or later

-[ Related examples ]-

* Using AMS as an ASE Calculator

* Reaction Discovery with ReaxFF

-[ Related tutorials ]-

* Python Getting Started tutorial

-[ Related documentation ]-

* Geometry Optimization task

* AMS pipe protocol

* ForceField introduction

[image]


Constrained geometry optimizations with the AMSWorker
=====================================================

The "AMSWorker" class allows geometry optimization of multiple
molecules without the overhead of AMS start-up for each molecule. Here
we want to optimize the geometries of three different molecules, with
benzene as the common substructure, but wit different substituents.

   import os
   from scm.plams import Molecule, plot_image_grid, view

   path = os.path.join(os.environ["AMSRESOURCES"], "Molecules", "TestMols", "PLAMS")
   filenames = ["tbut_benzene.in", "o_di_tbut_benzene.in", "tri_tbut_benzene.in"]
   molecules = [Molecule(os.path.join(path, fn)) for fn in filenames]

The structures are unoptimized, as the crowded geometry of the third
structure demonstrates.

   plot_image_grid({f: view(m) for f, m in zip(filenames, molecules)}, rows=1);

   [image: image generated from notebook][image]

The geometry of the three structures can be optimized with the
AMSWorker as follows.

   from scm.plams import AMSWorker, Settings

   # Create the general settings object and start upt the amsworker
   settings = Settings()
   settings.input.ForceField.Type = "UFF"
   worker = AMSWorker(settings)

   stackmol = Molecule()
   for i, mol in enumerate(molecules):
       results = worker.GeometryOptimization("go%i" % (i), mol)
       stackmol += results.get_main_molecule()

   view(stackmol, width=300, height=300)

   [image: image generated from notebook][image]

We may prefer to perform the optimization while constraining the
positions of the benzene carbon atoms, so that the benzene rings can
be stacked directly on top of one another. The constraints can be
passed to the "GeometryOptimization()" call as a settings object. The
settings object has the same layout as the constraints passed to a
regular "AMSJob".

   stackmol = Molecule()
   s = Settings()
   s.input.ams.Constraints.Atom = [1, 2, 3, 4, 5, 6]
   for i, mol in enumerate(molecules):
       # Pass the constraints to the optimizer
       results = worker.GeometryOptimization("constrained%i" % (i), mol, constraints=s)
       stackmol += results.get_main_molecule()

   view(stackmol, width=300, height=300)

   [image: image generated from notebook][image]

If we use contraints designed for one molecule in a geometry
optimization for a different molecule, this may result in an error. We
can look at the error message to check.

   from scm.plams import JobError
   from scm.plams import from_smiles
   from scm.amspipe import AMSPipeError

   results = worker.GeometryOptimization("water", from_smiles("O"), constraints=s)
   print(results.get_errormsg())

   Atom index 4 read from Constraints%Atom[4] is out of range.

   worker.stop();


See also
========


Related examples
----------------

* Using AMS as an ASE Calculator

* Reaction Discovery with ReaxFF


Related tutorials
-----------------

* Python Getting Started tutorial


Related documentation
---------------------

* Geometry Optimization task

* AMS pipe protocol

* ForceField introduction


Python Script
=============

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

   # ## Constrained geometry optimizations with the AMSWorker
   #
   # The `AMSWorker` class allows geometry optimization of multiple molecules without the overhead of AMS start-up for each molecule.
   # Here we want to optimize the geometries of three different molecules, with benzene as the common substructure,
   # but wit different substituents.

   import os
   from scm.plams import Molecule, plot_image_grid, view

   path = os.path.join(os.environ["AMSRESOURCES"], "Molecules", "TestMols", "PLAMS")
   filenames = ["tbut_benzene.in", "o_di_tbut_benzene.in", "tri_tbut_benzene.in"]
   molecules = [Molecule(os.path.join(path, fn)) for fn in filenames]


   # The structures are unoptimized, as the crowded geometry of the third structure demonstrates.

   plot_image_grid({f: view(m) for f, m in zip(filenames, molecules)}, rows=1, save_path="picture1.png")
   # The geometry of the three structures can be optimized with the AMSWorker as follows.

   from scm.plams import AMSWorker, Settings

   # Create the general settings object and start upt the amsworker
   settings = Settings()
   settings.input.ForceField.Type = "UFF"
   worker = AMSWorker(settings)

   stackmol = Molecule()
   for i, mol in enumerate(molecules):
       results = worker.GeometryOptimization("go%i" % (i), mol)
       stackmol += results.get_main_molecule()

   view(stackmol, width=300, height=300, picture_path="picture2.png")


   # We may prefer to perform the optimization while constraining the positions of the benzene carbon atoms, so that the benzene rings can be stacked directly on top of one another. The constraints can be passed to the `GeometryOptimization()` call as a settings object. The settings object has the same layout as the constraints passed to a regular `AMSJob`.

   stackmol = Molecule()
   s = Settings()
   s.input.ams.Constraints.Atom = [1, 2, 3, 4, 5, 6]
   for i, mol in enumerate(molecules):
       # Pass the constraints to the optimizer
       results = worker.GeometryOptimization("constrained%i" % (i), mol, constraints=s)
       stackmol += results.get_main_molecule()

   view(stackmol, width=300, height=300, picture_path="picture3.png")


   # If we use contraints designed for one molecule in a geometry optimization for a different molecule, this may result in an error. We can look at the error message to check.

   from scm.plams import JobError
   from scm.plams import from_smiles
   from scm.amspipe import AMSPipeError

   results = worker.GeometryOptimization("water", from_smiles("O"), constraints=s)
   print(results.get_errormsg())


   worker.stop()
