KarrLab/wc_analysis

View on GitHub
wc_analysis/core.py

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
""" Analyze knowledge base, model, and simulation results.

:Author: Jonathan Karr <karr@mssm.edu>
:Date: 2018-02-13
:Copyright: 2018, Karr Lab
:License: MIT
"""

from matplotlib import pyplot
import abc
import matplotlib
import os
import wc_kb.core
import wc_lang.core


class Analysis(object, metaclass=abc.ABCMeta):
    """ An analysis of a knowledge base, model, or simulation results

    Attributes:
        out_path (:obj:`str`): optional path to save analysis
        options (:obj:`dict`): options
    """

    def __init__(self, out_path=None, options=None):
        """
        Args:
            out_path (:obj:`str`, optional): path to save analyses
            options (:obj:`dict`, optional): options
        """
        self.out_path = out_path

        # make the output directory if it doesn't exist
        if out_path and not os.path.isdir(out_path):
            os.makedirs(out_path)

        self.options = options or {}
        self.clean_and_validate_options()

    def clean_and_validate_options(self):
        """ Apply default options and validate options """
        pass

    @abc.abstractmethod
    def run(self):
        """ Run the analysis """
        pass  # pragma: no cover

    def create_fig(self, rows=1, cols=1):
        """ Create a figure composed of a grid of subfigures

        Args:
            rows (:obj:`int`, optional): number of rows of subfigures
            cols (:obj:`int`, optional): number of columns of subfigures

        Returns:
            :obj:`matplotlib.figure.Figure`: figure
            :obj:`matplotlib.axes.Axes`: axes
        """
        return pyplot.subplots(nrows=rows, ncols=cols)

    def show_or_save_fig(self, fig, filename=None):
        """ Show or save a figure

        Args:
            fig (:obj:`matplotlib.figure.Figure`): figure
            filename (:obj:`str`, optional): filename to save figure
        """
        if self.out_path:
            fig.savefig(os.path.join(self.out_path, filename), transparent=True, bbox_inches='tight')
            pyplot.close(fig)
        else:
            fig.show()  # pragma: no cover


class KnowledgeBaseAnalysis(Analysis):
    """ Analysis of a knowledge base

    Attributes:
        knowledge_base (:obj:`wc_kb.core.KnowledgeBase`): knowledge base
    """

    def __init__(self, knowledge_base, out_path=None, options=None):
        """
        Args:
            knowledge_base (:obj:`wc_kb.core.KnowledgeBase`): knowledge base
            out_path (:obj:`str`, optional): path to save analyses
            options (:obj:`dict`, optional): options
        """
        super(KnowledgeBaseAnalysis, self).__init__(out_path=out_path, options=options)
        self.knowledge_base = knowledge_base


class ModelAnalysis(Analysis):
    """ Analysis of a model

    Attributes:
        model (:obj:`wc_lang.core.Model`): model
        knowledge_base (:obj:`wc_kb.core.KnowledgeBase`): knowledge base
    """

    def __init__(self, model, knowledge_base=None, out_path=None, options=None):
        """
        Args:
            model (:obj:`wc_lang.core.Model`): model
            knowledge_base (:obj:`wc_kb.core.KnowledgeBase`, optional): knowledge base
            out_path (:obj:`str`, optional): path to save analyses
            options (:obj:`dict`, optional): options
        """
        super(ModelAnalysis, self).__init__(out_path=out_path, options=options)
        self.model = model
        self.knowledge_base = knowledge_base


class SimulationAnalysis(Analysis):
    """ Analysis of a simulation result

    Attributes:
        sim_results_path (:obj:`str`): path to simulation results
        model (:obj:`wc_lang.core.Model`): model
        knowledge_base (:obj:`wc_kb.core.KnowledgeBase`): knowledge base
    """

    def __init__(self, sim_results_path, model=None, knowledge_base=None, out_path=None, options=None):
        """
        Args:
            sim_results_path (:obj:`str`): path to simulation results
            model (:obj:`wc_lang.core.Model`, optional): model
            knowledge_base (:obj:`wc_kb.core.KnowledgeBase`, optional): knowledge base
            out_path (:obj:`str`, optional): path to save analyses
            options (:obj:`dict`, optional): options
        """
        super(SimulationAnalysis, self).__init__(out_path=out_path, options=options)
        self.sim_results_path = sim_results_path
        self.model = model
        self.knowledge_base = knowledge_base


class AnalysisRunner(object):
    """ Run one or more analyses of a whole-cell knowledge base, model, and/or simulation results

    Attributes:
        knowledge_base (:obj:`wc_kb.core.KnowledgeBase`): knowledge base
        model (:obj:`wc_lang.core.Model`): model
        sim_results_path (:obj:`str`): path to simulation results
        analyses (:obj:`list` of :obj:`Analysis`): analyses to run
        out_path (:obj:`str`):  path to save analyses
        options (:obj:`dict`): options
    """

    DEFAULT_ANALYSES = ()

    def __init__(self, knowledge_base=None, model=None, sim_results_path=None,
        analyses=None, out_path=None, options=None):
        """
        Args:
            knowledge_base (:obj:`wc_kb.core.KnowledgeBase`, optional): knowledge base
            model (:obj:`wc_lang.core.Model`, optional): model
            sim_results_path (:obj:`str`, optional): path to simulation results
            analyses (:obj:`list` of :obj:`Analysis`, optional): analyses to run
            out_path (:obj:`str`, optional): path to save analyses
            options (:obj:`dict`, optional): options
        """
        if analyses is None:
            analyses = self.DEFAULT_ANALYSES

        self.knowledge_base = knowledge_base
        self.model = model
        self.sim_results_path = sim_results_path
        self.analyses = analyses
        self.out_path = out_path
        self.options = options or {}

        self.clean_and_validate_options()

    def clean_and_validate_options(self):
        """ Apply default options and validate options """
        pass

    def run(self):
        """ Run multiple analyses

        Raises:
            :obj:`ValueError`: if the analysis is not supported
        """
        options = self.options.get('analysis', {})
        for analysis_cls in self.analyses:
            if self.out_path:
                out_path = os.path.join(self.out_path, analysis_cls.__name__)
            else:
                out_path = None

            if issubclass(analysis_cls, KnowledgeBaseAnalysis):
                analysis = analysis_cls(knowledge_base=self.knowledge_base,
                                        out_path=out_path,
                                        options=options.get(analysis_cls.__name__, {}))
            elif issubclass(analysis_cls, ModelAnalysis):
                analysis = analysis_cls(knowledge_base=self.knowledge_base,
                                        model=self.model,
                                        out_path=out_path,
                                        options=options.get(analysis_cls.__name__, {}))
            elif issubclass(analysis_cls, SimulationAnalysis):
                analysis = analysis_cls(knowledge_base=self.knowledge_base,
                                        model=self.model,
                                        sim_results_path=self.sim_results_path,
                                        out_path=out_path,
                                        options=options.get(analysis_cls.__name__, {}))
            else:
                raise ValueError('Unsupported analysis of type {}'.format(analysis_cls.__name__))

            analysis.run()