feat(convert): integrate process metrics into JSON and SQLite converters
- add process_metrics function to record peak RSS and CPU time - update JSON converter to include metrics in completion marker - modify SQLite converter to utilize new metrics for performance tracking - enhance storage size updates with locking mechanism for concurrent access
This commit is contained in:
15
common/queries/__init__.py
Normal file
15
common/queries/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Benchmark query implementations for the file-based formats (spec 08).
|
||||
|
||||
One module per format (csv_queries, json_queries, rdf_queries), each with
|
||||
functions q1..q7 returning canonical result rows (see common/results.py).
|
||||
The runnable wrappers under out/bench/queries/ are generated by
|
||||
make_queries.py and delegate here. SQLite / PostgreSQL implementations are
|
||||
plain SQL files written by make_queries.py.
|
||||
|
||||
The relational formats answer Q2/Q3/Q4/Q7 from the precomputed
|
||||
track_summary (their idiomatic strength); the file formats derive the same
|
||||
values from raw cycles via common/track_summary.py - identical algorithm,
|
||||
identical results (bench-methodology section 5).
|
||||
"""
|
||||
|
||||
Q1_TRACK_CODE = "B722-W2-C13-T2" # fixed benchmark track, spec 08
|
||||
139
common/queries/csv_queries.py
Normal file
139
common/queries/csv_queries.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Q1-Q7 over the raw CSV tree: pure python + csv module, streaming.
|
||||
|
||||
Q1 is a direct path read (the honest CSV strength, spec 08); Q2-Q7 are
|
||||
deterministic directory walks with programmatic joins. No manifest
|
||||
validation here - queries measure retrieval, not integrity checking.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from common.queries import Q1_TRACK_CODE
|
||||
from common.track_summary import summarize_track
|
||||
|
||||
|
||||
def _rows(path: Path) -> tuple[list[str], list[list[str]]]:
|
||||
with open(path, newline="", encoding="ascii") as fh:
|
||||
reader = csv.reader(fh)
|
||||
header = [h.lower() for h in next(reader)]
|
||||
return header, list(reader)
|
||||
|
||||
|
||||
def _dict_row(path: Path) -> dict[str, str]:
|
||||
header, rows = _rows(path)
|
||||
return dict(zip(header, rows[0]))
|
||||
|
||||
|
||||
def _track_path(csv_root: Path, track_code: str) -> Path:
|
||||
batch, wafer, coupon, track = track_code.split("-")
|
||||
return csv_root / f"batch_{batch}" / f"wafer_{wafer}" / f"coupon_{coupon}" / f"track_{track}"
|
||||
|
||||
|
||||
def _coupon_dirs(csv_root: Path):
|
||||
for batch_dir in sorted(csv_root.glob("batch_*")):
|
||||
batch_code = batch_dir.name[6:]
|
||||
for wafer_dir in sorted(batch_dir.glob("wafer_*")):
|
||||
for coupon_dir in sorted(wafer_dir.glob("coupon_*")):
|
||||
yield batch_code, coupon_dir
|
||||
|
||||
|
||||
def _track_dirs(coupon_dir: Path) -> list[Path]:
|
||||
return sorted(coupon_dir.glob("track_T*"))
|
||||
|
||||
|
||||
def _cofs(track_dir: Path) -> list[float]:
|
||||
_, rows = _rows(track_dir / "cof_vs_cycle.csv")
|
||||
return [float(r[1]) for r in rows]
|
||||
|
||||
|
||||
def _mean(values: list[float]) -> float:
|
||||
total = 0.0
|
||||
for v in values:
|
||||
total += v
|
||||
return total / len(values)
|
||||
|
||||
|
||||
def q1(csv_root: Path) -> list[tuple]:
|
||||
_, rows = _rows(_track_path(csv_root, Q1_TRACK_CODE) / "cof_vs_cycle.csv")
|
||||
return [(int(r[0]), float(r[1])) for r in rows]
|
||||
|
||||
|
||||
def q2(csv_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for _, coupon_dir in _coupon_dirs(csv_root):
|
||||
info = _dict_row(coupon_dir / "coupon_info.csv")
|
||||
if info["run_code"] == "RESERVE" or not 9.5 <= float(info["au_wtpct_mean"]) <= 10.5:
|
||||
continue
|
||||
tracks = _track_dirs(coupon_dir)
|
||||
env = _dict_row(tracks[0] / "track_info.csv")["environment"]
|
||||
cof_ss = _mean([summarize_track(_cofs(t))[0] for t in tracks])
|
||||
out.append((info["coupon_code"], env, cof_ss))
|
||||
return out
|
||||
|
||||
|
||||
def q3(csv_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for batch_code, coupon_dir in _coupon_dirs(csv_root):
|
||||
info = _dict_row(coupon_dir / "coupon_info.csv")
|
||||
if info["run_code"] == "RESERVE":
|
||||
continue
|
||||
header, rows = _rows(coupon_dir / "nanoindentation.csv")
|
||||
h_col = header.index("hardness_gpa")
|
||||
hardness = _mean([float(r[h_col]) for r in rows])
|
||||
cof_ss = _mean([summarize_track(_cofs(t))[0] for t in _track_dirs(coupon_dir)])
|
||||
out.append((info["coupon_code"], batch_code, hardness, cof_ss))
|
||||
return out
|
||||
|
||||
|
||||
def q4(csv_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for _, coupon_dir in _coupon_dirs(csv_root):
|
||||
for track_dir in _track_dirs(coupon_dir):
|
||||
info = _dict_row(track_dir / "track_info.csv")
|
||||
if info["environment"] != "dry_n2" or float(info["load_mn"]) != 100.0:
|
||||
continue
|
||||
cof_ss = summarize_track(_cofs(track_dir))[0]
|
||||
if cof_ss > 0.20:
|
||||
out.append((info["track_code"], cof_ss))
|
||||
return out
|
||||
|
||||
|
||||
def q5(csv_root: Path) -> list[tuple]:
|
||||
sums: dict[str, list[float]] = {}
|
||||
for batch_code, coupon_dir in _coupon_dirs(csv_root):
|
||||
for track_dir in _track_dirs(coupon_dir):
|
||||
run_in = summarize_track(_cofs(track_dir))[2]
|
||||
acc = sums.setdefault(batch_code, [0.0, 0.0])
|
||||
acc[0] += run_in
|
||||
acc[1] += 1
|
||||
return [(batch, acc[0] / acc[1]) for batch, acc in sorted(sums.items())]
|
||||
|
||||
|
||||
def q6(csv_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for _, coupon_dir in _coupon_dirs(csv_root):
|
||||
info = _dict_row(coupon_dir / "coupon_info.csv")
|
||||
if info["run_code"] == "RESERVE":
|
||||
continue
|
||||
tracks = _track_dirs(coupon_dir)
|
||||
load = float(_dict_row(tracks[0] / "track_info.csv")["load_mn"])
|
||||
wear = _mean([float(_dict_row(t / "wear.csv")["wear_volume_um3"]) for t in tracks])
|
||||
out.append((info["coupon_code"], load, wear))
|
||||
return out
|
||||
|
||||
|
||||
def q7(csv_root: Path) -> list[tuple]:
|
||||
groups: dict[tuple[str, int], list[float]] = {}
|
||||
for _, coupon_dir in _coupon_dirs(csv_root):
|
||||
for track_dir in _track_dirs(coupon_dir):
|
||||
info = _dict_row(track_dir / "track_info.csv")
|
||||
bucket = int(round(float(info["speed_mm_s"]) * float(info["load_mn"])))
|
||||
cofs = _cofs(track_dir)
|
||||
run_in = summarize_track(cofs)[2]
|
||||
acc = groups.setdefault((info["environment"], bucket), [0.0, 0.0])
|
||||
for cof in cofs[run_in:]: # cycles strictly past run-in
|
||||
acc[0] += cof
|
||||
acc[1] += 1
|
||||
return [(env, bucket, acc[0] / acc[1]) for (env, bucket), acc in sorted(groups.items())]
|
||||
169
common/queries/json_queries.py
Normal file
169
common/queries/json_queries.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""Q1-Q7 over the JSON-LD FULL variant: ijson streaming parser (spec 08).
|
||||
|
||||
Each coupon file is streamed event-by-event; scalar coupon fields appear
|
||||
before the bulk arrays in the serialization, so filtering queries (Q2) can
|
||||
abandon a non-matching file before parsing its megabytes of points.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import ijson
|
||||
|
||||
from common.queries import Q1_TRACK_CODE
|
||||
from common.track_summary import summarize_track
|
||||
|
||||
|
||||
class CouponDoc:
|
||||
__slots__ = ("coupon_code", "batch_code", "run_code", "au", "hardness", "tracks")
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.coupon_code = ""
|
||||
self.batch_code = ""
|
||||
self.run_code = ""
|
||||
self.au = 0.0
|
||||
self.hardness: list[float] = []
|
||||
self.tracks: list[dict] = []
|
||||
|
||||
|
||||
def _scan(path: Path, au_range: tuple[float, float] | None = None) -> CouponDoc | None:
|
||||
"""Stream one coupon file; with au_range set, bail out early on mismatch."""
|
||||
doc = CouponDoc()
|
||||
track: dict | None = None
|
||||
with open(path, "rb") as fh:
|
||||
for prefix, event, value in ijson.parse(fh, use_float=True):
|
||||
if prefix == "coupon_code":
|
||||
doc.coupon_code = value
|
||||
elif prefix == "batch_code":
|
||||
doc.batch_code = value
|
||||
elif prefix == "run_code":
|
||||
doc.run_code = value
|
||||
if doc.run_code == "RESERVE":
|
||||
return None
|
||||
elif prefix == "au_wtpct_mean":
|
||||
doc.au = value
|
||||
if au_range and not au_range[0] <= value <= au_range[1]:
|
||||
return None
|
||||
elif prefix == "nanoindentation.indents.item.hardness_gpa":
|
||||
doc.hardness.append(value)
|
||||
elif prefix == "tracks.item" and event == "start_map":
|
||||
track = {"cycles": [], "cofs": []}
|
||||
elif track is not None:
|
||||
if prefix == "tracks.item.track_code":
|
||||
track["track_code"] = value
|
||||
elif prefix == "tracks.item.environment":
|
||||
track["environment"] = value
|
||||
elif prefix == "tracks.item.load_mn":
|
||||
track["load_mn"] = value
|
||||
elif prefix == "tracks.item.speed_mm_s":
|
||||
track["speed_mm_s"] = value
|
||||
elif prefix == "tracks.item.cycles.item.cycle":
|
||||
track["cycles"].append(value)
|
||||
elif prefix == "tracks.item.cycles.item.cof":
|
||||
track["cofs"].append(value)
|
||||
elif prefix == "tracks.item.wear.wear_volume_um3":
|
||||
track["wear"] = value
|
||||
elif prefix == "tracks.item" and event == "end_map":
|
||||
doc.tracks.append(track)
|
||||
track = None
|
||||
return doc
|
||||
|
||||
|
||||
def _files(json_root: Path) -> list[Path]:
|
||||
return sorted((json_root / "full").glob("*.jsonld"))
|
||||
|
||||
|
||||
def _mean(values: list[float]) -> float:
|
||||
total = 0.0
|
||||
for v in values:
|
||||
total += v
|
||||
return total / len(values)
|
||||
|
||||
|
||||
def _cof_ss(doc: CouponDoc) -> float:
|
||||
return _mean([summarize_track(t["cofs"])[0] for t in doc.tracks])
|
||||
|
||||
|
||||
def q1(json_root: Path) -> list[tuple]:
|
||||
coupon_code = Q1_TRACK_CODE.rsplit("-", 1)[0]
|
||||
doc = _scan(json_root / "full" / f"{coupon_code}.jsonld")
|
||||
for track in doc.tracks:
|
||||
if track["track_code"] == Q1_TRACK_CODE:
|
||||
return [(int(c), cof) for c, cof in zip(track["cycles"], track["cofs"])]
|
||||
raise LookupError(f"track {Q1_TRACK_CODE} not found")
|
||||
|
||||
|
||||
def q2(json_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path, au_range=(9.5, 10.5))
|
||||
if doc is None:
|
||||
continue
|
||||
out.append((doc.coupon_code, doc.tracks[0]["environment"], _cof_ss(doc)))
|
||||
return out
|
||||
|
||||
|
||||
def q3(json_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path)
|
||||
if doc is None:
|
||||
continue
|
||||
out.append((doc.coupon_code, doc.batch_code, _mean(doc.hardness), _cof_ss(doc)))
|
||||
return out
|
||||
|
||||
|
||||
def q4(json_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path)
|
||||
if doc is None:
|
||||
continue
|
||||
for track in doc.tracks:
|
||||
if track["environment"] != "dry_n2" or track["load_mn"] != 100.0:
|
||||
continue
|
||||
cof_ss = summarize_track(track["cofs"])[0]
|
||||
if cof_ss > 0.20:
|
||||
out.append((track["track_code"], cof_ss))
|
||||
return out
|
||||
|
||||
|
||||
def q5(json_root: Path) -> list[tuple]:
|
||||
sums: dict[str, list[float]] = {}
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path)
|
||||
if doc is None:
|
||||
continue
|
||||
acc = sums.setdefault(doc.batch_code, [0.0, 0.0])
|
||||
for track in doc.tracks:
|
||||
acc[0] += summarize_track(track["cofs"])[2]
|
||||
acc[1] += 1
|
||||
return [(batch, acc[0] / acc[1]) for batch, acc in sorted(sums.items())]
|
||||
|
||||
|
||||
def q6(json_root: Path) -> list[tuple]:
|
||||
out = []
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path)
|
||||
if doc is None:
|
||||
continue
|
||||
load = doc.tracks[0]["load_mn"]
|
||||
out.append((doc.coupon_code, load, _mean([t["wear"] for t in doc.tracks])))
|
||||
return out
|
||||
|
||||
|
||||
def q7(json_root: Path) -> list[tuple]:
|
||||
groups: dict[tuple[str, int], list[float]] = {}
|
||||
for path in _files(json_root):
|
||||
doc = _scan(path)
|
||||
if doc is None:
|
||||
continue
|
||||
for track in doc.tracks:
|
||||
bucket = int(round(track["speed_mm_s"] * track["load_mn"]))
|
||||
run_in = summarize_track(track["cofs"])[2]
|
||||
acc = groups.setdefault((track["environment"], bucket), [0.0, 0.0])
|
||||
for cof in track["cofs"][run_in:]:
|
||||
acc[0] += cof
|
||||
acc[1] += 1
|
||||
return [(env, bucket, acc[0] / acc[1]) for (env, bucket), acc in sorted(groups.items())]
|
||||
173
common/queries/rdf_queries.py
Normal file
173
common/queries/rdf_queries.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""Q1-Q7 over the oxigraph triplestore: SPARQL retrieval (spec 08).
|
||||
|
||||
The store holds raw data only (no precomputed summaries), so queries that
|
||||
need steady-state COF or run-in retrieve the raw cycles via SPARQL and
|
||||
derive the values with the shared algorithm (common/track_summary.py) -
|
||||
identical semantics to every other format. The SPARQL text of each query
|
||||
is exported to out/bench/queries/rdf/q<N>.sparql by make_queries.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from pyoxigraph import Store
|
||||
|
||||
from common.queries import Q1_TRACK_CODE
|
||||
from common.track_summary import summarize_track
|
||||
|
||||
PREFIX = "PREFIX tribo: <https://sandia.gov/ontology/tribology#>\n"
|
||||
TRACK_IRI = f"<https://sandia.gov/ontology/tribology/id/track/{Q1_TRACK_CODE}>"
|
||||
|
||||
SPARQL = {
|
||||
"q1": PREFIX + f"""SELECT ?cycle ?cof WHERE {{
|
||||
{TRACK_IRI} tribo:hasCycle ?c .
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}}""",
|
||||
"q2": PREFIX + """SELECT ?ccode ?env ?track ?cycle ?cof WHERE {
|
||||
?coupon tribo:au_wtpct_mean ?au ; tribo:coupon_code ?ccode .
|
||||
FILTER(?au >= 9.5 && ?au <= 10.5)
|
||||
?track tribo:partOf ?coupon ; tribo:environment ?env ; tribo:hasCycle ?c .
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}""",
|
||||
"q3_hardness": PREFIX + """SELECT ?ccode ?bcode (AVG(?h) AS ?hardness) WHERE {
|
||||
?coupon tribo:coupon_code ?ccode ; tribo:cutFrom ?wafer ; tribo:nanoindentation ?m .
|
||||
?wafer tribo:partOf ?batch .
|
||||
?batch tribo:batch_code ?bcode .
|
||||
?m tribo:hasIndent ?i .
|
||||
?i tribo:hardness_gpa ?h .
|
||||
} GROUP BY ?ccode ?bcode""",
|
||||
"q3_cycles": PREFIX + """SELECT ?ccode ?track ?cycle ?cof WHERE {
|
||||
?track a tribo:FrictionTest ; tribo:partOf ?coupon ; tribo:hasCycle ?c .
|
||||
?coupon tribo:coupon_code ?ccode .
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}""",
|
||||
"q4": PREFIX + """SELECT ?tcode ?track ?cycle ?cof WHERE {
|
||||
?track tribo:environment "dry_n2" ; tribo:load_mn ?load ; tribo:track_code ?tcode ; tribo:hasCycle ?c .
|
||||
FILTER(?load = 100)
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}""",
|
||||
"q5": PREFIX + """SELECT ?bcode ?track ?cycle ?cof WHERE {
|
||||
?track a tribo:FrictionTest ; tribo:partOf ?coupon ; tribo:hasCycle ?c .
|
||||
?coupon tribo:cutFrom ?wafer .
|
||||
?wafer tribo:partOf ?batch .
|
||||
?batch tribo:batch_code ?bcode .
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}""",
|
||||
"q6": PREFIX + """SELECT ?ccode ?load (AVG(?wv) AS ?wear) WHERE {
|
||||
?track tribo:partOf ?coupon ; tribo:load_mn ?load ; tribo:wear ?w .
|
||||
?w tribo:wear_volume_um3 ?wv .
|
||||
?coupon tribo:coupon_code ?ccode .
|
||||
} GROUP BY ?ccode ?load""",
|
||||
"q7": PREFIX + """SELECT ?env ?speed ?load ?track ?cycle ?cof WHERE {
|
||||
?track tribo:environment ?env ; tribo:speed_mm_s ?speed ; tribo:load_mn ?load ; tribo:hasCycle ?c .
|
||||
?c tribo:cycle ?cycle ; tribo:cof ?cof .
|
||||
}""",
|
||||
}
|
||||
|
||||
|
||||
def _store(store_path: Path) -> Store:
|
||||
try:
|
||||
return Store.read_only(str(store_path))
|
||||
except AttributeError:
|
||||
return Store(str(store_path))
|
||||
|
||||
|
||||
def _track_cofs(solutions, track_var: str, keep: tuple[str, ...]) -> dict[str, dict]:
|
||||
"""Group cycle solutions by track: ordered cof list + kept literals."""
|
||||
tracks: dict[str, dict] = {}
|
||||
for sol in solutions:
|
||||
key = str(sol[track_var])
|
||||
entry = tracks.get(key)
|
||||
if entry is None:
|
||||
entry = {"pairs": []}
|
||||
for name in keep:
|
||||
entry[name] = sol[name].value
|
||||
tracks[key] = entry
|
||||
entry["pairs"].append((int(sol["cycle"].value), float(sol["cof"].value)))
|
||||
for entry in tracks.values():
|
||||
entry["pairs"].sort()
|
||||
entry["cofs"] = [cof for _, cof in entry["pairs"]]
|
||||
return tracks
|
||||
|
||||
|
||||
def _mean(values: list[float]) -> float:
|
||||
total = 0.0
|
||||
for v in values:
|
||||
total += v
|
||||
return total / len(values)
|
||||
|
||||
|
||||
def q1(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
return [(int(s["cycle"].value), float(s["cof"].value)) for s in store.query(SPARQL["q1"])]
|
||||
|
||||
|
||||
def q2(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
tracks = _track_cofs(store.query(SPARQL["q2"]), "track", ("ccode", "env"))
|
||||
coupons: dict[tuple[str, str], list[float]] = {}
|
||||
for entry in sorted(tracks.values(), key=lambda e: e["ccode"]):
|
||||
coupons.setdefault((entry["ccode"], entry["env"]), []).append(summarize_track(entry["cofs"])[0])
|
||||
return [(code, env, _mean(values)) for (code, env), values in sorted(coupons.items())]
|
||||
|
||||
|
||||
def q3(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
hardness = {
|
||||
s["ccode"].value: (s["bcode"].value, float(s["hardness"].value))
|
||||
for s in store.query(SPARQL["q3_hardness"])
|
||||
}
|
||||
tracks = _track_cofs(store.query(SPARQL["q3_cycles"]), "track", ("ccode",))
|
||||
coupons: dict[str, list[float]] = {}
|
||||
for entry in sorted(tracks.values(), key=lambda e: e["ccode"]):
|
||||
coupons.setdefault(entry["ccode"], []).append(summarize_track(entry["cofs"])[0])
|
||||
return [
|
||||
(code, hardness[code][0], hardness[code][1], _mean(values))
|
||||
for code, values in sorted(coupons.items())
|
||||
]
|
||||
|
||||
|
||||
def q4(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
tracks = _track_cofs(store.query(SPARQL["q4"]), "track", ("tcode",))
|
||||
out = []
|
||||
for entry in sorted(tracks.values(), key=lambda e: e["tcode"]):
|
||||
cof_ss = summarize_track(entry["cofs"])[0]
|
||||
if cof_ss > 0.20:
|
||||
out.append((entry["tcode"], cof_ss))
|
||||
return out
|
||||
|
||||
|
||||
def q5(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
tracks = _track_cofs(store.query(SPARQL["q5"]), "track", ("bcode",))
|
||||
sums: dict[str, list[float]] = {}
|
||||
for entry in sorted(tracks.values(), key=lambda e: e["bcode"]):
|
||||
acc = sums.setdefault(entry["bcode"], [0.0, 0.0])
|
||||
acc[0] += summarize_track(entry["cofs"])[2]
|
||||
acc[1] += 1
|
||||
return [(batch, acc[0] / acc[1]) for batch, acc in sorted(sums.items())]
|
||||
|
||||
|
||||
def q6(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
return [
|
||||
(s["ccode"].value, float(s["load"].value), float(s["wear"].value))
|
||||
for s in store.query(SPARQL["q6"])
|
||||
]
|
||||
|
||||
|
||||
def q7(store_path: Path) -> list[tuple]:
|
||||
store = _store(store_path)
|
||||
tracks = _track_cofs(store.query(SPARQL["q7"]), "track", ("env", "speed", "load"))
|
||||
groups: dict[tuple[str, int], list[float]] = {}
|
||||
for key in sorted(tracks):
|
||||
entry = tracks[key]
|
||||
bucket = int(round(float(entry["speed"]) * float(entry["load"])))
|
||||
run_in = summarize_track(entry["cofs"])[2]
|
||||
acc = groups.setdefault((entry["env"], bucket), [0.0, 0.0])
|
||||
for cof in entry["cofs"][run_in:]:
|
||||
acc[0] += cof
|
||||
acc[1] += 1
|
||||
return [(env, bucket, acc[0] / acc[1]) for (env, bucket), acc in sorted(groups.items())]
|
||||
Reference in New Issue
Block a user