Source code for biogeme.configuration

"""Represents the configuration of a multiple expression

:author: Michel Bierlaire
:date: Sun Apr  2 14:15:17 2023

"""

import logging
from typing import NamedTuple
import biogeme.exceptions as excep

logger = logging.getLogger(__name__)


[docs] class SelectionTuple(NamedTuple): controller: str selection: str
SEPARATOR = ';' SELECTION_SEPARATOR = ':'
[docs] class Configuration: """Represents the configuration of a multiple expression. It is internally represented as a sorted list of tuples. """
[docs] def __init__(self, selections=None): """Ctor. :param selections: list of tuples, where each of them associates a controller name with the selected configuration :type list_of_selections: list(SelectionTuple) """ if selections is None: self.__selections = None else: self.selections = selections
@property def selections(self): return self.__selections @selections.setter def selections(self, value): self.__selections = sorted(value) self.__check_list_validity() self.string_id = self.get_string_id()
[docs] @classmethod def from_string(cls, string_id): """Ctor from a string representation :param string_id: string ID :type string_id: str """ terms = string_id.split(SEPARATOR) the_config = {} for term in terms: try: controller, selection = term.split(SELECTION_SEPARATOR) except ValueError as exc: error_msg = ( f'{exc}: Invalid syntax for ID {term}. Expecting a separator ' f'[{SELECTION_SEPARATOR}]' ) raise excep.BiogemeError(error_msg) the_config[controller] = selection return cls.from_dict(the_config)
[docs] @classmethod def from_dict(cls, dict_of_selections): """Ctor from dict :param dict_of_selections: dict associating a catalog name with the selected configuration :type dict_of_selections: dict(str: str) """ the_list = ( SelectionTuple(controller=controller, selection=selection) for controller, selection in dict_of_selections.items() ) return cls(selections=the_list)
[docs] @classmethod def from_tuple_of_configurations(cls, tuple_of_configurations): """Ctor from tuple of configurations that are merged together. In the presence of two different selections for the same catalog, the first one is selected, and the others ignored. :param tuple_of_configurations: tuple of configurations to merge :type tuple_of_configurations: tuple(Configuration) """ known_controllers = set() selections = [] for configuration in tuple_of_configurations: if configuration.selections is not None: for selection in configuration.selections: if selection.controller not in known_controllers: selections.append(selection) known_controllers.add(selection.controller) return cls(selections)
[docs] def set_of_controllers(self): return {selection.controller for selection in self.selections}
[docs] def __eq__(self, other): return self.string_id == other.string_id
def __hash__(self): return hash(self.string_id) def __repr__(self): return repr(self.string_id) def __str__(self): return str(self.string_id)
[docs] def get_string_id(self): """The string ID is a unique string representation of the configuration :return: string ID :rtype: str """ terms = [ f'{selection.controller}{SELECTION_SEPARATOR}{selection.selection}' for selection in self.selections ] return SEPARATOR.join(terms)
[docs] def get_html(self): html = '<p>Specification</p><p><ul>\n' for selection_tuple in self.selections: html += ( f'<li>{selection_tuple.controller}: ' f'{selection_tuple.selection}</li>\n' ) html += '</ul></p>\n' return html
[docs] def get_selection(self, controller_name): """Retrieve the selection of a given controller :param controller_name: name of the controller :type controller_name: str :return: name of the selected config, or None if controller is not known :rtype: str """ for selection in self.selections: if selection.controller == controller_name: return selection.selection return None
def __check_list_validity(self): """Check the validity of the list. :raise BiogemeError: if the same catalog appears more than once in the list """ unique_items = set() for item in self.__selections: if item.controller in unique_items: error_msg = ( f'Controller {item.controller} appears more than once in the ' f'configuration: {self.__selections}' ) raise excep.BiogemeError(error_msg) unique_items.add(item.controller)