@@ -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- ]
276+
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
262283
263284
264- def make_podium (config : defs .Config , d : dict [str , Any ], for_division : bool , track : defs .Track ) -> PodiumDivision :
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,15 +351,15 @@ 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
311361def sq_generate_datas (
312- config : defs .Config , results : pl .LazyFrame , for_division : bool , track : defs .Track
362+ config : defs .Config , selection : pl . LazyFrame , results : pl .LazyFrame , for_division : bool , track : defs .Track
313363) -> dict [str , PodiumDivision ]:
314364 """
315365 Generate datas for divisions or for logics
@@ -322,12 +372,13 @@ def sq_generate_datas(
322372 group_by = "logic"
323373 name_of_int = defs .Logic .name_of_int
324374
375+ selection = selection .filter (selected = True )
376+
325377 # TODO it should be done after filter_for
326- len_by_division = results .group_by (group_by ).agg (total = pl .col ( "file" ). n_unique ())
378+ len_by_division = selection .group_by (group_by ).agg (total = pl .len ())
327379
328380 def info_for_podium_step (kind : smtcomp .scoring .Kind , config : defs .Config , results : pl .LazyFrame ) -> pl .LazyFrame :
329381 results = smtcomp .scoring .filter_for (kind , config , results )
330-
331382 return (
332383 sort (
333384 intersect (results , len_by_division , on = [group_by ])
@@ -368,8 +419,8 @@ def info_for_podium_step(kind: smtcomp.scoring.Kind, config: defs.Config, result
368419
369420 if for_division :
370421 lf_logics = [
371- results .group_by ("division" , "logic" )
372- .agg (n = pl .col ( "file" ). n_unique ())
422+ selection .group_by ("division" , "logic" )
423+ .agg (n = pl .len ())
373424 .group_by ("division" )
374425 .agg (logics = pl .struct ("logic" , "n" ))
375426 ]
@@ -382,7 +433,7 @@ def info_for_podium_step(kind: smtcomp.scoring.Kind, config: defs.Config, result
382433
383434 df = r .collect ()
384435
385- return dict ((name_of_int (d [group_by ]), make_podium (config , d , for_division , track )) for d in df .to_dicts ())
436+ return dict ((name_of_int (d [group_by ]), make_podium (config , d , for_division , track , results )) for d in df .to_dicts ())
386437
387438
388439def get_kind (a : PodiumDivision , k : smtcomp .scoring .Kind ) -> list [PodiumStep ]:
@@ -471,7 +522,7 @@ def get_winner(l: List[PodiumStepBiggestLead] | None) -> str:
471522 winner_seq = get_winner (sequential )
472523
473524 return PodiumBiggestLead (
474- resultdate = "2024-07-08 " ,
525+ resultdate = "2025-08-11 " ,
475526 year = config .current_year ,
476527 track = track ,
477528 results = f"results_{ config .current_year } " ,
@@ -589,7 +640,7 @@ def get_winner(
589640 winner_seq = get_winner (sequential , scores , data , track )
590641
591642 return PodiumBestOverall (
592- resultdate = "2024-07-08 " ,
643+ resultdate = "2025-08-11 " ,
593644 year = config .current_year ,
594645 track = track ,
595646 results = f"results_{ config .current_year } " ,
@@ -684,7 +735,7 @@ def timeScore(vws_step: PodiumStep) -> float:
684735 steps_seq = ld [smtcomp .scoring .Kind .seq ]
685736
686737 return PodiumLargestContribution (
687- resultdate = "2024-07-08 " ,
738+ resultdate = "2025-08-11 " ,
688739 year = config .current_year ,
689740 track = track ,
690741 results = f"results_{ config .current_year } " ,
@@ -702,7 +753,9 @@ def timeScore(vws_step: PodiumStep) -> float:
702753 )
703754
704755
705- def largest_contribution (config : defs .Config , scores : pl .LazyFrame , track : defs .Track ) -> PodiumLargestContribution :
756+ def largest_contribution (
757+ config : defs .Config , selection : pl .LazyFrame , scores : pl .LazyFrame , track : defs .Track
758+ ) -> PodiumLargestContribution :
706759 for_division = True
707760 # For each solver compute its corresponding best solver
708761 # TODO: check what is competitive solver (unsound?)
@@ -727,11 +780,10 @@ 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 )
734- virtual_datas = sq_generate_datas (config , virtual_scores , for_division , track )
786+ virtual_datas = sq_generate_datas (config , selection , virtual_scores , for_division , track )
735787
736788 # For each solver Compute virtual solver without the solver
737789 solvers = scores .select ("division" , "solver" ).unique ()
@@ -749,10 +801,11 @@ 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 )
755- virtual_without_solver_datas = sq_generate_datas (config , virtual_without_solver_scores , for_division , track )
806+ virtual_without_solver_datas = sq_generate_datas (
807+ config , selection , virtual_without_solver_scores , for_division , track
808+ )
756809
757810 large = largest_contribution_ranking (config , virtual_datas , virtual_without_solver_datas , ratio_by_division , track )
758811
@@ -764,8 +817,7 @@ def largest_contribution(config: defs.Config, scores: pl.LazyFrame, track: defs.
764817 return large
765818
766819
767- def export_results (config : defs .Config , results : pl .LazyFrame , track : defs .Track ) -> None :
768-
820+ def export_results (config : defs .Config , selection : pl .LazyFrame , results : pl .LazyFrame , track : defs .Track ) -> None :
769821 page_suffix = page_track_suffix (track )
770822
771823 dst = config .web_results
@@ -780,7 +832,7 @@ def export_results(config: defs.Config, results: pl.LazyFrame, track: defs.Track
780832 all_divisions : list [PodiumDivision ] = []
781833
782834 for for_division in [True , False ]:
783- datas = sq_generate_datas (config , scores , for_division , track )
835+ datas = sq_generate_datas (config , selection , scores , for_division , track )
784836
785837 for name , data in datas .items ():
786838 (dst / f"{ name .lower ()} -{ page_suffix } .md" ).write_text (data .model_dump_json (indent = 1 ))
@@ -795,7 +847,7 @@ def export_results(config: defs.Config, results: pl.LazyFrame, track: defs.Track
795847 bigdata = biggest_lead_ranking (config , datas , track )
796848 (dst / f"biggest-lead-{ page_suffix } .md" ).write_text (bigdata .model_dump_json (indent = 1 ))
797849
798- largedata = largest_contribution (config , scores , track )
850+ largedata = largest_contribution (config , selection , scores , track )
799851 (dst / f"largest-contribution-{ page_suffix } .md" ).write_text (largedata .model_dump_json (indent = 1 ))
800852
801853 all_divisions .sort (key = lambda x : x .division )
0 commit comments