コード例 #1
0
    def __init__(self, variable: Variable,
                 factor_names: List[str], msg_sender=None,
                 comp_def: ComputationDef=None):
        """

        :param variable: variable object
        :param factor_names: a list containing the names of the factors that
        depend on the variable managed by this algorithm
        :param msg_sender: the object that will be used to send messages to
        neighbors, it must have a  post_msg(sender, target_name, name) method.
        """
        super().__init__(variable, comp_def)

        self._msg_handlers['max_sum'] = self._on_cost_msg

        # self._v = variable.clone()
        # Add noise to the variable, on top of cost if needed
        if hasattr(variable, 'cost_for_val'):
            self._v = VariableNoisyCostFunc(
                variable.name, variable.domain,
                cost_func=lambda x: variable.cost_for_val(x),
                initial_value= variable.initial_value)
        else:
            self._v = VariableNoisyCostFunc(
                variable.name, variable.domain,
                cost_func=lambda x: 0,
                initial_value= variable.initial_value)
            
        self.var_with_cost = True

        # the currently selected value, will evolve when the algorithm is
        # still running.
        # if self._v.initial_value:
        #     self.value_selection(self._v.initial_value, None)
        #
        # elif self.var_with_cost:
        #     current_cost, current_value =\
        #         min(((self._v.cost_for_val(dv), dv) for dv in self._v.domain ))
        #     self.value_selection(current_value, current_cost)

        # The list of factors (names) this variables is linked with
        self._factors = factor_names

        # The object used to send messages to factor
        self._msg_sender = msg_sender

        # costs : this dict is used to store, for each value of the domain,
        # the associated cost sent by each factor this variable is involved
        # with. { factor : {domain value : cost }}
        self._costs = {}

        self.logger = logging.getLogger('pydcop.maxsum.' + variable.name)
        self.cycle_logger = logging.getLogger('cycle')
        self._is_stable = False
        self._prev_messages = defaultdict(lambda: (None, 0))
コード例 #2
0
def maxsum_equality_noise():
    """
    This sample demonstrates the use of noise to break ties between variables.
    """

    l1 = VariableNoisyCostFunc('l1', list(range(10)), lambda x: x)
    l2 = VariableNoisyCostFunc('l2', list(range(10)), lambda x: x)

    # Scene action
    y1 = VariableWithCostFunc('y1', list(range(10)), lambda x: 10 * abs(5 - x))

    @AsNAryFunctionRelation(l1, l2, y1)
    def scene_rel(x, y, z):
        if z == x + y:
            return 0
        return 10000

    # Create computation for factors and variables
    # Light 1
    algo_l1 = VariableAlgo(l1, [scene_rel.name])

    # Light 2
    algo_l2 = VariableAlgo(l2, [scene_rel.name])

    # Scene
    algo_y1 = VariableAlgo(y1, [scene_rel.name])
    algo_scene = FactorAlgo(scene_rel)

    comm = InProcessCommunicationLayer()

    a1 = Agent('A1', comm)
    a1.add_computation(algo_l1)

    a2 = Agent('A2', comm)
    a2.add_computation(algo_l2)

    a3 = Agent('A3', comm)
    a3.add_computation(algo_y1)
    a3.add_computation(algo_scene)
    dcop_agents = [a1, a2, a3]

    results, _, _ = synchronous_single_run(dcop_agents, 5)

    print(results)

    if results['y1'] == 5 and results['l1'] + results['l2'] == 5:
        logging.info('SUCCESS !! ')
        return 0
    else:
        logging.info('invalid result found, needs some debugging ...' +
                     str(results))
        return 1
コード例 #3
0
    def test_from_repr_with_expression_function(self):
        domain = VariableDomain('d', 'd', [1, 2, 3, 4])
        cost_func = ExpressionFunction('v / 2')
        v = VariableNoisyCostFunc('v', domain, cost_func=cost_func)

        r = simple_repr(v)
        v2 = from_repr(r)

        # Due to the noise, the cost will de different
        c1 = v2.cost_for_val(2)
        c2 = v.cost_for_val(2)
        self.assertLessEqual(abs(c1 - c2), v.noise_level * 2)

        self.assertEquals(v2, v)
コード例 #4
0
ファイル: yamldcop.py プロジェクト: qslim/pyDcop
def _build_variables(loaded, dcop) -> Dict[str, Variable]:
    variables = {}
    if "variables" in loaded:
        for v_name in loaded["variables"]:
            v = loaded["variables"][v_name]
            domain = dcop.domain(v["domain"])
            initial_value = v["initial_value"] if "initial_value" in v else None
            if initial_value and initial_value not in domain.values:
                raise ValueError("initial value {} is not in the domain {} "
                                 "of the variable {}".format(
                                     initial_value, domain.name, v_name))

            if "cost_function" in v:
                cost_expression = v["cost_function"]
                cost_func = ExpressionFunction(cost_expression)
                if "noise_level" in v:
                    variables[v_name] = VariableNoisyCostFunc(
                        v_name,
                        domain,
                        cost_func,
                        initial_value,
                        noise_level=v["noise_level"],
                    )
                else:
                    variables[v_name] = VariableWithCostFunc(
                        v_name, domain, cost_func, initial_value)

            else:
                variables[v_name] = Variable(v_name, domain, initial_value)
    return variables
コード例 #5
0
ファイル: maxsum.py プロジェクト: khoihd/pyDcop
    def __init__(self, comp_def: ComputationDef):
        super().__init__(comp_def.node.variable, comp_def)
        assert comp_def.algo.algo == "maxsum"
        self.mode = comp_def.algo.mode
        self.logger.warning(f"Neiborghs {self.neighbors}")

        # The list of factors (names) this variables is linked with
        self.factors = [link.factor_node for link in comp_def.node.links]
        # costs : this dict is used to store, for each value of the domain,
        # the associated cost sent by each factor this variable is involved
        # with. { factor : {domain value : cost }}
        self.costs = {}

        # to store previous messages, necessary to detect convergence
        self._prev_messages = defaultdict(lambda: (None, 0))

        # TODO: restore support for damping
        # self.damping = comp_def.algo.params["damping"]
        # self.logger.info("Running maxsum with damping %s", self.damping)

        # Add noise to the variable, on top of cost if needed
        # TODO: make this configurable through parameters
        self._variable = VariableNoisyCostFunc(
            self.variable.name,
            self.variable.domain,
            cost_func=lambda x: self.variable.cost_for_val(x),
            initial_value=self.variable.initial_value,
        )
コード例 #6
0
ファイル: amaxsum.py プロジェクト: sankarshandamle/pyDcop
    def __init__(self, comp_def: ComputationDef = None):
        super().__init__(comp_def.node.variable, comp_def)
        assert comp_def.algo.algo == "amaxsum"

        self.mode = comp_def.algo.mode
        self.damping = comp_def.algo.params["damping"]
        self.damping_nodes = comp_def.algo.params["damping_nodes"]
        self.stability_coef = comp_def.algo.params["stability"]
        self.start_messages = comp_def.algo.params["start_messages"]
        self.logger.info(
            f"Running amaxsum with params: {comp_def.algo.params}")

        # The list of factors (names) this variables is linked with
        self._factors = [link.factor_node for link in comp_def.node.links]

        # Add noise to the variable, on top of cost if needed
        if comp_def.algo.params["noise"] != 0:
            self.logger.info(
                f"Adding noise on variable {comp_def.algo.params['noise']}")
            self._variable = VariableNoisyCostFunc(
                self.variable.name,
                self.variable.domain,
                cost_func=lambda x: self.variable.cost_for_val(x),
                initial_value=self.variable.initial_value,
                noise_level=comp_def.algo.params["noise"],
            )

        # costs : this dict is used to store, for each value of the domain,
        # the associated cost sent by each factor this variable is involved
        # with. { factor : {domain value : cost }}
        self._costs = {}  # type: Dict[str, Dict[Any, float]]

        self._prev_messages = defaultdict(lambda: (None, 0))
コード例 #7
0
def _build_variables(loaded, dcop) -> Dict[str, Variable]:
    variables = {}
    if 'variables' in loaded:
        for v_name in loaded['variables']:
            v = loaded['variables'][v_name]
            domain = dcop.domain(v['domain'])
            initial_value = v['initial_value'] if 'initial_value' in v \
                else None
            if initial_value and initial_value not in domain.values:
                raise ValueError('initial value {} is not in the domain {} '
                                 'of the variable {}'.format(
                                     initial_value, domain.name, v_name))

            if 'cost_function' in v:
                cost_expression = v['cost_function']
                cost_func = ExpressionFunction(cost_expression)
                if 'noise_level' in v:
                    variables[v_name] = VariableNoisyCostFunc(
                        v_name,
                        domain,
                        cost_func,
                        initial_value,
                        noise_level=v['noise_level'])
                else:
                    variables[v_name] = VariableWithCostFunc(
                        v_name, domain, cost_func, initial_value)

            else:
                variables[v_name] = Variable(v_name, domain, initial_value)
    return variables
コード例 #8
0
    def test_simple_repr_with_expression_function(self):
        domain = VariableDomain('d', 'd', [1, 2, 3, 4])
        cost_func = ExpressionFunction('v / 2')
        v = VariableNoisyCostFunc('v', domain, cost_func=cost_func)

        r = simple_repr(v)

        self.assertEquals(r['name'], 'v')
        self.assertEquals(r['cost_func']['expression'], 'v / 2')
コード例 #9
0
    def test_simple_repr_with_expression_function(self):
        domain = VariableDomain("d", "d", [1, 2, 3, 4])
        cost_func = ExpressionFunction("v / 2")
        v = VariableNoisyCostFunc("v", domain, cost_func=cost_func)

        r = simple_repr(v)

        self.assertEqual(r["name"], "v")
        self.assertEqual(r["cost_func"]["expression"], "v / 2")
コード例 #10
0
    def test_hash(self):
        d1 = VariableDomain("d", "foo", [1, 2, 3])
        v1 = VariableNoisyCostFunc("v1", d1, cost_func=ExpressionFunction("v1/2"))
        v1_othercosts = VariableNoisyCostFunc(
            "v1", d1, cost_func=ExpressionFunction("v1/3")
        )

        self.assertNotEqual(hash(v1), hash(v1_othercosts))

        v1_othername = VariableNoisyCostFunc(
            "v1_other", d1, cost_func=ExpressionFunction("v1_other/2")
        )

        self.assertNotEqual(hash(v1), hash(v1_othername))

        self.assertEqual(
            hash(v1),
            hash(VariableNoisyCostFunc("v1", d1, cost_func=ExpressionFunction("v1/2"))),
        )
コード例 #11
0
    def test_hash(self):
        d1 = VariableDomain('d', 'foo', [1, 2, 3])
        v1 = VariableNoisyCostFunc('v1',
                                   d1,
                                   cost_func=ExpressionFunction('v1/2'))
        v1_othercosts = \
            VariableNoisyCostFunc('v1', d1,
                                  cost_func=ExpressionFunction('v1/3'))

        self.assertNotEqual(hash(v1), hash(v1_othercosts))

        v1_othername = \
            VariableNoisyCostFunc('v1_other', d1,
                                  cost_func=ExpressionFunction('v1_other/2'))

        self.assertNotEqual(hash(v1), hash(v1_othername))

        self.assertEqual(
            hash(v1),
            hash(
                VariableNoisyCostFunc('v1',
                                      d1,
                                      cost_func=ExpressionFunction('v1/2'))))
コード例 #12
0
ファイル: amaxsum.py プロジェクト: khoihd/pyDcop
    def __init__(self, comp_def: ComputationDef = None):
        """

        :param variable: variable object
        :param factor_names: a list containing the names of the factors that
        depend on the variable managed by this algorithm
        :param msg_sender: the object that will be used to send messages to
        neighbors, it must have a  post_msg(sender, target_name, name) method.
        """
        super().__init__(comp_def.node.variable, comp_def)

        assert comp_def.algo.algo == "amaxsum"
        assert (comp_def.algo.mode == "min") or (comp_def.algo.mode == "max")

        self.mode = comp_def.algo.mode

        # Add noise to the variable, on top of cost if needed
        # TODO: make this configurable through parameters
        self._variable = VariableNoisyCostFunc(
            self.variable.name,
            self.variable.domain,
            cost_func=lambda x: self.variable.cost_for_val(x),
            initial_value=self.variable.initial_value,
        )

        # The list of factors (names) this variables is linked with
        self._factors = [link.factor_node for link in comp_def.node.links]

        # costs : this dict is used to store, for each value of the domain,
        # the associated cost sent by each factor this variable is involved
        # with. { factor : {domain value : cost }}
        self._costs = {}

        self._prev_messages = defaultdict(lambda: (None, 0))

        self.damping = comp_def.algo.params["damping"]
        self.logger.info("Running maxsum with damping %s", self.damping)
コード例 #13
0
def dmaxsum_external_variable():

    domain = VariableDomain('colors', 'color', ['R', 'G', 'B'])
    # RW Variables
    v1 = VariableNoisyCostFunc('v1', domain, prefer_color('R'))
    v2 = VariableNoisyCostFunc('v2', domain, prefer_color('B'))
    v3 = VariableNoisyCostFunc('v3', domain, prefer_color('B'))
    v4 = VariableNoisyCostFunc('v4', domain, prefer_color('R'))

    # External Variable
    domain_e = VariableDomain('boolean', 'abstract', [False, True])
    e1 = ExternalVariable('e1', domain_e, False)

    def r1(v1_, v2_, v3_):
        if v1_ != v2_ and v2_ != v3_ and v1_ != v3_:
            return 0
        return 100

    condition = NAryFunctionRelation(lambda x: x, [e1], name='r1_cond')
    relation_if_true = NAryFunctionRelation(r1, [v1, v2, v3], name='r1')
    r1 = ConditionalRelation(condition, relation_if_true)

    def r2(v2_, v4_):
        if v2_ != v4_:
            return 0
        return 100

    r2 = NAryFunctionRelation(r2, [v2, v4], name='r2')

    def r3(v3_, v4_):
        if v3_ != v4_:
            return 0
        return 100

    r3 = NAryFunctionRelation(r3, [v3, v4], name='r3')

    r1_computation = DynamicFactorComputation(r1, name='r1')
    r2_computation = DynamicFactorComputation(r2)
    r3_computation = DynamicFactorComputation(r3)

    e1_computation = ExternalVariableComputation(e1)

    # MUST only consider current relation when building computation objects !!
    # When a relation uses external variable, these must be sliced out.
    current_r1 = r1.slice({e1.name: e1.value})
    relations = [current_r1, r2, r3]
    v1_computation = \
        DynamicFactorVariableComputation(
            v1,
            [r.name for r in find_dependent_relations(v1, relations)])
    v2_computation = \
        DynamicFactorVariableComputation(
            v2,
            [r.name for r in find_dependent_relations(v2, relations)])
    v3_computation = \
        DynamicFactorVariableComputation(
            v3,
            [r.name for r in find_dependent_relations(v3, relations)])
    v4_computation = \
        DynamicFactorVariableComputation(
            v4,
            [r.name for r in find_dependent_relations(v4, relations)])

    # Prepare the agents
    comm = InProcessCommunicationLayer()
    a1 = Agent('a1', comm)
    a1.add_computation(v1_computation)
    a1.add_computation(r1_computation)

    a2 = Agent('a2', comm)
    a2.add_computation(v2_computation)
    a1.add_computation(r2_computation)

    a3 = Agent('a3', comm)
    a3.add_computation(v3_computation)
    a3.add_computation(v4_computation)
    a3.add_computation(r3_computation)

    a4 = Agent('a4', comm)
    a4.add_computation(e1_computation)

    agents = [a1, a2, a3, a4]
    runner = AgentsRunner(agents)
    runner.run_agents()

    # Now change a factor function every two seconds
    fail = False
    for i in range(5):
        time.sleep(2)
        current_value = e1_computation.current_value
        print('###  Iteration {} - function {}'.format(i, current_value))
        print(runner.status_string())
        results = runner.variable_values()
        if current_value:
            c = r1(filter_assignment_dict(results, r1.dimensions)) + \
                r2(filter_assignment_dict(results, r2.dimensions)) + \
                r3(filter_assignment_dict(results, r3.dimensions))
            if c != 0:
                print('Error on results for {} : \nGot {}  !'.format(
                    current_value, results))
                fail = True
                break
        else:
            c = r2(filter_assignment_dict(results, r2.dimensions)) + \
                r3(filter_assignment_dict(results, r3.dimensions))
        if c != 0:
            print('Error on results for {} : \nGot {} !'.format(
                current_value, results))
            fail = True
            break

        new_val = not current_value
        print('## Changing e1 value to {}'.format(new_val))
        e1_computation.change_value(new_val)

    print('Finished, stopping agents')
    runner.request_stop_agents(wait_for_stop=True)

    if fail:
        print('Failed !')
        return 1
    else:
        print('Success !')
        return 0
