Source code for biogeme.dict_of_formulas

"""Functions managing the dict of formulas used by Biogeme


:author: Michel Bierlaire

:date: Mon May 13 13:42:50 2024

"""

import difflib
import logging
from typing import Any

from biogeme.exceptions import BiogemeError
from biogeme.expressions import Expression
from fuzzywuzzy import fuzz

logger = logging.getLogger(__name__)

# Valid names for the log likelihood.  Only one of them can be present in the dict.
log_like_names = ['log_like', 'loglike']

# Valid names for the weights.  Only one of them can be present in the dict.
weight_names = ['log_like', 'loglike']


[docs] def check_validity(dict_of_formulas: dict[str, Any]) -> None: """Verifies if the formulas are Biogeme expressions. If not, an exception is raised""" for k, f in dict_of_formulas.items(): if not isinstance(f, Expression): raise BiogemeError( f'Expression for "{k}" is not of type ' f"biogeme.expressions.Expression. " f"It is of type {type(f)}" )
[docs] def is_similar_to(word_1: str, word_2: str) -> bool: """Checks if two words are similar.""" return fuzz.ratio(word_1, word_2) >= 80
[docs] def insert_valid_keyword( dict_of_formulas: dict[str, Expression], reference_keyword: str, valid_keywords: list[str], ) -> dict[str, Expression]: """Insert the reference keyword if a valid keyword is used.""" key_log_like_expression: str | None = None for key in dict_of_formulas: if key == reference_keyword: continue if key in valid_keywords: if reference_keyword in dict_of_formulas: warning_msg = f'Both {reference_keyword} and {key} are defined. Only {reference_keyword} is considered.' else: warning_msg = ( f'As {key} is defined, it is used to define {reference_keyword}.' ) key_log_like_expression = key logger.warning(warning_msg) else: matches = difflib.get_close_matches( reference_keyword, [key], n=1, cutoff=0.8 ) if matches: logger.warning( f'Formula key "{key}" is similar to "{reference_keyword}". ' f'Did you mean to use "{reference_keyword}"?', ) if key_log_like_expression is not None: dict_of_formulas[reference_keyword] = dict_of_formulas.pop( key_log_like_expression ) return dict_of_formulas
[docs] def get_expression( dict_of_formulas: dict[str, Expression], valid_keywords: list[str] ) -> Expression | None: """Extract the formula for specific keywords :param dict_of_formulas: as the name says... :param valid_keywords: keywords that are considered valid to represent the expression :return: the requested expression """ found_name = None for valid_name in valid_keywords: the_expression = dict_of_formulas.get(valid_name) if the_expression is not None: if found_name is not None: error_msg = ( f"This expression can be defined with the keyword " f"'{found_name}' or '{valid_name}' but not both." ) raise BiogemeError(error_msg) found_name = valid_name if found_name is None: for valid_name in valid_keywords: for key in dict_of_formulas: if is_similar_to(valid_name, key): warning_msg = f'In the formulas, one key is "{key}". Should it be "{valid_name}" instead?' logger.warning(warning_msg) return None return dict_of_formulas[found_name]