Source code for biogeme.function_output

"""Object containing the output of numerical expression  calculation"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Protocol, runtime_checkable, Sequence, TypeVar

import numpy as np

GenericType = TypeVar('GenericType')


[docs] def convert_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 dictionary for name, index in the_map.items(): if index >= len(the_sequence) or index < 0: raise IndexError( f"Index {index} for variable '{name}' is out of range for sequence of length {len(the_sequence)}." ) # Use dictionary comprehension to create the result result = {name: the_sequence[index] for name, index in the_map.items()} return result
[docs] @dataclass class FunctionOutput: """Output of a function calculation""" function: float gradient: np.ndarray | None = None hessian: np.ndarray | None = None bhhh: np.ndarray | None = None
[docs] @dataclass class DisaggregateFunctionOutput: """Output of a function calculation""" functions: np.ndarray gradients: np.ndarray | None = None hessians: np.ndarray | None = None bhhhs: np.ndarray | None = None def __len__(self): return len(self.functions)
[docs] def unique_entry(self) -> FunctionOutput | None: """When there is only one entry, we generate the BiogemeFunctionOutput object""" if len(self) == 1: return FunctionOutput( function=float(self.functions[0]), gradient=self.gradients[0] if self.gradients is not None else None, hessian=self.hessians[0] if self.hessians is not None else None, bhhh=self.bhhhs[0] if self.bhhhs is not None else None, ) return None
[docs] @runtime_checkable class NamedFunctionResult(Protocol): """Protocol defining the interface for named function outputs.""" function: float gradient: dict[str, float] | None hessian: dict[str, dict[str, float]] | None bhhh: dict[str, dict[str, float]] | None
[docs] class NamedOutputMixin: """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] @dataclass class NamedFunctionOutput(NamedOutputMixin, NamedFunctionResult): """Output of a function calculation, with names of variables""" function_output: FunctionOutput mapping: dict[str, int] function: float = None gradient: dict[str, float] | None = None hessian: dict[str, dict[str, float]] | None = None bhhh: dict[str, dict[str, float]] | None = None def __post_init__(self): self.function = self.function_output.function self.gradient = ( None if self.function_output.gradient is None else convert_to_dict(self.function_output.gradient, self.mapping) ) self.hessian = ( None if self.function_output.hessian is None else convert_to_dict( [ convert_to_dict(row, self.mapping) for row in self.function_output.hessian ], self.mapping, ) ) self.bhhh = ( None if self.function_output.bhhh is None else convert_to_dict( [ convert_to_dict(row, self.mapping) for row in self.function_output.bhhh ], self.mapping, ) )