--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.14.1 kernelspec: display_name: Python 3 language: python name: python3 --- # Constructing Models +++ _popkinmocks_ offers several options to construct models: * from simulation particles (`FromParticle`), * using a library of parameterised components (`ParametricComponent`), * as a mixture of other components (`Mixture`), * from a pixel representation of $p(t, v, \textbf{x}, z)$ (using the base `Component` class), * save and load existing models from file (using `dill`). The first four options are related via the following inheritance diagram: ```{eval-rst} .. inheritance-diagram:: popkinmocks.components.particle.FromParticle popkinmocks.components.stream.Stream popkinmocks.components.growing_disk.GrowingDisk popkinmocks.components.mixture.Mixture :top-classes: popkinmocks.components.Component :parts: 1 ``` Parameterised components are based on simple analytic equations while simulations can provide more physically self-consistent models. Different types of components have customised methods to improve various calculations e.g.: * we use exact expressions for Fourier transforms to evaluate datacubes for parameterised components, * we use [exact expressions](https://en.wikipedia.org/wiki/Mixture_distribution#Moments) to evaluate moments for mixture components. This notebook will demonstrate each option. To ensure that these examples run quickly when building the documentation, we will restrict the size of the problem to something manageable: we _thin_ the parameters of our SSP grid, and use fairly coarse spatial and velocity discretisations for the cube: ```{code-cell} import numpy as np np.random.seed(881230) np.seterr(divide='ignore', invalid='ignore') # hide some warnings import matplotlib.pyplot as plt import popkinmocks as pkm ssps = pkm.milesSSPs(thin_age=6, thin_z=2) cube = pkm.ifu_cube.IFUCube( ssps=ssps, nx1=21, nx2=21, nv=25, vrng=(-1000,1000) ) ``` ## Simulation data Let's create some random toy data representing (equal-mass) stellar particle data from a simulation: ```{code-cell} N = 10000 t = np.random.uniform(0, 13, N) # age [Gyr] v = np.random.normal(0, 200., N) # LOS velocity [km/s] x1 = np.random.normal(0, 0.4, N) # x1 position [arbitary] x2 = np.random.normal(0, 0.2, N) # x2 position [arbitary] z = np.random.uniform(-2.5, 0.6, N) # metallicty [M/H] ``` You can create a _popkinmocks_ component from this data as follows, :::{note} If particles aren't equal-mass, use the `mass_weights` argument of `FromParticle`. ::: ```{code-cell} simulation = pkm.components.FromParticle(cube, t, v, x1, x2, z) ``` The output tells you how many particles have been ignored as they fall outside our variable limits e.g. here 800 particles had a metallicity above the upper bound of our SSP grid and will not enter into any subsequent calculations. We can evaluate the datacube $\bar{y}$ for this component, ```{code-cell} simulation.evaluate_ybar() simulation.ybar.shape ``` This shape corresponds to our sampling in (log) wavelength and spatial dimensions. We could produce an image by summing over wavelength, ```{code-cell} image = np.sum(simulation.ybar, 0) _ = cube.imshow(image) ``` or look at the spatially-integrated spectrum by summing over both spatial dimensions, ```{code-cell} integrated_spectrum = np.sum(simulation.ybar, (1,2)) _ = cube.plot_spectrum(integrated_spectrum) ``` The large spikes at the start and end of this spectrum are unphysical artifacts - see [FAQs](faqs.md) for more info. ## Parameterised components The base `ParametricComponent` class arises from assuming (i) a specific factorisation of $p(t, v, \textbf{x}, z)$, and (ii) specific functional forms for factors of $p(t, v, \textbf{x}, z)$. The assumed factorisation is, $$ p(t, v, \textbf{x}, z) = p(t) p(\textbf{x}|t) p(v|t,\textbf{x}) p(z|t,\textbf{x}) \tag{1} \label{eq:factor_p} $$ The velocity factor $p(v|t,\textbf{x})$ depends on stellar age and position but not on metallicity. This is a simplifying assumption we have used to speed up evaluation of datacubes for `ParametricComponents`. The remaining assumptions are specific choices of the factors in $\eqref{eq:factor_p}$. Some of these are shared across all instances of `ParametricComponent`, while some are implemented for individual subclasses (i.e. `GrowingDisk` or `Stream`): * $p(t)$ is a beta distribution, * $p(\textbf{x}|t)$ implemented in subclasses, * $p(v|t,\textbf{x}) = \mathcal{N}(v ; \mu_v(\textbf{x},t),\sigma_v(\textbf{x},t))$, * the functions $\mu_v(\textbf{x},t)$ and $\sigma_v(\textbf{x},t)$ implemented in subclasses, * $p(z|t,\textbf{x})$ from chemical enrichment model in [Zhu et al. 20](https://arxiv.org/abs/2003.05561) (Section 3), * depends on a spatially varying depletion timescale $t_\mathrm{dep}(\textbf{x})$ * i.e. $p(z|t,\textbf{x}) = p(z|t;t_\mathrm{dep}(\textbf{x}))$ * the function $t_\mathrm{dep}(\textbf{x})$ implemented in subclasses To give you a feel for this chemical evolution model, here are three age-metallity distributions for different depletion timescales: ```{code-cell} fig, ax = plt.subplots(1, 3) parametric_cmp = pkm.components.parametric.ParametricComponent(cube=cube) for t_dep, ax0 in zip([0.5, 5., 13.], ax): p_z_t = parametric_cmp.evaluate_chemical_enrichment_model(t_dep) cube.imshow(p_z_t.T, view=['t', 'z'], colorbar=False, ax=ax0) ax0.set_title('$t_\mathrm{dep} = '+str(t_dep)+'$ Gyr') fig.tight_layout() ``` You can see that for a larger $t_\mathrm{dep}$, enrichment (i) is suppressed to lower overall metallicities, (ii) proceeds more slowly, and (ii) has a larger metal spread at fixed age. Here I'll demonstrate the two types of `ParametricComponent` currently implemented: `GrowingDisk` and `Stream`. ### `GrowingDisk` Initialise the disk component, ```{code-cell} disk = pkm.components.GrowingDisk(cube=cube, rotation=0., center=(0,0)) ``` and set its star formation history $p(t)$. This is a beta distribution parameterised with mean `0