Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ New:
* Add Integrator2D base class for integration of two-dimensional functions. (#472)
* Support Raysect 0.9. (#486)
* Test against Python 3.9, 3.10, 3.11, 3.12, 3.13 and latest released Numpy. Drop Python 3.7, 3.8 and older Numpy from tests. (#486)
* Make values in `cherab.core.utility.constants` accessible to Python. (#509)

Release 1.5.0 (27 Aug 2024)
-------------------
Expand Down
49 changes: 49 additions & 0 deletions cherab/core/utility/constants.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
#
# See the Licence for the specific language governing permissions and limitations
# under the Licence.
import sys
from types import ModuleType

from libc.math cimport M_PI


cdef:

Expand All @@ -35,3 +40,47 @@ cdef:
double RYDBERG_CONSTANT_EV = 13.605693122994
double VACUUM_PERMITTIVITY = 8.8541878128e-12
double BOHR_MAGNETON = 5.78838180123e-5 # in eV/T


# Make the constants available to Python too.
# To ensure the Python and Cython constants do not got out of sync the exported
# Python attributes of the module are made read only using module getattr.
cdef dict _CONSTANTS = {
# c stdlib
"RECIP_2_PI": RECIP_2_PI,
"RECIP_4_PI": RECIP_4_PI,
"DEGREES_TO_RADIANS": DEGREES_TO_RADIANS,
"RADIANS_TO_DEGREES": RADIANS_TO_DEGREES,
# NIST 2018
"ATOMIC_MASS": ATOMIC_MASS,
"ELEMENTARY_CHARGE": ELEMENTARY_CHARGE,
"SPEED_OF_LIGHT": SPEED_OF_LIGHT,
"PLANCK_CONSTANT": PLANCK_CONSTANT,
"HC_EV_NM": HC_EV_NM,
"ELECTRON_CLASSICAL_RADIUS": ELECTRON_CLASSICAL_RADIUS,
"ELECTRON_REST_MASS": ELECTRON_REST_MASS,
"RYDBERG_CONSTANT_EV": RYDBERG_CONSTANT_EV,
"VACUUM_PERMITTIVITY": VACUUM_PERMITTIVITY,
"BOHR_MAGNETON": BOHR_MAGNETON,
}


def __getattr__(name):
if name not in _CONSTANTS:
raise AttributeError()
return _CONSTANTS[name]


def __dir__():
return list(_CONSTANTS.keys())


class ReadOnlyModule(ModuleType):
def __setattr__(self, attr, value):
raise AttributeError("Constants are read-only")

def __delattr__(self, attr):
raise AttributeError("Constants are read-only")


sys.modules[__name__].__class__ = ReadOnlyModule
84 changes: 84 additions & 0 deletions cherab/core/utility/tests/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2026 Oak Ridge National Laboratory
#
# 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 math
import unittest
from cherab.core.utility import constants


class TestConstants(unittest.TestCase):
def setUp(self):
self._expected_constants = dict(
# sourced c standard maths library
# CPython wraps libc's math so uses the same constants as cython's
# cimport of libc.math.
RECIP_2_PI=1 / (2 * math.pi),
RECIP_4_PI=1 / (4 * math.pi),
DEGREES_TO_RADIANS=math.pi / 180,
RADIANS_TO_DEGREES=180 / math.pi,

# sourced from NIST, CODATA 2018=https://physics.nist.gov/cuu/Constants/Table/allascii.txt
ATOMIC_MASS=1.66053906660e-27,
ELEMENTARY_CHARGE=1.602176634e-19,
SPEED_OF_LIGHT=299792458.0,
PLANCK_CONSTANT=6.62607015e-34,
HC_EV_NM=1239.8419738620933, # (Planck constant in eV s) x (speed of light in nm/s)
ELECTRON_CLASSICAL_RADIUS=2.8179403262e-15,
ELECTRON_REST_MASS=9.1093837015e-31,
RYDBERG_CONSTANT_EV=13.605693122994,
VACUUM_PERMITTIVITY=8.8541878128e-12,
BOHR_MAGNETON=5.78838180123e-5, # in eV/T
)

def test_all_exported(self):
"""
Test Cython constants exported as Python floats.
"""
for name, value in self._expected_constants.items():
self.assertEqual(value, getattr(constants, name))

def test_exported_literal(self):
"""
Test a constant accessed by literal name.
"""
self.assertEqual(self._expected_constants['ATOMIC_MASS'], constants.ATOMIC_MASS)

def test_exported_names(self):
"""
Test all exported names are as expected by this test class.
"""
self.assertEqual(sorted(self._expected_constants.keys()), sorted(dir(constants)))

def test_readonly(self):
"""
Test that constants can't be modified or removed and new constants can't be added.
"""
with self.assertRaises(AttributeError):
constants.ATOMIC_MASS = 1.66e-27

def test_nonew(self):
"""
Test that attempting to assign a new constant from Python errors.
"""
with self.assertRaises(AttributeError):
constants.TAU = math.tau

def test_nodel(self):
"""
Test that attempting to delete and constant from Python errors.
"""
with self.assertRaises(AttributeError):
del constants.RYDBERG_CONSTANT_EV
Loading