コード例 #14
0
def dmaxsum_graphcoloring():

    v1 = VariableNoisyCostFunc('v1', d, prefer_color('R'))
    v2 = VariableNoisyCostFunc('v2', d, prefer_color('G'))
    v3 = VariableNoisyCostFunc('v3', d, prefer_color('B'))
    v4 = VariableNoisyCostFunc('v4', d, prefer_color('R'))

    def r1(v1_, v2_, v3_):
        if v1_ != v2_ and v2_ != v3_ and v1_ != v3_:
            return 0
        return 100

    r1 = NAryFunctionRelation(r1, [v1, v2, v3], name='r1')

    def r1_2(v1_, v2_, v4_):
        if v1_ != v2_ and v2_ != v4_ and v1_ != v4_:
            return 0
        return 100

    r1_2 = NAryFunctionRelation(r1_2, [v1, v2, v4], name='r1_2')

    def r2(v2_, v4_):
        if v2_ != v4_:
            return 0
        return 100

    r2 = NAryFunctionRelation(r2, [v2, v4], name='r2')

    def r3(v3_, v4_):
        if v3_ != v4_:
            return 0
        return 100

    r3 = NAryFunctionRelation(r3, [v3, v4], name='r3')

    relations = [r1, r2, r3]
    r1_computation = DynamicFactorComputation(r1, name='r1')
    r2_computation = DynamicFactorComputation(r2)
    r3_computation = DynamicFactorComputation(r3)

    v1_computation = \
        DynamicFactorVariableComputation(
            v1,
            [r.name for r in find_dependent_relations(v1, relations)])
    v2_computation = \
        DynamicFactorVariableComputation(
            v2,
            [r.name for r in find_dependent_relations(v2, relations)])
    v3_computation = \
        DynamicFactorVariableComputation(
            v3,
            [r.name for r in find_dependent_relations(v3, relations)])
    v4_computation = \
        DynamicFactorVariableComputation(
            v4,
            [r.name for r in find_dependent_relations(v4, relations)])

    # Prepare the agents
    comm = InProcessCommunicationLayer()
    a1 = Agent('a1', comm)
    a1.add_computation(v1_computation)
    a1.add_computation(r1_computation)

    a2 = Agent('a2', comm)
    a2.add_computation(v2_computation)
    a1.add_computation(r2_computation)

    a3 = Agent('a3', comm)
    a3.add_computation(v3_computation)
    a3.add_computation(v4_computation)
    a3.add_computation(r3_computation)

    # Expected results to check for success
    expected_results = {
        'r1': {
            'v1': 'R',
            'v2': 'G',
            'v3': 'B',
            'v4': 'R'
        },
        'r1_2': {
            'v1': 'B',
            'v2': 'G',
            'v3': 'B',
            'v4': 'R'
        }
    }
    r1_fcts = [r1, r1_2]

    agents = [a1, a2, a3]

    for a in agents:
        a.start()

    # Now change a factor function every two seconds
    r1_fct, iteration, fail = 0, 0, False
    for _ in range(5):
        iteration += 1
        time.sleep(2)
        print('###  Iteration {} - function {}'.format(iteration,
                                                       r1_fcts[r1_fct].name))
        print(runner.status_string())
        results = runner.variable_values()
        if not results == expected_results[r1_fcts[r1_fct].name]:
            print('Error on results for {} : \nGot {} instead of {}  !'.format(
                r1_fcts[r1_fct].name, results,
                expected_results[r1_fcts[r1_fct].name]))
            fail = True
            break
        r1_fct = (r1_fct + 1) % 2
        print('## Changing to function {}'.format(r1_fcts[r1_fct].name))
        r1_computation.change_factor_function(r1_fcts[r1_fct])

    print('Finished, stopping agents')
    runner.request_stop_agents(wait_for_stop=True)

    if fail:
        print('Failed !')
        return 1
    else:
        print('Success !')
        return 0
