@@ -94,6 +94,8 @@ def page_track_suffix(track: defs.Track) -> str:
9494
9595class 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
388438def 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
767818def 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
0 commit comments