From 3d6b2fe1ee049bf7758a7a9d59f7a5a2944241d5 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:48:42 +0200 Subject: [PATCH 1/9] bump fans version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0c40f94..2f5fff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ dependencies = {aiida-fans = "==0.1.5"} # [tool.pixi.feature.aiida] # dependencies = {aiida-core = "2.6.*"} [tool.pixi.feature.fans] -dependencies = {fans = "0.3.*"} +dependencies = {fans = "0.4.*"} [tool.pixi.feature.ruff] dependencies = {ruff = "*"} tasks = {fmt = "ruff check", dummy = "echo dummy", my-dummy="echo my-dummy"} From 8f41c42f91a4372b049bca5392558d707bd2d422 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:49:21 +0200 Subject: [PATCH 2/9] remove pointless entry-points --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2f5fff0..81e1f08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,15 +23,15 @@ dependencies = [ ] # Entry Points -[project.entry-points."aiida.data"] -"fans" = "aiida_fans.data:FANSParameters" +# [project.entry-points."aiida.data"] +# "fans" = "aiida_fans.data:FANSParameters" [project.entry-points."aiida.calculations"] "fans.stashed" = "aiida_fans.calculations:FansStashedCalculation" "fans.fragmented" = "aiida_fans.calculations:FansFragmentedCalculation" [project.entry-points."aiida.parsers"] "fans" = "aiida_fans.parsers:FansParser" -[project.entry-points."aiida.cmdline.data"] -"fans" = "aiida_fans.cli:data_cli" +# [project.entry-points."aiida.cmdline.data"] +# "fans" = "aiida_fans.cli:data_cli" # Build System [build-system] From bd72aa8e918f11240f4c49ee2dee2a6b5033eb58 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:50:09 +0200 Subject: [PATCH 3/9] def input generator tool --- src/aiida_fans/helpers.py | 51 ++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/aiida_fans/helpers.py b/src/aiida_fans/helpers.py index 6ab9fc4..db06016 100644 --- a/src/aiida_fans/helpers.py +++ b/src/aiida_fans/helpers.py @@ -1,30 +1,37 @@ -"""Tools required by aiida_fans.""" +"""Tools required by aiida-fans.""" -import json +from typing import Any -from aiida.orm import ArrayData, Dict, Float, Int, List, SinglefileData, Str +from aiida.engine import CalcJob from numpy import allclose, ndarray -class InputEncoder(json.JSONEncoder): - """Prepares a dictionary of calcjob inputs for json representation.""" - - def default(self, obj): - """Converts aiida datatypes to their python counterparts.""" - match obj: - case Str() | Int() | Float(): - return obj.value - case List(): - return obj.get_list() - case Dict(): - return obj.get_dict() - case ArrayData(): - return [a[1].tolist() for a in obj.get_iterarrays()] #! Caution: may be disordered - case SinglefileData(): - return obj.filename - case _: - # Let the base class default method raise the TypeError - return super().default(obj) +def make_input_dict(job: CalcJob) -> dict[str, Any]: + """Prepares a dictionary that maps to an input.json from calcjob inputs.""" + return { + ## Microstructure Definition + "microstructure": { + "filename": None, # path to stashed microstructure, must be overwritten by impl + "datasetname": job.inputs.microstructure.datasetname.value, + "L": job.inputs.microstructure.L.get_list() + }, + ## Problem Type and Material Model + "problem_type": job.inputs.problem_type.value, + "matmodel": job.inputs.matmodel.value, + "material_properties": job.inputs.material_properties.get_dict(), + ## Solver Settings + "method": job.inputs.method.value, + "n_it": job.inputs.n_it.value, + "error_parameters": { + "measure": job.inputs.error_parameters.measure.value, + "type": job.inputs.error_parameters.type.value, + "tolerance": job.inputs.error_parameters.tolerance.value + }, + ## Macroscale Loading Conditions + "macroscale_loading": job.inputs.macroscale_loading.get_list(), + ## Results Specification + "results": job.inputs.results.get_list() + } def arraydata_equal(first: dict[str, ndarray], second: dict[str, ndarray]) -> bool: """Return whether two dicts of arrays are roughly equal.""" From 72a69dbd39671f93dffdd6a99ab4beb0e8e05592 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:52:12 +0200 Subject: [PATCH 4/9] use input generator tool --- src/aiida_fans/calculations.py | 50 ++++------------------------------ 1 file changed, 6 insertions(+), 44 deletions(-) diff --git a/src/aiida_fans/calculations.py b/src/aiida_fans/calculations.py index 61c64bf..9afef7e 100644 --- a/src/aiida_fans/calculations.py +++ b/src/aiida_fans/calculations.py @@ -11,6 +11,8 @@ from aiida.orm import ArrayData, Dict, Float, Int, List, SinglefileData, Str from h5py import File as h5File +from aiida_fans.helpers import make_input_dict + class FansCalcBase(CalcJob): """Base class of all calculations using FANS.""" @@ -107,28 +109,8 @@ def prepare_for_submission(self, folder: Folder) -> CalcInfo: copyfileobj(source, target) # input.json as dict - input_dict = { - ## Microstructure Definition - "ms_filename": str(ms_filepath), # path to stashed microstructure - "ms_datasetname": self.inputs.microstructure.datasetname.value, - "ms_L": self.inputs.microstructure.L.get_list(), - ## Problem Type and Material Model - "problem_type": self.inputs.problem_type.value, - "matmodel": self.inputs.matmodel.value, - "material_properties": self.inputs.material_properties.get_dict(), - ## Solver Settings - "method": self.inputs.method.value, - "n_it": self.inputs.n_it.value, - "error_parameters": { - "measure": self.inputs.error_parameters.measure.value, - "type": self.inputs.error_parameters.type.value, - "tolerance": self.inputs.error_parameters.tolerance.value - }, - ## Macroscale Loading Conditions - "macroscale_loading": [a[1].tolist() for a in self.inputs.macroscale_loading.get_iterarrays()], - ## Results Specification - "results": self.inputs.results.get_list() - } + input_dict = make_input_dict(self) + input_dict["microstructure"]["filename"] = str(ms_filepath) # write input.json to working directory with folder.open(self.options.input_filename, "w", "utf8") as json: dump(input_dict, json, indent=4) @@ -154,28 +136,8 @@ def prepare_for_submission(self, folder: Folder) -> CalcInfo: h5_src.copy(datasetname, h5_dest, name=datasetname) # input.json as dict - input_dict = { - ## Microstructure Definition - "ms_filename": "microstructure.h5", # path to fragmented microstructure - "ms_datasetname": self.inputs.microstructure.datasetname.value, - "ms_L": self.inputs.microstructure.L.get_list(), - ## Problem Type and Material Model - "problem_type": self.inputs.problem_type.value, - "matmodel": self.inputs.matmodel.value, - "material_properties": self.inputs.material_properties.get_dict(), - ## Solver Settings - "method": self.inputs.method.value, - "n_it": self.inputs.n_it.value, - "error_parameters": { - "measure": self.inputs.error_parameters.measure.value, - "type": self.inputs.error_parameters.type.value, - "tolerance": self.inputs.error_parameters.tolerance.value - }, - ## Macroscale Loading Conditions - "macroscale_loading": [a[1].tolist() for a in self.inputs.macroscale_loading.get_iterarrays()], - ## Results Specification - "results": self.inputs.results.get_list() - } + input_dict = make_input_dict(self) + input_dict["microstructure"]["filename"] = "microstructure.h5" # write input.json to working directory with folder.open(self.options.input_filename, "w", "utf8") as json: dump(input_dict, json, indent=4) From a62892f3176ae09c11962e268ef70f572ec0e355 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:52:34 +0200 Subject: [PATCH 5/9] change macroscale_loading type to List --- src/aiida_fans/calculations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aiida_fans/calculations.py b/src/aiida_fans/calculations.py index 9afef7e..5c84c75 100644 --- a/src/aiida_fans/calculations.py +++ b/src/aiida_fans/calculations.py @@ -24,7 +24,6 @@ def define(cls, spec: CalcJobProcessSpec) -> None: # Metadata spec.inputs["metadata"]["label"].default = "FANS" - # spec.inputs["metadata"]["dry_run"].default = True ## Processing Power spec.inputs["metadata"]["options"]["withmpi"].default = True spec.inputs["metadata"]["options"]["resources"].default = { @@ -55,7 +54,7 @@ def define(cls, spec: CalcJobProcessSpec) -> None: spec.input("error_parameters.type", valid_type=Str) spec.input("error_parameters.tolerance", valid_type=Float) ## Macroscale Loading Conditions - spec.input("macroscale_loading", valid_type=ArrayData) + spec.input("macroscale_loading", valid_type=List) ## Results Specification spec.input("results", valid_type=List) From 7680cb1510890281f0b39268532eb99f7c05e324 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 16:52:54 +0200 Subject: [PATCH 6/9] code cleanup --- src/aiida_fans/utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/aiida_fans/utils.py b/src/aiida_fans/utils.py index f1be6d4..c6c407b 100644 --- a/src/aiida_fans/utils.py +++ b/src/aiida_fans/utils.py @@ -10,7 +10,7 @@ from aiida_fans.helpers import arraydata_equal -def aiida_type(value : Any) -> type[Data]: +def aiida_type(value: Any) -> type[Data]: """Find the corresponding AiiDA datatype for a variable with pythonic type. Args: @@ -39,7 +39,7 @@ def aiida_type(value : Any) -> type[Data]: case _: raise NotImplementedError(f"Received an input of value: {value} with type: {type(value)}") -def fetch(label : str, value : Any) -> list[Node]: +def fetch(label: str, value: Any) -> list[Node]: """Return a list of nodes matching the label and value provided. Args: @@ -61,12 +61,16 @@ def fetch(label : str, value : Any) -> list[Node]: else: array_nodes = [] for array_node in nodes: - array_value = {k:v for k, v in [(name, array_node.get_array(name)) for name in array_node.get_arraynames()]} + array_value = { + k: v for k, v in [ + (name, array_node.get_array(name)) for name in array_node.get_arraynames() # type: ignore + ] + } if arraydata_equal(value, array_value): array_nodes.append(array_node) return array_nodes -def generate(label : str, value : Any) -> Node: +def generate(label: str, value: Any) -> Node: """Return a single node with the label and value provided. Uses an existing node when possible, but otherwise creates one instead. @@ -89,7 +93,7 @@ def generate(label : str, value : Any) -> Node: else: raise RuntimeError -def convert(ins : dict[str, Any], path : list[str] = []): +def convert(ins: dict[str, Any], path: list[str] = []): """Takes a dictionary of inputs and converts the values to their respective Nodes. Args: @@ -104,7 +108,7 @@ def convert(ins : dict[str, Any], path : list[str] = []): else: ins[k] = generate(".".join([*path, k]), v) -def compile_query(ins : dict[str,Any], qb : QueryBuilder) -> None: +def compile_query(ins: dict[str,Any], qb: QueryBuilder) -> None: """Interate over the converted input dictionary and append to the QueryBuilder for each node. Args: From f22a7d158ce0ba1c63a2e3752e1337e51491a9b5 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 18:54:11 +0200 Subject: [PATCH 7/9] filename -> filepath --- src/aiida_fans/calculations.py | 4 ++-- src/aiida_fans/helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aiida_fans/calculations.py b/src/aiida_fans/calculations.py index 5c84c75..68916a3 100644 --- a/src/aiida_fans/calculations.py +++ b/src/aiida_fans/calculations.py @@ -109,7 +109,7 @@ def prepare_for_submission(self, folder: Folder) -> CalcInfo: # input.json as dict input_dict = make_input_dict(self) - input_dict["microstructure"]["filename"] = str(ms_filepath) + input_dict["microstructure"]["filepath"] = str(ms_filepath) # write input.json to working directory with folder.open(self.options.input_filename, "w", "utf8") as json: dump(input_dict, json, indent=4) @@ -136,7 +136,7 @@ def prepare_for_submission(self, folder: Folder) -> CalcInfo: # input.json as dict input_dict = make_input_dict(self) - input_dict["microstructure"]["filename"] = "microstructure.h5" + input_dict["microstructure"]["filepath"] = "microstructure.h5" # write input.json to working directory with folder.open(self.options.input_filename, "w", "utf8") as json: dump(input_dict, json, indent=4) diff --git a/src/aiida_fans/helpers.py b/src/aiida_fans/helpers.py index db06016..57ed5f2 100644 --- a/src/aiida_fans/helpers.py +++ b/src/aiida_fans/helpers.py @@ -11,7 +11,7 @@ def make_input_dict(job: CalcJob) -> dict[str, Any]: return { ## Microstructure Definition "microstructure": { - "filename": None, # path to stashed microstructure, must be overwritten by impl + "filepath": None, # path to stashed microstructure, must be overwritten by impl "datasetname": job.inputs.microstructure.datasetname.value, "L": job.inputs.microstructure.L.get_list() }, From 3d84391c04a954233eeed261cf1024103aa1cf46 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 18:57:10 +0200 Subject: [PATCH 8/9] move results input to metadata.options and add results_prefix input under metadata.options --- src/aiida_fans/calculations.py | 8 +++++--- src/aiida_fans/helpers.py | 3 ++- src/aiida_fans/parsers.py | 6 +++++- src/aiida_fans/utils.py | 4 ++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/aiida_fans/calculations.py b/src/aiida_fans/calculations.py index 68916a3..0e9ea88 100644 --- a/src/aiida_fans/calculations.py +++ b/src/aiida_fans/calculations.py @@ -22,7 +22,7 @@ def define(cls, spec: CalcJobProcessSpec) -> None: """Define inputs, outputs, and exit codes of the calculation.""" super().define(spec) - # Metadata + # Default Metadata spec.inputs["metadata"]["label"].default = "FANS" ## Processing Power spec.inputs["metadata"]["options"]["withmpi"].default = True @@ -36,6 +36,10 @@ def define(cls, spec: CalcJobProcessSpec) -> None: ## Parser spec.inputs["metadata"]["options"]["parser_name"].default = "fans" + # Custom Metadata + spec.input("metadata.options.results_prefix", valid_type=str, default="") + spec.input("metadata.options.results", valid_type=list, default=[]) + # Input Ports ## Microstructure Definition spec.input_namespace("microstructure") @@ -55,8 +59,6 @@ def define(cls, spec: CalcJobProcessSpec) -> None: spec.input("error_parameters.tolerance", valid_type=Float) ## Macroscale Loading Conditions spec.input("macroscale_loading", valid_type=List) - ## Results Specification - spec.input("results", valid_type=List) # Output Ports spec.output("output", valid_type=SinglefileData) diff --git a/src/aiida_fans/helpers.py b/src/aiida_fans/helpers.py index 57ed5f2..f527c54 100644 --- a/src/aiida_fans/helpers.py +++ b/src/aiida_fans/helpers.py @@ -15,6 +15,7 @@ def make_input_dict(job: CalcJob) -> dict[str, Any]: "datasetname": job.inputs.microstructure.datasetname.value, "L": job.inputs.microstructure.L.get_list() }, + "results_prefix": job.inputs.metadata.options.results_prefix, ## Problem Type and Material Model "problem_type": job.inputs.problem_type.value, "matmodel": job.inputs.matmodel.value, @@ -30,7 +31,7 @@ def make_input_dict(job: CalcJob) -> dict[str, Any]: ## Macroscale Loading Conditions "macroscale_loading": job.inputs.macroscale_loading.get_list(), ## Results Specification - "results": job.inputs.results.get_list() + "results": job.inputs.metadata.options.results } def arraydata_equal(first: dict[str, ndarray], second: dict[str, ndarray]) -> bool: diff --git a/src/aiida_fans/parsers.py b/src/aiida_fans/parsers.py index 9d05479..f755ba8 100644 --- a/src/aiida_fans/parsers.py +++ b/src/aiida_fans/parsers.py @@ -26,7 +26,11 @@ def parse(self, **kwargs) -> ExitCode | None: return self.exit_codes.ERROR_MISSING_OUTPUT with h5File(output_path) as h5: - results = h5[self.node.inputs.microstructure.datasetname.value] + results = h5[ + self.node.inputs.microstructure.datasetname.value + \ + "_results/" + \ + self.node.get_option('results_prefix') + ] results.visititems(self.parse_h5) if self.results_dict: diff --git a/src/aiida_fans/utils.py b/src/aiida_fans/utils.py index c6c407b..bbc82c5 100644 --- a/src/aiida_fans/utils.py +++ b/src/aiida_fans/utils.py @@ -179,6 +179,10 @@ def execute_fans( print("ERROR: Calculation strategy must be either 'Fragmented' or 'Stashed'.") raise ValueError + # move results_prefix and results items to metadata.options + inputs.setdefault("metadata", {}).setdefault("options", {})["results_prefix"] = inputs.pop("results_prefix", "") + inputs.setdefault("metadata", {}).setdefault("options", {})["results"] = inputs.pop("results", []) +# # fetch the inputs if possible or otherwise create them convert(inputs) From d3d10d3246f2343cb3ca196af78f46cd963198e6 Mon Sep 17 00:00:00 2001 From: Ethan Shanahan Date: Mon, 26 May 2025 19:02:45 +0200 Subject: [PATCH 9/9] satisfy ruff --- src/aiida_fans/calculations.py | 2 +- src/aiida_fans/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aiida_fans/calculations.py b/src/aiida_fans/calculations.py index 0e9ea88..a53965a 100644 --- a/src/aiida_fans/calculations.py +++ b/src/aiida_fans/calculations.py @@ -8,7 +8,7 @@ from aiida.common.folders import Folder from aiida.engine import CalcJob from aiida.engine.processes.process_spec import CalcJobProcessSpec -from aiida.orm import ArrayData, Dict, Float, Int, List, SinglefileData, Str +from aiida.orm import Dict, Float, Int, List, SinglefileData, Str from h5py import File as h5File from aiida_fans.helpers import make_input_dict diff --git a/src/aiida_fans/utils.py b/src/aiida_fans/utils.py index bbc82c5..6aa478a 100644 --- a/src/aiida_fans/utils.py +++ b/src/aiida_fans/utils.py @@ -182,7 +182,7 @@ def execute_fans( # move results_prefix and results items to metadata.options inputs.setdefault("metadata", {}).setdefault("options", {})["results_prefix"] = inputs.pop("results_prefix", "") inputs.setdefault("metadata", {}).setdefault("options", {})["results"] = inputs.pop("results", []) -# + # fetch the inputs if possible or otherwise create them convert(inputs)