def test_basicExampleWithoutCycles(self):
        genes = {
            1: (1, 6),
            2: (2, 6),
            3: (1, 7),
            4: (3, 7),
            5: (6, 4),
            6: (7, 4),
            7: (2, 5),
            8: (7, 5),
        }

        storage_genome = StorageGenome()
        analysis_result = AnalysisResult()

        storage_genome.inputs['input_1'] = 1
        storage_genome.inputs['input_2'] = 2
        storage_genome.inputs['input_3'] = 3
        storage_genome.outputs['output_1'] = 4
        storage_genome.outputs['output_2'] = 5
        storage_genome.genes[1] = (False, float(Fraction(2, 10)))
        storage_genome.genes[2] = (False, float(Fraction(7, 10)))
        storage_genome.genes[3] = (False, float(Fraction(1, 10)))
        storage_genome.genes[4] = (False, float(Fraction(3, 10)))
        storage_genome.genes[5] = (False, float(Fraction(6, 10)))
        storage_genome.genes[6] = (False, float(Fraction(8, 10)))
        storage_genome.genes[7] = (False, float(Fraction(9, 10)))
        storage_genome.genes[8] = (False, float(Fraction(5, 10)))

        analysis_result.topologically_sorted_nodes = [3, 2, 1, 7, 5, 6, 4]
        analysis_result.topologically_sorted_cycle_nodes = []

        storage_genome.analysis_result = analysis_result

        mock_gene_repository = MagicMock()
        mock_gene_repository.get_node_labels_by_gene_id =\
            lambda node_id: genes[node_id]

        gen = SimulationGenome(
            mock_gene_repository,
            storage_genome
        )

        result = gen.calculate_step({
            'input_1': float(Fraction(5, 10)),
            'input_2': float(Fraction(5, 10)),
            'input_3': float(Fraction(5, 10))
        })  # type: Dict[str, float]

        self.assertEqual(float(Fraction(43, 100)), result['output_1'])
        self.assertEqual(float(Fraction(55, 100)), result['output_2'])
    def decode_AnalysisResult(document: dict) -> AnalysisResult:
        """
        decodes dictionary in an AnalysisResult
        :param document: dictionary to decode
        :return: AnalysisResult
        """
        try:
            if document['_type'] == 'AnalysisResult':
                document.pop('_type')
                document["gene_closes_cycle_map"] = {
                    ObjectId(gene_id_as_str): boolean_flag
                    for gene_id_as_str, boolean_flag
                    in document["gene_closes_cycle_map"].items()
                }
                result = AnalysisResult()
                result.__dict__ = document

                return result
        except KeyError:
            return None
 def __init__(
         self,
         genome: 'StorageGenome' = None,
         inputs: List[str] = None,
         outputs: List[str] = None
 ):
     """
     :param genome: If genome is given, its inputs are copied
         (except for id)
     :param inputs: A list of strings, representing the inputs nodes' labels
     """
     self._id = ObjectId()
     self.is_alive = True if genome is None else genome.is_alive
     self.fitness = 0 if genome is None else genome.fitness  # type: float
     self.inputs = {} if genome is None else deepcopy(genome.inputs)  # type: Dict[str, int]
     self.outputs = {} if genome is None else deepcopy(genome.outputs)  # type: Dict[str, int]
     self.genes = {} if genome is None else deepcopy(genome.genes)  # type: Dict[int, Tuple[bool, Fraction]]
     self.analysis_result = AnalysisResult() if genome is None \
         else AnalysisResult(genome.analysis_result)
     self.cluster = ObjectId() if genome is None else genome.cluster
     if inputs and outputs:
         self._init_with_nodes(inputs, outputs)
class StorageGenome(object):
    """
    A data structure for storing genome information in a
    compact way.

    Attributes:
        genome_id (_id):
            The Genome's ID, unique in the population.
        inputs:
            A list of the ids of the input nodes. There has to be at least
            one gene per input that is connected to it. Represented as a list
            of tuples of the form (Input_Label, Node-ID).
        outputs:
            A list of the ids of the output nodes. There has to be at least
            one gene per output that is connected to it. Represented as a list
            of tuples of the form (Output_Label, Node-ID).
        genes:
            A list of all genes, that make up the genome. Presented as a tu-
            ple of Gene-ID, a boolean that is true, if the gene is disabled
            and a Fraction that stores the weight of the gene.
        analysis_result:
            A result object that is generated when analyzing the genome.
            This is empty per default.
        cluster:
            The cluster to which the Genome belongs.
    """

    def __init__(
            self,
            genome: 'StorageGenome' = None,
            inputs: List[str] = None,
            outputs: List[str] = None
    ):
        """
        :param genome: If genome is given, its inputs are copied
            (except for id)
        :param inputs: A list of strings, representing the inputs nodes' labels
        """
        self._id = ObjectId()
        self.is_alive = True if genome is None else genome.is_alive
        self.fitness = 0 if genome is None else genome.fitness  # type: float
        self.inputs = {} if genome is None else deepcopy(genome.inputs)  # type: Dict[str, int]
        self.outputs = {} if genome is None else deepcopy(genome.outputs)  # type: Dict[str, int]
        self.genes = {} if genome is None else deepcopy(genome.genes)  # type: Dict[int, Tuple[bool, Fraction]]
        self.analysis_result = AnalysisResult() if genome is None \
            else AnalysisResult(genome.analysis_result)
        self.cluster = ObjectId() if genome is None else genome.cluster
        if inputs and outputs:
            self._init_with_nodes(inputs, outputs)

    def _init_with_nodes(self, inputs: List[str], outputs: List[str]):
        node_id = 0
        for input_label in inputs:
            self.inputs[input_label] = node_id
            node_id += 1
        for output_label in outputs:
            self.outputs[output_label] = node_id
            node_id += 1

    def __eq__(self, obj: 'StorageGenome'):
        if not self._id.__eq__(obj._id) \
                or not self.is_alive.__eq__(obj.is_alive) \
                or not self.inputs.__eq__(obj.inputs) \
                or not self.outputs.__eq__(obj.outputs) \
                or not self.genes.__eq__(obj.genes) \
                or not self.analysis_result.__eq__(obj.analysis_result) \
                or not self.fitness.__eq__(obj.fitness) \
                or not self.cluster.__eq__(obj.cluster):
            return False
        return True

    @property
    def genome_id(self):
        return self._id