diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da0d082c..25e2467d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,12 +7,11 @@ on: jobs: tests: name: Run tests - runs-on: ubuntu-22.04 # Needed for Python 3.7 compatibility + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - numpy-version: ["oldest-supported-numpy", "'numpy<2'"] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout code uses: actions/checkout@v2 @@ -23,9 +22,9 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: python -m pip install --prefer-binary cython~=3.0 ${{ matrix.numpy-version }} scipy matplotlib "pyopencl[pocl]>=2022.2.4" + run: python -m pip install --prefer-binary setuptools cython~=3.1 numpy>=2 scipy matplotlib "pyopencl[pocl]>=2022.2.4" - name: Install Raysect from pypi - run: pip install raysect==0.8.1.* + run: pip install raysect==0.9.* - name: Build cherab run: dev/build.sh - name: Run tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f22a5d4..417bd613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ New: * Add Function6D framework. (#478) * Add e_field attribute to Plasma object for electric field vector. (#465) * 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) Release 1.5.0 (27 Aug 2024) ------------------- @@ -134,7 +136,7 @@ API changes: New: * Merged cherab-openadas package into the core cherab package to simplify installation. -* Beam object uses a cone primitive instead of a cylinder for the bounding volume of divergent beams. +* Beam object uses a cone primitive instead of a cylinder for the bounding volume of divergent beams. * Added Clamp functions. * Added ThermalCXRate. * Added optimised ray transfer grid calculation tools. @@ -162,7 +164,7 @@ New: Bug fixes: * Improved handling on non c-order arrays in various methods. -* Numerous minor bug fixes (see commit history) +* Numerous minor bug fixes (see commit history) Release 1.0.1 (1 Oct 2018) diff --git a/cherab/core/VERSION b/cherab/core/VERSION index 4c39f7c4..8a3469e1 100644 --- a/cherab/core/VERSION +++ b/cherab/core/VERSION @@ -1 +1 @@ -1.6.0.dev1 +1.6.0.dev2 diff --git a/cherab/core/model/laser/profile.pyx b/cherab/core/model/laser/profile.pyx index 82376980..ed5f6024 100644 --- a/cherab/core/model/laser/profile.pyx +++ b/cherab/core/model/laser/profile.pyx @@ -738,7 +738,7 @@ def generate_segmented_cylinder(radius, length): Generates a segmented cylindrical laser geometry Approximates a long cylinder with a cylindrical segments to optimize - targetted and importance sampling. The height of a cylinder segments is roughly + targeted and importance sampling. The height of a cylinder segments is roughly 2 * cylinder radius. :return: List of cylinders diff --git a/cherab/tools/observers/bolometry.py b/cherab/tools/observers/bolometry.py index 20179796..33870b0a 100644 --- a/cherab/tools/observers/bolometry.py +++ b/cherab/tools/observers/bolometry.py @@ -18,16 +18,17 @@ # under the Licence. from enum import Enum +from warnings import warn import functools import numpy as np from raysect.core import Node, translate, rotate_basis, Point3D, Vector3D, Ray as CoreRay, Primitive, World -from raysect.core.math.sampler import TargettedHemisphereSampler, RectangleSampler3D +from raysect.core.math.sampler import TargetedHemisphereSampler, RectangleSampler3D from raysect.primitive import Box, Cylinder, Subtract, Union from raysect.optical.observer import PowerPipeline0D, RadiancePipeline0D, \ - SpectralPowerPipeline0D, SpectralRadiancePipeline0D, SightLine, TargettedPixel + SpectralPowerPipeline0D, SpectralRadiancePipeline0D, SightLine, TargetedPixel from raysect.optical.observer import PowerPipeline2D, RadiancePipeline2D, \ - SpectralPowerPipeline2D, SpectralRadiancePipeline2D, TargettedCCDArray + SpectralPowerPipeline2D, SpectralRadiancePipeline2D, TargetedCCDArray from raysect.optical.material.material import NullMaterial from raysect.optical.material import AbsorbingSurface @@ -213,7 +214,7 @@ class BolometerSlit(Node): larger than the slit dx and dy, which can cause partial occlusion of nearby primitives. It also relies on no rays being launched with directions outside the solid angle of the aperture's bounding sphere: depending on the - foil-slit distance and slit size, and also the foil's targetted_path_prob, + foil-slit distance and slit size, and also the foil's targeted_path_prob, this may not be guaranteed. Supplying a proper mesh geometry for the camera is recommended instead of using a CSG aperture. @@ -351,7 +352,7 @@ def curvature_radius(self): return self._curvature_radius -class BolometerFoil(TargettedPixel): +class BolometerFoil(TargetedPixel): """ A rectangular foil bolometer detector. @@ -447,7 +448,7 @@ def __init__(self, detector_id, centre_point, basis_x, dx, basis_y, dy, slit, translation = translate(centre_point.x, centre_point.y, centre_point.z) rotation = rotate_basis(normal_vec, basis_y) - super().__init__([slit.target], targetted_path_prob=1.0, + super().__init__([slit.target], targeted_path_prob=1.0, pixel_samples=1000, x_width=dx, y_width=dy, spectral_bins=1, quiet=True, parent=parent, transform=translation * rotation, name=detector_id) @@ -516,6 +517,24 @@ def accumulate(self, value): # Discard any samples from previous accumulate behaviour pipeline.value.clear() + @property + def targetted_path_prob(self): + warn( + "The 'targetted_path_prob' property is deprecated, use 'targeted_path_prob' instead.", + DeprecationWarning, + stacklevel=2 + ) + return self._targeted_path_prob + + @targetted_path_prob.setter + def targetted_path_prob(self, value): + warn( + "The 'targetted_path_prob' property is deprecated, use 'targeted_path_prob' instead.", + DeprecationWarning, + stacklevel=2 + ) + self.targeted_path_prob = value + def as_sightline(self): """ Constructs a SightLine observer for this bolometer. @@ -661,8 +680,8 @@ def calculate_etendue(self, ray_count=10000, batches=10, max_distance=1e999): # generate bounding sphere and convert to local coordinate system sphere = target.bounding_sphere() spheres = [(sphere.centre.transform(self.to_local()), sphere.radius, 1.0)] - # instance targetted pixel sampler to sample directions - targetted_sampler = TargettedHemisphereSampler(spheres) + # instance targeted pixel sampler to sample directions + targeted_sampler = TargetedHemisphereSampler(spheres) # instance rectangle pixel sampler to sample origins point_sampler = RectangleSampler3D(width=self.x_width, height=self.y_width) @@ -671,8 +690,8 @@ def etendue_single_run(_): origins = point_sampler(samples=ray_count) passed = 0.0 for origin in origins: - # obtain targetted vector sample - direction, pdf = targetted_sampler(origin, pdf=True) + # obtain targeted vector sample + direction, pdf = targeted_sampler(origin, pdf=True) path_weight = R_2_PI * direction.z / pdf # Transform to world space origin = origin.transform(detector_transform) @@ -701,7 +720,7 @@ def etendue_single_run(_): return etendue, etendue_error -class BolometerIRVB(TargettedCCDArray): +class BolometerIRVB(TargetedCCDArray): """ A rectangular infra red video bolometer (IRVB). @@ -784,7 +803,7 @@ def __init__(self, name, width, pixels, slit, transform, parent=None, self._accumulate = None # Will be set after pipeline is created. super().__init__([slit.target], pixels=pixels, width=width, - targetted_path_prob=0.99, parent=parent, pipelines=[], + targeted_path_prob=0.99, parent=parent, pipelines=[], transform=transform, name=name) self.pixel_samples = 1000 self.spectral_bins = 1 @@ -896,6 +915,24 @@ def accumulate(self, value): if pipeline.frame is not None: pipeline.frame.clear() + @property + def targetted_path_prob(self): + warn( + "The 'targetted_path_prob' property is deprecated, use 'targeted_path_prob' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._targeted_path_prob + + @targetted_path_prob.setter + def targetted_path_prob(self, value): + warn( + "The 'targetted_path_prob' property is deprecated, use 'targeted_path_prob' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.targeted_path_prob = value + def as_sightlines(self): """ Constructs a SightLine observer for each pixel in this bolometer. diff --git a/cherab/tools/observers/group/targettedpixel.py b/cherab/tools/observers/group/targettedpixel.py index 07d13e7f..f21694aa 100644 --- a/cherab/tools/observers/group/targettedpixel.py +++ b/cherab/tools/observers/group/targettedpixel.py @@ -17,14 +17,14 @@ # under the Licence. from numpy import ndarray -from raysect.optical.observer import TargettedPixel +from raysect.optical.observer import TargetedPixel from .base import Observer0DGroup class TargettedPixelGroup(Observer0DGroup): """ - A group of targetted pixel under a single scene-graph node. + A group of targeted pixel under a single scene-graph node. A scene-graph object regrouping a series of 'TargettedPixel' observers as a scene-graph parent. Allows combined observation and display @@ -35,7 +35,8 @@ class TargettedPixelGroup(Observer0DGroup): :ivar list targets: Targets for preferential sampling :ivar list targetted_path_prob: Probability of ray being casted at the target """ - _OBSERVER_TYPE = TargettedPixel + + _OBSERVER_TYPE = TargetedPixel @property def x_width(self): @@ -76,7 +77,7 @@ def targets(self): """ List of target lists used by pixels for preferential sampling - :param list value: List of primitives to be set to each pixel or + :param list value: List of primitives to be set to each pixel or list of lists containing targets specific for each pixel in this case the number of lists must match number of pixels @@ -100,17 +101,16 @@ def targets(self, value): @property def targetted_path_prob(self): - return [pixel.targetted_path_prob for pixel in self._observers] - + return [pixel.targeted_path_prob for pixel in self._observers] + @targetted_path_prob.setter def targetted_path_prob(self, value): if isinstance(value, (list, tuple)): if len(value) == len(self._observers): for pixel, v in zip(self._observers, value): - pixel.targetted_path_prob = v + pixel.targeted_path_prob = v else: - raise ValueError("The length of 'value' ({}) " - "mismatches the number of pixels ({}).".format(len(value), len(self._observers))) + raise ValueError("The length of 'value' ({}) mismatches the number of pixels ({}).".format(len(value), len(self._observers))) else: for pixel in self._observers: - pixel.targetted_path_prob = value + pixel.targeted_path_prob = value diff --git a/cherab/tools/tests/test_observer_groups.py b/cherab/tools/tests/test_observer_groups.py index 1f5b7cb0..0418eab9 100644 --- a/cherab/tools/tests/test_observer_groups.py +++ b/cherab/tools/tests/test_observer_groups.py @@ -1,7 +1,7 @@ import unittest from raysect.core.workflow import RenderEngine -from raysect.optical.observer import Observer0D, SightLine, FibreOptic, Pixel, TargettedPixel, PowerPipeline0D, SpectralPowerPipeline0D +from raysect.optical.observer import Observer0D, SightLine, FibreOptic, Pixel, TargetedPixel, PowerPipeline0D, SpectralPowerPipeline0D from raysect.primitive import Sphere from cherab.tools.observers.group.base import Observer0DGroup @@ -26,7 +26,7 @@ def test_get_item(self): idx = slice(1, 3, 1) for observer, input_observer in zip(group[idx], self.observers[idx]): self.assertIs(observer, input_observer) - + for i, name in enumerate(names): self.assertIs(group[name], self.observers[i]) @@ -83,7 +83,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.pipelines = [ppln_0] - # render_engine + # render_engine engine = RenderEngine() group.render_engine = engine for group_engine in group.render_engine: @@ -102,7 +102,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.render_engine = [RenderEngine() for _ in range(len(group) - 1)] - # wavelengths + # wavelengths wvl = 500 group.min_wavelength = wvl - 100 group.max_wavelength = wvl + 100 @@ -139,7 +139,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.spectral_bins = [1000] * (len(group) + 1) - # quiet + # quiet quiet = [True] * len(group) group.quiet = quiet self.assertListEqual(group.quiet, quiet) @@ -152,7 +152,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.quiet = [False] * (len(group) + 1) - # rays + # rays probs = [0.2 + i*0.1 for i in range(len(group))] max_depths = [5 + i for i in range(len(group))] min_depths = [2 + i for i in range(len(group))] @@ -196,7 +196,7 @@ def test_assignments(self): group.ray_importance_sampling = [False] * (len(group) + 1) with self.assertRaises(ValueError): group.ray_important_path_weight = [0.7] * (len(group) + 1) - + # samples pixel_samples = [2000 + i*500 for i in range(len(group))] per_task = [5000 + i*100 for i in range(len(group))] @@ -352,7 +352,7 @@ class TargettedPixelGroupTestCase(PixelGroupTestCase): _GROUP_CLASS = TargettedPixelGroup def setUp(self): - self.observers = [TargettedPixel(targets=[Sphere()], pipelines=[PowerPipeline0D()]) for _ in range(self._NUM)] + self.observers = [TargetedPixel(targets=[Sphere()], pipelines=[PowerPipeline0D()]) for _ in range(self._NUM)] def test_targets(self): group = self._GROUP_CLASS(observers=self.observers) @@ -381,8 +381,8 @@ def test_targets(self): prob = 0.8 group.targetted_path_prob = prob - for group_targetted_path_prob in group.targetted_path_prob: - self.assertEqual(group_targetted_path_prob, prob) + for group_targeted_path_prob in group.targetted_path_prob: + self.assertEqual(group_targeted_path_prob, prob) with self.assertRaises(ValueError): group.targetted_path_prob = [0.7] * (len(group) + 1) diff --git a/cherab/tools/tests/test_voxels.py b/cherab/tools/tests/test_voxels.py index 046fffbe..62b4fcc6 100644 --- a/cherab/tools/tests/test_voxels.py +++ b/cherab/tools/tests/test_voxels.py @@ -280,8 +280,7 @@ def test_rectangle_area(self): for rectangle in RECTANGULAR_VOXEL_COORDS: coords = np.asarray(rectangle) voxel = AxisymmetricVoxel(coords) - dx = coords[:, 0].ptp() - dy = coords[:, 1].ptp() + dx, dy = np.ptp(coords, axis=0) expected_area = dx * dy self.assertEqual(voxel.cross_sectional_area, expected_area) diff --git a/pyproject.toml b/pyproject.toml index 4849f0b5..e198bb5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=62.3", "oldest-supported-numpy", "cython~=3.0", "raysect==0.8.1.*"] +requires = ["setuptools>=62.3", "numpy", "cython~=3.1", "raysect==0.9.1.*"] build-backend="setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 9a13464d..c99e710f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -cython~=3.0 -numpy>=1.14,<2.0 +cython~=3.1 +numpy>=2.0 scipy matplotlib -raysect==0.8.1.* +raysect==0.9.1.* diff --git a/setup.py b/setup.py index f11dd08f..c57bb0d3 100644 --- a/setup.py +++ b/setup.py @@ -117,14 +117,14 @@ long_description=long_description, long_description_content_type="text/markdown", install_requires=[ - "numpy>=1.14,<2.0", + "numpy>=2.0", "scipy", "matplotlib", - "raysect==0.8.1.*", + "raysect==0.9.1.*", ], extras_require={ # Running ./dev/build_docs.sh runs setup.py, which requires cython. - "docs": ["cython~=3.0", "sphinx", "sphinx-rtd-theme", "sphinx-tabs"], + "docs": ["cython~=3.1", "sphinx", "sphinx-rtd-theme", "sphinx-tabs"], }, packages=find_packages(include=["cherab*"]), package_data={"": [