Git Product home page Git Product logo

Comments (6)

jkitchin avatar jkitchin commented on June 11, 2024 2

I submitted a pull request. I listed @liamhuber as an author in the notebook since most of the EMTJob code is from the discussion above/

from pyiron.

liamhuber avatar liamhuber commented on June 11, 2024

Hi @jkitchin, welcome to pyiron!

First: I had never thought of just slapping an ASE calculator onto a structure and making it a job -- that's absolutely brilliant. Pyiron team, I think we can make a whole family of jobs very quickly with this!

What you're trying to do is absolutely feasible and your example is not too far off. Murnhaghan is expecting some things your job doesn't have, and our "run" cycle is poorly documented and a bit more complex than overriding run. Below is a working example that (I believe) accomplishes what you want, and I've tried to explain why I make each of the changes relative to your example code. It's also the case that Murn is not up-to-date with our latest storage practices, so I show a bit how I'd like to do it, as well as how it actually needs to be done right now.

from pyiron_base import PythonTemplateJob
from pyiron_atomistics import Project, Atoms
# Our Atoms inherit from ASE.Atoms, so you can use it directly
from ase.calculators.emt import EMT

class EMTJob(PythonTemplateJob):
    def __init__(self, project, job_name):
        super(EMTJob, self).__init__(project, job_name)
        # The template job comes with `input` and `output` attributes that will  
        # handle all the (de)serialization for you
        self.input.atoms = pr.atomistics.structure.bulk('Cu')  # Or whatever default
        # self.output.energy = None  # Let's just populate the field for now
        
    @property
    def structure(self):
        """
        'structure' is the canonical name for an `Atoms` object in pyiron.
        The Murnhaghan job is expecting(?) a `structure` attribute on its input jobs.
        """
        return self.input.atoms
    
    @structure.setter
    def structure(self, structure: Atoms):
        if isinstance(structure, Atoms):
            self.input.atoms = structure
        else:
            raise TypeError(f"Expected an Atoms object but got {type(structure)}")
        
    def run_static(self):
        """
        `run` is actually already a method higher up the job inheritence tree,
        it does things like let the project know the job exists -- this is why you 
        weren't seeing the job in `job_table`.
        What we actually want to define is `run_static`.
        ...This definitely needs to be better documented!!! (Actually, the whole 
        run paradigm needs some work to make it simpler and clearer, but documentation
        would be a good enough start!)
        """
        print("RUNNING")
        self.structure.set_calculator(EMT())
        self.output.energy = self.structure.get_potential_energy()
        # With the output attribute, all we need to do is tell the job to
        # write itself:
        self.to_hdf()
        
        # Buuuuut, the Murnhaghan job doesn't know about this functionality,
        # so we still need to use the old-school way of writing directly to a
        # particular place in the HDF. Note that Murnhaghan looks at "energy_pot"
        # not just "energy". It also expects that value to be a list of energies.
        # Murn is also expecting a (list of) volume(s) stored in a particular place,
        # so we'll write that too
        with self.project_hdf5.open("output/generic") as h5out: 
            h5out["energy_pot"] = [self.output.energy]
            h5out["volume"] = [self.structure.get_volume()]
            
        
        # Ok, now we're really done
        self.status.finished = True
        
pr = Project('emt')
pr.remove_jobs(silently=True, recursive=True)

job = pr.create_job(job_type=EMTJob, job_name="Al_1")
job.structure = pr.atomistics.structure.bulk("Al")
job.run()
print(job.output.energy)

loaded = pr.load("Al_1")
print(loaded.output.energy)
# The job shows up in the job table, and can be loaded later

pr.remove_jobs(silently=True, recursive=True)

job = pr.create_job(job_type=EMTJob, job_name="Cu_1")
job.structure = pr.atomistics.structure.bulk("Cu")

murn = pr.atomistics.job.Murnaghan("murn")
murn.ref_job = job
murn.input["num_points"] = 5  # I just want it faster
murn.run()
murn.plot()  # Looks right for me!

# Lastly I'll wipe everything from storage because this is just
# an example
pr.remove_jobs(silently=True, recursive=True)
pr.remove(enable=True)

from pyiron.

jan-janssen avatar jan-janssen commented on June 11, 2024

