From 6584c90aba35accbf6c56f17439c740d73a7ea2b Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 18 Nov 2022 17:16:37 +0300 Subject: [PATCH 01/32] Add ThermalCXLine model. --- cherab/core/atomic/rates.pxd | 4 +- cherab/core/atomic/rates.pyx | 24 ++++- cherab/core/model/plasma/__init__.pxd | 1 + cherab/core/model/plasma/__init__.py | 1 + cherab/core/model/plasma/thermal_cx.pxd | 35 +++++++ cherab/core/model/plasma/thermal_cx.pyx | 133 ++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 cherab/core/model/plasma/thermal_cx.pxd create mode 100644 cherab/core/model/plasma/thermal_cx.pyx diff --git a/cherab/core/atomic/rates.pxd b/cherab/core/atomic/rates.pxd index 1f844122..239fafc1 100644 --- a/cherab/core/atomic/rates.pxd +++ b/cherab/core/atomic/rates.pxd @@ -46,8 +46,8 @@ cdef class RecombinationPEC(_PECRate): pass -cdef class ThermalCXPEC(_PECRate): - pass +cdef class ThermalCXPEC: + cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999 cdef class BeamCXPEC: diff --git a/cherab/core/atomic/rates.pyx b/cherab/core/atomic/rates.pyx index 25b94e7e..5cd1916c 100644 --- a/cherab/core/atomic/rates.pyx +++ b/cherab/core/atomic/rates.pyx @@ -101,8 +101,8 @@ cdef class _PECRate: cpdef double evaluate(self, double density, double temperature) except? -1e999: """Returns a photon emissivity coefficient at given conditions. - :param temperature: Receiver ion temperature in eV. - :param density: Receiver ion density in m^-3 + :param density: Electron density in m^-3. + :param temperature: Electron temperature in eV. :return: The effective PEC rate in W/m^3. """ raise NotImplementedError("The evaluate() virtual method must be implemented.") @@ -130,11 +130,27 @@ cdef class RecombinationPEC(_PECRate): pass -cdef class ThermalCXPEC(_PECRate): +cdef class ThermalCXPEC: """ Thermal charge exchange rate coefficient. """ - pass + + def __call__(self, double electron_density, double electron_temperature, donor_temperature): + """Returns a CX photon emissivity coefficient at the specified plasma conditions. + + This function just wraps the cython evaluate() method. + """ + return self.evaluate(electron_density, electron_temperature, donor_temperature) + + cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999: + """Returns a CX photon emissivity coefficient at given conditions. + + :param electron_density: Electron density in m^-3. + :param electron_temperature: Electron temperature in eV. + :param donor_temperature: Donor temperature in eV. + :return: The effective CX PEC rate in W/m^3. + """ + raise NotImplementedError("The evaluate() virtual method must be implemented.") cdef class BeamCXPEC: diff --git a/cherab/core/model/plasma/__init__.pxd b/cherab/core/model/plasma/__init__.pxd index 6971f828..c398fd30 100644 --- a/cherab/core/model/plasma/__init__.pxd +++ b/cherab/core/model/plasma/__init__.pxd @@ -20,4 +20,5 @@ from cherab.core.model.plasma.bremsstrahlung cimport Bremsstrahlung from cherab.core.model.plasma.impact_excitation cimport ExcitationLine from cherab.core.model.plasma.recombination cimport RecombinationLine +from cherab.core.model.plasma.thermal_cx cimport ThermalCXLine from cherab.core.model.plasma.total_radiated_power cimport TotalRadiatedPower diff --git a/cherab/core/model/plasma/__init__.py b/cherab/core/model/plasma/__init__.py index 52766032..436060b5 100644 --- a/cherab/core/model/plasma/__init__.py +++ b/cherab/core/model/plasma/__init__.py @@ -20,4 +20,5 @@ from .bremsstrahlung import Bremsstrahlung from .impact_excitation import ExcitationLine from .recombination import RecombinationLine +from .thermal_cx import ThermalCXLine from .total_radiated_power import TotalRadiatedPower diff --git a/cherab/core/model/plasma/thermal_cx.pxd b/cherab/core/model/plasma/thermal_cx.pxd new file mode 100644 index 00000000..636fddaa --- /dev/null +++ b/cherab/core/model/plasma/thermal_cx.pxd @@ -0,0 +1,35 @@ +# 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. + +from cherab.core.atomic cimport Line +from cherab.core.plasma cimport PlasmaModel +from cherab.core.species cimport Species +from cherab.core.model.lineshape cimport LineShapeModel + + +cdef class ThermalCXLine(PlasmaModel): + + cdef: + Line _line + double _wavelength + Species _target_species + list _rates + LineShapeModel _lineshape + object _lineshape_class, _lineshape_args, _lineshape_kwargs + + cdef int _populate_cache(self) except -1 diff --git a/cherab/core/model/plasma/thermal_cx.pyx b/cherab/core/model/plasma/thermal_cx.pyx new file mode 100644 index 00000000..f290649a --- /dev/null +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -0,0 +1,133 @@ +# 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. + +from raysect.optical cimport Spectrum, Point3D, Vector3D +from cherab.core cimport Plasma, AtomicData +from cherab.core.atomic cimport ThermalCXPEC +from cherab.core.model.lineshape cimport GaussianLine, LineShapeModel +from cherab.core.utility.constants cimport RECIP_4_PI + + +cdef class ThermalCXLine(PlasmaModel): + + def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, + object lineshape_args=None, object lineshape_kwargs=None): + + super().__init__(plasma, atomic_data) + + self._line = line + + self._lineshape_class = lineshape or GaussianLine + if not issubclass(self._lineshape_class, LineShapeModel): + raise TypeError("The attribute lineshape must be a subclass of LineShapeModel.") + + if lineshape_args: + self._lineshape_args = lineshape_args + else: + self._lineshape_args = [] + if lineshape_kwargs: + self._lineshape_kwargs = lineshape_kwargs + else: + self._lineshape_kwargs = {} + + # ensure that cache is initialised + self._change() + + def __repr__(self): + return ''.format(self._line.element.name, self._line.charge, self._line.transition) + + cpdef Spectrum emission(self, Point3D point, Vector3D direction, Spectrum spectrum): + + cdef: + double ne, te, receiver_density, donor_density, donor_temperature, weighted_rate, radiance + Species species + ThermalCXPEC rate + + # cache data on first run + if self._target_species is None: + self._populate_cache() + + 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 + + receiver_density = self._target_species.distribution.density(point.x, point.y, point.z) + if receiver_density <= 0.0: + return spectrum + + weighted_rate = 0 + for species, rate in self._rates: + donor_density = species.distribution.density(point.x, point.y, point.z) + donor_temperature = species.distribution.effective_temperature(point.x, point.y, point.z) + weighted_rate += donor_density * rate.evaluate(ne, te, donor_temperature) + + # add emission line to spectrum + radiance = RECIP_4_PI * weighted_rate * receiver_density + return self._lineshape.add_line(radiance, point, direction, spectrum) + + cdef int _populate_cache(self) except -1: + + cdef: + int receiver_charge + Species species + ThermalCXPEC rate + + # sanity checks + if self._plasma is None: + raise RuntimeError("The emission model is not connected to a plasma object.") + if self._atomic_data is None: + raise RuntimeError("The emission model is not connected to an atomic data source.") + + if self._line is None: + raise RuntimeError("The emission line has not been set.") + + # locate target species + receiver_charge = self._line.charge + 1 + try: + self._target_species = self._plasma.composition.get(self._line.element, receiver_charge) + except ValueError: + raise RuntimeError("The plasma object does not contain the ion species for the specified CX line " + "(element={}, ionisation={}).".format(self._line.element.symbol, receiver_charge)) + + # obtain rate functions + self._rates = [] + for species in self._plasma.composition: + if species.charge < species.element.atomic_number: + rate = self._atomic_data.thermal_cx_pec(species.element, species.charge, # donor + self._line.element, receiver_charge, # receiver + self._line.transition) + self._rates.append((species, rate)) + + # identify wavelength + self._wavelength = self._atomic_data.wavelength(self._line.element, self._line.charge, self._line.transition) + + # instance line shape renderer + self._lineshape = self._lineshape_class(self._line, self._wavelength, self._target_species, self._plasma, + *self._lineshape_args, **self._lineshape_kwargs) + + def _change(self): + + # clear cache to force regeneration on first use + self._target_species = None + self._wavelength = 0.0 + self._rates = None + self._lineshape = None From 7b357e10b3b945d6aaad1a2f61e86b317f56ca20 Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 18 Nov 2022 17:21:02 +0300 Subject: [PATCH 02/32] Add ThermalCXPEC and update atomic data interface. --- cherab/core/atomic/interface.pxd | 2 + cherab/core/atomic/interface.pyx | 3 + cherab/openadas/openadas.py | 42 ++++++++++ cherab/openadas/rates/pec.pxd | 18 ++-- cherab/openadas/rates/pec.pyx | 59 ++++++++++++- cherab/openadas/repository/pec.py | 134 +++++++++++++++++++++++++----- 6 files changed, 230 insertions(+), 28 deletions(-) diff --git a/cherab/core/atomic/interface.pxd b/cherab/core/atomic/interface.pxd index 9ada928e..b072bd1a 100644 --- a/cherab/core/atomic/interface.pxd +++ b/cherab/core/atomic/interface.pxd @@ -44,6 +44,8 @@ cdef class AtomicData: cpdef RecombinationPEC recombination_pec(self, Element ion, int charge, tuple transition) + cpdef ThermalCXPEC thermal_cx_pec(self, Element donor_ion, int donor_charge, Element receiver_ion, int receiver_charge, tuple transition) + cpdef TotalRadiatedPower total_radiated_power(self, Element element) cpdef LineRadiationPower line_radiated_power_rate(self, Element element, int charge) diff --git a/cherab/core/atomic/interface.pyx b/cherab/core/atomic/interface.pyx index e5219205..1a9702b6 100644 --- a/cherab/core/atomic/interface.pyx +++ b/cherab/core/atomic/interface.pyx @@ -75,6 +75,9 @@ cdef class AtomicData: cpdef RecombinationPEC recombination_pec(self, Element ion, int charge, tuple transition): raise NotImplementedError("The recombination() virtual method is not implemented for this atomic data source.") + cpdef ThermalCXPEC thermal_cx_pec(self, Element donor_ion, int donor_charge, Element receiver_ion, int receiver_charge, tuple transition): + raise NotImplementedError("The thermal_cx_pec() virtual method is not implemented for this atomic data source.") + cpdef TotalRadiatedPower total_radiated_power(self, Element element): raise NotImplementedError("The total_radiated_power() virtual method is not implemented for this atomic data source.") diff --git a/cherab/openadas/openadas.py b/cherab/openadas/openadas.py index 7e850bc4..5d42935a 100644 --- a/cherab/openadas/openadas.py +++ b/cherab/openadas/openadas.py @@ -386,6 +386,48 @@ def recombination_pec(self, ion, charge, transition): return RecombinationPEC(wavelength, data, extrapolate=self._permit_extrapolation) + def thermal_cx_pec(self, donor_element, donor_charge, receiver_element, receiver_charge, transition): + """ + Thermal CX photon emission coefficient for a given species. + + Open-ADAS data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param donor_element: Element object defining the donor ion type. + :param donor_charge: Charge state of the donor ion. + :param receiver_element: Element object defining the receiver ion type. + :param receiver_charge: Charge state of the receiver ion. + :param transition: Tuple containing (initial level, final level) of the receiver + in charge state receiver_charge - 1. + :return: Thermal charge exchange photon emission coefficient in W.m^3 + as a function of electron density, electron temperature and donor temperature. + """ + + # extract elements from isotopes because there are no isotope rates in ADAS + if isinstance(donor_element, Isotope): + donor_element = donor_element.element + + if isinstance(receiver_element, Isotope): + receiver_element = receiver_element.element + + try: + # read thermal CX rate from json file in the repository + data = repository.get_pec_thermal_cx_rate(donor_element, donor_charge, + receiver_element, receiver_charge, + transition, + repository_path=self._data_path) + + except RuntimeError: + if self._missing_rates_return_null: + return NullThermalCXPEC() + raise + + # obtain isotope's rest wavelength for a given transition + # the wavelength is used ot convert the PEC from photons/s/m3 to W/m3 + wavelength = self.wavelength(receiver_element, receiver_charge - 1, transition) + + return ThermalCXPEC(wavelength, data, extrapolate=self._permit_extrapolation) + def line_radiated_power_rate(self, ion, charge): """ Line radiated power coefficient for a given species. diff --git a/cherab/openadas/rates/pec.pxd b/cherab/openadas/rates/pec.pxd index 46a54cbe..57bc7560 100644 --- a/cherab/openadas/rates/pec.pxd +++ b/cherab/openadas/rates/pec.pxd @@ -19,7 +19,7 @@ from cherab.core cimport ImpactExcitationPEC as CoreImpactExcitationPEC from cherab.core cimport RecombinationPEC as CoreRecombinationPEC from cherab.core cimport ThermalCXPEC as CoreThermalCXPEC -from cherab.core.math cimport Function2D +from cherab.core.math cimport Function2D, Function3D cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): @@ -48,8 +48,14 @@ cdef class NullRecombinationPEC(CoreRecombinationPEC): pass -# cdef class CXThermalRate(CoreCXThermalRate): -# pass -# -# cdef class ThermalCXRate(CoreThermalCXRate): -# pass +cdef class ThermalCXPEC(CoreThermalCXPEC): + + cdef: + readonly dict raw_data + readonly double wavelength + readonly tuple density_range, temperature_range, donor_temperature_range + Function3D _rate + + +cdef class NullThermalCXPEC(CoreThermalCXPEC): + pass diff --git a/cherab/openadas/rates/pec.pyx b/cherab/openadas/rates/pec.pyx index eafc6c64..7a17b6a6 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/openadas/rates/pec.pyx @@ -20,7 +20,7 @@ import numpy as np from libc.math cimport INFINITY, log10 -from raysect.core.math.function.float cimport Interpolator2DArray +from raysect.core.math.function.float cimport Interpolator2DArray, Interpolator3DArray from cherab.core.utility.conversion import PhotonToJ @@ -126,3 +126,60 @@ cdef class NullRecombinationPEC(CoreRecombinationPEC): cpdef double evaluate(self, double density, double temperature) except? -1e999: return 0.0 + + +cdef class ThermalCXPEC(CoreThermalCXPEC): + + def __init__(self, double wavelength, dict data, extrapolate=False): + """ + :param wavelength: Resting wavelength of corresponding emission line in nm. + :param data: Dictionary containing rate data. + :param extrapolate: Enable extrapolation (default=False). + """ + + self.wavelength = wavelength + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + td = data['td'] + rate = data['rate'] + + # pre-convert data to W m^3 from Photons s^-1 cm^3 prior to interpolation + rate = np.log10(PhotonToJ.to(rate, wavelength)) + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + self.donor_temperature_range = td.min(), td.max() + + # interpolate rate + # using nearest extrapolation to avoid infinite values at 0 for some rates + extrapolation_type = 'nearest' if extrapolate else 'none' + self._rate = Interpolator3DArray(np.log10(ne), np.log10(te), np.log10(td), rate, 'cubic', extrapolation_type, INFINITY, INFINITY, INFINITY) + + cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999: + + # need to handle zeros, also density and temperature can become negative due to cubic interpolation + if electron_density < 1.e-300: + electron_density = 1.e-300 + + if electron_temperature < 1.e-300: + electron_temperature = 1.e-300 + + if donor_temperature < 1.e-300: + donor_temperature = 1.e-300 + + # calculate rate and convert from log10 space to linear space + return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature), log10(donor_temperature)) + + +cdef class NullThermalCXPEC(CoreThermalCXPEC): + """ + A PEC rate that always returns zero. + Needed for use cases where the required atomic data is missing. + """ + + cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999: + return 0.0 diff --git a/cherab/openadas/repository/pec.py b/cherab/openadas/repository/pec.py index 8eb867fc..cd07dbea 100644 --- a/cherab/openadas/repository/pec.py +++ b/cherab/openadas/repository/pec.py @@ -82,44 +82,39 @@ def add_pec_recombination_rate(element, charge, transition, rate, repository_pat }, repository_path) -def add_pec_thermalcx_rate(element, charge, transition, rate, repository_path=None): +def add_pec_thermal_cx_rate(donor_element, donor_charge, receiver_element, receiver_charge, transition, rate, repository_path=None): """ - Adds a single PEC thermalcx rate to the repository. + Adds a single PEC thermal CX rate to the repository. - If adding multiple rate, consider using the update_pec_rates() function + If adding multiple rate, consider using the update_pec_thermal_rates() function instead. The update function avoid repeatedly opening and closing the rate files. - :param element: - :param charge: + :param donor_element: + :param donor_charge: + :param receiver_element: + :param receiver_charge: :param transition: :param rate: :param repository_path: :return: """ + rates2update = RecursiveDict() + rates2update[donor_element][donor_charge][receiver_element][receiver_charge][transition] = rate - update_pec_rates({ - 'thermalcx': { - element: { - charge: { - transition: rate - } - } - } - }, repository_path) + update_pec_thermal_cx_rates(rates2update, repository_path) def update_pec_rates(rates, repository_path=None): """ - PEC rate file structure + Excitation and recombination PEC rate file structure /pec///.json """ valid_classes = [ 'excitation', - 'recombination', - 'thermalcx' + 'recombination' ] repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -183,6 +178,83 @@ def update_pec_rates(rates, repository_path=None): json.dump(content, f, indent=2, sort_keys=True) +def update_pec_thermal_cx_rates(rates, repository_path=None): + """ + Thermal CX PEC rate file structure + + /pec/thermal_cx////.json + """ + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for donor_element, donor_charge_states in rates.items(): + for donor_charge, receiver_elements in donor_charge_states.items(): + for receiver_element, receiver_charge_states in receiver_elements.items(): + for receiver_charge, transitions in receiver_charge_states.items(): + + # sanitise and validate + if not isinstance(donor_element, Element): + raise TypeError('The donor element must be an Element object.') + + if not valid_charge(donor_element, donor_charge + 1): + raise ValueError('Donor charge state is equal to or larger than the number of protons in the element.') + + if not isinstance(receiver_element, Element): + raise TypeError('The receiver element must be an Element object.') + + if not valid_charge(receiver_element, receiver_charge): + raise ValueError('Receiver charge state is larger than the number of protons in the element.') + + rate_path = 'pec/thermal_cx/{0}/{1}/{2}/{3}.json'.format(donor_element.symbol.lower(), donor_charge, + receiver_element.symbol.lower(), receiver_charge) + path = os.path.join(repository_path, rate_path) + + # read in any existing rates + try: + with open(path, 'r') as f: + content = RecursiveDict.from_dict(json.load(f)) + except FileNotFoundError: + content = RecursiveDict() + + # add/replace data for a transition + for transition, data in transitions.items(): + key = encode_transition(transition) + + # sanitise/validate data + ne = np.array(data['ne'], np.float64) + te = np.array(data['te'], np.float64) + td = np.array(data['td'], np.float64) + rate = np.array(data['rate'], np.float64) + + if ne.ndim != 1: + raise ValueError('Electron density array must be a 1D array.') + + if te.ndim != 1: + raise ValueError('Electron temperature array must be a 1D array.') + + if td.ndim != 1: + raise ValueError('Donor temperature array must be a 1D array.') + + if (ne.shape[0], te.shape[0], td.shape[0]) != rate.shape: + raise ValueError('Electron density, electron temperature, donor temperature' + ' and rate data arrays have inconsistent sizes.') + + content[key] = { + 'ne': ne.tolist(), + 'te': te.tolist(), + 'td': td.tolist(), + 'rate': rate.tolist() + } + + # create directory structure if missing + directory = os.path.dirname(path) + if not os.path.isdir(directory): + os.makedirs(directory) + + # write new data + with open(path, 'w') as f: + json.dump(content, f, indent=2, sort_keys=True) + + def get_pec_excitation_rate(element, charge, transition, repository_path=None): return _get_pec_rate('excitation', element, charge, transition, repository_path) @@ -191,10 +263,6 @@ def get_pec_recombination_rate(element, charge, transition, repository_path=None return _get_pec_rate('recombination', element, charge, transition, repository_path) -def get_pec_thermalcx_rate(element, charge, transition, repository_path=None): - return _get_pec_rate('thermalcx', element, charge, transition, repository_path) - - def _get_pec_rate(cls, element, charge, transition, repository_path=None): repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -214,3 +282,27 @@ def _get_pec_rate(cls, element, charge, transition, repository_path=None): return d + +def get_pec_thermal_cx_rate(donor_element, donor_charge, receiver_element, receiver_charge, transition, repository_path=None): + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + rate_path = 'pec/thermal_cx/{0}/{1}/{2}/{3}.json'.format(donor_element.symbol.lower(), donor_charge, + receiver_element.symbol.lower(), receiver_charge) + path = os.path.join(repository_path, rate_path) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[encode_transition(transition)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested thermal charge-exchange PEC (donor={}, donor charge={}, receiver={}, receiver charge={})' + ' is not available.' + ''.format(donor_element.symbol, donor_charge, receiver_element.symbol, receiver_charge)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['td'] = np.array(d['td'], np.float64) + d['rate'] = np.array(d['rate'], np.float64) + + return d From 4a6807fbde06bf39c93c7d0c760d8fcee634ee22 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 23 Jan 2023 17:11:43 +0300 Subject: [PATCH 03/32] Update install_adf15() so it could install thermal CX PECs from starndard ADF15 files. --- cherab/openadas/install.py | 35 +++++++++++++++++++++++++++++++--- cherab/openadas/parse/adf15.py | 12 ++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/cherab/openadas/install.py b/cherab/openadas/install.py index 27e0c5f4..bc0268cd 100644 --- a/cherab/openadas/install.py +++ b/cherab/openadas/install.py @@ -1,7 +1,7 @@ -# 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"); @@ -21,9 +21,11 @@ import os import urllib.parse import urllib.request +import numpy as np from cherab.openadas import repository from cherab.openadas.parse import * +from cherab.core.atomic import hydrogen from cherab.core.utility import RecursiveDict, PerCm3ToPerM3, Cm3ToM3 @@ -264,6 +266,13 @@ def install_adf15(element, ionisation, file_path, download=False, repository_pat # decode file and write out rates rates, wavelengths = parse_adf15(element, ionisation, path, header_format=header_format) + + if 'thermalcx' in rates: + cx_rates = rates.pop('thermalcx') + # CX rates for Tdon = Trec (2D function of Ne, Te) + # converting to 3D function of Ne, Te, Tdon + repository.update_pec_thermal_cx_rates(_thermalcx_adf15_2dto3d_converter(cx_rates)) + repository.update_pec_rates(rates, repository_path) repository.update_wavelengths(wavelengths, repository_path) @@ -393,3 +402,23 @@ def _notation_adf11_adas2cherab(rate_adas, filetype): return rate_cherab +def _thermalcx_adf15_2dto3d_converter(rates): + """ + Converts thermal CX PEC rates parsed from a standard ADF 15 file + to the format supported by the repository. + + In the standard ADF 15 file, it is assumed that the donor is H0 and Tdon = Trec. + """ + new_rates = RecursiveDict() + for element, charge_states in rates.items(): + for charge, transitions in charge_states.items(): + for transition, rate in transitions.items(): + data = np.empty((len(rate['ne']), len(rate['te']), 2)) + data[:, :, :] = rate['rate'][:, :, None] + new_rate = {'ne': rate['ne'], + 'te': rate['te'], + 'td': np.array([0.01, 10000]), + 'rate': data} + new_rates[hydrogen][0][element][charge][transition] = new_rate + + return new_rates diff --git a/cherab/openadas/parse/adf15.py b/cherab/openadas/parse/adf15.py index b9fe42be..ec93fbf2 100644 --- a/cherab/openadas/parse/adf15.py +++ b/cherab/openadas/parse/adf15.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"); @@ -118,7 +118,7 @@ def _scrape_metadata_hydrogen(file, element, charge): elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': - rate_type = 'cx_thermal' + rate_type = 'thermalcx' else: raise ValueError("Unrecognised rate type - {}".format(rate_type_adas)) @@ -161,7 +161,7 @@ def _scrape_metadata_hydrogen_like(file, element, charge): elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': - rate_type = 'cx_thermal' + rate_type = 'thermalcx' else: raise ValueError("Unrecognised rate type - {}".format(rate_type_adas)) @@ -229,7 +229,7 @@ def _scrape_metadata_full(file, element, charge): elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': - rate_type = 'cx_thermal' + rate_type = 'thermalcx' else: raise ValueError("Unrecognised rate type - {}".format(rate_type_adas)) From 520abd06e81359ea7dca14399f73c33a94591c18 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Thu, 26 Jan 2023 14:57:23 +0300 Subject: [PATCH 04/32] Fixed an error in _thermalcx_adf15_2dto3d_converter. --- cherab/openadas/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherab/openadas/install.py b/cherab/openadas/install.py index bc0268cd..dc1ef6e7 100644 --- a/cherab/openadas/install.py +++ b/cherab/openadas/install.py @@ -419,6 +419,6 @@ def _thermalcx_adf15_2dto3d_converter(rates): 'te': rate['te'], 'td': np.array([0.01, 10000]), 'rate': data} - new_rates[hydrogen][0][element][charge][transition] = new_rate + new_rates[hydrogen][0][element][charge + 1][transition] = new_rate return new_rates From dc6afcea24ebb27e3eda1e042118325af6812d2a Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Mon, 4 Dec 2023 10:27:59 +0300 Subject: [PATCH 05/32] Added a test for the ExcitationLine model. --- cherab/core/tests/test_line_emission.py | 164 ++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 cherab/core/tests/test_line_emission.py diff --git a/cherab/core/tests/test_line_emission.py b/cherab/core/tests/test_line_emission.py new file mode 100644 index 00000000..56b0ff8e --- /dev/null +++ b/cherab/core/tests/test_line_emission.py @@ -0,0 +1,164 @@ +# 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 unittest + +import numpy as np + +from raysect.core import Point3D, Vector3D, translate +from raysect.optical import World, Spectrum, Ray + +from cherab.core.atomic import Line, AtomicData, ImpactExcitationPEC, RecombinationPEC, ThermalCXPEC +from cherab.core.atomic import deuterium, carbon +from cherab.tools.plasmas.slab import build_constant_slab_plasma +from cherab.core.model import ExcitationLine, RecombinationLine, ThermalCXLine, GaussianLine, ZeemanTriplet + + +class ConstantImpactExcitationPEC(ImpactExcitationPEC): + """ + Constant electron impact excitation PEC for test purpose. + """ + + def __init__(self, value): + self.value = value + + def evaluate(self, density, temperature): + + return self.value + + +class ConstantRecombinationPEC(RecombinationPEC): + """ + Constant recombination PEC for test purpose. + """ + + def __init__(self, value): + self.value = value + + def evaluate(self, density, temperature): + + return self.value + + +class ConstantThermalCXPEC(ThermalCXPEC): + """ + Constant recombination PEC for test purpose. + """ + + def __init__(self, value): + self.value = value + + def evaluate(self, electron_density, electron_temperature, donor_temperature): + + return self.value + + +class TestAtomicData(AtomicData): + """Fake atomic data for test purpose.""" + + def impact_excitation_pec(self, ion, charge, transition): + + return ConstantImpactExcitationPEC(1.4e-39) + + def recombination_pec(self, ion, charge, transition): + + return ConstantRecombinationPEC(8.e-41) + + def thermal_cx_pec(self, donor_ion, donor_charge, receiver_ion, receiver_charge, transition): + + return ConstantThermalCXPEC(1.2e-46) + + def wavelength(self, ion, charge, transition): + + return 529.27 + + +class TestExcitationLine(unittest.TestCase): + + world = World() + + atomic_data = TestAtomicData() + + plasma_species = [(carbon, 5, 2.e18, 200., Vector3D(0, 0, 0))] + slab_length = 1. + plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + plasma.atomic_data = atomic_data + plasma.parent = world + + def test_default_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [ExcitationLine(line)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + excit_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.impact_excitation_pec(line.element, line.charge, line.transition)(ne, te) + target_species = self.plasma.composition.get(line.element, line.charge) + ni = target_species.distribution.density(0.5, 0, 0) # constant slab + radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length + + gaussian_line = GaussianLine(line, wavelength, target_species, self.plasma) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(excit_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='ExcitationLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + def test_custom_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [ExcitationLine(line, lineshape=ZeemanTriplet)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + excit_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.impact_excitation_pec(line.element, line.charge, line.transition)(ne, te) + target_species = self.plasma.composition.get(line.element, line.charge) + ni = target_species.distribution.density(0.5, 0, 0) # constant slab + radiance = 0.25 / np.pi * rate * ni * ne * self.slab_length + + zeeman_line = ZeemanTriplet(line, wavelength, target_species, self.plasma) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(excit_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='ExcitationLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + +if __name__ == '__main__': + unittest.main() From 270241b8c88407b87c46362a3548a9f5de00d27f Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 4 Dec 2023 22:12:05 +0300 Subject: [PATCH 06/32] Added tests for ExcitationLine, RecombinationLine and ThermalCXLine. --- cherab/core/tests/test_line_emission.py | 163 +++++++++++++++++++++++- 1 file changed, 157 insertions(+), 6 deletions(-) diff --git a/cherab/core/tests/test_line_emission.py b/cherab/core/tests/test_line_emission.py index 56b0ff8e..eb5a8599 100644 --- a/cherab/core/tests/test_line_emission.py +++ b/cherab/core/tests/test_line_emission.py @@ -94,8 +94,8 @@ class TestExcitationLine(unittest.TestCase): atomic_data = TestAtomicData() - plasma_species = [(carbon, 5, 2.e18, 200., Vector3D(0, 0, 0))] - slab_length = 1. + plasma_species = [(carbon, 5, 2.e18, 800., Vector3D(0, 0, 0))] + slab_length = 1.2 plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) plasma.atomic_data = atomic_data @@ -115,11 +115,11 @@ def test_default_lineshape(self): excit_spectrum = ray.trace(self.world) # validating - ne = self.plasma.electron_distribution.density(0.5, 0, 0) + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) rate = self.atomic_data.impact_excitation_pec(line.element, line.charge, line.transition)(ne, te) target_species = self.plasma.composition.get(line.element, line.charge) - ni = target_species.distribution.density(0.5, 0, 0) # constant slab + 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) @@ -144,11 +144,11 @@ def test_custom_lineshape(self): excit_spectrum = ray.trace(self.world) # validating - ne = self.plasma.electron_distribution.density(0.5, 0, 0) + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) rate = self.atomic_data.impact_excitation_pec(line.element, line.charge, line.transition)(ne, te) target_species = self.plasma.composition.get(line.element, line.charge) - ni = target_species.distribution.density(0.5, 0, 0) # constant slab + 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) @@ -160,5 +160,156 @@ def test_custom_lineshape(self): msg='ExcitationLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) +class TestRecombinationLine(unittest.TestCase): + + world = World() + + atomic_data = TestAtomicData() + + plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0))] + slab_length = 1.2 + plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + plasma.atomic_data = atomic_data + plasma.parent = world + + def test_default_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [RecombinationLine(line)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + recomb_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.recombination_pec(line.element, line.charge, line.transition)(ne, te) + target_species = self.plasma.composition.get(line.element, line.charge + 1) + 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) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(recomb_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='RecombinationLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + def test_custom_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [RecombinationLine(line, lineshape=ZeemanTriplet)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + recomb_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.recombination_pec(line.element, line.charge, line.transition)(ne, te) + target_species = self.plasma.composition.get(line.element, line.charge + 1) + 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) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(recomb_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='RecombinationLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + +class TestThermalCXLine(unittest.TestCase): + + world = World() + + atomic_data = TestAtomicData() + + plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0)), + (deuterium, 0, 1.e19, 100., Vector3D(0, 0, 0))] + slab_length = 1.2 + plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + plasma.atomic_data = atomic_data + plasma.parent = world + + def test_default_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [ThermalCXLine(line)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + thermalcx_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + donor_species = self.plasma.composition.get(deuterium, 0) + donor_density = donor_species.distribution.density(0.5, 0, 0) + donor_temperature = donor_species.distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.thermal_cx_pec(deuterium, 0, line.element, line.charge, line.transition)(ne, te, donor_temperature) + target_species = self.plasma.composition.get(line.element, line.charge + 1) + 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) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = gaussian_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(thermalcx_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='ThermalCXLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + def test_custom_lineshape(self): + # setting up the model + line = Line(carbon, 5, (8, 7)) + self.plasma.models = [ThermalCXLine(line, lineshape=ZeemanTriplet)] + wavelength = self.atomic_data.wavelength(line.element, line.charge, line.transition) + + # observing + origin = Point3D(1.5, 0, 0) + direction = Vector3D(-1, 0, 0) + ray = Ray(origin=origin, direction=direction, + min_wavelength=wavelength - 1.5, max_wavelength=wavelength + 1.5, bins=512) + thermalcx_spectrum = ray.trace(self.world) + + # validating + ne = self.plasma.electron_distribution.density(0.5, 0, 0) # constant slab + te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + donor_species = self.plasma.composition.get(deuterium, 0) + donor_density = donor_species.distribution.density(0.5, 0, 0) + donor_temperature = donor_species.distribution.effective_temperature(0.5, 0, 0) + rate = self.atomic_data.thermal_cx_pec(deuterium, 0, line.element, line.charge, line.transition)(ne, te, donor_temperature) + target_species = self.plasma.composition.get(line.element, line.charge + 1) + 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) + spectrum = Spectrum(ray.min_wavelength, ray.max_wavelength, ray.bins) + spectrum = zeeman_line.add_line(radiance, Point3D(0.5, 0, 0), direction, spectrum) + + for i in range(ray.bins): + self.assertAlmostEqual(thermalcx_spectrum.samples[i], spectrum.samples[i], delta=1e-8, + msg='ThermalCXLine model gives a wrong value at {} nm.'.format(spectrum.wavelengths[i])) + + if __name__ == '__main__': unittest.main() From fb6e5497d33161a4b7bd98a978b90498016c15f7 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 4 Dec 2023 22:13:04 +0300 Subject: [PATCH 07/32] Fix error in BeamCXLine docstring. --- cherab/core/model/beam/charge_exchange.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/cherab/core/model/beam/charge_exchange.pyx b/cherab/core/model/beam/charge_exchange.pyx index 295d5f5f..314cfd43 100644 --- a/cherab/core/model/beam/charge_exchange.pyx +++ b/cherab/core/model/beam/charge_exchange.pyx @@ -61,6 +61,7 @@ cdef class BeamCXLine(BeamModel): :ivar Line line: The emission line object. .. code-block:: pycon + >>> from cherab.core.model import BeamCXLine >>> from cherab.core.atomic import carbon >>> from cherab.core.model import ParametrisedZeemanTriplet From 7b068c7a2486ea7320b7a72fcbd238806883fce3 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 4 Dec 2023 22:15:30 +0300 Subject: [PATCH 08/32] Added documentation for line emission models. --- .../core/model/plasma/impact_excitation.pyx | 23 ++++++++++++++++ cherab/core/model/plasma/recombination.pyx | 23 ++++++++++++++++ cherab/core/model/plasma/thermal_cx.pyx | 26 +++++++++++++++++++ .../models/basic_line/basic_line_emission.rst | 6 ++++- 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/cherab/core/model/plasma/impact_excitation.pyx b/cherab/core/model/plasma/impact_excitation.pyx index 343059ad..70abdfe0 100644 --- a/cherab/core/model/plasma/impact_excitation.pyx +++ b/cherab/core/model/plasma/impact_excitation.pyx @@ -23,6 +23,29 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class ExcitationLine(PlasmaModel): + """ + Emitter that calculates spectral line emission from a plasma object + as a result of excitation of the target species by electron impact. + + .. math:: + \\epsilon_{\\mathrm{excit}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i}} n_\\mathrm{e} + \\mathrm{PEC}_{\\mathrm{excit}}(n_\\mathrm{e}, T_\\mathrm{e}) f(\\lambda), + + where :math:`n_{Z_\\mathrm{i}}` is the target species density, + :math:`\\mathrm{PEC}_{\\mathrm{excit}}` is the electron impact excitation photon emission coefficient + for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, + :math:`f(\\lambda)` is the normalised spectral line shape, + + :param Line line: Spectroscopic emission line object. + :param Plasma plasma: The plasma to which this emission model is attached. Default is None. + :param AtomicData atomic_data: The atomic data provider for this model. Default is None. + :param object lineshape: Line shape model class. Default is None (GaussianLine). + :param object lineshape_args: A list of line shape model arguments. Default is None. + :param object lineshape_kwargs: A dictionary of line shape model keyword arguments. Default is None. + + :ivar Plasma plasma: The plasma to which this emission model is attached. + :ivar AtomicData atomic_data: The atomic data provider for this model. + """ def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, object lineshape_args=None, object lineshape_kwargs=None): diff --git a/cherab/core/model/plasma/recombination.pyx b/cherab/core/model/plasma/recombination.pyx index 6edac138..c025cb26 100644 --- a/cherab/core/model/plasma/recombination.pyx +++ b/cherab/core/model/plasma/recombination.pyx @@ -23,6 +23,29 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class RecombinationLine(PlasmaModel): + """ + Emitter that calculates spectral line emission from a plasma object + as a result of dielectronic recombination of the target species. + + .. math:: + \\epsilon_{\\mathrm{recomb}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} n_\\mathrm{e} + \\mathrm{PEC}_{\\mathrm{recomb}}(n_\\mathrm{e}, T_\\mathrm{e}) f(\\lambda), + + where :math:`n_{Z_\\mathrm{i} + 1}` is the recombining species density, + :math:`\\mathrm{PEC}_{\\mathrm{recomb}}` is the dielectronic recombination photon emission coefficient + for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, + :math:`f(\\lambda)` is the normalised spectral line shape, + + :param Line line: Spectroscopic emission line object. + :param Plasma plasma: The plasma to which this emission model is attached. Default is None. + :param AtomicData atomic_data: The atomic data provider for this model. Default is None. + :param object lineshape: Line shape model class. Default is None (GaussianLine). + :param object lineshape_args: A list of line shape model arguments. Default is None. + :param object lineshape_kwargs: A dictionary of line shape model keyword arguments. Default is None. + + :ivar Plasma plasma: The plasma to which this emission model is attached. + :ivar AtomicData atomic_data: The atomic data provider for this model. + """ def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, object lineshape_args=None, object lineshape_kwargs=None): diff --git a/cherab/core/model/plasma/thermal_cx.pyx b/cherab/core/model/plasma/thermal_cx.pyx index f290649a..a5f08a4b 100644 --- a/cherab/core/model/plasma/thermal_cx.pyx +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -24,6 +24,32 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class ThermalCXLine(PlasmaModel): + """ + Emitter that calculates spectral line emission from a plasma object + as a result of thermal charge exchange of the target species with the donor species. + + .. math:: + \\epsilon_{\\mathrm{recomb}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} + \\sum_j{n_{Z_\\mathrm{j}} \\mathrm{PEC}_{\\mathrm{cx}}(n_\\mathrm{e}, T_\\mathrm{e}, T_{Z_\\mathrm{j}})} + f(\\lambda), + + where :math:`n_{Z_\\mathrm{i} + 1}` is the receiver species density, + :math:`n_{Z_\\mathrm{j}}` is the donor species density, + :math:`\\mathrm{PEC}_{\\mathrm{cx}}` is the thermal CX photon emission coefficient + for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, + :math:`T_{Z_\\mathrm{j}}` is the donor species temperature, + :math:`f(\\lambda)` is the normalised spectral line shape, + + :param Line line: Spectroscopic emission line object. + :param Plasma plasma: The plasma to which this emission model is attached. Default is None. + :param AtomicData atomic_data: The atomic data provider for this model. Default is None. + :param object lineshape: Line shape model class. Default is None (GaussianLine). + :param object lineshape_args: A list of line shape model arguments. Default is None. + :param object lineshape_kwargs: A dictionary of line shape model keyword arguments. Default is None. + + :ivar Plasma plasma: The plasma to which this emission model is attached. + :ivar AtomicData atomic_data: The atomic data provider for this model. + """ def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, object lineshape_args=None, object lineshape_kwargs=None): diff --git a/docs/source/models/basic_line/basic_line_emission.rst b/docs/source/models/basic_line/basic_line_emission.rst index c07995db..b71fc03c 100644 --- a/docs/source/models/basic_line/basic_line_emission.rst +++ b/docs/source/models/basic_line/basic_line_emission.rst @@ -2,4 +2,8 @@ Basic Line Emission =================== -Documentation for this model will go here soon... +.. autoclass:: cherab.core.model.plasma.impact_excitation.ExcitationLine + +.. autoclass:: cherab.core.model.plasma.recombination.RecombinationLine + +.. autoclass:: cherab.core.model.plasma.thermal_cx.ThermalCXLine From 259971723854bf3d3227de950960b68282fe6559 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 4 Dec 2023 22:15:56 +0300 Subject: [PATCH 09/32] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13202802..ea77c327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ New: * Replace the coarse numerical constant in the Bremsstrahlung model with an exact expression. (#409) * Add the kind attribute to RayTransferPipelineXD that determines whether the ray transfer matrix is multiplied by sensitivity ('power') or not ('radiance'). (#412) * Improved parsing of metadata from the ADAS ADF15 'bnd' files for H-like ions. Raises a runtime error if the metadata cannot be parsed. (#424) +* Add thermal charge-exchange emission model. (#57) Bug fixes: * Fix deprecated transforms being cached in LaserMaterial after laser.transform update (#420) From 0518d727eb5a83f89a2b71d9212507a00adc2a93 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Tue, 5 Dec 2023 17:46:40 +0300 Subject: [PATCH 10/32] Remove target species as a donor from cached CX PECs. --- 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 a5f08a4b..5f157bb7 100644 --- a/cherab/core/model/plasma/thermal_cx.pyx +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -137,7 +137,7 @@ cdef class ThermalCXLine(PlasmaModel): # obtain rate functions self._rates = [] for species in self._plasma.composition: - if species.charge < species.element.atomic_number: + if species != self._target_species and species.charge < species.element.atomic_number: rate = self._atomic_data.thermal_cx_pec(species.element, species.charge, # donor self._line.element, receiver_charge, # receiver self._line.transition) From 25428af6adfd2df07eee73fbe92add4a1d2e0fd8 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Tue, 5 Dec 2023 17:47:39 +0300 Subject: [PATCH 11/32] Add demo for ThermalCXLine model. --- demos/emission_models/charge_exchange.py | 2 +- .../thermal_charge_exchange.py | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 demos/emission_models/thermal_charge_exchange.py diff --git a/demos/emission_models/charge_exchange.py b/demos/emission_models/charge_exchange.py index 11d95386..02d61235 100644 --- a/demos/emission_models/charge_exchange.py +++ b/demos/emission_models/charge_exchange.py @@ -122,7 +122,7 @@ integration_step = 0.0025 beam_transform = translate(-0.5, 0.0, 0) * rotate_basis(Vector3D(1, 0, 0), Vector3D(0, 0, 1)) -beam_energy = 50000 # keV +beam_energy = 50000 # eV beam_full = Beam(parent=world, transform=beam_transform) beam_full.plasma = plasma diff --git a/demos/emission_models/thermal_charge_exchange.py b/demos/emission_models/thermal_charge_exchange.py new file mode 100644 index 00000000..f4cead84 --- /dev/null +++ b/demos/emission_models/thermal_charge_exchange.py @@ -0,0 +1,130 @@ + +import numpy as np +import matplotlib.pyplot as plt +from scipy.constants import electron_mass, atomic_mass + +from raysect.core import translate, rotate_basis, Point3D, Vector3D +from raysect.primitive import Box +from raysect.optical import World, Ray + +from cherab.core import Plasma, Beam, Maxwellian, Species +from cherab.core.math import ScalarToVectorFunction3D +from cherab.core.atomic import hydrogen, deuterium, carbon, Line +from cherab.core.model import SingleRayAttenuator, BeamCXLine, ThermalCXLine +from cherab.tools.plasmas.slab import NeutralFunction, IonFunction +from cherab.openadas import OpenADAS +from cherab.openadas.install import install_adf15 + + +# Install PECs for CVI spectral lines +install_adf15(carbon, 5, 'adf15/pec96#c/pec96#c_pju#c5.dat', download=True) + +############### +# Make Plasma # + +width = 1.0 +length = 1.0 +height = 3.0 +peak_density = 5e19 +edge_density = 1e18 +pedestal_top = 1 +neutral_temperature = 0.5 +peak_temperature = 2500 +edge_temperature = 25 +impurities = [(carbon, 6, 0.005)] + +world = World() +adas = OpenADAS(permit_extrapolation=True, missing_rates_return_null=True) + +plasma = Plasma(parent=world) +plasma.atomic_data = adas + +# plasma slab along x direction +plasma.geometry = Box(Point3D(0, -width / 2, -height / 2), Point3D(length, width / 2, height / 2)) + +species = [] + +# make a non-zero velocity profile for the plasma +vy_profile = IonFunction(1E5, 0, pedestal_top=pedestal_top) +velocity_profile = ScalarToVectorFunction3D(0, vy_profile, 0) + +# define neutral species distribution +h0_density = NeutralFunction(peak_density, 0.1, pedestal_top=pedestal_top) +h0_temperature = neutral_temperature +h0_distribution = Maxwellian(h0_density, h0_temperature, velocity_profile, + hydrogen.atomic_weight * atomic_mass) +species.append(Species(hydrogen, 0, h0_distribution)) + +# define hydrogen ion species distribution +h1_density = IonFunction(peak_density, edge_density, pedestal_top=pedestal_top) +h1_temperature = IonFunction(peak_temperature, edge_temperature, pedestal_top=pedestal_top) +h1_distribution = Maxwellian(h1_density, h1_temperature, velocity_profile, + hydrogen.atomic_weight * atomic_mass) +species.append(Species(hydrogen, 1, h1_distribution)) + +# add impurities +if impurities: + for impurity, ionisation, concentration in impurities: + imp_density = IonFunction(peak_density * concentration, edge_density * concentration, pedestal_top=pedestal_top) + imp_temperature = IonFunction(peak_temperature, edge_temperature, pedestal_top=pedestal_top) + imp_distribution = Maxwellian(imp_density, imp_temperature, velocity_profile, + impurity.atomic_weight * atomic_mass) + species.append(Species(impurity, ionisation, imp_distribution)) + +# define the electron distribution +e_density = IonFunction(peak_density, edge_density, pedestal_top=pedestal_top) +e_temperature = IonFunction(peak_temperature, edge_temperature, pedestal_top=pedestal_top) +e_distribution = Maxwellian(e_density, e_temperature, velocity_profile, electron_mass) + +# define species +plasma.electron_distribution = e_distribution +plasma.composition = species + +# add thermal CX emission model +cVI_5_4 = Line(carbon, 5, (5, 4)) +plasma.models = [ThermalCXLine(cVI_5_4)] + +# trace thermal CX spectrum along y direction +ray = Ray(origin=Point3D(0.4, -3.5, 0), direction=Vector3D(0, 1, 0), + min_wavelength=112.3, max_wavelength=112.7, bins=512) +thermal_cx_spectrum = ray.trace(world) + +########################### +# Inject beam into plasma # + +integration_step = 0.0025 +# injected along x direction +beam_transform = translate(-0.5, 0.0, 0) * rotate_basis(Vector3D(1, 0, 0), Vector3D(0, 0, 1)) +beam_energy = 50000 # eV + +beam = Beam(parent=world, transform=beam_transform) +beam.plasma = plasma +beam.atomic_data = adas +beam.energy = beam_energy +beam.power = 3e6 +beam.element = deuterium +beam.sigma = 0.05 +beam.divergence_x = 0.5 +beam.divergence_y = 0.5 +beam.length = 3.0 +beam.attenuator = SingleRayAttenuator(clamp_to_zero=True) +beam.integrator.step = integration_step +beam.integrator.min_samples = 10 + +# remove thermal CX model and add beam CX model +plasma.models = [] +beam.models = [BeamCXLine(cVI_5_4)] + +# trace the spectrum again +beam_cx_spectrum = ray.trace(world) + +# plot the spectra +plt.figure() +plt.plot(thermal_cx_spectrum.wavelengths, thermal_cx_spectrum.samples, label='thermal CX') +plt.plot(beam_cx_spectrum.wavelengths, beam_cx_spectrum.samples, label='beam CX') +plt.legend() +plt.xlabel('Wavelength (nm)') +plt.ylabel('Radiance (W/m^2/str/nm)') +plt.title('Sampled CX spectra') + +plt.show() From e2f3c6d1f8c03ca975da441fecfccc6249235b26 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 13 May 2024 00:14:43 +0200 Subject: [PATCH 12/32] Undo adding docstrings to ExcitationLine and RecombinationLine as they will be added in a separate PR. --- .../core/model/plasma/impact_excitation.pyx | 23 ------------------- cherab/core/model/plasma/recombination.pyx | 23 ------------------- .../models/basic_line/basic_line_emission.rst | 4 ---- 3 files changed, 50 deletions(-) diff --git a/cherab/core/model/plasma/impact_excitation.pyx b/cherab/core/model/plasma/impact_excitation.pyx index 70abdfe0..343059ad 100644 --- a/cherab/core/model/plasma/impact_excitation.pyx +++ b/cherab/core/model/plasma/impact_excitation.pyx @@ -23,29 +23,6 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class ExcitationLine(PlasmaModel): - """ - Emitter that calculates spectral line emission from a plasma object - as a result of excitation of the target species by electron impact. - - .. math:: - \\epsilon_{\\mathrm{excit}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i}} n_\\mathrm{e} - \\mathrm{PEC}_{\\mathrm{excit}}(n_\\mathrm{e}, T_\\mathrm{e}) f(\\lambda), - - where :math:`n_{Z_\\mathrm{i}}` is the target species density, - :math:`\\mathrm{PEC}_{\\mathrm{excit}}` is the electron impact excitation photon emission coefficient - for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, - :math:`f(\\lambda)` is the normalised spectral line shape, - - :param Line line: Spectroscopic emission line object. - :param Plasma plasma: The plasma to which this emission model is attached. Default is None. - :param AtomicData atomic_data: The atomic data provider for this model. Default is None. - :param object lineshape: Line shape model class. Default is None (GaussianLine). - :param object lineshape_args: A list of line shape model arguments. Default is None. - :param object lineshape_kwargs: A dictionary of line shape model keyword arguments. Default is None. - - :ivar Plasma plasma: The plasma to which this emission model is attached. - :ivar AtomicData atomic_data: The atomic data provider for this model. - """ def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, object lineshape_args=None, object lineshape_kwargs=None): diff --git a/cherab/core/model/plasma/recombination.pyx b/cherab/core/model/plasma/recombination.pyx index c025cb26..6edac138 100644 --- a/cherab/core/model/plasma/recombination.pyx +++ b/cherab/core/model/plasma/recombination.pyx @@ -23,29 +23,6 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class RecombinationLine(PlasmaModel): - """ - Emitter that calculates spectral line emission from a plasma object - as a result of dielectronic recombination of the target species. - - .. math:: - \\epsilon_{\\mathrm{recomb}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} n_\\mathrm{e} - \\mathrm{PEC}_{\\mathrm{recomb}}(n_\\mathrm{e}, T_\\mathrm{e}) f(\\lambda), - - where :math:`n_{Z_\\mathrm{i} + 1}` is the recombining species density, - :math:`\\mathrm{PEC}_{\\mathrm{recomb}}` is the dielectronic recombination photon emission coefficient - for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, - :math:`f(\\lambda)` is the normalised spectral line shape, - - :param Line line: Spectroscopic emission line object. - :param Plasma plasma: The plasma to which this emission model is attached. Default is None. - :param AtomicData atomic_data: The atomic data provider for this model. Default is None. - :param object lineshape: Line shape model class. Default is None (GaussianLine). - :param object lineshape_args: A list of line shape model arguments. Default is None. - :param object lineshape_kwargs: A dictionary of line shape model keyword arguments. Default is None. - - :ivar Plasma plasma: The plasma to which this emission model is attached. - :ivar AtomicData atomic_data: The atomic data provider for this model. - """ def __init__(self, Line line, Plasma plasma=None, AtomicData atomic_data=None, object lineshape=None, object lineshape_args=None, object lineshape_kwargs=None): diff --git a/docs/source/models/basic_line/basic_line_emission.rst b/docs/source/models/basic_line/basic_line_emission.rst index b71fc03c..b849d1a9 100644 --- a/docs/source/models/basic_line/basic_line_emission.rst +++ b/docs/source/models/basic_line/basic_line_emission.rst @@ -2,8 +2,4 @@ Basic Line Emission =================== -.. autoclass:: cherab.core.model.plasma.impact_excitation.ExcitationLine - -.. autoclass:: cherab.core.model.plasma.recombination.RecombinationLine - .. autoclass:: cherab.core.model.plasma.thermal_cx.ThermalCXLine From c78e020d25c67ae5461231596bdc01c9bd8469db Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 13 May 2024 00:20:06 +0200 Subject: [PATCH 13/32] Add comments for atomic rates caching. --- cherab/core/model/plasma/thermal_cx.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cherab/core/model/plasma/thermal_cx.pyx b/cherab/core/model/plasma/thermal_cx.pyx index 5f157bb7..4be9b44c 100644 --- a/cherab/core/model/plasma/thermal_cx.pyx +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -29,7 +29,7 @@ cdef class ThermalCXLine(PlasmaModel): as a result of thermal charge exchange of the target species with the donor species. .. math:: - \\epsilon_{\\mathrm{recomb}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} + \\epsilon_{\\mathrm{CX}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} \\sum_j{n_{Z_\\mathrm{j}} \\mathrm{PEC}_{\\mathrm{cx}}(n_\\mathrm{e}, T_\\mathrm{e}, T_{Z_\\mathrm{j}})} f(\\lambda), @@ -100,6 +100,7 @@ cdef class ThermalCXLine(PlasmaModel): if receiver_density <= 0.0: return spectrum + # obtain composite CX PEC by iterating over all possible CX donors weighted_rate = 0 for species, rate in self._rates: donor_density = species.distribution.density(point.x, point.y, point.z) @@ -136,7 +137,10 @@ cdef class ThermalCXLine(PlasmaModel): # obtain rate functions self._rates = [] + # iterate over all posible electron donors in plasma composition + # and for each donor, cache the PEC rate function for the CX reaction with this receiver for species in self._plasma.composition: + # exclude the receiver species from the list of donors and omit fully ionised species if species != self._target_species and species.charge < species.element.atomic_number: rate = self._atomic_data.thermal_cx_pec(species.element, species.charge, # donor self._line.element, receiver_charge, # receiver From e799a6b393a9b5420e67fc18cadbcbf09a15bf30 Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 13 May 2024 00:55:08 +0200 Subject: [PATCH 14/32] Make ThermalCXPEC to return zero if plasma parameters are <=zero threshold. --- cherab/openadas/rates/pec.pyx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cherab/openadas/rates/pec.pyx b/cherab/openadas/rates/pec.pyx index 7a17b6a6..7b888b5b 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/openadas/rates/pec.pyx @@ -24,6 +24,9 @@ from raysect.core.math.function.float cimport Interpolator2DArray, Interpolator3 from cherab.core.utility.conversion import PhotonToJ +DEF ZERO_THRESHOLD = 1.e-300 + + cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): def __init__(self, double wavelength, dict data, extrapolate=False): @@ -134,7 +137,7 @@ cdef class ThermalCXPEC(CoreThermalCXPEC): """ :param wavelength: Resting wavelength of corresponding emission line in nm. :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). + :param extrapolate: Enable nearest-neighbour extrapolation (default=False). """ self.wavelength = wavelength @@ -162,14 +165,14 @@ cdef class ThermalCXPEC(CoreThermalCXPEC): cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if electron_density < 1.e-300: - electron_density = 1.e-300 + if electron_density < ZERO_THRESHOLD: + return 0 - if electron_temperature < 1.e-300: - electron_temperature = 1.e-300 + if electron_temperature < ZERO_THRESHOLD: + return 0 - if donor_temperature < 1.e-300: - donor_temperature = 1.e-300 + if donor_temperature < ZERO_THRESHOLD: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature), log10(donor_temperature)) From 751878f8228176540023a0c067222880ed5b14fd Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 13 May 2024 00:59:08 +0200 Subject: [PATCH 15/32] Pass RecursiveDict as normal dict to functions. --- cherab/openadas/repository/pec.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cherab/openadas/repository/pec.py b/cherab/openadas/repository/pec.py index cd07dbea..eda90e8a 100644 --- a/cherab/openadas/repository/pec.py +++ b/cherab/openadas/repository/pec.py @@ -102,7 +102,7 @@ def add_pec_thermal_cx_rate(donor_element, donor_charge, receiver_element, recei rates2update = RecursiveDict() rates2update[donor_element][donor_charge][receiver_element][receiver_charge][transition] = rate - update_pec_thermal_cx_rates(rates2update, repository_path) + update_pec_thermal_cx_rates(rates2update.freeze(), repository_path) def update_pec_rates(rates, repository_path=None): @@ -248,11 +248,11 @@ def update_pec_thermal_cx_rates(rates, repository_path=None): # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): - os.makedirs(directory) + os.makedirs(directory, exist_ok=True) # write new data with open(path, 'w') as f: - json.dump(content, f, indent=2, sort_keys=True) + json.dump(content.freeze(), f, indent=2, sort_keys=True) def get_pec_excitation_rate(element, charge, transition, repository_path=None): From 4e2d169dc22c27389dbb86795025b158a86c0092 Mon Sep 17 00:00:00 2001 From: vsnever Date: Tue, 14 May 2024 18:21:35 +0200 Subject: [PATCH 16/32] Convert docstring for ThermalCXLine to raw string format. --- cherab/core/model/plasma/thermal_cx.pyx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cherab/core/model/plasma/thermal_cx.pyx b/cherab/core/model/plasma/thermal_cx.pyx index 4be9b44c..1325919c 100644 --- a/cherab/core/model/plasma/thermal_cx.pyx +++ b/cherab/core/model/plasma/thermal_cx.pyx @@ -24,21 +24,21 @@ from cherab.core.utility.constants cimport RECIP_4_PI cdef class ThermalCXLine(PlasmaModel): - """ + r""" Emitter that calculates spectral line emission from a plasma object as a result of thermal charge exchange of the target species with the donor species. .. math:: - \\epsilon_{\\mathrm{CX}}(\\lambda) = \\frac{1}{4 \\pi} n_{Z_\\mathrm{i} + 1} - \\sum_j{n_{Z_\\mathrm{j}} \\mathrm{PEC}_{\\mathrm{cx}}(n_\\mathrm{e}, T_\\mathrm{e}, T_{Z_\\mathrm{j}})} - f(\\lambda), - - where :math:`n_{Z_\\mathrm{i} + 1}` is the receiver species density, - :math:`n_{Z_\\mathrm{j}}` is the donor species density, - :math:`\\mathrm{PEC}_{\\mathrm{cx}}` is the thermal CX photon emission coefficient - for the specified spectral line of the :math:`Z_\\mathrm{i}` ion, - :math:`T_{Z_\\mathrm{j}}` is the donor species temperature, - :math:`f(\\lambda)` is the normalised spectral line shape, + \epsilon_{\mathrm{CX}}(\lambda) = \frac{1}{4 \pi} n_{Z_\mathrm{i} + 1} + \sum_j{n_{Z_\mathrm{j}} \mathrm{PEC}_{\mathrm{cx}}(n_\mathrm{e}, T_\mathrm{e}, T_{Z_\mathrm{j}})} + f(\lambda), + + where :math:`n_{Z_\mathrm{i} + 1}` is the receiver species density, + :math:`n_{Z_\mathrm{j}}` is the donor species density, + :math:`\mathrm{PEC}_{\mathrm{cx}}` is the thermal CX photon emission coefficient + for the specified spectral line of the :math:`Z_\mathrm{i}` ion, + :math:`T_{Z_\mathrm{j}}` is the donor species temperature, + :math:`f(\lambda)` is the normalised spectral line shape, :param Line line: Spectroscopic emission line object. :param Plasma plasma: The plasma to which this emission model is attached. Default is None. From 6d034321e03ecfdf1da74d87e41be36b3422e8e0 Mon Sep 17 00:00:00 2001 From: vsnever Date: Thu, 16 May 2024 18:02:04 +0200 Subject: [PATCH 17/32] Moved plasma setup to setUp() function in the unit tests for line emission. --- cherab/core/tests/test_line_emission.py | 61 ++++++++++++++----------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/cherab/core/tests/test_line_emission.py b/cherab/core/tests/test_line_emission.py index eb5a8599..b55af871 100644 --- a/cherab/core/tests/test_line_emission.py +++ b/cherab/core/tests/test_line_emission.py @@ -68,7 +68,7 @@ def evaluate(self, electron_density, electron_temperature, donor_temperature): return self.value -class TestAtomicData(AtomicData): +class MockAtomicData(AtomicData): """Fake atomic data for test purpose.""" def impact_excitation_pec(self, ion, charge, transition): @@ -90,16 +90,19 @@ def wavelength(self, ion, charge, transition): class TestExcitationLine(unittest.TestCase): - world = World() + def setUp(self): - atomic_data = TestAtomicData() + self.world = World() - plasma_species = [(carbon, 5, 2.e18, 800., Vector3D(0, 0, 0))] - slab_length = 1.2 - plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., - plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) - plasma.atomic_data = atomic_data - plasma.parent = world + self.atomic_data = MockAtomicData() + + plasma_species = [(carbon, 5, 2.e18, 800., Vector3D(0, 0, 0))] + self.slab_length = 1.2 + self.plasma = build_constant_slab_plasma(length=self.slab_length, width=1, height=1, + electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + self.plasma.atomic_data = self.atomic_data + self.plasma.parent = self.world def test_default_lineshape(self): # setting up the model @@ -162,16 +165,19 @@ def test_custom_lineshape(self): class TestRecombinationLine(unittest.TestCase): - world = World() + def setUp(self): + + self.world = World() - atomic_data = TestAtomicData() + self.atomic_data = MockAtomicData() - plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0))] - slab_length = 1.2 - plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., - plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) - plasma.atomic_data = atomic_data - plasma.parent = world + plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0))] + self.slab_length = 1.2 + self.plasma = build_constant_slab_plasma(length=self.slab_length, width=1, height=1, + electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + self.plasma.atomic_data = self.atomic_data + self.plasma.parent = self.world def test_default_lineshape(self): # setting up the model @@ -234,17 +240,20 @@ def test_custom_lineshape(self): class TestThermalCXLine(unittest.TestCase): - world = World() + def setUp(self): + + self.world = World() - atomic_data = TestAtomicData() + self.atomic_data = MockAtomicData() - plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0)), - (deuterium, 0, 1.e19, 100., Vector3D(0, 0, 0))] - slab_length = 1.2 - plasma = build_constant_slab_plasma(length=slab_length, width=1, height=1, electron_density=1e19, electron_temperature=1000., - plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) - plasma.atomic_data = atomic_data - plasma.parent = world + plasma_species = [(carbon, 6, 1.67e18, 800., Vector3D(0, 0, 0)), + (deuterium, 0, 1.e19, 100., Vector3D(0, 0, 0))] + self.slab_length = 1.2 + self.plasma = build_constant_slab_plasma(length=self.slab_length, width=1, height=1, + electron_density=1e19, electron_temperature=1000., + plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) + self.plasma.atomic_data = self.atomic_data + self.plasma.parent = self.world def test_default_lineshape(self): # setting up the model From c04bac7e989689ae16fc813631daef890f07d813 Mon Sep 17 00:00:00 2001 From: vsnever Date: Thu, 16 May 2024 18:05:47 +0200 Subject: [PATCH 18/32] Replaced zero threshold with 0 in openadas ThermalCXPEC. --- cherab/openadas/rates/pec.pyx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/cherab/openadas/rates/pec.pyx b/cherab/openadas/rates/pec.pyx index 7b888b5b..d8c66d9b 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/openadas/rates/pec.pyx @@ -24,9 +24,6 @@ from raysect.core.math.function.float cimport Interpolator2DArray, Interpolator3 from cherab.core.utility.conversion import PhotonToJ -DEF ZERO_THRESHOLD = 1.e-300 - - cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): def __init__(self, double wavelength, dict data, extrapolate=False): @@ -165,13 +162,7 @@ cdef class ThermalCXPEC(CoreThermalCXPEC): cpdef double evaluate(self, double electron_density, double electron_temperature, double donor_temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if electron_density < ZERO_THRESHOLD: - return 0 - - if electron_temperature < ZERO_THRESHOLD: - return 0 - - if donor_temperature < ZERO_THRESHOLD: + if electron_density <= 0 or electron_temperature <= 0 or donor_temperature <= 0: return 0 # calculate rate and convert from log10 space to linear space From b413c4f4bf15a202094d461358698042be7ad382 Mon Sep 17 00:00:00 2001 From: vsnever Date: Thu, 16 May 2024 18:27:17 +0200 Subject: [PATCH 19/32] Moved C5+ PEC installation from the demo to populate(). --- cherab/openadas/repository/create.py | 1 + demos/emission_models/thermal_charge_exchange.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cherab/openadas/repository/create.py b/cherab/openadas/repository/create.py index 6b9a4f33..b42e5651 100644 --- a/cherab/openadas/repository/create.py +++ b/cherab/openadas/repository/create.py @@ -147,6 +147,7 @@ def populate(download=True, repository_path=None, adas_path=None): (carbon, 0, 'adf15/pec96#c/pec96#c_vsu#c0.dat'), (carbon, 1, 'adf15/pec96#c/pec96#c_vsu#c1.dat'), (carbon, 2, 'adf15/pec96#c/pec96#c_vsu#c2.dat'), + (carbon, 5, 'adf15/pec96#c/pec96#c_pju#c5.dat'), # (neon, 0, 'adf15/pec96#ne/pec96#ne_pju#ne0.dat'), #TODO: OPENADAS DATA CORRUPT # (neon, 1, 'adf15/pec96#ne/pec96#ne_pju#ne1.dat'), #TODO: OPENADAS DATA CORRUPT (nitrogen, 0, 'adf15/pec96#n/pec96#n_vsu#n0.dat'), diff --git a/demos/emission_models/thermal_charge_exchange.py b/demos/emission_models/thermal_charge_exchange.py index f4cead84..42c7b88a 100644 --- a/demos/emission_models/thermal_charge_exchange.py +++ b/demos/emission_models/thermal_charge_exchange.py @@ -1,5 +1,4 @@ -import numpy as np import matplotlib.pyplot as plt from scipy.constants import electron_mass, atomic_mass @@ -16,9 +15,6 @@ from cherab.openadas.install import install_adf15 -# Install PECs for CVI spectral lines -install_adf15(carbon, 5, 'adf15/pec96#c/pec96#c_pju#c5.dat', download=True) - ############### # Make Plasma # From 76b533f977cf2e6623f0d7000903107452d62b95 Mon Sep 17 00:00:00 2001 From: vsnever Date: Thu, 16 May 2024 18:31:25 +0200 Subject: [PATCH 20/32] Removed isdir() check before makedirs because exist_ok is set to True. --- cherab/openadas/repository/pec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cherab/openadas/repository/pec.py b/cherab/openadas/repository/pec.py index eda90e8a..a2bc17b5 100644 --- a/cherab/openadas/repository/pec.py +++ b/cherab/openadas/repository/pec.py @@ -247,8 +247,7 @@ def update_pec_thermal_cx_rates(rates, repository_path=None): # create directory structure if missing directory = os.path.dirname(path) - if not os.path.isdir(directory): - os.makedirs(directory, exist_ok=True) + os.makedirs(directory, exist_ok=True) # write new data with open(path, 'w') as f: From b0113d9174aefc5c8f916858a541c63d42aff259 Mon Sep 17 00:00:00 2001 From: vsnever Date: Tue, 21 May 2024 23:43:21 +0200 Subject: [PATCH 21/32] Updated CHANGELOG with the info about the C VI lines. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716df7d7..5d7798c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ New: * **Beam dispersion calculation has changed from sigma(z) = sigma + z * tan(alpha) to sigma(z) = sqrt(sigma^2 + (z * tan(alpha))^2) for consistancy with the Gaussian beam model. Attention!!! The results of BES and CX spectroscopy are affected by this change. (#414)** * Improved beam direction calculation to allow for natural broadening of the BES line shape due to beam divergence. (#414) * Add thermal charge-exchange emission model. (#57) +* PECs for C VI spectral lines for n <= 5 are now included in populate(). Rerun populate() after upgrading to 1.5 to update the atomic data repository. Bug fixes: * Fix deprecated transforms being cached in LaserMaterial after laser.transform update (#420) From b5866254e86f81cae2b9dfa6cface41730b25daf Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 28 Jun 2024 00:37:53 +0200 Subject: [PATCH 22/32] Add donor_metastable attribute to core BeamCXPEC (#411). Replace receiver ion density with the total ion density in BeamCXPEC (#441). --- cherab/core/atomic/rates.pxd | 2 ++ cherab/core/atomic/rates.pyx | 7 ++++++- cherab/core/model/beam/charge_exchange.pxd | 2 +- cherab/core/model/beam/charge_exchange.pyx | 14 +++++++------- cherab/core/tests/test_beamcxline.py | 2 +- cherab/openadas/rates/cx.pxd | 1 - cherab/openadas/rates/cx.pyx | 4 ++-- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cherab/core/atomic/rates.pxd b/cherab/core/atomic/rates.pxd index 1f844122..ac8d69ce 100644 --- a/cherab/core/atomic/rates.pxd +++ b/cherab/core/atomic/rates.pxd @@ -51,6 +51,8 @@ cdef class ThermalCXPEC(_PECRate): cdef class BeamCXPEC: + cdef readonly int donor_metastable + cpdef double evaluate(self, double energy, double temperature, double density, double z_effective, double b_field) except? -1e999 diff --git a/cherab/core/atomic/rates.pyx b/cherab/core/atomic/rates.pyx index f3a653db..52510e4b 100644 --- a/cherab/core/atomic/rates.pyx +++ b/cherab/core/atomic/rates.pyx @@ -144,8 +144,13 @@ cdef class BeamCXPEC: transition :math:`n\rightarrow n'` of ion :math:`Z^{(\alpha+1)+}` with electron donor :math:`H^0` in metastable state :math:`m_{i}`. Equivalent to :math:`q^{eff}_{n\rightarrow n'}` in `adf12 _`. + + :param donor_metastable: The metastable state of the donor species for which the rate data applies. """ + def __init__(self, int donor_metastable): + self.donor_metastable = donor_metastable + def __call__(self, double energy, double temperature, double density, double z_effective, double b_field): """Evaluates the Beam CX rate at the given plasma conditions. @@ -158,7 +163,7 @@ cdef class BeamCXPEC: :param float energy: Interaction energy in eV/amu. :param float temperature: Receiver ion temperature in eV. - :param float density: Receiver ion density in m^-3 + :param float density: Plasma total ion density in m^-3 :param float z_effective: Plasma Z-effective. :param float b_field: Magnetic field magnitude in Tesla. :return: The effective rate diff --git a/cherab/core/model/beam/charge_exchange.pxd b/cherab/core/model/beam/charge_exchange.pxd index cd0b48db..46e22891 100644 --- a/cherab/core/model/beam/charge_exchange.pxd +++ b/cherab/core/model/beam/charge_exchange.pxd @@ -36,7 +36,7 @@ cdef class BeamCXLine(BeamModel): object _lineshape_class, _lineshape_args, _lineshape_kwargs cdef double _composite_cx_rate(self, double x, double y, double z, double interaction_energy, - Vector3D donor_velocity, double receiver_temperature, double receiver_density) except? -1e999 + Vector3D donor_velocity, double receiver_temperature) except? -1e999 cdef double _beam_population(self, double x, double y, double z, Vector3D beam_velocity, list population_data) except? -1e999 diff --git a/cherab/core/model/beam/charge_exchange.pyx b/cherab/core/model/beam/charge_exchange.pyx index 295d5f5f..12650ddb 100644 --- a/cherab/core/model/beam/charge_exchange.pyx +++ b/cherab/core/model/beam/charge_exchange.pyx @@ -158,7 +158,7 @@ cdef class BeamCXLine(BeamModel): interaction_energy = ms_to_evamu(interaction_speed) # calculate the composite charge-exchange emission coefficient - emission_rate = self._composite_cx_rate(x, y, z, interaction_energy, donor_velocity, receiver_temperature, receiver_density) + emission_rate = self._composite_cx_rate(x, y, z, interaction_energy, donor_velocity, receiver_temperature) # spectral line emission in W/m^3/str radiance = RECIP_4_PI * donor_density * receiver_density * emission_rate @@ -169,7 +169,7 @@ cdef class BeamCXLine(BeamModel): @cython.wraparound(False) @cython.cdivision(True) cdef double _composite_cx_rate(self, double x, double y, double z, double interaction_energy, - Vector3D donor_velocity, double receiver_temperature, double receiver_density) except? -1e999: + Vector3D donor_velocity, double receiver_temperature) except? -1e999: """ Performs a beam population weighted average of the effective cx rates. @@ -188,23 +188,23 @@ cdef class BeamCXLine(BeamModel): :param interaction_energy: The donor-receiver interaction energy in eV/amu. :param donor_velocity: A Vector defining the donor particle velocity in m/s. :param receiver_temperature: The receiver species temperature in eV. - :param receiver_density: The receiver species density in m^-3 :return: The composite charge exchange rate in W.m^3. """ cdef: - double z_effective, b_field, rate, total_population, population, effective_rate + double z_effective, b_field, rate, total_population, population, effective_rate, ion_density BeamCXPEC cx_rate list population_data - # calculate z_effective and the B-field magnitude + # calculate ion density, z_effective and the B-field magnitude + ion_density = self._plasma.ion_density(x, y, z) z_effective = self._plasma.z_effective(x, y, z) b_field = self._plasma.get_b_field().evaluate(x, y, z).get_length() # rate for the ground state (metastable = 1) rate = self._ground_beam_rate.evaluate(interaction_energy, receiver_temperature, - receiver_density, + ion_density, z_effective, b_field) @@ -219,7 +219,7 @@ cdef class BeamCXLine(BeamModel): effective_rate = cx_rate.evaluate(interaction_energy, receiver_temperature, - receiver_density, + ion_density, z_effective, b_field) diff --git a/cherab/core/tests/test_beamcxline.py b/cherab/core/tests/test_beamcxline.py index ccb0c855..f20c4861 100644 --- a/cherab/core/tests/test_beamcxline.py +++ b/cherab/core/tests/test_beamcxline.py @@ -36,7 +36,7 @@ class ConstantBeamCXPEC(BeamCXPEC): """ def __init__(self, donor_metastable, value): - self.donor_metastable = donor_metastable + super().__init__(donor_metastable) self.value = value def evaluate(self, energy, temperature, density, z_effective, b_field): diff --git a/cherab/openadas/rates/cx.pxd b/cherab/openadas/rates/cx.pxd index 393027d0..4afcadfc 100644 --- a/cherab/openadas/rates/cx.pxd +++ b/cherab/openadas/rates/cx.pxd @@ -25,7 +25,6 @@ cdef class BeamCXPEC(CoreBeamCXPEC): cdef readonly: dict raw_data double wavelength - int donor_metastable Function1D _eb, _ti, _ni, _zeff, _b readonly tuple beam_energy_range readonly tuple density_range diff --git a/cherab/openadas/rates/cx.pyx b/cherab/openadas/rates/cx.pyx index cb827a8b..dfdcf8e8 100644 --- a/cherab/openadas/rates/cx.pyx +++ b/cherab/openadas/rates/cx.pyx @@ -37,7 +37,7 @@ cdef class BeamCXPEC(CoreBeamCXPEC): @cython.cdivision(True) def __init__(self, int donor_metastable, double wavelength, dict data, bint extrapolate=False): - self.donor_metastable = donor_metastable + super().__init__(donor_metastable) self.wavelength = wavelength self.raw_data = data @@ -79,7 +79,7 @@ cdef class BeamCXPEC(CoreBeamCXPEC): :param energy: Interaction energy in eV/amu. :param temperature: Receiver ion temperature in eV. - :param density: Receiver ion density in m^-3 + :param density: Plasma total ion density in m^-3 :param z_effective: Plasma Z-effective. :param b_field: Magnetic field magnitude in Tesla. :return: The effective cx rate in W.m^3 From 5208b016b9deb9c2abbe0ba7d0f89c4fa822112c Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 28 Jun 2024 00:39:23 +0200 Subject: [PATCH 23/32] Update figures for CX spectroscopy in documentation. --- .../CXS_multi_sightlines.png | Bin 59708 -> 56528 bytes .../active_spectroscopy/CXS_spectrum.png | Bin 24577 -> 26470 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/demonstrations/active_spectroscopy/CXS_multi_sightlines.png b/docs/source/demonstrations/active_spectroscopy/CXS_multi_sightlines.png index b04c94c0f5d8951b21edaff49875ea09ffebd141..f94e95ab45872636f40cb1cdda7bac29b4abbbc0 100644 GIT binary patch literal 56528 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

