Skip to content

Commit f532484

Browse files
Add example SPARQL queries
Some example SPARQL queries were added that represent querying of relevant information typically obtained from tensile tests. Additionally, a Jupyter notebook is provided to run the queries.
1 parent b9d9577 commit f532484

7 files changed

Lines changed: 389 additions & 0 deletions
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# S355 Tensile Test SPARQL Queries\n",
8+
"\n",
9+
"This Jupyter Notebook provides some examples of SPARQL queries that can be performed to obtain information relevant to tensile testing. \n",
10+
"An [example dataset of tensile tests performed on an S355 steel](https://github.com/materialdigital/tensile-test-ontology/blob/main/tensile_test_data/S355_data_tto.rdf) is used as a basis. In this Jupyter Notebook, a local triple store is created using the OWLready2 Python package. Within this triple store, the respective ontology and the data are loaded and can be queried afterwards.\n",
11+
"Accordingly, necessary and useful libraries are imported and helper functions are implemented.\n",
12+
"The SPARQL queries are read in from especially created files that contain only the SPARQL query body (text of SPARQL query) and can be found in a dedicated [sparql folder](https://github.com/materialdigital/tensile-test-ontology/tree/main/tensile_test_data/sparql).\n",
13+
"\n",
14+
"The queries follow the general pattern of SPARQL queries:\n",
15+
"\n",
16+
"```SPARQL\n",
17+
"PREFIX ex: <https://example.org/my/namespace/>\n",
18+
"\n",
19+
"SELECT ?s ?p ?o\n",
20+
"WHERE {\n",
21+
" ?s ?p ?o\n",
22+
"}\n",
23+
"```"
24+
]
25+
},
26+
{
27+
"cell_type": "markdown",
28+
"metadata": {},
29+
"source": [
30+
"## Import of relevant packages | Definition of helper functions"
31+
]
32+
},
33+
{
34+
"cell_type": "code",
35+
"execution_count": 1,
36+
"metadata": {},
37+
"outputs": [],
38+
"source": [
39+
"%%capture\n",
40+
"# Import relevant and useful packages\n",
41+
"import requests\n",
42+
"from io import BytesIO\n",
43+
"import os\n",
44+
"import numpy as np\n",
45+
"import pandas as pd\n",
46+
"import owlready2 as or2\n",
47+
"from owlready2 import World\n",
48+
"import re\n",
49+
"from tabulate import tabulate\n",
50+
"\n",
51+
"# Definition of helper functions\n",
52+
"# Function to transform inputs to IRIs.\n",
53+
"def to_iri(input):\n",
54+
" try:\n",
55+
" return input.iri\n",
56+
" except:\n",
57+
" pass\n",
58+
" return input\n",
59+
"\n",
60+
"# Function to write the result of a SPARQL query into a (pandas) data frame.\n",
61+
"def sparql_result_to_df(res):\n",
62+
" l = []\n",
63+
" for row in res:\n",
64+
" r = [ to_iri(item) for item in row]\n",
65+
" l.append(r)\n",
66+
" return pd.DataFrame(l)\n",
67+
"\n",
68+
"\n",
69+
"def load_ontologies_to_world(*ontology_urls):\n",
70+
" \"\"\"\n",
71+
" Loads ontologies from the given URLs into an OWLready2 World instance.\n",
72+
" \n",
73+
" Parameters:\n",
74+
" ontology_urls: A variable number of URLs pointing to ontologies.\n",
75+
" \n",
76+
" Returns:\n",
77+
" An OWLready2 World instance containing the loaded ontologies.\n",
78+
" \"\"\"\n",
79+
" # Create a new World instance for loading ontologies\n",
80+
" world = World()\n",
81+
" \n",
82+
" # Iterate over each provided ontology URL\n",
83+
" for url in ontology_urls:\n",
84+
" try:\n",
85+
" # Fetch the ontology content, following redirects\n",
86+
" response = requests.get(url, allow_redirects=True)\n",
87+
" response.raise_for_status() # Check for HTTP errors\n",
88+
"\n",
89+
" # Load the ontology from the response content\n",
90+
" world.get_ontology(url).load(fileobj=BytesIO(response.content))\n",
91+
" \n",
92+
" except requests.exceptions.RequestException as e:\n",
93+
" print(f\"Failed to load ontology from {url}: {e}\")\n",
94+
" \n",
95+
" return world\n",
96+
"\n",
97+
"\n",
98+
"import requests\n",
99+
"\n",
100+
"def load_sparql(query_name: str) -> str:\n",
101+
" \"\"\"\n",
102+
" Loads a SPARQL query file directly from GitHub (raw URL).\n",
103+
" \n",
104+
" Parameters\n",
105+
" ---------\n",
106+
" query_name : str\n",
107+
" Name of the SPARQL file without extension (.sparql).\n",
108+
" \n",
109+
" Return\n",
110+
" --------\n",
111+
" str\n",
112+
" Content of the SPARQL file as a string.\n",
113+
" \"\"\"\n",
114+
" base_url = \"https://raw.githubusercontent.com/materialdigital/tensile-test-ontology/main/tensile_test_data/sparql\"\n",
115+
" url = f\"{base_url}/{query_name}.sparql\"\n",
116+
" \n",
117+
" response = requests.get(url)\n",
118+
" if response.status_code == 200:\n",
119+
" return response.text\n",
120+
" else:\n",
121+
" raise FileNotFoundError(f\"Datei konnte nicht geladen werden: {url} (Status {response.status_code})\")\n"
122+
]
123+
},
124+
{
125+
"cell_type": "markdown",
126+
"metadata": {},
127+
"source": [
128+
"## Definition of Sources\n",
129+
"\n",
130+
"In the following cell, the sources of ontologies to be parsed as well as the source of the A-Box (example dataset of tensile tests performed on an S355 steel) are specified."
131+
]
132+
},
133+
{
134+
"cell_type": "code",
135+
"execution_count": null,
136+
"metadata": {},
137+
"outputs": [],
138+
"source": [
139+
"# Definition of links to ontologies, files, etc. to be loaded in the local triple store\n",
140+
"link_ontology_1 = \"https://w3id.org/pmd/co/\" # PMD Core Ontology (PMDco) as basis for tensile test ontology\n",
141+
"link_ontology_2 = \"https://w3id.org/pmd/ao/tto/\" # Tensile Test Ontology (TTO)\n",
142+
"link_data = \"https://raw.githubusercontent.com/materialdigital/tensile-test-ontology/refs/heads/main/tensile_test_data/S355_data_tto.rdf\" # Example data on S355 steel\n",
143+
"\n",
144+
"# Loading ontologies and data files (A-Box) in the local triple store\n",
145+
"triple_store = load_ontologies_to_world(link_ontology_1, link_ontology_2)\n",
146+
"triple_store.get_ontology(link_data).load()"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"metadata": {},
152+
"source": [
153+
"## SPARQL Query\n",
154+
"\n",
155+
"In the following cell, the source, meaning the name, of the SPARQL query file **is to be selected / specified by users**. \n",
156+
"\n",
157+
"The query contained in this file will be used for querying in the subsequent cell.\n",
158+
"\n",
159+
"### Depiction of Results \n",
160+
"\n",
161+
"For a depiction / visualization of results in table format, the module tabulate is used in the following.\n",
162+
"Furthermore, as the SPARQL query is defined by a dedicated SPARQL query file (link_SPARQL_query), the headers of the result table can be read from the select clause in the query. This way, the result can be double-checked manually and consistency is ensured (did the SPARQL query select statement really address the information I wanted to obtain?). Hence, the following code includes a read in of the information queried for (the terms / concepts / entities addressed using the select clause)."
163+
]
164+
},
165+
{
166+
"cell_type": "code",
167+
"execution_count": null,
168+
"metadata": {},
169+
"outputs": [],
170+
"source": [
171+
"# Specification of the SPARQL query of interest\n",
172+
"# Which SPARQL query is to be performed?\n",
173+
"# Please insert the name of the query (to be found in the \"sparql\" folder)\n",
174+
"\n",
175+
"query_name = 'count_all_entities'"
176+
]
177+
},
178+
{
179+
"cell_type": "code",
180+
"execution_count": null,
181+
"metadata": {},
182+
"outputs": [],
183+
"source": [
184+
"# Load the file from the resource and read the SPARQL query\n",
185+
"query = load_sparql(query_name)\n",
186+
"\n",
187+
"# Execute the SPARQL query\n",
188+
"res = triple_store.sparql(query)\n",
189+
"\n",
190+
"# Convert the result to a DataFrame\n",
191+
"data = sparql_result_to_df(res)\n",
192+
"\n",
193+
"# Visualization Part\n",
194+
"# Step: Extract the terms from the SELECT clause\n",
195+
"# This regular expression looks for the SELECT or SELECT DISTINCT clause and captures the terms.\n",
196+
"select_clause_match = re.search(r'SELECT\\s+(DISTINCT\\s+)?(.*?)\\s+WHERE', query, re.DOTALL)\n",
197+
"\n",
198+
"if select_clause_match:\n",
199+
" select_clause = select_clause_match.group(2) # Use group(2) to capture the variables\n",
200+
" # Split the terms by whitespace and strip any leading or trailing spaces\n",
201+
" headers = [term.strip().lstrip('?') for term in select_clause.split() if term.strip().startswith('?')]\n",
202+
"else:\n",
203+
" print(\"No headers were found. Please check the select clause within the SPARQL query.\")\n",
204+
"\n",
205+
"# Step: Use the headers in the tabulate print statement\n",
206+
"# Print the data with tabulate\n",
207+
"print(tabulate(data, headers=headers, tablefmt='psql', showindex=True))"
208+
]
209+
},
210+
{
211+
"cell_type": "markdown",
212+
"metadata": {},
213+
"source": [
214+
"## Perform all SPARQL Queries\n",
215+
"\n",
216+
"Using the following cell, all SPARQL queries available in the [example sparql folder]() will be performed one after the other automatically. All results are depicted. "
217+
]
218+
},
219+
{
220+
"cell_type": "code",
221+
"execution_count": null,
222+
"metadata": {},
223+
"outputs": [],
224+
"source": [
225+
"# GitHub API URL to list all files in the SPARQL folder\n",
226+
"repo_api_url = \"https://api.github.com/repos/materialdigital/tensile-test-ontology/contents/tensile_test_data/sparql\"\n",
227+
"\n",
228+
"# Get the JSON response\n",
229+
"response = requests.get(repo_api_url)\n",
230+
"files_json = response.json()\n",
231+
"\n",
232+
"# Filter only .sparql files\n",
233+
"query_files = [f['name'] for f in files_json if f['name'].endswith('.sparql')]\n",
234+
"\n",
235+
"# Dictionary to store results\n",
236+
"all_results = {}\n",
237+
"\n",
238+
"for query_file in query_files:\n",
239+
" query_name = query_file.replace(\".sparql\", \"\")\n",
240+
" try:\n",
241+
" # Load SPARQL content from GitHub using your existing function\n",
242+
" query = load_sparql(query_name)\n",
243+
" \n",
244+
" # Execute SPARQL query on the triple store\n",
245+
" res = triple_store.sparql(query)\n",
246+
" \n",
247+
" # Convert to DataFrame\n",
248+
" df = sparql_result_to_df(res)\n",
249+
" \n",
250+
" # Store in dictionary\n",
251+
" all_results[query_name] = df\n",
252+
" \n",
253+
" # Extract headers from SELECT clause\n",
254+
" select_clause_match = re.search(r'SELECT\\s+(DISTINCT\\s+)?(.*?)\\s+WHERE', query, re.DOTALL)\n",
255+
" if select_clause_match:\n",
256+
" select_clause = select_clause_match.group(2)\n",
257+
" headers = [term.strip().lstrip('?') for term in select_clause.split() if term.strip().startswith('?')]\n",
258+
" else:\n",
259+
" headers = None\n",
260+
" \n",
261+
" # Print results nicely\n",
262+
" print(f\"\\n=== Results for Query: {query_name} ===\")\n",
263+
" print(tabulate(df, headers=headers, tablefmt='psql', showindex=True))\n",
264+
" \n",
265+
" except Exception as e:\n",
266+
" print(f\"Error executing query '{query_name}': {e}\")"
267+
]
268+
}
269+
],
270+
"metadata": {
271+
"kernelspec": {
272+
"display_name": "Python 3",
273+
"language": "python",
274+
"name": "python3"
275+
},
276+
"language_info": {
277+
"codemirror_mode": {
278+
"name": "ipython",
279+
"version": 3
280+
},
281+
"file_extension": ".py",
282+
"mimetype": "text/x-python",
283+
"name": "python",
284+
"nbconvert_exporter": "python",
285+
"pygments_lexer": "ipython3",
286+
"version": "3.10.11"
287+
}
288+
},
289+
"nbformat": 4,
290+
"nbformat_minor": 2
291+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SELECT (COUNT(?s) AS ?count)
2+
WHERE {
3+
?s ?p ?o
4+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
PREFIX pmdco: <https://w3id.org/pmd/co/>
2+
3+
SELECT (COUNT(?process) AS ?numerOfTests)
4+
WHERE {
5+
?process a pmdco:PMD_0000974 .
6+
FILTER(CONTAINS(STR(?process), "_process"))
7+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
PREFIX pmdco: <https://w3id.org/pmd/co/>
2+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
4+
5+
SELECT DISTINCT ?material_tested
6+
WHERE {
7+
# Find the instance describing the material identification
8+
?identifier pmdco:PMD_0060000 ?material_value .
9+
FILTER(CONTAINS(STR(?material_value), "_material_identifier_value"))
10+
11+
# Get the actual value (literal), regardless of whether it is rdfs:label or rdf:value or similar.
12+
?material_value ?p ?material_tested .
13+
FILTER(isLiteral(?material_tested))
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SELECT ?s ?p ?o
2+
WHERE {
3+
?s ?p ?o
4+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
PREFIX obo: <http://purl.obolibrary.org/obo/>
2+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3+
PREFIX pmdco: <https://w3id.org/pmd/co/>
4+
PREFIX prefix: <https://w3id.org/pmd/ao/demodata/tensiletest_S355/>
5+
6+
SELECT ?subprocessShort
7+
WHERE {
8+
{
9+
SELECT ?mainProcess
10+
WHERE {
11+
?mainProcess a pmdco:PMD_0000974 .
12+
FILTER(CONTAINS(STR(?mainProcess), "_process"))
13+
}
14+
LIMIT 1
15+
}
16+
17+
?subprocess obo:BFO_0000132 ?mainProcess .
18+
OPTIONAL { ?subprocess (obo:BFO_0000062)+ ?prev }
19+
20+
# Abkürzen auf "prefix:..."
21+
BIND(REPLACE(STR(?subprocess), "^https://w3id.org/pmd/ao/demodata/tensiletest_S355/pmdao-tto-tt-S355-1_process_", "") AS ?subprocessShort)
22+
}
23+
GROUP BY ?subprocessShort
24+
ORDER BY (COUNT(?prev))

0 commit comments

Comments
 (0)