"""
__Mass Profiles__

**PyAutoLens** uses `MassProfile` objects to represent a galaxy's mass distribution and perform ray-tracing
calculations. 

Below we create an `EllIsothermal` `MassProfile` and compute its deflection angles on our Cartesian grid:
"""
isothermal_mass_profile = al.mp.EllIsothermal(centre=(0.0, 0.0),
                                              elliptical_comps=(0.1, 0.0),
                                              einstein_radius=1.6)
deflections = isothermal_mass_profile.deflections_yx_2d_from(grid=grid)
"""
Lets plot the `MassProfile`'s deflection angle map.
"""
mass_profile_plotter = aplt.MassProfilePlotter(
    mass_profile=isothermal_mass_profile, grid=grid)
mass_profile_plotter.figures_2d(convergence=False,
                                potential=False,
                                deflections_y=True,
                                deflections_x=True)
"""
The deflection angles describe how a given mass distribution deflects the light-rays of the source galaxy, allowing
us create strong lens systems like the one shown above!

__Galaxies__

A `Galaxy` object is a collection of `LightProfile` and `MassProfile` objects at a given redshift. The code below 
creates two galaxies representing the lens and source galaxies shown in the strong lensing diagram above.
"""
lens_galaxy = al.Galaxy(redshift=0.5,
                        bulge=sersic_light_profile,
    elliptical_comps=al.convert.elliptical_comps_from(axis_ratio=0.7,
                                                      angle=45.0),
    slope=2.1,
)
"""
We also need the 2D grid the `MassProfile`'s are evaluated on.
"""
grid = al.Grid2D.uniform(shape_native=(100, 100), pixel_scales=0.05)
"""
We now pass the mass profiles and grid to a `MassProfilePlotter` and create a `MultiYX1DPlotter` which will be
used to plot both of their convergences in 1D on the same figure.
"""
mat_plot_1d = aplt.MatPlot1D(yx_plot=aplt.YXPlot(plot_axis_type="semilogy"))

mass_profile_plotter_0 = aplt.MassProfilePlotter(mass_profile=mass_0,
                                                 grid=grid,
                                                 mat_plot_1d=mat_plot_1d)
mass_profile_plotter_1 = aplt.MassProfilePlotter(mass_profile=mass_1,
                                                 grid=grid,
                                                 mat_plot_1d=mat_plot_1d)
"""
We use these plotters to create a `MultiYX1DPlotter` which plot both of their convergences in 1D on the same figure.
"""
multi_plotter = aplt.MultiYX1DPlotter(
    plotter_list=[mass_profile_plotter_0, mass_profile_plotter_1])
"""
We now use the multi plotter to plot the convergences, where:

 - `func_name`: he name of the `MassProfilePlotter` function we call, in this case `figures_1d`.
 - `figure_name`: the name of the function's boolean input we set to True such that it plots, in this case `convergence`.
 
"""
mass = al.mp.EllIsothermal(
    centre=(0.0, 0.0),
    einstein_radius=1.6,
    elliptical_comps=al.convert.elliptical_comps_from(axis_ratio=0.7,
                                                      angle=45.0),
)
"""
We also need the 2D grid the `MassProfile` is evaluated on.
"""
grid = al.Grid2D.uniform(shape_native=(100, 100), pixel_scales=0.05)
"""
We now pass the mass profile and grid to a `MassProfilePlotter` and call various `figure_*` methods to 
plot different attributes in 1D and 2D.
"""
mass_profile_plotter = aplt.MassProfilePlotter(mass_profile=mass, grid=grid)
mass_profile_plotter.figures_2d(
    convergence=True,
    potential=True,
    deflections_y=True,
    deflections_x=True,
    magnification=True,
)
mass_profile_plotter.figures_1d(convergence=True, potential=True)
"""
A `MassProfile` and its `Grid2D` contains the following attributes which can be plotted automatically via 
the `Include2D` object.

(By default, a `Grid2D` does not contain a `Mask2D`, we therefore manually created a `Grid2D` with a mask to illustrate
plotting its mask and border below).
"""
    einstein_radius=1.0,
)
"""
We also need the 2D grid the `MassProfile` is evaluated on.
"""
grid = al.Grid2D.uniform(shape_native=(100, 100), pixel_scales=0.05)
"""
We now pass the mass profile and grid to a `MassProfilePlotter` and call the `figures_1d` methods to plot its 
convergence as a function of radius.

The `MassProfile` includes the einstein radius as an internal property, meaning we can plot it via an `Include1D` 
object.
"""
include_1d = aplt.Include1D(einstein_radius=True)
mass_profile_plotter = aplt.MassProfilePlotter(mass_profile=mass,
                                               grid=grid,
                                               include_1d=include_1d)
mass_profile_plotter.figures_1d(convergence=True)
"""
The appearance of the einstein radius is customized using a `EinsteinRadiusAXVLine` object.

To plot the einstein radius as a vertical line this wraps the following matplotlib method:

 plt.axvline: https://matplotlib.org/3.3.2/api/_as_gen/matplotlib.pyplot.axvline.html
"""
einstein_radius_axvline = aplt.EinsteinRadiusAXVLine(linestyle="-.",
                                                     c="r",
                                                     linewidth=20)

mat_plot_1d = aplt.MatPlot1D(einstein_radius_axvline=einstein_radius_axvline)
(If you are still unclear what exactly a deflection angle means or how it will help us with gravitational lensing,
things should become a lot clearer in tutorial 4 of this chapter. For now, just look at the pretty pictures they make!).
"""
mass_profile_deflections = sis_mass_profile.deflections_yx_2d_from(grid=grid)

print("deflection-angles of `Grid2D` pixel 0:")
print(mass_profile_deflections.native[0, 0])
print("deflection-angles of `Grid2D` pixel 1:")
print(mass_profile_deflections.slim[1])
print()
"""
A `MassProfilePlotter` can plot the deflection angles.

(The black and red lines are called the `critical curve` and `caustic`. we'll cover what these are in a later tutorial.)
"""
mass_profile_plottter = aplt.MassProfilePlotter(mass_profile=sis_mass_profile,
                                                grid=grid)
mass_profile_plottter.figures_2d(deflections_y=True, deflections_x=True)
"""
__Other Properties__

`MassProfile`'s have a range of other properties that are used for lensing calculations, a couple of which we've plotted 
images of below:

 - `convergence`: The surface mass density of the mass profile in dimensionless units.
 - `potential`: The gravitational of the mass profile again in convenient dimensionless units.
 - `agnification`: Describes how much brighter each image-pixel appears due to focusing of light rays.

Extracting `Array2D`'s of these quantities from **PyAutoLens** is exactly the same as for the image and deflection 
angles above.
"""
mass_profile_convergence = sis_mass_profile.convergence_2d_from(grid=grid)