Source code for biogeme.expressions.beta_parameters

""" Representation of unknown parameters

:author: Michel Bierlaire
:date: Sat Apr 20 14:54:16 2024

"""

from __future__ import annotations

import logging

from biogeme.deprecated import deprecated
from biogeme.exceptions import BiogemeError
from biogeme.expressions import TypeOfElementaryExpression
from biogeme.expressions.elementary_expressions import Elementary


logger = logging.getLogger(__name__)


[docs] class Beta(Elementary): """ Unknown parameters to be estimated from data. """ def __init__( self, name: str, value: float, lowerbound: float | None, upperbound: float | None, status: int, ): """Constructor :param name: name of the parameter. :param value: default value. :param lowerbound: if different from None, imposes a lower bound on the value of the parameter during the optimization. :param upperbound: if different from None, imposes an upper bound on the value of the parameter during the optimization. :param status: if different from 0, the parameter is fixed to its default value, and not modified by the optimization algorithm. :raise BiogemeError: if the first parameter is not a str. :raise BiogemeError: if the second parameter is not an int or a float. """ if not isinstance(value, (int, float)): error_msg = ( f"The second parameter for {name} must be " f"a float and not a {type(value)}: {value}" ) raise BiogemeError(error_msg) if not isinstance(name, str): error_msg = ( f"The first parameter must be a string and " f"not a {type(name)}: {name}" ) raise BiogemeError(error_msg) Elementary.__init__(self, name) self.initValue = value self.lb = lowerbound self.ub = upperbound self.status = status self.betaId = None
[docs] def set_id_manager(self, id_manager: 'IdManager | None'): """The ID manager contains the IDs of the elementary expressions. It is externally created, as it may need to coordinate the numbering of several expressions. It is stored only in the expressions of type Elementary. :param id_manager: ID manager to be propagated to the elementary expressions. If None, all the IDs are set to None. :type id_manager: class IdManager """ self.id_manager = id_manager if id_manager is None: self.elementaryIndex = None self.betaId = None return self.elementaryIndex = self.id_manager.elementary_expressions.indices[self.name] if self.status != 0: self.betaId = self.id_manager.fixed_betas.indices[self.name] else: self.betaId = self.id_manager.free_betas.indices[self.name]
def __str__(self) -> str: return ( f"Beta('{self.name}', {self.initValue}, {self.lb}, " f"{self.ub}, {self.status})" )
[docs] def fix_betas( self, beta_values: dict[str, float], prefix: str | None = None, suffix: str | None = None, ): """Fix all the values of the Beta parameters appearing in the dictionary :param beta_values: dictionary containing the betas to be fixed (as key) and their value. :type beta_values: dict(str: float) :param prefix: if not None, the parameter is renamed, with a prefix defined by this argument. :type prefix: str :param suffix: if not None, the parameter is renamed, with a suffix defined by this argument. :type suffix: str """ if self.name in beta_values: self.initValue = beta_values[self.name] self.status = 1 if prefix is not None: self.name = f"{prefix}{self.name}" if suffix is not None: self.name = f"{self.name}{suffix}"
[docs] def dict_of_elementary_expression( self, the_type: TypeOfElementaryExpression ) -> dict[str, Elementary]: """Extract a dict with all elementary expressions of a specific type :param the_type: the type of expression :type the_type: TypeOfElementaryExpression :return: returns a dict with the variables appearing in the expression the keys being their names. :rtype: dict(string:biogeme.expressions.Expression) """ if the_type == TypeOfElementaryExpression.BETA: return {self.name: self} if the_type == TypeOfElementaryExpression.FREE_BETA and self.status == 0: return {self.name: self} if the_type == TypeOfElementaryExpression.FIXED_BETA and self.status != 0: return {self.name: self} return {}
[docs] def get_value(self) -> float: """Evaluates the value of the expression :return: value of the expression :rtype: float :raise BiogemeError: if the Beta is not fixed. """ return self.initValue
[docs] @deprecated(get_value) def getValue(self) -> float: pass
[docs] def change_init_values(self, betas: dict[str, float]): """Modifies the initial values of the Beta parameters. The fact that the parameters are fixed or free is irrelevant here. :param betas: dictionary where the keys are the names of the parameters, and the values are the new value for the parameters. :type betas: dict(string:float) """ value = betas.get(self.name) if value is not None and value != self.initValue: if self.status != 0: warning_msg = ( f'Parameter {self.name} is fixed, but its value ' f'is changed from {self.initValue} to {value}.' ) logger.warning(warning_msg) self.initValue = value
[docs] def get_signature(self) -> list[bytes]: """The signature of a string characterizing an expression. This is designed to be communicated to C++, so that the expression can be reconstructed in this environment. The list contains the following elements: 1. the name of the expression between < > 2. the id of the expression between { } 3. the name of the parameter, 4. the status between [ ] 5. the unique ID, preceded by a comma 6. the Beta ID, preceded by a comma Consider the following expression: .. math:: 2 \\beta_1 V_1 - \\frac{\\exp(-\\beta_2 V_2) }{ \\beta_3 (\\beta_2 \\geq \\beta_1)}. It is defined as:: 2 * beta1 * Variable1 - expressions.exp(-beta2*Variable2) / (beta3 * (beta2 >= beta1)) And its signature is:: [b'<Numeric>{4780527008},2', b'<Beta>{4780277152}"beta1"[0],0,0', b'<Times>{4780526952}(2),4780527008,4780277152', b'<Variable>{4511837152}"Variable1",5,2', b'<Times>{4780527064}(2),4780526952,4511837152', b'<Beta>{4780277656}"beta2"[0],1,1', b'<UnaryMinus>{4780527120}(1),4780277656', b'<Variable>{4511837712}"Variable2",6,3', b'<Times>{4780527176}(2),4780527120,4511837712', b'<exp>{4780527232}(1),4780527176', b'<Beta>{4780277264}"beta3"[1],2,0', b'<Beta>{4780277656}"beta2"[0],1,1', b'<Beta>{4780277152}"beta1"[0],0,0', b'<GreaterOrEqual>{4780527288}(2),4780277656,4780277152', b'<Times>{4780527344}(2),4780277264,4780527288', b'<Divide>{4780527400}(2),4780527232,4780527344', b'<Minus>{4780527456}(2),4780527064,4780527400'] :return: list of the signatures of an expression and its children. :rtype: list(string) :raise biogeme.exceptions.BiogemeError: if no id has been defined for elementary expression :raise biogeme.exceptions.BiogemeError: if no id has been defined for parameter """ if self.elementaryIndex is None: error_msg = ( f"No id has been defined for elementary " f"expression {self.name}." ) raise BiogemeError(error_msg) if self.betaId is None: raise BiogemeError(f"No id has been defined for parameter {self.name}.") signature = f"<{self.get_class_name()}>" signature += f"{{{self.get_id()}}}" signature += ( f'"{self.name}"[{self.status}],' f"{self.elementaryIndex},{self.betaId}" ) return [signature.encode()]