# Copyright French Alternative Energies and Atomic Energy Commission
# Contributors: valjean developers
# valjean-support@cea.fr
#
# This software is a computer program whose purpose is to analyze and
# post-process numerical simulation results.
#
# This software is governed by the CeCILL license under French law and abiding
# by the rules of distribution of free software. You can use, modify and/ or
# redistribute the software under the terms of the CeCILL license as circulated
# by CEA, CNRS and INRIA at the following URL: http://www.cecill.info.
#
# As a counterpart to the access to the source code and rights to copy, modify
# and redistribute granted by the license, users are provided only with a
# limited warranty and the software's author, the holder of the economic
# rights, and the successive licensors have only limited liability.
#
# In this respect, the user's attention is drawn to the risks associated with
# loading, using, modifying and/or developing or reproducing the software by
# the user in light of its specific status of free software, that may mean that
# it is complicated to manipulate, and that also therefore means that it is
# reserved for developers and experienced professionals having in-depth
# computer knowledge. Users are therefore encouraged to load and test the
# software's suitability as regards their requirements in conditions enabling
# the security of their systems and/or data to be ensured and, more generally,
# to use and operate it in the same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
'''This module contains code that converts a test result into some kind of
human-readable representation (table, plots, etc.).
.. todo::
Possible improvement: turn :class:`Representer` into an `ABC`; loop over
the classes in :mod:`~.eponine` that inherit from `:class:`~.TestResult`
and add `@abstractmethod` methods in :class:`Representer`. This way, if
a new :class:`~.TestResult` is added to :mod:`~.eponine`, it will no longer
be possible to instantiate any of the classes that derive from
:class:`Representer`, pointing to the fact that the code in this module
needs to be extended to handle the new class. This is better than silently
falling back to some default do-nothing implementation, which may lead to
bugs.
This module uses the `Strategy` design pattern. The :class:`Representation`
class plays the role of `Context`, :class:`Representer` plays the role of
`Strategy` and the classes derived from :class:`Representer` (such as
:class:`TableRepresenter`, :class:`PlotRepresenter`, etc.) play the role of
`ConcreteStrategy`. See E. Gamma et al., "Design Patterns" (1995),
ISBN 0-201-63361-2, Addison-Weasley, USA.
Currently we have 3 main ``Representer`` classes:
* :class:`Representer`: parent class of all others, containing the default
:meth:`Representer.__call__` method, calling the class method named
``'repr_' + class.name`` of the test;
* :class:`TableRepresenter`: inherited from :class:`Representer`,
designed as a parent class for user's own representations of tables. Its
:meth:`TableRepresenter.__call__` method first looks for a method called
``'repr_' + class.name``; if it does not exist call the default method from
the catalogue of table representation accessible in :mod:`.table_repr`;
* :class:`PlotRepresenter`: inherited from :class:`Representer`, designed
as a parent class for user's own representations of plots. Its
:meth:`PlotRepresenter.__call__` method first looks for a method called
``'repr_' + class.name``; if it does not exist call the default method from
the catalogue of plot representation accessible in :mod:`.plot_repr`.
An example of use of the ``Representer`` objects can be seen in the
:class:`FullTableRepresenter`. In this table representation, for the Bonferroni
test result, the input test result is first represented in a table, then the
Bonferroni itself is represented in a second table.
Thus the use of the ``Representer`` is foreseen as:
* use the default methods provided in :class:`TableRepresenter` and
:class:`PlotRepresenter`;
* if customisation is needed, you can easily call the additional method
available in :mod:`.table_repr` and :mod:`.plot_repr`;
* you can also write your own representation method provided they follow the
naming convention ``'repr_' + class.name``;
* :mod:`valjean` calls the ``Representer`` classes through the
:class:`Representation` class.
'''
import logging
from . import table_repr
from . import plot_repr
from .verbosity import Verbosity
LOGGER = logging.getLogger(__name__)
[docs]class Representation:
'''Class for representing test results as templates calling the available
representers (tables or plots).
This class corresponds to the `Context` role in the `Strategy` design
pattern.
'''
[docs] def __init__(self, representer, verbosity=Verbosity.DEFAULT):
''''Initilialisation of the :class:`Representation` class with the
Representer to use.
:param Representer: representer to use (table, plots, both, etc)
:type representer: :class:`Representer`
'''
self.representer = representer
self.verbosity = verbosity
[docs] def __call__(self, result):
'''Dispatch handling of `result` to the ``__call__`` methods of the
representer class.'''
if callable(self.verbosity):
verbosity = self.verbosity(result)
else:
verbosity = self.verbosity
res = self.representer(result, verbosity)
if res is None:
return []
LOGGER.debug('representing the result of test %s as %s',
result.test.name, res)
return res
[docs]class Representer:
'''Base class for representing test results as templates (in the sense of
the :mod:`~.templates` module).
This class corresponds to the `Strategy` role in the `Strategy` design
pattern. Its subclasses play the role of `ConcreteStrategy`.
'''
[docs] def __call__(self, result, verbosity=Verbosity.DEFAULT):
'''Dispatch handling of `result` to the appropriate subclass method,
based on the name of the class of `result`. This methods essentially
implements a simplified, run-time version of the Visitor pattern.
'''
LOGGER.debug("In Representer.__call__")
class_name = result.__class__.__name__
meth_name = 'repr_' + class_name.lower()
try:
meth = getattr(self, meth_name)
except AttributeError:
LOGGER.debug('no representer for class %s with name %s',
class_name, meth_name)
return None
return meth(result, verbosity)
[docs]class ExternalRepresenter:
'''This class is the default representation class for external tests, i.e.
tests defined by the users who already defined the test representation as
templates.
'''
[docs] def __call__(self, result, _verbosity):
LOGGER.debug("In ExternalRepresenter.__call__")
if result.__class__.__name__ != 'TestResultExternal':
LOGGER.debug('ExternalRepresenter only deals with '
'TestResultExternal')
return []
return result.test.templates
[docs]class TableRepresenter(Representer):
'''This class is the default representation class for tables. It contains
the overridden :meth:`Representer.__call__`.
Advice: users willing to customize the behaviour of TableRepresenter for
specific test results should subclass TableRepresenter and define the
relevant ``repr_*`` methods.
'''
[docs] def __call__(self, result, verbosity=Verbosity.DEFAULT):
LOGGER.debug("In TableRepresenter.__call__, %s", verbosity)
res = super().__call__(result, verbosity)
if res is None:
class_name = result.__class__.__name__
meth_name = 'repr_' + class_name.lower()
try:
meth = getattr(table_repr, meth_name)
except AttributeError:
LOGGER.info('no table representer %s', meth_name)
return None
res = meth(result, verbosity)
return res
[docs]class FullTableRepresenter(TableRepresenter):
'''Class to define the specific methods for full representation of tables.
This only involve few cases needing the :meth:`TableRepresenter.__call__`
method like in Bonferroni and Holm-Bonferroni test results.
'''
[docs] def repr_testresultbonferroni(self, result, verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestBonferroni` test in two
tables:
1. First test result (Student, equal, etc)
2. Bonferroni test result
:param result: a test result.
:type result: TestResultBonferroni
:returns: Representation of a :class:`~.TestResultBonferroni` as
two tables (the first test result and the Bonferroni result).
:rtype: list(TableTemplate)
'''
LOGGER.debug("In FullTableRepresenter.repr_testresultbonferroni")
if verbosity == Verbosity.SILENT:
return table_repr.repr_testresultbonferroni(result, verbosity)
ftest_verb = (Verbosity(verbosity.value-1)
if bool(result) else verbosity)
return (table_repr.repr_testresultbonferroni(result, verbosity)
+ super().__call__(result.first_test_res, ftest_verb))
[docs] def repr_testresultholmbonferroni(self, result,
verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestHolmBonferroni` test in two
tables:
1. First test result (Student, equal, etc)
2. Holm-Bonferroni test result
:param result: a test result.
:type result: TestResultHolmBonferroni
:returns: Representation of a :class:`~.TestResultHolmBonferroni` as
two tables (the first test result and the Holm-Bonferroni result).
:rtype: list(TableTemplate)
'''
LOGGER.debug(
"In FullTableRepresenter.repr_testresultholmbonferroni")
if verbosity == Verbosity.SILENT:
return table_repr.repr_testresultholmbonferroni(result, verbosity)
ftest_verb = (Verbosity(verbosity.value-1)
if bool(result) else verbosity)
return (table_repr.repr_testresultholmbonferroni(result, verbosity)
+ super().__call__(result.first_test_res, ftest_verb))
[docs]class PlotRepresenter(Representer):
'''This class is the default representation class for plots. It contains
the overridden :meth:`Representer.__call__`.
Advice: users willing to customize the behaviour of
:class:`PlotRepresenter` for specific test results should subclass
:class:`PlotRepresenter` and define the relevant ``repr_*`` methods.
'''
[docs] def __init__(self, post='default'):
if callable(post):
self.post = post
elif post.lower() == 'none':
self.post = None
elif post.lower() == 'default':
self.post = plot_repr.post_treatment
else:
LOGGER.warning('Plot post-treatment must be a callable.')
self.post = None
[docs] def __call__(self, result, verbosity=Verbosity.DEFAULT):
LOGGER.debug("In PlotRepresenter.__call__")
res = super().__call__(result, verbosity)
if res is None:
class_name = result.__class__.__name__
meth_name = 'repr_' + class_name.lower()
try:
meth = getattr(plot_repr, meth_name)
except AttributeError:
LOGGER.info('no plot representer %s', meth_name)
return None
res = meth(result, verbosity)
if res and self.post is not None:
res = self.post(res, result)
return res
[docs] def repr_testresultbonferroni(self, result, verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestBonferroni` test one a plot
(only the input test for the moment) (Student, equal, etc)
:param result: a test result.
:type result: TestResultBonferroni
:returns: Representation of a :class:`~.TestResultBonferroni` as a plot
(the first test result).
:rtype: list(PlotTemplate)
'''
LOGGER.debug("In FullPlotRepresenter.repr_testresultbonferroni")
return self(result.first_test_res, verbosity)
[docs] def repr_testresultholmbonferroni(self, result,
verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestHolmBonferroni` test as a
plot (Student, equal, etc)
:param result: a test result.
:type result: TestResultHolmBonferroni
:returns: Representation of a :class:`~.TestResultHolmBonferroni` as a
plot (the first test result).
:rtype: list(PlotTemplate)
'''
LOGGER.debug("In PlotRepresenter.repr_testresultholmbonferroni")
return self(result.first_test_res, verbosity)
[docs]class FullPlotRepresenter(PlotRepresenter):
'''Class to define the specific methods for full representation of plots.
This only involve few cases needing the :meth:`PlotRepresenter.__call__`
method like in Bonferroni and Holm-Bonferroni test results.
'''
[docs] def repr_testresultbonferroni(self, result, verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestBonferroni` test one a plot
(only the input test for the moment) (Student, equal, etc)
:param result: a test result.
:type result: TestResultBonferroni
:returns: Representation of a :class:`~.TestResultBonferroni` as a plot
(the first test result).
:rtype: list(PlotTemplate)
'''
LOGGER.debug("In FullPlotRepresenter.repr_testresultbonferroni")
return self(result.first_test_res, verbosity)
[docs] def repr_testresultholmbonferroni(self, result,
verbosity=Verbosity.DEFAULT):
'''Represent the result of a :class:`~.TestHolmBonferroni` test as a
plot (Student, equal, etc)
:param result: a test result.
:type result: TestResultHolmBonferroni
:returns: Representation of a :class:`~.TestResultHolmBonferroni` as a
plot (the first test result).
:rtype: list(PlotTemplate)
'''
LOGGER.debug(
"In FullPlotRepresenter.repr_testresultholmbonferroni")
return self(result.first_test_res, verbosity)
[docs]class EmptyRepresenter(Representer):
'''Class that does not generate any templates for any test result.'''
[docs]class FullRepresenter(Representer):
'''This class generates the fullest possible representation for test
results. If anything can be represented as an template,
:class:`FullRepresenter` will do it.
'''
[docs] def __init__(self, post=plot_repr.post_treatment):
'''Initialisation of :class:`FullRepresenter`.
Two instance objects are built: a :class:`FullTableRepresenter` and
a :class:`PlotRepresenter`.
'''
LOGGER.debug("In initialisation of FullRepresenter")
self.table_repr = FullTableRepresenter()
self.plot_repr = FullPlotRepresenter(post=post)
self.ext_repr = ExternalRepresenter()
[docs] def __call__(self, result, verbosity=Verbosity.DEFAULT):
'''Dispatch handling of `result` to all the Representer subclass
instance attributes of :class:`FullRepresenter`, based on the name of
the class of `result`.
If the representer does not exist in :mod:`~.table_repr` or
:mod:`~.plot_repr` a ``None`` is returned and replaced here by an
empty list. If none of them exist the global return will be an empty
list (no ``None`` returned from this step).
'''
res = []
pltres = self.plot_repr(result, verbosity)
if pltres is not None:
res.extend(pltres)
tabres = self.table_repr(result, verbosity)
if tabres is not None:
res.extend(tabres)
extres = self.ext_repr(result, verbosity)
if extres is not None:
res.extend(extres)
return res