1 """
2 Defines transformations between the a fit space and the parameter space.
3
4 The parameter space maps a set of possibly bounded dimensions into a
5 real-valued fitness::
6
7 f: [a,b]**n |-> R
8
9 The fit space is constrained to map values in the unit box::
10
11 f': [0,1]**n |-> R
12
13 Here we can define f' using the zero-one mapper::
14
15 f' = ZeroOneMapper(f,a,b)
16 f'(v) = f(x)
17
18 An asymptote function maps x to [0,1], preserving at least 12 digits of
19 precision.
20
21 Using this mapping, the optimizer can operate in a well known space
22 independent of parameter precision. This allows the use of reasonable
23 constants for items such as a step size and initial value.
24
25 Note: we do not yet support fitness functions with analytic derivatives.
26 Given df/dp and mapping function f'(v) = f(M(v)) then df'/dv = df/dM dM/dv.
27 So the derivatives need to be multiplied by the derivative of the
28 parameter mapper. Not difficult computationally, but the definition of
29 the fitness function does not currently support this organization.
30 """
31 import numpy
32
34 """
35 Arctan asymptote function for mapping (-inf,inf) to [0,1].
36
37 With all attributes constant, either the class or an instance can be used.
38
39 An asymptote function is monotonically increasing and finite everywhere.
40 Assuming the function is a at -inf and b at +inf, scale is 1/(b-a) and
41 offset is a. Together with an inverse function, this allows us to
42 transform between an infinite range and the range [0,1]. Semidefinite
43 map to (-inf,0] and [0,inf).
44
45 ArctanAsymptote is approximately linear in [-0.5,0.5], and is useful
46 out to the range [-1e15,1e15], though with much reduced precision
47 for larger values.
48 """
49 scale = 1/numpy.pi
50 offset = -numpy.pi/2
51 forward = numpy.arctan
52 inverse = numpy.tan
53
55 """
56 Linearize floating point values using an exponential scale.
57
58 Convert sign*m*2^e to sign*(e+1023+m), yielding a value in [-2047,2047]
59 """
60 (m,e) = numpy.frexp(x)
61 s = numpy.sign(x)
62 v = (e+1023+m*s)*s
63 return v
65 """
66 Restore floating point value from linear form on an exponential scale.
67
68 Convert sign*(e+1023+m) to sign*m*2^e, yielding a value in [-1e308,1e308]
69 """
70 s = sign(v)
71 v *= s
72 m = floor(v)
73 e = v-m
74 x = ldexp(s*m,e)
75 return x
76 fp_min, fp_max = -(1024+1023), (1024+1023)
78 """
79 Logarithmic asymptote function.
80
81 This is an asymptote function which follows the floating point
82 representation, using the following mapping::
83
84 [-1e308,-1e-308] -> [0,0.5)
85 0 -> 0.5
86 [1e-308,1e308] -> (0.5+eps,1]
87
88 With 11 bits of the 53 bits of available precision for the exponent,
89 this leaves a tolerance of 1e-12 on the fitting parameters, which is
90 easily good enough for any real unbounded fitting problem. The user
91 can increased the precision on the bounded parameters by using tighter
92 bounds.
93 """
94 scale = 1/(fp_max-fp_min)
95 offset = fp_min
96 forward = fp_forward
97 inverse = fp_inverse
98
100 """
101 Map function range into [0,1]**n.
102
103 Given f: [a,b] -> R
104 Defines f': [0,1] -> R
105
106 The method encode(x) returns a value in [0,1]
107 The method decode(v) returns a value in [a,b]
108 The method __call__(v) returns f(decode(v))
109
110 For indefinite and semidefinte ranges, an asymptote transformation
111 is needed. Normally this is `park.rangemap.Asymptote` which supports
112 the full floating point range of values, but is limited to about
113 12 digits of precision. The arctan function can also be used
114 (see `park.rangemap.ArctanAsymptote`).
115
116 Range is determined by the bounds low and high. Each dimension may be
117 unbounded, semi-definite or bounded. Bounded functions use a linear
118 mapping between low and high. Unbounded functions use an asymptote
119 function, which should be -1 at -inf, 0 at 0 and 1 at +inf, and
120 approximate the identity function between [-0.5,0.5].
121
122 This can be used to turn any unconstrained optimizer into a [0,1]
123 bounded optimizer by sending infeasible points to infinity.
124
125 Note: Newton-style optimizers will not work in this regime, but
126 instead require a hint about the direction of the unconstrained
127 region.
128 """
129 - def __init__(self, base_function, low, high, asymptote=Asymptote,
130 range_check=False):
131 """
132 Function range mapper. See `park.rangemap.ZeroOneMapper` for details.
133
134 base_function is the function being wrapped.
135
136 low[k],high[k] is the range of values for each fitting parameter k.
137
138 range_check is True if calls to the mapper may include out of range
139 values. In this case, f(v) returns inf for out of range values and
140 encode/decode raise exceptions.
141
142 asymptote is the asymptote function to use when linearizing
143 indefinite ranges.
144 """
145
146 unbounded_low = numpy.isinf(low)
147 unbounded_high = numpy.isinf(high)
148 unbounded = unbounded_low|unbounded_high
149
150
151
152
153
154 scale = (high-low)
155 offset = -low
156 scale[unbounded_low | unbounded_high] = 0.
157 offset[unbounded_low] = high[unbounded_low]
158 offset[unbounded_high] = low[unbounded_high]
159 offset[unbounded_low & unbounded_high] = 0.
160
161
162
163
164
165 a_scale = numpy.ones(scale.shape)*asymptote.scale
166 a_scale[unbounded_low ^ unbounded_high] *= 0.5
167 a_offset = numpy.zeros(offset.shape)+asymptote.offset
168 a_offset[unbounded_high & ~unbounded_low] += 0.5*asymptote.scale
169 a_forward = asymptote.forward
170 a_inverse = asymptote.inverse
171
172
173 a_scale = a_scale[unbounded]
174 a_offset = a_offset[unbounded]
175
176
177 self.low,self.high = low,high
178 self.base_function = base_function
179 self.scale,self.offset = scale,offset
180 self.a_scale,self.a_offset = a_scale,a_offset
181 self.a_forward,self.a_inverse = a_forward,a_inverse
182
183
184
185 if numpy.any(unbounded):
186 self.__call__ = self._indefinite
187 else:
188 self.__call__ = self._definite
189
191 """Use this function if some bounds are indefinite"""
192 x = v.copy()
193 x[unbounded] = self.a_inverse(x[unbounded])*self.a_scale + self.a_offset
194 x = x*self.scale+self.offset
195 return self.base_function(x)
197 """Use this function if all bounds are definite"""
198 return self.base_function(v*self.scale+self.offset)
200 """Use this function if some bounds are indefinite and ranges need
201 to be checked"""
202 if numpy.any(v<0|v>1): return numpy.inf
203 x = v.copy()
204 x[unbounded] = self.a_inverse(x[unbounded])*self.a_scale + self.a_offset
205 x = x*self.scale+self.offset
206 return self.base_function(x)
208 """Use this function if all bounds are definite, but ranges need
209 to be checked"""
210 if numpy.any(v<0|v>1): return numpy.inf
211 return self.base_function(v*self.scale+self.offset)
212
214 """Transform from range [0,1] to [a,b]."""
215 if numpy.any(v<0|v>1):
216 raise RuntimeError("value out of range [0,1]")
217 x = v.copy()
218 x[unbounded] = self.a_forward(x[unbounded])*a_scale + a_offset
219 x = x*scale+offset
220 return x
222 """Transform from range [a,b] to [0,1]."""
223 if numpy.any(x<self.low|x>self.high):
224 raise RuntimeError("value out of range [a,b]")
225 v = (x-offset)/scale
226 v[unbounded] = (self.a_inverse(v[unbounded])-a_offset)/a_scale
227 return v
228
229
230
232 """
233 Map function range into [0,1]**n.
234
235 Returns a pair of functions f and inv. The function f takes an
236 encoded value in
237 encode, which takes x and returns a value
238 in [0,1] and decode, which takes a value in [0,1] and returns x.
239
240 Range is determined by the bounds low and high. Each dimension may be
241 unbounded, semi-definite or bounded. Bounded functions use a linear
242 mapping between low and high. Unbounded functions use an asymptote
243 function, which should be -1 at -inf, 0 at 0 and 1 at +inf, and
244 approximate the identity function between [-0.5,0.5].
245
246 This can be used to turn any unconstrained optimizer into a [0,1]
247 bounded optimizer by sending infeasible points to infinity.
248
249 Note: Newton-style optimizers will not work in this regime, but
250 instead require a hint about the direction of the unconstrained
251 region.
252 """
253
254 unbounded_low = numpy.isinf(low)
255 unbounded_high = numpy.isinf(high)
256 unbounded = unbounded_low|unbounded_high
257
258
259
260
261
262 scale = (high-low)
263 offset = -low
264 scale[unbounded_low | unbounded_high] = 0.
265 offset[unbounded_low] = high[unbounded_low]
266 offset[unbounded_high] = low[unbounded_high]
267 offset[unbounded_low & unbounded_high] = 0.
268
269
270
271
272
273 a_scale = numpy.ones(scale.shape)*asymptote.scale
274 a_scale[unbounded_low ^ unbounded_high] *= 0.5
275 a_offset = numpy.zeros(offset.shape)+asymptote.offset
276 a_offset[unbounded_high & ~unbounded_low] += 0.5*asymptote.scale
277
278
279 a_scale = a_scale[unbounded]
280 a_offset = a_offset[unbounded]
281
282
283 if numpy.any(unbounded):
284 def function(v):
285 x = v.copy()
286 x[unbounded] = asymptote.inverse(x[unbounded])*a_scale + a_offset
287 x = x*scale+offset
288 return base_function(x)
289 def decode(v):
290 x = v.copy()
291 x[unbounded] = asymptote.inverse(x[unbounded])*a_scale + a_offset
292 x = x*scale+offset
293 return x
294 def encode(x):
295 v = (x-offset)/scale
296 v[unbounded] = (asymptote.function(v[unbounded])-a_offset)/a_scale
297 return v
298 else:
299 def function(v): return base_function( (x-offset)/scale )
300 def decode(v): return (v*scale+offset)
301 def function(x): return base_function( (x-offset)/scale )
302 def decode(v): return (v*scale+offset)
303
304 return function, encode, decode
305
306
308 """
309 Evaluate a function with bounds checking.
310
311 This can be used to turn any unconstrained optimizer into a
312 constrained optimizer by sending infeasible points to infinity.
313
314 Note: Newton-style optimizers will not work in this regime, but
315 instead require a hint about the direction of the unconstrained
316 region.
317
318 Note: unused function which may be removed in future versions.
319 """
320 def function_wrapper(x):
321 if numpy.any((x<low) | (x>high)):
322 return numpy.Inf
323 else:
324 return function(x)
325 function_wrapper.__doc__ = function.__doc__
326 return function_wrapper
327