Skip to content

Make the surface samplers seedable (closes #54) - #55

Merged
gattia merged 3 commits into
mainfrom
fix/seedable-surface-sampling
Aug 16, 2026
Merged

Make the surface samplers seedable (closes #54)#55
gattia merged 3 commits into
mainfrom
fix/seedable-surface-sampling

Conversation

@gattia

@gattia gattia commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Closes #54.

Mesh.rand_pts_around_surface() had two independent random draws that a caller could not
reach, so identical inputs produced a different point cloud on every call. Both are now
seeded through a single optional seed argument.

Seeding only one of the two would have left the function random, which is the main reason
this was easy to miss — so every test here checks the whole call rather than one draw.

The change

  • rand_sample_pts_mesh(..., seed=None) passes random_seed to both
    pcu.sample_mesh_random and pcu.sample_mesh_poisson_disk.
  • Mesh.rand_surface_pts(..., seed=None) forwards it.
  • Mesh.rand_pts_around_surface(..., seed=None) uses np.random.default_rng(seed) for the
    offsets instead of np.random.default_rng(), and forwards the seed to the surface draw.

pcu_random_seed()

pcu documents random_seed=0 as "use the current time", so a user's seed cannot be
handed over directly: seed=0 would silently mean "unseeded", and 0 is the first seed most
people try. pcu_random_seed() maps None to pcu's own 0 sentinel (preserving the
existing default) and any integer seed to a deterministic non-zero 32-bit value.

Backwards compatibility

seed=None is the default and behaves exactly as before — unseeded, and unaffected by
np.random.seed(), which never reached either draw. There is a test asserting that,
because it is the property a patch release must not break.

Verified, not assumed

Case Result
same seed twice identical
seed=0 twice identical (the pcu trap)
different seeds differ
seed=None twice still differ — unchanged default
distribution="laplace" seeded too
rand_sample_pts_mesh, both random and bluenoise seeded

14 new tests in testing/mesh/meshTools/rand_sample_pts_mesh_seed_test.py.

One thing this does NOT fix

rand_pts_around_surface(surface_method="bluenoise") — the default surface_method
is broken independently of seeding and remains broken:

ValueError: operands could not be broadcast together with shapes (1436,3) (1000,3)

pcu.sample_mesh_poisson_disk returns approximately num_samples points (it took 1450
for a requested 1000), and base_pts + rand_pts then broadcasts mismatched shapes. This
reproduces on main at 0.1.20, so it predates this branch. It is left alone deliberately:
it is a different defect with a design question attached (truncate? oversample and trim?
raise?), and bundling it into a seeding patch release would hide it.

Consequence for this PR's coverage: bluenoise seeding is verified at the
rand_sample_pts_mesh level, which is where the seed lands, but not end-to-end through
rand_pts_around_surface, because that path raises before it can be checked. The tests say
so at the point where it matters.

Version bumped 0.1.20 → 0.1.21.

gattia added 3 commits August 16, 2026 02:57
Closes #54.

`Mesh.rand_pts_around_surface()` had two independent random draws a caller could
not reach, so identical inputs gave a different point cloud on every call:

- base surface points went to `pcu.sample_mesh_random` / `sample_mesh_poisson_disk`
  with no `random_seed`, and pcu documents `random_seed=0` as "use the current
  time", not "seed 0";
- the offsets used `np.random.default_rng()` with no argument, which seeds from OS
  entropy and ignores `np.random.seed()`.

Both are now driven by one optional `seed`, threaded through `rand_sample_pts_mesh`,
`Mesh.rand_surface_pts` and `Mesh.rand_pts_around_surface`. Seeding only one of the
two would leave the function random, which is why this was easy to miss, so the
tests check the whole call rather than a single draw.

`pcu_random_seed()` maps None to pcu's own 0 sentinel and any integer seed to a
deterministic non-zero 32-bit value -- otherwise `seed=0` would silently mean
"unseeded", and 0 is the first seed most people try.

`seed=None` is the default and behaves exactly as before; there is a test asserting
that, because it is the property a patch release must not break.

Not fixed here: `rand_pts_around_surface(surface_method="bluenoise")` -- the default
surface_method -- raises a broadcast error because `sample_mesh_poisson_disk` returns
approximately, not exactly, `num_samples` points. That reproduces on main at 0.1.20
and is a separate defect with a design question attached. Consequence: bluenoise
seeding is verified at the `rand_sample_pts_mesh` level, where the seed lands, but
not end-to-end.

Full suite: 163 passed, 51 skipped, 0 failed.
Caught by CI. I ran black --check locally but not the repo's own `make lint`
target, which runs isort first.
…es through

The maintainer read the previous version as 'converts None to 0'. That is the
trivial half; the derivation is what stops seed=0 meaning unseeded, keeps seeds
0 and 1 distinct, and keeps a large seed inside pcu's 32-bit binding.
@gattia
gattia merged commit 73ba335 into main Aug 16, 2026
9 checks passed
@gattia
gattia deleted the fix/seedable-surface-sampling branch August 16, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rand_pts_around_surface() is not reproducible: both random draws bypass any caller-settable seed

1 participant