"""Audit procedure for arithmetic expressionsMichel BierlaireFri Mar 28 17:01:01 2025"""from__future__importannotationsfromtypingimportTYPE_CHECKINGfrombiogeme.audit_tupleimportAuditTuplefrom.belongs_toimportBelongsTofrom.comparison_expressionsimportComparisonOperatorfrom.drawsimportDrawsfrom.integrateimportIntegrateNormalfrom.logit_expressionsimportLogLogitfrom.montecarloimportMonteCarlofrom.random_variableimportRandomVariablefrom.visitorimportExpressionVisitor_audit_visitor=ExpressionVisitor()register_audit=_audit_visitor.registerifTYPE_CHECKING:from.base_expressionsimportExpression
[docs]defaudit_expression(expr:Expression)->AuditTuple:""" Audits an expression tree for structural consistency. :param expr: The root expression to audit. :return: A tuple containing two lists: - error_messages: list of error strings. - warning_messages: list of warning strings. """error_messages=[]warning_messages=[]ancestors=[]context={"errors":error_messages,"warnings":warning_messages,"ancestors":ancestors,# Stack of ancestor expressions}_audit_visitor.visit(expr,context)returnAuditTuple(errors=error_messages,warnings=warning_messages)
[docs]defaudit_default(expr:Expression,context:dict[str,list[str]])->None:""" Default audit function for expressions that do not have a specific audit function. :param expr: The current expression node being audited. :param context: Dictionary to collect error and warning messages. """ifhasattr(expr,'left')andhasattr(expr,'right'):ifisinstance(expr,ComparisonOperator):ifisinstance(expr.left,ComparisonOperator)orisinstance(expr.right,ComparisonOperator):warning=(f"The expression [{expr}] may be a chained comparison (e.g., a <= b <= c), "f"which Biogeme interprets as nested comparisons, not as Python-style chains. "f"Consider splitting it: (a <= b) & (b <= c).")context["warnings"].append(warning)
[docs]@register_audit(MonteCarlo)defaudit_montecarlo(expr:MonteCarlo,context:dict[str,list[str]])->None:""" Audits a MonteCarlo expression for structural consistency. :param expr: The MonteCarlo expression to audit. :param context: Dictionary to collect error and warning messages. """ifnotexpr.embed_expression(Draws):context["errors"].append(f'MonteCarlo expression {repr(expr)} does not contain any Draws expression.')ifany(child.embed_expression(MonteCarlo)forchildinexpr.get_children()):context["errors"].append(f'MonteCarlo expression {repr(expr)} cannot contain another MonteCarlo expression.')
[docs]@register_audit(IntegrateNormal)defaudit_integrate(expr:IntegrateNormal,context:dict[str,list[str]])->None:""" Audits an Integrate expression for structural consistency. :param expr: The Integrate expression to audit. :param context: Dictionary to collect error and warning messages. """ifnotexpr.embed_expression(RandomVariable):context["warnings"].append(f'Integrate expression {repr(expr)} does not contain any RandomVariable expression.')
[docs]@register_audit(BelongsTo)defaudit_belongsto(expr:BelongsTo,context:dict[str,list[str]])->None:""" Audits a BelongsTo expression for structural consistency. :param expr: The BelongsTo expression to audit. :param context: Dictionary to collect error and warning messages. """ifnotall(float(x).is_integer()forxinexpr.the_set):the_warning=(f'The set of numbers used in the expression "BelongsTo" contains 'f'numbers that are not integer. If it is the intended use, ignore 'f'this warning: {expr.the_set}.')context["warnings"].append(the_warning)
[docs]@register_audit(LogLogit)defaudit_loglogit(expr:LogLogit,context:dict[str,list[str]])->None:""" Audits a LogLogit expression for structural consistency. :param expr: The LogLogit expression to audit. :param context: Dictionary to collect error and warning messages. """ifexpr.avisNone:returnifexpr.util.keys()!=expr.av.keys():the_error='Incompatible list of alternatives in logit expression. 'my_set=expr.util.keys()-expr.av.keys()ifmy_set:my_set_content=', '.join(f'{str(k)} 'forkinmy_set)the_error+=('Id(s) used for utilities and not for availabilities: ')+my_set_contentmy_set=expr.av.keys()-expr.util.keys()ifmy_set:my_set_content=', '.join(f'{str(k)} 'forkinmy_set)the_error+=(' Id(s) used for availabilities and not for utilities: ')+my_set_contentcontext["errors"].append(the_error)
[docs]@register_audit(RandomVariable)defaudit_randomvariable(expr:RandomVariable,context:dict[str,list[str]])->None:ifnotany(isinstance(ancestor,IntegrateNormal)forancestorincontext['ancestors']):context['errors'].append(f'RandomVariable {repr(expr)} is not embedded inside an IntegrateNormal expression.')
[docs]@register_audit(Draws)defaudit_draws(expr:Draws,context:dict[str,list[str]])->None:ifnotany(isinstance(ancestor,MonteCarlo)forancestorincontext['ancestors']):context['errors'].append(f'Draws {repr(expr)} is not embedded inside a MonteCarlo expression.')