326 with_domination_counts: bool =
False,
327 max_work: int |
None =
None,
329 """Return the Pareto front of the eligible rows of ``source`` over ``axes``.
331 ``source`` is whatever ``_rows.read_source`` takes: a path to a result JSON or a
332 rows JSONL, a parsed result dict or a parsed list of rows. ``axes`` is a list of at
333 least two ``{"name": <metric name as recorded>, "direction": "maximize" or
334 "minimize"}`` objects with distinct names; a direction has no default.
335 ``with_domination_counts`` adds, for every eligible id, how many eligible rows
336 dominate it (0 exactly on the front). ``max_work`` is a positive integer bound on
337 the admission figure rows read + N(N-1)/2; None means no bound.
339 Returns the ``pineforge-hpo-pareto-front/v1`` document (see the module text).
340 Raises ``ParetoError`` for every refusal; nothing is returned for a refused call.
342 names, directions = _check_axes(axes)
343 _check_options(with_domination_counts, max_work)
345 loaded = read_source(source)
346 check_context(loaded.rows)
347 except RowsError
as error:
348 raise _from_rows_error(error)
from None
349 rows = sorted(loaded.rows, key=
lambda row: row.trial_id)
350 rows_read = len(rows)
355 if not any(row.metrics
is not None and name
in row.metrics
for row
in rows)
359 "pareto_axis_unknown",
360 "an axis name is recorded in no row's metrics object",
364 involved: dict = {reason: set()
for reason
in _METRIC_REASONS}
368 unfit_names: set = set()
370 cells, absent, null, unfit = _axis_cells(row, names)
372 unfit_ids.append(row.trial_id)
373 unfit_names.update(unfit)
375 entries.append((row.trial_id, row.status, cells))
376 if row.status !=
"ok":
377 excluded.setdefault(f
"status_{row.status}", []).append(row.trial_id)
379 excluded.setdefault(
"metric_not_recorded", []).append(row.trial_id)
380 involved[
"metric_not_recorded"].update(absent)
382 excluded.setdefault(
"metric_unavailable", []).append(row.trial_id)
383 involved[
"metric_unavailable"].update(null)
385 eligible.append((row.trial_id, tuple(cells[name]
for name
in names)))
388 "pareto_input_invalid",
389 "an axis value is not a finite number, or null on an ok row",
391 [name
for name
in names
if name
in unfit_names],
393 digest = _input_digest(entries)
394 count = len(eligible)
397 f
"{reason} {len(excluded[reason])}" for reason
in sorted(excluded)
400 "pareto_no_eligible_rows",
401 f
"no row remains after exclusions ({rows_read} rows read: {reasons})"
403 else "no row remains after exclusions (the source holds no rows)",
405 pairs = count * (count - 1) // 2
406 work = rows_read + pairs
407 if max_work
is not None and work > max_work:
409 "pareto_resource_exceeded",
410 f
"the admission figure {work} (rows read {rows_read} + {pairs} pairs of "
411 f
"{count} eligible rows) is above max_work {max_work}",
413 ids = [trial_id
for trial_id, _
in eligible]
414 vectors = [vector
for _, vector
in eligible]
417 -value
if direction ==
"minimize" else value
418 for value, direction
in zip(vector, directions)
420 for vector
in vectors
422 dominated_by = [0] * count
423 for first
in range(count):
424 for second
in range(first + 1, count):
425 if _dominates(scored[first], scored[second]):
426 dominated_by[second] += 1
427 elif _dominates(scored[second], scored[first]):
428 dominated_by[first] += 1
429 front = [index
for index
in range(count)
if dominated_by[index] == 0]
430 excluded_document: dict = {}
431 for reason
in sorted(excluded):
432 entry: dict = {
"count": len(excluded[reason]),
"ids": excluded[reason]}
433 if reason
in involved:
434 entry[
"axes"] = [name
for name
in names
if name
in involved[reason]]
435 excluded_document[reason] = entry
437 "contract": CONTRACT,
440 {
"name": name,
"direction": direction}
441 for name, direction
in zip(names, directions)
443 "rows_read": rows_read,
444 "rows_eligible": count,
445 "rows_excluded": rows_read - count,
446 "excluded": excluded_document,
447 "pareto_front": [ids[index]
for index
in front],
448 "n_front": len(front),
449 "n_distinct_vectors_on_front": len({vectors[index]
for index
in front}),
451 if with_domination_counts:
452 document[
"domination_counts"] = {
453 ids[index]: dominated_by[index]
for index
in range(count)
455 document[
"algorithm"] = ALGORITHM
456 document[
"input_sha256"] = digest
457 document[
"interpretation"] = INTERPRETATION