diff --git a/CHANGELOG.md b/CHANGELOG.md index 13202802..d6b0b964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,31 @@ Project Changelog Release 1.5.0 (TBD) ------------------- +API changes: +* Interpolated rates and local atomic repository functions are separated from OpenADAS parsers and installers and moved to cherab.atomic. (#364) +* Default interface to atomic data repository is changed to cherab.atomic.AtomicData, as it includes the data beyond the frame of Open-ADAS. (#364) +* The interface cherab.openadas.OpenADAS will continue to work as a wrapper for cherab.atomic.AtomicData for backward compatibility. (#364) +* Default path to atomic data repository changed to `~/.cherab/atomicdata/default_repository`. Call populate() from cherab.atomic.repository after updating to 1.5.0 (#364) +* InterpolatedFreeFreeGauntFactor and MaxwellianFreeFreeGauntFactor are replaced with a general interpolator FreeFreeGauntFactor from cherab.atomic.gaunt. (#364) +* ZeemanStructure class is splitted to base (abstract) and interpolator classes. The latter is moved to cherab.atomic.zeeman and its initialiser interface is chaged to match the other atomic data interpolators. (#364) + New: * Support Raysect 0.8 * Add custom line shape support to BeamCXLine model. (#394) +* Add functions to read/write free-free Gaunt factor in the atomic repository. (#364) +* Add functions to read/write Zeeman structure in the atomic repository. (#364) * Add PeriodicTransformXD and VectorPeriodicTransformXD functions to support the data simulated with periodic boundary conditions. (#387) * Add CylindricalTransform and VectorCylindricalTransform to transform functions from cylindrical to Cartesian coordinates. (#387) * Add numerical integration of Bremsstrahlung spectrum over a spectral bin. (#395) * 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) +* Get the path to the default atomic data repository from the CHERAB_ATOMIC_DATA environment variable. (#416) Bug fixes: * Fix deprecated transforms being cached in LaserMaterial after laser.transform update (#420) + Release 1.4.0 (3 Feb 2023) ------------------- diff --git a/cherab/atomic/__init__.py b/cherab/atomic/__init__.py new file mode 100644 index 00000000..4f0d2a42 --- /dev/null +++ b/cherab/atomic/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from . import repository +from .atomicdata import AtomicData diff --git a/cherab/atomic/atomicdata.py b/cherab/atomic/atomicdata.py new file mode 100644 index 00000000..18d81fc3 --- /dev/null +++ b/cherab/atomic/atomicdata.py @@ -0,0 +1,753 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core import AtomicData as _BaseAtomicData +from cherab.core.atomic.elements import Isotope +from cherab.atomic.repository import DEFAULT_REPOSITORY_PATH + +from .rates import * +from .gaunt import FreeFreeGauntFactor +from .zeeman import ZeemanStructure +from cherab.atomic import repository + + +class AtomicData(_BaseAtomicData): + """ + Atomic data source. + + :param str data_path: Path to local atomic data repository. The path is determined by the + `CHERAB_ATOMIC_DATA` environment variable and defaults to + `~/.cherab/atomicdata/default_repository` if the variable is not set. + :param bool permit_extrapolation: If true, informs interpolation objects to allow extrapolation + beyond the limits of the tabulated data. Default is False. + :param bool missing_rates_return_null: If true, allows Null rate objects to be returned when + the requested atomic data is missing. Default is False. + :param bool wavelength_element_fallback: If true, allows to use the element's wavelength when + the isotope's wavelength is not available. + Default is False. + :param bool rate_element_fallback: If true, allows to use the element's rate when + the isotope's rate is not available. + Default is True. + """ + + def __init__(self, data_path=None, permit_extrapolation=False, missing_rates_return_null=False, + wavelength_element_fallback=False, rate_element_fallback=True): + + super().__init__() + self._data_path = data_path or DEFAULT_REPOSITORY_PATH + + self._permit_extrapolation = permit_extrapolation + + self._missing_rates_return_null = missing_rates_return_null + + self._wavelength_element_fallback = wavelength_element_fallback + + self._rate_element_fallback = rate_element_fallback + + @property + def data_path(self): + return self._data_path + + def wavelength(self, ion, charge, transition): + """ + Spectral line wavelength for a given transition. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :param transition: Tuple containing (initial level, final level) + :return: Wavelength in nanometers. + """ + + if isinstance(ion, Isotope) and self._wavelength_element_fallback: + try: + return repository.get_wavelength(ion, charge, transition, repository_path=self._data_path) + except RuntimeError: + return repository.get_wavelength(ion.element, charge, transition, repository_path=self._data_path) + + return repository.get_wavelength(ion, charge, transition, repository_path=self._data_path) + + def ionisation_rate(self, ion, charge): + """ + Electron impact ionisation rate for a given species. + + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Ionisation rate in m^3/s as a function of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read ionisation rate from json file in the repository + data = repository.get_ionisation_rate(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_ionisation_rate(ion.element, charge, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullIonisationRate() + raise + elif self._missing_rates_return_null: + return NullIonisationRate() + else: + raise + + return IonisationRate(data, extrapolate=self._permit_extrapolation) + + def recombination_rate(self, ion, charge): + """ + Recombination rate for a given species. + + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Recombination rate in m^3/s as a function of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read recombination rate from json file in the repository + data = repository.get_recombination_rate(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_recombination_rate(ion.element, charge, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullRecombinationRate() + raise + elif self._missing_rates_return_null: + return NullRecombinationRate() + else: + raise + + return RecombinationRate(data, extrapolate=self._permit_extrapolation) + + def thermal_cx_rate(self, donor_element, donor_charge, receiver_element, receiver_charge): + """ + Thermal charge exchange effective rate coefficient for a given donor and receiver species. + + Data is interpolated with cubic spline in log-log space. + Linear 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. + :return: Thermal charge exchange rate in m^3/s as a function of electron density and + temperature. + """ + + # try to read the rates for the isotopes + try: + # read thermal CX rate from json file in the repository + data = repository.get_thermal_cx_rate(donor_element, donor_charge, + receiver_element, receiver_charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the receiver + if isinstance(receiver_element, Isotope) and self._rate_element_fallback: + try: + data = repository.get_thermal_cx_rate(donor_element, donor_charge, + receiver_element.element, receiver_charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the donor + if isinstance(donor_element, Isotope): + try: + data = repository.get_thermal_cx_rate(donor_element.element, donor_charge, + receiver_element, receiver_charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the elements for both + try: + data = repository.get_thermal_cx_rate(donor_element.element, donor_charge, + receiver_element.element, receiver_charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullThermalCXRate() + raise + elif self._missing_rates_return_null: + return NullThermalCXRate() + else: + raise + # if the receiver is not an isotope, fallback to the element for the donor + elif isinstance(donor_element, Isotope) and self._rate_element_fallback: + try: + data = repository.get_thermal_cx_rate(donor_element.element, donor_charge, + receiver_element, receiver_charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullThermalCXRate() + raise + elif self._missing_rates_return_null: + return NullThermalCXRate() + else: + raise + + return ThermalCXRate(data, extrapolate=self._permit_extrapolation) + + def beam_cx_pec(self, donor_ion, receiver_ion, receiver_charge, transition): + """ + Effective charge exchange photon emission coefficient for a given donor (beam) + and receiver (plasma) species and a given transition. + + 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" when permit_extrapolation is True. + + + :param donor_ion: Element object defining the donor ion type. + :param receiver_ion: 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 species. + :return: Charge exchange photon emission coefficient in W.m^3 as a function of + interaction energy, receiver ion temperature, receiver ion density, + plasma Z-effective, magnetic field magnitude. + """ + + # try to read the rates for the isotopes + try: + # read beam CX PEC from json file in the repository + data = repository.get_beam_cx_rates(donor_ion, receiver_ion, + receiver_charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the receiver + if isinstance(receiver_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_cx_rates(donor_ion, receiver_ion.element, + receiver_charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the donor + if isinstance(donor_ion, Isotope): + try: + data = repository.get_beam_cx_rates(donor_ion.element, receiver_ion, + receiver_charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the elements for both + try: + data = repository.get_beam_cx_rates(donor_ion.element, receiver_ion.element, + receiver_charge, transition, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return [NullBeamCXPEC()] + raise + elif self._missing_rates_return_null: + return [NullBeamCXPEC()] + else: + raise + # if the receiver is not an isotope, fallback to the element for the donor + elif isinstance(donor_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_cx_rates(donor_ion.element, receiver_ion, + receiver_charge, transition, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return [NullBeamCXPEC()] + raise + elif self._missing_rates_return_null: + return [NullBeamCXPEC()] + else: + 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_ion, receiver_charge - 1, transition) + + # load and interpolate the relevant transition data from each file + rates = [] + for donor_metastable, rate_data in data: + rates.append(BeamCXPEC(donor_metastable, wavelength, rate_data, extrapolate=self._permit_extrapolation)) + return rates + + def beam_stopping_rate(self, beam_ion, plasma_ion, charge): + """ + Beam stopping coefficient for a given beam and target species. + + The data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + when permit_extrapolation is True. + + :param beam_ion: Element object defining the beam ion type. + :param plasma_ion: Element object defining the target ion type. + :param charge: Charge state of the target ion. + :return: The beam stopping coefficient in m^3.s^-1 as a function of interaction energy, + target equivalent electron density, target temperature. + """ + + # try to read the rates for the isotopes + try: + # read beam stopping rate from json file in the repository + data = repository.get_beam_stopping_rate(beam_ion, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the plasma ion + if isinstance(plasma_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_stopping_rate(beam_ion, plasma_ion.element, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the beam ion + if isinstance(beam_ion, Isotope): + try: + data = repository.get_beam_stopping_rate(beam_ion.element, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the elements for both + try: + data = repository.get_beam_stopping_rate(beam_ion.element, plasma_ion.element, charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamStoppingRate() + raise + elif self._missing_rates_return_null: + return NullBeamStoppingRate() + else: + raise + # if the receiver is not an isotope, fallback to the element for the beam ion + elif isinstance(beam_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_stopping_rate(beam_ion.element, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamStoppingRate() + raise + elif self._missing_rates_return_null: + return NullBeamStoppingRate() + else: + raise + + # load and interpolate data + return BeamStoppingRate(data, extrapolate=self._permit_extrapolation) + + def beam_population_rate(self, beam_ion, metastable, plasma_ion, charge): + """ + Beam population coefficient for a given beam and target species. + + The data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + when permit_extrapolation is True. + + :param beam_ion: Element object defining the beam ion type. + :param metastable: The beam ion metastable number. + :param plasma_ion: Element object defining the target ion type. + :param charge: Charge state of the target ion. + :return: The beam population coefficient in dimensionless units as a function of + interaction energy, target equivalent electron density, target temperature. + """ + + # try to read the rates for the isotopes + try: + # read beam population rate from json file in the repository + data = repository.get_beam_population_rate(beam_ion, metastable, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the plasma ion + if isinstance(plasma_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_population_rate(beam_ion, metastable, plasma_ion.element, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the beam ion + if isinstance(beam_ion, Isotope): + try: + data = repository.get_beam_population_rate(beam_ion.element, metastable, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the elements for both + try: + data = repository.get_beam_population_rate(beam_ion.element, metastable, plasma_ion.element, charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamPopulationRate() + raise + elif self._missing_rates_return_null: + return NullBeamPopulationRate() + else: + raise + # if the receiver is not an isotope, fallback to the element for the beam ion + elif isinstance(beam_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_stopping_rate(beam_ion.element, plasma_ion, charge, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamPopulationRate() + raise + elif self._missing_rates_return_null: + return NullBeamPopulationRate() + else: + raise + + # load and interpolate data + return BeamPopulationRate(data, extrapolate=self._permit_extrapolation) + + def beam_emission_pec(self, beam_ion, plasma_ion, charge, transition): + """ + The beam photon emission coefficient for a given beam and target species + and a given transition. + + The data is interpolated with cubic spline in log-log space. + Linear and quadratic extrapolations are used for "sen" and "st" respectively + when permit_extrapolation is True. + + :param beam_ion: Element object defining the beam ion type. + :param plasma_ion: Element object defining the target ion type. + :param charge: Charge state of the target ion. + :param transition: Tuple containing (initial level, final level) of the beam ion. + :return: The beam photon emission coefficient in W.m^3 as a function of + interaction energy, target equivalent electron density, target temperature. + """ + + # try to read the rates for the isotopes + try: + # read beam PEC from json file in the repository + data = repository.get_beam_emission_rate(beam_ion, plasma_ion, charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the plasma ion + if isinstance(plasma_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_emission_rate(beam_ion, plasma_ion.element, charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the element for the beam ion + if isinstance(beam_ion, Isotope): + try: + data = repository.get_beam_emission_rate(beam_ion.element, plasma_ion, charge, transition, + repository_path=self._data_path) + except RuntimeError: + # if fails, fallback to the elements for both + try: + data = repository.get_beam_emission_rate(beam_ion.element, plasma_ion.element, charge, transition, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamEmissionPEC() + raise + elif self._missing_rates_return_null: + return NullBeamEmissionPEC() + else: + raise + # if the receiver is not an isotope, fallback to the element for the beam ion + elif isinstance(beam_ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_beam_emission_rate(beam_ion.element, plasma_ion, charge, transition, + repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullBeamEmissionPEC() + raise + elif self._missing_rates_return_null: + return NullBeamEmissionPEC() + else: + 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(beam_ion, 0, transition) + + # load and interpolate data + return BeamEmissionPEC(data, wavelength, extrapolate=self._permit_extrapolation) + + def impact_excitation_pec(self, ion, charge, transition): + """ + Electron impact excitation photon emission coefficient for a given species. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :param transition: Tuple containing (initial level, final level). + :return: Impact excitation photon emission coefficient in W.m^3 as a + function of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read excitation PEC from json file in the repository + data = repository.get_pec_excitation_rate(ion, charge, transition, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_pec_excitation_rate(ion.element, charge, transition, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullExcitationPEC() + raise + elif self._missing_rates_return_null: + return NullExcitationPEC() + else: + 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(ion, charge, transition) + + return ImpactExcitationPEC(wavelength, data, extrapolate=self._permit_extrapolation) + + def recombination_pec(self, ion, charge, transition): + """ + Recombination photon emission coefficient for a given species. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion after recombination. + :param transition: Tuple containing (initial level, final level). + :return: Recombination photon emission coefficient in W.m^3 as a function of electron + density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read recombination PEC from json file in the repository + data = repository.get_pec_recombination_rate(ion, charge, transition, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_pec_recombination_rate(ion.element, charge, transition, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullRecombinationPEC() + raise + elif self._missing_rates_return_null: + return NullRecombinationPEC() + else: + 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(ion, charge, transition) + + return RecombinationPEC(wavelength, data, extrapolate=self._permit_extrapolation) + + def line_radiated_power_rate(self, ion, charge): + """ + Line radiated power coefficient for a given species. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Line radiated power coefficient in W.m^3 as a function of electron + density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read total line radiated power rate from json file in the repository + data = repository.get_line_radiated_power_rate(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_line_radiated_power_rate(ion.element, charge, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullLineRadiationPower(ion, charge) + raise + elif self._missing_rates_return_null: + return NullLineRadiationPower(ion, charge) + else: + raise + + return LineRadiationPower(ion, charge, data, extrapolate=self._permit_extrapolation) + + def continuum_radiated_power_rate(self, ion, charge): + """ + Recombination continuum radiated power coefficient for a given species. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Continuum radiated power coefficient in W.m^3 as a function + of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read continuum radiated power rate from json file in the repository + data = repository.get_continuum_radiated_power_rate(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_continuum_radiated_power_rate(ion.element, charge, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullContinuumPower(ion, charge) + raise + elif self._missing_rates_return_null: + return NullContinuumPower(ion, charge) + else: + raise + + return ContinuumPower(ion, charge, data, extrapolate=self._permit_extrapolation) + + def cx_radiated_power_rate(self, ion, charge): + """ + Charge exchange radiated power coefficient for a given species. + + The data is interpolated with cubic spline in log-log space. + Linear extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Charge exchange radiated power coefficient in W.m^3 as a function + of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read CX radiated power rate from json file in the repository + data = repository.get_cx_radiated_power_rate(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_cx_radiated_power_rate(ion.element, charge, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullCXRadiationPower(ion, charge) + raise + elif self._missing_rates_return_null: + return NullCXRadiationPower(ion, charge) + else: + raise + + return CXRadiationPower(ion, charge, data, extrapolate=self._permit_extrapolation) + + def total_radiated_power(self, ion): + """ + Total radiated power coefficient in equilibrium conditions for a given species. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :return: Total radiated power coefficient in W.m^3 as a function + of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read total radiated power rate from json file in the repository + data = repository.get_total_radiated_power_rate(ion, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + try: + data = repository.get_total_radiated_power_rate(ion.element, repository_path=self._data_path) + except RuntimeError: + if self._missing_rates_return_null: + return NullTotalRadiatedPower(ion, charge) + raise + elif self._missing_rates_return_null: + return NullTotalRadiatedPower(ion, charge) + else: + raise + + return TotalRadiatedPower(ion, data, extrapolate=self._permit_extrapolation) + + def fractional_abundance(self, ion, charge): + """ + Fractional abundance of a given species in thermodynamic equilibrium. + + The data is interpolated with cubic spline. + Linear extrapolation is used when permit_extrapolation is True. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion. + :return: Fractional abundance as a function + of electron density and temperature. + """ + + # try to read the rate for the isotope, fallback to the element if fails + try: + # read total radiated power rate from json file in the repository + data = repository.get_fractional_abundance(ion, charge, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + data = repository.get_fractional_abundance(ion.element, charge, repository_path=self._data_path) + else: + raise + + return FractionalAbundance(ion, charge, data, extrapolate=self._permit_extrapolation) + + def zeeman_structure(self, ion, charge, transition): + r""" + Wavelengths and ratios of + :math:`\pi`-/:math:`\sigma`-polarised Zeeman components for any given value of + magnetic field strength. + + :param ion: Element object defining the ion type. + :param charge: Charge state of the ion after recombination. + :param transition: Tuple containing (initial level, final level). + :return: ZeemanStructure object. + """ + + try: + # read Zeeman structure from json file in the repository + data = repository.get_zeeman_structure(ion, charge, transition, repository_path=self._data_path) + except RuntimeError: + if isinstance(ion, Isotope) and self._rate_element_fallback: + data = repository.get_zeeman_structure(ion.element, charge, transition, repository_path=self._data_path) + else: + raise + + return ZeemanStructure(data, extrapolate=self._permit_extrapolation) + + def free_free_gaunt_factor(self): + r""" + Free-free Gaunt factor used in the bremsstrahlung emission model. + + The Gaunt factor is defined in the space of parameters: + :math:`u = h{\nu}/kT` and :math:`{\gamma}^{2} = Z^{2}Ry/kT`. + See T.R. Carson, 1988, Astron. & Astrophys., 189, + `319 `_ for details. + + The cubic interpolation in a semi-log space is used. + + The Born approximation and classical limits are used outside the interpolation range. + + :return: Free-free Gaunt factor as a function of species charge, electron temperature + and wavelength. + """ + + data = repository.get_free_free_gaunt_factor(repository_path=self._data_path) + + return FreeFreeGauntFactor(data) diff --git a/cherab/atomic/gaunt/__init__.pxd b/cherab/atomic/gaunt/__init__.pxd new file mode 100644 index 00000000..9867cec6 --- /dev/null +++ b/cherab/atomic/gaunt/__init__.pxd @@ -0,0 +1,19 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.atomic.gaunt.gaunt cimport * diff --git a/cherab/atomic/gaunt/__init__.py b/cherab/atomic/gaunt/__init__.py new file mode 100644 index 00000000..8e309aec --- /dev/null +++ b/cherab/atomic/gaunt/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from .gaunt import FreeFreeGauntFactor diff --git a/cherab/atomic/gaunt/gaunt.pxd b/cherab/atomic/gaunt/gaunt.pxd new file mode 100644 index 00000000..c7d79080 --- /dev/null +++ b/cherab/atomic/gaunt/gaunt.pxd @@ -0,0 +1,29 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.math cimport Function2D +from cherab.core.atomic cimport FreeFreeGauntFactor as CoreFreeFreeGauntFactor + + +cdef class FreeFreeGauntFactor(CoreFreeFreeGauntFactor): + + cdef: + readonly tuple u_range, gamma2_range + readonly dict raw_data + double _u_min, _u_max, _gamma2_min, _gamma2_max + Function2D _gaunt_factor diff --git a/cherab/atomic/gaunt/gaunt.pyx b/cherab/atomic/gaunt/gaunt.pyx new file mode 100644 index 00000000..dd818d8e --- /dev/null +++ b/cherab/atomic/gaunt/gaunt.pyx @@ -0,0 +1,104 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +import numpy as np +from libc.math cimport log10, log, M_PI, sqrt +from raysect.core.math.function.float cimport Interpolator2DArray +from cherab.core.utility.constants cimport RYDBERG_CONSTANT_EV, SPEED_OF_LIGHT, ELEMENTARY_CHARGE, PLANCK_CONSTANT + +cimport cython + + +DEF EULER_GAMMA = 0.5772156649015329 + +cdef double PH_TO_EV_FACTOR = PLANCK_CONSTANT * SPEED_OF_LIGHT * 1e9 / ELEMENTARY_CHARGE + + +cdef class FreeFreeGauntFactor(CoreFreeFreeGauntFactor): + r""" + The temperature-averaged free-free Gaunt factors interpolated in the space of parameters: + :math:`u = h{\nu}/kT` and :math:`{\gamma}^{2} = Z^{2}Ry/kT`. + See T.R. Carson, 1988, Astron. & Astrophys., 189, + `319 `_ for details. + + The cubic interpolation in a semi-log space is used. + + The Born approximation and classical limits are used outside the interpolation range. + + :param dict data: Dictionary containing the Gaunt factor data with the following keys: + + | 'u': A 1D array of real values. + | 'gamma2': A 1D array of real values. + | 'gaunt_factor': 2D array of real values storing the Gaunt factor values at u, gamma2. + + :ivar tuple u_range: The interpolation range of `u` parameter. + :ivar tuple gamma2_range: The interpolation range of :math:`\\gamma^2` parameter. + :ivar dict raw_data: Dictionary containing the raw data. + """ + + def __init__(self, dict data): + + self.raw_data = data + + u = data['u'] + gamma2 = data['gamma2'] + gaunt_factor = data['gaunt_factor'] + + self._u_min = u.min() + self._u_max = u.max() + self._gamma2_min = gamma2.min() + self._gamma2_max = gamma2.max() + + self.u_range = (self._u_min, self._u_max) + self.gamma2_range = (self._gamma2_min, self._gamma2_max) + + self._gaunt_factor = Interpolator2DArray(np.log10(u), np.log10(gamma2), gaunt_factor, 'cubic', 'none', 0, 0) + + @cython.cdivision(True) + cpdef double evaluate(self, double z, double temperature, double wavelength) except? -1e999: + """ + Returns the temperature-averaged free-free Gaunt factor for the supplied parameters. + + :param double z: Species charge or effective plasma charge. + :param double temperature: Electron temperature in eV. + :param double wavelength: Spectral wavelength. + + :return: free-free Gaunt factor + """ + + cdef: + double u, gamma2 + + if z == 0: + + return 0 + + gamma2 = z * z * RYDBERG_CONSTANT_EV / temperature + u = PH_TO_EV_FACTOR / (temperature * wavelength) + + # classical limit + if u >= self._u_max or gamma2 >= self._gamma2_max: + + return 1 + + # Born approximation limit + if u < self._u_min or gamma2 < self._gamma2_min: + + return sqrt(3) / M_PI * (log(4 / u) - EULER_GAMMA) + + return self._gaunt_factor.evaluate(log10(u), log10(gamma2)) diff --git a/cherab/atomic/rates/__init__.pxd b/cherab/atomic/rates/__init__.pxd new file mode 100644 index 00000000..df8811f3 --- /dev/null +++ b/cherab/atomic/rates/__init__.pxd @@ -0,0 +1,24 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.atomic.rates.beam cimport * +from cherab.atomic.rates.cx cimport * +from cherab.atomic.rates.pec cimport * +from cherab.atomic.rates.atomic cimport * +from cherab.atomic.rates.radiated_power cimport * +from cherab.atomic.rates.fractional_abundance cimport * diff --git a/cherab/atomic/rates/__init__.py b/cherab/atomic/rates/__init__.py new file mode 100644 index 00000000..28b6b658 --- /dev/null +++ b/cherab/atomic/rates/__init__.py @@ -0,0 +1,24 @@ +# 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 .beam import * +from .cx import * +from .pec import * +from .atomic import * +from .radiated_power import * +from .fractional_abundance import * diff --git a/cherab/openadas/rates/atomic.pxd b/cherab/atomic/rates/atomic.pxd similarity index 100% rename from cherab/openadas/rates/atomic.pxd rename to cherab/atomic/rates/atomic.pxd diff --git a/cherab/openadas/rates/atomic.pyx b/cherab/atomic/rates/atomic.pyx similarity index 58% rename from cherab/openadas/rates/atomic.pyx rename to cherab/atomic/rates/atomic.pyx index 04500124..e6505d4e 100644 --- a/cherab/openadas/rates/atomic.pyx +++ b/cherab/atomic/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-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"); @@ -23,13 +23,30 @@ from libc.math cimport INFINITY, log10 from raysect.core.math.function.float cimport Interpolator2DArray +DEF ZERO_THRESHOLD = 1.e-300 + + cdef class IonisationRate(CoreIonisationRate): + """ + Ionisation rate. - def __init__(self, dict data, extrapolate=False): - """ - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when extrapolate is True. + + :param dict data: Ionisation rate dictionary containing the following fields: + + | '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, bint extrapolate=False): self.raw_data = data @@ -50,11 +67,11 @@ 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 density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -62,7 +79,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 +88,26 @@ cdef class NullIonisationRate(CoreIonisationRate): cdef class RecombinationRate(CoreRecombinationRate): + """ + Recombination rate. - def __init__(self, dict data, extrapolate=False): - """ - :param data: Dictionary containing rate data. - :param extrapolate: Enable extrapolation (default=False). - """ + Data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when extrapolate is True. + + :param dict data: Recombination rate dictionary containing the following fields: + + | '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, bint extrapolate=False): self.raw_data = data @@ -97,11 +128,11 @@ 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 density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -109,7 +140,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 +149,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 when extrapolate is True. + + :param dict data: CX rate dictionary containing the following fields: + + | '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 @@ -143,11 +188,11 @@ 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 density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -155,7 +200,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.pxd b/cherab/atomic/rates/beam.pxd similarity index 68% rename from cherab/openadas/rates/beam.pxd rename to cherab/atomic/rates/beam.pxd index 4f23f2bf..ef3d0811 100644 --- a/cherab/openadas/rates/beam.pxd +++ b/cherab/atomic/rates/beam.pxd @@ -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-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"); @@ -24,13 +24,13 @@ from cherab.core.math cimport Function1D, Function2D cdef class BeamStoppingRate(CoreBeamStoppingRate): - cdef readonly: - dict raw_data + cdef: + readonly dict raw_data + readonly tuple beam_energy_range + readonly tuple density_range + readonly tuple temperature_range Function2D _npl_eb Function1D _tp - tuple beam_energy_range - tuple density_range - tuple temperature_range cdef class NullBeamStoppingRate(CoreBeamStoppingRate): @@ -39,13 +39,13 @@ cdef class NullBeamStoppingRate(CoreBeamStoppingRate): cdef class BeamPopulationRate(CoreBeamPopulationRate): - cdef readonly: - dict raw_data + cdef: + readonly dict raw_data + readonly tuple beam_energy_range + readonly tuple density_range + readonly tuple temperature_range Function2D _npl_eb Function1D _tp - tuple beam_energy_range - tuple density_range - tuple temperature_range cdef class NullBeamPopulationRate(CoreBeamPopulationRate): @@ -54,14 +54,14 @@ cdef class NullBeamPopulationRate(CoreBeamPopulationRate): cdef class BeamEmissionPEC(CoreBeamEmissionPEC): - cdef readonly: - double wavelength - dict raw_data + cdef: + readonly double wavelength + readonly dict raw_data + readonly tuple beam_energy_range + readonly tuple density_range + readonly tuple temperature_range Function2D _npl_eb Function1D _tp - tuple beam_energy_range - tuple density_range - tuple temperature_range cdef class NullBeamEmissionPEC(CoreBeamEmissionPEC): diff --git a/cherab/openadas/rates/beam.pyx b/cherab/atomic/rates/beam.pyx similarity index 64% rename from cherab/openadas/rates/beam.pyx rename to cherab/atomic/rates/beam.pyx index f40af8dd..70ca30b2 100644 --- a/cherab/openadas/rates/beam.pyx +++ b/cherab/atomic/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-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"); @@ -25,14 +25,30 @@ from libc.math cimport INFINITY, log10 from raysect.core.math.function.float cimport Interpolator1DArray, Interpolator2DArray, Constant2D, Arg2D from cherab.core.math cimport IsoMapper2D -# todo: clarify variables + +DEF ZERO_THRESHOLD = 1.e-300 + 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). + :param dict data: A beam stopping rate dictionary containing the following fields: + + | '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) @@ -69,23 +85,21 @@ cdef class BeamStoppingRate(CoreBeamStoppingRate): """ Interpolates and returns the beam coefficient for the supplied parameters. - If the requested data is out-of-range then the call with throw a ValueError exception. - - :param energy: Interaction energy in eV/amu. - :param density: Target electron density in m^-3 - :param temperature: Target temperature in eV. + :param double energy: Interaction energy in eV/amu. + :param double density: Target electron density in m^-3 + :param double temperature: Target temperature in eV. :return: The beam stopping coefficient in m^3.s^-1 """ # 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 energy < ZERO_THRESHOLD: + energy = ZERO_THRESHOLD - if density < 1.e-300: - density = 1.e-300 + if density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # 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))) @@ -105,8 +119,22 @@ 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). + :param dict data: Beam population rate dictionary containing the following fields: + + | '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) @@ -143,23 +171,21 @@ cdef class BeamPopulationRate(CoreBeamPopulationRate): """ Interpolates and returns the beam coefficient for the supplied parameters. - If the requested data is out-of-range then the call with throw a ValueError exception. - - :param energy: Interaction energy in eV/amu. - :param density: Target electron density in m^-3 - :param temperature: Target temperature in eV. + :param double energy: Interaction energy in eV/amu. + :param double density: Target electron density in m^-3 + :param double temperature: Target temperature in eV. :return: The beam population coefficient in dimensionless units. """ # 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 energy < ZERO_THRESHOLD: + energy = ZERO_THRESHOLD - if density < 1.e-300: - density = 1.e-300 + if density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # 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))) @@ -179,9 +205,22 @@ 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). + :param dict data: Beam emission rate dictionary containing the following fields: + + | '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 +233,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 @@ -221,21 +260,21 @@ cdef class BeamEmissionPEC(CoreBeamEmissionPEC): If the requested data is out-of-range then the call with throw a ValueError exception. - :param energy: Interaction energy in eV/amu. - :param density: Target electron density in m^-3 - :param temperature: Target temperature in eV. + :param double energy: Interaction energy in eV/amu. + :param double density: Target electron density in m^-3 + :param double temperature: Target temperature in eV. :return: The beam emission coefficient in m^3.s^-1 """ # 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 energy < ZERO_THRESHOLD: + energy = ZERO_THRESHOLD - if density < 1.e-300: - density = 1.e-300 + if density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # 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.pxd b/cherab/atomic/rates/cx.pxd similarity index 80% rename from cherab/openadas/rates/cx.pxd rename to cherab/atomic/rates/cx.pxd index 393027d0..4242decb 100644 --- a/cherab/openadas/rates/cx.pxd +++ b/cherab/atomic/rates/cx.pxd @@ -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-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"); @@ -22,16 +22,16 @@ from cherab.core.math cimport Function1D cdef class BeamCXPEC(CoreBeamCXPEC): - cdef readonly: - dict raw_data - double wavelength - int donor_metastable - Function1D _eb, _ti, _ni, _zeff, _b + cdef: + readonly dict raw_data + readonly double wavelength + readonly int donor_metastable readonly tuple beam_energy_range readonly tuple density_range readonly tuple temperature_range readonly tuple zeff_range readonly tuple b_field_range + Function1D _eb, _ti, _ni, _zeff, _b cdef class NullBeamCXPEC(CoreBeamCXPEC): diff --git a/cherab/openadas/rates/cx.pyx b/cherab/atomic/rates/cx.pyx similarity index 60% rename from cherab/openadas/rates/cx.pyx rename to cherab/atomic/rates/cx.pyx index cb827a8b..21a0ed65 100644 --- a/cherab/openadas/rates/cx.pyx +++ b/cherab/atomic/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-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"); @@ -24,14 +24,46 @@ from libc.math cimport INFINITY, log10 from raysect.core.math.function.float cimport Interpolator1DArray, Constant1D +DEF ZERO_THRESHOLD = 1.e-300 + + 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" when permit_extrapolation 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 fields: + + | '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) @@ -75,21 +107,19 @@ cdef class BeamCXPEC(CoreBeamCXPEC): """ Interpolates and returns the effective cx rate for the given plasma parameters. - If the requested data is out-of-range then the call with throw a ValueError exception. - - :param energy: Interaction energy in eV/amu. - :param temperature: Receiver ion temperature in eV. - :param density: Receiver ion density in m^-3 - :param z_effective: Plasma Z-effective. - :param b_field: Magnetic field magnitude in Tesla. + :param double energy: Interaction energy in eV/amu. + :param double temperature: Receiver ion temperature in eV. + :param double density: Receiver ion density in m^-3 + :param double z_effective: Plasma Z-effective. + :param double b_field: Magnetic field magnitude in Tesla. :return: The effective cx rate in W.m^3 """ cdef double rate # need to handle zeros for log-log interpolation - if energy < 1.e-300: - energy = 1.e-300 + if energy < ZERO_THRESHOLD: + energy = ZERO_THRESHOLD rate = 10 ** self._eb.evaluate(log10(energy)) diff --git a/cherab/atomic/rates/fractional_abundance.pxd b/cherab/atomic/rates/fractional_abundance.pxd new file mode 100644 index 00000000..ea141159 --- /dev/null +++ b/cherab/atomic/rates/fractional_abundance.pxd @@ -0,0 +1,28 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.core.math cimport Function2D +from cherab.core.atomic.rates cimport FractionalAbundance as CoreFractionalAbundance + + +cdef class FractionalAbundance(CoreFractionalAbundance): + + cdef: + readonly dict raw_data + readonly tuple density_range, temperature_range + Function2D _abundance diff --git a/cherab/atomic/rates/fractional_abundance.pyx b/cherab/atomic/rates/fractional_abundance.pyx new file mode 100644 index 00000000..30163349 --- /dev/null +++ b/cherab/atomic/rates/fractional_abundance.pyx @@ -0,0 +1,70 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from libc.math cimport INFINITY + +from raysect.core.math.function.float cimport Interpolator2DArray +from cherab.core.atomic cimport Element + + +cdef class FractionalAbundance(CoreFractionalAbundance): + """ + Fractional abundance in thermodynamic equilibrium. + + The data is interpolated with cubic spline. + Linear extrapolation is used when permit_extrapolation is True. + + :param Element species: the radiating element + :param int ionisation: Charge state of the ion. + :param dict data: Fractional abundance dictionary containing the following fields: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'fractional_abundance': 2D array of size (N, M) with fractional abundance. + + :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, Element species, int ionisation, dict data, bint extrapolate=False): + + super().__init__(species, ionisation) + + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + fractional_abundance = data['fractional_abundance'] + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + + extrapolation_type = 'linear' if extrapolate else 'none' + self._abundance = Interpolator2DArray(ne, te, fractional_abundance, 'cubic', extrapolation_type, INFINITY, INFINITY) + + cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: + + if electron_density <= 0 or electron_temperature <= 0: + return 0.0 + + return self._abundance.evaluate(electron_density, electron_temperature) diff --git a/cherab/openadas/rates/pec.pxd b/cherab/atomic/rates/pec.pxd similarity index 91% rename from cherab/openadas/rates/pec.pxd rename to cherab/atomic/rates/pec.pxd index 46a54cbe..b50e07da 100644 --- a/cherab/openadas/rates/pec.pxd +++ b/cherab/atomic/rates/pec.pxd @@ -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-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"); diff --git a/cherab/openadas/rates/pec.pyx b/cherab/atomic/rates/pec.pyx similarity index 60% rename from cherab/openadas/rates/pec.pyx rename to cherab/atomic/rates/pec.pyx index eafc6c64..197246c1 100644 --- a/cherab/openadas/rates/pec.pyx +++ b/cherab/atomic/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-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"); @@ -24,14 +24,31 @@ from raysect.core.math.function.float cimport Interpolator2DArray from cherab.core.utility.conversion import PhotonToJ +DEF ZERO_THRESHOLD = 1.e-300 + + 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 when permit_extrapolation is True. + + :param double wavelength: Resting wavelength of corresponding emission line in nm. + :param dict data: Excitation PEC dictionary containing the following fields: + + | '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). - """ + def __init__(self, double wavelength, dict data, bint extrapolate=False): self.wavelength = wavelength self.raw_data = data @@ -56,11 +73,11 @@ 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 density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # calculate rate and convert from log10 space to linear space return 10 ** self._rate.evaluate(log10(density), log10(temperature)) @@ -77,13 +94,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 when permit_extrapolation is True. + + :param double wavelength: Resting wavelength of corresponding emission line in nm. + :param dict data: Rcombination PEC dictionary containing the following fields: + + | '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). - """ + def __init__(self, double wavelength, dict data, bint extrapolate=False): self.wavelength = wavelength self.raw_data = data @@ -108,11 +139,11 @@ 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 density < ZERO_THRESHOLD: + density = ZERO_THRESHOLD - if temperature < 1.e-300: - temperature = 1.e-300 + if temperature < ZERO_THRESHOLD: + temperature = ZERO_THRESHOLD # 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.pxd b/cherab/atomic/rates/radiated_power.pxd similarity index 77% rename from cherab/openadas/rates/radiated_power.pxd rename to cherab/atomic/rates/radiated_power.pxd index 73fa09aa..c43d5844 100644 --- a/cherab/openadas/rates/radiated_power.pxd +++ b/cherab/atomic/rates/radiated_power.pxd @@ -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-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"); @@ -22,6 +22,7 @@ from cherab.core.math cimport Function2D from cherab.core.atomic.rates cimport LineRadiationPower as CoreLineRadiationPower from cherab.core.atomic.rates cimport ContinuumPower as CoreContinuumPower from cherab.core.atomic.rates cimport CXRadiationPower as CoreCXRadiationPower +from cherab.core.atomic.rates cimport TotalRadiatedPower as CoreTotalRadiatedPower cdef class LineRadiationPower(CoreLineRadiationPower): @@ -58,3 +59,15 @@ cdef class CXRadiationPower(CoreCXRadiationPower): cdef class NullCXRadiationPower(CoreCXRadiationPower): pass + + +cdef class TotalRadiatedPower(CoreTotalRadiatedPower): + + cdef: + readonly dict raw_data + readonly tuple density_range, temperature_range + Function2D _rate + + +cdef class NullTotalRadiatedPower(CoreTotalRadiatedPower): + pass diff --git a/cherab/atomic/rates/radiated_power.pyx b/cherab/atomic/rates/radiated_power.pyx new file mode 100644 index 00000000..56c3596c --- /dev/null +++ b/cherab/atomic/rates/radiated_power.pyx @@ -0,0 +1,286 @@ + +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +import numpy as np +from libc.math cimport INFINITY, log10 + +from raysect.core.math.function.float cimport Interpolator2DArray +from cherab.core.atomic cimport Element + + +DEF ZERO_THRESHOLD = 1.e-300 + + +cdef class LineRadiationPower(CoreLineRadiationPower): + """ + Line radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation 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 fields: + + | '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, Element species, int ionisation, dict data, bint extrapolate=False): + + super().__init__(species, ionisation) + + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + rate = np.log10(data['rate']) + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + + # interpolate rate + # using nearest extrapolation to avoid infinite values at 0 for some rates + extrapolation_type = 'nearest' if extrapolate else 'none' + self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) + + 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 < ZERO_THRESHOLD: + electron_density = ZERO_THRESHOLD + + if electron_temperature < ZERO_THRESHOLD: + electron_temperature = ZERO_THRESHOLD + + # calculate rate and convert from log10 space to linear space + return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) + + +cdef class NullLineRadiationPower(CoreLineRadiationPower): + """ + A line radiation power rate that always returns zero. + Needed for use cases where the required atomic data is missing. + """ + + cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: + return 0.0 + + +cdef class ContinuumPower(CoreContinuumPower): + """ + Recombination continuum radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation 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 fields: + + | '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, Element species, int ionisation, dict data, bint extrapolate=False): + + super().__init__(species, ionisation) + + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + rate = np.log10(data['rate']) + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + + # interpolate rate + # using nearest extrapolation to avoid infinite values at 0 for some rates + extrapolation_type = 'nearest' if extrapolate else 'none' + self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) + + 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 < ZERO_THRESHOLD: + electron_density = ZERO_THRESHOLD + + if electron_temperature < ZERO_THRESHOLD: + electron_temperature = ZERO_THRESHOLD + + # calculate rate and convert from log10 space to linear space + return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) + + +cdef class NullContinuumPower(CoreContinuumPower): + """ + A continuum radiation power rate that always returns zero. + Needed for use cases where the required atomic data is missing. + """ + + cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: + return 0.0 + + +cdef class CXRadiationPower(CoreCXRadiationPower): + """ + Charge exchange radiated power coefficient. + + The data is interpolated with cubic spline in log-log space. + Linear extrapolation is used when permit_extrapolation 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 fields: + + | '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, Element species, int ionisation, dict data, bint extrapolate=False): + + super().__init__(species, ionisation) + + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + rate = np.log10(data['rate']) + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + + # interpolate rate + extrapolation_type = 'linear' if extrapolate else 'none' + self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) + + 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 < ZERO_THRESHOLD: + electron_density = ZERO_THRESHOLD + + if electron_temperature < ZERO_THRESHOLD: + electron_temperature = ZERO_THRESHOLD + + # calculate rate and convert from log10 space to linear space + return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) + + +cdef class NullCXRadiationPower(CoreCXRadiationPower): + """ + A CX radiation power rate that always returns zero. + Needed for use cases where the required atomic data is missing. + """ + + cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: + return 0.0 + + +cdef class TotalRadiatedPower(CoreTotalRadiatedPower): + """ + The total radiated power rate in equilibrium conditions. + + The data is interpolated with cubic spline in log-log space. + Nearest neighbour extrapolation is used when permit_extrapolation is True. + + :param Element species: Element object defining the ion type. + :param int ionisation: Charge state of the ion. + :param dict data: Total radiated power rate dictionary containing the following fields: + + | '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, Element species, dict data, bint extrapolate=False): + + super().__init__(species) + + self.raw_data = data + + # unpack + ne = data['ne'] + te = data['te'] + rate = np.log10(data['rate']) + + # store limits of data + self.density_range = ne.min(), ne.max() + self.temperature_range = te.min(), te.max() + + # interpolate rate + # using nearest extrapolation to avoid infinite values at 0 for some rates + extrapolation_type = 'nearest' if extrapolate else 'none' + self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) + + 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 < ZERO_THRESHOLD: + electron_density = ZERO_THRESHOLD + + if electron_temperature < ZERO_THRESHOLD: + electron_temperature = ZERO_THRESHOLD + + # calculate rate and convert from log10 space to linear space + return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) + + +cdef class NullTotalRadiatedPower(CoreTotalRadiatedPower): + """ + A total radiated power rate that always returns zero. + Needed for use cases where the required atomic data is missing. + """ + + cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: + return 0.0 diff --git a/cherab/atomic/repository/__init__.py b/cherab/atomic/repository/__init__.py new file mode 100644 index 00000000..493d2d21 --- /dev/null +++ b/cherab/atomic/repository/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from .beam import * +from .pec import * +from .atomic import * +from .wavelength import * +from .radiated_power import * +from .fractional_abundance import * +from .gaunt import * +from .zeeman import * +from .utility import DEFAULT_REPOSITORY_PATH +from .create import populate diff --git a/cherab/atomic/repository/atomic.py b/cherab/atomic/repository/atomic.py new file mode 100644 index 00000000..0ad237fe --- /dev/null +++ b/cherab/atomic/repository/atomic.py @@ -0,0 +1,376 @@ +# 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 os +import json +import numpy as np + +from cherab.core.atomic import Element +from cherab.core.utility import RecursiveDict +from .utility import DEFAULT_REPOSITORY_PATH, valid_charge + + +def add_ionisation_rate(species, charge, rate, repository_path=None): + """ + Adds a single ionisation rate to the repository. + + If adding multiple rates, consider using the update_ionisation_rates() + function instead. The update function avoids repeatedly opening and closing + the rate files. + + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Ionisation rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_ionisation_rates({ + species: { + charge: rate + } + }, repository_path) + + +def update_ionisation_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, rate_data in rates.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'ionisation/{}.json'.format(species.symbol.lower())) + + _update_and_write_bivariate_rate(species, rate_data, path) + + +def add_recombination_rate(species, charge, rate, repository_path=None): + """ + Adds a single recombination rate to the repository. + + If adding multiple rates, consider using the update_recombination_rates() + function instead. The update function avoids repeatedly opening and closing + the rate files. + + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Recombination rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_recombination_rates({ + species: { + charge: rate + } + }, repository_path) + + +def update_recombination_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, rate_data in rates.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'recombination/{}.json'.format(species.symbol.lower())) + + _update_and_write_bivariate_rate(species, rate_data, path) + + +def add_thermal_cx_rate(donor_element, donor_charge, receiver_element, receiver_charge, rate, repository_path=None): + """ + Adds a single thermal charge exchange rate to the repository. + + If adding multiple rates, consider using the update_recombination_rates() + function instead. The update function avoids repeatedly opening and closing + 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 receiver_charge: Charge of the receiving atom/ion. + :param rate: Thermal CX rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + rates2update = RecursiveDict() + rates2update[donor_element][donor_charge][receiver_element][receiver_charge] = rate + + update_thermal_cx_rates(rates2update, repository_path) + + +def update_thermal_cx_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for donor_element in rates.keys(): + for donor_charge in rates[donor_element].keys(): + for receiver_element, rate_data in rates[donor_element][donor_charge].items(): + + # sanitise and validate arguments + if not isinstance(receiver_element, Element): + raise TypeError('The receiver_element must be an Element object.') + + rate_path = 'thermal_cx/{0}/{1}/{2}.json'.format(donor_element.symbol.lower(), + donor_charge, receiver_element.symbol.lower()) + path = os.path.join(repository_path, rate_path) + + _update_and_write_bivariate_rate(receiver_element, rate_data, path) + + +def _update_and_write_bivariate_rate(species, rate_data, path): + + # read in any existing rates + try: + with open(path, 'r') as f: + content = RecursiveDict.from_dict(json.load(f)) + except FileNotFoundError: + content = RecursiveDict() + + for charge, rates in rate_data.items(): + + if not valid_charge(species, charge): + raise ValueError('Charge state is larger than the number of protons in the specified species.') + + # sanitise and validate rate data + te = np.array(rates['te'], np.float64) + ne = np.array(rates['ne'], np.float64) + rate_table = np.array(rates['rate'], np.float64) + + if ne.ndim != 1: + raise ValueError('Density array must be a 1D array.') + + if te.ndim != 1: + raise ValueError('Temperature array must be a 1D array.') + + if (ne.shape[0], te.shape[0]) != rate_table.shape: + raise ValueError('Electron temperature, density and rate data arrays have inconsistent sizes.') + + # update file content with new rate + content[str(charge)] = { + 'te': te.tolist(), + 'ne': ne.tolist(), + 'rate': rate_table.tolist(), + } + if 'reference' in rates: + content[str(charge)]['reference'] = str(rates['reference']) + + # 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_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 fields: + + | '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. + | 'reference': Optional data reference string. + + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + path = os.path.join(repository_path, 'ionisation/{}.json'.format(element.symbol.lower())) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[str(charge)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested ionisation rate (element={}, charge={})' + ' is not available.'.format(element.symbol, charge)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['rate'] = np.array(d['rate'], np.float64) + + return d + + +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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + path = os.path.join(repository_path, 'recombination/{}.json'.format(element.symbol.lower())) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[str(charge)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested recombination rate (element={}, charge={})' + ' is not available.'.format(element.symbol, charge)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['rate'] = np.array(d['rate'], np.float64) + + return d + + +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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + rate_path = 'thermal_cx/{0}/{1}/{2}.json'.format(donor_element.symbol.lower(), donor_charge, + receiver_element.symbol.lower()) + path = os.path.join(repository_path, rate_path) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[str(receiver_charge)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested thermal charge-exchange rate (donor={}, donor charge={}, receiver={})' + ' 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['rate'] = np.array(d['rate'], np.float64) + + return d diff --git a/cherab/atomic/repository/beam/__init__.py b/cherab/atomic/repository/beam/__init__.py new file mode 100644 index 00000000..3b5a95ae --- /dev/null +++ b/cherab/atomic/repository/beam/__init__.py @@ -0,0 +1,22 @@ +# 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 .cx import * +from .stopping import * +from .population import * +from .emission import * diff --git a/cherab/openadas/repository/beam/cx.py b/cherab/atomic/repository/beam/cx.py similarity index 59% rename from cherab/openadas/repository/beam/cx.py rename to cherab/atomic/repository/beam/cx.py index 65bc3ceb..4579b6eb 100644 --- a/cherab/openadas/repository/beam/cx.py +++ b/cherab/atomic/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-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"); @@ -29,19 +29,34 @@ 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 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 fields: + + | '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 receiver ion density in m^-3, + | 'z': array-like of size (L) with receiver 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. + | 'reference': Optional data reference string. + | 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 +73,36 @@ 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 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 fields: + | '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 receiver ion density in m^-3, + | 'z': array-like of size (L) with receiver 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. + | 'reference': Optional data reference string. + | The total beam CX PEC: q = qeb * qti * qni * qz * qb / qref^4. + """ def sanitise_and_validate(data, x_key, x_name, y_key, y_name): """ @@ -155,6 +196,8 @@ def sanitise_and_validate(data, x_key, x_name, y_key, y_name): 'qz': data['qz'].tolist(), 'qb': data['qb'].tolist(), } + if 'reference' in data: + content[transition_key][metastable]['reference'] = str(data['reference']) # create directory structure if missing directory = os.path.dirname(path) @@ -167,6 +210,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 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 fields: + + | '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. + | 'reference': Optional data reference string. + | 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/atomic/repository/beam/emission.py similarity index 58% rename from cherab/openadas/repository/beam/emission.py rename to cherab/atomic/repository/beam/emission.py index b6cd14f2..9501b046 100644 --- a/cherab/openadas/repository/beam/emission.py +++ b/cherab/atomic/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-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"); @@ -24,7 +24,7 @@ from ..utility import DEFAULT_REPOSITORY_PATH, valid_charge, encode_transition """ -Utilities for managing the local rate repository - PEC section. +Utilities for managing the local rate repository - beam emission section. """ @@ -36,8 +36,25 @@ 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 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 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. + | 'reference': Optional data reference string. + | The total beam emission rate: s = sen * st / sref. + + :param repository_path: Path to the atomic data repository. """ update_beam_emission_rates({ @@ -53,11 +70,33 @@ 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 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 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. + | 'reference': Optional data reference string. + | 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 @@ -124,6 +163,8 @@ def update_beam_emission_rates(rates, repository_path=None): 'tref': float(rate['tref']), 'sref': float(rate['sref']) } + if 'reference' in rate: + content[key]['reference'] = str(rate['reference']) # create directory structure if missing directory = os.path.dirname(path) @@ -136,6 +177,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 to 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 rate: Beam emission rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + | 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 path = os.path.join(repository_path, 'beam/emission/{}/{}/{}.json'.format(beam_species.symbol.lower(), target_ion.symbol.lower(), target_charge)) @@ -154,4 +218,4 @@ def get_beam_emission_rate(beam_species, target_ion, target_charge, transition, rate['sen'] = np.array(rate['sen'], np.float64) rate['st'] = np.array(rate['st'], np.float64) - return rate \ No newline at end of file + return rate diff --git a/cherab/openadas/repository/beam/population.py b/cherab/atomic/repository/beam/population.py similarity index 54% rename from cherab/openadas/repository/beam/population.py rename to cherab/atomic/repository/beam/population.py index 54c57df1..083dc0e2 100644 --- a/cherab/openadas/repository/beam/population.py +++ b/cherab/atomic/repository/beam/population.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"); @@ -31,12 +31,25 @@ 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 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. + | 'reference': Optional data reference string. + | 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 @@ -87,6 +100,8 @@ def add_beam_population_rate(beam_species, beam_metastable, target_ion, target_c rate['nref'] = float(rate['nref']) rate['tref'] = float(rate['tref']) rate['sref'] = float(rate['sref']) + if 'reference' in rate: + rate['reference'] = str(rate['reference']) path = os.path.join(repository_path, 'beam/population/{}/{}/{}/{}.json'.format(beam_species.symbol.lower(), beam_metastable, target_ion.symbol.lower(), target_charge)) @@ -102,11 +117,33 @@ 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. + | 'reference': Optional data reference string. + | 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 +154,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 fields: + + | '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. + | 'reference': Optional data reference string. + | 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/atomic/repository/beam/stopping.py similarity index 53% rename from cherab/openadas/repository/beam/stopping.py rename to cherab/atomic/repository/beam/stopping.py index cf8d1492..378364e4 100644 --- a/cherab/openadas/repository/beam/stopping.py +++ b/cherab/atomic/repository/beam/stopping.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"); @@ -31,11 +31,24 @@ 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 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 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. + | 'reference': Optional data reference string. + | 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 @@ -83,6 +96,8 @@ def add_beam_stopping_rate(beam_species, target_ion, target_charge, rate, reposi rate['nref'] = float(rate['nref']) rate['tref'] = float(rate['tref']) rate['sref'] = float(rate['sref']) + if 'reference' in rate: + rate['reference'] = str(rate['reference']) path = os.path.join(repository_path, 'beam/stopping/{}/{}/{}.json'.format(beam_species.symbol.lower(), target_ion.symbol.lower(), target_charge)) @@ -98,11 +113,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 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 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. + | 'reference': Optional data reference string. + | The total beam stopping rate: s = sen * st / sref. """ for beam_species, target_ions in rates.items(): @@ -112,6 +146,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 fields: + + | '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. + | 'reference': Optional data reference string. + | 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/create.py b/cherab/atomic/repository/create.py similarity index 93% rename from cherab/openadas/repository/create.py rename to cherab/atomic/repository/create.py index 6b9a4f33..7f417aec 100644 --- a/cherab/openadas/repository/create.py +++ b/cherab/atomic/repository/create.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"); @@ -16,15 +16,36 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. +import shutil +import os + from cherab.core.utility import RecursiveDict from cherab.core.atomic.elements import * from cherab.openadas.install import install_files -from cherab.openadas import repository +from cherab.atomic import repository +from .utility import DEFAULT_REPOSITORY_PATH + + +def _copy_default_data(repository_path=None): + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + default_data_path = os.path.join(os.path.dirname(__file__), "default_data/") + + # Gaunt factor + # The Maxwellian-averaged free-free Gaunt factor interpolated over the data from Table A.1 in + # M.A. de Avillez and D. Breitschwerdt, "Temperature-averaged and total free-free Gaunt factors + # for κ and Maxwellian distributions of electrons", 2015, Astron. & Astrophys. 580, + # A124, https://www.aanda.org/articles/aa/full_html/2015/08/aa26104-15/aa26104-15.html>. + gaunt_dir = os.path.join(repository_path, 'gaunt') + if not os.path.isdir(gaunt_dir): + os.makedirs(gaunt_dir) + shutil.copy(os.path.join(default_data_path, 'maxwellian_free_free_gaunt_factor.json'), + os.path.join(gaunt_dir, 'free_free_gaunt_factor.json')) def populate(download=True, repository_path=None, adas_path=None): """ - Populates the OpenADAS repository with a typical set of rates and wavelengths. + Populates the local atomic data repository with the default atomic data and + a typical set of rates and wavelengths from OpenADAS. If an ADAS file is not note found an attempt will be made to download the file from the OpenADAS website. This behaviour can be disabled by setting @@ -35,6 +56,9 @@ def populate(download=True, repository_path=None, adas_path=None): :param adas_path: Alternate path in which to search for ADAS files (default=None) . """ + # copy default data + _copy_default_data(repository_path) + # install a common selection of open adas files rates = { 'adf11scd': ( diff --git a/cherab/core/atomic/data/maxwellian_free_free_gaunt_factor.json b/cherab/atomic/repository/default_data/maxwellian_free_free_gaunt_factor.json similarity index 100% rename from cherab/core/atomic/data/maxwellian_free_free_gaunt_factor.json rename to cherab/atomic/repository/default_data/maxwellian_free_free_gaunt_factor.json diff --git a/cherab/atomic/repository/fractional_abundance.py b/cherab/atomic/repository/fractional_abundance.py new file mode 100644 index 00000000..55c4e663 --- /dev/null +++ b/cherab/atomic/repository/fractional_abundance.py @@ -0,0 +1,166 @@ + +# 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 os +import json +import numpy as np + +from cherab.core.atomic import Element +from cherab.core.utility import RecursiveDict +from .utility import DEFAULT_REPOSITORY_PATH, valid_charge + + +def add_fractional_abundance(species, charge, data, repository_path=None): + """ + Adds fractional abundance for a single species to the repository. + + If adding multiple abundances, consider using the update_fractional_abundances() + function instead. The update function avoids repeatedly opening and closing + the data files. + + :param species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param data: Fractional abundance dictionary containing the following fields: + + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'fractional_abundance': array-like of size (N, M) with fractional abundance. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_fractional_abundances({ + species: { + charge: data + } + }, repository_path) + + +def update_fractional_abundances(data, repository_path=None): + """ + Update the files for the fractional abundances: + /fractional_abundance/.json + in the atomic data repository. + + File contains multiple fractional abundances, indexed by the ion's charge state. + + :param data: Dictionary in the form {: {: }}, where + + | is the plasma species (Element/Isotope), + | is the charge of the plasma species, + | is the fractional abundance dictionary containing the following fields: + | 'ne': array-like of size (N) with electron density in m^-3, + | 'te': array-like of size (M) with electron temperature in eV, + | 'fractional_abundance': array-like of size (N, M) with the fractional abundance. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, abundances in data.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'fractional_abundance/{}.json'.format(species.symbol.lower())) + + # read in any existing fractional abundances + try: + with open(path, 'r') as f: + content = RecursiveDict.from_dict(json.load(f)) + except FileNotFoundError: + content = RecursiveDict() + + for charge, abundance in abundances.items(): + + if not valid_charge(species, charge): + raise ValueError('The charge state is larger than the number of protons in the specified species.') + + # sanitise and validate abundance data + te = np.array(abundance['te'], np.float64) + ne = np.array(abundance['ne'], np.float64) + fractional_abundance = np.array(abundance['fractional_abundance'], np.float64) + + if ne.ndim != 1: + raise ValueError('Density array must be a 1D array.') + + if te.ndim != 1: + raise ValueError('Temperature array must be a 1D array.') + + if (ne.shape[0], te.shape[0]) != fractional_abundance.shape: + raise ValueError('Electron temperature, density and abundance data arrays have inconsistent sizes.') + + # update file content with new fractional abundance + content[str(charge)] = { + 'te': te.tolist(), + 'ne': ne.tolist(), + 'fractional_abundance': fractional_abundance.tolist(), + } + if 'reference' in abundance: + content[str(charge)]['reference'] = str(abundance['reference']) + + # 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_fractional_abundance(element, charge, repository_path=None): + """ + Reads fractional abundance 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 data: Fractional abundance dictionary containing the following fields: + + | 'ne': 1D array of size (N) with electron density in m^-3, + | 'te': 1D array of size (M) with electron temperature in eV, + | 'fractional_abundance': 2D array of size (N, M) with fractional abundance. + | 'reference': Optional data reference string. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + path = os.path.join(repository_path, 'fractional_abundance/{}.json'.format(element.symbol.lower())) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[str(charge)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested fractional abundance (element={}, charge={})' + ' is not available.'.format(element.symbol, charge)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['fractional_abundance'] = np.array(d['fractional_abundance'], np.float64) + + return d diff --git a/cherab/atomic/repository/gaunt.py b/cherab/atomic/repository/gaunt.py new file mode 100644 index 00000000..ff2ffaee --- /dev/null +++ b/cherab/atomic/repository/gaunt.py @@ -0,0 +1,109 @@ +# 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 os +import json +import numpy as np +from .utility import DEFAULT_REPOSITORY_PATH + +""" +Utilities for managing the local atomic repository - Gaunt factor section. +""" + + +def update_free_free_gaunt_factor(data, repository_path=None): + r""" + Updates the free-free Gaunt factor in the repository. + The Gaunt factor is defined in the space of parameters: + :math:`u = h{\nu}/kT` and :math:`{\gamma}^{2} = Z^{2}Ry/kT`. + See T.R. Carson, 1988, Astron. & Astrophys., 189, + `319 `_ for details. + + :param data: Dictionary containing the Gaunt factor data with the following keys: + | 'u': A 1D array-like of size (N) of real values. + | 'gamma2': A 1D array-like of size (M) of real values. + | 'gaunt_factor': 2D array of size (N, M) of real values storing the Gaunt factor values at u, gamma2. + 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + u = np.array(data['u'], np.float64) + gamma2 = np.array(data['gamma2'], np.float64) + gaunt_factor = np.array(data['gaunt_factor'], np.float64) + + if u.ndim != 1: + raise ValueError('The "u" array must be a 1D array.') + + if gamma2.ndim != 1: + raise ValueError('The "gamma2" array must be a 1D array') + + if (u.shape[0], gamma2.shape[0]) != gaunt_factor.shape: + raise ValueError('The "u", "gamma2" and "gaunt factor" data arrays have inconsistent sizes.') + + content = { + 'u': u.tolist(), + 'gamma2': gamma2.tolist(), + 'gaunt_factor': gaunt_factor.tolist() + } + if 'reference' in data: + content['reference'] = str(data['reference']) + + path = os.path.join(repository_path, 'gaunt/free_free_gaunt_factor.json') + # 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_free_free_gaunt_factor(repository_path=None): + r""" + Reads the free-free Gaunt factor from the repository. + The Gaunt factor is defined in the space of parameters: + :math:`u = h{\nu}/kT` and :math:`{\gamma}^{2} = Z^{2}Ry/kT`. + See T.R. Carson, 1988, Astron. & Astrophys., 189, + `319 `_ for details. + + :return data: Dictionary containing the Gaunt factor data with the following keys: + + | 'u': A 1D array of size (N) of real values. + | 'gamma2': A 1D array of size (M) of real values. + | 'gaunt_factor': 2D array of size (N, M) of real values storing the Gaunt factor values at u, gamma2. + | 'reference': Optional data reference string. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + path = os.path.join(repository_path, 'gaunt/free_free_gaunt_factor.json') + try: + with open(path, 'r') as f: + data = json.load(f) + except (FileNotFoundError): + raise RuntimeError('Free-free Gaunt factor is missing in the atomic repository.') + + # convert to numpy arrays + data['u'] = np.array(data['u'], np.float64) + data['gamma2'] = np.array(data['gamma2'], np.float64) + data['gaunt_factor'] = np.array(data['gaunt_factor'], np.float64) + + return data diff --git a/cherab/openadas/repository/pec.py b/cherab/atomic/repository/pec.py similarity index 55% rename from cherab/openadas/repository/pec.py rename to cherab/atomic/repository/pec.py index 8eb867fc..98df52a9 100644 --- a/cherab/openadas/repository/pec.py +++ b/cherab/atomic/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-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -36,12 +36,17 @@ 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 fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -63,12 +68,17 @@ 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 fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -84,18 +94,23 @@ 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 fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. """ update_pec_rates({ @@ -111,9 +126,25 @@ 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. - /pec///.json + :param rates: Dictionary in the form: + + | { : { : { : { : } } } }, 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. """ valid_classes = [ @@ -172,6 +203,8 @@ def update_pec_rates(rates, repository_path=None): 'te': data['te'].tolist(), 'rate': data['rate'].tolist() } + if 'reference' in data: + content[key]['reference'] = str(data['reference']) # create directory structure if missing directory = os.path.dirname(path) @@ -184,14 +217,65 @@ 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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + 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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + 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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + return _get_pec_rate('thermalcx', element, charge, transition, repository_path) @@ -213,4 +297,3 @@ def _get_pec_rate(cls, element, charge, transition, repository_path=None): d['rate'] = np.array(d['rate'], np.float64) return d - diff --git a/cherab/atomic/repository/radiated_power.py b/cherab/atomic/repository/radiated_power.py new file mode 100644 index 00000000..7e518b75 --- /dev/null +++ b/cherab/atomic/repository/radiated_power.py @@ -0,0 +1,449 @@ + +# 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 os +import json +import numpy as np + +from cherab.core.atomic import Element +from cherab.core.utility import RecursiveDict +from .utility import DEFAULT_REPOSITORY_PATH, valid_charge + + +def add_line_power_rate(species, charge, rate, repository_path=None): + """ + 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 species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Line radiated power rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_line_power_rates({ + species: { + charge: rate + } + }, repository_path) + + +def update_line_power_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, rate_data in rates.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'radiated_power/line/{}.json'.format(species.symbol.lower())) + + _update_and_write_bivariate_rate(species, rate_data, path) + + +def add_continuum_power_rate(species, charge, rate, repository_path=None): + """ + 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 species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: Continuum power rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_line_power_rates({ + species: { + charge: rate + } + }, repository_path) + + +def update_continuum_power_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, rate_data in rates.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'radiated_power/continuum/{}.json'.format(species.symbol.lower())) + + _update_and_write_bivariate_rate(species, rate_data, path) + + +def add_cx_power_rate(species, charge, rate, repository_path=None): + """ + 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 species: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param rate: CX power rate dictionary containing the following fields: + + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + update_line_power_rates({ + species: { + charge: rate + } + }, repository_path) + + +def update_cx_power_rates(rates, repository_path=None): + """ + 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 fields: + | '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. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for species, rate_data in rates.items(): + + # sanitise and validate arguments + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'radiated_power/cx/{}.json'.format(species.symbol.lower())) + + _update_and_write_bivariate_rate(species, rate_data, path) + + +def _update_and_write_bivariate_rate(species, rate_data, path): + + # read in any existing rates + try: + with open(path, 'r') as f: + content = RecursiveDict.from_dict(json.load(f)) + except FileNotFoundError: + content = RecursiveDict() + + for charge, rates in rate_data.items(): + + if not valid_charge(species, charge): + raise ValueError('The charge state is larger than the number of protons in the specified species.') + + # sanitise and validate rate data + te = np.array(rates['te'], np.float64) + ne = np.array(rates['ne'], np.float64) + rate_table = np.array(rates['rate'], np.float64) + + if ne.ndim != 1: + raise ValueError('Density array must be a 1D array.') + + if te.ndim != 1: + raise ValueError('Temperature array must be a 1D array.') + + if (ne.shape[0], te.shape[0]) != rate_table.shape: + raise ValueError('Electron temperature, density and rate data arrays have inconsistent sizes.') + + # update file content with new rate + content[str(charge)] = { + 'te': te.tolist(), + 'ne': ne.tolist(), + 'rate': rate_table.tolist(), + } + if 'reference' in rates: + content[str(charge)]['reference'] = str(rates['reference']) + + # 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 add_total_power_rate(species, rate, repository_path=None): + """ + Adds a single total radiated power rate in equilibrium conditions to the repository. + + :param species: Plasma species (Element/Isotope). + :param rate: Total radiated power rate dictionary containing the following fields: + + | '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 total radiated power rate in W.m^3. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + if not isinstance(species, Element): + raise TypeError('The species must be an Element object.') + + path = os.path.join(repository_path, 'radiated_power/total/{}.json'.format(species.symbol.lower())) + + # sanitise and validate rate data + te = np.array(rate['te'], np.float64) + ne = np.array(rate['ne'], np.float64) + rate_table = np.array(rate['rate'], np.float64) + + if ne.ndim != 1: + raise ValueError('Density array must be a 1D array.') + + if te.ndim != 1: + raise ValueError('Temperature array must be a 1D array.') + + if (ne.shape[0], te.shape[0]) != rate_table.shape: + raise ValueError('Electron temperature, density and rate data arrays have inconsistent sizes.') + + # update file content with new rate + content = { + 'te': te.tolist(), + 'ne': ne.tolist(), + 'rate': rate_table.tolist(), + } + if 'reference' in rate: + content['reference'] = str(rate['reference']) + + # 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 update_total_power_rates(rates, repository_path=None): + """ + Update the files for the total radiated power rates in equilibrium conditions: + /radiated_power/total/.json + in the atomic data repository. + + :param rates: Dictionary in the form {: }, where + + | is the plasma species (Element/Isotope), + | is the total radiated power rate dictionary containing the following fields: + | '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 total radiated power rate in W.m^3. + | 'reference': Optional data reference string. + + :param repository_path: Path to the atomic data repository. + """ + for species, rate in rates: + add_total_power_rate(species, rate, repository_path=repository_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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + + return _get_radiated_power_rate('line', element, charge, repository_path) + + +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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + + return _get_radiated_power_rate('continuum', element, charge, repository_path) + + +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 fields: + + | '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. + | 'reference': Optional data reference string. + """ + + return _get_radiated_power_rate('cx', element, charge, repository_path) + + +def _get_radiated_power_rate(cls, element, charge, repository_path=None): + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + path = os.path.join(repository_path, 'radiated_power/{}/{}.json'.format(cls, element.symbol.lower())) + try: + with open(path, 'r') as f: + content = json.load(f) + d = content[str(charge)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested {} radiated power rate (element={}, charge={})' + ' is not available.'.format(cls, element.symbol, charge)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['rate'] = np.array(d['rate'], np.float64) + + return d + + +def get_total_radiated_power_rate(element, repository_path=None): + """ + Reads the total radiated power rate in equilibrium conditions for the given species + from the atomic data repository. + + :param element: Plasma species (Element/Isotope). + :param repository_path: Path to the atomic data repository. + + :return rate: Total radiated power rate dictionary containing the following fields: + + | '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 the total radiated power rate in W.m^3. + | 'reference': Optional data reference string. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + path = os.path.join(repository_path, 'radiated_power/total/{}.json'.format(element.symbol.lower())) + try: + with open(path, 'r') as f: + d = json.load(f) + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested total radiated power rate (element={}) is not available.'.format(element.symbol)) + + # convert to numpy arrays + d['ne'] = np.array(d['ne'], np.float64) + d['te'] = np.array(d['te'], np.float64) + d['rate'] = np.array(d['rate'], np.float64) + + return d diff --git a/cherab/openadas/repository/utility.py b/cherab/atomic/repository/utility.py similarity index 79% rename from cherab/openadas/repository/utility.py rename to cherab/atomic/repository/utility.py index f47c1964..1c67945f 100644 --- a/cherab/openadas/repository/utility.py +++ b/cherab/atomic/repository/utility.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -22,7 +22,11 @@ Utilities for managing the local rate repository. """ -DEFAULT_REPOSITORY_PATH = os.path.expanduser('~/.cherab/openadas/repository') + +try: + DEFAULT_REPOSITORY_PATH = os.environ["CHERAB_ATOMIC_DATA"] +except KeyError: + DEFAULT_REPOSITORY_PATH = os.path.expanduser('~/.cherab/atomicdata/default_repository') def encode_transition(transition): @@ -48,6 +52,3 @@ def valid_charge(element, charge): :return: True/False. """ return charge <= element.atomic_number - - - diff --git a/cherab/openadas/repository/wavelength.py b/cherab/atomic/repository/wavelength.py similarity index 70% rename from cherab/openadas/repository/wavelength.py rename to cherab/atomic/repository/wavelength.py index 5981e9e6..c575555d 100644 --- a/cherab/openadas/repository/wavelength.py +++ b/cherab/atomic/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-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -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)) @@ -100,4 +126,3 @@ def get_wavelength(element, charge, transition, repository_path=None): except (FileNotFoundError, KeyError): raise RuntimeError('Requested wavelength (element={}, charge={}, transition={})' ' is not available.'.format(element.symbol, charge, transition)) - diff --git a/cherab/atomic/repository/zeeman.py b/cherab/atomic/repository/zeeman.py new file mode 100644 index 00000000..49a0af2e --- /dev/null +++ b/cherab/atomic/repository/zeeman.py @@ -0,0 +1,201 @@ +# 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 os +import json +import numpy as np +from cherab.core.utility import RecursiveDict +from cherab.core.atomic import Element +from .utility import DEFAULT_REPOSITORY_PATH, valid_charge, encode_transition + +""" +Utilities for managing the local atomic repository - Zeeman splitting section. +""" + + +def add_zeeman_structure(element, charge, transition, data, repository_path=None): + r""" + Adds a single Zeeman multiplet structure to the repository. + + If adding multiple structures, consider using the update_zeeman_structures() function + instead. The update function avoid repeatedly opening and closing the Zeeman structure + files. + + :param element: Plasma species (Element/Isotope). + :param charge: Charge of the plasma species. + :param transition: Tuple containing (initial level, final level). + :param data: A dictionary containing the central wavelengths and relative intensities + of the polarised Zeeman components with respect to the magnetic field strength. + It has the following keys: + | 'b': A 1D array of shape (N,) with magnetic field strength. + | 'polarisation': A 1D array of shape (M,) with component polarisation + | 0 for :math:`\pi`-polarisation, + | -1 for :math:`\sigma-`-polarisation, + | 1 for :math:`\sigma+`-polarisation. + | 'wavelength': A 2D array of shape (M, N) with component wavelength as functions of + | magnetic field strength. + | 'ratio': A 2D array of shape (M, N) with component relative intensities + | as functions of magnetic field strength. + | 'reference': Optional string containg the reference to the data source. + + :param repository_path: Path to the atomic data repository. + """ + + update_zeeman_structures({ + element: { + charge: { + transition: data + } + } + }, repository_path) + + +def update_zeeman_structures(zeeman_structures, repository_path=None): + r""" + Updates the Zeeman multiplet structure files lineshape/zeeman/multiplet//.json + in the atomic data repository. + + File contains multiple Zeeman structures, indexed by the transition. + + :param zeeman_structures: 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 dictionary containing the central wavelengths and relative intensities + | of the polaraised Zeeman components with respect to the magnetic field strength. + | It has the following keys: + | 'b': A 1D array of shape (N,) with magnetic field strength. + | 'polarisation': A 1D array of shape (M,) with component polarisation + | 0 for :math:`\pi`-polarisation, + | -1 for :math:`\sigma-`-polarisation, + | 1 for :math:`\sigma+`-polarisation. + | 'wavelength': A 2D array of shape (M, N) with component wavelength as + | functions of magnetic field strength. + | 'ratio': A 2D array of shape (M, N) with component relative intensities + | as functions of magnetic field strength. + | 'reference': Optional string containg the reference to the data source. + + :param repository_path: Path to the atomic data repository. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + + for element, charge_states in zeeman_structures.items(): + for charge, transitions in charge_states.items(): + + # sanitise and validate + if not isinstance(element, Element): + raise TypeError('The element must be an Element object.') + + if not valid_charge(element, charge): + raise ValueError('Charge state is larger than the number of protons in the element.') + + path = os.path.join(repository_path, 'lineshape/zeeman/multiplet/{}/{}.json'.format(element.symbol.lower(), charge)) + + # read in any existing zeeman structures + 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 + data['b'] = np.array(data['b'], np.float64) + data['polarisation'] = np.array(data['polarisation'], np.int32) + data['wavelength'] = np.array(data['wavelength'], np.float64) + data['ratio'] = np.array(data['ratio'], np.float64) + + if data['b'].ndim != 1: + raise ValueError('Magnetic field strength array must be a 1D array.') + + if data['polarisation'].ndim != 1: + raise ValueError('Polarisation array must be a 1D array.') + + if (data['polarisation'].shape[0], data['b'].shape[0]) != data['wavelength'].shape: + raise ValueError('Polarisation, magnetic field strength and wavelength data arrays have inconsistent sizes.') + + if data['wavelength'].shape != data['ratio'].shape: + raise ValueError('Wavelength and ratio data arrays have inconsistent sizes.') + + content[key] = { + 'b': data['b'].tolist(), + 'polarisation': data['polarisation'].tolist(), + 'wavelength': data['wavelength'].tolist(), + 'ratio': data['ratio'].tolist() + } + if 'reference' in data: + content[key]['reference'] = str(data['reference']) + + # 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_zeeman_structure(element, charge, transition, repository_path=None): + r""" + Reads the Zeeman multiplet structure 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 data: A dictionary containing the central wavelengths and relative intensities + of the polaraised Zeeman components with respect to the magnetic field strength. + It has the following keys: + | 'b': A 1D array of shape (N,) with magnetic field strength. + | 'polarisation': A 1D array of shape (M,) with component polarisation + | 0 for :math:`\pi`-polarisation, + | -1 for :math:`\sigma-`-polarisation, + | 1 for :math:`\sigma+`-polarisation. + | 'wavelength': A 2D array of shape (M, N) with component wavelength as functions of + | magnetic field strength. + | 'ratio': A 2D array of shape (M, N) with component relative intensities + | as functions of magnetic field strength. + | 'reference': Optional string containg the reference to the data source. + """ + + repository_path = repository_path or DEFAULT_REPOSITORY_PATH + path = os.path.join(repository_path, 'lineshape/zeeman/multiplet/{}/{}.json'.format(element.symbol.lower(), charge)) + try: + with open(path, 'r') as f: + content = json.load(f) + data = content[encode_transition(transition)] + except (FileNotFoundError, KeyError): + raise RuntimeError('Requested Zeeman structure (element={}, charge={}, transition={})' + ' is not available.'.format(element.symbol, charge, transition)) + + # convert to numpy arrays + data['b'] = np.array(data['b'], np.float64) + data['polarisation'] = np.array(data['polarisation'], np.int32) + data['wavelength'] = np.array(data['wavelength'], np.float64) + data['ratio'] = np.array(data['ratio'], np.float64) + + return data diff --git a/cherab/atomic/zeeman/__init__.pxd b/cherab/atomic/zeeman/__init__.pxd new file mode 100644 index 00000000..03de1a25 --- /dev/null +++ b/cherab/atomic/zeeman/__init__.pxd @@ -0,0 +1,19 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from cherab.atomic.zeeman.zeeman cimport * diff --git a/cherab/atomic/zeeman/__init__.py b/cherab/atomic/zeeman/__init__.py new file mode 100644 index 00000000..42f7658a --- /dev/null +++ b/cherab/atomic/zeeman/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from .zeeman import ZeemanStructure diff --git a/cherab/atomic/zeeman/zeeman.pxd b/cherab/atomic/zeeman/zeeman.pxd new file mode 100644 index 00000000..09352b09 --- /dev/null +++ b/cherab/atomic/zeeman/zeeman.pxd @@ -0,0 +1,26 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. +from cherab.core.atomic.zeeman cimport ZeemanStructure as CoreZeemanStructure + + +cdef class ZeemanStructure(CoreZeemanStructure): + + cdef: + readonly tuple b_range + readonly dict raw_data + list _pi_components, _sigma_plus_components, _sigma_minus_components diff --git a/cherab/atomic/zeeman/zeeman.pyx b/cherab/atomic/zeeman/zeeman.pyx new file mode 100644 index 00000000..e030c83f --- /dev/null +++ b/cherab/atomic/zeeman/zeeman.pyx @@ -0,0 +1,132 @@ +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +import numpy as np +cimport numpy as np +from libc.math cimport INFINITY +from raysect.core.math.function.float cimport Function1D, Interpolator1DArray + +cimport cython + +np.import_array() + +DEF MULTIPLET_WAVELENGTH = 0 +DEF MULTIPLET_RATIO = 1 + +DEF PI_POLARISATION = 0 +DEF SIGMA_PLUS_POLARISATION = 1 +DEF SIGMA_MINUS_POLARISATION = -1 + +cdef class ZeemanStructure(CoreZeemanStructure): + r""" + Provides wavelengths and ratios of + :math:`\pi`-/:math:`\sigma`-polarised Zeeman components for any given value of + magnetic field strength. + + :param dict data: Dictionary containing the central wavelengths and relative + intensities of Zeeman components with the following keys: + + | 'b': A 1D array of shape (N,) with magnetic field strength. + | 'polarisation': A 1D array of shape (M,) with component polarisation + | 0 for :math:`\pi`-polarisation, + | -1 for :math:`\sigma-`-polarisation, + | 1 for :math:`\sigma+`-polarisation. + | 'wavelength': A 2D array of shape (M, N) with component wavelength as functions of + | magnetic field strength. + | 'ratio': A 2D array of shape (M, N) with component relative intensities + | as functions of magnetic field strength. + + :param bint extrapolate: Enable extrapolation (default=False). + + :ivar tuple b_range: The interpolation range of magnetic field strength. + :ivar dict raw_data: Dictionary containing the raw data. + """ + + def __init__(self, dict data, bint extrapolate=False): + + self.raw_data = data + + b = data['b'] + polarisation = data['polarisation'] + wvl = data['wavelength'] + ratio = data['ratio'] + + self.b_range = b.min(), b.max() + + extrapolation_type = 'quadratic' if extrapolate else 'none' + + components = {PI_POLARISATION: [], SIGMA_PLUS_POLARISATION: [], SIGMA_MINUS_POLARISATION: []} + + for pol in (PI_POLARISATION, SIGMA_PLUS_POLARISATION, SIGMA_MINUS_POLARISATION): + indx, = np.where(polarisation == pol) + for i in indx: + wvl_func = Interpolator1DArray(b, wvl[i], 'cubic', extrapolation_type, INFINITY) + ratio_func = Interpolator1DArray(b, ratio[i], 'cubic', extrapolation_type, INFINITY) + components[pol].append((wvl_func, ratio_func)) + + self._pi_components = components[PI_POLARISATION] + self._sigma_plus_components = components[SIGMA_PLUS_POLARISATION] + self._sigma_minus_components = components[SIGMA_MINUS_POLARISATION] + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + @cython.cdivision(True) + cdef double[:, :] evaluate(self, double b, int polarisation): + + cdef int i + cdef np.npy_intp multiplet_shape[2] + cdef double ratio_sum + cdef np.ndarray multiplet + cdef double[:, :] multiplet_mv + cdef Function1D wavelength, ratio + cdef list components + + if b < 0: # safety check in case if used stand-alone + raise ValueError('Argument "b" (magnetic field strength) must be non-negative.') + + if polarisation == PI_POLARISATION: + components = self._pi_components + elif polarisation == SIGMA_PLUS_POLARISATION: + components = self._sigma_plus_components + elif polarisation == SIGMA_MINUS_POLARISATION: + components = self._sigma_minus_components + else: + raise ValueError('Argument "polarisation" must be 0, 1 or -1, {} given.'.format(polarisation)) + + multiplet_shape[0] = 2 + multiplet_shape[1] = len(components) + multiplet = np.PyArray_SimpleNew(2, multiplet_shape, np.NPY_FLOAT64) + multiplet_mv = multiplet + + ratio_sum = 0 + for i in range(len(components)): + wavelength = components[i][MULTIPLET_WAVELENGTH] + ratio = components[i][MULTIPLET_RATIO] + + multiplet_mv[MULTIPLET_WAVELENGTH, i] = wavelength.evaluate(b) + multiplet_mv[MULTIPLET_RATIO, i] = ratio.evaluate(b) + + ratio_sum += multiplet_mv[MULTIPLET_RATIO, i] + + # normalising ratios + if ratio_sum > 0: + for i in range(multiplet_mv.shape[1]): + multiplet_mv[MULTIPLET_RATIO, i] /= ratio_sum + + return multiplet_mv diff --git a/cherab/core/atomic/__init__.pxd b/cherab/core/atomic/__init__.pxd index 3b2aa5c6..f5d64f5e 100644 --- a/cherab/core/atomic/__init__.pxd +++ b/cherab/core/atomic/__init__.pxd @@ -22,5 +22,5 @@ from cherab.core.atomic.line cimport Line from cherab.core.atomic.interface cimport AtomicData from cherab.core.atomic.rates cimport * from cherab.core.atomic.zeeman cimport ZeemanStructure -from cherab.core.atomic.gaunt cimport * +from cherab.core.atomic.gaunt cimport FreeFreeGauntFactor diff --git a/cherab/core/atomic/__init__.py b/cherab/core/atomic/__init__.py index 35a7f80e..a76eb15b 100644 --- a/cherab/core/atomic/__init__.py +++ b/cherab/core/atomic/__init__.py @@ -22,4 +22,4 @@ from .interface import AtomicData from .rates import * from .zeeman import ZeemanStructure -from .gaunt import * +from .gaunt import FreeFreeGauntFactor diff --git a/cherab/core/atomic/gaunt.pxd b/cherab/core/atomic/gaunt.pxd index 827831b6..a4a3c934 100644 --- a/cherab/core/atomic/gaunt.pxd +++ b/cherab/core/atomic/gaunt.pxd @@ -1,6 +1,6 @@ -# Copyright 2016-2022 Euratom -# Copyright 2016-2022 United Kingdom Atomic Energy Authority -# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,24 +16,7 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from cherab.core.math cimport Function2D - cdef class FreeFreeGauntFactor(): cpdef double evaluate(self, double z, double temperature, double wavelength) except? -1e999 - - -cdef class InterpolatedFreeFreeGauntFactor(FreeFreeGauntFactor): - - cdef: - readonly tuple u_range, gamma2_range - readonly dict raw_data - double _u_min, _u_max, _gamma2_min, _gamma2_max - Function2D _gaunt_factor - - -cdef class MaxwellianFreeFreeGauntFactor(InterpolatedFreeFreeGauntFactor): - - pass - diff --git a/cherab/core/atomic/gaunt.pyx b/cherab/core/atomic/gaunt.pyx index eb1dbe59..ec14474b 100644 --- a/cherab/core/atomic/gaunt.pyx +++ b/cherab/core/atomic/gaunt.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2022 Euratom -# Copyright 2016-2022 United Kingdom Atomic Energy Authority -# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,21 +16,6 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from os import path -import numpy as np -import json - -from libc.math cimport log10, log, M_PI, sqrt -from raysect.core.math.function.float cimport Interpolator2DArray -from cherab.core.utility.constants cimport RYDBERG_CONSTANT_EV, SPEED_OF_LIGHT, ELEMENTARY_CHARGE, PLANCK_CONSTANT - -cimport cython - - -DEF EULER_GAMMA = 0.5772156649015329 - -cdef double PH_TO_EV_FACTOR = PLANCK_CONSTANT * SPEED_OF_LIGHT * 1e9 / ELEMENTARY_CHARGE - cdef class FreeFreeGauntFactor(): """ @@ -61,98 +46,3 @@ cdef class FreeFreeGauntFactor(): """ return self.evaluate(z, temperature, wavelength) - - -cdef class InterpolatedFreeFreeGauntFactor(FreeFreeGauntFactor): - r""" - The temperature-averaged free-free Gaunt factors interpolated in the space of parameters: - :math:`u = h{\nu}/kT` and :math:`{\gamma}^{2} = Z^{2}Ry/kT`. - See T.R. Carson, 1988, Astron. & Astrophys., 189, - `319 `_ for details. - - The cubic interpolation in a semi-log space is used. - - The Born approximation is used outside the interpolation range. - - :param object u: A 1D array-like object of real values. - :param object gamma2: A 1D array-like object of real values. - :param object gaunt_factor: 2D array-like object of real values - storing the Gaunt factor values at u, gamma2. - - :ivar tuple u_range: The interpolation range of `u` parameter. - :ivar tuple gamma2_range: The interpolation range of :math:`\\gamma^2` parameter. - :ivar dict raw_data: Dictionary containing the raw data. - """ - - def __init__(self, object u, object gamma2, object gaunt_factor): - - u = np.array(u, dtype=np.float64) - u.flags.writeable = False - gamma2 = np.array(gamma2, dtype=np.float64) - gamma2.flags.writeable = False - gaunt_factor = np.array(gaunt_factor, dtype=np.float64) - gaunt_factor.flags.writeable = False - - self.raw_data = {'u': u, 'gamma2': gamma2, 'gaunt_factor': gaunt_factor} - - self._u_min = u.min() - self._u_max = u.max() - self._gamma2_min = gamma2.min() - self._gamma2_max = gamma2.max() - - self.u_range = (self._u_min, self._u_max) - self.gamma2_range = (self._gamma2_min, self._gamma2_max) - - self._gaunt_factor = Interpolator2DArray(np.log10(u), np.log10(gamma2), gaunt_factor, 'cubic', 'none', 0, 0) - - @cython.cdivision(True) - cpdef double evaluate(self, double z, double temperature, double wavelength) except? -1e999: - """ - Returns the temperature-averaged free-free Gaunt factor for the supplied parameters. - - :param double z: Species charge or effective plasma charge. - :param double temperature: Electron temperature in eV. - :param double wavelength: Spectral wavelength. - - :return: free-free Gaunt factor - """ - - cdef: - double u, gamma2 - - if z == 0: - - return 0 - - gamma2 = z * z * RYDBERG_CONSTANT_EV / temperature - u = PH_TO_EV_FACTOR / (temperature * wavelength) - - # classical limit - if u >= self._u_max or gamma2 >= self._gamma2_max: - - return 1 - - # Born approximation limit - if u < self._u_min or gamma2 < self._gamma2_min: - - return sqrt(3) / M_PI * (log(4 / u) - EULER_GAMMA) - - return self._gaunt_factor.evaluate(log10(u), log10(gamma2)) - - -cdef class MaxwellianFreeFreeGauntFactor(InterpolatedFreeFreeGauntFactor): - r""" - The Maxwellian-averaged free-free Gaunt factor interpolated over the data from Table A.1 in - M.A. de Avillez and D. Breitschwerdt, "Temperature-averaged and total free-free Gaunt factors - for κ and Maxwellian distributions of electrons", 2015, Astron. & Astrophys. 580, - `A124 `_. - - The Born approximation is used outside the interpolation range. - """ - - def __init__(self): - - with open(path.join(path.dirname(__file__), "data/maxwellian_free_free_gaunt_factor.json")) as f: - data = json.load(f) - - super().__init__(data['u'], data['gamma2'], data['gaunt_factor']) diff --git a/cherab/core/atomic/interface.pxd b/cherab/core/atomic/interface.pxd index 66d8faf8..cc246e0b 100644 --- a/cherab/core/atomic/interface.pxd +++ b/cherab/core/atomic/interface.pxd @@ -1,6 +1,6 @@ -# Copyright 2016-2022 Euratom -# Copyright 2016-2022 United Kingdom Atomic Energy Authority -# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -17,7 +17,6 @@ # under the Licence. from cherab.core.atomic.elements cimport Element -from cherab.core.atomic.line cimport Line from cherab.core.atomic.zeeman cimport ZeemanStructure from cherab.core.atomic.gaunt cimport FreeFreeGauntFactor from cherab.core.atomic.rates cimport * @@ -55,7 +54,6 @@ cdef class AtomicData: cpdef FractionalAbundance fractional_abundance(self, Element ion, int charge) - cpdef ZeemanStructure zeeman_structure(self, Line line, object b_field=*) + cpdef ZeemanStructure zeeman_structure(self, Element ion, int charge, tuple transition) cpdef FreeFreeGauntFactor free_free_gaunt_factor(self) - diff --git a/cherab/core/atomic/interface.pyx b/cherab/core/atomic/interface.pyx index 286ebfb4..4ed910fd 100644 --- a/cherab/core/atomic/interface.pyx +++ b/cherab/core/atomic/interface.pyx @@ -1,6 +1,6 @@ -# Copyright 2016-2022 Euratom -# Copyright 2016-2022 United Kingdom Atomic Energy Authority -# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,8 +16,6 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from .gaunt import MaxwellianFreeFreeGauntFactor - cdef class AtomicData: """ @@ -29,79 +27,120 @@ 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) """ 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): + """ + Recombination 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): + cpdef ZeemanStructure zeeman_structure(self, Element ion, int charge, tuple transition): + 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): """ - Returns the Maxwellian-averaged free-free Gaunt factor interpolated over the data - from Table A.1 in M.A. de Avillez and D. Breitschwerdt, 2015, Astron. & Astrophys. 580, - `A124 `_. - - The Born approximation is used outside the interpolation range. + Free-free Gaunt factor used in the bremsstrahlung emission model. """ - return MaxwellianFreeFreeGauntFactor() + raise NotImplementedError("The free_free_gaunt_factor() virtual method is not implemented for this atomic data source.") diff --git a/cherab/core/atomic/rates.pxd b/cherab/core/atomic/rates.pxd index 1f844122..8f6454d7 100644 --- a/cherab/core/atomic/rates.pxd +++ b/cherab/core/atomic/rates.pxd @@ -104,6 +104,5 @@ cdef class FractionalAbundance: cdef: readonly Element element readonly int charge - public str name cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999 diff --git a/cherab/core/atomic/rates.pyx b/cherab/core/atomic/rates.pyx index 25b94e7e..bbc910d9 100644 --- a/cherab/core/atomic/rates.pyx +++ b/cherab/core/atomic/rates.pyx @@ -37,7 +37,7 @@ cdef class IonisationRate: :param temperature: Electron temperature in eV. :param density: Electron density in m^-3 - :return: The effective ionisation rate in m^-3. + :return: The effective ionisation rate in m^3.s^-1. """ raise NotImplementedError("The evaluate() virtual method must be implemented.") @@ -59,7 +59,7 @@ cdef class RecombinationRate: :param temperature: Electron temperature in eV. :param density: Electron density in m^-3 - :return: The effective ionisation rate in m^-3. + :return: The effective ionisation rate in m^3.s^-1. """ raise NotImplementedError("The evaluate() virtual method must be implemented.") @@ -81,7 +81,7 @@ cdef class ThermalCXRate: :param temperature: Electron temperature in eV. :param density: Electron density in m^-3 - :return: The effective charge exchange rate in m^-3. + :return: The effective charge exchange rate in m^3.s^-1. """ raise NotImplementedError("The evaluate() virtual method must be implemented.") @@ -103,7 +103,7 @@ cdef class _PECRate: :param temperature: Receiver ion temperature in eV. :param density: Receiver ion density in m^-3 - :return: The effective PEC rate in W/m^3. + :return: The effective PEC rate in W.m^3. """ raise NotImplementedError("The evaluate() virtual method must be implemented.") @@ -310,12 +310,10 @@ cdef class FractionalAbundance: :param Element element: the radiating element :param int charge: the integer charge state for this ionisation stage - :param str name: optional label identifying this rate """ - def __init__(self, element, charge, name=''): + def __init__(self, element, charge): - self.name = name self.element = element if charge < 0: diff --git a/cherab/core/atomic/zeeman.pxd b/cherab/core/atomic/zeeman.pxd index 62279f64..66e69c14 100644 --- a/cherab/core/atomic/zeeman.pxd +++ b/cherab/core/atomic/zeeman.pxd @@ -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"); @@ -19,8 +19,4 @@ cdef class ZeemanStructure: - cdef: - list _pi_components, _sigma_plus_components, _sigma_minus_components - cdef double[:, :] evaluate(self, double b, int polarisation) - diff --git a/cherab/core/atomic/zeeman.pyx b/cherab/core/atomic/zeeman.pyx index 68382d32..4ee6c5ca 100644 --- a/cherab/core/atomic/zeeman.pyx +++ b/cherab/core/atomic/zeeman.pyx @@ -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"); @@ -17,116 +17,23 @@ # under the Licence. import numpy as np -cimport numpy as np -from cherab.core.math.function cimport autowrap_function1d, Function1D - -cimport cython - -np.import_array() - -DEF MULTIPLET_WAVELENGTH = 0 -DEF MULTIPLET_RATIO = 1 DEF PI_POLARISATION = 0 DEF SIGMA_PLUS_POLARISATION = 1 DEF SIGMA_MINUS_POLARISATION = -1 + cdef class ZeemanStructure(): r""" Provides wavelengths and ratios of :math:`\pi`-/:math:`\sigma`-polarised Zeeman components for any given value of magnetic field strength. - - :param list pi_components: A list of 2-tuples of Function1D objects that provide the - wavelengths and ratios of individual :math:`\pi`-polarised - Zeeman components for a given magnetic field strength: - [(wvl_func1, ratio_func1), (wvl_func2, ratio_func2), ...] - :param list sigma_plus_components: A list of 2-tuples of Function1D objects that provide the - wavelengths and ratios of individual - :math:`\sigma^{+}`-polarised Zeeman components for - a given magnetic field strength: - [(wvl_func1, ratio_func1), (wvl_func2, ratio_func2), ...] - :param list sigma_minus_components: A list of 2-tuples of Function1D objects that provide the - wavelengths and ratios of individual - :math:`\sigma^{-}`-polarised - Zeeman components for a given magnetic field strength: - [(wvl_func1, ratio_func1), (wvl_func2, ratio_func2), ...] """ - def __init__(self, pi_components, sigma_plus_components, sigma_minus_components): - - cdef tuple component - - self._pi_components = [] - self._sigma_plus_components = [] - self._sigma_minus_components = [] - - for component in pi_components: - if len(component) != 2: - raise ValueError('Argument "pi_components" must be a list of 2-tuples.') - self._pi_components.append((autowrap_function1d(component[MULTIPLET_WAVELENGTH]), - autowrap_function1d(component[MULTIPLET_RATIO]))) - - for component in sigma_plus_components: - if len(component) != 2: - raise ValueError('Argument "sigma_plus_components" must be a list of 2-tuples.') - self._sigma_plus_components.append((autowrap_function1d(component[MULTIPLET_WAVELENGTH]), - autowrap_function1d(component[MULTIPLET_RATIO]))) - - for component in sigma_minus_components: - if len(component) != 2: - raise ValueError('Argument "sigma_minus_components" must be a list of 2-tuples.') - self._sigma_minus_components.append((autowrap_function1d(component[MULTIPLET_WAVELENGTH]), - autowrap_function1d(component[MULTIPLET_RATIO]))) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - @cython.cdivision(True) cdef double[:, :] evaluate(self, double b, int polarisation): - cdef int i - cdef np.npy_intp multiplet_shape[2] - cdef double ratio_sum - cdef np.ndarray multiplet - cdef double[:, :] multiplet_mv - cdef Function1D wavelength, ratio - cdef list components - - if b < 0: # safety check in case if used stand-alone - raise ValueError('Argument "b" (magnetic field strength) must be non-negative.') - - if polarisation == PI_POLARISATION: - components = self._pi_components - elif polarisation == SIGMA_PLUS_POLARISATION: - components = self._sigma_plus_components - elif polarisation == SIGMA_MINUS_POLARISATION: - components = self._sigma_minus_components - else: - raise ValueError('Argument "polarisation" must be 0, 1 or -1, {} given.'.format(polarisation)) - - multiplet_shape[0] = 2 - multiplet_shape[1] = len(components) - multiplet = np.PyArray_SimpleNew(2, multiplet_shape, np.NPY_FLOAT64) - multiplet_mv = multiplet - - ratio_sum = 0 - for i in range(len(components)): - wavelength = components[i][MULTIPLET_WAVELENGTH] - ratio = components[i][MULTIPLET_RATIO] - - multiplet_mv[MULTIPLET_WAVELENGTH, i] = wavelength.evaluate(b) - multiplet_mv[MULTIPLET_RATIO, i] = ratio.evaluate(b) - - ratio_sum += multiplet_mv[MULTIPLET_RATIO, i] - - # normalising ratios - if ratio_sum > 0: - for i in range(multiplet_mv.shape[1]): - multiplet_mv[MULTIPLET_RATIO, i] /= ratio_sum - - return multiplet_mv + raise NotImplementedError("The evaluate() virtual method must be implemented.") def __call__(self, double b, str polarisation): diff --git a/cherab/core/model/plasma/bremsstrahlung.pxd b/cherab/core/model/plasma/bremsstrahlung.pxd index 44025c8c..dca37644 100644 --- a/cherab/core/model/plasma/bremsstrahlung.pxd +++ b/cherab/core/model/plasma/bremsstrahlung.pxd @@ -39,7 +39,6 @@ cdef class Bremsstrahlung(PlasmaModel): cdef: BremsFunction _brems_func - bint _user_provided_gaunt_factor Integrator1D _integrator cdef int _populate_cache(self) except -1 diff --git a/cherab/core/model/plasma/bremsstrahlung.pyx b/cherab/core/model/plasma/bremsstrahlung.pyx index 404750a2..ee657097 100644 --- a/cherab/core/model/plasma/bremsstrahlung.pyx +++ b/cherab/core/model/plasma/bremsstrahlung.pyx @@ -119,35 +119,20 @@ cdef class Bremsstrahlung(PlasmaModel): :ivar Plasma plasma: The plasma to which this emission model is attached. Default is None. :ivar AtomicData atomic_data: The atomic data provider for this model. Default is None. - :ivar FreeFreeGauntFactor gaunt_factor: Free-free Gaunt factor as a function of Z, Te and - wavelength. If not provided, - the `atomic_data` is used. :ivar Integrator1D integrator: Integrator1D instance to integrate Bremsstrahlung radiation over the spectral bin. Default is `GaussianQuadrature`. """ - def __init__(self, Plasma plasma=None, AtomicData atomic_data=None, FreeFreeGauntFactor gaunt_factor=None, Integrator1D integrator=None): + def __init__(self, Plasma plasma=None, AtomicData atomic_data=None, Integrator1D integrator=None): super().__init__(plasma, atomic_data) self._brems_func = BremsFunction.__new__(BremsFunction) - self.gaunt_factor = gaunt_factor self.integrator = integrator or GaussianQuadrature() # ensure that cache is initialised self._change() - @property - def gaunt_factor(self): - - return self._brems_func.gaunt_factor - - @gaunt_factor.setter - def gaunt_factor(self, value): - - self._brems_func.gaunt_factor = value - self._user_provided_gaunt_factor = True if value else False - @property def integrator(self): @@ -215,12 +200,11 @@ cdef class Bremsstrahlung(PlasmaModel): if self._plasma is None: raise RuntimeError("The emission model is not connected to a plasma object.") - if self._brems_func.gaunt_factor is None: - if self._atomic_data is None: - raise RuntimeError("The emission model is not connected to an atomic data source.") + if self._atomic_data is None: + raise RuntimeError("The emission model is not connected to an atomic data source.") - # initialise Gaunt factor on first run using the atomic data - self._brems_func.gaunt_factor = self._atomic_data.free_free_gaunt_factor() + # initialise Gaunt factor on first run using the atomic data + self._brems_func.gaunt_factor = self._atomic_data.free_free_gaunt_factor() species_charge = [] for species in self._plasma.get_composition(): @@ -237,8 +221,7 @@ cdef class Bremsstrahlung(PlasmaModel): def _change(self): # clear cache to force regeneration on first use - if not self._user_provided_gaunt_factor: - self._brems_func.gaunt_factor = None + self._brems_func.gaunt_factor = None self._brems_func.species_charge = None self._brems_func.species_charge_mv = None self._brems_func.species_density = None diff --git a/cherab/core/tests/test_bremsstrahlung.py b/cherab/core/tests/test_bremsstrahlung.py index 5373776b..ae86e0df 100644 --- a/cherab/core/tests/test_bremsstrahlung.py +++ b/cherab/core/tests/test_bremsstrahlung.py @@ -19,11 +19,14 @@ import unittest import numpy as np +import os +import json from raysect.core import Point3D, Vector3D from raysect.optical import World, Ray -from cherab.core.atomic import AtomicData, MaxwellianFreeFreeGauntFactor +from cherab.core.atomic import AtomicData +from cherab.atomic.gaunt import FreeFreeGauntFactor from cherab.core.math.integrators import GaussianQuadrature from cherab.core.atomic import deuterium, nitrogen from cherab.tools.plasmas.slab import build_constant_slab_plasma @@ -32,6 +35,24 @@ import scipy.constants as const +class TestAtomicData(AtomicData): + """Atomic data for test purpose.""" + + def free_free_gaunt_factor(self): + + test_directory = os.path.dirname(__file__) + path = os.path.join(test_directory, '../../atomic/repository/default_data/maxwellian_free_free_gaunt_factor.json') + with open(path, 'r') as f: + data = json.load(f) + + # convert to numpy arrays + data['u'] = np.array(data['u'], np.float64) + data['gamma2'] = np.array(data['gamma2'], np.float64) + data['gaunt_factor'] = np.array(data['gaunt_factor'], np.float64) + + return FreeFreeGauntFactor(data) + + class TestBremsstrahlung(unittest.TestCase): world = World() @@ -40,12 +61,11 @@ class TestBremsstrahlung(unittest.TestCase): 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() + plasma.atomic_data = TestAtomicData() def test_bremsstrahlung_model(self): # setting up the model - gaunt_factor = MaxwellianFreeFreeGauntFactor() - bremsstrahlung = Bremsstrahlung(gaunt_factor=gaunt_factor) + bremsstrahlung = Bremsstrahlung() self.plasma.models = [bremsstrahlung] # observing @@ -65,6 +85,8 @@ def test_bremsstrahlung_model(self): ne = self.plasma.electron_distribution.density(0.5, 0, 0) te = self.plasma.electron_distribution.effective_temperature(0.5, 0, 0) + gaunt_factor = self.plasma.atomic_data.free_free_gaunt_factor() + def brems_func(wvl): ni_gff_z2 = 0 for species in self.plasma.composition: @@ -87,7 +109,7 @@ def brems_func(wvl): for i in range(brems_spectrum.bins): self.assertAlmostEqual(brems_spectrum.samples[i], test_samples[i], delta=1e-10, - msg='BeamCXLine model gives a wrong value at {} nm.'.format(brems_spectrum.wavelengths[i])) + msg='Bremsstrahlung model gives a wrong value at {} nm.'.format(brems_spectrum.wavelengths[i])) if __name__ == '__main__': diff --git a/cherab/core/tests/test_lineshapes.py b/cherab/core/tests/test_lineshapes.py index da4dc41d..ebe20624 100644 --- a/cherab/core/tests/test_lineshapes.py +++ b/cherab/core/tests/test_lineshapes.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2023 Euratom +# Copyright 2016-2023 United Kingdom Atomic Energy Authority +# Copyright 2016-2023 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -23,12 +23,12 @@ from scipy.integrate import quadrature from raysect.core import Point3D, Vector3D -from raysect.core.math.function.float import Arg1D, Constant1D from raysect.optical import Spectrum from cherab.core import Line from cherab.core.math.integrators import GaussianQuadrature -from cherab.core.atomic import deuterium, nitrogen, ZeemanStructure +from cherab.core.atomic import deuterium, nitrogen +from cherab.atomic.zeeman import ZeemanStructure from cherab.tools.plasmas.slab import build_constant_slab_plasma from cherab.core.model import GaussianLine, MultipletLineShape, StarkBroadenedLine, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet @@ -228,11 +228,17 @@ def test_zeeman_multiplet(self): wavelength = 656.104 photon_energy = HC_EV_NM / wavelength - pi_components = [(Constant1D(wavelength), Constant1D(1.0))] - sigma_plus_components = [(HC_EV_NM / (photon_energy - BOHR_MAGNETON * Arg1D()), Constant1D(0.5))] - sigma_minus_components = [(HC_EV_NM / (photon_energy + BOHR_MAGNETON * Arg1D()), Constant1D(0.5))] - - zeeman_structure = ZeemanStructure(pi_components, sigma_plus_components, sigma_minus_components) + zeeman_data = {} + zeeman_data['b'] = np.linspace(0, 10, 100) + zeeman_data['polarisation'] = np.array([0, 1, -1], dtype=np.int32) + zeeman_data['wavelength'] = np.array([np.ones_like(zeeman_data['b']) * wavelength, + HC_EV_NM / (photon_energy - BOHR_MAGNETON * zeeman_data['b']), + HC_EV_NM / (photon_energy + BOHR_MAGNETON * zeeman_data['b'])]) + zeeman_data['ratio'] = np.array([np.ones_like(zeeman_data['b']), + 0.5 * np.ones_like(zeeman_data['b']), + 0.5 * np.ones_like(zeeman_data['b'])]) + + zeeman_structure = ZeemanStructure(zeeman_data) multiplet = ZeemanMultiplet(line, wavelength, target_species, self.plasma, zeeman_structure) # spectrum parameters diff --git a/cherab/openadas/__init__.py b/cherab/openadas/__init__.py index 16037e38..c3074407 100644 --- a/cherab/openadas/__init__.py +++ b/cherab/openadas/__init__.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,11 +16,7 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from .openadas import OpenADAS -from . import install -from . import repository - - - - +# for backward compatibility +from cherab.atomic.atomicdata import AtomicData as OpenADAS +from . import install diff --git a/cherab/openadas/install.py b/cherab/openadas/install.py index 27e0c5f4..53ce7f47 100644 --- a/cherab/openadas/install.py +++ b/cherab/openadas/install.py @@ -22,7 +22,7 @@ import urllib.parse import urllib.request -from cherab.openadas import repository +from cherab.atomic import repository from cherab.openadas.parse import * from cherab.core.utility import RecursiveDict, PerCm3ToPerM3, Cm3ToM3 @@ -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 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) @@ -388,8 +397,6 @@ def _notation_adf11_adas2cherab(rate_adas, filetype): rate_cherab[i][j + charge_correction]["ne"] = PerCm3ToPerM3.to(10**rate_adas[i][j]["ne"]) # convert from adas log10 to cherab electron temperature notation rate_cherab[i][j + charge_correction]["te"] = 10**rate_adas[i][j]["te"] - rate_cherab[i][j + charge_correction]["rates"] = Cm3ToM3.to(10**rate_adas[i][j]["rates"]) + rate_cherab[i][j + charge_correction]["rate"] = Cm3ToM3.to(10**rate_adas[i][j]["rate"]) return rate_cherab - - diff --git a/cherab/openadas/openadas.py b/cherab/openadas/openadas.py deleted file mode 100644 index 7e850bc4..00000000 --- a/cherab/openadas/openadas.py +++ /dev/null @@ -1,471 +0,0 @@ -# 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 import AtomicData -from cherab.core.atomic.elements import Isotope -from cherab.openadas.repository import DEFAULT_REPOSITORY_PATH - -from .rates import * -from cherab.openadas import repository - - -class OpenADAS(AtomicData): - """ - OpenADAS atomic data source. - - :param str data_path: OpenADAS local repository path. - :param bool permit_extrapolation: If true, informs interpolation objects to allow extrapolation - beyond the limits of the tabulated data. Default is False. - :param bool missing_rates_return_null: If true, allows Null rate objects to be returned when - the requested atomic data is missing. Default is False. - :param bool wavelength_element_fallback: If true, allows to use the element's wavelength when - the isotope's wavelength is not available. - Default is False. - """ - - def __init__(self, data_path=None, permit_extrapolation=False, missing_rates_return_null=False, - wavelength_element_fallback=False): - - super().__init__() - self._data_path = data_path or DEFAULT_REPOSITORY_PATH - - self._permit_extrapolation = permit_extrapolation - - self._missing_rates_return_null = missing_rates_return_null - - self._wavelength_element_fallback = wavelength_element_fallback - - @property - def data_path(self): - return self._data_path - - def wavelength(self, ion, charge, transition): - """ - Spectral line wavelength for a given transition. - - :param ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :param transition: Tuple containing (initial level, final level) - :return: Wavelength in nanometers. - """ - - if isinstance(ion, Isotope) and self._wavelength_element_fallback: - try: - return repository.get_wavelength(ion, charge, transition, repository_path=self._data_path) - except RuntimeError: - return repository.get_wavelength(ion.element, charge, transition, repository_path=self._data_path) - - return repository.get_wavelength(ion, charge, transition, repository_path=self._data_path) - - def ionisation_rate(self, ion, charge): - """ - Electron impact ionisation rate 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :return: Ionisation rate in m^3/s as a function of electron density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - if isinstance(ion, Isotope): - ion = ion.element - - try: - # read ionisation rate from json file in the repository - data = repository.get_ionisation_rate(ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullIonisationRate() - raise - - return IonisationRate(data, extrapolate=self._permit_extrapolation) - - def recombination_rate(self, ion, charge): - """ - Recombination rate 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :return: Recombination rate in m^3/s as a function of electron density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - if isinstance(ion, Isotope): - ion = ion.element - - try: - # read recombination rate from json file in the repository - data = repository.get_recombination_rate(ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullRecombinationRate() - raise - - return RecombinationRate(data, extrapolate=self._permit_extrapolation) - - def thermal_cx_rate(self, donor_element, donor_charge, receiver_element, receiver_charge): - """ - Thermal charge exchange effective rate coefficient for a given donor and receiver species. - - Open-ADAS data is interpolated with cubic spline in log-log space. - Linear 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. - :return: Thermal charge exchange rate in m^3/s as a function of electron density and - 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_thermal_cx_rate(donor_element, donor_charge, - receiver_element, receiver_charge, - repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullThermalCXRate() - raise - - return ThermalCXRate(data, extrapolate=self._permit_extrapolation) - - def beam_cx_pec(self, donor_ion, receiver_ion, receiver_charge, transition): - """ - Effective charge exchange photon emission coefficient for a given donor (beam) - and receiver (plasma) species and a given transition. - - 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" when permit_extrapolation is True. - - - :param donor_ion: Element object defining the donor ion type. - :param receiver_ion: 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 species. - :return: Charge exchange photon emission coefficient in W.m^3 as a function of - interaction energy, receiver ion temperature, receiver ion density, - plasma Z-effective, magnetic field magnitude. - """ - - # extract element from donor isotope because there are no isotope rates in ADAS - if isinstance(donor_ion, Isotope): - donor_ion = donor_ion.element - - # extract element from receiver isotope, but keep the receiver isotope for the wavelength - receiver_ion_element = receiver_ion.element if isinstance(receiver_ion, Isotope) else receiver_ion - - try: - # read element CX rate from json file in the repository - data = repository.get_beam_cx_rates(donor_ion, receiver_ion_element, receiver_charge, transition, - repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return [NullBeamCXPEC()] - 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_ion, receiver_charge - 1, transition) - - # load and interpolate the relevant transition data from each file - rates = [] - for donor_metastable, rate_data in data: - rates.append(BeamCXPEC(donor_metastable, wavelength, rate_data, extrapolate=self._permit_extrapolation)) - return rates - - def beam_stopping_rate(self, beam_ion, plasma_ion, charge): - """ - Beam stopping coefficient for a given beam and target species. - - Open-ADAS data is interpolated with cubic spline in log-log space. - Linear and quadratic extrapolations are used for "sen" and "st" respectively - when permit_extrapolation is True. - - :param beam_ion: Element object defining the beam ion type. - :param plasma_ion: Element object defining the target ion type. - :param charge: Charge state of the target ion. - :return: The beam stopping coefficient in m^3.s^-1 as a function of interaction energy, - target equivalent electron density, target temperature. - """ - - # extract elements from isotopes because there are no isotope rates in ADAS - if isinstance(beam_ion, Isotope): - beam_ion = beam_ion.element - - if isinstance(plasma_ion, Isotope): - plasma_ion = plasma_ion.element - - try: - # read beam stopping rate from json file in the repository - data = repository.get_beam_stopping_rate(beam_ion, plasma_ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullBeamStoppingRate() - raise - - # load and interpolate data - return BeamStoppingRate(data, extrapolate=self._permit_extrapolation) - - def beam_population_rate(self, beam_ion, metastable, plasma_ion, charge): - """ - Beam population coefficient for a given beam and target species. - - Open-ADAS data is interpolated with cubic spline in log-log space. - Linear and quadratic extrapolations are used for "sen" and "st" respectively - when permit_extrapolation is True. - - :param beam_ion: Element object defining the beam ion type. - :param metastable: The beam ion metastable number. - :param plasma_ion: Element object defining the target ion type. - :param charge: Charge state of the target ion. - :return: The beam population coefficient in dimensionless units as a function of - interaction energy, target equivalent electron density, target temperature. - """ - - # extract elements from isotopes because there are no isotope rates in ADAS - if isinstance(beam_ion, Isotope): - beam_ion = beam_ion.element - - if isinstance(plasma_ion, Isotope): - plasma_ion = plasma_ion.element - - try: - # read beam population rate from json file in the repository - data = repository.get_beam_population_rate(beam_ion, metastable, plasma_ion, charge, - repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullBeamPopulationRate() - raise - - # load and interpolate data - return BeamPopulationRate(data, extrapolate=self._permit_extrapolation) - - def beam_emission_pec(self, beam_ion, plasma_ion, charge, transition): - """ - The beam photon emission coefficient for a given beam and target species - and a given transition. - - Open-ADAS data is interpolated with cubic spline in log-log space. - Linear and quadratic extrapolations are used for "sen" and "st" respectively - when permit_extrapolation is True. - - :param beam_ion: Element object defining the beam ion type. - :param plasma_ion: Element object defining the target ion type. - :param charge: Charge state of the target ion. - :param transition: Tuple containing (initial level, final level) of the beam ion. - :return: The beam photon emission coefficient in W.m^3 as a function of - interaction energy, target equivalent electron density, target temperature. - """ - - # extract element from beam isotope, but keep the beam isotope for the wavelength - beam_ion_element = beam_ion.element if isinstance(beam_ion, Isotope) else beam_ion - - # extract element from plasma isotope because there are no isotope rates in ADAS - if isinstance(plasma_ion, Isotope): - plasma_ion = plasma_ion.element - - try: - # read beam emission PEC from json file in the repository - data = repository.get_beam_emission_rate(beam_ion_element, plasma_ion, charge, transition, - repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullBeamEmissionPEC() - 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(beam_ion, 0, transition) - - # load and interpolate data - return BeamEmissionPEC(data, wavelength, extrapolate=self._permit_extrapolation) - - def impact_excitation_pec(self, ion, charge, transition): - """ - Electron impact excitation 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :param transition: Tuple containing (initial level, final level). - :return: Impact excitation photon emission coefficient in W.m^3 as a - function of electron density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - # keep the isotope for the wavelength - ion_element = ion.element if isinstance(ion, Isotope) else ion - - try: - # read electron impact excitation PEC from json file in the repository - data = repository.get_pec_excitation_rate(ion_element, charge, transition, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullImpactExcitationPEC() - 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(ion, charge, transition) - - return ImpactExcitationPEC(wavelength, data, extrapolate=self._permit_extrapolation) - - def recombination_pec(self, ion, charge, transition): - """ - Recombination 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion after recombination. - :param transition: Tuple containing (initial level, final level). - :return: Recombination photon emission coefficient in W.m^3 as a function of electron - density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - # keep the isotope for the wavelength - ion_element = ion.element if isinstance(ion, Isotope) else ion - - try: - # read free electron recombination PEC from json file in the repository - data = repository.get_pec_recombination_rate(ion_element, charge, transition, repository_path=self._data_path) - - except (FileNotFoundError, KeyError): - if self._missing_rates_return_null: - return NullRecombinationPEC() - 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(ion, charge, transition) - - return RecombinationPEC(wavelength, data, extrapolate=self._permit_extrapolation) - - def line_radiated_power_rate(self, ion, charge): - """ - Line radiated power 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :return: Line radiated power coefficient in W.m^3 as a function of electron - density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - if isinstance(ion, Isotope): - ion = ion.element - - try: - # read total line radiated power rate from json file in the repository - data = repository.get_line_radiated_power_rate(ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullLineRadiationPower(ion, charge) - raise - - return LineRadiationPower(ion, charge, data, extrapolate=self._permit_extrapolation) - - def continuum_radiated_power_rate(self, ion, charge): - """ - Recombination continuum radiated power 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 ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :return: Continuum radiated power coefficient in W.m^3 as a function - of electron density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - if isinstance(ion, Isotope): - ion = ion.element - - try: - # read continuum radiated power rate from json file in the repository - data = repository.get_continuum_radiated_power_rate(ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullContinuumPower(ion, charge) - raise - - return ContinuumPower(ion, charge, data, extrapolate=self._permit_extrapolation) - - def cx_radiated_power_rate(self, ion, charge): - """ - Charge exchange radiated power coefficient for a given species. - - Open-ADAS data is interpolated with cubic spline in log-log space. - Linear extrapolation is used when permit_extrapolation is True. - - :param ion: Element object defining the ion type. - :param charge: Charge state of the ion. - :return: Charge exchange radiated power coefficient in W.m^3 as a function - of electron density and temperature. - """ - - # extract element from isotope because there are no isotope rates in ADAS - if isinstance(ion, Isotope): - ion = ion.element - - try: - # read CX radiated power rate from json file in the repository - data = repository.get_cx_radiated_power_rate(ion, charge, repository_path=self._data_path) - - except RuntimeError: - if self._missing_rates_return_null: - return NullCXRadiationPower(ion, charge) - raise - - return CXRadiationPower(ion, charge, data, extrapolate=self._permit_extrapolation) diff --git a/cherab/openadas/parse/adf11.py b/cherab/openadas/parse/adf11.py index f97ab132..2c082925 100644 --- a/cherab/openadas/parse/adf11.py +++ b/cherab/openadas/parse/adf11.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"); @@ -92,7 +92,7 @@ def parse_adf11(element, adf_file_path): rates[element][ion_charge]['ne'] = densities rates[element][ion_charge]['te'] = temperatures - rates[element][ion_charge]['rates'] = np.swapaxes(rates_table, 0, 1) + rates[element][ion_charge]['rate'] = np.swapaxes(rates_table, 0, 1) # if end of data block beak the loop or reassign start of data block for next stage if re.match("^\s*C{1}-{2,}", lines[i]) or re.match("^\s*C{0,1}-{2,}", lines[i]) and \ diff --git a/cherab/openadas/rates/__init__.pxd b/cherab/openadas/rates/__init__.pxd index b8e7aec8..e0f42905 100644 --- a/cherab/openadas/rates/__init__.pxd +++ b/cherab/openadas/rates/__init__.pxd @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,8 +16,10 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from cherab.openadas.rates.beam cimport * -from cherab.openadas.rates.cx cimport * -from cherab.openadas.rates.pec cimport * -from cherab.openadas.rates.atomic cimport * -from cherab.openadas.rates.radiated_power cimport * +# for backward compatibility +from cherab.atomic.rates.beam cimport * +from cherab.atomic.rates.cx cimport * +from cherab.atomic.rates.pec cimport * +from cherab.atomic.rates.atomic cimport * +from cherab.atomic.rates.radiated_power cimport * + diff --git a/cherab/openadas/rates/__init__.py b/cherab/openadas/rates/__init__.py index 9d128d25..5699eae6 100644 --- a/cherab/openadas/rates/__init__.py +++ b/cherab/openadas/rates/__init__.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,8 +16,9 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from .beam import * -from .cx import * -from .pec import * -from .atomic import * -from .radiated_power import * +# for backward compatibility +from cherab.atomic.rates.beam import * +from cherab.atomic.rates.cx import * +from cherab.atomic.rates.pec import * +from cherab.atomic.rates.atomic import * +from cherab.atomic.rates.radiated_power import * diff --git a/cherab/openadas/rates/radiated_power.pyx b/cherab/openadas/rates/radiated_power.pyx deleted file mode 100644 index 570ced92..00000000 --- a/cherab/openadas/rates/radiated_power.pyx +++ /dev/null @@ -1,160 +0,0 @@ - -# Copyright 2016-2021 Euratom -# Copyright 2016-2021 United Kingdom Atomic Energy Authority -# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - -import numpy as np -from libc.math cimport INFINITY, log10 - -from raysect.core.math.function.float cimport Interpolator2DArray - - -cdef class LineRadiationPower(CoreLineRadiationPower): - """Base class for radiated powers.""" - - def __init__(self, species, ionisation, dict data, extrapolate=False): - - super().__init__(species, ionisation) - - self.raw_data = data - - # unpack - ne = data['ne'] - te = data['te'] - rate = np.log10(data['rate']) - - # store limits of data - self.density_range = ne.min(), ne.max() - self.temperature_range = te.min(), te.max() - - # interpolate rate - # using nearest extrapolation to avoid infinite values at 0 for some rates - extrapolation_type = 'nearest' if extrapolate else 'none' - self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) - - 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 - - # calculate rate and convert from log10 space to linear space - return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) - - -cdef class NullLineRadiationPower(CoreLineRadiationPower): - """ - A line radiation power rate that always returns zero. - Needed for use cases where the required atomic data is missing. - """ - - cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: - return 0.0 - - -cdef class ContinuumPower(CoreContinuumPower): - """Base class for radiated powers.""" - - def __init__(self, species, ionisation, dict data, extrapolate=False): - - super().__init__(species, ionisation) - - self.raw_data = data - - # unpack - ne = data['ne'] - te = data['te'] - rate = np.log10(data['rate']) - - # store limits of data - self.density_range = ne.min(), ne.max() - self.temperature_range = te.min(), te.max() - - # interpolate rate - # using nearest extrapolation to avoid infinite values at 0 for some rates - extrapolation_type = 'nearest' if extrapolate else 'none' - self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) - - 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 - - # calculate rate and convert from log10 space to linear space - return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) - - -cdef class NullContinuumPower(CoreContinuumPower): - """ - A continuum radiation power rate that always returns zero. - Needed for use cases where the required atomic data is missing. - """ - - cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: - return 0.0 - - -cdef class CXRadiationPower(CoreCXRadiationPower): - """Base class for radiated powers.""" - - def __init__(self, species, ionisation, dict data, extrapolate=False): - - super().__init__(species, ionisation) - - self.raw_data = data - - # unpack - ne = data['ne'] - te = data['te'] - rate = np.log10(data['rate']) - - # store limits of data - self.density_range = ne.min(), ne.max() - self.temperature_range = te.min(), te.max() - - # interpolate rate - extrapolation_type = 'linear' if extrapolate else 'none' - self._rate = Interpolator2DArray(np.log10(ne), np.log10(te), rate, 'cubic', extrapolation_type, INFINITY, INFINITY) - - 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 - - # calculate rate and convert from log10 space to linear space - return 10 ** self._rate.evaluate(log10(electron_density), log10(electron_temperature)) - - -cdef class NullCXRadiationPower(CoreCXRadiationPower): - """ - A CX radiation power rate that always returns zero. - Needed for use cases where the required atomic data is missing. - """ - - cdef double evaluate(self, double electron_density, double electron_temperature) except? -1e999: - return 0.0 diff --git a/cherab/openadas/repository/__init__.py b/cherab/openadas/repository/__init__.py index 32c25536..1c95a4e7 100644 --- a/cherab/openadas/repository/__init__.py +++ b/cherab/openadas/repository/__init__.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,10 +16,11 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from .beam import * -from .pec import * -from .atomic import * -from .wavelength import * -from .radiated_power import * -from .utility import DEFAULT_REPOSITORY_PATH -from .create import populate +# for backward compatibility +from cherab.atomic.repository.beam import * +from cherab.atomic.repository.pec import * +from cherab.atomic.repository.atomic import * +from cherab.atomic.repository.wavelength import * +from cherab.atomic.repository.radiated_power import * +from cherab.atomic.repository.utility import DEFAULT_REPOSITORY_PATH +from cherab.atomic.repository.create import populate diff --git a/cherab/openadas/repository/atomic.py b/cherab/openadas/repository/atomic.py deleted file mode 100644 index c24234f3..00000000 --- a/cherab/openadas/repository/atomic.py +++ /dev/null @@ -1,268 +0,0 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - - -import os -import json -import numpy as np - -from cherab.core.atomic import Element -from cherab.core.utility import RecursiveDict -from .utility import DEFAULT_REPOSITORY_PATH, valid_charge - - -def add_ionisation_rate(species, charge, rate, repository_path=None): - """ - Adds a single ionisation rate to the repository. - - If adding multiple rates, consider using the update_ionisation_rates() - function instead. The update function avoids repeatedly opening and closing - the rate files. - - :param repository_path: - :return: - """ - - update_ionisation_rates({ - species: { - charge: rate - } - }, repository_path) - - -def update_ionisation_rates(rates, repository_path=None): - """ - Ionisation rate file structure - - /ionisation/.json - - File contains multiple rates, indexed by the ion charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for species, rate_data in rates.items(): - - # sanitise and validate arguments - if not isinstance(species, Element): - raise TypeError('The species must be an Element object.') - - path = os.path.join(repository_path, 'ionisation/{}.json'.format(species.symbol.lower())) - - _update_and_write_adf11(species, rate_data, path) - - -def add_recombination_rate(species, charge, rate, repository_path=None): - """ - Adds a single recombination rate to the repository. - - If adding multiple rates, consider using the update_recombination_rates() - function instead. The update function avoids repeatedly opening and closing - the rate files. - - :param repository_path: - :return: - """ - - update_recombination_rates({ - species: { - charge: rate - } - }, repository_path) - - -def update_recombination_rates(rates, repository_path=None): - """ - Ionisation rate file structure - - /recombination/.json - - File contains multiple rates, indexed by the ion charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for species, rate_data in rates.items(): - - # sanitise and validate arguments - if not isinstance(species, Element): - raise TypeError('The species must be an Element object.') - - path = os.path.join(repository_path, 'recombination/{}.json'.format(species.symbol.lower())) - - _update_and_write_adf11(species, rate_data, path) - - -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. - - If adding multiple rates, consider using the update_recombination_rates() - function instead. The update function avoids repeatedly opening and closing - 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: - """ - - rates2update = RecursiveDict() - rates2update[donor_element][donor_charge][receiver_element] = rate - - update_thermal_cx_rates(rates2update, repository_path) - - -def update_thermal_cx_rates(rates, repository_path=None): - """ - Thermal charge exchange rate file structure - - /thermal_cx///.json - - File contains multiple rates, indexed by the ion charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for donor_element in rates.keys(): - for donor_charge in rates[donor_element].keys(): - for receiver_element, rate_data in rates[donor_element][donor_charge].items(): - - # sanitise and validate arguments - if not isinstance(receiver_element, Element): - raise TypeError('The receiver_element must be an Element object.') - - rate_path = 'thermal_cx/{0}/{1}/{2}.json'.format(donor_element.symbol.lower(), - donor_charge, receiver_element.symbol.lower()) - path = os.path.join(repository_path, rate_path) - - _update_and_write_adf11(receiver_element, rate_data, path) - - -def _update_and_write_adf11(species, rate_data, path): - - # read in any existing rates - try: - with open(path, 'r') as f: - content = RecursiveDict.from_dict(json.load(f)) - except FileNotFoundError: - content = RecursiveDict() - - for charge, rates in rate_data.items(): - - if not valid_charge(species, charge): - raise ValueError('Charge state is larger than the number of protons in the specified species.') - - # sanitise and validate rate data - te = np.array(rates['te'], np.float64) - ne = np.array(rates['ne'], np.float64) - rate_table = np.array(rates['rates'], np.float64) - - if ne.ndim != 1: - raise ValueError('Density array must be a 1D array.') - - if te.ndim != 1: - raise ValueError('Temperature array must be a 1D array.') - - if (ne.shape[0], te.shape[0]) != rate_table.shape: - raise ValueError('Electron temperature, density and rate data arrays have inconsistent sizes.') - - # update file content with new rate - content[str(charge)] = { - 'te': te.tolist(), - 'ne': ne.tolist(), - 'rate': rate_table.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_ionisation_rate(element, charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - path = os.path.join(repository_path, 'ionisation/{}.json'.format(element.symbol.lower())) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested ionisation rate (element={}, charge={})' - ' is not available.'.format(element.symbol, charge)) - - # convert to numpy arrays - d['ne'] = np.array(d['ne'], np.float64) - d['te'] = np.array(d['te'], np.float64) - d['rate'] = np.array(d['rate'], np.float64) - - return d - - -def get_recombination_rate(element, charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - path = os.path.join(repository_path, 'recombination/{}.json'.format(element.symbol.lower())) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested recombination rate (element={}, charge={})' - ' is not available.'.format(element.symbol, charge)) - - # convert to numpy arrays - d['ne'] = np.array(d['ne'], np.float64) - d['te'] = np.array(d['te'], np.float64) - d['rate'] = np.array(d['rate'], np.float64) - - return d - - -def get_thermal_cx_rate(donor_element, donor_charge, receiver_element, receiver_charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - rate_path = 'thermal_cx/{0}/{1}/{2}.json'.format(donor_element.symbol.lower(), donor_charge, - receiver_element.symbol.lower()) - path = os.path.join(repository_path, rate_path) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(receiver_charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested thermal charge-exchange rate (donor={}, donor charge={}, receiver={})' - ' 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['rate'] = np.array(d['rate'], np.float64) - - return d diff --git a/cherab/openadas/repository/beam/__init__.py b/cherab/openadas/repository/beam/__init__.py index 3b5a95ae..eabdd9ec 100644 --- a/cherab/openadas/repository/beam/__init__.py +++ b/cherab/openadas/repository/beam/__init__.py @@ -16,7 +16,8 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from .cx import * -from .stopping import * -from .population import * -from .emission import * +# for backward compatibility +from cherab.atomic.repository.beam.cx import * +from cherab.atomic.repository.beam.stopping import * +from cherab.atomic.repository.beam.population import * +from cherab.atomic.repository.beam.emission import * diff --git a/cherab/openadas/repository/radiated_power.py b/cherab/openadas/repository/radiated_power.py deleted file mode 100644 index a6a4b390..00000000 --- a/cherab/openadas/repository/radiated_power.py +++ /dev/null @@ -1,261 +0,0 @@ - -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - - -import os -import json -import numpy as np - -from cherab.core.atomic import Element -from cherab.core.utility import RecursiveDict -from .utility import DEFAULT_REPOSITORY_PATH, valid_charge - - -def add_line_power_rate(species, charge, rate, repository_path=None): - """ - Adds a single LineRadiationPower 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: - """ - - update_line_power_rates({ - species: { - charge: rate - } - }, repository_path) - - -def update_line_power_rates(rates, repository_path=None): - """ - Update the repository of LineRadiationPower rates. - - LineRadiationPower rate file structure - - /radiated_power/line/.json - - File contains multiple rates, indexed by the ion's charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for species, rate_data in rates.items(): - - # sanitise and validate arguments - if not isinstance(species, Element): - raise TypeError('The species must be an Element object.') - - path = os.path.join(repository_path, 'radiated_power/line/{}.json'.format(species.symbol.lower())) - - _update_and_write_adf11(species, rate_data, path) - - -def add_continuum_power_rate(species, charge, rate, repository_path=None): - """ - Adds a single ContinuumPower 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: - """ - - update_line_power_rates({ - species: { - charge: rate - } - }, repository_path) - - -def update_continuum_power_rates(rates, repository_path=None): - """ - Update the repository of ContinuumPower rates. - - ContinuumPower rate file structure - - /radiated_power/continuum/.json - - File contains multiple rates, indexed by ion's charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for species, rate_data in rates.items(): - - # sanitise and validate arguments - if not isinstance(species, Element): - raise TypeError('The species must be an Element object.') - - path = os.path.join(repository_path, 'radiated_power/continuum/{}.json'.format(species.symbol.lower())) - - _update_and_write_adf11(species, rate_data, path) - - -def add_cx_power_rate(species, charge, rate, repository_path=None): - """ - Adds a single CXRadiationPower rate to the repository. - - 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: - """ - - update_line_power_rates({ - species: { - charge: rate - } - }, repository_path) - - -def update_cx_power_rates(rates, repository_path=None): - """ - Update the repository of CXRadiationPower rates. - - CXRadiationPower rate file structure - - /radiated_power/cx/.json - - File contains multiple rates, indexed by ion's charge state. - """ - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - for species, rate_data in rates.items(): - - # sanitise and validate arguments - if not isinstance(species, Element): - raise TypeError('The species must be an Element object.') - - path = os.path.join(repository_path, 'radiated_power/cx/{}.json'.format(species.symbol.lower())) - - _update_and_write_adf11(species, rate_data, path) - - -def _update_and_write_adf11(species, rate_data, path): - - # read in any existing rates - try: - with open(path, 'r') as f: - content = RecursiveDict.from_dict(json.load(f)) - except FileNotFoundError: - content = RecursiveDict() - - for charge, rates in rate_data.items(): - - if not valid_charge(species, charge): - raise ValueError('The charge state is larger than the number of protons in the specified species.') - - # sanitise and validate rate data - te = np.array(rates['te'], np.float64) - ne = np.array(rates['ne'], np.float64) - rate_table = np.array(rates['rates'], np.float64) - - if ne.ndim != 1: - raise ValueError('Density array must be a 1D array.') - - if te.ndim != 1: - raise ValueError('Temperature array must be a 1D array.') - - if (ne.shape[0], te.shape[0]) != rate_table.shape: - raise ValueError('Electron temperature, density and rate data arrays have inconsistent sizes.') - - # update file content with new rate - content[str(charge)] = { - 'te': te.tolist(), - 'ne': ne.tolist(), - 'rate': rate_table.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_line_radiated_power_rate(element, charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - path = os.path.join(repository_path, 'radiated_power/line/{}.json'.format(element.symbol.lower())) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested radiated power rate (element={}, charge={})' - ' is not available.'.format(element.symbol, charge)) - - # convert to numpy arrays - d['ne'] = np.array(d['ne'], np.float64) - d['te'] = np.array(d['te'], np.float64) - d['rate'] = np.array(d['rate'], np.float64) - - return d - - -def get_continuum_radiated_power_rate(element, charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - path = os.path.join(repository_path, 'radiated_power/continuum/{}.json'.format(element.symbol.lower())) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested radiated power rate (element={}, charge={})' - ' is not available.'.format(element.symbol, charge)) - - # convert to numpy arrays - d['ne'] = np.array(d['ne'], np.float64) - d['te'] = np.array(d['te'], np.float64) - d['rate'] = np.array(d['rate'], np.float64) - - return d - - -def get_cx_radiated_power_rate(element, charge, repository_path=None): - - repository_path = repository_path or DEFAULT_REPOSITORY_PATH - - path = os.path.join(repository_path, 'radiated_power/cx/{}.json'.format(element.symbol.lower())) - try: - with open(path, 'r') as f: - content = json.load(f) - d = content[str(charge)] - except (FileNotFoundError, KeyError): - raise RuntimeError('Requested radiated power rate (element={}, charge={})' - ' is not available.'.format(element.symbol, charge)) - - # convert to numpy arrays - d['ne'] = np.array(d['ne'], np.float64) - d['te'] = np.array(d['te'], np.float64) - d['rate'] = np.array(d['rate'], np.float64) - - return d diff --git a/cherab/openadas/tests/test_adf11_charges.py b/cherab/openadas/tests/test_adf11_charges.py index d510696d..384c4a33 100644 --- a/cherab/openadas/tests/test_adf11_charges.py +++ b/cherab/openadas/tests/test_adf11_charges.py @@ -1,11 +1,11 @@ from cherab.openadas.parse.adf11 import parse_adf11 -from cherab.openadas import repository +from cherab.atomic import repository from cherab.core.atomic import neon, hydrogen -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.openadas.parse import parse_adf11 from cherab.core.utility import RecursiveDict, Cm3ToM3, PerCm3ToPerM3 import unittest -from cherab.openadas.rates.atomic import IonisationRate, RecombinationRate, ThermalCXRate +from cherab.atomic.rates.atomic import IonisationRate, RecombinationRate, ThermalCXRate # Todo: this uses rate data, it must be stand alone. Fix it. diff --git a/demos/balmer_series.py b/demos/balmer_series.py index 789ba65c..7c1e4867 100755 --- a/demos/balmer_series.py +++ b/demos/balmer_series.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -18,20 +18,18 @@ # External imports -import os from scipy.constants import electron_mass, atomic_mass import matplotlib.pyplot as plt -import numpy as np from cherab.core.model import ExcitationLine, RecombinationLine, Bremsstrahlung # Cherab and raysect imports from cherab.core import Species, Maxwellian, Plasma, Line, elements -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.plasmas import GaussianVolume # Core and external imports -from raysect.optical import World, translate, rotate, Vector3D, Point3D, Ray +from raysect.optical import World, translate, Vector3D, Point3D, Ray from raysect.primitive import Sphere from raysect.optical.observer import PinholeCamera from raysect.optical.material.emitter.inhomogeneous import NumericalIntegrator @@ -44,11 +42,11 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) -plasma.atomic_data = adas +plasma.atomic_data = atomic_data plasma.geometry = Sphere(sigma * 5.0) plasma.geometry_transform = None plasma.integrator = NumericalIntegrator(step=sigma / 5.0) diff --git a/demos/beam.py b/demos/beam.py index 49bdc648..567be538 100644 --- a/demos/beam.py +++ b/demos/beam.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -16,9 +16,6 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -import sys - -import numpy as np from scipy.constants import electron_mass, atomic_mass from matplotlib.pyplot import ion, ioff, plot, show @@ -30,7 +27,7 @@ from cherab.core import Plasma, Beam, Species, Maxwellian from cherab.core.atomic import elements, Line -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.core.model import SingleRayAttenuator, BeamCXLine from cherab.tools.plasmas import GaussianVolume @@ -40,7 +37,7 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) @@ -76,7 +73,7 @@ # BEAM ------------------------------------------------------------------------ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 beam.power = 3e6 beam.element = elements.deuterium @@ -99,7 +96,7 @@ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 / 2 beam.power = 3e6 beam.element = elements.deuterium @@ -122,7 +119,7 @@ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 / 3 beam.power = 3e6 beam.element = elements.deuterium diff --git a/demos/emission_models/beam_emission_spectrum.py b/demos/emission_models/beam_emission_spectrum.py index f7675449..2ecd815c 100644 --- a/demos/emission_models/beam_emission_spectrum.py +++ b/demos/emission_models/beam_emission_spectrum.py @@ -1,3 +1,20 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. import numpy as np import matplotlib.pyplot as plt @@ -13,7 +30,7 @@ from cherab.core.model.beam.beam_emission import SIGMA_TO_PI, SIGMA1_TO_SIGMA0, \ PI2_TO_PI3, PI4_TO_PI3 from cherab.tools.plasmas.slab import build_slab_plasma -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData ############### @@ -24,7 +41,7 @@ plasma = build_slab_plasma(width=1.0, height=3.0, peak_density=1e18, neutral_temperature=20.0, impurities=[(carbon, 6, 0.005)], parent=world) plasma.b_field = Vector3D(0, 1.5, 0) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) # add background emission h_alpha = Line(hydrogen, 0, (3, 2)) @@ -61,7 +78,7 @@ ########################### # Inject beam into plasma # -adas = OpenADAS(permit_extrapolation=True, missing_rates_return_null=True) +atomic_data = AtomicData(permit_extrapolation=True, missing_rates_return_null=True) integration_step = 0.0025 beam_transform = translate(-0.5, 0.0, 0) * rotate_basis(Vector3D(1, 0, 0), Vector3D(0, 0, 1)) @@ -79,7 +96,7 @@ beam_full = Beam(parent=world, transform=beam_transform) beam_full.plasma = plasma -beam_full.atomic_data = adas +beam_full.atomic_data = atomic_data beam_full.energy = beam_energy beam_full.power = 3e6 # beam_energy * beam_current beam_full.temperature = beam_temperature @@ -99,7 +116,7 @@ beam_half = Beam(parent=world, transform=beam_transform) beam_half.plasma = plasma -beam_half.atomic_data = adas +beam_half.atomic_data = atomic_data beam_half.energy = beam_energy / 2 beam_half.power = 3e6 # beam_energy / 2 * beam_current beam_half.temperature = beam_temperature @@ -119,7 +136,7 @@ beam_third = Beam(parent=world, transform=beam_transform) beam_third.plasma = plasma -beam_third.atomic_data = adas +beam_third.atomic_data = atomic_data beam_third.energy = beam_energy / 3 beam_third.power = 3e6 # beam_energy / 3 * beam_current beam_third.temperature = beam_temperature diff --git a/demos/emission_models/charge_exchange.py b/demos/emission_models/charge_exchange.py index 11d95386..a253f6dc 100644 --- a/demos/emission_models/charge_exchange.py +++ b/demos/emission_models/charge_exchange.py @@ -1,3 +1,20 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. import numpy as np import matplotlib.pyplot as plt @@ -13,7 +30,7 @@ from cherab.core.atomic import hydrogen, deuterium, carbon, Line from cherab.core.model import SingleRayAttenuator, BeamCXLine from cherab.tools.plasmas.slab import NeutralFunction, IonFunction -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData ############### @@ -29,10 +46,10 @@ impurities = [(carbon, 6, 0.005)] world = World() -adas = OpenADAS(permit_extrapolation=True, missing_rates_return_null=True) +atomic_data = AtomicData(permit_extrapolation=True, missing_rates_return_null=True) plasma = Plasma(parent=world) -plasma.atomic_data = adas +plasma.atomic_data = atomic_data plasma.geometry = Box(Point3D(0, -width / 2, -height / 2), Point3D(length, width / 2, height / 2)) species = [] @@ -126,7 +143,7 @@ beam_full = Beam(parent=world, transform=beam_transform) beam_full.plasma = plasma -beam_full.atomic_data = adas +beam_full.atomic_data = atomic_data beam_full.energy = beam_energy beam_full.power = 3e6 beam_full.element = deuterium @@ -141,7 +158,7 @@ beam_half = Beam(parent=world, transform=beam_transform) beam_half.plasma = plasma -beam_half.atomic_data = adas +beam_half.atomic_data = atomic_data beam_half.energy = beam_energy / 2 beam_half.power = 3e6 beam_half.element = deuterium @@ -156,7 +173,7 @@ beam_third = Beam(parent=world, transform=beam_transform) beam_third.plasma = plasma -beam_third.atomic_data = adas +beam_third.atomic_data = atomic_data beam_third.energy = beam_energy / 3 beam_third.power = 3e6 beam_third.element = deuterium diff --git a/demos/emission_models/multiplet.py b/demos/emission_models/multiplet.py index faf6bda9..6c48b151 100755 --- a/demos/emission_models/multiplet.py +++ b/demos/emission_models/multiplet.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -28,7 +28,7 @@ from cherab.core import Species, Maxwellian, Plasma, Line from cherab.core.atomic.elements import deuterium, nitrogen from cherab.core.model import ExcitationLine, RecombinationLine, MultipletLineShape -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.plasmas import GaussianVolume @@ -40,11 +40,11 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) -plasma.atomic_data = adas +plasma.atomic_data = atomic_data plasma.geometry = Sphere(sigma * 5.0) plasma.geometry_transform = None plasma.integrator = NumericalIntegrator(step=sigma / 5.0) diff --git a/demos/emission_models/stark_broadening.py b/demos/emission_models/stark_broadening.py index 090e929a..6868cb48 100755 --- a/demos/emission_models/stark_broadening.py +++ b/demos/emission_models/stark_broadening.py @@ -1,6 +1,6 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -29,7 +29,7 @@ from cherab.core.atomic.elements import deuterium, nitrogen from cherab.core.model import ExcitationLine, RecombinationLine,\ MultipletLineShape, StarkBroadenedLine -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.plasmas import GaussianVolume @@ -41,11 +41,11 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) -plasma.atomic_data = adas +plasma.atomic_data = atomic_data plasma.geometry = Sphere(sigma * 5.0) plasma.geometry_transform = None plasma.integrator = NumericalIntegrator(step=sigma / 5.0) diff --git a/demos/emission_models/zeeman_splitting.py b/demos/emission_models/zeeman_splitting.py index b978a536..a4863d90 100755 --- a/demos/emission_models/zeeman_splitting.py +++ b/demos/emission_models/zeeman_splitting.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"); @@ -18,20 +18,19 @@ # External imports -from numpy import cos, sin, deg2rad +import numpy as np import matplotlib.pyplot as plt from scipy.constants import electron_mass, atomic_mass -from raysect.core.math.function.float import Arg1D, Constant1D from raysect.optical import World, Vector3D, Point3D, Ray from raysect.primitive import Sphere from raysect.optical.material.emitter.inhomogeneous import NumericalIntegrator # Cherab imports from cherab.core import Species, Maxwellian, Plasma, Line -from cherab.core.atomic import ZeemanStructure +from cherab.atomic.zeeman import ZeemanStructure from cherab.core.atomic.elements import deuterium from cherab.core.model import ExcitationLine, RecombinationLine, ZeemanTriplet, ParametrisedZeemanTriplet, ZeemanMultiplet -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.plasmas import GaussianVolume @@ -43,11 +42,11 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) -plasma.atomic_data = adas +plasma.atomic_data = atomic_data plasma.geometry = Sphere(sigma * 5.0) plasma.geometry_transform = None plasma.integrator = NumericalIntegrator(step=sigma / 5.0) @@ -88,8 +87,8 @@ # Ray-trace the spectrum for different angles between the ray and the magnetic field triplet = [] for angle in angles: - angle_rad = deg2rad(angle) - r = Ray(origin=Point3D(0, -5 * sin(angle_rad), -5 * cos(angle_rad)), direction=Vector3D(0, sin(angle_rad), cos(angle_rad)), + angle_rad = np.deg2rad(angle) + r = Ray(origin=Point3D(0, -5 * np.sin(angle_rad), -5 * np.cos(angle_rad)), direction=Vector3D(0, np.sin(angle_rad), np.cos(angle_rad)), min_wavelength=655.4, max_wavelength=656.8, bins=500) triplet.append(r.trace(world)) @@ -104,8 +103,8 @@ # Ray-trace the spectrum again parametrised_triplet = [] for angle in angles: - angle_rad = deg2rad(angle) - r = Ray(origin=Point3D(0, -5 * sin(angle_rad), -5 * cos(angle_rad)), direction=Vector3D(0, sin(angle_rad), cos(angle_rad)), + angle_rad = np.deg2rad(angle) + r = Ray(origin=Point3D(0, -5 * np.sin(angle_rad), -5 * np.cos(angle_rad)), direction=Vector3D(0, np.sin(angle_rad), np.cos(angle_rad)), min_wavelength=655.4, max_wavelength=656.8, bins=500) parametrised_triplet.append(r.trace(world)) @@ -117,11 +116,17 @@ wavelength = plasma.atomic_data.wavelength(deuterium, 0, (3, 2)) photon_energy = HC_EV_NM / wavelength -pi_components = [(Constant1D(wavelength), Constant1D(1.0))] -sigma_minus_components = [(HC_EV_NM / (photon_energy - BOHR_MAGNETON * Arg1D()), Constant1D(0.5))] -sigma_plus_components = [(HC_EV_NM / (photon_energy + BOHR_MAGNETON * Arg1D()), Constant1D(0.5))] +zeeman_data = {} +zeeman_data['b'] = np.linspace(0, 10, 100) +zeeman_data['polarisation'] = np.array([0, 1, -1], dtype=np.int32) +zeeman_data['wavelength'] = np.array([np.ones_like(zeeman_data['b']) * wavelength, + HC_EV_NM / (photon_energy - BOHR_MAGNETON * zeeman_data['b']), + HC_EV_NM / (photon_energy + BOHR_MAGNETON * zeeman_data['b'])]) +zeeman_data['ratio'] = np.array([np.ones_like(zeeman_data['b']), + 0.5 * np.ones_like(zeeman_data['b']), + 0.5 * np.ones_like(zeeman_data['b'])]) -zeeman_structure = ZeemanStructure(pi_components, sigma_plus_components, sigma_minus_components) +zeeman_structure = ZeemanStructure(zeeman_data) plasma.models = [ ExcitationLine(deuterium_I_656, lineshape=ZeemanMultiplet, lineshape_args=[zeeman_structure]), @@ -131,8 +136,8 @@ # Ray-trace the spectrum again multiplet = [] for angle in angles: - angle_rad = deg2rad(angle) - r = Ray(origin=Point3D(0, -5 * sin(angle_rad), -5 * cos(angle_rad)), direction=Vector3D(0, sin(angle_rad), cos(angle_rad)), + angle_rad = np.deg2rad(angle) + r = Ray(origin=Point3D(0, -5 * np.sin(angle_rad), -5 * np.cos(angle_rad)), direction=Vector3D(0, np.sin(angle_rad), np.cos(angle_rad)), min_wavelength=655.4, max_wavelength=656.8, bins=500) multiplet.append(r.trace(world)) diff --git a/demos/observers/groups.py b/demos/observers/groups.py index 5db127e1..6751862a 100644 --- a/demos/observers/groups.py +++ b/demos/observers/groups.py @@ -1,4 +1,7 @@ -# Copyright 2014-2021 United Kingdom Atomic Energy Authority + +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -27,7 +30,7 @@ from cherab.core.model import ExcitationLine, RecombinationLine from cherab.core.atomic import Line, hydrogen -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.generomak.machine import load_first_wall from cherab.tools.observers import FibreOpticGroup from cherab.tools.observers.group.plotting import plot_group_spectra, plot_group_total @@ -39,7 +42,7 @@ plasma = get_edge_plasma() # Adding H-alpha excitation and recombination models -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) h_alpha = Line(hydrogen, 0, (3, 2)) plasma.models = [ExcitationLine(h_alpha), RecombinationLine(h_alpha)] diff --git a/demos/openadas/adf15_plots.py b/demos/openadas/adf15_plots.py index 642f4b09..6dcf38d8 100644 --- a/demos/openadas/adf15_plots.py +++ b/demos/openadas/adf15_plots.py @@ -1,18 +1,36 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib.pyplot as plt from cherab.core.atomic import deuterium, helium, carbon -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData -adas = OpenADAS() +atomic_data = AtomicData() # load the PEC rate objects for transitions of interest -dalpha = adas.impact_excitation_pec(deuterium, 0, (3, 2)) -heliumii_468 = adas.impact_excitation_pec(helium, 1, (4, 3)) -carbonii_515 = adas.impact_excitation_pec(carbon, 1, ("2s1 2p1 3d1 2D4.5", "2s2 4d1 2D4.5")) -carboniii_465 = adas.impact_excitation_pec(carbon, 2, ("2s1 3p1 3P4.0", "2s1 3s1 3S1.0")) +dalpha = atomic_data.impact_excitation_pec(deuterium, 0, (3, 2)) +heliumii_468 = atomic_data.impact_excitation_pec(helium, 1, (4, 3)) +carbonii_515 = atomic_data.impact_excitation_pec(carbon, 1, ("2s1 2p1 3d1 2D4.5", "2s2 4d1 2D4.5")) +carboniii_465 = atomic_data.impact_excitation_pec(carbon, 2, ("2s1 3p1 3P4.0", "2s1 3s1 3S1.0")) # settings for plot range temp_low = 1 diff --git a/demos/openadas/beam_plasma_interaction_rates.py b/demos/openadas/beam_plasma_interaction_rates.py index 2d5f8ad3..a8e9a63d 100644 --- a/demos/openadas/beam_plasma_interaction_rates.py +++ b/demos/openadas/beam_plasma_interaction_rates.py @@ -1,8 +1,27 @@ + +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib import matplotlib.pyplot as plt from cherab.core.atomic import deuterium, carbon -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData # Make Latex available in matplotlib figures matplotlib.rcParams.update({'font.size': 12}) @@ -10,10 +29,10 @@ matplotlib.rc('font', **{'family': 'serif', 'serif': ['Computer Modern']}) # initialise the atomic data provider -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # Request beam stopping rate and sample at three different electron temperatures -bms = adas.beam_stopping_rate(deuterium, carbon, 6) +bms = atomic_data.beam_stopping_rate(deuterium, carbon, 6) beam_energies = [10**x for x in np.linspace(np.log10(5000), np.log10(125000), num=512)] bms_rates_1 = [bms(x, 1E19, 1) for x in beam_energies] bms_rates_2 = [bms(x, 1E19, 100) for x in beam_energies] @@ -30,7 +49,7 @@ plt.legend() # Sample the beam population rates -bmp = adas.beam_population_rate(deuterium, 2, carbon, 6) +bmp = atomic_data.beam_population_rate(deuterium, 2, carbon, 6) bmp_rates_1 = [bmp(x, 1E19, 1) for x in beam_energies] bmp_rates_2 = [bmp(x, 1E19, 100) for x in beam_energies] bmp_rates_3 = [bmp(x, 1E19, 1000) for x in beam_energies] @@ -46,7 +65,7 @@ plt.legend() # Sample the beam emission rates -bme = adas.beam_emission_pec(deuterium, deuterium, 1, (3, 2)) +bme = atomic_data.beam_emission_pec(deuterium, deuterium, 1, (3, 2)) bme_rates_1 = [bme(x, 1E19, 1) for x in beam_energies] bme_rates_2 = [bme(x, 1E19, 100) for x in beam_energies] bme_rates_3 = [bme(x, 1E19, 1000) for x in beam_energies] @@ -62,7 +81,7 @@ plt.legend() # Sample the effective CX emission rates -cxr = adas.beam_cx_pec(deuterium, carbon, 6, (8, 7)) +cxr = atomic_data.beam_cx_pec(deuterium, carbon, 6, (8, 7)) cxr_n1, cxr_n2 = cxr cxr_rate_1 = [cxr[0](x, 100, 1E19, 1, 1) for x in beam_energies] cxr_rate_2 = [cxr[1](x, 1000, 1E19, 1, 1) for x in beam_energies] diff --git a/demos/openadas/frac_abundance.py b/demos/openadas/frac_abundance.py index b24fa155..7a73c039 100644 --- a/demos/openadas/frac_abundance.py +++ b/demos/openadas/frac_abundance.py @@ -1,33 +1,52 @@ + +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib.pyplot as plt from cherab.core.atomic import neon, hydrogen -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from scipy.optimize import lsq_linear -def get_rates_recombination(element): +def get_rates_recombination(atomic_data, element): """ load recombinatio rates for all ionic stages """ coef_recom = {} for i in np.arange(1, elem.atomic_number + 1): - coef_recom[i] = adas.recombination_rate(element, int(i)) + coef_recom[i] = atomic_data.recombination_rate(element, int(i)) return coef_recom -def get_rates_tcx(donor, donor_charge, receiver): +def get_rates_tcx(atomic_data, donor, donor_charge, receiver): """ load thermal charge-exchange recombination rates for all ionic stages """ coef_tcx = {} for i in np.arange(1, elem.atomic_number + 1): - coef_tcx[i] = adas.thermal_cx_rate(donor, donor_charge, receiver, int(i)) + coef_tcx[i] = atomic_data.thermal_cx_rate(donor, donor_charge, receiver, int(i)) return coef_tcx -def get_rates_ionisation(element): +def get_rates_ionisation(atomic_data, element): """ load ionisation rates for all ionic stages :param element: @@ -35,7 +54,7 @@ def get_rates_ionisation(element): """ coef_ionis = {} for i in np.arange(0, elem.atomic_number): - coef_ionis[i] = adas.ionisation_rate(element, int(i)) + coef_ionis[i] = atomic_data.ionisation_rate(element, int(i)) return coef_ionis @@ -83,7 +102,7 @@ def solve_ion_balance(element, n_e, t_e, coef_ion, coef_recom, nh0=None, coef_tc # initialise the atomic data provider -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) elem = neon temperature_steps = 100 @@ -92,9 +111,9 @@ def solve_ion_balance(element, n_e, t_e, coef_ion, coef_recom, nh0=None, coef_tc numstates = elem.atomic_number + 1 # Collect rate coefficients -rates_ion = get_rates_ionisation(elem) -rates_recom = get_rates_recombination(elem) -rates_tcx = get_rates_tcx(hydrogen, 0, elem) +rates_ion = get_rates_ionisation(atomic_data, elem) +rates_recom = get_rates_recombination(atomic_data, elem) +rates_tcx = get_rates_tcx(atomic_data, hydrogen, 0, elem) electron_temperatures = [10 ** x for x in np.linspace(np.log10(rates_recom[1].raw_data["te"].min()), np.log10(rates_recom[1].raw_data["te"].max()), diff --git a/demos/openadas/plot_thermalxcrates.py b/demos/openadas/plot_thermalxcrates.py index b9dec7dc..90fa77f9 100644 --- a/demos/openadas/plot_thermalxcrates.py +++ b/demos/openadas/plot_thermalxcrates.py @@ -1,9 +1,28 @@ + +# Copyright 2016-2021 Euratom +# Copyright 2016-2021 United Kingdom Atomic Energy Authority +# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib.pyplot as plt from cherab.core.atomic import neon, carbon, helium, hydrogen -from cherab.openadas import OpenADAS -adas = OpenADAS(permit_extrapolation=True) +from cherab.atomic import AtomicData +atomic_data = AtomicData(permit_extrapolation=True) electron_temperatures = [10**x for x in np.linspace(np.log10(1), np.log10(10000), num=100)] electron_density = 1e19 @@ -15,11 +34,11 @@ # Collect rate coefficients coef_tcx = {} for i in np.arange(1, elem.atomic_number+1): - coef_tcx[i] = adas.thermal_cx_rate(hydrogen, 0, neon, int(i)) + coef_tcx[i] = atomic_data.thermal_cx_rate(hydrogen, 0, neon, int(i)) # test correctness of available charge numbers try: - adas.thermal_cx_rate(hydrogen, 0, neon, i) + atomic_data.thermal_cx_rate(hydrogen, 0, neon, i) except RuntimeError: print("Check that thermal charge exchange between a neutral element and neutral hydrogen " "is not allowed.") @@ -41,6 +60,6 @@ # test loading rates for CX between neutrals is not allowed try: - coef_notallowed = adas.thermal_cx_rate(hydrogen, 0, neon, 0) + coef_notallowed = atomic_data.thermal_cx_rate(hydrogen, 0, neon, 0) except RuntimeError: print("All correct") diff --git a/demos/plasma-and-beam.py b/demos/plasma-and-beam.py index 8180bba0..ff883286 100644 --- a/demos/plasma-and-beam.py +++ b/demos/plasma-and-beam.py @@ -16,9 +16,6 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -import sys - -import numpy as np from scipy.constants import electron_mass, atomic_mass from matplotlib.pyplot import ion, ioff, plot, show @@ -30,7 +27,7 @@ from cherab.core import Plasma, Beam, Species, Maxwellian from cherab.core.atomic import elements, Line -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.core.model import SingleRayAttenuator, BeamCXLine from cherab.tools.plasmas import GaussianVolume @@ -40,7 +37,7 @@ world = World() # create atomic data source -adas = OpenADAS(permit_extrapolation=True) +atomic_data = AtomicData(permit_extrapolation=True) # PLASMA ---------------------------------------------------------------------- plasma = Plasma(parent=world) @@ -83,7 +80,7 @@ plasma.geometry_transform = None plasma.integrator.step = integration_step plasma.integrator.min_samples = 5 -plasma.atomic_data = adas +plasma.atomic_data = atomic_data # Setup elements.deuterium lines d_alpha = Line(elements.deuterium, 0, (3, 2)) @@ -105,7 +102,7 @@ # BEAM ------------------------------------------------------------------------ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 beam.power = 1e4 beam.element = elements.deuterium @@ -128,7 +125,7 @@ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 / 2 beam.power = 1e4 beam.element = elements.deuterium @@ -151,7 +148,7 @@ beam = Beam(parent=world, transform=translate(1.0, 0.0, 0) * rotate(90, 0, 0)) beam.plasma = plasma -beam.atomic_data = adas +beam.atomic_data = atomic_data beam.energy = 60000 / 3 beam.power = 1e4 beam.element = elements.deuterium diff --git a/demos/plasmas/analytic_plasma.py b/demos/plasmas/analytic_plasma.py index c0c7da23..4169af1e 100644 --- a/demos/plasmas/analytic_plasma.py +++ b/demos/plasmas/analytic_plasma.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-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -30,7 +30,7 @@ from cherab.core.math import sample3d from cherab.core.atomic import deuterium from cherab.core.model import ExcitationLine, RecombinationLine -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData class NeutralFunction: @@ -106,7 +106,7 @@ def __call__(self, x, y, z): # plasma creation # plasma = Plasma(parent=world) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) plasma.geometry = Cylinder(3.5, 2.2, transform=translate(0, 0, -1.1)) plasma.geometry_transform = translate(0, 0, -1.1) diff --git a/demos/plasmas/analytic_plasma_function_framework.py b/demos/plasmas/analytic_plasma_function_framework.py index 1459f28e..c5a9b18f 100644 --- a/demos/plasmas/analytic_plasma_function_framework.py +++ b/demos/plasmas/analytic_plasma_function_framework.py @@ -31,7 +31,7 @@ from cherab.core.math import sample3d, AxisymmetricMapper from cherab.core.atomic import deuterium from cherab.core.model import ExcitationLine -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData def NeutralFunction(peak_value, sigma, magnetic_axis, lcfs_radius=1): @@ -73,7 +73,7 @@ def IonFunction(v_core, v_lcfs, magnetic_axis, p=4, q=3, lcfs_radius=1): # plasma creation # plasma = Plasma(parent=world) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) plasma.geometry = Cylinder(3.5, 2.2, transform=translate(0, 0, -1.1)) plasma.geometry_transform = translate(0, 0, -1.1) diff --git a/demos/plasmas/beam_into_slab.py b/demos/plasmas/beam_into_slab.py index 373ae7f9..0eec4b2a 100644 --- a/demos/plasmas/beam_into_slab.py +++ b/demos/plasmas/beam_into_slab.py @@ -1,4 +1,22 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib.pyplot as plt @@ -11,7 +29,7 @@ from cherab.core.atomic import hydrogen, deuterium, carbon, Line from cherab.core.model import SingleRayAttenuator, BeamCXLine from cherab.tools.plasmas.slab import build_slab_plasma -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData ############### @@ -20,7 +38,7 @@ world = World() plasma = build_slab_plasma(peak_density=5e19, impurities=[(carbon, 6, 0.005)], parent=world) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) #################### # Visualise Plasma # @@ -60,7 +78,7 @@ ########################### # Inject beam into plasma # -adas = OpenADAS(permit_extrapolation=True, missing_rates_return_null=True) +atomic_data = AtomicData(permit_extrapolation=True, missing_rates_return_null=True) integration_step = 0.0025 beam_transform = translate(-0.5, 0.0, 0) * rotate_basis(Vector3D(1, 0, 0), Vector3D(0, 0, 1)) @@ -69,7 +87,7 @@ beam_full = Beam(parent=world, transform=beam_transform) beam_full.plasma = plasma -beam_full.atomic_data = adas +beam_full.atomic_data = atomic_data beam_full.energy = beam_energy beam_full.power = 3e6 beam_full.element = deuterium @@ -84,7 +102,7 @@ beam_half = Beam(parent=world, transform=beam_transform) beam_half.plasma = plasma -beam_half.atomic_data = adas +beam_half.atomic_data = atomic_data beam_half.energy = beam_energy / 2 beam_half.power = 3e6 beam_half.element = deuterium @@ -99,7 +117,7 @@ beam_third = Beam(parent=world, transform=beam_transform) beam_third.plasma = plasma -beam_third.atomic_data = adas +beam_third.atomic_data = atomic_data beam_third.energy = beam_energy / 3 beam_third.power = 3e6 beam_third.element = deuterium diff --git a/demos/plasmas/ionisation_balance_1d.py b/demos/plasmas/ionisation_balance_1d.py index de7b8274..c1a4bbd7 100644 --- a/demos/plasmas/ionisation_balance_1d.py +++ b/demos/plasmas/ionisation_balance_1d.py @@ -1,4 +1,22 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + from collections.abc import Iterable import matplotlib._color_data as mcd import matplotlib.pyplot as plt @@ -6,7 +24,7 @@ from raysect.core.math.function.float import Interpolator1DArray from cherab.core.atomic import neon, hydrogen, helium -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.plasmas.ionisation_balance import (fractional_abundance, interpolators1d_fractional, from_elementdensity, match_plasma_neutrality, interpolators1d_from_elementdensity, @@ -75,8 +93,8 @@ def exp_decay(r, lamb, max_val): n_element2 = Interpolator1DArray(psin_1d, n_element2_profile, 'cubic', 'none', 0) n_tcx_donor = Interpolator1DArray(psin_1d, n_tcx_donor_profile, 'cubic', 'none', 0) -# load adas atomic database and define elements -adas = OpenADAS(permit_extrapolation=True) +# load atomic database and define elements +atomic_data = AtomicData(permit_extrapolation=True) element = neon element2 = helium @@ -84,8 +102,8 @@ def exp_decay(r, lamb, max_val): donor_element = hydrogen # calculate profiles of fractional abundance for the element -abundance_fractional_profile = fractional_abundance(adas, element, n_e_profile, t_e_profile) -abundance_fractional_profile_tcx = fractional_abundance(adas, element, n_e_profile, t_e_profile, +abundance_fractional_profile = fractional_abundance(atomic_data, element, n_e_profile, t_e_profile) +abundance_fractional_profile_tcx = fractional_abundance(atomic_data, element, n_e_profile, t_e_profile, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor, tcx_donor_charge=0, free_variable=psin_1d) @@ -102,9 +120,9 @@ def exp_decay(r, lamb, max_val): plt.title('Fractional Abundance VS $\Psi_n$') # calculate charge state density profiles by specifying element density -density_element_profiles = from_elementdensity(adas, element, n_element, n_e_profile, +density_element_profiles = from_elementdensity(atomic_data, element, n_element, n_e_profile, t_e, free_variable=psin_1d) -density_element_profiles_tcx = from_elementdensity(adas, element, n_element, n_e_profile, +density_element_profiles_tcx = from_elementdensity(atomic_data, element, n_element, n_e_profile, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor_profile, tcx_donor_charge=0, free_variable=psin_1d) @@ -123,12 +141,12 @@ def exp_decay(r, lamb, max_val): # calculate fill the plasma with bulk element to match plasma neutrality condition # calculate ion densities for a 2nd element -density_element2_profiles_tcx = from_elementdensity(adas, element2, n_element2, n_e_profile, +density_element2_profiles_tcx = from_elementdensity(atomic_data, element2, n_element2, n_e_profile, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor_profile, tcx_donor_charge=0, free_variable=psin_1d) # fill plasma with 3rd element to match plasma neutrality -density_element3_profiles_tcx = match_plasma_neutrality(adas, element_bulk, +density_element3_profiles_tcx = match_plasma_neutrality(atomic_data, element_bulk, [density_element_profiles_tcx, density_element2_profiles_tcx], n_e, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor_profile, @@ -160,18 +178,18 @@ def exp_decay(r, lamb, max_val): ax.set_ylabel("ion density [m$^{-3}]$") # create ion density 1d interpolators -interpolators_element_1d_fractional = interpolators1d_fractional(adas, element, psin_1d, n_e, t_e, +interpolators_element_1d_fractional = interpolators1d_fractional(atomic_data, element, psin_1d, n_e, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor, tcx_donor_charge=0) -interpolators_element_1d_density = interpolators1d_from_elementdensity(adas, element, psin_1d, n_element, n_e, t_e, +interpolators_element_1d_density = interpolators1d_from_elementdensity(atomic_data, element, psin_1d, n_element, n_e, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor, tcx_donor_charge=0) -interpolators_element2_1d_density = interpolators1d_from_elementdensity(adas, element2, psin_1d, n_element2, n_e, t_e, +interpolators_element2_1d_density = interpolators1d_from_elementdensity(atomic_data, element2, psin_1d, n_element2, n_e, t_e, tcx_donor=donor_element, tcx_donor_n=n_tcx_donor, tcx_donor_charge=0) # also it is possible to combine different kinds of parameter types (profiles. numbers and interpolators) -interpolators_element3_1d_density = interpolators1d_match_plasma_neutrality(adas, element_bulk, psin_1d, +interpolators_element3_1d_density = interpolators1d_match_plasma_neutrality(atomic_data, element_bulk, psin_1d, [interpolators_element_1d_density, density_element2_profiles_tcx], n_e, t_e, tcx_donor=donor_element, diff --git a/demos/plasmas/ionisation_balance_2d.py b/demos/plasmas/ionisation_balance_2d.py index 7a8a9ca9..c316d3b8 100644 --- a/demos/plasmas/ionisation_balance_2d.py +++ b/demos/plasmas/ionisation_balance_2d.py @@ -1,10 +1,29 @@ + +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + from collections.abc import Iterable import matplotlib.pyplot as plt import numpy as np from raysect.core.math.function.float import Interpolator1DArray, Interpolator2DArray from cherab.core.atomic import neon, hydrogen, helium -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData from cherab.tools.equilibrium import example_equilibrium from cherab.tools.plasmas.ionisation_balance import (fractional_abundance, equilibrium_map3d_fractional, equilibrium_map3d_from_elementdensity, @@ -78,8 +97,8 @@ def exp_decay(r, lamb, max_val, lim=None): equilibrium = example_equilibrium() # plot_equilibrium(equilibrium, detail=True) -# load adas atomic database and define elements -atomic_data = OpenADAS(permit_extrapolation=True) +# load atomic database and define elements +atomic_data = AtomicData(permit_extrapolation=True) element = neon element2 = helium diff --git a/demos/plasmas/mesh_plasma.py b/demos/plasmas/mesh_plasma.py index 9148073d..eb82d072 100644 --- a/demos/plasmas/mesh_plasma.py +++ b/demos/plasmas/mesh_plasma.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-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas # # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); @@ -32,7 +32,7 @@ from cherab.core.math import sample3d, AxisymmetricMapper from cherab.core.atomic import deuterium from cherab.core.model import ExcitationLine, RecombinationLine -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData # tunable parameters @@ -94,7 +94,7 @@ def ion_distribution(r, v_core, v_lcfs, p=4, q=3, lcfs_radius=1): world = World() # setup scenegraph plasma = Plasma(parent=world) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) plasma.geometry = Cylinder(1.5, 4, transform=translate(0, 0, -2)) plasma.geometry_transform = translate(0, 0, -2) diff --git a/demos/plasmas/slab_plasma.py b/demos/plasmas/slab_plasma.py index 37e1a395..02862109 100644 --- a/demos/plasmas/slab_plasma.py +++ b/demos/plasmas/slab_plasma.py @@ -1,4 +1,22 @@ +# Copyright 2016-2022 Euratom +# Copyright 2016-2022 United Kingdom Atomic Energy Authority +# Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + import numpy as np import matplotlib.pyplot as plt from raysect.optical import World @@ -6,13 +24,13 @@ from cherab.core.math import sample3d from cherab.core.atomic import hydrogen, carbon from cherab.tools.plasmas.slab import build_slab_plasma -from cherab.openadas import OpenADAS +from cherab.atomic import AtomicData # make a slab plasma world = World() plasma = build_slab_plasma(peak_density=5e19, impurities=[(carbon, 6, 0.005)], parent=world) -plasma.atomic_data = OpenADAS(permit_extrapolation=True) +plasma.atomic_data = AtomicData(permit_extrapolation=True) #################### # Visualise Plasma # diff --git a/docs/source/atomic/atomic_data.rst b/docs/source/atomic/atomic_data.rst index 650d89b4..5b0b08ea 100644 --- a/docs/source/atomic/atomic_data.rst +++ b/docs/source/atomic/atomic_data.rst @@ -5,5 +5,9 @@ Atomic Data .. toctree:: elements_and_isotopes emission_lines - rate_coefficients - gaunt_factors + atomic_data_interface + core_classes + data_interpolators + repository + openadas + diff --git a/docs/source/atomic/atomic_data_interface.rst b/docs/source/atomic/atomic_data_interface.rst new file mode 100644 index 00000000..774cbad1 --- /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: + +Local atomic data source +------------------------ + +Interface to local atomic data repository. + +.. autoclass:: cherab.atomic.atomicdata.AtomicData + :members: diff --git a/docs/source/atomic/rate_coefficients.rst b/docs/source/atomic/core_classes.rst similarity index 62% rename from docs/source/atomic/rate_coefficients.rst rename to docs/source/atomic/core_classes.rst index b6547820..5066df49 100644 --- a/docs/source/atomic/rate_coefficients.rst +++ b/docs/source/atomic/core_classes.rst @@ -1,3 +1,7 @@ +Abstract classes +================ + +Abstract classes for various atomic data. Rate Coefficients ----------------- @@ -14,6 +18,34 @@ For example, some atomic data providers might use interpolated data while others provide theoretical equations. Cherab emission models only need to know how to call 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 +71,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 density: Electron density in m^-3 + :param float temperature: Electron temperature in eV. :return: The effective PEC rate [Wm^3]. Some example code for requesting PEC objects and sampling them with the __call__() @@ -51,15 +83,15 @@ method. >>> import numpy as np >>> import matplotlib.pyplot as plt >>> from cherab.core.atomic import deuterium - >>> from cherab.openadas import OpenADAS + >>> from cherab.atomic import AtomicData >>> >>> # initialise the atomic data provider - >>> adas = OpenADAS() + >>> atomic_data = AtomicData() >>> >>> # request d-alpha instance of ImpactExcitationRate - >>> dalpha_excit = adas.impact_excitation_pec(deuterium, 0, (3, 2)) + >>> dalpha_excit = atomic_data.impact_excitation_pec(deuterium, 0, (3, 2)) >>> # request d-alpha instance of RecombinationRate - >>> dalpha_recom = adas.recombination_pec(deuterium, 0, (3, 2)) + >>> dalpha_recom = atomic_data.recombination_pec(deuterium, 0, (3, 2)) >>> >>> # evaluate D-alpha ImpactExcitationRate PEC at n_e = 1E19 m^-3 and t_e = 2 eV >>> dalpha_excit(1E19, 2) @@ -103,23 +135,23 @@ method. .. code-block:: pycon >>> from cherab.core.atomic import deuterium, carbon - >>> from cherab.openadas import OpenADAS + >>> from cherab.atomic import AtomicData >>> >>> # initialise the atomic data provider - >>> adas = OpenADAS(permit_extrapolation=True) + >>> atomic_data = AtomicData(permit_extrapolation=True) >>> >>> # Request beam stopping rate and sample - >>> bms = adas.beam_stopping_rate(deuterium, carbon, 6) + >>> bms = atomic_data.beam_stopping_rate(deuterium, carbon, 6) >>> bms(50000, 1E19, 1) 1.777336e-13 >>> >>> # Sample the beam population rate - >>> bmp = adas.beam_population_rate(deuterium, 2, carbon, 6) + >>> bmp = atomic_data.beam_population_rate(deuterium, 2, carbon, 6) >>> bmp(50000, 1E19, 1) 7.599066e-4 >>> >>> # Sample the beam emission rate - >>> bme = adas.beam_emission_pec(deuterium, deuterium, 1, (3, 2)) + >>> bme = atomic_data.beam_emission_pec(deuterium, deuterium, 1, (3, 2)) >>> bme(50000, 1E19, 1) 8.651598e-34 @@ -131,12 +163,12 @@ Some example code for requesting beam CX rate object and sampling it with the __ .. code-block:: pycon >>> from cherab.core.atomic import deuterium, carbon - >>> from cherab.openadas import OpenADAS + >>> from cherab.atomic import AtomicData >>> >>> # initialise the atomic data provider - >>> adas = OpenADAS(permit_extrapolation=True) + >>> atomic_data = AtomicData(permit_extrapolation=True) >>> - >>> cxr = adas.beam_cx_pec(deuterium, carbon, 6, (8, 7)) + >>> cxr = atomic_data.beam_cx_pec(deuterium, carbon, 6, (8, 7)) >>> cxr_n1, cxr_n2 = cxr >>> cxr_n1(50000, 100, 1E19, 1, 1) 5.826619e-33 @@ -163,9 +195,9 @@ Abundances .. code-block:: pycon >>> from cherab.core.atomic import neon - >>> from cherab.adas import ADAS + >>> from cherab.atomic import AtomicData >>> - >>> atomic_data = ADAS() + >>> atomic_data = AtomicData() >>> >>> ne0_frac = atomic_data.fractional_abundance(neon, 0) >>> ne0_frac(1E19, 1.0) @@ -175,19 +207,20 @@ Abundances Radiated Power ^^^^^^^^^^^^^^ -.. class:: cherab.core.atomic.rates.RadiatedPower +.. autoclass:: cherab.core.atomic.rates.TotalRadiatedPower + +.. autoclass:: cherab.core.atomic.rates.LineRadiationPower - Total radiated power for a given species and radiation type. +.. autoclass:: cherab.core.atomic.rates.ContinuumPower - Radiation type can be: - - 'total' (line + recombination + bremsstrahlung + charge exchange) - - 'line' radiation - - 'continuum' (recombination + bremsstrahlung) - - 'cx' charge exchange +.. autoclass:: cherab.core.atomic.rates.CXRadiationPower + +The `TotalRadiatedPower`, `LineRadiationPower`, `ContinuumPower` and 'CXRadiationPower' classes all share +the same call signatures. .. function:: __call__(electron_density, electron_temperature) - Evaluate the total radiated power of this species at the given plasma conditions. + Evaluate the radiated power of this species at the given plasma conditions. This function just wraps the cython evaluate() method. @@ -198,48 +231,42 @@ Radiated Power .. code-block:: pycon >>> from cherab.core.atomic import neon - >>> from cherab.adas import ADAS + >>> from cherab.atomic import AtomicData >>> - >>> atomic_data = ADAS() + >>> atomic_data = AtomicData() >>> - >>> ne_total_rad = atomic_data.radiated_power_rate(neon, 'total') + >>> ne_total_rad = atomic_data.total_radiated_power(neon) >>> ne_total_rad(1E19, 10) * 1E19 9.2261136594e-08 >>> - >>> ne_continuum_rad = atomic_data.radiated_power_rate(neon, 'continuum') + >>> ne_continuum_rad = atomic_data.continuum_radiated_power_rate(neon, 1) >>> ne_continuum_rad(1E19, 10) * 1E19 3.4387672228e-10 + >>> + >>> ne1_line_rad = atomic_data.line_radiated_power_rate(neon, 1) + >>> ne1_line_rad(1E19, 10) * 1E19 + 1.7723122151e-11 -.. class:: cherab.core.atomic.rates.StageResolvedLineRadiation - - Total ionisation state resolved line radiated power rate. - - :param Element element: the radiating element - :param int ionisation: the integer charge state for this ionisation stage - :param str name: optional label identifying this rate +Gaunt Factors +------------- - .. function:: __call__(electron_density, electron_temperature) +This includes classes for temperature-averaged Gaunt factors used to calculate Bremsstrahlung (free-free Gaunt factor) +and radiative recombination continuum (bound-free Gaunt factor) emission. - Evaluate the total radiated power of this species at the given plasma conditions. +.. autoclass:: cherab.core.atomic.gaunt.FreeFreeGauntFactor + :members: + :special-members: __call__ - This function just wraps the cython evaluate() method. +Zeeman structure +---------------- - :param float electron_density: electron density in m^-3 - :param float electron_temperature: electron temperature in eV +The class that provides wavelengths and ratios of +:math:`\pi`-/:math:`\sigma`-polarised Zeeman components for any given value of +magnetic field strength. -.. code-block:: pycon +.. autoclass:: cherab.core.atomic.zeeman.ZeemanStructure + :members: + :special-members: __call__ - >>> from cherab.core.atomic import neon - >>> from cherab.adas import ADAS - >>> - >>> atomic_data = ADAS() - >>> - >>> ne0_line_rad = atomic_data.stage_resolved_line_radiation_rate(neon, 0) - >>> ne0_line_rad(1E19, 10) * 1E19 - 6.1448254527e-16 - >>> - >>> ne1_line_rad = atomic_data.stage_resolved_line_radiation_rate(neon, 1) - >>> ne1_line_rad(1E19, 10) * 1E19 - 1.7723122151e-11 diff --git a/docs/source/atomic/data_interpolators.rst b/docs/source/atomic/data_interpolators.rst new file mode 100644 index 00000000..663642e2 --- /dev/null +++ b/docs/source/atomic/data_interpolators.rst @@ -0,0 +1,115 @@ +Atomic data interpolators +========================= + +Classes that interpolate numerical atomic data. + +Rate Coefficients +----------------- + +Atomic Processes +^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.atomic.rates.atomic.IonisationRate + :members: + +.. autoclass:: cherab.atomic.rates.atomic.NullIonisationRate + :members: + +.. autoclass:: cherab.atomic.rates.atomic.RecombinationRate + :members: + +.. autoclass:: cherab.atomic.rates.atomic.NullRecombinationRate + :members: + +.. autoclass:: cherab.atomic.rates.atomic.ThermalCXRate + :members: + +.. autoclass:: cherab.atomic.rates.atomic.NullThermalCXRate + :members: + +Photon Emissivity Coefficients +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.atomic.rates.pec.ImpactExcitationPEC + :members: + +.. autoclass:: cherab.atomic.rates.pec.NullImpactExcitationPEC + :members: + +.. autoclass:: cherab.atomic.rates.pec.RecombinationPEC + :members: + +.. autoclass:: cherab.atomic.rates.pec.NullRecombinationPEC + :members: + +Beam-Plasma Interaction Rates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: cherab.atomic.rates.cx.BeamCXPEC + :members: + +.. autoclass:: cherab.atomic.rates.cx.NullBeamCXPEC + :members: + +.. autoclass:: cherab.atomic.rates.beam.BeamStoppingRate + :members: + +.. autoclass:: cherab.atomic.rates.beam.NullBeamStoppingRate + :members: + +.. autoclass:: cherab.atomic.rates.beam.BeamPopulationRate + :members: + +.. autoclass:: cherab.atomic.rates.beam.NullBeamPopulationRate + :members: + +.. autoclass:: cherab.atomic.rates.beam.BeamEmissionPEC + :members: + +.. autoclass:: cherab.atomic.rates.beam.NullBeamEmissionPEC + :members: + +Abundances +^^^^^^^^^^ + +.. autoclass:: cherab.atomic.rates.fractional_abundance.FractionalAbundance + :members: + +Radiated Power +^^^^^^^^^^^^^^ + +.. autoclass:: cherab.atomic.rates.radiated_power.LineRadiationPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.NullLineRadiationPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.ContinuumPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.NullContinuumPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.CXRadiationPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.NullCXRadiationPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.TotalRadiatedPower + :members: + +.. autoclass:: cherab.atomic.rates.radiated_power.NullTotalRadiatedPower + :members: + +Gaunt Factors +------------- + +.. autoclass:: cherab.atomic.gaunt.gaunt.FreeFreeGauntFactor + :members: + +Zeeman structure +---------------- + +.. autoclass:: cherab.atomic.zeeman.zeeman.ZeemanStructure + :members: diff --git a/docs/source/atomic/emission_lines.rst b/docs/source/atomic/emission_lines.rst index 513598e2..88b93631 100644 --- a/docs/source/atomic/emission_lines.rst +++ b/docs/source/atomic/emission_lines.rst @@ -4,6 +4,3 @@ Spectroscopic Emission Lines .. autoclass:: cherab.core.atomic.line.Line :members: - -.. autoclass:: cherab.core.atomic.zeeman.ZeemanStructure - :members: diff --git a/docs/source/atomic/gaunt_factors.rst b/docs/source/atomic/gaunt_factors.rst deleted file mode 100644 index b7950ff9..00000000 --- a/docs/source/atomic/gaunt_factors.rst +++ /dev/null @@ -1,23 +0,0 @@ - -Gaunt factors -------------- - -This includes classes for temperature-averaged Gaunt factors used to calculate Bremsstrahlung (free-free Gaunt factor) -and radiative recombination continuum (bound-free Gaunt factor) emission. - - -Free-free Gaunt factors -^^^^^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: cherab.core.atomic.gaunt.FreeFreeGauntFactor - :members: - :special-members: __call__ - -.. autoclass:: cherab.core.atomic.gaunt.InterpolatedFreeFreeGauntFactor - :show-inheritance: - :members: - -.. autoclass:: cherab.core.atomic.gaunt.MaxwellianFreeFreeGauntFactor - :show-inheritance: - :members: - diff --git a/docs/source/atomic/openadas.rst b/docs/source/atomic/openadas.rst new file mode 100644 index 00000000..5e55fe88 --- /dev/null +++ b/docs/source/atomic/openadas.rst @@ -0,0 +1,28 @@ + +Open-ADAS +--------- + +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..a6b1f528 --- /dev/null +++ b/docs/source/atomic/repository.rst @@ -0,0 +1,86 @@ + +Atomic data repository +---------------------- + +The following functions allow to manipulate the local atomic data repository. + +The path to the default atomic data repository is determined by the `CHERAB_ATOMIC_DATA` environment variable. +If this variable is not set, the default repository is created at `~/.cherab/atomicdata/default_repository`. + +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.atomic.repository import populate + >>> populate() + + +.. autofunction:: cherab.atomic.repository.create.populate + +Wavelength +^^^^^^^^^^ + +.. automodule:: cherab.atomic.repository.wavelength + :members: + +Ionisation +^^^^^^^^^^ + +.. autofunction:: cherab.atomic.repository.atomic.add_ionisation_rate + +.. autofunction:: cherab.atomic.repository.atomic.get_ionisation_rate + +.. autofunction:: cherab.atomic.repository.atomic.update_ionisation_rates + +Recombination +^^^^^^^^^^^^^ + +.. autofunction:: cherab.atomic.repository.atomic.add_recombination_rate + +.. autofunction:: cherab.atomic.repository.atomic.get_recombination_rate + +.. autofunction:: cherab.atomic.repository.atomic.update_recombination_rates + +Thermal Charge Exchange +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: cherab.atomic.repository.atomic.add_thermal_cx_rate + +.. autofunction:: cherab.atomic.repository.atomic.get_thermal_cx_rate + +.. autofunction:: cherab.atomic.repository.atomic.update_thermal_cx_rates + +Photon Emissivity Coefficients +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: cherab.atomic.repository.pec + :members: + +Radiated Power +^^^^^^^^^^^^^^ + +.. automodule:: cherab.atomic.repository.radiated_power + :members: + +Fractional Abundance +^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: cherab.atomic.repository.fractional_abundance + :members: + +Beam +^^^^ + +.. automodule:: cherab.atomic.repository.beam.cx + :members: + +.. automodule:: cherab.atomic.repository.beam.emission + :members: + +.. automodule:: cherab.atomic.repository.beam.population + :members: + +.. automodule:: cherab.atomic.repository.beam.stopping + :members: + diff --git a/docs/source/installation_and_structure.rst b/docs/source/installation_and_structure.rst index e9c051bf..2170dcae 100644 --- a/docs/source/installation_and_structure.rst +++ b/docs/source/installation_and_structure.rst @@ -140,7 +140,7 @@ the demos supplied with Cherab. To run the script please enter the following com .. code-block:: pycon - >>> from cherab.openadas.repository import populate + >>> from cherab.atomic.repository import populate >>> populate() diff --git a/setup.py b/setup.py index 6cf51624..0b52c151 100644 --- a/setup.py +++ b/setup.py @@ -142,7 +142,7 @@ # setup a rate repository with common rates if install_rates: try: - from cherab.openadas import repository + from cherab.atomic import repository repository.populate() except ImportError: