summaryrefslogtreecommitdiffstats
path: root/conductor/conductor/solver/optimizer/best_first.py
blob: 3b579c612b86ff93a6b6dd1d4c407a46912cad5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#
# -------------------------------------------------------------------------
#   Copyright (c) 2015-2017 AT&T Intellectual Property
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
# -------------------------------------------------------------------------
#

import copy
import operator
from oslo_log import log
import sys

from conductor.solver.optimizer import decision_path as dpath
from conductor.solver.optimizer import search

LOG = log.getLogger(__name__)


class BestFirst(search.Search):

    def __init__(self, conf):
        search.Search.__init__(self, conf)

    def search(self, _demand_list, _objective):
        dlist = copy.deepcopy(_demand_list)
        heuristic_solution = self._search_by_fit_first(dlist, _objective)
        if heuristic_solution is None:
            LOG.debug("no solution")
            return None

        open_list = []
        close_paths = {}

        ''' for the decision length heuristic '''
        # current_decision_length = 0

        # create root path
        decision_path = dpath.DecisionPath()
        decision_path.set_decisions({})

        # insert the root path into open_list
        open_list.append(decision_path)

        while len(open_list) > 0:
            p = open_list.pop(0)

            ''' for the decision length heuristic '''
            # dl = len(p.decisions)
            # if dl >= current_decision_length:
            #     current_decision_length = dl
            # else:
            #     continue

            # if explored all demands in p, complete the search with p
            unexplored_demand = self._get_new_demand(p, _demand_list)
            if unexplored_demand is None:
                return p

            p.current_demand = unexplored_demand

            msg = "demand = {}, decisions = {}, value = {}"
            LOG.debug(msg.format(p.current_demand.name,
                                 p.decision_id, p.total_value))

            # constraint solving
            candidate_list = self._solve_constraints(p)
            if len(candidate_list) > 0:
                for candidate in candidate_list:
                    # create path for each candidate for given demand
                    np = dpath.DecisionPath()
                    np.set_decisions(p.decisions)
                    np.decisions[p.current_demand.name] = candidate
                    _objective.compute(np)

                    valid_candidate = True

                    # check closeness for this decision
                    np.set_decision_id(p, candidate.name)
                    if np.decision_id in close_paths.keys():
                        valid_candidate = False

                    ''' for base comparison heuristic '''
                    # TODO(gjung): how to know this is about min
                    if _objective.goal == "min":
                        if np.total_value >= heuristic_solution.total_value:
                            valid_candidate = False

                    if valid_candidate is True:
                        open_list.append(np)

                # sort open_list by value
                open_list.sort(key=operator.attrgetter("total_value"))
            else:
                LOG.debug("no candidates")

            # insert p into close_paths
            close_paths[p.decision_id] = p

        return heuristic_solution

    def _get_new_demand(self, _p, _demand_list):
        for demand in _demand_list:
            if demand.name not in _p.decisions.keys():
                return demand

        return None

    def _search_by_fit_first(self, _demand_list, _objective):
        decision_path = dpath.DecisionPath()
        decision_path.set_decisions({})

        return self._find_current_best(_demand_list, _objective, decision_path)

    def _find_current_best(self, _demand_list, _objective, _decision_path):
        if len(_demand_list) == 0:
            LOG.debug("search done")
            return _decision_path

        demand = _demand_list.pop(0)
        LOG.debug("demand = {}".format(demand.name))
        _decision_path.current_demand = demand
        candidate_list = self._solve_constraints(_decision_path)

        bound_value = 0.0
        if _objective.goal == "min":
            bound_value = sys.float_info.max

        while True:
            best_resource = None
            for candidate in candidate_list:
                _decision_path.decisions[demand.name] = candidate
                _objective.compute(_decision_path) #TODO call the compute of latencyBetween
                if _objective.goal == "min":
                    if _decision_path.total_value < bound_value:
                        bound_value = _decision_path.total_value
                        best_resource = candidate

            if best_resource is None:
                LOG.debug("no resource, rollback")
                return None
            else:
                _decision_path.decisions[demand.name] = best_resource
                _decision_path.total_value = bound_value
                decision_path = self._find_current_best(
                    _demand_list, _objective, _decision_path)
                if decision_path is None:
                    candidate_list.remove(best_resource)
                else:
                    return decision_path