DiagonalGridSearchOptimizer.__init__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 25
rs 9.4
c 0
b 0
f 0
cc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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