.. _buildplugin:

==================
Plugin Development
==================

Atmospheric retrievals are not an isolated science. We regularly use different codes and models from various fields
and contributors to better characterise exoplanetary systems. Often repetitive steps are needed to make use of an external code, and frequently, these are difficult to share or distribute to a broader audience.
Plugins are a new feature in TauREx 3.1 that allows developers to simplify the distribution and usage of their profile/models/chemistry etc., to other users for their retrievals.
The plugin system can be used to add the following new components:

    - :class:`~taurex.data.profiles.temperature.tprofile.TemperatureProfile`
    - :class:`~taurex.data.profiles.chemistry.chemistry.Chemistry`
    - :class:`~taurex.data.profiles.chemistry.gas.gas.Gas`
    - :class:`~taurex.data.profiles.pressure.pressureprofile.PressureProfile`
    - :class:`~taurex.data.stellar.star.Star`
    - :class:`~taurex.data.planet.Planet`
    - :class:`~taurex.model.model.ForwardModel`
    - :class:`~taurex.contributions.contribution.Contribution`
    - :class:`~taurex.opacity.opacity.Opacity`
    - :class:`~taurex.core.priors.Priors`
    - :class:`~taurex.optimizer.optimizer.Optimizer`
    - :class:`~taurex.mixin.core.Mixin`
  
Refer to the :ref:`Developers` on how to build each individual component.
This guide will outline how to package your new components into a TauREx plugin.


Anatomy of a Plugin
===================

Plugins are installable python packages that TauREx will integrate automatically into its pipeline.
Plugins can come from existing python libraries or dedicated packages. Dedicated packages only
contain TauREx components and generally have the name like ``taurex_something``. For example, 
a package that provides wrappers to the GGchem chemistry code would be called ``taurex_ggchem``
(which exists btw if you need it).

Importantly, open-source plugins should be registered to PyPI installable with a single command::
    
    > pip install taurex_myplugin

If they rely on an external FORTRAN/C++ code then they should be packaged into
a binary wheel distribution. We recommoned `cibuildwheel <cibuildwheel_>`_ for building these wheels.
After install the plugin will be automatically detected by TauREx::

    > taurex --plugins
    Successfully loaded plugins
    ---------------------------
    myplugin

A plugin, in its most basic form, points TauREx to the place where your components exist in your package.
This is accomplised through the ``entry_point`` parameter in ``setup.py`` of the plugin package::

    entry_points = {'taurex.plugins': 'myplugin = taurex_myplugin'}

    setup(name='taurex_myplugin',
          ..
          entry_points=entry_points,
          ..)

What this does is allow TauREx to access ``taurex_myplugin`` under ``taurex.plugins.myplugin``

Plugins can also be defined in existing packages as well. If you have a ``coolscience`` python
library and have built some TauREx components under ``coolscience.taurex`` then you can add to
your ``entry_point``::

    entry_points = {'taurex.plugins': 'coolscience = coolscience.taurex',
                    # ... other entrypoints
                    }

    setup(name='coolscience',
          ...
          entry_points=entry_points,
          ...)

The package will still be installable without TauREx. If later on someone installs TauREx then
they automatically get the plugin for free! Neat!


TauREx Hello World!
===================

Lets create a first plugin ``taurex_helloworld`` where 
we will define a new component: a randomized temperature profile. 
First we setup our folder structure::

    taurex_helloworld/
        __init__.py
        randomtemp.py
    LICENSE
    README.md
    setup.py

setup.py
--------

The most essential part is the :file:`setup.py` file to install the package and plugin.
The following is something you can work with::

    #!/usr/bin/env python
    import setuptools
    from setuptools import find_packages
    from setuptools import setup

    packages = find_packages(exclude=('tests', 'doc'))
    provides = ['taurex_helloworld', ]

    requires = []

    install_requires = ['taurex', ]

    entry_points = {'taurex.plugins': 'helloworld = taurex_helloworld'}

    setup(name='taurex_helloworld',
          url='http://example.com/taurex_helloworld',
          license='BSD',
          author='Your Name',
          author_email='your-email@example.com',
          description='Very short description',
          long_description=__doc__,
          packages=packages,
          entry_points=entry_points,
          provides=provides,
          requires=requires,
          install_requires=install_requires)

randomtemp.py
-------------

This is our random temperature profile, we will steal the implementation from
:ref:`customtypes` and change it a little:

.. code-block:: python

    from taurex.temperature import TemperatureProfile
    from taurex.core import fitparam
    import numpy as np


    class RandomTemperature(TemperatureProfile):

        def __init__(self, base_temp=1500.0,
                    random_scale=10.0):
            super().__init__(self.__class__.__name__)

            self._base_temp = base_temp
            self._random_scale = random_scale

        # -----Fitting Parameters--------------

        @fitparam(param_name='rand_scale',param_latex='rand')
        def randomScale(self):
            return self._random_scale

        @randomScale.setter
        def randomScale(self, value):
            self._random_scale = value

        @fitparam(param_name='base_T',param_latex='$T_{base}$')
        def baseTemperature(self):
            return self._base_temp

        @baseTemperature.setter
        def baseTemperature(self, value):
            self._base_temp = value

        # -------Actual calculation -----------

        @property
        def profile(self):
            return self._base_temp + \
                        np.random.rand(self.nlayers) * self._random_scale
        
        BIBTEX_ENTRIES = [
            """
            @article{myart,
                title={School of Life}, 
            """
        ]

        # -----Plugin related------------------

        @classmethod
        def input_keywords(cls):
            return ['helloworld', 'helloearth', 'hello-world',]

As before a terrible temperature profile we now include two extra parameters.
The class method ``input_keywords`` informs TauREx on how this temperature profile
is selected in the input file. It must return a list and can include more than one keyword.
If this plugin is installed we can use the profile through one of those keywords::

    [Temperature]
    profile_type = helloworld    # Valid keyword RandomTemperature
    # profile_type = helloearth  # Also valid
    # profile_type = hello-world # Also valid


The ``BIBTEX_ENTRIES`` parameter is used by TauREx to list relevant publications
involved with the atmospheric compoenent. See :ref:`basics` for more information.


__init__.py
-----------

We can use :file:`__init__.py` to expose the temperature profile to TauREx
by importing it like so:

.. code-block:: python

    from .randomtemp import RandomTemperature

.. tip::

    You could also just point the ``entry_point`` to ``taurex_helloworld.randomtemp``.
    However we recommend either putting it in an :file:`__init__.py` or defining
    another python file that includes these imports. This allows you to include
    components from different files and allows you to be selective on what to 
    expose to TauREx


Using our plugin
----------------

To use our plugin we can now do::

    pip install .

Running ``taurex --plugins`` we see::

    Successfully loaded plugins
    ---------------------------
    helloworld

Our plugin has now been loaded into TauREx! We can also see that our temperature
profile was detected as well by doing ``taurex --keywords temperature``::

    ╒═══════════════════════════════════════╤═══════════════════╤════════════╕
    │ profile_type                          │ Class             │ Source     │
    ╞═══════════════════════════════════════╪═══════════════════╪════════════╡
    │ file / fromfile                       │ TemperatureFile   │ taurex     │
    ├───────────────────────────────────────┼───────────────────┼────────────┤
    │ isothermal                            │ Isothermal        │ taurex     │
    ├───────────────────────────────────────┼───────────────────┼────────────┤
    │ guillot / guillot2010                 │ Guillot2010       │ taurex     │
    ├───────────────────────────────────────┼───────────────────┼────────────┤
    │ npoint                                │ NPoint            │ taurex     │
    ├───────────────────────────────────────┼───────────────────┼────────────┤
    │ helloworld / helloearth / hello-world │ RandomTemperature │ helloworld │
    ├───────────────────────────────────────┼───────────────────┼────────────┤
    │ rodgers / rodgers2010                 │ Rodgers2000       │ taurex     │
    ╘═══════════════════════════════════════╧═══════════════════╧════════════╛


Now we can write in the input file::

    [Temperature]
    profile_type = helloworld
    base_temp = 500.0
    random_scale = 100.0

Which gives us

.. figure::  _static/random_tp_500.png
   :align:   center

   *Still* terrible

This is a minimal guide to developing plugins but we always
recommend looking at plugin projects and seeing how they accomplish
their tasks.


.. _cibuildwheel: https://cibuildwheel.readthedocs.io/en/stable/





