8.1. Custom emission models¶
Custom emitters are implemented as Raysect materials with the VolumeEmitterInhomogeneous class. You model should inherit from this base class, only two methods need to be implemented, the __init__() and emission_function() methods.
The __init__() method should be used to setup any plasma parameters you will need access to for calculations. You could pass in the whole plasma object, or alternatively just the species you need. In this example, I will need the electron distribution and atomic deuterium distribution. Both of these can be passed in from the plasma object.
I’m also passing in a spectroscopic Line object which holds useful information about my emission line. But this can change alot from application to application. We are also loading some atomic data from ADAS. You could optionally pass in the atomic data you want to use in the __init__().
The step parameter is the only parameter required by the parent VolumeEmitterInhomogeneous class. This parameter determines the integration step size for sampling and will need to be adjusted based on your application’s scale lengths. Make ure you call the parent init whith the super method, e.g. super().__init__(step=step).
All the magic happens in the emission_function() method. This method is called at every point in space where the ray-tracer would like to know the emission. The arguments are fixed and are as follows:
point (Point3D) - current position in local primitive coordinates
direction (Vector3D) - current ray direction in local primitive coordinates
spectrum (Spectrum) - measured spectrum so far. Don’t overwrite it. Add your local emission to the measured spectrum. Units are in spectral radiance (W/m3/str/nm).
world (World) - the world being ray-traced. You may have multiple worlds.
ray (Ray) - the current ray being traced.
primitive (Primitive) - the primitive container for this material. Could be a sphere, cyliner, or CAD mesh for example.
to_local (AffineMatrix3D) - Affine matrix for coordinate transformations to local coordinates.
to_world (AffineMatrix3D) - Affine matrix for coordinate transformations to world coordinates.
Warning
Don’t override the spectrum parameter, you will loose all the previous ray samples. Instead you should add your local emission at the current point in space to the measured spectrum array.
Here is an example class implementation of an excitation line.
class ExcitationLine(VolumeEmitterInhomogeneous):
def __init__(self, line, electron_distribution, atom_species, step=0.005,
block=0, filename=None):
super().__init__(step=step)
self.line = line
self.electron_distribution = electron_distribution
self.atom_species = atom_species
# Function to load ADAS rate coefficients
# Replace this with your own atomic data as necessary.
self.pec_excitation = get_adf15_pec(line, 'EXCIT', filename=filename, block=block)
def emission_function(self, point, direction, spectrum, world, ray, primitive,
to_local, to_world):
##########################################
# Load all data you need for calculation #
# Get the current position in world coordinates,
# 'point' is in local primitive coordinates by default
x, y, z = point.transform(to_world)
# electron density n_e(x, y, z) at current point
ne = self.electron_distribution.density(x, y, z)
# electron temperature t_e(x, y, z) at current point
te = self.electron_distribution.effective_temperature(x, y, z)
# density of neutral atoms of species specified by line.element
na = self.atom_species.distribution.density(x, y, z)
# Electron temperature and density must be in valid range for ADAS data.
if not 5E13 < ne < 2E21:
return spectrum
if not 0.2 < te < 10000:
return spectrum
# Photo Emission Coefficient (PEC) for excitation at this temperature and density
pece = self.pec_excitation(ne, te)
# calculate line intensity
inty = 1E6 * (pece * ne * na)
weight = self.line.element.atomic_weight
rest_wavelength = self.line.wavelength
###############################
# Calculate the emission line #
# Calculate a simple gaussian line at each line wavelength in spectrum
# Add it to the existing spectrum. Don't override previous results!
sigma = sqrt(te * ELEMENTARY_CHARGE / (weight * AMU)) * rest_wavelength / SPEED_OF_LIGHT
i0 = inty/(sigma * sqrt(2 * PI))
width = 2*sigma**2
for i, wvl in enumerate(spectrum.wavelengths):
spectrum.samples[i] += i0 * exp(-(wvl - rest_wavelength)**2 / width)
return spectrum