Source code for pyiron_base.jobs.flex.executablecontainer

from typing import Optional

import cloudpickle
import numpy as np

from pyiron_base.jobs.job.runfunction import (
    CalculateFunctionCaller,
    write_input_files_from_input_dict,
)
from pyiron_base.jobs.job.template import TemplateJob


[docs] class ExecutableContainerJob(TemplateJob): """ The ExecutableContainerJob is designed to wrap any kind of external executable into a pyiron job object by providing a write_input(input_dict, working_directory) and a collect_output(working_directory) function. Example: >>> import os >>> >>> def write_input(input_dict, working_directory="."): >>> with open(os.path.join(working_directory, "input_file"), "w") as f: >>> f.write(str(input_dict["energy"])) >>> >>> >>> def collect_output(working_directory="."): >>> with open(os.path.join(working_directory, "output_file"), "r") as f: >>> return {"energy": float(f.readline())} >>> >>> >>> from pyiron_base import Project >>> pr = Project("test") >>> pr.create_job_class( >>> class_name="CatJob", >>> write_input_funct=write_input, >>> collect_output_funct=collect_output, >>> default_input_dict={"energy": 1.0}, >>> executable_str="cat input_file > output_file", >>> ) >>> job = pr.create.job.CatJob(job_name="job_test") >>> job.input["energy"] = 2.0 >>> job.run() >>> print(job.output) DataContainer({'energy': 2.0}) """
[docs] def __init__(self, project, job_name): super().__init__(project, job_name) self._write_input_funct = None # Set job_with_calculate_function flag to true to use run_static() to execute the python function generated by # the job with its arguments job.get_calculate_function(**job.calculate_kwargs) without calling the old # interface with write_input() and collect_output(). Finally, the output dictionary is stored in the HDF5 file # using self.save_output(output_dict, shell_output) self._job_with_calculate_function = True
@property def calculate_kwargs(self) -> dict: """ Generate keyword arguments for the calculate() function. Example: >>> calculate_function = job.get_calculate_function() >>> shell_output, parsed_output, job_crashed = calculate_function(**job.calculate_kwargs) >>> job.save_output(output_dict=parsed_output, shell_output=shell_output) Returns: dict: keyword arguments for the calculate() function """ kwargs = super().calculate_kwargs kwargs.update( { "input_parameter_dict": self.input.to_builtin(), "executable_script": self.executable.executable_path, "shell_parameter": True, } ) return kwargs
[docs] def set_job_type( self, executable_str: str, write_input_funct: callable = None, collect_output_funct: callable = None, default_input_dict: dict = None, ): """ Set the pre-defined write_input() and collect_output() function plus a dictionary of default inputs and an executable string. Args: executable_str (str): Call to an external executable write_input_funct (callable): The write input function write_input(input_dict, working_directory) collect_output_funct (callable): The collect output function collect_output(working_directory) default_input_dict (dict/None): Default input for the newly created job class Returns: callable: Function which requires a project and a job_name as input and returns a job object """ self.executable = executable_str if write_input_funct is not None: self._write_input_funct = write_input_funct if collect_output_funct is not None: self._collect_output_funct = collect_output_funct if default_input_dict is not None: self.input.update(default_input_dict)
[docs] def get_calculate_function(self) -> callable: """ Generate calculate() function Example: >>> calculate_function = job.get_calculate_function() >>> shell_output, parsed_output, job_crashed = calculate_function(**job.calculate_kwargs) >>> job.save_output(output_dict=parsed_output, shell_output=shell_output) Returns: callable: calculate() functione """ def get_combined_write_input_funct( input_job_dict: dict, write_input_funct: Optional[callable] = None ): def write_input_combo_funct(working_directory: str, input_dict: dict): write_input_files_from_input_dict( input_dict=input_job_dict, working_directory=working_directory, ) if write_input_funct is not None: write_input_funct( working_directory=working_directory, input_dict=input_dict, ) return write_input_combo_funct return CalculateFunctionCaller( write_input_funct=get_combined_write_input_funct( input_job_dict=self.get_input_parameter_dict(), write_input_funct=self._write_input_funct, ), collect_output_funct=self._collect_output_funct, )
def _to_dict(self) -> dict: """ Convert the job object to a dictionary representation. Returns: dict: A dictionary representation of the job object. """ job_dict = super()._to_dict() if self._write_input_funct is not None: job_dict["write_input_function"] = np.void( cloudpickle.dumps(self._write_input_funct) ) if self._collect_output_funct is not None: job_dict["collect_output_function"] = np.void( cloudpickle.dumps(self._collect_output_funct) ) return job_dict def _from_dict(self, obj_dict: dict, version: str = None): """ Load the job attributes from a dictionary representation. Args: obj_dict (dict): A dictionary containing the job attributes. version (str): The version of the job object. """ super()._from_dict(obj_dict=obj_dict) if "write_input_function" in obj_dict.keys(): self._write_input_funct = cloudpickle.loads( obj_dict["write_input_function"] ) if "write_input_function" in obj_dict.keys(): self._collect_output_funct = cloudpickle.loads( obj_dict["collect_output_function"] )