LightningBO 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,208 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License.
67
+
68
+ Subject to the terms and conditions of this License, each Contributor
69
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
70
+ royalty-free, irrevocable copyright license to reproduce, prepare
71
+ Derivative Works of, publicly display, publicly perform, sublicense,
72
+ and distribute the Work and such Derivative Works in Source or Object
73
+ form.
74
+
75
+ 3. Grant of Patent License.
76
+
77
+ Subject to the terms and conditions of this License, each Contributor
78
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
79
+ royalty-free, irrevocable (except as stated in this section) patent
80
+ license to make, have made, use, offer to sell, sell, import, and
81
+ otherwise transfer the Work, where such license applies only to those
82
+ patent claims licensable by such Contributor that are necessarily
83
+ infringed by their Contribution(s) alone or by combination of their
84
+ Contribution(s) with the Work to which such Contribution(s) was
85
+ submitted. If You institute patent litigation against any entity
86
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
87
+ the Work or a Contribution incorporated within the Work constitutes
88
+ direct or contributory patent infringement, then any patent licenses
89
+ granted to You under this License for that Work shall terminate as of
90
+ the date such litigation is filed.
91
+
92
+ 4. Redistribution.
93
+
94
+ You may reproduce and distribute copies of the Work or Derivative
95
+ Works thereof in any medium, with or without modifications, and in
96
+ Source or Object form, provided that You meet the following conditions:
97
+
98
+ (a) You must give any other recipients of the Work or Derivative Works
99
+ a copy of this License; and
100
+
101
+ (b) You must cause any modified files to carry prominent notices stating
102
+ that You changed the files; and
103
+
104
+ (c) You must retain, in the Source form of any Derivative Works that You
105
+ distribute, all copyright, patent, trademark, and attribution
106
+ notices from the Source form of the Work, excluding those notices
107
+ that do not pertain to any part of the Derivative Works; and
108
+
109
+ (d) If the Work includes a "NOTICE" text file as part of its distribution,
110
+ then any Derivative Works that You distribute must include a readable
111
+ copy of the attribution notices contained within such NOTICE file,
112
+ excluding those notices that do not pertain to any part of the
113
+ Derivative Works, in at least one of the following places: within a
114
+ NOTICE text file distributed as part of the Derivative Works; within
115
+ the Source form or documentation, if provided along with the
116
+ Derivative Works; or, within a display generated by the Derivative
117
+ Works, if and wherever such third-party notices normally appear. The
118
+ contents of the NOTICE file are for informational purposes only and
119
+ do not modify the License. You may add Your own attribution notices
120
+ within Derivative Works that You distribute, alongside or as an
121
+ addendum to the NOTICE text from the Work, provided that such
122
+ additional attribution notices cannot be construed as modifying the
123
+ License.
124
+
125
+ You may add Your own copyright statement to Your modifications and may
126
+ provide additional or different license terms and conditions for use,
127
+ reproduction, or distribution of Your modifications, or for any such
128
+ Derivative Works as a whole, provided Your use, reproduction, and
129
+ distribution of the Work otherwise complies with the conditions
130
+ stated in this License.
131
+
132
+ 5. Submission of Contributions.
133
+
134
+ Unless You explicitly state otherwise, any Contribution intentionally
135
+ submitted for inclusion in the Work by You to the Licensor shall be
136
+ under the terms and conditions of this License, without any additional
137
+ terms or conditions. Notwithstanding the above, nothing herein shall
138
+ supersede or modify the terms of any separate license agreement you
139
+ may have executed with Licensor regarding such Contributions.
140
+
141
+ 6. Trademarks.
142
+
143
+ This License does not grant permission to use the trade names,
144
+ trademarks, service marks, or product names of the Licensor, except as
145
+ required for describing the origin of the Work and reproducing the
146
+ content of the NOTICE file.
147
+
148
+ 7. Disclaimer of Warranty.
149
+
150
+ Unless required by applicable law or agreed to in writing, Licensor
151
+ provides the Work (and each Contributor provides its Contributions) on
152
+ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
153
+ express or implied, including, without limitation, any warranties or
154
+ conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
155
+ A PARTICULAR PURPOSE. You are solely responsible for determining the
156
+ appropriateness of using or redistributing the Work and assume any
157
+ risks associated with Your exercise of permissions under this License.
158
+
159
+ 8. Limitation of Liability.
160
+
161
+ In no event and under no legal theory, whether in tort (including
162
+ negligence), contract, or otherwise, unless required by applicable law
163
+ (such as deliberate and grossly negligent acts) or agreed to in writing,
164
+ shall any Contributor be liable to You for damages, including any direct,
165
+ indirect, special, incidental, or consequential damages of any character
166
+ arising as a result of this License or out of the use or inability to
167
+ use the Work (including but not limited to damages for loss of goodwill,
168
+ work stoppage, computer failure or malfunction, or any and all other
169
+ commercial damages or losses), even if such Contributor has been advised
170
+ of the possibility of such damages.
171
+
172
+ 9. Accepting Warranty or Additional Liability.
173
+
174
+ While redistributing the Work or Derivative Works thereof, You may choose
175
+ to offer, and charge a fee for, acceptance of support, warranty, indemnity,
176
+ or other liability obligations and/or rights consistent with this License.
177
+ However, in accepting such obligations, You may act only on Your own behalf
178
+ and on Your sole responsibility, not on behalf of any other Contributor,
179
+ and only if You agree to indemnify, defend, and hold each Contributor
180
+ harmless for any liability incurred by, or claims asserted against, such
181
+ Contributor by reason of your accepting any such warranty or additional
182
+ liability.
183
+
184
+ END OF TERMS AND CONDITIONS
185
+
186
+ APPENDIX: How to apply the Apache License to your work.
187
+
188
+ To apply the Apache License to your work, attach the following boilerplate
189
+ notice, with the fields enclosed by brackets "[]" replaced with your own
190
+ identifying information. (Don't include the brackets!) The text should be
191
+ enclosed in the appropriate comment syntax for the file format. We also
192
+ recommend that a file or class name and description of purpose be included
193
+ on the same "printed page" as the copyright notice for easier
194
+ identification within third-party archives.
195
+
196
+ Copyright [2025] [s-ohtake]
197
+
198
+ Licensed under the Apache License, Version 2.0 (the "License");
199
+ you may not use this file except in compliance with the License.
200
+ You may obtain a copy of the License at
201
+
202
+ http://www.apache.org/licenses/LICENSE-2.0
203
+
204
+ Unless required by applicable law or agreed to in writing, software
205
+ distributed under the License is distributed on an "AS IS" BASIS,
206
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
207
+ See the License for the specific language governing permissions and
208
+ limitations under the License.
@@ -0,0 +1,2 @@
1
+ # LightningBO
2
+ __version__ = "0.1.0"
@@ -0,0 +1,460 @@
1
+ """
2
+ Bayesian Optimization Core Library
3
+
4
+
5
+ This module provides core components for Bayesian optimization.
6
+
7
+ - Parameter space definitions (Continuous, Integer, ParamSpace)
8
+ - Gaussian Process utilities (kernels, prediction, hyperparameter fitting)
9
+ - Acquisition functions (EI, PI, UCB, LCB)
10
+
11
+ Design philosophy:
12
+ - Flexible abstraction of parameter space for use from Optimizer
13
+ - Fast numerical computation and automatic differentiation via JAX
14
+ - Reusable for both research and production
15
+
16
+ Main use cases:
17
+ - Black-box function optimization
18
+ - Hyperparameter search
19
+ - Experiment planning and automation
20
+
21
+ All components are designed to be reusable and extensible for other optimization algorithms.
22
+ """
23
+
24
+ from collections import namedtuple
25
+ from functools import partial
26
+ from typing import Any
27
+
28
+ import jax
29
+ import jax.numpy as jnp
30
+ import optax
31
+ from jax.scipy.linalg import cholesky, solve_triangular
32
+ from jax.scipy.stats import norm
33
+
34
+ jax.config.update("jax_enable_x64", True)
35
+ from abc import ABC, abstractmethod
36
+
37
+ class ParameterDomain(ABC):
38
+ """パラメータ空間の抽象基底クラス"""
39
+
40
+ def __init__(self, dtype):
41
+ self.dtype = dtype
42
+
43
+ def __hash__(self):
44
+ return hash((type(self), self.dtype))
45
+
46
+ def __eq__(self, other):
47
+ return type(self) == type(other) and self.dtype == other.dtype
48
+
49
+ @abstractmethod
50
+ def transform(self, x):
51
+ pass
52
+
53
+ @abstractmethod
54
+ def sample(self, key, shape):
55
+ pass
56
+
57
+
58
+
59
+ class Continuous(ParameterDomain):
60
+ """連続値パラメータ空間"""
61
+
62
+ def __init__(self, lower: float, upper: float):
63
+ if not isinstance(lower, (float, int)):
64
+ raise TypeError(f"lower must be float or int, got {type(lower)}")
65
+ if not isinstance(upper, (float, int)):
66
+ raise TypeError(f"upper must be float or int, got {type(upper)}")
67
+ if lower > upper:
68
+ raise ValueError("lower must be <= upper")
69
+ self.lower = float(lower)
70
+ self.upper = float(upper)
71
+ super().__init__(dtype=jnp.float32)
72
+
73
+ def __hash__(self):
74
+ return hash((type(self), self.lower, self.upper))
75
+
76
+ def __eq__(self, other):
77
+ return (
78
+ isinstance(other, Continuous)
79
+ and self.lower == other.lower
80
+ and self.upper == other.upper
81
+ )
82
+
83
+ def transform(self, x):
84
+ return jnp.clip(x, self.lower, self.upper)
85
+
86
+ def sample(self, key, shape):
87
+ samples = jax.random.uniform(key, shape, minval=self.lower, maxval=self.upper)
88
+ return self.transform(samples)
89
+
90
+
91
+ class Integer(ParameterDomain):
92
+ """
93
+ Discrete integer-valued domain with rounding and clipping.
94
+
95
+ Represents a parameter that can take integer values within [lower, upper].
96
+ """
97
+
98
+ def __init__(self, lower, upper):
99
+ """
100
+ Initialize an integer domain with bounds.
101
+
102
+ Args:
103
+ lower: Lower integer bound (inclusive).
104
+ upper: Upper integer bound (inclusive).
105
+ """
106
+ if not isinstance(lower, int):
107
+ raise TypeError("Lower bound must be an integer")
108
+ if not isinstance(upper, int):
109
+ raise TypeError("Upper bound must be an integer")
110
+ if lower >= upper:
111
+ raise ValueError("Lower bound must be less than upper bound")
112
+
113
+ self.lower = int(lower)
114
+ self.upper = int(upper)
115
+ super().__init__(dtype=jnp.int32)
116
+
117
+ def __hash__(self):
118
+ """Return hash based on bounds."""
119
+ return hash((self.lower, self.upper))
120
+
121
+ def __eq__(self, other):
122
+ """Check equality based on bounds."""
123
+ return self.lower == other.lower and self.upper == other.upper
124
+
125
+ def transform(self, x: jax.Array):
126
+ """
127
+ Round and clip values to the integer domain.
128
+
129
+ Args:
130
+ x: Input values.
131
+
132
+ Returns
133
+ -------
134
+ Rounded and clipped values as float32.
135
+ """
136
+ return jnp.clip(jnp.round(x), self.lower, self.upper).astype(jnp.float32)
137
+
138
+ def sample(self, key: jax.Array, shape: tuple):
139
+ """
140
+ Sample integers uniformly from the domain.
141
+
142
+ Args:
143
+ key: JAX PRNGKey.
144
+ shape: Desired output shape.
145
+
146
+ Returns
147
+ -------
148
+ Sampled values clipped to valid integer range.
149
+ """
150
+ samples = jax.random.randint(
151
+ key, shape, minval=self.lower, maxval=self.upper + 1
152
+ )
153
+ return self.transform(samples)
154
+
155
+
156
+
157
+ class ParamSpace:
158
+ """
159
+ Parameter space manager for collections of named parameter domains.
160
+
161
+ This utility encapsulates logic for sampling, transforming, and handling
162
+ structured parameter inputs defined by a mapping of variable names to ParameterDomain
163
+ instances (e.g., Continuous, Integer).
164
+ """
165
+
166
+ def __init__(self, space: dict):
167
+ self.space = space
168
+
169
+ def sample_params(self, key: jax.Array, shape: tuple) -> dict:
170
+ """Sample parameters from all domains."""
171
+ keys = jax.random.split(key, len(self.space))
172
+ return {
173
+ name: self.space[name].sample(k, shape)
174
+ for name, k in zip(self.space, keys)
175
+ }
176
+
177
+ def to_array(self, tree: dict) -> jax.Array:
178
+ """
179
+ Transform a batch of parameter values into a 2D array suitable for GP input.
180
+
181
+ Args:
182
+ tree: A dictionary of parameter name → array of raw values.
183
+
184
+ Returns
185
+ -------
186
+ A JAX array of shape (batch_size, num_params) with transformed values.
187
+ """
188
+ return jnp.stack([self.space[k].transform(tree[k]) for k in self.space], axis=1)
189
+
190
+ def to_dict(self, xs: jax.Array) -> dict:
191
+ """
192
+ Convert a stacked parameter matrix back into named parameter trees.
193
+
194
+ Args:
195
+ xs: A 2D JAX array of shape (batch_size, num_params), with each column
196
+ corresponding to a parameter.
197
+
198
+ Returns
199
+ -------
200
+ A dictionary mapping parameter names to individual 1D arrays.
201
+ """
202
+ return {k: self.space[k].transform(xs[:, i]) for i, k in enumerate(self.space)}
203
+
204
+
205
+ # =============================================================================
206
+ # Gaussian Process Components
207
+ # =============================================================================
208
+
209
+ MASK_VARIANCE = 1e12 # High variance for masked points to not affect the process.
210
+
211
+ GPParams = namedtuple("GPParams", ["noise", "amplitude", "lengthscale"])
212
+ GPState = namedtuple("GPState", ["params", "momentums", "scales"])
213
+
214
+
215
+ def softplus(x):
216
+ """Softplus activation function."""
217
+ return jnp.logaddexp(x, 0.0)
218
+
219
+
220
+ def exp_quadratic(x1, x2, mask):
221
+ """Exponential quadratic kernel function."""
222
+ distance = jnp.sum((x1 - x2) ** 2)
223
+ return jnp.exp(-distance) * mask
224
+
225
+
226
+ def cov(x1, x2, mask1, mask2):
227
+ """Covariance function with masking."""
228
+ M = jnp.outer(mask1, mask2)
229
+ k = exp_quadratic
230
+ return jax.vmap(jax.vmap(k, in_axes=(None, 0, 0)), in_axes=(0, None, 0))(x1, x2, M)
231
+
232
+
233
+ def gaussian_process(
234
+ params,
235
+ x: jnp.ndarray,
236
+ y: jnp.ndarray,
237
+ mask,
238
+ xt: Any = None,
239
+ compute_ml: bool = False,
240
+ ) -> Any:
241
+ """Core Gaussian Process implementation with masking support."""
242
+ # Number of points in the prior distribution
243
+ n = x.shape[0]
244
+
245
+ noise, amp, ls = jax.tree_util.tree_map(softplus, params)
246
+
247
+ ymean = jnp.mean(y, where=mask.astype(bool))
248
+ y = (y - ymean) * mask
249
+ x = x / ls
250
+ K = amp * cov(x, x, mask, mask) + (jnp.eye(n) * (noise + 1e-6))
251
+ K += jnp.eye(n) * (1.0 - mask.astype(float)) * MASK_VARIANCE
252
+ L = cholesky(K, lower=True)
253
+ K_inv_y = solve_triangular(L.T, solve_triangular(L, y, lower=True), lower=False)
254
+
255
+ if compute_ml:
256
+ logp = 0.5 * jnp.dot(y.T, K_inv_y)
257
+ logp += jnp.sum(jnp.log(jnp.diag(L)))
258
+ logp -= jnp.sum(1.0 - mask) * 0.5 * jnp.log(MASK_VARIANCE)
259
+ logp += (jnp.sum(mask) / 2) * jnp.log(2 * jnp.pi)
260
+ logp += jnp.sum(-0.5 * jnp.log(2 * jnp.pi) - jnp.log(amp) - jnp.log(amp) ** 2)
261
+ return jnp.sum(logp)
262
+
263
+ if xt is None:
264
+ raise ValueError("xt cannot be None during prediction")
265
+ xt = xt / ls
266
+
267
+ # Compute the covariance with the new point xt
268
+ mask_t = jnp.ones(len(xt)) == 1
269
+ K_cross = amp * cov(x, xt, mask, mask_t)
270
+
271
+ K_inv_y = K_inv_y * mask # masking
272
+ pred_mean = jnp.dot(K_cross.T, K_inv_y) + ymean
273
+ v = solve_triangular(L, K_cross, lower=True)
274
+ pred_var = amp * cov(xt, xt, mask_t, mask_t) - v.T @ v
275
+ pred_std = jnp.sqrt(jnp.maximum(jnp.diag(pred_var), 1e-10))
276
+ return pred_mean, pred_std
277
+
278
+
279
+ marginal_likelihood = partial(gaussian_process, compute_ml=True)
280
+ grad_fun = jax.jit(jax.grad(marginal_likelihood))
281
+ predict = jax.jit(partial(gaussian_process, compute_ml=False))
282
+
283
+
284
+ def neg_log_likelihood(params, x, y, mask):
285
+ """Negative log likelihood for GP hyperparameter optimization."""
286
+ ll = marginal_likelihood(params, x, y, mask)
287
+
288
+ # Weak priors to keep things sane
289
+ priors = GPParams(-8.0, 1.0, 1.0)
290
+ log_prior = jax.tree.map(lambda p, m: jnp.sum((p - m) ** 2), params, priors)
291
+ log_prior = sum(jax.tree.leaves(log_prior))
292
+ log_posterior = ll - 0.5 * log_prior
293
+ return -log_posterior
294
+
295
+
296
+ def posterior_fit(
297
+ y: jax.Array,
298
+ x: jax.Array,
299
+ mask: jax.Array,
300
+ params: GPParams,
301
+ lr: float = 1e-3,
302
+ trainsteps: int = 100,
303
+ ) -> GPParams:
304
+ """Fit GP hyperparameters using gradient descent."""
305
+ optimizer = optax.chain(optax.clip_by_global_norm(10.0), optax.adamw(lr))
306
+ opt_state = optimizer.init(params)
307
+
308
+ def train_step(carry, _):
309
+ params, opt_state = carry
310
+ grads = jax.grad(neg_log_likelihood)(params, x, y, mask)
311
+ updates, opt_state = optimizer.update(grads, opt_state, params=params)
312
+ params = optax.apply_updates(params, updates)
313
+ return (params, opt_state), None
314
+
315
+ (params, _), __ = jax.lax.scan(
316
+ train_step, (params, opt_state), None, length=trainsteps
317
+ )
318
+ return params
319
+
320
+
321
+ # =============================================================================
322
+ # Acquisition Functions
323
+ # =============================================================================
324
+
325
+
326
+ def expected_improvement(
327
+ x_pred: jnp.ndarray,
328
+ xs: jax.Array,
329
+ ys: jax.Array,
330
+ mask: jax.Array,
331
+ gp_params: GPParams,
332
+ xi: float = 0.01,
333
+ ):
334
+ r"""
335
+ Compute Expected Improvement (EI) acquisition function.
336
+
337
+ Favors points with high improvement over the current best observed value,
338
+ balancing exploitation and exploration.
339
+
340
+ The formula is:
341
+
342
+ .. math::
343
+
344
+ EI(x) = (\mu(x) - y^* - \xi) \Phi(z) + \sigma(x) \phi(z)
345
+
346
+ where:
347
+
348
+ .. math::
349
+
350
+ z = \frac{\mu(x) - y^* - \xi}{\sigma(x)}
351
+
352
+ Args:
353
+ x_pred: Candidate input locations to evaluate.
354
+ xs: Observed inputs.
355
+ ys: Observed function values.
356
+ mask: Boolean mask indicating valid entries in `ys`.
357
+ gp_params: Gaussian Process hyperparameters.
358
+ xi: Exploration-exploitation tradeoff parameter.
359
+
360
+ Returns
361
+ -------
362
+ EI scores at `x_pred`.
363
+ """
364
+ ymax = jnp.max(ys, where=mask.astype(bool), initial=-jnp.inf)
365
+ mu, std = predict(gp_params, xs, ys, mask, xt=x_pred)
366
+ a = mu - ymax - xi
367
+ z = a / (std + 1e-3)
368
+ ei = a * norm.cdf(z) + std * norm.pdf(z)
369
+ return ei
370
+
371
+
372
+ def probability_improvement(
373
+ x_pred: jnp.ndarray,
374
+ xs: jax.Array,
375
+ ys: jax.Array,
376
+ mask: jax.Array,
377
+ gp_params: GPParams,
378
+ xi: float = 0.01,
379
+ ):
380
+ """
381
+ Probability of Improvement (PI) acquisition function.
382
+
383
+ Estimates the probability that a candidate point will improve
384
+ over the current best observed value.
385
+
386
+ Args:
387
+ x_pred: Candidate input locations to evaluate.
388
+ xs: Observed inputs.
389
+ ys: Observed function values.
390
+ mask: Boolean mask indicating valid entries in `ys`.
391
+ gp_params: Gaussian Process hyperparameters.
392
+ xi: Improvement margin for sensitivity.
393
+
394
+ Returns
395
+ -------
396
+ PI scores at `x_pred`.
397
+ """
398
+ y_max = ys.max()
399
+ mu, std = predict(gp_params, xs, ys, mask, xt=x_pred)
400
+ z = (mu - y_max - xi) / std
401
+ return norm.cdf(z)
402
+
403
+
404
+ def upper_confidence_bounds(
405
+ x_pred: jnp.ndarray,
406
+ xs: jax.Array,
407
+ ys: jax.Array,
408
+ mask: jax.Array,
409
+ gp_params: GPParams,
410
+ kappa: float = 0.01,
411
+ ):
412
+ """
413
+ Upper Confidence Bound (UCB) acquisition function.
414
+
415
+ Promotes exploration by favoring points with high predictive uncertainty.
416
+
417
+ Args:
418
+ x_pred: Candidate input locations to evaluate.
419
+ xs: Observed inputs.
420
+ ys: Observed function values.
421
+ mask: Boolean mask indicating valid entries in `ys`.
422
+ gp_params: Gaussian Process hyperparameters.
423
+ kappa: Weighting factor for uncertainty.
424
+
425
+ Returns
426
+ -------
427
+ UCB scores at `x_pred`.
428
+ """
429
+ mu, std = predict(gp_params, xs, ys, mask, xt=x_pred)
430
+ return mu + kappa * std
431
+
432
+
433
+ def lower_confidence_bounds(
434
+ x_pred: jnp.ndarray,
435
+ xs: jax.Array,
436
+ ys: jax.Array,
437
+ mask: jax.Array,
438
+ gp_params: GPParams,
439
+ kappa: float = 2.576,
440
+ ):
441
+ """
442
+ Lower Confidence Bound (LCB) acquisition function.
443
+
444
+ Useful for minimization tasks. Encourages sampling in uncertain regions
445
+ with low predicted values.
446
+
447
+ Args:
448
+ x_pred: Candidate input locations to evaluate.
449
+ xs: Observed inputs.
450
+ ys: Observed function values.
451
+ mask: Boolean mask indicating valid entries in `ys`.
452
+ gp_params: Gaussian Process hyperparameters.
453
+ kappa: Weighting factor for uncertainty.
454
+
455
+ Returns
456
+ -------
457
+ LCB scores at `x_pred`.
458
+ """
459
+ mu, std = predict(gp_params, xs, ys, mask, xt=x_pred)
460
+ return mu - kappa * std
@@ -0,0 +1,336 @@
1
+
2
+ """
3
+ Bayesian Optimization High-Level Interface
4
+
5
+ This module provides a high-level API for Bayesian optimization using Gaussian Processes.
6
+ It manages the optimization loop, parameter space, and acquisition functions.
7
+
8
+ Main features:
9
+ - Optimizer class for black-box function optimization
10
+ - Flexible parameter space definition (Continuous, Integer, etc.)
11
+ - Acquisition functions: EI, PI, UCB, LCB
12
+ - Automatic state management and buffer expansion
13
+ - JAX-based fast computation and differentiation
14
+
15
+ Typical use cases:
16
+ - Hyperparameter tuning
17
+ - Experimental design
18
+ - Automated search for expensive functions
19
+
20
+ Designed for extensibility and integration with custom objective functions.
21
+ """
22
+
23
+
24
+ from collections.abc import Callable
25
+ from functools import partial
26
+ from typing import NamedTuple
27
+
28
+
29
+ import jax
30
+ from typing import Any
31
+ import jax.numpy as jnp
32
+ import numpy as np
33
+ import optax
34
+
35
+ from .bayesian_core import (
36
+ GPParams,
37
+ ParamSpace,
38
+ expected_improvement,
39
+ lower_confidence_bounds,
40
+ posterior_fit,
41
+ probability_improvement,
42
+ upper_confidence_bounds,
43
+ )
44
+
45
+
46
+ UtilityFunctionType = Callable[[jnp.ndarray], float]
47
+
48
+
49
+
50
+ class OptimizerState(NamedTuple):
51
+ """
52
+ State container for Bayesian optimizer.
53
+
54
+ Attributes:
55
+ params: dict[str, jnp.ndarray] - Padded parameter arrays.
56
+ ys: jnp.ndarray | np.ndarray - Objective values (with padding).
57
+ best_score: float - Best observed objective value.
58
+ best_params: dict[str, float] - Parameter configuration for best_score.
59
+ mask: jnp.ndarray - Boolean mask for valid entries.
60
+ gp_params: GPParams - Fitted Gaussian Process parameters.
61
+ """
62
+ params: dict[str, jnp.ndarray]
63
+ ys: jnp.ndarray | np.ndarray
64
+ best_score: float
65
+ best_params: dict[str, float]
66
+ mask: jnp.ndarray
67
+ gp_params: GPParams
68
+
69
+
70
+
71
+ def _optimize_suggestion(params: jnp.ndarray, fun: Callable[[jnp.ndarray], float], max_iter: int = 10) -> jnp.ndarray:
72
+ """
73
+ Local optimization using L-BFGS (Optax).
74
+
75
+ Args:
76
+ params: Initial point in input space.
77
+ fun: Objective function to maximize (returns scalar, supports autodiff).
78
+ max_iter: Number of L-BFGS steps.
79
+
80
+ Returns:
81
+ Optimized point after `max_iter` iterations.
82
+ """
83
+ opt = optax.lbfgs()
84
+ value_and_grad_fun = optax.value_and_grad_from_state(lambda x: -fun(x))
85
+
86
+ def step(carry, _):
87
+ params, state = carry
88
+ value, grad = value_and_grad_fun(params, state=state)
89
+ updates, state = opt.update(
90
+ grad, state, params=params, value=value, grad=grad, value_fn=fun
91
+ )
92
+ params = optax.apply_updates(params, updates)
93
+ params = jnp.clip(params, -1e6, 1e6)
94
+ return (params, state), None
95
+
96
+ init_carry = (params, opt.init(params))
97
+ (final_params, _), __ = jax.lax.scan(step, init_carry, None, length=max_iter)
98
+ return final_params
99
+
100
+
101
+
102
+ class Optimizer:
103
+ """
104
+ Bayesian optimizer using Gaussian Processes and acquisition functions.
105
+
106
+ Manages the optimization loop for expensive black-box functions by modeling
107
+ them with a Gaussian Process and selecting samples via acquisition functions
108
+ (EI, PI, UCB, LCB).
109
+ """
110
+
111
+ def __init__(self, domain: dict[str, Any], acq: str = "EI", maximize: bool = False):
112
+ """
113
+ Initialize the optimizer.
114
+
115
+ Args:
116
+ domain: A dict mapping parameter names to domain objects (e.g., Real, Integer).
117
+ acq: Acquisition function ('EI', 'PI', 'UCB', 'LCB').
118
+ maximize: Whether to maximize or minimize the objective.
119
+ """
120
+ self.domain = domain
121
+ self.sign = 1 if maximize else -1
122
+ self.param_space = ParamSpace(domain)
123
+
124
+ acq_map = {
125
+ "EI": expected_improvement,
126
+ "PI": probability_improvement,
127
+ "UCB": upper_confidence_bounds,
128
+ "LCB": lower_confidence_bounds,
129
+ }
130
+ if acq not in acq_map:
131
+ raise ValueError(f"Acquisition function {acq} is not implemented")
132
+ self.acq = jax.jit(acq_map[acq])
133
+
134
+ def init(self, ys: jax.Array, params: dict, noise_scale: float = -8.0):
135
+ """
136
+ Initialize the optimizer state from initial data.
137
+
138
+ Args:
139
+ ys: Objective values for the initial parameters.
140
+ params: Dict of parameter arrays (same keys as domain).
141
+ noise_scale: Initial noise scale for GP.
142
+
143
+ Returns
144
+ -------
145
+ Initialized OptimizerState.
146
+ """
147
+ # Create a padded jax array for each parameter and each score.
148
+ # In order to keep jax compilations at a bay.
149
+ num_entries = len(ys)
150
+ pad_value = int(np.ceil(len(ys) / 10) * 10)
151
+
152
+ # Convert to jax arrays if they are not already
153
+ ys = jnp.asarray(ys)
154
+ ys = self.sign * ys
155
+ params = jax.tree.map(lambda x: jnp.asarray(x), params)
156
+
157
+ # Define padded arrays for the inputs and the outputs
158
+ mask = jnp.zeros(shape=(pad_value,), dtype=jnp.bool_).at[:num_entries].set(True)
159
+ ys = jnp.zeros(shape=(pad_value,), dtype=ys.dtype).at[:num_entries].set(ys)
160
+
161
+ _params = {}
162
+ for key, entries in params.items():
163
+ # Assert that the parameter is in the domain dictionary
164
+ if key not in self.domain:
165
+ raise ValueError(f"Parameter {key} is not in the domain")
166
+
167
+ # Get dtype from the domain and create a padded array
168
+ dtype = self.domain[key].dtype
169
+ values = (
170
+ jnp.zeros(shape=(pad_value,), dtype=dtype).at[:num_entries].set(entries)
171
+ )
172
+ _params[key] = values
173
+
174
+ # From the given observation, find the better one (either maxima or minima) and return the
175
+ # initial optimizer state.
176
+ best_score = float(jnp.max(ys[mask]))
177
+ best_params_idx = jnp.argmax(ys[mask])
178
+ best_params = jax.tree.map(lambda x: x[mask][best_params_idx], _params)
179
+
180
+ # Initialize the gaussian processes state
181
+ gp_params = GPParams(
182
+ noise=jnp.full((1, 1), 1.0 * noise_scale),
183
+ amplitude=jnp.zeros((1, 1)),
184
+ lengthscale=jnp.zeros((1, len(_params))),
185
+ )
186
+
187
+ # Fit to the current observations
188
+ xs = jnp.stack(
189
+ [self.domain[key].transform(_params[key]) for key in _params], axis=1
190
+ )
191
+ gp_params = posterior_fit(ys, xs, mask=mask, params=gp_params)
192
+
193
+ ys = self.sign * ys
194
+ best_score = self.sign * best_score
195
+ opt_state = OptimizerState(
196
+ params=_params,
197
+ ys=ys,
198
+ best_score=best_score,
199
+ best_params=best_params,
200
+ mask=mask,
201
+ gp_params=gp_params,
202
+ )
203
+
204
+ return opt_state
205
+
206
+ @partial(jax.jit, static_argnames=("self", "size"))
207
+ def sample(self, key, state, size=10_000):
208
+ """
209
+ Sample new parameters using the acquisition function.
210
+
211
+ Args:
212
+ key: JAX PseudoRandom key for random sampling.
213
+ state: Current optimizer state.
214
+ size: Number of samples to draw.
215
+
216
+ Returns
217
+ -------
218
+ Sampled parameters (dict).
219
+ """
220
+ # Sample 'size' elements of each distribution.
221
+ samples = self.param_space.sample_params(key, (size,))
222
+ xs_samples = self.param_space.to_array(samples)
223
+
224
+ # Prepare the data for the Gaussian process prediction.
225
+ xs = self.param_space.to_array(state.params)
226
+ ys = self.sign * state.ys
227
+ mask = state.mask
228
+ gp_params = state.gp_params
229
+
230
+ # Compute the acquisition function values for the sampled points.
231
+ acq_vals = self.acq(xs_samples, xs, ys, mask, gp_params)
232
+
233
+ # Of those, find the best 50 points and optimize them using BFGS.
234
+ top_idxs = jnp.argsort(acq_vals)[-50:]
235
+ init_points = xs_samples[top_idxs]
236
+
237
+ def acquisition_objective(x):
238
+ return jnp.squeeze(self.acq(x[None, :], xs, ys, mask, gp_params))
239
+
240
+ optimized = jax.vmap(
241
+ lambda x: _optimize_suggestion(x, acquisition_objective, max_iter=10)
242
+ )(init_points)
243
+ opt_vals = self.acq(optimized, xs, ys, mask, gp_params)
244
+
245
+ # Return the best point from the optimized points and the sampled points.
246
+ all_points = jnp.concatenate((optimized, xs_samples), axis=0)
247
+ all_vals = jnp.concatenate((opt_vals, acq_vals), axis=0)
248
+ chosen_suggestion = jnp.argmax(all_vals)
249
+
250
+ best_params = self.param_space.to_dict(all_points[chosen_suggestion][None])
251
+ return best_params
252
+
253
+ def expand(self, opt_state: OptimizerState):
254
+ """
255
+ Expand internal buffers if no space is available.
256
+
257
+ Args:
258
+ opt_state: Current optimizer state.
259
+
260
+ Returns
261
+ -------
262
+ OptimizerState with expanded storage.
263
+ """
264
+ current = jnp.sum(opt_state.mask)
265
+
266
+ if current == len(opt_state.mask):
267
+ pad_value = int(np.ceil(len(opt_state.mask) * 2 / 10) * 10)
268
+ diff = pad_value - len(opt_state.mask)
269
+ mask = jnp.pad(opt_state.mask, (0, diff))
270
+ ys = jnp.pad(opt_state.ys, (0, diff))
271
+ params = {}
272
+ for key in opt_state.params:
273
+ params[key] = jnp.pad(opt_state.params[key], (0, diff))
274
+ else:
275
+ mask = opt_state.mask
276
+ ys = opt_state.ys
277
+ params = opt_state.params
278
+
279
+ opt_state = OptimizerState(
280
+ params=params,
281
+ ys=ys,
282
+ best_score=opt_state.best_score,
283
+ best_params=opt_state.best_params,
284
+ mask=mask,
285
+ gp_params=opt_state.gp_params,
286
+ )
287
+ return opt_state
288
+
289
+ def fit(self, opt_state, y, new_params):
290
+ """
291
+ Update optimizer state with a new observation.
292
+
293
+ Args:
294
+ opt_state: Current optimizer state.
295
+ y: New objective value.
296
+ new_params: Parameters that produced y.
297
+
298
+ Returns
299
+ -------
300
+ Updated OptimizerState.
301
+ """
302
+ opt_state = self.expand(opt_state) # Prompts recompilation
303
+ opt_state = self._fit(opt_state, y, new_params)
304
+ return opt_state
305
+
306
+ @partial(jax.jit, static_argnums=(0,))
307
+ def _fit(self, opt_state, y, new_params):
308
+ """Update optimizer state with new observation (JIT-compiled)."""
309
+ last_idx = jnp.arange(len(opt_state.mask)) == jnp.argmin(opt_state.mask)
310
+ mask = jnp.asarray(jnp.where(last_idx, True, opt_state.mask))
311
+ ys = jnp.where(last_idx, y, opt_state.ys)
312
+ params = jax.tree_util.tree_map(
313
+ lambda x, y: jnp.where(last_idx, y, x), opt_state.params, new_params
314
+ )
315
+
316
+ xs = jnp.stack(
317
+ [self.domain[key].transform(params[key]) for key in params], axis=1
318
+ )
319
+ ys = self.sign * ys
320
+ gp_params = posterior_fit(ys, xs, mask=mask, params=opt_state.gp_params)
321
+
322
+ best_score = jnp.max(ys, where=mask, initial=-jnp.inf)
323
+ best_params_idx = jnp.argmax(jnp.where(mask, ys, -jnp.inf))
324
+ best_params = jax.tree_util.tree_map(lambda x: x[best_params_idx], params)
325
+
326
+ ys = self.sign * ys
327
+ best_score = self.sign * best_score
328
+ opt_state = OptimizerState(
329
+ params=params,
330
+ ys=ys,
331
+ best_score=best_score,
332
+ best_params=best_params,
333
+ mask=mask,
334
+ gp_params=gp_params,
335
+ )
336
+ return opt_state
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: LightningBO
3
+ Version: 0.1.0
4
+ Summary: Bayesian optimization library
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.12
7
+ License-File: LICENSE
8
+ Requires-Dist: jax
9
+ Requires-Dist: jaxlib
10
+ Requires-Dist: optax
11
+ Requires-Dist: numpy
12
+ Requires-Dist: absl-py
13
+ Requires-Dist: pytest
14
+ Requires-Dist: matplotlib
15
+ Dynamic: license-file
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ LightningBO/__init__.py
5
+ LightningBO/bayesian_core.py
6
+ LightningBO/bayesian_optimization.py
7
+ LightningBO.egg-info/PKG-INFO
8
+ LightningBO.egg-info/SOURCES.txt
9
+ LightningBO.egg-info/dependency_links.txt
10
+ LightningBO.egg-info/requires.txt
11
+ LightningBO.egg-info/top_level.txt
12
+ tests/test_bayesian_core.py
13
+ tests/test_bayesian_optimization.py
@@ -0,0 +1,7 @@
1
+ jax
2
+ jaxlib
3
+ optax
4
+ numpy
5
+ absl-py
6
+ pytest
7
+ matplotlib
@@ -0,0 +1 @@
1
+ LightningBO
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: LightningBO
3
+ Version: 0.1.0
4
+ Summary: Bayesian optimization library
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.12
7
+ License-File: LICENSE
8
+ Requires-Dist: jax
9
+ Requires-Dist: jaxlib
10
+ Requires-Dist: optax
11
+ Requires-Dist: numpy
12
+ Requires-Dist: absl-py
13
+ Requires-Dist: pytest
14
+ Requires-Dist: matplotlib
15
+ Dynamic: license-file
@@ -0,0 +1,48 @@
1
+ # LightningBO
2
+
3
+ LightningBOは、ベイズ最適化(Bayesian Optimization)を簡単に実装・拡張できるPythonライブラリです。
4
+ 本ライブラリは、ブラックボックス関数の最適化やハイパーパラメータ探索など、試行回数を抑えつつ効率的に最適解を見つけたい場面で活用できます。
5
+
6
+ ## 特徴
7
+
8
+ - JAXベースの高速な計算
9
+ - 連続・離散パラメータ両対応
10
+ - 期待値改善(EI)などの獲得関数
11
+ - 拡張性の高い設計
12
+
13
+ ## 使い方例
14
+
15
+ ```python
16
+ import LightningBO.bayesian_optimization as bo
17
+ import LightningBO.bayesian_core as bc
18
+
19
+ # 最適化したい関数
20
+ def f(x):
21
+ return x**2 - 4*x + 5
22
+
23
+ # 探索空間の定義
24
+ domain = {"x": bc.Continuous(-10, 10)}
25
+
26
+ # Optimizerの作成
27
+ optimizer = bo.Optimizer(domain=domain, maximize=False)
28
+
29
+ # 初期点
30
+ params = {"x": [0.0, 2.0, 5.0]}
31
+ ys = [f(x) for x in params["x"]]
32
+ state = optimizer.init(ys, params)
33
+
34
+ # 最適化ループ
35
+ for step in range(20):
36
+ new_params = optimizer.sample(None, state)
37
+ y = f(**new_params)
38
+ state = optimizer.fit(state, y, new_params)
39
+
40
+ print("最適値:", state.best_score)
41
+ print("最適パラメータ:", state.best_params)
42
+ ```
43
+
44
+ ## 主な用途
45
+
46
+ - 機械学習モデルのハイパーパラメータ最適化
47
+ - 実験・シミュレーションのパラメータ探索
48
+ - その他ブラックボックス関数の最適化
@@ -0,0 +1,16 @@
1
+ [project]
2
+ name = "LightningBO"
3
+ version = "0.1.0"
4
+ description = "Bayesian optimization library"
5
+ requires-python = ">=3.12"
6
+
7
+ license = "Apache-2.0"
8
+ dependencies = [
9
+ "jax",
10
+ "jaxlib",
11
+ "optax",
12
+ "numpy",
13
+ "absl-py",
14
+ "pytest",
15
+ "matplotlib"
16
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,62 @@
1
+ """Tests for the Bayesian core functionality including GP operations and acquisition functions."""
2
+
3
+ import jax
4
+ import jax.numpy as jnp
5
+ from absl.testing import absltest, parameterized
6
+
7
+ from LightningBO.bayesian_core import (
8
+ GPParams,
9
+ Integer,
10
+ Continuous,
11
+ cov,
12
+ exp_quadratic,
13
+ expected_improvement,
14
+ gaussian_process,
15
+ lower_confidence_bounds,
16
+ marginal_likelihood,
17
+ predict,
18
+ probability_improvement,
19
+ upper_confidence_bounds,
20
+ )
21
+
22
+ class BayesianCoreDomainTest(parameterized.TestCase):
23
+ """Test suite for Bayesian domain classes (Continuous, Integer)."""
24
+
25
+ @parameterized.parameters(
26
+ (0.0, 1.0),
27
+ (-5.5, 5.5),
28
+ )
29
+ def test_continuous_transform_clips_within_bounds(self, lower, upper):
30
+ """Test that Continuous domain clips values within bounds."""
31
+ domain = Continuous(lower, upper)
32
+ x = jnp.array([lower - 1, (lower + upper) / 2, upper + 1])
33
+ out = domain.transform(x)
34
+ self.assertTrue(jnp.all(out >= lower) and jnp.all(out <= upper))
35
+
36
+ def test_continuous_sample_within_bounds(self):
37
+ """Test that Continuous domain samples are within bounds."""
38
+ domain = Continuous(0.0, 1.0)
39
+ key = jax.random.PRNGKey(0)
40
+ shape = (100,)
41
+ samples = domain.sample(key, shape)
42
+ self.assertEqual(samples.shape, shape)
43
+ self.assertTrue(jnp.all(samples >= 0.0) and jnp.all(samples <= 1.0))
44
+
45
+ @parameterized.parameters(
46
+ (0, 5),
47
+ )
48
+ def test_integer_transform_clips_within_bounds(self, lower, upper):
49
+ domain = Integer(lower, upper)
50
+ x = jnp.array([lower - 1, lower, upper, upper + 1])
51
+ out = domain.transform(x)
52
+ self.assertTrue(jnp.all(out >= lower) and jnp.all(out <= upper))
53
+
54
+ def test_integer_sample_within_bounds(self):
55
+ domain = Integer(0, 5)
56
+ key = jax.random.PRNGKey(0)
57
+ shape = (100,)
58
+ samples = domain.sample(key, shape)
59
+ self.assertEqual(samples.shape, shape)
60
+ self.assertTrue(jnp.all(samples >= 0) and jnp.all(samples <= 5))
61
+
62
+ # ...他のテスト...
@@ -0,0 +1,48 @@
1
+ """Tests for Bayesian optimization functionality."""
2
+
3
+ import jax
4
+ import jax.numpy as jnp
5
+ import numpy as np
6
+ from absl.testing import absltest
7
+
8
+ import LightningBO.bayesian_core as bc
9
+ import LightningBO.bayesian_optimization as bo
10
+
11
+ KEY = jax.random.PRNGKey(42)
12
+ SEED = 42
13
+
14
+ class BayesianOptimizationTest(absltest.TestCase):
15
+ """Test suite for Bayesian optimization functionality."""
16
+
17
+ def test_1D_optim(self):
18
+ """Test 1D optimization with sin function."""
19
+ def f(x):
20
+ return jnp.sin(x) - 0.1 * x**2
21
+
22
+ TARGET = np.max(f(np.linspace(-2, 2, 1000)))
23
+ domain = {"x": bc.Continuous(-2, 2)}
24
+ opt = bo.Optimizer(domain=domain, maximize=True)
25
+ params = {"x": [0.0, 1.0, 2.0]}
26
+ ys = [f(x) for x in params["x"]]
27
+ opt_state = opt.init(ys, params)
28
+ self.assertEqual(opt_state.best_score, np.max(ys))
29
+ self.assertEqual(opt_state.best_params["x"], params["x"][np.argmax(ys)])
30
+ self.assertEqual(type(opt_state), bo.OptimizerState)
31
+ self.assertEqual(type(opt_state.gp_params), bc.GPParams)
32
+ self.assertEqual(opt_state.params["x"].shape, (10,))
33
+ self.assertEqual(opt_state.ys.shape, (10,))
34
+ self.assertEqual(opt_state.mask.shape, (10,))
35
+ self.assertTrue(np.allclose(opt_state.params["x"][:3], params["x"]))
36
+ self.assertTrue(np.allclose(opt_state.ys[:3], ys))
37
+ self.assertTrue(np.allclose(opt_state.mask[:3], [True, True, True]))
38
+ self.assertTrue(np.allclose(opt_state.mask[3:], [False] * 7))
39
+ key = jax.random.PRNGKey(SEED)
40
+ sample_fn = jax.jit(opt.sample)
41
+ for step in range(100):
42
+ key = jax.random.fold_in(key, step)
43
+ new_params = sample_fn(key, opt_state)
44
+ y = f(**new_params)
45
+ opt_state = opt.fit(opt_state, y, new_params)
46
+ if jnp.allclose(opt_state.best_score, TARGET, atol=1e-03):
47
+ break
48
+ # ...他のテスト...