npWdp%+a4*>b82!^VTaD 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 IbxsLQ0K{d>jQ{`u diff --git a/docs/source/demonstrations/active_spectroscopy/CXS_spectrum.png b/docs/source/demonstrations/active_spectroscopy/CXS_spectrum.png index 75b083d70b5d0aa7758419b32a19629393701ecc..a258c6edf44d476882434bf440d92f28c16cbb39 100644 GIT binary patch literal 26470 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 literal 24577 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 MihIHK{OgDQ1N8Q7Jpcdz From b2230cb647e477dfe2f514a8536f0cb24a45e6c9 Mon Sep 17 00:00:00 2001 From: vsnever Date: Fri, 28 Jun 2024 00:48:09 +0200 Subject: [PATCH 24/32] Update changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c70837fb..43f62a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ New: Bug fixes: * Fix deprecated transforms being cached in LaserMaterial after laser.transform update (#420) * Fix IRVB calculate sensitivity method. +* Fix missing donor_metastable attribute in the core BeamCXPEC class (#411). +* **Fix the receiver ion density being passed to the BeamCXPEC instead of the total ion density in the BeamCXLine. Also fix incorrect BeamCXPEC dosctrings. Attention!!! The results of CX spectroscopy are affected by this change. (#441)** Release 1.4.0 (3 Feb 2023) ------------------- From 645d697f4541d852adc16f5c3e2386c5c64cb8d7 Mon Sep 17 00:00:00 2001 From: vsnever Date: Thu, 11 Jul 2024 00:32:46 +0200 Subject: [PATCH 25/32] Brake variable declaration line in BeamCXLine._composite_cx_rate() into several lines. --- cherab/core/model/beam/charge_exchange.pyx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cherab/core/model/beam/charge_exchange.pyx b/cherab/core/model/beam/charge_exchange.pyx index 12650ddb..83fe237f 100644 --- a/cherab/core/model/beam/charge_exchange.pyx +++ b/cherab/core/model/beam/charge_exchange.pyx @@ -192,7 +192,10 @@ cdef class BeamCXLine(BeamModel): """ cdef: - double z_effective, b_field, rate, total_population, population, effective_rate, ion_density + double ion_density, z_effective + double b_field + double rate, effective_rate + double population, total_population BeamCXPEC cx_rate list population_data From dc8e00c2dc835ce33ef4c1a7a4336d7a7c1fc01a Mon Sep 17 00:00:00 2001 From: vsnever Date: Sat, 20 Jul 2024 00:52:16 +0200 Subject: [PATCH 26/32] Add setUp to the TestCases containing Plasma to ensure that Plasma state is unchanged between the tests. --- cherab/core/tests/test_beam.py | 58 +++++++++++++----------- cherab/core/tests/test_beamcxline.py | 46 +++++++++++-------- cherab/core/tests/test_bremsstrahlung.py | 19 +++++--- cherab/core/tests/test_lineshapes.py | 13 ++++-- 4 files changed, 80 insertions(+), 56 deletions(-) diff --git a/cherab/core/tests/test_beam.py b/cherab/core/tests/test_beam.py index ef10126f..7d2ec203 100644 --- a/cherab/core/tests/test_beam.py +++ b/cherab/core/tests/test_beam.py @@ -55,32 +55,38 @@ def beam_stopping_rate(self, beam_ion, plasma_ion, charge): class TestBeam(unittest.TestCase): - atomic_data = MockAtomicData() - - world = World() - - plasma_density = 1.e19 - plasma_temperature = 1.e3 - plasma_species = [(deuterium, 1, plasma_density, plasma_temperature, Vector3D(0, 0, 0))] - plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=plasma_density, - electron_temperature=plasma_temperature, - plasma_species=plasma_species) - plasma.atomic_data = atomic_data - plasma.parent = world - - beam = Beam(transform=translate(0.5, 0, 0)) - beam.atomic_data = atomic_data - beam.plasma = plasma - beam.attenuator = SingleRayAttenuator(clamp_to_zero=True) - beam.energy = 50000 - beam.power = 1e6 - beam.temperature = 10 - beam.element = deuterium - beam.parent = world - beam.sigma = 0.2 - beam.divergence_x = 1. - beam.divergence_y = 2. - beam.length = 10. + def setUp(self): + + self.atomic_data = MockAtomicData() + + self.world = World() + + self.plasma_density = 1.e19 + self.plasma_temperature = 1.e3 + plasma_species = [(deuterium, 1, self.plasma_density, self.plasma_temperature, Vector3D(0, 0, 0))] + plasma = build_constant_slab_plasma(length=1, width=1, height=1, + electron_density=self.plasma_density, + electron_temperature=self.plasma_temperature, + plasma_species=plasma_species) + plasma.atomic_data = self.atomic_data + plasma.parent = self.world + + beam = Beam(transform=translate(0.5, 0, 0)) + beam.atomic_data = self.atomic_data + beam.plasma = plasma + beam.attenuator = SingleRayAttenuator(clamp_to_zero=True) + beam.energy = 50000 + beam.power = 1e6 + beam.temperature = 10 + beam.element = deuterium + beam.parent = self.world + beam.sigma = 0.2 + beam.divergence_x = 1. + beam.divergence_y = 2. + beam.length = 10. + + self.plasma = plasma + self.beam = beam def test_beam_density(self): diff --git a/cherab/core/tests/test_beamcxline.py b/cherab/core/tests/test_beamcxline.py index ccb0c855..250da0e8 100644 --- a/cherab/core/tests/test_beamcxline.py +++ b/cherab/core/tests/test_beamcxline.py @@ -76,25 +76,33 @@ def wavelength(self, ion, charge, transition): class TestBeamCXLine(unittest.TestCase): - world = World() - - atomic_data = MockAtomicData() - - plasma_species = [(deuterium, 1, 1.e19, 200., Vector3D(0, 0, 0))] - plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=1e19, electron_temperature=200., - plasma_species=plasma_species, b_field=Vector3D(0, 10., 0)) - plasma.atomic_data = atomic_data - plasma.parent = world - - beam = Beam(transform=translate(0.5, 0, 0)) - beam.atomic_data = atomic_data - beam.plasma = plasma - beam.attenuator = SingleRayAttenuator(clamp_to_zero=True) - beam.energy = 50000 - beam.power = 1e6 - beam.temperature = 10 - beam.element = deuterium - beam.parent = world + def setUp(self): + + self.world = World() + + self.atomic_data = MockAtomicData() + + plasma_species = [(deuterium, 1, 1.e19, 200., Vector3D(0, 0, 0))] + plasma = build_constant_slab_plasma(length=1, width=1, height=1, + electron_density=1e19, + electron_temperature=200., + plasma_species=plasma_species, + b_field=Vector3D(0, 10., 0)) + plasma.atomic_data = self.atomic_data + plasma.parent = self.world + + beam = Beam(transform=translate(0.5, 0, 0)) + beam.atomic_data = self.atomic_data + beam.plasma = plasma + beam.attenuator = SingleRayAttenuator(clamp_to_zero=True) + beam.energy = 50000 + beam.power = 1e6 + beam.temperature = 10 + beam.element = deuterium + beam.parent = self.world + + self.plasma = plasma + self.beam = beam def test_default_lineshape(self): # setting up the model diff --git a/cherab/core/tests/test_bremsstrahlung.py b/cherab/core/tests/test_bremsstrahlung.py index 5373776b..a77a1da5 100644 --- a/cherab/core/tests/test_bremsstrahlung.py +++ b/cherab/core/tests/test_bremsstrahlung.py @@ -34,13 +34,18 @@ class TestBremsstrahlung(unittest.TestCase): - world = World() - - plasma_species = [(deuterium, 1, 1.e19, 2000., Vector3D(0, 0, 0)), (nitrogen, 7, 1.e18, 2000., Vector3D(0, 0, 0))] - plasma = build_constant_slab_plasma(length=1, width=1, height=1, electron_density=1e19, electron_temperature=2000., - plasma_species=plasma_species) - plasma.parent = world - plasma.atomic_data = AtomicData() + def setUp(self): + + self.world = World() + + plasma_species = [(deuterium, 1, 1.e19, 2000., Vector3D(0, 0, 0)), + (nitrogen, 7, 1.e18, 2000., Vector3D(0, 0, 0))] + self.plasma = build_constant_slab_plasma(length=1, width=1, height=1, + electron_density=1e19, + electron_temperature=2000., + plasma_species=plasma_species) + self.plasma.parent = self.world + self.plasma.atomic_data = AtomicData() def test_bremsstrahlung_model(self): # setting up the model diff --git a/cherab/core/tests/test_lineshapes.py b/cherab/core/tests/test_lineshapes.py index da4dc41d..97a32f1f 100644 --- a/cherab/core/tests/test_lineshapes.py +++ b/cherab/core/tests/test_lineshapes.py @@ -42,10 +42,15 @@ class TestLineShapes(unittest.TestCase): - plasma_species = [(deuterium, 0, 1.e18, 5., Vector3D(2.e4, 0, 0)), - (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)) + def setUp(self): + + plasma_species = [(deuterium, 0, 1.e18, 5., Vector3D(2.e4, 0, 0)), + (nitrogen, 1, 1.e17, 10., Vector3D(1.e4, 5.e4, 0))] + self.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)) def test_gaussian_line(self): # setting up a line shape model From 3ad37ad8d014506a4dde2a3dbe75fcebae34afd4 Mon Sep 17 00:00:00 2001 From: vsnever Date: Sat, 20 Jul 2024 17:54:26 +0200 Subject: [PATCH 27/32] Make atomic rates return zero if plasma or beam parameters <= 0. --- cherab/openadas/rates/atomic.pyx | 21 +++++------------ cherab/openadas/rates/beam.pyx | 30 +++++------------------- cherab/openadas/rates/cx.pyx | 4 ++-- cherab/openadas/rates/pec.pyx | 14 ++++------- cherab/openadas/rates/radiated_power.pyx | 21 +++++------------ 5 files changed, 24 insertions(+), 66 deletions(-) diff --git a/cherab/openadas/rates/atomic.pyx b/cherab/openadas/rates/atomic.pyx index 04500124..bccd1ff3 100644 --- a/cherab/openadas/rates/atomic.pyx +++ b/cherab/openadas/rates/atomic.pyx @@ -50,11 +50,8 @@ cdef class IonisationRate(CoreIonisationRate): cpdef double evaluate(self, double density, double temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -97,11 +94,8 @@ cdef class RecombinationRate(CoreRecombinationRate): cpdef double evaluate(self, double density, double temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -143,11 +137,8 @@ cdef class ThermalCXRate(CoreThermalCXRate): cpdef double evaluate(self, double density, double temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) diff --git a/cherab/openadas/rates/beam.pyx b/cherab/openadas/rates/beam.pyx index f40af8dd..58bdaa87 100644 --- a/cherab/openadas/rates/beam.pyx +++ b/cherab/openadas/rates/beam.pyx @@ -78,14 +78,8 @@ cdef class BeamStoppingRate(CoreBeamStoppingRate): """ # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if energy < 1.e-300: - energy = 1.e-300 - - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if energy <= 0 or density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** (self._npl_eb.evaluate(log10(energy), log10(density)) + self._tp.evaluate(log10(temperature))) @@ -152,14 +146,8 @@ cdef class BeamPopulationRate(CoreBeamPopulationRate): """ # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if energy < 1.e-300: - energy = 1.e-300 - - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if energy <= 0 or density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** (self._npl_eb.evaluate(log10(energy), log10(density)) + self._tp.evaluate(log10(temperature))) @@ -228,14 +216,8 @@ cdef class BeamEmissionPEC(CoreBeamEmissionPEC): """ # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if energy < 1.e-300: - energy = 1.e-300 - - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if energy <= 0 or density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** (self._npl_eb.evaluate(log10(energy), log10(density)) + self._tp.evaluate(log10(temperature))) diff --git a/cherab/openadas/rates/cx.pyx b/cherab/openadas/rates/cx.pyx index cb827a8b..342a9dbc 100644 --- a/cherab/openadas/rates/cx.pyx +++ b/cherab/openadas/rates/cx.pyx @@ -88,8 +88,8 @@ cdef class BeamCXPEC(CoreBeamCXPEC): cdef double rate # need to handle zeros for log-log interpolation - if energy < 1.e-300: - energy = 1.e-300 + if energy <= 0: + return 0 rate = 10 ** self._eb.evaluate(log10(energy)) diff --git a/cherab/openadas/rates/pec.pyx b/cherab/openadas/rates/pec.pyx index eafc6c64..c33e2bc2 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/openadas/rates/pec.pyx @@ -56,11 +56,8 @@ cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): cpdef double evaluate(self, double density, double temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -108,11 +105,8 @@ cdef class RecombinationPEC(CoreRecombinationPEC): cpdef double evaluate(self, double density, double temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if density < 1.e-300: - density = 1.e-300 - - if temperature < 1.e-300: - temperature = 1.e-300 + if density <= 0 or temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) diff --git a/cherab/openadas/rates/radiated_power.pyx b/cherab/openadas/rates/radiated_power.pyx index 570ced92..bf9c1667 100644 --- a/cherab/openadas/rates/radiated_power.pyx +++ b/cherab/openadas/rates/radiated_power.pyx @@ -49,11 +49,8 @@ cdef class LineRadiationPower(CoreLineRadiationPower): cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if electron_density < 1.e-300: - electron_density = 1.e-300 - - if electron_temperature < 1.e-300: - electron_temperature = 1.e-300 + if electron_density <= 0 or electron_temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) @@ -95,11 +92,8 @@ cdef class ContinuumPower(CoreContinuumPower): cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if electron_density < 1.e-300: - electron_density = 1.e-300 - - if electron_temperature < 1.e-300: - electron_temperature = 1.e-300 + if electron_density <= 0 or electron_temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) @@ -140,11 +134,8 @@ cdef class CXRadiationPower(CoreCXRadiationPower): cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: # need to handle zeros, also density and temperature can become negative due to cubic interpolation - if electron_density < 1.e-300: - electron_density = 1.e-300 - - if electron_temperature < 1.e-300: - electron_temperature = 1.e-300 + if electron_density <= 0 or electron_temperature <= 0: + return 0 # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) From d81096abed284094f3f7c33339212783e499217d Mon Sep 17 00:00:00 2001 From: vsnever Date: Mon, 22 Jul 2024 14:15:27 +0200 Subject: [PATCH 28/32] Correct and expand documentation for the core AtomicData class --- cherab/core/atomic/interface.pyx | 63 +++++++++++++++++--- docs/source/atomic/atomic_data.rst | 1 + docs/source/atomic/atomic_data_interface.rst | 19 ++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 docs/source/atomic/atomic_data_interface.rst diff --git a/cherab/core/atomic/interface.pyx b/cherab/core/atomic/interface.pyx index 286ebfb4..3e44d52c 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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -29,70 +29,117 @@ cdef class AtomicData: cpdef double wavelength(self, Element ion, int charge, tuple transition): """ - Returns the natural wavelength of the specified transition in nm. + The natural wavelength of the specified transition in nm. """ raise NotImplementedError("The wavelength() virtual method is not implemented for this atomic data source.") cpdef IonisationRate ionisation_rate(self, Element ion, int charge): + """ + Electron impact ionisation rate for a given species in m^3/s. + """ + raise NotImplementedError("The ionisation_rate() virtual method is not implemented for this atomic data source.") cpdef RecombinationRate recombination_rate(self, Element ion, int charge): + """ + Recombination rate for a given species in m^3/s. + """ + raise NotImplementedError("The recombination_rate() virtual method is not implemented for this atomic data source.") cpdef ThermalCXRate thermal_cx_rate(self, Element donor_ion, int donor_charge, Element receiver_ion, int receiver_charge): + """ + Thermal charge exchange effective rate coefficient for a given donor and receiver species in m^3/s. + """ + raise NotImplementedError("The thermal_cx_rate() virtual method is not implemented for this atomic data source.") cpdef list beam_cx_pec(self, Element donor_ion, Element receiver_ion, int receiver_charge, tuple transition): """ - Returns a list of applicable charge exchange emission rates in W.m^3. + A list of Effective charge exchange photon emission coefficient for a given donor (beam) in W.m^3. """ raise NotImplementedError("The cxs_rates() virtual method is not implemented for this atomic data source.") cpdef BeamStoppingRate beam_stopping_rate(self, Element beam_ion, Element plasma_ion, int charge): """ - Returns a list of applicable beam stopping coefficients in m^3/s. + Beam stopping coefficient for a given beam and target species in m^3/s. """ raise NotImplementedError("The beam_stopping() virtual method is not implemented for this atomic data source.") cpdef BeamPopulationRate beam_population_rate(self, Element beam_ion, int metastable, Element plasma_ion, int charge): """ - Returns a list of applicable dimensionless beam population coefficients. + Dimensionless Beam population coefficient for a given beam and target species. """ raise NotImplementedError("The beam_population() virtual method is not implemented for this atomic data source.") cpdef BeamEmissionPEC beam_emission_pec(self, Element beam_ion, Element plasma_ion, int charge, tuple transition): """ - Returns a list of applicable beam emission coefficients in W.m^3. + The beam photon emission coefficient for a given beam and target species + and a given transition in W.m^3. """ raise NotImplementedError("The beam_emission() virtual method is not implemented for this atomic data source.") cpdef ImpactExcitationPEC impact_excitation_pec(self, Element ion, int charge, tuple transition): + """ + Electron impact excitation photon emission coefficient for a given species in W.m^3. + """ + raise NotImplementedError("The impact_excitation() virtual method is not implemented for this atomic data source.") cpdef RecombinationPEC recombination_pec(self, Element ion, int charge, tuple transition): + """ + Recombination photon emission coefficient for a given species in W.m^3. + """ + raise NotImplementedError("The recombination() virtual method is not implemented for this atomic data source.") cpdef TotalRadiatedPower total_radiated_power(self, Element element): + """ + The total (summed over all charge states) radiated power + in equilibrium conditions for a given species in W.m^3. + """ + raise NotImplementedError("The total_radiated_power() virtual method is not implemented for this atomic data source.") cpdef LineRadiationPower line_radiated_power_rate(self, Element element, int charge): + """ + Line radiated power coefficient for a given species in W.m^3. + """ + raise NotImplementedError("The line_radiated_power_rate() virtual method is not implemented for this atomic data source.") cpdef ContinuumPower continuum_radiated_power_rate(self, Element element, int charge): + """ + Continuum radiated power coefficient for a given species in W.m^3. + """ + raise NotImplementedError("The continuum_radiated_power_rate() virtual method is not implemented for this atomic data source.") cpdef CXRadiationPower cx_radiated_power_rate(self, Element element, int charge): + """ + Charge exchange radiated power coefficient for a given species in W.m^3. + """ + raise NotImplementedError("The cx_radiated_power_rate() virtual method is not implemented for this atomic data source.") cpdef FractionalAbundance fractional_abundance(self, Element ion, int charge): + """ + Fractional abundance of a given species in thermodynamic equilibrium. + """ + raise NotImplementedError("The fractional_abundance() virtual method is not implemented for this atomic data source.") cpdef ZeemanStructure zeeman_structure(self, Line line, object b_field=None): + r""" + Wavelengths and ratios of :math:`\pi`-/:math:`\sigma`-polarised Zeeman components + for any given value of magnetic field strength. + """ + raise NotImplementedError("The zeeman_structure() virtual method is not implemented for this atomic data source.") cpdef FreeFreeGauntFactor free_free_gaunt_factor(self): diff --git a/docs/source/atomic/atomic_data.rst b/docs/source/atomic/atomic_data.rst index 650d89b4..6eeb09a1 100644 --- a/docs/source/atomic/atomic_data.rst +++ b/docs/source/atomic/atomic_data.rst @@ -7,3 +7,4 @@ Atomic Data emission_lines rate_coefficients gaunt_factors + atomic_data_interface diff --git a/docs/source/atomic/atomic_data_interface.rst b/docs/source/atomic/atomic_data_interface.rst new file mode 100644 index 00000000..0d54ff67 --- /dev/null +++ b/docs/source/atomic/atomic_data_interface.rst @@ -0,0 +1,19 @@ + +Atomic Data Interface +===================== + +Abstract (interface) class +-------------------------- + +Abstract atomic data interface. + +.. autoclass:: cherab.core.atomic.interface.AtomicData + :members: + +OpenADAS atomic data source +--------------------------- + +Interface to local atomic data repository. + +.. autoclass:: cherab.openadas.openadas.OpenADAS + :members: From 8934c8ccec63b74d40fd599bbf57e188a437dccc Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 24 Jul 2024 22:51:09 +0200 Subject: [PATCH 29/32] Improve documentation for openadas repository install and manipulation functions. --- cherab/openadas/install.py | 61 ++++---- cherab/openadas/repository/atomic.py | 143 +++++++++++++++--- cherab/openadas/repository/beam/cx.py | 97 ++++++++++-- cherab/openadas/repository/beam/emission.py | 74 ++++++++- cherab/openadas/repository/beam/population.py | 72 ++++++++- cherab/openadas/repository/beam/stopping.py | 69 ++++++++- cherab/openadas/repository/pec.py | 125 ++++++++++++--- cherab/openadas/repository/radiated_power.py | 140 ++++++++++++++--- cherab/openadas/repository/wavelength.py | 42 ++++- docs/source/atomic/atomic_data.rst | 2 + docs/source/atomic/openadas.rst | 29 ++++ docs/source/atomic/repository.rst | 83 ++++++++++ 12 files changed, 798 insertions(+), 139 deletions(-) create mode 100644 docs/source/atomic/openadas.rst create mode 100644 docs/source/atomic/repository.rst diff --git a/cherab/openadas/install.py b/cherab/openadas/install.py index 27e0c5f4..d634cde7 100644 --- a/cherab/openadas/install.py +++ b/cherab/openadas/install.py @@ -269,14 +269,17 @@ def install_adf15(element, ionisation, file_path, download=False, repository_pat def install_adf21(beam_species, target_ion, target_charge, file_path, download=False, repository_path=None, adas_path=None): - # """ - # Adds the rate defined in an ADF21 file to the repository. - # - # :param file_path: Path relative to ADAS root. - # :param download: Attempt to download file if not present (Default=True). - # :param repository_path: Path to the repository in which to install the rates (optional). - # :param adas_path: Path to ADAS files repository (optional). - # """ + """ + Adds the beam stopping rate defined in an ADF21 file to the repository. + + :param beam_species: Beam neutral atom (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param file_path: Path relative to ADAS root. + :param download: Attempt to download file if not present (Default=True). + :param repository_path: Path to the repository in which to install the rates (optional). + :param adas_path: Path to ADAS files repository (optional). + """ print('Installing {}...'.format(file_path)) path = _locate_adas_file(file_path, download, adas_path, repository_path) @@ -289,15 +292,18 @@ def install_adf21(beam_species, target_ion, target_charge, file_path, download=F def install_adf22bmp(beam_species, beam_metastable, target_ion, target_charge, file_path, download=False, repository_path=None, adas_path=None): - pass - # """ - # Adds the rate defined in an ADF21 file to the repository. - # - # :param file_path: Path relative to ADAS root. - # :param download: Attempt to download file if not present (Default=True). - # :param repository_path: Path to the repository in which to install the rates (optional). - # :param adas_path: Path to ADAS files repository (optional). - # """ + """ + Adds the beam population rate defined in an ADF22 BMP file to the repository. + + :param beam_species: Beam neutral atom (Element/Isotope). + :param beam_metastable: Metastable/excitation level of beam neutral atom. + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param file_path: Path relative to ADAS root. + :param download: Attempt to download file if not present (Default=True). + :param repository_path: Path to the repository in which to install the rates (optional). + :param adas_path: Path to ADAS files repository (optional). + """ print('Installing {}...'.format(file_path)) path = _locate_adas_file(file_path, download, adas_path, repository_path) @@ -310,15 +316,18 @@ def install_adf22bmp(beam_species, beam_metastable, target_ion, target_charge, f def install_adf22bme(beam_species, target_ion, target_charge, transition, file_path, download=False, repository_path=None, adas_path=None): - pass - # """ - # Adds the rate defined in an ADF21 file to the repository. - # - # :param file_path: Path relative to ADAS root. - # :param download: Attempt to download file if not present (Default=True). - # :param repository_path: Path to the repository in which to install the rates (optional). - # :param adas_path: Path to ADAS files repository (optional). - # """ + """ + Adds the beam emission rate defined in an ADF22 BME file to the repository. + + :param beam_species: Beam neutral atom (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param transition: Tuple containing (initial level, final level). + :param file_path: Path relative to ADAS root. + :param download: Attempt to download file if not present (Default=True). + :param repository_path: Path to the repository in which to install the rates (optional). + :param adas_path: Path to ADAS files repository (optional). + """ print('Installing {}...'.format(file_path)) path = _locate_adas_file(file_path, download, adas_path, repository_path) diff --git a/cherab/openadas/repository/atomic.py b/cherab/openadas/repository/atomic.py index c24234f3..cb8add6c 100644 --- a/cherab/openadas/repository/atomic.py +++ b/cherab/openadas/repository/atomic.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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -34,8 +34,15 @@ def add_ionisation_rate(species, charge, rate, repository_path=None): function instead. The update function avoids repeatedly opening and closing the rate files. - :param repository_path: - :return: + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Ionisation rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with ionisation rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ update_ionisation_rates({ @@ -47,11 +54,21 @@ def add_ionisation_rate(species, charge, rate, repository_path=None): def update_ionisation_rates(rates, repository_path=None): """ - Ionisation rate file structure - - /ionisation/.json + Updates the ionisation rate files `/ionisation/.json` + in atomic data repository. File contains multiple rates, indexed by the ion charge state. + + :param rates: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the ionisation rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with ionisation rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -75,8 +92,15 @@ def add_recombination_rate(species, charge, rate, repository_path=None): function instead. The update function avoids repeatedly opening and closing the rate files. - :param repository_path: - :return: + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Recombination rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with recombination rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ update_recombination_rates({ @@ -88,11 +112,21 @@ def add_recombination_rate(species, charge, rate, repository_path=None): def update_recombination_rates(rates, repository_path=None): """ - Ionisation rate file structure - - /recombination/.json + Updates the recombination rate files `/recombination/.json` + in the atomic data repository. File contains multiple rates, indexed by the ion charge state. + + :param rates: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the recombination rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with recombination rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -109,7 +143,6 @@ def update_recombination_rates(rates, repository_path=None): def add_thermal_cx_rate(donor_element, donor_charge, receiver_element, rate, repository_path=None): - """ Adds a single thermal charge exchange rate to the repository. @@ -118,11 +151,16 @@ def add_thermal_cx_rate(donor_element, donor_charge, receiver_element, rate, rep the rate files. :param donor_element: Element donating the electron. - :param donor_charge: Charge of the donating atom/ion - :param receiver_element: Element receiving the electron - :param rate: rates - :param repository_path: - :return: + :param donor_charge: Charge of the donating atom/ion. + :param receiver_element: Element receiving the electron. + :param receiver_charge: Charge of the receiving atom/ion. + :param rate: Thermal CX rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with thermal CX rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ rates2update = RecursiveDict() @@ -133,11 +171,25 @@ def add_thermal_cx_rate(donor_element, donor_charge, receiver_element, rate, rep def update_thermal_cx_rates(rates, repository_path=None): """ - Thermal charge exchange rate file structure - - /thermal_cx///.json + Updates the thermal charge exchange rate files + `/thermal_cx///.json` + in the atomic data repository. File contains multiple rates, indexed by the ion charge state. + + :param rates: Dictionary in the form: + + | { : { : { : { : } } } }, where + | is the element donating the electron. + | is the charge of the donating atom/ion. + | is the element receiving the electron. + | is the charge of the receiving atom/ion. + | is the thermal CX rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with thermal CX rate in m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -203,6 +255,21 @@ def _update_and_write_adf11(species, rate_data, path): def get_ionisation_rate(element, charge, repository_path=None): + """ + Reads the ionisation rate for the given species and charge + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param repository_path: Path to the atomic data repository. + + :return rate: Ionisation rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with ionisation rate in m^3.s^-1. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -224,6 +291,21 @@ def get_ionisation_rate(element, charge, repository_path=None): def get_recombination_rate(element, charge, repository_path=None): + """ + Reads the recombination rate for the given species and charge + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param repository_path: Path to the atomic data repository. + + :return rate: Recombination rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with recombination rate in m^3.s^-1. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -245,6 +327,23 @@ def get_recombination_rate(element, charge, repository_path=None): def get_thermal_cx_rate(donor_element, donor_charge, receiver_element, receiver_charge, repository_path=None): + """ + Reads the thermal charge exchange rate for the given species and charge + from the atomic data repository. + + :param donor_element: Element donating the electron. + :param donor_charge: Charge of the donating atom/ion. + :param receiver_element: Element receiving the electron. + :param receiver_charge: Charge of the receiving atom/ion. + :param repository_path: Path to the atomic data repository. + + :return rate: Thermal CX rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with thermal CX rate in m^3.s^-1. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH diff --git a/cherab/openadas/repository/beam/cx.py b/cherab/openadas/repository/beam/cx.py index 65bc3ceb..ef79c102 100644 --- a/cherab/openadas/repository/beam/cx.py +++ b/cherab/openadas/repository/beam/cx.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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -29,19 +29,33 @@ def add_beam_cx_rate(donor_ion, donor_metastable, receiver_ion, receiver_charge, transition, rate, repository_path=None): """ - Adds a single beam CX rate to the repository. + Adds a single beam CX PEC to the repository. If adding multiple rate, consider using the update_beam_cx_rates() function instead. The update function avoid repeatedly opening and closing the rate files. - :param donor_ion: - :param donor_metastable: - :param receiver_ion: - :param receiver_charge: - :param rate: - :param repository_path: - :return: + :param donor_ion: Beam neutral atom (Element/Isotope) donating the electron. + :param donor_metastable: Metastable/excited level of beam neutral atom. + :param receiver_ion: Element/Isotope receiving the electron. + :param receiver_charge: Charge of the receiving atom/ion. + :param transition: Tuple containing (initial level, final level). + :param rate: Beam CX PEC dictionary containing the following entries: + + | 'eb': array-like of size (N) with beam energy in eV/amu, + | 'ti': array-like of size (M) with receiver ion temperature in eV, + | 'ni': array-like of size (K) with plasma ion density in m^-3, + | 'z': array-like of size (L) with plasma Z-effective, + | 'b': array-like of size (J) with magnetic field strength in Tesla, + | 'qeb': array-like of size (N) with CX PEC energy component in photon.m^3.s-1, + | 'qti': array-like of size (M) with CX PEC temperature component in photon.m^3.s-1, + | 'qni': array-like of size (K) with CX PEC density component in photon.m^3.s-1, + | 'qz': array-like of size (L) with CX PEC Zeff component in photon.m^3.s-1, + | 'qb': array-like of size (J) with CX PEC B-field component in photon.m^3.s-1, + | 'qref': reference CX PEC in photon.m^3.s-1. + | The total beam CX PEC: q = qeb * qti * qni * qz * qb / qref^4. + + :param repository_path: Path to the atomic data repository. """ update_beam_cx_rates({ @@ -58,10 +72,37 @@ def add_beam_cx_rate(donor_ion, donor_metastable, receiver_ion, receiver_charge, def update_beam_cx_rates(rates, repository_path=None): - # organisation in repository: - # beam/cx/donor_ion/receiver_ion/receiver_charge.json - # inside json file: - # transition: [list of donor_metastables with rates] + """ + Updates the beam CX PEC files + beam/cx///.json + in the atomic data repository. + + File contains multiple metastable-resolved rates, indexed by transition. + + :param rates: Dictionary in the form: + + | { : { : { : { : {: } } } } }, where + | is the beam neutral atom (Element/Isotope) donating the electron. + | is the metastable/excited level of beam neutral atom. + | is the Element/Isotope receiving the electron. + | is the charge of the receiving atom/ion. + | is the tuple containing (initial level, final level). + | is the beam CX PEC dictionary containing the following entries: + | 'eb': array-like of size (N) with beam energy in eV/amu, + | 'ti': array-like of size (M) with receiver ion temperature in eV, + | 'ni': array-like of size (K) with plasma ion density in m^-3, + | 'z': array-like of size (L) with plasma Z-effective, + | 'b': array-like of size (J) with magnetic field strength in Tesla, + | 'qeb': array-like of size (N) with CX PEC energy component in photon.m^3.s-1, + | 'qti': array-like of size (M) with CX PEC temperature component in photon.m^3.s-1, + | 'qni': array-like of size (K) with CX PEC density component in photon.m^3.s-1, + | 'qz': array-like of size (L) with CX PEC Zeff component in photon.m^3.s-1, + | 'qb': array-like of size (J) with CX PEC B-field component in photon.m^3.s-1, + | 'qref': reference CX PEC in photon.m^3.s-1. + | The total beam CX PEC: q = qeb * qti * qni * qz * qb / qref^4. + + :param repository_path: Path to the atomic data repository. + """ def sanitise_and_validate(data, x_key, x_name, y_key, y_name): """ @@ -167,6 +208,32 @@ def sanitise_and_validate(data, x_key, x_name, y_key, y_name): def get_beam_cx_rates(donor_ion, receiver_ion, receiver_charge, transition, repository_path=None): + """ + Reads a single beam CX PEC from the repository. + + :param donor_ion: Beam neutral atom (Element/Isotope) donating the electron. + :param donor_metastable: Metastable/excited level of beam neutral atom. + :param receiver_ion: Element/Isotope receiving the electron. + :param receiver_charge: Charge of the receiving atom/ion. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return rate: Beam CX PEC dictionary containing the following entries: + + | 'eb': 1D array of size (N) with beam energy in eV/amu, + | 'ti': 1D array of size (M) with receiver ion temperature in eV, + | 'ni': 1D array of size (K) with plasma ion density in m^-3, + | 'z': 1D array of size (L) with plasma Z-effective, + | 'b': 1D array of size (J) with magnetic field strength in Tesla, + | 'qeb': 1D array of size (N) with CX PEC energy component in photon.m^3.s-1, + | 'qti': 1D array of size (M) with CX PEC temperature component in photon.m^3.s-1, + | 'qni': 1D array of size (K) with CX PEC density component in photon.m^3.s-1, + | 'qz': 1D array of size (L) with CX PEC Zeff component in photon.m^3.s-1, + | 'qb': 1D array of size (J) with CX PEC B-field component in photon.m^3.s-1, + | 'qref': reference CX PEC in photon.m^3.s-1. + | The total beam CX PEC: q = qeb * qti * qni * qz * qb / qref^4. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH path = os.path.join(repository_path, 'beam/cx/{}/{}/{}.json'.format(donor_ion.symbol.lower(), receiver_ion.symbol.lower(), receiver_charge)) diff --git a/cherab/openadas/repository/beam/emission.py b/cherab/openadas/repository/beam/emission.py index b6cd14f2..c7c6e2e1 100644 --- a/cherab/openadas/repository/beam/emission.py +++ b/cherab/openadas/repository/beam/emission.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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -36,8 +36,24 @@ def add_beam_emission_rate(beam_species, target_ion, target_charge, transition, function instead. The update function avoid repeatedly opening and closing the rate files. - :param repository_path: - :return: + :param beam_species: Beam neutral species (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param transition: Tuple containing (initial level, final level). + :param rate: Beam emission rate dictionary containing the following entries: + + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n' array-like of size (M) with target electron density in m^-3, + | 't' array-like of size (K) with target electron temperature in eV, + | 'sen' array-like of size (N, M) with beam emission rate energy component in photon.m^3.s^-1. + | 'st' array-like of size (K) with beam emission rate temperature component in photon.m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam emission rate in photon.m^3.s^-1. + | The total beam emission rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ update_beam_emission_rates({ @@ -53,11 +69,32 @@ def add_beam_emission_rate(beam_species, target_ion, target_charge, transition, def update_beam_emission_rates(rates, repository_path=None): """ - Beam emission rate file structure - + Updates the beam emission rate files: /beam/emission///.json + in the atomic repository. File contains multiple rates, indexed by transition. + + :param rates: Dictionary in the form: + + | { : { : { : {: } } } }, where + | is the beam neutral species (Element/Isotope) + | is the target species (Element/Isotope). + | is the charge of the target species. + | is the tuple containing (initial level, final level). + | Beam emission rate dictionary containing the following entries: + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n' array-like of size (M) with target electron density in m^-3, + | 't' array-like of size (K) with target electron temperature in eV, + | 'sen' array-like of size (N, M) with beam emission rate energy component in photon.m^3.s^-1. + | 'st' array-like of size (K) with beam emission rate temperature component in photon.m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam emission rate in photon.m^3.s^-1. + | The total beam emission rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -136,6 +173,29 @@ def update_beam_emission_rates(rates, repository_path=None): def get_beam_emission_rate(beam_species, target_ion, target_charge, transition, repository_path=None): + """ + Reads a single beam emission rate from the repository. + + :param beam_species: Beam neutral species (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return rate: Beam emission rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n' 1D array of size (M) with target electron density in m^-3, + | 't' 1D array of size (K) with target electron temperature in eV, + | 'sen' 2D array of size (N, M) with beam emission rate energy component in photon.m^3.s^-1. + | 'st' 1D array of size (K) with beam emission rate temperature component in photon.m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam emission rate in photon.m^3.s^-1. + | The total beam emission rate: s = sen * st / sref. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH path = os.path.join(repository_path, 'beam/emission/{}/{}/{}.json'.format(beam_species.symbol.lower(), target_ion.symbol.lower(), target_charge)) diff --git a/cherab/openadas/repository/beam/population.py b/cherab/openadas/repository/beam/population.py index 54c57df1..9ecfa6eb 100644 --- a/cherab/openadas/repository/beam/population.py +++ b/cherab/openadas/repository/beam/population.py @@ -31,12 +31,24 @@ def add_beam_population_rate(beam_species, beam_metastable, target_ion, target_c """ Adds a single beam population rate to the repository. - :param beam_species: - :param beam_metastable: - :param target_ion: - :param target_charge: - :param rate: - :return: + :param beam_species: Beam neutral species (Element/Isotope). + :param beam_metastable: Metastable level of beam neutral atom. + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param rate: Beam population rate dictionary containing the following entries: + + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n': array-like of size (M) with target electron density in m^-3, + | 't': array-like of size (K) with target electron temperature in eV, + | 'sen': array-like of size (N, M) with dimensionless beam population rate energy component. + | 'st': array-like of size (K) with dimensionless beam population rate temperature component. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference dimensionless beam population rate. + | The total beam population rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -102,11 +114,32 @@ def add_beam_population_rate(beam_species, beam_metastable, target_ion, target_c def update_beam_population_rates(rates, repository_path=None): """ - Beam population rate file structure - + Updates the beam population rate files /beam/population////.json + in the atomic data repository. Each json file contains a single rate, so it can simply be replaced. + + :param rates: Dictionary in the form: + + | { : { : { : {: } } } }, where + | is the beam neutral species (Element/Isotope) + | is the metastable level of beam neutral atom. + | is the target species (Element/Isotope). + | is the charge of the target species. + | is the beam population rate dictionary containing the following fields: + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n': array-like of size (M) with target electron density in m^-3, + | 't': array-like of size (K) with target electron temperature in eV, + | 'sen': array-like of size (N, M) with dimensionless beam population rate energy component. + | 'st': array-like of size (K) with dimensionless beam population rate temperature component. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference dimensionless beam population rate. + | The total beam population rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ for beam_species, beam_metastables in rates.items(): @@ -117,6 +150,29 @@ def update_beam_population_rates(rates, repository_path=None): def get_beam_population_rate(beam_species, beam_metastable, target_ion, target_charge, repository_path=None): + """ + Reads a single beam population rate from the repository. + + :param beam_species: Beam neutral species (Element/Isotope). + :param beam_metastable: Metastable level of beam neutral atom. + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param repository_path: Path to the atomic data repository. + + :return rate: Beam population rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n': 1D array of size (M) with target electron density in m^-3, + | 't': 1D array of size (K) with target electron temperature in eV, + | 'sen': 2D array of size (N, M) with dimensionless beam population rate energy component. + | 'st': 1D array of size (K) with dimensionless beam population rate temperature component. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference dimensionless beam population rate. + | The total beam population rate: s = sen * st / sref. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH path = os.path.join(repository_path, 'beam/population/{}/{}/{}/{}.json'.format(beam_species.symbol.lower(), beam_metastable, target_ion.symbol.lower(), target_charge)) diff --git a/cherab/openadas/repository/beam/stopping.py b/cherab/openadas/repository/beam/stopping.py index cf8d1492..46d8caae 100644 --- a/cherab/openadas/repository/beam/stopping.py +++ b/cherab/openadas/repository/beam/stopping.py @@ -31,11 +31,23 @@ def add_beam_stopping_rate(beam_species, target_ion, target_charge, rate, reposi """ Adds a single beam stopping/excitation rate to the repository. - :param beam_species: - :param target_ion: - :param target_charge: - :param rate: - :return: + :param beam_species: Beam neutral atom (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param rate: Beam stopping rate dictionary containing the following entries: + + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n': array-like of size (M) with target electron density in m^-3, + | 't': array-like of size (K) with target electron temperature in eV, + | 'sen': array-like of size (N, M) with beam stopping rate energy component in m^3.s^-1. + | 'st': array-like of size (K) with beam stopping rate temperature component in m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam stopping rate in m^3.s^-1. + | The total beam stopping rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -98,11 +110,30 @@ def add_beam_stopping_rate(beam_species, target_ion, target_charge, rate, reposi def update_beam_stopping_rates(rates, repository_path=None): """ - Beam stopping rate file structure - - /beam/stopping///.json + Updates the beam stopping rate files + /beam/stopping////.json + in the atomic data repository. Each json file contains a single rate, so it can simply be replaced. + + :param rates: Dictionary in the form: + + | { : { : { : {: } } } }, where + | is the beam neutral species (Element/Isotope). + | is the target species (Element/Isotope). + | is the charge of the target species. + | is the beam stopping rate dictionary containing the following entries: + | 'e': array-like of size (N) with interaction energy in eV/amu, + | 'n': array-like of size (M) with target electron density in m^-3, + | 't': array-like of size (K) with target electron temperature in eV, + | 'sen': array-like of size (N, M) with beam stopping rate energy component in m^3.s^-1. + | 'st': array-like of size (K) with beam stopping rate temperature component in m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam stopping rate in m^3.s^-1. + | The total beam stopping rate: s = sen * st / sref. + """ for beam_species, target_ions in rates.items(): @@ -112,6 +143,28 @@ def update_beam_stopping_rates(rates, repository_path=None): def get_beam_stopping_rate(beam_species, target_ion, target_charge, repository_path=None): + """ + Reads a single beam stopping/excitation rate from the repository. + + :param beam_species: Beam neutral atom (Element/Isotope). + :param target_ion: Target species (Element/Isotope). + :param target_charge: Charge of the target species. + :param repository_path: Path to the atomic data repository. + + :return rate: Beam stopping rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n': 1D array of size (M) with target electron density in m^-3, + | 't': 1D array of size (K) with target electron temperature in eV, + | 'sen': 2D array of size (N, M) with beam stopping rate energy component in m^3.s^-1. + | 'st': 1D array of size (K) with beam stopping rate temperature component in m^3.s^-1. + | 'eref': reference interaction energy in eV/amu, + | 'nref': reference target electron density in m^-3, + | 'tref': reference target electron temperature in eV, + | 'sref': reference beam stopping rate in m^3.s^-1. + | The total beam stopping rate: s = sen * st / sref. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH path = os.path.join(repository_path, 'beam/stopping/{}/{}/{}.json'.format(beam_species.symbol.lower(), target_ion.symbol.lower(), target_charge)) diff --git a/cherab/openadas/repository/pec.py b/cherab/openadas/repository/pec.py index 8eb867fc..13fc055e 100644 --- a/cherab/openadas/repository/pec.py +++ b/cherab/openadas/repository/pec.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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -36,12 +36,16 @@ def add_pec_excitation_rate(element, charge, transition, rate, repository_path=N instead. The update function avoid repeatedly opening and closing the rate files. - :param element: - :param charge: - :param transition: - :param rate: - :param repository_path: - :return: + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param rate: Excitation PEC dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with excitation PEC in photon.m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -63,12 +67,16 @@ def add_pec_recombination_rate(element, charge, transition, rate, repository_pat instead. The update function avoid repeatedly opening and closing the rate files. - :param element: - :param charge: - :param transition: - :param rate: - :param repository_path: - :return: + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param rate: Recombination PEC dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with recombination PEC in photon.m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -84,18 +92,22 @@ def add_pec_recombination_rate(element, charge, transition, rate, repository_pat def add_pec_thermalcx_rate(element, charge, transition, rate, repository_path=None): """ - Adds a single PEC thermalcx rate to the repository. + Adds a single PEC thermal charge exchange rate to the repository. If adding multiple rate, consider using the update_pec_rates() function instead. The update function avoid repeatedly opening and closing the rate files. - :param element: - :param charge: - :param transition: - :param rate: - :param repository_path: - :return: + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param rate: Thermal CX PEC dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with thermal CX PEC in photon.m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -111,9 +123,24 @@ def add_pec_thermalcx_rate(element, charge, transition, rate, repository_path=No def update_pec_rates(rates, repository_path=None): """ - PEC rate file structure + Updates the PEC files /pec///.json. + in the atomic data repository. + + File contains multiple PECs, indexed by the transition. + + :param rates: Dictionary in the form: - /pec///.json + | { : { : { : { : } } } }, where + | is the one of the following PEC types: 'excitation', 'recombination', 'thermalcx'. + | is the plasma species (Element/Isotope). + | is the charge of the plasma species. + | is the tuple containing (initial level, final level). + | is the PEC dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with PEC in photon.m^3.s^-1. + + :param repository_path: Path to the atomic data repository. """ valid_classes = [ @@ -184,14 +211,64 @@ def update_pec_rates(rates, repository_path=None): def get_pec_excitation_rate(element, charge, transition, repository_path=None): + """ + Reads the excitation PEC from the repository for the given + element, charge and transition. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return rate: Excitation PEC dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with excitation PEC in photon.m^3.s^-1. + + """ + return _get_pec_rate('excitation', element, charge, transition, repository_path) def get_pec_recombination_rate(element, charge, transition, repository_path=None): + """ + Reads the recombination PEC from the repository for the given + element, charge and transition. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return rate: Recombination PEC dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with recombination PEC in photon.m^3.s^-1. + + """ + return _get_pec_rate('recombination', element, charge, transition, repository_path) def get_pec_thermalcx_rate(element, charge, transition, repository_path=None): + """ + Reads the thermal charge exchange PEC from the repository for the given + element, charge and transition. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return rate: Thermal CX PEC dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with thermal CX PEC in photon.m^3.s^-1. + + """ return _get_pec_rate('thermalcx', element, charge, transition, repository_path) diff --git a/cherab/openadas/repository/radiated_power.py b/cherab/openadas/repository/radiated_power.py index a6a4b390..7b908cfc 100644 --- a/cherab/openadas/repository/radiated_power.py +++ b/cherab/openadas/repository/radiated_power.py @@ -1,7 +1,7 @@ -# 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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -29,13 +29,21 @@ def add_line_power_rate(species, charge, rate, repository_path=None): """ - Adds a single LineRadiationPower rate to the repository. + Adds a single line radiated power rate to the repository. If adding multiple rates, consider using the update_line_power_rates() function instead. The update function avoids repeatedly opening and closing the rate files. - :param repository_path: + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Line radiated power rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with line radiated power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ update_line_power_rates({ @@ -47,13 +55,22 @@ def add_line_power_rate(species, charge, rate, repository_path=None): def update_line_power_rates(rates, repository_path=None): """ - Update the repository of LineRadiationPower rates. - - LineRadiationPower rate file structure - + Update the files for the line radiated power rates: /radiated_power/line/.json + in the atomic data repository. File contains multiple rates, indexed by the ion's charge state. + + :param rates: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the line radiated rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with line radiated power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -71,13 +88,21 @@ def update_line_power_rates(rates, repository_path=None): def add_continuum_power_rate(species, charge, rate, repository_path=None): """ - Adds a single ContinuumPower rate to the repository. + Adds a single continuum power rate to the repository. If adding multiple rates, consider using the update_continuum_power_rates() function instead. The update function avoids repeatedly opening and closing the rate files. - :param repository_path: + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Continuum power rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with continuum power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ update_line_power_rates({ @@ -89,13 +114,22 @@ def add_continuum_power_rate(species, charge, rate, repository_path=None): def update_continuum_power_rates(rates, repository_path=None): """ - Update the repository of ContinuumPower rates. - - ContinuumPower rate file structure - + Update the files for the continuum power rates: /radiated_power/continuum/.json + in the atomic data repository. File contains multiple rates, indexed by ion's charge state. + + :param rates: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the continuum power rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with continuum power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -113,13 +147,22 @@ def update_continuum_power_rates(rates, repository_path=None): def add_cx_power_rate(species, charge, rate, repository_path=None): """ - Adds a single CXRadiationPower rate to the repository. + Adds a single CX radiation power rate to the repository + (charge exchage with neutral hydrogen). If adding multiple rates, consider using the update_cx_power_rates() function instead. The update function avoids repeatedly opening and closing the rate files. - :param repository_path: + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: CX power rate dictionary containing the following entries: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with CX power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ update_line_power_rates({ @@ -131,13 +174,23 @@ def add_cx_power_rate(species, charge, rate, repository_path=None): def update_cx_power_rates(rates, repository_path=None): """ - Update the repository of CXRadiationPower rates. - - CXRadiationPower rate file structure - + Update the files for the CX radiation power rates + (charge exchage with neutral hydrogen): /radiated_power/cx/.json + in the atomic data repository. File contains multiple rates, indexed by ion's charge state. + + :param rates: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the thermal CX power rate dictionary containing the following entries: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'rate': array-like of size (N, M) with thermal CX power rate in W.m^3. + + :param repository_path: Path to the atomic data repository. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -199,6 +252,21 @@ def _update_and_write_adf11(species, rate_data, path): def get_line_radiated_power_rate(element, charge, repository_path=None): + """ + Reads the line radiated power rate for the given species and charge + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param repository_path: Path to the atomic data repository. + + :return rate: Line radiated power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with line radiated power rate in W.m^3. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -220,6 +288,21 @@ def get_line_radiated_power_rate(element, charge, repository_path=None): def get_continuum_radiated_power_rate(element, charge, repository_path=None): + """ + Reads the continuum power rate for the given species and charge + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param repository_path: Path to the atomic data repository. + + :return rate: Continuum power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with continuum power rate in W.m^3. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -241,6 +324,21 @@ def get_continuum_radiated_power_rate(element, charge, repository_path=None): def get_cx_radiated_power_rate(element, charge, repository_path=None): + """ + Reads the CX radiation power rate for the given species and charge + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param repository_path: Path to the atomic data repository. + + :return rate: CX radiation power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with CX radiation power rate in W.m^3. + + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH diff --git a/cherab/openadas/repository/wavelength.py b/cherab/openadas/repository/wavelength.py index 5981e9e6..83758f51 100644 --- a/cherab/openadas/repository/wavelength.py +++ b/cherab/openadas/repository/wavelength.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-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -35,11 +35,11 @@ def add_wavelength(element, charge, transition, wavelength, repository_path=None function instead. The update function avoid repeatedly opening and closing the rate files. - :param element: - :param charge: - :param transition: - :param wavelength: - :param repository_path: + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param wavelength: Transition's wavelength in nm. + :param repository_path: Path to the atomic data repository. """ update_wavelengths({ @@ -52,6 +52,22 @@ def add_wavelength(element, charge, transition, wavelength, repository_path=None def update_wavelengths(wavelengths, repository_path=None): + """ + Updates the wavelength files `/wavelength//.json` + in atomic data repository. + + File contains multiple rates, indexed by the transitions. + + :param wavelengths: Dictionary in the form: + + | { : { : { : } } }, where + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the tuple containing (initial level, final level), + | is the transition's wavelength in nm. + + :param repository_path: Path to the atomic data repository. + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH @@ -90,6 +106,16 @@ def update_wavelengths(wavelengths, repository_path=None): def get_wavelength(element, charge, transition, repository_path=None): + """ + Reads the wavelength for the given species, charge and transition from the repository. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param repository_path: Path to the atomic data repository. + + :return wavelength: Wavelength in nm. + """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH path = os.path.join(repository_path, 'wavelength/{}/{}.json'.format(element.symbol.lower(), charge)) diff --git a/docs/source/atomic/atomic_data.rst b/docs/source/atomic/atomic_data.rst index 650d89b4..802943af 100644 --- a/docs/source/atomic/atomic_data.rst +++ b/docs/source/atomic/atomic_data.rst @@ -7,3 +7,5 @@ Atomic Data emission_lines rate_coefficients gaunt_factors + repository + openadas \ No newline at end of file diff --git a/docs/source/atomic/openadas.rst b/docs/source/atomic/openadas.rst new file mode 100644 index 00000000..3dde4242 --- /dev/null +++ b/docs/source/atomic/openadas.rst @@ -0,0 +1,29 @@ +Open-ADAS +--------- + +Although a typical Open-ADAS data set is installed to the local atomic data repository +using the `populate()` function, additional atomic data can be installed manually. + +The following functions allow to parse the Open-ADAS files and install the rates of the atomic processes +to the local atomic data repository. + +Parse +^^^^^ + +.. autofunction:: cherab.openadas.parse.adf11.parse_adf11 + +.. autofunction:: cherab.openadas.parse.adf12.parse_adf12 + +.. autofunction:: cherab.openadas.parse.adf15.parse_adf15 + +.. autofunction:: cherab.openadas.parse.adf21.parse_adf21 + +.. autofunction:: cherab.openadas.parse.adf22.parse_adf22bmp + +.. autofunction:: cherab.openadas.parse.adf22.parse_adf22bme + +Install +^^^^^^^ + +.. automodule:: cherab.openadas.install + :members: diff --git a/docs/source/atomic/repository.rst b/docs/source/atomic/repository.rst new file mode 100644 index 00000000..3a93085e --- /dev/null +++ b/docs/source/atomic/repository.rst @@ -0,0 +1,83 @@ + +Atomic data repository +---------------------- + +The following functions allow to manipulate the local atomic data repository: +add the rates of the atomic processes, update existing ones or get the data +already present in the repository. + +The default repository is created at `~/.cherab/openadas/repository`. +Cherab supports multiple atomic data repositories. The user can configure different +repositories by setting the `repository_path` parameter. +The data in these repositories can be accessed through the `OpenADAS` atomic data provider +by specifying the `data_path` parameter. + +To create the new atomic data repository at the default location and populate it with a typical +set of rates and wavelengths from Open-ADAS, do: + +.. code-block:: pycon + + >>> from cherab.openadas.repository import populate + >>> populate() + +.. autofunction:: cherab.openadas.repository.create.populate + +Wavelength +^^^^^^^^^^ + +.. automodule:: cherab.openadas.repository.wavelength + :members: + +Ionisation +^^^^^^^^^^ + +.. autofunction:: cherab.openadas.repository.atomic.add_ionisation_rate + +.. autofunction:: cherab.openadas.repository.atomic.get_ionisation_rate + +.. autofunction:: cherab.openadas.repository.atomic.update_ionisation_rates + +Recombination +^^^^^^^^^^^^^ + +.. autofunction:: cherab.openadas.repository.atomic.add_recombination_rate + +.. autofunction:: cherab.openadas.repository.atomic.get_recombination_rate + +.. autofunction:: cherab.openadas.repository.atomic.update_recombination_rates + +Thermal Charge Exchange +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: cherab.openadas.repository.atomic.add_thermal_cx_rate + +.. autofunction:: cherab.openadas.repository.atomic.get_thermal_cx_rate + +.. autofunction:: cherab.openadas.repository.atomic.update_thermal_cx_rates + +Photon Emissivity Coefficients +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: cherab.openadas.repository.pec + :members: + +Radiated Power +^^^^^^^^^^^^^^ + +.. automodule:: cherab.openadas.repository.radiated_power + :members: + +Beam +^^^^ + +.. automodule:: cherab.openadas.repository.beam.cx + :members: + +.. automodule:: cherab.openadas.repository.beam.emission + :members: + +.. automodule:: cherab.openadas.repository.beam.population + :members: + +.. automodule:: cherab.openadas.repository.beam.stopping + :members: From 9710d18a2956bbc815649d17450c6e8dcc495efe Mon Sep 17 00:00:00 2001 From: vsnever Date: Sat, 27 Jul 2024 21:18:29 +0200 Subject: [PATCH 30/32] Improve documentation for openadas atomic data interpolators. --- cherab/openadas/rates/atomic.pyx | 78 +++++++++++++++----- cherab/openadas/rates/beam.pyx | 81 +++++++++++++++++---- cherab/openadas/rates/cx.pyx | 50 ++++++++++--- cherab/openadas/rates/pec.pyx | 58 +++++++++++---- cherab/openadas/rates/radiated_power.pyx | 69 ++++++++++++++++-- docs/source/atomic/atomic_data.rst | 1 + docs/source/atomic/data_interpolators.rst | 89 +++++++++++++++++++++++ docs/source/atomic/rate_coefficients.rst | 31 +++++++- 8 files changed, 393 insertions(+), 64 deletions(-) create mode 100644 docs/source/atomic/data_interpolators.rst diff --git a/cherab/openadas/rates/atomic.pyx b/cherab/openadas/rates/atomic.pyx index 04500124..903baf9f 100644 --- a/cherab/openadas/rates/atomic.pyx +++ b/cherab/openadas/rates/atomic.pyx @@ -1,7 +1,7 @@ -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -24,12 +24,26 @@ from raysect.core.math.function.float cimport Interpolator2DArray cdef class IonisationRate(CoreIonisationRate): + """ + Ionisation rate. + + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param dict data: Ionisation rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with ionisation rate in m^3.s^-1. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, dict data, extrapolate=False): - """ - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ self.raw_data = data @@ -62,7 +76,7 @@ cdef class IonisationRate(CoreIonisationRate): cdef class NullIonisationRate(CoreIonisationRate): """ - A PEC rate that always returns zero. + An ionisation rate that always returns zero. Needed for use cases where the required atomic data is missing. """ @@ -71,12 +85,26 @@ cdef class NullIonisationRate(CoreIonisationRate): cdef class RecombinationRate(CoreRecombinationRate): + """ + Recombination rate. + + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param dict data: Recombination rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with recombination rate in m^3.s^-1. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, dict data, extrapolate=False): - """ - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ self.raw_data = data @@ -109,7 +137,7 @@ cdef class RecombinationRate(CoreRecombinationRate): cdef class NullRecombinationRate(CoreRecombinationRate): """ - A PEC rate that always returns zero. + A recombination rate that always returns zero. Needed for use cases where the required atomic data is missing. """ @@ -118,12 +146,26 @@ cdef class NullRecombinationRate(CoreRecombinationRate): cdef class ThermalCXRate(CoreThermalCXRate): + """ + Thermal charge exchange rate. + + Data is interpolated with cubic spline in log-log space. + Linear extrapolation is used if extrapolate is True. + + :param dict data: CX rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with thermal CX rate in m^3.s^-1. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, dict data, extrapolate=False): - """ - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ self.raw_data = data @@ -155,7 +197,7 @@ cdef class ThermalCXRate(CoreThermalCXRate): cdef class NullThermalCXRate(CoreThermalCXRate): """ - A PEC rate that always returns zero. + A thermal CX rate that always returns zero. Needed for use cases where the required atomic data is missing. """ diff --git a/cherab/openadas/rates/beam.pyx b/cherab/openadas/rates/beam.pyx index f40af8dd..7059e243 100644 --- a/cherab/openadas/rates/beam.pyx +++ b/cherab/openadas/rates/beam.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -31,8 +31,26 @@ cdef class BeamStoppingRate(CoreBeamStoppingRate): """ The beam stopping coefficient interpolation class. - :param data: A dictionary holding the beam coefficient data. - :param extrapolate: Set to True to enable extrapolation, False to disable (default). + Data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + if extrapolate is True. + + :param dict data: A beam stopping rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n': 1D array of size (M) with target electron density in m^-3, + | 't': 1D array of size (K) with target electron temperature in eV, + | 'sen': 2D array of size (N, M) with beam stopping rate energy component in m^3.s^-1. + | 'st': 1D array of size (K) with beam stopping rate temperature component in m^3.s^-1. + | 'sref': reference beam stopping rate in m^3.s^-1. + | The total beam stopping rate: s = sen * st / sref. + + :param bint extrapolate: Set to True to enable extrapolation, False to disable (default). + + :ivar tuple beam_energy_range: Interaction energy interpolation range. + :ivar tuple density_range: Target electron density interpolation range. + :ivar tuple temperature_range: Target electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. """ @cython.cdivision(True) @@ -93,7 +111,7 @@ cdef class BeamStoppingRate(CoreBeamStoppingRate): cdef class NullBeamStoppingRate(CoreBeamStoppingRate): """ - A beam rate that always returns zero. + A beam stopping rate that always returns zero. Needed for use cases where the required atomic data is missing. """ @@ -105,8 +123,26 @@ cdef class BeamPopulationRate(CoreBeamPopulationRate): """ The beam population coefficient interpolation class. - :param data: A dictionary holding the beam coefficient data. - :param extrapolate: Set to True to enable extrapolation, False to disable (default). + Data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + if extrapolate is True. + + :param dict data: Beam population rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n': 1D array of size (M) with target electron density in m^-3, + | 't': 1D array of size (K) with target electron temperature in eV, + | 'sen': 2D array of size (N, M) with dimensionless beam population rate energy component. + | 'st': 1D array of size (K) with dimensionless beam population rate temperature component. + | 'sref': reference dimensionless beam population rate. + | The total beam population rate: s = sen * st / sref. + + :param bint extrapolate: Set to True to enable extrapolation, False to disable (default). + + :ivar tuple beam_energy_range: Interaction energy interpolation range. + :ivar tuple density_range: Target electron density interpolation range. + :ivar tuple temperature_range: Target electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. """ @cython.cdivision(True) @@ -167,7 +203,7 @@ cdef class BeamPopulationRate(CoreBeamPopulationRate): cdef class NullBeamPopulationRate(CoreBeamPopulationRate): """ - A beam rate that always returns zero. + A beam population rate that always returns zero. Needed for use cases where the required atomic data is missing. """ @@ -179,9 +215,26 @@ cdef class BeamEmissionPEC(CoreBeamEmissionPEC): """ The beam emission coefficient interpolation class. - :param data: A dictionary holding the beam coefficient data. - :param wavelength: The natural wavelength of the emission line associated with the rate data in nm. - :param extrapolate: Set to True to enable extrapolation, False to disable (default). + Data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + if extrapolate is True. + + :param dict data: Beam emission rate dictionary containing the following entries: + + | 'e': 1D array of size (N) with interaction energy in eV/amu, + | 'n' 1D array of size (M) with target electron density in m^-3, + | 't' 1D array of size (K) with target electron temperature in eV, + | 'sen' 2D array of size (N, M) with beam emission rate energy component in photon.m^3.s^-1. + | 'st' 1D array of size (K) with beam emission rate temperature component in photon.m^3.s^-1. + | 'sref': reference beam emission rate in photon.m^3.s^-1. + + :param double wavelength: The natural wavelength of the emission line associated with the rate data in nm. + :param bint extrapolate: Set to True to enable extrapolation, False to disable (default). + + :ivar tuple beam_energy_range: Interaction energy interpolation range. + :ivar tuple density_range: Target electron density interpolation range. + :ivar tuple temperature_range: Target electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. """ @cython.cdivision(True) @@ -194,7 +247,7 @@ cdef class BeamEmissionPEC(CoreBeamEmissionPEC): e = data["e"] # eV/amu n = data["n"] # m^-3 t = data["t"] # eV - sen = np.log10(PhotonToJ.to(data["sen"], wavelength)) # W.m^3/s + sen = np.log10(PhotonToJ.to(data["sen"], wavelength)) # W.m^3 st = np.log10(data["st"] / data["sref"]) # dimensionless # store limits of data @@ -243,7 +296,7 @@ cdef class BeamEmissionPEC(CoreBeamEmissionPEC): cdef class NullBeamEmissionPEC(CoreBeamEmissionPEC): """ - A beam rate that always returns zero. + A beam emission PEC that always returns zero. Needed for use cases where the required atomic data is missing. """ diff --git a/cherab/openadas/rates/cx.pyx b/cherab/openadas/rates/cx.pyx index cb827a8b..ef6ff535 100644 --- a/cherab/openadas/rates/cx.pyx +++ b/cherab/openadas/rates/cx.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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,12 +26,42 @@ from raysect.core.math.function.float cimport Interpolator1DArray, Constant1D cdef class BeamCXPEC(CoreBeamCXPEC): """ - The effective cx rate interpolation class. - - :param donor_metastable: The metastable state of the donor species for which the rate data applies. - :param wavelength: The natural wavelength of the emission line associated with the rate data in nm. - :param data: A dictionary holding the rate data. - :param extrapolate: Set to True to enable extrapolation, False to disable (default). + Effective charge exchange photon emission coefficient. + + The data for "qeb" is interpolated with a cubic spline in log-log space. + The data for "qti", "qni", "qz" and "qb" are interpolated with a cubic spline + in linear space. + + Quadratic extrapolation is used for "qeb" and nearest neighbour extrapolation is used for + "qti", "qni", "qz" and "qb" if extrapolate is True. + + :param int donor_metastable: The metastable state of the donor species for which the rate data applies. + :param double wavelength: The natural wavelength of the emission line associated with the rate data in nm. + :param data: Beam CX PEC dictionary containing the following entries: + + | 'eb': 1D array of size (N) with beam energy in eV/amu, + | 'ti': 1D array of size (M) with receiver ion temperature in eV, + | 'ni': 1D array of size (K) with receiver ion density in m^-3, + | 'z': 1D array of size (L) with receiver Z-effective, + | 'b': 1D array of size (J) with magnetic field strength in Tesla, + | 'qeb': 1D array of size (N) with CX PEC energy component in photon.m^3.s-1, + | 'qti': 1D array of size (M) with CX PEC temperature component in photon.m^3.s-1, + | 'qni': 1D array of size (K) with CX PEC density component in photon.m^3.s-1, + | 'qz': 1D array of size (L) with CX PEC Zeff component in photon.m^3.s-1, + | 'qb': 1D array of size (J) with CX PEC B-field component in photon.m^3.s-1, + | 'qref': reference CX PEC in photon.m^3.s-1. + | The total beam CX PEC: q = qeb * qti * qni * qz * qb / qref^4. + + :param bint extrapolate: Set to True to enable extrapolation, False to disable (default). + + :ivar tuple beam_energy_range: Interaction energy interpolation range. + :ivar tuple density_range: Receiver ion density interpolation range. + :ivar tuple temperature_range: Receiver ion temperature interpolation range. + :ivar tuple zeff_range: Z-effective interpolation range. + :ivar tuple b_field_range: Magnetic field strength interpolation range. + :ivar int donor_metastable: The metastable state of the donor species. + :ivar double wavelength: The natural wavelength of the emission line in nm. + :ivar dict raw_data: Dictionary containing the raw data. """ @cython.cdivision(True) @@ -79,7 +109,7 @@ cdef class BeamCXPEC(CoreBeamCXPEC): :param energy: Interaction energy in eV/amu. :param temperature: Receiver ion temperature in eV. - :param density: Receiver ion density in m^-3 + :param density: Plasma total ion density in m^-3 :param z_effective: Plasma Z-effective. :param b_field: Magnetic field magnitude in Tesla. :return: The effective cx rate in W.m^3 diff --git a/cherab/openadas/rates/pec.pyx b/cherab/openadas/rates/pec.pyx index eafc6c64..fb6f392c 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/openadas/rates/pec.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -25,13 +25,27 @@ from cherab.core.utility.conversion import PhotonToJ cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): + """ + Electron impact excitation photon emission coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param double wavelength: Resting wavelength of corresponding emission line in nm. + :param dict data: Excitation PEC dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with excitation PEC in photon.m^3.s^-1. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, double wavelength, dict data, extrapolate=False): - """ - :param wavelength: Resting wavelength of corresponding emission line in nm. - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ self.wavelength = wavelength self.raw_data = data @@ -68,7 +82,7 @@ cdef class ImpactExcitationPEC(CoreImpactExcitationPEC): cdef class NullImpactExcitationPEC(CoreImpactExcitationPEC): """ - A PEC rate that always returns zero. + A electron impact excitation PEC rate that always returns zero. Needed for use cases where the required atomic data is missing. """ @@ -77,13 +91,27 @@ cdef class NullImpactExcitationPEC(CoreImpactExcitationPEC): cdef class RecombinationPEC(CoreRecombinationPEC): + """ + Recombination photon emission coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param double wavelength: Resting wavelength of corresponding emission line in nm. + :param dict data: Rcombination PEC dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with recombination PEC in photon.m^3.s^-1. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, double wavelength, dict data, extrapolate=False): - """ - :param wavelength: Resting wavelength of corresponding emission line in nm. - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ self.wavelength = wavelength self.raw_data = data @@ -120,7 +148,7 @@ cdef class RecombinationPEC(CoreRecombinationPEC): cdef class NullRecombinationPEC(CoreRecombinationPEC): """ - A PEC rate that always returns zero. + A recombination PEC rate that always returns zero. Needed for use cases where the required atomic data is missing. """ diff --git a/cherab/openadas/rates/radiated_power.pyx b/cherab/openadas/rates/radiated_power.pyx index 570ced92..32371c8f 100644 --- a/cherab/openadas/rates/radiated_power.pyx +++ b/cherab/openadas/rates/radiated_power.pyx @@ -1,7 +1,7 @@ -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2024 Euratom +# Copyright 2016-2024 United Kingdom Atomic Energy Authority +# Copyright 2016-2024 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"); @@ -24,7 +24,26 @@ from raysect.core.math.function.float cimport Interpolator2DArray cdef class LineRadiationPower(CoreLineRadiationPower): - """Base class for radiated powers.""" + """ + Line radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param Element species: Element object defining the ion type. + :param int ionisation: Charge state of the ion. + :param dict data: Line radiated power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with radiated power rate in W.m^3. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, species, ionisation, dict data, extrapolate=False): @@ -70,7 +89,26 @@ cdef class NullLineRadiationPower(CoreLineRadiationPower): cdef class ContinuumPower(CoreContinuumPower): - """Base class for radiated powers.""" + """ + Recombination continuum radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used if extrapolate is True. + + :param Element species: Element object defining the ion type. + :param int ionisation: Charge state of the ion. + :param dict data: Recombination continuum radiated power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with radiated power rate in W.m^3. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, species, ionisation, dict data, extrapolate=False): @@ -116,7 +154,26 @@ cdef class NullContinuumPower(CoreContinuumPower): cdef class CXRadiationPower(CoreCXRadiationPower): - """Base class for radiated powers.""" + """ + Charge exchange radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Linear extrapolation is used if extrapolate is True. + + :param Element species: Element object defining the ion type. + :param int ionisation: Charge state of the ion. + :param dict data: CX radiated power rate dictionary containing the following entries: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'rate': 2D array of size (N, M) with radiated power rate in W.m^3. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple density_range: Electron density interpolation range. + :ivar tuple temperature_range: Electron temperature interpolation range. + :ivar dict raw_data: Dictionary containing the raw data. + """ def __init__(self, species, ionisation, dict data, extrapolate=False): diff --git a/docs/source/atomic/atomic_data.rst b/docs/source/atomic/atomic_data.rst index 650d89b4..edd24b0d 100644 --- a/docs/source/atomic/atomic_data.rst +++ b/docs/source/atomic/atomic_data.rst @@ -7,3 +7,4 @@ Atomic Data emission_lines rate_coefficients gaunt_factors + data_interpolators diff --git a/docs/source/atomic/data_interpolators.rst b/docs/source/atomic/data_interpolators.rst new file mode 100644 index 00000000..56fa0327 --- /dev/null +++ b/docs/source/atomic/data_interpolators.rst @@ -0,0 +1,89 @@ +Atomic data interpolators +========================= + +The following classes interpolate atomic data defined on a numerical grid. + + +Atomic Processes +^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.openadas.rates.atomic.IonisationRate + :members: + +.. autoclass:: cherab.openadas.rates.atomic.NullIonisationRate + :members: + +.. autoclass:: cherab.openadas.rates.atomic.RecombinationRate + :members: + +.. autoclass:: cherab.openadas.rates.atomic.NullRecombinationRate + :members: + +.. autoclass:: cherab.openadas.rates.atomic.ThermalCXRate + :members: + +.. autoclass:: cherab.openadas.rates.atomic.NullThermalCXRate + :members: + +Photon Emissivity Coefficients +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.openadas.rates.pec.ImpactExcitationPEC + :members: + +.. autoclass:: cherab.openadas.rates.pec.NullImpactExcitationPEC + :members: + +.. autoclass:: cherab.openadas.rates.pec.RecombinationPEC + :members: + +.. autoclass:: cherab.openadas.rates.pec.NullRecombinationPEC + :members: + +Beam-Plasma Interaction Rates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.openadas.rates.cx.BeamCXPEC + :members: + +.. autoclass:: cherab.openadas.rates.cx.NullBeamCXPEC + :members: + +.. autoclass:: cherab.openadas.rates.beam.BeamStoppingRate + :members: + +.. autoclass:: cherab.openadas.rates.beam.NullBeamStoppingRate + :members: + +.. autoclass:: cherab.openadas.rates.beam.BeamPopulationRate + :members: + +.. autoclass:: cherab.openadas.rates.beam.NullBeamPopulationRate + :members: + +.. autoclass:: cherab.openadas.rates.beam.BeamEmissionPEC + :members: + +.. autoclass:: cherab.openadas.rates.beam.NullBeamEmissionPEC + :members: + +Radiated Power +^^^^^^^^^^^^^^ + +.. autoclass:: cherab.openadas.rates.radiated_power.LineRadiationPower + :members: + +.. autoclass:: cherab.openadas.rates.radiated_power.NullLineRadiationPower + :members: + +.. autoclass:: cherab.openadas.rates.radiated_power.ContinuumPower + :members: + +.. autoclass:: cherab.openadas.rates.radiated_power.NullContinuumPower + :members: + +.. autoclass:: cherab.openadas.rates.radiated_power.CXRadiationPower + :members: + +.. autoclass:: cherab.openadas.rates.radiated_power.NullCXRadiationPower + :members: diff --git a/docs/source/atomic/rate_coefficients.rst b/docs/source/atomic/rate_coefficients.rst index b6547820..82116bf3 100644 --- a/docs/source/atomic/rate_coefficients.rst +++ b/docs/source/atomic/rate_coefficients.rst @@ -15,6 +15,35 @@ provide theoretical equations. Cherab emission models only need to know how to c them after they have been instantiated. +Atomic Processes +^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.core.atomic.rates.IonisationRate + +.. autoclass:: cherab.core.atomic.rates.RecombinationRate + +.. autoclass:: cherab.core.atomic.rates.ThermalCXRate + +The `IonisationRate`, `RecombinationRate` and `ThermalCXRate` classes all share +the same call signatures. + +.. function:: __call__(density, temperature) + + Returns an effective rate coefficient at the specified plasma conditions. + + This function just wraps the cython evaluate() method. + +.. function:: evaluate(density, temperature) + + an effective recombination rate coefficient at the specified plasma conditions. + + This function needs to be implemented by the atomic data provider. + + :param float density: Electron density in m^-3 + :param float temperature: Electron temperature in eV. + :return: The effective ionisation rate in [m^3.s^-1]. + + Photon Emissivity Coefficients ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,8 +68,8 @@ the same call signatures. This function needs to be implemented by the atomic data provider. - :param float temperature: Receiver ion temperature in eV. :param float density: Receiver ion density in m^-3 + :param float temperature: Receiver ion temperature in eV. :return: The effective PEC rate [Wm^3]. Some example code for requesting PEC objects and sampling them with the __call__() From ccdd28072a2f4463ca3809c75319f318250b7137 Mon Sep 17 00:00:00 2001 From: vsnever Date: Tue, 30 Jul 2024 23:58:33 +0200 Subject: [PATCH 31/32] Update CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c70837fb..a71951eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ New: * **Beam dispersion calculation has changed from sigma(z) = sigma + z * tan(alpha) to sigma(z) = sqrt(sigma^2 + (z * tan(alpha))^2) for consistancy with the Gaussian beam model. Attention!!! The results of BES and CX spectroscopy are affected by this change. (#414)** * Improved beam direction calculation to allow for natural broadening of the BES line shape due to beam divergence. (#414) * Add kwargs to invert_regularised_nnls to pass them to scipy.optimize.nnls. (#438) +* All interpolated atomic rates now return 0 if plasma parameters <= 0, which matches the behaviour of emission models. (#450) Bug fixes: * Fix deprecated transforms being cached in LaserMaterial after laser.transform update (#420) From b173acc02df33cd199dcdb7ac00d2a09c91fc1a7 Mon Sep 17 00:00:00 2001 From: vsnever Date: Wed, 31 Jul 2024 00:03:27 +0200 Subject: [PATCH 32/32] Add an empty line after code-block directive. --- cherab/core/model/beam/charge_exchange.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/cherab/core/model/beam/charge_exchange.pyx b/cherab/core/model/beam/charge_exchange.pyx index 83fe237f..9eb562a0 100644 --- a/cherab/core/model/beam/charge_exchange.pyx +++ b/cherab/core/model/beam/charge_exchange.pyx @@ -61,6 +61,7 @@ cdef class BeamCXLine(BeamModel): :ivar Line line: The emission line object. .. code-block:: pycon + >>> from cherab.core.model import BeamCXLine >>> from cherab.core.atomic import carbon >>> from cherab.core.model import ParametrisedZeemanTriplet