Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #34

Merged
merged 8 commits into from
Jun 17, 2023
Merged

Dev #34

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions PyXAB/algos/SOO.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ def pull(self, time):

self.iteration = time
node_list = self.partition.get_node_list()
flag = False # indicate if we should terminate the iteration

while True:
h = 0
Expand All @@ -148,15 +147,9 @@ def pull(self, time):
max_node = node
if max_value >= v_max:
if max_node is not None: # Found a leaf node
self.partition.make_children(max_node, newlayer=True)
self.partition.make_children(max_node, newlayer=(h>=self.partition.get_depth()))
v_max = max_value
h += 1
if max_node is None:
if (
flag
): # We terminate the outer loop if we cannot find a leaf node that satisfies those conditions
return self.partition.get_root().get_cpoint()
flag = True # We set flag = True iff the loop cannot find the node starting from root.

def receive_reward(self, time, reward):
"""
Expand Down
4 changes: 2 additions & 2 deletions PyXAB/algos/SequOOL.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def pull(self, t):
if self.curr_depth == 0:
node = node_list[0][0]
if node.get_children() is None:
self.partition.make_children(node, newlayer=True)
self.partition.make_children(node, newlayer=(self.curr_depth >= self.partition.get_depth()))
if self.loc < len(node.get_children()):
if self.loc == len(node.get_children()) - 1:
self.loc = 0
Expand All @@ -184,7 +184,7 @@ def pull(self, t):
max_node = node

if max_node.get_children() is None:
self.partition.make_children(max_node, newlayer=True)
self.partition.make_children(max_node, newlayer=(self.curr_depth >= self.partition.get_depth()))
if self.loc < len(max_node.get_children()):
if self.loc == len(max_node.get_children()) - 1:
max_node.open()
Expand Down
4 changes: 2 additions & 2 deletions PyXAB/algos/StroquOOL.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def pull(self, time):
# init
if self.curr_depth == 0:
if node_list[0][0].get_children() is None:
self.partition.make_children(node_list[0][0], newlayer=True)
self.partition.make_children(node_list[0][0], newlayer=(self.curr_depth >= self.partition.get_depth()))
self.chosen.append(node_list[0][0].get_children()[0])
self.chosen.append(node_list[0][0].get_children()[1])
if self.iteration <= self.h_max:
Expand Down Expand Up @@ -245,7 +245,7 @@ def pull(self, time):
self.eval = False
# partition
if self.max_node.get_children() is None:
self.partition.make_children(self.max_node, newlayer=True)
self.partition.make_children(self.max_node, newlayer=(self.curr_depth >= self.partition.get_depth()))
self.chosen.append(self.max_node.get_children()[0])
self.chosen.append(self.max_node.get_children()[1])
# evaluate children
Expand Down
312 changes: 312 additions & 0 deletions PyXAB/algos/VROOM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
# -*- coding: utf-8 -*-
"""Implementation of VROOM (Ammar, Haitham, et al., 2020)
"""
# Author: Haoze Li <[email protected]>
# License: MIT

import math
import numpy as np
from PyXAB.algos.Algo import Algorithm
from PyXAB.partition.Node import P_node
from PyXAB.partition.BinaryPartition import BinaryPartition
import pdb

class VROOM_node(P_node):
"""
Implementation of the node in the VROOM algorithm
"""

def __init__(self, depth, index, parent, domain):
"""
Initialization of the VROOM node

Parameters
----------
depth: int
depth of the node
index: int
index of the node
parent:
parent node of the current node
domain: list(list)
domain that this node represents
"""
super(VROOM_node, self).__init__(depth, index, parent, domain)

self.reward = []
self.rank = []
self.reward_tilde = []

def update_reward(self, reward):
"""
The function to update the reward of the node

Parameters
----------
reward: float
the reward for evaluating the node

Returns
-------

"""
self.reward.append(reward)

def update_reward_tilde(self, reward):
"""
The function to update the reward tilde of the node

Parameters
----------
reward: float
the reward tilde statistc of the node

Returns
-------
"""
self.reward_tilde.append(reward)

def get_mean_reward(self):
"""
The function to get the mean of the reward of the node

Returns
-------

"""
return np.mean(self.reward)

def get_reward_tilde(self):
"""
The function to get the reward tilde statistic of the node

Returns
-------

"""
if self.reward_tilde:
return np.sum(self.reward)
return -np.inf

def get_eval_time(self):
"""
The function to get the evaluation time of the node

Returns
-------

"""
return len(self.reward)

def sample_uniform(self):
"""
The function to uniformly sample a point from the domain of the node

Returns
-------
res: list
the point sampled by the sampler
"""
# TODO: extend the function to the case where the domain is of the form [a, b]\cup [c, d]
res = []
for domain in self.domain:
point = np.random.uniform(domain[0], domain[1])
res.append(point)
return res

def get_rank(self):
"""
The function to get the rank of the cell

Returns
-------
rank: int
the rank of the cell at current depth
"""
return self.rank

def add_rank(self, rank):
"""
The method to set the rank of the cell

