Coverage for python/lsst/sims/featureScheduler/thomson/thomson.py : 9%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2from scipy.optimize import minimize
3from lsst.sims.utils import _angularSeparation
5__all__ = ['thetaphi2xyz', 'even_points', 'elec_potential', 'ang_potential', 'fib_sphere_grid',
6 'iterate_potential_random', 'iterate_potential_smart', 'even_points_xyz', 'elec_potential_xyz',
7 'xyz2thetaphi']
10def thetaphi2xyz(theta, phi):
11 x = np.sin(phi)*np.cos(theta)
12 y = np.sin(phi)*np.sin(theta)
13 z = np.cos(phi)
14 return x, y, z
17def xyz2thetaphi(x, y, z):
18 phi = np.arccos(z)
19 theta = np.arctan2(y, x)
20 return theta, phi
23def elec_potential(x0):
24 """
25 Compute the potential energy for electrons on a sphere
27 Parameters
28 ----------
29 x0 : array
30 First half of x0 or theta values, secnd half phi
32 Returns
33 -------
34 Potential energy
35 """
37 theta = x0[0:x0.size/2]
38 phi = x0[x0.size/2:]
40 x, y, z = thetaphi2xyz(theta, phi)
41 # Distance squared
42 dsq = 0.
44 indices = np.triu_indices(x.size, k=1)
46 for coord in [x, y, z]:
47 coord_i = np.tile(coord, (coord.size, 1))
48 coord_j = coord_i.T
49 d = (coord_i[indices]-coord_j[indices])**2
50 dsq += d
52 U = np.sum(1./np.sqrt(dsq))
53 return U
56def potential_single(coord0, x, y, z):
57 """
58 Find the potential contribution from a single point.
59 """
61 x0 = coord0[0]
62 y0 = coord0[1]
63 z0 = coord0[2]
64 # Enforce point has to be on a sphere
65 rsq = x0**2+y0**2 + z0**2
66 r = np.sqrt(rsq)
67 x0 = x0/r
68 y0 = y0/r
69 z0 = z0/r
71 dsq = (x-x0)**2+(y-y0)**2+(z-z0)**2
72 U = np.sum(1./np.sqrt(dsq))
73 return U
75def xyz2U(x, y, z):
76 """
77 compute the potential
78 """
79 dsq = 0.
81 indices = np.triu_indices(x.size, k=1)
83 for coord in [x, y, z]:
84 coord_i = np.tile(coord, (coord.size, 1))
85 coord_j = coord_i.T
86 dsq += (coord_i[indices]-coord_j[indices])**2
88 d = np.sqrt(dsq)
89 U = np.sum(1./d)
90 return U
92def iterate_potential_smart(x0, stepfrac=0.1):
93 """
94 Calculate the change in potential by shifting points in theta and phi directions
95 # wow, that sure didn't work at all.
96 """
98 theta = x0[0:x0.size/2]
99 phi = x0[x0.size/2:]
100 x, y, z = thetaphi2xyz(theta, phi)
102 U_input = xyz2U(x, y, z)
104 # Now to loop over each point, and find where it's potenital minimum would be, and move it
105 # half-way there.
106 xyz_new = np.zeros((x.size, 3), dtype=float)
107 mask = np.ones(x.size, dtype=bool)
108 for i in np.arange(x.size):
109 mask[i] = 0
110 fit = minimize(potential_single, [x[i], y[i], z[i]], args=(x[mask], y[mask], z[mask]))
111 mask[i] = 1
112 xyz_new[i] = fit.x/np.sqrt(np.sum(fit.x**2))
114 xyz_input = np.array((x, y, z)).T
115 diff = xyz_input - xyz_new
117 # Move half way in x-y-z space
118 xyz_out = xyz_input + stepfrac*diff
119 # Project back onto sphere
120 xyz_out = xyz_out.T/np.sqrt(np.sum(xyz_out**2, axis=1))
121 U_new = xyz2U(xyz_out[0, :], xyz_out[1, :], xyz_out[2, :])
122 theta, phi = xyz2thetaphi(xyz_out[0, :], xyz_out[1, :], xyz_out[2, :])
123 return np.concatenate((theta, phi)), U_new
126def iterate_potential_random(x0, stepsize=.05):
127 """
128 Given a bunch of theta,phi values, shift things around to minimize potential
129 """
131 theta = x0[0:x0.size/2]
132 phi = x0[x0.size/2:]
134 x, y, z = thetaphi2xyz(theta, phi)
135 # Distance squared
136 dsq = 0.
138 indices = np.triu_indices(x.size, k=1)
140 for coord in [x, y, z]:
141 coord_i = np.tile(coord, (coord.size, 1))
142 coord_j = coord_i.T
143 d = (coord_i[indices]-coord_j[indices])**2
144 dsq += d
146 d = np.sqrt(dsq)
148 U_input = 1./d
150 # offset everything by a random ammount
151 x_new = x + np.random.random(theta.size) * stepsize
152 y_new = y + np.random.random(theta.size) * stepsize
153 z_new = z + np.random.random(theta.size) * stepsize
155 r = (x_new**2 + y_new**2 + z_new**2)**0.5
156 # put back on the sphere
157 x_new = x_new/r
158 y_new = y_new/r
159 z_new = z_new/r
161 dsq_new = 0
162 for coord, coord_new in zip([x, y, z], [x_new, y_new, z_new]):
163 coord_i_new = np.tile(coord_new, (coord_new.size, 1))
164 coord_j = coord_i_new.T
165 d_new = (coord_i_new[indices]-coord_j[indices])**2
166 dsq_new += d_new
167 U_new = 1./np.sqrt(dsq_new)
169 U_diff = np.sum(U_new)-np.sum(U_input)
170 if U_diff > 0:
171 return x0, 0.
172 else:
173 theta, phi = xyz2thetaphi(x_new, y_new, z_new)
174 return np.concatenate((theta, phi)), U_diff
177def ang_potential(x0):
178 """
179 If distance is computed along sphere rather than through 3-space.
180 """
181 theta = x0[0:x0.size/2]
182 phi = np.pi/2-x0[x0.size/2:]
184 indices = np.triu_indices(theta.size, k=1)
186 theta_i = np.tile(theta, (theta.size, 1))
187 theta_j = theta_i.T
188 phi_i = np.tile(phi, (phi.size, 1))
189 phi_j = phi_i.T
190 d = _angularSeparation(theta_i[indices], phi_i[indices], theta_j[indices], phi_j[indices])
191 U = np.sum(1./d)
192 return U
195def fib_sphere_grid(npoints):
196 """
197 Use a Fibonacci spiral to distribute points uniformly on a sphere.
199 based on https://people.sc.fsu.edu/~jburkardt/py_src/sphere_fibonacci_grid/sphere_fibonacci_grid_points.py
201 Returns theta and phi in radians
202 """
204 phi = (1.0 + np.sqrt(5.0)) / 2.0
206 i = np.arange(npoints, dtype=float)
207 i2 = 2*i - (npoints-1)
208 theta = (2.0*np.pi * i2/phi) % (2.*np.pi)
209 sphi = i2/npoints
210 phi = np.arccos(sphi)
211 return theta, phi
214def even_points(npts, use_fib_init=True, method='CG', potential_func=elec_potential, maxiter=None):
215 """
216 Distribute npts over a sphere and minimize their potential, making them
217 "evenly" distributed
219 Starting with the Fibonacci spiral speeds things up by ~factor of 2.
220 """
222 if use_fib_init:
223 # Start with fibonacci spiral guess
224 theta, phi = fib_sphere_grid(npts)
225 else:
226 # Random on a sphere
227 theta = np.random.rand(npts)*np.pi*2.
228 phi = np.arccos(2.*np.random.rand(npts)-1.)
230 x = np.concatenate((theta, phi))
231 # XXX--need to check if this is the best minimizer
232 min_fit = minimize(potential_func, x, method='CG', options={'maxiter': maxiter})
234 x = min_fit.x
235 theta = x[0:x.size/2]
236 phi = x[x.size/2:]
237 # Looks like I get the same energy values as https://en.wikipedia.org/wiki/Thomson_problem
238 return theta, phi
241def elec_potential_xyz(x0):
242 x0 = x0.reshape(3, x0.size/3)
243 x = x0[0, :]
244 y = x0[1, :]
245 z = x0[2, :]
246 dsq = 0.
248 r = np.sqrt(x**2 + y**2 + z**2)
249 x = x/r
250 y = y/r
251 z = z/r
252 indices = np.triu_indices(x.size, k=1)
254 for coord in [x, y, z]:
255 coord_i = np.tile(coord, (coord.size, 1))
256 coord_j = coord_i.T
257 d = (coord_i[indices]-coord_j[indices])**2
258 dsq += d
260 U = np.sum(1./np.sqrt(dsq))
261 return U
264def x02sphere(x0):
265 x0 = x0.reshape(3, x0.size/3)
266 x = x0[0, :]
267 y = x0[1, :]
268 z = x0[2, :]
270 r = np.sqrt(x**2 + y**2 + z**2)
271 x = x/r
272 y = y/r
273 z = z/r
275 return np.concatenate((x, y, z))
278def even_points_xyz(npts, use_fib_init=True, method='CG', potential_func=elec_potential_xyz, maxiter=None,
279 callback=None):
280 """
281 Distribute npts over a sphere and minimize their potential, making them
282 "evenly" distributed
284 Starting with the Fibonacci spiral speeds things up by ~factor of 2.
285 """
287 if use_fib_init:
288 # Start with fibonacci spiral guess
289 theta, phi = fib_sphere_grid(npts)
290 else:
291 # Random on a sphere
292 theta = np.random.rand(npts)*np.pi*2.
293 phi = np.arccos(2.*np.random.rand(npts)-1.)
295 x = np.concatenate(thetaphi2xyz(theta, phi))
296 # XXX--need to check if this is the best minimizer
297 min_fit = minimize(potential_func, x, method='CG', options={'maxiter': maxiter}, callback=callback)
299 x = x02sphere(min_fit.x)
301 # Looks like I get the same energy values as https://en.wikipedia.org/wiki/Thomson_problem
302 return x