def __init__(self,
                 params: FlockPartialSwitchNodeGroupParams,
                 name: str = "FlockPartialSwitchNodeGroup"):
        super().__init__(name,
                         inputs=FlockPartialSwitchInputs(self),
                         outputs=FlockPartialSwitchOutputs(self))
        dim = 0
        n_fork_1 = ForkNode(
            dim, [params.split_idx, params.flock_size - params.split_idx])
        n_fork_2 = ForkNode(
            dim, [params.split_idx, params.flock_size - params.split_idx])
        n_switch = SwitchNode(n_inputs=2)
        n_join = JoinNode(dim, n_inputs=2)
        self.add_node(n_fork_1)
        self.add_node(n_fork_2)
        self.add_node(n_switch)
        self.add_node(n_join)

        Connector.connect(self.inputs.input_1.output, n_fork_1.inputs.input)
        Connector.connect(self.inputs.input_2.output, n_fork_2.inputs.input)
        Connector.connect(n_fork_1.outputs[0], n_switch.inputs[0])
        Connector.connect(n_fork_2.outputs[0], n_switch.inputs[1])
        Connector.connect(n_switch.outputs.output, n_join.inputs[0])
        Connector.connect(n_fork_2.outputs[1], n_join.inputs[1])
        Connector.connect(n_join.outputs.output, self.outputs.output.input)

        self._n_switch = n_switch
    def __init__(self):
        super().__init__("cuda")
        actions_descriptor = GridWorldActionDescriptor()
        node_action_monitor = ActionMonitorNode(actions_descriptor)

        grid_world_params = GridWorldParams('MapE')
        grid_world_params.tile_size = 3
        node_grid_world = GridWorldNode(grid_world_params)

        random_action_generator = RandomNumberNode(upper_bound=len(actions_descriptor.action_names()))

        join_node = JoinNode(flatten=True)

        # GridWorld sizes
        width = grid_world_params.egocentric_width * grid_world_params.tile_size
        height = grid_world_params.egocentric_height * grid_world_params.tile_size
        fork_node = ForkNode(dim=0, split_sizes=[width * height, 4])

        self.add_node(node_grid_world)
        self.add_node(node_action_monitor)
        self.add_node(random_action_generator)
        self.add_node(join_node)
        self.add_node(fork_node)

        Connector.connect(node_grid_world.outputs.egocentric_image, join_node.inputs[0])
        Connector.connect(node_grid_world.outputs.output_action, join_node.inputs[1])

        self._create_and_connect_agent(join_node, fork_node)

        Connector.connect(random_action_generator.outputs.one_hot_output,
                          node_action_monitor.inputs.action_in)
        Connector.connect(node_action_monitor.outputs.action_out,
                          node_grid_world.inputs.agent_action)
    def _connect_expert_output(self):
        self.fork_node = ForkNode(1, [
            self._almost_top_level_expert_output_size(self._params_list),
            self.se_io.get_num_labels()
        ])
        self.add_node(self.fork_node)

        # top-level-expert -> fork
        Connector.connect(
            self._top_level_flock_node.outputs.sp.current_reconstructed_input,
            self.fork_node.inputs.input)
        # fork -> dataset/se
        Connector.connect(self.fork_node.outputs[1],
                          self.se_io.inputs.agent_to_task_label,
                          is_backward=True)
    def __init__(self,
                 input_data_size: int,
                 labels_size: int,
                 sp_params: Optional[ExpertParams] = None,
                 name: str = "",
                 seed: Optional[int] = None):
        super().__init__("SpReconstructionLayer",
                         inputs=ClassificationInputs(self),
                         outputs=ClassificationOutputs(self))

        join_node = JoinNode(n_inputs=2, flatten=True, name=name + " Join")
        self.add_node(join_node)
        self.join_node = join_node
        Connector.connect(self.inputs.data.output, join_node.inputs[0])
        Connector.connect(self.inputs.label.output, join_node.inputs[1])

        unsqueeze_node = UnsqueezeNode(0)
        self.add_node(unsqueeze_node)
        Connector.connect(join_node.outputs.output,
                          unsqueeze_node.inputs.input)

        if sp_params is None:
            sp_params = ExpertParams()

        sp_node = SpatialPoolerFlockNode(sp_params,
                                         name=name + " SP Expert",
                                         seed=seed)
        self.add_node(sp_node)
        self.sp_node = sp_node
        Connector.connect(unsqueeze_node.outputs.output,
                          sp_node.inputs.sp.data_input)

        fork_node = ForkNode(1, [input_data_size, labels_size],
                             name=name + " Fork")
        self.add_node(fork_node)
        self.fork_node = fork_node
        Connector.connect(sp_node.outputs.sp.current_reconstructed_input,
                          fork_node.inputs.input)

        Connector.connect(fork_node.outputs[1], self.outputs.label.input)
    def __init__(self, curriculum: tuple = (1, -1)):
        super().__init__()

        se_config = SpaceEngineersConnectorConfig()
        se_config.render_width = 16
        se_config.render_height = 16
        se_config.curriculum = list(curriculum)

        base_expert_params = ExpertParams()
        base_expert_params.flock_size = 1
        base_expert_params.n_cluster_centers = 100
        base_expert_params.compute_reconstruction = False
        base_expert_params.spatial.cluster_boost_threshold = 1000
        base_expert_params.spatial.learning_rate = 0.2
        base_expert_params.spatial.batch_size = 1000
        base_expert_params.spatial.buffer_size = 1010
        base_expert_params.spatial.learning_period = 100

        base_expert_params.temporal.batch_size = 1000
        base_expert_params.temporal.buffer_size = 1010
        base_expert_params.temporal.learning_period = 200
        base_expert_params.temporal.forgetting_limit = 20000

        # parent_expert_params = ExpertParams()
        # parent_expert_params.flock_size = 1
        # parent_expert_params.n_cluster_centers = 20
        # parent_expert_params.compute_reconstruction = True
        # parent_expert_params.temporal.exploration_probability = 0.9
        # parent_expert_params.spatial.cluster_boost_threshold = 1000
        # parent_expert_params.spatial.learning_rate = 0.2
        # parent_expert_params.spatial.batch_size = 1000
        # parent_expert_params.spatial.buffer_size = 1010
        # parent_expert_params.spatial.learning_period = 100
        # parent_expert_params.temporal.context_without_rewards_size = se_config.LOCATION_SIZE_ONE_HOT

        # SE nodes
        actions_descriptor = SpaceEngineersActionsDescriptor()
        node_se_connector = SpaceEngineersConnectorNode(
            actions_descriptor, se_config)
        node_action_monitor = ActionMonitorNode(actions_descriptor)

        # flock-related nodes
        flock_node = ExpertFlockNode(base_expert_params)
        blank_task_control = ConstantNode((se_config.TASK_CONTROL_SIZE, ))
        blank_task_labels = ConstantNode((20, ))

        # parent_flock_node = ExpertFlockNode(parent_expert_params)

        join_node = JoinNode(flatten=True)

        actions = ['FORWARD', 'BACKWARD', 'LEFT', 'RIGHT']
        action_count = len(actions)

        pass_actions_node = PassNode(output_shape=(action_count, ),
                                     name="pass actions")
        fork_node = ForkNode(
            0, [base_expert_params.n_cluster_centers, action_count])

        def squeeze(inputs, outputs):
            outputs[0].copy_(inputs[0].squeeze())

        squeeze_node = LambdaNode(
            squeeze,
            1, [(base_expert_params.n_cluster_centers + action_count, )],
            name="squeeze lambda node")

        def stack_and_unsqueeze(inputs, outputs):
            outputs[0].copy_(torch.stack([inputs[0], inputs[1]]).unsqueeze(0))

        stack_unsqueeze_node = LambdaNode(
            stack_and_unsqueeze,
            2, [(1, 2, se_config.LOCATION_SIZE_ONE_HOT)],
            name="stack and unsqueeze node")

        to_one_hot_node = ToOneHotNode()

        action_parser_node = AgentActionsParserNode(actions_descriptor,
                                                    actions)

        random_node = RandomNumberNode(0,
                                       action_count,
                                       name="random action generator",
                                       generate_new_every_n=5,
                                       randomize_intervals=True)

        switch_node = SwitchNode(2)

        # add nodes to the graph
        self.add_node(flock_node)
        # self.add_node(parent_flock_node)
        self.add_node(node_se_connector)
        self.add_node(node_action_monitor)
        self.add_node(blank_task_control)
        self.add_node(blank_task_labels)
        # self.add_node(join_node)
        # self.add_node(fork_node)
        # self.add_node(pass_actions_node)
        # self.add_node(squeeze_node)
        # self.add_node(to_one_hot_node)
        # self.add_node(stack_unsqueeze_node)
        self.add_node(action_parser_node)
        self.add_node(random_node)
        # self.add_node(switch_node)

        # first layer
        Connector.connect(node_se_connector.outputs.image_output,
                          flock_node.inputs.sp.data_input)

        # Connector.connect(
        #     flock_node.outputs.tp.projection_outputs,
        #     join_node.inputs[0]
        # )
        # Connector.connect(
        #     pass_actions_node.outputs.output,
        #     join_node.inputs[1]
        # )

        # # second layer
        # Connector.connect(
        #     join_node.outputs.output,
        #     parent_flock_node.inputs.sp.data_input
        # )

        # Connector.connect(
        #     node_se_connector.outputs.task_to_agent_location_one_hot,
        #     stack_unsqueeze_node.inputs[0]
        # )
        # Connector.connect(
        #     node_se_connector.outputs.task_to_agent_location_target_one_hot,
        #     stack_unsqueeze_node.inputs[1]
        # )
        # Connector.connect(
        #     stack_unsqueeze_node.outputs[0],
        #     parent_flock_node.inputs.tp.context_input
        # )
        #
        # # actions
        # Connector.connect(
        #     parent_flock_node.outputs.sp.predicted_reconstructed_input,
        #     squeeze_node.inputs[0]
        # )
        # Connector.connect(
        #     squeeze_node.outputs[0],
        #     fork_node.inputs.input
        # )
        # Connector.connect(
        #     fork_node.outputs[1],
        #     to_one_hot_node.inputs.input
        # )
        # Connector.connect(
        #     random_node.outputs.one_hot_output,
        #     switch_node.inputs[0]
        # )
        # Connector.connect(
        #     to_one_hot_node.outputs.output,
        #     switch_node.inputs[1]
        # )
        # Connector.connect(
        #     switch_node.outputs.output,
        #     action_parser_node.inputs.input
        # )
        # directly use random exploration
        Connector.connect(random_node.outputs.one_hot_output,
                          action_parser_node.inputs.input)

        Connector.connect(action_parser_node.outputs.output,
                          node_action_monitor.inputs.action_in)
        # Connector.connect(
        #     switch_node.outputs.output,
        #     pass_actions_node.inputs.input,
        #     is_low_priority=True
        # )
        Connector.connect(
            node_action_monitor.outputs.action_out,
            node_se_connector.inputs.agent_action,
            # is_low_priority=True
            is_backward=False)

        # blank connection
        Connector.connect(blank_task_control.outputs.output,
                          node_se_connector.inputs.task_control)
        Connector.connect(blank_task_labels.outputs.output,
                          node_se_connector.inputs.agent_to_task_label)

        # Save the SE connector so we can check testing/training phase.
        # When the se_io interface has been added, this can be removed.
        self._node_se_connector = node_se_connector
    def create_topology(self):
        """
                                        +----------------+
            +-------------+             | dataset_switch |
            |             |             +--+-----+-------+
            |             v                |     |
            |  +----------+------------+   |     |
            |  | context_feedback_pass |   |     |
            |  +--------------------+--+   |     |
            |                       |      |     |
            |                       v      v     |
            |               +-------+------+--+  |
            |               | gate_input_join |  |
            |               +-------+---------+  |
            |                       |            |
            |                       v            |
            |              +--------+---------+  |
            |              | gate_input_noise |  |
            |              +--------+---------+  |
            |                       |            |
            |                       v            |
            |                   +---+--+         |
            |                   | gate |         |
            |                   +---+--+         |
            |                       |            |
            |                       v            |
            |               +-------+--------+   +--------+
            |               | format_context |   |        |
            |               +-------+--------+   |        |
            |                       |            v        |
            |                       |     +------+-----+  |
            |                       ---->-+ specialist |  |
            |                             +--+--------++  |
            |                                |        |   |
            +--------------------------------+        v   v
                                                   ++--------++
                                                   | accuracy |
                                                   +----------+
        """

        n_gate = SpatialPoolerFlockNode(
            ExpertParams(flock_size=self._params.flock_size,
                         n_cluster_centers=self._params.seq_count,
                         spatial=SpatialPoolerParams(
                             # input_size=3,
                             enable_learning=True,
                             buffer_size=self._params.gate_buffer_size,
                             batch_size=100,
                             learning_rate=0.2,
                             learning_period=10,
                             cluster_boost_threshold=100,
                             max_boost_time=200
                         ),
                         ),
            name="Gate"
        )
        self.add_node(n_gate)

        # Specialist
        n_specialist = SpecialistNodeGroup(SpecialistNodeGroupParams(
            flock_size=self._params.flock_size,
            n_symbols=len(self._params.symbols),
            gate_input_context_multiplier=self._params.gate_input_context_multiplier,
            gate_input_context_avg_window_size=self._params.gate_input_context_avg_window_size,
            seq_count=self._params.seq_count,
            convert_context_to_one_hot=self._params.convert_context_to_one_hot
        ))
        self.add_node(n_specialist)
        self._n_specialist = n_specialist

        n_context_feedback_pass = PassNode((self._params.flock_size, self._params.seq_count))
        n_gate_input_join = JoinNode(dim=1, n_inputs=2)
        n_gate_input_noise = RandomNoiseNode(RandomNoiseParams(amplitude=0.0001))
        n_format_context = SPFormatContextNodeGroup(self._params.seq_count, self._params.flock_size)

        self.add_node(n_context_feedback_pass)
        self.add_node(n_gate_input_join)
        self.add_node(n_gate_input_noise)
        self.add_node(n_format_context)

        # Dataset
        n_dataset_switch = DatasetSwitchNodeGroup(DatasetSwitchNodeGroupParams(
            dataset_params=DatasetAlphabetNodeGroupParams(
                flock_size=self._params.flock_size,
                symbols=self._params.symbols,
                seq_length=self._params.seq_length,
                seq_count=self._params.seq_count,
                seq_repeat=self._params.seq_repeat
            ),
            flock_split=self._params.flock_split
        ))

        self._n_dataset_switch = n_dataset_switch
        self.add_node(n_dataset_switch)

        # dataset to specialist
        Connector.connect(n_dataset_switch.outputs.output, n_specialist.inputs.input)
        # specialist to gate
        Connector.connect(n_specialist.outputs.context_feedback, n_context_feedback_pass.inputs.input, is_backward=True)
        Connector.connect(n_context_feedback_pass.outputs.output, n_gate_input_join.inputs[0])
        # dataset to gate
        Connector.connect(n_dataset_switch.outputs.sequence_id_one_hot, n_gate_input_join.inputs[1])
        Connector.connect(n_gate_input_join.outputs.output, n_gate_input_noise.inputs.input)
        Connector.connect(n_gate_input_noise.outputs.output, n_gate.inputs.sp.data_input)
        # gate to specialist
        Connector.connect(n_gate.outputs.sp.forward_clusters, n_format_context.inputs.input)
        Connector.connect(n_format_context.outputs.output, n_specialist.inputs.context_input)

        # Measuring accuracy
        # Fork
        n_fork_dataset = ForkNode(0, [self._params.flock_split, self._params.flock_size - self._params.flock_split])
        n_fork_prediction = ForkNode(0, [self._params.flock_split, self._params.flock_size - self._params.flock_split])
        self.add_node(n_fork_dataset)
        self.add_node(n_fork_prediction)
        Connector.connect(n_dataset_switch.outputs.output, n_fork_dataset.inputs.input)
        Connector.connect(n_specialist.outputs.output, n_fork_prediction.inputs.input)

        self._n_accuracy_single_1 = AccuracyNode(1, name='Accuracy single 1')
        self.add_node(self._n_accuracy_single_1)
        Connector.connect(n_fork_dataset.outputs[0], self._n_accuracy_single_1.inputs.input_a)
        Connector.connect(n_fork_prediction.outputs[0], self._n_accuracy_single_1.inputs.input_b)

        self._n_accuracy_single_2 = AccuracyNode(1, name='Accuracy single 2')
        self.add_node(self._n_accuracy_single_2)
        Connector.connect(n_fork_dataset.outputs[1], self._n_accuracy_single_2.inputs.input_a)
        Connector.connect(n_fork_prediction.outputs[1], self._n_accuracy_single_2.inputs.input_b)

        self._n_accuracy_1 = AccuracyNode(self._params.accuracy_average_steps, name='Accuracy 1')
        self.add_node(self._n_accuracy_1)
        Connector.connect(n_fork_dataset.outputs[0], self._n_accuracy_1.inputs.input_a)
        Connector.connect(n_fork_prediction.outputs[0], self._n_accuracy_1.inputs.input_b)

        self._n_accuracy_2 = AccuracyNode(self._params.accuracy_average_steps, name='Accuracy 2')
        self.add_node(self._n_accuracy_2)
        Connector.connect(n_fork_dataset.outputs[1], self._n_accuracy_2.inputs.input_a)
        Connector.connect(n_fork_prediction.outputs[1], self._n_accuracy_2.inputs.input_b)
    def __init__(self,
                 action_count=4,
                 location_vector_size=100,
                 use_grayscale: bool = False):
        super().__init__("Task 1 - Basic expert",
                         inputs=Task1BasicExpertGroupInputs(self),
                         outputs=Task1BasicExpertGroupOutputs(self))

        base_expert_params = ExpertParams()
        base_expert_params.flock_size = 1
        base_expert_params.n_cluster_centers = 100
        base_expert_params.compute_reconstruction = False
        base_expert_params.spatial.cluster_boost_threshold = 1000
        base_expert_params.spatial.learning_rate = 0.2
        base_expert_params.spatial.batch_size = 1000
        base_expert_params.spatial.buffer_size = 1010
        base_expert_params.spatial.learning_period = 100

        parent_expert_params = ExpertParams()
        parent_expert_params.flock_size = 1
        parent_expert_params.n_cluster_centers = 20
        parent_expert_params.compute_reconstruction = True
        parent_expert_params.temporal.exploration_probability = 0.9
        parent_expert_params.spatial.cluster_boost_threshold = 1000
        parent_expert_params.spatial.learning_rate = 0.2
        parent_expert_params.spatial.batch_size = 1000
        parent_expert_params.spatial.buffer_size = 1010
        parent_expert_params.spatial.learning_period = 100
        parent_expert_params.temporal.context_without_rewards_size = location_vector_size

        # flock-related nodes
        flock_node = ExpertFlockNode(base_expert_params)

        parent_flock_node = ExpertFlockNode(parent_expert_params)

        join_node = JoinNode(flatten=True)

        unsqueeze_node_to_base_expert = UnsqueezeNode(0)

        unsqueeze_node_to_parent_expert = UnsqueezeNode(0)

        fork_node = ForkNode(
            0, [base_expert_params.n_cluster_centers, action_count])

        def squeeze(inputs, outputs):
            outputs[0].copy_(inputs[0].squeeze())

        squeeze_node = LambdaNode(
            squeeze,
            1, [(base_expert_params.n_cluster_centers + action_count, )],
            name="squeeze lambda node")

        def stack_and_unsqueeze(inputs, outputs):
            outputs[0].copy_(torch.stack([inputs[0], inputs[1]]).unsqueeze(0))

        stack_unsqueeze_node = LambdaNode(stack_and_unsqueeze,
                                          2, [(1, 2, location_vector_size)],
                                          name="stack and unsqueeze node")

        # add nodes to the graph
        self.add_node(flock_node)
        self.add_node(unsqueeze_node_to_base_expert)
        self.add_node(parent_flock_node)
        self.add_node(unsqueeze_node_to_parent_expert)
        self.add_node(join_node)
        self.add_node(fork_node)
        self.add_node(squeeze_node)
        self.add_node(stack_unsqueeze_node)

        Connector.connect(self.inputs.actions.output, join_node.inputs[1])

        if use_grayscale:
            grayscale_node = GrayscaleNode(squeeze_channel=True)
            self.add_node(grayscale_node)
            Connector.connect(self.inputs.image.output,
                              grayscale_node.inputs.input)
            Connector.connect(grayscale_node.outputs.output,
                              unsqueeze_node_to_base_expert.inputs.input)
        else:
            Connector.connect(self.inputs.image.output,
                              unsqueeze_node_to_base_expert.inputs.input)

        Connector.connect(unsqueeze_node_to_base_expert.outputs.output,
                          flock_node.inputs.sp.data_input)

        Connector.connect(self.inputs.current_location.output,
                          stack_unsqueeze_node.inputs[0])

        Connector.connect(self.inputs.target_location.output,
                          stack_unsqueeze_node.inputs[1])

        Connector.connect(fork_node.outputs[1], self.outputs.actions.input)

        # first layer
        Connector.connect(flock_node.outputs.tp.projection_outputs,
                          join_node.inputs[0])

        # second layer
        Connector.connect(join_node.outputs.output,
                          unsqueeze_node_to_parent_expert.inputs.input)

        # second layer
        Connector.connect(unsqueeze_node_to_parent_expert.outputs.output,
                          parent_flock_node.inputs.sp.data_input)

        Connector.connect(stack_unsqueeze_node.outputs[0],
                          parent_flock_node.inputs.tp.context_input)

        # actions
        Connector.connect(
            parent_flock_node.outputs.sp.predicted_reconstructed_input,
            squeeze_node.inputs[0])
        Connector.connect(squeeze_node.outputs[0], fork_node.inputs.input)