From a3b444d089195dccce3f4cc18304fb3d2185df12 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 15:01:22 +0100 Subject: [PATCH 1/5] Add PR documentation preview deployments via GitHub Pages Co-authored-by: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> --- .github/workflows/R.yml | 43 +++++++++++++++++++++- .github/workflows/docs-preview-cleanup.yml | 34 +++++++++++++++++ .github/workflows/python.yml | 43 +++++++++++++++++++++- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/docs-preview-cleanup.yml diff --git a/.github/workflows/R.yml b/.github/workflows/R.yml index 284c93df..07e5eaf5 100644 --- a/.github/workflows/R.yml +++ b/.github/workflows/R.yml @@ -190,8 +190,9 @@ jobs: needs: R-CMD-check permissions: contents: write # github-pages-deploy-action + pull-requests: write # post PR preview comment concurrency: # Recommended if you intend to make multiple deployments in quick succession. - group: web-${{ github.workflow }}-${{ github.ref }} + group: web-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -233,6 +234,46 @@ jobs: single-commit: true clean: true + - name: Deploy PR preview šŸš€ + if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: r/docs + target-folder: previews/pr-${{ github.event.pull_request.number }}/r + single-commit: true + clean: true + + - name: Comment PR preview link + if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v7 + with: + script: | + const marker = ''; + const prNumber = context.issue.number; + const url = `https://multi-objective.github.io/moocore/previews/pr-${prNumber}/r/`; + const body = `${marker}\nšŸ“š [**R docs preview** for this PR](${url})\n\n`; + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + const existing = comments.data.find(c => c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + } + rchk: needs: pkgdown uses: multi-objective/moocore/.github/workflows/rchk.yml@main diff --git a/.github/workflows/docs-preview-cleanup.yml b/.github/workflows/docs-preview-cleanup.yml new file mode 100644 index 00000000..5ad60598 --- /dev/null +++ b/.github/workflows/docs-preview-cleanup.yml @@ -0,0 +1,34 @@ +name: Cleanup docs preview + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + cleanup: + name: Remove docs preview for closed PR + runs-on: ubuntu-latest + # Only run for same-repo PRs; fork PRs never get a preview deployed + if: github.event.pull_request.head.repo.full_name == github.repository + steps: + - uses: actions/checkout@v6 + with: + ref: gh-pages + + - name: Remove preview folder + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + PREVIEW_DIR="previews/pr-${PR_NUMBER}" + if [ -d "${PREVIEW_DIR}" ]; then + git rm -rf "${PREVIEW_DIR}" + git config user.email "actions@github.com" + git config user.name "GitHub Actions" + git commit -m "Remove docs preview for PR #${PR_NUMBER}" + git push + echo "Removed ${PREVIEW_DIR}" + else + echo "No preview directory found for PR #${PR_NUMBER}, nothing to clean up" + fi diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 603dcc65..940cfcd9 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -117,8 +117,9 @@ jobs: if: "! contains(github.event.head_commit.message, '[skip ci]')" permissions: contents: write + pull-requests: write # post PR preview comment concurrency: # Recommended if you intend to make multiple deployments in quick succession. - group: web-${{ github.workflow }}-${{ github.ref }} + group: web-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} timeout-minutes: 10 runs-on: ubuntu-latest steps: @@ -165,6 +166,46 @@ jobs: single-commit: true clean: true + - name: Deploy PR preview šŸš€ + if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: python/doc/_build/html/ + target-folder: previews/pr-${{ github.event.pull_request.number }}/python + single-commit: true + clean: true + + - name: Comment PR preview link + if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v7 + with: + script: | + const marker = ''; + const prNumber = context.issue.number; + const url = `https://multi-objective.github.io/moocore/previews/pr-${prNumber}/python/`; + const body = `${marker}\nšŸ“š [**Python docs preview** for this PR](${url})\n\n`; + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + const existing = comments.data.find(c => c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + } + coverage: timeout-minutes: 15 needs: build-doc From 18358b8e612fa42a5f5ad6677dc9a321954d5e0e Mon Sep 17 00:00:00 2001 From: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:24:02 +0100 Subject: [PATCH 2/5] FPRAS WIP --- c/config.h | 7 + c/hvapprox.c | 91 +++++++++++ c/hvapprox.h | 12 ++ c/rng.h | 43 +++++ .../source/reference/functions.metrics.rst | 1 + python/examples/plot_hv_approx.py | 59 +++++++ python/src/moocore/__init__.py | 2 + python/src/moocore/_moocore.py | 149 +++++++++++++++++- python/src/moocore/libmoocore.h | 4 + 9 files changed, 364 insertions(+), 4 deletions(-) diff --git a/c/config.h b/c/config.h index c605e45b..aeea3941 100644 --- a/c/config.h +++ b/c/config.h @@ -25,6 +25,13 @@ typedef uint_fast8_t dimension_t; // Maximum number of objectives is 31. It cannot be larger than 255. #define MOOCORE_DIMENSION_MAX 31 +#ifndef UINT32_MAX +# define UINT32_MAX ((uint32_t)-1) +#endif +#ifndef UINT64_MAX +# define UINT64_MAX ((uint64_t)-1) +#endif + // Use boolvec for boolean arrays that will be passed from/to R/Python. It also // helps with autovectorization. #ifdef R_PACKAGE diff --git a/c/hvapprox.c b/c/hvapprox.c index 77a553a7..a0f26faf 100644 --- a/c/hvapprox.c +++ b/c/hvapprox.c @@ -5,6 +5,7 @@ #include "hvapprox.h" #include "pow_int.h" #include "rng.h" +#include "sort.h" #define ALMOST_ZERO_WEIGHT 1e-20 @@ -863,3 +864,93 @@ hv_approx_rphi_fang_wang_plus(const double * restrict data, size_t npoints, dime const long double c_m = sphere_area_div_2_pow_d_times_d[dim]; return STATIC_CAST(double, c_m * (expected / STATIC_CAST(long double, nsamples))); } + + +// FIXME: Make sure that this is vectorized. +static double * +compute_vols(const double * restrict points, size_t npoints, dimension_t dim) +{ + double * vol = malloc(npoints * sizeof(*vol)); + for (size_t i = 0; i < npoints; i++) { + const double * restrict pi = points + i * dim; + double tmp = pi[0]; + for (dimension_t d = 1; d < dim; d++) + tmp *= pi[d]; + vol[i] = tmp; + } + return vol; +} + +static void +cumsum_of_vector(double * restrict v, size_t n) +{ + for (size_t i = 1; i < n; i++) + v[i] += v[i-1]; +} + +/** + FPRAS (fully polynomial-time randomized approximation scheme) + + K. Bringmann, T. Friedrich. Approximating the volume of unions and + intersections of high-dimensional geometric objects. Computational Geometry: + Theory and Applications, Vol. 43, pages 601-610,. 2010. + + delta : Probability of failure. +*/ +double +hv_approx_fpras(const double * restrict data, size_t npoints, dimension_t dim, + const double * restrict ref, const boolvec * restrict maximise, + uint32_t random_seed, double epsilon, double delta) +{ + ASSUME(2 <= dim && dim <= MOOCORE_DIMENSION_MAX); + const double * points = transform_and_filter(data, &npoints, dim, ref, maximise); + if (points == NULL) + return 0; + + ASSUME(0 < npoints && npoints < UINT32_MAX); + ASSUME(0 < epsilon && epsilon < 1); + ASSUME(0 < delta && delta < 1); + + const double T_factor = 8 * (log(2) - log(delta)) * (1. + epsilon) / (epsilon * epsilon); + const double T_double = T_factor * (double)npoints; + if (T_double >= (double)UINT64_MAX) + return -1; // This will run for too long! + + const uint64_t T = (uint64_t) ceil(T_double); + double * vols = compute_vols(points, npoints, dim); // VolumeQuery(B_i) + // Convert vols into a piece-wise cdf. + cumsum_of_vector(vols, npoints); + double total_vol = vols[npoints - 1]; + for (size_t k = 0; k < npoints; k++) + vols[k] /= total_vol; + + rng_state * rng = rng_new(random_seed); + uint64_t t_sum = 0, m = 0; + while (true) { + // FIXME: Ideally, this should be done in O(1), but currently it takes O(log n). + uint32_t i = rng_random_wheel_uint32(rng, vols, (uint32_t) npoints); + + // SampleQuery(B_i) + double x[MOOCORE_DIMENSION_MAX + 1]; + for (dimension_t d = 0; d < dim; d++) + x[d] = rng_random(rng); + const double * restrict p_i = points + i * dim; + for (dimension_t d = 0; d < dim; d++) + x[d] *= p_i[d]; + + while (true) { + if (unlikely(t_sum >= T)) { + free(vols); + rng_free(rng); + free((void *) points); + return total_vol * (T_factor / (double) m); + } + t_sum++; + uint32_t j = rng_uint32(rng, 0, (uint32_t) npoints); + const double * restrict p_j = points + j * dim; + if (weakly_dominates(x, p_j, dim)) // PointQuery(x, B_j) + break; + } + m++; + } +} diff --git a/c/hvapprox.h b/c/hvapprox.h index fd7af7d4..c8cba61f 100644 --- a/c/hvapprox.h +++ b/c/hvapprox.h @@ -37,5 +37,17 @@ MOOCORE_API double hv_approx_rphi_fang_wang_plus( const double * restrict ref, const boolvec * restrict maximise, uint_fast32_t nsamples); +/** + FPRAS (fully polynomial-time randomized approximation scheme) + + K. Bringmann, T. Friedrich. Approximating the volume of unions and + intersections of high-dimensional geometric objects. Computational Geometry: + Theory and Applications, Vol. 43, pages 601-610,. 2010. +*/ +MOOCORE_API double hv_approx_fpras( + const double * restrict data, size_t npoints, dimension_t dim, + const double * restrict ref, const boolvec * restrict maximise, + uint32_t random_seed, double epsilon, double delta); + END_C_DECLS #endif // HV_APPROX_H_ diff --git a/c/rng.h b/c/rng.h index 00876ec1..f54d638c 100644 --- a/c/rng.h +++ b/c/rng.h @@ -34,6 +34,49 @@ rng_uniform(rng_state * rng, double low, double high) return low + (high - low) * rng_random(rng); } +static inline uint32_t +rng_uint32(rng_state * rng, uint32_t low, uint32_t high) +{ + assert(rng != NULL); + if (low >= high) + return low; + return low + (mt19937_next32(rng) % (high - low)); +} + +/** + Returns a random value within [0, n - 1] based on the given CDF. + + The CDF values should be non-decreasing and within [0,1]. + + Requires O(log n). +*/ +static inline uint32_t +rng_random_wheel_uint32(rng_state * rng, const double * cdf, uint32_t n) +{ + // Check the CDF is correct. + DEBUG1(for (uint32_t j = 1; j < n; j++) assert(cdf[j-1] <= cdf[j])); + DEBUG1(for (uint32_t j = 0; j < n; j++) assert(0 <= cdf[j] && cdf[j] <= 1)); + + double r = rng_random(rng); + if (r < cdf[0]) + return 0; + if (n == 2 || r >= cdf[n - 2]) + return n - 1; + + // Binary search. + uint32_t low = 1, high = n - 2; + while (low < high) { + uint32_t mid = low + (high - low) / 2; + if (r < cdf[mid]) + high = mid; + else + low = mid + 1; + } + + DEBUG1(for (uint32_t j = 0; j < low; j++) assert(cdf[j] < r)); + DEBUG1(for (uint32_t j = low; j < n; j++) assert(r <= cdf[j])); + return low; +} double rng_standard_normal(rng_state *rng); void rng_bivariate_normal_fill(rng_state * rng, diff --git a/python/doc/source/reference/functions.metrics.rst b/python/doc/source/reference/functions.metrics.rst index 7efb1ad0..4f0f388e 100644 --- a/python/doc/source/reference/functions.metrics.rst +++ b/python/doc/source/reference/functions.metrics.rst @@ -201,6 +201,7 @@ Approximating the hypervolume metric :toctree: generated/ hv_approx + hv_approx_fpras whv_hype Computing the hypervolume can be time consuming, thus several approaches have diff --git a/python/examples/plot_hv_approx.py b/python/examples/plot_hv_approx.py index 2bf27c44..843e55bb 100644 --- a/python/examples/plot_hv_approx.py +++ b/python/examples/plot_hv_approx.py @@ -16,6 +16,7 @@ """ # sphinx_gallery_multi_image = "single" +import time import numpy as np import pandas as pd import matplotlib.pyplot as plt @@ -142,3 +143,61 @@ plt.tight_layout() plt.show() + + +# %% +# +# Fully polynomial-time randomized approximation scheme (FPRAS) +# ------------------------------------------------------------- +# +# :func:`moocore.hv_approx_fpras()` allows obtaining an approximation with +# relative error smaller than :math:`\epsilon` (``epsilon``) with a given +# probability :math:`1-\delta` (``delta``). As the plot below shows, the +# approximation error is often better than the requested value, but the +# computation time increases very quickly for smaller ``epsilon``. +# +shape = "convex-sphere" +ref = 1.1 +npoints = 50 +dim = 6 +reps = 5 +rng = np.random.default_rng(42) +res = [] +for r in range(reps): + z = moocore.generate_ndset(npoints, dim, method=shape, seed=42 + r) + t_start = time.perf_counter() + exact = moocore.hypervolume(z, ref=ref) + t_end = time.perf_counter() - t_start + for epsilon in [0.1, 0.025, 0.01, 0.005]: + for delta in [0.25, 0.1, 0.05]: + t_start = time.perf_counter() + hv = moocore.hv_approx_fpras( + z, ref=ref, seed=rng, epsilon=epsilon, delta=delta + ) + t_end = time.perf_counter() - t_start + res.append( + dict( + r=r, + epsilon=epsilon, + delta=delta, + hverror=np.abs(1 - hv / exact), + time=t_end, + ) + ) + + +df = pd.DataFrame(res) +df["delta"] = df["delta"].astype("category") +plt.figure() +ax = sns.lineplot(data=df, x="epsilon", y="hverror", hue="delta", marker="o") +ax.set_title(f"{shape}-{npoints}-{dim}d", fontsize=10) +ax.set(yscale="log", ylabel="Relative error") +ax.set_xscale("log", base=10) +plt.tight_layout() +plt.figure() +ax = sns.lineplot(data=df, x="epsilon", y="time", hue="delta", marker="o") +ax.set_title(f"{shape}-{npoints}-{dim}d", fontsize=10) +ax.set(yscale="log", ylabel="CPU time (s)") +ax.set_xscale("log", base=10) +plt.tight_layout() +plt.show() diff --git a/python/src/moocore/__init__.py b/python/src/moocore/__init__.py index 393b416b..b20027e1 100644 --- a/python/src/moocore/__init__.py +++ b/python/src/moocore/__init__.py @@ -15,6 +15,7 @@ filter_dominated_within_sets, generate_ndset, hv_approx, + hv_approx_fpras, hv_contributions, hypervolume, igd, @@ -61,6 +62,7 @@ "get_dataset", "get_dataset_path", "hv_approx", + "hv_approx_fpras", "hv_contributions", "hypervolume", "igd", diff --git a/python/src/moocore/_moocore.py b/python/src/moocore/_moocore.py index e159950d..2fb9ce1f 100644 --- a/python/src/moocore/_moocore.py +++ b/python/src/moocore/_moocore.py @@ -541,7 +541,7 @@ def hypervolume( >>> len(dat) 100 - Dominated points are ignored, so this: + Weakly-dominated points are ignored, so this: >>> moocore.hypervolume(dat, ref=[10, 10]) 93.55331425585321 @@ -881,7 +881,7 @@ def hv_approx( seed: int | np.random.Generator | None = None, method: Literal["DZ2019-HW", "DZ2019-MC", "Rphi-FWE+"] = "Rphi-FWE+", ) -> float: - r"""Approximate the hypervolume indicator. + r"""Approximate the hypervolume indicator via (quasi)-Monte-Carlo sampling. Approximate the value of the hypervolume metric with respect to a given reference point assuming minimization of all objectives. Methods @@ -983,21 +983,30 @@ def hv_approx( Merge all the sets of a dataset by removing the set number column: >>> x = moocore.get_dataset("input1.dat")[:, :-1] + >>> len(x) + 100 - Dominated points are ignored, so this: + Weakly-dominated points increase the runtime, but do not change the returned value, + so the following: >>> moocore.hv_approx(x, ref=10) 93.5533 >>> moocore.hv_approx(x, ref=10, method="DZ2019-HW") 93.5533 + >>> moocore.hv_approx(x, ref=10, method="DZ2019-MC", seed=42) + 93.5919 - gives the same hypervolume approximation as this: + give the same hypervolume approximation as: >>> x = moocore.filter_dominated(x) + >>> len(x) + 6 >>> moocore.hv_approx(x, ref=10) 93.5533 >>> moocore.hv_approx(x, ref=10, method="DZ2019-HW") 93.5533 + >>> moocore.hv_approx(x, ref=10, method="DZ2019-MC", seed=42) + 93.5919 The approximation is far from perfect for large number of dimensions: @@ -1050,6 +1059,138 @@ def hv_approx( return hv +@DocSubstitute() +def hv_approx_fpras( + points: ArrayLike, + /, + ref: ArrayLike, + *, + maximise: bool | Sequence[bool] = False, + seed: int | np.random.Generator | None = None, + epsilon: float = 0.01, + delta: float = 0.1, +) -> float: + r"""Approximate the hypervolume indicator via a fully polynomial-time randomized approximation scheme (FPRAS). + + This function implements the approximation algorithm by + :footcite:t:`BriFri2010approx`. This FPRAS returns, with probability + :math:`(1 - \delta)`, an :math:`\epsilon`-approximation of the hypervolume + metric with respect to the given reference point, assuming minimization of + all objectives by default. + + .. warning:: Lower values of ``epsilon`` (:math:`\epsilon`) or ``delta`` (:math:`\delta`) require significantly longer computation time. + + .. seealso:: For details of the calculation, see the Notes section below. + + See :ref:`Benchmarks: Approximation of the hypervolume `. + + + Parameters + ---------- + points : + ${points} + ref : + ${ref_point} + maximise : + ${maximise} + seed : + ${random_seed} + epsilon : + Desired relative error of the approximation, :math:`\epsilon > 0`. + delta : + Desired failure probability :math:`0 < \delta < 1`, :math:`(1 - \delta)` gives the confidence level. + + Returns + ------- + A single numerical value, the approximate hypervolume indicator. + + See Also + -------- + hypervolume, whv_hype, hv_approx + + Notes + ----- + This function computes an approximation :math:`\hat{v}` of the + true hypervolume :math:`v = \text{hyp}_r(A)` of the input points in :math:`A + \subset \mathbb{R}^m` with respect to the reference point :math:`r \in + \mathbb{R}^m`, such that + + .. math:: + \text{Pr}[(1-\epsilon)v \leq \hat{v} \leq (1+\epsilon)v] \geq (1 - \delta) + + where :math:`\epsilon > 0` and :math:`0 < \delta < 1`. + + The algorithm requires :math:`O(\frac{nm}{\epsilon^2\log\delta})`. That is, + it is linear on the number of points and dimensions, but quadratic in the + approximation error. In other words, more accurate approximations require + significantly more time. + + In contrast to the (quasi)-Monte-Carlo methods provided by + :func:`~moocore.hv_approx`, the presence of weakly-dominated points not + only increases the runtime, but also changes the returned approximation for + a fixed random seed. + + + References + ---------- + .. footbibliography:: + + Examples + -------- + >>> x = np.array([[5, 5], [4, 6], [2, 7], [7, 4]]) + >>> moocore.hypervolume(x, ref=[10, 10]) + 38.0 + >>> moocore.hv_approx(x, ref=[10, 10], method="Rphi-FWE+") + 37.99998 + >>> moocore.hv_approx_fpras(x, ref=[10, 10], epsilon=0.1, delta=0.2, seed=42) + 38.17619 + >>> moocore.hv_approx_fpras(x, ref=[10, 10], epsilon=0.01, delta=0.2, seed=42) + 38.08557 + + Contrary to :func:`~moocore.hv_approx`, the presence of dominated points + not only increases the runtime, but also changes the output for the same random + seed, so this: + + >>> x = moocore.get_dataset("input1.dat")[:, :-1] + >>> moocore.hv_approx_fpras(x, ref=10, seed=42) + 93.6140 + + does NOT give the same hypervolume approximation as this: + + >>> x = moocore.filter_dominated(x) + >>> moocore.hv_approx_fpras(x, ref=10, seed=42) + 93.5539 + + """ + # Convert to numpy.array in case the user provides a list. We use + # np.asarray to convert it to floating-point, otherwise if a user inputs + # something like ref = np.array([10, 10]) then numpy would interpret it as + # an int array. + points = np.asarray(points, dtype=float) + nobj = points.shape[1] + ref = array_1d_of_length_n(np.asarray(ref, dtype=float), nobj, name="ref") + + if epsilon <= 0: + raise ValueError(f"epsilon must be positive: {epsilon}") + if delta <= 0 or delta >= 1: + raise ValueError(f"delta must be strictly within (0, 1): {delta}") + + maximise_p = _parse_maximise_to_bool_array(maximise, nobj) + points_p, npoints, nobj = np2d_to_double_array( + points, ctype_shape=("size_t", "uint_fast8_t") + ) + ref = ffi.from_buffer("double []", ref) + seed = _get_seed_for_c(seed) + epsilon = ffi.cast("double", float(epsilon)) + delta = ffi.cast("double", float(delta)) + hv = lib.hv_approx_fpras( + points_p, npoints, nobj, ref, maximise_p, seed, epsilon, delta + ) + if hv < 0: + raise ValueError("The requested approximation would run for too long") + return hv + + # FIXME: Implement also WFG hard type described here: # https://www.sciencedirect.com/science/article/pii/S0305054816301538?via=ihub#bib26 # (code: diff --git a/python/src/moocore/libmoocore.h b/python/src/moocore/libmoocore.h index 412b46a5..8d1951cd 100644 --- a/python/src/moocore/libmoocore.h +++ b/python/src/moocore/libmoocore.h @@ -74,6 +74,10 @@ double hv_approx_rphi_fang_wang_plus(const double * restrict data, const double * restrict ref, const boolvec * restrict maximise, uint_fast32_t nsamples); + +double hv_approx_fpras(const double * restrict data, size_t npoints, dimension_t dim, + const double * restrict ref, const boolvec * restrict maximise, + uint32_t random_seed, double epsilon, double delta); /* typedef ... hype_sample_dist; hype_sample_dist * hype_dist_unif_new(unsigned long seed); From 2bac5e33e43e6c0a2252a75c29a6186c7c86385f Mon Sep 17 00:00:00 2001 From: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> Date: Fri, 8 May 2026 17:14:46 +0100 Subject: [PATCH 3/5] try to avoid uninit warning. --- c/hvapprox.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/c/hvapprox.c b/c/hvapprox.c index a0f26faf..e7b756b8 100644 --- a/c/hvapprox.c +++ b/c/hvapprox.c @@ -881,11 +881,13 @@ compute_vols(const double * restrict points, size_t npoints, dimension_t dim) return vol; } -static void +static double cumsum_of_vector(double * restrict v, size_t n) { + ASSUME(n > 0); for (size_t i = 1; i < n; i++) v[i] += v[i-1]; + return v[n - 1]; } /** @@ -919,8 +921,7 @@ hv_approx_fpras(const double * restrict data, size_t npoints, dimension_t dim, const uint64_t T = (uint64_t) ceil(T_double); double * vols = compute_vols(points, npoints, dim); // VolumeQuery(B_i) // Convert vols into a piece-wise cdf. - cumsum_of_vector(vols, npoints); - double total_vol = vols[npoints - 1]; + double total_vol = cumsum_of_vector(vols, npoints); for (size_t k = 0; k < npoints; k++) vols[k] /= total_vol; From c8692475a3eef52a13f602571bf5851678cfe7f0 Mon Sep 17 00:00:00 2001 From: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> Date: Fri, 8 May 2026 17:21:46 +0100 Subject: [PATCH 4/5] use min/max in example --- python/examples/plot_hv_approx.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/python/examples/plot_hv_approx.py b/python/examples/plot_hv_approx.py index 843e55bb..cd761abc 100644 --- a/python/examples/plot_hv_approx.py +++ b/python/examples/plot_hv_approx.py @@ -160,7 +160,7 @@ ref = 1.1 npoints = 50 dim = 6 -reps = 5 +reps = 10 rng = np.random.default_rng(42) res = [] for r in range(reps): @@ -189,7 +189,14 @@ df = pd.DataFrame(res) df["delta"] = df["delta"].astype("category") plt.figure() -ax = sns.lineplot(data=df, x="epsilon", y="hverror", hue="delta", marker="o") +ax = sns.lineplot( + data=df, + x="epsilon", + y="hverror", + hue="delta", + marker="o", + errorbar=("pi", 100), +) ax.set_title(f"{shape}-{npoints}-{dim}d", fontsize=10) ax.set(yscale="log", ylabel="Relative error") ax.set_xscale("log", base=10) From a858d23ca1ff7d186124538b6bec17987f5b200f Mon Sep 17 00:00:00 2001 From: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> Date: Fri, 8 May 2026 17:52:25 +0100 Subject: [PATCH 5/5] improve plot --- python/examples/plot_hv_approx.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/python/examples/plot_hv_approx.py b/python/examples/plot_hv_approx.py index cd761abc..c15ec6ab 100644 --- a/python/examples/plot_hv_approx.py +++ b/python/examples/plot_hv_approx.py @@ -188,18 +188,12 @@ df = pd.DataFrame(res) df["delta"] = df["delta"].astype("category") +df["epsilon"] = df["epsilon"].astype("category") plt.figure() -ax = sns.lineplot( - data=df, - x="epsilon", - y="hverror", - hue="delta", - marker="o", - errorbar=("pi", 100), -) +ax = sns.boxplot(df, x="epsilon", y="hverror", hue="delta", whis=[0, 100]) ax.set_title(f"{shape}-{npoints}-{dim}d", fontsize=10) ax.set(yscale="log", ylabel="Relative error") -ax.set_xscale("log", base=10) +ax.yaxis.grid(True) plt.tight_layout() plt.figure() ax = sns.lineplot(data=df, x="epsilon", y="time", hue="delta", marker="o")