Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cms/db/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ def score_type_object(self) -> "ScoreType":
from cms.grading.scoretypes import get_score_type
# This can raise.
self._cached_score_type_object = get_score_type(
self.score_type, self.score_type_parameters, public_testcases)
self.score_type, self.score_type_parameters, public_testcases,
self.task.score_precision)
# If an exception is raised these updates don't take place:
# that way, next time this property is accessed, we get a
# cache miss again and the same exception is raised again.
Expand Down
5 changes: 3 additions & 2 deletions cms/grading/scoretypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def get_score_type_class(name: str):


def get_score_type(
name: str, parameters: object, public_testcases: dict[str, bool]
name: str, parameters: object, public_testcases: dict[str, bool],
score_precision: int,
) -> ScoreType:
"""Construct the ScoreType specified by parameters.

Expand All @@ -63,4 +64,4 @@ def get_score_type(

"""
class_ = get_score_type_class(name)
return class_(parameters, public_testcases)
return class_(parameters, public_testcases, score_precision)
34 changes: 23 additions & 11 deletions cms/grading/scoretypes/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ class ScoreType(metaclass=ABCMeta):

TEMPLATE = ""

def __init__(self, parameters: object, public_testcases: dict[str, bool]):
def __init__(self, parameters: object, public_testcases: dict[str, bool],
score_precision: int):
"""Initializer.

parameters: format is specified in the subclasses.
Expand All @@ -70,6 +71,7 @@ def __init__(self, parameters: object, public_testcases: dict[str, bool]):
"""
self.parameters = parameters
self.public_testcases = public_testcases
self.score_precision = score_precision

# Preload the maximum possible scores.
try:
Expand All @@ -87,7 +89,6 @@ def format_score(
score: float,
max_score: float,
unused_score_details: object,
score_precision: int,
translation: Translation = DEFAULT_TRANSLATION,
) -> str:
"""Produce the string of the score that is shown in CWS.
Expand All @@ -102,16 +103,14 @@ def format_score(
max_score: the maximum score that can be achieved.
unused_score_details: the opaque data structure that
the ScoreType produced for the submission when scoring it.
score_precision: the maximum number of digits of the
fractional digits to show.
translation: the translation to use.

return: the message to show.

"""
return "%s / %s" % (
translation.format_decimal(round(score, score_precision)),
translation.format_decimal(round(max_score, score_precision)))
translation.format_decimal(score),
translation.format_decimal(max_score))

