def __add_generator(self, cases, config):
        # select one case to take care of
        index = random.randrange(len(cases))
        feature_generator = cases[index][0]
        bond = cases[index][1]

        print 'selected feature generator', cases[index][0].get_name()
        print 'path', cases[index][0].get_features()
        print 'bond value', cases[index][1].get_value()

        # Get labels by bond value/modality
        #labels = self.__ontology.get_equivalent_labels(g.get_name());
        labels = self.__ontology.get_labels_per_modality(bond.get_value())
        print 'labels', labels

        # Select label from the list of possible labels
        index = random.randrange(len(labels))
        label = labels[index]
        print 'selected label', label

        cost = 0
        time = feature_generator.get_time()
        level = self.__ontology.get_level(label)
        modalities = self.__ontology.get_modalities(label)
        bond_structure = self.__ontology.get_bond_structure(label)

        # name,level,modality,type,cost,time
        new_generator = MyGenerator(label, level, modalities, 'ontological',
                                    cost, time)

        # set feature location associated to the new candidate
        new_generator.set_rlocation(feature_generator.get_rlocation())

        # define the bond structure of the generator here
        new_generator.set_bond_structure(bond_structure)

        config.add_generator(new_generator)

        # make changes to the connection of the new configuration given the newly added generator
        self.__place_support_bonds(feature_generator, new_generator, config)
        self.__place_ontological_bonds(new_generator, config)

        return config
    def propose(self,
                config,
                with_temporal_bonds=False,
                selection_type='level',
                feature_thresh=0.5):
        ontological_generators = config.get_ontological_generators()
        if not ontological_generators: return config

        # First, select an ontological generator to be replaced in the configuration
        g_r = None
        level = 4
        while level == 4:
            g_r_index = self.sample('uniform', ontological_generators)
            g_r = ontological_generators[g_r_index]
            level = g_r.get_level()
        g_r_inbonds = g_r.get_inbonds()
        unconnected_feature_generators = []

        # Second, find the feature generators that explain such ontological generator
        cost = 0
        time = g_r.get_time()
        for inbond in g_r_inbonds:
            print 'inbond connector: ', inbond.get_value(
            ), inbond.get_connector()
            if inbond.get_connector() != None:
                g = config.get_generator_by(inbond.get_connector(), 'id')
                if g.get_type() == 'feature':
                    unconnected_feature_generators.append(g)

        print 'unconnected features: ', repr(unconnected_feature_generators)
        # print configuration information
        # config.print_info()

        # for when feature generator close their inbonds with outbonds of ontological generators
        if not unconnected_feature_generators:
            g_r_outbonds = g_r.get_outbonds()
            for outbond in g_r_outbonds:
                if outbond.get_connector() != None:
                    g = config.get_generator_by(outbond.get_connector())
                    if g.get_type() == 'feature':
                        unconnected_feature_generators.append(g)

        print 'REPLACE ', g_r.get_name()

        labels = None
        if selection_type == 'level':
            labels = self.__ontology.get_same_level_labels(g_r.get_name())
        else:  # if by modality
            labels = self.__ontology.get_equivalent_labels(g_r.get_name())

#        print 'candidates for replacement: ',labels

