# Path: deep_architect/searchers/common.py
# class Searcher:
#     """Abstract base class from which new searchers should inherit from.
# 
#     A search takes a function that when called returns a new search space, i.e.,
#     a search space where all the hyperparameters have not being specified.
#     Searchers essentially sample a sequence of models in the search space by
#     specifying the hyperparameters sequentially. After the sampled architecture
#     has been evaluated somehow, the state of the searcher can be updated with
#     the performance information, guaranteeing that future architectures
#     are sampled from the search space in a more informed manner.
# 
#     Args:
#         search_space_fn (() -> (dict[str,deep_architect.core.Input], dict[str,deep_architect.core.Output], dict[str,deep_architect.core.Hyperparameter])):
#             Search space function that when called returns a dictionary of
#             inputs, dictionary of outputs, and dictionary of hyperparameters
#             encoding the search space from which models can be sampled by
#             specifying all hyperparameters (i.e., both those arising in the
#             graph part and those in the dictionary of hyperparameters).
#         reset_default_scope_upon_get (bool): Whether to clean the scope upon getting
#             a new search space. Should be ``True`` unless you want to persist models
#             across samples.
#     """
# 
#     def __init__(self, search_space_fn, reset_default_scope_upon_sample=True):
#         x = mo.SearchSpaceFactory(search_space_fn,
#                                   reset_default_scope_upon_sample)
#         self.search_space_fn = x.get_search_space
# 
#     def sample(self):
#         """Returns a model from the search space.
# 
#         Models are encoded via a dictionary of inputs, a dictionary of outputs,
#         and a dictionary of hyperparameters. The forward computation for the
#         model can then be done as all values for the hyperparameters have been
#         chosen.
# 
#         Returns:
#             (dict[str, deep_architect.core.Input], dict[str, deep_architect.core.Output], dict[str, deep_architect.core.Hyperparameter], list[object], dict[str, object]):
#                 Tuple encoding the model sampled from the search space.
#                 The positional arguments have the following semantics:
#                 1: Dictionary of names to inputs of the model.
#                 2: Dictionary of names to outputs of the model.
#                 3: List with list of values that can be to replay the sequence
#                 of values assigned to the hyperparameters, and therefore,
#                 reproduce, given the search space, the model sampled.
#                 4: Searcher evaluation token that is sufficient for the searcher
#                 to update its state when combined with the results of the
#                 evaluation.
#         """
#         raise NotImplementedError
# 
#     def update(self, val, searcher_eval_token):
#         """Updates the state of the searcher based on the searcher token
#         for a particular evaluation and the results of the evaluation.
# 
#         Args:
#             val (object): Result of the evaluation to use to update the state of the searcher.
#             searcher_eval_token (dict[str, object]): Searcher evaluation token
#                 that is sufficient for the searcher to update its state when
#                 combined with the results of the evaluation.
#         """
#         raise NotImplementedError
# 
#     # TODO: needs to be changed to a nicer API. use sparsely.
#     # NOTE: this solution only allows a single saved state per frolder.
#     def save_state(self, folderpath):
#         raise NotImplementedError
# 
#     def load_state(self, folderpath):
#         raise NotImplementedError


import deep_architect.core as co
import deep_architect.hyperparameters as hp
import numpy as np
import deep_architect.utils as ut
import os
from deep_architect.searchers.common import Searcher

    # expands a node creating all the placeholders for the children.
    def expand(self, num_children):
        self.children = [MCTSTreeNode(self) for _ in range(num_children)]

    #### TODO: remove serialize and deserialize and
    @staticmethod
    def serialize(node):
        children = [] if node.children is None else [
            MCTSTreeNode.serialize(child) for child in node.children
        ]
        return (node.num_trials, node.sum_scores, children)

    @staticmethod
    def deserialize(serialization, parent=None):
        node = MCTSTreeNode(parent)
        node.num_trials = serialization[0]
        node.sum_scores = serialization[1]
        node.children = [
            MCTSTreeNode.deserialize(child, node) for child in serialization[2]
        ]
        return node


class MCTSSearcher(Searcher):

    def __init__(self,
                 search_space_fn,
                 exploration_bonus=1.0,
                 reset_default_scope_upon_sample=True):

