Skip to content

Commit e337367

Browse files
committed
reporting updates
1 parent 2209a20 commit e337367

8 files changed

Lines changed: 825 additions & 276 deletions

File tree

templates/_shared/objectives.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,128 @@ def build_best_record(
387387
record["scalarization_policy"] = scalarization_policy(objective_cfg)
388388
record["objective_vector"] = vector
389389
return record
390+
391+
392+
def objective_config_snapshot(objective_cfg: JSONDict) -> JSONDict:
393+
primary = _require_object(
394+
objective_cfg.get("primary_objective"),
395+
field_name="objective_cfg.primary_objective",
396+
)
397+
secondary = [
398+
_require_object(item, field_name=f"objective_cfg.secondary_objectives[{index}]")
399+
for index, item in enumerate(objective_cfg.get("secondary_objectives", []))
400+
]
401+
objectives = [
402+
_require_object(item, field_name=f"objective_cfg.objectives[{index}]")
403+
for index, item in enumerate(objective_cfg.get("objectives", []))
404+
]
405+
scalarization = _require_object(
406+
objective_cfg.get("scalarization"),
407+
field_name="objective_cfg.scalarization",
408+
)
409+
return {
410+
"primary_objective": dict(primary),
411+
"secondary_objectives": [dict(item) for item in secondary],
412+
"objectives": [dict(item) for item in objectives],
413+
"objective_names": objective_names(objective_cfg),
414+
"scalarization": dict(scalarization),
415+
}
416+
417+
418+
def nullable_objective_vector(raw_objectives: Any, objective_cfg: JSONDict) -> JSONDict:
419+
names = objective_names(objective_cfg)
420+
if raw_objectives is None:
421+
return {name: None for name in names}
422+
423+
objectives = _require_object(raw_objectives, field_name="objectives")
424+
missing = [name for name in names if name not in objectives]
425+
if missing:
426+
raise ValueError(f"objectives missing configured names {missing}")
427+
428+
extras = sorted(set(objectives) - set(names))
429+
if extras:
430+
raise ValueError(f"objectives include unknown names {extras}")
431+
432+
out: JSONDict = {}
433+
for name in names:
434+
value = objectives.get(name)
435+
out[name] = (
436+
None
437+
if value is None
438+
else _numeric_objective_value(value, field_name=f"objectives.{name}")
439+
)
440+
return out
441+
442+
443+
def build_objective_metadata(raw_objectives: Any, objective_cfg: JSONDict) -> JSONDict:
444+
vector = nullable_objective_vector(raw_objectives, objective_cfg)
445+
primary_name = primary_objective_name(objective_cfg)
446+
metadata: JSONDict = {
447+
"objective_name": primary_name,
448+
"objective_value": vector[primary_name],
449+
"objective_vector": vector,
450+
"scalarized_objective": None,
451+
}
452+
453+
if all(value is not None for value in vector.values()):
454+
canonical = {name: float(value) for name, value in vector.items()}
455+
metadata["scalarized_objective"] = float(scalarize_objectives(canonical, objective_cfg))
456+
457+
if (
458+
len(objective_names(objective_cfg)) > 1
459+
or scalarization_policy(objective_cfg) != _PRIMARY_ONLY_POLICY
460+
):
461+
metadata["scalarization_policy"] = scalarization_policy(objective_cfg)
462+
return metadata
463+
464+
465+
def _transformed_vector(raw_objectives: Any, objective_cfg: JSONDict) -> JSONDict:
466+
vector = canonical_objective_vector(raw_objectives, objective_cfg)
467+
return {
468+
str(objective["name"]): _transformed_value(
469+
float(vector[str(objective["name"])]),
470+
direction=str(objective["direction"]),
471+
)
472+
for objective in objective_descriptors(objective_cfg)
473+
}
474+
475+
476+
def _dominates(left: JSONDict, right: JSONDict, objective_cfg: JSONDict) -> bool:
477+
transformed_left = _transformed_vector(left, objective_cfg)
478+
transformed_right = _transformed_vector(right, objective_cfg)
479+
names = objective_names(objective_cfg)
480+
return all(
481+
float(transformed_left[name]) <= float(transformed_right[name]) for name in names
482+
) and any(float(transformed_left[name]) < float(transformed_right[name]) for name in names)
483+
484+
485+
def pareto_front_records(records: list[JSONDict], objective_cfg: JSONDict) -> list[JSONDict]:
486+
prepared: list[tuple[JSONDict, JSONDict]] = []
487+
for index, record in enumerate(records):
488+
if not isinstance(record, dict):
489+
raise ValueError(f"records[{index}] must be an object")
490+
trial_id = record.get("trial_id")
491+
if not isinstance(trial_id, int):
492+
raise ValueError(f"records[{index}].trial_id must be an integer")
493+
prepared.append(
494+
(record, canonical_objective_vector(record.get("objectives"), objective_cfg))
495+
)
496+
497+
frontier: list[tuple[JSONDict, JSONDict]] = []
498+
for index, (record, vector) in enumerate(prepared):
499+
dominated = any(
500+
_dominates(other_vector, vector, objective_cfg)
501+
for other_index, (_, other_vector) in enumerate(prepared)
502+
if other_index != index
503+
)
504+
if not dominated:
505+
frontier.append((record, vector))
506+
507+
frontier.sort(
508+
key=lambda item: best_rank_key(
509+
item[1],
510+
objective_cfg,
511+
trial_id=int(item[0]["trial_id"]),
512+
)
513+
)
514+
return [record for record, _ in frontier]

0 commit comments

Comments
 (0)