-
Notifications
You must be signed in to change notification settings - Fork 0
/
ustep.py
executable file
·150 lines (125 loc) · 4.87 KB
/
ustep.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env python
'''
Step-Size Algorithm for Bonse-Hart Ultra-Small-Angle Scattering Instruments
:see: http://usaxs.xray.aps.anl.gov/docs/ustep/index.html
'''
import logging
logger = logging.getLogger(__name__)
class ustep(object):
'''
find the series of positions for the USAXS
:param float start: required first position of list
:param float center: position to take minStep
:param float finish: required ending position of list
:param float numPts: length of the list
:param float exponent: :math:`\eta`, exponential factor
:param float minStep: smallest allowed step size
:param float factor: :math:`k`, multiplying factor (computed internally)
:param [float] series: computed list of positions
'''
def __init__(self, start, center, finish, numPts, exponent, minStep):
self.start = start
self.center = center
self.finish = finish
self.numPts = numPts
self.exponent = exponent
self.minStep = minStep
self.sign = {True: 1, False: -1}[start < finish]
self.series = []
self.factor = self.find_factor()
def find_factor(self):
'''
Determine the factor that will make a series with the specified parameters.
This method improves on find_factor_simplistic() by
choosing next choice for factor from recent history.
'''
def assess(factor):
self.make_series(factor)
span_diff = abs(self.series[0] - self.series[-1]) - span_target
return span_diff
span_target = abs(self.finish - self.start)
span_precision = abs(self.minStep) * 0.2
factor = abs(self.finish-self.start) / (self.numPts -1)
span_diff = assess(factor)
f = [factor, factor]
d = [span_diff, span_diff]
# first make certain that d[0] < 0 and d[1] > 0, expand f[0] and f[1]
for _ in range(100):
if d[0] * d[1] < 0:
break # now, d[0] and d[1] have opposite sign
factor *= {True: 2, False: 0.5}[span_diff < 0]
span_diff = assess(factor)
key = {True: 1, False: 0}[span_diff > d[1]]
f[key] = factor
d[key] = span_diff
# now: d[0] < 0 and d[1] > 0, squeeze f[0] & f[1] to converge
for _ in range(100):
if (d[1] - d[0]) > span_target:
factor = (f[0] + f[1])/2 # bracket by bisection when not close
else:
factor = f[0] - d[0] * (f[1]-f[0])/(d[1]-d[0]) # linear interpolation when close
span_diff = assess(factor)
if abs(span_diff) <= span_precision:
break
key = {True: 0, False: 1}[span_diff < 0]
f[key] = factor
d[key] = span_diff
return factor
def find_factor_simplistic(self):
'''
Determine the factor that will make a series with the specified parameters.
Choose the factor that will minimize :math:`| x_n - finish |` subject to:
.. math::
x_1 = start
x_n <= finish
This routine CAN FAIL if :math:`(finish - start)/minStep >= numPts`
This search technique picks a new factor based on the fit of the present choice.
It converges but not quickly.
'''
logger.debug('\t'.join('factor diff'.split()))
span_target = abs(self.finish - self.start)
span_precision = abs(self.minStep) * 0.2
factor = abs(self.finish-self.start) / (self.numPts -1)
fStep = factor
larger = 3.0
smaller = 0.5
for _ in range(100):
self.make_series(factor)
span = abs(self.series[0] - self.series[-1])
span_diff = span - span_target
logger.debug('\t'.join(map(str,[factor, span_diff])))
if abs(span_diff) <= span_precision:
break
if span_diff < 0:
fStep = abs(fStep) * larger
else:
fStep = -abs(fStep) * smaller
factor += fStep
return factor
def make_series(self, factor):
'''create self.series with the given factor'''
x = self.start
series = [x, ]
for _ in range(self.numPts - 1):
x += self.sign * self.uascanStepFunc(x, factor)
series.append(x)
self.series = series
def uascanStepFunc(self, x, factor):
'''Calculate the next step size with the given parameters'''
if abs(x - self.center) > 1e100:
step = 1e100
else:
step = factor * pow( abs(x - self.center), self.exponent ) + self.minStep
return step
def main():
start = 10.0
center = 9.5
finish = 7
numPts = 100
exponent = 1.2
minStep = 0.0001
u = ustep(start, center, finish, numPts, exponent, minStep)
print u.factor
print u.series
if __name__ == '__main__':
main()