# Path: deep_architect/searchers/common.py
# class Searcher:
#     """Abstract base class from which new searchers should inherit from.
# 
#     A search takes a function that when called returns a new search space, i.e.,
#     a search space where all the hyperparameters have not being specified.
#     Searchers essentially sample a sequence of models in the search space by
#     specifying the hyperparameters sequentially. After the sampled architecture
#     has been evaluated somehow, the state of the searcher can be updated with
#     the performance information, guaranteeing that future architectures
#     are sampled from the search space in a more informed manner.
# 
#     Args:
#         search_space_fn (() -> (dict[str,deep_architect.core.Input], dict[str,deep_architect.core.Output], dict[str,deep_architect.core.Hyperparameter])):
#             Search space function that when called returns a dictionary of
#             inputs, dictionary of outputs, and dictionary of hyperparameters
#             encoding the search space from which models can be sampled by
#             specifying all hyperparameters (i.e., both those arising in the
#             graph part and those in the dictionary of hyperparameters).
#         reset_default_scope_upon_get (bool): Whether to clean the scope upon getting
#             a new search space. Should be ``True`` unless you want to persist models
#             across samples.
#     """
# 
#     def __init__(self, search_space_fn, reset_default_scope_upon_sample=True):
#         x = mo.SearchSpaceFactory(search_space_fn,
#                                   reset_default_scope_upon_sample)
#         self.search_space_fn = x.get_search_space
# 
#     def sample(self):
#         """Returns a model from the search space.
# 
#         Models are encoded via a dictionary of inputs, a dictionary of outputs,
#         and a dictionary of hyperparameters. The forward computation for the
#         model can then be done as all values for the hyperparameters have been
#         chosen.
# 
#         Returns:
#             (dict[str, deep_architect.core.Input], dict[str, deep_architect.core.Output], dict[str, deep_architect.core.Hyperparameter], list[object], dict[str, object]):
#                 Tuple encoding the model sampled from the search space.
#                 The positional arguments have the following semantics:
#                 1: Dictionary of names to inputs of the model.
#                 2: Dictionary of names to outputs of the model.
#                 3: List with list of values that can be to replay the sequence
#                 of values assigned to the hyperparameters, and therefore,
#                 reproduce, given the search space, the model sampled.
#                 4: Searcher evaluation token that is sufficient for the searcher
#                 to update its state when combined with the results of the
#                 evaluation.
#         """
#         raise NotImplementedError
# 
#     def update(self, val, searcher_eval_token):
#         """Updates the state of the searcher based on the searcher token
#         for a particular evaluation and the results of the evaluation.
# 
#         Args:
#             val (object): Result of the evaluation to use to update the state of the searcher.
#             searcher_eval_token (dict[str, object]): Searcher evaluation token
#                 that is sufficient for the searcher to update its state when
#                 combined with the results of the evaluation.
#         """
#         raise NotImplementedError
# 
#     # TODO: needs to be changed to a nicer API. use sparsely.
#     # NOTE: this solution only allows a single saved state per frolder.
#     def save_state(self, folderpath):
#         raise NotImplementedError
# 
#     def load_state(self, folderpath):
#         raise NotImplementedError


# Path: deep_architect/searchers/mcts.py
import deep_architect.core as co
import deep_architect.hyperparameters as hp
import numpy as np
import deep_architect.utils as ut
import os
from deep_architect.searchers.common import Searcher


    # expands a node creating all the placeholders for the children.
    def expand(self, num_children):
        self.children = [MCTSTreeNode(self) for _ in range(num_children)]

    #### TODO: remove serialize and deserialize and
    @staticmethod
    def serialize(node):
        children = [] if node.children is None else [
            MCTSTreeNode.serialize(child) for child in node.children
        ]
        return (node.num_trials, node.sum_scores, children)

    @staticmethod
    def deserialize(serialization, parent=None):
        node = MCTSTreeNode(parent)
        node.num_trials = serialization[0]
        node.sum_scores = serialization[1]
        node.children = [
            MCTSTreeNode.deserialize(child, node) for child in serialization[2]
        ]
        return node


class MCTSSearcher(Searcher):

    def __init__(self,
                 search_space_fn,
                 exploration_bonus=1.0,
                 reset_default_scope_upon_sample=True):

        Searcher.__init__(self, search_space_fn,