Hide keyboard shortcuts

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 

4 

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'] 

8 

9 

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 

15 

16 

17def xyz2thetaphi(x, y, z): 

18 phi = np.arccos(z) 

19 theta = np.arctan2(y, x) 

20 return theta, phi 

21 

22 

23def elec_potential(x0): 

24 """ 

25 Compute the potential energy for electrons on a sphere 

26 

27 Parameters 

28 ---------- 

29 x0 : array 

30 First half of x0 or theta values, secnd half phi 

31 

32 Returns 

33 ------- 

34 Potential energy 

35 """ 

36 

37 theta = x0[0:x0.size/2] 

38 phi = x0[x0.size/2:] 

39 

40 x, y, z = thetaphi2xyz(theta, phi) 

41 # Distance squared 

42 dsq = 0. 

43 

44 indices = np.triu_indices(x.size, k=1) 

45 

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 

51 

52 U = np.sum(1./np.sqrt(dsq)) 

53 return U 

54 

55 

56def potential_single(coord0, x, y, z): 

57 """ 

58 Find the potential contribution from a single point. 

59 """ 

60 

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 

70 

71 dsq = (x-x0)**2+(y-y0)**2+(z-z0)**2 

72 U = np.sum(1./np.sqrt(dsq)) 

73 return U 

74 

75def xyz2U(x, y, z): 

76 """ 

77 compute the potential 

78 """ 

79 dsq = 0. 

80 

81 indices = np.triu_indices(x.size, k=1) 

82 

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 

87 

88 d = np.sqrt(dsq) 

89 U = np.sum(1./d) 

90 return U 

91 

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 """ 

97 

98 theta = x0[0:x0.size/2] 

99 phi = x0[x0.size/2:] 

100 x, y, z = thetaphi2xyz(theta, phi) 

101 

102 U_input = xyz2U(x, y, z) 

103 

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)) 

113 

114 xyz_input = np.array((x, y, z)).T 

115 diff = xyz_input - xyz_new 

116 

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 

124 

125 

126def iterate_potential_random(x0, stepsize=.05): 

127 """ 

128 Given a bunch of theta,phi values, shift things around to minimize potential 

129 """ 

130 

131 theta = x0[0:x0.size/2] 

132 phi = x0[x0.size/2:] 

133 

134 x, y, z = thetaphi2xyz(theta, phi) 

135 # Distance squared 

136 dsq = 0. 

137 

138 indices = np.triu_indices(x.size, k=1) 

139 

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 

145 

146 d = np.sqrt(dsq) 

147 

148 U_input = 1./d 

149 

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 

154 

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 

160 

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) 

168 

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 

175 

176 

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:] 

183 

184 indices = np.triu_indices(theta.size, k=1) 

185 

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 

193 

194 

195def fib_sphere_grid(npoints): 

196 """ 

197 Use a Fibonacci spiral to distribute points uniformly on a sphere. 

198 

199 based on https://people.sc.fsu.edu/~jburkardt/py_src/sphere_fibonacci_grid/sphere_fibonacci_grid_points.py 

200 

201 Returns theta and phi in radians 

202 """ 

203 

204 phi = (1.0 + np.sqrt(5.0)) / 2.0 

205 

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 

212 

213 

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 

218 

219 Starting with the Fibonacci spiral speeds things up by ~factor of 2. 

220 """ 

221 

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.) 

229 

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}) 

233 

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 

239 

240 

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. 

247 

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) 

253 

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 

259 

260 U = np.sum(1./np.sqrt(dsq)) 

261 return U 

262 

263 

264def x02sphere(x0): 

265 x0 = x0.reshape(3, x0.size/3) 

266 x = x0[0, :] 

267 y = x0[1, :] 

268 z = x0[2, :] 

269 

270 r = np.sqrt(x**2 + y**2 + z**2) 

271 x = x/r 

272 y = y/r 

273 z = z/r 

274 

275 return np.concatenate((x, y, z)) 

276 

277 

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 

283 

284 Starting with the Fibonacci spiral speeds things up by ~factor of 2. 

285 """ 

286 

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.) 

294 

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) 

298 

299 x = x02sphere(min_fit.x) 

300 

301 # Looks like I get the same energy values as https://en.wikipedia.org/wiki/Thomson_problem 

302 return x 

303 

304