Full Visibilities Imaging
Status `~/work/VLBISkyRegularizers.jl/VLBISkyRegularizers.jl/examples/Project.toml`
⌃ [13f3f980] CairoMakie v0.12.5
⌃ [99d987ce] Comrade v0.10.4
[0b91fe84] DisplayAs v0.1.6
⌃ [31c24e10] Distributions v0.25.109
⌃ [7da242da] Enzyme v0.12.31
⌃ [7f7a1694] Optimization v3.27.0
[42dfb2eb] OptimizationOptimisers v0.2.1
[3d61700d] Pyehtim v0.1.3
[860ef19b] StableRNGs v1.0.2
[2913bbd2] StatsBase v0.34.3
⌃ [fd094767] Suppressor v0.2.7
[272825ce] VLBISkyRegularizers v1.0.0-DEV `~/work/VLBISkyRegularizers.jl/VLBISkyRegularizers.jl`
[b77e0a4c] InteractiveUtils
[56ddb016] Logging
Info Packages marked with ⌃ have new versions available and may be upgradable.
nothing
Loading the Data
We start by loading Comrade and VLBISkyRegularizers.
using Comrade
using VLBISkyRegularizers
We use Pyehtim to load the uvfits files.
using Pyehtim
[33m[1m┌ [22m[39m[33m[1mWarning: [22m[39mModule SciMLBase with build ID fafbfcfd-c24d-fca5-0000-004b1aa0cd12 is missing from the cache.
[33m[1m│ [22m[39mThis may mean SciMLBase [0bca4576-84f4-4d90-8ffe-ffa030f20462] does not support precompilation but is imported by a module that does.
[33m[1m└ [22m[39m[90m@ Base loading.jl:1948[39m
[33m[1m┌ [22m[39m[33m[1mWarning: [22m[39mModule BangBang with build ID fafbfcfd-7e12-50d4-0000-00459fb53c4a is missing from the cache.
[33m[1m│ [22m[39mThis may mean BangBang [198e06fe-97b7-11e9-32a5-e1d131e6ad66] does not support precompilation but is imported by a module that does.
[33m[1m└ [22m[39m[90m@ Base loading.jl:1948[39m
[33m[1m┌ [22m[39m[33m[1mWarning: [22m[39mModule Comrade with build ID fafbfcfd-f82d-34a3-0000-007be2c79b20 is missing from the cache.
[33m[1m│ [22m[39mThis may mean Comrade [99d987ce-9a1e-4df8-bc0b-1ea019aa547b] does not support precompilation but is imported by a module that does.
[33m[1m└ [22m[39m[90m@ Base loading.jl:1948[39m
We also use a stable random number generator
using StableRNGs
rng = StableRNG(100)
StableRNGs.LehmerRNG(state=0x000000000000000000000000000000c9)
Load the observation data with Pyehtim and do some preprocessing
file = joinpath(__DIR, "data", "L2V1_M87_2018_111_b3_hops_netcal_10s_StokesI.uvfits")
obseht = ehtim.obsdata.load_uvfits(file)
obs = Pyehtim.scan_average(obseht).add_fractional_noise(0.02)
Python: <ehtim.obsdata.Obsdata object at 0x7fdb612c1390>
Extract the visibilities from the obsdata object
dvis = extract_table(obs, Visibilities())
EHTObservationTable{Comrade.EHTVisibilityDatum{:I}}
source: M87
mjd: 58228
bandwidth: 1.856e9
sites: [:AA, :AX, :GL, :LM, :MG, :MM, :PV, :SW]
nsamples: 339
Build sky model
We build a model consisting of a raster of pixels and an extended Gaussian component. The pixel raster is convolved with a pulse to make a ContinuousImage
object. The Gaussian component is used to model the zero baselines. The sky model consists of two components, θ
and metadata
. θ
is a NamedTuple with parameters we want to fit. impixel
here is the pixel raster, and fg
controls the relative scaling between the raster and the Gaussian component. metadata
is also a NamedTuple with constant parameters. ftot
controls the total flux scaling, pulse
is the pulse with which the pixel raster is convolved, and regularizers
is the set of regularizers we will be using.
function sky(θ, metadata)
(; impixel, fg) = θ
(; ftot, pulse, regs) = metadata
# Transform image to the image simplex domain and scale
rast = ftot*(1-fg).*transform_image(image_domain(regs), impixel)
# Form the image
img = IntensityMap(rast, VLBISkyRegularizers.grid(regs))
m = ContinuousImage(img, pulse)
# Add an extended Gaussian
gauss = modify(Gaussian(), Stretch(μas2rad(250.0)), Renormalize(fg*ftot))
return m + gauss
end
sky (generic function with 1 method)
Define Image and Regularizers
Next, we define the grid on which our image model will exist.
global fov = 150
global npix = 32
grid_p = imagepixels(μas2rad(fov), μas2rad(fov), npix, npix)
RectiGrid(
executor: ComradeBase.Serial()
Dimensions:
(↓ X Sampled{Float64} LinRange{Float64}(-3.5224744018114725e-10, 3.5224744018114725e-10, 32) ForwardOrdered Regular Points,
→ Y Sampled{Float64} LinRange{Float64}(-3.5224744018114725e-10, 3.5224744018114725e-10, 32) ForwardOrdered Regular Points)
)
Now we define the regularizers we want to use. The available regulariers are:
subtypes(AbstractRegularizer)
8-element Vector{Any}:
AddRegularizer
KLE
L1
PSDistance
TSV
TV
WaveletL1
WeightRegularizer
Each regularizer must be passed with four arguments: the hyperparameter, the image domain, the evaluation domain, and the grid. The hyperparameter defines the weight of the regularizer, the image domain defines the domain to which the image will be transformed, the evaluation domain defines the domain on which the regularizer will be evaluated, and the grid defines the grid of pixels on which the regularizer will be evaluated. The available domains are:
subtypes(AbstractDomain)
7-element Vector{Any}:
ALRDomain
CLRDomain
LinearDomain
LogDomain
ParameterDomain
PolarizationDomain
VLBISkyRegularizers.PSDDomain
Note
When using multiple regularizers, the image domains and grids of all regularizers must match.
We will use a TSV regularizer in the log domain and a wavelet-space L1-norm regularizer in the linear domain, and have a log image domain.
r1 = TSV(1, LinearDomain(), LogDomain(), grid_p)
r2 = WaveletL1(1, LinearDomain(), ParameterDomain(), grid_p)
regs = r1 + r2
Added Regularizer:
Regularizer 1:
Regularizer: TSV
Hyperparameter: 1
Evaluation Domain: LogDomain()
Regularizer 2:
Regularizer: Wavelet L1
Hyperparameter: 1
Evaluation Domain: ParameterDomain()
Wavelet: Full db2
Image Domain: LinearDomain()
Grid FOV: 150.0x150.0 μas
Grid Size: 32x32
Build Posterior
Now we build our sky model.
using Distributions
skymeta = (;ftot = 1.1, pulse = BSplinePulse{3}(), regs = regs)
skyprior = (
impixel=regs,
fg=Uniform(0,1)
)
skymodel = SkyModel(sky, skyprior, grid_p, metadata=skymeta)
SkyModel
with map: sky
on grid: ComradeBase.RectiGrid
And next our intstrument model.
G = SingleStokesGain() do x
lg = x.lg
gp = x.gp
return exp(lg + 1im*gp)
end
intpr = (
lg= ArrayPrior(IIDSitePrior(ScanSeg(), Normal(0.0, 0.2)); LM = IIDSitePrior(ScanSeg(), Normal(0.0, 1.0))),
gp= ArrayPrior(IIDSitePrior(ScanSeg(), DiagonalVonMises(0.0, inv(π^2))); refant=SEFDReference(0.0), phase=true)
)
intmodel = InstrumentModel(G, intpr)
InstrumentModel
with Jones: SingleStokesGain
with reference basis: PolarizedTypes.CirBasis()
Now we can make our posterior.
post = VLBIPosterior(skymodel, intmodel, dvis)
VLBIPosterior
ObservedSkyModel
with map: sky
on grid: VLBISkyModels.FourierDualDomainObservedInstrumentModel
with Jones: SingleStokesGain
with reference basis: PolarizedTypes.CirBasis()Data Products: Comrade.EHTVisibilityDatum
Image Reconstruction
We use the solve_opt
function to solve for the MAP estimate. By default, this uses an Adam optimizer from the OptimizationOptimisers.jl
package and automatic differentiation with Enzyme from the Optimization.jl
package. This function runs the optimization ntrials
number of times. Each optimization trial first runs half of maxiters
number of iterations, adds some random noise, then runs maxiters
iterations from that point. The output values are sorted by order of objective value.
using Optimization
using OptimizationOptimisers
using Enzyme
solve_opt(post, Optimisers.Adam(), Optimization.AutoEnzyme(); ntrials=5, maxiters=10_000, verbose=false)
(@NamedTuple{sky::@NamedTuple{impixel::Matrix{Float64}, fg::Float64}, instrument::@NamedTuple{lg::Comrade.SiteArray{Float64, 1, Vector{Float64}, Vector{Comrade.IntegrationTime{Float64}}, FillArrays.Fill{Float64, 1, Tuple{Base.OneTo{Int64}}}, Vector{Symbol}}, gp::Comrade.SiteArray{Float64, 1, Vector{Float64}, Vector{Comrade.IntegrationTime{Float64}}, FillArrays.Fill{Float64, 1, Tuple{Base.OneTo{Int64}}}, Vector{Symbol}}}}[(sky = (impixel = [0.014116881532772881 0.014777348727080645 … 0.01928854389001646 0.01669730603028104; 0.01505454278668511 0.01576975748986589 … 0.019111485626086586 0.0161713022013274; … ; 0.042272395180912525 0.04278591819471418 … 0.04124726158080567 0.034855794542158335; 0.03722135770597106 0.03816550957356247 … 0.03658272823542657 3.0729314908842665e-5], fg = 0.010141558999722994), instrument = (lg = [0.04489238204647651, -0.04606488786268801, 0.3470290050730102, -0.022038531834720758, 0.02262126731612938, 0.4036955614195387, 0.04122279280137001, -0.04140931310657406, -0.41630675517520277, 0.11141481523298999 … 0.079825067546233, 0.011147081592849025, -0.005309780300181497, 0.04158697906021826, -0.037676577401611885, -0.23679753741417786, -0.017909345620873693, 0.05189485939983817, 0.017688703602043946, -0.00875819102976772], gp = [0.0, -0.011845868156976034, 1.720140350167542, 0.0, 0.00817141599764446, 1.6938804603926274, 0.0, 0.003144109471192846, 0.5120597583927129, 1.632119407923321 … -0.04401939041434011, 0.7423009347864573, 2.4433389604712628, 0.0, -0.007501962465781716, 8.035437017197811, -2.422351205594254, -0.06196343157542439, 0.7082278243311373, 2.4207586442959976])), (sky = (impixel = [0.013441215266534676 0.015548354342624054 … 0.02707289832058558 0.024435013204324972; 0.01539014379228806 0.017777575602651613 … 0.029731759959913267 0.028589881846785164; … ; 0.027361134001015235 0.03643422970704207 … 0.014264753666071477 0.012822436718106869; 0.022568201120293403 0.033584230917270336 … 0.012192195764841084 3.0240703147351368e-5], fg = 0.009519817709150594), instrument = (lg = [0.040097957190886296, -0.04191010537120548, 0.22721999414497276, -0.022174532935162604, 0.022287908668420172, 0.30786256830109054, 0.029700790665919602, -0.03142785178247717, -0.2745971018096853, 0.09322850382923188 … 0.1094489427583667, 0.01186223178355275, -0.003967101492655268, 0.0422502458025887, -0.03864964693818655, -0.0029971448063534035, 0.010756327462598263, 0.08238603141016752, 0.0213408370409757, -0.006407302427419483], gp = [0.0, -0.012842261475362103, -1.6870798194698418, 0.0, 0.0069189889759192266, -1.7290458709809853, 0.0, 0.0021612287947506536, -0.43213664974020793, -1.8315197598750144 … 1.3846039805665704, -3.9511150346761874, -2.2503796674263072, 0.0, -0.007338519881097632, -4.963865659122968, 0.5130051553624224, 1.4031081744930898, -3.936079943667181, -2.223500473300341])), (sky = (impixel = [0.015280592835368602 0.016888818125155476 … 0.03130646171660247 0.033017023239183756; 0.016476775633160592 0.01854480135445185 … 0.026951315322809963 0.028166828154406236; … ; 0.020949864649752275 0.019160442830889746 … 0.01917678438286594 0.01780645495968482; 0.020075141495790882 0.019211232338352868 … 0.018032600184138537 0.000853801091354461], fg = 0.0138143122730759), instrument = (lg = [0.04243677172789193, -0.043854345333596986, 0.2937452088940615, -0.020995131151342352, 0.021441367960363573, 0.3696426308886042, 0.040817057482141234, -0.04110249741496388, -0.3886218975333081, 0.13196341666322825 … 0.046641599489931976, 0.01209030941896644, -0.003308257462947773, 0.05260021238174424, -0.04669953793478552, -0.06532886905375648, -0.07021448243535963, 0.0122548387062525, 0.02222089760741121, -0.005340893750370735], gp = [0.0, -0.01241046231188238, 2.7109806031010253, 0.0, 0.008060508895743597, 2.7183913809693414, 0.0, 0.0026745293824776504, -0.09310167299963205, 2.741227800044074 … -3.087535375622896, -2.243142585778503, -0.5421365037860262, 0.0, -0.006998817835539079, -1.162115596416734, 1.9156520395319696, -3.064317533887305, -2.235831771824322, -0.5233557971464764])), (sky = (impixel = [0.051425696812565466 0.06475872803906368 … 0.018211615955449054 0.01577979367541639; 0.07208025507861712 0.08631707090475153 … 0.019885954384702982 0.017119242192147298; … ; 0.012778017916469861 0.013668189487316655 … 0.00381376588987237 0.003451530960739808; 0.012512988972101428 0.01360827308072558 … 0.003533341927671574 -0.000297645862500775], fg = 0.015803390701168377), instrument = (lg = [0.04177405821399194, -0.04330546970351625, 0.2712813326794514, -0.020590909395077534, 0.02095700628839115, 0.3537031376989496, 0.02783137707982636, -0.02967921446286997, -0.3948666430699576, 0.13884614171973197 … 0.09966989297601339, 0.011270927563175194, -0.005302286116112987, 0.052726342122735415, -0.04673563134316933, 0.06621099342679113, -0.2801398476845709, 0.06265838172488969, 0.021891934865992613, -0.006292747159763147], gp = [0.0, -0.01288713221565279, -0.2877696472906244, 0.0, 0.007249201942704609, -0.3020219704073036, 0.0, 0.0021971830605524607, 1.1577281058769415, -0.33909576049638535 … 0.2765735051441467, 0.20213443852951674, 1.9032294829529983, 0.0, -0.006471082650920775, -6.305809937410017, -0.2506013964024031, 0.3303626274378504, 0.24240617637911366, 1.9546735013226877])), (sky = (impixel = [0.009495653929475802 0.009553918377790186 … 0.018653688241786995 0.019581683720821162; 0.009611069539436199 0.009611059909862172 … 0.020869904904270355 0.02409703537634546; … ; 0.013517867586278116 0.013014326889236278 … 0.006589827140816798 0.006556213260172392; 0.012774050682595697 0.01234779095467627 … 0.0065476242867046845 0.15207484573159447], fg = 0.024147653034550437), instrument = (lg = [0.04130538328457871, -0.04292728760016511, 0.25879590364472504, -0.0211015551315059, 0.02136814128878209, 0.34018959979702634, 0.03675607574922615, -0.03755831629585652, -0.42468848447183916, 0.11470804780734008 … 0.03434638092598969, 0.010770707615055702, -0.006614295348504435, 0.04331699029544688, -0.03922960383902749, -0.19207341478484163, -0.17995621692107724, 0.004314486591638323, 0.01967256757777385, -0.009262151128626557], gp = [0.0, -0.012542730277176761, 0.23964021510856776, 0.0, 0.008610804456515119, 0.28496446769177836, 0.0, 0.0028466089575114525, 2.2898392411853163, 0.3994724524939677 … 2.027752503179682, -1.5431312734677836, 0.1581862625431607, 0.0, -0.006529459493124263, -2.8983327387650273, 0.9877998699043422, 2.062756623132175, -1.539673093710795, 0.17271151730068462]))], [2039.042069660225, 2032.0132279102982, 2030.3593215491917, 557.1642661110371, -208.26492040794975])
Now we plot the MAP estimate.
import CairoMakie as CM
grid_plot = imagepixels(μas2rad(fov), μas2rad(fov), npix*4, npix*4)
img = intensitymap(Comrade.skymodel(post, xopts[1]), grid_plot)
fig = imageviz(img);
This page was generated using Literate.jl.