From 0964da44fe8fd1d8251a77e043ec3dc1dc6f3351 Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 30 Dec 2022 19:55:12 +0300 Subject: [PATCH 01/16] Move lineshapes to a dedicated submodule. Add Doppler and Zeeman effects to StarkBroadenedLine. --- cherab/core/model/beam/__init__.pxd | 1 + cherab/core/model/beam/beam_emission.pyx | 3 +- cherab/core/model/lineshape.pxd | 112 -- cherab/core/model/lineshape.pyx | 968 ------------------ cherab/core/model/lineshape/__init__.pxd | 25 + cherab/core/model/lineshape/__init__.py | 25 + cherab/core/model/lineshape/base.pxd | 37 + cherab/core/model/lineshape/base.pyx | 43 + cherab/core/model/lineshape/beam/__init__.pxd | 20 + cherab/core/model/lineshape/beam/__init__.py | 20 + cherab/core/model/lineshape/beam/base.pxd | 35 + cherab/core/model/lineshape/beam/base.pyx | 39 + cherab/core/model/lineshape/beam/mse.pxd | 30 + cherab/core/model/lineshape/beam/mse.pyx | 134 +++ cherab/core/model/lineshape/doppler.pxd | 26 + cherab/core/model/lineshape/doppler.pyx | 59 ++ cherab/core/model/lineshape/gaussian.pxd | 29 + cherab/core/model/lineshape/gaussian.pyx | 138 +++ cherab/core/model/lineshape/multiplet.pxd | 31 + cherab/core/model/lineshape/multiplet.pyx | 116 +++ cherab/core/model/lineshape/stark.pxd | 36 + cherab/core/model/lineshape/stark.pyx | 331 ++++++ cherab/core/model/lineshape/zeeman.pxd | 48 + cherab/core/model/lineshape/zeeman.pyx | 417 ++++++++ .../core/model/plasma/impact_excitation.pyx | 2 +- cherab/core/utility/constants.pxd | 2 + cherab/core/utility/constants.pyx | 2 + 27 files changed, 1647 insertions(+), 1082 deletions(-) delete mode 100644 cherab/core/model/lineshape.pxd delete mode 100644 cherab/core/model/lineshape.pyx create mode 100644 cherab/core/model/lineshape/__init__.pxd create mode 100644 cherab/core/model/lineshape/__init__.py create mode 100644 cherab/core/model/lineshape/base.pxd create mode 100644 cherab/core/model/lineshape/base.pyx create mode 100644 cherab/core/model/lineshape/beam/__init__.pxd create mode 100644 cherab/core/model/lineshape/beam/__init__.py create mode 100644 cherab/core/model/lineshape/beam/base.pxd create mode 100644 cherab/core/model/lineshape/beam/base.pyx create mode 100644 cherab/core/model/lineshape/beam/mse.pxd create mode 100644 cherab/core/model/lineshape/beam/mse.pyx create mode 100644 cherab/core/model/lineshape/doppler.pxd create mode 100644 cherab/core/model/lineshape/doppler.pyx create mode 100644 cherab/core/model/lineshape/gaussian.pxd create mode 100644 cherab/core/model/lineshape/gaussian.pyx create mode 100644 cherab/core/model/lineshape/multiplet.pxd create mode 100644 cherab/core/model/lineshape/multiplet.pyx create mode 100644 cherab/core/model/lineshape/stark.pxd create mode 100644 cherab/core/model/lineshape/stark.pyx create mode 100644 cherab/core/model/lineshape/zeeman.pxd create mode 100644 cherab/core/model/lineshape/zeeman.pyx diff --git a/cherab/core/model/beam/__init__.pxd b/cherab/core/model/beam/__init__.pxd index 8a3c6e22..c5a0ebbe 100644 --- a/cherab/core/model/beam/__init__.pxd +++ b/cherab/core/model/beam/__init__.pxd @@ -1 +1,2 @@ from cherab.core.model.beam.charge_exchange cimport BeamCXLine +from cherab.core.model.beam.beam_emission cimport BeamEmissionLine diff --git a/cherab/core/model/beam/beam_emission.pyx b/cherab/core/model/beam/beam_emission.pyx index 8ca55331..67c41ac1 100644 --- a/cherab/core/model/beam/beam_emission.pyx +++ b/cherab/core/model/beam/beam_emission.pyx @@ -22,7 +22,8 @@ cimport cython from libc.math cimport sqrt from raysect.core cimport Point3D, Vector3D -from cherab.core cimport Species, Plasma, Beam, Element, BeamEmissionPEC, Spectrum, AtomicData +from raysect.optical cimport Spectrum +from cherab.core cimport Species, Plasma, Beam, Element, BeamEmissionPEC, AtomicData from cherab.core.math.function cimport autowrap_function1d, autowrap_function2d from cherab.core.atomic.elements import Isotope, hydrogen from cherab.core.model.lineshape cimport BeamEmissionMultiplet diff --git a/cherab/core/model/lineshape.pxd b/cherab/core/model/lineshape.pxd deleted file mode 100644 index 2591efe3..00000000 --- a/cherab/core/model/lineshape.pxd +++ /dev/null @@ -1,112 +0,0 @@ -# cython: language_level=3 - -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - -import numpy as np -cimport numpy as np - -from raysect.optical cimport Spectrum, Point3D, Vector3D -from cherab.core cimport Line, Species, Plasma, Beam -from cherab.core.math cimport Function1D, Function2D -from cherab.core.math.integrators cimport Integrator1D -from cherab.core.atomic.zeeman cimport ZeemanStructure - - -cpdef double doppler_shift(double wavelength, Vector3D observation_direction, Vector3D velocity) - -cpdef double thermal_broadening(double wavelength, double temperature, double atomic_weight) - -cpdef Spectrum add_gaussian_line(double radiance, double wavelength, double sigma, Spectrum spectrum) - - -cdef class LineShapeModel: - - cdef: - Line line - double wavelength - Species target_species - Plasma plasma - Integrator1D integrator - - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum) - - -cdef class GaussianLine(LineShapeModel): - pass - - -cdef class MultipletLineShape(LineShapeModel): - - cdef: - int _number_of_lines - np.ndarray _multiplet - double[:,::1] _multiplet_mv - - -cdef class StarkBroadenedLine(LineShapeModel): - - cdef double _aij, _bij, _cij - - pass - - -cdef class ZeemanLineShapeModel(LineShapeModel): - - cdef double _polarisation - - pass - - -cdef class ZeemanTriplet(ZeemanLineShapeModel): - - pass - - -cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): - - cdef double _alpha, _beta, _gamma - - pass - - -cdef class ZeemanMultiplet(ZeemanLineShapeModel): - - cdef ZeemanStructure _zeeman_structure - - pass - - -cdef class BeamLineShapeModel: - - cdef: - - Line line - double wavelength - Beam beam - - cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, - Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum) - - -cdef class BeamEmissionMultiplet(BeamLineShapeModel): - - cdef: - - Function2D _sigma_to_pi - Function1D _sigma1_to_sigma0, _pi2_to_pi3, _pi4_to_pi3 diff --git a/cherab/core/model/lineshape.pyx b/cherab/core/model/lineshape.pyx deleted file mode 100644 index 69278560..00000000 --- a/cherab/core/model/lineshape.pyx +++ /dev/null @@ -1,968 +0,0 @@ -# cython: language_level=3 - -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - -import numpy as np -from scipy.special import hyp2f1 - -cimport numpy as np -from libc.math cimport sqrt, erf, M_SQRT2, floor, ceil, fabs -from raysect.optical.spectrum cimport new_spectrum -from raysect.core.math.function.float cimport Function1D - -from cherab.core cimport Plasma -from cherab.core.atomic.elements import hydrogen, deuterium, tritium, helium, helium3, beryllium, boron, carbon, nitrogen, oxygen, neon -from cherab.core.math.function cimport autowrap_function1d, autowrap_function2d -from cherab.core.math.integrators cimport GaussianQuadrature -from cherab.core.utility.constants cimport ATOMIC_MASS, ELEMENTARY_CHARGE, SPEED_OF_LIGHT - -cimport cython - -# required by numpy c-api -np.import_array() - - -cdef double RECIP_ATOMIC_MASS = 1 / ATOMIC_MASS - - -cdef double evamu_to_ms(double x): - return sqrt(2 * x * ELEMENTARY_CHARGE * RECIP_ATOMIC_MASS) - - -@cython.cdivision(True) -cpdef double doppler_shift(double wavelength, Vector3D observation_direction, Vector3D velocity): - """ - Calculates the Doppler shifted wavelength for a given velocity and observation direction. - - :param wavelength: The wavelength to Doppler shift in nanometers. - :param observation_direction: A Vector defining the direction of observation. - :param velocity: A Vector defining the relative velocity of the emitting source in m/s. - :return: The Doppler shifted wavelength in nanometers. - """ - cdef double projected_velocity - - # flow velocity projected on the direction of observation - observation_direction = observation_direction.normalise() - projected_velocity = velocity.dot(observation_direction) - - return wavelength * (1 + projected_velocity / SPEED_OF_LIGHT) - - -@cython.cdivision(True) -cpdef double thermal_broadening(double wavelength, double temperature, double atomic_weight): - """ - Returns the line width for a gaussian line as a standard deviation. - - :param wavelength: Central wavelength. - :param temperature: Temperature in eV. - :param atomic_weight: Atomic weight in AMU. - :return: Standard deviation of gaussian line. - """ - - # todo: add input sanity checks - return sqrt(temperature * ELEMENTARY_CHARGE / (atomic_weight * ATOMIC_MASS)) * wavelength / SPEED_OF_LIGHT - - -# the number of standard deviations outside the rest wavelength the line is considered to add negligible value (including a margin for safety) -DEF GAUSSIAN_CUTOFF_SIGMA = 10.0 - - -@cython.cdivision(True) -@cython.initializedcheck(False) -@cython.boundscheck(False) -@cython.wraparound(False) -cpdef Spectrum add_gaussian_line(double radiance, double wavelength, double sigma, Spectrum spectrum): - r""" - Adds a Gaussian line to the given spectrum and returns the new spectrum. - - The formula used is based on the following definite integral: - :math:`\frac{1}{\sigma \sqrt{2 \pi}} \int_{\lambda_0}^{\lambda_1} \exp(-\frac{(x-\mu)^2}{2\sigma^2}) dx = \frac{1}{2} \left[ -Erf(\frac{a-\mu}{\sqrt{2}\sigma}) +Erf(\frac{b-\mu}{\sqrt{2}\sigma}) \right]` - - :param float radiance: Intensity of the line in radiance. - :param float wavelength: central wavelength of the line in nm. - :param float sigma: width of the line in nm. - :param Spectrum spectrum: the current spectrum to which the gaussian line is added. - :return: - """ - - cdef double temp - cdef double cutoff_lower_wavelength, cutoff_upper_wavelength - cdef double lower_wavelength, upper_wavelength - cdef double lower_integral, upper_integral - cdef int start, end, i - - if sigma <= 0: - return spectrum - - # calculate and check end of limits - cutoff_lower_wavelength = wavelength - GAUSSIAN_CUTOFF_SIGMA * sigma - if spectrum.max_wavelength < cutoff_lower_wavelength: - return spectrum - - cutoff_upper_wavelength = wavelength + GAUSSIAN_CUTOFF_SIGMA * sigma - if spectrum.min_wavelength > cutoff_upper_wavelength: - return spectrum - - # locate range of bins where there is significant contribution from the gaussian (plus a health margin) - start = max(0, floor((cutoff_lower_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) - end = min(spectrum.bins, ceil((cutoff_upper_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) - - # add line to spectrum - temp = 1 / (M_SQRT2 * sigma) - lower_wavelength = spectrum.min_wavelength + start * spectrum.delta_wavelength - lower_integral = erf((lower_wavelength - wavelength) * temp) - for i in range(start, end): - - upper_wavelength = spectrum.min_wavelength + spectrum.delta_wavelength * (i + 1) - upper_integral = erf((upper_wavelength - wavelength) * temp) - - spectrum.samples_mv[i] += radiance * 0.5 * (upper_integral - lower_integral) / spectrum.delta_wavelength - - lower_wavelength = upper_wavelength - lower_integral = upper_integral - - return spectrum - - -cdef class LineShapeModel: - """ - A base class for building line shapes. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param Integrator1D integrator: Integrator1D instance to integrate the line shape - over the spectral bin. Default is None. - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, Integrator1D integrator=None): - - self.line = line - self.wavelength = wavelength - self.target_species = target_species - self.plasma = plasma - self.integrator = integrator - - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - raise NotImplementedError('Child lineshape class must implement this method.') - - -cdef class GaussianLine(LineShapeModel): - """ - Produces Gaussian line shape. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - - .. code-block:: pycon - - >>> from cherab.core.atomic import Line, deuterium - >>> from cherab.core.model import ExcitationLine, GaussianLine - >>> - >>> # Adding Gaussian line to the plasma model. - >>> d_alpha = Line(deuterium, 0, (3, 2)) - >>> excit = ExcitationLine(d_alpha, lineshape=GaussianLine) - >>> plasma.models.add(excit) - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma): - - super().__init__(line, wavelength, target_species, plasma) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef double ts, sigma, shifted_wavelength - cdef Vector3D ion_velocity - - ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) - if ts <= 0.0: - return spectrum - - ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) - - # calculate emission line central wavelength, doppler shifted along observation direction - shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) - - # calculate the line width - sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) - - return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) - - -DEF MULTIPLET_WAVELENGTH = 0 -DEF MULTIPLET_RATIO = 1 - - -cdef class MultipletLineShape(LineShapeModel): - """ - Produces Multiplet line shapes. - - The lineshape radiance is calculated from a base PEC rate that is unresolved. This - radiance is then divided over a number of components as specified in the multiplet - argument. The multiplet components are specified with an Nx2 array where N is the - number of components in the multiplet. The first axis of the array contains the - wavelengths of each component, the second contains the line ratio for each component. - The component line ratios must sum to one. For example: - - :param Line line: The emission line object for the base rate radiance calculation. - :param float wavelength: The rest wavelength of the base emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param multiplet: An Nx2 array that specifies the multiplet wavelengths and line ratios. - - .. code-block:: pycon - - >>> from cherab.core.atomic import Line, nitrogen - >>> from cherab.core.model import ExcitationLine, MultipletLineShape - >>> - >>> # multiplet specification in Nx2 array - >>> multiplet = [[403.509, 404.132, 404.354, 404.479, 405.692], [0.205, 0.562, 0.175, 0.029, 0.029]] - >>> - >>> # Adding the multiplet to the plasma model. - >>> nitrogen_II_404 = Line(nitrogen, 1, ("2s2 2p1 4f1 3G13.0", "2s2 2p1 3d1 3F10.0")) - >>> excit = ExcitationLine(nitrogen_II_404, lineshape=MultipletLineShape, lineshape_args=[multiplet]) - >>> plasma.models.add(excit) - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, - object multiplet): - - super().__init__(line, wavelength, target_species, plasma) - - multiplet = np.array(multiplet, dtype=np.float64) - - if not (len(multiplet.shape) == 2 and multiplet.shape[0] == 2): - raise ValueError("The multiplet specification must be an array of shape (Nx2).") - - if not multiplet[1,:].sum() == 1.0: - raise ValueError("The multiplet line ratios should sum to one.") - - self._number_of_lines = multiplet.shape[1] - self._multiplet = multiplet - self._multiplet_mv = self._multiplet - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef double ts, sigma, shifted_wavelength, component_wavelength, component_radiance - cdef Vector3D ion_velocity - - ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) - if ts <= 0.0: - return spectrum - - ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) - - # calculate the line width - sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) - - for i in range(self._number_of_lines): - - component_wavelength = self._multiplet_mv[MULTIPLET_WAVELENGTH, i] - component_radiance = radiance * self._multiplet_mv[MULTIPLET_RATIO, i] - - # calculate emission line central wavelength, doppler shifted along observation direction - shifted_wavelength = doppler_shift(component_wavelength, direction, ion_velocity) - - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - return spectrum - - -DEF LORENZIAN_CUTOFF_GAMMA = 50.0 - - -cdef class StarkFunction(Function1D): - """ - Normalised Stark function for the StarkBroadenedLine line shape. - """ - - cdef double _a, _x0, _norm - - STARK_NORM_COEFFICIENT = 4 * LORENZIAN_CUTOFF_GAMMA * hyp2f1(0.4, 1, 1.4, -(2 * LORENZIAN_CUTOFF_GAMMA)**2.5) - - def __init__(self, double wavelength, double lambda_1_2): - - if wavelength <= 0: - raise ValueError("Argument 'wavelength' must be positive.") - - if lambda_1_2 <= 0: - raise ValueError("Argument 'lambda_1_2' must be positive.") - - self._x0 = wavelength - self._a = (0.5 * lambda_1_2)**2.5 - # normalise, so the integral over x is equal to 1 in the limits - # (_x0 - LORENZIAN_CUTOFF_GAMMA * lambda_1_2, _x0 + LORENZIAN_CUTOFF_GAMMA * lambda_1_2) - self._norm = (0.5 * lambda_1_2)**1.5 / self.STARK_NORM_COEFFICIENT - - @cython.cdivision(True) - cdef double evaluate(self, double x) except? -1e999: - - return self._norm / ((fabs(x - self._x0))**2.5 + self._a) - - -cdef class StarkBroadenedLine(LineShapeModel): - """ - Parametrised Stark broadened line shape based on the Model Microfield Method (MMM). - Contains embedded atomic data in the form of fits to MMM. - Only Balmer and Paschen series are supported by default. - See B. Lomanowski, et al. "Inferring divertor plasma properties from hydrogen Balmer - and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) - `123028 `_. - - Call `show_supported_transitions()` to see the list of supported transitions and - default model coefficients. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param dict stark_model_coefficients: Alternative model coefficients in the form - {line_ij: (c_ij, a_ij, b_ij), ...}. - If None, the default model parameters will be used. - :param Integrator1D integrator: Integrator1D instance to integrate the line shape - over the spectral bin. Default is `GaussianQuadrature()`. - - """ - - STARK_MODEL_COEFFICIENTS_DEFAULT = { - Line(hydrogen, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(hydrogen, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(hydrogen, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(hydrogen, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(hydrogen, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(hydrogen, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(hydrogen, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(hydrogen, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(hydrogen, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(hydrogen, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(hydrogen, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(hydrogen, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(hydrogen, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), - Line(deuterium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(deuterium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(deuterium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(deuterium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(deuterium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(deuterium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(deuterium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(deuterium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(deuterium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(deuterium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(deuterium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(deuterium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(deuterium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), - Line(tritium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(tritium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(tritium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(tritium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(tritium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(tritium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(tritium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(tritium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(tritium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(tritium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(tritium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(tritium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(tritium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033) - } - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, - dict stark_model_coefficients=None, integrator=GaussianQuadrature()): - - stark_model_coefficients = stark_model_coefficients or self.STARK_MODEL_COEFFICIENTS_DEFAULT - - try: - # Fitted Stark Constants - cij, aij, bij = stark_model_coefficients[line] - if cij <= 0: - raise ValueError('Coefficient c_ij must be positive.') - if aij <= 0: - raise ValueError('Coefficient a_ij must be positive.') - if bij <= 0: - raise ValueError('Coefficient b_ij must be positive.') - self._aij = aij - self._bij = bij - self._cij = cij - except IndexError: - raise ValueError('Stark broadening coefficients for {} is not currently available.'.format(line)) - - super().__init__(line, wavelength, target_species, plasma, integrator) - - def show_supported_transitions(self): - """ Prints all supported transitions.""" - for line, coeff in self.STARK_MODEL_COEFFICIENTS_DEFAULT.items(): - print('{}: c_ij={}, a_ij={}, b_ij={}'.format(line, coeff[0], coeff[1], coeff[2])) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef: - double ne, te, lambda_1_2, lambda_5_2, wvl - double cutoff_lower_wavelength, cutoff_upper_wavelength - double lower_wavelength, upper_wavelength - double bin_integral - int start, end, i - Spectrum raw_lineshape - - ne = self.plasma.get_electron_distribution().density(point.x, point.y, point.z) - if ne <= 0.0: - return spectrum - - te = self.plasma.get_electron_distribution().effective_temperature(point.x, point.y, point.z) - if te <= 0.0: - return spectrum - - lambda_1_2 = self._cij * ne**self._aij / (te**self._bij) - - self.integrator.function = StarkFunction(self.wavelength, lambda_1_2) - - # calculate and check end of limits - cutoff_lower_wavelength = self.wavelength - LORENZIAN_CUTOFF_GAMMA * lambda_1_2 - if spectrum.max_wavelength < cutoff_lower_wavelength: - return spectrum - - cutoff_upper_wavelength = self.wavelength + LORENZIAN_CUTOFF_GAMMA * lambda_1_2 - if spectrum.min_wavelength > cutoff_upper_wavelength: - return spectrum - - # locate range of bins where there is significant contribution from the gaussian (plus a health margin) - start = max(0, floor((cutoff_lower_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) - end = min(spectrum.bins, ceil((cutoff_upper_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) - - # add line to spectrum - lower_wavelength = spectrum.min_wavelength + start * spectrum.delta_wavelength - - for i in range(start, end): - upper_wavelength = spectrum.min_wavelength + spectrum.delta_wavelength * (i + 1) - - bin_integral = self.integrator.evaluate(lower_wavelength, upper_wavelength) - spectrum.samples_mv[i] += radiance * bin_integral / spectrum.delta_wavelength - - lower_wavelength = upper_wavelength - - return spectrum - - -DEF BOHR_MAGNETON = 5.78838180123e-5 # in eV/T -DEF HC_EV_NM = 1239.8419738620933 # (Planck constant in eV s) x (speed of light in nm/s) - -DEF PI_POLARISATION = 0 -DEF SIGMA_POLARISATION = 1 -DEF SIGMA_PLUS_POLARISATION = 1 -DEF SIGMA_MINUS_POLARISATION = -1 -DEF NO_POLARISATION = 2 - - -cdef class ZeemanLineShapeModel(LineShapeModel): - r""" - A base class for building Zeeman line shapes. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: - "pi" - leave only :math:`\pi`-polarised components, - "sigma" - leave only :math:`\sigma`-polarised components, - "no" - leave all components (default). - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation): - super().__init__(line, wavelength, target_species, plasma) - - self.polarisation = polarisation - - @property - def polarisation(self): - if self._polarisation == PI_POLARISATION: - return 'pi' - if self._polarisation == SIGMA_POLARISATION: - return 'sigma' - if self._polarisation == NO_POLARISATION: - return 'no' - - @polarisation.setter - def polarisation(self, value): - if value.lower() == 'pi': - self._polarisation = PI_POLARISATION - elif value.lower() == 'sigma': - self._polarisation = SIGMA_POLARISATION - elif value.lower() == 'no': - self._polarisation = NO_POLARISATION - else: - raise ValueError('Select between "pi", "sigma" or "no", {} is unsupported.'.format(value)) - - -cdef class ZeemanTriplet(ZeemanLineShapeModel): - r""" - Simple Doppler-Zeeman triplet (Paschen-Back effect). - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: - "pi" - leave central component, - "sigma" - leave side components, - "no" - all components (default). - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation='no'): - - super().__init__(line, wavelength, target_species, plasma, polarisation) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef double ts, sigma, shifted_wavelength, photon_energy, b_magn, component_radiance, cos_sqr, sin_sqr - cdef Vector3D ion_velocity, b_field - - ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) - if ts <= 0.0: - return spectrum - - ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) - - # calculate emission line central wavelength, doppler shifted along observation direction - shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) - - # calculate the line width - sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) - - # obtain magnetic field - b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) - b_magn = b_field.get_length() - - if b_magn == 0: - # no splitting if magnetic field strength is zero - if self._polarisation == NO_POLARISATION: - return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) - - return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) - - # coefficients for intensities parallel and perpendicular to magnetic field - cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 - sin_sqr = 1. - cos_sqr - - # adding pi component of the Zeeman triplet in case of NO_POLARISATION or PI_POLARISATION - if self._polarisation != SIGMA_POLARISATION: - component_radiance = 0.5 * sin_sqr * radiance - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - # adding sigma +/- components of the Zeeman triplet in case of NO_POLARISATION or SIGMA_POLARISATION - if self._polarisation != PI_POLARISATION: - component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance - - photon_energy = HC_EV_NM / self.wavelength - - shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy - BOHR_MAGNETON * b_magn), direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy + BOHR_MAGNETON * b_magn), direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - return spectrum - - -cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): - r""" - Parametrised Doppler-Zeeman triplet. It takes into account additional broadening due to - the line's fine structure without resolving the individual components of the fine - structure. The model is described with three parameters: :math:`\alpha`, - :math:`\beta` and :math:`\gamma`. - - The distance between :math:`\sigma^+` and :math:`\sigma^-` peaks: - :math:`\Delta \lambda_{\sigma} = \alpha B`, - where `B` is the magnetic field strength. - The ratio between Zeeman and thermal broadening line widths: - :math:`\frac{W_{Zeeman}}{W_{Doppler}} = \beta T^{\gamma}`, - where `T` is the species temperature in eV. - - Call `show_supported_transitions()` to see the list of supported transitions and - default parameters of the model. - - For details see A. Blom and C. Jupén, Parametrisation of the Zeeman effect - for hydrogen-like spectra in high-temperature plasmas, - Plasma Phys. Control. Fusion 44 (2002) `1229-1241 - `_. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param dict line_parameters: Alternative parameters of the model in the form - {line_i: (alpha_i, beta_i, gamma_i), ...}. - If None, the default model parameters will be used. - :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: - "pi" - leave central component, - "sigma" - leave side components, - "no" - all components (default). - """ - - LINE_PARAMETERS_DEFAULT = { # alpha, beta, gamma parameters for selected lines - Line(hydrogen, 0, (3, 2)): (0.0402267, 0.3415, -0.5247), - Line(hydrogen, 0, (4, 2)): (0.0220724, 0.2837, -0.5346), - Line(deuterium, 0, (3, 2)): (0.0402068, 0.4384, -0.5015), - Line(deuterium, 0, (4, 2)): (0.0220610, 0.3702, -0.5132), - Line(helium3, 1, (4, 3)): (0.0205200, 1.4418, -0.4892), - Line(helium3, 1, (5, 3)): (0.0095879, 1.2576, -0.5001), - Line(helium3, 1, (6, 4)): (0.0401980, 0.8976, -0.4971), - Line(helium3, 1, (7, 4)): (0.0273538, 0.8529, -0.5039), - Line(helium, 1, (4, 3)): (0.0205206, 1.6118, -0.4838), - Line(helium, 1, (5, 3)): (0.0095879, 1.4294, -0.4975), - Line(helium, 1, (6, 4)): (0.0401955, 1.0058, -0.4918), - Line(helium, 1, (7, 4)): (0.0273521, 0.9563, -0.4981), - Line(beryllium, 3, (5, 4)): (0.0060354, 2.1245, -0.3190), - Line(beryllium, 3, (6, 5)): (0.0202754, 1.6538, -0.3192), - Line(beryllium, 3, (7, 5)): (0.0078966, 1.7017, -0.3348), - Line(beryllium, 3, (8, 6)): (0.0205025, 1.4581, -0.3450), - Line(boron, 4, (6, 5)): (0.0083423, 2.0519, -0.2960), - Line(boron, 4, (7, 6)): (0.0228379, 1.6546, -0.2941), - Line(boron, 4, (8, 6)): (0.0084065, 1.8041, -0.3177), - Line(boron, 4, (8, 7)): (0.0541883, 1.4128, -0.2966), - Line(boron, 4, (9, 7)): (0.0190781, 1.5440, -0.3211), - Line(boron, 4, (10, 8)): (0.0391914, 1.3569, -0.3252), - Line(carbon, 5, (6, 5)): (0.0040900, 2.4271, -0.2818), - Line(carbon, 5, (7, 6)): (0.0110398, 1.9785, -0.2816), - Line(carbon, 5, (8, 6)): (0.0040747, 2.1776, -0.3035), - Line(carbon, 5, (8, 7)): (0.0261405, 1.6689, -0.2815), - Line(carbon, 5, (9, 7)): (0.0092096, 1.8495, -0.3049), - Line(carbon, 5, (10, 8)): (0.0189020, 1.6191, -0.3078), - Line(carbon, 5, (11, 8)): (0.0110428, 1.6600, -0.3162), - Line(carbon, 5, (10, 9)): (0.0359009, 1.4464, -0.3104), - Line(nitrogen, 6, (7, 6)): (0.0060010, 2.4789, -0.2817), - Line(nitrogen, 6, (8, 7)): (0.0141271, 2.0249, -0.2762), - Line(nitrogen, 6, (9, 8)): (0.0300127, 1.7415, -0.2753), - Line(nitrogen, 6, (10, 8)): (0.0102089, 1.9464, -0.2975), - Line(nitrogen, 6, (11, 9)): (0.0193799, 1.7133, -0.2973), - Line(oxygen, 7, (8, 7)): (0.0083081, 2.4263, -0.2747), - Line(oxygen, 7, (9, 8)): (0.0176049, 2.0652, -0.2721), - Line(oxygen, 7, (10, 8)): (0.0059933, 2.3445, -0.2944), - Line(oxygen, 7, (10, 9)): (0.0343805, 1.8122, -0.2718), - Line(oxygen, 7, (11, 9)): (0.0113640, 2.0268, -0.2911), - Line(neon, 9, (9, 8)): (0.0072488, 2.8838, -0.2758), - Line(neon, 9, (10, 9)): (0.0141002, 2.4755, -0.2718), - Line(neon, 9, (11, 9)): (0.0046673, 2.8410, -0.2917), - Line(neon, 9, (11, 10)): (0.0257292, 2.1890, -0.2715) - } - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, dict line_parameters=None, polarisation='no'): - - super().__init__(line, wavelength, target_species, plasma, polarisation) - - line_parameters = line_parameters or self.LINE_PARAMETERS_DEFAULT - - try: - alpha, beta, gamma = line_parameters[self.line] - if alpha <= 0: - raise ValueError('Parameter alpha must be positive.') - if beta < 0: - raise ValueError('Parameter beta must be non-negative.') - self._alpha = alpha - self._beta = beta - self._gamma = gamma - - except KeyError: - raise ValueError('Data for {} is not available.'.format(self.line)) - - def show_supported_transitions(self): - """ Prints all supported transitions.""" - for line, param in self.LINE_PARAMETERS_DEFAULT.items(): - print('{}: alpha={}, beta={}, gamma={}'.format(line, param[0], param[1], param[2])) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef double ts, sigma, shifted_wavelength, b_magn, component_radiance, cos_sqr, sin_sqr - cdef Vector3D ion_velocity, b_field - - ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) - if ts <= 0.0: - return spectrum - - ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) - - # calculate emission line central wavelength, doppler shifted along observation direction - shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) - - # calculate the line width - sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) - - # fine structure broadening correction - sigma *= sqrt(1. + self._beta * self._beta * ts**(2. * self._gamma)) - - # obtain magnetic field - b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) - b_magn = b_field.get_length() - - if b_magn == 0: - # no splitting if magnetic filed strength is zero - if self._polarisation == NO_POLARISATION: - return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) - - return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) - - # coefficients for intensities parallel and perpendicular to magnetic field - cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 - sin_sqr = 1. - cos_sqr - - # adding pi component of the Zeeman triplet in case of NO_POLARISATION or PI_POLARISATION - if self._polarisation != SIGMA_POLARISATION: - component_radiance = 0.5 * sin_sqr * radiance - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - # adding sigma +/- components of the Zeeman triplet in case of NO_POLARISATION or SIGMA_POLARISATION - if self._polarisation != PI_POLARISATION: - component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance - - shifted_wavelength = doppler_shift(self.wavelength + 0.5 * self._alpha * b_magn, direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - shifted_wavelength = doppler_shift(self.wavelength - 0.5 * self._alpha * b_magn, direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) - - return spectrum - - -cdef class ZeemanMultiplet(ZeemanLineShapeModel): - r""" - Doppler-Zeeman Multiplet. - - The lineshape radiance is calculated from a base PEC rate that is unresolved. This - radiance is then divided over a number of components as specified in the ``zeeman_structure`` - argument. The ``zeeman_structure`` specifies wavelengths and ratios of - :math:`\pi`-/:math:`\sigma`-polarised components as functions of the magnetic field strength. - These functions can be obtained using the output of the ADAS603 routines. - - :param Line line: The emission line object for the base rate radiance calculation. - :param float wavelength: The rest wavelength of the base emission line. - :param Species target_species: The target plasma species that is emitting. - :param Plasma plasma: The emitting plasma object. - :param zeeman_structure: A ``ZeemanStructure`` object that provides wavelengths and ratios - of :math:`\pi`-/:math:`\sigma^{+}`-/:math:`\sigma^{-}`-polarised - components for any given magnetic field strength. - :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: - "pi" - leave only :math:`\pi`-polarised components, - "sigma" - leave only :math:`\sigma`-polarised components, - "no" - leave all components (default). - - """ - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, - ZeemanStructure zeeman_structure, polarisation='no'): - - super().__init__(line, wavelength, target_species, plasma, polarisation) - - self._zeeman_structure = zeeman_structure - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): - - cdef int i - cdef double ts, sigma, shifted_wavelength, component_radiance - cdef Vector3D ion_velocity - cdef double[:, :] multiplet_pi_mv, multiplet_sigma_mv - - ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) - if ts <= 0.0: - return spectrum - - ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) - - # calculate the line width - sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) - - # obtain magnetic field - b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) - b_magn = b_field.get_length() - - if b_magn == 0: - # no splitting if magnetic filed strength is zero - shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) - if self._polarisation == NO_POLARISATION: - return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) - - return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) - - # coefficients for intensities parallel and perpendicular to magnetic field - cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 - sin_sqr = 1. - cos_sqr - - # adding pi components of the Zeeman multiplet in case of NO_POLARISATION or PI_POLARISATION - if self._polarisation != SIGMA_POLARISATION: - component_radiance = 0.5 * sin_sqr * radiance - multiplet_mv = self._zeeman_structure.evaluate(b_magn, PI_POLARISATION) - - for i in range(multiplet_mv.shape[1]): - shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) - - # adding sigma components of the Zeeman multiplet in case of NO_POLARISATION or SIGMA_POLARISATION - if self._polarisation != PI_POLARISATION: - component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance - - multiplet_mv = self._zeeman_structure.evaluate(b_magn, SIGMA_PLUS_POLARISATION) - - for i in range(multiplet_mv.shape[1]): - shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) - - multiplet_mv = self._zeeman_structure.evaluate(b_magn, SIGMA_MINUS_POLARISATION) - - for i in range(multiplet_mv.shape[1]): - shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) - spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) - - return spectrum - - -cdef class BeamLineShapeModel: - """ - A base class for building beam emission line shapes. - - :param Line line: The emission line object for this line shape. - :param float wavelength: The rest wavelength for this emission line. - :param Beam beam: The beam class that is emitting. - """ - - def __init__(self, Line line, double wavelength, Beam beam): - - self.line = line - self.wavelength = wavelength - self.beam = beam - - cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, - Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum): - raise NotImplementedError('Child lineshape class must implement this method.') - - -DEF STARK_SPLITTING_FACTOR = 2.77e-8 - - -cdef class BeamEmissionMultiplet(BeamLineShapeModel): - """ - Produces Beam Emission Multiplet line shape, also known as the Motional Stark Effect spectrum. - """ - - def __init__(self, Line line, double wavelength, Beam beam, object sigma_to_pi, - object sigma1_to_sigma0, object pi2_to_pi3, object pi4_to_pi3): - - super().__init__(line, wavelength, beam) - - self._sigma_to_pi = autowrap_function2d(sigma_to_pi) - self._sigma1_to_sigma0 = autowrap_function1d(sigma1_to_sigma0) - self._pi2_to_pi3 = autowrap_function1d(pi2_to_pi3) - self._pi4_to_pi3 = autowrap_function1d(pi4_to_pi3) - - @cython.cdivision(True) - cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, - Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum): - - cdef double x, y, z - cdef Plasma plasma - cdef double te, ne, beam_energy, sigma, stark_split, beam_ion_mass, beam_temperature - cdef double natural_wavelength, central_wavelength - cdef double sigma_to_pi, d, intensity_sig, intensity_pi, e_field - cdef double s1_to_s0, intensity_s0, intensity_s1 - cdef double pi2_to_pi3, pi4_to_pi3, intensity_pi2, intensity_pi3, intensity_pi4 - cdef Vector3D b_field, beam_velocity - - # extract for more compact code - x = plasma_point.x - y = plasma_point.y - z = plasma_point.z - - plasma = self.beam.get_plasma() - - te = plasma.get_electron_distribution().effective_temperature(x, y, z) - if te <= 0.0: - return spectrum - - ne = plasma.get_electron_distribution().density(x, y, z) - if ne <= 0.0: - return spectrum - - beam_energy = self.beam.get_energy() - - # calculate Stark splitting - b_field = plasma.get_b_field().evaluate(x, y, z) - beam_velocity = beam_direction.normalise().mul(evamu_to_ms(beam_energy)) - e_field = beam_velocity.cross(b_field).get_length() - stark_split = fabs(STARK_SPLITTING_FACTOR * e_field) # TODO - calculate splitting factor? Reject other lines? - - # calculate emission line central wavelength, doppler shifted along observation direction - natural_wavelength = self.wavelength - central_wavelength = doppler_shift(natural_wavelength, observation_direction, beam_velocity) - - # calculate doppler broadening - beam_ion_mass = self.beam.get_element().atomic_weight - beam_temperature = self.beam.get_temperature() - sigma = thermal_broadening(self.wavelength, beam_temperature, beam_ion_mass) - - # calculate relative intensities of sigma and pi lines - sigma_to_pi = self._sigma_to_pi.evaluate(ne, beam_energy) - d = 1 / (1 + sigma_to_pi) - intensity_sig = sigma_to_pi * d * radiance - intensity_pi = 0.5 * d * radiance - - # add Sigma lines to output - s1_to_s0 = self._sigma1_to_sigma0.evaluate(ne) - intensity_s0 = 1 / (s1_to_s0 + 1) - intensity_s1 = 0.5 * s1_to_s0 * intensity_s0 - - spectrum = add_gaussian_line(intensity_sig * intensity_s0, central_wavelength, sigma, spectrum) - spectrum = add_gaussian_line(intensity_sig * intensity_s1, central_wavelength + stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_sig * intensity_s1, central_wavelength - stark_split, sigma, spectrum) - - # add Pi lines to output - pi2_to_pi3 = self._pi2_to_pi3.evaluate(ne) - pi4_to_pi3 = self._pi4_to_pi3.evaluate(ne) - intensity_pi3 = 1 / (1 + pi2_to_pi3 + pi4_to_pi3) - intensity_pi2 = pi2_to_pi3 * intensity_pi3 - intensity_pi4 = pi4_to_pi3 * intensity_pi3 - - spectrum = add_gaussian_line(intensity_pi * intensity_pi2, central_wavelength + 2 * stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_pi * intensity_pi2, central_wavelength - 2 * stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_pi * intensity_pi3, central_wavelength + 3 * stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_pi * intensity_pi3, central_wavelength - 3 * stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_pi * intensity_pi4, central_wavelength + 4 * stark_split, sigma, spectrum) - spectrum = add_gaussian_line(intensity_pi * intensity_pi4, central_wavelength - 4 * stark_split, sigma, spectrum) - - return spectrum diff --git a/cherab/core/model/lineshape/__init__.pxd b/cherab/core/model/lineshape/__init__.pxd new file mode 100644 index 00000000..1eeb4f7c --- /dev/null +++ b/cherab/core/model/lineshape/__init__.pxd @@ -0,0 +1,25 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.model.lineshape.beam cimport BeamLineShapeModel, BeamEmissionMultiplet +from cherab.core.model.lineshape.base cimport LineShapeModel +from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening +from cherab.core.model.lineshape.gaussian cimport add_gaussian_line, GaussianLine +from cherab.core.model.lineshape.multiplet cimport MultipletLineShape +from cherab.core.model.lineshape.stark cimport StarkBroadenedLine +from cherab.core.model.lineshape.zeeman cimport ZeemanLineShapeModel, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet diff --git a/cherab/core/model/lineshape/__init__.py b/cherab/core/model/lineshape/__init__.py new file mode 100644 index 00000000..e0ad6a24 --- /dev/null +++ b/cherab/core/model/lineshape/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from .beam import BeamLineShapeModel, BeamEmissionMultiplet +from .base import LineShapeModel +from .doppler import doppler_shift, thermal_broadening +from .gaussian import add_gaussian_line, GaussianLine +from .multiplet import MultipletLineShape +from .stark import StarkBroadenedLine +from .zeeman import ZeemanLineShapeModel, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet diff --git a/cherab/core/model/lineshape/base.pxd b/cherab/core/model/lineshape/base.pxd new file mode 100644 index 00000000..4ec2d2e6 --- /dev/null +++ b/cherab/core/model/lineshape/base.pxd @@ -0,0 +1,37 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from raysect.optical cimport Spectrum, Point3D, Vector3D +from cherab.core.atomic cimport Line +from cherab.core.species cimport Species +from cherab.core.plasma cimport Plasma +from cherab.core.math.integrators cimport Integrator1D + + +cdef class LineShapeModel: + + cdef: + Line line + double wavelength + Species target_species + Plasma plasma + Integrator1D integrator + + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum) diff --git a/cherab/core/model/lineshape/base.pyx b/cherab/core/model/lineshape/base.pyx new file mode 100644 index 00000000..7577508e --- /dev/null +++ b/cherab/core/model/lineshape/base.pyx @@ -0,0 +1,43 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + + +cdef class LineShapeModel: + """ + A base class for building line shapes. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param Integrator1D integrator: Integrator1D instance to integrate the line shape + over the spectral bin. Default is None. + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, Integrator1D integrator=None): + + self.line = line + self.wavelength = wavelength + self.target_species = target_species + self.plasma = plasma + self.integrator = integrator + + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + raise NotImplementedError('Child lineshape class must implement this method.') diff --git a/cherab/core/model/lineshape/beam/__init__.pxd b/cherab/core/model/lineshape/beam/__init__.pxd new file mode 100644 index 00000000..6172d6c3 --- /dev/null +++ b/cherab/core/model/lineshape/beam/__init__.pxd @@ -0,0 +1,20 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.model.lineshape.beam.base cimport * +from cherab.core.model.lineshape.beam.mse cimport * diff --git a/cherab/core/model/lineshape/beam/__init__.py b/cherab/core/model/lineshape/beam/__init__.py new file mode 100644 index 00000000..48d34582 --- /dev/null +++ b/cherab/core/model/lineshape/beam/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from .base import BeamLineShapeModel +from .mse import BeamEmissionMultiplet diff --git a/cherab/core/model/lineshape/beam/base.pxd b/cherab/core/model/lineshape/beam/base.pxd new file mode 100644 index 00000000..b3da2323 --- /dev/null +++ b/cherab/core/model/lineshape/beam/base.pxd @@ -0,0 +1,35 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from raysect.optical cimport Spectrum, Point3D, Vector3D +from cherab.core.atomic cimport Line +from cherab.core.beam cimport Beam + + +cdef class BeamLineShapeModel: + + cdef: + + Line line + double wavelength + Beam beam + + cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, + Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum) diff --git a/cherab/core/model/lineshape/beam/base.pyx b/cherab/core/model/lineshape/beam/base.pyx new file mode 100644 index 00000000..793e17d4 --- /dev/null +++ b/cherab/core/model/lineshape/beam/base.pyx @@ -0,0 +1,39 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + + +cdef class BeamLineShapeModel: + """ + A base class for building beam emission line shapes. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Beam beam: The beam class that is emitting. + """ + + def __init__(self, Line line, double wavelength, Beam beam): + + self.line = line + self.wavelength = wavelength + self.beam = beam + + cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, + Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum): + raise NotImplementedError('Child lineshape class must implement this method.') diff --git a/cherab/core/model/lineshape/beam/mse.pxd b/cherab/core/model/lineshape/beam/mse.pxd new file mode 100644 index 00000000..a7b6131a --- /dev/null +++ b/cherab/core/model/lineshape/beam/mse.pxd @@ -0,0 +1,30 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.math cimport Function1D, Function2D +from cherab.core.model.lineshape.beam.base cimport BeamLineShapeModel + + +cdef class BeamEmissionMultiplet(BeamLineShapeModel): + + cdef: + + Function2D _sigma_to_pi + Function1D _sigma1_to_sigma0, _pi2_to_pi3, _pi4_to_pi3 diff --git a/cherab/core/model/lineshape/beam/mse.pyx b/cherab/core/model/lineshape/beam/mse.pyx new file mode 100644 index 00000000..3075cff2 --- /dev/null +++ b/cherab/core/model/lineshape/beam/mse.pyx @@ -0,0 +1,134 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from libc.math cimport fabs, sqrt +from raysect.optical cimport Spectrum, Point3D, Vector3D + +from cherab.core.plasma cimport Plasma +from cherab.core.beam cimport Beam +from cherab.core.atomic cimport Line +from cherab.core.math.function cimport autowrap_function1d, autowrap_function2d +from cherab.core.utility.constants cimport ATOMIC_MASS, ELEMENTARY_CHARGE +from cherab.core.model.lineshape.gaussian cimport add_gaussian_line +from cherab.core.model.lineshape.doppler cimport thermal_broadening, doppler_shift + +cimport cython + + +cdef double RECIP_ATOMIC_MASS = 1 / ATOMIC_MASS + + +cdef double evamu_to_ms(double x): + return sqrt(2 * x * ELEMENTARY_CHARGE * RECIP_ATOMIC_MASS) + + +DEF STARK_SPLITTING_FACTOR = 2.77e-8 + + +cdef class BeamEmissionMultiplet(BeamLineShapeModel): + """ + Produces Beam Emission Multiplet line shape, also known as the Motional Stark Effect spectrum. + """ + + def __init__(self, Line line, double wavelength, Beam beam, object sigma_to_pi, + object sigma1_to_sigma0, object pi2_to_pi3, object pi4_to_pi3): + + super().__init__(line, wavelength, beam) + + self._sigma_to_pi = autowrap_function2d(sigma_to_pi) + self._sigma1_to_sigma0 = autowrap_function1d(sigma1_to_sigma0) + self._pi2_to_pi3 = autowrap_function1d(pi2_to_pi3) + self._pi4_to_pi3 = autowrap_function1d(pi4_to_pi3) + + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, + Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum): + + cdef double x, y, z + cdef Plasma plasma + cdef double te, ne, beam_energy, sigma, stark_split, beam_ion_mass, beam_temperature + cdef double natural_wavelength, central_wavelength + cdef double sigma_to_pi, d, intensity_sig, intensity_pi, e_field + cdef double s1_to_s0, intensity_s0, intensity_s1 + cdef double pi2_to_pi3, pi4_to_pi3, intensity_pi2, intensity_pi3, intensity_pi4 + cdef Vector3D b_field, beam_velocity + + # extract for more compact code + x = plasma_point.x + y = plasma_point.y + z = plasma_point.z + + plasma = self.beam.get_plasma() + + te = plasma.get_electron_distribution().effective_temperature(x, y, z) + if te <= 0.0: + return spectrum + + ne = plasma.get_electron_distribution().density(x, y, z) + if ne <= 0.0: + return spectrum + + beam_energy = self.beam.get_energy() + + # calculate Stark splitting + b_field = plasma.get_b_field().evaluate(x, y, z) + beam_velocity = beam_direction.normalise().mul(evamu_to_ms(beam_energy)) + e_field = beam_velocity.cross(b_field).get_length() + stark_split = fabs(STARK_SPLITTING_FACTOR * e_field) # TODO - calculate splitting factor? Reject other lines? + + # calculate emission line central wavelength, doppler shifted along observation direction + natural_wavelength = self.wavelength + central_wavelength = doppler_shift(natural_wavelength, observation_direction, beam_velocity) + + # calculate doppler broadening + beam_ion_mass = self.beam.get_element().atomic_weight + beam_temperature = self.beam.get_temperature() + sigma = thermal_broadening(self.wavelength, beam_temperature, beam_ion_mass) + + # calculate relative intensities of sigma and pi lines + sigma_to_pi = self._sigma_to_pi.evaluate(ne, beam_energy) + d = 1 / (1 + sigma_to_pi) + intensity_sig = sigma_to_pi * d * radiance + intensity_pi = 0.5 * d * radiance + + # add Sigma lines to output + s1_to_s0 = self._sigma1_to_sigma0.evaluate(ne) + intensity_s0 = 1 / (s1_to_s0 + 1) + intensity_s1 = 0.5 * s1_to_s0 * intensity_s0 + + spectrum = add_gaussian_line(intensity_sig * intensity_s0, central_wavelength, sigma, spectrum) + spectrum = add_gaussian_line(intensity_sig * intensity_s1, central_wavelength + stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_sig * intensity_s1, central_wavelength - stark_split, sigma, spectrum) + + # add Pi lines to output + pi2_to_pi3 = self._pi2_to_pi3.evaluate(ne) + pi4_to_pi3 = self._pi4_to_pi3.evaluate(ne) + intensity_pi3 = 1 / (1 + pi2_to_pi3 + pi4_to_pi3) + intensity_pi2 = pi2_to_pi3 * intensity_pi3 + intensity_pi4 = pi4_to_pi3 * intensity_pi3 + + spectrum = add_gaussian_line(intensity_pi * intensity_pi2, central_wavelength + 2 * stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_pi * intensity_pi2, central_wavelength - 2 * stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_pi * intensity_pi3, central_wavelength + 3 * stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_pi * intensity_pi3, central_wavelength - 3 * stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_pi * intensity_pi4, central_wavelength + 4 * stark_split, sigma, spectrum) + spectrum = add_gaussian_line(intensity_pi * intensity_pi4, central_wavelength - 4 * stark_split, sigma, spectrum) + + return spectrum diff --git a/cherab/core/model/lineshape/doppler.pxd b/cherab/core/model/lineshape/doppler.pxd new file mode 100644 index 00000000..3d4438fc --- /dev/null +++ b/cherab/core/model/lineshape/doppler.pxd @@ -0,0 +1,26 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from raysect.optical cimport Vector3D + + +cpdef double doppler_shift(double wavelength, Vector3D observation_direction, Vector3D velocity) + +cpdef double thermal_broadening(double wavelength, double temperature, double atomic_weight) diff --git a/cherab/core/model/lineshape/doppler.pyx b/cherab/core/model/lineshape/doppler.pyx new file mode 100644 index 00000000..93755733 --- /dev/null +++ b/cherab/core/model/lineshape/doppler.pyx @@ -0,0 +1,59 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from libc.math cimport sqrt + +from cherab.core.utility.constants cimport ATOMIC_MASS, ELEMENTARY_CHARGE, SPEED_OF_LIGHT + +cimport cython + + +@cython.cdivision(True) +cpdef double doppler_shift(double wavelength, Vector3D observation_direction, Vector3D velocity): + """ + Calculates the Doppler shifted wavelength for a given velocity and observation direction. + + :param wavelength: The wavelength to Doppler shift in nanometers. + :param observation_direction: A Vector defining the direction of observation. + :param velocity: A Vector defining the relative velocity of the emitting source in m/s. + :return: The Doppler shifted wavelength in nanometers. + """ + cdef double projected_velocity + + # flow velocity projected on the direction of observation + observation_direction = observation_direction.normalise() + projected_velocity = velocity.dot(observation_direction) + + return wavelength * (1 + projected_velocity / SPEED_OF_LIGHT) + + +@cython.cdivision(True) +cpdef double thermal_broadening(double wavelength, double temperature, double atomic_weight): + """ + Returns the line width for a gaussian line as a standard deviation. + + :param wavelength: Central wavelength. + :param temperature: Temperature in eV. + :param atomic_weight: Atomic weight in AMU. + :return: Standard deviation of gaussian line. + """ + + # todo: add input sanity checks + return sqrt(temperature * ELEMENTARY_CHARGE / (atomic_weight * ATOMIC_MASS)) * wavelength / SPEED_OF_LIGHT diff --git a/cherab/core/model/lineshape/gaussian.pxd b/cherab/core/model/lineshape/gaussian.pxd new file mode 100644 index 00000000..cd8d0091 --- /dev/null +++ b/cherab/core/model/lineshape/gaussian.pxd @@ -0,0 +1,29 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from raysect.optical cimport Spectrum +from cherab.core.model.lineshape.base cimport LineShapeModel + + +cpdef Spectrum add_gaussian_line(double radiance, double wavelength, double sigma, Spectrum spectrum) + + +cdef class GaussianLine(LineShapeModel): + pass diff --git a/cherab/core/model/lineshape/gaussian.pyx b/cherab/core/model/lineshape/gaussian.pyx new file mode 100644 index 00000000..936d70df --- /dev/null +++ b/cherab/core/model/lineshape/gaussian.pyx @@ -0,0 +1,138 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from libc.math cimport erf, M_SQRT2, floor, ceil +from raysect.optical cimport Point3D, Vector3D + +from cherab.core.atomic cimport Line +from cherab.core.species cimport Species +from cherab.core.plasma cimport Plasma +from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening + +cimport cython + + +# the number of standard deviations outside the rest wavelength the line is considered to add negligible value (including a margin for safety) +DEF GAUSSIAN_CUTOFF_SIGMA = 10.0 + + +@cython.cdivision(True) +@cython.initializedcheck(False) +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef Spectrum add_gaussian_line(double radiance, double wavelength, double sigma, Spectrum spectrum): + r""" + Adds a Gaussian line to the given spectrum and returns the new spectrum. + + The formula used is based on the following definite integral: + :math:`\frac{1}{\sigma \sqrt{2 \pi}} \int_{\lambda_0}^{\lambda_1} \exp(-\frac{(x-\mu)^2}{2\sigma^2}) dx = \frac{1}{2} \left[ -Erf(\frac{a-\mu}{\sqrt{2}\sigma}) +Erf(\frac{b-\mu}{\sqrt{2}\sigma}) \right]` + + :param float radiance: Intensity of the line in radiance. + :param float wavelength: central wavelength of the line in nm. + :param float sigma: width of the line in nm. + :param Spectrum spectrum: the current spectrum to which the gaussian line is added. + :return: + """ + + cdef double temp + cdef double cutoff_lower_wavelength, cutoff_upper_wavelength + cdef double lower_wavelength, upper_wavelength + cdef double lower_integral, upper_integral + cdef int start, end, i + + if sigma <= 0: + return spectrum + + # calculate and check end of limits + cutoff_lower_wavelength = wavelength - GAUSSIAN_CUTOFF_SIGMA * sigma + if spectrum.max_wavelength < cutoff_lower_wavelength: + return spectrum + + cutoff_upper_wavelength = wavelength + GAUSSIAN_CUTOFF_SIGMA * sigma + if spectrum.min_wavelength > cutoff_upper_wavelength: + return spectrum + + # locate range of bins where there is significant contribution from the gaussian (plus a health margin) + start = max(0, floor((cutoff_lower_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) + end = min(spectrum.bins, ceil((cutoff_upper_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) + + # add line to spectrum + temp = 1 / (M_SQRT2 * sigma) + lower_wavelength = spectrum.min_wavelength + start * spectrum.delta_wavelength + lower_integral = erf((lower_wavelength - wavelength) * temp) + for i in range(start, end): + + upper_wavelength = spectrum.min_wavelength + spectrum.delta_wavelength * (i + 1) + upper_integral = erf((upper_wavelength - wavelength) * temp) + + spectrum.samples_mv[i] += radiance * 0.5 * (upper_integral - lower_integral) / spectrum.delta_wavelength + + lower_wavelength = upper_wavelength + lower_integral = upper_integral + + return spectrum + + +cdef class GaussianLine(LineShapeModel): + """ + Produces Gaussian line shape. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + + .. code-block:: pycon + + >>> from cherab.core.atomic import Line, deuterium + >>> from cherab.core.model import ExcitationLine, GaussianLine + >>> + >>> # Adding Gaussian line to the plasma model. + >>> d_alpha = Line(deuterium, 0, (3, 2)) + >>> excit = ExcitationLine(d_alpha, lineshape=GaussianLine) + >>> plasma.models.add(excit) + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma): + + super().__init__(line, wavelength, target_species, plasma) + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef double ts, sigma, shifted_wavelength + cdef Vector3D ion_velocity + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + if ts <= 0.0: + return spectrum + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate emission line central wavelength, doppler shifted along observation direction + shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) + + # calculate the line width + sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) + + return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) diff --git a/cherab/core/model/lineshape/multiplet.pxd b/cherab/core/model/lineshape/multiplet.pxd new file mode 100644 index 00000000..577afadc --- /dev/null +++ b/cherab/core/model/lineshape/multiplet.pxd @@ -0,0 +1,31 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +cimport numpy as np + +from cherab.core.model.lineshape.base cimport LineShapeModel + + +cdef class MultipletLineShape(LineShapeModel): + + cdef: + int _number_of_lines + np.ndarray _multiplet + double[:, ::1] _multiplet_mv diff --git a/cherab/core/model/lineshape/multiplet.pyx b/cherab/core/model/lineshape/multiplet.pyx new file mode 100644 index 00000000..e30093eb --- /dev/null +++ b/cherab/core/model/lineshape/multiplet.pyx @@ -0,0 +1,116 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +import numpy as np + +from raysect.optical cimport Spectrum, Point3D, Vector3D + +from cherab.core.atomic cimport Line +from cherab.core.species cimport Species +from cherab.core.plasma cimport Plasma +from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening +from cherab.core.model.lineshape.gaussian cimport add_gaussian_line + +cimport cython + +# required by numpy c-api +np.import_array() + + +DEF MULTIPLET_WAVELENGTH = 0 +DEF MULTIPLET_RATIO = 1 + + +cdef class MultipletLineShape(LineShapeModel): + """ + Produces Multiplet line shapes. + + The lineshape radiance is calculated from a base PEC rate that is unresolved. This + radiance is then divided over a number of components as specified in the multiplet + argument. The multiplet components are specified with an Nx2 array where N is the + number of components in the multiplet. The first axis of the array contains the + wavelengths of each component, the second contains the line ratio for each component. + The component line ratios must sum to one. For example: + + :param Line line: The emission line object for the base rate radiance calculation. + :param float wavelength: The rest wavelength of the base emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param multiplet: An Nx2 array that specifies the multiplet wavelengths and line ratios. + + .. code-block:: pycon + + >>> from cherab.core.atomic import Line, nitrogen + >>> from cherab.core.model import ExcitationLine, MultipletLineShape + >>> + >>> # multiplet specification in Nx2 array + >>> multiplet = [[403.509, 404.132, 404.354, 404.479, 405.692], [0.205, 0.562, 0.175, 0.029, 0.029]] + >>> + >>> # Adding the multiplet to the plasma model. + >>> nitrogen_II_404 = Line(nitrogen, 1, ("2s2 2p1 4f1 3G13.0", "2s2 2p1 3d1 3F10.0")) + >>> excit = ExcitationLine(nitrogen_II_404, lineshape=MultipletLineShape, lineshape_args=[multiplet]) + >>> plasma.models.add(excit) + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, + object multiplet): + + super().__init__(line, wavelength, target_species, plasma) + + multiplet = np.array(multiplet, dtype=np.float64) + + if not (len(multiplet.shape) == 2 and multiplet.shape[0] == 2): + raise ValueError("The multiplet specification must be an array of shape (Nx2).") + + if not multiplet[1, :].sum() == 1.0: + raise ValueError("The multiplet line ratios should sum to one.") + + self._number_of_lines = multiplet.shape[1] + self._multiplet = multiplet + self._multiplet_mv = self._multiplet + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef double ts, sigma, shifted_wavelength, component_wavelength, component_radiance + cdef Vector3D ion_velocity + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + if ts <= 0.0: + return spectrum + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate the line width + sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) + + for i in range(self._number_of_lines): + + component_wavelength = self._multiplet_mv[MULTIPLET_WAVELENGTH, i] + component_radiance = radiance * self._multiplet_mv[MULTIPLET_RATIO, i] + + # calculate emission line central wavelength, doppler shifted along observation direction + shifted_wavelength = doppler_shift(component_wavelength, direction, ion_velocity) + + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + return spectrum diff --git a/cherab/core/model/lineshape/stark.pxd b/cherab/core/model/lineshape/stark.pxd new file mode 100644 index 00000000..8b4ac4b0 --- /dev/null +++ b/cherab/core/model/lineshape/stark.pxd @@ -0,0 +1,36 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from raysect.optical cimport Spectrum +from cherab.core.math.integrators cimport Integrator1D +from cherab.core.model.lineshape.zeeman cimport ZeemanLineShapeModel + + +cpdef Spectrum add_lorentzian_line(double radiance, double wavelength, double lambda_1_2, Spectrum spectrum, + Integrator1D integrator) + + +cdef class StarkBroadenedLine(ZeemanLineShapeModel): + + cdef: + double _aij, _bij, _cij + double _fwhm_poly_coeff_gauss[7] + double _fwhm_poly_coeff_lorentz[7] + double _weight_poly_coeff[6] diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx new file mode 100644 index 00000000..ea26fd27 --- /dev/null +++ b/cherab/core/model/lineshape/stark.pyx @@ -0,0 +1,331 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from scipy.special import hyp2f1 + +from libc.math cimport sqrt, floor, ceil, fabs, log, exp +from raysect.core.math.function.float cimport Function1D +from raysect.optical cimport Point3D, Vector3D + +from cherab.core.atomic cimport Line +from cherab.core.species cimport Species +from cherab.core.plasma cimport Plasma +from cherab.core.atomic.elements import hydrogen, deuterium, tritium +from cherab.core.math.function cimport autowrap_function1d, autowrap_function2d +from cherab.core.math.integrators cimport GaussianQuadrature +from cherab.core.utility.constants cimport BOHR_MAGNETON, HC_EV_NM +from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening +from cherab.core.model.lineshape.gaussian cimport add_gaussian_line + + +cimport cython + + +DEF PI_POLARISATION = 0 +DEF SIGMA_POLARISATION = 1 +DEF SIGMA_PLUS_POLARISATION = 1 +DEF SIGMA_MINUS_POLARISATION = -1 +DEF NO_POLARISATION = 2 + +DEF LORENTZIAN_CUTOFF_GAMMA = 50.0 + +cdef double _SIGMA2FWHM = 2 * sqrt(2 * log(2)) + + +cdef class StarkFunction(Function1D): + """ + Normalised Stark function for the StarkBroadenedLine line shape. + """ + + cdef double _a, _x0, _norm + + STARK_NORM_COEFFICIENT = 4 * LORENTZIAN_CUTOFF_GAMMA * hyp2f1(0.4, 1, 1.4, -(2 * LORENTZIAN_CUTOFF_GAMMA)**2.5) + + def __init__(self, double wavelength, double lambda_1_2): + + if wavelength <= 0: + raise ValueError("Argument 'wavelength' must be positive.") + + if lambda_1_2 <= 0: + raise ValueError("Argument 'lambda_1_2' must be positive.") + + self._x0 = wavelength + self._a = (0.5 * lambda_1_2)**2.5 + # normalise, so the integral over x is equal to 1 in the limits + # (_x0 - LORENTZIAN_CUTOFF_GAMMA * lambda_1_2, _x0 + LORENTZIAN_CUTOFF_GAMMA * lambda_1_2) + self._norm = (0.5 * lambda_1_2)**1.5 / self.STARK_NORM_COEFFICIENT + + @cython.cdivision(True) + cdef double evaluate(self, double x) except? -1e999: + + return self._norm / ((fabs(x - self._x0))**2.5 + self._a) + + +@cython.cdivision(True) +@cython.initializedcheck(False) +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef Spectrum add_lorentzian_line(double radiance, double wavelength, double lambda_1_2, Spectrum spectrum, Integrator1D integrator): + r""" + Adds a modified Lorentzian line to the given spectrum and returns the new spectrum. + + :param float radiance: Intensity of the line in radiance. + :param float wavelength: central wavelength of the line in nm. + :param float sigma: width of the line in nm. + :param Spectrum spectrum: the current spectrum to which the gaussian line is added. + :return: + """ + + cdef double cutoff_lower_wavelength, cutoff_upper_wavelength + cdef double lower_wavelength, upper_wavelength + cdef double bin_integral + cdef int start, end, i + + if lambda_1_2 <= 0: + return spectrum + + integrator.function = StarkFunction(wavelength, lambda_1_2) + + # calculate and check end of limits + cutoff_lower_wavelength = wavelength - LORENTZIAN_CUTOFF_GAMMA * lambda_1_2 + if spectrum.max_wavelength < cutoff_lower_wavelength: + return spectrum + + cutoff_upper_wavelength = wavelength + LORENTZIAN_CUTOFF_GAMMA * lambda_1_2 + if spectrum.min_wavelength > cutoff_upper_wavelength: + return spectrum + + # locate range of bins where there is significant contribution from the gaussian (plus a health margin) + start = max(0, floor((cutoff_lower_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) + end = min(spectrum.bins, ceil((cutoff_upper_wavelength - spectrum.min_wavelength) / spectrum.delta_wavelength)) + + # add line to spectrum + lower_wavelength = spectrum.min_wavelength + start * spectrum.delta_wavelength + + for i in range(start, end): + upper_wavelength = spectrum.min_wavelength + spectrum.delta_wavelength * (i + 1) + + bin_integral = integrator.evaluate(lower_wavelength, upper_wavelength) + spectrum.samples_mv[i] += radiance * bin_integral / spectrum.delta_wavelength + + lower_wavelength = upper_wavelength + + return spectrum + + +cdef class StarkBroadenedLine(ZeemanLineShapeModel): + """ + Parametrised Stark broadened line shape based on the Model Microfield Method (MMM). + Contains embedded atomic data in the form of fits to MMM. + Only Balmer and Paschen series are supported by default. + See B. Lomanowski, et al. "Inferring divertor plasma properties from hydrogen Balmer + and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) + `123028 `_. + + Call `show_supported_transitions()` to see the list of supported transitions and + default model coefficients. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param dict stark_model_coefficients: Alternative model coefficients in the form + {line_ij: (c_ij, a_ij, b_ij), ...}. + If None, the default model parameters will be used. + :param Integrator1D integrator: Integrator1D instance to integrate the line shape + over the spectral bin. Default is `GaussianQuadrature()`. + + """ + + STARK_MODEL_COEFFICIENTS_DEFAULT = { + Line(hydrogen, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), + Line(hydrogen, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), + Line(hydrogen, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), + Line(hydrogen, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), + Line(hydrogen, 0, (7, 2)): (6.258e-16, 0.712, 0.029), + Line(hydrogen, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), + Line(hydrogen, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), + Line(hydrogen, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), + Line(hydrogen, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), + Line(hydrogen, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), + Line(hydrogen, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), + Line(hydrogen, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), + Line(hydrogen, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), + Line(deuterium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), + Line(deuterium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), + Line(deuterium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), + Line(deuterium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), + Line(deuterium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), + Line(deuterium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), + Line(deuterium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), + Line(deuterium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), + Line(deuterium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), + Line(deuterium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), + Line(deuterium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), + Line(deuterium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), + Line(deuterium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), + Line(tritium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), + Line(tritium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), + Line(tritium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), + Line(tritium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), + Line(tritium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), + Line(tritium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), + Line(tritium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), + Line(tritium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), + Line(tritium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), + Line(tritium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), + Line(tritium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), + Line(tritium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), + Line(tritium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033) + } + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, + dict stark_model_coefficients=None, Integrator1D integrator=GaussianQuadrature(), polarisation='no'): + + stark_model_coefficients = stark_model_coefficients or self.STARK_MODEL_COEFFICIENTS_DEFAULT + + try: + # Fitted Stark Constants + cij, aij, bij = stark_model_coefficients[line] + if cij <= 0: + raise ValueError('Coefficient c_ij must be positive.') + if aij <= 0: + raise ValueError('Coefficient a_ij must be positive.') + if bij <= 0: + raise ValueError('Coefficient b_ij must be positive.') + self._aij = aij + self._bij = bij + self._cij = cij + except IndexError: + raise ValueError('Stark broadening coefficients for {} is not currently available.'.format(line)) + + # polynomial coefficients in increasing powers + self._fwhm_poly_coeff_gauss = [1., 0, 0.57575, 0.37902, -0.42519, -0.31525, 0.31718] + self._fwhm_poly_coeff_lorentz = [1., 0.15882, 1.04388, -1.38281, 0.46251, 0.82325, -0.58026] + + self._weight_poly_coeff = [5.14820e-04, 1.38821e+00, -9.60424e-02, -3.83995e-02, -7.40042e-03, -5.47626e-04] + + super().__init__(line, wavelength, target_species, plasma, polarisation, integrator) + + def show_supported_transitions(self): + """ Prints all supported transitions.""" + for line, coeff in self.STARK_MODEL_COEFFICIENTS_DEFAULT.items(): + print('{}: c_ij={}, a_ij={}, b_ij={}'.format(line, coeff[0], coeff[1], coeff[2])) + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef: + double ne, te, ts, shifted_wavelength, photon_energy, b_magn, comp_radiance, cos_sqr, sin_sqr + double sigma, fwhm_lorentz, fwhm_gauss, fwhm_full, fwhm_ratio, fwhm_lorentz_to_total, lorentz_weight, gauss_weight + cdef Vector3D ion_velocity, b_field + int i + + ne = self.plasma.get_electron_distribution().density(point.x, point.y, point.z) + + te = self.plasma.get_electron_distribution().effective_temperature(point.x, point.y, point.z) + + fwhm_lorentz = self._cij * ne**self._aij / (te**self._bij) if ne > 0 and te > 0 else 0 + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + + fwhm_gauss = _SIGMA2FWHM * thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) if ts > 0 else 0 + + if fwhm_lorentz == 0 and fwhm_gauss == 0: + return spectrum + + # calculating total FWHM + if fwhm_gauss <= fwhm_lorentz: + fwhm_ratio = fwhm_gauss / fwhm_lorentz + fwhm_full = self._fwhm_poly_coeff_gauss[0] + for i in range(1, 7): + fwhm_full += self._fwhm_poly_coeff_gauss[i] * fwhm_ratio**i + else: + fwhm_ratio = fwhm_lorentz / fwhm_gauss + fwhm_full = self._fwhm_poly_coeff_lorentz[0] + for i in range(1, 7): + fwhm_full += self._fwhm_poly_coeff_lorentz[i] * fwhm_ratio**i + + sigma = fwhm_full / _SIGMA2FWHM + + fwhm_lorentz_to_total = fwhm_lorentz / fwhm_full + + # calculating Lorentzian weight + if fwhm_lorentz_to_total < 0.01: + lorentz_weight = 0 + fwhm_full = 0 # force add_lorentzian_line() to immediately return + elif fwhm_lorentz_to_total > 0.9999: + lorentz_weight = 1 + sigma = 0 # force add_gaussian_line() to immediately return + else: + lorentz_weight = self._weight_poly_coeff[i] + for i in range(1, 6): + lorentz_weight += self._weight_poly_coeff[i] * log(fwhm_lorentz_to_total)**i + lorentz_weight = exp(lorentz_weight) + + gauss_weight = 1 - lorentz_weight + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate emission line central wavelength, doppler shifted along observation direction + shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) + + # obtain magnetic field + b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) + b_magn = b_field.get_length() + + if b_magn == 0: + # no splitting if magnetic field strength is zero + if self._polarisation != NO_POLARISATION: + radiance *= 0.5 # pi or sigma polarisation, collecting only half of intensity + + spectrum = add_gaussian_line(gauss_weight * radiance, shifted_wavelength, sigma, spectrum) + spectrum = add_lorentzian_line(lorentz_weight * radiance, shifted_wavelength, fwhm_full, spectrum, self.integrator) + + return spectrum + + # coefficients for intensities parallel and perpendicular to magnetic field + cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 + sin_sqr = 1. - cos_sqr + + # adding pi component of the Zeeman triplet in case of NO_POLARISATION or PI_POLARISATION + if self._polarisation != SIGMA_POLARISATION: + comp_radiance = 0.5 * sin_sqr * radiance + spectrum = add_gaussian_line(gauss_weight * comp_radiance, shifted_wavelength, sigma, spectrum) + spectrum = add_lorentzian_line(lorentz_weight * comp_radiance, shifted_wavelength, fwhm_full, spectrum, self.integrator) + + # adding sigma +/- components of the Zeeman triplet in case of NO_POLARISATION or SIGMA_POLARISATION + if self._polarisation != PI_POLARISATION: + comp_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance + + photon_energy = HC_EV_NM / self.wavelength + + shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy - BOHR_MAGNETON * b_magn), direction, ion_velocity) + spectrum = add_gaussian_line(gauss_weight * comp_radiance, shifted_wavelength, sigma, spectrum) + spectrum = add_lorentzian_line(lorentz_weight * comp_radiance, shifted_wavelength, fwhm_full, spectrum, self.integrator) + + shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy + BOHR_MAGNETON * b_magn), direction, ion_velocity) + spectrum = add_gaussian_line(gauss_weight * comp_radiance, shifted_wavelength, sigma, spectrum) + spectrum = add_lorentzian_line(lorentz_weight * comp_radiance, shifted_wavelength, fwhm_full, spectrum, self.integrator) + + return spectrum diff --git a/cherab/core/model/lineshape/zeeman.pxd b/cherab/core/model/lineshape/zeeman.pxd new file mode 100644 index 00000000..b67d2042 --- /dev/null +++ b/cherab/core/model/lineshape/zeeman.pxd @@ -0,0 +1,48 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.atomic.zeeman cimport ZeemanStructure +from cherab.core.model.lineshape.base cimport LineShapeModel + + +cdef class ZeemanLineShapeModel(LineShapeModel): + + cdef double _polarisation + + pass + + +cdef class ZeemanTriplet(ZeemanLineShapeModel): + + pass + + +cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): + + cdef double _alpha, _beta, _gamma + + pass + + +cdef class ZeemanMultiplet(ZeemanLineShapeModel): + + cdef ZeemanStructure _zeeman_structure + + pass diff --git a/cherab/core/model/lineshape/zeeman.pyx b/cherab/core/model/lineshape/zeeman.pyx new file mode 100644 index 00000000..42c1e71f --- /dev/null +++ b/cherab/core/model/lineshape/zeeman.pyx @@ -0,0 +1,417 @@ +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from libc.math cimport sqrt +from raysect.optical cimport Spectrum, Point3D, Vector3D + +from cherab.core.atomic cimport Line +from cherab.core.species cimport Species +from cherab.core.plasma cimport Plasma +from cherab.core.atomic.elements import hydrogen, deuterium, tritium, helium, helium3, beryllium, boron, carbon, nitrogen, oxygen, neon +from cherab.core.math.integrators cimport Integrator1D +from cherab.core.utility.constants cimport BOHR_MAGNETON, HC_EV_NM +from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening +from cherab.core.model.lineshape.gaussian cimport add_gaussian_line + +cimport cython + + +DEF MULTIPLET_WAVELENGTH = 0 +DEF MULTIPLET_RATIO = 1 + +DEF PI_POLARISATION = 0 +DEF SIGMA_POLARISATION = 1 +DEF SIGMA_PLUS_POLARISATION = 1 +DEF SIGMA_MINUS_POLARISATION = -1 +DEF NO_POLARISATION = 2 + + +cdef class ZeemanLineShapeModel(LineShapeModel): + r""" + A base class for building Zeeman line shapes. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: + "pi" - leave only :math:`\pi`-polarised components, + "sigma" - leave only :math:`\sigma`-polarised components, + "no" - leave all components (default). + :param Integrator1D integrator: Integrator1D instance to integrate the line shape + over the spectral bin. Default is None. + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation, + Integrator1D integrator=None): + super().__init__(line, wavelength, target_species, plasma, integrator) + + self.polarisation = polarisation + + @property + def polarisation(self): + if self._polarisation == PI_POLARISATION: + return 'pi' + if self._polarisation == SIGMA_POLARISATION: + return 'sigma' + if self._polarisation == NO_POLARISATION: + return 'no' + + @polarisation.setter + def polarisation(self, value): + if value.lower() == 'pi': + self._polarisation = PI_POLARISATION + elif value.lower() == 'sigma': + self._polarisation = SIGMA_POLARISATION + elif value.lower() == 'no': + self._polarisation = NO_POLARISATION + else: + raise ValueError('Select between "pi", "sigma" or "no", {} is unsupported.'.format(value)) + + +cdef class ZeemanTriplet(ZeemanLineShapeModel): + r""" + Simple Doppler-Zeeman triplet (Paschen-Back effect). + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: + "pi" - leave central component, + "sigma" - leave side components, + "no" - all components (default). + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation='no'): + + super().__init__(line, wavelength, target_species, plasma, polarisation) + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef double ts, sigma, shifted_wavelength, photon_energy, b_magn, component_radiance, cos_sqr, sin_sqr + cdef Vector3D ion_velocity, b_field + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + if ts <= 0.0: + return spectrum + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate emission line central wavelength, doppler shifted along observation direction + shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) + + # calculate the line width + sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) + + # obtain magnetic field + b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) + b_magn = b_field.get_length() + + if b_magn == 0: + # no splitting if magnetic field strength is zero + if self._polarisation == NO_POLARISATION: + return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) + + return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) + + # coefficients for intensities parallel and perpendicular to magnetic field + cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 + sin_sqr = 1. - cos_sqr + + # adding pi component of the Zeeman triplet in case of NO_POLARISATION or PI_POLARISATION + if self._polarisation != SIGMA_POLARISATION: + component_radiance = 0.5 * sin_sqr * radiance + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + # adding sigma +/- components of the Zeeman triplet in case of NO_POLARISATION or SIGMA_POLARISATION + if self._polarisation != PI_POLARISATION: + component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance + + photon_energy = HC_EV_NM / self.wavelength + + shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy - BOHR_MAGNETON * b_magn), direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + shifted_wavelength = doppler_shift(HC_EV_NM / (photon_energy + BOHR_MAGNETON * b_magn), direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + return spectrum + + +cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): + r""" + Parametrised Doppler-Zeeman triplet. It takes into account additional broadening due to + the line's fine structure without resolving the individual components of the fine + structure. The model is described with three parameters: :math:`\alpha`, + :math:`\beta` and :math:`\gamma`. + + The distance between :math:`\sigma^+` and :math:`\sigma^-` peaks: + :math:`\Delta \lambda_{\sigma} = \alpha B`, + where `B` is the magnetic field strength. + The ratio between Zeeman and thermal broadening line widths: + :math:`\frac{W_{Zeeman}}{W_{Doppler}} = \beta T^{\gamma}`, + where `T` is the species temperature in eV. + + Call `show_supported_transitions()` to see the list of supported transitions and + default parameters of the model. + + For details see A. Blom and C. Jupén, Parametrisation of the Zeeman effect + for hydrogen-like spectra in high-temperature plasmas, + Plasma Phys. Control. Fusion 44 (2002) `1229-1241 + `_. + + :param Line line: The emission line object for this line shape. + :param float wavelength: The rest wavelength for this emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param dict line_parameters: Alternative parameters of the model in the form + {line_i: (alpha_i, beta_i, gamma_i), ...}. + If None, the default model parameters will be used. + :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: + "pi" - leave central component, + "sigma" - leave side components, + "no" - all components (default). + """ + + LINE_PARAMETERS_DEFAULT = { # alpha, beta, gamma parameters for selected lines + Line(hydrogen, 0, (3, 2)): (0.0402267, 0.3415, -0.5247), + Line(hydrogen, 0, (4, 2)): (0.0220724, 0.2837, -0.5346), + Line(deuterium, 0, (3, 2)): (0.0402068, 0.4384, -0.5015), + Line(deuterium, 0, (4, 2)): (0.0220610, 0.3702, -0.5132), + Line(helium3, 1, (4, 3)): (0.0205200, 1.4418, -0.4892), + Line(helium3, 1, (5, 3)): (0.0095879, 1.2576, -0.5001), + Line(helium3, 1, (6, 4)): (0.0401980, 0.8976, -0.4971), + Line(helium3, 1, (7, 4)): (0.0273538, 0.8529, -0.5039), + Line(helium, 1, (4, 3)): (0.0205206, 1.6118, -0.4838), + Line(helium, 1, (5, 3)): (0.0095879, 1.4294, -0.4975), + Line(helium, 1, (6, 4)): (0.0401955, 1.0058, -0.4918), + Line(helium, 1, (7, 4)): (0.0273521, 0.9563, -0.4981), + Line(beryllium, 3, (5, 4)): (0.0060354, 2.1245, -0.3190), + Line(beryllium, 3, (6, 5)): (0.0202754, 1.6538, -0.3192), + Line(beryllium, 3, (7, 5)): (0.0078966, 1.7017, -0.3348), + Line(beryllium, 3, (8, 6)): (0.0205025, 1.4581, -0.3450), + Line(boron, 4, (6, 5)): (0.0083423, 2.0519, -0.2960), + Line(boron, 4, (7, 6)): (0.0228379, 1.6546, -0.2941), + Line(boron, 4, (8, 6)): (0.0084065, 1.8041, -0.3177), + Line(boron, 4, (8, 7)): (0.0541883, 1.4128, -0.2966), + Line(boron, 4, (9, 7)): (0.0190781, 1.5440, -0.3211), + Line(boron, 4, (10, 8)): (0.0391914, 1.3569, -0.3252), + Line(carbon, 5, (6, 5)): (0.0040900, 2.4271, -0.2818), + Line(carbon, 5, (7, 6)): (0.0110398, 1.9785, -0.2816), + Line(carbon, 5, (8, 6)): (0.0040747, 2.1776, -0.3035), + Line(carbon, 5, (8, 7)): (0.0261405, 1.6689, -0.2815), + Line(carbon, 5, (9, 7)): (0.0092096, 1.8495, -0.3049), + Line(carbon, 5, (10, 8)): (0.0189020, 1.6191, -0.3078), + Line(carbon, 5, (11, 8)): (0.0110428, 1.6600, -0.3162), + Line(carbon, 5, (10, 9)): (0.0359009, 1.4464, -0.3104), + Line(nitrogen, 6, (7, 6)): (0.0060010, 2.4789, -0.2817), + Line(nitrogen, 6, (8, 7)): (0.0141271, 2.0249, -0.2762), + Line(nitrogen, 6, (9, 8)): (0.0300127, 1.7415, -0.2753), + Line(nitrogen, 6, (10, 8)): (0.0102089, 1.9464, -0.2975), + Line(nitrogen, 6, (11, 9)): (0.0193799, 1.7133, -0.2973), + Line(oxygen, 7, (8, 7)): (0.0083081, 2.4263, -0.2747), + Line(oxygen, 7, (9, 8)): (0.0176049, 2.0652, -0.2721), + Line(oxygen, 7, (10, 8)): (0.0059933, 2.3445, -0.2944), + Line(oxygen, 7, (10, 9)): (0.0343805, 1.8122, -0.2718), + Line(oxygen, 7, (11, 9)): (0.0113640, 2.0268, -0.2911), + Line(neon, 9, (9, 8)): (0.0072488, 2.8838, -0.2758), + Line(neon, 9, (10, 9)): (0.0141002, 2.4755, -0.2718), + Line(neon, 9, (11, 9)): (0.0046673, 2.8410, -0.2917), + Line(neon, 9, (11, 10)): (0.0257292, 2.1890, -0.2715) + } + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, dict line_parameters=None, polarisation='no'): + + super().__init__(line, wavelength, target_species, plasma, polarisation) + + line_parameters = line_parameters or self.LINE_PARAMETERS_DEFAULT + + try: + alpha, beta, gamma = line_parameters[self.line] + if alpha <= 0: + raise ValueError('Parameter alpha must be positive.') + if beta < 0: + raise ValueError('Parameter beta must be non-negative.') + self._alpha = alpha + self._beta = beta + self._gamma = gamma + + except KeyError: + raise ValueError('Data for {} is not available.'.format(self.line)) + + def show_supported_transitions(self): + """ Prints all supported transitions.""" + for line, param in self.LINE_PARAMETERS_DEFAULT.items(): + print('{}: alpha={}, beta={}, gamma={}'.format(line, param[0], param[1], param[2])) + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef double ts, sigma, shifted_wavelength, b_magn, component_radiance, cos_sqr, sin_sqr + cdef Vector3D ion_velocity, b_field + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + if ts <= 0.0: + return spectrum + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate emission line central wavelength, doppler shifted along observation direction + shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) + + # calculate the line width + sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) + + # fine structure broadening correction + sigma *= sqrt(1. + self._beta * self._beta * ts**(2. * self._gamma)) + + # obtain magnetic field + b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) + b_magn = b_field.get_length() + + if b_magn == 0: + # no splitting if magnetic filed strength is zero + if self._polarisation == NO_POLARISATION: + return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) + + return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) + + # coefficients for intensities parallel and perpendicular to magnetic field + cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 + sin_sqr = 1. - cos_sqr + + # adding pi component of the Zeeman triplet in case of NO_POLARISATION or PI_POLARISATION + if self._polarisation != SIGMA_POLARISATION: + component_radiance = 0.5 * sin_sqr * radiance + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + # adding sigma +/- components of the Zeeman triplet in case of NO_POLARISATION or SIGMA_POLARISATION + if self._polarisation != PI_POLARISATION: + component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance + + shifted_wavelength = doppler_shift(self.wavelength + 0.5 * self._alpha * b_magn, direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + shifted_wavelength = doppler_shift(self.wavelength - 0.5 * self._alpha * b_magn, direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance, shifted_wavelength, sigma, spectrum) + + return spectrum + + +cdef class ZeemanMultiplet(ZeemanLineShapeModel): + r""" + Doppler-Zeeman Multiplet. + + The lineshape radiance is calculated from a base PEC rate that is unresolved. This + radiance is then divided over a number of components as specified in the ``zeeman_structure`` + argument. The ``zeeman_structure`` specifies wavelengths and ratios of + :math:`\pi`-/:math:`\sigma`-polarised components as functions of the magnetic field strength. + These functions can be obtained using the output of the ADAS603 routines. + + :param Line line: The emission line object for the base rate radiance calculation. + :param float wavelength: The rest wavelength of the base emission line. + :param Species target_species: The target plasma species that is emitting. + :param Plasma plasma: The emitting plasma object. + :param zeeman_structure: A ``ZeemanStructure`` object that provides wavelengths and ratios + of :math:`\pi`-/:math:`\sigma^{+}`-/:math:`\sigma^{-}`-polarised + components for any given magnetic field strength. + :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: + "pi" - leave only :math:`\pi`-polarised components, + "sigma" - leave only :math:`\sigma`-polarised components, + "no" - leave all components (default). + + """ + + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, + ZeemanStructure zeeman_structure, polarisation='no'): + + super().__init__(line, wavelength, target_species, plasma, polarisation) + + self._zeeman_structure = zeeman_structure + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef int i + cdef double ts, sigma, shifted_wavelength, component_radiance + cdef Vector3D ion_velocity + cdef double[:, :] multiplet_pi_mv, multiplet_sigma_mv + + ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) + if ts <= 0.0: + return spectrum + + ion_velocity = self.target_species.distribution.bulk_velocity(point.x, point.y, point.z) + + # calculate the line width + sigma = thermal_broadening(self.wavelength, ts, self.line.element.atomic_weight) + + # obtain magnetic field + b_field = self.plasma.get_b_field().evaluate(point.x, point.y, point.z) + b_magn = b_field.get_length() + + if b_magn == 0: + # no splitting if magnetic filed strength is zero + shifted_wavelength = doppler_shift(self.wavelength, direction, ion_velocity) + if self._polarisation == NO_POLARISATION: + return add_gaussian_line(radiance, shifted_wavelength, sigma, spectrum) + + return add_gaussian_line(0.5 * radiance, shifted_wavelength, sigma, spectrum) + + # coefficients for intensities parallel and perpendicular to magnetic field + cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 + sin_sqr = 1. - cos_sqr + + # adding pi components of the Zeeman multiplet in case of NO_POLARISATION or PI_POLARISATION + if self._polarisation != SIGMA_POLARISATION: + component_radiance = 0.5 * sin_sqr * radiance + multiplet_mv = self._zeeman_structure.evaluate(b_magn, PI_POLARISATION) + + for i in range(multiplet_mv.shape[1]): + shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) + + # adding sigma components of the Zeeman multiplet in case of NO_POLARISATION or SIGMA_POLARISATION + if self._polarisation != PI_POLARISATION: + component_radiance = (0.25 * sin_sqr + 0.5 * cos_sqr) * radiance + + multiplet_mv = self._zeeman_structure.evaluate(b_magn, SIGMA_PLUS_POLARISATION) + + for i in range(multiplet_mv.shape[1]): + shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) + + multiplet_mv = self._zeeman_structure.evaluate(b_magn, SIGMA_MINUS_POLARISATION) + + for i in range(multiplet_mv.shape[1]): + shifted_wavelength = doppler_shift(multiplet_mv[MULTIPLET_WAVELENGTH, i], direction, ion_velocity) + spectrum = add_gaussian_line(component_radiance * multiplet_mv[MULTIPLET_RATIO, i], shifted_wavelength, sigma, spectrum) + + return spectrum diff --git a/cherab/core/model/plasma/impact_excitation.pyx b/cherab/core/model/plasma/impact_excitation.pyx index 343059ad..2aa9691c 100644 --- a/cherab/core/model/plasma/impact_excitation.pyx +++ b/cherab/core/model/plasma/impact_excitation.pyx @@ -18,7 +18,7 @@ from raysect.optical cimport Spectrum, Point3D, Vector3D from cherab.core cimport Plasma, AtomicData -from cherab.core.model.lineshape cimport GaussianLine, LineShapeModel +from cherab.core.model.lineshape cimport GaussianLine from cherab.core.utility.constants cimport RECIP_4_PI diff --git a/cherab/core/utility/constants.pxd b/cherab/core/utility/constants.pxd index 9cce304d..aa7db394 100644 --- a/cherab/core/utility/constants.pxd +++ b/cherab/core/utility/constants.pxd @@ -27,6 +27,8 @@ cdef: double ELEMENTARY_CHARGE double SPEED_OF_LIGHT double PLANCK_CONSTANT + double HC_EV_NM double ELECTRON_CLASSICAL_RADIUS double ELECTRON_REST_MASS double RYDBERG_CONSTANT_EV + double BOHR_MAGNETON diff --git a/cherab/core/utility/constants.pyx b/cherab/core/utility/constants.pyx index 03e70617..e881f910 100644 --- a/cherab/core/utility/constants.pyx +++ b/cherab/core/utility/constants.pyx @@ -29,6 +29,8 @@ cdef: double ELEMENTARY_CHARGE = 1.602176634e-19 double SPEED_OF_LIGHT = 299792458.0 double PLANCK_CONSTANT = 6.62607015e-34 + double HC_EV_NM = 1239.8419738620933 # (Planck constant in eV s) x (speed of light in nm/s) double ELECTRON_CLASSICAL_RADIUS = 2.8179403262e-15 double ELECTRON_REST_MASS = 9.1093837015e-31 double RYDBERG_CONSTANT_EV = 13.605693122994 + double BOHR_MAGNETON = 5.78838180123e-5 # in eV/T From 85cece3b6483ad82e6ea1da381c978f575cfc612 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 9 Jan 2023 17:12:33 +0300 Subject: [PATCH 02/16] Fixed errors in StarkBroadenedLine. --- cherab/core/model/lineshape/stark.pyx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx index ea26fd27..6c4a3b8a 100644 --- a/cherab/core/model/lineshape/stark.pyx +++ b/cherab/core/model/lineshape/stark.pyx @@ -254,17 +254,19 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): if fwhm_lorentz == 0 and fwhm_gauss == 0: return spectrum - # calculating total FWHM + # calculating full FWHM if fwhm_gauss <= fwhm_lorentz: fwhm_ratio = fwhm_gauss / fwhm_lorentz fwhm_full = self._fwhm_poly_coeff_gauss[0] for i in range(1, 7): fwhm_full += self._fwhm_poly_coeff_gauss[i] * fwhm_ratio**i + fwhm_full *= fwhm_lorentz else: fwhm_ratio = fwhm_lorentz / fwhm_gauss fwhm_full = self._fwhm_poly_coeff_lorentz[0] for i in range(1, 7): fwhm_full += self._fwhm_poly_coeff_lorentz[i] * fwhm_ratio**i + fwhm_full *= fwhm_gauss sigma = fwhm_full / _SIGMA2FWHM @@ -274,11 +276,11 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): if fwhm_lorentz_to_total < 0.01: lorentz_weight = 0 fwhm_full = 0 # force add_lorentzian_line() to immediately return - elif fwhm_lorentz_to_total > 0.9999: + elif fwhm_lorentz_to_total > 0.999: lorentz_weight = 1 sigma = 0 # force add_gaussian_line() to immediately return else: - lorentz_weight = self._weight_poly_coeff[i] + lorentz_weight = self._weight_poly_coeff[0] for i in range(1, 6): lorentz_weight += self._weight_poly_coeff[i] * log(fwhm_lorentz_to_total)**i lorentz_weight = exp(lorentz_weight) From 7a5041622817fac4b2118b59ff596ab70264ae41 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Tue, 10 Jan 2023 14:57:40 +0300 Subject: [PATCH 03/16] Plot Stark-broadened lines in logscale to show the power-law decay of the line wings. --- demos/emission_models/stark_broadening.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demos/emission_models/stark_broadening.py b/demos/emission_models/stark_broadening.py index 090e929a..9a3f747e 100755 --- a/demos/emission_models/stark_broadening.py +++ b/demos/emission_models/stark_broadening.py @@ -34,7 +34,7 @@ # tunables -ion_density = 1e20 +ion_density = 2e20 sigma = 0.25 # setup scenegraph @@ -99,4 +99,5 @@ plt.xlabel('Wavelength (nm)') plt.ylabel('Radiance (W/m^2/str/nm)') plt.title('Observed Spectrum') +plt.yscale('log') plt.show() From 86b4cd71b9ee957dc60badca4d2e7f9efa3714e9 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Thu, 12 Jan 2023 19:43:34 +0300 Subject: [PATCH 04/16] Fixed the unit test for the Stark-broadened line after changing the line shape model. --- cherab/core/tests/test_lineshapes.py | 66 +++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/cherab/core/tests/test_lineshapes.py b/cherab/core/tests/test_lineshapes.py index da4dc41d..72b02973 100644 --- a/cherab/core/tests/test_lineshapes.py +++ b/cherab/core/tests/test_lineshapes.py @@ -300,23 +300,75 @@ def test_stark_broadened_line(self): spectrum = stark_line.add_line(radiance, point, direction, spectrum) # validating + velocity = target_species.distribution.bulk_velocity(point.x, point.y, point.z) + doppler_factor = (1 + velocity.dot(direction.normalise()) / SPEED_OF_LIGHT) + + b_field = self.plasma.b_field(point.x, point.y, point.z) + b_magn = b_field.length + photon_energy = HC_EV_NM / wavelength + wl_sigma_plus = HC_EV_NM / (photon_energy - BOHR_MAGNETON * b_magn) + wl_sigma_minus = HC_EV_NM / (photon_energy + BOHR_MAGNETON * b_magn) + cos_sqr = (b_field.dot(direction.normalise()) / b_magn)**2 + sin_sqr = 1. - cos_sqr + + # Gaussian parameters + temperature = target_species.distribution.effective_temperature(point.x, point.y, point.z) + sigma = np.sqrt(temperature * ELEMENTARY_CHARGE / (line.element.atomic_weight * ATOMIC_MASS)) * wavelength / SPEED_OF_LIGHT + fwhm_gauss = 2 * np.sqrt(2 * np.log(2)) * sigma + + # Lorentzian parameters cij, aij, bij = stark_line.STARK_MODEL_COEFFICIENTS_DEFAULT[line] ne = self.plasma.electron_distribution.density(point.x, point.y, point.z) te = self.plasma.electron_distribution.effective_temperature(point.x, point.y, point.z) - lambda_1_2 = cij * ne**aij / (te**bij) + fwhm_lorentz = cij * ne**aij / (te**bij) + + # Total FWHM + if fwhm_gauss <= fwhm_lorentz: + fwhm_poly_coeff = [1., 0, 0.57575, 0.37902, -0.42519, -0.31525, 0.31718] + fwhm_ratio = fwhm_gauss / fwhm_lorentz + fwhm_full = fwhm_lorentz * np.poly1d(fwhm_poly_coeff[::-1])(fwhm_ratio) + else: + fwhm_poly_coeff = [1., 0.15882, 1.04388, -1.38281, 0.46251, 0.82325, -0.58026] + fwhm_ratio = fwhm_lorentz / fwhm_gauss + fwhm_full = fwhm_gauss * np.poly1d(fwhm_poly_coeff[::-1])(fwhm_ratio) + + wavelengths, delta = np.linspace(min_wavelength, max_wavelength, bins + 1, retstep=True) + # Gaussian part + temp = 2 * np.sqrt(np.log(2)) / fwhm_full + erfs = erf((wavelengths - wavelength * doppler_factor) * temp) + gaussian = 0.25 * sin_sqr * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - wl_sigma_plus * doppler_factor) * temp) + gaussian += 0.5 * (0.25 * sin_sqr + 0.5 * cos_sqr) * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - wl_sigma_minus * doppler_factor) * temp) + gaussian += 0.5 * (0.25 * sin_sqr + 0.5 * cos_sqr) * (erfs[1:] - erfs[:-1]) / delta + + # Lorentzian part lorenzian_cutoff_gamma = 50 stark_norm_coeff = 4 * lorenzian_cutoff_gamma * hyp2f1(0.4, 1, 1.4, -(2 * lorenzian_cutoff_gamma)**2.5) - norm = (0.5 * lambda_1_2)**1.5 / stark_norm_coeff + norm = (0.5 * fwhm_full)**1.5 / stark_norm_coeff - wavelengths, delta = np.linspace(min_wavelength, max_wavelength, bins + 1, retstep=True) + def stark_lineshape_pi(x): + return norm / ((np.abs(x - wavelength * doppler_factor))**2.5 + (0.5 * fwhm_full)**2.5) + + def stark_lineshape_sigma_plus(x): + return norm / ((np.abs(x - wl_sigma_plus * doppler_factor))**2.5 + (0.5 * fwhm_full)**2.5) + + def stark_lineshape_sigma_minus(x): + return norm / ((np.abs(x - wl_sigma_minus * doppler_factor))**2.5 + (0.5 * fwhm_full)**2.5) - def stark_lineshape(x): - return norm / ((np.abs(x - wavelength))**2.5 + (0.5 * lambda_1_2)**2.5) + weight_poly_coeff = [5.14820e-04, 1.38821e+00, -9.60424e-02, -3.83995e-02, -7.40042e-03, -5.47626e-04] + lorentz_weight = np.exp(np.poly1d(weight_poly_coeff[::-1])(np.log(fwhm_lorentz / fwhm_full))) for i in range(bins): - stark_bin = quadrature(stark_lineshape, wavelengths[i], wavelengths[i + 1], rtol=integrator.relative_tolerance)[0] / delta - self.assertAlmostEqual(stark_bin, spectrum.samples[i], delta=1e-9, + lorentz_bin = 0.5 * sin_sqr * quadrature(stark_lineshape_pi, wavelengths[i], wavelengths[i + 1], + rtol=integrator.relative_tolerance)[0] / delta + lorentz_bin += (0.25 * sin_sqr + 0.5 * cos_sqr) * quadrature(stark_lineshape_sigma_plus, wavelengths[i], wavelengths[i + 1], + rtol=integrator.relative_tolerance)[0] / delta + lorentz_bin += (0.25 * sin_sqr + 0.5 * cos_sqr) * quadrature(stark_lineshape_sigma_minus, wavelengths[i], wavelengths[i + 1], + rtol=integrator.relative_tolerance)[0] / delta + ref_value = lorentz_bin * lorentz_weight + gaussian[i] * (1. - lorentz_weight) + self.assertAlmostEqual(ref_value, spectrum.samples[i], delta=1e-9, msg='StarkBroadenedLine.add_line() method gives a wrong value at {} nm.'.format(wavelengths[i])) From b252beee51ab5c17db10ee9b8e18329feea3567f Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Thu, 12 Jan 2023 23:21:22 +0300 Subject: [PATCH 05/16] Added demo that plots Balmer-alpha and Paschen-beta Stark-Zeeman lines as in B.A. Lomanowski 2015 NF paper. --- demos/emission_models/stark_broadening.py | 6 +- demos/emission_models/stark_zeeman.py | 94 +++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100755 demos/emission_models/stark_zeeman.py diff --git a/demos/emission_models/stark_broadening.py b/demos/emission_models/stark_broadening.py index 9a3f747e..b3f2cf95 100755 --- a/demos/emission_models/stark_broadening.py +++ b/demos/emission_models/stark_broadening.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); diff --git a/demos/emission_models/stark_zeeman.py b/demos/emission_models/stark_zeeman.py new file mode 100755 index 00000000..e212f27b --- /dev/null +++ b/demos/emission_models/stark_zeeman.py @@ -0,0 +1,94 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +""" +Plots Balmer-alpha and Paschen-beta Stark-Zeeman spectral lines +as in Figure 2 in B.A. Lomanowski at al, Nucl. Fusion 55 (2015) 123028, +https://iopscience.iop.org/article/10.1088/0029-5515/55/12/123028 +""" + + +# External imports +import matplotlib.pyplot as plt +from raysect.optical import World, translate, rotate, Vector3D, Point3D, Ray + +# Cherab imports +from cherab.core import Line +from cherab.core.atomic.elements import deuterium +from cherab.core.model import ExcitationLine, StarkBroadenedLine +from cherab.openadas import OpenADAS +from cherab.tools.plasmas.slab import build_constant_slab_plasma + + +# tunables +ion_density = 2e20 +sigma = 0.25 + +# setup scenegraph +world = World() + +# create atomic data source +adas = OpenADAS(permit_extrapolation=True) + +# setup the Balmer and Paschen lines +balmer_alpha = Line(deuterium, 0, (3, 2)) +paschen_beta = Line(deuterium, 0, (5, 3)) + +# setup plasma for Balmer-alpha line +plasma_species = [(deuterium, 0, 1.e20, 0.3, Vector3D(0, 0, 0))] +plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=5e20, electron_temperature=1., + plasma_species=plasma_species, b_field=Vector3D(0, 3., 0), parent=world) +plasma.atomic_data = adas + +# add Balmer-alpha line to the plasma +plasma.models = [ExcitationLine(balmer_alpha, lineshape=StarkBroadenedLine)] + +# Ray-trace and plot the results +r = Ray(origin=Point3D(-5, 0, 0), direction=Vector3D(1, 0, 0), + min_wavelength=655.9, max_wavelength=656.3, bins=200) +s = r.trace(world) + +plt.figure() +plt.plot(s.wavelengths, s.samples, ls='--', color='C3') +plt.xlabel('Wavelength (nm)') +plt.ylabel('Radiance (W/m^2/str/nm)') +plt.title('Balmer-alpha spectrum, Ne = 5e20 m-3, Te = 1 eV, B = 3 T') + +plasma.parent = None + +# setup plasma for Paschen-beta line +plasma_species = [(deuterium, 0, 1.e20, 0.3, Vector3D(0, 0, 0))] +plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=1e20, electron_temperature=1., + plasma_species=plasma_species, b_field=Vector3D(0, 3., 0), parent=world) +plasma.atomic_data = adas + +# add Paschen-beta line to the plasma +plasma.models = [ExcitationLine(paschen_beta, lineshape=StarkBroadenedLine)] + +# Ray-trace and plot the results +r = Ray(origin=Point3D(-5, 0, 0), direction=Vector3D(1, 0, 0), + min_wavelength=1280.3, max_wavelength=1282.6, bins=200) +s = r.trace(world) + +plt.figure() +plt.plot(s.wavelengths, s.samples, ls='--', color='C3') +plt.xlabel('Wavelength (nm)') +plt.ylabel('Radiance (W/m^2/str/nm)') +plt.title('Paschen-beta spectrum, Ne = 1e20 m-3, Te = 1 eV, B = 3 T') + +plt.show() From 55336c3fb8a322a1a6454ca00922f98e87b4a328 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 16 Jan 2023 15:56:18 +0300 Subject: [PATCH 06/16] Converted show_supported_transitions() to a class method. --- cherab/core/model/lineshape/stark.pyx | 5 +++-- cherab/core/model/lineshape/zeeman.pyx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx index 6c4a3b8a..8adc71af 100644 --- a/cherab/core/model/lineshape/stark.pyx +++ b/cherab/core/model/lineshape/stark.pyx @@ -224,9 +224,10 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): super().__init__(line, wavelength, target_species, plasma, polarisation, integrator) - def show_supported_transitions(self): + @classmethod + def show_supported_transitions(cls): """ Prints all supported transitions.""" - for line, coeff in self.STARK_MODEL_COEFFICIENTS_DEFAULT.items(): + for line, coeff in cls.STARK_MODEL_COEFFICIENTS_DEFAULT.items(): print('{}: c_ij={}, a_ij={}, b_ij={}'.format(line, coeff[0], coeff[1], coeff[2])) @cython.boundscheck(False) diff --git a/cherab/core/model/lineshape/zeeman.pyx b/cherab/core/model/lineshape/zeeman.pyx index 42c1e71f..81bbb991 100644 --- a/cherab/core/model/lineshape/zeeman.pyx +++ b/cherab/core/model/lineshape/zeeman.pyx @@ -261,9 +261,10 @@ cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): except KeyError: raise ValueError('Data for {} is not available.'.format(self.line)) - def show_supported_transitions(self): + @classmethod + def show_supported_transitions(cls): """ Prints all supported transitions.""" - for line, param in self.LINE_PARAMETERS_DEFAULT.items(): + for line, param in cls.LINE_PARAMETERS_DEFAULT.items(): print('{}: alpha={}, beta={}, gamma={}'.format(line, param[0], param[1], param[2])) @cython.boundscheck(False) From 035c95f9d01ed8a272742b4a959d8406ec5fd81d Mon Sep 17 00:00:00 2001 From: vsnever Date: Tue, 17 Jan 2023 17:50:00 +0300 Subject: [PATCH 07/16] Updated documentation for the line shape models. --- cherab/core/model/lineshape/__init__.py | 2 +- cherab/core/model/lineshape/stark.pyx | 82 +++++++++++++++++-- demos/emission_models/stark_zeeman.py | 11 +-- .../line_shapes/spectral_line_shapes.rst | 34 +++++--- 4 files changed, 103 insertions(+), 26 deletions(-) diff --git a/cherab/core/model/lineshape/__init__.py b/cherab/core/model/lineshape/__init__.py index e0ad6a24..30100403 100644 --- a/cherab/core/model/lineshape/__init__.py +++ b/cherab/core/model/lineshape/__init__.py @@ -21,5 +21,5 @@ from .doppler import doppler_shift, thermal_broadening from .gaussian import add_gaussian_line, GaussianLine from .multiplet import MultipletLineShape -from .stark import StarkBroadenedLine +from .stark import add_lorentzian_line, StarkBroadenedLine from .zeeman import ZeemanLineShapeModel, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx index 8adc71af..93b165f7 100644 --- a/cherab/core/model/lineshape/stark.pyx +++ b/cherab/core/model/lineshape/stark.pyx @@ -52,6 +52,9 @@ cdef double _SIGMA2FWHM = 2 * sqrt(2 * log(2)) cdef class StarkFunction(Function1D): """ Normalised Stark function for the StarkBroadenedLine line shape. + + :param float wavelength: central wavelength of the line in nm. + :param float lambda_1_2: FWHM of the function in nm. """ cdef double _a, _x0, _norm @@ -86,10 +89,24 @@ cpdef Spectrum add_lorentzian_line(double radiance, double wavelength, double la r""" Adds a modified Lorentzian line to the given spectrum and returns the new spectrum. + The modified Lorentzian: + :math:`L(\lambda-\lambda_0, \Delta\lambda_{1/2}^{(L)})=\frac{C_0(\Delta\lambda_{1/2}^{(L)})^{3/2}}{(\lambda-\lambda_0)^{5/2}+(\frac{\Delta\lambda_{1/2}^{(L)}}{2})^{5/2}},` + + :math:`C_0=\frac{(1/2)^{3/2}}{4R{_2}F_1(\frac{2}{5},1,\frac{7}{5},-R^{5/2})},` + + where :math:`\Delta\lambda_{1/2}^{(L)}` is the line FWHM, :math:`{_2}F_1` is the hypergeometric function and :math:`R=50`. + The line shape is truncated at :math:`\lambdaR\Delta\lambda_{1/2}^{(L)}`. + + See B. Lomanowski, et al. "Inferring divertor plasma properties from hydrogen Balmer + and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) + `123028 `_ for details. + :param float radiance: Intensity of the line in radiance. :param float wavelength: central wavelength of the line in nm. - :param float sigma: width of the line in nm. - :param Spectrum spectrum: the current spectrum to which the gaussian line is added. + :param float lambda_1_2: FWHM of the line shape in nm. + :param Spectrum spectrum: the current spectrum to which the Lorentzian line is added. + :param Integrator1D integrator: Integrator1D instance to integrate the line shape + over the spectral bin. :return: """ @@ -131,14 +148,60 @@ cpdef Spectrum add_lorentzian_line(double radiance, double wavelength, double la cdef class StarkBroadenedLine(ZeemanLineShapeModel): - """ - Parametrised Stark broadened line shape based on the Model Microfield Method (MMM). - Contains embedded atomic data in the form of fits to MMM. - Only Balmer and Paschen series are supported by default. - See B. Lomanowski, et al. "Inferring divertor plasma properties from hydrogen Balmer + r""" + Parametrised Stark-Doppler-Zeeman line shape for Balmer and Paschen series based on + B. Lomanowski, et al. "Inferring divertor plasma properties from hydrogen Balmer and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) `123028 `_. + The following approximations are used: + + - The Zeeman and Stark effects are considered independently. + - Zeeman splitting is taken in the form of a simple triplet with a :math:`\pi`-component + centred at :math:`h\nu`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{h\nu-\mu B}` + and :math:`\sigma^{-}`-component at :math:`\frac{hc}{h\nu+\mu B}`. + - The model of Stark broadening is obtained by fitting the Model Microfield Method (MMM). + - The convolution of Stark-Zeeman and Doppler profiles is replaced with the weighted sum + to speed-up calculations (so-called pseudo-Voigt profile). + + The Stark-broadened line shape is modelled as modified Lorentzian: + :math:`L(\lambda-\lambda_0, \Delta\lambda_{1/2}^{(L)})=\frac{C_0(\Delta\lambda_{1/2}^{(L)})^{3/2}}{(\lambda-\lambda_0)^{5/2}+(\frac{\Delta\lambda_{1/2}^{(L)}}{2})^{5/2}},` + + :math:`C_0=\frac{(1/2)^{3/2}}{4R{_2}F_1(\frac{2}{5},1,\frac{7}{5},-R^{5/2})},` + + where :math:`\Delta\lambda_{1/2}^{(L)}=c_{ij}\frac{n_e^{a_{ij}}}{T_e^{b_{ij}}}` is the line FWHM, + :math:`{_2}F_1` is the hypergeometric function and :math:`R=50`. + The line shape is truncated at :math:`\lambdaR\Delta\lambda_{1/2}^{(L)}`. + The :math:`a_{ij}`, :math:`b_{ij}` and :math:`c_{ij}` are the fitting coefficients. + + Each Zeeman component is modelled as a weighted sum of Stark (Lorentzian), :math:`L(\lambda-\lambda_0, \Delta\lambda_{1/2}^{(L)})`, + and Doppler (Gauss), :math:`G(\lambda'-\lambda_0, \Delta\lambda_{1/2}^{(G)})` profiles: + + :math:`\eta L(\lambda-\lambda_0, \Delta\lambda_{1/2}^{(V)}) + (1-\eta)G(\lambda-\lambda_0, \Delta\lambda_{1/2}^{(V)})`, + + with :math:`\Delta\lambda_{1/2}^{(V)}\equiv \Delta\lambda_{1/2}^{(V)}(\Delta\lambda_{1/2}^{(G)}, \Delta\lambda_{1/2}^{(L)})` + and :math:`\eta\equiv \eta(\Delta\lambda_{1/2}^{(G)}, \Delta\lambda_{1/2}^{(L)})`. + + Both :math:`\Delta\lambda_{1/2}^{(V)}` and :math:`\eta` are obtained by fitting the convolution: + + :math:`\int_{-\infty}^{\infty}G(\lambda'-\lambda_0, \Delta\lambda_{1/2}^{(G)})L(\lambda-\lambda'-\lambda_0, \Delta\lambda_{1/2}^{(L)})d\lambda'`. + + The :math:`\Delta\lambda_{1/2}^{(V)}` function is fitted as: + :math:`\Delta\lambda_{1/2}^{(V)}=\sum_{n=0}^{6}a_n(\frac{\Delta\lambda_{1/2}^{(L)}}{\Delta\lambda_{1/2}^{(G)}})^n` + for :math:`\frac{\Delta\lambda_{1/2}^{(L)}}{\Delta\lambda_{1/2}^{(G)}} \le 1` and + :math:`\Delta\lambda_{1/2}^{(V)}=\sum_{n=0}^{6}b_n(\frac{\Delta\lambda_{1/2}^{(G)}}{\Delta\lambda_{1/2}^{(L)}})^n` + for :math:`\frac{\Delta\lambda_{1/2}^{(L)}}{\Delta\lambda_{1/2}^{(G)}} > 1` with + + `a` = [1., 0.15882, 1.04388, -1.38281, 0.46251, 0.82325, -0.58026] and + + `b` = [1., 0, 0.57575, 0.37902, -0.42519, -0.31525, 0.31718]. + + While the :math:`\eta` function is fitted as: + :math:`\eta=exp(\sum_{n=0}^{5}c_n(ln(\frac{\Delta\lambda_{1/2}^{(L)}}{\Delta\lambda_{1/2}^{(V)}}))^n` + for :math:`0.01<\frac{\Delta\lambda_{1/2}^{(L)}}{\Delta\lambda_{1/2}^{(V)}}<0.999` with + + `c` = [5.14820e-04, 1.38821e+00, -9.60424e-02, -3.83995e-02, -7.40042e-03, -5.47626e-04]. + Call `show_supported_transitions()` to see the list of supported transitions and default model coefficients. @@ -151,7 +214,10 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): If None, the default model parameters will be used. :param Integrator1D integrator: Integrator1D instance to integrate the line shape over the spectral bin. Default is `GaussianQuadrature()`. - + :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: + "pi" - leave only :math:`\pi`-polarised components, + "sigma" - leave only :math:`\sigma`-polarised components, + "no" - leave all components (default). """ STARK_MODEL_COEFFICIENTS_DEFAULT = { diff --git a/demos/emission_models/stark_zeeman.py b/demos/emission_models/stark_zeeman.py index e212f27b..6dac025d 100755 --- a/demos/emission_models/stark_zeeman.py +++ b/demos/emission_models/stark_zeeman.py @@ -17,15 +17,16 @@ # under the Licence. """ -Plots Balmer-alpha and Paschen-beta Stark-Zeeman spectral lines -as in Figure 2 in B.A. Lomanowski at al, Nucl. Fusion 55 (2015) 123028, +Calculates Balmer-alpha and Paschen-beta Stark-Zeeman spectral lines. + +Compare the figures with Figure 2 in B.A. Lomanowski at al, Nucl. Fusion 55 (2015) 123028, https://iopscience.iop.org/article/10.1088/0029-5515/55/12/123028 """ # External imports import matplotlib.pyplot as plt -from raysect.optical import World, translate, rotate, Vector3D, Point3D, Ray +from raysect.optical import World, Vector3D, Point3D, Ray # Cherab imports from cherab.core import Line @@ -58,7 +59,7 @@ # add Balmer-alpha line to the plasma plasma.models = [ExcitationLine(balmer_alpha, lineshape=StarkBroadenedLine)] -# Ray-trace and plot the results +# Ray-trace perpendicular to magnetic field and plot the results r = Ray(origin=Point3D(-5, 0, 0), direction=Vector3D(1, 0, 0), min_wavelength=655.9, max_wavelength=656.3, bins=200) s = r.trace(world) @@ -80,7 +81,7 @@ # add Paschen-beta line to the plasma plasma.models = [ExcitationLine(paschen_beta, lineshape=StarkBroadenedLine)] -# Ray-trace and plot the results +# Ray-trace perpendicular to magnetic field and plot the results r = Ray(origin=Point3D(-5, 0, 0), direction=Vector3D(1, 0, 0), min_wavelength=1280.3, max_wavelength=1282.6, bins=200) s = r.trace(world) diff --git a/docs/source/models/line_shapes/spectral_line_shapes.rst b/docs/source/models/line_shapes/spectral_line_shapes.rst index 5dc6325e..0d7fac55 100644 --- a/docs/source/models/line_shapes/spectral_line_shapes.rst +++ b/docs/source/models/line_shapes/spectral_line_shapes.rst @@ -2,36 +2,46 @@ Spectral Line Shapes ==================== -Cherab contains Doppler-broadened, Stark-broadened and Doppler-Zeeman line shapes of -atomic spectra. Stark-Doppler and Stark-Doppler-Zeeman line shapes of hydrogen spectra -will be added in the future. +Cherab contains Doppler-broadened, Doppler-Zeeman and Stark-Doppler-Zeeman line shapes of +atomic spectra. **Assumption: Maxwellian distribution of emitting species is assumed.** **A general model of Doppler broadening will be implemented in the future.** -.. autoclass:: cherab.core.model.lineshape.add_gaussian_line +.. autoclass:: cherab.core.model.lineshape.doppler.doppler_shift -.. autoclass:: cherab.core.model.lineshape.LineShapeModel +.. autoclass:: cherab.core.model.lineshape.doppler.thermal_broadening + +.. autoclass:: cherab.core.model.lineshape.gaussian.add_gaussian_line + +.. autoclass:: cherab.core.model.lineshape.stark.add_lorentzian_line + +.. autoclass:: cherab.core.model.lineshape.base.LineShapeModel :members: -.. autoclass:: cherab.core.model.lineshape.GaussianLine +.. autoclass:: cherab.core.model.lineshape.gaussian.GaussianLine :members: -.. autoclass:: cherab.core.model.lineshape.MultipletLineShape +.. autoclass:: cherab.core.model.lineshape.multiplet.MultipletLineShape :members: -.. autoclass:: cherab.core.model.lineshape.StarkBroadenedLine +.. autoclass:: cherab.core.model.lineshape.zeeman.ZeemanLineShapeModel :members: -.. autoclass:: cherab.core.model.lineshape.ZeemanLineShapeModel +.. autoclass:: cherab.core.model.lineshape.zeeman.ZeemanTriplet :members: -.. autoclass:: cherab.core.model.lineshape.ZeemanTriplet +.. autoclass:: cherab.core.model.lineshape.zeeman.ParametrisedZeemanTriplet :members: -.. autoclass:: cherab.core.model.lineshape.ParametrisedZeemanTriplet +.. autoclass:: cherab.core.model.lineshape.zeeman.ZeemanMultiplet :members: -.. autoclass:: cherab.core.model.lineshape.ZeemanMultiplet +.. autoclass:: cherab.core.model.lineshape.stark.StarkBroadenedLine :members: +.. autoclass:: cherab.core.model.lineshape.beam.base.BeamLineShapeModel + :members: + +.. autoclass:: cherab.core.model.lineshape.beam.mse.BeamEmissionMultiplet + :members: From f07fd32510b9d7a9453b803f07319d71527f6631 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Tue, 17 Jan 2023 23:44:40 +0300 Subject: [PATCH 08/16] Updated documentation for demos. Changelog update. --- CHANGELOG.md | 9 ++++ docs/source/demonstrations/demonstrations.rst | 17 +++++--- .../passive_spectroscopy/stark_broadening.rst | 21 +++++----- .../stark_line_zoomed.png | Bin 26626 -> 0 bytes .../passive_spectroscopy/stark_spectrum.png | Bin 26687 -> 41296 bytes .../passive_spectroscopy/stark_zeeman.rst | 39 ++++++++++++++++++ .../stark_zeeman_balmer_alpha.png | Bin 0 -> 35119 bytes .../stark_zeeman_paschen_beta.png | Bin 0 -> 35725 bytes 8 files changed, 69 insertions(+), 17 deletions(-) delete mode 100644 docs/source/demonstrations/passive_spectroscopy/stark_line_zoomed.png create mode 100644 docs/source/demonstrations/passive_spectroscopy/stark_zeeman.rst create mode 100644 docs/source/demonstrations/passive_spectroscopy/stark_zeeman_balmer_alpha.png create mode 100644 docs/source/demonstrations/passive_spectroscopy/stark_zeeman_paschen_beta.png diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bf7c6d..f392324b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Project Changelog ================= +Release 1.5.0 (TBD) +------------------- + +API changes: +* The line shape models are moved to a dedicated submodule. The user code should not be affected though. (#396) + +New: +* StarkBroadenedLine now supports Doppler broadening and Zeeman splitting. (#393) + Release 1.4.0 (TBD) ------------------- diff --git a/docs/source/demonstrations/demonstrations.rst b/docs/source/demonstrations/demonstrations.rst index 6201aae1..e91f358a 100644 --- a/docs/source/demonstrations/demonstrations.rst +++ b/docs/source/demonstrations/demonstrations.rst @@ -151,16 +151,21 @@ Passive Spectroscopy - .. image:: ./passive_spectroscopy/multiplet_spectrum.png :height: 150px :width: 150px - * - :ref:`Stark Broadened Lines ` - - Specifying a Stark broadened lineshape instead of Doppler broadening. - - .. image:: ./passive_spectroscopy/stark_line_zoomed.png - :height: 150px - :width: 150px * - :ref:`Zeeman Spectroscopy ` - Specifying a Zeeman triplet or multiplet line shapes. - .. image:: ./passive_spectroscopy/zeeman_spectrum_45deg.png :height: 150px - :width: 150px + :width: 150px + * - :ref:`Stark Broadened Lines ` + - Specifying a Stark broadened lineshape. + - .. image:: ./passive_spectroscopy/stark_spectrum.png + :height: 150px + :width: 150px + * - :ref:`Stark-Zeeman Lines ` + - Modelling Stark-Zeeman lineshapes. + - .. image:: ./passive_spectroscopy/stark_zeeman_balmer_alpha.png + :height: 150px + :width: 150px Bolometry diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_broadening.rst b/docs/source/demonstrations/passive_spectroscopy/stark_broadening.rst index bd670a49..b7ebf3d1 100644 --- a/docs/source/demonstrations/passive_spectroscopy/stark_broadening.rst +++ b/docs/source/demonstrations/passive_spectroscopy/stark_broadening.rst @@ -12,9 +12,15 @@ electric field due to the presence of neighbouring ions. In normal tokamak operations this effect is negligible, except in the divertor region. It is possible to override the default doppler broadened line shape by -specifying the StarkBroadenedLine() lineshape class. In this example -we can see two stark broadened balmer series lines surrounding a -Nitrogen II multiplet feature. +specifying the StarkBroadenedLine() lineshape class. +This class suppors Balmer and Paschen series and is based on +the Stark-Doppler-Zeeman line shape model from B. Lomanowski, et al. +"Inferring divertor plasma properties from hydrogen Balmer +and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) +`123028 `_. +In this example we can see two stark broadened balmer series lines surrounding a +Nitrogen II multiplet feature. The logarithmic scale is chosen to illustrate +the power-law decay of the Stark-broadened line wings. .. literalinclude:: ../../../../demos/emission_models/stark_broadening.py @@ -23,11 +29,4 @@ Nitrogen II multiplet feature. :width: 450px **Caption:** The observed spectrum with two stark broadened balmer lines - (397nm and 410nm) surrounding a NII multiplet feature. - -.. figure:: stark_line_zoomed.png - :align: center - :width: 450px - - **Caption:** A zoomed in view of the 410nm Balmer line revealing the - characteristic stark broadened lineshape. + (397nm and 410nm) surrounding a NII multiplet feature in the logarithmic scale. diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_line_zoomed.png b/docs/source/demonstrations/passive_spectroscopy/stark_line_zoomed.png deleted file mode 100644 index 2a6ee04b9b1fc3b875e2a6a8ef924446b4a7de3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26626 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 JY~b|!e*xUA+iU;; diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_spectrum.png b/docs/source/demonstrations/passive_spectroscopy/stark_spectrum.png index 53230968c5f25157865e0a57cd7e419d3c0e5e3b..04d90589b54ce2560d0a999e166cf00bd7e41b54 100644 GIT binary patch literal 41296 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�pkL}MELO}7BFP=~Z$}&rB*YNG{55X$t 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 Y$5s7~frXkhACIUn{Db_2zLDAg1IjjNW&i*H literal 26687 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 fuSdgEKLw_pf(_fQfBp`iE0Xa6bJ`O`_ Stark and Zeeman features +are treated independently. This is a simplification and for better accuracy these effects +must be taken into account jointly as in J. Rosato, Y. Marandet, R. Stamm, +Journal of Quantitative Spectroscopy & Radiative Transfer 187 (2017) +`333 `_. + +The StarkBroadenedLine() follows the Lomanowski's paper, but introduces a couple of additional +approximations: + +* Zeeman splitting is taken in the form of a simple triplet with a :math:`\pi`-component + centred at :math:`h\nu`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{h\nu-\mu B}` + and :math:`\sigma^{-}`-component at :math:`\frac{hc}{h\nu+\mu B}`. +* The convolution of Stark-Zeeman and Doppler profiles is replaced with the weighted sum + to speed-up calculations (`pseudo-Voigt `_ approximation). + +This example calculates Balmer-alpha and Paschen-beta Stark-Zeeman spectral lines +for the same parameters of plasma as in Figure 2 in Lomanowski's paper. + +.. literalinclude:: ../../../../demos/emission_models/stark_zeeman.py + +.. figure:: stark_zeeman_balmer_alpha.png + :align: center + :width: 450px + + **Caption:** The Stark-Zeeman structure of the Balmer-alpha line. + +.. figure:: stark_zeeman_paschen_beta.png + :align: center + :width: 450px + + **Caption:** The Stark-Zeeman structure of the Paschen-beta line. diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_zeeman_balmer_alpha.png b/docs/source/demonstrations/passive_spectroscopy/stark_zeeman_balmer_alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..532a8eabf61dbab380df567f75a701d442a2829b GIT binary patch literal 35119 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 Mdr0k>%89H01-E-E%m4rY literal 0 HcmV?d00001 diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_zeeman_paschen_beta.png b/docs/source/demonstrations/passive_spectroscopy/stark_zeeman_paschen_beta.png new file mode 100644 index 0000000000000000000000000000000000000000..afa5044abfab78d6232d9d506e4319bf8fb39114 GIT binary patch literal 35725 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 Nqot?$O5ODO{{qr==`a8Q literal 0 HcmV?d00001 From d9441758a2e903759d8258837c939704d5204475 Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 18 Jan 2023 16:56:59 +0300 Subject: [PATCH 09/16] A small fix in documentation for StarkBroadenedLine and Stark-Zeeman demo. --- cherab/core/model/lineshape/stark.pyx | 12 ++++++------ .../passive_spectroscopy/stark_zeeman.rst | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx index 93b165f7..e290ca98 100644 --- a/cherab/core/model/lineshape/stark.pyx +++ b/cherab/core/model/lineshape/stark.pyx @@ -156,12 +156,12 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): The following approximations are used: - - The Zeeman and Stark effects are considered independently. - - Zeeman splitting is taken in the form of a simple triplet with a :math:`\pi`-component - centred at :math:`h\nu`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{h\nu-\mu B}` - and :math:`\sigma^{-}`-component at :math:`\frac{hc}{h\nu+\mu B}`. - - The model of Stark broadening is obtained by fitting the Model Microfield Method (MMM). - - The convolution of Stark-Zeeman and Doppler profiles is replaced with the weighted sum + * The Zeeman and Stark effects are considered independently. + * Zeeman splitting is taken in the form of a simple triplet with a :math:`\pi`-component + centred at :math:`\lambda`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{hc/\lambda -\mu B}` + and :math:`\sigma^{-}`-component at :math:`\frac{hc}{hc/\lambda +\mu B}`. + * The model of Stark broadening is obtained by fitting the Model Microfield Method (MMM). + * The convolution of Stark-Zeeman and Doppler profiles is replaced with the weighted sum to speed-up calculations (so-called pseudo-Voigt profile). The Stark-broadened line shape is modelled as modified Lorentzian: diff --git a/docs/source/demonstrations/passive_spectroscopy/stark_zeeman.rst b/docs/source/demonstrations/passive_spectroscopy/stark_zeeman.rst index 46ec7161..eb07750e 100644 --- a/docs/source/demonstrations/passive_spectroscopy/stark_zeeman.rst +++ b/docs/source/demonstrations/passive_spectroscopy/stark_zeeman.rst @@ -16,8 +16,8 @@ The StarkBroadenedLine() follows the Lomanowski's paper, but introduces a couple approximations: * Zeeman splitting is taken in the form of a simple triplet with a :math:`\pi`-component - centred at :math:`h\nu`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{h\nu-\mu B}` - and :math:`\sigma^{-}`-component at :math:`\frac{hc}{h\nu+\mu B}`. + centred at :math:`\lambda`, :math:`\sigma^{+}`-component at :math:`\frac{hc}{hc/\lambda -\mu B}` + and :math:`\sigma^{-}`-component at :math:`\frac{hc}{hc/\lambda +\mu B}`. * The convolution of Stark-Zeeman and Doppler profiles is replaced with the weighted sum to speed-up calculations (`pseudo-Voigt `_ approximation). From 0b290cd97c269a16b3acf60addaea9aedd582329 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Thu, 19 Jan 2023 18:31:48 +0300 Subject: [PATCH 10/16] Update changelog.md. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f392324b..3a677175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Release 1.5.0 (TBD) API changes: * The line shape models are moved to a dedicated submodule. The user code should not be affected though. (#396) +* The method show_supported_transitions() of StarkBroadenedLine and ParametrisedZeemanTriplet is made a class method. New: * StarkBroadenedLine now supports Doppler broadening and Zeeman splitting. (#393) From e2ed3cc327fb8bfac37656be38d1d352347b4de6 Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 5 Apr 2023 16:20:56 +0300 Subject: [PATCH 11/16] Added a test for the BeamEmissionMultiplet line shape model. --- cherab/core/tests/test_lineshapes.py | 91 +++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/cherab/core/tests/test_lineshapes.py b/cherab/core/tests/test_lineshapes.py index 72b02973..0f7ab7ef 100644 --- a/cherab/core/tests/test_lineshapes.py +++ b/cherab/core/tests/test_lineshapes.py @@ -26,11 +26,12 @@ from raysect.core.math.function.float import Arg1D, Constant1D from raysect.optical import Spectrum -from cherab.core import Line +from cherab.core import Beam, Line from cherab.core.math.integrators import GaussianQuadrature from cherab.core.atomic import deuterium, nitrogen, ZeemanStructure from cherab.tools.plasmas.slab import build_constant_slab_plasma from cherab.core.model import GaussianLine, MultipletLineShape, StarkBroadenedLine, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet +from cherab.core.model import BeamEmissionMultiplet ATOMIC_MASS = 1.66053906660e-27 @@ -46,6 +47,11 @@ class TestLineShapes(unittest.TestCase): (nitrogen, 1, 1.e17, 10., Vector3D(1.e4, 5.e4, 0))] plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=1e19, electron_temperature=20., plasma_species=plasma_species, b_field=Vector3D(0, 5., 0)) + beam = Beam() + beam.plasma = plasma + beam.energy = 60000 + beam.temperature = 10 + beam.element = deuterium def test_gaussian_line(self): # setting up a line shape model @@ -371,6 +377,89 @@ def stark_lineshape_sigma_minus(x): self.assertAlmostEqual(ref_value, spectrum.samples[i], delta=1e-9, msg='StarkBroadenedLine.add_line() method gives a wrong value at {} nm.'.format(wavelengths[i])) + def test_beam_emission_multiplet(self): + # Test MSE line shape + # setting up a line shape model + line = Line(deuterium, 0, (3, 2)) # D-alpha line + wavelength = 656.104 + sigma_to_pi = 0.56 + sigma1_to_sigma0 = 0.7060001671878492 + pi2_to_pi3 = 0.3140003593919741 + pi4_to_pi3 = 0.7279994935840365 + mse_line = BeamEmissionMultiplet(line, wavelength, self.beam, sigma_to_pi, + sigma1_to_sigma0, pi2_to_pi3, pi4_to_pi3) + + # spectrum parameters + min_wavelength = wavelength - 3 + max_wavelength = wavelength + 3 + bins = 512 + point = Point3D(0.5, 0.5, 0.5) + direction = Vector3D(-1, 1, 0) / np.sqrt(2) + beam_direction = self.beam.direction(point.x, point.y, point.z) + + # obtaining spectrum + radiance = 1.0 + spectrum = Spectrum(min_wavelength, max_wavelength, bins) + spectrum = mse_line.add_line(radiance, point, point, beam_direction, direction, spectrum) + + # validating + + # calculate Stark splitting + b_field = self.plasma.b_field(point.x, point.y, point.z) + beam_velocity = beam_direction.normalise() * np.sqrt(2 * self.beam.energy * ELEMENTARY_CHARGE / ATOMIC_MASS) + e_field = beam_velocity.cross(b_field).length + STARK_SPLITTING_FACTOR = 2.77e-8 + stark_split = np.abs(STARK_SPLITTING_FACTOR * e_field) + + # calculate emission line central wavelength, doppler shifted along observation direction + central_wavelength = wavelength * (1 + beam_velocity.dot(direction.normalise()) / SPEED_OF_LIGHT) + + # calculate doppler broadening + beam_ion_mass = self.beam.element.atomic_weight + beam_temperature = self.beam.temperature + sigma = np.sqrt(beam_temperature * ELEMENTARY_CHARGE / (beam_ion_mass * ATOMIC_MASS)) * wavelength / SPEED_OF_LIGHT + temp = 1. / (np.sqrt(2.) * sigma) + + # calculate relative intensities of sigma and pi lines + d = 1 / (1 + sigma_to_pi) + intensity_sig = sigma_to_pi * d * radiance + intensity_pi = 0.5 * d * radiance + + wavelengths, delta = np.linspace(min_wavelength, max_wavelength, bins + 1, retstep=True) + + # add Sigma lines to output + intensity_s0 = 1 / (sigma1_to_sigma0 + 1) + intensity_s1 = 0.5 * sigma1_to_sigma0 * intensity_s0 + + erfs = erf((wavelengths - central_wavelength) * temp) + test_spectrum = 0.5 * intensity_sig * intensity_s0 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength - stark_split) * temp) + test_spectrum += 0.5 * intensity_sig * intensity_s1 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength + stark_split) * temp) + test_spectrum += 0.5 * intensity_sig * intensity_s1 * (erfs[1:] - erfs[:-1]) / delta + + # add Pi lines to output + intensity_pi3 = 1 / (1 + pi2_to_pi3 + pi4_to_pi3) + intensity_pi2 = pi2_to_pi3 * intensity_pi3 + intensity_pi4 = pi4_to_pi3 * intensity_pi3 + + erfs = erf((wavelengths - central_wavelength - 2 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi2 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength + 2 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi2 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength - 3 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi3 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength + 3 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi3 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength - 4 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi4 * (erfs[1:] - erfs[:-1]) / delta + erfs = erf((wavelengths - central_wavelength + 4 * stark_split) * temp) + test_spectrum += 0.5 * intensity_pi * intensity_pi4 * (erfs[1:] - erfs[:-1]) / delta + + for i in range(bins): + self.assertAlmostEqual(test_spectrum[i], spectrum.samples[i], delta=1e-10, + msg='BeamEmissionMultiplet.add_line() method gives a wrong value at {} nm.'.format(wavelengths[i])) + if __name__ == '__main__': unittest.main() From 04d5d19eeed384a72dd85e6b6a8119baf97fd7a2 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Tue, 11 Apr 2023 23:54:35 +0300 Subject: [PATCH 12/16] Moved the parameters of StarkBroadenedLine and ParametrisedZeemanTriplet to the default atomic data repository. --- CHANGELOG.md | 5 +- .../core/atomic/data/lineshape/stark/d.json | 70 +++++++++++++ .../core/atomic/data/lineshape/stark/h.json | 70 +++++++++++++ .../core/atomic/data/lineshape/stark/t.json | 70 +++++++++++++ .../data/lineshape/zeeman/parametrised/b.json | 35 +++++++ .../lineshape/zeeman/parametrised/be.json | 25 +++++ .../data/lineshape/zeeman/parametrised/c.json | 45 +++++++++ .../data/lineshape/zeeman/parametrised/d.json | 15 +++ .../data/lineshape/zeeman/parametrised/h.json | 15 +++ .../lineshape/zeeman/parametrised/he.json | 25 +++++ .../lineshape/zeeman/parametrised/he3.json | 25 +++++ .../data/lineshape/zeeman/parametrised/n.json | 30 ++++++ .../lineshape/zeeman/parametrised/ne.json | 25 +++++ .../data/lineshape/zeeman/parametrised/o.json | 30 ++++++ cherab/core/atomic/interface.pxd | 5 +- cherab/core/atomic/interface.pyx | 52 +++++++++- cherab/core/model/beam/beam_emission.pyx | 10 +- cherab/core/model/lineshape/base.pxd | 2 + cherab/core/model/lineshape/base.pyx | 4 +- cherab/core/model/lineshape/beam/base.pxd | 2 + cherab/core/model/lineshape/beam/base.pyx | 4 +- cherab/core/model/lineshape/beam/mse.pyx | 7 +- cherab/core/model/lineshape/gaussian.pyx | 7 +- cherab/core/model/lineshape/multiplet.pyx | 7 +- cherab/core/model/lineshape/stark.pyx | 72 ++------------ cherab/core/model/lineshape/zeeman.pyx | 97 +++++-------------- .../core/model/plasma/impact_excitation.pyx | 10 +- cherab/core/model/plasma/recombination.pyx | 10 +- cherab/core/tests/test_lineshapes.py | 29 +++--- 29 files changed, 623 insertions(+), 180 deletions(-) create mode 100644 cherab/core/atomic/data/lineshape/stark/d.json create mode 100644 cherab/core/atomic/data/lineshape/stark/h.json create mode 100644 cherab/core/atomic/data/lineshape/stark/t.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/b.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/be.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/c.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/d.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/h.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/he.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/he3.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/n.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/ne.json create mode 100644 cherab/core/atomic/data/lineshape/zeeman/parametrised/o.json diff --git a/CHANGELOG.md b/CHANGELOG.md index de83a946..f486aabd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ Release 1.5.0 (TBD) API changes: * The line shape models are moved to a dedicated submodule. The user code should not be affected though. (#396) -* The method show_supported_transitions() of StarkBroadenedLine and ParametrisedZeemanTriplet is made a class method. +* The line shape models now have AtomicData as a required parameter. +* The method show_supported_transitions() of StarkBroadenedLine and ParametrisedZeemanTriplet is removed. +* The argument stark_model_coefficients of StarkBroadenedLine is now a tuple instead of a dict. +* The argument line_parameters of ParametrisedZeemanTriplet is now a tuple instead of a dict. New: * Support Raysect 0.8 diff --git a/cherab/core/atomic/data/lineshape/stark/d.json b/cherab/core/atomic/data/lineshape/stark/d.json new file mode 100644 index 00000000..c0092760 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/stark/d.json @@ -0,0 +1,70 @@ +{ + "0": { + "3 -> 2": [ + 3.71e-18, + 0.7665, + 0.064 + ], + "4 -> 2": [ + 8.425e-18, + 0.7803, + 0.050 + ], + "5 -> 2": [ + 1.31e-15, + 0.6796, + 0.030 + ], + "6 -> 2": [ + 3.954e-16, + 0.7149, + 0.028 + ], + "7 -> 2": [ + 6.258e-16, + 0.712, + 0.029 + ], + "8 -> 2": [ + 7.378e-16, + 0.7159, + 0.032 + ], + "9 -> 2": [ + 8.947e-16, + 0.7177, + 0.033 + ], + "4 -> 3": [ + 1.330e-16, + 0.7449, + 0.045 + ], + "5 -> 3": [ + 6.64e-16, + 0.7356, + 0.044 + ], + "6 -> 3": [ + 2.481e-15, + 0.7118, + 0.016 + ], + "7 -> 3": [ + 3.270e-15, + 0.7137, + 0.029 + ], + "8 -> 3": [ + 4.343e-15, + 0.7133, + 0.032 + ], + "9 -> 3": [ + 5.588e-15, + 0.7165, + 0.033 + ] + }, + "reference": "B. Lomanowski, et al. Inferring divertor plasma properties from hydrogen Balmer and Paschen series spectroscopy in JET-ILW. Nuclear Fusion 55.12 (2015) 123028" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/stark/h.json b/cherab/core/atomic/data/lineshape/stark/h.json new file mode 100644 index 00000000..c0092760 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/stark/h.json @@ -0,0 +1,70 @@ +{ + "0": { + "3 -> 2": [ + 3.71e-18, + 0.7665, + 0.064 + ], + "4 -> 2": [ + 8.425e-18, + 0.7803, + 0.050 + ], + "5 -> 2": [ + 1.31e-15, + 0.6796, + 0.030 + ], + "6 -> 2": [ + 3.954e-16, + 0.7149, + 0.028 + ], + "7 -> 2": [ + 6.258e-16, + 0.712, + 0.029 + ], + "8 -> 2": [ + 7.378e-16, + 0.7159, + 0.032 + ], + "9 -> 2": [ + 8.947e-16, + 0.7177, + 0.033 + ], + "4 -> 3": [ + 1.330e-16, + 0.7449, + 0.045 + ], + "5 -> 3": [ + 6.64e-16, + 0.7356, + 0.044 + ], + "6 -> 3": [ + 2.481e-15, + 0.7118, + 0.016 + ], + "7 -> 3": [ + 3.270e-15, + 0.7137, + 0.029 + ], + "8 -> 3": [ + 4.343e-15, + 0.7133, + 0.032 + ], + "9 -> 3": [ + 5.588e-15, + 0.7165, + 0.033 + ] + }, + "reference": "B. Lomanowski, et al. Inferring divertor plasma properties from hydrogen Balmer and Paschen series spectroscopy in JET-ILW. Nuclear Fusion 55.12 (2015) 123028" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/stark/t.json b/cherab/core/atomic/data/lineshape/stark/t.json new file mode 100644 index 00000000..c0092760 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/stark/t.json @@ -0,0 +1,70 @@ +{ + "0": { + "3 -> 2": [ + 3.71e-18, + 0.7665, + 0.064 + ], + "4 -> 2": [ + 8.425e-18, + 0.7803, + 0.050 + ], + "5 -> 2": [ + 1.31e-15, + 0.6796, + 0.030 + ], + "6 -> 2": [ + 3.954e-16, + 0.7149, + 0.028 + ], + "7 -> 2": [ + 6.258e-16, + 0.712, + 0.029 + ], + "8 -> 2": [ + 7.378e-16, + 0.7159, + 0.032 + ], + "9 -> 2": [ + 8.947e-16, + 0.7177, + 0.033 + ], + "4 -> 3": [ + 1.330e-16, + 0.7449, + 0.045 + ], + "5 -> 3": [ + 6.64e-16, + 0.7356, + 0.044 + ], + "6 -> 3": [ + 2.481e-15, + 0.7118, + 0.016 + ], + "7 -> 3": [ + 3.270e-15, + 0.7137, + 0.029 + ], + "8 -> 3": [ + 4.343e-15, + 0.7133, + 0.032 + ], + "9 -> 3": [ + 5.588e-15, + 0.7165, + 0.033 + ] + }, + "reference": "B. Lomanowski, et al. Inferring divertor plasma properties from hydrogen Balmer and Paschen series spectroscopy in JET-ILW. Nuclear Fusion 55.12 (2015) 123028" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/b.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/b.json new file mode 100644 index 00000000..b63c7c85 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/b.json @@ -0,0 +1,35 @@ +{ + "4": { + "6 -> 5": [ + 0.0083423, + 2.0519, + -0.2960 + ], + "7 -> 6": [ + 0.0228379, + 1.6546, + -0.2941 + ], + "8 -> 6": [ + 0.0084065, + 1.8041, + -0.3177 + ], + "8 -> 7": [ + 0.0541883, + 1.4128, + -0.2966 + ], + "9 -> 7": [ + 0.0190781, + 1.5440, + -0.3211 + ], + "10 -> 8": [ + 0.0391914, + 1.3569, + -0.3252 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/be.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/be.json new file mode 100644 index 00000000..a5e37caa --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/be.json @@ -0,0 +1,25 @@ +{ + "3": { + "5 -> 4": [ + 0.0060354, + 2.1245, + -0.3190 + ], + "6 -> 5": [ + 0.0202754, + 1.6538, + -0.3192 + ], + "7 -> 5": [ + 0.0078966, + 1.7017, + -0.3348 + ], + "8 -> 6": [ + 0.0205025, + 1.4581, + -0.3450 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/c.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/c.json new file mode 100644 index 00000000..8e6ad067 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/c.json @@ -0,0 +1,45 @@ +{ + "5": { + "6 -> 5": [ + 0.0040900, + 2.4271, + -0.2818 + ], + "7 -> 6": [ + 0.0110398, + 1.9785, + -0.2816 + ], + "8 -> 6": [ + 0.0040747, + 2.1776, + -0.3035 + ], + "8 -> 7": [ + 0.0261405, + 1.6689, + -0.2815 + ], + "9 -> 7": [ + 0.0092096, + 1.8495, + -0.3049 + ], + "10 -> 8": [ + 0.0189020, + 1.6191, + -0.3078 + ], + "11 -> 8": [ + 0.0110428, + 1.6600, + -0.3162 + ], + "10 -> 9": [ + 0.0359009, + 1.4464, + -0.3104 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/d.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/d.json new file mode 100644 index 00000000..3b7e8150 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/d.json @@ -0,0 +1,15 @@ +{ + "0": { + "3 -> 2": [ + 0.0402068, + 0.4384, + -0.5015 + ], + "4 -> 2": [ + 0.0220610, + 0.3702, + -0.5132 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/h.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/h.json new file mode 100644 index 00000000..13745495 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/h.json @@ -0,0 +1,15 @@ +{ + "0": { + "3 -> 2": [ + 0.0402267, + 0.3415, + -0.5247 + ], + "4 -> 2": [ + 0.0220724, + 0.2837, + -0.5346 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/he.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/he.json new file mode 100644 index 00000000..2ebd0625 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/he.json @@ -0,0 +1,25 @@ +{ + "1": { + "4 -> 3": [ + 0.0205206, + 1.6118, + -0.4838 + ], + "5 -> 3": [ + 0.0095879, + 1.4294, + -0.4975 + ], + "6 -> 4": [ + 0.0401955, + 1.0058, + -0.4918 + ], + "7 -> 4": [ + 0.0273521, + 0.9563, + -0.4981 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/he3.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/he3.json new file mode 100644 index 00000000..04729f74 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/he3.json @@ -0,0 +1,25 @@ +{ + "1": { + "4 -> 3": [ + 0.0205200, + 1.4418, + -0.4892 + ], + "5 -> 3": [ + 0.0095879, + 1.2576, + -0.5001 + ], + "6 -> 4": [ + 0.0401980, + 0.8976, + -0.4971 + ], + "7 -> 4": [ + 0.0273538, + 0.8529, + -0.5039 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/n.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/n.json new file mode 100644 index 00000000..d086f4a0 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/n.json @@ -0,0 +1,30 @@ +{ + "6": { + "7 -> 6": [ + 0.0060010, + 2.4789, + -0.2817 + ], + "8 -> 7": [ + 0.0141271, + 2.0249, + -0.2762 + ], + "9 -> 8": [ + 0.0300127, + 1.7415, + -0.2753 + ], + "10 -> 8": [ + 0.0102089, + 1.9464, + -0.2975 + ], + "11 -> 9": [ + 0.0193799, + 1.7133, + -0.2973 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/ne.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/ne.json new file mode 100644 index 00000000..07e2c41b --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/ne.json @@ -0,0 +1,25 @@ +{ + "9": { + "9 -> 8": [ + 0.0072488, + 2.8838, + -0.2758 + ], + "10 -> 9": [ + 0.0141002, + 2.4755, + -0.2718 + ], + "11 -> 9": [ + 0.0046673, + 2.8410, + -0.2917 + ], + "11 -> 10": [ + 0.0257292, + 2.1890, + -0.2715 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/data/lineshape/zeeman/parametrised/o.json b/cherab/core/atomic/data/lineshape/zeeman/parametrised/o.json new file mode 100644 index 00000000..3f3663d7 --- /dev/null +++ b/cherab/core/atomic/data/lineshape/zeeman/parametrised/o.json @@ -0,0 +1,30 @@ +{ + "7": { + "8 -> 7": [ + 0.0083081, + 2.4263, + -0.2747 + ], + "9 -> 8": [ + 0.0176049, + 2.0652, + -0.2721 + ], + "10 -> 8": [ + 0.0059933, + 2.3445, + -0.2944 + ], + "10 -> 9": [ + 0.0343805, + 1.8122, + -0.2718 + ], + "11 -> 9": [ + 0.0113640, + 2.0268, + -0.2911 + ] + }, + "reference": "A. Blom and C. Jupén. Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas. Plasma Phys. Control. Fusion 44 (2002) 1229-1241" +} \ No newline at end of file diff --git a/cherab/core/atomic/interface.pxd b/cherab/core/atomic/interface.pxd index 66d8faf8..2b671176 100644 --- a/cherab/core/atomic/interface.pxd +++ b/cherab/core/atomic/interface.pxd @@ -57,5 +57,8 @@ cdef class AtomicData: cpdef ZeemanStructure zeeman_structure(self, Line line, object b_field=*) - cpdef FreeFreeGauntFactor free_free_gaunt_factor(self) + cpdef tuple zeeman_triplet_parameters(self, Line line) + + cpdef tuple stark_model_coefficients(self, Line line) + cpdef FreeFreeGauntFactor free_free_gaunt_factor(self) diff --git a/cherab/core/atomic/interface.pyx b/cherab/core/atomic/interface.pyx index 286ebfb4..9e4e1113 100644 --- a/cherab/core/atomic/interface.pyx +++ b/cherab/core/atomic/interface.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2022 Euratom -# Copyright 2016-2022 United Kingdom Atomic Energy Authority -# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -17,6 +17,8 @@ # under the Licence. from .gaunt import MaxwellianFreeFreeGauntFactor +import json +from os import path cdef class AtomicData: @@ -95,6 +97,50 @@ cdef class AtomicData: cpdef ZeemanStructure zeeman_structure(self, Line line, object b_field=None): raise NotImplementedError("The zeeman_structure() virtual method is not implemented for this atomic data source.") + cpdef tuple zeeman_triplet_parameters(self, Line line): + """ + Returns Zeeman truplet parameters. See Table 1 in A. Blom and C. Jupén. + "Parametrisation of the Zeeman effect for hydrogen-like spectra in + high-temperature plasmas", Plasma Phys. Control. Fusion 44 (2002) `1229-1241 + `_. + """ + + symbol = line.element.symbol.lower() + upper, lower = line.transition + encoded_transition = '{} -> {}'.format(str(upper).lower(), str(lower).lower()) + + try: + with open(path.join(path.dirname(__file__), "data/lineshape/zeeman/parametrised/{}.json".format(symbol))) as f: + data = json.load(f) + coefficients = data[str(line.charge)][encoded_transition] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested Zeeman triplet parameters (element={}, charge={}, transition={})' + ' are not available.'.format(line.element.symbol, line.charge, line.transition)) + + return tuple(coefficients) + + cpdef tuple stark_model_coefficients(self, Line line): + """ + Returns Stark model coefficients. See Table 1 in B. Lomanowski, et al. + "Inferring divertor plasma properties from hydrogen Balmer + and Paschen series spectroscopy in JET-ILW." Nuclear Fusion 55.12 (2015) + `123028 `_. + """ + + symbol = line.element.symbol.lower() + upper, lower = line.transition + encoded_transition = '{} -> {}'.format(str(upper).lower(), str(lower).lower()) + + try: + with open(path.join(path.dirname(__file__), "data/lineshape/stark/{}.json".format(symbol))) as f: + data = json.load(f) + coefficients = data[str(line.charge)][encoded_transition] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested Stark model coefficients (element={}, charge={}, transition={})' + ' are not available.'.format(line.element.symbol, line.charge, line.transition)) + + return tuple(coefficients) + cpdef FreeFreeGauntFactor free_free_gaunt_factor(self): """ Returns the Maxwellian-averaged free-free Gaunt factor interpolated over the data diff --git a/cherab/core/model/beam/beam_emission.pyx b/cherab/core/model/beam/beam_emission.pyx index 67c41ac1..e00aaf5d 100644 --- a/cherab/core/model/beam/beam_emission.pyx +++ b/cherab/core/model/beam/beam_emission.pyx @@ -1,8 +1,8 @@ # cython: language_level=3 -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -212,8 +212,8 @@ cdef class BeamEmissionLine(BeamModel): self._rates_list.append((species, rate)) # instance line shape renderer - self._lineshape = BeamEmissionMultiplet(self._line, self._wavelength, self._beam, self._sigma_to_pi, - self._sigma1_to_sigma0, self._pi2_to_pi3, self._pi4_to_pi3) + self._lineshape = BeamEmissionMultiplet(self._line, self._wavelength, self._beam, self._atomic_data, + self._sigma_to_pi, self._sigma1_to_sigma0, self._pi2_to_pi3, self._pi4_to_pi3) def _change(self): diff --git a/cherab/core/model/lineshape/base.pxd b/cherab/core/model/lineshape/base.pxd index 4ec2d2e6..b6267d45 100644 --- a/cherab/core/model/lineshape/base.pxd +++ b/cherab/core/model/lineshape/base.pxd @@ -22,6 +22,7 @@ from raysect.optical cimport Spectrum, Point3D, Vector3D from cherab.core.atomic cimport Line from cherab.core.species cimport Species from cherab.core.plasma cimport Plasma +from cherab.core.atomic cimport AtomicData from cherab.core.math.integrators cimport Integrator1D @@ -32,6 +33,7 @@ cdef class LineShapeModel: double wavelength Species target_species Plasma plasma + AtomicData atomic_data Integrator1D integrator cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum) diff --git a/cherab/core/model/lineshape/base.pyx b/cherab/core/model/lineshape/base.pyx index 7577508e..0b0cf7a2 100644 --- a/cherab/core/model/lineshape/base.pyx +++ b/cherab/core/model/lineshape/base.pyx @@ -27,16 +27,18 @@ cdef class LineShapeModel: :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. :param Integrator1D integrator: Integrator1D instance to integrate the line shape over the spectral bin. Default is None. """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, Integrator1D integrator=None): + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, Integrator1D integrator=None): self.line = line self.wavelength = wavelength self.target_species = target_species self.plasma = plasma + self.atomic_data = atomic_data self.integrator = integrator cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): diff --git a/cherab/core/model/lineshape/beam/base.pxd b/cherab/core/model/lineshape/beam/base.pxd index b3da2323..e5633446 100644 --- a/cherab/core/model/lineshape/beam/base.pxd +++ b/cherab/core/model/lineshape/beam/base.pxd @@ -21,6 +21,7 @@ from raysect.optical cimport Spectrum, Point3D, Vector3D from cherab.core.atomic cimport Line from cherab.core.beam cimport Beam +from cherab.core.atomic cimport AtomicData cdef class BeamLineShapeModel: @@ -30,6 +31,7 @@ cdef class BeamLineShapeModel: Line line double wavelength Beam beam + AtomicData atomic_data cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum) diff --git a/cherab/core/model/lineshape/beam/base.pyx b/cherab/core/model/lineshape/beam/base.pyx index 793e17d4..d333dd6e 100644 --- a/cherab/core/model/lineshape/beam/base.pyx +++ b/cherab/core/model/lineshape/beam/base.pyx @@ -26,13 +26,15 @@ cdef class BeamLineShapeModel: :param Line line: The emission line object for this line shape. :param float wavelength: The rest wavelength for this emission line. :param Beam beam: The beam class that is emitting. + :param AtomicData atomic_data: The atomic data provider. """ - def __init__(self, Line line, double wavelength, Beam beam): + def __init__(self, Line line, double wavelength, Beam beam, AtomicData atomic_data): self.line = line self.wavelength = wavelength self.beam = beam + self.atomic_data = atomic_data cpdef Spectrum add_line(self, double radiance, Point3D beam_point, Point3D plasma_point, Vector3D beam_direction, Vector3D observation_direction, Spectrum spectrum): diff --git a/cherab/core/model/lineshape/beam/mse.pyx b/cherab/core/model/lineshape/beam/mse.pyx index 3075cff2..9c64d435 100644 --- a/cherab/core/model/lineshape/beam/mse.pyx +++ b/cherab/core/model/lineshape/beam/mse.pyx @@ -23,6 +23,7 @@ from raysect.optical cimport Spectrum, Point3D, Vector3D from cherab.core.plasma cimport Plasma from cherab.core.beam cimport Beam +from cherab.core.atomic cimport AtomicData from cherab.core.atomic cimport Line from cherab.core.math.function cimport autowrap_function1d, autowrap_function2d from cherab.core.utility.constants cimport ATOMIC_MASS, ELEMENTARY_CHARGE @@ -47,10 +48,10 @@ cdef class BeamEmissionMultiplet(BeamLineShapeModel): Produces Beam Emission Multiplet line shape, also known as the Motional Stark Effect spectrum. """ - def __init__(self, Line line, double wavelength, Beam beam, object sigma_to_pi, - object sigma1_to_sigma0, object pi2_to_pi3, object pi4_to_pi3): + def __init__(self, Line line, double wavelength, Beam beam, AtomicData atomic_data, + object sigma_to_pi, object sigma1_to_sigma0, object pi2_to_pi3, object pi4_to_pi3): - super().__init__(line, wavelength, beam) + super().__init__(line, wavelength, beam, atomic_data) self._sigma_to_pi = autowrap_function2d(sigma_to_pi) self._sigma1_to_sigma0 = autowrap_function1d(sigma1_to_sigma0) diff --git a/cherab/core/model/lineshape/gaussian.pyx b/cherab/core/model/lineshape/gaussian.pyx index 936d70df..ef854ea9 100644 --- a/cherab/core/model/lineshape/gaussian.pyx +++ b/cherab/core/model/lineshape/gaussian.pyx @@ -21,7 +21,7 @@ from libc.math cimport erf, M_SQRT2, floor, ceil from raysect.optical cimport Point3D, Vector3D -from cherab.core.atomic cimport Line +from cherab.core.atomic cimport Line, AtomicData from cherab.core.species cimport Species from cherab.core.plasma cimport Plasma from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening @@ -98,6 +98,7 @@ cdef class GaussianLine(LineShapeModel): :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. .. code-block:: pycon @@ -110,9 +111,9 @@ cdef class GaussianLine(LineShapeModel): >>> plasma.models.add(excit) """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma): + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data): - super().__init__(line, wavelength, target_species, plasma) + super().__init__(line, wavelength, target_species, plasma, atomic_data) @cython.boundscheck(False) @cython.wraparound(False) diff --git a/cherab/core/model/lineshape/multiplet.pyx b/cherab/core/model/lineshape/multiplet.pyx index e30093eb..6f9e39ab 100644 --- a/cherab/core/model/lineshape/multiplet.pyx +++ b/cherab/core/model/lineshape/multiplet.pyx @@ -22,7 +22,7 @@ import numpy as np from raysect.optical cimport Spectrum, Point3D, Vector3D -from cherab.core.atomic cimport Line +from cherab.core.atomic cimport Line, AtomicData from cherab.core.species cimport Species from cherab.core.plasma cimport Plasma from cherab.core.model.lineshape.doppler cimport doppler_shift, thermal_broadening @@ -53,6 +53,7 @@ cdef class MultipletLineShape(LineShapeModel): :param float wavelength: The rest wavelength of the base emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. :param multiplet: An Nx2 array that specifies the multiplet wavelengths and line ratios. .. code-block:: pycon @@ -69,10 +70,10 @@ cdef class MultipletLineShape(LineShapeModel): >>> plasma.models.add(excit) """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, object multiplet): - super().__init__(line, wavelength, target_species, plasma) + super().__init__(line, wavelength, target_species, plasma, atomic_data) multiplet = np.array(multiplet, dtype=np.float64) diff --git a/cherab/core/model/lineshape/stark.pyx b/cherab/core/model/lineshape/stark.pyx index e290ca98..9e333a5f 100644 --- a/cherab/core/model/lineshape/stark.pyx +++ b/cherab/core/model/lineshape/stark.pyx @@ -24,7 +24,7 @@ from libc.math cimport sqrt, floor, ceil, fabs, log, exp from raysect.core.math.function.float cimport Function1D from raysect.optical cimport Point3D, Vector3D -from cherab.core.atomic cimport Line +from cherab.core.atomic cimport Line, AtomicData from cherab.core.species cimport Species from cherab.core.plasma cimport Plasma from cherab.core.atomic.elements import hydrogen, deuterium, tritium @@ -202,16 +202,14 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): `c` = [5.14820e-04, 1.38821e+00, -9.60424e-02, -3.83995e-02, -7.40042e-03, -5.47626e-04]. - Call `show_supported_transitions()` to see the list of supported transitions and - default model coefficients. - :param Line line: The emission line object for this line shape. :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. - :param dict stark_model_coefficients: Alternative model coefficients in the form - {line_ij: (c_ij, a_ij, b_ij), ...}. - If None, the default model parameters will be used. + :param AtomicData atomic_data: The atomic data provider. + :param tuple stark_model_coefficients: Stark model coefficients in the form (c_ij, a_ij, b_ij). + Default is None (will use + `atomic_data.stark_model_coefficients`). :param Integrator1D integrator: Integrator1D instance to integrate the line shape over the spectral bin. Default is `GaussianQuadrature()`. :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: @@ -220,56 +218,14 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): "no" - leave all components (default). """ - STARK_MODEL_COEFFICIENTS_DEFAULT = { - Line(hydrogen, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(hydrogen, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(hydrogen, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(hydrogen, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(hydrogen, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(hydrogen, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(hydrogen, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(hydrogen, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(hydrogen, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(hydrogen, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(hydrogen, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(hydrogen, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(hydrogen, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), - Line(deuterium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(deuterium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(deuterium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(deuterium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(deuterium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(deuterium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(deuterium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(deuterium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(deuterium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(deuterium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(deuterium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(deuterium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(deuterium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033), - Line(tritium, 0, (3, 2)): (3.71e-18, 0.7665, 0.064), - Line(tritium, 0, (4, 2)): (8.425e-18, 0.7803, 0.050), - Line(tritium, 0, (5, 2)): (1.31e-15, 0.6796, 0.030), - Line(tritium, 0, (6, 2)): (3.954e-16, 0.7149, 0.028), - Line(tritium, 0, (7, 2)): (6.258e-16, 0.712, 0.029), - Line(tritium, 0, (8, 2)): (7.378e-16, 0.7159, 0.032), - Line(tritium, 0, (9, 2)): (8.947e-16, 0.7177, 0.033), - Line(tritium, 0, (4, 3)): (1.330e-16, 0.7449, 0.045), - Line(tritium, 0, (5, 3)): (6.64e-16, 0.7356, 0.044), - Line(tritium, 0, (6, 3)): (2.481e-15, 0.7118, 0.016), - Line(tritium, 0, (7, 3)): (3.270e-15, 0.7137, 0.029), - Line(tritium, 0, (8, 3)): (4.343e-15, 0.7133, 0.032), - Line(tritium, 0, (9, 3)): (5.588e-15, 0.7165, 0.033) - } - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, - dict stark_model_coefficients=None, Integrator1D integrator=GaussianQuadrature(), polarisation='no'): - - stark_model_coefficients = stark_model_coefficients or self.STARK_MODEL_COEFFICIENTS_DEFAULT + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, + tuple stark_model_coefficients=None, Integrator1D integrator=GaussianQuadrature(), polarisation='no'): + + super().__init__(line, wavelength, target_species, plasma, atomic_data, polarisation, integrator) try: # Fitted Stark Constants - cij, aij, bij = stark_model_coefficients[line] + cij, aij, bij = stark_model_coefficients or self.atomic_data.stark_model_coefficients(line) if cij <= 0: raise ValueError('Coefficient c_ij must be positive.') if aij <= 0: @@ -288,14 +244,6 @@ cdef class StarkBroadenedLine(ZeemanLineShapeModel): self._weight_poly_coeff = [5.14820e-04, 1.38821e+00, -9.60424e-02, -3.83995e-02, -7.40042e-03, -5.47626e-04] - super().__init__(line, wavelength, target_species, plasma, polarisation, integrator) - - @classmethod - def show_supported_transitions(cls): - """ Prints all supported transitions.""" - for line, coeff in cls.STARK_MODEL_COEFFICIENTS_DEFAULT.items(): - print('{}: c_ij={}, a_ij={}, b_ij={}'.format(line, coeff[0], coeff[1], coeff[2])) - @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) diff --git a/cherab/core/model/lineshape/zeeman.pyx b/cherab/core/model/lineshape/zeeman.pyx index 81bbb991..eeea9f7b 100644 --- a/cherab/core/model/lineshape/zeeman.pyx +++ b/cherab/core/model/lineshape/zeeman.pyx @@ -21,7 +21,7 @@ from libc.math cimport sqrt from raysect.optical cimport Spectrum, Point3D, Vector3D -from cherab.core.atomic cimport Line +from cherab.core.atomic cimport Line, AtomicData from cherab.core.species cimport Species from cherab.core.plasma cimport Plasma from cherab.core.atomic.elements import hydrogen, deuterium, tritium, helium, helium3, beryllium, boron, carbon, nitrogen, oxygen, neon @@ -51,6 +51,7 @@ cdef class ZeemanLineShapeModel(LineShapeModel): :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: "pi" - leave only :math:`\pi`-polarised components, "sigma" - leave only :math:`\sigma`-polarised components, @@ -59,9 +60,9 @@ cdef class ZeemanLineShapeModel(LineShapeModel): over the spectral bin. Default is None. """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation, - Integrator1D integrator=None): - super().__init__(line, wavelength, target_species, plasma, integrator) + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, + polarisation='no', Integrator1D integrator=None): + super().__init__(line, wavelength, target_species, plasma, atomic_data, integrator) self.polarisation = polarisation @@ -94,15 +95,16 @@ cdef class ZeemanTriplet(ZeemanLineShapeModel): :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: "pi" - leave central component, "sigma" - leave side components, "no" - all components (default). """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, polarisation='no'): + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, polarisation='no'): - super().__init__(line, wavelength, target_species, plasma, polarisation) + super().__init__(line, wavelength, target_species, plasma, atomic_data, polarisation) @cython.boundscheck(False) @cython.wraparound(False) @@ -174,9 +176,6 @@ cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): :math:`\frac{W_{Zeeman}}{W_{Doppler}} = \beta T^{\gamma}`, where `T` is the species temperature in eV. - Call `show_supported_transitions()` to see the list of supported transitions and - default parameters of the model. - For details see A. Blom and C. Jupén, Parametrisation of the Zeeman effect for hydrogen-like spectra in high-temperature plasmas, Plasma Phys. Control. Fusion 44 (2002) `1229-1241 @@ -186,70 +185,22 @@ cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): :param float wavelength: The rest wavelength for this emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. - :param dict line_parameters: Alternative parameters of the model in the form - {line_i: (alpha_i, beta_i, gamma_i), ...}. - If None, the default model parameters will be used. + :param AtomicData atomic_data: The atomic data provider. + :param tuple line_parameters: Parameters of the model in the form (alpha, beta, gamma). + Default is None (will use `atomic_data.zeeman_triplet_parameters`). :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: "pi" - leave central component, "sigma" - leave side components, "no" - all components (default). """ - LINE_PARAMETERS_DEFAULT = { # alpha, beta, gamma parameters for selected lines - Line(hydrogen, 0, (3, 2)): (0.0402267, 0.3415, -0.5247), - Line(hydrogen, 0, (4, 2)): (0.0220724, 0.2837, -0.5346), - Line(deuterium, 0, (3, 2)): (0.0402068, 0.4384, -0.5015), - Line(deuterium, 0, (4, 2)): (0.0220610, 0.3702, -0.5132), - Line(helium3, 1, (4, 3)): (0.0205200, 1.4418, -0.4892), - Line(helium3, 1, (5, 3)): (0.0095879, 1.2576, -0.5001), - Line(helium3, 1, (6, 4)): (0.0401980, 0.8976, -0.4971), - Line(helium3, 1, (7, 4)): (0.0273538, 0.8529, -0.5039), - Line(helium, 1, (4, 3)): (0.0205206, 1.6118, -0.4838), - Line(helium, 1, (5, 3)): (0.0095879, 1.4294, -0.4975), - Line(helium, 1, (6, 4)): (0.0401955, 1.0058, -0.4918), - Line(helium, 1, (7, 4)): (0.0273521, 0.9563, -0.4981), - Line(beryllium, 3, (5, 4)): (0.0060354, 2.1245, -0.3190), - Line(beryllium, 3, (6, 5)): (0.0202754, 1.6538, -0.3192), - Line(beryllium, 3, (7, 5)): (0.0078966, 1.7017, -0.3348), - Line(beryllium, 3, (8, 6)): (0.0205025, 1.4581, -0.3450), - Line(boron, 4, (6, 5)): (0.0083423, 2.0519, -0.2960), - Line(boron, 4, (7, 6)): (0.0228379, 1.6546, -0.2941), - Line(boron, 4, (8, 6)): (0.0084065, 1.8041, -0.3177), - Line(boron, 4, (8, 7)): (0.0541883, 1.4128, -0.2966), - Line(boron, 4, (9, 7)): (0.0190781, 1.5440, -0.3211), - Line(boron, 4, (10, 8)): (0.0391914, 1.3569, -0.3252), - Line(carbon, 5, (6, 5)): (0.0040900, 2.4271, -0.2818), - Line(carbon, 5, (7, 6)): (0.0110398, 1.9785, -0.2816), - Line(carbon, 5, (8, 6)): (0.0040747, 2.1776, -0.3035), - Line(carbon, 5, (8, 7)): (0.0261405, 1.6689, -0.2815), - Line(carbon, 5, (9, 7)): (0.0092096, 1.8495, -0.3049), - Line(carbon, 5, (10, 8)): (0.0189020, 1.6191, -0.3078), - Line(carbon, 5, (11, 8)): (0.0110428, 1.6600, -0.3162), - Line(carbon, 5, (10, 9)): (0.0359009, 1.4464, -0.3104), - Line(nitrogen, 6, (7, 6)): (0.0060010, 2.4789, -0.2817), - Line(nitrogen, 6, (8, 7)): (0.0141271, 2.0249, -0.2762), - Line(nitrogen, 6, (9, 8)): (0.0300127, 1.7415, -0.2753), - Line(nitrogen, 6, (10, 8)): (0.0102089, 1.9464, -0.2975), - Line(nitrogen, 6, (11, 9)): (0.0193799, 1.7133, -0.2973), - Line(oxygen, 7, (8, 7)): (0.0083081, 2.4263, -0.2747), - Line(oxygen, 7, (9, 8)): (0.0176049, 2.0652, -0.2721), - Line(oxygen, 7, (10, 8)): (0.0059933, 2.3445, -0.2944), - Line(oxygen, 7, (10, 9)): (0.0343805, 1.8122, -0.2718), - Line(oxygen, 7, (11, 9)): (0.0113640, 2.0268, -0.2911), - Line(neon, 9, (9, 8)): (0.0072488, 2.8838, -0.2758), - Line(neon, 9, (10, 9)): (0.0141002, 2.4755, -0.2718), - Line(neon, 9, (11, 9)): (0.0046673, 2.8410, -0.2917), - Line(neon, 9, (11, 10)): (0.0257292, 2.1890, -0.2715) - } - - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, dict line_parameters=None, polarisation='no'): - - super().__init__(line, wavelength, target_species, plasma, polarisation) - - line_parameters = line_parameters or self.LINE_PARAMETERS_DEFAULT + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, + tuple line_parameters=None, polarisation='no'): + + super().__init__(line, wavelength, target_species, plasma, atomic_data, polarisation) try: - alpha, beta, gamma = line_parameters[self.line] + alpha, beta, gamma = line_parameters or self.atomic_data.zeeman_triplet_parameters(line) if alpha <= 0: raise ValueError('Parameter alpha must be positive.') if beta < 0: @@ -261,12 +212,6 @@ cdef class ParametrisedZeemanTriplet(ZeemanLineShapeModel): except KeyError: raise ValueError('Data for {} is not available.'.format(self.line)) - @classmethod - def show_supported_transitions(cls): - """ Prints all supported transitions.""" - for line, param in cls.LINE_PARAMETERS_DEFAULT.items(): - print('{}: alpha={}, beta={}, gamma={}'.format(line, param[0], param[1], param[2])) - @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) @@ -337,9 +282,11 @@ cdef class ZeemanMultiplet(ZeemanLineShapeModel): :param float wavelength: The rest wavelength of the base emission line. :param Species target_species: The target plasma species that is emitting. :param Plasma plasma: The emitting plasma object. + :param AtomicData atomic_data: The atomic data provider. :param zeeman_structure: A ``ZeemanStructure`` object that provides wavelengths and ratios of :math:`\pi`-/:math:`\sigma^{+}`-/:math:`\sigma^{-}`-polarised components for any given magnetic field strength. + Default is None (will use atomic_data.zeeman_structure). :param str polarisation: Leaves only :math:`\pi`-/:math:`\sigma`-polarised components: "pi" - leave only :math:`\pi`-polarised components, "sigma" - leave only :math:`\sigma`-polarised components, @@ -347,12 +294,12 @@ cdef class ZeemanMultiplet(ZeemanLineShapeModel): """ - def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, - ZeemanStructure zeeman_structure, polarisation='no'): + def __init__(self, Line line, double wavelength, Species target_species, Plasma plasma, AtomicData atomic_data, + ZeemanStructure zeeman_structure=None, polarisation='no'): - super().__init__(line, wavelength, target_species, plasma, polarisation) + super().__init__(line, wavelength, target_species, plasma, atomic_data, polarisation) - self._zeeman_structure = zeeman_structure + self._zeeman_structure = zeeman_structure or self.atomic_data.zeeman_structure(line) @cython.boundscheck(False) @cython.wraparound(False) diff --git a/cherab/core/model/plasma/impact_excitation.pyx b/cherab/core/model/plasma/impact_excitation.pyx index 2aa9691c..e0305889 100644 --- a/cherab/core/model/plasma/impact_excitation.pyx +++ b/cherab/core/model/plasma/impact_excitation.pyx @@ -1,6 +1,8 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -100,7 +102,7 @@ cdef class ExcitationLine(PlasmaModel): # instance line shape renderer self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, - *self._lineshape_args, **self._lineshape_kwargs) + self._atomic_data, *self._lineshape_args, **self._lineshape_kwargs) def _change(self): diff --git a/cherab/core/model/plasma/recombination.pyx b/cherab/core/model/plasma/recombination.pyx index 6edac138..7193d5e8 100644 --- a/cherab/core/model/plasma/recombination.pyx +++ b/cherab/core/model/plasma/recombination.pyx @@ -1,6 +1,8 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# cython: language_level=3 + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -103,7 +105,7 @@ cdef class RecombinationLine(PlasmaModel): # instance line shape renderer self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, - *self._lineshape_args, **self._lineshape_kwargs) + self._atomic_data, *self._lineshape_args, **self._lineshape_kwargs) def _change(self): diff --git a/cherab/core/tests/test_lineshapes.py b/cherab/core/tests/test_lineshapes.py index 0f7ab7ef..6f300e59 100644 --- a/cherab/core/tests/test_lineshapes.py +++ b/cherab/core/tests/test_lineshapes.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -26,7 +26,7 @@ from raysect.core.math.function.float import Arg1D, Constant1D from raysect.optical import Spectrum -from cherab.core import Beam, Line +from cherab.core import Beam, Line, AtomicData from cherab.core.math.integrators import GaussianQuadrature from cherab.core.atomic import deuterium, nitrogen, ZeemanStructure from cherab.tools.plasmas.slab import build_constant_slab_plasma @@ -47,6 +47,7 @@ class TestLineShapes(unittest.TestCase): (nitrogen, 1, 1.e17, 10., Vector3D(1.e4, 5.e4, 0))] plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=1e19, electron_temperature=20., plasma_species=plasma_species, b_field=Vector3D(0, 5., 0)) + atomic_data = AtomicData() beam = Beam() beam.plasma = plasma beam.energy = 60000 @@ -58,7 +59,7 @@ def test_gaussian_line(self): line = Line(deuterium, 0, (3, 2)) # D-alpha line target_species = self.plasma.composition.get(line.element, line.charge) wavelength = 656.104 - gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma, self.atomic_data) # spectrum parameters min_wavelength = wavelength - 0.5 @@ -93,7 +94,7 @@ def test_multiplet_line_shape(self): target_species = self.plasma.composition.get(line.element, line.charge) multiplet = [[403.509, 404.132, 404.354, 404.479, 405.692], [0.205, 0.562, 0.175, 0.029, 0.029]] wavelength = 404.21 - multiplet_line = MultipletLineShape(line, wavelength, target_species, self.plasma, multiplet) + multiplet_line = MultipletLineShape(line, wavelength, target_species, self.plasma, self.atomic_data, multiplet) # spectrum parameters min_wavelength = min(multiplet[0]) - 0.5 @@ -129,7 +130,7 @@ def test_zeeman_triplet(self): line = Line(deuterium, 0, (3, 2)) # D-alpha line target_species = self.plasma.composition.get(line.element, line.charge) wavelength = 656.104 - triplet = ZeemanTriplet(line, wavelength, target_species, self.plasma) + triplet = ZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) # spectrum parameters min_wavelength = wavelength - 0.5 @@ -180,7 +181,7 @@ def test_parametrised_zeeman_triplet(self): line = Line(deuterium, 0, (3, 2)) # D-alpha line target_species = self.plasma.composition.get(line.element, line.charge) wavelength = 656.104 - triplet = ParametrisedZeemanTriplet(line, wavelength, target_species, self.plasma) + triplet = ParametrisedZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) # spectrum parameters min_wavelength = wavelength - 0.5 @@ -198,7 +199,7 @@ def test_parametrised_zeeman_triplet(self): spectrum[pol] = triplet.add_line(radiance, point, direction, spectrum[pol]) # validating - alpha, beta, gamma = triplet.LINE_PARAMETERS_DEFAULT[line] + alpha, beta, gamma = self.atomic_data.zeeman_triplet_parameters(line) temperature = target_species.distribution.effective_temperature(point.x, point.y, point.z) velocity = target_species.distribution.bulk_velocity(point.x, point.y, point.z) sigma = np.sqrt(temperature * ELEMENTARY_CHARGE / (line.element.atomic_weight * ATOMIC_MASS)) * wavelength / SPEED_OF_LIGHT @@ -239,7 +240,7 @@ def test_zeeman_multiplet(self): sigma_minus_components = [(HC_EV_NM / (photon_energy + BOHR_MAGNETON * Arg1D()), Constant1D(0.5))] zeeman_structure = ZeemanStructure(pi_components, sigma_plus_components, sigma_minus_components) - multiplet = ZeemanMultiplet(line, wavelength, target_species, self.plasma, zeeman_structure) + multiplet = ZeemanMultiplet(line, wavelength, target_species, self.plasma, self.atomic_data, zeeman_structure) # spectrum parameters min_wavelength = wavelength - 0.5 @@ -291,7 +292,7 @@ def test_stark_broadened_line(self): target_species = self.plasma.composition.get(line.element, line.charge) wavelength = 656.104 integrator = GaussianQuadrature(relative_tolerance=1.e-5) - stark_line = StarkBroadenedLine(line, wavelength, target_species, self.plasma, integrator=integrator) + stark_line = StarkBroadenedLine(line, wavelength, target_species, self.plasma, self.atomic_data, integrator=integrator) # spectrum parameters min_wavelength = wavelength - 0.2 @@ -323,7 +324,7 @@ def test_stark_broadened_line(self): fwhm_gauss = 2 * np.sqrt(2 * np.log(2)) * sigma # Lorentzian parameters - cij, aij, bij = stark_line.STARK_MODEL_COEFFICIENTS_DEFAULT[line] + cij, aij, bij = self.atomic_data.stark_model_coefficients(line) ne = self.plasma.electron_distribution.density(point.x, point.y, point.z) te = self.plasma.electron_distribution.effective_temperature(point.x, point.y, point.z) fwhm_lorentz = cij * ne**aij / (te**bij) @@ -386,8 +387,8 @@ def test_beam_emission_multiplet(self): sigma1_to_sigma0 = 0.7060001671878492 pi2_to_pi3 = 0.3140003593919741 pi4_to_pi3 = 0.7279994935840365 - mse_line = BeamEmissionMultiplet(line, wavelength, self.beam, sigma_to_pi, - sigma1_to_sigma0, pi2_to_pi3, pi4_to_pi3) + mse_line = BeamEmissionMultiplet(line, wavelength, self.beam, self.atomic_data, + sigma_to_pi, sigma1_to_sigma0, pi2_to_pi3, pi4_to_pi3) # spectrum parameters min_wavelength = wavelength - 3 From 09d519cdddd4c0bc50cba8e33a1a7e6ce4e51a0e Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Fri, 5 May 2023 12:21:21 +0300 Subject: [PATCH 13/16] Updated BeamCXLine and related tests to support new LineShapeModel interface. --- cherab/core/model/beam/charge_exchange.pyx | 2 +- cherab/core/tests/test_beamcxline.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cherab/core/model/beam/charge_exchange.pyx b/cherab/core/model/beam/charge_exchange.pyx index 295d5f5f..50f34249 100644 --- a/cherab/core/model/beam/charge_exchange.pyx +++ b/cherab/core/model/beam/charge_exchange.pyx @@ -358,7 +358,7 @@ cdef class BeamCXLine(BeamModel): self._excited_beam_data.append((rate, population_data)) # instance line shape renderer - self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, + self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, self._atomic_data, *self._lineshape_args, **self._lineshape_kwargs) def _change(self): diff --git a/cherab/core/tests/test_beamcxline.py b/cherab/core/tests/test_beamcxline.py index 6de8ad58..6fdd80b4 100644 --- a/cherab/core/tests/test_beamcxline.py +++ b/cherab/core/tests/test_beamcxline.py @@ -120,7 +120,7 @@ def test_default_lineshape(self): target_species = self.plasma.composition.get(line.element, line.charge + 1) wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) - gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -152,7 +152,7 @@ def test_custom_lineshape(self): target_species = self.plasma.composition.get(line.element, line.charge + 1) wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) - zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma) + zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) From b84f9ad459c74336d2bedc10ff7b2350c261f125 Mon Sep 17 00:00:00 2001 From: vsnever Date: Tue, 19 Dec 2023 14:15:14 +0300 Subject: [PATCH 14/16] Add missing declarations for internal variables in ZeemanMultiplet. --- cherab/core/model/lineshape/zeeman.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cherab/core/model/lineshape/zeeman.pyx b/cherab/core/model/lineshape/zeeman.pyx index eeea9f7b..2be33020 100644 --- a/cherab/core/model/lineshape/zeeman.pyx +++ b/cherab/core/model/lineshape/zeeman.pyx @@ -308,9 +308,9 @@ cdef class ZeemanMultiplet(ZeemanLineShapeModel): cpdef Spectrum add_line(self, double radiance, Point3D point, Vector3D direction, Spectrum spectrum): cdef int i - cdef double ts, sigma, shifted_wavelength, component_radiance - cdef Vector3D ion_velocity - cdef double[:, :] multiplet_pi_mv, multiplet_sigma_mv + cdef double ts, sigma, shifted_wavelength, component_radiance, b_magn, cos_sqr, sin_sqr + cdef Vector3D ion_velocity, b_field + cdef double[:, :] multiplet_mv ts = self.target_species.distribution.effective_temperature(point.x, point.y, point.z) if ts <= 0.0: From 87d3d6610dced05daf4ca02d0d55f9f3f905f4c1 Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 31 Jul 2024 15:53:06 +0200 Subject: [PATCH 15/16] Pass atomic_data to lineshape initialiser in ThermalCXLine. --- cherab/core/model/plasma/thermal_cx.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherab/core/model/plasma/thermal_cx.pyx b/cherab/core/model/plasma/thermal_cx.pyx index 1325919c..88af9ae8 100644 --- a/cherab/core/model/plasma/thermal_cx.pyx +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -152,7 +152,7 @@ cdef class ThermalCXLine(PlasmaModel): # instance line shape renderer self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, - *self._lineshape_args, **self._lineshape_kwargs) + self._atomic_data, *self._lineshape_args, **self._lineshape_kwargs) def _change(self): From d74d66c8df5b1e9f503f5234764bca3d34d1e640 Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 31 Jul 2024 15:54:48 +0200 Subject: [PATCH 16/16] Pass atomic_data to custom lineshapes in the line emission tests. --- cherab/core/tests/test_line_emission.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cherab/core/tests/test_line_emission.py b/cherab/core/tests/test_line_emission.py index b55af871..bf40aac0 100644 --- a/cherab/core/tests/test_line_emission.py +++ b/cherab/core/tests/test_line_emission.py @@ -125,7 +125,7 @@ def test_default_lineshape(self): ni = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length - gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -154,7 +154,7 @@ def test_custom_lineshape(self): ni = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length - zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma) + zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -200,7 +200,7 @@ def test_default_lineshape(self): ni = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length - gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -229,7 +229,7 @@ def test_custom_lineshape(self): ni = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length - zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma) + zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -279,7 +279,7 @@ def test_default_lineshape(self): receiver_density = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * receiver_density * donor_density * self.slab_length - gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) @@ -311,7 +311,7 @@ def test_custom_lineshape(self): receiver_density = target_species.distribution.density(0.5, 0, 0) radiance = 0.25 / np.pi * rate * receiver_density * donor_density * self.slab_length - zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma) + zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma, self.atomic_data) spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum)