"""Object containing the output of numerical expression calculation"""from__future__importannotationsfromdataclassesimportdataclassfromtypingimportProtocol,runtime_checkable,Sequence,TypeVarimportnumpyasnpGenericType=TypeVar('GenericType')
[docs]defconvert_to_dict(the_sequence:Sequence[GenericType],the_map:dict[str,int])->dict[str,GenericType]:"""Convert an indexed sequence into a dict mapping a name to the elements. :param the_sequence: sequence of element to convert :param the_map: dict mapping the names with their index in the sequence. :return: the sequence converted into dict """# Check for any index out of range errors before creating the dictionaryforname,indexinthe_map.items():ifindex>=len(the_sequence)orindex<0:raiseIndexError(f"Index {index} for variable '{name}' is out of range for sequence of length {len(the_sequence)}.")# Use dictionary comprehension to create the resultresult={name:the_sequence[index]forname,indexinthe_map.items()}returnresult
[docs]@dataclassclassFunctionOutput:"""Output of a function calculation"""function:floatgradient:np.ndarray|None=Nonehessian:np.ndarray|None=Nonebhhh:np.ndarray|None=None
[docs]@dataclassclassDisaggregateFunctionOutput:"""Output of a function calculation"""functions:np.ndarraygradients:np.ndarray|None=Nonehessians:np.ndarray|None=Nonebhhhs:np.ndarray|None=Nonedef__len__(self):returnlen(self.functions)
[docs]defunique_entry(self)->FunctionOutput|None:"""When there is only one entry, we generate the BiogemeFunctionOutput object"""iflen(self)==1:returnFunctionOutput(function=float(self.functions[0]),gradient=self.gradients[0]ifself.gradientsisnotNoneelseNone,hessian=self.hessians[0]ifself.hessiansisnotNoneelseNone,bhhh=self.bhhhs[0]ifself.bhhhsisnotNoneelseNone,)returnNone
[docs]@runtime_checkableclassNamedFunctionResult(Protocol):"""Protocol defining the interface for named function outputs."""function:floatgradient:dict[str,float]|Nonehessian:dict[str,dict[str,float]]|Nonebhhh:dict[str,dict[str,float]]|None
[docs]classNamedOutputMixin:"""Mixin providing common methods for named outputs."""def__repr__(self):return(f"<{self.__class__.__name__}: function={self.function}, "f"gradient={self.gradient}, hessian={self.hessian}, bhhh={self.bhhh}>")
[docs]@dataclassclassNamedFunctionOutput(NamedOutputMixin,NamedFunctionResult):"""Output of a function calculation, with names of variables"""function_output:FunctionOutputmapping:dict[str,int]function:float=Nonegradient:dict[str,float]|None=Nonehessian:dict[str,dict[str,float]]|None=Nonebhhh:dict[str,dict[str,float]]|None=Nonedef__post_init__(self):self.function=self.function_output.functionself.gradient=(Noneifself.function_output.gradientisNoneelseconvert_to_dict(self.function_output.gradient,self.mapping))self.hessian=(Noneifself.function_output.hessianisNoneelseconvert_to_dict([convert_to_dict(row,self.mapping)forrowinself.function_output.hessian],self.mapping,))self.bhhh=(Noneifself.function_output.bhhhisNoneelseconvert_to_dict([convert_to_dict(row,self.mapping)forrowinself.function_output.bhhh],self.mapping,))