Source code for biogeme.segmentation

"""Class that provides some automatic specification for segmented parameters

:author: Michel Bierlaire
:date: Fri Dec 31 10:41:33 2021

"""

from collections import namedtuple, deque
from biogeme.expressions import Beta, bioMultSum

DiscreteSegmentationTuple = namedtuple(
    'DiscreteSegmentationTuple', 'variable mapping'
)


[docs]def combine_segmented_expressions(variable, mapping_of_expressions): """Create an expressions that combines all the segments :param variable: variable that characterizes the segmentation :type variable: biogeme.expressions.Variable :param mapping_of_expressions: dictionary that maps each value of the variable with the expression for the corresponding segment. :type mapping_of_expressions: dict(int: biogeme.expressions.Expression) :return: combined expression :rtype: biogeme.expressions.bioMultSum """ terms = [ expr * (variable == value) for value, expr in mapping_of_expressions.items() ] return bioMultSum(terms)
[docs]def code_to_combine_segmented_expressions( variable, mapping_of_expressions, prefix ): """Create the Python code for an expressions that combines all the segments :param variable: variable that characterizes the segmentation :type variable: biogeme.expressions.Variable :param mapping_of_expressions: dictionary that maps each value of the variable with the Python code of the expression for the corresponding segment. :type mapping_of_expressions: dict(int: biogeme.expressions.Expression) :param prefix: name of the current expression, used as prefix :type prefix: str :return: code for the combined expression :rtype: str """ result = '' for value, _ in mapping_of_expressions.items(): result += ( f'{prefix}_{variable}_{value} = ' f'{prefix} * ({variable} == {value})\n' ) terms = ', '.join( [ f'{prefix}_{variable}_{value}' for value, expr in mapping_of_expressions.items() ] ) result += f'{prefix}_{variable} = bioMultSum([{terms}])\n' return result
[docs]def create_segmented_parameter(parameter, mapping): """Create a version of the parameter for each segment :param parameter: parameter :type parameter: biogeme.expressions.Beta :param mapping: dictionary that maps each segment id with the name of the segment. :type mapping: dict(int: str) :return: a dictionary that maps each segment id with the created parameters :rtype: dict(int: biogeme.expressions.Beta) """ segmented_parameters = { value: Beta( f'{parameter.name}_{name}', parameter.initValue, parameter.lb, parameter.ub, parameter.status, ) for value, name in mapping.items() } return segmented_parameters
[docs]def segment_parameter( parameter, list_of_discrete_segmentations, combinatorial=False ): """Segment a parameter expression along several dimensions of segmentation :param parameter: parameter to segment :type parameter: biogeme.expressions.Beta :param list_of_discrete_segmentations: each element of the list is a tuple with the variable characterizing the segmentation, and a dictionary mapping the values with the names of the segments. :type list_of_discrete_segmentations: tuple(DiscreteSegmentationTuple(biogeme.expressions.Variable, dict(int:str))) :param combinatorial: if True, a parameter is associated with each combination of values for the discrete segmentations. If :math:`N_s` is the number of values for segmentation s, the total numper of parameters is :math:`\\prod_s N_s`. If False, for each segmentation in the list, a parameter is associated with each value of this segmentation. If `:math:`N_s` is the number of values for segmentation s, the total number of parameters is :math:`\\sum_s N_s`. :type combinatorial: bool :return: expression involving all the segments :rtype: biogeme.expressions.Expression """ if combinatorial: # Recursive call to the function, based on a stack stack_of_segmentations = deque(list_of_discrete_segmentations) if not stack_of_segmentations: return parameter next_segment = stack_of_segmentations.pop() segmented_parameters = create_segmented_parameter( parameter, next_segment.mapping ) map_of_expressions = { key: segment_parameter( value, stack_of_segmentations, combinatorial=True ) for key, value in segmented_parameters.items() } return combine_segmented_expressions( next_segment.variable, map_of_expressions ) # If not combinatorial, just a list of terms. all_segments = [ expr * (s.variable == value) for s in list_of_discrete_segmentations for value, expr in create_segmented_parameter( parameter, s.mapping ).items() ] return bioMultSum(all_segments)
[docs]def code_to_segment_parameter( parameter, list_of_discrete_segmentations, prefix='' ): """Generate the Python code to segment a parameter along several dimensions of segmentation :param parameter: parameter to segment :type parameter: biogeme.expressions.Beta :param list_of_discrete_segmentations: each element of the list is a tuple with the variable characterizing the segmentation, and a dictionary mapping the values with the names of the segments. :type list_of_discrete_segmentations: tuple(DiscreteSegmentationTuple(biogeme.expressions.Variable, dict(int:str))) :param prefix: name of the current expression, used as prefix :type prefix: str :return: code for the segmentation :rtype: str """ stack_of_segmentations = deque(list_of_discrete_segmentations) next_segment = stack_of_segmentations.pop() if prefix == '': prefix = parameter.name # prefix = 'beta_var_1' result = '' if stack_of_segmentations: for value in next_segment.mapping.values(): result += code_to_segment_parameter( value, stack_of_segmentations, f'{prefix}_{value}' ) result += '\n' else: for value in next_segment.mapping.values(): param_name = f'{prefix}_{value}' result += f"{param_name} = Beta('{param_name}', 0, None, None)\n" terms = ', '.join( [ f'{prefix}_{value} * ({next_segment.variable.name} == {key}))' for key, value in next_segment.mapping.items() ] ) result += f'{prefix} = bioMultSum([{terms}])' return result