# select candidates uniformly random
        k = self.__k if self.__k <= len(labels) else max(self.__k, len(labels))
        candidates = random.sample(labels, k)

        candidate_configs = []
        candidate_generators = []
        for i in range(len(candidates)):
            # collect all data to create a generator
            label_name = candidates[i]
            level = self.__ontology.get_level(label_name)
            modalities = self.__ontology.get_modalities(label_name)
            bond_structure = self.__ontology.get_bond_structure(label_name)

            # name,level,modality,type,cost,time
            new_generator = MyGenerator(label_name, level, modalities,
                                        'ontological', cost, time)

            # set feature location associated to the new candidate
            new_generator.set_rlocation(g_r.get_rlocation())

            # define the bond structure of the generator here
            new_generator.set_bond_structure(bond_structure)

            # create a new configuration in which the new candidate replaces the one to be remove
            #print 'removing generator ',g_r.get_id()
            new_config = config.replace(g_r, new_generator)

            # make changes to the connection of the new configuration given the newly added generator
            for feature_generator in unconnected_feature_generators:
                self.__place_support_bonds(feature_generator, new_generator,
                                           new_config, feature_thresh)
            self.__place_ontological_bonds(new_generator, new_config)

            # record newly proposed configs with
            candidate_configs.append(new_config)
            candidate_generators.append(new_generator)
            #self.__update_bonds_after_replacement(new_config,g_f,new_generator)

        if with_temporal_bonds:
            #print 'LOCAL PROPOSAL: TRYING TEMPORAL BONDS...'
            time.sort()

            # account for temporal bonds linking to the past
            for i in range(len(candidate_generators)):
                self.__place_past_ontological_bonds(candidate_generators[i],
                                                    candidate_configs[i],
                                                    time[0])

            # account for temporal connections linking to the future
            for i in range(len(candidate_generators)):
                self.__place_future_ontological_bonds(candidate_generators[i],
                                                      candidate_configs[i],
                                                      time[-1])

        # compute local energy contributions by new candidates configuration
        candidate_local_energies = []
        for i in range(len(candidate_generators)):
            # get local energy that is being contributed by new
            #candidate_configs[i].print_info()
            local_energy = candidate_configs[i].get_local_energy(
                candidate_generators[i], self.__bond_weights)
            #print 'i#', i, ' local energy: ', local_energy
            candidate_local_energies.append(local_energy)
#            os.system("read -p 'pause'")

