Coverage for frank/tests.py: 99%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Frankenstein: 1D disc brightness profile reconstruction from Fourier data
2# using non-parametric Gaussian Processes
3#
4# Copyright (C) 2019-2020 R. Booth, J. Jennings, M. Tazzari
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <https://www.gnu.org/licenses/>
18#
19"""This module runs tests to confirm Frankenstein is working correctly."""
21import numpy as np
22import os
23import json
25from frank.constants import rad_to_arcsec
26from frank.hankel import DiscreteHankelTransform
27from frank.radial_fitters import FourierBesselFitter, FrankFitter
28from frank.debris_fitters import FrankDebrisFitter
29from frank.geometry import (
30 FixedGeometry, FitGeometryGaussian, FitGeometryFourierBessel
31)
32from frank import utilities
33from frank.io import load_uvtable, save_uvtable, load_sol, save_fit
34from frank.statistical_models import VisibilityMapping
35from frank import fit
37def test_hankel_gauss():
38 """Check the Hankel transform"""
40 def gauss_real_space(r):
41 x = r
42 return np.exp(-0.5 * x * x)
44 def gauss_vis_space(q):
45 qs = (2 * np.pi) * q
46 return np.exp(-0.5 * qs * qs) * (2 * np.pi)
48 DHT = DiscreteHankelTransform(5.0, 100)
50 Ir = gauss_real_space(DHT.r)
51 Iq = gauss_vis_space(DHT.q)
53 # Test at the DHT points.
54 # Use a large error estimate because the DHT is approximate
55 np.testing.assert_allclose(Iq, DHT.transform(Ir, direction='forward'),
56 atol=1e-5, rtol=0, err_msg="Forward DHT")
58 np.testing.assert_allclose(Ir, DHT.transform(Iq, direction='backward'),
59 atol=1e-5, rtol=0, err_msg="Inverse DHT")
61 # Test at generic points.
62 # Larger error needed
63 q = np.linspace(0.0, 1.0, 25)
64 np.testing.assert_allclose(gauss_vis_space(q),
65 DHT.transform(Ir, q=q, direction='forward'),
66 atol=1e-4, rtol=0, err_msg="Generic Forward DHT")
68 r = np.linspace(0, 5.0, 25)
69 np.testing.assert_allclose(gauss_real_space(r),
70 DHT.transform(Iq, q=r, direction='backward'),
71 atol=1e-4, rtol=0, err_msg="Generic Inverse DHT")
73 # Check the coefficients matrix works
74 Hf = DHT.coefficients(direction='forward')
75 Hb = DHT.coefficients(direction='backward')
77 np.testing.assert_allclose(np.dot(Hf, Ir),
78 DHT.transform(Ir, direction='forward'),
79 rtol=1e-7, err_msg="Forward DHT Coeffs")
80 np.testing.assert_allclose(np.dot(Hb, Iq),
81 DHT.transform(Iq, direction='backward'),
82 rtol=1e-7, err_msg="Inverse DHT Coeffs")
84 # Compare cached vs non-cached DHT points
85 np.testing.assert_allclose(DHT.coefficients(q=DHT.q),
86 DHT.coefficients(),
87 atol=1e-12, rtol=0,
88 err_msg="Cached forward DHT Coeffs"
89 )
90 np.testing.assert_allclose(DHT.coefficients(q=DHT.r, direction='backward'),
91 DHT.coefficients(direction='backward'),
92 atol=1e-12, rtol=0,
93 err_msg="Cached inverse DHT Coeffs"
94 )
97def test_vis_mapping():
98 def gauss_real_space(r):
99 x = r
100 return np.exp(-0.5 * x * x)
102 def gauss_vis_space(q):
103 qs = (2 * np.pi) * q / rad_to_arcsec
104 return np.exp(-0.5 * qs * qs) * (2 * np.pi / rad_to_arcsec**2)
106 DHT = DiscreteHankelTransform(5.0/rad_to_arcsec, 100)
107 geometry = FixedGeometry(60, 0, 0, 0)
109 VM = VisibilityMapping(DHT, geometry)
111 Ir = gauss_real_space(VM.r)
112 Iq = gauss_vis_space(VM.q)
114 # Test at the DHT points.
115 # Use a large error estimate because the DHT is approximate
116 np.testing.assert_allclose(Iq, 2*VM.predict_visibilities(Ir, VM.q),
117 atol=1e-5, rtol=0, err_msg="Forward DHT with VisibilityMapping")
119 np.testing.assert_allclose(Ir, 0.5*VM.invert_visibilities(Iq, VM.r),
120 atol=1e-5, rtol=0, err_msg="Inverse DHT with VisibilityMapping")
122 # Test generic_dht, which uses these functions
123 _, Iq_dht = utilities.generic_dht(VM.r, Ir, inc=60, Rmax=5, N=100)
124 _, Ir_dht = utilities.generic_dht(VM.q, Iq, inc=60, Rmax=5, N=100, direction='backward')
126 np.testing.assert_allclose(Iq, 2*Iq_dht,
127 atol=1e-5, rtol=0, err_msg="Forward DHT with generic_dht")
129 np.testing.assert_allclose(Ir, 0.5*Ir_dht,
130 atol=1e-5, rtol=0, err_msg="Inverse DHT with generic_dht")
133def test_import_data():
134 """Check the UVTable import function works for a .txt"""
135 load_uvtable('docs/tutorials/test_datafile.txt')
138def load_AS209(uv_cut=None):
139 """Load data for subsequent tests"""
140 uv_AS209_DSHARP = np.load('docs/tutorials/AS209_continuum.npz')
141 geometry = FixedGeometry(dRA=-1.9e-3, dDec=2.5e-3, inc=34.97,
142 PA=85.76)
144 if uv_cut is not None:
145 u, v = [uv_AS209_DSHARP[x] for x in ['u', 'v']]
147 q = np.hypot(*geometry.deproject(u,v))
149 keep = q < uv_cut
151 cut_data = {}
152 for key in uv_AS209_DSHARP:
153 if key not in { 'u', 'v', 'V', 'weights' }:
154 continue
155 cut_data[key] = uv_AS209_DSHARP[key][keep]
157 uv_AS209_DSHARP = cut_data
159 return uv_AS209_DSHARP, geometry
161def test_fit_geometry():
162 """Check the geometry fit on a subset of the AS209 data"""
163 AS209, _ = load_AS209()
164 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
166 inc_pa = [30.916257151011674, 85.46246241142246]
167 phase_centre = [-0.6431627790617276e-3, -1.161768824369382e-3]
169 geom = FitGeometryGaussian()
170 geom.fit(u, v, vis, weights)
172 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
173 1e3 * geom.dDec],
174 [30.916256948647096,
175 85.46246845532691,
176 -0.6434703241180601, -1.1623515516661052],
177 err_msg="Gaussian geometry fit")
179 geom = FitGeometryGaussian(inc_pa=inc_pa)
180 geom.fit(u, v, vis, weights)
182 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
183 1e3 * geom.dDec],
184 [30.916257151011674,
185 85.46246241142246,
186 -0.6432951224590862, -1.1619271783674576],
187 err_msg="Gaussian geometry fit (provided inc_pa)")
189 geom = FitGeometryGaussian(phase_centre=phase_centre)
190 geom.fit(u, v, vis, weights)
192 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
193 1e3 * geom.dDec],
194 [30.91601671340713,
195 85.471787339838,
196 -0.6431627790617276, -1.161768824369382],
197 err_msg="Gaussian geometry fit (provided phase_centre)")
199 geom = FitGeometryGaussian(guess=[1.0, 1.0, 0.1, 0.1])
200 geom.fit(u, v, vis, weights)
202 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
203 1e3 * geom.dDec],
204 [30.91625882521282, 85.46246494153092,
205 -0.6440453613101292, -1.1622414671266803],
206 err_msg="Gaussian geometry fit (provided guess)")
208 geom = FitGeometryFourierBessel(1.6, 20)
209 geom.fit(u, v, vis, weights)
211 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
212 1e3 * geom.dDec],
213 [33.81936473347169, 85.26142233735665,
214 0.5611211784189547, -1.170097994325657],
215 rtol=1e-5,
216 err_msg="FourierBessel geometry fit")
219 geom = FitGeometryFourierBessel(1.6, 20, inc_pa=inc_pa)
220 geom.fit(u, v, vis, weights)
222 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
223 1e3 * geom.dDec],
224 [30.916257151011674, 85.46246241142246,
225 1.4567005881700168, -1.658896248809076],
226 rtol=1e-5,
227 err_msg="FourierBessel geometry fit (provided inc_pa)")
230 geom = FitGeometryFourierBessel(1.6, 20, phase_centre=phase_centre)
231 geom.fit(u, v, vis, weights)
233 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
234 1e3 * geom.dDec],
235 [33.83672738960381, 85.2562498368987,
236 -0.6431627790617276, -1.161768824369382],
237 rtol=1e-5,
238 err_msg="FourierBessel geometry fit (provided phase_centre)")
241def test_fourier_bessel_fitter():
242 """Check FourierBesselFitter fitting routine with AS 209 dataset"""
243 AS209, geometry = load_AS209()
245 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
247 Rmax = 1.6
249 FB = FourierBesselFitter(Rmax, 20, geometry=geometry)
251 sol = FB.fit(u, v, vis, weights)
252 expected = np.array([1.89446696e+10, 1.81772972e+10, 1.39622125e+10,
253 1.20709653e+10,
254 9.83716859e+09, 3.26308106e+09, 2.02453146e+08,
255 4.73919867e+09,
256 1.67911877e+09, 1.73161931e+08, 4.50233539e+08,
257 3.57108466e+08,
258 4.04216831e+09, 1.89085113e+09, 6.73819228e+08,
259 5.50895976e+08,
260 1.53683576e+08, 1.02413038e+08, 2.32589333e+07,
261 3.33260713e+07
262 ])
264 np.testing.assert_allclose(sol.I, expected,
265 err_msg="Testing FourierBessel Fit to AS 209")
268def test_frank_fitter():
269 """Check FrankFitter fitting routine with AS 209 dataset"""
270 AS209, geometry = load_AS209(uv_cut=1e6)
272 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
274 Rmax = 1.6
276 FF = FrankFitter(Rmax, 20, geometry, alpha=1.05, weights_smooth=1e-2)
278 sol = FF.fit(u, v, vis, weights)
279 expected = np.array([
280 2.007570578977623749e+10, 1.843513239499150085e+10,
281 1.349013094584499741e+10, 1.272363099855433273e+10,
282 1.034881472041586494e+10, 2.579145371666701317e+09,
283 6.973651829234187603e+08, 4.127687040627769947e+09,
284 2.502124003048851490e+09, -2.756950827897560596e+08,
285 2.823720459944381118e+08, 8.705940396227477789e+08,
286 3.257425109322027683e+09, 3.112003905406182289e+09,
287 -5.145431577819123268e+08, 1.491165153547247887e+09,
288 -5.190942564982021451e+08, 5.100334030941848755e+08,
289 -1.922568182176418006e+08, 8.067782715878820419e+07,
290 ])
292 np.testing.assert_allclose(sol.I, expected,
293 err_msg="Testing Frank Fit to AS 209")
296def test_two_stage_fit():
297 """Check FrankFitter fitting routine with AS 209 dataset"""
298 AS209, geometry = load_AS209(uv_cut=1e6)
300 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
302 Rmax = 1.6
304 FF = FrankFitter(Rmax, 20, geometry, alpha=1.05, weights_smooth=1e-2)
306 # 1 step fit
307 sol = FF.fit(u, v, vis, weights)
309 # 2 step fit
310 preproc = FF.preprocess_visibilities(u, v, vis, weights)
311 sol2 = FF.fit_preprocessed(preproc)
313 np.testing.assert_equal(sol.I, sol2.I,
314 err_msg="Testing two-step fit")
317def test_solve_non_negative():
318 """Check FrankFitter fitting routine with non-negative fit using AS 209 dataset"""
319 AS209, geometry = load_AS209(uv_cut=1e6)
321 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
323 Rmax = 1.6
325 FF = FrankFitter(Rmax, 20, geometry, alpha=1.05, weights_smooth=1e-2)
327 sol = FF.fit(u, v, vis, weights)
328 I_nn = sol.solve_non_negative()
330 expected = np.array([
331 2.42756717e+10, 1.28541672e+10, 1.90032938e+10, 8.31444339e+09,
332 1.30814112e+10, 1.59442160e+09, 2.93990783e+08, 5.29739902e+09,
333 1.24011568e+09, 5.40689479e+08, 1.97475180e+08, 2.12294162e+08,
334 4.45700329e+09, 1.67658919e+09, 8.61662448e+08, 3.81032165e+08,
335 2.41202443e+08, 7.68452028e+07, 0.00000000e+00, 2.86208170e+07
336 ])
338 np.testing.assert_allclose(I_nn, expected,
339 err_msg="Testing Frank Fit to AS 209")
342def test_frank_fitter_log_normal():
343 """Check FrankFitter fitting routine with AS 209 dataset"""
344 AS209, geometry = load_AS209(uv_cut=1e6)
346 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
348 Rmax = 1.6
350 FF = FrankFitter(Rmax, 20, geometry, alpha=1.3, weights_smooth=1e-2,
351 method='LogNormal')
353 sol = FF.fit(u, v, vis, weights)
354 expected = np.array([
355 2.36087004e+10, 1.36923798e+10, 1.82805612e+10, 8.72975548e+09,
356 1.30516037e+10, 1.28158462e+09, 8.14949172e+08, 4.74472433e+09,
357 1.66592277e+09, 3.39438704e+08, 1.56219080e+08, 4.42345087e+08,
358 4.13155298e+09, 2.00246824e+09, 6.07773834e+08, 5.34020982e+08,
359 1.80820913e+08, 7.71858927e+07, 2.89354816e+07, 2.45967370e+06,])
361 np.testing.assert_allclose(sol.MAP, expected, rtol=7e-5,
362 err_msg="Testing Frank Log-Normal Fit to AS 209")
365def test_geom_deproject():
366 """Check predict works properly with a different geometry"""
367 AS209, geometry = load_AS209(uv_cut=1e6)
369 u, v, vis, weights = [AS209[k] for k in ['u', 'v', 'V', 'weights']]
371 Rmax = 1.6
373 FF = FrankFitter(Rmax, 20, geometry, alpha=1.05, weights_smooth=1e-2)
374 sol = FF.fit(u, v, vis, weights)
376 geom2 = FixedGeometry(0,0,0,0)
378 q = np.hypot(u,v)
379 q = np.geomspace(q.min(), q.max(), 20)
381 V = sol.predict(q, 0*q, geometry=geom2)
382 Vexpected = [
383 0.3152656, 0.31260669, 0.30831366, 0.30143546,
384 0.29055445, 0.27369896, 0.24848676, 0.2129406,
385 0.1677038, 0.11989993, 0.08507635, 0.07491307,
386 0.06555019, 0.01831576, -0.00173855, 0.00042803,
387 -0.00322264, 0.00278782, -0.00978981, 0.00620259,
388 ]
390 np.testing.assert_allclose(V, Vexpected, rtol=2e-5, atol=1e-8,
391 err_msg="Testing predict with different geometry")
394def test_fit_geometry_inside():
395 """Check the geometry fit embedded in a call to FrankFitter"""
396 AS209, _ = load_AS209(uv_cut=1e6)
398 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
400 Rmax = 1.6
402 FF = FrankFitter(Rmax, 20, FitGeometryGaussian(),
403 alpha=1.05, weights_smooth=1e-2)
405 sol = FF.fit(u, v, vis, weights)
407 geom = sol.geometry
408 np.testing.assert_allclose([geom.inc, geom.PA, 1e3 * geom.dRA,
409 1e3 * geom.dDec],
410 [34.50710460482996, 86.4699107557648,
411 0.21017246809441995, -2.109586872914908],
412 err_msg="Gaussian geometry fit inside Frank fit")
415def test_throw_error_on_bad_q_range():
416 """Check that frank correctly raises an error when the
417 q range is bad."""
418 AS209, geometry = load_AS209()
420 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
422 Rmax = 1.6
424 FF = FrankFitter(Rmax, 20, geometry,
425 alpha=1.05, weights_smooth=1e-2)
427 try:
428 FF.fit(u, v, vis, weights)
429 raise RuntimeError("Expected ValueError due to bad range")
430 except ValueError:
431 pass
434def test_uvbin():
435 """Check the uv-data binning routine"""
436 AS209, geometry = load_AS209()
438 uv = np.hypot(*geometry.deproject(AS209['u'], AS209['v']))
440 uvbin = utilities.UVDataBinner(uv, AS209['V'], AS209['weights'], 50e3)
442 uvmin = 1e6
443 uvmax = 1e6 + 50e3
445 idx = (uv >= uvmin) & (uv < uvmax)
447 widx = AS209['weights'][idx]
449 w = np.sum(widx)
450 V = np.sum(widx*AS209['V'][idx]) / w
451 q = np.sum(widx*uv[idx]) / w
453 i = (uvbin.uv >= uvmin) & (uvbin.uv < uvmax)
455 np.testing.assert_allclose(q, uvbin.uv[i])
456 np.testing.assert_allclose(V, uvbin.V[i])
457 np.testing.assert_allclose(w, uvbin.weights[i])
458 np.testing.assert_allclose(len(widx), uvbin.bin_counts[i])
460def test_save_load_sol():
461 """Check saving/loading a frank 'sol' object"""
462 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
463 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
464 Rmax, N = 1.6, 20
466 # generate a sol from a standard frank fit
467 FF = FrankFitter(Rmax, N, AS209_geometry, alpha=1.05, weights_smooth=1e-2)
468 sol = FF.fit(u, v, vis, weights)
470 # and from a frank debris fit (has additional keys over a standard fit sol)
471 FF_deb = FrankDebrisFitter(Rmax, N, AS209_geometry, lambda x : 0.05 * x,
472 alpha=1.05, weights_smooth=1e-2)
473 sol_deb = FF_deb.fit(u, v, vis, weights)
475 tmp_dir = '/tmp/frank/tests'
476 os.makedirs(tmp_dir, exist_ok=True)
478 save_prefix = [os.path.join(tmp_dir, 'standard'), os.path.join(tmp_dir, 'debris')]
479 sols = [sol, sol_deb]
481 for ii, jj in enumerate(save_prefix):
482 # save the 'sol' object
483 save_fit(u, v, vis, weights, sols[ii], prefix=jj,
484 save_profile_fit=False, save_vis_fit=False, save_uvtables=False
485 )
486 # load it
487 load_sol(jj + '_frank_sol.obj')
490def test_arcsec_baseline():
491 """Check utilities.arcsec_baseline"""
492 result = utilities.arcsec_baseline(1e6)
493 np.testing.assert_almost_equal(result, 0.2062648)
496def test_radius_convert():
497 """Check utilities.radius_convert"""
498 result = utilities.radius_convert(2.0, 100)
499 assert result == 200
501 result_bwd = utilities.radius_convert(200.0, 100, conversion='au_arcsec')
502 assert result_bwd == 2
505def test_jy_convert():
506 """Check utilities.jy_convert"""
507 x = 10
509 bmaj, bmin = 0.1, 0.1
511 expected = {'beam_sterad': 37547916727553.23,
512 'beam_arcsec2': 882.5424006,
513 'arcsec2_beam': 0.113309,
514 'arcsec2_sterad': 425451702961.5221,
515 'sterad_beam': 2.6632636e-12,
516 'sterad_arcsec2': 2.3504431e-10}
518 for key in expected:
519 result = utilities.jy_convert(x, conversion=key, bmaj=bmaj, bmin=bmin)
520 np.testing.assert_almost_equal(result, expected[key], decimal=7)
523def test_get_fit_stat_uncer():
524 """Check utilities.get_fit_stat_uncer"""
525 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
526 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
528 # generate a sol from a standard frank fit
529 FF = FrankFitter(1.6, 20, AS209_geometry, alpha=1.3, weights_smooth=1e-2)
530 sol = FF.fit(u, v, vis, weights)
532 # call with normal model
533 result = utilities.get_fit_stat_uncer(sol)
534 expected = [4.29701157e+08, 4.38007127e+08, 3.81819050e+08, 2.80884179e+08,
535 2.01486438e+08, 1.99436182e+08, 2.15565926e+08, 1.99285093e+08,
536 1.65080363e+08, 1.49458838e+08, 1.55552558e+08, 1.55906224e+08,
537 1.39929834e+08, 1.23399577e+08, 1.18687679e+08, 1.20329729e+08,
538 1.19973246e+08, 1.15672282e+08, 1.05924406e+08, 7.19982652e+07]
540 np.testing.assert_allclose(result, expected, rtol=2e-5, atol=1e-8)
542 # call with lognormal model
543 FF_logn = FrankFitter(1.6, 20, AS209_geometry, alpha=1.3, weights_smooth=1e-2,
544 method='LogNormal')
545 sol_logn = FF.fit(u, v, vis, weights)
546 result_logn = utilities.get_fit_stat_uncer(sol_logn)
547 np.testing.assert_allclose(result_logn, expected, rtol=2e-5, atol=1e-8)
550def test_normalize_uv():
551 """Check utilities.normalize_uv"""
552 result = utilities.normalize_uv(1e5, 1e5, 1e-3)
553 np.testing.assert_almost_equal(result, (([1e8]), ([1e8])))
556def test_cut_data_by_baseline():
557 """Check utilities.cut_data_by_baseline"""
558 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
559 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
561 # restrictive cut range to keep only a single baseline
562 cut_range = [0, 11570]
563 # call with no supplied geometry
564 result = utilities.cut_data_by_baseline(u, v, vis, weights, cut_range)
566 np.testing.assert_almost_equal(result, (([9105.87121309]),
567 ([-7126.8802574]),
568 ([0.25705367-0.00452954j]),
569 ([14390.94693293])
570 ))
572 # restrictive cut range to keep only a single baseline
573 cut_range_geom = [0, 10370]
574 # call with supplied geometry
575 result_geom = utilities.cut_data_by_baseline(u, v, vis, weights, cut_range_geom,
576 geometry=AS209_geometry)
578 np.testing.assert_almost_equal(result_geom, (([3080.37968279]),
579 ([-12126.45120077]),
580 ([0.01581838-0.22529848j]),
581 ([47.91090538])
582 ))
585def test_estimate_weights():
586 """Check utilities.estimate_weights"""
587 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
588 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
590 # call with u, v, vis
591 result = utilities.estimate_weights(u, v, vis)
592 expected = [343.4011447938792, 1040.388154761485, 323.33779140104497,
593 670.5799827294733, 746.6778204045879, 262.537321708902,
594 916.0141170546902, 2478.5780126781183, 220.49922955106743,
595 343.4011447938792]
596 np.testing.assert_allclose(result[:10], expected, rtol=2e-5, atol=1e-8)
598 # call with only u, vis
599 result_no_v = utilities.estimate_weights(u, vis)
600 expected_no_v = [140.15619775524289, 140.15619775524289,
601 136.20331899175486, 144.80828130035127,
602 751.9714145412686, 14.69762047498323,
603 775.4926337220135, 106.4511685363733,
604 188.5850930080213, 299.3538060369927]
605 np.testing.assert_allclose(result_no_v[:10], expected_no_v, rtol=2e-5, atol=1e-8)
607 # call with u, v, vis, use_median
608 result_med = utilities.estimate_weights(u, v, vis, use_median=True)
609 np.testing.assert_almost_equal(result_med[0], 1040.3881547614856)
612def test_make_image():
613 """Check utilities.make_image"""
614 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
615 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
617 # generate a sol from a standard frank fit
618 FF = FrankFitter(1.6, 20, AS209_geometry, alpha=1.3, weights_smooth=1e-2)
619 sol = FF.fit(u, v, vis, weights)
621 # call without projection
622 result = utilities.make_image(sol, Npix=4, project=False)
623 expected = (([-2.4, -0.8, 0.8, 2.4]),
624 ([-2.4, -0.8, 0.8, 2.4]),
625 ([[ 6.47513532e+06, -1.51846712e+07, -1.28084898e+08, -1.51846712e+07],
626 [-1.51846712e+07, 3.53545721e+07, 3.13428201e+08, 3.53545721e+07],
627 [-1.28084898e+08, 3.13428201e+08, 3.30602278e+09, 3.13428201e+08],
628 [-1.51846712e+07, 3.53545721e+07, 3.13428201e+08, 3.53545721e+07]])
629 )
631 # check pixel coordinates
632 np.testing.assert_allclose(result[:2], expected[:2], rtol=2e-5, atol=1e-8)
633 # check pixel brightness
634 Iresult = np.asarray(result[2])
635 Iexpected = np.asarray(expected[2])
636 np.testing.assert_allclose(Iresult, Iexpected, rtol=2e-5, atol=1e-8)
638 # call with projection
639 result_proj = utilities.make_image(sol, Npix=4, project=True)
640 expected_proj = (([-2.4, -0.8, 0.8, 2.4]),
641 ([-2.4, -0.8, 0.8, 2.4]),
642 ([[ 2.40226316e+06, -8.52280178e+06, -1.37522143e+08, -8.18630605e+06],
643 [-8.84143838e+06, 2.75149090e+07, 3.28577699e+08, 1.74953407e+07],
644 [-1.02387759e+08, 2.33877598e+08, 3.44906494e+09, 2.25119693e+08],
645 [-9.02378853e+06, 1.87158156e+07, 3.35326195e+08, 2.71103094e+07]])
646 )
648 # check pixel coordinates
649 np.testing.assert_allclose(result_proj[:2], expected_proj[:2], rtol=2e-5, atol=1e-8)
650 # check pixel brightness
651 Iresult_proj = np.asarray(result_proj[2])
652 Iexpected_proj = np.asarray(expected_proj[2])
653 np.testing.assert_allclose(Iresult_proj, Iexpected_proj, rtol=2e-5, atol=1e-8)
656def test_add_vis_noise():
657 """Check utilities.add_vis_noise"""
658 # dummy vis and weight
659 vis, weights = [1.1 + 0.9j], 0.5
661 result = utilities.add_vis_noise(vis, weights, seed=47)
662 np.testing.assert_almost_equal(result, [-0.0992665+2.74683048j])
665def test_make_mock_data():
666 """Check utilities.add_vis_noise"""
667 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
668 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
670 # generate a sol from a standard frank fit
671 FF = FrankFitter(1.6, 20, AS209_geometry, alpha=1.3, weights_smooth=1e-2)
672 sol = FF.fit(u, v, vis, weights)
674 # call with minimal inputs
675 result = utilities.make_mock_data(sol.r, sol.I, 3.0, u, v)
676 expected = [ 0.06699455, 0.18302498, 0.27758414, 0.04789997, -0.00240399,
677 0.06339718, 0.00358722, 0.28862088, 0.07058801, 0.06617371]
678 np.testing.assert_allclose(result[1][:10], expected, rtol=2e-5, atol=1e-8)
680 # call with deprojection
681 result_dep = utilities.make_mock_data(sol.r, sol.I, 3.0, u, v,
682 projection='deproject',
683 geometry=AS209_geometry)
684 expected_dep = [0.06244746, 0.15925137, 0.2345302 , 0.0623711 , 0.00404342,
685 0.06277, 0.00361453, 0.23649558, 0.06326574, 0.0632122 ]
686 np.testing.assert_allclose(result_dep[1][:10], expected_dep, rtol=2e-5, atol=1e-8)
688 # call with reprojection
689 result_rep = utilities.make_mock_data(sol.r, sol.I, 3.0, u, v,
690 projection='reproject',
691 geometry=AS209_geometry)
692 expected_rep = [ 0.05219592, 0.11866411, 0.22040989, 0.03889961, -0.00133919,
693 0.05194375, -0.00054036, 0.23372006, 0.04987515, 0.04541111]
694 np.testing.assert_allclose(result_rep[1][:10], expected_rep, rtol=2e-5, atol=1e-8)
696 # call with added noise
697 result_noi = utilities.make_mock_data(sol.r, sol.I, 3.0, u, v,
698 add_noise=True, weights=weights, seed=47)
699 expected_noi = [-0.06817425, 0.3195001 , 0.36992457, 0.11576222, -0.18251663,
700 0.38046765, -0.13962233, 0.42048773, 0.01093563, -0.08652271]
701 np.testing.assert_allclose(result_noi[1][:10], expected_noi, rtol=2e-5, atol=1e-8)
704def test_get_collocation_points():
705 """Check utilities.get_collocation_points"""
706 # call with forward DHT
707 result = utilities.get_collocation_points(N=10)
708 expected = [0.14239924, 0.32686567, 0.51242148, 0.69822343, 0.88411873,
709 1.07005922, 1.25602496, 1.44200623, 1.62799772, 1.8139963 ]
710 np.testing.assert_allclose(result, expected, rtol=2e-5, atol=1e-8)
712 # call with backward DHT
713 result_bwd = utilities.get_collocation_points(N=10, direction='backward')
714 expected_bwd = [ 39472.88305737, 90606.73736504, 142042.56471889, 193546.62066389,
715 245076.55732463, 296619.01772663, 348168.47711355, 399722.24089812,
716 451278.83939289, 502837.4032234 ]
717 np.testing.assert_allclose(result_bwd, expected_bwd, rtol=2e-5, atol=1e-8)
719def test_prob_model():
720 """Check the probabilities for the frank model"""
721 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
722 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
724 # generate a sol from a standard frank fit
725 FF = FrankFitter(1.6, 20, AS209_geometry, alpha=1.3, weights_smooth=1e-2)
726 sol = FF.fit(u, v, vis, weights)
728 result = FF.log_evidence_laplace()
729 np.testing.assert_allclose([result], [18590.205198687152], rtol=1e-7)
732def _run_pipeline(geometry='gaussian', fit_phase_offset=True,
733 fit_inc_pa=True, make_figs=False,
734 multifit=False, bootstrap=False):
735 """Check the full pipeline that performs a fit and outputs results"""
737 # Build a subset of the data that we'll load during the fit
738 AS209, AS209_geometry = load_AS209(uv_cut=1e6)
740 u, v, vis, weights = [AS209[k][::100] for k in ['u', 'v', 'V', 'weights']]
742 tmp_dir = '/tmp/frank/tests'
743 os.makedirs(tmp_dir, exist_ok=True)
745 uv_table = os.path.join(tmp_dir, 'small_uv.npz')
746 save_uvtable(uv_table, u, v, vis, weights)
748 # Build a parameter file
749 params = fit.load_default_parameters()
751 params['input_output']['uvtable_filename'] = uv_table
753 # Set the model parameters
754 params['hyperparameters']['n'] = 20
755 params['hyperparameters']['rout'] = 1.6
756 params['hyperparameters']['alpha'] = 1.05
757 params['hyperparameters']['wmsooth'] = 1e-2
759 geom = params['geometry']
760 geom['type'] = geometry
761 geom['fit_phase_offset'] = fit_phase_offset
762 geom['fit_inc_pa'] = fit_inc_pa
763 geom['inc'] = AS209_geometry.inc
764 geom['pa'] = AS209_geometry.PA
765 geom['dra'] = AS209_geometry.dRA
766 geom['ddec'] = AS209_geometry.dDec
768 if make_figs:
769 params['plotting']['quick_plot'] = True
770 params['plotting']['full_plot'] = True
771 params['plotting']['diag_plot'] = True
772 params['plotting']['deprojec_plot'] = True
773 params['input_output']['save_figures'] = False
774 params['plotting']['distance'] = 121.
775 params['plotting']['bin_widths'] = [1e5]
776 params['plotting']['iter_plot_range'] = [0, 5]
777 params['analysis']['compare_profile'] = 'docs/tutorials/AS209_clean_profile.txt'
778 params['analysis']['clean_beam'] = {'bmaj' : 0.03883,
779 'bmin' : 0.03818,
780 'beam_pa' : 85.82243
781 }
783 else:
784 params['plotting']['quick_plot'] = False
785 params['plotting']['full_plot'] = False
786 params['plotting']['diag_plot'] = False
787 params['plotting']['deprojec_plot'] = False
789 if multifit:
790 params['hyperparameters']['alpha'] = [1.05, 1.30]
792 if bootstrap:
793 params['analysis']['bootstrap_ntrials'] = 2
795 # Save the new parameter file
796 param_file = os.path.join(tmp_dir, 'params.json')
797 with open(param_file, 'w') as f:
798 json.dump(params, f)
800 # Call the pipeline to perform the fit
801 fit.main(['-p', param_file])
804def test_pipeline_full_geom():
805 """Check the full fit pipeline when fitting for the disc's inc, PA, dRA, dDec"""
806 _run_pipeline('gaussian', fit_phase_offset=True)
809def test_pipeline_no_phase():
810 """Check the full fit pipeline when only fitting for the disc's inc, PA"""
811 _run_pipeline('gaussian', fit_phase_offset=False)
813def test_pipeline_no_inc_no_pa():
814 """Check the full fit pipeline when only fitting for the disc's phase center"""
815 _run_pipeline('gaussian', fit_phase_offset=True, fit_inc_pa=False)
818def test_pipeline_known_geom():
819 """Check the full fit pipeline when supplying a known disc geometry"""
820 _run_pipeline('known')
823def test_pipeline_figure_generation():
824 """Check the full fit pipeline when producing all figures"""
825 _run_pipeline('known', make_figs=True)
828def test_pipeline_multifit():
829 """Check the full fit pipeline when producing all figures and running multiple fits"""
830 _run_pipeline('known', multifit=True)
833def test_pipeline_bootstrap():
834 """Check the full fit pipeline when producing all figures and running bootstrap"""
835 _run_pipeline('known', bootstrap=True)