"""Defines a catalog of expressions that may be considered in a specificationMichel BierlaireWed Apr 16 18:35:02 2025"""from__future__importannotationsimportloggingfromtypingimportIteratorfrombiogeme.exceptionsimportBiogemeErrorfrombiogeme.expressionsimport(Expression,MultipleExpression,NamedExpression,validate_and_convert,)from.controllerimportControllerlogger=logging.getLogger(__name__)
[docs]classCatalog(MultipleExpression):"""Catalog of expressions that are interchangeable. Only one of them defines the specification. They are designed to be modified algorithmically by a controller. """def__init__(self,catalog_name:str,named_expressions:list[NamedExpression],controlled_by:Controller|None=None,):"""Ctor :param catalog_name: name of the catalog of expressions :param named_expressions: list of NamedExpression, each containing a name and an expression. :param controlled_by: Object controlling the selection of the specifications. :raise BiogemeError: if list_of_named_expressions is empty :raise BiogemeError: if incompatible Controller """super().__init__(catalog_name)ifnotnamed_expressions:raiseBiogemeError(f'{catalog_name}: cannot create a catalog from an empty list.')ifcontrolled_byandnotisinstance(controlled_by,Controller):error_msg=(f'The controller must be of type Controller and not 'f'{type(controlled_by)}')raiseBiogemeError(error_msg)self.named_expressions=[NamedExpression(name=named.name,expression=validate_and_convert(named.expression),)fornamedinnamed_expressions]# Declare the expressions as children of the catalogfornamed_expressioninself.named_expressions:self.children.append(named_expression.expression)names=[named_expr.namefornamed_exprinself.named_expressions]ifcontrolled_byisNone:controller_name=catalog_nameself.controlled_by=Controller(controller_name=controller_name,specification_names=names)else:self.controlled_by=controlled_bycontroller_names=list(controlled_by.specification_names)ifnames!=controller_names:error_msg=(f'Incompatible IDs between catalog [{names}] and controller 'f'[{controller_names}]')raiseBiogemeError(error_msg)
[docs]defget_all_controllers(self)->set[Controller]:"""Provides all controllers controlling the specifications of a multiple expression :return: a set of controllers :rtype: set(biogeme.controller.Controller) """all_controllers={self.controlled_by}foreinself.children:all_controllers|=e.get_all_controllers()returnall_controllers
[docs]@classmethoddeffrom_dict(cls,catalog_name:str,dict_of_expressions:dict[str,Expression],controlled_by:Controller|None=None,)->Catalog:"""Ctor using a dict instead of a list. Python used not to guarantee the order of elements of a dict, although, in practice, it is always preserved. If the order is critical, it is better to use the main constructor. If not, this constructor provides a more readable code. :param catalog_name: name of the catalog :param dict_of_expressions: dict associating the name of an expression and the expression itself. :param controlled_by: Object controlling the selection of the specifications. """named_expressions=[NamedExpression(name=name,expression=expression)forname,expressionindict_of_expressions.items()]returncls(catalog_name=catalog_name,named_expressions=named_expressions,controlled_by=controlled_by,)
def__iter__(self)->Iterator[NamedExpression]:"""Obtain an iterator on the named expressions"""returnself.get_iterator()
[docs]defget_iterator(self)->Iterator[NamedExpression]:"""Obtain an iterator on the named expressions"""returniter(self.named_expressions)
[docs]defselected(self)->NamedExpression:"""Return the selected expression and its name :return: the name and the selected expression :rtype: NamedExpression """returnself.named_expressions[self.controlled_by.current_index]