Skip to content

Commit d7f9105

Browse files
authored
An/fix scoreboard (#317)
<!-- Solution for PR template choice: https://stackoverflow.com/a/75030350/24543008 --> Please go to the `Preview` tab and select the appropriate template: * [Submit Student task (English)](?expand=1&template=task_submission_en.md) * [Submit Student task (Russian)](?expand=1&template=task_submission_ru.md)
1 parent db56615 commit d7f9105

File tree

27 files changed

+221
-102
lines changed

27 files changed

+221
-102
lines changed

scoreboard/data/points-info.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ processes:
1010
R: 2
1111
variants_max: 27
1212
Total: 12
13+
# Task 1: all variants are category A (simple array operations)
14+
perf_category: A
1315
- name: mpi_task_2
1416
mpi:
1517
- S: 12
@@ -19,6 +21,14 @@ processes:
1921
R: 3
2022
variants_max: 23
2123
Total: 23
24+
# Task 2 variant categories:
25+
# C: 1-10 (collective ops + topologies)
26+
# B: 11-21 (matrix computations, Gauss, iterative methods, sort)
27+
# A: 22-23 (image processing)
28+
perf_category_by_variant:
29+
default: B
30+
A: [22, 23]
31+
C: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2232
- name: mpi_task_3
2333
mpi:
2434
- S: 16
@@ -28,6 +38,14 @@ processes:
2838
R: 5
2939
variants_max: 32
3040
Total: 35
41+
# Task 3 variant categories:
42+
# A: 1-3 (Cannon/Fox/Strassen), 10 (Monte Carlo), 26-30 (image processing)
43+
# B: 4-9, 12, 14-21, 23-25, 31-32 (most algorithms)
44+
# C: 11, 13 (Strongin), 22 (Dijkstra)
45+
perf_category_by_variant:
46+
default: B
47+
A: [1, 2, 3, 10, 26, 27, 28, 29, 30]
48+
C: [11, 13, 22]
3149
threads:
3250
semester_total: 64
3351
variants_max: 30
@@ -58,6 +76,40 @@ threads:
5876
Total: 21
5977
efficiency:
6078
num_proc: 4
79+
# Performance category scales (efficiency % -> points %)
80+
# Category A: easily scalable tasks (>=50% = 100%)
81+
# Category B: moderately scalable tasks (>=40% = 100%)
82+
# Category C: communication-bound/sequential tasks (>=30% = 100%)
83+
scales:
84+
A:
85+
- [50, 100]
86+
- [45, 90]
87+
- [42, 80]
88+
- [40, 70]
89+
- [37, 60]
90+
- [35, 50]
91+
- [32, 40]
92+
- [30, 30]
93+
- [27, 20]
94+
- [25, 10]
95+
- [0, 0]
96+
B:
97+
- [40, 100]
98+
- [35, 90]
99+
- [30, 80]
100+
- [27, 70]
101+
- [25, 60]
102+
- [20, 40]
103+
- [15, 20]
104+
- [0, 0]
105+
C:
106+
- [30, 100]
107+
- [25, 90]
108+
- [20, 75]
109+
- [15, 50]
110+
- [10, 30]
111+
- [5, 10]
112+
- [0, 0]
61113
copying:
62114
coefficient: 0.5
63115
note: "Penalty C = -coefficient * S (scoreboard notation)"

scoreboard/main.py

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -322,43 +322,88 @@ def _find_performance_max(points_info, task_type: str) -> int:
322322
return 0
323323

324324

325-
def _calc_perf_points_from_efficiency(efficiency_str: str, max_points: int) -> float:
325+
def _get_perf_category(points_info, task_number: int, variant: int) -> str:
326+
"""Determine performance category (A, B, or C) for a given task and variant.
327+
328+
Categories determine which efficiency scale to use:
329+
A: easily scalable tasks (>=50% = 100%)
330+
B: moderately scalable tasks (>=40% = 100%)
331+
C: communication-bound/sequential tasks (>=30% = 100%)
332+
"""
333+
proc_tasks = (points_info.get("processes", {}) or {}).get("tasks", [])
334+
key = f"mpi_task_{task_number}"
335+
for t in proc_tasks:
336+
if str(t.get("name")) == key:
337+
# Check if task has a single category for all variants
338+
if "perf_category" in t:
339+
return str(t.get("perf_category", "A"))
340+
# Check if task has per-variant categories
341+
by_variant = t.get("perf_category_by_variant", {})
342+
if by_variant:
343+
# Check each category list
344+
for cat in ["A", "B", "C"]:
345+
variants_in_cat = by_variant.get(cat, [])
346+
if variant in variants_in_cat:
347+
return cat
348+
# Return default if not found in any list
349+
return str(by_variant.get("default", "A"))
350+
return "A" # Default to category A
351+
352+
353+
def _calc_perf_points_from_efficiency(
354+
efficiency_str: str, max_points: int, category: str = "A", scales: dict = None
355+
) -> float:
326356
"""Calculate Performance points as a real number (x.yy).
327357
328-
Mapping (eff -> percent of max):
329-
>=50 -> 100; [45,50) -> 90; [42,45) -> 80; [40,42) -> 70; [37,40) -> 60;
330-
[35,37) -> 50; [32,35) -> 40; [30,32) -> 30; [27,30) -> 20; [25,27) -> 10; <25 -> 0
331-
Returns a float rounded to 2 decimals (no ceil).
358+
Uses category-specific scales from points-info.yml:
359+
A: easily scalable (>=50% = 100%)
360+
B: moderately scalable (>=40% = 100%)
361+
C: communication-bound (>=30% = 100%)
362+
363+
Falls back to default scale A if scales not provided.
364+
Returns a float rounded to 2 decimals.
332365
"""
333366
if not isinstance(efficiency_str, str) or not efficiency_str.endswith("%"):
334367
return 0.0
335368
try:
336369
val = float(efficiency_str.rstrip("%"))
337370
except Exception:
338371
return 0.0
339-
perc = 0.0
340-
if val >= 50:
341-
perc = 1.0
342-
elif 45 <= val < 50:
343-
perc = 0.9
344-
elif 42 <= val < 45:
345-
perc = 0.8
346-
elif 40 <= val < 42:
347-
perc = 0.7
348-
elif 37 <= val < 40:
349-
perc = 0.6
350-
elif 35 <= val < 37:
351-
perc = 0.5
352-
elif 32 <= val < 35:
353-
perc = 0.4
354-
elif 30 <= val < 32:
355-
perc = 0.3
356-
elif 27 <= val < 30:
357-
perc = 0.2
358-
elif 25 <= val < 27:
359-
perc = 0.1
372+
373+
# Get scale for category (list of [threshold, percentage] pairs, sorted descending)
374+
if scales and category in scales:
375+
scale = scales[category]
376+
perc = 0.0
377+
for threshold, points_pct in scale:
378+
if val >= threshold:
379+
perc = points_pct / 100.0
380+
break
360381
else:
382+
# Fallback to default category A scale
361383
perc = 0.0
384+
if val >= 50:
385+
perc = 1.0
386+
elif 45 <= val < 50:
387+
perc = 0.9
388+
elif 42 <= val < 45:
389+
perc = 0.8
390+
elif 40 <= val < 42:
391+
perc = 0.7
392+
elif 37 <= val < 40:
393+
perc = 0.6
394+
elif 35 <= val < 37:
395+
perc = 0.5
396+
elif 32 <= val < 35:
397+
perc = 0.4
398+
elif 30 <= val < 32:
399+
perc = 0.3
400+
elif 27 <= val < 30:
401+
perc = 0.2
402+
elif 25 <= val < 27:
403+
perc = 0.1
404+
else:
405+
perc = 0.0
406+
362407
pts = max_points * perc if max_points > 0 else 0.0
363408
# round to 2 decimals (banker's rounding acceptable here)
364409
return round(pts, 2)
@@ -950,14 +995,15 @@ def _match_dir(csv_key: str) -> str | None:
950995
target = _match_dir(key)
951996
if target:
952997
targets.add(target)
953-
# 2) If key encodes processes_N, spread to all dirs with that task_number
954-
m_num = _re.search(r"processes_(\d+)", key)
955-
if m_num:
956-
try:
957-
num = int(m_num.group(1))
958-
targets.update(process_tasknum_map.get(num, []))
959-
except Exception:
960-
pass
998+
# 2) If key is for example_processes_N, map to that specific example directory only
999+
# (Do NOT spread to all dirs with that task_number - that was causing data duplication)
1000+
if "test_task_processes" in key or "example_processes" in key:
1001+
m_num = _re.search(r"processes_(\d+)", key)
1002+
if m_num:
1003+
suffix = m_num.group(1)
1004+
example_dir = f"example_processes_{suffix}"
1005+
if example_dir in directories:
1006+
targets.add(example_dir)
9611007
# 3) Fallback: if nothing matched and "threads" in key, apply to example_threads only
9621008
if not targets and "threads" in key:
9631009
if "example_threads" in directories:
@@ -1109,9 +1155,30 @@ def _build_process_rows(processes_dirs: list[str]):
11091155
has_seq = status_seq in ("done", "disabled")
11101156
report_present = (tasks_dir / d / "report.md").exists()
11111157

1158+
# Get student's variant for this task to determine perf category
1159+
vmax = _find_process_variants_max(cfg, n)
1160+
try:
1161+
variant_idx = assign_variant(
1162+
surname=str(student.get("last_name", "")),
1163+
name=str(student.get("first_name", "")),
1164+
patronymic=str(student.get("middle_name", "")),
1165+
group=str(student.get("group_number", "")),
1166+
repo=f"{REPO_SALT}/processes/task-{n}",
1167+
num_variants=vmax,
1168+
)
1169+
variant = variant_idx + 1 # 1-based
1170+
except Exception:
1171+
variant = 1
1172+
1173+
# Get performance category and scales
1174+
perf_category = _get_perf_category(cfg, n, variant)
1175+
perf_scales = (cfg.get("efficiency", {}) or {}).get("scales", {})
1176+
11121177
mpi_eff = group_cells[0].get("efficiency", "N/A")
11131178
perf_points_mpi = (
1114-
_calc_perf_points_from_efficiency(mpi_eff, a_mpi)
1179+
_calc_perf_points_from_efficiency(
1180+
mpi_eff, a_mpi, category=perf_category, scales=perf_scales
1181+
)
11151182
if (status_mpi == "done" and status_seq == "done")
11161183
else 0
11171184
)

tasks/egashin_k_lexicographical_check/info.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"last_name": "Егашин",
55
"middle_name": "Олегович",
66
"group_number": "3823Б1ФИ2",
7-
"task_number": "26"
7+
"task_number": 1
88
}
9-
}
9+
}
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
{
2-
"student": {
3-
"first_name": "Егорова",
4-
"last_name": "Лариса",
5-
"middle_name": "Алексеевна",
6-
"group_number": "3823Б1ФИ1",
7-
"task_number": "16"
8-
}
9-
}
1+
{
2+
"student": {
3+
"first_name": "Лариса",
4+
"last_name": "Егорова",
5+
"middle_name": "Алексеевна",
6+
"group_number": "3823Б1ФИ1",
7+
"task_number": 1
8+
}
9+
}