def get_html_details(
self,
Expand Down Expand Up @@ -348,9 +347,17 @@ class ScoreTypeGroup(ScoreTypeAlone):

def get_max_score(self, group_parameter: ScoreTypeGroupParameters) -> float:
if isinstance(group_parameter, tuple) or isinstance(group_parameter, list):
return group_parameter[0]
score = group_parameter[0]
else:
return group_parameter["max_score"]
score = group_parameter["max_score"]
assert (
round(
score,
self.score_precision
) == score
), (f"The max score for a subtask"
"has more precision than the task allows.")
return score

def get_testcases(self, group_parameter: ScoreTypeGroupParameters) -> int | str | list[str]:
if isinstance(group_parameter, tuple) or isinstance(group_parameter, list):
Expand Down Expand Up @@ -507,7 +514,7 @@ def compute_score(self, submission_result):
tc["show_in_oi_restricted_feedback"] = (
tc["idx"] == tc_first_lowest_idx)

score += st_score
score += rounded_score
subtasks.append({
"idx": st_idx,
# We store the fraction so that an "example" testcase
Expand All @@ -519,12 +526,17 @@ def compute_score(self, submission_result):
"max_score": self.get_max_score(parameter),
"testcases": testcases})
if all(self.public_testcases[tc_idx] for tc_idx in target):
public_score += st_score
public_score += rounded_score
public_subtasks.append(subtasks[-1])
else:
public_subtasks.append({"idx": st_idx,
"testcases": public_testcases})
ranking_details.append("%g" % st_score)
ranking_details.append("%g" % rounded_score)

# The following line should be unnecessary since subtask scores
# are rounded. However we are using floats not Decimals
# and this can cause errors. So we round again to be sure.
score = round(score, score_precision)

return score, subtasks, public_score, public_subtasks, ranking_details

Expand Down
10 changes: 6 additions & 4 deletions cms/grading/scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ def task_score(
task: Task,
public: bool = False,
only_tokened: bool = False,
rounded: bool = False,
) -> tuple[float, bool]:
"""Return the score of a contest's user on a task.

Expand All @@ -122,7 +121,6 @@ def task_score(
at the results of tokened submissions (that is, the score that the user
would obtain if all non-tokened submissions scored 0.0, or equivalently
had not been scored yet).
rounded: if True, round the score to the task's score_precision.

return: the score of user on task, and True if not
all submissions of the participation in the task have been scored.
Expand Down Expand Up @@ -177,8 +175,12 @@ def task_score(
score = _task_score_max_tokened_last(score_details_tokened)
else:
raise ValueError("Unknown score mode '%s'" % task.score_mode)
if rounded:
score = round(score, task.score_precision)

# The following line should be unnecessary since subtask scores
# are rounded. However we are using floats not Decimals
# and this can cause errors. So we round again to be sure.
score = round(score, task.score_precision)

return score, partial


Expand Down
2 changes: 1 addition & 1 deletion cms/server/admin/handlers/contestranking.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get(self, contest_id, format="online"):
total_score = 0.0
partial = False
for task in self.contest.tasks:
t_score, t_partial = task_score(p, task, rounded=True)
t_score, t_partial = task_score(p, task)
p.scores.append((t_score, t_partial))
total_score += t_score
partial = partial or t_partial
Expand Down
4 changes: 2 additions & 2 deletions cms/server/admin/templates/submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ <h2 id="title_details" class="toggling_on">Submission details</h2>
<td>Score as seen by user</td>
<td>
{% if s.token is not none %}
{{ st.format_score(sr.score, st.max_score, sr.score_details, s.task.score_precision) }}
{{ st.format_score(sr.score, st.max_score, sr.score_details) }}
{% else %}
{{ st.format_score(sr.public_score, st.max_public_score, sr.public_score_details, s.task.score_precision) }}
{{ st.format_score(sr.public_score, st.max_public_score, sr.public_score_details) }}
{% endif %}
</td>
</tr>
Expand Down
4 changes: 1 addition & 3 deletions cms/server/contest/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def format_token_rules(
return result


def get_score_class(score: float, max_score: float, score_precision: int) -> str:
def get_score_class(score: float, max_score: float) -> str:
"""Return a CSS class to visually represent the score/max_score

score: the score of the submission.
Expand All @@ -139,8 +139,6 @@ def get_score_class(score: float, max_score: float, score_precision: int) -> str
return: class name

"""
score = round(score, score_precision)
max_score = round(max_score, score_precision)
if score <= 0:
return "score_0"
elif score >= max_score:
Expand Down
24 changes: 10 additions & 14 deletions cms/server/contest/handlers/tasksubmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ def get(self, task_name):
)

public_score, is_public_score_partial = task_score(
participation, task, public=True, rounded=True)
participation, task, public=True)
tokened_score, is_tokened_score_partial = task_score(
participation, task, only_tokened=True, rounded=True)
participation, task, only_tokened=True)
# These two should be the same, anyway.
is_score_partial = is_public_score_partial or is_tokened_score_partial

Expand Down Expand Up @@ -221,9 +221,9 @@ def add_task_score(self, participation: Participation, task: Task, data: dict):
.options(joinedload(Submission.results))\
.all()
data["task_public_score"], public_score_is_partial = \
task_score(participation, task, public=True, rounded=True)
task_score(participation, task, public=True)
data["task_tokened_score"], tokened_score_is_partial = \
task_score(participation, task, only_tokened=True, rounded=True)
task_score(participation, task, only_tokened=True)
# These two should be the same, anyway.
data["task_score_is_partial"] = \
public_score_is_partial or tokened_score_is_partial
Expand Down Expand Up @@ -267,26 +267,22 @@ def get(self, task_name, opaque_id):

score_type = task.active_dataset.score_type_object
if score_type.max_public_score > 0:
data["max_public_score"] = \
round(score_type.max_public_score, task.score_precision)
data["max_public_score"] = score_type.max_public_score
if data["status"] == SubmissionResult.SCORED:
data["public_score"] = \
round(sr.public_score, task.score_precision)
data["public_score"] = sr.public_score
data["public_score_message"] = score_type.format_score(
sr.public_score, score_type.max_public_score,
sr.public_score_details, task.score_precision,
sr.public_score_details,
translation=self.translation)
if score_type.max_public_score < score_type.max_score:
data["max_score"] = \
round(score_type.max_score, task.score_precision)
data["max_score"] = score_type.max_score
if data["status"] == SubmissionResult.SCORED \
and (submission.token is not None
or self.r_params["actual_phase"] == 3):
data["score"] = \
round(sr.score, task.score_precision)
data["score"] = sr.score
data["score_message"] = score_type.format_score(
sr.score, score_type.max_score,
sr.score_details, task.score_precision,
sr.score_details,
translation=self.translation)

self.write(data)
Expand Down
8 changes: 4 additions & 4 deletions cms/server/contest/templates/macro/submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@
</td>
{% if score_type is defined and score_type.max_public_score > 0 %}
{% if status == SubmissionResult.SCORED %}
<td class="public_score {{ get_score_class(sr.public_score, score_type.max_public_score, task.score_precision) }}">
{{ score_type.format_score(sr.public_score, score_type.max_public_score, sr.public_score_details, task.score_precision, translation=translation) }}
<td class="public_score {{ get_score_class(sr.public_score, score_type.max_public_score) }}">
{{ score_type.format_score(sr.public_score, score_type.max_public_score, sr.public_score_details, translation=translation) }}
</td>
{% else %}
<td class="public_score undefined">
Expand All @@ -180,8 +180,8 @@
{% endif %}
{% if score_type is defined and score_type.max_public_score < score_type.max_score %}
{% if status == SubmissionResult.SCORED and (s.token is not none or actual_phase == 3) %}
<td class="total_score {{ get_score_class(sr.score, score_type.max_score, task.score_precision) }}">
{{ score_type.format_score(sr.score, score_type.max_score, sr.score_details, task.score_precision, translation=translation) }}
<td class="total_score {{ get_score_class(sr.score, score_type.max_score) }}">
{{ score_type.format_score(sr.score, score_type.max_score, sr.score_details, translation=translation) }}
</td>
{% else %}
<td class="total_score undefined">
Expand Down
8 changes: 4 additions & 4 deletions cms/server/contest/templates/task_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ <h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
{% if score_type.max_public_score > 0 %}
{# Show the public score (alone, if everything is public or tokens are disabled, or together with the tokened score). #}
<div id="task_score_public"
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(public_score, score_type.max_public_score, task.score_precision) }}">
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(public_score, score_type.max_public_score) }}">
<span>
{% if score_type.max_public_score == score_type.max_score %}
{% trans %}Score:{% endtrans %}
Expand All @@ -203,7 +203,7 @@ <h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
</span>
<br/>
<span class="score">
{{ score_type.format_score(public_score, score_type.max_public_score, none, task.score_precision, translation=translation) }}
{{ score_type.format_score(public_score, score_type.max_public_score, none, translation=translation) }}
{% if is_score_partial %}
<img src="{{ static_url("static", "loading.gif") }}" />
{% endif %}
Expand All @@ -214,7 +214,7 @@ <h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
{% if score_type.max_public_score < score_type.max_score %}
{# Show the tokened score (alone if everything is non-public, or together with the public score). #}
<div id="task_score_tokened"
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(tokened_score, score_type.max_score, task.score_precision) if can_use_tokens else "undefined" }}">
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(tokened_score, score_type.max_score) if can_use_tokens else "undefined" }}">
<span>
{% if can_use_tokens %}
{% trans %}Score of tokened submissions:{% endtrans %}
Expand All @@ -225,7 +225,7 @@ <h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
<br/>
<span class="score">
{% if can_use_tokens %}
{{ score_type.format_score(tokened_score, score_type.max_score, none, task.score_precision, translation=translation) }}
{{ score_type.format_score(tokened_score, score_type.max_score, none, translation=translation) }}
{% if is_score_partial %}
<img src="{{ static_url("static", "loading.gif") }}" />
{% endif %}
Expand Down
1 change: 0 additions & 1 deletion cms/service/ProxyService.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,6 @@ def operations_for_score(self, submission: Submission):

# This check is probably useless.
if submission_result is not None and submission_result.scored():
# We're sending the unrounded score to RWS
subchange_data["score"] = submission_result.score
subchange_data["extra"] = submission_result.ranking_score_details

Expand Down
Loading
Loading