<jupyter_start><jupyter_text>Sampling Spatial DistributionsTo make an artificial image of a stellar system, you need the fluxes of the stars _and_ their spatial positions. In the [Building Stellar Populations](https://artpop.readthedocs.io/en/latest/tutorials/pops.html) tutorial, we showed how to use **ArtPop** to generate stellar magnitudes. In this tutorial, we demonstrate how to sample spatial positions. At the moment, **ArtPop** provides functions for sampling the [Plummer](https://artpop.readthedocs.io/en/latest/api/artpop.space.plummer_xy.html), [Sersic](https://artpop.readthedocs.io/en/latest/api/artpop.space.sersic_xy.html), and [uniform](https://artpop.readthedocs.io/en/latest/api/artpop.space.uniform_xy.html) distributions.  Install ArtPop and its dependencies<jupyter_code>!pip install artpop
# Third-party imports
import numpy as np
import matplotlib.pyplot as plt
from astropy import units as u

# Project import
import artpop

# artpop's matplotlib style
plt.style.use(artpop.mpl_style)

# use this random state for reproducibility
rng = np.random.RandomState(9)<jupyter_output><empty_output><jupyter_text>The Plummer Distribution The Plummer distribution is sampled using [inverse transform sampling](https://en.wikipedia.org/wiki/Inverse_transform_sampling). To generate ``xy`` pixel positions that are distributed according to a Plummer distribution, use the [plummer_xy](https://artpop.readthedocs.io/en/latest/api/artpop.space.plummer_xy.html) function:<jupyter_code># keyword arguments for the plummer function
kws = dict(
    num_stars = 1e4,          # number of stars
    scale_radius = 50 * u.pc, # plummer scale radius
    distance = 55 * u.Mpc,    # distance to system
    xy_dim = (201, 201),      # xy dimensions of image
    pixel_scale = 0.2,        # pixel scale in pixel/arcsec
    random_state = rng        # random state for reproducibility
)

xy = artpop.plummer_xy(**kws)

plt.plot(xy[:, 0], xy[:, 1], 'o', ms=1)
plt.gca().set(xlim=(0, 200), ylim=(0, 200), aspect='equal', 
              xlabel='$x$ [pixel]', ylabel='$y$ [pixel]');<jupyter_output><empty_output><jupyter_text>The sampled positions are returned as a [NumPy Masked Array](https://numpy.org/doc/stable/reference/maskedarray.baseclass.htmlnumpy.ma.MaskedArray), ``xy``, where positions that are outside the image, based on the ``xy_dim`` parameter, are masked.If there are any stars outside the image, a warning will be printed. Let's move the system to a much closer distance so that we'll have some stars outside the image:<jupyter_code>kws['distance'] = 2 * u.Mpc

xy = artpop.plummer_xy(**kws)

plt.plot(xy[:, 0], xy[:, 1], 'o', ms=1, alpha=0.1)
plt.gca().set(xlim=(0, 200), ylim=(0, 200), aspect='equal', 
              xlabel='$x$ [pixel]', ylabel='$y$ [pixel]');<jupyter_output><empty_output><jupyter_text>The Sersic DistributionThe Sersic distribution is sampled within a discrete grid using [NumPy's choice function](https://docs.scipy.org/doc//numpy-1.10.4/reference/generated/numpy.random.choice.html). The ``num_r_eff`` parameter is used to set the number of effective radii within the discrete grid. You want this number to be large enough to ensure that you don't oversample small radii, but keep in mind that it is using a discrete grid, which requires more and more memory as ``num_r_eff`` increases.Use the [sersic_xy](https://artpop.readthedocs.io/en/latest/api/artpop.space.sersic_xy.htmlartpop.space.sersic_xy) function to sample positions from a Sersic distribution:<jupyter_code># note that all parameters with units can also be given 
# as floats, in which case a default unit (see doc string) 
# is assumed. e.g., r_eff will be assumed to be in kpc
xy = artpop.sersic_xy(
    num_stars = 1e5,     # the number of stars
    r_eff = 0.5,         # effective radius (kpc)
    n = 0.8,             # Sersic index
    theta = 45,          # position angle (deg)
    ellip = 0.3,         # ellipticity 
    distance = 15,       # distance to system (Mpc)
    xy_dim = 601,        # xy dimensions of image
    pixel_scale = 0.2,   # pixel scale in pixel/arcsec
    num_r_eff = 10,      # number of r_eff in discrete grid
    random_state = rng   # random state for reproducibility
)

plt.plot(xy[:, 0], xy[:, 1], 'o', ms=1, alpha=0.1)
plt.gca().set(xlim=(0, 600), ylim=(0, 600), aspect='equal', 
              xlabel='$x$ [pixel]', ylabel='$y$ [pixel]');<jupyter_output><empty_output><jupyter_text>Grid Sampling As noted above, **ArtPop** samples from a Sersic distribution using a discrete grid. Grid sampling for arbitrary two-dimensional functions is accessible to the user through the [xy_from_grid](https://artpop.readthedocs.io/en/latest/api/artpop.space.xy_from_grid.html) function. The input function is expected to be an [astropy model](https://docs.astropy.org/en/stable/modeling/predef_models2D.html). Here's an example using the [Ring2D](https://docs.astropy.org/en/stable/api/astropy.modeling.functional_models.Ring2D.htmlastropy.modeling.functional_models.Ring2D) model:<jupyter_code>from astropy.modeling.models import Ring2D

# initialize an astropy 2D ring model
ring = Ring2D(x_0=250, y_0=250, r_in=100, width=50)

xy = artpop.xy_from_grid(
    num_stars = 1e3,
    model = ring,
    xy_dim = (501, 501),
    random_state = rng
)

plt.plot(xy[:, 0], xy[:, 1], 'o', ms=1, alpha=0.5)
plt.gca().set(xlim=(0, 500), ylim=(0, 500), aspect='equal', 
              xlabel='$x$ [pixel]', ylabel='$y$ [pixel]');<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1232876712328767, 'fraction_numerical': 0.021369863013698632, 'mean_word_length': 4.332035053554041, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 12, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '297890', 'license': 'MIT', 'n_tokens_mistral': 1937, 'n_tokens_neox': 1799, 'n_words': 532, 'path': 'colab_tutorials/spatial.ipynb', 'repo_name': 'tbmiller-astro/ArtPop', 'size': '5475'}