Source code for linchemin.cgu.translate

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional, Union

import networkx as nx
import pydot

from linchemin.cgu.graph_transformations.data_model_converters import (
    DataModelCatalog,
    DataModelConverter,
)
from linchemin.cgu.graph_transformations.exceptions import UnavailableTranslation
from linchemin.cgu.graph_transformations.format_translators import (
    GraphFormatCatalog,
    get_formats,
)
from linchemin.cgu.iron import Iron
from linchemin.cgu.syngraph import (
    BipartiteSynGraph,
    MonopartiteMolSynGraph,
    MonopartiteReacSynGraph,
)
from linchemin.utilities import console_logger

"""
Module containing functions and classes to transform a graph from an input format to a different output format.

"""

logger = console_logger(__name__)


@dataclass
class TranslationParameters:
    """Class to store translation parameters"""

    input_format: str = field(default_factory=str)
    output_format: str = field(default_factory=str)
    data_model_converter: Optional[DataModelConverter] = None


class Translation:
    @staticmethod
    def start_translation(
        input_format: str, input_graph, output_format: str, out_data_model: str
    ):
        data_model_converter = DataModelCatalog.get_data_model(out_data_model)
        parameters = TranslationParameters(
            input_format=input_format,
            output_format=output_format,
            data_model_converter=data_model_converter,
        )
        return InputToIron().translate(input_graph, parameters)


# Chain of responsibility structure to handle the translation input -> iron -> syngraph -> iron -> output
class Handler(ABC):
    """Abstract handler for the concrete translators taking care of the single steps in the workflow"""

    @abstractmethod
    def translate(
        self,
        graph: Union[
            MonopartiteReacSynGraph,
            MonopartiteMolSynGraph,
            BipartiteSynGraph,
            nx.classes.digraph.DiGraph,
            pydot.Dot,
            list,
            dict,
            Iron,
        ],
        parameters: TranslationParameters,
    ):
        """To perform the translation/conversion of a specific step in the chain"""
        pass


class InputToIron(Handler):
    """Handler taking care of the first step: input to iron"""

    def translate(self, graph, parameters):
        """To perform the first step: input to iron"""
        # If the input graph is a syngraph
        if parameters.input_format == "syngraph":
            syngraph = parameters.data_model_converter.convert_syngraph(graph)
            graph = SynGraphToIron().translate(syngraph, parameters)
            return graph
        input_to_iron_translator = GraphFormatCatalog.get_format(
            parameters.input_format
        )
        graph = input_to_iron_translator.to_iron(graph)
        if parameters.output_format == "iron":
            return graph
        elif graph is None:
            return None
        else:
            return IronToSynGraph().translate(graph, parameters)


class IronToSynGraph(Handler):
    """Handler taking care of the second step: iron to syngraph in the desired data model"""

    def translate(self, graph, parameters):
        """To perform the second step: iron to syngraph"""
        graph = parameters.data_model_converter.iron_to_syngraph(graph)
        if parameters.output_format == "syngraph" or graph is None:
            return graph
        else:
            graph = SynGraphToIron().translate(graph, parameters)
        return graph


class SynGraphToIron(Handler):
    """Handler taking care of the third step: syngraph to iron"""

    def translate(self, graph, parameters):
        """To perform the third step: syngraph to iron"""
        graph = parameters.data_model_converter.syngraph_to_iron(graph)
        graph = IronToOutput().translate(graph, parameters)
        return graph


class IronToOutput(Handler):
    """Handler taking care of the fourth step: iron to output"""

    def translate(self, graph, parameters):
        """To perform the fourth step: iron to output"""
        if parameters.output_format == "iron":
            return graph
        iron_to_output_translator = GraphFormatCatalog.get_format(
            parameters.output_format
        )
        graph = iron_to_output_translator.from_iron(graph)
        return graph


# Facade function
[docs] def translator( input_format: str, original_graph: Union[ MonopartiteReacSynGraph, MonopartiteMolSynGraph, BipartiteSynGraph, nx.classes.digraph.DiGraph, pydot.Dot, list, dict, Iron, ], output_format: str, out_data_model: str, ) -> Union[ MonopartiteReacSynGraph, BipartiteSynGraph, MonopartiteMolSynGraph, nx.DiGraph, pydot.Dot, Iron, None, ]: """ To translate an input graph from a format/data model to another one Parameters: ------------ input_format: str The format of the input graph object original_graph: Union[MonopartiteReacSynGraph, MonopartiteMolSynGraph, BipartiteSynGraph, nx.classes.digraph.DiGraph, pydot.Dot, list, dict, Iron] The input graph output_format: str The desired output format out_data_model: str The desired type of output graph (monopartite, bipartite...) Returns: ---------- out_graph: Union[MonopartiteReacSynGraph, BipartiteSynGraph, MonopartiteMolSynGraph, nx.DiGraph, pydot.Dot, Iron, None] The output graph Raises: ------- UnavailableTranslation: if the selected translation is not available Example: --------- >>> graph = json.loads(open('ibm_file.json').read()) >>> nx_graph = translator('ibm_retro', graph[1], 'networkx', out_data_model='monopartite_molecules') """ if "syngraph" in input_format and "syngraph" in output_format: logger.error( 'To convert between data models, please use the "converter" function.' ) raise UnavailableTranslation translation = Translation() return translation.start_translation( input_format, original_graph, output_format, out_data_model )
# Helper functions def get_available_formats() -> dict: """ To retrieve all the available formats names and brief descriptions Returns: -------- d: dict The information about the available formats Example: >>> av_formats = get_available_formats() """ return get_formats() def get_input_formats() -> dict: """To retrieve graph formats available as input""" d = get_formats() return d["as_input"] def get_output_formats() -> dict: """To retrieve graph formats available as output""" d = get_formats() return d["as_output"] def get_available_data_models() -> dict: """ To retrieve all the available data models names and brief descriptions Returns: -------- d: dict The information about the available data models Example: >>> av_datamodels = get_available_data_models() """ all_datamodels = DataModelCatalog.list_data_models() return {f: additional_info["info"] for f, additional_info in all_datamodels.items()}