First: I had never thought of just slapping an ASE calculator onto a structure and making it a job -- that's absolutely brilliant. Pyiron team, I think we can make a whole family of jobs very quickly with this!

Did not we do this already? For example the GPAW wrapper is implemented in this way.
https://github.com/pyiron/pyiron_atomistics/blob/main/pyiron_atomistics/gpaw/gpaw.py

class Gpaw(AseJob, GenericDFTJob):
    @import_alarm
    def __init__(self, project, job_name):
        super(Gpaw, self).__init__(project, job_name)

        self.input = GpawInput()

    @property
    def plane_wave_cutoff(self):
        return self.input["encut"]

    @plane_wave_cutoff.setter
    def plane_wave_cutoff(self, val):
        self.input["encut"] = val

    def get_kpoints(self):
        return self.input["kpoints"]

    def _set_kpoints(
        self,
        mesh=None,
        scheme="MP",
        center_shift=None,
        symmetry_reduction=True,
        manual_kpoints=None,
        weights=None,
        reciprocal=True,
        n_path=None,
        path_name=None,
    ):
        if scheme != "MP":
            raise ValueError("Currently only MP is supported in the pyiron wrapper.")
        if center_shift is not None:
            raise ValueError("centershift is not implemented in the pyiron wrapper.")
        if not symmetry_reduction:
            raise ValueError(
                "symmetry_reduction is not implemented in the pyiron wrapper."
            )
        if manual_kpoints is not None:
            raise ValueError(
                "manual_kpoints are not implemented in the pyiron wrapper."
            )
        if weights is not None:
            raise ValueError("weights are not implemented in the pyiron wrapper.")
        if not reciprocal:
            raise ValueError("reciprocal is not implemented in the pyiron wrapper.")
        if n_path is not None:
            raise ValueError("n_path is not implemented in the pyiron wrapper.")
        if path_name is not None:
            raise ValueError("path_name is not implemented in the pyiron wrapper.")
        self.input["kpoints"] = mesh

    def set_calculator(self):
        kpoints = self.input["kpoints"]
        if isinstance(kpoints, str):
            kpoints = self.input["kpoints"].replace("[", "").replace("]", "").split()
        self._create_working_directory()
        calc = GPAWcode(
            mode=PW(float(self.input["encut"])),
            xc=self.input["potential"],
            occupations=MethfesselPaxton(width=float(self.input["sigma"])),
            kpts=kpoints,
            txt=self.working_directory + "/" + self.job_name + ".txt",
        )
        self.structure.calc = calc

    def to_hdf(self, hdf=None, group_name=None):
        """
        Store the ExampleJob object in the HDF5 File
        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(Gpaw, self).to_hdf(hdf=hdf, group_name=group_name)
        with self.project_hdf5.open("input") as hdf5_input:
            self.input.to_hdf(hdf5_input)

    def from_hdf(self, hdf=None, group_name=None):
        """
        Restore the ExampleJob object in the HDF5 File
        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(Gpaw, self).from_hdf(hdf=hdf, group_name=group_name)
        with self.project_hdf5.open("input") as hdf5_input:
            self.input.from_hdf(hdf5_input)

@jkitchin There are some functions which we try to unify for all DFT codes, like _set_kpoints() but the important part happens in set_calculator() where we set the parameter saved in the input class to the corresponding ASE calculator. In this example the separate to_hdf() and from_hdf() functions are still necessary to tell pyiron which information to store in the HDF5 file, but we are working on removing the need to define them explicitly, as illustrated by the example @liamhuber posted.

from pyiron.

liamhuber avatar liamhuber commented on June 11, 2024

Did not we do this already? For example the GPAW wrapper is implemented in this way.

Ah nice! I had never looked at the GPAW implementation. Seems like some low hanging fruit for adding a bunch of functionality. Might be well suited to an MPIE hackathon where they get new devs to learn by doing.

from pyiron.

jkitchin avatar jkitchin commented on June 11, 2024

Thanks for the pointers. Would you be interested in adding a notebook example on this in https://github.com/pyiron/pyiron/tree/main/notebooks?

from pyiron.

liamhuber avatar liamhuber commented on June 11, 2024

Thanks for the pointers. Would you be interested in adding a notebook example on this in https://github.com/pyiron/pyiron/tree/main/notebooks?

Yeah, I think that would be a nice addition 👍

from pyiron.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.