Skip to content

Commit 6e327cc

Browse files
martinjonasMartin Jonášwintered
authored
2026 results (#297)
* feat: Add command for computing contribution of derived solvers. * feat: Add results for all tracks except Parallel and Cloud. * a few adjustments for 2026 result presentation * fixing CI * feat: New derived solver scoring for most of the tracks. * chore: Fix typing. * chore: Enable result generation. * chore: Show results page. * feat: Visually distinguish non-eligible solvers in the results. * fix: Allow generated HTML pages. * fix: Use only result files for cerfiticates (ignore e.g. charts) * feat: Only consider competitive solvers for largest_contribution. * feat: Derived score solving for Sequential and 24s scores. * feat: Derived solver scoring for Overal Best score. * chore: Lint. * feat: Show PAR2 scores and base solver ratio on the website. * chore: Update certificates for 2026. * fix: Remove double Winners heading. * chore: Update result dates. * feat: Make the non-eligible solvers less visible. * chore: Enable cache generation. --------- Co-authored-by: Martin Jonáš <martin.jonas@mail.muni.cz> Co-authored-by: Dominik Winterer <domwinterer@gmail.com>
1 parent 4013026 commit 6e327cc

16 files changed

Lines changed: 228 additions & 88 deletions

Makefile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ test: generation ## Test the code with pytest
1919
@echo "🚀 Testing code: Running pytest"
2020
@poetry run pytest
2121

22-
generation: submission-generation participant-data track-data division-track-data # results-generation charts-generation ## Files generation for the website
22+
generation: submission-generation participant-data track-data division-track-data results-generation charts-generation ## Files generation for the website
2323

2424
.PHONY: build
2525
build: clean-build ## Build wheel file using poetry
@@ -81,8 +81,8 @@ results-generation:
8181
@poetry run smtcomp export-results-pages data Incremental
8282
# @echo "🚀 Generating results to web/content/results for Cloud"
8383
# @poetry run smtcomp export-results-pages data Cloud
84-
@echo "🚀 Generating results to web/content/results for Parallel"
85-
@poetry run smtcomp export-results-pages data Parallel
84+
# @echo "🚀 Generating results to web/content/results for Parallel"
85+
# @poetry run smtcomp export-results-pages data Parallel
8686

8787
charts-generation:
8888
@echo "🚀 Generating results to web/content/results for SingleQuery"
@@ -95,13 +95,13 @@ charts-generation:
9595
@poetry run smtcomp generate-website-graphics data Incremental
9696
# @echo "🚀 Generating results to web/content/results for Cloud"
9797
# @poetry run smtcomp generate-website-graphics data Cloud
98-
@echo "🚀 Generating results to web/content/results for Parallel"
99-
@poetry run smtcomp generate-website-graphics data Parallel
98+
# @echo "🚀 Generating results to web/content/results for Parallel"
99+
# @poetry run smtcomp generate-website-graphics data Parallel
100100

101101

102102
cache:
103-
# @echo "🚀 Generating cache"
104-
# @poetry run smtcomp create-cache data
103+
@echo "🚀 Generating cache"
104+
@poetry run smtcomp create-cache data
105105

106106
hugo-server:
107107
(cd web; hugo server)

data/latex-certificates/gen_certificates_ornament.tex

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
\vspace{1cm}
7474
\begin{tikzpicture}
7575
\node [align=center]
76-
{\resizebox{10cm}{!}{\Huge\textsc{SMT-COMP 2025}}};
76+
{\resizebox{10cm}{!}{\Huge\textsc{SMT-COMP 2026}}};
7777
\end{tikzpicture}
7878
\end{minipage}
7979
\vspace{1.55cm}
@@ -119,17 +119,15 @@
119119
% \scalebox{.7}{
120120
{\tiny
121121
\begin{tabular}{ccccccc}
122-
\includegraphics[scale=0.25,trim=1.5cm 2.75cm 0pt 0pt]{FB-signature.png} &&
123-
\includegraphics[scale=0.06,trim=0pt 0.20cm 0pt 0pt]{DD-Signature.png} &&
124-
\includegraphics[scale=0.4,trim=1.5pt 2.75cm 0pt 0pt]{MJ-Signature.png} &&
125-
\includegraphics[scale=0.4,trim=0pt 23pt 0pt 0pt]{DW-Signature.png}\\
122+
\includegraphics[scale=0.4,trim=1.5pt 2.75cm 0pt 0pt]{MJ-Signature.png} &&
123+
\includegraphics[scale=0.1,trim=1.5pt 3.25cm 0pt 0pt]{TK-Signature.png} &&
124+
\includegraphics[scale=0.4,trim=0pt 23pt 0pt 0pt]{DW-Signature.png}\\
126125
\cline{1-1}
127126
\cline{3-3}
128127
\cline{5-5}
129-
\cline{7-7}
130128
\\ \
131-
François Bobot & & David Déharbe & & Martin Jon\'{a}\v{s} & & Dominik Winterer \\
132-
Organizer & & Organizer & & Chair & & Organizer \\
129+
Martin Jon\'{a}\v{s} & & Tom\'{a}\v{s} Kol\'{a}rik & & Dominik Winterer \\
130+
Organizer & & Organizer & & Chair \\
133131
\end{tabular}
134132
}}
135133
\end{myframe}

data/results-inc-2026.json.gz

960 KB
Binary file not shown.

data/results-mv-2026.json.gz

2.47 MB
Binary file not shown.

data/results-sq-2026.json.gz

12.1 MB
Binary file not shown.

data/results-uc-2026.json.gz

3.35 MB
Binary file not shown.

smtcomp/certificates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,8 @@ def generate_certificates(
215215
list_dir.sort()
216216
for result_basename in list_dir:
217217
file = website_results / result_basename
218-
if not file.is_file():
219-
break
218+
if not file.is_file() or file.suffix != ".md":
219+
continue
220220

221221
result = page.Podium.model_validate_json(file.read_text()).root
222222

smtcomp/defs.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,20 +1520,6 @@ class Config:
15201520
Benchmarks to remove after running the solvers. Can be used when the selection has already been done.
15211521
"""
15221522

1523-
"""
1524-
Solver -> Base solver map for 2025
1525-
TODO: refactor this into Submission
1526-
"""
1527-
baseSolverMap2025 = {
1528-
"Bitwuzla-MachBV": "Bitwuzla-MachBV-base",
1529-
"Z3-Inc-Z3++": "Z3-Inc-Z3++-base",
1530-
"Z3-Noodler-Mocha": "Z3-Noodler-Mocha-base",
1531-
"Z3-Owl": "Z3-Owl-base",
1532-
"Z3-Noodler": "Z3-Noodler-base",
1533-
"z3siri": "z3siri-base",
1534-
"Z3-alpha": "Z3-alpha-base",
1535-
}
1536-
15371523
def __init__(self, data: Path | None) -> None:
15381524
self.id = self.__class__.__next_id__
15391525
self.__class__.__next_id__ += 1

smtcomp/generate_website_page.py

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ class PodiumStep(BaseModel):
108108
abstained: int
109109
timeout: int
110110
memout: int
111+
par2score: float_6dig
112+
par2scoreBase: float_6dig | None
113+
eligibleForWinning: bool
111114

112115

113116
class PodiumDivision(BaseModel):
@@ -149,6 +152,7 @@ class PodiumStepOverallScore(BaseModel):
149152
contribution: float_6dig # nn_D * log10 N_D
150153
division: str
151154
tieBreakTimeScore: float_6dig
155+
eligibleForWinning: bool
152156

153157

154158
class PodiumBestOverall(BaseModel):
@@ -240,25 +244,37 @@ class Podium(RootModel):
240244
root: PodiumDivision | PodiumCrossDivision | PodiumSummaryResults = Field(..., discriminator="layout")
241245

242246

243-
def podium_steps(config: defs.Config, podium: List[dict[str, Any]] | None) -> List[PodiumStep]:
247+
def podium_steps(config: defs.Config, podium: List[dict[str, Any]] | None, scoring: str) -> List[PodiumStep]:
248+
def par2(s: dict[str, Any]) -> Any:
249+
if scoring == smtcomp.scoring.Kind.seq.name:
250+
return s["cpu_time_score"] + 2 * config.cpuCores * config.timelimit_s * s["unsolved"]
251+
elif scoring == smtcomp.scoring.Kind.twentyfour.name:
252+
return s["wallclock_time_score"] + 2 * 24 * s["unsolved"]
253+
else:
254+
return s["wallclock_time_score"] + 2 * config.timelimit_s * s["unsolved"]
255+
244256
if podium is None:
245257
return []
246258
else:
247259
podiums = []
248-
non_competitive = []
260+
base_solvers = []
249261
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
262+
base = None
263+
for sprime in podium:
264+
if sprime["solver"] == s["solver"] + "-base":
265+
base = sprime
266+
break
267+
268+
solver_par2 = par2(s)
269+
base_par2 = None if base is None else par2(base)
270+
eligible = s["solver"] in config.competitive_solvers and (
271+
base_par2 is None or solver_par2 <= 0.9 * base_par2
272+
)
273+
delta = 0 if base is None else s["correctly_solved_score"] - base["correctly_solved_score"]
258274

259275
ps = PodiumStep(
260276
name=s["solver"],
261-
baseSolver=derived_solver,
277+
baseSolver=base["solver"] if base is not None else "",
262278
deltaBaseSolver=delta,
263279
competing="yes" if s["solver"] in config.competitive_solvers else "no",
264280
errorScore=s["error_score"],
@@ -272,29 +288,32 @@ def podium_steps(config: defs.Config, podium: List[dict[str, Any]] | None) -> Li
272288
abstained=s["abstained"],
273289
timeout=s["timeout"],
274290
memout=s["memout"],
291+
par2score=solver_par2,
292+
par2scoreBase=base_par2,
293+
eligibleForWinning=eligible,
275294
)
276295

277-
if not s["solver"] in config.competitive_solvers:
278-
non_competitive.append(ps)
296+
if s["solver"].endswith("-base"):
297+
base_solvers.append(ps)
279298
else:
280299
podiums.append(ps)
281300

282-
return podiums + non_competitive
301+
return podiums + base_solvers
283302

284303

285304
def make_podium(
286305
config: defs.Config, d: dict[str, Any], for_division: bool, track: defs.Track, results: pl.LazyFrame
287306
) -> PodiumDivision:
288-
def get_winner(l: List[dict[str, str]] | None) -> str:
307+
def get_winner(l: List[PodiumStep] | None) -> str:
289308
if l is None or not l:
290309
return "-"
291310

292-
l = [e for e in l if e["solver"] in config.competitive_solvers]
311+
l = [s for s in l if s.eligibleForWinning]
293312

294-
if l is None or not l or l[0]["correctly_solved_score"] == 0:
313+
if l is None or not l or l[0].correctScore == 0:
295314
return "-"
296315
else:
297-
return l[0]["solver"]
316+
return l[0].name
298317

299318
def is_competitive_division(results: pl.LazyFrame, division: int, for_division: bool) -> bool:
300319
"""
@@ -312,7 +331,7 @@ def is_competitive_division(results: pl.LazyFrame, division: int, for_division:
312331
)
313332

314333
# Avoid solvers of the same solver family under the assumption
315-
# of the following format: <solver-family>-<suffix> (holds for SMT-COMP 2025)
334+
# of the following format: <solver-family>-<suffix> (holds for SMT-COMP 2025/26)
316335
# TODO: improve this criterion in the future
317336
return len(set([sol.split("-")[0].lower() for sol in solvers])) >= 2
318337

@@ -325,15 +344,25 @@ def is_competitive_division(results: pl.LazyFrame, division: int, for_division:
325344
competitive_division = is_competitive_division(results, d["logic"], for_division)
326345
logics = dict()
327346

347+
steps: dict[str, List[Any]] = {}
348+
328349
if (track == defs.Track.Cloud) | (track == defs.Track.Parallel):
329-
winner_seq = "-"
330-
steps_seq = []
350+
steps[smtcomp.scoring.Kind.seq.name] = []
331351
else:
332-
winner_seq = get_winner(d[smtcomp.scoring.Kind.seq.name])
333-
steps_seq = podium_steps(config, d[smtcomp.scoring.Kind.seq.name])
352+
steps[smtcomp.scoring.Kind.seq.name] = podium_steps(
353+
config, d[smtcomp.scoring.Kind.seq.name], smtcomp.scoring.Kind.seq.name
354+
)
355+
356+
for score in (
357+
smtcomp.scoring.Kind.par.name,
358+
smtcomp.scoring.Kind.sat.name,
359+
smtcomp.scoring.Kind.unsat.name,
360+
smtcomp.scoring.Kind.twentyfour.name,
361+
):
362+
steps[score] = podium_steps(config, d[score], score)
334363

335364
return PodiumDivision(
336-
resultdate="2025-08-11",
365+
resultdate="2026-07-25",
337366
year=config.current_year,
338367
divisions=f"divisions_{config.current_year}",
339368
is_competitive=competitive_division,
@@ -345,16 +374,16 @@ def is_competitive_division(results: pl.LazyFrame, division: int, for_division:
345374
time_limit=config.timelimit_s,
346375
mem_limit=config.memlimit_M,
347376
logics=dict(sorted(logics.items())),
348-
winner_seq=winner_seq,
349-
winner_par=get_winner(d[smtcomp.scoring.Kind.par.name]),
350-
winner_sat=get_winner(d[smtcomp.scoring.Kind.sat.name]),
351-
winner_unsat=get_winner(d[smtcomp.scoring.Kind.unsat.name]),
352-
winner_24s=get_winner(d[smtcomp.scoring.Kind.twentyfour.name]),
353-
sequential=steps_seq,
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]),
377+
winner_seq=get_winner(steps[smtcomp.scoring.Kind.seq.name]),
378+
winner_par=get_winner(steps[smtcomp.scoring.Kind.par.name]),
379+
winner_sat=get_winner(steps[smtcomp.scoring.Kind.sat.name]),
380+
winner_unsat=get_winner(steps[smtcomp.scoring.Kind.unsat.name]),
381+
winner_24s=get_winner(steps[smtcomp.scoring.Kind.twentyfour.name]),
382+
sequential=steps[smtcomp.scoring.Kind.seq.name],
383+
parallel=steps[smtcomp.scoring.Kind.par.name],
384+
sat=steps[smtcomp.scoring.Kind.sat.name],
385+
unsat=steps[smtcomp.scoring.Kind.unsat.name],
386+
twentyfour=steps[smtcomp.scoring.Kind.twentyfour.name],
358387
)
359388

360389

@@ -521,7 +550,7 @@ def get_winner(l: List[PodiumStepBiggestLead] | None) -> str:
521550
winner_seq = get_winner(sequential)
522551

523552
return PodiumBiggestLead(
524-
resultdate="2025-08-11",
553+
resultdate="2026-07-25",
525554
year=config.current_year,
526555
track=track,
527556
results=f"results_{config.current_year}",
@@ -567,6 +596,7 @@ def normalized_correctness_score(
567596
contribution=nn_D * (math.log10(N_D) if N_D > 0 else 0),
568597
tieBreakTimeScore=sol_in_div.CPUScore if k == smtcomp.scoring.Kind.seq else sol_in_div.WallScore,
569598
division=division,
599+
eligibleForWinning=sol_in_div.eligibleForWinning,
570600
)
571601
)
572602
podiumSteps = sorted(podiumSteps, key=lambda x: (x.contribution, x.tieBreakTimeScore), reverse=True)
@@ -619,11 +649,19 @@ def get_winner(
619649
if l is None or not l:
620650
return ("-", 0.0)
621651
else:
622-
podium: DefaultDict[str, Dict[str, float]] = defaultdict(lambda: {"score": 0.0, "tie_break_time": 0.0})
652+
podium: DefaultDict[str, Dict[str, Any]] = defaultdict(
653+
lambda: {"score": 0.0, "tie_break_time": 0.0, "eligibleForWinning": False}
654+
)
623655
for entry in l:
624656
podium[entry.name]["score"] += entry.contribution
625657
podium[entry.name]["tie_break_time"] += entry.tieBreakTimeScore
626-
winner, winner_data = max(podium.items(), key=lambda item: (item[1]["score"], -item[1]["tie_break_time"]))
658+
podium[entry.name]["eligibleForWinning"] = (
659+
podium[entry.name]["eligibleForWinning"] or entry.eligibleForWinning
660+
)
661+
winner, winner_data = max(
662+
filter(lambda i: i[1]["eligibleForWinning"], podium.items()),
663+
key=lambda item: (item[1]["score"], -item[1]["tie_break_time"]),
664+
)
627665
return (winner, winner_data["score"])
628666

629667
sequential = normalized_correctness_score(data, scores, track, smtcomp.scoring.Kind.seq)
@@ -639,7 +677,7 @@ def get_winner(
639677
winner_seq = get_winner(sequential, scores, data, track)
640678

641679
return PodiumBestOverall(
642-
resultdate="2025-08-11",
680+
resultdate="2026-07-25",
643681
year=config.current_year,
644682
track=track,
645683
results=f"results_{config.current_year}",
@@ -737,7 +775,7 @@ def timeScore(vws_step: PodiumStep) -> float:
737775
steps_seq = ld[smtcomp.scoring.Kind.seq]
738776

739777
return PodiumLargestContribution(
740-
resultdate="2025-08-11",
778+
resultdate="2026-07-25",
741779
year=config.current_year,
742780
track=track,
743781
results=f"results_{config.current_year}",
@@ -786,7 +824,7 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
786824
virtual_datas = sq_generate_datas(config, virtual_scores, for_division, track)
787825

788826
# For each solver Compute virtual solver without the solver
789-
solvers = scores.select("division", "solver").unique()
827+
solvers = scores.select("division", "solver").unique().filter(pl.col("solver").is_in(config.competitive_solvers))
790828
virtual_without_solver_scores = (
791829
intersect(scores.rename({"solver": "other_solver"}), solvers, on=["division"])
792830
.filter(pl.col("solver") != pl.col("other_solver"))

0 commit comments

Comments
 (0)