# select a candidate according to probability computed based on their locally contributed energy
        candidate_index = self.__probabilistic_candidate_selection(
            candidate_local_energies)

        # update the current configuration to the new one
        #self.__discard_configuration(config) # check if the upper code is making a copy of this configuration for other purposes
        new_proposal = candidate_configs[candidate_index]

        # discard temporary configurations, and others...
        del candidate_configs[candidate_index]
        for i in range(len(candidate_configs)):
            self.__discard_configuration(candidate_configs[0])

        del labels
        return new_proposal
    def __construct_new_configuration(self, config, candidates,
                                      feature_generator):
        # follow similar idea of function below...
        candidate_configs = []
        candidate_generators = []

        feature_generator.get_time().sort()
        current_time = feature_generator.get_time()[-1]

        for i in range(len(candidates)):
            # collect all data to create a generator
            label_name = candidates[i]
            level = self.__ontology.get_level(label_name)
            modalities = self.__ontology.get_modalities(label_name)
            bond_structure = self.__ontology.get_bond_structure(label_name)

            # name,level,modality,type,cost,time
            ontological_generator = MyGenerator(label_name, level, modalities,
                                                'ontological', 0,
                                                [current_time])

            # set feature location associated to the new candidate
            ontological_generator.set_rlocation(
                feature_generator.get_rlocation())

            # define the bond structure of the generator here
            ontological_generator.set_bond_structure(bond_structure)

            # create a new configuration in which the new candidate replaces the one to be remove
            new_config = config.get_copy()
            new_config.add_generator(ontological_generator)

            candidate_generators.append(ontological_generator)

            # make changes to the connection of the new configuration given the newly added generator
            #self.__update_bonds_after_replacement(new_config, g_f, g_c)
            self.__place_support_bonds(feature_generator,
                                       ontological_generator, new_config)
            self.__place_ontological_bonds(ontological_generator, new_config)

            # record newly proposed configs with
            candidate_configs.append(new_config)

        # account for temporal bonds linking to the past
        for i in range(len(candidate_generators)):
            self.__place_past_ontological_bonds(candidate_generators[i],
                                                candidate_configs[i],
                                                current_time)

        # account for temporal connections linking to the future
        #for i in range(len(candidate_generators)):
        #    self.__place_future_ontological_bonds(candidate_generator[i],candidate_configs[i],current_time)

        # compute local energy contributions by new candidates configuration
        candidate_local_energies = []
        for i in range(len(candidate_generators)):
            # get local energy that is being contributed by new
            local_energy = candidate_configs[i].get_local_energy(
                candidate_generators[i], self.__bond_weights)
            candidate_local_energies.append(local_energy)

        # select a candidate according to probability computed based on their locally contributed energy
        candidate_index = self.__probabilistic_candidate_selection(
            candidate_local_energies)

        # update the current configuration to the new one
        #self.__discard_configuration(config) # check if the upper code is making a copy of this configuration for other purposes
        new_config = candidate_configs[candidate_index]

        # discard temporary configurations, and others...
        del candidate_configs[candidate_index]
        for i in range(len(candidate_configs)):
            self.__discard_configuration(candidate_configs[0])

        return new_config
    def time_based_propose(self, config, current_time):
        # probability of maintaining the same explanation
        change_explanation = True
        same_state_prob = 0.3
        if random.uniform(0, 1) < same_state_prob:
            change_explanation = False

        feature_generators = config.get_feature_generators()

        newly_added_feature_generators = []
        past_feature_generators = []
        past_feature_generator_connectors = {}
        past_inbond_labels = []
        for feature_generator in feature_generators:
            if self.__intersect(feature_generator.get_time(), [current_time]):
                newly_added_feature_generators.append(feature_generator)
            elif self.__intersect(feature_generator.get_time(),
                                  [current_time - self.__time_unit]):
                past_feature_generators.append(feature_generator)
                # used to know to which ontological generator to connect
                # (case when aiming at keeping the same explanation for the new feat generators)
                past_feature_generator_connectors[
                    feature_generator.get_id()] = []
                for bond in feature_generator.get_outbonds():
                    # check to which one it is
                    if bond.get_connector() != None:
                        #past_feature_generator_connectors[feature_generator.get_id()].append(bond)
                        if bond.get_value(
                        ) not in past_feature_generator_connectors:
                            past_feature_generator_connectors[
                                bond.get_value()] = []
                        past_feature_generator_connectors[
                            bond.get_value()].append((feature_generator, bond))

                for bond in feature_generator.get_inbonds():
                    #coordinate = bond.get_connector_coordinate()
                    ontological_generator = config.get_generator_by(
                        bond.get_connector())
                    past_inbond_labels.append(ontological_generator.get_name())
                    #outbond = ontological_generator.get_outbond_by_coordinate(coordinate)
                    #past_labels.append()

        #print len(newly_added_feature_generators),' newly added features: ',repr(newly_added_feature_generators)
        #print len(past_feature_generators),' past feature generators: ',repr(past_feature_generators)
        #print len(past_inbond_labels),' past inbond labels: ',repr(past_inbond_labels)

        #any_feature = newly_added_feature_generators[random.randrange(len(newly_added_feature_generators))]
        if not past_feature_generators and not past_inbond_labels and not len(
                config.get_ontological_generators()):
            for feature_generator in newly_added_feature_generators:
                top_labels = feature_generator.get_top_labels()
                index = random.randrange(len(top_labels))
                label = top_labels[index]

                #print 'feature: ' + g_f.get_name()
                #print ' top labels: '

                #print 'selected label: ',label.name
                #print 'level: ',self.__ontology.get_level(label.name)
                ontological_generator = MyGenerator(
                    label.name, self.__ontology.get_level(label.name),
                    self.__ontology.get_modalities(label.name), 'ontological',
                    label.cost, [current_time])

                #print 'feature time: ', feature.time
                ontological_generator.set_rlocation(
                    feature_generator.get_rlocation())

                bond_structure = self.__ontology.get_bond_structure(label.name)
                ontological_generator.set_bond_structure(bond_structure)

                config.add_generator(ontological_generator)
                self.__place_support_bonds(feature_generator,
                                           ontological_generator, config)

                #print 'ontological generators ',len(config.get_ontological_generators())
                self.__place_ontological_bonds(ontological_generator, config)

            return config

        if not change_explanation:
            new_ontological_generators = []
            for feature_generator in newly_added_feature_generators:
                #print 'feature id: ',feature_generator.get_id(),' feature name: ',feature_generator.get_name()

                # for when feature generators's outbonds connect to ontological generators's inbonds
                for bond in feature_generator.get_outbonds():
                    #print 'out of outbond connections...'
                    # if current generator can connect to existing concepts already explaining past features
                    if bond.get_value(
                    ) in past_feature_generator_connectors:  # escolher a mesma configuracao ou uma nova dada certa probabilidade
                        # find out what the concept is
                        past_bond = past_feature_generator_connectors[
                            bond.get_value()][1]
                        if bond.get_connector() != past_bond.get_connector():
                            if bond.get_connector() != None:
                                config.remove_generator(bond.get_connector())
                            ontological_generator = config.get_generator_by(
                                past_bond.get_connector())
                            ontological_generator.get_time().append(
                                current_time)
                            self.__place_support_bonds(feature_generator,
                                                       ontological_generator,
                                                       config)

                # for when ontological generators's outbonds connect to feature generators's inbonds
                context_labels = []
                for label in past_inbond_labels:
                    bond_structure = self.__ontology.get_bond_structure(label)

                    for bond in bond_structure:
                        bond_value = bond[0]
                        if self.__intersect([bond_value],
                                            feature_generator.get_modality()):
                            context_labels.append(label)

                if context_labels:
                    #print 'out of inbond connections...'
                    label = context_labels[random.randrange(
                        len(context_labels))]
                    level = self.__ontology.get_level(label)
                    modalities = self.__ontology.get_modalities(label)

                    ontological_generator = MyGenerator(
                        label, level, modalities, 'ontological', 0,
                        [current_time])
                    ontological_generator.set_rlocation(
                        feature_generator.get_rlocation())

                    bond_structure = self.__ontology.get_bond_structure(label)
                    ontological_generator.set_bond_structure(bond_structure)

                    # remove old explanation if it exists before proposing a new one
                    #print 'before - number of inbonds: ',len(feature_generator.get_inbonds())
                    #config.print_info()

                    connectors = []
                    for inbond in feature_generator.get_inbonds():
                        connectors.append(inbond.get_connector())
                        #print 'remove connector ',inbond.get_connector()
                    for generator_id in connectors:
                        config.remove_generator(generator_id)
                    del connectors
                    #print 'after - number of inbonds: ',len(feature_generator.get_inbonds())

                    config.add_generator(ontological_generator)
                    new_ontological_generators.append(ontological_generator)

                    self.__place_support_bonds(feature_generator,
                                               ontological_generator, config)
                    #print 'after connecting new generator: ',len(feature_generator.get_inbonds())
                    #config.print_info()
                    self.__place_ontological_bonds(ontological_generator,
                                                   config)

                    # move to explain the next feature generator
                    #past_inbond_labels.remove(label)
                    #break

            #for generator in new_ontological_generators:

            return config

