Skip to content

Commit 5300501

Browse files
committed
Refactor
1 parent a75fff3 commit 5300501

File tree

5 files changed

+385
-25
lines changed

5 files changed

+385
-25
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import semantic_benchmark
2+
import utils
3+
4+
def main():
5+
benchmark_file = "/Users/mahdi/Documents/GitHub/NFDI4IngModelValidationPlatform/examples/linear-elastic-plate-with-hole/benchmark.json"
6+
simulation_result_path = "/Users/mahdi/Documents/GitHub/NFDI4IngModelValidationPlatform/examples/linear-elastic-plate-with-hole/fenics/results"
7+
benchmark_object = semantic_benchmark.BenchmarkLoader(benchmark_file).load()
8+
9+
for step in benchmark_object.processing_steps:
10+
print(f"Processing Step: {step.label}")
11+
for config in step.configurations:
12+
print(f" Configuration: {config.label}")
13+
for part in config.parts:
14+
if isinstance(part, semantic_benchmark.NumericalParameter):
15+
print(f" Numerical Parameter: {part.label} = {part.numerical_value} {part.unit}")
16+
elif isinstance(part, semantic_benchmark.TextParameter):
17+
print(f" Text Parameter: {part.label} = {part.string_value}")
18+
19+
for tool in step.employed_tools:
20+
print(f" Employed Tool: {tool.label}")
21+
22+
utils.create_main_ro(simulation_result_path, benchmark_object)
23+
24+
if __name__ == "__main__":
25+
main()
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
from __future__ import annotations
2+
3+
import pathlib
4+
from dataclasses import dataclass, field
5+
from typing import Optional, Union
6+
7+
from rdflib import Graph, Namespace, URIRef, Literal, RDF, RDFS
8+
9+
10+
# ---------------------------------------------------------------------------
11+
# Namespace declarations
12+
# ---------------------------------------------------------------------------
13+
14+
M4I = Namespace("http://w3id.org/nfdi4ing/metadata4ing#")
15+
MATHMOD = Namespace("https://mardi4nfdi.de/mathmoddb#")
16+
OBO = Namespace("http://purl.obolibrary.org/obo/")
17+
18+
HAS_NUMERICAL_VALUE = M4I.hasNumericalValue
19+
HAS_STRING_VALUE = M4I.hasStringValue
20+
HAS_UNIT = M4I.hasUnit
21+
HAS_KIND_OF_QTY = M4I.hasKindOfQuantity
22+
HAS_PART = OBO.BFO_0000051
23+
USES_CONFIG = M4I.usesConfiguration
24+
HAS_EMPLOYED_TOOL = M4I.hasEmployedTool
25+
INVESTIGATES = M4I.investigates
26+
EVALUATES = M4I.evaluates
27+
USES = URIRef("https://mardi4nfdi.de/mathmoddb#uses")
28+
DESCRIBED_BY = URIRef("https://mardi4nfdi.de/mathmoddb#describedAsDocumentedBy")
29+
30+
T_BENCHMARK = M4I.Benchmark
31+
T_PARAMETER_SET = M4I.ParameterSet
32+
T_NUMERICAL_VARIABLE = M4I.NumericalVariable
33+
T_PROCESSING_STEP = M4I.ProcessingStep
34+
35+
36+
# ---------------------------------------------------------------------------
37+
# Domain Classes
38+
# ---------------------------------------------------------------------------
39+
40+
@dataclass
41+
class KGNode:
42+
id: str
43+
label: Optional[str] = None
44+
45+
46+
@dataclass
47+
class ResearchProblem(KGNode):
48+
pass
49+
50+
51+
@dataclass
52+
class MathematicalModel(KGNode):
53+
pass
54+
55+
56+
@dataclass
57+
class Publication(KGNode):
58+
pass
59+
60+
61+
@dataclass
62+
class NumericalVariable(KGNode):
63+
unit: Optional[str] = None
64+
quantity_kind: Optional[str] = None
65+
66+
67+
@dataclass
68+
class NumericalParameter(KGNode):
69+
numerical_value: Optional[float] = None
70+
unit: Optional[str] = None
71+
72+
73+
@dataclass
74+
class TextParameter(KGNode):
75+
string_value: Optional[str] = None
76+
77+
78+
ParameterEntry = Union[NumericalParameter, TextParameter, NumericalVariable]
79+
80+
81+
@dataclass
82+
class ParameterSet(KGNode):
83+
label: Optional[str] = None
84+
parts: list[ParameterEntry] = field(default_factory=list)
85+
86+
87+
@dataclass
88+
class Tool(KGNode):
89+
pass
90+
91+
92+
@dataclass
93+
class ProcessingStep(KGNode):
94+
configurations: list[ParameterSet] = field(default_factory=list)
95+
employed_tools: list[Tool] = field(default_factory=list)
96+
97+
98+
@dataclass
99+
class BenchmarkSemantic(KGNode):
100+
investigates: Optional[ResearchProblem] = None
101+
uses: Optional[MathematicalModel] = None
102+
evaluates: list[NumericalVariable] = field(default_factory=list)
103+
parameter_sets: list[ParameterSet] = field(default_factory=list)
104+
described_by: Optional[Publication] = None
105+
processing_steps: list[ProcessingStep] = field(default_factory=list)
106+
107+
108+
# ---------------------------------------------------------------------------
109+
# Loader Class (NEW)
110+
# ---------------------------------------------------------------------------
111+
112+
class BenchmarkLoader:
113+
def __init__(self, jsonld_path: str | pathlib.Path):
114+
self.path = pathlib.Path(jsonld_path)
115+
116+
if not self.path.exists():
117+
raise FileNotFoundError(f"File not found: {self.path}")
118+
119+
self.graph = Graph()
120+
self.graph.parse(str(self.path), format="json-ld")
121+
122+
for s,p,o in self.graph:
123+
print(s, p, o)
124+
125+
def _str(self, uri: URIRef) -> str:
126+
return str(uri)
127+
128+
def _label(self, subject: URIRef) -> Optional[str]:
129+
# print(f"Getting label for {subject}")
130+
val = self.graph.value(subject, RDFS.label)
131+
return str(val) if val else None
132+
133+
def _scalar(self, subject: URIRef, predicate: URIRef):
134+
val = self.graph.value(subject, predicate)
135+
if val is None:
136+
return None
137+
return val.toPython() if isinstance(val, Literal) else str(val)
138+
139+
def build_numerical_parameter(self, uri: URIRef) -> NumericalParameter:
140+
return NumericalParameter(
141+
id=self._str(uri),
142+
label=self._label(uri),
143+
numerical_value=self._scalar(uri, HAS_NUMERICAL_VALUE),
144+
unit=self._scalar(uri, HAS_UNIT),
145+
)
146+
147+
def build_text_parameter(self, uri: URIRef) -> TextParameter:
148+
return TextParameter(
149+
id=self._str(uri),
150+
label=self._label(uri),
151+
string_value=self._scalar(uri, HAS_STRING_VALUE),
152+
)
153+
154+
def build_numerical_variable(self, uri: URIRef) -> NumericalVariable:
155+
return NumericalVariable(
156+
id=self._str(uri),
157+
label=self._label(uri),
158+
unit=self._scalar(uri, HAS_UNIT),
159+
quantity_kind=self._scalar(uri, HAS_KIND_OF_QTY),
160+
)
161+
162+
def build_parameter_entry(self, uri: URIRef) -> ParameterEntry:
163+
if self.graph.value(uri, HAS_STRING_VALUE):
164+
return self.build_text_parameter(uri)
165+
if (uri, RDF.type, T_NUMERICAL_VARIABLE) in self.graph:
166+
return self.build_numerical_variable(uri)
167+
return self.build_numerical_parameter(uri)
168+
169+
def build_parameter_set(self, uri: URIRef) -> ParameterSet:
170+
label = self._label(uri)
171+
parts = [
172+
self.build_parameter_entry(part)
173+
for part in self.graph.objects(uri, HAS_PART)
174+
]
175+
return ParameterSet(id=self._str(uri), label=label, parts=parts)
176+
177+
def build_tool(self, uri: URIRef) -> Tool:
178+
return Tool(id=self._str(uri), label=self._label(uri))
179+
180+
def build_processing_step(self, uri: URIRef) -> ProcessingStep:
181+
configs = [
182+
self.build_parameter_set(c)
183+
for c in self.graph.objects(uri, USES_CONFIG)
184+
]
185+
tools = [
186+
self.build_tool(t)
187+
for t in self.graph.objects(uri, HAS_EMPLOYED_TOOL)
188+
]
189+
return ProcessingStep(
190+
id=self._str(uri),
191+
label=self._label(uri),
192+
configurations=configs,
193+
employed_tools=tools,
194+
)
195+
196+
def load(self) -> BenchmarkSemantic:
197+
g = self.graph
198+
199+
bm_uri = next(g.subjects(RDF.type, T_BENCHMARK), None)
200+
if bm_uri is None:
201+
raise ValueError("No m4i:Benchmark node found.")
202+
203+
rp_uri = g.value(bm_uri, INVESTIGATES)
204+
mm_uri = g.value(bm_uri, USES)
205+
pub_uri = g.value(bm_uri, DESCRIBED_BY)
206+
207+
research_problem = (
208+
ResearchProblem(id=self._str(rp_uri), label=self._label(rp_uri))
209+
if rp_uri else None
210+
)
211+
212+
math_model = (
213+
MathematicalModel(id=self._str(mm_uri), label=self._label(mm_uri))
214+
if mm_uri else None
215+
)
216+
217+
publication = (
218+
Publication(id=self._str(pub_uri), label=self._label(pub_uri))
219+
if pub_uri else None
220+
)
221+
222+
metrics = [
223+
self.build_numerical_variable(m)
224+
for m in g.objects(bm_uri, EVALUATES)
225+
]
226+
227+
param_sets = [
228+
self.build_parameter_set(ps)
229+
for ps in g.objects(bm_uri, M4I.hasParameterSet)
230+
]
231+
232+
steps = [
233+
self.build_processing_step(s)
234+
for s in g.subjects(RDF.type, T_PROCESSING_STEP)
235+
]
236+
237+
return BenchmarkSemantic(
238+
id=self._str(bm_uri),
239+
label=self._label(bm_uri),
240+
investigates=research_problem,
241+
uses=math_model,
242+
evaluates=metrics,
243+
parameter_sets=param_sets,
244+
described_by=publication,
245+
processing_steps=steps,
246+
)

