Repository: optuna/optuna

Issue:
An invalid `weights` argument of `TPESamper` does not raise any error
### Expected behavior

If we pass an invalid `weights` to `TPESamper`, it does not raise any error. We should raise an `ValueError` for those ivalid `weight`s. Examples of invalid `weight`s are as follows.

```python
lambda x: np.zeros(x),
lambda x: -np.ones(x),
lambda x: float("inf") * np.ones(x),
lambda x: -float("inf") * np.ones(x),
lambda x: np.asarray([float("nan") for _ in range(x)])
```

### Environment

- Optuna version: current master https://github.com/optuna/optuna/commit/b213753a816689a97b6433e1e780ca32c4293e07
- Python version: 3.9
- OS: mac
- (Optional) Other libraries and their versions:


### Error messages, stack traces, or logs

```shell
(venv) mamu@HideakinoMacBook-puro code_snippets_for_optuna % python tpe-tests/hoge.py 
/Users/mamu/Dev/pfn/optuna/optuna/samplers/_tpe/parzen_estimator.py:230: RuntimeWarning: invalid value encountered in true_divide
  weights /= weights.sum()
[nan]
[1.]
[nan]
[nan]
[nan]
```


### Steps to reproduce

```python
import optuna
from optuna import distributions
from optuna.samplers._tpe.parzen_estimator import _ParzenEstimator
from optuna.samplers._tpe.parzen_estimator import _ParzenEstimatorParameters

import numpy as np

def test_invalid_weights(weights):
    parameters = _ParzenEstimatorParameters(
        prior_weight=1.0,
        consider_prior=False,
        consider_magic_clip=False,
        consider_endpoints=False,
        weights=weights,
        multivariate=False,
    )
    mpe = _ParzenEstimator(
        {"a": np.asarray([0.0])}, {"a": distributions.FloatDistribution(-1.0, 1.0)}, parameters
    )

    print(mpe._weights)

weights_funcs = [
	lambda x: np.zeros(x),
	lambda x: -np.ones(x),
	lambda x: float("inf") * np.ones(x),
	lambda x: -float("inf") * np.ones(x),
	lambda x: np.asarray([float("nan") for _ in range(x)]),
]

for weights in weights_funcs:
	test_invalid_weights(weights)

```


### Additional context (optional)

_No response_


Discussion:

Resolving patch:
diff --git a/optuna/samplers/_tpe/parzen_estimator.py b/optuna/samplers/_tpe/parzen_estimator.py
--- a/optuna/samplers/_tpe/parzen_estimator.py
+++ b/optuna/samplers/_tpe/parzen_estimator.py
@@ -216,6 +216,22 @@ def _calculate_weights(self, predetermined_weights: Optional[np.ndarray]) -> np.
 
         if predetermined_weights is None:
             w = weights_func(n_observations)[:n_observations]
+            if w is not None:
+                if np.any(w < 0):
+                    raise ValueError(
+                        f"The `weights` function is not allowed to return negative values {w}. "
+                        + f"The argument of the `weights` function is {n_observations}."
+                    )
+                if len(w) > 0 and np.sum(w) <= 0:
+                    raise ValueError(
+                        f"The `weight` function is not allowed to return all-zero values {w}."
+                        + f" The argument of the `weights` function is {n_observations}."
+                    )
+                if not np.all(np.isfinite(w)):
+                    raise ValueError(
+                        "The `weights`function is not allowed to return infinite or NaN values "
+                        + f"{w}. The argument of the `weights` function is {n_observations}."
+                    )
         else:
             w = predetermined_weights[:n_observations]
 


Test patch:
diff --git a/tests/samplers_tests/tpe_tests/test_multi_objective_sampler.py b/tests/samplers_tests/tpe_tests/test_multi_objective_sampler.py
--- a/tests/samplers_tests/tpe_tests/test_multi_objective_sampler.py
+++ b/tests/samplers_tests/tpe_tests/test_multi_objective_sampler.py
@@ -142,9 +142,6 @@ def test_multi_objective_sample_independent_misc_arguments() -> None:
     sampler = TPESampler(gamma=lambda _: 1, seed=0)
     assert suggest(sampler, study, trial, dist, past_trials) != suggestion
 
-    sampler = TPESampler(weights=lambda n: np.zeros(n), seed=0)
-    assert suggest(sampler, study, trial, dist, past_trials) != suggestion
-
 
 @pytest.mark.parametrize("log, step", [(False, None), (True, None), (False, 0.1)])
 def test_multi_objective_sample_independent_float_distributions(
diff --git a/tests/samplers_tests/tpe_tests/test_parzen_estimator.py b/tests/samplers_tests/tpe_tests/test_parzen_estimator.py
--- a/tests/samplers_tests/tpe_tests/test_parzen_estimator.py
+++ b/tests/samplers_tests/tpe_tests/test_parzen_estimator.py
@@ -389,12 +389,7 @@ def test_invalid_weights(weights: Callable[[int], np.ndarray]) -> None:
         weights=weights,
         multivariate=False,
     )
-    mpe = _ParzenEstimator(
-        {"a": np.asarray([0.0])}, {"a": distributions.FloatDistribution(-1.0, 1.0)}, parameters
-    )
-
-    # TODO(HideakiImamura): Currently, the weight calculation does not raise if the specified
-    #  weights function is invalid. To be fixed to raise a `ValueError`.
-    assert len(mpe._weights) == 1
-    # If `weights = lambda x: -np.ones(x)`, the calculated weight is 1, otherwise nan.
-    assert np.isnan(mpe._weights[0]) or mpe._weights[0] == 1.0
+    with pytest.raises(ValueError):
+        _ParzenEstimator(
+            {"a": np.asarray([0.0])}, {"a": distributions.FloatDistribution(-1.0, 1.0)}, parameters
+        )