"""A module for working with a parametric black holes.
Contains the BlackHole class for use with parametric systems. This houses
all the attributes and functionality related to parametric black holes.
Example usages::
bhs = BlackHole(
bolometric_luminosity,
mass,
accretion_rate,
epsilon,
inclination,
spin,
metallicity,
offset,
)
"""
import numpy as np
from unyt import Msun, cm, deg, erg, km, kpc, s, yr
from synthesizer import exceptions
from synthesizer.components.blackhole import BlackholesComponent
from synthesizer.emission_models.utils import get_param
from synthesizer.parametric.morphology import PointSource
from synthesizer.units import accepts
from synthesizer.utils.operation_timers import timed
[docs]
class BlackHole(BlackholesComponent):
"""The base parametric BlackHole class.
Attributes:
morphology (PointSource)
An instance of the PointSource morphology that describes the
location of this blackhole
"""
@accepts(
mass=Msun.in_base("galactic"),
accretion_rate=Msun.in_base("galactic") / yr,
inclination=deg,
offset=kpc,
bolometric_luminosity=erg / s,
hydrogen_density_blr=1 / cm**3,
hydrogen_density_nlr=1 / cm**3,
velocity_dispersion_blr=km / s,
velocity_dispersion_nlr=km / s,
theta_torus=deg,
)
def __init__(
self,
mass=None,
accretion_rate=None,
accretion_rate_eddington=None,
epsilon=0.1,
inclination=None,
spin=None,
offset=np.array([0.0, 0.0]) * kpc,
bolometric_luminosity=None,
metallicity=None,
ionisation_parameter_blr=0.1,
hydrogen_density_blr=1e9 / cm**3,
covering_fraction_blr=0.1,
velocity_dispersion_blr=2000 * km / s,
ionisation_parameter_nlr=0.01,
hydrogen_density_nlr=1e4 / cm**3,
covering_fraction_nlr=0.1,
velocity_dispersion_nlr=500 * km / s,
theta_torus=10 * deg,
fesc=None,
**kwargs,
):
"""Intialise the BlackHole instance.
Args:
mass (float):
The mass of each particle in Msun.
accretion_rate (float):
The accretion rate of the black hole in Msun/yr. No need to
provide if accretion_rate_eddington is given.
accretion_rate_eddington (float):
The accretion rate of the black hole in terms of the Eddington
accretion rate. No need to provide if accretion_rate is given.
epsilon (float):
The radiative efficiency. By default set to 0.1.
inclination (float):
The inclination of the black hole. Necessary for some disc
models.
spin (float):
The spin of the black hole. Necessary for some disc models.
offset (unyt_array):
The (x,y) offsets of the black hole relative to the centre of
the image. Units can be length or angle but should be
consistent with the scene.
bolometric_luminosity (float):
The bolometric luminosity
metallicity (float):
The metallicity of the region surrounding the/each black hole.
ionisation_parameter_blr (np.ndarray of float):
The ionisation parameter of the broad line region.
hydrogen_density_blr (np.ndarray of float):
The hydrogen density of the broad line region.
covering_fraction_blr (np.ndarray of float):
The covering fraction of the broad line region (effectively
the escape fraction).
velocity_dispersion_blr (np.ndarray of float):
The velocity dispersion of the broad line region.
ionisation_parameter_nlr (np.ndarray of float):
The ionisation parameter of the narrow line region.
hydrogen_density_nlr (np.ndarray of float):
The hydrogen density of the narrow line region.
covering_fraction_nlr (np.ndarray of float):
The covering fraction of the narrow line region (effectively
the escape fraction).
velocity_dispersion_nlr (np.ndarray of float):
The velocity dispersion of the narrow line region.
theta_torus (np.ndarray of float):
The angle of the torus.
fesc (np.ndarray of float):
The escape fraction of the black hole. If None then the
escape fraction is set to 0.0.
kwargs (dict):
Any parameter for the emission models can be provided as kwargs
here to override the defaults of the emission models.
"""
# Initialise base class
BlackholesComponent.__init__(
self,
fesc=fesc,
bolometric_luminosity=bolometric_luminosity,
mass=mass,
accretion_rate=accretion_rate,
accretion_rate_eddington=accretion_rate_eddington,
epsilon=epsilon,
inclination=inclination,
spin=spin,
metallicity=metallicity,
ionisation_parameter_blr=ionisation_parameter_blr,
hydrogen_density_blr=hydrogen_density_blr,
covering_fraction_blr=covering_fraction_blr,
velocity_dispersion_blr=velocity_dispersion_blr,
ionisation_parameter_nlr=ionisation_parameter_nlr,
hydrogen_density_nlr=hydrogen_density_nlr,
covering_fraction_nlr=covering_fraction_nlr,
velocity_dispersion_nlr=velocity_dispersion_nlr,
theta_torus=theta_torus,
**kwargs,
)
# By default a parametric black hole will explicitly have 1 "particle",
# set this here so that the downstream extraction can access the
# attribute.
self.nparticles = 1
self.nbh = 1
# Initialise morphology using the in-built point-source class
self.morphology = PointSource(offset=offset)
[docs]
@timed("BlackHole.get_mask")
def get_mask(
self,
attr,
thresh,
op,
mask=None,
attr_override_obj=None,
):
"""Return a mask based on the attribute and threshold.
Will derive a mask of the form attr op thresh, e.g. age > 10 Myr.
Args:
attr (str):
The attribute to derive the mask from.
thresh (float):
The threshold value.
op (str):
The operation to apply. Can be '<', '>', '<=', '>=', "==",
or "!=".
mask (np.ndarray):
Optionally, a mask to combine with the new mask.
attr_override_obj (object):
An alternative object to check from the attribute. This
is specifically used when an EmissionModel may have a
fixed parameter override, but can be used more generally.
Returns:
mask (np.ndarray):
The mask array.
"""
# Get the attribute
attr = get_param(attr, attr_override_obj, None, self)
# Resolve a string threshold as an attribute alias on the emitter
if isinstance(thresh, str):
thresh = get_param(thresh, attr_override_obj, None, self)
# Apply the operator
if op == ">":
new_mask = attr > thresh
elif op == "<":
new_mask = attr < thresh
elif op == ">=":
new_mask = attr >= thresh
elif op == "<=":
new_mask = attr <= thresh
elif op == "==":
new_mask = attr == thresh
elif op == "!=":
new_mask = attr != thresh
else:
raise exceptions.InconsistentArguments(
"Masking operation must be '<', '>', '<=', '>=', '==', or "
f"'!=', not {op}"
)
# Combine with the existing mask
if mask is not None:
new_mask = np.logical_and(new_mask, mask)
return new_mask
[docs]
def get_weighted_attr(self, *args, **kwargs):
"""Raise an error, weighted attributes are meaningless for a BlackHole.
Raises:
NotImplementedError
Parametric black holes are singular and so weighted attributes
make no sense.
"""
raise NotImplementedError(
"Parametric black holes are by definition singular "
"making weighted attributes non-sensical."
)
[docs]
def calculate_ionising_luminosity(self):
"""Calculates the ionising luminosity of the blackhole(s).
This requires that the disc_incident spectra be available.
Returns:
unyt_array:
The ionising photon production rate (s^-1).
"""
if "disc_incident" in self.spectra.keys():
return self.spectra[
"disc_incident"
].calculate_ionising_photon_production_rate()
else:
raise exceptions.MissingSpectraType(
"It is necessary to first calculate the disc_incident "
"spectra before calculating the ionising luminosity"
)