Case Study: Extending the Tractor to do Strong Gravitational Lensing¶
This tutorial (really more of a case study) goes through extending the Tractor for a new kind of astronomical source. Hi, Phil!
We want to create a new kind of Source
object: a
strongly gravitationally lensed quasar. The lens will have a visible
component: a DevGalaxy
galaxy profile,
as well as a dark component that defines its mass. The lens will
produce 2 to 4 images of the quasar.
The lens model produces only approximate estimates of the brightness of the multiple quasar images, so we will need a “fudge factor” for the magnitudes predicted by the lens model.
We want to create a class to hold our Lensed Quasar:
from tractor import MultiParams
from tractor.sdss_galaxy import DevGalaxy
class LensedQuasar(MultiParams):
@staticmethod
def getNamedParams():
return dict(light=0, mass=1, quasar=2, magfudge=3)
We chose to make it inherit from MultiParams
because
we want to think of it as being composed of the light (visible
component of the lens that determines its appearance), mass (the
dark mass of the lens that determines its lensing behavior), and the
quasar being lensed. We also have magfudge, our fudge-factor for
the quasar’s brightness at each image.
We want our LensedQuasar
to be a Source
that can
be manipulated by the Tractor, though. We therefore have to implement
the interface described by Source
; we have to make our
LensedQuasar
quack like a Source
duck.
To do this, we add the methods defined in Source
to our LensedQuasar
:
from tractor import PointSource
class LensedQuasar(MultiParams):
# ... as before ...
def getModelPatch(self, img):
# We start by rendering the visible lens galaxy.
patch = self.light.getModelPatch(img)
# We will use the lens model to predict the quasar's image positions.
positions,mags = self.mass.getLensedImages(self.light.position, self.quasar)
# 'positions' should be a list of RaDecPos objects
# 'mags' should be a list of Mags objects
for pos,mag,fudge in zip(positions, mags, self.magfudge):
# For each image of the quasar, we will create a PointSource
ps = PointSource(pos, mag + fudge)
# ... and add it to the patch.
patch += ps.getModelPatch(img)
return patch
def getParamDerivatives(img):
pass
In the getModelPatch
method, we have to return a
Patch
object: a synthetic rendering of our
LensedQuasar
as it would appear in the given
Image
. We will do that by combining the appearance
of self.light
– the visible component of the lens – with the
multiple images of the quasar whose positions and brightnesses are
estimated by the lensing model, self.mass
.
Now, what is the mass
going to look like? It is going to have
parameters that we want the Tractor to be able to optimize, so it has
to be a Params
. Actually, as you might have
guessed, it just has to quack like a Params
. Since
our mass
is just going to have a few parameters, we could inherit
from ParamList
:
from tractor import ParamList
class LensingMass(ParamList):
@staticmethod
def getNamedParams():
return dict(mass=0, radius=1)
def getStepSizes(self):
'''We're using units of solar masses and arcsec'''
return [1e12, 0.1]
def getLensedImages(self, mypos, quasar):
pass
The getLensedImages
function is the one we’re going to call from
LensedQuasar.getModelPatch()
to predict the lensed image
properties.
Let’s fill in the blanks and get the code to run. To create a
LensedQuasar
object, we’ll have to create its components. We will
mock up the Quasar
and MagFudge
classes. Currently Quasar
doesn’t even have any parameters, and that’s ok:
from tractor import RaDecPos, Mags
from tractor.sdss_galaxy import GalaxyShape
class Quasar(ParamList):
pass
class MagFudge(ParamList):
pass
if __name__ == '__main__':
# Create properties of the lensing galaxy:
pos = RaDecPos(234.5, 17.9)
bright = Mags(r=17.4, g=18.9, order=['g','r'])
# GalaxyShape( re [arcsec], ab ratio, phi [deg] )
shape = GalaxyShape(2., 0.5, 48.)
light = DevGalaxy(pos, bright, shape)
mass = LensingMass(1e14, 0.1)
quasar = Quasar()
# Four parameters for up to four images.
fudge = MagFudge(0., 0., 0., 0.)
# Create a LensedQuasar object from its components.
lq = LensedQuasar(light, mass, quasar, fudge)
print 'LensedQuasar params:'
for nm,val in zip(lq.getParamNames(), lq.getParams()):
print ' ', nm, '=', val
and this will print:
LensedQuasar params:
light.pos.ra = 234.5
light.pos.dec = 17.9
light.brightness.g = 18.9
light.brightness.r = 17.4
light.shape.re = 2.0
light.shape.ab = 0.5
light.shape.phi = 48.0
mass.mass = 1e+14
mass.radius = 0.1
magfudge.param0 = 0.0
magfudge.param1 = 0.0
magfudge.param2 = 0.0
magfudge.param3 = 0.0