###############
Advanced Topics
###############
==============================
Consolidating fit dictionaries
==============================
To reduce the amount of disk I/O, by default the individual fit dictionaries will be gathered up (along with some useful
meta information) into an overall fit dictionary. If you're working with the full samplers for huge cubes, this file
may be too large to handle on a machine. In which case, you may wish to set ``consolidate_fit_dict = false`` in the
``fitting_parameters`` section of the config file.
==================================
Initial fitting using downsampling
==================================
For complex datasets, doing an initial fit to get a first guess number of components
can speed up the fitting by a significant amount (15% or more). For this, we offer
a "downsampling" fitter, which will rebin the data in the x/y axis by a factor (default 10)
and fit these, before passing those to the full resolution fitting. This can also help to
improve the fits in areas of low signal-to-noise, but generally keeps the fit quality the same.
Note that by doing this, the only thing passed to the fitter is the number of components. It will
still then fit additional components and remove them independently per-spaxel.
.. code-block:: python
fit_dict_fitting_filename = os.path.join(fit_dir, downsample_fit_dir, fit_dict_filename)
n_comp_fitting_filename = os.path.join(fit_dir, downsample_fit_dir, n_comp_filename)
likelihood_fitting_filename = os.path.join(fit_dir, downsample_fit_dir, likelihood_filename)
hf_fitter.downsample_fitter(fit_dict_filename=fit_dict_fitting_filename,
n_comp_filename=n_comp_fitting_filename,
likelihood_filename=likelihood_fitting_filename,
downsample_factor=10,
)
=====================
Different fit methods
=====================
The default behaviour of mcfine is to use MCMC to explore every potential fit after the
initial guesses for parameters. This is robust, but slow. By setting ``fit_method = "leastsq"``
in the ``fit_parameters`` section of the config, you can instead just make use of the least-squares
guesses for the parameters, and then mcfine will only run an MCMC once, after a final number of
components and initial guesses for the parameters for each component has been calculated. This
leads to a speed up of around a factor 4 with a negligible change in the fit quality, so is useful
for large datasets.
===================
Different fit types
===================
We expect that most users will use LTE modelling, there also
exists options for RADEX and pure Gaussian fitting. These
are accessed by the ``fit_type`` parameter:
.. code-block:: toml
[fitting_params]
fit_type = "lte" # Can be "lte", "radex", or "pure_gauss"
===============
Initial guesses
===============
A significant amount of time is spent in producing the initial
guesses via ``lmfit``. This is because by default we use basinhopping,
which works but is slow. The majority of this is because getting initial
guesses for the velocities is critical, but difficult. We also offer
a method called ``iterative``, which finds peaks in the spectrum using
zero-crossings in the derivatives (via specutils). For the total number
of components, it will iteratively subtract off a model for the flux
(from previous fits in this iterative step), find peaks in the
model-subtracted flux (i.e. any unaccounted for emission), and then fit
all the components simultaneously once again (to avoid biases that can arise
by fitting one component at a time) in a small window around the various peaks.
This generally produces identical results in testing to the default method, but
runs significantly (about a factor 5 for a 4-component fit) faster.
If using ``iterative``, the strong recommendation for the minimization algorith
is ``powell``.
======================
``emcee`` optimization
======================
By default, we use a fixed number of walkers and steps in the emcee run. This
may not be optimal, so we also offer a couple of adaptive methods.
The first is you can adapt the number of walkers to the number of parameters
being fitted. A typical rule of thumb is to use **at least** twice the number
of walkers as parameters. This can be specified in the config file by using
a string for ``n_walkers`` under the ``[mcmc]`` section: if you use something like
.. code-block:: toml
[mcmc]
n_walkers = "3*n_params"
then the number of walkers will be adapted to the number of components being fit.
This can effectively speed up the fit and reduce sampler filesizes, without affecting
the final fit quality
The second is using an adaptive number of steps, checking against an autocorrelation criterion.
By default, we use a fixed number of steps, using a quarter of these as "burn-in", before a full
run. Then, when calculating parameters, we will discard the first half of this chain as parameters
may still be moving around. However, it may be more optimal to use an adaptive number of steps,
and tune this to some multiple of the autocorrelation length. More details of this can be found
in `the emcee docs `_. You
can switch to this mode like so:
.. code-block:: toml
[mcmc]
emcee_run_method = "adaptive"
this will use another few parameters:
.. code-block:: toml
[mcmc]
convergence_factor = 10
tau_change = 0.01
thin = 0.5
burn_in = 2
max_steps = 100000
The first two control when fitting will stop. We will step out of the MCMC run once the chain
is ``convergence_factor`` times the autocorrelation length, and the change in that factor varies
by less than a factor of ``tau_change``. To ensure the MCMC doesn't run forever, the maximum number
of steps is ``max_steps``.
After this, for both methods we then use ``thin`` and ``burn_in`` to control how the sampler is pared down for
parameter estimation. We use a factor of ``burn_in`` time the autocorrelation length to define the burn-in, and then
thin by a factor of ``thin`` times the autocorrelation length to thin out the chains. For more details on
these, see the `emcee docs `_.
============
Space saving
============
In some cases it may be worthwhile to try and save as much space as possible
for the output fit dictionaries. This is especially true for huge datasets
where the total space requirements can ramp up into the TB. By default,
the ``fit_dict`` files will contain both the full ``emcee`` sampler and
information for a covariance matrix. If your corner plots look well-behaved
(i.e. mostly Gaussian), then you can save a **significant** amount of space
by setting ``keep_sampler = false`` in ``[fitting_params]``.
=======================
Configurable parameters
=======================
To see the configurable parameters in McFine, there is an
in-built convenience function:
.. code-block:: python
from mcfine.utils import print_config_params
print_config_params()
This will list all the parameters, as well as their type
and default values. You can also see the default values
McFine will use in ``mcfine/toml/`` in the GitHub repository,
which may also be useful for those who aren't too familiar
with toml.
==================
Exploring samplers
==================
Although we expose convenience functions for exploring
the fits (see :doc:`here `), you
can also directly access the `emcee` sampler object:
.. code-block:: python
from mcfine.utils import load_fit_dict
fit_dict = load_fit_dict(file_name)
sampler = fit_dict["sampler"]
from there, you can mess around with this as you'd like.
=============================
Exploring covariance matrices
=============================
If you set ``keep_covariance`` to True in the config, then
along with parameter dictionaries a covariance dictionary
will also be saved out. You can load this in just like
a fit dictionary, and for each pixel fit (in i, j notation),
there will be a median array and covariance matrix. From this,
you can generate samples like:
.. code-block:: python
import numpy as np
samples = np.random.multivariate_normal(
par_dict[f"{i}_{j}"]["cov_med"],
par_dict[f"{i}_{j}"]["cov_matrix"],
size=10000,
)
===================
Adding another line
===================
It is possible to add other lines to McFine relatively
simply. The majority of the info just needs to be put
into ``line_info.py``. For the LTE case, these are
``v_lines`` and ``strength_lines``. For a single-peak
line, this is just 0 and 1. For RT there's
``transition_lines`` and ``freq_lines``. These should
descend from the RADEX naming scheme. Once you've added
those, include your new line in ``ALLOWED_LINES`` in
``fitting.py``, and edit the config file to use this
new line.
============================
Limiting number of processes
============================
McFine is highly multi-processed, but so are a number of
packages that McFine relies on which can cause issues,
especially on larger machines. To limit the number of threads
packages such as numpy will use, before you call your code you can
put (in the shell):
.. code-block:: shell
setenv MKL_NUM_THREADS 1
setenv NUMEXPR_NUM_THREADS 1
setenv OMP_NUM_THREADS 1
setenv OPENBLAS_NUM_THREADS 1
setenv GOTO_NUM_THREADS 1
or the equivalent EXPORT call, depending on the shell you use.
For macOS >= 10.13, you may also need to set
.. code-block:: shell
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES