Note
This tutorial was generated from a Jupyter notebook that can be downloaded here.
Lazy versus greedy evaluation¶
tl;dr¶
Version 1.0
of the code evaluates things lazily by default, meaning that all internal values are nodes in a graph, stored as theano
tensors. Lazy mode is required for interfacing with pymc3
to do inference (refer to the several tutorials on pymc3
sampling). If you really need the value of a theano
object, you can always call its eval()
method, but keep in mind that operation can be somewhat slow.
If, on the other hand, you’re not interested in using pymc3
or in any of the derivatives of starry
models, you can disable lazy evaluation by typing
starry.config.lazy = False
at the top of your script, before you instantiate any starry
maps. If you do that, starry
will behave as it did in previous versions: you don’t have to call the eval()
method or worry about any tensor nonsense.
Lazy mode¶
One of the big changes in version 1.0
of starry
is lazy evaluation mode, which is now the default. Lazy evaluation means that the evaluation of all expressions in the code is delayed until a numerical value is needed (i.e., when outputting or plotting the result). This is as opposed to greedy or eager evaluation, in which all expressions are evaluated on-the-fly, as soon as the code
encounters them. In lazy evaluation mode, expressions are compiled and stored in memory as nodes in a graph, which are only executed when a numerical value is required. This strategy allows for some cool compile-time optimization under the hood. But by far the greatest advantage of lazy evaluation (at least in our case) is that it makes it easy to autodifferentiate expressions using backpropagation. This lets us compute derivatives of all expressions extremely efficiently, and those can be
seemlessly integrated into derivative-based MCMC sampling schemes such as Hamiltonian Monte Carlo or NUTS.
Version 1.0
of starry
is built on top of the theano machine learning library, which handles all of the graph compiling and backpropagation. There’s lots of other software that does similar things (such as tensorflow
and pytorch
), but the advantage of theano
is that it is also the backbone of exoplanet and pymc3. This allows us to easily integrate starry
with all the cool inference machinery of those two packages.
Let’s look at some examples of how lazy evaluation works in starry
. Let’s instantiate a regular starry
map:
[4]:
import starry
map = starry.Map(ydeg=1)
We can give this map a simple dipole by assigning a value to the coefficient of the \(Y_{1,0}\) spherical harmonic:
[5]:
map[1, 0] = 0.5
Since the coefficient of the \(Y_{0,0}\) harmonic is fixed at unity, our spherical harmonic coefficients are now the vector \(y = (1, 0, \frac{1}{2}, 0)\). Here’s what that looks like:
[6]:
map.show()

Recall that the spherical harmonic coefficients are stored in the y
attribute of the map. Let’s take a look:
[7]:
map.y
[7]:
AdvancedIncSubtensor1{no_inplace,set}.0
That doesn’t look right, but it is: the vector \(y\) is stored internally as a theano
tensor and doesn’t yet have a numerical value:
[8]:
type(map.y)
[8]:
theano.tensor.var.TensorVariable
In order to access its value, I can call its eval
method:
[9]:
map.y.eval()
[9]:
array([1. , 0. , 0.5, 0. ])
Which is what we expected.
A similar thing happens when we call a method such as flux
:
[10]:
map.flux(xo=0.4, yo=0.3, ro=0.1, theta=30)
[10]:
Elemwise{mul,no_inplace}.0
[11]:
map.flux(xo=0.4, yo=0.3, ro=0.1, theta=30).eval()
[11]:
array([1.48179813])
As we mentioned above, it’s not generally a good idea to call the eval()
method, since it can be quite slow. The whole point of lazy evaluation mode is so that starry
can be easily integrated with pymc3
. When building a pymc3
model, pymc3
handles all of the evaluations internally, so there’s no need to call eval()
. Within a pymc3
model context, users can pass pymc3
variables, theano
variables, and/or numpy
arrays to any starry
method; casting is
handled internally in all cases. Check out the tutorials on inference with pymc3
for more information.
If, on the other hand, you’re not planning on integrating starry
with pymc3
, you should probably run it in greedy mode. See below.
Greedy mode¶
To run starry
in greedy (i.e., not lazy) mode, you can add the following line somewhere near the top of your script:
[13]:
starry.config.lazy = False
(Note that if you try to change the evaluation mode after you’ve instantiated a starry
map, the code will complain.)
In greedy mode, things behave as they did in previous versions of the code. Check it out:
[14]:
map = starry.Map(ydeg=1)
[15]:
map[1, 0] = 0.5
[16]:
map.y
[16]:
array([1. , 0. , 0.5, 0. ])
[17]:
type(map.y)
[17]:
numpy.ndarray
All methods are automatically compiled and return numerical outputs:
[18]:
map.flux(xo=0.4, yo=0.3, ro=0.1, theta=30)
[18]:
array([1.48179813])
Because theses methods are autocompiled, it’s much faster to use greedy mode than to call eval()
every time.