"""Defines controllers for multiple expressions.:author: Michel Bierlaire:date: Sun Jul 16 15:23:46 2023"""from__future__importannotationsimportloggingfromtypingimportTYPE_CHECKING,Iterable,Callablefrombiogeme.expressionsimportSELECTION_SEPARATORfrombiogeme.exceptionsimportBiogemeErrorfrom.configurationimport(Configuration,)ifTYPE_CHECKING:frombiogeme.catalogimportCatalogControllerOperator=Callable[[Configuration,int],tuple[Configuration,int],]logger=logging.getLogger(__name__)
[docs]classController:"""Class controlling the specification of the Catalogs"""def__init__(self,controller_name:str,specification_names:Iterable[str]):"""Constructor :param controller_name: name of the controller :type controller_name: str :param specification_names: list or tuple of the names of the specification controlled by the controller :type specification_names: list(str) or tuple(str) """self.controller_name:str=controller_nameself.specification_names:tuple[str,...]=tuple(specification_names)self.current_index:int=0self.dict_of_index:dict[str,int]={name:indexforindex,nameinenumerate(self.specification_names)}self.controlled_catalogs:list[Catalog]=[]
[docs]defall_configurations(self)->set[str]:"""Return the code of all configurations :return: set of codes :rtype: set(str) """return{f"{self.controller_name}{SELECTION_SEPARATOR}{specification}"forspecificationinself.specification_names}
[docs]defcontroller_size(self)->int:"""Number of specifications managed by this controller"""returnlen(self.specification_names)
[docs]defcurrent_name(self)->str:"""Name of the currently selected expression"""returnself.specification_names[self.current_index]
[docs]defset_name(self,name:str)->None:"""Set the index of the controller based on the name of the specification :param name: name of the specification :type name: str """the_index=self.dict_of_index.get(name)ifthe_indexisNone:error_msg=(f"{name}: unknown specification for controller {self.controller_name}")raiseBiogemeError(error_msg)self.set_index(the_index)
[docs]defset_index(self,index:int)->None:"""Set the index of the controller, and update the controlled catalogs :param index: value of the index :type index: int :raises BiogemeError: if index is out of range """ifindex<0orindex>=self.controller_size():error_msg=(f"Wrong index {index} for controller {self.controller_name}. "f"Must be in [0, {self.controller_size()}]")raiseBiogemeError(error_msg)self.current_index=indexforcataloginself.controlled_catalogs:catalog.current_index=index
[docs]defreset_selection(self)->None:"""Select the first specification"""self.set_index(0)
[docs]defmodify_controller(self,step:int,circular:bool)->int:"""Modify the specification of the controller :param step: increment of the modifications. Can be negative. :type step: int :param circular: If True, the modification is always made. If the selection needs to move past the last one, it comes back to the first one. For instance, if the catalog is currently at its last value, and the step is 1, it is set to its first value. If circular is False, and the selection needs to move past the last one, the selection is set to the last one. It works symmetrically if the step is negative :type circular: bool :return: number of actual modifications :rtype: int """the_size=self.controller_size()new_index=self.current_index+stepifcircular:self.set_index(new_index%the_size)returnstepifnew_index<0:total_modif=self.current_indexself.set_index(0)returntotal_modififnew_index>=the_size:total_modif=the_size-1-self.current_indexself.set_index(the_size-1)returntotal_modifself.set_index(new_index)returnstep