Source code for pynbody.sph

"""
Low-level functionality for SPH interpolation and rendering

For most users, SPH functionality is accessed through the derived arrays `rho` and `smooth` in a snapshot object.
These arrays are calculated on-the-fly when accessed.

SPH-interpolated images and grids can also be generated. For most users, the :func:`pynbody.plot.sph.image` function
is the most convenient interface for rendering images of SPH simulations.

A slightly lower-level approach is to call :func:`render_image` directly; or to get closer still to the underlying
machinery, use :func:`~pynbody.sph.renderers.make_render_pipeline` to create a renderer object, which then produces
an image when the :meth:`~pynbody.sph.renderers.ImageRendererBase.render` method is called.

The :func:`to_3d_grid` function can be used to create a 3D grid of interpolated values from an SPH simulation. This
functionality is also a thin wrapper around the :func:`~pynbody.sph.renderers.make_render_pipeline` function and
its associated classes in the :mod:`~pynbody.sph.renderers` module.

"""

import copy
import logging
import math
import os
import sys
import threading
import time
import warnings
from time import process_time

import numpy as np
import scipy
import scipy.ndimage

logger = logging.getLogger('pynbody.sph')

from .. import array, config, config_parser, kdtree, snapshot, units, util
from . import kernels, renderers


[docs] @snapshot.simsnap.SimSnap.stable_derived_array def smooth(sim): """Return the smoothing length array for the simulation, using the configured number of neighbours""" sim.build_tree() logger.info('Smoothing with %d nearest neighbours' % config['sph']['smooth-particles']) sm = array.SimArray(np.empty(len(sim['pos']), dtype=sim['pos'].dtype), sim['pos'].units) start = time.time() sim.kdtree.set_array_ref('smooth', sm) sim.kdtree.populate('hsm', config['sph']['smooth-particles']) end = time.time() logger.info('Smoothing done in %5.3gs' % (end - start)) sim._kdtree_derived_smoothing = True return sm
def _get_smooth_array_ensuring_compatibility(sim): # On-disk smoothing information may conflict; KDTree assumes the number of nearest neighbours # is rigidly adhered to. Thus we must use our own self-consistent smoothing. if 'smooth' in sim: if not getattr(sim, '_kdtree_derived_smoothing', False): smooth_ar = smooth(sim) else: smooth_ar = sim['smooth'] else: sim['smooth'] = smooth_ar = smooth(sim) return smooth_ar
[docs] @snapshot.simsnap.SimSnap.stable_derived_array def rho(sim): """Return the SPH density array for the simulation, using the configured number of neighbours""" sim.build_tree() logger.info('Calculating SPH density') rho = array.SimArray( np.empty(len(sim['pos'])), sim['mass'].units / sim['pos'].units ** 3, dtype=sim['pos'].dtype) start = time.time() sim.kdtree.set_array_ref('smooth', _get_smooth_array_ensuring_compatibility(sim)) sim.kdtree.set_array_ref('mass', sim['mass']) sim.kdtree.set_array_ref('rho', rho) sim.kdtree.populate('rho', config['sph']['smooth-particles']) end = time.time() logger.info('Density calculation done in %5.3g s' % (end - start)) return rho
[docs] def render_spherical_image(snap, quantity='rho', nside=None, kernel=None, denoise=None, out_units=None, threaded=None, weight=None, qty=None): """Render an SPH image projected onto the sky around the origin. At present, only projection is supported (i.e., there is no implementation for rendering on a spherical shell). For example, if rendering density, the results are in units of mass per solid angle. The image is returned in healpix format, with the specified nside. Weighted projections are supported, e.g. one may look at the projected temperature, weighted by density, by passing 'temp' as the qty and 'rho' as the weight. Parameters ---------- snap : snapshot.simsnap.SimSnap The snapshot to render quantity : str | np.ndarray The name of the array within the simulation to render, or an actual array. Default 'rho' weight : str, bool, optional The name of the array within the simulation to use as a weight for averaging down the line of sight; or True to use volume weighting. nside : int The healpix nside resolution to use (must be power of 2) kernel : str, kernels.KernelBase, optional The Kernel object to use (defaults to 3D spline kernel) denoise : bool, optional if True, divide through by an estimate of the discreteness noise. The returned image is then not strictly an SPH estimate, but this option can be useful to reduce noise. out_units : str, optional The units to convert the output image into threaded : bool, optional Whether to render the image across multiple threads. Yes if true; no if false. The number of threads to be used is determined by the configuration file. If None, the use of threading is also determined by the configuration file. qty : str, optional Deprecated - use 'quantity' instead """ if qty is not None: warnings.warn("The 'qty' parameter is deprecated; use 'quantity' instead", DeprecationWarning) quantity = qty renderer = renderers.make_render_pipeline(snap, quantity, nside=nside, target='healpix', kernel=kernel, out_units=out_units, threaded=threaded, approximate_fast=False, weight=weight) return renderer.render()
[docs] def render_3d_grid(snap, quantity='rho', nx=None, ny=None, nz=None, width="10 kpc", x2=None, out_units=None, kernel=None, approximate_fast=None, threaded=None, denoise=None, qty=None): """Create a 3d grid via SPH interpolation Parameters ---------- snap : snapshot.simsnap.SimSnap The snapshot to render quantity : str | np.ndarray The name of the array within the simulation to render, or an actual array. Default 'rho' nx : int, optional The number of pixels wide to make the grid. If not specified, the default is to use the resolution from the configuration file. ny : int, optional The number of pixels tall to make the grid. If not specified, the default is to use the same as nx. nz : int, optional The number of pixels deep to make the grid. If not specified, the default is to use the same as nx. width : float, str, optional The width of the grid. x2 : float, optional Deprecated - use width instead. If provided, x2 overrides the width parameter and specifies half the width. out_units : str, optional The units to convert the output grid into kernel : str, kernels.KernelBase, optional The kernel to be used for the image rendering. If None, the default kernel is assigned. For more information see :func:`kernels.create_kernel`. approximate_fast : bool, optional Whether to render the image using a lower-resolution approximation for large smoothing lengths. The default is None, in which case the use of approximation is determined by the configuration file. threaded : bool, optional Whether to render the image across multiple threads. Yes if true; no if false. The number of threads to be used is determined by the configuration file. If None, the use of threading is also determined by the configuration file. denoise : bool, optional Whether to include denoising in the rendering process. If None, denoising is applied only if the image is likely to benefit from it. If True, denoising is to be forced on the image; if that is actually impossible, this routine raises an exception. If False, denoising is never applied. qty : str, optional Deprecated - use 'quantity' instead """ if x2 is not None: width = x2*2 if qty is not None: warnings.warn("The 'qty' parameter is deprecated; use 'quantity' instead", DeprecationWarning) quantity = qty renderer = renderers.make_render_pipeline(snap, quantity=quantity, resolution=nx, width=width, out_units = out_units, kernel = kernel, approximate_fast=approximate_fast, threaded=threaded, denoise=denoise, target='volume', nx=nx, ny=ny, nz=nz) return renderer.render()
[docs] @util.deprecated("to_3d_grid is deprecated; use render_3d_grid instead") def to_3d_grid(*args, **kwargs): """Deprecated alias for :func:`render_3d_grid`""" return render_3d_grid(*args, **kwargs)
[docs] def render_image(*args, **kwargs): """Render an SPH image. This is a wrapper around the :mod:`renderers` module for convenience. All arguments and keyword arguments are forwarded to :func:`renderers.make_render_pipeline`, and the result is immediately rendered. """ return renderers.make_render_pipeline(*args, **kwargs).render()