diff --git a/.gitignore b/.gitignore index 00ffd67..fb27c23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ #custom rdf_structure_store/ *.json +*.ttl # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/atomrdf/graph.py b/atomrdf/graph.py index 3f82e14..0d4f617 100644 --- a/atomrdf/graph.py +++ b/atomrdf/graph.py @@ -44,6 +44,7 @@ import atomrdf.properties as prp from atomrdf.stores import create_store import atomrdf.json_io as json_io +from atomrdf.workflow.workflow import Workflow from atomrdf.namespace import Namespace, CMSO, PLDO, PODO, ASMO @@ -212,6 +213,7 @@ def __init__( self.store = store self._n_triples = 0 self._initialize_graph() + self.workflow = Workflow(self) def add_structure(self, structure): """ @@ -697,6 +699,7 @@ def visualise( rankdir="BT", hide_types=False, workflow_view=False, + sample_view=False, size=None, layout="neato", ): @@ -713,6 +716,8 @@ def visualise( Whether to hide the types in the visualization. Default is False. workflow_view : bool, optional Whether to enable the workflow view. Default is False. + sample_view : bool, optional + Whether to enable the sample view. Default is False. size : tuple, optional The size of the visualization. Default is None. layout : str, optional @@ -765,6 +770,7 @@ def visualise( rankdir=rankdir, hide_types=hide_types, workflow_view=workflow_view, + sample_view=sample_view, size=size, layout=layout, ) @@ -1178,3 +1184,13 @@ def to_file(self, sample, filename=None, format="poscar"): else: asesys = sys.write.ase() write(filename, asesys, format=format) + + def enable_workflow(self, workflow_object, workflow_environment=None, workflow_module=None): + self.workflow.inform_graph(workflow_object, + workflow_environment=workflow_environment, + workflow_module=workflow_module) + + def add_workflow(self, job, workflow_environment=None, workflow_module=None, job_dict=None): + self.workflow.to_graph(job, workflow_environment=workflow_environment, + workflow_module=workflow_module, + job_dict=job_dict) diff --git a/atomrdf/stores.py b/atomrdf/stores.py index 4066e95..96b1732 100644 --- a/atomrdf/stores.py +++ b/atomrdf/stores.py @@ -4,9 +4,6 @@ import os -# special methods; for supporting workflow envs -from atomrdf.workflow import inform_graph - def create_store(kg, store, identifier, store_file=None, structure_store=None): """ @@ -32,7 +29,7 @@ def create_store(kg, store, identifier, store_file=None, structure_store=None): """ kg.store_file = store_file - if store == "Memory": + if store in ["Memory", "memory"]: store_memory( kg, store, @@ -40,7 +37,7 @@ def create_store(kg, store, identifier, store_file=None, structure_store=None): store_file=store_file, structure_store=structure_store, ) - elif store == "SQLAlchemy": + elif store in ["SQLAlchemy", "db", "database", "sqlalchemy"]: store_alchemy( kg, store, @@ -48,14 +45,6 @@ def create_store(kg, store, identifier, store_file=None, structure_store=None): store_file=store_file, structure_store=structure_store, ) - elif type(store).__name__ == "Project": - store_pyiron( - kg, - store, - identifier, - store_file=store_file, - structure_store=structure_store, - ) else: raise ValueError("Unknown store found!") @@ -121,37 +110,6 @@ def store_alchemy(kg, store, identifier, store_file=None, structure_store=None): kg.graph.open(uri, create=True) kg.structure_store = _setup_structure_store(structure_store=structure_store) - -def store_pyiron(kg, store, identifier, store_file=None, structure_store=None): - """ - Store the pyiron knowledge graph in a database. - - Parameters - ---------- - kg : pyiron.atomistics.structure.pyiron_atomistics.structure.AtomisticStructure - The pyiron knowledge graph to be stored. - store : pyiron.atomistics.structure.pyiron_atomistics.structure.AtomisticStructure - The store object where the knowledge graph will be stored. - identifier : str - The identifier for the knowledge graph. - store_file : str, optional - The path to the store file. If not provided, a default path will be used. - structure_store : str, optional - The path to the structure store. If not provided, a default path will be used. - - Returns - ------- - None - - """ - structure_store = os.path.join(store.path, "rdf_structure_store") - kg.structure_store = _setup_structure_store(structure_store=structure_store) - store_file = os.path.join(store.path, f"{store.name}.db") - store_alchemy(kg, store, identifier, store_file, structure_store=structure_store) - # finally update project object - inform_graph(store, kg) - - def _check_if_sqlalchemy_is_available(): try: import sqlalchemy as sa diff --git a/atomrdf/visualize.py b/atomrdf/visualize.py index cd95cbb..1e72240 100644 --- a/atomrdf/visualize.py +++ b/atomrdf/visualize.py @@ -102,6 +102,7 @@ def visualize_graph( rankdir="TB", hide_types=False, workflow_view=False, + sample_view=False, size=None, layout="dot", ): @@ -120,6 +121,8 @@ def visualize_graph( Whether to hide nodes with the "type" attribute. Default is False. workflow_view : bool, optional Whether to enable the workflow view. Default is False. + sample_view : bool, optional + Whether to enable the sample view. Default is False. size : str, optional The size of the graph. Default is None. layout : str, optional @@ -173,6 +176,12 @@ def visualize_graph( fontname=styledict[istype1]["fontname"], ) plot = False + + elif sample_view: + green_list = ['wasDerivedFrom', 'wasGeneratedBy'] + if string3 not in green_list: + plot = False + if hide_types and (string3 == "type"): plot = False diff --git a/atomrdf/workflow/__init__.py b/atomrdf/workflow/__init__.py index 83c8c9d..d3f5a12 100644 --- a/atomrdf/workflow/__init__.py +++ b/atomrdf/workflow/__init__.py @@ -1 +1 @@ -from atomrdf.workflow.pyiron import inform_graph + diff --git a/atomrdf/workflow/pyiron.py b/atomrdf/workflow/pyiron.py index a5ad871..2e66f63 100644 --- a/atomrdf/workflow/pyiron.py +++ b/atomrdf/workflow/pyiron.py @@ -13,16 +13,147 @@ from atomrdf.structure import System -def _check_if_job_is_valid(job): +def process_job(job): + """ + Checkes if the job is valid and creates the necessary output dict + for the job. + + Parameters + ---------- + job : pyiron.Job + The pyiron job object to check. + + Raises + ------ + TypeError + If the job is not a valid pyiron job. + """ valid_jobs = [ "Lammps", ] - if not type(job).__name__ in valid_jobs: + if type(job).__name__ == 'Lammps': + return process_lammps_job(job) + else: raise TypeError("These type of pyiron Job is not currently supported") + + + +def inform_graph(pr, kg): + """ + this function in general can be used to do extra methods to set up things as needed + for the workflow environment. + + For example, for pyiron, this updates the project object to have the graph and creator objects + """ + + try: + from pyiron_base import Creator, PyironFactory + from pyiron_atomistics.atomistics.structure.atoms import ( + ase_to_pyiron, + pyiron_to_ase, + ) + import pyiron_atomistics.atomistics.structure.factory as sf + except ImportError: + raise ImportError("Please install pyiron_base and pyiron_atomistics") + + class AnnotatedStructureFactory: + def __init__(self, graph): + self._graph = graph + + def bulk( + self, + element, + repetitions=None, + crystalstructure=None, + a=None, + covera=None, + cubic=True, + graph=None, + ): + + if crystalstructure is None: + crystalstructure = element_dict[element]["structure"] + if a is None: + a = element_dict[element]["lattice_constant"] + + struct = _make_crystal( + crystalstructure, + repetitions=repetitions, + lattice_constant=a, + ca_ratio=covera, + element=element, + primitive=not cubic, + graph=self._graph, + ) + + ase_structure = struct.write.ase() + pyiron_structure = ase_to_pyiron(ase_structure) + pyiron_structure.info["sample_id"] = struct.sample + return pyiron_structure + + def grain_boundary( + self, + element, + axis, + sigma, + gb_plane, + repetitions=(1, 1, 1), + crystalstructure=None, + a=1, + overlap=0.0, + graph=None, + ): + + struct = self._graph._annotated_make_grain_boundary( + axis, + sigma, + gb_plane, + structure=crystalstructure, + element=element, + lattice_constant=a, + repetitions=repetitions, + overlap=overlap, + graph=self._graph, + ) + + ase_structure = struct.write.ase() + pyiron_structure = ase_to_pyiron(ase_structure) + pyiron_structure.info["sample_id"] = struct.sample + return pyiron_structure + + class StructureFactory(sf.StructureFactory): + def __init__(self, graph): + super().__init__() + self._annotated_structure = AnnotatedStructureFactory(graph) + + @property + def annotated_structure(self): + return self._annotated_structure + + class StructureCreator(Creator): + def __init__(self, project): + super().__init__(project) + self._structure = StructureFactory(project.graph) + + @property + def structure(self): + return self._structure + + pr.graph = kg + pr._creator = StructureCreator(pr) +def process_lammps_job(job): + structure_dict = get_structures(job) + method_dict = lammps_identify_method(job) + output_dict = lammps_extract_calculated_quantities(job) -def _add_structures(job): + method_dict['structure'] = structure_dict['structure'] + method_dict['sample'] = structure_dict['sample'] + method_dict['outputs'] = output_dict + return method_dict + +def get_structures(job): initial_pyiron_structure = job.structure final_pyiron_structure = job.get_structure(frame=-1) initial_pyscal_structure = System.read.ase(initial_pyiron_structure) @@ -34,10 +165,15 @@ def _add_structures(job): final_pyscal_structure = System.read.ase(final_pyiron_structure) # now we do rthe transfer - return initial_pyscal_structure, initial_sample_id, final_pyscal_structure, None + return {'structure': + {'initial': initial_pyscal_structure, + 'final': final_pyscal_structure,}, + 'sample': + {'initial':initial_sample_id, + 'final': None}} -def _identify_method(job): +def lammps_identify_method(job): job_dict = job.input.to_dict() input_dict = { job_dict["control_inp/data_dict"]["Parameter"][x]: job_dict[ @@ -127,12 +263,9 @@ def _identify_method(job): } mdict["software"] = [software] - # finally add calculated quantities - quantdict = extract_calculated_quantities(job) - mdict["outputs"] = quantdict return mdict -def extract_calculated_quantities(job): +def lammps_extract_calculated_quantities(job): """ Extracts calculated quantities from a job. @@ -169,103 +302,4 @@ def extract_calculated_quantities(job): return outputs -def inform_graph(pr, kg): - """ - Update project to add extra creator functions - """ - - try: - from pyiron_base import Creator, PyironFactory - from pyiron_atomistics.atomistics.structure.atoms import ( - ase_to_pyiron, - pyiron_to_ase, - ) - import pyiron_atomistics.atomistics.structure.factory as sf - except ImportError: - raise ImportError("Please install pyiron_base and pyiron_atomistics") - - class AnnotatedStructureFactory: - def __init__(self, graph): - self._graph = graph - - def bulk( - self, - element, - repetitions=None, - crystalstructure=None, - a=None, - covera=None, - cubic=True, - graph=None, - ): - - if crystalstructure is None: - crystalstructure = element_dict[element]["structure"] - if a is None: - a = element_dict[element]["lattice_constant"] - - struct = _make_crystal( - crystalstructure, - repetitions=repetitions, - lattice_constant=a, - ca_ratio=covera, - element=element, - primitive=not cubic, - graph=self._graph, - ) - ase_structure = struct.write.ase() - pyiron_structure = ase_to_pyiron(ase_structure) - pyiron_structure.info["sample_id"] = struct.sample - return pyiron_structure - - def grain_boundary( - self, - element, - axis, - sigma, - gb_plane, - repetitions=(1, 1, 1), - crystalstructure=None, - a=1, - overlap=0.0, - graph=None, - ): - - struct = self._graph._annotated_make_grain_boundary( - axis, - sigma, - gb_plane, - structure=crystalstructure, - element=element, - lattice_constant=a, - repetitions=repetitions, - overlap=overlap, - graph=self._graph, - ) - - ase_structure = struct.write.ase() - pyiron_structure = ase_to_pyiron(ase_structure) - pyiron_structure.info["sample_id"] = struct.sample - return pyiron_structure - - class StructureFactory(sf.StructureFactory): - def __init__(self, graph): - super().__init__() - self._annotated_structure = AnnotatedStructureFactory(graph) - - @property - def annotated_structure(self): - return self._annotated_structure - - class StructureCreator(Creator): - def __init__(self, project): - super().__init__(project) - self._structure = StructureFactory(project.graph) - - @property - def structure(self): - return self._structure - - pr.graph = kg - pr._creator = StructureCreator(pr) diff --git a/atomrdf/workflow/workflow.py b/atomrdf/workflow/workflow.py index 4229bf8..88934a2 100644 --- a/atomrdf/workflow/workflow.py +++ b/atomrdf/workflow/workflow.py @@ -22,18 +22,15 @@ import copy import ast import uuid +import importlib from atomrdf.structure import System # Move imports to another file from atomrdf.namespace import PROV, CMSO, PODO, ASMO -# custom imports as needed -import atomrdf.workflow.pyiron as pi - - class Workflow: - def __init__(self, kg, environment="pyiron"): + def __init__(self, kg): """ Initialize the workflow environment @@ -45,51 +42,99 @@ def __init__(self, kg, environment="pyiron"): """ self.kg = kg - if environment == "pyiron": - self.wenv = pi - else: - raise ValueError("unknown workflow environment") - def _prepare_job(self, workflow_object): - - self.wenv._check_if_job_is_valid(workflow_object) - parent_structure, parent_sample, structure, sample = self.wenv._add_structures( - workflow_object - ) - method_dict = self.wenv._identify_method(workflow_object) - - if (structure is None) and (sample is None): + def inform_graph(self, pr, workflow_environment=None, workflow_module=None): + if workflow_environment is not None: + workflow_module = importlib.import_module(f"atomrdf.workflow.{workflow_environment}") + + if workflow_module is not None: + workflow_module.inform_graph(pr, self.kg) + + + def to_graph(self, job, workflow_environment=None, workflow_module=None, job_dict=None): + + if workflow_environment is not None: + workflow_module = importlib.import_module(f"atomrdf.workflow.{workflow_environment}") + job_dict = workflow_module.process_job(job) + elif workflow_module is not None: + job_dict = workflow_module.process_job(job) + + if job_dict is None: + raise ValueError("Job dict could not be calculated!") + + #print(job_dict) + #now we call the functions in order + job_dict = self._add_structure(job_dict) + self._add_structural_relation(job_dict) + self._add_method(job_dict) + + + def _add_structure(self, job_dict): + #ensure these are not strings + if isinstance(job_dict['sample']['initial'], str): + job_dict['sample']['initial'] = URIRef(job_dict['sample']['initial']) + if isinstance(job_dict['sample']['final'], str): + job_dict['sample']['final'] = URIRef(job_dict['sample']['final']) + + if (job_dict['structure']['final'] is None) and (job_dict['sample']['final'] is None): raise ValueError("Either structure or sample should be specified") - if sample is None: + if job_dict['sample']['final'] is None: # its not added to graph yet + structure = job_dict['structure']['final'] structure.graph = self.kg structure.to_graph() - sample = structure.sample + job_dict['sample']['final'] = structure.sample - if parent_sample is None: + if job_dict['sample']['initial'] is None: # its not added to graph yet + parent_sample = job_dict['structure']['initial'] if parent_structure is not None: parent_structure.graph = self.kg parent_structure.to_graph() - parent_sample = parent_structure.sample + job_dict['sample']['initial'] = parent_structure.sample + return job_dict + + def _add_structural_relation( + self, job_dict, + ): + """ + Add structural relation between samples. + + This method adds the structural relation between the current sample and its parent sample. + It also retrieves lattice properties and adds inherited properties, such as defect information. + + Parameters + ---------- + None + + Returns + ------- + None + """ + parent_sample = job_dict['sample']['initial'] + sample = job_dict['sample']['final'] - self.structure = structure - self.sample = sample - self.mdict = method_dict - self.main_id = method_dict["id"] - self.parent_sample = parent_sample + self.kg.add((sample, RDF.type, PROV.Entity)) + + if parent_sample is not None: + self.kg.add((parent_sample, RDF.type, PROV.Entity)) + self.kg.add((sample, PROV.wasDerivedFrom, parent_sample)) + self._get_lattice_properties(job_dict) + self._add_inherited_properties(job_dict) def _get_lattice_properties( - self, + self, job_dict, ): - if self.parent_sample is None: + if job_dict['sample']['final'] is None: return + parent_sample = job_dict['sample']['initial'] + material = list( [ k[2] - for k in self.kg.triples((self.parent_sample, CMSO.hasMaterial, None)) + for k in self.kg.triples((parent_sample, CMSO.hasMaterial, None)) ] )[0] crystal_structure = self.kg.value(material, CMSO.hasStructure) @@ -125,20 +170,24 @@ def _get_lattice_properties( [lattice_angle_x, lattice_angle_y, lattice_angle_z], ] - self.structure._add_crystal_structure(targets=targets) - + job_dict['structure']['final']._add_crystal_structure(targets=targets) + + def _add_inherited_properties( - self, + self, job_dict, ): # Here we need to add inherited info: CalculatedProperties will be lost # Defects will be inherited - if self.parent_sample is None: + if job_dict['sample']['final'] is None: return + parent_sample = job_dict['sample']['initial'] + sample = job_dict['sample']['final'] + parent_material = list( [ k[2] - for k in self.kg.triples((self.parent_sample, CMSO.hasMaterial, None)) + for k in self.kg.triples((parent_sample, CMSO.hasMaterial, None)) ] )[0] parent_defects = list( @@ -146,7 +195,7 @@ def _add_inherited_properties( ) # now for each defect we copy add this to the final sample material = list( - [k[2] for k in self.kg.triples((self.sample, CMSO.hasMaterial, None))] + [k[2] for k in self.kg.triples((sample, CMSO.hasMaterial, None))] )[0] for defect in parent_defects: @@ -157,8 +206,8 @@ def _add_inherited_properties( self.kg.add((new_defect, triple[1], triple[2])) # now add the special props for vacancy - parent_simcell = self.kg.value(self.sample, CMSO.hasSimulationCell) - simcell = self.kg.value(self.parent_sample, CMSO.hasSimulationCell) + parent_simcell = self.kg.value(sample, CMSO.hasSimulationCell) + simcell = self.kg.value(parent_sample, CMSO.hasSimulationCell) for triple in self.kg.triples( (parent_simcell, PODO.hasVacancyConcentration, None) @@ -169,32 +218,10 @@ def _add_inherited_properties( ): self.kg.add((simcell, triple[1], triple[2])) - def add_structural_relation( - self, - ): - """ - Add structural relation between samples. - - This method adds the structural relation between the current sample and its parent sample. - It also retrieves lattice properties and adds inherited properties, such as defect information. - Parameters - ---------- - None - Returns - ------- - None - """ - self.kg.add((self.sample, RDF.type, PROV.Entity)) - if self.parent_sample is not None: - self.kg.add((self.parent_sample, RDF.type, PROV.Entity)) - self.kg.add((self.sample, PROV.wasDerivedFrom, self.parent_sample)) - self._get_lattice_properties() - self._add_inherited_properties() - - def add_method( - self, + def _add_method( + self, job_dict, ): """ Add the computational method and related information to the knowledge graph. @@ -217,28 +244,28 @@ def add_method( The structure generation information is also added to the graph. """ - if self.mdict is None: - return # add activity # ---------------------------------------------------------- - activity = URIRef(f"activity_{self.main_id}") + main_id = job_dict['id'] + activity = URIRef(f"activity_{main_id}") self.kg.add((activity, RDF.type, PROV.Activity)) # add method # ---------------------------------------------------------- - method = URIRef(f"method_{self.main_id}") - if self.mdict["method"] == "MolecularStatics": - self.kg.add((method, RDF.type, ASMO.MolecularStatics)) - elif self.mdict["method"] == "MolecularDynamics": + method = URIRef(f"method_{main_id}") + if job_dict["method"] == "MolecularStatics": + #TODO: Replace with ASMO.MolecularStatics self.kg.add((method, RDF.type, ASMO.MolecularDynamics)) - elif self.mdict["method"] == "DensityFunctionalTheory": + elif job_dict["method"] == "MolecularDynamics": + self.kg.add((method, RDF.type, ASMO.MolecularDynamics)) + elif job_dict["method"] == "DensityFunctionalTheory": self.kg.add((method, RDF.type, ASMO.DensityFunctionalTheory)) self.kg.add((activity, ASMO.hasComputationalMethod, method)) # choose if its rigid energy or structure optimisation # ---------------------------------------------------------- - if len(self.mdict["dof"]) == 0: + if len(job_dict["dof"]) == 0: self.kg.add( ( activity, @@ -251,45 +278,32 @@ def add_method( else: self.kg.add((activity, RDF.type, ASMO.StructureOptimization)) # add DOFs - for dof in self.mdict["dof"]: + for dof in job_dict["dof"]: self.kg.add((activity, ASMO.hasRelaxationDOF, getattr(ASMO, dof))) # add method specific items - if self.mdict["method"] in ["MolecularStatics", "MolecularDynamics"]: - self._add_md(method, activity) - elif self.mdict["method"] in ["DensityFunctionalTheory"]: - self._add_dft(method, activity) + if job_dict["method"] in ["MolecularStatics", "MolecularDynamics"]: + self._add_md(job_dict, method, activity) + elif job_dict["method"] in ["DensityFunctionalTheory"]: + self._add_dft(job_dict, method, activity) # add that structure was generated - self.kg.add((self.sample, PROV.wasGeneratedBy, activity)) - self._add_inputs(activity) - self._add_outputs(activity) - self._add_software(method) - - def to_graph(self, workflow_object): - """ - Converts a workflow object to a graph representation. - - Parameters: - - workflow_object: The workflow object to convert. - - Returns: - - None - """ - self._prepare_job(workflow_object) - self.add_structural_relation() - self.add_method() - - def _add_outputs(self, activity): - if "outputs" in self.mdict.keys(): - for out in self.mdict["outputs"]: + self.kg.add((job_dict['sample']['final'], PROV.wasGeneratedBy, activity)) + self._add_inputs(job_dict, activity) + self._add_outputs(job_dict, activity) + self._add_software(job_dict, method) + + def _add_inputs(self, job_dict, activity): + main_id = job_dict['id'] + if "inputs" in job_dict.keys(): + for inp in job_dict["inputs"]: prop = self.kg.create_node( - f'{self.main_id}_{out["label"]}', CMSO.CalculatedProperty + f'{main_id}_{inp["label"]}', ASMO.InputParameter ) - self.kg.add((prop, RDFS.label, Literal(out["label"]))) - self.kg.add((prop, ASMO.hasValue, Literal(out["value"]))) - if "unit" in out.keys(): - unit = out["unit"] + self.kg.add((prop, RDFS.label, Literal(inp["label"]))) + self.kg.add((prop, ASMO.hasValue, Literal(inp["value"]))) + if "unit" in inp.keys(): + unit = inp["unit"] self.kg.add( ( prop, @@ -297,20 +311,19 @@ def _add_outputs(self, activity): URIRef(f"http://qudt.org/vocab/unit/{unit}"), ) ) - self.kg.add((prop, ASMO.wasCalculatedBy, activity)) - if out["associate_to_sample"]: - self.kg.add((self.sample, CMSO.hasCalculatedProperty, prop)) + self.kg.add((activity, ASMO.hasInputParameter, prop)) - def _add_inputs(self, activity): - if "inputs" in self.mdict.keys(): - for inp in self.mdict["inputs"]: + def _add_outputs(self, job_dict, activity): + main_id = job_dict['id'] + if "outputs" in job_dict.keys(): + for out in job_dict["outputs"]: prop = self.kg.create_node( - f'{self.main_id}_{inp["label"]}', ASMO.InputParameter + f'{main_id}_{out["label"]}', CMSO.CalculatedProperty ) - self.kg.add((prop, RDFS.label, Literal(inp["label"]))) - self.kg.add((prop, ASMO.hasValue, Literal(inp["value"]))) - if "unit" in inp.keys(): - unit = inp["unit"] + self.kg.add((prop, RDFS.label, Literal(out["label"]))) + self.kg.add((prop, ASMO.hasValue, Literal(out["value"]))) + if "unit" in out.keys(): + unit = out["unit"] self.kg.add( ( prop, @@ -318,21 +331,23 @@ def _add_inputs(self, activity): URIRef(f"http://qudt.org/vocab/unit/{unit}"), ) ) - self.kg.add((activity, ASMO.hasInputParameter, prop)) + self.kg.add((prop, ASMO.wasCalculatedBy, activity)) + if out["associate_to_sample"]: + self.kg.add((job_dict['sample']['final'], CMSO.hasCalculatedProperty, prop)) - def _add_software(self, method): + def _add_software(self, job_dict, method): # finally add software wfagent = None - if "workflow_manager" in self.mdict.keys(): + if "workflow_manager" in job_dict.keys(): wfagent = self.kg.create_node( - self.mdict["workflow_manager"]["uri"], PROV.SoftwareAgent + job_dict["workflow_manager"]["uri"], PROV.SoftwareAgent ) self.kg.add( - (wfagent, RDFS.label, Literal(self.mdict["workflow_manager"]["label"])) + (wfagent, RDFS.label, Literal(job_dict["workflow_manager"]["label"])) ) self.kg.add((method, PROV.wasAssociatedWith, wfagent)) - for software in self.mdict["software"]: + for software in job_dict["software"]: agent = self.kg.create_node(software["uri"], PROV.SoftwareAgent) self.kg.add((agent, RDFS.label, Literal(software["label"]))) if wfagent is not None: @@ -340,15 +355,17 @@ def _add_software(self, method): else: self.kg.add((method, PROV.wasAssociatedWith, agent)) - def _add_md(self, method, activity): - self.kg.add( - (method, ASMO.hasStatisticalEnsemble, getattr(ASMO, self.mdict["ensemble"])) - ) + def _add_md(self, job_dict, method, activity): + main_id = job_dict['id'] + if job_dict["ensemble"] is not None: + self.kg.add( + (method, ASMO.hasStatisticalEnsemble, getattr(ASMO, job_dict["ensemble"])) + ) # add temperature if needed - if self.mdict["temperature"] is not None: + if job_dict["temperature"] is not None: temperature = self.kg.create_node( - f"temperature_{self.main_id}", ASMO.InputParameter + f"temperature_{main_id}", ASMO.InputParameter ) self.kg.add( (temperature, RDFS.label, Literal("temperature", datatype=XSD.string)) @@ -358,16 +375,16 @@ def _add_md(self, method, activity): ( temperature, ASMO.hasValue, - Literal(self.mdict["temperature"], datatype=XSD.float), + Literal(job_dict["temperature"], datatype=XSD.float), ) ) self.kg.add( (temperature, ASMO.hasUnit, URIRef("http://qudt.org/vocab/unit/K")) ) - if self.mdict["pressure"] is not None: + if job_dict["pressure"] is not None: pressure = self.kg.create_node( - f"pressure_{self.main_id}", ASMO.InputParameter + f"pressure_{main_id}", ASMO.InputParameter ) self.kg.add( (pressure, RDFS.label, Literal("pressure", datatype=XSD.string)) @@ -377,7 +394,7 @@ def _add_md(self, method, activity): ( pressure, ASMO.hasValue, - Literal(self.mdict["pressure"], datatype=XSD.float), + Literal(job_dict["pressure"], datatype=XSD.float), ) ) self.kg.add( @@ -385,29 +402,29 @@ def _add_md(self, method, activity): ) # potentials need to be mapped - potential = URIRef(f"potential_{self.main_id}") - if "meam" in self.mdict["potential"]["type"]: + potential = URIRef(f"potential_{main_id}") + if "meam" in job_dict["potential"]["type"]: self.kg.add((potential, RDF.type, ASMO.ModifiedEmbeddedAtomModel)) - elif "eam" in self.mdict["potential"]["type"]: + elif "eam" in job_dict["potential"]["type"]: self.kg.add((potential, RDF.type, ASMO.EmbeddedAtomModel)) - elif "lj" in self.mdict["potential"]["type"]: + elif "lj" in job_dict["potential"]["type"]: self.kg.add((potential, RDF.type, ASMO.LennardJonesPotential)) - elif "ace" in self.mdict["potential"]["type"]: + elif "ace" in job_dict["potential"]["type"]: self.kg.add((potential, RDF.type, ASMO.MachineLearningPotential)) else: self.kg.add((potential, RDF.type, ASMO.InteratomicPotential)) - if "uri" in self.mdict["potential"].keys(): + if "uri" in job_dict["potential"].keys(): self.kg.add( ( potential, CMSO.hasReference, - Literal(self.mdict["potential"]["uri"], datatype=XSD.string), + Literal(job_dict["potential"]["uri"], datatype=XSD.string), ) ) - if "label" in self.mdict["potential"].keys(): + if "label" in job_dict["potential"].keys(): self.kg.add( - (potential, RDFS.label, Literal(self.mdict["potential"]["label"])) + (potential, RDFS.label, Literal(job_dict["potential"]["label"])) ) self.kg.add((method, ASMO.hasInteratomicPotential, potential)) diff --git a/examples/06_workflow_pyiron.ipynb b/examples/06_workflow_pyiron.ipynb index e774ba4..e029bd6 100644 --- a/examples/06_workflow_pyiron.ipynb +++ b/examples/06_workflow_pyiron.ipynb @@ -34,20 +34,10 @@ "scrolled": true }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/menon/miniconda3/envs/workflow-rdf-v0.2/lib/python3.11/site-packages/numpy/core/getlimits.py:542: UserWarning: Signature b'\\x00\\xd0\\xcc\\xcc\\xcc\\xcc\\xcc\\xcc\\xfb\\xbf\\x00\\x00\\x00\\x00\\x00\\x00' for does not match any known type: falling back to type probe function.\n", - "This warnings indicates broken support for the dtype!\n", - " machar = _get_machar(dtype)\n", - "2024-04-18 12:54:21,059 - pyiron_log - WARNING - pyiron found a 'templates' folder in the /home/menon/pyiron/resources resource directory. These are no longer supported in pyiron_base >=0.7.0. They are replaced by Project.create_job_class() and Project.wrap_python_function().\n" - ] - }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b3e1540e94144972bc50c9b3fdce538f", + "model_id": "1bc7f7716890468ab750205a2cf67a45", "version_major": 2, "version_minor": 0 }, @@ -70,7 +60,7 @@ "metadata": {}, "outputs": [], "source": [ - "pr = Project('wf1a')" + "pr = Project('wf1c')" ] }, { @@ -80,7 +70,7 @@ "metadata": {}, "outputs": [], "source": [ - "kg = KnowledgeGraph(store=pr)" + "kg = KnowledgeGraph(store='db', store_file='kg_db.db')" ] }, { @@ -90,7 +80,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf = Workflow(kg, environment='pyiron')" + "kg.enable_workflow(pr, workflow_environment='pyiron')" ] }, { @@ -124,17 +114,17 @@ " viewBox=\"0.00 0.00 232.50 44.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n", "\n", "\n", - "\n", + "\n", "\n", - "sample_d6ce3789-c330-4dc9-be4c-ad2aa3fee411\n", + "sample_d8e7688b-117c-4851-bbf9-00f2d0373a69\n", "\n", - "sample_d6ce3789-c330-4dc9-be4c-ad2aa3fee411\n", + "sample_d8e7688b-117c-4851-bbf9-00f2d0373a69\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -196,7 +186,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job j1 was saved and received the ID: 8176\n" + "The job j1 was saved and received the ID: 52\n" ] } ], @@ -211,7 +201,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf.to_graph(job)" + "kg.add_workflow(job, workflow_environment='pyiron')" ] }, { @@ -231,78 +221,78 @@ "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "sample_d6ce3789-c330-4dc9-be4c-ad2aa3fee411\n", - "\n", - "sample_d6ce3789-c330-4dc9-be4c-ad2aa3fee411\n", + "sample_d8e7688b-117c-4851-bbf9-00f2d0373a69\n", + "\n", + "sample_d8e7688b-117c-4851-bbf9-00f2d0373a69\n", "\n", - "\n", + "\n", "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7\n", - "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693\n", + "\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693\n", "\n", - "\n", + "\n", "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7->sample_d6ce3789-c330-4dc9-be4c-ad2aa3fee411\n", - "\n", - "\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693->sample_d8e7688b-117c-4851-bbf9-00f2d0373a69\n", + "\n", + "\n", "wasDerivedFrom\n", "\n", - "\n", + "\n", "\n", - "activity_8176\n", - "\n", - "activity_8176\n", + "activity_52\n", + "\n", + "activity_52\n", "\n", - "\n", + "\n", "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7->activity_8176\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693->activity_52\n", "\n", "\n", "wasGeneratedBy\n", "\n", - "\n", + "\n", "\n", - "8176_TotalEnergy\n", - "\n", - "8176_TotalEnergy\n", + "52_TotalEnergy\n", + "\n", + "52_TotalEnergy\n", "\n", - "\n", + "\n", "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7->8176_TotalEnergy\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693->52_TotalEnergy\n", "\n", "\n", "cmso.hasCalculatedProperty\n", "\n", - "\n", + "\n", "\n", - "8176_TotalVolume\n", - "\n", - "8176_TotalVolume\n", + "52_TotalVolume\n", + "\n", + "52_TotalVolume\n", "\n", - "\n", + "\n", "\n", - "sample_7c113ec5-bf1f-4c2c-b863-b7bb5b9091b7->8176_TotalVolume\n", - "\n", - "\n", + "sample_6a085219-8a90-44c6-aec8-e46cb6f0e693->52_TotalVolume\n", + "\n", + "\n", "cmso.hasCalculatedProperty\n", "\n", - "\n", + "\n", "\n", - "method_8176\n", - "\n", - "method_8176\n", + "method_52\n", + "\n", + "method_52\n", "\n", - "\n", + "\n", "\n", - "activity_8176->method_8176\n", - "\n", + "activity_52->method_52\n", + "\n", "\n", "asmo.hasComputationalMethod\n", "\n", @@ -312,10 +302,10 @@ "\n", "asmo.AtomicPosition\n", "\n", - "\n", + "\n", "\n", - "activity_8176->asmo.AtomicPosition\n", - "\n", + "activity_52->asmo.AtomicPosition\n", + "\n", "\n", "asmo.hasRelaxationDOF\n", "\n", @@ -325,38 +315,38 @@ "\n", "asmo.CellVolume\n", "\n", - "\n", + "\n", "\n", - "activity_8176->asmo.CellVolume\n", + "activity_52->asmo.CellVolume\n", "\n", "\n", "asmo.hasRelaxationDOF\n", "\n", - "\n", + "\n", "\n", - "temperature_8176\n", - "\n", - "temperature_8176\n", + "temperature_52\n", + "\n", + "temperature_52\n", "\n", - "\n", + "\n", "\n", - "activity_8176->temperature_8176\n", - "\n", - "\n", + "activity_52->temperature_52\n", + "\n", + "\n", "asmo.hasInputParameter\n", "\n", - "\n", + "\n", "\n", - "pressure_8176\n", - "\n", - "pressure_8176\n", + "pressure_52\n", + "\n", + "pressure_52\n", "\n", - "\n", + "\n", "\n", - "activity_8176->pressure_8176\n", - "\n", - "\n", - "asmo.hasInputParameter\n", + "activity_52->pressure_52\n", + "\n", + "\n", + "asmo.hasInputParameter\n", "\n", "\n", "\n", @@ -364,135 +354,135 @@ "\n", "asmo.Isothermal–isobaricEnsemble\n", "\n", - "\n", + "\n", "\n", - "method_8176->asmo.Isothermal–isobaricEnsemble\n", - "\n", - "\n", - "asmo.hasStatisticalEnsemble\n", + "method_52->asmo.Isothermal–isobaricEnsemble\n", + "\n", + "\n", + "asmo.hasStatisticalEnsemble\n", "\n", - "\n", + "\n", "\n", - "potential_8176\n", - "\n", - "potential_8176\n", + "potential_52\n", + "\n", + "potential_52\n", "\n", - "\n", + "\n", "\n", - "method_8176->potential_8176\n", - "\n", - "\n", - "asmo.hasInteratomicPotential\n", + "method_52->potential_52\n", + "\n", + "\n", + "asmo.hasInteratomicPotential\n", "\n", "\n", "\n", "matwerk.E457491\n", - "\n", - "matwerk.E457491\n", + "\n", + "matwerk.E457491\n", "\n", - "\n", + "\n", "\n", - "method_8176->matwerk.E457491\n", - "\n", - "\n", - "wasAssociatedWith\n", + "method_52->matwerk.E457491\n", + "\n", + "\n", + "wasAssociatedWith\n", "\n", "\n", "\n", "unit.K\n", - "\n", - "unit.K\n", + "\n", + "unit.K\n", "\n", - "\n", + "\n", "\n", - "temperature_8176->unit.K\n", - "\n", - "\n", - "asmo.hasUnit\n", + "temperature_52->unit.K\n", + "\n", + "\n", + "asmo.hasUnit\n", "\n", - "\n", + "\n", "\n", - "c28634b6-575e-4a85-a1a5-6d75bc798da8\n", - "\n", - "Temperature\n", + "2f52cdc1-de9d-4ece-ba7b-9f0490dcc4b3\n", + "\n", + "Temperature\n", "\n", - "\n", + "\n", "\n", - "temperature_8176->c28634b6-575e-4a85-a1a5-6d75bc798da8\n", - "\n", - "\n", - "label\n", + "temperature_52->2f52cdc1-de9d-4ece-ba7b-9f0490dcc4b3\n", + "\n", + "\n", + "label\n", "\n", - "\n", + "\n", "\n", - "28fd3ec9-1c42-4914-a782-37b30dab9227\n", - "\n", - "500.0\n", + "0763a038-ba3b-430f-9236-9124f66ba771\n", + "\n", + "500.0\n", "\n", - "\n", + "\n", "\n", - "temperature_8176->28fd3ec9-1c42-4914-a782-37b30dab9227\n", - "\n", - "\n", - "asmo.hasValue\n", + "temperature_52->0763a038-ba3b-430f-9236-9124f66ba771\n", + "\n", + "\n", + "asmo.hasValue\n", "\n", "\n", "\n", "unit.GigaPA\n", - "\n", - "unit.GigaPA\n", + "\n", + "unit.GigaPA\n", "\n", - "\n", + "\n", "\n", - "pressure_8176->unit.GigaPA\n", - "\n", - "\n", - "asmo.hasUnit\n", + "pressure_52->unit.GigaPA\n", + "\n", + "\n", + "asmo.hasUnit\n", "\n", - "\n", + "\n", "\n", - "20658bc9-06a9-4003-9c13-509e346d5f41\n", - "\n", - "Pressure\n", + "12e3e26c-6458-4747-8c25-9b26a00b7ae2\n", + "\n", + "Pressure\n", "\n", - "\n", + "\n", "\n", - "pressure_8176->20658bc9-06a9-4003-9c13-509e346d5f41\n", - "\n", - "\n", - "label\n", + "pressure_52->12e3e26c-6458-4747-8c25-9b26a00b7ae2\n", + "\n", + "\n", + "label\n", "\n", - "\n", + "\n", "\n", - "0042105c-fd16-4778-aeeb-6408b322a1b6\n", - "\n", - "0.0\n", + "5cba3b81-e74c-447d-95e8-3a54cd5e4b32\n", + "\n", + "0.0\n", "\n", - "\n", + "\n", "\n", - "pressure_8176->0042105c-fd16-4778-aeeb-6408b322a1b6\n", - "\n", - "\n", - "asmo.hasValue\n", + "pressure_52->5cba3b81-e74c-447d-95e8-3a54cd5e4b32\n", + "\n", + "\n", + "asmo.hasValue\n", "\n", - "\n", + "\n", "\n", - "5a0e55bc-afeb-4d22-b5e1-72b8692a6e6c\n", - "\n", - "2001--Mishin-Y--Cu-1--Lammps--Ipr1\n", + "5b2bd4f0-76ce-402a-a8d5-e07f70a21c98\n", + "\n", + "2001--Mishin-Y--Cu-1--Lammps--Ipr1\n", "\n", - "\n", + "\n", "\n", - "potential_8176->5a0e55bc-afeb-4d22-b5e1-72b8692a6e6c\n", - "\n", - "\n", - "label\n", + "potential_52->5b2bd4f0-76ce-402a-a8d5-e07f70a21c98\n", + "\n", + "\n", + "label\n", "\n", - "\n", + "\n", "\n", - "8176_TotalEnergy->activity_8176\n", - "\n", - "\n", + "52_TotalEnergy->activity_52\n", + "\n", + "\n", "asmo.wasCalculatedBy\n", "\n", "\n", @@ -501,43 +491,43 @@ "\n", "unit.EV\n", "\n", - "\n", + "\n", "\n", - "8176_TotalEnergy->unit.EV\n", - "\n", - "\n", + "52_TotalEnergy->unit.EV\n", + "\n", + "\n", "asmo.hasUnit\n", "\n", - "\n", + "\n", "\n", - "a919bb29-412b-4b7a-8a9e-67c280ab5cbe\n", + "188b1a42-43c8-4a00-9ba7-c7a0714c16a0\n", "\n", "Totalenergy\n", "\n", - "\n", + "\n", "\n", - "8176_TotalEnergy->a919bb29-412b-4b7a-8a9e-67c280ab5cbe\n", + "52_TotalEnergy->188b1a42-43c8-4a00-9ba7-c7a0714c16a0\n", "\n", "\n", "label\n", "\n", - "\n", + "\n", "\n", - "30de06a7-9ca9-4b53-b42a-65230b0aa37d\n", + "187b0c07-140e-43dc-98ae-f5cdb96ead29\n", "\n", "-13.7346\n", "\n", - "\n", + "\n", "\n", - "8176_TotalEnergy->30de06a7-9ca9-4b53-b42a-65230b0aa37d\n", + "52_TotalEnergy->187b0c07-140e-43dc-98ae-f5cdb96ead29\n", "\n", "\n", "asmo.hasValue\n", "\n", - "\n", + "\n", "\n", - "8176_TotalVolume->activity_8176\n", - "\n", + "52_TotalVolume->activity_52\n", + "\n", "\n", "asmo.wasCalculatedBy\n", "\n", @@ -547,83 +537,83 @@ "\n", "unit.ANGSTROM3\n", "\n", - "\n", + "\n", "\n", - "8176_TotalVolume->unit.ANGSTROM3\n", + "52_TotalVolume->unit.ANGSTROM3\n", "\n", "\n", "asmo.hasUnit\n", "\n", - "\n", + "\n", "\n", - "64a38d93-403d-471a-91a1-c52bfb00ef77\n", + "e1402a80-e0dd-407d-9ad6-832d28fe46df\n", "\n", "Totalvolume\n", "\n", - "\n", + "\n", "\n", - "8176_TotalVolume->64a38d93-403d-471a-91a1-c52bfb00ef77\n", + "52_TotalVolume->e1402a80-e0dd-407d-9ad6-832d28fe46df\n", "\n", "\n", "label\n", "\n", - "\n", + "\n", "\n", - "0358a436-7878-4e47-86de-053abfece585\n", + "86ee43ac-61a6-4a7a-ab99-0bb7b9fec751\n", "\n", "48.2558\n", "\n", - "\n", + "\n", "\n", - "8176_TotalVolume->0358a436-7878-4e47-86de-053abfece585\n", - "\n", - "\n", + "52_TotalVolume->86ee43ac-61a6-4a7a-ab99-0bb7b9fec751\n", + "\n", + "\n", "asmo.hasValue\n", "\n", "\n", "\n", "matwerk.E447986\n", - "\n", - "matwerk.E447986\n", + "\n", + "matwerk.E447986\n", "\n", "\n", "\n", "matwerk.E457491->matwerk.E447986\n", - "\n", - "\n", - "actedOnBehalfOf\n", + "\n", + "\n", + "actedOnBehalfOf\n", "\n", - "\n", + "\n", "\n", - "2785804e-e4e8-415d-860b-62fa830b5dfa\n", - "\n", - "Pyiron\n", + "dc0c28b4-24d4-4df7-98fc-ba818b5cdc2b\n", + "\n", + "Pyiron\n", "\n", - "\n", + "\n", "\n", - "matwerk.E457491->2785804e-e4e8-415d-860b-62fa830b5dfa\n", - "\n", - "\n", - "label\n", + "matwerk.E457491->dc0c28b4-24d4-4df7-98fc-ba818b5cdc2b\n", + "\n", + "\n", + "label\n", "\n", - "\n", + "\n", "\n", - "3b64e836-85ed-4e47-b3dd-8bea5e000434\n", - "\n", - "Lammps\n", + "a85302aa-be2f-4731-b70a-b8a50af24200\n", + "\n", + "Lammps\n", "\n", - "\n", + "\n", "\n", - "matwerk.E447986->3b64e836-85ed-4e47-b3dd-8bea5e000434\n", - "\n", - "\n", - "label\n", + "matwerk.E447986->a85302aa-be2f-4731-b70a-b8a50af24200\n", + "\n", + "\n", + "label\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 14, diff --git a/tests/test_workflow.py b/tests/test_workflow.py index c0d68bc..3929212 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -6,51 +6,6 @@ import shutil def test_wf_creation(): - s = KnowledgeGraph() - wf = Workflow(s) + kg = KnowledgeGraph() + #kg.enable_workflow(pr, workflow_environment='pyiron') -def test_lattice_props(): - s = KnowledgeGraph() - wf = Workflow(s) - parent_sys = System.create.element.Cr(graph=s) - #add some defects - parent_sys.delete(indices=[0]) - child_sys = System.create.element.Cr(graph=s) - #trick workflow and add info - wf.parent_sample = parent_sys.sample - wf.structure = child_sys - wf.sample = child_sys.sample - wf.add_structural_relation() - - -def test_add_method(): - for pot in ['meam', 'eam', 'lj', 'ace', 'nequip']: - s = KnowledgeGraph() - wf = Workflow(s) - parent_sys = System.create.element.Cr(graph=s) - #add some defects - parent_sys.delete(indices=[0]) - child_sys = System.create.element.Cr(graph=s) - #trick workflow and add info - wf.parent_sample = parent_sys.sample - wf.structure = child_sys - wf.sample = child_sys.sample - wf.add_structural_relation() - wf.main_id = 2314 - wf.mdict = {"method": "MolecularDynamics", - "temperature": 100, - "pressure": 0, - "dof": ["AtomicPosition", "CellVolume"], - "ensemble": "IsothermalisobaricEnsemble", - "id": 2314, - "potential": {"uri": "https://doi.org/xxx", - "type": pot, - "label": "string" }, - "workflow_manager": {"uri": "xxxx", - "label": "pyiron"}, - "software": [ {"uri": "xxxx", "label": "lammps"},], - "outputs": [{"label": "TotalEnergy", "value": 2.301, "unit": "EV", - "associate_to_sample": True}], - "inputs": [ {"label": "AnotherInput", "value": 0.1, "unit": None },] - } - wf.add_method()