From 5cffacc661f0173adada269b567a0e8162625978 Mon Sep 17 00:00:00 2001 From: Fransman Date: Thu, 4 Jun 2020 15:14:37 +0200 Subject: [PATCH 1/5] DOC: removed 'initial_value' from domain definition --- docs/usage/file_formats/dcop_format.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/usage/file_formats/dcop_format.yml b/docs/usage/file_formats/dcop_format.yml index c43a98fe..6c58a3c3 100644 --- a/docs/usage/file_formats/dcop_format.yml +++ b/docs/usage/file_formats/dcop_format.yml @@ -29,7 +29,6 @@ domains: d3: values : [1 .. 10] type : non_semantic - initial_value: 3 dbool: values : [true, false] From 6d2859451cd6bc1f9739d17f4217c38651ce028c Mon Sep 17 00:00:00 2001 From: Fransman Date: Thu, 4 Jun 2020 15:15:21 +0200 Subject: [PATCH 2/5] ADD: ContinuousDomain class as child class of Domain --- pydcop/dcop/objects.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pydcop/dcop/objects.py b/pydcop/dcop/objects.py index c6653d4f..a3040cb5 100644 --- a/pydcop/dcop/objects.py +++ b/pydcop/dcop/objects.py @@ -165,6 +165,51 @@ def to_domain_value(self, val: str): raise ValueError(str(val) + " is not in the domain " + self._name) +class ContinuousDomain(Domain): + """ + A ContinuousDomain indicates the lower and upper bound of the values + that are valid for variables with this domain. + It also indicates the type of environment state represented + by there variable : 'luminosity', humidity', etc. + + The lower and upper bound of the domain is stored separately + as well as within the values (values = [lower bound, upper bound]) + in order to be compatible with the Domain class. + """ + + def __init__(self, name: str, domain_type: str, lower_bound: float, upper_bound: float) -> None: + """ + + :param: name: name of the domain. + :param domain_type: a string identifying the kind of value in the + domain. For example : 'luminosity', 'humidity', ... + :param lower_bound: the lower bound of the domain of the valid values + :param upper_bound: the upper bound of the domain of the valid values + """ + # Check the bounds for consistency + assert lower_bound <= upper_bound + + # Initiate the parent class + super().__init__(name, domain_type, [lower_bound, upper_bound]) + + # Store the parameters + self._lower_bound = lower_bound + self._upper_bound = upper_bound + + @property + def upper_bound(self) -> float: + return self._upper_bound + + @property + def lower_bound(self) -> float: + return self._lower_bound + + def __str__(self): + return f"ContinuousDomain({self.name})" + + def __repr__(self): + return f"ContinuousDomain({self.name}, {self.type}, ({self.lower_bound}..{self.upper_bound}))" + # We keep VariableDomain as an alias for the moment, but Domain should be # preferred. VariableDomain = Domain From 554387a4b76f52635b12b542445dbdf44d705781 Mon Sep 17 00:00:00 2001 From: Fransman Date: Thu, 4 Jun 2020 15:16:07 +0200 Subject: [PATCH 3/5] UPDATE: yamldcop parsing of domain defintions for continuous domains --- pydcop/dcop/yamldcop.py | 42 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/pydcop/dcop/yamldcop.py b/pydcop/dcop/yamldcop.py index 1a76f45e..d92a4f5a 100644 --- a/pydcop/dcop/yamldcop.py +++ b/pydcop/dcop/yamldcop.py @@ -35,6 +35,7 @@ import yaml from pydcop.dcop.objects import ( + ContinuousDomain, VariableDomain, Variable, ExternalVariable, @@ -143,13 +144,22 @@ def _build_domains(loaded) -> Dict[str, VariableDomain]: domains = {} if "domains" in loaded: for d_name in loaded["domains"]: + # Retrieve the properties d = loaded["domains"][d_name] values = d["values"] + d_type = d["type"] if "type" in d else "" + # Check the type of domain if len(values) == 1 and ".." in values[0]: - values = str_2_domain_values(d["values"][0]) - d_type = d["type"] if "type" in d else "" - domains[d_name] = VariableDomain(d_name, d_type, values) + # Continuous domain + bounds = [float(b) + for b + in values[0].replace(' ','').split('..')] + domains[d_name] = ContinuousDomain( + d_name, d_type, bounds[0], bounds[1]) + else: + # Discrete domain + domains[d_name] = VariableDomain(d_name, d_type, values) return domains @@ -475,32 +485,6 @@ def _build_dist_hints(loaded, dcop): must_host, dict(host_with) if host_with is not None else {} ) - -def str_2_domain_values(domain_str): - """ - Deserialize a domain expressed as a string. - - If all variable in the domain can be interpreted as a int, the list is a - list of int, otherwise it is a list of strings. - - :param domain_str: a string like 0..5 of A, B, C, D - - :return: the list of values in the domain - """ - try: - sep_index = domain_str.index("..") - # Domain str is : [0..5] - min_d = int(domain_str[0:sep_index]) - max_d = int(domain_str[sep_index + 2 :]) - return list(range(min_d, max_d + 1)) - except ValueError: - values = [v.strip() for v in domain_str[1:].split(",")] - try: - return [int(v) for v in values] - except ValueError: - return values - - def load_scenario_from_file(filename: str) -> Scenario: """ Load a scenario from a yaml file. From f113b68518666cee30834bbce1327c064a27dde6 Mon Sep 17 00:00:00 2001 From: Fransman Date: Thu, 4 Jun 2020 15:16:34 +0200 Subject: [PATCH 4/5] ADDED: testcase for dcop parsing for domain definitions --- tests/dcop/test_load_dcop.py | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/dcop/test_load_dcop.py diff --git a/tests/dcop/test_load_dcop.py b/tests/dcop/test_load_dcop.py new file mode 100644 index 00000000..b2eb923d --- /dev/null +++ b/tests/dcop/test_load_dcop.py @@ -0,0 +1,100 @@ +""" + +Tests for loading DCOP yaml files. + +These tests check for correct parsing of the DCOP definition files. + +""" +import unittest +from pydcop.dcop.yamldcop import load_dcop_from_file, load_dcop +from pydcop.dcop.objects import ( + ContinuousDomain, + Domain, +) + +dcop_test_str = """ + name: 'dcop test' + description: 'Testing of DCOP yaml parsing' + objective: min + + domains: + dint: + values : [0, 1, 2, 3, 4] + type : non_semantic + dstr: + values : ['A', 'B', 'C', 'D', 'E'] + dcont: + values : [0 .. 1] + type : non_semantic + initial_value: 3 + dbool: + values : [true, false] + + variables: + var1: + domain: dint + initial_value: 0 + yourkey: yourvalue + foo: bar + var2: + domain: dstr + initial_value: 'A' + var3: + domain: dint + initial_value: 0 + cost_function: var3 * 0.5 + var4: + domain: dcont + initial_value: 0 + cost_function: var4 * 0.6 + + external_variables: + ext_var1: + domain: dbool + initial_value: False + + constraints: + c1: + type: intention + function: var3 - var1 + + agents: + a1: + capacity: 100 + a2: + capacity: 100 + a3: + capacity: 100 + a4: + capacity: 100 +""" + +class TestDomains(unittest.TestCase): + + def test_classes(self): + # Load the DCOP + dcop = load_dcop(dcop_test_str) + + # Check the classes + self.assertIsInstance(dcop.domains['dint'], Domain) + self.assertIsInstance(dcop.domains['dstr'], Domain) + self.assertIsInstance(dcop.domains['dcont'], ContinuousDomain) + self.assertIsInstance(dcop.domains['dbool'], Domain) + + def test_values(self): + # Load the DCOP + dcop = load_dcop(dcop_test_str) + + # Check the values + self.assertEqual(dcop.domains['dint'].values, (0, 1, 2, 3, 4)) + self.assertEqual(dcop.domains['dstr'].values, ('A', 'B', 'C', 'D', 'E')) + self.assertEqual(dcop.domains['dcont'].values, (0.0, 1.0)) + self.assertEqual(dcop.domains['dbool'].values, (True, False)) + + def test_bounds(self): + # Load the DCOP + dcop = load_dcop(dcop_test_str) + + # Check the bounds + self.assertEqual(dcop.domains['dcont'].lower_bound, 0.0) + self.assertEqual(dcop.domains['dcont'].upper_bound, 1.0) From 7497d4a1a6fd7025edb7caf8d57171587ad88039 Mon Sep 17 00:00:00 2001 From: Fransman Date: Thu, 4 Jun 2020 16:10:28 +0200 Subject: [PATCH 5/5] UPDATE: added checks for all (discrete) DCOP solvers for domain class --- pydcop/algorithms/adsa.py | 3 +++ pydcop/algorithms/amaxsum.py | 5 ++++- pydcop/algorithms/dba.py | 6 ++++-- pydcop/algorithms/dpop.py | 6 ++++-- pydcop/algorithms/dsa.py | 4 +++- pydcop/algorithms/dsatuto.py | 4 ++++ pydcop/algorithms/gdba.py | 4 +++- pydcop/algorithms/maxsum.py | 7 +++++-- pydcop/algorithms/maxsum_dynamic.py | 3 +++ pydcop/algorithms/mgm.py | 3 +++ pydcop/algorithms/mgm2.py | 4 +++- pydcop/algorithms/mixeddsa.py | 4 +++- pydcop/algorithms/ncbb.py | 4 +++- pydcop/algorithms/syncbb.py | 4 +++- 14 files changed, 48 insertions(+), 13 deletions(-) diff --git a/pydcop/algorithms/adsa.py b/pydcop/algorithms/adsa.py index a8da3f4d..f7361fea 100644 --- a/pydcop/algorithms/adsa.py +++ b/pydcop/algorithms/adsa.py @@ -83,6 +83,7 @@ from typing import Tuple, Any, List, Dict from pydcop.algorithms import AlgoParameterDef, ComputationDef +from pydcop.dcop.objects import Domain from pydcop.dcop.relations import ( find_optimum, assignment_cost, @@ -134,6 +135,8 @@ def __init__(self, comp_def): assert comp_def.algo.algo == "adsa" assert (comp_def.algo.mode == "min") or (comp_def.algo.mode == "max") + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self.mode = comp_def.algo.mode self.probability = comp_def.algo.param_value("probability") diff --git a/pydcop/algorithms/amaxsum.py b/pydcop/algorithms/amaxsum.py index 340dd301..b2e27527 100644 --- a/pydcop/algorithms/amaxsum.py +++ b/pydcop/algorithms/amaxsum.py @@ -72,7 +72,7 @@ from collections import defaultdict from typing import Dict, Any, List -from pydcop.dcop.objects import VariableNoisyCostFunc, Variable +from pydcop.dcop.objects import VariableNoisyCostFunc, Variable, Domain from pydcop.algorithms import AlgoParameterDef, ComputationDef from pydcop.algorithms import maxsum from pydcop.dcop.relations import generate_assignment_as_dict @@ -114,6 +114,9 @@ class MaxSumFactorComputation(DcopComputation): def __init__(self, comp_def=None): assert comp_def.algo.algo == "amaxsum" super().__init__(comp_def.node.factor.name, comp_def) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain + self.mode = comp_def.algo.mode self.factor = comp_def.node.factor self.variables = self.factor.dimensions diff --git a/pydcop/algorithms/dba.py b/pydcop/algorithms/dba.py index 2280b8ab..e575472e 100644 --- a/pydcop/algorithms/dba.py +++ b/pydcop/algorithms/dba.py @@ -107,7 +107,7 @@ from pydcop.computations_graph.constraints_hypergraph import \ VariableComputationNode -from pydcop.dcop.objects import Variable +from pydcop.dcop.objects import Variable, Domain from pydcop.dcop.relations import RelationProtocol, filter_assignment_dict INFINITY = 10000 @@ -296,7 +296,9 @@ def __init__(self, variable: Variable, raise ValueError('DBA is a constraint **satisfaction** ' 'algorithm and only support ' 'minimization objective') - + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain + self._msg_sender = msg_sender global INFINITY diff --git a/pydcop/algorithms/dpop.py b/pydcop/algorithms/dpop.py index 69bacdfd..eaef2b02 100644 --- a/pydcop/algorithms/dpop.py +++ b/pydcop/algorithms/dpop.py @@ -58,7 +58,7 @@ from pydcop.computations_graph.pseudotree import get_dfs_relations from pydcop.infrastructure.computations import Message, VariableComputation, register -from pydcop.dcop.objects import Variable +from pydcop.dcop.objects import Variable, Domain from pydcop.dcop.relations import ( NAryMatrixRelation, Constraint, @@ -172,12 +172,14 @@ class DpopAlgo(VariableComputation): """ def __init__(self, comp_def: ComputationDef): - assert comp_def.algo.algo == "dpop" super().__init__(comp_def.node.variable, comp_def) self._mode = comp_def.algo.mode + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain + self._parent, self._pseudo_parents, self._children, self._pseudo_children = get_dfs_relations( self.computation_def.node ) diff --git a/pydcop/algorithms/dsa.py b/pydcop/algorithms/dsa.py index 8e74751c..0d5f342c 100644 --- a/pydcop/algorithms/dsa.py +++ b/pydcop/algorithms/dsa.py @@ -97,7 +97,7 @@ from pydcop.algorithms import AlgoParameterDef, ComputationDef from pydcop.infrastructure.computations import Message, VariableComputation, register - +from pydcop.dcop.objects import Domain from pydcop.computations_graph.constraints_hypergraph import VariableComputationNode from pydcop.dcop.relations import ( find_optimum, @@ -247,6 +247,8 @@ def __init__(self, comp_def: ComputationDef): assert comp_def.algo.algo == "dsa" assert (comp_def.algo.mode == "min") or (comp_def.algo.mode == "max") + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self.mode = comp_def.algo.mode self.probability = comp_def.algo.param_value("probability") diff --git a/pydcop/algorithms/dsatuto.py b/pydcop/algorithms/dsatuto.py index 96f0e233..9375da0a 100644 --- a/pydcop/algorithms/dsatuto.py +++ b/pydcop/algorithms/dsatuto.py @@ -50,6 +50,7 @@ from pydcop.algorithms import ComputationDef from pydcop.dcop.relations import assignment_cost, find_optimal +from pydcop.dcop.objects import Domain from pydcop.infrastructure.computations import ( VariableComputation, message_type, @@ -82,6 +83,9 @@ def __init__(self, computation_definition: ComputationDef): super().__init__(computation_definition.node.variable, computation_definition) assert computation_definition.algo.algo == "dsatuto" + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain + self.mode = computation_definition.algo.mode self.constraints = computation_definition.node.constraints diff --git a/pydcop/algorithms/gdba.py b/pydcop/algorithms/gdba.py index 948fd937..66f17c5c 100644 --- a/pydcop/algorithms/gdba.py +++ b/pydcop/algorithms/gdba.py @@ -46,7 +46,7 @@ from pydcop.algorithms import AlgoParameterDef, ComputationDef from pydcop.infrastructure.computations import Message, VariableComputation, register from pydcop.computations_graph.constraints_hypergraph import VariableComputationNode -from pydcop.dcop.objects import Variable +from pydcop.dcop.objects import Variable, Domain from pydcop.dcop.relations import ( RelationProtocol, NAryMatrixRelation, @@ -232,6 +232,8 @@ def __init__( """ super().__init__(variable, comp_def) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self._msg_sender = msg_sender diff --git a/pydcop/algorithms/maxsum.py b/pydcop/algorithms/maxsum.py index 264583b1..7eff68aa 100644 --- a/pydcop/algorithms/maxsum.py +++ b/pydcop/algorithms/maxsum.py @@ -90,7 +90,7 @@ FactorComputationNode, VariableComputationNode, ) -from pydcop.dcop.objects import Variable, VariableNoisyCostFunc +from pydcop.dcop.objects import Variable, VariableNoisyCostFunc, Domain from pydcop.dcop.relations import Constraint, generate_assignment_as_dict from pydcop.infrastructure.computations import ( DcopComputation, @@ -280,7 +280,10 @@ class MaxSumFactorComputation(SynchronousComputationMixin, DcopComputation): def __init__(self, comp_def: ComputationDef): assert comp_def.algo.algo == "maxsum" super().__init__(comp_def.node.factor.name, comp_def) - self.logger.warning(f"Neiborghs {self.neighbors}") + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain + + self.logger.warning(f"Neighbors {self.neighbors}") self.mode = comp_def.algo.mode self.factor = comp_def.node.factor diff --git a/pydcop/algorithms/maxsum_dynamic.py b/pydcop/algorithms/maxsum_dynamic.py index f420acd3..9012783a 100644 --- a/pydcop/algorithms/maxsum_dynamic.py +++ b/pydcop/algorithms/maxsum_dynamic.py @@ -35,6 +35,7 @@ from pydcop.algorithms.amaxsum import MaxSumFactorComputation, MaxSumVariableComputation from pydcop.algorithms.maxsum import MaxSumMessage from pydcop.dcop.relations import NeutralRelation +from pydcop.dcop.objects import Domain class DynamicFunctionFactorComputation(MaxSumFactorComputation): @@ -76,6 +77,8 @@ class DynamicFunctionFactorComputation(MaxSumFactorComputation): def __init__(self, comp_def=None): super().__init__(comp_def=comp_def) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain def change_factor_function(self, fn): """ diff --git a/pydcop/algorithms/mgm.py b/pydcop/algorithms/mgm.py index eaca1213..75b71718 100644 --- a/pydcop/algorithms/mgm.py +++ b/pydcop/algorithms/mgm.py @@ -63,6 +63,7 @@ find_arg_optimal, optimal_cost_value, ) +from pydcop.dcop.objects import Domain from pydcop.infrastructure.computations import Message, VariableComputation, register GRAPH_TYPE = "constraints_hypergraph" @@ -231,6 +232,8 @@ def __init__(self, computation_definition: ComputationDef): ) super().__init__(computation_definition.node.variable, computation_definition) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self.__utilities__ = list(computation_definition.node.constraints) self._mode = computation_definition.algo.mode # min or max diff --git a/pydcop/algorithms/mgm2.py b/pydcop/algorithms/mgm2.py index 61e56610..4ce63d4f 100644 --- a/pydcop/algorithms/mgm2.py +++ b/pydcop/algorithms/mgm2.py @@ -46,7 +46,7 @@ from pydcop.algorithms import AlgoParameterDef, ComputationDef from pydcop.infrastructure.computations import Message, VariableComputation, register - +from pydcop.dcop.objects import Domain from pydcop.computations_graph.constraints_hypergraph import VariableComputationNode from pydcop.dcop.relations import ( find_dependent_relations, @@ -417,6 +417,8 @@ class Mgm2Computation(VariableComputation): def __init__(self, computation_def: ComputationDef = None): assert computation_def.algo.algo == "mgm2" super().__init__(computation_def.node.variable, computation_def) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self._mode = computation_def.algo.mode self.stop_cycle = computation_def.algo.param_value("stop_cycle") diff --git a/pydcop/algorithms/mixeddsa.py b/pydcop/algorithms/mixeddsa.py index 0cb0a6c5..4f41f8a5 100644 --- a/pydcop/algorithms/mixeddsa.py +++ b/pydcop/algorithms/mixeddsa.py @@ -38,7 +38,7 @@ from pydcop.dcop.relations import RelationProtocol, generate_assignment_as_dict, \ filter_assignment_dict - +from pydcop.dcop.objects import Domain from pydcop.algorithms import AlgoParameterDef, ComputationDef from pydcop.infrastructure.computations import Message, VariableComputation, \ register @@ -183,6 +183,8 @@ def __init__(self, variable, constraints, variant='B', proba_hard=0.7, """ super().__init__(variable, comp_def) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain self.proba_hard = proba_hard self.proba_soft = proba_soft diff --git a/pydcop/algorithms/ncbb.py b/pydcop/algorithms/ncbb.py index 435584ef..8ebc0d97 100644 --- a/pydcop/algorithms/ncbb.py +++ b/pydcop/algorithms/ncbb.py @@ -102,6 +102,7 @@ from pydcop.algorithms import ComputationDef from pydcop.computations_graph.pseudotree import get_dfs_relations +from pydcop.dcop.objects import Domain from pydcop.dcop.relations import find_optimal from pydcop.infrastructure.computations import ( VariableComputation, @@ -149,7 +150,8 @@ class NcbbAlgo(SynchronousComputationMixin, VariableComputation): def __init__(self, computation_definition: ComputationDef): super().__init__(computation_definition.node.variable, computation_definition) - + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain assert computation_definition.algo.algo == "ncbb" self._mode = computation_definition.algo.mode diff --git a/pydcop/algorithms/syncbb.py b/pydcop/algorithms/syncbb.py index ac2bc340..17b73e95 100644 --- a/pydcop/algorithms/syncbb.py +++ b/pydcop/algorithms/syncbb.py @@ -149,7 +149,7 @@ from typing import Optional, List, Any, Tuple from pydcop.algorithms import ComputationDef -from pydcop.dcop.objects import Variable +from pydcop.dcop.objects import Variable, Domain from pydcop.dcop.relations import assignment_cost, Constraint from pydcop.infrastructure.computations import ( VariableComputation, @@ -181,6 +181,8 @@ class SyncBBComputation(VariableComputation): def __init__(self, computation_definition: ComputationDef): super().__init__(computation_definition.node.variable, computation_definition) + # Check if the domains of the variables are suitable + assert type(self._variable.domain) is Domain assert computation_definition.algo.algo == "syncbb" self.constraints = computation_definition.node.constraints