11
11
from scipy .optimize import OptimizeResult
12
12
from scipy .optimize import minimize , Bounds
13
13
from scipy .special import gammaln
14
- from scipy ._lib ._util import check_random_state
14
+ from scipy ._lib ._util import check_random_state , _transition_to_rng
15
15
from scipy .optimize ._constraints import new_bounds_to_old
16
16
17
17
__all__ = ['dual_annealing' ]
@@ -38,21 +38,22 @@ class VisitingDistribution:
38
38
makes the algorithm jump to a more distant region.
39
39
The value range is (1, 3]. Its value is fixed for the life of the
40
40
object.
41
- rand_gen : {`~numpy.random.RandomState`, `~numpy.random.Generator`}
42
- A `~numpy.random.RandomState`, `~numpy.random.Generator` object
43
- for using the current state of the created random generator container.
41
+ rng_gen : {`~numpy.random.Generator`}
42
+ A `~numpy.random.Generator` object for generating new locations.
43
+ (can be a `~numpy.random.RandomState` object until SPEC007 transition
44
+ is fully complete).
44
45
45
46
"""
46
47
TAIL_LIMIT = 1.e8
47
48
MIN_VISIT_BOUND = 1.e-10
48
49
49
- def __init__ (self , lb , ub , visiting_param , rand_gen ):
50
+ def __init__ (self , lb , ub , visiting_param , rng_gen ):
50
51
# if you wish to make _visiting_param adjustable during the life of
51
52
# the object then _factor2, _factor3, _factor5, _d1, _factor6 will
52
53
# have to be dynamically calculated in `visit_fn`. They're factored
53
54
# out here so they don't need to be recalculated all the time.
54
55
self ._visiting_param = visiting_param
55
- self .rand_gen = rand_gen
56
+ self .rng_gen = rng_gen
56
57
self .lower = lb
57
58
self .upper = ub
58
59
self .bound_range = ub - lb
@@ -79,7 +80,7 @@ def visiting(self, x, step, temperature):
79
80
if step < dim :
80
81
# Changing all coordinates with a new visiting value
81
82
visits = self .visit_fn (temperature , dim )
82
- upper_sample , lower_sample = self .rand_gen .uniform (size = 2 )
83
+ upper_sample , lower_sample = self .rng_gen .uniform (size = 2 )
83
84
visits [visits > self .TAIL_LIMIT ] = self .TAIL_LIMIT * upper_sample
84
85
visits [visits < - self .TAIL_LIMIT ] = - self .TAIL_LIMIT * lower_sample
85
86
x_visit = visits + x
@@ -94,9 +95,9 @@ def visiting(self, x, step, temperature):
94
95
x_visit = np .copy (x )
95
96
visit = self .visit_fn (temperature , 1 )[0 ]
96
97
if visit > self .TAIL_LIMIT :
97
- visit = self .TAIL_LIMIT * self .rand_gen .uniform ()
98
+ visit = self .TAIL_LIMIT * self .rng_gen .uniform ()
98
99
elif visit < - self .TAIL_LIMIT :
99
- visit = - self .TAIL_LIMIT * self .rand_gen .uniform ()
100
+ visit = - self .TAIL_LIMIT * self .rng_gen .uniform ()
100
101
index = step - dim
101
102
x_visit [index ] = visit + x [index ]
102
103
a = x_visit [index ] - self .lower [index ]
@@ -110,7 +111,7 @@ def visiting(self, x, step, temperature):
110
111
111
112
def visit_fn (self , temperature , dim ):
112
113
""" Formula Visita from p. 405 of reference [2] """
113
- x , y = self .rand_gen .normal (size = (dim , 2 )).T
114
+ x , y = self .rng_gen .normal (size = (dim , 2 )).T
114
115
115
116
factor1 = np .exp (np .log (temperature ) / (self ._visiting_param - 1.0 ))
116
117
factor4 = self ._factor4_p * factor1
@@ -156,14 +157,14 @@ def __init__(self, lower, upper, callback=None):
156
157
self .upper = upper
157
158
self .callback = callback
158
159
159
- def reset (self , func_wrapper , rand_gen , x0 = None ):
160
+ def reset (self , func_wrapper , rng_gen , x0 = None ):
160
161
"""
161
162
Initialize current location is the search domain. If `x0` is not
162
163
provided, a random location within the bounds is generated.
163
164
"""
164
165
if x0 is None :
165
- self .current_location = rand_gen .uniform (self .lower , self .upper ,
166
- size = len (self .lower ))
166
+ self .current_location = rng_gen .uniform (self .lower , self .upper ,
167
+ size = len (self .lower ))
167
168
else :
168
169
self .current_location = np .copy (x0 )
169
170
init_error = True
@@ -181,9 +182,9 @@ def reset(self, func_wrapper, rand_gen, x0=None):
181
182
'trying new random parameters'
182
183
)
183
184
raise ValueError (message )
184
- self .current_location = rand_gen .uniform (self .lower ,
185
- self .upper ,
186
- size = self .lower .size )
185
+ self .current_location = rng_gen .uniform (self .lower ,
186
+ self .upper ,
187
+ size = self .lower .size )
187
188
reinit_counter += 1
188
189
else :
189
190
init_error = False
@@ -448,10 +449,11 @@ def local_search(self, x, e):
448
449
return e , x_tmp
449
450
450
451
452
+ @_transition_to_rng ("seed" , position_num = 10 )
451
453
def dual_annealing (func , bounds , args = (), maxiter = 1000 ,
452
454
minimizer_kwargs = None , initial_temp = 5230. ,
453
455
restart_temp_ratio = 2.e-5 , visit = 2.62 , accept = - 5.0 ,
454
- maxfun = 1e7 , seed = None , no_local_search = False ,
456
+ maxfun = 1e7 , rng = None , no_local_search = False ,
455
457
callback = None , x0 = None ):
456
458
"""
457
459
Find the global minimum of a function using Dual Annealing.
@@ -507,15 +509,14 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
507
509
algorithm is in the middle of a local search, this number will be
508
510
exceeded, the algorithm will stop just after the local search is
509
511
done. Default value is 1e7.
510
- seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional
511
- If `seed` is None (or `np.random`), the `numpy.random.RandomState`
512
- singleton is used.
513
- If `seed` is an int, a new ``RandomState`` instance is used,
514
- seeded with `seed`.
515
- If `seed` is already a ``Generator`` or ``RandomState`` instance then
516
- that instance is used.
517
- Specify `seed` for repeatable minimizations. The random numbers
518
- generated with this seed only affect the visiting distribution function
512
+ rng : `numpy.random.Generator`, optional
513
+ Pseudorandom number generator state. When `rng` is None, a new
514
+ `numpy.random.Generator` is created using entropy from the
515
+ operating system. Types other than `numpy.random.Generator` are
516
+ passed to `numpy.random.default_rng` to instantiate a `Generator`.
517
+
518
+ Specify `rng` for repeatable minimizations. The random numbers
519
+ generated only affect the visiting distribution function
519
520
and new coordinates generation.
520
521
no_local_search : bool, optional
521
522
If `no_local_search` is set to True, a traditional Generalized
@@ -666,19 +667,19 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
666
667
minimizer_wrapper = LocalSearchWrapper (
667
668
bounds , func_wrapper , * args , ** minimizer_kwargs )
668
669
669
- # Initialization of random Generator for reproducible runs if seed provided
670
- rand_state = check_random_state (seed )
670
+ # Initialization of random Generator for reproducible runs if rng provided
671
+ rng_gen = check_random_state (rng )
671
672
# Initialization of the energy state
672
673
energy_state = EnergyState (lower , upper , callback )
673
- energy_state .reset (func_wrapper , rand_state , x0 )
674
+ energy_state .reset (func_wrapper , rng_gen , x0 )
674
675
# Minimum value of annealing temperature reached to perform
675
676
# re-annealing
676
677
temperature_restart = initial_temp * restart_temp_ratio
677
678
# VisitingDistribution instance
678
- visit_dist = VisitingDistribution (lower , upper , visit , rand_state )
679
+ visit_dist = VisitingDistribution (lower , upper , visit , rng_gen )
679
680
# Strategy chain instance
680
681
strategy_chain = StrategyChain (accept , visit_dist , func_wrapper ,
681
- minimizer_wrapper , rand_state , energy_state )
682
+ minimizer_wrapper , rng_gen , energy_state )
682
683
need_to_stop = False
683
684
iteration = 0
684
685
message = []
@@ -701,7 +702,7 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
701
702
break
702
703
# Need a re-annealing process?
703
704
if temperature < temperature_restart :
704
- energy_state .reset (func_wrapper , rand_state )
705
+ energy_state .reset (func_wrapper , rng_gen )
705
706
break
706
707
# starting strategy chain
707
708
val = strategy_chain .run (i , temperature )
0 commit comments