Skip to content

Commit

Permalink
Update NPP example
Browse files Browse the repository at this point in the history
  • Loading branch information
rrei committed Mar 11, 2017
1 parent fdbffe4 commit 2033e62
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 31 deletions.
67 changes: 48 additions & 19 deletions src/examples/partition.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import sys

from math import log
from bisect import insort
from collections import defaultdict

import rr.opt.mcts.simple as mcts
from rr.opt import mcts


JOIN = 0
Expand Down Expand Up @@ -33,28 +40,32 @@ def karmarkar_karp(labels):
return edges, sum_remaining


class TreeNode(mcts.TreeNode):
EXPANSION_LIMIT = float("inf")
class NppTreeNode(mcts.TreeNode):

EXPANSION_LIMIT = float("inf") # 2 would also suffice
EXPLORATION_COEFF = 0.05


@classmethod
def root(cls, instance):
class NppState(mcts.State):

def __init__(self, instance):
if isinstance(instance, str):
instance = load_instance(instance)
assert type(instance) is list # NPP instances are flat lists of positive integers
root = cls()
root.labels = sorted((n, i) for i, n in enumerate(instance)) # vertex labels (nums)
root.edges = [] # [(i, j, EDGE_TYPE<JOIN|SPLIT>)]
root.sum_remaining = sum(instance) # sum of all numbers still unassigned
return root
self.labels = sorted((n, i) for i, n in enumerate(instance)) # vertex labels (nums)
self.edges = [] # [(i, j, EDGE_TYPE<JOIN|SPLIT>)]
self.sum_remaining = sum(instance) # sum of all numbers still unassigned
self.kk_sol = None

def copy(self):
clone = mcts.TreeNode.copy(self)
clone = mcts.State.copy(self)
clone.labels = list(self.labels)
clone.edges = list(self.edges)
clone.sum_remaining = self.sum_remaining
clone.kk_sol = self.kk_sol
return clone

def branches(self):
def actions(self):
# If there are only 4 or less items left, KK is optimal (and we've already done it in
# simulate()). We only branch if the largest number does not exceed the sum of the other
# items +1, and that was also already verified in the simulate() method.
Expand All @@ -73,9 +84,9 @@ def apply(self, edge_type):

def simulate(self):
edges = self.edges
if len(edges) > 0 and edges[-1][-1] == SPLIT:
if self.kk_sol is not None and len(edges) > 0 and edges[-1][-1] == SPLIT:
# reuse parent solution if this is the differencing child
return self.parent.sim_sol
return self.kk_sol
labels = self.labels
largest, i = labels[-1]
delta = largest - (self.sum_remaining - largest)
Expand All @@ -86,10 +97,11 @@ def simulate(self):
for _, j in labels:
edges.append((i, j, SPLIT))
del labels[:] # force next branches() call to return empty branch list
return mcts.Solution(value=objective(delta), data=edges)
self.kk_sol = mcts.Solution(value=objective(delta), data=edges)
else:
kk_edges, diff = karmarkar_karp(self.labels)
return mcts.Solution(value=objective(diff), data=edges+kk_edges)
self.kk_sol = mcts.Solution(value=objective(diff), data=edges+kk_edges)
return self.kk_sol


def make_partition(edges):
Expand Down Expand Up @@ -118,6 +130,23 @@ def _assign_subset(adj, subset, i, s):
_assign_subset(adj, subset, j, s_j)


mcts.config_logging()
r = TreeNode.root("instances/npp/hard1000.dat")
s = mcts.run(r, iter_limit=1000)
mcts.utils.config_logging()
r = NppTreeNode(NppState("instances/npp/hard0100.dat"))
s = mcts.Solver(r)


if __name__ == "__main__":
i = "instances/npp/hard1000.dat"
t = 60.0
if len(sys.argv) > 1:
i = sys.argv[1]
if len(sys.argv) > 2:
t = float(sys.argv[2])
if len(sys.argv) > 3:
print("Usage: partition.py [<instance> [<time>]]")
exit(1)

mcts.utils.config_logging()
r = NppTreeNode(NppState(i))
s = mcts.Solver(r)
s.run(t)
1 change: 1 addition & 0 deletions src/rr/opt/mcts/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def __init__(self, root, pruning=None, rng_seed=None, rng_state=None, status_int
root.stats.overall.on_update.append(self.overall.update)

def run(self, time_limit=INF, iter_limit=INF):
info("Running with time_limit={} and iter_limit={}".format(time_limit, iter_limit))
root = self.root
cpu = self.cpu
time_limit += cpu.elapsed # convert rel time limit into abs limit
Expand Down
37 changes: 25 additions & 12 deletions src/rr/opt/mcts/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ def is_exhausted(self):
"""
return self.expansion.is_finished and len(self.children) == 0

# This parameter controls interleaved selection of still-expanding parent nodes with their
# children, thereby allowing the search to deepen without forcing the full expansion of all
# ancestors. Turn off to force parents to be fully expanded before starting to select their
# children.
SELECTION_INTERLEAVING = False
"""This parameter controls interleaved selection of still-expanding parent nodes with their
children, thereby allowing the search to deepen without forcing the full expansion of all
ancestors. Turn off to force parents to be fully expanded before starting to select their
children.
"""

def select(self):
"""Go down the tree picking the "best" (*i.e.* with highest selection_score()) child at
Expand All @@ -136,15 +137,26 @@ def select(self):
node = next_node
return node

EXPLORATION_COEFF = 1.0
"""UCT exploration coefficient. Controls how much weight the exploration term is given. Set
to 0 for best-first search.
"""

def selection_score(self):
stats = self.stats
return stats.opt_exploitation_score() + stats.uct_exploration_score()
exploration_coeff = self.EXPLORATION_COEFF
if exploration_coeff == 0.0:
exploration_term = 0.0
else:
exploration_term = exploration_coeff * stats.uct_exploration_score()
return stats.opt_exploitation_score() + exploration_term

# Parameter controlling how many child nodes (at most) are created for each call to expand()
# (normally one per iteration of MCTS). The default value is 1, which means that nodes are
# expanded one child at a time. This allows the algorithm to pick other sites for exploration
# if the initial children of the current node reveal it to be a bad choice.
EXPANSION_LIMIT = 1
"""Parameter controlling how many child nodes (at most) are created for each call to expand()
(normally one per iteration of MCTS). The default value is 1, which means that nodes are
expanded one child at a time. This allows the algorithm to pick other sites for exploration
if the initial children of the current node reveal it to be a bad choice.
"""

def expand(self, pruning=False):
"""Create and link (after performing optional pruning checks) new child nodes.
Expand Down Expand Up @@ -176,13 +188,14 @@ def expand(self, pruning=False):
if len(self.children) == 0:
self.delete()
else:
self.expansion_finished()
self.expansion_finished(pruning)

def expansion_finished(self):
def expansion_finished(self, pruning):
"""Called when the node's expansion is complete, but the node is not yet exhausted. This
can be used to save resources *e.g.* by releasing the memory taken by the node's state.
"""
self.bound() # ensures we cache the node's bound before getting rid of the state
if pruning:
self.bound() # ensures we cache the node's bound before getting rid of the state
self.state = None

def simulate(self):
Expand Down

0 comments on commit 2033e62

Please sign in to comment.