Passed
Push — master ( 502a6e...660aa4 )
by Simon
04:50 queued 14s
created

DiagonalGridSearchOptimizer.get_direction()   A

Complexity

Conditions 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nop 1
dl 0
loc 20
rs 9.9
c 0
b 0
f 0
1
# Author: ...
2
# Email: ...
3
# License: MIT License
4
5
import numpy as np
6
7
try:
8
    from fractions import gcd
9
except:
10
    from math import gcd
11
12
from ..base_optimizer import BaseOptimizer
13
14
15
class DiagonalGridSearchOptimizer(BaseOptimizer):
16
    def __init__(self, *args, step_size=1, **kwargs):
17
        super().__init__(*args, **kwargs)
18
        self.initial_position = np.zeros((self.conv.n_dimensions,), dtype=int)
19
        # current position mapped in [|0,search_space_size-1|] (1D)
20
        self.high_dim_pointer = 0
21
        # direction is a generator of our search space (prime with search_space_size)
22
        self.direction_calc = None
23
        # step_size describes how many steps of size direction are jumped at each iteration
24
        self.step_size = step_size
25
26
    def get_direction(self):
27
        """
28
        Aim here is to generate a prime number of the search space size we call our direction.
29
        As direction is prime with the search space size, we know it is
30
        a generator of Z/(search_space_size*Z).
31
        """
32
        n_dims = self.conv.n_dimensions
33
        search_space_size = self.conv.search_space_size
34
35
        # Find prime number near search_space_size ** (1/n_dims)
36
        dim_root = int(np.round(np.power(search_space_size, 1 / n_dims)))
37
        is_prime = False
38
39
        while not is_prime:
40
            if gcd(int(search_space_size), int(dim_root)) == 1:
41
                is_prime = True
42
            else:
43
                dim_root += -1
44
45
        return dim_root
46
47
    def grid_move(self):
48
        """
49
        We convert our pointer of Z/(search_space_size * Z) in a position of our search space.
50
        This algorithm uses a bijection from Z/(search_space_size * Z) -> (Z/dim_1*Z)x...x(Z/dim_n*Z).
51
        """
52
        new_pos = []
53
        dim_sizes = self.conv.dim_sizes
54
        pointer = self.high_dim_pointer
55
56
        # The coordinate of our new position for each dimension is
57
        # the quotient of the pointer by the product of remaining dimensions
58
        # Describes a bijection from Z/search_space_size*Z -> (Z/dim_1*Z)x...x(Z/dim_n*Z)
59
        for dim in range(len(dim_sizes) - 1):
60
            new_pos.append(pointer // np.prod(dim_sizes[dim + 1 :]) % dim_sizes[dim])
61
            pointer = pointer % np.prod(dim_sizes[dim + 1 :])
62
        new_pos.append(pointer)
63
64
        return np.array(new_pos)
65
66
    @BaseOptimizer.track_new_pos
67
    def iterate(self):
68
        # while loop for constraint opt
69
        while True:
70
            # If this is the first iteration:
71
            # Generate the direction and return initial_position
72
            if self.direction_calc is None:
73
                self.direction_calc = self.get_direction()
74
75
                pos_new = self.initial_position
76
                if self.conv.not_in_constraint(pos_new):
77
                    return pos_new
78
                else:
79
                    return self.move_random()
80
81
            # If this is not the first iteration:
82
            # Update high_dim_pointer by taking a step of size step_size * direction.
83
84
            # Multiple passes are needed in order to observe the entire search space
85
            # depending on the step_size parameter.
86
            _, current_pass = self.nth_trial, self.high_dim_pointer % self.step_size
87
            current_pass_finished = (
88
                (self.nth_trial + 1) * self.step_size // self.conv.search_space_size
89
                > self.nth_trial * self.step_size // self.conv.search_space_size
90
            )
91
            # Begin the next pass if current is finished.
92
            if current_pass_finished:
93
                self.high_dim_pointer = current_pass + 1
94
            else:
95
                # Otherwise update pointer in Z/(search_space_size*Z)
96
                # using the prime step direction and step_size.
97
                self.high_dim_pointer = (
98
                    self.high_dim_pointer + self.step_size * self.direction_calc
99
                ) % self.conv.search_space_size
100
101
            # Compute corresponding position in our search space.
102
103
            pos_new = self.grid_move()
104
            pos_new = self.conv2pos(pos_new)
105
106
            if self.conv.not_in_constraint(pos_new):
107
                return pos_new
108
109
    @BaseOptimizer.track_new_score
110
    def evaluate(self, score_new):
111
        BaseOptimizer.evaluate(self, score_new)
112