1
0

run_simulated_annealing.html 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
  6. <meta name="generator" content="pdoc 0.5.2" />
  7. <title>run_simulated_annealing API documentation</title>
  8. <meta name="description" content="Simulated annealing algorithm used to match the simulation of the
  9. Antennae to the observations by comparing binarized images." />
  10. <link href='https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css' rel='stylesheet'>
  11. <link href='https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/8.0.0/sanitize.min.css' rel='stylesheet'>
  12. <link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" rel="stylesheet">
  13. <style>.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{font-weight:bold}#index h4 + ul{margin-bottom:.6em}#index .two-column{column-count:2}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.name small{font-weight:normal}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase;cursor:pointer}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}.admonition{padding:.1em .5em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
  14. <style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
  15. <style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
  16. </head>
  17. <body>
  18. <main>
  19. <article id="content">
  20. <header>
  21. <h1 class="title"><code>run_simulated_annealing</code> module</h1>
  22. </header>
  23. <section id="section-intro">
  24. <p>Simulated annealing algorithm used to match the simulation of the
  25. Antennae to the observations by comparing binarized images.</p>
  26. <details class="source">
  27. <summary>Source code</summary>
  28. <pre><code class="python">&#34;&#34;&#34;Simulated annealing algorithm used to match the simulation of the
  29. Antennae to the observations by comparing binarized images.&#34;&#34;&#34;
  30. import numpy as np
  31. import scipy
  32. import pickle
  33. from scipy import ndimage
  34. from fast_histogram import histogram2d
  35. from scipy.signal import convolve2d
  36. from numba import jit
  37. from matplotlib.image import imread
  38. import datetime
  39. from simulation import Simulation, Galaxy
  40. T = .25 # Initial tempreature
  41. STEPS = 0
  42. DECAY = .998 # Exponential decay factor
  43. def simToBitmap(sim, theta, phi, view, scale, x, y, galaxy):
  44. &#34;&#34;&#34;Obtain a bitmap of one galaxy as viewed from a given direction.
  45. The binning has been chosen so that the scale and the offset (x,y)
  46. are expected to be approximately 1 and (0, 0) respectively.
  47. Parameters:
  48. sim (Simulation): Simulation to project.
  49. theta (float): polar angle of viewing direction.
  50. phi (float): azimuthal angle of viewing direction.
  51. view (float): rotation angle along viewing direction.
  52. scale (float): scaling factor.
  53. x (float): x offset
  54. y (float): y offset
  55. galaxy (int): galaxy to plot. Either 0, or 1. They are assumed
  56. to have the same number of particles.
  57. &#34;&#34;&#34;
  58. # Obtain components in new x&#39;,y&#39; plane
  59. r_vec = (sim.project(theta, phi, view) - [[x+.12,y+1.3]]) * scale
  60. # Select a single galaxy. We match them separately in the algorithm.
  61. if galaxy==0: r_vec = r_vec[2:len(r_vec)//2-1] #omit central masses
  62. if galaxy==1: r_vec = r_vec[len(r_vec)//2-1:]
  63. # Use a fast histogram, much faster than numpy ()
  64. H = histogram2d(r_vec[:,0], r_vec[:,1],
  65. range=[[-5,5], [-5,5]], bins=(50, 50))
  66. im = np.zeros((50, 50))
  67. H = convolve2d(H, np.ones((2,2)), mode=&#39;same&#39;) # Smooth the image
  68. im[H&gt;=1] = 1 # Binary the image
  69. return im
  70. @jit(nopython=True) # Numba annotation
  71. def bitmapScoreAlgo(im1, im2):
  72. &#34;&#34;&#34;Computes the f1 score betwen two binarized images
  73. Parameters
  74. im1 (arr): nxm ground truth image
  75. im2 (arr): nxm candidate image
  76. Returns
  77. f1 score
  78. &#34;&#34;&#34;
  79. TP = np.sum((im1==1.0) &amp; (im2==1.0))
  80. TN = np.sum((im1==0.0) &amp; (im2==0.0))
  81. FP = np.sum((im1==0.0) &amp; (im2==1.0))
  82. FN = np.sum((im1==1.0) &amp; (im2==0.0))
  83. if TP==0: return 0
  84. precision = TP/(TP+FP)
  85. recall = TP/(TP+FN)
  86. return 2*precision*recall / (precision + recall)
  87. # The matching algorithm attempts to improve the match by shifting the
  88. # image by one pixel in each direction. If none improve the score the
  89. # f1-score of said local maximum is retuned. To make this highly efficient
  90. # (as this is run millions of time) we explicitely write functions to
  91. # shift an image by 1 pixel in each direction, as these can then be compiled
  92. # using Numba (jit annotation) to low-level code.
  93. # Performance is crucial here and must sadly be prioritized over conciseness
  94. @jit(nopython=True)
  95. def shiftBottom(im, im2):
  96. &#34;&#34;&#34;Shifts an image by one pixel downwards.
  97. Parameters:
  98. im (arr): the nxm image to shift by one pixel.
  99. im2 (arr): an nxm array where the new image will be placed.
  100. Returns:
  101. A reference to im2
  102. &#34;&#34;&#34;
  103. im2[1:] = im[:-1]
  104. im2[0] = 0
  105. return im2
  106. @jit(nopython=True)
  107. def shiftTop(im, im2):
  108. &#34;&#34;&#34;Shifts an image by one pixel upwards.&#34;&#34;&#34;
  109. im2[:-1] = im[1:]
  110. im2[-1] = 0
  111. return im2
  112. @jit(nopython=True)
  113. def shiftLeft(im, im2):
  114. &#34;&#34;&#34;Shifts an image by one pixel to the left.&#34;&#34;&#34;
  115. im2[:,1:] = im[:,:-1]
  116. im2[:,0] = 0
  117. return im2
  118. @jit(nopython=True)
  119. def shiftRight(im, im2):
  120. &#34;&#34;&#34;Shifts an image by one pixel to the right.&#34;&#34;&#34;
  121. im2[:,:-1] = im[:,1:]
  122. im2[:,-1] = 0
  123. return im2
  124. @jit
  125. def bitmapScore(im1, im2, _prev=None, _bestscore=None, _zeros=None):
  126. &#34;&#34;&#34;Computes the bitmap score between two images. This is the f1-score
  127. but we allow the algorithm to attempt to improve the score by
  128. shifting the images. The algorithm is implemented recursively.
  129. Parameters:
  130. im1 (array): The ground truth nxm image.
  131. im2 (array): The candidate nxm imgae.
  132. _prev: Used internally for recursion.
  133. _bestscore: Used internally for recursion.
  134. _zeros: Used internally for recursion.
  135. Returns:
  136. f1-score for the two images.
  137. &#34;&#34;&#34;
  138. # When the function is called externally, initialize an array of zeros
  139. # and compute the score for no shifting. The zeros array is used for
  140. # performance to only create a new array once.
  141. if _bestscore is None:
  142. _bestscore = bitmapScoreAlgo(im1, im2)
  143. _zeros = np.zeros_like(im2)
  144. # Attempt to improve the score by shifting the image in a direction
  145. # Keeping track of _prev allows to not &#39;go back&#39; and undo and attempt
  146. # to undo a shift needlessly.
  147. if _prev is not 0: # try left
  148. shifted = shiftLeft(im2, _zeros)
  149. score = bitmapScoreAlgo(im1, shifted)
  150. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  151. _prev=1, _bestscore=score, _zeros=_zeros)
  152. if _prev is not 1: # try right
  153. shifted = shiftRight(im2, _zeros)
  154. score = bitmapScoreAlgo(im1, shifted)
  155. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  156. _prev=0, _bestscore=score, _zeros=_zeros)
  157. if _prev is not 2: # try top
  158. shifted = shiftTop(im2, _zeros)
  159. score = bitmapScoreAlgo(im1, shifted)
  160. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  161. _prev=3, _bestscore=score, _zeros=_zeros)
  162. if _prev is not 3: # try bottom
  163. shifted = shiftBottom(im2, _zeros)
  164. score = bitmapScoreAlgo(im1, shifted)
  165. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  166. _prev=2, _bestscore=score, _zeros=_zeros)
  167. # Return _bestscore if shifting did not improve (local maximum).
  168. return _bestscore
  169. def attemptSimulation(theta1, phi1, theta2, phi2, rmin=1, e=.5, R=2,
  170. disk1=.75, disk2=.65, mu=1, plot=False, steps=2000):
  171. &#34;&#34;&#34;Runs a simulation with the given parameters and compares it
  172. to observations of the antennae to return a score out of 2.
  173. Parameters:
  174. theta1 (float): polar angle for the spin of the first galaxy.
  175. phi1 (float): azimuthal angle for the spin of the first galaxy.
  176. theta2 (float): polar angle for the spin of the second galaxy.
  177. phi2 (float): azimuthal angle for the spin of the second galaxy.
  178. rmin (float): closest distance of approach of orbit.
  179. e (float): eccentricity of the orbit.
  180. R (float): initial separation
  181. disk1 (float): radius of the uniform disk of the first galaxy
  182. disk2 (float): radius of the uniform disk of the first galaxy
  183. mu (float): ratio of masses of the two galaxies
  184. plot (float): if true the simulation will be saved to data/annealing/
  185. steps (float): number of times the f1 score will be computed, along
  186. random viewing directions per 100 timesteps.
  187. Returns:
  188. f1-score: score obtained by the simulation.
  189. &#34;&#34;&#34;
  190. # Initialize the simulation
  191. sim = Simulation(dt=1E-3, soft=0.1, verbose=False, CONFIG=None, method=&#39;bruteForceNumbaOptimized&#39;)
  192. galaxy1 = Galaxy(orientation = (theta1, phi1), centralMass=2/(1+mu),
  193. sim=sim, bulge={&#39;model&#39;:&#39;plummer&#39;, &#39;particles&#39;:0, &#39;totalMass&#39;:0, &#39;l&#39;:0},
  194. disk={&#39;model&#39;:&#39;uniform&#39;, &#39;particles&#39;:2000, &#39;l&#39;:disk1, &#39;totalMass&#39;:0},
  195. halo={&#39;model&#39;:&#39;NFW&#39;, &#39;particles&#39;:0, &#39;rs&#39;:1, &#39;totalMass&#39;:0})
  196. galaxy2 = Galaxy(orientation = (theta2, phi2), centralMass=2*mu/(1+mu),
  197. sim=sim, bulge={&#39;model&#39;:&#39;plummer&#39;, &#39;particles&#39;:0, &#39;totalMass&#39;:0, &#39;l&#39;:0},
  198. disk={&#39;model&#39;:&#39;uniform&#39;, &#39;particles&#39;:2000, &#39;l&#39;:disk2, &#39;totalMass&#39;:0},
  199. halo={&#39;model&#39;:&#39;NFW&#39;, &#39;particles&#39;:0, &#39;rs&#39;:1, &#39;totalMass&#39;:0})
  200. sim.setOrbit(galaxy1, galaxy2, e=e, R0=R, rmin=rmin) # define the orbit
  201. # Run the simulation manually
  202. # Initialize the parameters consistently with the velocities
  203. sim.rprev_vec = sim.r_vec - sim.v_vec * sim.dt
  204. # Keep track of the best score
  205. bestScore = 0
  206. # and its corresponding binarized image and parameters
  207. bestImage, bestParams = 0, 0
  208. hasReachedPericenter = False
  209. # Run until tmax = 25
  210. for i in range(25001):
  211. sim.step()
  212. if i%100==0: # Every $\Delta t = 0.1$
  213. # Check if we are close to pericenter
  214. centers = sim.r_vec[sim.type[:,0] == &#39;center&#39;]
  215. if np.linalg.norm(centers[0] - centers[1]) &lt; 1.3*rmin:
  216. hasReachedPericenter = True
  217. # Do not evaluate the f1-score until pericenter.
  218. if not hasReachedPericenter: continue
  219. # Check multiple (steps) viewing directions at random
  220. localBestScore = 0
  221. localBestImage, localBestParams = 0, 0
  222. for j in range(steps):
  223. # The viewing directions are isotropically distributed
  224. theta = np.arccos(np.random.uniform(-1, 1))
  225. phi = np.random.uniform(0, 2*np.pi)
  226. # Rotation along line of sight
  227. view = np.random.uniform(0, 2*np.pi)
  228. scale = np.random.uniform(0.6, 1.0)
  229. x = np.random.uniform(-1.0, 1.0) # Offsets
  230. y = np.random.uniform(-1.0, 1.0)
  231. # Get images for each galaxy and compute their score separately
  232. im1 = simToBitmap(sim, theta, phi, view, scale, x, y, galaxy=0)
  233. im2 = simToBitmap(sim, theta, phi, view, scale, x, y, galaxy=1)
  234. score = bitmapScore(GT1, im1) + bitmapScore(GT2, im2)
  235. if score &gt; localBestScore:
  236. localBestScore = score
  237. localBestImage = [im1,im2]
  238. localBestParams = [i, theta, phi, view, scale, x, y]
  239. if bestScore &lt; localBestScore:
  240. bestScore = localBestScore
  241. bestImage = localBestImage
  242. bestParams = localBestParams
  243. if plot:
  244. sim.save(&#39;annealing&#39;, type=&#39;2D&#39;)
  245. print(&#39;Best score for this attempt&#39;, bestScore)
  246. print(&#39;using viewing parameters&#39;, bestParams)
  247. return bestScore
  248. ################################################################################
  249. ################################################################################
  250. # Generate a (50, 50) bitmap for each galaxy
  251. # They are stored globally in GT1 and GT2 (Ground Truth)
  252. im = imread(&#39;literature/figs/g1c.tif&#39;)
  253. im = np.mean(im, axis=-1)
  254. im = scipy.misc.imresize(im, (50,50))
  255. GT1 = np.zeros((50, 50))
  256. GT1[im &gt; 50] = 1
  257. im = imread(&#39;literature/figs/g2c.tif&#39;)
  258. im = np.mean(im, axis=-1)
  259. im = scipy.misc.imresize(im, (50,50))
  260. GT2 = np.zeros((50, 50))
  261. # Define the limits and relate scale of the variations in each parameter
  262. # In the same order as attemptSimulation
  263. # phi1, theta1, phi2, theta2, rmin (fixed), e, R (fixed), disk1, disk2
  264. LIMITS = np.array([[np.pi, 2 * np.pi], [-np.pi, np.pi],
  265. [0, np.pi], [-np.pi, np.pi],
  266. [1,1], [.5,1.0], [2.2,2.2], [.5,.8], [.5,.8]])
  267. VARIATIONS = np.array([.08, .15, .08, .15, 0, .01, 0, .01, .01])
  268. # Choose a random starting point and evaluate it
  269. bestparams = [np.random.uniform(*l) for l in LIMITS]
  270. log = []
  271. bestscore = attemptSimulation(*bestparams, steps=500)
  272. print(&#39;Starting with score&#39;, bestscore, &#39;with parameters&#39;, bestparams)
  273. log.append([bestscore, bestparams, True])
  274. for i in range(STEPS):
  275. T = T * DECAY #exponential decay
  276. # Perturb the parameters
  277. params = bestparams + np.random.normal(scale=VARIATIONS) * 2 * T / 0.04
  278. # Allow the angles from $-\pi$ to $\pi$ to wrap around
  279. for j in [1,3]:
  280. params[j] = np.mod(params[j] - LIMITS[j][0], LIMITS[j][1] - LIMITS[j][0])
  281. params[j] += LIMITS[j][0]
  282. # Clip parameters outside their allowed range
  283. params = np.clip(params, LIMITS[:, 0], LIMITS[:, 1])
  284. # Evaluate the score for this attempt, use more steps as i progresses
  285. # so as to reduce the noise in the evaluation of the score
  286. score = attemptSimulation(*params, steps=500 + i)
  287. # Perform simulated annealing with a typical exponential rule
  288. if score &gt; bestscore or np.exp(-(bestscore-score)/T) &gt; np.random.rand():
  289. # Flip to this new point
  290. print(&#39;NEW BEST ____&#39;, i, T, score, params)
  291. bestscore = score
  292. bestparams = params
  293. log.append([score, params, True])
  294. else: # Remain in the old point
  295. log.append([score, params, False])
  296. # Save the progress for future plotting
  297. pickle.dump(log, open(&#39;data/logs/logb.pickle&#39;, &#34;wb&#34; ) ) </code></pre>
  298. </details>
  299. </section>
  300. <section>
  301. </section>
  302. <section>
  303. </section>
  304. <section>
  305. <h2 class="section-title" id="header-functions">Functions</h2>
  306. <dl>
  307. <dt id="run_simulated_annealing.attemptSimulation"><code class="name flex">
  308. <span>def <span class="ident">attemptSimulation</span></span>(<span>theta1, phi1, theta2, phi2, rmin=1, e=0.5, R=2, disk1=0.75, disk2=0.65, mu=1, plot=False, steps=2000)</span>
  309. </code></dt>
  310. <dd>
  311. <section class="desc"><p>Runs a simulation with the given parameters and compares it
  312. to observations of the antennae to return a score out of 2.</p>
  313. <h2 id="parameters">Parameters</h2>
  314. <dl>
  315. <dt><strong><code>theta1</code></strong> :&ensp;<code>float</code></dt>
  316. <dd>polar angle for the spin of the first galaxy.</dd>
  317. <dt><strong><code>phi1</code></strong> :&ensp;<code>float</code></dt>
  318. <dd>azimuthal angle for the spin of the first galaxy.</dd>
  319. <dt><strong><code>theta2</code></strong> :&ensp;<code>float</code></dt>
  320. <dd>polar angle for the spin of the second galaxy.</dd>
  321. <dt><strong><code>phi2</code></strong> :&ensp;<code>float</code></dt>
  322. <dd>azimuthal angle for the spin of the second galaxy.</dd>
  323. <dt><strong><code>rmin</code></strong> :&ensp;<code>float</code></dt>
  324. <dd>closest distance of approach of orbit.</dd>
  325. <dt><strong><code>e</code></strong> :&ensp;<code>float</code></dt>
  326. <dd>eccentricity of the orbit.</dd>
  327. <dt><strong><code>R</code></strong> :&ensp;<code>float</code></dt>
  328. <dd>initial separation</dd>
  329. <dt><strong><code>disk1</code></strong> :&ensp;<code>float</code></dt>
  330. <dd>radius of the uniform disk of the first galaxy</dd>
  331. <dt><strong><code>disk2</code></strong> :&ensp;<code>float</code></dt>
  332. <dd>radius of the uniform disk of the first galaxy</dd>
  333. <dt><strong><code>mu</code></strong> :&ensp;<code>float</code></dt>
  334. <dd>ratio of masses of the two galaxies</dd>
  335. <dt><strong><code>plot</code></strong> :&ensp;<code>float</code></dt>
  336. <dd>if true the simulation will be saved to data/annealing/</dd>
  337. <dt><strong><code>steps</code></strong> :&ensp;<code>float</code></dt>
  338. <dd>number of times the f1 score will be computed, along
  339. random viewing directions per 100 timesteps.</dd>
  340. </dl>
  341. <h2 id="returns">Returns</h2>
  342. <p>f1-score: score obtained by the simulation.</p></section>
  343. <details class="source">
  344. <summary>Source code</summary>
  345. <pre><code class="python">def attemptSimulation(theta1, phi1, theta2, phi2, rmin=1, e=.5, R=2,
  346. disk1=.75, disk2=.65, mu=1, plot=False, steps=2000):
  347. &#34;&#34;&#34;Runs a simulation with the given parameters and compares it
  348. to observations of the antennae to return a score out of 2.
  349. Parameters:
  350. theta1 (float): polar angle for the spin of the first galaxy.
  351. phi1 (float): azimuthal angle for the spin of the first galaxy.
  352. theta2 (float): polar angle for the spin of the second galaxy.
  353. phi2 (float): azimuthal angle for the spin of the second galaxy.
  354. rmin (float): closest distance of approach of orbit.
  355. e (float): eccentricity of the orbit.
  356. R (float): initial separation
  357. disk1 (float): radius of the uniform disk of the first galaxy
  358. disk2 (float): radius of the uniform disk of the first galaxy
  359. mu (float): ratio of masses of the two galaxies
  360. plot (float): if true the simulation will be saved to data/annealing/
  361. steps (float): number of times the f1 score will be computed, along
  362. random viewing directions per 100 timesteps.
  363. Returns:
  364. f1-score: score obtained by the simulation.
  365. &#34;&#34;&#34;
  366. # Initialize the simulation
  367. sim = Simulation(dt=1E-3, soft=0.1, verbose=False, CONFIG=None, method=&#39;bruteForceNumbaOptimized&#39;)
  368. galaxy1 = Galaxy(orientation = (theta1, phi1), centralMass=2/(1+mu),
  369. sim=sim, bulge={&#39;model&#39;:&#39;plummer&#39;, &#39;particles&#39;:0, &#39;totalMass&#39;:0, &#39;l&#39;:0},
  370. disk={&#39;model&#39;:&#39;uniform&#39;, &#39;particles&#39;:2000, &#39;l&#39;:disk1, &#39;totalMass&#39;:0},
  371. halo={&#39;model&#39;:&#39;NFW&#39;, &#39;particles&#39;:0, &#39;rs&#39;:1, &#39;totalMass&#39;:0})
  372. galaxy2 = Galaxy(orientation = (theta2, phi2), centralMass=2*mu/(1+mu),
  373. sim=sim, bulge={&#39;model&#39;:&#39;plummer&#39;, &#39;particles&#39;:0, &#39;totalMass&#39;:0, &#39;l&#39;:0},
  374. disk={&#39;model&#39;:&#39;uniform&#39;, &#39;particles&#39;:2000, &#39;l&#39;:disk2, &#39;totalMass&#39;:0},
  375. halo={&#39;model&#39;:&#39;NFW&#39;, &#39;particles&#39;:0, &#39;rs&#39;:1, &#39;totalMass&#39;:0})
  376. sim.setOrbit(galaxy1, galaxy2, e=e, R0=R, rmin=rmin) # define the orbit
  377. # Run the simulation manually
  378. # Initialize the parameters consistently with the velocities
  379. sim.rprev_vec = sim.r_vec - sim.v_vec * sim.dt
  380. # Keep track of the best score
  381. bestScore = 0
  382. # and its corresponding binarized image and parameters
  383. bestImage, bestParams = 0, 0
  384. hasReachedPericenter = False
  385. # Run until tmax = 25
  386. for i in range(25001):
  387. sim.step()
  388. if i%100==0: # Every $\Delta t = 0.1$
  389. # Check if we are close to pericenter
  390. centers = sim.r_vec[sim.type[:,0] == &#39;center&#39;]
  391. if np.linalg.norm(centers[0] - centers[1]) &lt; 1.3*rmin:
  392. hasReachedPericenter = True
  393. # Do not evaluate the f1-score until pericenter.
  394. if not hasReachedPericenter: continue
  395. # Check multiple (steps) viewing directions at random
  396. localBestScore = 0
  397. localBestImage, localBestParams = 0, 0
  398. for j in range(steps):
  399. # The viewing directions are isotropically distributed
  400. theta = np.arccos(np.random.uniform(-1, 1))
  401. phi = np.random.uniform(0, 2*np.pi)
  402. # Rotation along line of sight
  403. view = np.random.uniform(0, 2*np.pi)
  404. scale = np.random.uniform(0.6, 1.0)
  405. x = np.random.uniform(-1.0, 1.0) # Offsets
  406. y = np.random.uniform(-1.0, 1.0)
  407. # Get images for each galaxy and compute their score separately
  408. im1 = simToBitmap(sim, theta, phi, view, scale, x, y, galaxy=0)
  409. im2 = simToBitmap(sim, theta, phi, view, scale, x, y, galaxy=1)
  410. score = bitmapScore(GT1, im1) + bitmapScore(GT2, im2)
  411. if score &gt; localBestScore:
  412. localBestScore = score
  413. localBestImage = [im1,im2]
  414. localBestParams = [i, theta, phi, view, scale, x, y]
  415. if bestScore &lt; localBestScore:
  416. bestScore = localBestScore
  417. bestImage = localBestImage
  418. bestParams = localBestParams
  419. if plot:
  420. sim.save(&#39;annealing&#39;, type=&#39;2D&#39;)
  421. print(&#39;Best score for this attempt&#39;, bestScore)
  422. print(&#39;using viewing parameters&#39;, bestParams)
  423. return bestScore</code></pre>
  424. </details>
  425. </dd>
  426. <dt id="run_simulated_annealing.bitmapScore"><code class="name flex">
  427. <span>def <span class="ident">bitmapScore</span></span>(<span>im1, im2)</span>
  428. </code></dt>
  429. <dd>
  430. <section class="desc"><p>Computes the bitmap score between two images. This is the f1-score
  431. but we allow the algorithm to attempt to improve the score by
  432. shifting the images. The algorithm is implemented recursively.</p>
  433. <h2 id="parameters">Parameters</h2>
  434. <dl>
  435. <dt><strong><code>im1</code></strong> :&ensp;<code>array</code></dt>
  436. <dd>The ground truth nxm image.</dd>
  437. <dt><strong><code>im2</code></strong> :&ensp;<code>array</code></dt>
  438. <dd>The candidate nxm imgae.</dd>
  439. <dt><strong><code>_prev</code></strong></dt>
  440. <dd>Used internally for recursion.</dd>
  441. <dt><strong><code>_bestscore</code></strong></dt>
  442. <dd>Used internally for recursion.</dd>
  443. <dt><strong><code>_zeros</code></strong></dt>
  444. <dd>Used internally for recursion.</dd>
  445. </dl>
  446. <h2 id="returns">Returns</h2>
  447. <p>f1-score for the two images.</p></section>
  448. <details class="source">
  449. <summary>Source code</summary>
  450. <pre><code class="python">@jit
  451. def bitmapScore(im1, im2, _prev=None, _bestscore=None, _zeros=None):
  452. &#34;&#34;&#34;Computes the bitmap score between two images. This is the f1-score
  453. but we allow the algorithm to attempt to improve the score by
  454. shifting the images. The algorithm is implemented recursively.
  455. Parameters:
  456. im1 (array): The ground truth nxm image.
  457. im2 (array): The candidate nxm imgae.
  458. _prev: Used internally for recursion.
  459. _bestscore: Used internally for recursion.
  460. _zeros: Used internally for recursion.
  461. Returns:
  462. f1-score for the two images.
  463. &#34;&#34;&#34;
  464. # When the function is called externally, initialize an array of zeros
  465. # and compute the score for no shifting. The zeros array is used for
  466. # performance to only create a new array once.
  467. if _bestscore is None:
  468. _bestscore = bitmapScoreAlgo(im1, im2)
  469. _zeros = np.zeros_like(im2)
  470. # Attempt to improve the score by shifting the image in a direction
  471. # Keeping track of _prev allows to not &#39;go back&#39; and undo and attempt
  472. # to undo a shift needlessly.
  473. if _prev is not 0: # try left
  474. shifted = shiftLeft(im2, _zeros)
  475. score = bitmapScoreAlgo(im1, shifted)
  476. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  477. _prev=1, _bestscore=score, _zeros=_zeros)
  478. if _prev is not 1: # try right
  479. shifted = shiftRight(im2, _zeros)
  480. score = bitmapScoreAlgo(im1, shifted)
  481. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  482. _prev=0, _bestscore=score, _zeros=_zeros)
  483. if _prev is not 2: # try top
  484. shifted = shiftTop(im2, _zeros)
  485. score = bitmapScoreAlgo(im1, shifted)
  486. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  487. _prev=3, _bestscore=score, _zeros=_zeros)
  488. if _prev is not 3: # try bottom
  489. shifted = shiftBottom(im2, _zeros)
  490. score = bitmapScoreAlgo(im1, shifted)
  491. if score &gt; _bestscore: return bitmapScore(im1, shifted,
  492. _prev=2, _bestscore=score, _zeros=_zeros)
  493. # Return _bestscore if shifting did not improve (local maximum).
  494. return _bestscore</code></pre>
  495. </details>
  496. </dd>
  497. <dt id="run_simulated_annealing.bitmapScoreAlgo"><code class="name flex">
  498. <span>def <span class="ident">bitmapScoreAlgo</span></span>(<span>im1, im2)</span>
  499. </code></dt>
  500. <dd>
  501. <section class="desc"><p>Computes the f1 score betwen two binarized images</p>
  502. <dl>
  503. <dt><strong><code>Parameters</code></strong></dt>
  504. <dd>im1 (arr): nxm ground truth image
  505. im2 (arr): nxm candidate image</dd>
  506. <dt><strong><code>Returns</code></strong></dt>
  507. <dd>f1 score</dd>
  508. </dl></section>
  509. <details class="source">
  510. <summary>Source code</summary>
  511. <pre><code class="python">@jit(nopython=True) # Numba annotation
  512. def bitmapScoreAlgo(im1, im2):
  513. &#34;&#34;&#34;Computes the f1 score betwen two binarized images
  514. Parameters
  515. im1 (arr): nxm ground truth image
  516. im2 (arr): nxm candidate image
  517. Returns
  518. f1 score
  519. &#34;&#34;&#34;
  520. TP = np.sum((im1==1.0) &amp; (im2==1.0))
  521. TN = np.sum((im1==0.0) &amp; (im2==0.0))
  522. FP = np.sum((im1==0.0) &amp; (im2==1.0))
  523. FN = np.sum((im1==1.0) &amp; (im2==0.0))
  524. if TP==0: return 0
  525. precision = TP/(TP+FP)
  526. recall = TP/(TP+FN)
  527. return 2*precision*recall / (precision + recall)</code></pre>
  528. </details>
  529. </dd>
  530. <dt id="run_simulated_annealing.shiftBottom"><code class="name flex">
  531. <span>def <span class="ident">shiftBottom</span></span>(<span>im, im2)</span>
  532. </code></dt>
  533. <dd>
  534. <section class="desc"><p>Shifts an image by one pixel downwards.</p>
  535. <h2 id="parameters">Parameters</h2>
  536. <dl>
  537. <dt><strong><code>im</code></strong> :&ensp;<code>arr</code></dt>
  538. <dd>the nxm image to shift by one pixel.</dd>
  539. <dt><strong><code>im2</code></strong> :&ensp;<code>arr</code></dt>
  540. <dd>an nxm array where the new image will be placed.</dd>
  541. </dl>
  542. <h2 id="returns">Returns</h2>
  543. <p>A reference to im2</p></section>
  544. <details class="source">
  545. <summary>Source code</summary>
  546. <pre><code class="python">@jit(nopython=True)
  547. def shiftBottom(im, im2):
  548. &#34;&#34;&#34;Shifts an image by one pixel downwards.
  549. Parameters:
  550. im (arr): the nxm image to shift by one pixel.
  551. im2 (arr): an nxm array where the new image will be placed.
  552. Returns:
  553. A reference to im2
  554. &#34;&#34;&#34;
  555. im2[1:] = im[:-1]
  556. im2[0] = 0
  557. return im2</code></pre>
  558. </details>
  559. </dd>
  560. <dt id="run_simulated_annealing.shiftLeft"><code class="name flex">
  561. <span>def <span class="ident">shiftLeft</span></span>(<span>im, im2)</span>
  562. </code></dt>
  563. <dd>
  564. <section class="desc"><p>Shifts an image by one pixel to the left.</p></section>
  565. <details class="source">
  566. <summary>Source code</summary>
  567. <pre><code class="python">@jit(nopython=True)
  568. def shiftLeft(im, im2):
  569. &#34;&#34;&#34;Shifts an image by one pixel to the left.&#34;&#34;&#34;
  570. im2[:,1:] = im[:,:-1]
  571. im2[:,0] = 0
  572. return im2</code></pre>
  573. </details>
  574. </dd>
  575. <dt id="run_simulated_annealing.shiftRight"><code class="name flex">
  576. <span>def <span class="ident">shiftRight</span></span>(<span>im, im2)</span>
  577. </code></dt>
  578. <dd>
  579. <section class="desc"><p>Shifts an image by one pixel to the right.</p></section>
  580. <details class="source">
  581. <summary>Source code</summary>
  582. <pre><code class="python">@jit(nopython=True)
  583. def shiftRight(im, im2):
  584. &#34;&#34;&#34;Shifts an image by one pixel to the right.&#34;&#34;&#34;
  585. im2[:,:-1] = im[:,1:]
  586. im2[:,-1] = 0
  587. return im2</code></pre>
  588. </details>
  589. </dd>
  590. <dt id="run_simulated_annealing.shiftTop"><code class="name flex">
  591. <span>def <span class="ident">shiftTop</span></span>(<span>im, im2)</span>
  592. </code></dt>
  593. <dd>
  594. <section class="desc"><p>Shifts an image by one pixel upwards.</p></section>
  595. <details class="source">
  596. <summary>Source code</summary>
  597. <pre><code class="python">@jit(nopython=True)
  598. def shiftTop(im, im2):
  599. &#34;&#34;&#34;Shifts an image by one pixel upwards.&#34;&#34;&#34;
  600. im2[:-1] = im[1:]
  601. im2[-1] = 0
  602. return im2</code></pre>
  603. </details>
  604. </dd>
  605. <dt id="run_simulated_annealing.simToBitmap"><code class="name flex">
  606. <span>def <span class="ident">simToBitmap</span></span>(<span>sim, theta, phi, view, scale, x, y, galaxy)</span>
  607. </code></dt>
  608. <dd>
  609. <section class="desc"><p>Obtain a bitmap of one galaxy as viewed from a given direction.
  610. The binning has been chosen so that the scale and the offset (x,y)
  611. are expected to be approximately 1 and (0, 0) respectively.</p>
  612. <p>Parameters:
  613. sim (Simulation): Simulation to project.
  614. theta (float): polar angle of viewing direction.
  615. phi (float): azimuthal angle of viewing direction.
  616. view (float): rotation angle along viewing direction.
  617. scale (float): scaling factor.
  618. x (float): x offset
  619. y (float): y offset
  620. galaxy (int): galaxy to plot. Either 0, or 1. They are assumed
  621. <br>
  622. to have the same number of particles.</p></section>
  623. <details class="source">
  624. <summary>Source code</summary>
  625. <pre><code class="python">def simToBitmap(sim, theta, phi, view, scale, x, y, galaxy):
  626. &#34;&#34;&#34;Obtain a bitmap of one galaxy as viewed from a given direction.
  627. The binning has been chosen so that the scale and the offset (x,y)
  628. are expected to be approximately 1 and (0, 0) respectively.
  629. Parameters:
  630. sim (Simulation): Simulation to project.
  631. theta (float): polar angle of viewing direction.
  632. phi (float): azimuthal angle of viewing direction.
  633. view (float): rotation angle along viewing direction.
  634. scale (float): scaling factor.
  635. x (float): x offset
  636. y (float): y offset
  637. galaxy (int): galaxy to plot. Either 0, or 1. They are assumed
  638. to have the same number of particles.
  639. &#34;&#34;&#34;
  640. # Obtain components in new x&#39;,y&#39; plane
  641. r_vec = (sim.project(theta, phi, view) - [[x+.12,y+1.3]]) * scale
  642. # Select a single galaxy. We match them separately in the algorithm.
  643. if galaxy==0: r_vec = r_vec[2:len(r_vec)//2-1] #omit central masses
  644. if galaxy==1: r_vec = r_vec[len(r_vec)//2-1:]
  645. # Use a fast histogram, much faster than numpy ()
  646. H = histogram2d(r_vec[:,0], r_vec[:,1],
  647. range=[[-5,5], [-5,5]], bins=(50, 50))
  648. im = np.zeros((50, 50))
  649. H = convolve2d(H, np.ones((2,2)), mode=&#39;same&#39;) # Smooth the image
  650. im[H&gt;=1] = 1 # Binary the image
  651. return im</code></pre>
  652. </details>
  653. </dd>
  654. </dl>
  655. </section>
  656. <section>
  657. </section>
  658. </article>
  659. <nav id="sidebar">
  660. <h1>Index</h1>
  661. <div class="toc">
  662. <ul></ul>
  663. </div>
  664. <ul id="index">
  665. <li><h3><a href="#header-functions">Functions</a></h3>
  666. <ul class="two-column">
  667. <li><code><a title="run_simulated_annealing.attemptSimulation" href="#run_simulated_annealing.attemptSimulation">attemptSimulation</a></code></li>
  668. <li><code><a title="run_simulated_annealing.bitmapScore" href="#run_simulated_annealing.bitmapScore">bitmapScore</a></code></li>
  669. <li><code><a title="run_simulated_annealing.bitmapScoreAlgo" href="#run_simulated_annealing.bitmapScoreAlgo">bitmapScoreAlgo</a></code></li>
  670. <li><code><a title="run_simulated_annealing.shiftBottom" href="#run_simulated_annealing.shiftBottom">shiftBottom</a></code></li>
  671. <li><code><a title="run_simulated_annealing.shiftLeft" href="#run_simulated_annealing.shiftLeft">shiftLeft</a></code></li>
  672. <li><code><a title="run_simulated_annealing.shiftRight" href="#run_simulated_annealing.shiftRight">shiftRight</a></code></li>
  673. <li><code><a title="run_simulated_annealing.shiftTop" href="#run_simulated_annealing.shiftTop">shiftTop</a></code></li>
  674. <li><code><a title="run_simulated_annealing.simToBitmap" href="#run_simulated_annealing.simToBitmap">simToBitmap</a></code></li>
  675. </ul>
  676. </li>
  677. </ul>
  678. </nav>
  679. </main>
  680. <footer id="footer">
  681. <p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.5.2</a>.</p>
  682. </footer>
  683. <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
  684. <script>hljs.initHighlightingOnLoad()</script>
  685. </body>
  686. </html>