.. _extend_tutorial_polar_conversion:

Using the Polar Conversion Helper
=================================

Here is a list of tutorials and resources that this tutorial assumes you are already
familiar with.

- :ref:`math_polar_coordinates`
- :ref:`math_cartesian_coordinates`
- :ref:`extend_tutorial_domain_implementation`

There are occasions when writing a :code:`RealDomain` implementation, say a
:code:`Translation` transform where the "interesting" code is contained within the
:code:`_get_x` and :code:`_get_y` methods and all you need is a conversion from
cartesian to polar coordinates for the other methods.

This is where the :class:`PolarConversion <stylo.domain.helpers.PolarConversion>`
helper class comes in, it provides implementations of :code:`_get_r` and :code:`_get_t`
that convert the :math:`x` and :math:`y` values generated by your :code:`_get_x` and
:code:`_get_y` methods into their polar equivalents.

This tutorial walks you through the process of using this helper to implement an example
translation domain transform.

Setup
-----

As with any other domain transformation is starts with us creating a class and
subclassing :code:`RealDomainTransform`. However we also include
:class:`PolarConversion <stylo.domain.helpers.PolarConversion>` in our list of base
classes.

.. testcode:: extend_tutorial_polar_conversion_a

   from stylo.domain.helpers import PolarConversion
   from stylo.domain.transform import RealDomainTransform

   class Translation(PolarConversion, RealDomainTransform):
       pass

.. important::

   The order in which these classes are included is *very* important.
   :code:`PolarConversion` must come before :code:`RealDomainTransform`

If we were to try and create an instance of our class right now we will get an error
(as expected since :code:`RealDomainTransform` is an abstract class)

.. doctest:: extend_tutorial_polar_conversion_a

   >>> translate = Translation()
   Traceback (most recent call last):
      ...
   TypeError: Can't instantiate abstract class Translation with abstract methods _get_x, _get_y, _repr

But notice that :code:`_get_r` and :code:`_get_t` don't appear in the error message, this
is due to them being implemented by the :code:`PolarConversion` helper.

Implementation
--------------

If you've implemented a domain transform before, the rest should be familiar to you it's
a case of implementing the remaining methods.

- :code:`__init__`: We still need to take input from the user.
- :code:`_repr`: We need a textual representation of the object which will be used by the
  :code:`__repr__` method in the base class.
- :code:`_get_x`: A method to generate the :math:`x` values, here we will implement the
  horizontal shift
- :code:`_get_y`: A method to generate the :math:`y` values, here we will implement the
  vertical shift.

__init__
^^^^^^^^

The :code:`__init__` method is pretty straightforward, it's a case of taking a couple
of input values from the user to represent the translation. Just don't forget to call
the :code:`__init__` method on the base class!

.. code-block:: python

   def __init__(self, domain, dx, dy):
       super().__init__(domain)

       self.dx = dx
       self.dy = dy


_repr
^^^^^

Again nothing too interesting here, we need to return a string representing our
translation to the user.

.. code-block:: python

   def _repr(self):
      return "Translation: ({}, {})".format(self.dx, self.dy)

The reason domain transforms like :code:`RealDomainTransform` ask you to implement
:code:`_repr` and not :code:`__repr__` is that the base class implements
:code:`__repr__` in a way that also displays that the transformation is acting upon.

_get_x
^^^^^^

Here we implement the first half of the transform, the horizontal shift. First we need
to get the :math:`x` values from the domain we are acting on and then shift them by the
value given to us by the user.

.. code-block:: python

   def _get_x(self):

       xs = self.domain.x

       def mk_xs(width, height):
           return xs(width, height) - self.dx

       return mk_xs

Notice that we need to *take away* the value given by the user, this is so that when
they give a positive number the shape they draw is also moved in the positive direction.

_get_y
^^^^^^

Here we implement the second hald of the transform, the vertical shift. First we need to
get the :math:`y` values from the domain we are acting on and then shift them by the
value given by the user.

.. code-block:: python

   def _get_y(self):

       ys = self.domain.y

       def mk_ys(width, height):
           return ys(width, height) - self.dy

       return mk_ys

Notice that we need to *take away* the value given by the user, this is so that when they
give a positive number the shape they draw is also moved in the positive direction.


Bringing It All Together
------------------------

Here is the complete definition

.. testsetup:: extend_tutorial_polar_conversion_b


   from stylo.domain import UnitSquare
   from stylo.domain.helpers import PolarConversion
   from stylo.domain.transform import RealDomainTransform

   class Translation(PolarConversion, RealDomainTransform):

       def __init__(self, domain, dx, dy):
           super().__init__(domain)

           self.dx = dx
           self.dy = dy

       def _repr(self):
          return "Translation: ({}, {})".format(self.dx, self.dy)

       def _get_x(self):

           xs = self.domain.x

           def mk_xs(width, height):
               return xs(width, height) - self.dx

           return mk_xs

       def _get_y(self):

           ys = self.domain.y

           def mk_ys(width, height):
               return ys(width, height) - self.dy

           return mk_ys

.. doctest:: extend_tutorial_polar_conversion_b

   >>> translated = Translation(UnitSquare(), 1, 0)
   >>> translated
   Translation: (1, 0)
     UnitSquare: [0, 1] x [0, 1]

   >>> translated.r(4, 4)
   array([[1.41421356, 1.20185043, 1.05409255, 1.        ],
          [1.20185043, 0.94280904, 0.74535599, 0.66666667],
          [1.05409255, 0.74535599, 0.47140452, 0.33333333],
          [1.        , 0.66666667, 0.33333333, 0.        ]])
