Skip to content

Commit f1ec09a

Browse files
winteredbobot
authored andcommitted
2025 webpage (#205)
* 2024 -> 2025 * target for cleaning up web results * feat: show legends for base and non-competing only when at least one solver present * feat: non-competitive divisions will be marked * skip non-competing solvers on podiums * fix: -- -> - * fix: missing case for determining competitive divisions * fix: competitive solvers * fix competitive divisions * consistency submission vs. base solver * refactor * fix: ordering of solvers
1 parent d869bb3 commit f1ec09a

File tree

9 files changed

+214
-57
lines changed

9 files changed

+214
-57
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ build: clean-build ## Build wheel file using poetry
3131
clean-build: ## clean build artifacts
3232
@rm -rf dist
3333

34+
.PHONY: clean-web-results
35+
clean-web-results: ## clean web results
36+
@rm -rf web/content/results
37+
@rm -rf web/public/results
38+
39+
3440
.PHONY: help
3541
help:
3642
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

smtcomp/defs.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def name_is_default_field(cls, data: Any) -> Any:
135135

136136
class SolverType(EnumAutoInt):
137137
wrapped = "wrapped"
138-
derived = "derived"
138+
derived = "derived" # TODO: put a datatype information on base solver
139139
standalone = "Standalone"
140140
portfolio = "Portfolio"
141141

@@ -1317,6 +1317,7 @@ class Submission(BaseModel, extra="forbid"):
13171317
website: HttpUrl
13181318
system_description: HttpUrl
13191319
solver_type: SolverType
1320+
# TODO add field base_solver?
13201321
participations: Participations
13211322
seed: int | None = None
13221323
competitive: bool = True
@@ -1325,6 +1326,7 @@ class Submission(BaseModel, extra="forbid"):
13251326
description="Must be set for the final version of the submission. An archive on zenodo is needed in this case.",
13261327
)
13271328

1329+
# TODO: model validator to check the sanity of the new base_solver field
13281330
@model_validator(mode="after")
13291331
def check_archive(self) -> Submission:
13301332
if self.archive is None and not all(p.archive for p in self.participations.root):
@@ -1510,11 +1512,26 @@ class Config:
15101512
Benchmarks to remove before selection
15111513
"""
15121514

1513-
removed_results: Any = []
1515+
removed_results: list[Any] = []
1516+
15141517
"""
15151518
Benchmarks to remove after running the solvers. Can be used when the selection has already been done.
15161519
"""
15171520

1521+
"""
1522+
Solver -> Base solver map for 2025
1523+
TODO: refactor this into Submission
1524+
"""
1525+
baseSolverMap2025 = {
1526+
"Bitwuzla-MachBV": "Bitwuzla-MachBV-base",
1527+
"Z3-Inc-Z3++": "Z3-Inc-Z3++-base",
1528+
"Z3-Noodler-Mocha": "Z3-Noodler-Mocha-base",
1529+
"Z3-Owl": "Z3-Owl-base",
1530+
"Z3-Noodler": "Z3-Noodler",
1531+
"z3siri": "z3siri-base",
1532+
"Z3-alpha": "Z3-alpha-base",
1533+
}
1534+
15181535
def __init__(self, data: Path | None) -> None:
15191536
self.id = self.__class__.__next_id__
15201537
self.__class__.__next_id__ += 1
@@ -1578,6 +1595,10 @@ def submissions(self) -> list[Submission]:
15781595
Submission.model_validate_json(Path(file).read_text()) for file in self.data.glob("../submissions/*.json")
15791596
]
15801597

1598+
@functools.cached_property
1599+
def competitive_solvers(self) -> list[str]:
1600+
return [s.name for s in self.submissions if s.competitive]
1601+
15811602
@functools.cached_property
15821603
def web_results(self) -> Path:
15831604
return self.data / ".." / "web" / "content" / "results"

smtcomp/generate_website_page.py

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ def page_track_suffix(track: defs.Track) -> str:
9494

9595
class PodiumStep(BaseModel):
9696
name: str
97+
baseSolver: str
98+
deltaBaseSolver: int
9799
competing: str # yes or no
98100
errorScore: int
99101
correctScore: int
@@ -115,6 +117,7 @@ class PodiumDivision(BaseModel):
115117
participants: str # participants_2023
116118
disagreements: str # disagreements_2023
117119
division: str # Arith
120+
is_competitive: bool # true = least 2 subst. different solvers were submitted
118121
track: track_name
119122
n_benchmarks: int
120123
time_limit: int
@@ -237,14 +240,27 @@ class Podium(RootModel):
237240
root: PodiumDivision | PodiumCrossDivision | PodiumSummaryResults = Field(..., discriminator="layout")
238241

239242

240-
def podium_steps(podium: List[dict[str, Any]] | None) -> List[PodiumStep]:
243+
def podium_steps(config: defs.Config, podium: List[dict[str, Any]] | None) -> List[PodiumStep]:
241244
if podium is None:
242245
return []
243246
else:
244-
return [
245-
PodiumStep(
247+
podiums = []
248+
non_competitive = []
249+
for s in podium:
250+
cscore = s["correctly_solved_score"]
251+
delta = 0
252+
derived_solver = defs.Config.baseSolverMap2025.get(s["solver"], "")
253+
if derived_solver != "":
254+
for sprime in podium:
255+
if sprime["solver"] == defs.Config.baseSolverMap2025.get(s["solver"], ""):
256+
delta = cscore - sprime["correctly_solved_score"]
257+
break
258+
259+
ps = PodiumStep(
246260
name=s["solver"],
247-
competing="yes", # TODO
261+
baseSolver=derived_solver,
262+
deltaBaseSolver=delta,
263+
competing="yes" if s["solver"] in config.competitive_solvers else "no",
248264
errorScore=s["error_score"],
249265
correctScore=s["correctly_solved_score"],
250266
CPUScore=s["cpu_time_score"],
@@ -257,36 +273,70 @@ def podium_steps(podium: List[dict[str, Any]] | None) -> List[PodiumStep]:
257273
timeout=s["timeout"],
258274
memout=s["memout"],
259275
)
260-
for s in podium
261-
]
262276

277+
if not s["solver"] in config.competitive_solvers:
278+
non_competitive.append(ps)
279+
else:
280+
podiums.append(ps)
281+
282+
return podiums + non_competitive
263283

264-
def make_podium(config: defs.Config, d: dict[str, Any], for_division: bool, track: defs.Track) -> PodiumDivision:
284+
285+
def make_podium(
286+
config: defs.Config, d: dict[str, Any], for_division: bool, track: defs.Track, results: pl.LazyFrame
287+
) -> PodiumDivision:
265288
def get_winner(l: List[dict[str, str]] | None) -> str:
266-
# TODO select only participating
289+
if l is None or not l:
290+
return "-"
291+
292+
l = [e for e in l if e["solver"] in config.competitive_solvers]
293+
267294
if l is None or not l or l[0]["correctly_solved_score"] == 0:
268295
return "-"
269296
else:
270297
return l[0]["solver"]
271298

299+
def is_competitive_division(results: pl.LazyFrame, division: int, for_division: bool) -> bool:
300+
"""
301+
A division in a track is competitive if at least two substantially different
302+
solvers (i.e., solvers from two different teams) were submitted.
303+
"""
304+
305+
solvers = (
306+
results.filter(pl.col("division" if for_division else "logic") == division)
307+
.select("solver")
308+
.unique()
309+
.collect()
310+
.get_column("solver")
311+
.to_list()
312+
)
313+
314+
# Avoid solvers of the same solver family under the assumption
315+
# of the following format: <solver-family>-<suffix> (holds for SMT-COMP 2025)
316+
# TODO: improve this criterion in the future
317+
return len(set([sol.split("-")[0].lower() for sol in solvers])) >= 2
318+
272319
if for_division:
320+
competitive_division = is_competitive_division(results, d["division"], for_division)
273321
division = defs.Division.name_of_int(d["division"])
274322
logics = dict((defs.Logic.name_of_int(d2["logic"]), d2["n"]) for d2 in d["logics"])
275323
else:
276324
division = defs.Logic.name_of_int(d["logic"])
325+
competitive_division = is_competitive_division(results, d["logic"], for_division)
277326
logics = dict()
278327

279328
if (track == defs.Track.Cloud) | (track == defs.Track.Parallel):
280329
winner_seq = "-"
281330
steps_seq = []
282331
else:
283332
winner_seq = get_winner(d[smtcomp.scoring.Kind.seq.name])
284-
steps_seq = podium_steps(d[smtcomp.scoring.Kind.seq.name])
333+
steps_seq = podium_steps(config, d[smtcomp.scoring.Kind.seq.name])
285334

286335
return PodiumDivision(
287-
resultdate="2024-07-08",
336+
resultdate="2025-08-11",
288337
year=config.current_year,
289338
divisions=f"divisions_{config.current_year}",
339+
is_competitive=competitive_division,
290340
participants=f"participants_{config.current_year}",
291341
disagreements=f"disagreements_{config.current_year}",
292342
division=division,
@@ -301,10 +351,10 @@ def get_winner(l: List[dict[str, str]] | None) -> str:
301351
winner_unsat=get_winner(d[smtcomp.scoring.Kind.unsat.name]),
302352
winner_24s=get_winner(d[smtcomp.scoring.Kind.twentyfour.name]),
303353
sequential=steps_seq,
304-
parallel=podium_steps(d[smtcomp.scoring.Kind.par.name]),
305-
sat=podium_steps(d[smtcomp.scoring.Kind.sat.name]),
306-
unsat=podium_steps(d[smtcomp.scoring.Kind.unsat.name]),
307-
twentyfour=podium_steps(d[smtcomp.scoring.Kind.twentyfour.name]),
354+
parallel=podium_steps(config, d[smtcomp.scoring.Kind.par.name]),
355+
sat=podium_steps(config, d[smtcomp.scoring.Kind.sat.name]),
356+
unsat=podium_steps(config, d[smtcomp.scoring.Kind.unsat.name]),
357+
twentyfour=podium_steps(config, d[smtcomp.scoring.Kind.twentyfour.name]),
308358
)
309359

310360

@@ -382,7 +432,7 @@ def info_for_podium_step(kind: smtcomp.scoring.Kind, config: defs.Config, result
382432

383433
df = r.collect()
384434

385-
return dict((name_of_int(d[group_by]), make_podium(config, d, for_division, track)) for d in df.to_dicts())
435+
return dict((name_of_int(d[group_by]), make_podium(config, d, for_division, track, results)) for d in df.to_dicts())
386436

387437

388438
def get_kind(a: PodiumDivision, k: smtcomp.scoring.Kind) -> list[PodiumStep]:
@@ -471,7 +521,7 @@ def get_winner(l: List[PodiumStepBiggestLead] | None) -> str:
471521
winner_seq = get_winner(sequential)
472522

473523
return PodiumBiggestLead(
474-
resultdate="2024-07-08",
524+
resultdate="2025-08-11",
475525
year=config.current_year,
476526
track=track,
477527
results=f"results_{config.current_year}",
@@ -589,7 +639,7 @@ def get_winner(
589639
winner_seq = get_winner(sequential, scores, data, track)
590640

591641
return PodiumBestOverall(
592-
resultdate="2024-07-08",
642+
resultdate="2025-08-11",
593643
year=config.current_year,
594644
track=track,
595645
results=f"results_{config.current_year}",
@@ -630,7 +680,10 @@ def get_winner(l: List[PodiumStepLargestContribution] | None) -> str:
630680

631681
def aux(k: smtcomp.scoring.Kind, div: str) -> List[PodiumStepLargestContribution]:
632682
v_steps = get_kind(virtual_datas[div], k)
633-
vws_steps = get_kind(virtual_without_solver_datas[div], k)
683+
if div in virtual_without_solver_datas:
684+
vws_steps = get_kind(virtual_without_solver_datas[div], k)
685+
else:
686+
vws_steps = []
634687
if not v_steps:
635688
assert not vws_steps
636689
return []
@@ -684,7 +737,7 @@ def timeScore(vws_step: PodiumStep) -> float:
684737
steps_seq = ld[smtcomp.scoring.Kind.seq]
685738

686739
return PodiumLargestContribution(
687-
resultdate="2024-07-08",
740+
resultdate="2025-08-11",
688741
year=config.current_year,
689742
track=track,
690743
results=f"results_{config.current_year}",
@@ -718,7 +771,7 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
718771

719772
# Virtual solver
720773
virtual_scores = (
721-
scores.group_by("division", "file")
774+
scores.group_by("division", "logic", "file")
722775
.agg(
723776
pl.max("correctly_solved_score"),
724777
pl.min("walltime_s"),
@@ -727,7 +780,6 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
727780
pl.min("cpu_time_score"),
728781
sound_status=pl.col("sound_status").first(),
729782
answer=pl.col("answer").first(),
730-
logic=-1,
731783
)
732784
.with_columns(solver=pl.lit("virtual"), error_score=0)
733785
)
@@ -739,7 +791,7 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
739791
intersect(scores.rename({"solver": "other_solver"}), solvers, on=["division"])
740792
.filter(pl.col("solver") != pl.col("other_solver"))
741793
.drop("other_solver")
742-
.group_by("division", "solver", "file")
794+
.group_by("division", "logic", "solver", "file")
743795
.agg(
744796
pl.max("correctly_solved_score"),
745797
pl.min("walltime_s"),
@@ -749,7 +801,6 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
749801
sound_status=pl.col("sound_status").first(),
750802
error_score=0,
751803
answer=pl.col("answer").first(),
752-
logic=-1,
753804
)
754805
)
755806
virtual_without_solver_datas = sq_generate_datas(config, virtual_without_solver_scores, for_division, track)
@@ -765,7 +816,6 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
765816

766817

767818
def export_results(config: defs.Config, results: pl.LazyFrame, track: defs.Track) -> None:
768-
769819
page_suffix = page_track_suffix(track)
770820

771821
dst = config.web_results

web/content/solver_submission/template.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "<solver name>",
3+
"contributors": [
4+
"First Smith",
5+
{ "name": "Second Baker", "website": "http://baker.com/" }
6+
],
7+
"contacts": ["contact name <[email protected]>"],
8+
"archive": {
9+
"url": "http://example.com/solver.tar.gz",
10+
"h": { "sha256": "012345" }
11+
},
12+
"website": "http://example.com/",
13+
"system_description": "http://example.com/system.pdf",
14+
"command": ["relative_cmd", "default_command_line"],
15+
"solver_type": "Standalone",
16+
"seed": "42",
17+
"participations": [
18+
{ "tracks": ["SingleQuery"], "divisions": ["Equality"] },
19+
{
20+
"tracks": ["SingleQuery"],
21+
"logics": "QF_.*LRA.*",
22+
"command": ["relative_cmd", "other_option"]
23+
},
24+
{
25+
"tracks": ["SingleQuery"],
26+
"logics": ["LIA"],
27+
"archive": { "url": "http://example.com/solver_lia.tar.gz" },
28+
"command": ["relative_cmd", "--super-lia"]
29+
}
30+
]
31+
}

web/themes/smtcomp/assets/css/main.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ td.left {
593593
}
594594

595595
td.non-competing-grey {
596-
color: #707070;
596+
color: gray;
597597
a {
598598
color: #808080;
599599
}

0 commit comments

Comments
 (0)