From ddc3c31a55b6683b1b89c63f6770eab848a903e7 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:31:15 +0100 Subject: [PATCH 1/4] Fixes plot bug when resample is set on a subset of contrast (#189) --- pyproject.toml | 4 ++++ ratapi/utils/plotting.py | 28 +++++++++++++--------------- setup.py | 6 ------ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 99c65b3a..ff9cd29b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,10 @@ dependencies = [ "tqdm>=4.66.5", ] +[project.urls] +Documentation = "https://rascalsoftware.github.io/RAT/" +Repository = "https://github.com/RascalSoftware/python-RAT" + [project.optional-dependencies] dev = [ "pytest>=7.4.0", diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index 30ab8947..9293a1a1 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -76,9 +76,9 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool for j in range(len(sld)): results["sld"][-1].append([sld[j][:, 0], sld[j][:, 1]]) + results["sld_resample"].append([]) if event_data.resample[i] == 1 or event_data.modelType == "custom xy": layers = event_data.resampledLayers[i][0] - results["sld_resample"].append([]) for j in range(len(event_data.resampledLayers[i])): layer = event_data.resampledLayers[i][j] if layers.shape[1] == 4: @@ -198,15 +198,14 @@ def plot_ref_sld_helper( sld_min, sld_max = confidence_intervals["sld"][i][j] sld_plot.fill_between(plot_data["sld"][i][j][0], sld_min, sld_max, alpha=0.6, color="grey") - if plot_data["sld_resample"]: - for j in range(len(plot_data["sld_resample"][i])): - sld_plot.plot( - plot_data["sld_resample"][i][j][0], - plot_data["sld_resample"][i][j][1], - color=color, - linewidth=1, - animated=animated, - ) + for j in range(len(plot_data["sld_resample"][i])): + sld_plot.plot( + plot_data["sld_resample"][i][j][0], + plot_data["sld_resample"][i][j][1], + color=color, + linewidth=1, + animated=animated, + ) # Format the axis ref_plot.set_yscale("log") @@ -544,11 +543,10 @@ def update_foreground(self, data): self.figure.axes[1].draw_artist(self.figure.axes[1].lines[i]) i += 1 - if plot_data["sld_resample"]: - for resampled in plot_data["sld_resample"][j]: - self.figure.axes[1].lines[i].set_data(resampled[0], resampled[1]) - self.figure.axes[1].draw_artist(self.figure.axes[1].lines[i]) - i += 1 + for resampled in plot_data["sld_resample"][j]: + self.figure.axes[1].lines[i].set_data(resampled[0], resampled[1]) + self.figure.axes[1].draw_artist(self.figure.axes[1].lines[i]) + i += 1 for i, container in enumerate(self.figure.axes[0].containers): self.adjust_error_bar( diff --git a/setup.py b/setup.py index 3bdfea9a..38947e5e 100644 --- a/setup.py +++ b/setup.py @@ -151,12 +151,6 @@ def build_libraries(self, libraries): setup( name=PACKAGE_NAME, - author="", - author_email="", - url="https://github.com/RascalSoftware/python-RAT", - description="Python extension for the Reflectivity Analysis Toolbox (RAT)", - long_description=LONG_DESCRIPTION, - long_description_content_type="text/markdown", packages=find_packages(exclude=("tests",)), include_package_data=True, package_data={"": [get_shared_object_name(libevent[0])], "ratapi.examples": ["data/*.dat"]}, From 6ae1ac8137cb72ad1ec36359f1a1483d9e86f0a6 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:58:48 +0000 Subject: [PATCH 2/4] Fixes IPC bug and r1 to project bug (#190) --- ratapi/controls.py | 5 +++-- ratapi/utils/convert.py | 9 ++++++++- ratapi/utils/plotting.py | 3 ++- tests/conftest.py | 6 +++++- tests/test_controls.py | 4 ++-- tests/test_convert.py | 2 +- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/ratapi/controls.py b/ratapi/controls.py index 06c457b6..9dd815b5 100644 --- a/ratapi/controls.py +++ b/ratapi/controls.py @@ -206,7 +206,7 @@ def __str__(self) -> str: def initialise_IPC(self): """Set up the inter-process communication file.""" IPC_obj, self._IPCFilePath = tempfile.mkstemp() - os.write(IPC_obj, b"0") + os.write(IPC_obj, b"\x00") os.close(IPC_obj) return None @@ -221,7 +221,7 @@ def sendStopEvent(self): """ if os.path.isfile(self._IPCFilePath): with open(self._IPCFilePath, "wb") as f: - f.write(b"1") + f.write(b"\x01") else: warnings.warn("An IPC file was not initialised.", UserWarning, stacklevel=2) return None @@ -230,6 +230,7 @@ def delete_IPC(self): """Delete the inter-process communication file.""" with contextlib.suppress(FileNotFoundError): os.remove(self._IPCFilePath) + self._IPCFilePath = "" return None def save(self, filepath: str | Path = "./controls.json"): diff --git a/ratapi/utils/convert.py b/ratapi/utils/convert.py index 4e2649aa..b957789c 100644 --- a/ratapi/utils/convert.py +++ b/ratapi/utils/convert.py @@ -291,7 +291,14 @@ def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: floa if Path(custom_filepath).suffix != ".m": custom_filepath += ".m" model_name = Path(custom_filepath).stem - custom_file = ClassList([CustomFile(name=model_name, filename=custom_filepath, language=Languages.Matlab)]) + # Assume the custom file is in the same directory as the mat file + custom_file = ClassList( + [ + CustomFile( + name=model_name, filename=custom_filepath, language=Languages.Matlab, path=Path(filename).parent + ) + ] + ) layers = ClassList() for contrast in contrasts: contrast.model = [model_name] diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index 9293a1a1..d4a81e1a 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -492,6 +492,8 @@ def update_plot(self, data): """ if self.figure is not None: self.figure.clf() + + self.figure.tight_layout() plot_ref_sld_helper( data, self.figure, @@ -502,7 +504,6 @@ def update_plot(self, data): show_legend=self.show_legend, animated=True, ) - self.figure.tight_layout(pad=1) self.figure.canvas.draw() self.bg = self.figure.canvas.copy_from_bbox(self.figure.bbox) for line in self.figure.axes[0].lines: diff --git a/tests/conftest.py b/tests/conftest.py index 4d5ecc00..bd359192 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6858,7 +6858,11 @@ def r1_monolayer(): ), custom_files=ratapi.ClassList( ratapi.models.CustomFile( - name="Model_IIb", filename="Model_IIb.m", function_name="Model_IIb", language="matlab" + name="Model_IIb", + filename="Model_IIb.m", + function_name="Model_IIb", + language="matlab", + path=Path(__file__).parent / "test_data", ) ), ) diff --git a/tests/test_controls.py b/tests/test_controls.py index ec8cb128..5a83bccc 100644 --- a/tests/test_controls.py +++ b/tests/test_controls.py @@ -891,7 +891,7 @@ def test_initialise_IPC() -> None: assert test_controls._IPCFilePath != "" with open(test_controls._IPCFilePath, "rb") as f: file_content = f.read() - assert file_content == b"0" + assert file_content == b"\x00" os.remove(test_controls._IPCFilePath) @@ -900,7 +900,7 @@ def test_sendStopEvent(IPC_controls) -> None: IPC_controls.sendStopEvent() with open(IPC_controls._IPCFilePath, "rb") as f: file_content = f.read() - assert file_content == b"1" + assert file_content == b"\x01" def test_sendStopEvent_empty_file() -> None: diff --git a/tests/test_convert.py b/tests/test_convert.py index 29738346..fc6f4400 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -93,7 +93,7 @@ def mock_load(ignored_filename, **ignored_settings): monkeypatch.setattr("ratapi.utils.convert.loadmat", mock_load, raising=True) - converted_project = r1_to_project(project) + converted_project = r1_to_project(pathlib.Path(__file__).parent / "test_data" / project) for class_list in ratapi.project.class_lists: assert getattr(converted_project, class_list) == getattr(original_project, class_list) From 0ef08e421b73d4ccf8993077755f402269363f27 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:14:19 +0000 Subject: [PATCH 3/4] Updates GitHub action runner OS (#191) --- .github/workflows/build_wheel.yml | 13 +++++++------ .github/workflows/run_tests.yml | 10 +++++----- setup.py | 2 +- tests/conftest.py | 3 +++ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 1a966d3d..03176b7b 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -11,13 +11,14 @@ jobs: strategy: fail-fast: true matrix: - platform: [windows-2022, ubuntu-latest, macos-13, macos-14] + platform: [windows-latest, ubuntu-latest, macos-15-intel, macos-14] env: CIBW_SKIP: 'pp*' CIBW_ARCHS: 'auto64' CIBW_MANYLINUX_X86_64_IMAGE: 'manylinux_2_28' CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' CIBW_TEST_REQUIRES: 'pytest' + MACOSX_DEPLOYMENT_TARGET: '14.0' defaults: run: shell: bash -l {0} @@ -25,15 +26,15 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true - - name: Set up Python version ${{ matrix.version }} - uses: actions/setup-python@v4 + - name: Set up Python version + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Install OMP (MacOS Intel) - if: matrix.platform == 'macos-13' + if: matrix.platform == 'macos-15-intel' run: | brew install llvm@20 libomp echo "export CC=/usr/local/opt/llvm@20/bin/clang" >> ~/.bashrc @@ -66,7 +67,7 @@ jobs: export PATH="$pythonLocation:$PATH" CIBW_TEST_COMMAND='cd ${pwd}/tmp && python -m pytest tests' echo "CIBW_TEST_COMMAND=${CIBW_TEST_COMMAND}" >> $GITHUB_ENV - python -m pip install cibuildwheel==2.16.5 + python -m pip install cibuildwheel==2.23.3 python -m cibuildwheel --output-dir ./wheelhouse - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index c2018067..7dce1346 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - platform: [windows-2022, ubuntu-latest, macos-13, macos-14] + platform: [windows-latest, ubuntu-latest, macos-15-intel, macos-latest] version: ["3.10", "3.13"] defaults: run: @@ -28,15 +28,15 @@ jobs: runs-on: ${{ matrix.platform}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true - name: Set up Python version ${{ matrix.version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.version }} - name: Install OMP (MacOS Intel) - if: matrix.platform == 'macos-13' + if: matrix.platform == 'macos-15-intel' run: | brew install llvm@20 libomp echo "export CC=/usr/local/opt/llvm@20/bin/clang" >> ~/.bashrc @@ -46,7 +46,7 @@ jobs: echo "export LDFLAGS=\"$LDFLAGS -Wl,-rpath,/usr/local/opt/libomp/lib -L/usr/local/opt/libomp/lib -lomp\"" >> ~/.bashrc source ~/.bashrc - name: Install OMP (MacOS M1) - if: matrix.platform == 'macos-14' + if: matrix.platform == 'macos-latest' run: | brew install llvm@20 libomp echo "export CC=/opt/homebrew/opt/llvm@20/bin/clang" >> ~/.bashrc diff --git a/setup.py b/setup.py index 38947e5e..9f70ff77 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ class BuildExt(build_ext): } if sys.platform == "darwin": - darwin_opts = ["-stdlib=libc++", "-mmacosx-version-min=10.9"] + darwin_opts = ["-stdlib=libc++"] c_opts["unix"] = [*darwin_opts, "-fopenmp", "-O2"] l_opts["unix"] = [*darwin_opts, "-lomp"] diff --git a/tests/conftest.py b/tests/conftest.py index bd359192..854dc9b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,9 @@ import tempfile from pathlib import Path +import matplotlib + +matplotlib.use("Agg") import numpy as np import pytest From b0121f343cbeedd0715d656ee684c528d40c82c2 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:46:04 +0000 Subject: [PATCH 4/4] Adds method to get bayes result procedure and bumps version to dev10 (#192) --- pyproject.toml | 2 +- ratapi/outputs.py | 13 +++++++++++++ tests/conftest.py | 12 ++++++++++++ tests/test_outputs.py | 13 +++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff9cd29b..3cfbe5c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = 'setuptools.build_meta' [project] name = "ratapi" -version = "0.0.0.dev9" +version = "0.0.0.dev10" description = "Python extension for the Reflectivity Analysis Toolbox (RAT)" readme = "README.md" requires-python = ">=3.10" diff --git a/ratapi/outputs.py b/ratapi/outputs.py index 2add1653..c1f5c0ed 100644 --- a/ratapi/outputs.py +++ b/ratapi/outputs.py @@ -538,6 +538,19 @@ class BayesResults(Results): nestedSamplerOutput: NestedSamplerOutput chain: np.ndarray + def from_procedure(self) -> Procedures: + """Return the procedure that created the result. + + Returns + ------- + procedure: Procedures + The procedure that created the result. + """ + samples = self.nestedSamplerOutput.nestSamples + if samples.shape == (1, 2) and not np.any(samples): + return Procedures.DREAM + return Procedures.NS + def save(self, filepath: str | Path = "./results.json"): """Save the BayesResults object to a JSON file. diff --git a/tests/conftest.py b/tests/conftest.py index 854dc9b8..b717ec4e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6546,6 +6546,18 @@ def dream_results(): ) +@pytest.fixture +def nested_sampler_results(dream_results): + results = dream_results + results.nestedSamplerOutput = ratapi.outputs.NestedSamplerOutput( + logZ=-28.99992503667041, + logZErr=0.3391187711291207, + nestSamples=np.ones((100, 9)), + postSamples=np.ones((100, 10)), + ) + return results + + @pytest.fixture def r1_default_project(): """The Project corresponding to the data in R1defaultProject.mat.""" diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 9cd1ad90..7d2afff9 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -191,6 +191,19 @@ def test_make_results(test_procedure, test_output_results, test_bayes, test_resu check_results_equal(test_results, results) +@pytest.mark.parametrize( + ["test_procedure", "test_results"], + [ + (Procedures.NS, "nested_sampler_results"), + (Procedures.DREAM, "dream_results"), + ], +) +def test_results_procedure(test_procedure, test_results, request) -> None: + """Test that bayes results object return correct procedure.""" + test_output_results = request.getfixturevalue(test_results) + assert test_output_results.from_procedure() == test_procedure + + @pytest.mark.parametrize( ["test_output_results", "test_str"], [