From b6b1103629d1667ea6360ea6a806ccc622c30a13 Mon Sep 17 00:00:00 2001 From: TimRoith Date: Thu, 25 Jul 2024 15:53:45 +0200 Subject: [PATCH] added success eval, changed normal sampler and modularized cbo --- cbx/dynamics/cbo.py | 40 +++----- cbx/dynamics/cbo_memory.py | 62 +++++------- cbx/dynamics/cbs.py | 29 ++---- cbx/dynamics/pdyn.py | 27 +++--- cbx/dynamics/polarcbo.py | 32 +++++- cbx/noise.py | 39 +++++--- cbx/objectives.py | 18 +++- cbx/utils/resampling.py | 28 ++++-- cbx/utils/success.py | 59 +++++++++++ cbx/utils/torch_utils.py | 6 +- docs/conf.py | 5 +- docs/examples/JOSS/JOSS_Example.py | 12 +-- docs/examples/low_level.ipynb | 45 ++++++--- docs/examples/nns/mnist.ipynb | 76 +++------------ docs/examples/onedim_example.ipynb | 7 +- docs/examples/success_evaluation.ipynb | 129 +++++++++++++++++++++++++ docs/userguide/examples.rst | 3 +- tests/dynamics/test_cbo.py | 14 ++- tests/dynamics/test_pdyn.py | 5 +- tests/dynamics/test_polarcbo.py | 6 +- 20 files changed, 426 insertions(+), 216 deletions(-) create mode 100644 cbx/utils/success.py create mode 100644 docs/examples/success_evaluation.ipynb diff --git a/cbx/dynamics/cbo.py b/cbx/dynamics/cbo.py index f2859ea..89a8570 100644 --- a/cbx/dynamics/cbo.py +++ b/cbx/dynamics/cbo.py @@ -1,5 +1,8 @@ from .pdyn import CBXDynamic + +def cbo_update(drift, lamda, dt, sigma, noise): + return -lamda * dt * drift + sigma * noise #%% CBO class CBO(CBXDynamic): r"""Consensus-based optimization (CBO) class @@ -35,30 +38,17 @@ class CBO(CBXDynamic): def __init__(self, f, **kwargs) -> None: super().__init__(f, **kwargs) - - def inner_step(self,) -> None: - r"""Performs one step of the CBO algorithm. - - Parameters - ---------- - None - - Returns - ------- - None - - """ - # update, consensus point, drift and energy - self.consensus, energy = self.compute_consensus() - self.energy[self.consensus_idx] = energy - self.drift = self.x[self.particle_idx] - self.consensus - + def cbo_step(self,): + # compute consensus, sets self.energy and self.consensus + self.compute_consensus() + # update drift and apply drift correction + self.drift = self.correction(self.x[self.particle_idx] - self.consensus) + # perform cbo update step + self.x[self.particle_idx] += cbo_update( + self.drift, self.lamda, self.dt, + self.sigma, self.noise() + ) - # compute noise - self.s = self.sigma * self.noise() + inner_step = cbo_step - # update particle positions - self.x[self.particle_idx] = ( - self.x[self.particle_idx] - - self.correction(self.lamda * self.dt * self.drift) + - self.s) \ No newline at end of file + \ No newline at end of file diff --git a/cbx/dynamics/cbo_memory.py b/cbx/dynamics/cbo_memory.py index f0641eb..b1133cb 100644 --- a/cbx/dynamics/cbo_memory.py +++ b/cbx/dynamics/cbo_memory.py @@ -2,10 +2,10 @@ from typing import Union #from scipy.special import logsumexp -from .pdyn import CBXDynamic +from .cbo import CBO, cbo_update #%% CBO_Memory -class CBOMemory(CBXDynamic): +class CBOMemory(CBO): r"""Consensus-based optimization with memory effects (CBOMemory) class This class implements the CBO algorithm with memory effects as described in [1]_ and [2]_. The algorithm @@ -64,7 +64,8 @@ def __init__(self, self.sigma_memory = sigma_memory self.energy = self.f(self.x) - self.num_f_eval += np.ones(self.M, dtype=int) * self.N # update number of function evaluations + self.num_f_eval += np.ones(self.M, dtype=int) * self.N # update number of function evaluations + self.ergexp = tuple([Ellipsis] + [None for _ in range(self.x.ndim-2)]) def pre_step(self,): # save old positions @@ -74,6 +75,13 @@ def pre_step(self,): # set new batch indices self.set_batch_idx() + def memory_step(self,): + # add memory step, first define new drift + self.drift = self.x[self.particle_idx] - self.y[self.particle_idx] + self.x[self.particle_idx] += cbo_update( + self.drift, self.lamda_memory, self.dt, + self.sigma_memory, self.noise() + ) def inner_step(self,) -> None: r"""Performs one step of the CBOMemory algorithm. @@ -87,45 +95,23 @@ def inner_step(self,) -> None: None """ - - mind = self.consensus_idx - ind = self.particle_idx - # first update - self.consensus = self.compute_consensus(self.y[mind], self.energy[mind]) - # compute consensus and memory drift - consensus_drift = self.x[ind] - self.consensus - memory_drift = self.x[ind] - self.y[ind] - - # perform noise steps - # **NOTE**: noise always uses the ``drift`` attribute of the dynamic. - # We first use the actual drift here and - # then the memory difference - self.drift = consensus_drift - self.s_consensus = self.sigma * self.noise() - self.drift = memory_drift - self.s_memory = self.sigma_memory * self.noise() - - # dynamcis update - # momentaneous positions of particles - self.x[ind] = ( - self.x[ind] - - self.correction(self.lamda * self.dt * consensus_drift) - - self.lamda_memory * self.dt * memory_drift + - self.s_consensus + - self.s_memory) + # first perform regular cbo step + self.cbo_step() + self.memory_step() # evaluation of objective function on all particles - energy_new = self.f(self.x[ind]) - self.num_f_eval += np.ones(self.M, dtype=int) * self.x[ind].shape[-2] # update number of function evaluations + energy_new = self.eval_f(self.x[self.particle_idx]) - # historical best positions of particles - energy_expand = tuple([Ellipsis] + [None for _ in range(self.x.ndim-2)]) - self.y[ind] = self.y[ind] + ((self.energy>energy_new)[energy_expand]) * (self.x[ind] - self.y[ind]) + # historical best positions of particles + self.y[self.particle_idx] += ( + ((self.energy>energy_new)[self.ergexp]) * + (self.x[self.particle_idx] - self.y[self.particle_idx]) + ) self.energy = np.minimum(self.energy, energy_new) - def compute_consensus(self, x_batch, energy) -> None: + def compute_consensus(self,) -> None: r"""Updates the weighted mean of the particles. Parameters @@ -137,8 +123,10 @@ def compute_consensus(self, x_batch, energy) -> None: None """ - c, _ = self._compute_consensus(energy, self.x[self.consensus_idx], self.alpha[self.active_runs_idx, :]) - return c + self.consensus, _ = self._compute_consensus( + self.energy, self.y[self.consensus_idx], + self.alpha[self.active_runs_idx, :] + ) def update_best_cur_particle(self,) -> None: self.f_min = self.energy.min(axis=-1) diff --git a/cbx/dynamics/cbs.py b/cbx/dynamics/cbs.py index 5c92e90..4a6707f 100644 --- a/cbx/dynamics/cbs.py +++ b/cbx/dynamics/cbs.py @@ -1,40 +1,31 @@ import warnings -#%% -import numpy as np -from .pdyn import CBXDynamic +from .cbo import CBO from ..scheduler import scheduler #%% -class CBS(CBXDynamic): +class CBS(CBO): def __init__(self, f, mode='sampling', noise='covariance', M=1, track_args=None, **kwargs): track_args = track_args if track_args is not None else{'names':[]} super().__init__(f, track_args=track_args, noise=noise, M=M, **kwargs) + self.sigma = 1. if self.batched: raise NotImplementedError('Batched mode not implemented for CBS!') if self.x.ndim > 3: raise NotImplementedError('Multi dimensional domains not implemented for CBS! The particle should have the dimension M x N x d, where d is an integer!') - - self.exp_dt = np.exp(-self.dt) - if mode == 'sampling': - self.lamda = 1/(1 + self.alpha) - elif mode == 'optimization': - self.lamda = 1 - else: - raise NotImplementedError("Invalid mode") - + if noise not in ['covariance', 'sampling']: raise warnings.warn('For CBS usually covariance or sampling noise is used!', stacklevel=2) + self.noise_callable.mode = mode - def inner_step(self,): - self.consensus, energy = self.compute_consensus() - self.energy = energy - self.drift = self.x - self.consensus - self.update_covariance() - self.x = self.consensus + self.exp_dt * self.drift + self.noise() + # def inner_step(self,): + # self.consensus, energy = self.compute_consensus() + # self.energy = energy + # self.drift = self.x - self.consensus + # self.x = self.consensus + self.exp_dt * self.drift + self.noise() def run(self, sched = 'default'): if self.verbosity > 0: diff --git a/cbx/dynamics/pdyn.py b/cbx/dynamics/pdyn.py index 08ba98f..cc6ffcb 100644 --- a/cbx/dynamics/pdyn.py +++ b/cbx/dynamics/pdyn.py @@ -1,7 +1,7 @@ #%% from ..noise import get_noise from ..correction import get_correction -from ..scheduler import scheduler, multiply +from ..scheduler import scheduler, multiply, effective_sample_size from ..utils.termination import max_it_term from ..utils.history import track_x, track_energy, track_update_norm, track_consensus, track_drift, track_drift_mean from ..utils.particle_init import init_particles @@ -112,8 +112,8 @@ class ParticleDynamic: A callable that copies an array. The default is ``np.copy``. norm : Callable A callable that computes the norm of an array. The default is ``np.linalg.norm``. - normal : Callable - A callable that generates an array of random numbers that are distributed according to a normal distribution. The default is ``np.random.normal``. + sampler : Callable + A callable that generates an array of random numbers. The default is ``np.random.normal``. verbosity : int, optional The verbosity level. The default is 1. @@ -134,7 +134,7 @@ def __init__( verbosity: int = 1, copy: Callable = None, norm: Callable = None, - normal: Callable = None, + sampler: Callable = None, post_process: Callable = None, ) -> None: @@ -143,7 +143,8 @@ def __init__( # set utilities self.copy = copy if copy is not None else np.copy self.norm = norm if norm is not None else np.linalg.norm - self.normal = normal if normal is not None else np.random.normal + rng = np.random.default_rng(12345) + self.sampler = sampler if sampler is not None else rng.standard_normal # init particles self.init_x(x, M, N, d, x_min, x_max) @@ -219,7 +220,7 @@ def check_f_dims(self, check=True) -> None: None """ if check: # check if f returns correct shape - x = self.normal(0., 1., self.x.shape) + x = self.sampler(size=self.x.shape) if self.f(x).shape != (self.M,self.N): raise ValueError("The given objective function does not return the correct shape!") self.num_f_eval += self.N * np.ones((self.M,), dtype=int) # number of function evaluations @@ -340,6 +341,8 @@ def optimize(self, sched = scheduler([]) elif sched == 'default': sched = self.default_sched() + elif sched == 'effective': + sched = effective_sample_size() else: self.sched = sched @@ -535,7 +538,7 @@ def compute_mat_sqrt(A): """ B, V = np.linalg.eigh(A) B = np.maximum(B,0.) - return V@(np.sqrt(B)[...,None]*V.transpose(0,2,1)) + return V@(np.sqrt(B)[...,None]*np.moveaxis(V, -1, -2)) class compute_consensus_default: def __init__(self, check_coeffs = False): @@ -829,7 +832,7 @@ def set_noise(self, noise) -> None: """ # set noise model if isinstance(noise, str): - self.noise_callable = get_noise(noise, norm=self.norm, sampler=self.normal) + self.noise_callable = get_noise(noise, self) elif callable(noise): self.noise_callable = noise else: @@ -920,7 +923,9 @@ def compute_consensus(self,) -> None: # evaluation of objective function on batch energy = self.eval_f(self.x[self.consensus_idx]) # update energy - return self._compute_consensus(energy, self.x[self.consensus_idx], self.alpha[self.active_runs_idx, :]) - - + self.consensus, energy = self._compute_consensus( + energy, self.x[self.consensus_idx], + self.alpha[self.active_runs_idx, :] + ) + self.energy[self.consensus_idx] = energy \ No newline at end of file diff --git a/cbx/dynamics/polarcbo.py b/cbx/dynamics/polarcbo.py index 33d6a51..9dbaa45 100644 --- a/cbx/dynamics/polarcbo.py +++ b/cbx/dynamics/polarcbo.py @@ -3,6 +3,7 @@ from numpy.typing import ArrayLike from scipy.special import logsumexp +from .pdyn import compute_mat_sqrt from .cbo import CBO #%% Kernel for PolarCBO @@ -238,6 +239,33 @@ def kernel_factor(self, ): def compute_consensus(self,): x = self.x[self.consensus_idx] energy = self.eval_f(x) - neg_log_eval = self.kernel.neg_log(x[:,None,...], x[:,:,None,...]) - return self._compute_consensus(energy, x, neg_log_eval, alpha = self.alpha[self.active_runs_idx, :, None], kernel_factor = self.kernel_factor()) + self.neg_log_eval = self.kernel.neg_log(x[:,None,...], x[:,:,None,...]) + + + + self.consensus, energy = self._compute_consensus( + energy, x, self.neg_log_eval, + alpha = self.alpha[self.active_runs_idx, :, None], + kernel_factor = self.kernel_factor() + ) + self.energy[self.consensus_idx] = energy + + def update_covariance(self,): + r"""Update the covariance matrix :math:`\mathsf{C}(x_i)` of the noise model + + Parameters + ---------- + None + + Returns + ------- + None. + + """ + weights = - self.kernel_factor() * self.neg_log_eval - self.alpha * self.energy + coeffs = np.exp(weights - logsumexp(weights, axis=(-1,), keepdims=True)) + + D = self.drift[...,None] * self.drift[...,None,:] + D = np.sum(D * coeffs[..., None, None], axis = -3) + self.Cov_sqrt = compute_mat_sqrt(D) \ No newline at end of file diff --git a/cbx/noise.py b/cbx/noise.py index 90ecd02..b535db1 100644 --- a/cbx/noise.py +++ b/cbx/noise.py @@ -6,15 +6,13 @@ from numpy.random import normal import numpy as np -def get_noise(name: str, - norm: Callable = None, - sampler: Callable = None): +def get_noise(name: str, dyn): if name == 'isotropic': - return isotropic_noise(norm=norm, sampler=sampler) + return isotropic_noise(norm=dyn.norm, sampler=dyn.sampler) elif name == 'anisotropic': - return anisotropic_noise(norm=norm, sampler=sampler) + return anisotropic_noise(norm=dyn.norm, sampler=dyn.sampler) elif name == 'covariance' or name == 'sampling': - return covariance_noise(norm=norm, sampler=sampler) + return covariance_noise(norm=dyn.norm, sampler=dyn.sampler) else: raise NotImplementedError('Noise model {} not implemented'.format(name)) @@ -101,8 +99,8 @@ def sample(self, drift) -> ArrayLike: Only the norm of the drift is used for the noise. Therefore, the noise vector is scaled with the same factor in each dimension, which motivates the name **isotropic**. ''' - z = self.sampler(0, 1, size=(drift.shape)) - return z * self.norm(drift, axis=-1, keepdims=True) + z = self.sampler(size = drift.shape) + return z * self.norm(drift.reshape(*drift.shape[:2],-1), axis=-1).reshape(drift.shape[:2] + (drift.ndim-2)*(1,)) @@ -155,7 +153,7 @@ def sample(self, drift: ArrayLike) -> ArrayLike: which motivates the name **anisotropic**. """ - return self.sampler(0, 1, size=drift.shape) * drift + return self.sampler(size = drift.shape) * drift class covariance_noise(noise): r""" @@ -172,12 +170,22 @@ class covariance_noise(noise): def __init__(self, norm: Callable = None, - sampler: Callable = None): + sampler: Callable = None, + mode = 'sampling'): super().__init__(norm = norm, sampler = sampler) + self.mode = mode def __call__(self, dyn) -> ArrayLike: - factor = np.sqrt((1/dyn.lamda) * (1 - np.exp(-dyn.dt)**2))[(...,) + (None,) * (dyn.x.ndim - 2)] + dyn.update_covariance() + #factor = np.sqrt((1/dyn.lamda) * (1 - np.exp(-dyn.dt)**2))[(...,) + (None,) * (dyn.x.ndim - 2)] + factor = np.sqrt((2*dyn.dt)/self.lamda(dyn))[(...,) + (None,) * (dyn.x.ndim - 2)] return factor * self.sample(dyn.drift, dyn.Cov_sqrt) + + def lamda(self, dyn): + if self.mode == 'sampling': + return 1/(1 + dyn.alpha) + else: + return 1 def sample(self, drift:ArrayLike, Cov_sqrt:ArrayLike) -> ArrayLike: r""" @@ -202,7 +210,7 @@ def sample(self, drift:ArrayLike, Cov_sqrt:ArrayLike) -> ArrayLike: """ - z = self.sampler(0, 1, size = drift.shape) + z = self.sampler(size = drift.shape) return self.apply_cov_sqrt(Cov_sqrt, z) def apply_cov_sqrt(self, Cov_sqrt: ArrayLike, z:ArrayLike) -> ArrayLike: @@ -217,4 +225,9 @@ def apply_cov_sqrt(self, Cov_sqrt: ArrayLike, z:ArrayLike) -> ArrayLike: Returns: ArrayLike: The output of the matrix-vector product. """ - return (Cov_sqrt@z.transpose(0,2,1)).transpose(0,2,1) \ No newline at end of file + if Cov_sqrt.ndim == z.ndim: + return np.einsum('kij,klj->kli', Cov_sqrt, z) + elif Cov_sqrt.ndim == z.ndim + 1: + return np.einsum('klij,klj->kli', Cov_sqrt, z) + else: + raise RuntimeError('Shape mismatch between Cov_sqrt and sampled vector!') \ No newline at end of file diff --git a/cbx/objectives.py b/cbx/objectives.py index f9863b5..27ce391 100644 --- a/cbx/objectives.py +++ b/cbx/objectives.py @@ -8,6 +8,7 @@ """ import numpy as np +from scipy.stats import multivariate_normal from .utils.objective_handling import cbx_objective #%% @@ -370,8 +371,7 @@ def __init__(self, b=0., c=0.): self.minima = np.array([[self.b, self.b]]) def apply(self, x): - return (1/x.shape[-1]) * np.sum((x - self.b)**2 - \ - 10*np.cos(2*np.pi*(x - self.b)) + 10, axis=-1) + self.c + return (1/x.shape[-1]) * np.sum((x - self.b)**2 - 10*np.cos(2*np.pi*(x - self.b)) + 10, axis=-1) + self.c class Rastrigin_multimodal(cbx_objective): @@ -641,6 +641,20 @@ def apply(self, x): return ret +class Multimodal(cbx_objective): + def __init__(self, means=None, covs=None): + super().__init__() + self.means = [np.zeros((2,))] if means is None else means + self.covs = [np.eye(2)] if covs is None else covs + self.mns = [multivariate_normal(mean=m, cov=c) for m,c in zip(self.means, self.covs)] + + def apply(self, x): + res = 0 + for mn in self.mns: + res += mn.pdf(x) + return -np.log(res) + + class Bukin6(cbx_objective): r"""Bukin's function 6 diff --git a/cbx/utils/resampling.py b/cbx/utils/resampling.py index 2134fbf..fc1037c 100644 --- a/cbx/utils/resampling.py +++ b/cbx/utils/resampling.py @@ -2,7 +2,7 @@ from typing import Callable, List def apply_resampling_default(dyn, idx): - z = dyn.normal(0, 1., size=(len(idx), dyn.N, *dyn.d)) + z = dyn.sampler(size=(len(idx), dyn.N, *dyn.d)) dyn.x[idx, ...] += dyn.sigma * np.sqrt(dyn.dt) * z class resampling: @@ -22,10 +22,9 @@ class resampling: The function that should be performed on a given dynamic for selected indices. This function has to have the signature apply(dyn,idx). """ - def __init__(self, resamplings: List[Callable], M: int, apply:Callable = None): + def __init__(self, resamplings: List[Callable], apply:Callable = None): self.resamplings = resamplings - self.M = M - self.num_resampling = np.zeros(M) + self.num_resampling = None self.apply = apply if apply is not None else apply_resampling_default def __call__(self, dyn): @@ -41,12 +40,17 @@ def __call__(self, dyn): ------- None """ + self.check_num_resamplings(dyn.M) idx = np.unique(np.concatenate([r(dyn) for r in self.resamplings])) if len(idx)>0: self.apply(dyn, idx) self.num_resampling[idx] += 1 if dyn.verbosity > 0: print('Resampled in runs ' + str(idx)) + + def check_num_resamplings(self, M): + if self.num_resampling is None: + self.num_resampling = np.zeros(shape=(M)) class ensemble_update_resampling: """ @@ -88,13 +92,12 @@ class loss_update_resampling: The indices of the runs to resample as a numpy array. """ - def __init__(self, M:int, wait_thresh:int = 5): - self.M = M - self.best_energy = float('inf') * np.ones((self.M,)) - self.wait = np.zeros((self.M,), dtype=int) + def __init__(self, wait_thresh:int = 5): self.wait_thresh = wait_thresh + self.initalized = False - def __call__(self,dyn): + def __call__(self, dyn): + self.check_energy_wait(dyn.M) self.wait += 1 u_idx = self.best_energy > dyn.best_energy self.wait[u_idx] = 0 @@ -102,3 +105,10 @@ def __call__(self,dyn): idx = np.where(self.wait >= self.wait_thresh)[0] self.wait = np.mod(self.wait, self.wait_thresh) return idx + + def check_energy_wait(self, M): + if not self.initalized: + self.best_energy = float('inf') * np.ones((M,)) + self.wait = np.zeros((M,), dtype=int) + self.initalized = True + diff --git a/cbx/utils/success.py b/cbx/utils/success.py new file mode 100644 index 0000000..6708b17 --- /dev/null +++ b/cbx/utils/success.py @@ -0,0 +1,59 @@ +import numpy as np + +def dist_to_min_success(x, x_true, tol=0.25, p=float('inf')): + norm_diff = np.linalg.norm((x_true[None,...] - x.squeeze()).reshape(x.shape[0], -1), + axis=-1, ord = p) + idx = np.where(norm_diff < tol)[0] + + return {'num': len(idx), + 'rate': len(idx)/x.shape[0], + 'normdiff':norm_diff, + 'idx':idx} + +class dist_to_min: + def __init__(self, x_true, tol=0.25, p=float('inf')): + self.x_true = x_true + self.tol = tol + self.p = p + + def __call__(self, dyn): + return dist_to_min_success(dyn.best_particle, self.x_true, + tol=self.tol, p=self.p) + +def value_success(x, f, thresh=0.1): + vals = f(x) + idx = np.where(vals < thresh)[0] + + return {'num': len(idx), 'rate': len(idx)/x.shape[0], 'idx':idx} + +class value_thresh: + def __init__(self, thresh=0.1): + self.thresh = thresh + + def __call__(self, dyn): + return value_success(dyn.best_particle[:,None,...], dyn.f, thresh=self.thresh) + +class evaluation: + def __init__(self, criteria=None, verbosity = 1): + self.criteria = criteria + self.verbosity = 1 + + def __call__(self, dyn): + idx = np.arange(dyn.M) + for crit in self.criteria: + result = crit(dyn) + idx = np.intersect1d(result['idx'], idx) + + res = {'num': len(idx), 'rate': len(idx)/dyn.M, 'idx':idx} + self.print_result(res) + return res + + def print_result(self, res): + if self.verbosity < 1: + return + print('------------------------------') + print('Results of success evaluation:') + print('Success Rate: ' + str(res['rate'])) + print('Succesful runs: ' + str(res['num'])) + print('Succesful idx: ' + str(res['idx'])) + \ No newline at end of file diff --git a/cbx/utils/torch_utils.py b/cbx/utils/torch_utils.py index 1ef93b9..8ce833d 100644 --- a/cbx/utils/torch_utils.py +++ b/cbx/utils/torch_utils.py @@ -43,9 +43,9 @@ def compute_polar_consensus_torch(energy, x, neg_log_eval, alpha = 1., kernel_fa return c, energy.detach().cpu().numpy() @requires_torch -def normal_torch(device): - def _normal_torch(mean, std, size): - return torch.normal(mean, std, size).to(device) +def standard_normal_torch(device): + def _normal_torch(size=None): + return torch.randn(size=size).to(device) return _normal_torch @requires_torch diff --git a/docs/conf.py b/docs/conf.py index 7da253a..f655711 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,11 +18,11 @@ # -- Project information ----------------------------------------------------- project = 'CBX' -copyright = '2023, Tim Roith' +copyright = '2024, Tim Roith' author = 'Tim Roith' # The full version, including alpha/beta/rc tags -release = 'v0.1.1' +release = 'v0.1.6' # -- General configuration --------------------------------------------------- @@ -102,6 +102,7 @@ 'examples/custom_noise': '_static/cbx-logo.png', 'examples/low_level': '_static/cbx-logo.png', 'examples/sampling': '_static/cbx-logo.png', + 'examples/success_evaluation': '_static/cbx-logo.png', } def setup(app): diff --git a/docs/examples/JOSS/JOSS_Example.py b/docs/examples/JOSS/JOSS_Example.py index 7a7f70e..d5548b2 100644 --- a/docs/examples/JOSS/JOSS_Example.py +++ b/docs/examples/JOSS/JOSS_Example.py @@ -45,7 +45,7 @@ fig, ax = plt.subplots(1,1) cf = contour_2D(f, ax=ax, num_pts=1000, - x_min=-4, x_max =4., cmap='coolwarm', + x_min=-4, x_max =4., cmap='binary', levels=50) cbar = plt.colorbar(cf) cbar.set_label('Objective value', rotation=270, labelpad=10) @@ -78,11 +78,11 @@ cmap = color_map, c = [i for i in range(sc_idx)], s=52,zorder=3) -plt.legend(handles=sc.legend_elements()[0], - labels=['t = ' + str(i * kwargs['track_args']['save_int']*dyn.dt) - for i in range(sc_idx)], - loc='lower right', - title='Particle evolution') +# plt.legend(handles=sc.legend_elements()[0], +# labels=['t = ' + str(i * kwargs['track_args']['save_int']*dyn.dt) +# for i in range(sc_idx)], +# loc='lower right', +# title='Particle evolution') #%% save = True if save: diff --git a/docs/examples/low_level.ipynb b/docs/examples/low_level.ipynb index 0645cb8..eb81021 100644 --- a/docs/examples/low_level.ipynb +++ b/docs/examples/low_level.ipynb @@ -10,17 +10,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "1ce71f14-a97a-475a-b027-324bc5864f09", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "import numpy as np\n", "import cbx as cbx\n", "from cbx.dynamics import CBO, CBS\n", - "from cbx.objectives import Rastrigin\n", + "from cbx.objectives import Rastrigin, Ackley\n", "from cbx.utils.objective_handling import cbx_objective_fh\n", "from cbx.scheduler import effective_sample_size, scheduler\n", "from cbx.plotting import PlotDynamic, PlotDynamicHistory" @@ -28,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "dc027d54-35fc-44c8-bbc1-f6f8655a3863", "metadata": {}, "outputs": [], @@ -42,7 +51,7 @@ " 'batch_size':200,\n", " 'batch_partial': False},\n", " 'd': 2,\n", - " 'max_it': 500,\n", + " 'max_it': 20,\n", " 'N': 50,\n", " 'M': 3,\n", " 'track_args': {'names':\n", @@ -63,10 +72,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "b33670f7-14a3-4176-b014-1545b249dbf8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABLwAAAHDCAYAAAAukwOJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAD1aklEQVR4nOzdd3yV5f3/8dd9Rk72JAECSdgbZMoQEHCguNCvWq0VV9Wfs1qtlg5nW2qpFWstripqtVqtOEFEBVFAAUEZyh6BQBjZ8+SM+/dHTCSQQELOzvv5eOSh3LnPuT/3fU7uc1/vc13XbZimaSIiIiIiIiIiIhIhLMEuQERERERERERExJcUeImIiIiIiIiISERR4CUiIiIiIiIiIhFFgZeIiIiIiIiIiEQUBV4iIiIiIiIiIhJRFHiJiIiIiIiIiEhEUeAlIiIiIiIiIiIRRYGXiIiIiIiIiIhEFAVeIiIiIiIiIiISURR4iUhYWLx4MYZhsHjx4mCXIiIiIhJS3G4399xzD1lZWVgsFqZOndri55gzZw6GYbBq1SrfF3iYtnpNd/XVV9OlS5dgl9EiEyZMYMKECcEuQ+SEKfASaYMau6CZN28eDzzwQPCK+sE///lP5syZE+wyjqugoICZM2cyfvx40tPTSU5OZtSoUbz++uuNru90Orn33nvJzMwkJiaGkSNHsnDhwgBXLSIiEl7qrlma+vnyyy+DXWJIeP7555k5cyYXX3wxL774InfeeWeT64bLtdaJaun+3XnnnQwdOpTU1FRiY2Pp27cvDzzwAOXl5Uet68vrucrKSh544IGgB3/fffcdDzzwADt37gxqHSL+YAt2ASISGubNm8eTTz4Z9NDrn//8J+3atePqq69usHz8+PFUVVURFRUVnMKOsHz5cn77298yZcoUfve732Gz2fjf//7HZZddxnfffceDDz7YYP2rr76aN998kzvuuIOePXsyZ84cpkyZwqJFixg7dmyQ9kJERCQ8PPTQQ3Tt2vWo5T169AhCNaHn008/pVOnTjz22GPHXbepa61I0dL9W7lyJePGjeOaa64hOjqaNWvW8Oc//5mPP/6YJUuWYLH82EekNddzzz77LF6vt/7flZWV9deLwexFVXfdOmHChKN6oH300UfBKUrERxR4iYjfmKZJdXU1MTExrX4ui8VCdHS0D6ryjf79+7NlyxZycnLql918882cfvrpPPLII9xzzz3ExcUBsGLFCl577TVmzpzJ3XffDcC0adMYMGAA99xzD8uWLQvKPoiIiISLs88+m+HDhwe7DCoqKuo/30PJgQMHSE5ODnYZYemLL744aln37t25++67WbFiBaNGjQJafz1nt9t9X3wjfPkeDZUvmkVOlIY0ighXX301Tz75JECDYQJ1vF4vs2bNon///kRHR9O+fXtuvPFGioqKGjxPly5dOPfcc1mwYAHDhw8nJiaGp59+GoAXXniBSZMmkZGRgcPhoF+/fsyePfuox2/YsIHPPvusvoa6b7yamu/hjTfeYNiwYcTExNCuXTt+9rOfkZeXd9T+xcfHk5eXx9SpU4mPjyc9PZ27774bj8fTYN19+/axceNGXC7XMY9Z165dG4Rddcdu6tSpOJ1Otm/fXr/8zTffxGq1csMNN9Qvi46O5rrrrmP58uXs3r37mNsSERGRY9u5cyeGYfDXv/6VZ555hu7du+NwOBgxYgQrV648av2NGzdy8cUXk5qaSnR0NMOHD+fdd99tsE7dcMrPPvuMm2++mYyMDDp37lz/+yeffJJu3boRExPDySefzOeff95gzqPy8nLi4uL4xS9+cdT29+zZg9VqZcaMGcfcr4qKCu666y6ysrJwOBz07t2bv/71r5im2WC/Fy1axIYNG+qvn5oaJnesa606TqeTX/7yl6SnpxMXF8eFF17IwYMHj3qu+fPnM27cOOLi4khISOCcc85hw4YNx9yfpnz++edccsklZGdn43A4yMrK4s4776SqqqrBevn5+VxzzTV07twZh8NBx44dueCCC+qH4zVn/5qjrqdTcXFx/bLWXs8dPofXzp07SU9PB+DBBx+sr/XwkRatfY/u2rWLm2++md69exMTE0NaWhqXXHJJg6GLc+bM4ZJLLgFg4sSJR71/GpvD68CBA1x33XW0b9+e6OhoTjrpJF588cUG67Tk7/F4r6lIa6iHl4hw4403snfvXhYuXMjLL7/c6O/nzJnDNddcw+23386OHTv4xz/+wZo1a1i6dGmDb6w2bdrE5Zdfzo033sj1119P7969AZg9ezb9+/fn/PPPx2az8d5773HzzTfj9Xq55ZZbAJg1axa33XYb8fHx/Pa3vwWgffv2TdZdV9OIESOYMWMG+/fv5/HHH2fp0qWsWbOmwTedHo+HyZMnM3LkSP7617/y8ccf8+ijj9K9e3duuumm+vWmT5/Oiy++yI4dO05oYtH8/HwA2rVrV79szZo19OrVi8TExAbrnnzyyQB88803ZGVltXhbIiIibUVJSQmHDh1qsMwwDNLS0hose/XVVykrK+PGG2/EMAz+8pe/cNFFF7F9+/b665UNGzZwyimn0KlTJ379618TFxfHf//7X6ZOncr//vc/LrzwwgbPefPNN5Oens59991HRUUFUHtdc+uttzJu3DjuvPNOdu7cydSpU0lJSakPHOLj47nwwgt5/fXX+dvf/obVaq1/zv/85z+YpskVV1zR5D6bpsn555/PokWLuO666xg8eDALFizgV7/6FXl5eTz22GOkp6fz8ssv88c//pHy8vL6AK1v376NPmdzrrVuu+02UlJSuP/++9m5cyezZs3i1ltvbTBP6csvv8xVV13F5MmTeeSRR6isrGT27NmMHTuWNWvWtPga6o033qCyspKbbrqJtLQ0VqxYwRNPPMGePXt444036tf7v//7PzZs2MBtt91Gly5dOHDgAAsXLiQ3N5cuXbq0+Fqyjtvtpri4mJqaGtavX8/vfvc7EhIS6q/VwLfXc+np6cyePZubbrqJCy+8kIsuugiAQYMGAb55j65cuZJly5Zx2WWX0blzZ3bu3Mns2bOZMGEC3333HbGxsYwfP57bb7+dv//97/zmN7+pf9809f6pqqpiwoQJbN26lVtvvZWuXbvyxhtvcPXVV1NcXHxUuNucv8fjvaYirWKKSJvzwgsvmIC5cuXK+mW33HKL2dgp4fPPPzcB85VXXmmw/MMPPzxqeU5OjgmYH3744VHPU1lZedSyyZMnm926dWuwrH///uapp5561LqLFi0yAXPRokWmaZpmTU2NmZGRYQ4YMMCsqqqqX+/99983AfO+++6rX3bVVVeZgPnQQw81eM4hQ4aYw4YNa7Csbt0dO3YcVcPxFBQUmBkZGea4ceOO2qdJkyYdtf6GDRtMwHzqqadavC0REZG2oO6apbEfh8NRv96OHTtMwExLSzMLCwvrl7/zzjsmYL733nv1y0477TRz4MCBZnV1df0yr9drjhkzxuzZs+dR2x47dqzpdrvrlzudTjMtLc0cMWKE6XK56pfPmTPHBBpcxyxYsMAEzPnz5zfYr0GDBjV6vXO4t99+2wTMP/zhDw2WX3zxxaZhGObWrVvrl5166qlm//79j/l8dZq61qrb39NPP930er31y++8807TarWaxcXFpmmaZllZmZmcnGxef/31DR6fn59vJiUlHbX8SEde05lm49eJM2bMMA3DMHft2mWapmkWFRWZgDlz5swT2r9jWb58eYP3Vu/evRvUV/e8rbmeu+qqq8ycnJz6fx88eNAEzPvvv/+odVv7HjXNxo9p3X6+9NJL9cveeOONo16POqeeemqDYzlr1iwTMP/973/XL6upqTFHjx5txsfHm6WlpaZpNv/vsbmvqciJ0pBGETmmN954g6SkJM444wwOHTpU/zNs2DDi4+NZtGhRg/W7du3K5MmTj3qew+fxqvuW9tRTT2X79u2UlJS0uK5Vq1Zx4MABbr755gZze51zzjn06dOHDz744KjH/L//9/8a/HvcuHENhh5Cba8x0zRb/I2S1+vliiuuoLi4mCeeeKLB76qqqnA4HEc9pq7uI7vri4iISENPPvkkCxcubPAzf/78o9b7yU9+QkpKSv2/x40bB1D/eV9YWMinn37KpZdeSllZWf11TUFBAZMnT2bLli1HTY1w/fXXN+idtWrVKgoKCrj++uux2X4cMHPFFVc02DbA6aefTmZmJq+88kr9svXr17N27Vp+9rOfHXOf582bh9Vq5fbbb2+w/K677sI0zUb33xduuOGGBlNbjBs3Do/Hw65duwBYuHAhxcXFXH755Q2uDa1WKyNHjjzq2rA5Dr9OrKio4NChQ4wZMwbTNFmzZk39OlFRUSxevPioaTVaq1+/fixcuJC33367fh7WI+/SGKjrOV+8R6HhMXW5XBQUFNCjRw+Sk5NZvXr1CdU2b948OnTowOWXX16/zG63c/vtt1NeXs5nn33WYP3j/T368zUVAQ1pFJHj2LJlCyUlJWRkZDT6+wMHDjT4d2N3UAJYunQp999/P8uXL6eysrLB70pKSkhKSmpRXXUXXXVDJg/Xp0+foyYgjY6Orp8roU5KSorPPlxvu+02PvzwQ1566SVOOumkBr+LiYnB6XQe9Zjq6ur634uIiEjTTj755GZNWp+dnd3g33WN7brP+61bt2KaJr///e/5/e9/3+hzHDhwgE6dOtX/+8hrm7prkCPvEGmz2Y76wsxisXDFFVcwe/ZsKisriY2N5ZVXXiE6Orp+7qSm7Nq1i8zMTBISEhosrxtuVleHrx3vGG7ZsgWASZMmNfr4I4f8NUdubi733Xcf77777lHXZnVfjDocDh555BHuuusu2rdvz6hRozj33HOZNm0aHTp0aPE2j6z59NNPB+CCCy7g1Vdf5YILLmD16tX113WBup7zxXsUagO4GTNm8MILL5CXl1c/7xtwQl82Q+17rmfPng3uXAlNvyeP917y52sqAgq8ROQ4vF4vGRkZDb6ZPNyRIVJjH/bbtm3jtNNOo0+fPvztb38jKyuLqKgo5s2bx2OPPdbgFs3+cuS3Xr704IMP8s9//pM///nPXHnllUf9vmPHjkd9Ewe1E+QDZGZm+q02ERGRtqSpz/u6xn7dNcfdd9/daI90ODrIam2QMW3aNGbOnMnbb7/N5Zdfzquvvsq5557b4i/7AqW5x/Dll19uNJQ4vNdbc3g8Hs444wwKCwu599576dOnD3FxceTl5XH11Vc3uE684447OO+883j77bdZsGABv//975kxYwaffvopQ4YMadF2j+Wiiy7iyiuv5LXXXqsPvAJ1Peer9+htt93GCy+8wB133MHo0aNJSkrCMAwuu+yygFx7w/HfSxC411TaJgVeIgLQoOv64bp3787HH3/MKaeccsIXfO+99x5Op5N33323wTc9jXV5b6qOI9XdIXHTpk1HfcO4adOmo+6g6C9PPvkkDzzwAHfccQf33ntvo+sMHjyYRYsWUVpa2uBbz6+++qr+9yIiIuJ/3bp1A2qHYdX16GmpumuMrVu3MnHixPrlbrebnTt31k88XmfAgAEMGTKEV155hc6dO5Obm3vU9AdNbefjjz+mrKysQS+vjRs3NqijpZp7rdWU7t27A5CRkXHCx/Bw69atY/Pmzbz44otMmzatfvnChQub3P5dd93FXXfdxZYtWxg8eDCPPvoo//73v4HW7x/U3qnS6/U26Anl6+u5pur0xXsUau8qedVVV/Hoo4/WL6uurm5w58lj1dGYnJwc1q5di9frbdDLq7XvyeO9piInSnN4iQgAcXFxAEd9CF566aV4PB4efvjhox5Td0eb46n7dufIrtQvvPBCo3U05zmHDx9ORkYGTz31VIPu5fPnz+f777/nnHPOOe5zNGbfvn1s3LgRl8t13HVff/11br/9dq644gr+9re/NbnexRdfjMfj4Zlnnqlf5nQ6eeGFFxg5cqTu0CgiIhIgGRkZTJgwgaeffrq+Z87hDh48eNznGD58OGlpaTz77LO43e765a+88kqTUyVceeWVfPTRR8yaNYu0tDTOPvvs425nypQpeDwe/vGPfzRY/thjj2EYRrOeozHNvdZqyuTJk0lMTORPf/pTo9dLzTmGh2vsOtE0TR5//PEG61VWVtYPH6zTvXt3EhISGlwLtmT/iouLG92H5557DqDBMFpfX8/FxsbW13A4X7xHofa4Hn5MAZ544gk8Hk+DZU21ARozZcoU8vPzG9yx0+1288QTTxAfH8+pp57arNrqNPc1FTlR6uElIgAMGzYMgNtvv53JkydjtVq57LLLOPXUU7nxxhuZMWMG33zzDWeeeSZ2u50tW7bwxhtv8Pjjj3PxxRcf87nPPPNMoqKiOO+887jxxhspLy/n2WefJSMj46gP8mHDhjF79mz+8Ic/0KNHDzIyMhqdI8Jut/PII49wzTXXcOqpp3L55Zezf/9+Hn/8cbp06cKdd955Qsdh+vTpvPjii+zYseOYE9evWLGCadOmkZaWxmmnnXbUkM8xY8bUf0M3cuRILrnkEqZPn86BAwfo0aMHL774Ijt37uRf//rXCdUpIiLSlsyfP7++F8nhDv+8ba4nn3ySsWPHMnDgQK6//nq6devG/v37Wb58OXv27OHbb7895uOjoqJ44IEHuO2225g0aRKXXnopO3fuZM6cOXTv3r3RHjM//elPueeee5g7dy433XQTdrv9uHWed955TJw4kd/+9rfs3LmTk046iY8++oh33nmHO+64o76nVUs191qrKYmJicyePZsrr7ySoUOHctlll5Genk5ubi4ffPABp5xyylEh3bH06dOH7t27c/fdd5OXl0diYiL/+9//jgoPN2/ezGmnncall15Kv379sNlszJ07l/3793PZZZed0P4tXryY22+/nYsvvpiePXtSU1PD559/zltvvcXw4cMb3FjA19dzMTEx9OvXj9dff51evXqRmprKgAEDGDBgQKvfowDnnnsuL7/8MklJSfTr14/ly5fz8ccfk5aW1mC9wYMHY7VaeeSRRygpKcHhcDBp0qRG5++94YYbePrpp7n66qv5+uuv6dKlC2+++SZLly5l1qxZR803dzzNfU1FTlhQ7g0pIkFVdwvjlStX1i9zu93mbbfdZqanp5uGYZhHnh6eeeYZc9iwYWZMTIyZkJBgDhw40LznnnvMvXv31q+Tk5NjnnPOOY1u89133zUHDRpkRkdHm126dDEfeeQR8/nnnzcBc8eOHfXr5efnm+ecc46ZkJDQ4Nbejd3C2jRN8/XXXzeHDBliOhwOMzU11bziiivMPXv2NFjnqquuMuPi4o6q6f777z9qP6+66qqjamrMsW6TDpgvvPBCg/WrqqrMu+++2+zQoYPpcDjMESNGmB9++OExtyEiItLWNffzdseOHSZgzpw586jnAMz777+/wbJt27aZ06ZNMzt06GDa7XazU6dO5rnnnmu++eabR2378Oulw/397383c3JyTIfDYZ588snm0qVLzWHDhplnnXVWo+tPmTLFBMxly5Y1e//LysrMO++808zMzDTtdrvZs2dPc+bMmabX622w3qmnnmr279+/Wc/Z1LVWU/vb1DXYokWLzMmTJ5tJSUlmdHS02b17d/Pqq682V61adcztN/Z83333nXn66aeb8fHxZrt27czrr7/e/Pbbbxu8xocOHTJvueUWs0+fPmZcXJyZlJRkjhw50vzvf//brP1rzNatW81p06aZ3bp1M2NiYszo6Gizf//+5v3332+Wl5cftX5rrueuuuoqMycnp8GyZcuWmcOGDTOjoqKOep+29j1aVFRkXnPNNWa7du3M+Ph4c/LkyebGjRvNnJwc86qrrmqw7rPPPmt269bNtFqtDV6bU0899ajjt3///vrnjYqKMgcOHHjUdW9z/x6b+5qKnCjDNI/o5ygiIiIiIiIt4vV6SU9P56KLLuLZZ5896vcXXngh69atY+vWrUGoTkSk7dEcXiIiIiIiIi1QXV191PxIL730EoWFhUyYMOGo9fft28cHH3zQ6N2cRUTEP9TDS0REREREpAUWL17MnXfeySWXXEJaWhqrV6/mX//6F3379uXrr78mKioKgB07drB06VKee+45Vq5cybZt2+jQoUOQqxcRaRs0ab2IiIiIiEgLdOnShaysLP7+979TWFhIamoq06ZN489//nN92AXw2Wefcc0115Cdnc2LL76osEtEJID82sNr9uzZzJ49m507dwLQv39/7rvvvhO+ha6IiIiIiIiIiMjx+DXweu+997BarfTs2RPTNHnxxReZOXMma9asoX///v7arIiIiIiIiIiItGEBn8MrNTWVmTNnct111wVysyIiIiIiIiIi0kYEbA4vj8fDG2+8QUVFBaNH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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "from IPython import display\n", @@ -81,26 +101,27 @@ "sched = effective_sample_size(maximum=1e12)\n", "plotter = PlotDynamic(dyn, ax=ax[0],\n", " objective_args={'x_min':-3, 'x_max':3},\n", + " particle_args={'color':'xkcd:white'},\n", " plot_consensus=True,\n", " plot_drift=True)\n", "plotter.init_plot()\n", "while not dyn.terminate():\n", + " display.clear_output(wait=True)\n", " dyn.step()\n", " sched.update(dyn)\n", " # update energy plot\n", " ax[1].clear()\n", " ax[1].plot([e[0] for e in dyn.history['energy'][-30:]])\n", " ax[1].set_title('Energy of the last 30 iterations') \n", - " ax[0].set_title('Iteration: ' + str(dyn.it)) \n", + " ax[0].set_title('Iteration: ' + str(dyn.it))\n", + "\n", " plotter.update(wait=0.2)\n", - " display.display(fig)\n", - " display.clear_output(wait=True)\n", - " " + " display.display(fig)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "68fc7a07-cb08-4177-9328-1354ecf0150d", "metadata": {}, "outputs": [], diff --git a/docs/examples/nns/mnist.ipynb b/docs/examples/nns/mnist.ipynb index bd80d6d..d369023 100644 --- a/docs/examples/nns/mnist.ipynb +++ b/docs/examples/nns/mnist.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "1b37649d-ae72-49dc-855e-5109b13d9433", "metadata": {}, "outputs": [], @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "97fe038d-11a6-4114-bd6a-a09e83a96c7c", "metadata": {}, "outputs": [], @@ -68,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "165cef19-2b94-4490-a007-714df29ba5c1", "metadata": {}, "outputs": [], @@ -88,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "34def998-0a7e-43f9-bc9c-d890f5a0b7d5", "metadata": {}, "outputs": [], @@ -117,12 +117,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "705a76af-42bb-4ca6-9f8c-f1f6d9dbad6a", "metadata": {}, "outputs": [], "source": [ - "from cbx.utils.torch_utils import flatten_parameters, get_param_properties, eval_losses, norm_torch, compute_consensus_torch, normal_torch, eval_acc, effective_sample_size\n", + "from cbx.utils.torch_utils import flatten_parameters, get_param_properties, eval_losses, norm_torch, compute_consensus_torch, standard_normal_torch, eval_acc, effective_sample_size\n", "N = 50\n", "models = [model_class(sizes=[784,100,10]) for _ in range(N)]\n", "model = models[0]\n", @@ -147,7 +147,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "cac7a15f-8445-4eb3-ad00-bfdd15e201d8", "metadata": {}, "outputs": [], @@ -189,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "b40c7cf2-4295-4bf3-8328-e47ba7ba16d5", "metadata": {}, "outputs": [], @@ -225,19 +225,18 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "9e32f6f7-7937-4ceb-a043-93eb8e85f9b1", "metadata": {}, "outputs": [], "source": [ "f = objective(train_loader, N, device, model, pprop)\n", - "resampling = rsmp.resampling([rsmp.loss_update_resampling(M=1, wait_thresh=40)], 1)\n", - "noise = anisotropic_noise(norm = norm_torch, sampler = normal_torch(device))\n", + "resampling = rsmp.resampling([rsmp.loss_update_resampling(wait_thresh=40)])\n", "\n", - "dyn = CBO(f, f_dim='3D', x=w[None,...], noise=noise,\n", + "dyn = CBO(f, f_dim='3D', x=w[None,...], noise='anisotropic',\n", " norm=norm_torch,\n", " copy=torch.clone,\n", - " normal=normal_torch(device),\n", + " sampler=standard_normal_torch(device),\n", " compute_consensus=compute_consensus_torch,\n", " post_process = lambda dyn: resampling(dyn),\n", " **kwargs)\n", @@ -258,60 +257,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "7ed4c926-f239-42b0-93c2-fcf48171adaa", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "------------------------------\n", - "Epoch: 1\n", - "Accuracy: 0.5325999855995178\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 2\n", - "Accuracy: 0.6539000272750854\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 3\n", - "Accuracy: 0.6708999872207642\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 4\n", - "Accuracy: 0.7269999980926514\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 5\n", - "Accuracy: 0.737500011920929\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 6\n", - "Accuracy: 0.7563999891281128\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 7\n", - "Accuracy: 0.7552000284194946\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 8\n", - "Accuracy: 0.784500002861023\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 9\n", - "Accuracy: 0.7868000268936157\n", - "------------------------------\n", - "------------------------------\n", - "Epoch: 10\n", - "Accuracy: 0.779699981212616\n", - "------------------------------\n" - ] - } - ], + "outputs": [], "source": [ "e = 0\n", "while f.epochs < 10:\n", diff --git a/docs/examples/onedim_example.ipynb b/docs/examples/onedim_example.ipynb index 1ba66ed..0db5ff6 100644 --- a/docs/examples/onedim_example.ipynb +++ b/docs/examples/onedim_example.ipynb @@ -22,7 +22,7 @@ "#%% define the objective function and solve\n", "def f(x):\n", " return np.abs(np.sin(x)) + np.abs(x)**(3/4) * (np.sin(x)+1) - 0.5*(np.abs(x)>1)\n", - "dyn = cbx.dynamics.CBO(f, d=1, verbosity=0)\n", + "dyn = cbx.dynamics.CBO(f, d=1, verbosity=0, max_it=10)\n", "x = dyn.optimize()\n", "\n", "#%% visualize\n", @@ -30,8 +30,11 @@ "s = np.linspace(-4,4,1000)\n", "plt.plot(s, f(s), linewidth=3, color='xkcd:sky', label='Objective', zorder=-1)\n", "plt.scatter(x, f(x), label='Solution', c='green', s=50, marker='x')\n", + "plt.scatter(dyn.x, f(dyn.x), label='Solution', c='xkcd:red', s=30, marker='.')\n", "plt.xlim([-4,4])\n", - "plt.legend()" + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.savefig('1D')" ] } ], diff --git a/docs/examples/success_evaluation.ipynb b/docs/examples/success_evaluation.ipynb new file mode 100644 index 0000000..0c6ee4b --- /dev/null +++ b/docs/examples/success_evaluation.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "093c184f-c3d4-4dd8-83b1-517cad4acf50", + "metadata": {}, + "source": [ + "# Performance Evaluation\n", + "\n", + "In this notebook we explore the performance evaluation tools of ```cbxpy```. These are provided in the module ```cbx.utils.success```. For a test problem, we consider the Rastrigin function in dimension ```d=20```. We employ ```N=40``` particles and perform ```M=100``` runs." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8e52898b-d5d9-40d2-ad82-c816430f926f", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "import cbx\n", + "import cbx.dynamics as dyns\n", + "import cbx.utils.success as success\n", + "import numpy as np\n", + "\n", + "f = cbx.objectives.Rastrigin()\n", + "M = 100\n", + "N = 40\n", + "d = 20\n", + "\n", + "x = np.random.uniform(-3,3,(M,N,d))\n", + "kwargs = {'x':x, 'sigma':10.1, 'verbosity':0, 'max_it':5000, 'noise':'anisotropic', 'alpha':30, 'dt':0.01, 'f_dim':'3D'}\n", + "x_true = np.zeros((x.shape[-1],))" + ] + }, + { + "cell_type": "markdown", + "id": "f14c54bc-a412-4e37-93f4-4cee8d2fe243", + "metadata": {}, + "source": [ + "## Perform optimization\n", + "\n", + "Using the keywords from above, we define a dynamic and run the optimization." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e83d010-2d5f-42e0-9bd8-99ab619df68d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "dyn = dyns.CBO(f,**kwargs)\n", + "dyn.optimize();" + ] + }, + { + "cell_type": "markdown", + "id": "550d27a5-fa91-40e8-96e7-059b7c9205da", + "metadata": {}, + "source": [ + "## Performance evaluation\n", + "\n", + "To evaluate the performance, we use the ```evaluation``` class, which accepts a list of performance criteria. Each criterion should have a call function that accepts a dynamic as the input and outputs a dictionary containing the following key-value pairs:\n", + "\n", + "* ```rate```: the success rate,\n", + "* ```num```: the absolute number of successful runs,\n", + "* ```idx```: the indices of the successful runs.\n", + "\n", + "We can directly apply this to our dynamic to get a result. Here we use the criterion ```dist_to_min```, which needs the true minimum as the input. This criterion will be satisfied if\n", + "\n", + "$$\\|x_{\\text{true}} - x\\|_p < \\texttt{tol},$$\n", + "\n", + "where the tolerance and $p$ of the norm can also be specified." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bfca0359-1c09-4c17-8ac5-f36539b2b542", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------------------------\n", + "Results of success evaluation:\n", + "Success Rate: 1.0\n", + "Succesful runs: 100\n", + "Succesful idx: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n", + " 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47\n", + " 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71\n", + " 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95\n", + " 96 97 98 99]\n" + ] + } + ], + "source": [ + "seval = success.evaluation(criteria = [success.dist_to_min(x_true, tol=0.25, p=float('inf'))])\n", + "seval(dyn);" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/userguide/examples.rst b/docs/userguide/examples.rst index 5520a2b..a7d61d5 100644 --- a/docs/userguide/examples.rst +++ b/docs/userguide/examples.rst @@ -9,4 +9,5 @@ Examples ../examples/polarcbo.ipynb ../examples/onedim_example.ipynb ../examples/low_level.ipynb - ../examples/sampling.ipynb \ No newline at end of file + ../examples/sampling.ipynb + ../examples/success_evaluation.ipynb \ No newline at end of file diff --git a/tests/dynamics/test_cbo.py b/tests/dynamics/test_cbo.py index 2031823..dbe0698 100644 --- a/tests/dynamics/test_cbo.py +++ b/tests/dynamics/test_cbo.py @@ -91,13 +91,20 @@ def noise(dyn): def test_step_batched_partial(self, dynamic, f): '''Test if partial batched step is correctly performed''' x = np.random.uniform(-1,1,(3,5,7)) + class noise: + def __call__(self, dyn): + self.s = dyn.sampler(size=dyn.drift.shape) * dyn.drift + return self.s + N = noise() - dyn = dynamic(f, x=x, batch_args={'size':2, 'partial':True}) + dyn = dynamic(f, x=x, noise=N, batch_args={'size':2, 'partial':True}) dyn.step() ind = dyn.particle_idx[1] for j in range(x.shape[0]): for i in range(ind.shape[1]): - x[j, ind[j,i], :] = x[j, ind[j,i], :] - dyn.lamda * dyn.dt * (x[j, ind[j,i], :] - dyn.consensus[j, 0, :]) + dyn.s[j,i,:] + x[j, ind[j,i], :] = x[j, ind[j,i], :] -\ + dyn.lamda * dyn.dt * (x[j, ind[j,i], :] - dyn.consensus[j, 0, :])\ + + dyn.sigma * N.s[j,i,:] assert np.allclose(dyn.x, x) @@ -111,11 +118,12 @@ def g(x): return torch.sum(x, dim=-1) def norm_torch(x, axis, **kwargs): return torch.linalg.norm(x, dim=axis, **kwargs) + dyn = dynamic(g, x=x, max_it = 2, norm=norm_torch, copy=torch.clone, - normal=torch.normal, + sampler=torch.randn, f_dim='3D') dyn.optimize() assert dyn.x.shape == (6,5,7) diff --git a/tests/dynamics/test_pdyn.py b/tests/dynamics/test_pdyn.py index 5ec849e..5f3f190 100644 --- a/tests/dynamics/test_pdyn.py +++ b/tests/dynamics/test_pdyn.py @@ -77,7 +77,7 @@ def norm_torch(x, axis, **kwargs): max_it=2, norm=norm_torch, copy=torch.clone, - normal=torch.normal + sampler=torch.randn ) dyn.optimize() assert dyn.x.shape == (6,5,7) @@ -125,8 +125,7 @@ def test_update_cov(self, f, dynamic): d=4 N=12 dyn = dynamic(f, M=M, d=d, N=N) - dyn.consensus, energy = dyn.compute_consensus() - dyn.energy = energy + dyn.compute_consensus() dyn.drift = dyn.x - dyn.consensus dyn.update_covariance() diff --git a/tests/dynamics/test_polarcbo.py b/tests/dynamics/test_polarcbo.py index 0f3632e..830e293 100644 --- a/tests/dynamics/test_polarcbo.py +++ b/tests/dynamics/test_polarcbo.py @@ -67,11 +67,9 @@ def test_consensus_value(self, f, dynamic): nom += w * dyn.x[m,nn,:] denom += w c[m,n,:] = nom / denom - cc, _ = dyn.compute_consensus() - print(cc) - print(c) + dyn.compute_consensus() assert np.allclose(dd, d) - assert np.allclose(cc, c) + assert np.allclose(dyn.consensus, c) \ No newline at end of file