Source code for embodichain.lab.sim.motion.workspace.samplers.sobol_sampler
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
from __future__ import annotations
import numpy as np
import torch
from .base_sampler import BaseSampler
from ..configs.sampling_config import SamplingStrategy
__all__ = ["SobolSampler"]
[docs]
class SobolSampler(BaseSampler):
"""Scrambled Sobol sampling with a persistent stream for each dimension.
Successive draws continue the sequence. Power-of-two total sample counts
preserve Sobol's balance properties. Samples are drawn on CPU by Torch's
Sobol engine, then transferred as one tensor to the requested device.
"""
[docs]
def __init__(
self,
seed: int = 42,
device: torch.device | None = None,
scramble: bool = True,
skip: int = 0,
) -> None:
"""Initialize the sequence.
Args:
seed: Scrambling seed.
device: Destination device for samples.
scramble: Whether to scramble the Sobol sequence.
skip: Number of leading sequence points to skip in each dimension.
"""
super().__init__(seed, device)
if skip < 0:
raise ValueError("skip must be non-negative")
self.scramble = scramble
self.skip = skip
self._engines: dict[int, torch.quasirandom.SobolEngine] = {}
def _sample_from_bounds(
self, bounds: torch.Tensor | np.ndarray, num_samples: int
) -> torch.Tensor:
bounds = self._validate_bounds(bounds)
if num_samples <= 0:
raise ValueError("num_samples must be positive")
dimension = bounds.shape[0]
if dimension not in self._engines:
engine = torch.quasirandom.SobolEngine(
dimension, scramble=self.scramble, seed=self.seed
)
if self.skip:
engine.fast_forward(self.skip)
self._engines[dimension] = engine
unit = self._engines[dimension].draw(num_samples).to(self.device)
return self._scale_samples(unit, bounds)
[docs]
def get_strategy_name(self) -> str:
"""Return the registered strategy name."""
return SamplingStrategy.SOBOL.value