Skip to content
Closed
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
96 changes: 96 additions & 0 deletions .github/workflows/verify-backend-suites.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Verify backend suites

permissions:
contents: read

on:
pull_request:
branches:
- "**"

jobs:
verify:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend: [numpy, pytorch]
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Install system build dependencies
run: |
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done
"$@"
}

sudo rm -f \
/etc/apt/sources.list.d/azure-cli.list \
/etc/apt/sources.list.d/azure-cli.sources \
/etc/apt/sources.list.d/microsoft-prod.list \
/etc/apt/sources.list.d/microsoft-prod.sources
retry sudo apt-get update
retry sudo apt-get install -y \
gfortran pkg-config ninja-build \
libopenblas-dev liblapack-dev \
libfftw3-dev libhealpix-cxx-dev

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"

- name: Install dependencies
run: |
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done
"$@"
}

python -m pip install --upgrade pip
retry python -m pip install poetry
poetry env use python
retry poetry install --with dev --extras "healpy_support"
if [ "${{ matrix.backend }}" = "pytorch" ]; then
retry poetry run python -m pip install \
--index-url https://download.pytorch.org/whl/cpu \
--extra-index-url https://pypi.org/simple \
"torch>=2.4,<3.0"
fi

- name: Run complete suite
run: |
set +e
set -o pipefail
export PYRECEST_BACKEND=${{ matrix.backend }}
poetry run python -m pytest \
--rootdir . \
-vv \
--strict-config \
--tb=long \
--junitxml=${{ matrix.backend }}_suite.xml \
./tests \
2>&1 | tee ${{ matrix.backend }}_suite.log
status=${PIPESTATUS[0]}
echo "${status}" > ${{ matrix.backend }}_suite.exit-status
exit 0

- name: Upload diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.backend }}-suite-diagnostics
path: |
${{ matrix.backend }}_suite.xml
${{ matrix.backend }}_suite.log
${{ matrix.backend }}_suite.exit-status
8 changes: 6 additions & 2 deletions src/pyrecest/distributions/circle/von_mises_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ def set_mode(self, mode):
"""Return a copy with a replaced mode direction.

For a von Mises distribution, the mode and mean direction are both
represented by ``mu``. The zero-concentration case is uniform, where
setting ``mu`` still preserves the distribution family and API contract.
represented by ``mu``. Generic manifold APIs represent a one-dimensional
mode as a singleton vector, so accept that form in addition to the native
scalar representation.
"""
mode = array(mode)
if mode.shape == (1,):
mode = mode[0]
return self.set_mean(mode)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ def integrate(self, integration_boundaries=None) -> float:
left, right = integration_boundaries
left = _validate_boundary("left", left, self.dim)
right = _validate_boundary("right", right, self.dim)
_validate_boundary_order(left, right)
if self.dim > 1:
_validate_boundary_order(left, right)

volume = prod(right - left)
return 1.0 / (2.0 * pi) ** self.dim * volume
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ def test_integrate_rejects_reversed_boundaries():
dist.integrate((array([0.0, 1.0]), array([1.0, 0.5])))


def test_integrate_rejects_reversed_scalar_boundaries():
def test_integrate_preserves_signed_scalar_boundaries():
dist = HypertoroidalUniformDistribution(1)

with pytest.raises(ValueError, match="increasing"):
dist.integrate((array(1.0), array(0.0)))
assert dist.integrate((array(1.0), array(0.0))) == pytest.approx(
-1.0 / (2.0 * pi)
)


def test_integrate_accepts_scalar_boundaries_for_one_dimension():
Expand Down
4 changes: 2 additions & 2 deletions tests/distributions/test_wrapped_cauchy_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pyrecest.backend

# pylint: disable=no-name-in-module,no-member
from pyrecest.backend import arange, array, pi
from pyrecest.backend import arange, array, conj, pi
from pyrecest.distributions.circle.custom_circular_distribution import (
CustomCircularDistribution,
)
Expand Down Expand Up @@ -91,7 +91,7 @@ def test_trigonometric_moment_accepts_negative_integer_orders(self):
positive_moment = dist.trigonometric_moment(2)
negative_moment = dist.trigonometric_moment(-2)

npt.assert_allclose(negative_moment, positive_moment.conjugate(), rtol=1e-12)
npt.assert_allclose(negative_moment, conj(positive_moment), rtol=1e-12)

@unittest.skipIf(
pyrecest.backend.__backend_name__ in ("pytorch", "jax"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def test_accepts_numerically_equatorial_vmf_measurement(self):

estimate = filter_.get_point_estimate()
self.assertAlmostEqual(float(linalg.norm(estimate)), 1.0, places=5)
self.assertGreater(abs(float(estimate[0])), 0.9)
alignment = abs(float(estimate @ measurement))
self.assertGreater(alignment, math.cos(math.radians(30.0)))

def test_rejects_vmf_measurement_outside_equator_tolerance(self):
filter_ = HyperhemisphericalGridFilter(50, 2)
Expand Down
Loading