Source code for sherpa_ai.output_parsers.self_consistency.concretizer
import copy
from abc import ABC, abstractmethod
from typing import Any, Union, Dict, Optional
import pydash
from pydantic import BaseModel
from sherpa_ai.output_parsers.self_consistency.abstract_objects import AbstractObject
from sherpa_ai.output_parsers.self_consistency.distributions import Distribution, CountDistribution
from sherpa_ai.output_parsers.self_consistency.config import SelfConsistencyConfig
class Concretizer(ABC):
@abstractmethod
def concretize(
self, abstract_object: AbstractObject, return_dict: bool = False
) -> Union[BaseModel, dict[str, Any]]:
"""
Concretize an abstract object into a concrete object.
Args:
abstract_object (AbstractObject): The abstract object to concretize.
Returns:
BaseModel: A concrete object that matches the schema and is sampled from
the distributions in the abstract object.
"""
pass
[docs]
class MaximumLikelihoodConcretizer(Concretizer):
def __init__(self, config: Optional[SelfConsistencyConfig] = None):
"""
Initialize the MaximumLikelihoodConcretizer.
Args:
config: Configuration for self-consistency processing.
If None, default configuration will be used.
"""
self.config = config or SelfConsistencyConfig()
[docs]
def concretize(
self, abstract_object: AbstractObject, return_dict: bool = False
) -> Union[BaseModel, dict[str, Any]]:
"""
Concretize an abstract object by selecting the most likely value for each attribute.
For list attributes, uses top-k or threshold-based selection.
Args:
abstract_object (AbstractObject): The abstract object to concretize.
Returns:
BaseModel: A concrete object with the most likely values for each attribute.
""" # noqa: E501
def get_most_likely_value(value, field_path=""):
if isinstance(value, CountDistribution):
# Handle list attributes
field_config = self.config.get_list_config(field_path)
strategy = field_config.strategy
if strategy == "top_k":
top_k = field_config.top_k
if top_k > 0:
return value.get_top_k(top_k)
else:
# Default to top-1 for backward compatibility
return [value.get_mode()]
elif strategy == "threshold":
threshold = field_config.threshold
return value.get_above_threshold(threshold)
else:
# Default to top-1 for backward compatibility
return [value.get_mode()]
elif isinstance(value, Distribution):
return value.get_mode()
else:
return value
concrete_obj = copy.deepcopy(abstract_object.obj_dict)
# Use a custom function to handle field paths for list configuration
def map_values_with_path(obj, path=""):
if isinstance(obj, dict):
result = {}
for key, value in obj.items():
current_path = f"{path}.{key}" if path else key
if isinstance(value, dict):
result[key] = map_values_with_path(value, current_path)
else:
result[key] = get_most_likely_value(value, current_path)
return result
else:
return get_most_likely_value(obj, path)
concrete_obj = map_values_with_path(concrete_obj)
if return_dict:
return concrete_obj
else:
return abstract_object.obj_schema.model_validate(concrete_obj)