gradient_free_optimizers.optimizers.grid.diagonal_grid_search   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 13
eloc 77
dl 0
loc 135
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A DiagonalGridSearchOptimizer.evaluate() 0 3 1
A DiagonalGridSearchOptimizer.get_direction() 0 20 3
A DiagonalGridSearchOptimizer.grid_move() 0 20 2
B DiagonalGridSearchOptimizer.iterate() 0 47 6
A DiagonalGridSearchOptimizer.__init__() 0 25 1
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__(
17
        self,
18
        search_space,
19
        initialize={"grid": 4, "random": 2, "vertices": 4},
20
        constraints=[],
21
        random_state=None,
22
        rand_rest_p=0,
23
        nth_process=None,
24
        step_size=1,
25
    ):
26
        super().__init__(
27
            search_space=search_space,
28
            initialize=initialize,
29
            constraints=constraints,
30
            random_state=random_state,
31
            rand_rest_p=rand_rest_p,
32
            nth_process=nth_process,
33
        )
34
        self.initial_position = np.zeros((self.conv.n_dimensions,), dtype=int)
35
        # current position mapped in [|0,search_space_size-1|] (1D)
36
        self.high_dim_pointer = 0
37
        # direction is a generator of our search space (prime with search_space_size)
38
        self.direction_calc = None
39
        # step_size describes how many steps of size direction are jumped at each iteration
40
        self.step_size = step_size
41
42
    def get_direction(self):
43
        """
44
        Aim here is to generate a prime number of the search space size we call our direction.
45
        As direction is prime with the search space size, we know it is
46
        a generator of Z/(search_space_size*Z).
47
        """
48
        n_dims = self.conv.n_dimensions
49
        search_space_size = self.conv.search_space_size
50
51
        # Find prime number near search_space_size ** (1/n_dims)
52
        dim_root = int(np.round(np.power(search_space_size, 1 / n_dims)))
53
        is_prime = False
54
55
        while not is_prime:
56
            if gcd(int(search_space_size), int(dim_root)) == 1:
57
                is_prime = True
58
            else:
59
                dim_root += -1
60
61
        return dim_root
62
63
    def grid_move(self):
64
        """
65
        We convert our pointer of Z/(search_space_size * Z) in a position of our search space.
66
        This algorithm uses a bijection from Z/(search_space_size * Z) -> (Z/dim_1*Z)x...x(Z/dim_n*Z).
67
        """
68
        new_pos = []
69
        dim_sizes = self.conv.dim_sizes
70
        pointer = self.high_dim_pointer
71
72
        # The coordinate of our new position for each dimension is
73
        # the quotient of the pointer by the product of remaining dimensions
74
        # Describes a bijection from Z/search_space_size*Z -> (Z/dim_1*Z)x...x(Z/dim_n*Z)
75
        for dim in range(len(dim_sizes) - 1):
76
            new_pos.append(
77
                pointer // np.prod(dim_sizes[dim + 1 :]) % dim_sizes[dim]
78
            )
79
            pointer = pointer % np.prod(dim_sizes[dim + 1 :])
80
        new_pos.append(pointer)
81
82
        return np.array(new_pos)
83
84
    @BaseOptimizer.track_new_pos
85
    def iterate(self):
86
        # while loop for constraint opt
87
        while True:
88
            # If this is the first iteration:
89
            # Generate the direction and return initial_position
90
            if self.direction_calc is None:
91
                self.direction_calc = self.get_direction()
92
93
                pos_new = self.initial_position
94
                if self.conv.not_in_constraint(pos_new):
95
                    return pos_new
96
                else:
97
                    return self.move_random()
98
99
            # If this is not the first iteration:
100
            # Update high_dim_pointer by taking a step of size step_size * direction.
101
102
            # Multiple passes are needed in order to observe the entire search space
103
            # depending on the step_size parameter.
104
            _, current_pass = (
105
                self.nth_trial,
106
                self.high_dim_pointer % self.step_size,
107
            )
108
            current_pass_finished = (
109
                (self.nth_trial + 1)
110
                * self.step_size
111
                // self.conv.search_space_size
112
                > self.nth_trial * self.step_size // self.conv.search_space_size
113
            )
114
            # Begin the next pass if current is finished.
115
            if current_pass_finished:
116
                self.high_dim_pointer = current_pass + 1
117
            else:
118
                # Otherwise update pointer in Z/(search_space_size*Z)
119
                # using the prime step direction and step_size.
120
                self.high_dim_pointer = (
121
                    self.high_dim_pointer + self.step_size * self.direction_calc
122
                ) % self.conv.search_space_size
123
124
            # Compute corresponding position in our search space.
125
126
            pos_new = self.grid_move()
127
            pos_new = self.conv2pos(pos_new)
128
129
            if self.conv.not_in_constraint(pos_new):
130
                return pos_new
131
132
    @BaseOptimizer.track_new_score
133
    def evaluate(self, score_new):
134
        BaseOptimizer.evaluate(self, score_new)
135