Model Interface
Primitive Geometric Models
VLBISkyModels
aims to be more modular and extensible than previous VLBI modeling packages. Namely, simple models are composed to construct complicated source morphologies instead of making many different models. This is accomplished with a type and trait-based hierarchy.
Additionally, ComradeBase
is a low-dependency version of this package that defines this type and trait hierarchy that users can more easily incorporate into their packages.
To see how this works, we will go through a simplified implementation of the Gaussian model in VLBISkyModels
. The Gaussian model is a simple, compact emission structure model that can constrain the typical characteristic size of an image feature from VLBI data. To construct a Gaussian model, we will first define a struct:
struct MyGaussian <: VLBISkyModels.AbstractModel end
Notice that we don't provide any more information about the model, e.g., size, shape, flux, etc. This is because we will use VLBISkyModels
's extensive set of modifiers to change the structure of the model. Now a Gaussian
is the simplest model structure we can consider. We can consider this Gaussian to be a primitive model. That means a Gaussian is not a combination or modification of an existing model. To tell VLBISkyModels
that this is the case, we define the following method:
Now a Gaussian has an analytic expression in the image and Fourier domain. We can tell VLBISkyModels
this by setting:
# Fourier and image domains are analytic
VLBISkyModels.visanalytic(::Type{<:MyGaussian}) = IsAnalytic()
VLBISkyModels.imanalytic(::Type{<:MyGaussian}) = IsAnalytic()
Finally, we can specify if the model is intrinsically polarized by using the IsPolarized
and NotPolarized()
trait
VLBISkyModels.ispolarized(::Type{<:MyGaussian}) = NotPolarized()
The actual implementation defines the Gaussian to be a subtype of VLBISkyModels.GeometricModel
, which automatically defines these methods. However, for models that aren't a subtype of GeometricModel
, we assume the image domain IsAnalytic()
and the Fourier domain is NotAnalytic()
.
Since both the image and visibility domain representation of the Gaussian are analytic, we need to define an intensity_point
and visibility_point
method. For a Gaussian, these are given by
function ComradeBase.intensity_point(::MyGaussian, p)
(; X, Y) = p
return exp(-(X^2 + Y^2) / 2) / 2π
end
function ComradeBase.visibility_point(::MyGaussian, u, v, time, freq) where {T}
return exp(-2π^2 * (u^2 + v^2)) + 0im
end
Additionally, most models in VLBISkyModels
has two additional functions one can implement if possible:
flux(m::MyGaussian)
: This defines the flux of a model. If this isn't defined, the model won't have a flux until an image is created. For a Gaussian, the definition isflux(::MyGaussian) = 1.0
.radialextent(::MyGaussian)
: This defines the model's default radial extent. For a Gaussian, we will consider the radial extent to be $5σ$, soradialextent(::MyGaussian) = 5.0
.
This completely defines the model interface for VLBISkyModels
. With this, you can call the usual user API to evaluate, fit, and plot the model. Additionally, we can start talking about adding multiple Gaussians and modifying them. For instance, suppose you want an elliptical Gaussian with a flux of 2 Jy. This can be created by VLBISkyModels
as follows:
using Plots
gauss = MyGaussian()
ellgauss = 2.0 * rotated(stretched(gauss, 1.0, 0.5), π / 4)
fig = plot(gauss; layout=(1, 2), size=(800, 300))
plot!(fig[2], ellgauss; size=(800, 350))
using Plots
u = rand(100) * 0.5;
v = rand(100) * 0.5;
vg = visibilitymap(gauss, u, v)
veg = visibilitymap(ellgauss, u, v)
Plots.scatter(hypot.(u, v), abs.(vg); label="Gaussian")
Plots.scatter!(hypot.(u, v), abs.(veg); label="Elliptical Gaussian")