#        print 'NEW EXPLANATIONS BASED ON THE PAST'
# if new feature are to receive new explanations based on the past
        new_ontological_generators = []
        for feature_generator in newly_added_feature_generators:
            # for when feature generators's outbonds connect to ontological generators's inbonds
            for bond in feature_generator.get_outbonds():
                # if current generator can connect to existing concepts already explaining past features
                if bond.get_value() in past_feature_generator_connectors:
                    # find out what the concept is
                    past_bond = past_feature_generator_connectors[
                        bond.get_value()][1]
                    generator_id = past_bond.get_connector()
                    g_k = config.get_generator_by(generator_id)

                    # find out the exact interaction
                    # this can be later resolved by having a composite generator that has a name that combines one or more
                    # generators (that will be easier to implement and faster -- only thought of this now)
                    label = g_k.get_name(
                    )  #self.__get_interaction_name(g_k,config)

                    # choose probabilistically whether to propose new explanations for the newly added features
                    # or to make them explain the current configuration state
                    #                    conditional_cooccurrences = self.__ontology.conditional_cooccurrence('interactions_'+bond.get_value(),label,True,order=1)

                    conditional_cooccurrences = self.__ontology.conditional_cooccurrence(
                        bond.get_value() + '_' + bond.get_value(),
                        label,
                        True,
                        order=1)

                    labels = conditional_cooccurrences.keys()
                    label_counts = conditional_cooccurrences.values()
                    label_index = self.__probabilistic_candidate_selection(
                        label_counts)
                    candidates = [labels[label_index]]
                else:
                    # get all candidate labels
                    labels = self.__ontology.get_equivalent_labels(
                        bond.get_value())
                    # select candidates uniformly random
                    candidates = random.sample(labels,
                                               max(self.__k, len(labels)))
                    del labels

                self.__construct_new_configuration(config, candidates,
                                                   feature_generator)

            # for when ontological generator's outbonds connect to feature generators's inbonds
            #connect = False
            context_labels = []
            for label in past_inbond_labels:
                bond_structure = self.__ontology.get_bond_structure(label)

                for bond in bond_structure:
                    bond_value = bond[0]
                    if self.__intersect([bond_value],
                                        feature_generator.get_modality()):
                        context_labels.append(label)
                        #connect = True

            if context_labels:
                label = context_labels[random.randrange(len(context_labels))]
                level = self.__ontology.get_level(label)
                #print 'level: ',repr(level)
                modality = ''
                if level == 3:
                    modality = 'actions'
                elif level == 2:
                    modality = 'objects'

                #print 'modality: ',modality
                conditional_cooccurrences = self.__ontology.conditional_occurrence(
                    modality + '_' + modality, label, True, order=1)
                labels = conditional_cooccurrences.keys()
                #print 'labels: ',repr(labels)

                label_counts = conditional_cooccurrences.values()
                index = self.__probabilistic_candidate_selection(label_counts)
                next_label = labels[index]

                level = self.__ontology.get_level(next_label)
                modalities = self.__ontology.get_modalities(next_label)
                bond_structure = self.__ontology.get_bond_structure(next_label)

                ontological_generator = MyGenerator(next_label, level,
                                                    modalities, 'ontological',
                                                    0, [current_time])
                ontological_generator.set_rlocation(
                    feature_generator.get_rlocation())
                ontological_generator.set_bond_structure(bond_structure)

                config.add_generator(ontological_generator)
                new_ontological_generators.append(ontological_generator)

                # remove old explanation if it exists before proposing a new one
                #print 'before - number of inbonds: ',len(feature_generator.get_inbonds())
                #config.print_info()
                connectors = []
                for inbond in feature_generator.get_inbonds():
                    connectors.append(inbond.get_connector())
                    #print 'remove connector ',inbond.get_connector()
                for generator_id in connectors:
                    config.remove_generator(generator_id)
                del connectors
                #print 'after - number of inbonds: ',len(feature_generator.get_inbonds())

                self.__place_support_bonds(feature_generator,
                                           ontological_generator, config)
                #config.print_info()

                #print 'after connecting new generator: ',len(feature_generator.get_inbonds())
                self.__place_ontological_bonds(ontological_generator, config)

                # move to explain the next feature generator
                #past_inbond_labels.remove(label)
                #break

        # account for temporal bonds linking to the past

        for generator in new_ontological_generators:
            self.__place_past_ontological_bonds(generator, config,
                                                current_time)

        return config