Parameters
----------
rank: int
the rank of the cell at current depth
"""
self.rank.append(rank)


class VROOM(Algorithm):
"""
The implementation of the VROOM algorithm (Ammar, Haitham, et al., 2020)
"""

def __init__(self, n=100, h_max = 100, b=None, f_max=None, domain=None, partition=BinaryPartition):
"""
The initialization of the VROOM algorithm

Parameters
----------
n: int
The total number of rounds (budget)
b: float
The parameter that measures the variation of the function
f_max: float
An upper bound of the objective function
domain: list(list)
The domain of the objective to be optimized
partition:
The partition choice of the algorithm
"""
super(VROOM, self).__init__()
if b is None:
raise ValueError("Variance parameter is not given.")
if f_max is None:
raise ValueError("Upper bound of the objective function is not given.")
if domain is None:
raise ValueError("Parameter space is not given.")
if partition is None:
raise ValueError("Partition of the parameter space is not given")
self.partition = partition(domain=domain, node=VROOM_node)

self.iteration = 0
self.n = n
self.b = b
self.f_max = f_max
self.search_depth = math.floor(np.log2(n)) # the largest depth at the ranking stage
self.delta = 4 * self.b / (self.f_max * np.sqrt(self.n))
self.h_max = h_max

# generate the searching tree
while self.partition.get_depth() < self.search_depth:
self.partition.deepen()


# calculate the constant
self.const = 0
for h in range(1, self.search_depth + 1):
for l in range(1, 2**h + 1):
self.const += 1 / (h * l)

def pull(self, time):
"""
The pull function of VROOM that returns a point in every bound

Parameters
----------
time: int
time stamp parameter

Returns
-------
point: list
the point to be evaluated
"""

self.iteration = time
node_list = self.partition.get_node_list()

# sample node
index = []
self.prob = []
for h in range(1, self.search_depth + 1):
self.rank(node_list[h])
for l in range(len(node_list[h])):
index.append((h, l))
# print(node_list[h][l].get_rank()[-1])
self.prob.append(1 / (h * node_list[h][l].get_rank()[-1] * self.const))
sample = np.random.choice([i for i in range(len(index))], p=self.prob)
idx = index[sample]
self.curr_node = node_list[idx[0]][idx[1]]
node = node_list[idx[0]][idx[1]]

# sample point
h = idx[0]
self.update_list = [node]
while h < self.h_max:
if node.get_children() is None:
self.partition.make_children(node, newlayer=(h >= self.partition.get_depth()))
sign = np.random.randint(2)
node = node.get_children()[sign]
self.update_list.append(node)
h += 1
return node.sample_uniform()

def rank(self, nodes):
"""
The rank function of VROOM that rank nodes at the same depth

Parameters
----------
nodes: list
a list of node at the same depth

Returns
-------
"""
def rank_fun(node):
if node.get_eval_time() == 0:
return -np.inf
return node.get_mean_reward() - np.sqrt(np.log(4 * self.n**3 / self.delta) / (2 * node.get_eval_time()))
rank = sorted(nodes, key=rank_fun, reverse=True)
for i in range(len(rank)):
node = rank[i]
node.add_rank(i + 1)

def receive_reward(self, time, reward):
"""
The receive_reward function of VROOM to obtain the reward and update Statistics (for current node)

Parameters
----------
time: int
The time stamp parameter
reward: float
The reward of the evaluation

Returns
-------
"""

for i in range(len(self.update_list)):
node = self.update_list[i]
depth = node.get_depth()
prob = 0
idx = 0
for h in range(1, depth + 1):
for l in range(1, 2**h + 1):
prob += self.prob[idx]
idx += 1
if idx >= len(self.prob):
prob = 1
break
node.update_reward(reward)
node.update_reward_tilde(reward / (prob / (2**i))) # Eq. (4) in the paper

def get_last_point(self):
"""
The function to get the last point in VROOM

Returns
-------
point: list
The output of the VROOM algorithm at last
"""
max_value = -np.inf
max_node = None
node_list = self.partition.get_node_list()
for h in range(len(node_list)):
for node in node_list[h]:
value = node.get_reward_tilde() - self.f_max * np.sqrt(2 * self.n * self.const * np.log(2 * self.n **2 / self.delta) * np.sum(node.get_rank())) + self.f_max * self.const * (np.log(2 * self.n **2 / self.delta) / 3)
if value >= max_value:
max_node = node
depth = h
max_value = value
while depth < self.h_max:
if max_node.get_children() is None:
self.partition.make_children(max_node, newlayer=(depth >= self.partition.get_depth()))
sign = np.random.randint(2)
max_node = max_node.get_children()[sign]
depth += 1
return max_node.sample_uniform()
4 changes: 2 additions & 2 deletions PyXAB/tests/test_algos/test_SOO.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_SOO_ValueError_2():


def test_SOO_Garland():
T = 100
T = 1000
Target = Garland.Garland()
domain = [[0, 1]]
partition = BinaryPartition
Expand Down Expand Up @@ -70,4 +70,4 @@ def test_SOO_SmallSearchingDepth():
algo.receive_reward(t, reward)

last_point = algo.get_last_point()
print(T, Target.fmax - Target.f(last_point))
print(T, Target.fmax - Target.f(last_point))
Loading