benchmarks/common/utils.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from pathlib import Path
2+
from rocrate.rocrate import ROCrate
3+
import semantic_benchmark
4+
import uuid
5+
6+
def create_main_ro(path: str, benchmark_object: semantic_benchmark.BenchmarkSemantic):
7+
crate = ROCrate()
8+
input_path = Path(path)
9+
10+
if not input_path.is_dir():
11+
raise NotADirectoryError(f"{path} is not a valid directory")
12+
13+
subCrates = []
14+
15+
for subfolder in sorted(input_path.iterdir()):
16+
if subfolder.is_dir():
17+
subCrates.extend(sorted(subfolder.glob("SubCrate.zip")))
18+
19+
if not subCrates:
20+
raise ValueError("No .zip files found inside subfolders of the specified directory")
21+
22+
for subCrate in subCrates:
23+
crate.add_file(
24+
source=str(subCrate),
25+
dest_path=str(subCrate.relative_to(input_path)),
26+
properties={}
27+
)
28+
29+
object_ids = []
30+
31+
for subfolder in sorted(input_path.iterdir()):
32+
if subfolder.is_dir():
33+
obj_id = f"#{uuid.uuid4()}"
34+
crate.add_jsonld({
35+
"@id": obj_id,
36+
"@type": "PropertyValue",
37+
"name": subfolder.name
38+
})
39+
object_ids.append({"@id": obj_id})
40+
41+
crate.add_jsonld({
42+
"@id": f"#{uuid.uuid4()}",
43+
"@type": "CreateAction",
44+
"name": "Simulation Run",
45+
"object": object_ids,
46+
})
47+
48+
crate.write_zip(f"RO.zip")
49+
50+
def merge_all_rocrates_as_subcrates(
51+
input_folder: str,
52+
output_zip: str,
53+
parent_name: str = "Merged RO-Crate",
54+
parent_description: str = "This RO-Crate contains multiple nested RO-Crates as subcrates."
55+
):
56+
"""
57+
Merge all zipped RO-Crates in a folder into a single RO-Crate
58+
using the Subcrate mechanism (OPTION A).
59+
60+
Parameters:
61+
input_folder (str): Folder containing .zip RO-Crates
62+
output_zip (str): Output merged RO-Crate zip file
63+
parent_name (str): Name of the merged parent crate
64+
parent_description (str): Description of the merged parent crate
65+
"""
66+
67+
input_path = Path(input_folder)
68+
69+
if not input_path.is_dir():
70+
raise NotADirectoryError(f"{input_folder} is not a valid directory")
71+
72+
# Create new parent crate
73+
merged = ROCrate()
74+
merged.name = parent_name
75+
merged.description = parent_description
76+
77+
zip_files = sorted(input_path.glob("*.zip"))
78+
79+
if not zip_files:
80+
raise ValueError("No .zip files found in the specified folder")
81+
82+
for zip_path in zip_files:
83+
# Use zip filename (without extension) as subcrate folder name
84+
subcrate_name = zip_path.stem
85+
86+
print(f"Adding subcrate: {zip_path.name}{subcrate_name}/")
87+
88+
merged.add_subcrate(
89+
source=str(zip_path),
90+
dest_path=subcrate_name
91+
)
92+
93+
# Write final merged crate
94+
merged.write_zip(output_zip)
95+
96+
print(f"Merged RO-Crate written to: {output_zip}")
97+
print(f"Total subcrates added: {len(zip_files)}")
98+
99+
100+
# Example usage
101+
if __name__ == "__main__":
102+
merge_all_rocrates_as_subcrates(
103+
input_folder="/Users/mahdi/Downloads/RoCrates",
104+
output_zip="/Users/mahdi/Downloads/RoCrates/Merged.zip"
105+
)
106+

examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@
5858
with open(file, "r") as f:
5959
data = json.load(f)
6060
if data.get("element-size").get("value") >= 0.025:
61-
linear_elastic_problem.generate_workflow(file.name, data.get("configuration"))
62-
linear_elastic_problem.run_workflow()
63-
61+
configuration = data.get("configuration")
62+
linear_elastic_problem.generate_workflow(file.name, configuration)
63+
linear_elastic_problem.run_workflow()

0 commit comments

Comments
 (0)