tasks/kondakov_v_global_search/info.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"last_name": "Кондаков",
55
"middle_name": "Сергеевич",
66
"group_number": "3823Б1ФИ1",
7-
"task_number": "11"
7+
"task_number": 3
88
}
9-
}
9+
}

tasks/kondakov_v_min_val_in_matrix_str/info.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"last_name": "Кондаков",
55
"middle_name": "Сергеевич",
66
"group_number": "3823Б1ФИ1",
7-
"task_number": "17"
7+
"task_number": 1
88
}
9-
}
9+
}

tasks/kutergin_a_closest_pair/info.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"last_name": "Кутергин",
55
"middle_name": "Андреевич",
66
"group_number": "3823Б1ФИ1",
7-
"task_number": "7"
7+
"task_number": 1
88
}
9-
}
9+
}

tasks/kutergin_v_linear_contrast_stretching/info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"first_name": "Валентин",
44
"last_name": "Кутергин",
55
"middle_name": "Тимофеевич",
6-
"group_number": "3823Б1ФИ3 ",
6+
"group_number": "3823Б1ФИ3",
77
"task_number": "3"
88
}
99
}

tasks/kutergin_v_reduce/info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"first_name": "Валентин",
44
"last_name": "Кутергин",
55
"middle_name": "Тимофеевич",
6-
"group_number": "3823Б1ФИ3 ",
6+
"group_number": "3823Б1ФИ3",
77
"task_number": "2"
88
}
99
}

tasks/kutergin_v_trapezoid_method_of_integration/info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"first_name": "Валентин",
44
"last_name": "Кутергин",
55
"middle_name": "Тимофеевич",
6-
"group_number": "3823Б1ФИ3 ",
6+
"group_number": "3823Б1ФИ3",
77
"task_number": "1"
88
}
99
}

0 commit comments

Comments
 (0)