コード例 #15
0
class VariableAlgo(VariableComputation):

    def __init__(self, variable: Variable,
                 factor_names: List[str], msg_sender=None,
                 comp_def: ComputationDef=None):
        """

        :param variable: variable object
        :param factor_names: a list containing the names of the factors that
        depend on the variable managed by this algorithm
        :param msg_sender: the object that will be used to send messages to
        neighbors, it must have a  post_msg(sender, target_name, name) method.
        """
        super().__init__(variable, comp_def)

        self._msg_handlers['max_sum'] = self._on_cost_msg

        # self._v = variable.clone()
        # Add noise to the variable, on top of cost if needed
        if hasattr(variable, 'cost_for_val'):
            self._v = VariableNoisyCostFunc(
                variable.name, variable.domain,
                cost_func=lambda x: variable.cost_for_val(x),
                initial_value= variable.initial_value)
        else:
            self._v = VariableNoisyCostFunc(
                variable.name, variable.domain,
                cost_func=lambda x: 0,
                initial_value= variable.initial_value)
            
        self.var_with_cost = True

        # the currently selected value, will evolve when the algorithm is
        # still running.
        # if self._v.initial_value:
        #     self.value_selection(self._v.initial_value, None)
        #
        # elif self.var_with_cost:
        #     current_cost, current_value =\
        #         min(((self._v.cost_for_val(dv), dv) for dv in self._v.domain ))
        #     self.value_selection(current_value, current_cost)

        # The list of factors (names) this variables is linked with
        self._factors = factor_names

        # The object used to send messages to factor
        self._msg_sender = msg_sender

        # costs : this dict is used to store, for each value of the domain,
        # the associated cost sent by each factor this variable is involved
        # with. { factor : {domain value : cost }}
        self._costs = {}

        self.logger = logging.getLogger('pydcop.maxsum.' + variable.name)
        self.cycle_logger = logging.getLogger('cycle')
        self._is_stable = False
        self._prev_messages = defaultdict(lambda: (None, 0))

    @property
    def domain(self):
        # Return a copy of the domain to make sure nobody modifies it.
        return self._v.domain[:]

    @property
    def factors(self):
        """
        :return: a list containing the names of the factors which depend on
        the variable managed by this algorithm.
        """
        return self._factors[:]

    def footprint(self):
        return computation_memory(self.computation_def.node)

    def add_factor(self, factor_name):
        """
        Register a factor to this variable.

        All factors depending on a variable MUST be registered so that the
        variable algorithm can send cost messages to them.

        :param factor_name: the name of a factor which depends on this
        variable.
        """
        self._factors.append(factor_name)

    def on_start(self):
        init_stats = self._init_msg()
        return init_stats

    def _init_msg(self):
        # Each variable with integrated costs sends his costs to the factors
        # which depends on it.
        # A variable with no integrated costs simply sends neutral costs
        msg_count, msg_size = 0, 0

        # select our value
        if self.var_with_cost:
            self.value_selection(*self._select_value())
        elif self._v.initial_value:
            self.value_selection(self._v.initial_value, None)
        else:
            self.value_selection(choice(self._v.domain))
        self.logger.info('Initial value selected %s ', self.current_value)

        if self.var_with_cost:
            costs_factors = {}
            for f in self.factors:
                costs_f = self._costs_for_factor(f)
                costs_factors[f] = costs_f

            if self.logger.isEnabledFor(logging.DEBUG):
                debug = 'Var : init msgt {} \n'.format(self.name)
                for dest, msg in costs_factors.items():
                    debug += '  * {} -> {} : {}\n'.format(self.name, dest, msg)
                self.logger.debug(debug + '\n')
            else:
                self.logger.info('Sending init msg from %s (with cost) to %s',
                                 self.name, costs_factors)

            # Sent the messages to the factors
            for f, c in costs_factors.items():
                msg_size += self._send_costs(f, c)
                msg_count += 1
        else:
            c = {d: 0 for d in self._v.domain}
            debug = 'Var : init msg {} \n'.format(self.name)

            self.logger.info('Sending init msg from %s to %s',
                             self.name, self.factors)

            for f in self.factors:
                msg_size += self._send_costs(f, c)
                msg_count += 1
                debug += '  * {} -> {} : {}\n'.format(self.name, f, c)
            self.logger.debug(debug + '\n')

        return {
            'num_msg_out': msg_count,
            'size_msg_out': msg_size,
            'current_value': self.current_value
        }

    def _on_cost_msg(self, factor_name, msg, t):
        """
        Handling cost message from a neighbor factor.

        :param factor_name: the name of that factor that sent us this message.
        :param msg: a message whose content is a map { d -> cost } where:
         * d is a value from the domain of this variable
         * cost if the minimum cost of the factor when taking value d
        """
        self._costs[factor_name] = msg.costs

        # select our value
        self.value_selection(*self._select_value())

        # Compute and send our own costs to all other factors.
        # If our variable has his own costs, we must sent them back even
        # to the factor which sent us this message, as integrated costs are
        # similar to an unary factor and with an unary factor we would have
        # sent these costs back to the original sender:
        # factor -> variable -> unary_cost_factor -> variable -> factor
        fs = self.factors
        if not self.var_with_cost:
            fs.remove(factor_name)

        msg_count, msg_size = self._compute_and_send_costs(fs)

        # return stats about this cycle:
        return {
            'num_msg_out': msg_count,
            'size_msg_out': msg_size,
            'current_value': self.current_value
        }

    def _compute_and_send_costs(self, factor_names):
        """
        Computes and send costs messages for all factors in factor_names.

        :param factor_names: a list of names of factors to compute and send
        messages to.
        """
        debug = ''
        stable = True
        send, no_send = [], []
        msg_count, msg_size = 0, 0
        for f_name in factor_names:
            costs_f = self._costs_for_factor(f_name)
            same, same_count = self._match_previous(f_name, costs_f)
            if not same or same_count < 2:
                debug += '  * SEND : {} -> {} : {}\n'.format(self.name,
                                                             f_name,
                                                             costs_f)
                msg_size += self._send_costs(f_name, costs_f)
                send.append(f_name)
                self._prev_messages[f_name] = costs_f, same_count +1
                stable = False
                msg_count += 1

            else:
                no_send.append(f_name)
                debug += '  * NO-SEND : {} -> {} : {}\n'.format(self.name,
                                                                f_name,
                                                                costs_f)
        self._is_stable = stable

        # Display sent messages
        if self.logger.isEnabledFor(logging.DEBUG):
            self.logger.debug('Sending messages from %s :\n%s',
                              self.name, debug)
        else:
            self.logger.info('Sending messages from %s to %s, no_send %s',
                             self.name, send, no_send)

        return msg_count, msg_size

    def _send_costs(self, factor_name, costs):
        """
        Sends a cost messages and return the size of the message sent.
        :param factor_name:
        :param costs:
        :return:
        """
        msg = MaxSumMessage(costs)
        self.post_msg(factor_name, msg)
        return msg.size

    def _select_value(self)-> Tuple[Any, float]:
        """

        Returns
        -------
        a Tuple containing the selected value and the corresponding cost for
        this computation.
        """
        # If we have received costs from all our factor, we can select a
        # value from our domain.
        if self.var_with_cost:
            # If our variable has it's own cost, take them into account
            d_costs = {d: self._v.cost_for_val(d) for d in self._v.domain}
        else:
            d_costs = {d: 0 for d in self._v.domain}
        for d in self._v.domain:
            for f_costs in self._costs.values():
                if d not in f_costs:
                    # As infinite costs are not included in messages,
                    # if there is not cost for this value it means the costs
                    # is infinite and we can stop adding other costs.
                    d_costs[d] = INFINITY
                    break
                d_costs[d] += f_costs[d]

        from operator import itemgetter

        min_d = min(d_costs.items(), key=itemgetter(1))

        return min_d[0], min_d[1]

    def _match_previous(self, f_name, costs):
        """
        Check if a cost message for a factor f_name match the previous message
        sent to that factor.

        :param f_name: factor name
        :param costs: costs sent to this factor
        :return:
        """
        prev_costs, count = self._prev_messages[f_name]
        if prev_costs is not None:
            same = approx_match(costs, prev_costs)
            return same, count
        else:
            return False, 0

    def _costs_for_factor(self, factor_name):
        """
        Produce the message that must be sent to factor f.

        The content if this message is a d -> cost table, where
        * d is a value from the domain
        * cost is the sum of the costs received from all other factors except f
        for this value d for the domain.

        :param factor_name: the name of a factor for this variable
        :return: the value -> cost table
        """
        # If our variable has integrated costs, add them
        if self.var_with_cost:
            msg_costs = {d: self._v.cost_for_val(d) for d in self._v.domain}
        else:
            msg_costs = {d: 0 for d in self._v.domain}

        sum_cost = 0
        for d in self._v.domain:
            for f in [f for f in self.factors
                      if f != factor_name and f in self._costs]:
                f_costs = self._costs[f]
                if d not in f_costs:
                    msg_costs[d] = INFINITY
                    break
                c = f_costs[d]
                sum_cost += c
                msg_costs[d] += c

        # Experimentally, when we do not normalize costs the algorithm takes
        # more cycles to stabilize
        # return {d: c for d, c in msg_costs.items() if c != INFINITY}

        # Normalize costs with the average cost, to avoid exploding costs
        avg_cost = sum_cost/len(msg_costs)
        normalized_msg_costs = {d: c-avg_cost
                                for d, c in msg_costs.items()
                                if c != INFINITY}

        return normalized_msg_costs

    def __str__(self):
        return 'MaxsumVariable(' + self._v.name + ')'

    def __repr__(self):
        return self.__str__()