Code your own polarizability tensor workflow, part 1¶
This main goal of this notebook is to show how the Workflow
class can be used to quickly implement of a workflow. It allows to define a queue of BigDFT jobs to be run in order to compute a quantity afterwards, as a post-processing procedure.
We will stick to the polarizability tensor problem, in order to build on what was achieved in the previous exercise. The structure of this notebook is very similar to the previous one. You can use the same resources to help you, while the solution to this exercise can be found here.
The Workflow class¶
Using the Workflow
class allows you to start automatizing the generalization of a workflow. You will generally want to use it in order to create toy workflows, allowing you to easily experiment whether such workflows can be successfully implemented, without having to actually code a workflow class deriving from the AbstractWorkflow
class (to be presented in the next exercise).
One advantage of using this Workflow
class is that it allows you to properly divide the workflow in a three-step process:
the definition of a list of jobs so as to initialize the workflow,
the sequential running of all the calculations defined in the first step,
the post-processing of of the calculations outputs, so as to extract the quantity of interest, here the polarizability tensor.
Define your worflow¶
As already mentioned, a workflow is mainly made of a queue of BigDFT jobs. This means you need to define a list of Job
instances, to be run sequentially afterwards.
We want to compute the polarizability tensor of the N\(_2\) molecule using the Workflow
class, just like in the previous exercise. The main input parameters being given, you only have to add Job
instances to the pt_queue
list. You can do so by re-using the same jobs as in the previous notebook (especially the same electric fields and run directories): first, a ground state job (without electric field) and then three calculations with an electric field, along the \(x\),
\(y\) and \(z\) axis respectively. Try to use a for loop to add the last three jobs to the queue.
Only the base classes of the MyBigDFT
package are required to do that, and might also require the help of extra packages such as numpy
(you should not need it yet, though).
[ ]:
from mybigdft import Workflow, InputParams, Posinp, Atom, Job
import numpy as np
# Main input variables:
# - use the optimized structure for the N2 molecule (found using the
# LDA exchange-correlation functional)
atoms = [Atom('N', [0, 0, 0]), Atom('N', [0, 0, 1.0935])]
pos = Posinp(atoms, units="angstroem", boundary_conditions="free")
# - use some non-default input parameters to get high quality results
inp = InputParams({"dft": {"rmult": [7, 9], "hgrids": 0.35}})
# Electric field amplitude to be applied in each direction
ef_amplitude = 1.e-4
# Initialize the queue of jobs in order to be able to compute the
# polarizability tensor
pt_queue = []
pt_wf = Workflow(queue=pt_queue)
[ ]:
# Let us ensure that you followed the instructions:
# - there are four jobs in the queue
assert len(pt_wf.queue) == 4
# - correct electric fields are applied
assert "elecfield" not in pt_wf.queue[0].inputparams["dft"]
assert pt_wf.queue[1].inputparams["dft"]["elecfield"] == [1.e-4, 0.0, 0.0]
assert pt_wf.queue[2].inputparams["dft"]["elecfield"] == [0.0, 1.e-4, 0.0]
assert pt_wf.queue[3].inputparams["dft"]["elecfield"] == [0.0, 0.0, 1.e-4]
# - correct run directories
assert pt_wf.queue[0].run_dir.endswith("N2/pol_tensor")
assert pt_wf.queue[1].run_dir.endswith("N2/pol_tensor/EF_along_x+")
assert pt_wf.queue[2].run_dir.endswith("N2/pol_tensor/EF_along_y+")
assert pt_wf.queue[3].run_dir.endswith("N2/pol_tensor/EF_along_z+")
Run the jobs of your workflow¶
Nothing important to code here, as you might only want to change the value of the attributes to suit your needs.
Note that you do not have to use the context manager: no need for a with
statement before using the run
method of a Workflow
instance as it is actually taken care of internally. This is one of the advantage of using a workflow.
[ ]:
pt_wf.run(nmpi=6, nomp=3)
Note that, if you succeeded in performing the previous notebook, the calculations were not run again.
[ ]:
# Let us ensure that you followed the instructions:
assert pt_wf.is_completed
Post-process your results¶
Now that you ran the workflow, you can use the results to extract meaningful quantities. Let us define a post_proc
function to do just that. Its only argument must be a workflow instance. You should be able to do the post-processing without having to add another argument to the post_proc
function. Try to loop over the three calculations with an electric field to get their dipoles.
[ ]:
def post_proc(workflow):
"""
Use the logfile of the calculations run by the workflow to
extract the polarizability tensor.
Parameters
----------
workflow: Workflow
Workflow of run calculations allowing to compute the
polarizability tensor of a given system.
Returns
-------
numpy array
Polarizability tensor of the system.
"""
pol_tensor = np.zeros((3, 3))
return pol_tensor
# Run the post-processing of the workflow to compute
pol_tensor = post_proc(pt_wf)
print(pol_tensor)
[ ]:
# Let us ensure that you obtained the expected result:
expected_pol_tensor = [[ 1.075753e+01, 5.400000e-04, -9.900000e-04],
[ 5.400000e-04, 1.075753e+01, -9.900000e-04],
[-1.740000e-03, -1.740000e-03, 1.525630e+01]]
assert np.allclose(pol_tensor, expected_pol_tensor)
Congratulations, you obtained the same results as in the previous notebook! Note how easier it is to re-use the code implemented here compared to the previous one. This is one of the main advantage of using the Workflow
class. Another one is that it enables you to create a toy workflow which can be easily converted to a proper class, to make your code even more general and easier to re-use. This latter class must derive from the AbstractWorkflow
class: you will implement one in the next
exercise.