@@ -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