"""Model specification in a multiple expression context
:author: Michel Bierlaire
:date: Mon Apr 10 12:33:18 2023
Implements a model specification in a multiple expression context (using Catalogs)
"""
import logging
from biogeme_optimization.pareto import SetElement
import biogeme.biogeme as bio
from biogeme import tools
from biogeme.configuration import Configuration
import biogeme.exceptions as excep
logger = logging.getLogger(__name__)
[docs]class Specification:
"""Implements a specification"""
database = None #: :class:`biogeme.database.Database` object
all_results = {} #: dict(str: `biogeme.results.bioResults`)
expression = None #: :class:`biogeme.expressions.Expression` object
"""
function that generates all the objectives:
fct(bioResults) -> list[floatNone]
"""
validity = None
"""
function that checks the validity of the results
"""
generic_name = 'default_name' #: short name for file names
[docs] def __init__(self, configuration):
"""Creates a specification from a configuration
:param configuration: configuration of the multiple expression
:type configuration: biogeme.configuration.Configuration
"""
if not isinstance(configuration, Configuration):
error_msg = 'Ctor needs an object of type Configuration'
raise excep.BiogemeError(error_msg)
self.configuration = configuration
self.model_names = None
self._estimate()
[docs] @classmethod
def from_string_id(cls, configuration_id):
"""Constructor using a configuration"""
return cls(Configuration.from_string(configuration_id))
[docs] @classmethod
def default_specification(cls):
"""Alternative constructor for generate the default specification"""
cls.expression.reset_expression_selection()
the_config = cls.expression.current_configuration()
return cls(the_config)
@property
def config_id(self):
"""Defined config_id as a property"""
return self.configuration.get_string_id()
@config_id.setter
def config_id(self, value):
self.configuration = Configuration.from_string(value)
[docs] def get_results(self):
"""Obtain the estimation results of the specification"""
the_results = self.all_results.get(self.config_id)
if the_results is None:
error_msg = f'No result is available for specification {self.config_id}'
raise excep.BiogemeError(error_msg)
return the_results
def __repr__(self):
return str(self.config_id)
def _estimate(self):
"""Estimate the parameter of the current specification, if not already done
:param quick_estimate: if True, a "quick estimate" is
performed, in the sense that the final statistics are not
calculated
:type quick_estimate: bool
"""
if self.expression is None:
error_msg = 'No expression has been provided for the model.'
raise excep.BiogemeError(error_msg)
if self.database is None:
error_msg = 'No database has been provided for the estimation.'
raise excep.BiogemeError(error_msg)
if self.model_names is None:
self.model_names = tools.ModelNames(prefix=self.generic_name)
if self.config_id in self.all_results:
return
logger.debug(f'****** Estimate {self.config_id}')
the_biogeme = bio.BIOGEME.from_configuration(
config_id=self.config_id, expression=self.expression, database=self.database
)
the_biogeme.modelName = self.model_names(self.config_id)
logger.info(f'*** Estimate {the_biogeme.modelName}')
the_biogeme.generate_html = False
the_biogeme.generate_pickle = False
results = the_biogeme.quickEstimate()
self.all_results[self.config_id] = results
[docs] def describe(self):
"""Short description of the solution. Used for reporting.
:return: short description of the solution.
:rtype: str
"""
the_results = self.get_results()
return f'{the_results.short_summary()}'
[docs] def get_element(self, multi_objectives):
"""Obtains the element from the Pareto set corresponding to a specification
:param multi_objectives: function calculating the objectives
from the estimation results
:type multi_objectives: fct(biogeme.results.bioResults) --> list[float]
:return: element from the Pareto set
:rtype: biogeme.pareto.SetElement
"""
the_id = self.config_id
the_results = self.get_results()
the_objectives = multi_objectives(the_results)
element = SetElement(the_id, the_objectives)
logger.debug(f'{element=}')
return element