From c711fa0cbed71f399ae6bd1ec4b54422945320c8 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Tue, 2 Jan 2024 17:47:46 +0100 Subject: [PATCH 01/12] new constructor for stop window --- .../simulated_bifurcation_optimizer.py | 10 +- .../optimizer/stop_window.py | 110 +++++++++++------- tests/optimizer/test_stop_window.py | 50 ++------ 3 files changed, 84 insertions(+), 86 deletions(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 4acee27d..939d9a24 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -128,13 +128,9 @@ def __init_quadratic_scale_parameter(self, matrix: torch.Tensor): def __init_window(self, matrix: torch.Tensor, use_window: bool) -> None: self.window = StopWindow( - matrix, - self.agents, - self.convergence_threshold, - matrix.dtype, - matrix.device, - (self.verbose and use_window), + self.convergence_threshold, (self.verbose and use_window) ) + self.window.reset(matrix, self.agents) def __init_symplectic_integrator(self, matrix: torch.Tensor) -> None: self.symplectic_integrator = SymplecticIntegrator( @@ -294,6 +290,6 @@ def get_final_spins(self, spins: torch.Tensor, use_window: bool) -> torch.Tensor if use_window: if not self.window.has_bifurcated_spins(): warnings.warn(ConvergenceWarning(), stacklevel=2) - return self.window.get_bifurcated_spins(spins) + return self.window.get_final_spins(spins) else: return spins diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/stop_window.py index 1f8ffa61..d9325601 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/stop_window.py @@ -12,32 +12,40 @@ class StopWindow: Allows an early stopping of the iterations and saves computation time. """ - def __init__( - self, - ising_tensor: torch.Tensor, - n_agents: int, - convergence_threshold: int, - dtype: torch.dtype, - device: Union[str, torch.device], - verbose: bool, - ) -> None: + def __init__(self, convergence_threshold: int, verbose: bool) -> None: + self.__init_convergence_threshold(convergence_threshold) + self.verbose = verbose + self.ising_tensor = None + self.n_agents = None + self.stability = None + self.newly_bifurcated = None + self.previously_bifurcated = None + self.bifurcated = None + self.stable_agents = None + self.energies = None + self.progress = None + self.current_spins = None + self.final_spins = None + self.shifted_agents_index = None + + def reset(self, ising_tensor: torch.Tensor, n_agents: int): self.ising_tensor = ising_tensor - self.n_spins = self.ising_tensor.shape[0] self.n_agents = n_agents - self.__init_convergence_threshold(convergence_threshold) - self.dtype = dtype - self.device = device - self.__init_tensors() - self.__init_energies() - self.final_spins = self.__init_spins() - self.progress = self.__init_progress_bar(verbose) + self.__init_tensors(ising_tensor.device) + self.__init_energies(ising_tensor.dtype, ising_tensor.device) + self.__init_progress_bar(self.verbose) + self.__init_current_and_final_spins( + ising_tensor.shape[0], n_agents, ising_tensor.dtype, ising_tensor.device + ) + self.shifted_agents_index = list(range(n_agents)) - @property - def shape(self) -> Tuple[int, int]: - return self.n_spins, self.n_agents + def __compute_energies(self, sampled_spins: torch.Tensor) -> torch.Tensor: + return torch.nn.functional.bilinear( + sampled_spins.t(), sampled_spins.t(), torch.unsqueeze(self.ising_tensor, 0) + ).reshape(self.n_agents) - def __init_progress_bar(self, verbose: bool) -> tqdm: - return tqdm( + def __init_progress_bar(self, verbose: bool) -> None: + self.progress = tqdm( total=self.n_agents, desc="🏁 Bifurcated agents", disable=not verbose, @@ -63,21 +71,31 @@ def __init_convergence_threshold(self, convergence_threshold: int) -> None: ) self.convergence_threshold = convergence_threshold - def __init_tensor(self, dtype: torch.dtype) -> torch.Tensor: - return torch.zeros(self.n_agents, device=self.device, dtype=dtype) + def __init_tensor(self, dtype: torch.dtype, device: torch.device) -> torch.Tensor: + return torch.zeros(self.n_agents, device=device, dtype=dtype) - def __init_energies(self) -> None: - self.energies = torch.tensor([float("inf") for _ in range(self.n_agents)]) + def __init_tensors(self, device: torch.device) -> None: + self.stability = self.__init_tensor(torch.int16, device) + self.newly_bifurcated = self.__init_tensor(torch.bool, device) + self.previously_bifurcated = self.__init_tensor(torch.bool, device) + self.bifurcated = self.__init_tensor(torch.bool, device) + self.stable_agents = self.__init_tensor(torch.bool, device) - def __init_tensors(self) -> None: - self.stability = self.__init_tensor(torch.int16) - self.newly_bifurcated = self.__init_tensor(torch.bool) - self.previously_bifurcated = self.__init_tensor(torch.bool) - self.bifurcated = self.__init_tensor(torch.bool) - self.stable_agents = self.__init_tensor(torch.bool) + def __init_energies(self, dtype: torch.dtype, device: torch.device) -> None: + self.energies = torch.tensor( + [float("inf") for _ in range(self.n_agents)], dtype=dtype, device=device + ) + + def __init_spins( + self, n_spins: int, n_agents: int, dtype: torch.dtype, device: torch.device + ) -> torch.Tensor: + return torch.zeros(n_spins, n_agents, dtype=dtype, device=device) - def __init_spins(self) -> torch.Tensor: - return torch.zeros(size=self.shape, dtype=self.dtype, device=self.device) + def __init_current_and_final_spins( + self, n_spins: int, n_agents: int, dtype: torch.dtype, device: torch.device + ): + self.current_spins = self.__init_spins(n_spins, n_agents, dtype, device) + self.final_spins = self.__init_spins(n_spins, n_agents, dtype, device) def __update_final_spins(self, sampled_spins) -> None: self.final_spins[:, self.newly_bifurcated] = sampled_spins[ @@ -108,9 +126,7 @@ def not_bifurcated(self) -> torch.Tensor: return torch.logical_not(self.bifurcated) def __compare_energies(self, sampled_spins: torch.Tensor) -> None: - energies = torch.nn.functional.bilinear( - sampled_spins.t(), sampled_spins.t(), torch.unsqueeze(self.ising_tensor, 0) - ).reshape(self.n_agents) + energies = self.__compute_energies(sampled_spins) torch.eq( energies, self.energies, @@ -136,7 +152,21 @@ def must_continue(self) -> bool: ).item() def has_bifurcated_spins(self) -> bool: - return torch.any(self.bifurcated).item() - - def get_bifurcated_spins(self, spins: torch.Tensor) -> torch.Tensor: + return torch.any(torch.not_equal(self.final_spins, 0)).item() + + def get_final_spins(self, spins: torch.Tensor) -> torch.Tensor: + """ + Returns the final spins of the window. If an agent did not converge, + the spins provided in input are returned instead. + + Parameters + ---------- + spins : torch.Tensor + Spins coming from the optimizer. + + Returns + ------- + torch.Tensor + Final spins. + """ return torch.where(self.bifurcated, self.final_spins, spins) diff --git a/tests/optimizer/test_stop_window.py b/tests/optimizer/test_stop_window.py index 25362cc2..e437a261 100644 --- a/tests/optimizer/test_stop_window.py +++ b/tests/optimizer/test_stop_window.py @@ -51,38 +51,16 @@ ] -def test_init_window(): - window = StopWindow( - TENSOR, - AGENTS, - CONVERGENCE_THRESHOLD, - dtype=torch.float32, - device="cpu", - verbose=False, - ) - assert window.n_spins == 3 - assert window.n_agents == 2 - assert window.convergence_threshold == 3 - assert window.shape == (3, 2) - assert torch.equal(window.final_spins, torch.zeros((3, 2))) - - def test_wrong_convergence_threshold_value(): with pytest.raises(TypeError): # noinspection PyTypeChecker - StopWindow( - TENSOR, AGENTS, 30.0, dtype=torch.float32, device="cpu", verbose=False - ) + StopWindow(30.0, verbose=False) with pytest.raises(ValueError): - StopWindow(TENSOR, AGENTS, 0, dtype=torch.float32, device="cpu", verbose=False) + StopWindow(0, verbose=False) with pytest.raises(ValueError): - StopWindow( - TENSOR, AGENTS, -42, dtype=torch.float32, device="cpu", verbose=False - ) + StopWindow(-42, verbose=False) with pytest.raises(ValueError): - StopWindow( - TENSOR, AGENTS, 2**15, dtype=torch.float32, device="cpu", verbose=False - ) + StopWindow(2**15, verbose=False) def test_use_scenario(): @@ -94,14 +72,8 @@ def test_use_scenario(): - agent 1 converges to an optimal vector from step 3; - agent 2 oscillates in the optimal space from step 2. """ - window = StopWindow( - TENSOR, - AGENTS, - CONVERGENCE_THRESHOLD, - dtype=torch.float32, - device="cpu", - verbose=False, - ) + window = StopWindow(CONVERGENCE_THRESHOLD, verbose=False) + window.reset(TENSOR, AGENTS) # Initial state assert torch.equal( @@ -113,7 +85,7 @@ def test_use_scenario(): window.update(SCENARIO[0]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_bifurcated_spins(SCENARIO[0]), SCENARIO[0]) + assert torch.equal(window.get_final_spins(SCENARIO[0]), SCENARIO[0]) assert torch.equal(window.energies, torch.tensor([2.0, 0.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) @@ -134,7 +106,7 @@ def test_use_scenario(): window.update(SCENARIO[1]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_bifurcated_spins(SCENARIO[1]), SCENARIO[1]) + assert torch.equal(window.get_final_spins(SCENARIO[1]), SCENARIO[1]) assert torch.equal(window.energies, torch.tensor([0.0, -6.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) @@ -155,7 +127,7 @@ def test_use_scenario(): window.update(SCENARIO[2]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_bifurcated_spins(SCENARIO[2]), SCENARIO[2]) + assert torch.equal(window.get_final_spins(SCENARIO[2]), SCENARIO[2]) assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 1], dtype=torch.int16)) @@ -176,7 +148,7 @@ def test_use_scenario(): window.update(SCENARIO[3]) assert window.must_continue() assert window.has_bifurcated_spins() - assert torch.equal(window.get_bifurcated_spins(SCENARIO[3]), SCENARIO[3]) + assert torch.equal(window.get_final_spins(SCENARIO[3]), SCENARIO[3]) assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) assert torch.equal( window.final_spins, @@ -206,7 +178,7 @@ def test_use_scenario(): assert not window.must_continue() assert window.has_bifurcated_spins() assert torch.equal( - window.get_bifurcated_spins(SCENARIO[4]), + window.get_final_spins(SCENARIO[4]), torch.tensor( [ [-1, -1], From 37e0de84d531b3a90ac29bc2caf7270b0b0e6888 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Tue, 2 Jan 2024 18:10:47 +0100 Subject: [PATCH 02/12] new definition of final spins --- .../simulated_bifurcation_optimizer.py | 5 ++++- .../optimizer/stop_window.py | 19 ++++++------------- tests/optimizer/test_stop_window.py | 6 +----- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 939d9a24..1b91846a 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -290,6 +290,9 @@ def get_final_spins(self, spins: torch.Tensor, use_window: bool) -> torch.Tensor if use_window: if not self.window.has_bifurcated_spins(): warnings.warn(ConvergenceWarning(), stacklevel=2) - return self.window.get_final_spins(spins) + final_spins = self.window.get_final_spins() + # window_stable_spins[:, torch.all(torch.eq(final_spins, 0), dim=0)] = spins + # return final_spins + return torch.where(self.window.bifurcated, final_spins, spins) else: return spins diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/stop_window.py index d9325601..6d803054 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/stop_window.py @@ -125,8 +125,7 @@ def changed_agents(self) -> torch.Tensor: def not_bifurcated(self) -> torch.Tensor: return torch.logical_not(self.bifurcated) - def __compare_energies(self, sampled_spins: torch.Tensor) -> None: - energies = self.__compute_energies(sampled_spins) + def __compare_energies(self, energies: torch.Tensor) -> None: torch.eq( energies, self.energies, @@ -138,7 +137,8 @@ def __get_number_newly_bifurcated_agents(self) -> int: return torch.count_nonzero(self.newly_bifurcated).item() def update(self, sampled_spins: torch.Tensor): - self.__compare_energies(sampled_spins) + energies = self.__compute_energies(sampled_spins) + self.__compare_energies(energies) self.__update_stability_streak() self.__update_bifurcated_spins() self.__set_newly_bifurcated_spins() @@ -154,19 +154,12 @@ def must_continue(self) -> bool: def has_bifurcated_spins(self) -> bool: return torch.any(torch.not_equal(self.final_spins, 0)).item() - def get_final_spins(self, spins: torch.Tensor) -> torch.Tensor: + def get_final_spins(self) -> torch.Tensor: """ - Returns the final spins of the window. If an agent did not converge, - the spins provided in input are returned instead. - - Parameters - ---------- - spins : torch.Tensor - Spins coming from the optimizer. + Returns the final spins of the window. Returns ------- torch.Tensor - Final spins. """ - return torch.where(self.bifurcated, self.final_spins, spins) + return self.final_spins.clone() diff --git a/tests/optimizer/test_stop_window.py b/tests/optimizer/test_stop_window.py index e437a261..0c41d533 100644 --- a/tests/optimizer/test_stop_window.py +++ b/tests/optimizer/test_stop_window.py @@ -85,7 +85,6 @@ def test_use_scenario(): window.update(SCENARIO[0]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_final_spins(SCENARIO[0]), SCENARIO[0]) assert torch.equal(window.energies, torch.tensor([2.0, 0.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) @@ -106,7 +105,6 @@ def test_use_scenario(): window.update(SCENARIO[1]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_final_spins(SCENARIO[1]), SCENARIO[1]) assert torch.equal(window.energies, torch.tensor([0.0, -6.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) @@ -127,7 +125,6 @@ def test_use_scenario(): window.update(SCENARIO[2]) assert window.must_continue() assert not window.has_bifurcated_spins() - assert torch.equal(window.get_final_spins(SCENARIO[2]), SCENARIO[2]) assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) assert torch.equal(window.final_spins, torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 1], dtype=torch.int16)) @@ -148,7 +145,6 @@ def test_use_scenario(): window.update(SCENARIO[3]) assert window.must_continue() assert window.has_bifurcated_spins() - assert torch.equal(window.get_final_spins(SCENARIO[3]), SCENARIO[3]) assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) assert torch.equal( window.final_spins, @@ -178,7 +174,7 @@ def test_use_scenario(): assert not window.must_continue() assert window.has_bifurcated_spins() assert torch.equal( - window.get_final_spins(SCENARIO[4]), + window.get_final_spins(), torch.tensor( [ [-1, -1], From 3e223abcacf7dc8c61f2b03114f6c07eba17d944 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Tue, 2 Jan 2024 19:02:42 +0100 Subject: [PATCH 03/12] remove converged agents from oscillators --- .../simulated_bifurcation_optimizer.py | 28 ++- .../optimizer/stop_window.py | 171 +++++++----------- tests/optimizer/test_stop_window.py | 115 ++---------- 3 files changed, 100 insertions(+), 214 deletions(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 1b91846a..6510053d 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -128,9 +128,11 @@ def __init_quadratic_scale_parameter(self, matrix: torch.Tensor): def __init_window(self, matrix: torch.Tensor, use_window: bool) -> None: self.window = StopWindow( - self.convergence_threshold, (self.verbose and use_window) + self.convergence_threshold, + matrix, + self.agents, + (self.verbose and use_window), ) - self.window.reset(matrix, self.agents) def __init_symplectic_integrator(self, matrix: torch.Tensor) -> None: self.symplectic_integrator = SymplecticIntegrator( @@ -146,7 +148,9 @@ def __step_update(self) -> None: def __check_stop(self, use_window: bool) -> None: if use_window and self.__do_sampling: - self.run = self.window.must_continue() + stored_spins = self.window.get_stored_spins() + all_agents_converged = torch.any(torch.eq(stored_spins, 0)).item() + self.run = all_agents_converged if not self.run: LOGGER.info("Optimizer stopped. Reason: all agents converged.") return @@ -204,7 +208,13 @@ def __symplectic_update( self.__step_update() if use_window and self.__do_sampling: sampled_spins = self.symplectic_integrator.sample_spins() - self.window.update(sampled_spins) + not_converged_agents = self.window.update(sampled_spins) + self.symplectic_integrator.momentum = ( + self.symplectic_integrator.momentum[:, not_converged_agents] + ) + self.symplectic_integrator.position = ( + self.symplectic_integrator.position[:, not_converged_agents] + ) self.__check_stop(use_window) @@ -288,11 +298,11 @@ def get_final_spins(self, spins: torch.Tensor, use_window: bool) -> torch.Tensor torch.Tensor """ if use_window: - if not self.window.has_bifurcated_spins(): + final_spins = self.window.get_stored_spins() + any_converged_agents = torch.any(torch.not_equal(final_spins, 0)).item() + if not any_converged_agents: warnings.warn(ConvergenceWarning(), stacklevel=2) - final_spins = self.window.get_final_spins() - # window_stable_spins[:, torch.all(torch.eq(final_spins, 0), dim=0)] = spins - # return final_spins - return torch.where(self.window.bifurcated, final_spins, spins) + final_spins[:, torch.all(torch.eq(final_spins, 0), dim=0)] = spins + return final_spins else: return spins diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/stop_window.py index 6d803054..5fa3db52 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/stop_window.py @@ -12,46 +12,45 @@ class StopWindow: Allows an early stopping of the iterations and saves computation time. """ - def __init__(self, convergence_threshold: int, verbose: bool) -> None: + def __init__( + self, + convergence_threshold: int, + ising_tensor: torch.Tensor, + n_agents: int, + verbose: bool, + ) -> None: self.__init_convergence_threshold(convergence_threshold) self.verbose = verbose - self.ising_tensor = None - self.n_agents = None - self.stability = None - self.newly_bifurcated = None - self.previously_bifurcated = None - self.bifurcated = None - self.stable_agents = None - self.energies = None - self.progress = None - self.current_spins = None - self.final_spins = None - self.shifted_agents_index = None - - def reset(self, ising_tensor: torch.Tensor, n_agents: int): self.ising_tensor = ising_tensor - self.n_agents = n_agents - self.__init_tensors(ising_tensor.device) - self.__init_energies(ising_tensor.dtype, ising_tensor.device) - self.__init_progress_bar(self.verbose) - self.__init_current_and_final_spins( - ising_tensor.shape[0], n_agents, ising_tensor.dtype, ising_tensor.device + self.stability = torch.zeros( + n_agents, dtype=torch.int16, device=ising_tensor.device + ) + self.energies = torch.tensor( + [float("inf") for _ in range(n_agents)], + dtype=ising_tensor.dtype, + device=ising_tensor.device, ) - self.shifted_agents_index = list(range(n_agents)) - - def __compute_energies(self, sampled_spins: torch.Tensor) -> torch.Tensor: - return torch.nn.functional.bilinear( - sampled_spins.t(), sampled_spins.t(), torch.unsqueeze(self.ising_tensor, 0) - ).reshape(self.n_agents) - - def __init_progress_bar(self, verbose: bool) -> None: self.progress = tqdm( - total=self.n_agents, + total=n_agents, desc="🏁 Bifurcated agents", - disable=not verbose, + disable=not self.verbose, smoothing=0, unit=" agents", ) + self.stored_spins = torch.zeros( + ising_tensor.shape[0], + n_agents, + dtype=ising_tensor.dtype, + device=ising_tensor.device, + ) + self.shifted_agents_indices = torch.tensor( + list(range(n_agents)), device=ising_tensor.device + ) + + def __compute_energies(self, sampled_spins: torch.Tensor) -> torch.Tensor: + return torch.nn.functional.bilinear( + sampled_spins.t(), sampled_spins.t(), torch.unsqueeze(self.ising_tensor, 0) + ).reshape(sampled_spins.shape[1]) def __init_convergence_threshold(self, convergence_threshold: int) -> None: if not isinstance(convergence_threshold, int): @@ -71,95 +70,47 @@ def __init_convergence_threshold(self, convergence_threshold: int) -> None: ) self.convergence_threshold = convergence_threshold - def __init_tensor(self, dtype: torch.dtype, device: torch.device) -> torch.Tensor: - return torch.zeros(self.n_agents, device=device, dtype=dtype) - - def __init_tensors(self, device: torch.device) -> None: - self.stability = self.__init_tensor(torch.int16, device) - self.newly_bifurcated = self.__init_tensor(torch.bool, device) - self.previously_bifurcated = self.__init_tensor(torch.bool, device) - self.bifurcated = self.__init_tensor(torch.bool, device) - self.stable_agents = self.__init_tensor(torch.bool, device) - - def __init_energies(self, dtype: torch.dtype, device: torch.device) -> None: - self.energies = torch.tensor( - [float("inf") for _ in range(self.n_agents)], dtype=dtype, device=device - ) - - def __init_spins( - self, n_spins: int, n_agents: int, dtype: torch.dtype, device: torch.device - ) -> torch.Tensor: - return torch.zeros(n_spins, n_agents, dtype=dtype, device=device) - - def __init_current_and_final_spins( - self, n_spins: int, n_agents: int, dtype: torch.dtype, device: torch.device - ): - self.current_spins = self.__init_spins(n_spins, n_agents, dtype, device) - self.final_spins = self.__init_spins(n_spins, n_agents, dtype, device) - - def __update_final_spins(self, sampled_spins) -> None: - self.final_spins[:, self.newly_bifurcated] = sampled_spins[ - :, self.newly_bifurcated - ] - - def __set_previously_bifurcated_spins(self) -> None: - self.previously_bifurcated = torch.clone(self.bifurcated) - - def __set_newly_bifurcated_spins(self) -> None: - torch.logical_xor( - self.bifurcated, self.previously_bifurcated, out=self.newly_bifurcated - ) - - def __update_bifurcated_spins(self) -> None: - torch.eq(self.stability, self.convergence_threshold - 1, out=self.bifurcated) - - def __update_stability_streak(self) -> None: - self.stability[torch.logical_and(self.stable_agents, self.not_bifurcated)] += 1 - self.stability[torch.logical_and(self.changed_agents, self.not_bifurcated)] = 0 - - @property - def changed_agents(self) -> torch.Tensor: - return torch.logical_not(self.stable_agents) + def update(self, sampled_spins: torch.Tensor) -> torch.Tensor: + """_summary_ - @property - def not_bifurcated(self) -> torch.Tensor: - return torch.logical_not(self.bifurcated) + Parameters + ---------- + sampled_spins : torch.Tensor + _description_ - def __compare_energies(self, energies: torch.Tensor) -> None: - torch.eq( - energies, - self.energies, - out=self.stable_agents, - ) + Returns + ------- + torch.Tensor + The agents that still have not converged. + """ + current_agents = self.energies.shape[0] + energies = self.__compute_energies(sampled_spins) + stable_agents = torch.eq(energies, self.energies) self.energies = energies + self.stability = torch.where( + stable_agents, self.stability + 1, torch.zeros(current_agents) + ) - def __get_number_newly_bifurcated_agents(self) -> int: - return torch.count_nonzero(self.newly_bifurcated).item() - - def update(self, sampled_spins: torch.Tensor): - energies = self.__compute_energies(sampled_spins) - self.__compare_energies(energies) - self.__update_stability_streak() - self.__update_bifurcated_spins() - self.__set_newly_bifurcated_spins() - self.__set_previously_bifurcated_spins() - self.__update_final_spins(sampled_spins) - self.progress.update(self.__get_number_newly_bifurcated_agents()) + converged_agents = torch.eq(self.stability, self.convergence_threshold - 1) + not_converged_agents = torch.logical_not(converged_agents) - def must_continue(self) -> bool: - return torch.any( - torch.lt(self.stability, self.convergence_threshold - 1) - ).item() + self.stored_spins[ + :, self.shifted_agents_indices[converged_agents] + ] = sampled_spins[:, converged_agents] - def has_bifurcated_spins(self) -> bool: - return torch.any(torch.not_equal(self.final_spins, 0)).item() + self.shifted_agents_indices = self.shifted_agents_indices[not_converged_agents] + self.energies = self.energies[not_converged_agents] + self.stability = self.stability[not_converged_agents] + new_agents = self.energies.shape[0] + self.progress.update(current_agents - new_agents) + return not_converged_agents - def get_final_spins(self) -> torch.Tensor: + def get_stored_spins(self) -> torch.Tensor: """ - Returns the final spins of the window. + Returns the converged spins stored in the window. Returns ------- torch.Tensor """ - return self.final_spins.clone() + return self.stored_spins.clone() diff --git a/tests/optimizer/test_stop_window.py b/tests/optimizer/test_stop_window.py index 0c41d533..22d4b6a7 100644 --- a/tests/optimizer/test_stop_window.py +++ b/tests/optimizer/test_stop_window.py @@ -40,11 +40,12 @@ ], dtype=torch.float32, ), + # 1 agents has converged and was removed from the oscillators torch.tensor( [ - [-1, 1], - [1, -1], - [-1, 1], + [-1], + [1], + [-1], ], dtype=torch.float32, ), @@ -54,13 +55,13 @@ def test_wrong_convergence_threshold_value(): with pytest.raises(TypeError): # noinspection PyTypeChecker - StopWindow(30.0, verbose=False) + StopWindow(30.0, TENSOR, AGENTS, verbose=False) with pytest.raises(ValueError): - StopWindow(0, verbose=False) + StopWindow(0, TENSOR, AGENTS, verbose=False) with pytest.raises(ValueError): - StopWindow(-42, verbose=False) + StopWindow(-42, TENSOR, AGENTS, verbose=False) with pytest.raises(ValueError): - StopWindow(2**15, verbose=False) + StopWindow(2**15, TENSOR, AGENTS, verbose=False) def test_use_scenario(): @@ -72,82 +73,36 @@ def test_use_scenario(): - agent 1 converges to an optimal vector from step 3; - agent 2 oscillates in the optimal space from step 2. """ - window = StopWindow(CONVERGENCE_THRESHOLD, verbose=False) - window.reset(TENSOR, AGENTS) + window = StopWindow(CONVERGENCE_THRESHOLD, TENSOR, AGENTS, verbose=False) # Initial state - assert torch.equal( - window.previously_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) + assert torch.equal(window.get_stored_spins(), torch.zeros(3, 2)) assert torch.all(torch.isinf(window.energies)) + assert torch.equal(window.stability, torch.zeros(2)) # First update window.update(SCENARIO[0]) - assert window.must_continue() - assert not window.has_bifurcated_spins() assert torch.equal(window.energies, torch.tensor([2.0, 0.0])) - assert torch.equal(window.final_spins, torch.zeros((3, 2))) + assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) - assert torch.equal( - window.newly_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.stable_agents, torch.tensor([False, False], dtype=torch.bool) - ) # Second update - assert torch.equal( - window.previously_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) window.update(SCENARIO[1]) - assert window.must_continue() - assert not window.has_bifurcated_spins() assert torch.equal(window.energies, torch.tensor([0.0, -6.0])) - assert torch.equal(window.final_spins, torch.zeros((3, 2))) + assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) - assert torch.equal( - window.newly_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.stable_agents, torch.tensor([False, False], dtype=torch.bool) - ) # Third update - assert torch.equal( - window.previously_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) window.update(SCENARIO[2]) - assert window.must_continue() - assert not window.has_bifurcated_spins() assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) - assert torch.equal(window.final_spins, torch.zeros((3, 2))) + assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) assert torch.equal(window.stability, torch.tensor([0, 1], dtype=torch.int16)) - assert torch.equal( - window.newly_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) - assert torch.equal( - window.stable_agents, torch.tensor([False, True], dtype=torch.bool) - ) # Fourth update - assert torch.equal( - window.previously_bifurcated, torch.tensor([False, False], dtype=torch.bool) - ) window.update(SCENARIO[3]) - assert window.must_continue() - assert window.has_bifurcated_spins() - assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) + assert torch.equal(window.energies, torch.tensor([-6.0])) assert torch.equal( - window.final_spins, + window.get_stored_spins(), torch.tensor( [ [0, -1], @@ -157,24 +112,13 @@ def test_use_scenario(): dtype=torch.float32, ), ) - assert torch.equal(window.stability, torch.tensor([1, 2], dtype=torch.float32)) - assert torch.equal( - window.newly_bifurcated, torch.tensor([False, True], dtype=torch.bool) - ) - assert torch.equal(window.bifurcated, torch.tensor([False, True], dtype=torch.bool)) - assert torch.equal( - window.stable_agents, torch.tensor([True, True], dtype=torch.bool) - ) + assert torch.equal(window.stability, torch.tensor([1], dtype=torch.float32)) # Fourth update - assert torch.equal( - window.previously_bifurcated, torch.tensor([False, True], dtype=torch.bool) - ) window.update(SCENARIO[4]) - assert not window.must_continue() - assert window.has_bifurcated_spins() + assert torch.equal(window.energies, torch.tensor([])) assert torch.equal( - window.get_final_spins(), + window.get_stored_spins(), torch.tensor( [ [-1, -1], @@ -184,23 +128,4 @@ def test_use_scenario(): dtype=torch.float32, ), ) - assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) - assert torch.equal( - window.final_spins, - torch.tensor( - [ - [-1, -1], - [1, 1], - [-1, -1], - ], - dtype=torch.float32, - ), - ) - assert torch.equal(window.stability, torch.tensor([2, 2], dtype=torch.int16)) - assert torch.equal( - window.newly_bifurcated, torch.tensor([True, False], dtype=torch.bool) - ) - assert torch.equal(window.bifurcated, torch.tensor([True, True], dtype=torch.bool)) - assert torch.equal( - window.stable_agents, torch.tensor([True, True], dtype=torch.bool) - ) + assert torch.equal(window.stability, torch.tensor([])) From 5f9f325f46f57e2de74fbf88c3250e52bff2d453 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Tue, 2 Jan 2024 23:07:15 +0100 Subject: [PATCH 04/12] docstring --- .../simulated_bifurcation_optimizer.py | 15 ++- .../optimizer/stop_window.py | 110 +++++++++++++++--- tests/optimizer/test_stop_window.py | 19 ++- 3 files changed, 115 insertions(+), 29 deletions(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 6510053d..54eb3003 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -209,18 +209,21 @@ def __symplectic_update( if use_window and self.__do_sampling: sampled_spins = self.symplectic_integrator.sample_spins() not_converged_agents = self.window.update(sampled_spins) - self.symplectic_integrator.momentum = ( - self.symplectic_integrator.momentum[:, not_converged_agents] - ) - self.symplectic_integrator.position = ( - self.symplectic_integrator.position[:, not_converged_agents] - ) + self.__remove_converged_agents(not_converged_agents) self.__check_stop(use_window) sampled_spins = self.symplectic_integrator.sample_spins() return sampled_spins + def __remove_converged_agents(self, not_converged_agents: torch.Tensor): + self.symplectic_integrator.momentum = self.symplectic_integrator.momentum[ + :, not_converged_agents + ] + self.symplectic_integrator.position = self.symplectic_integrator.position[ + :, not_converged_agents + ] + def __heat(self, momentum_copy: torch.Tensor) -> None: torch.add( self.symplectic_integrator.momentum, diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/stop_window.py index 5fa3db52..3cd1e69d 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/stop_window.py @@ -1,5 +1,3 @@ -from typing import Tuple, Union - import torch from tqdm import tqdm @@ -7,9 +5,9 @@ class StopWindow: """ - Optimization tool to monitor spins bifurcation and convergence - for the Simulated Bifurcation (SB) algorithm. - Allows an early stopping of the iterations and saves computation time. + Optimization tool to monitor agents bifurcation and convergence for the Simulated + Bifurcation (SB) algorithm. Allows an early stopping of the iterations and saves + computation time. """ def __init__( @@ -19,8 +17,8 @@ def __init__( n_agents: int, verbose: bool, ) -> None: - self.__init_convergence_threshold(convergence_threshold) - self.verbose = verbose + self.__check_convergence_threshold(convergence_threshold) + self.convergence_threshold = convergence_threshold self.ising_tensor = ising_tensor self.stability = torch.zeros( n_agents, dtype=torch.int16, device=ising_tensor.device @@ -33,7 +31,7 @@ def __init__( self.progress = tqdm( total=n_agents, desc="🏁 Bifurcated agents", - disable=not self.verbose, + disable=not verbose, smoothing=0, unit=" agents", ) @@ -48,11 +46,39 @@ def __init__( ) def __compute_energies(self, sampled_spins: torch.Tensor) -> torch.Tensor: + """ + Compute the Ising energy (modulo a -2 factor) of the sampled spins. + + Parameters + ---------- + sampled_spins : torch.Tensor + Sampled spins provided by the optimizer. + + Returns + ------- + torch.Tensor + The energy of each agent. + """ return torch.nn.functional.bilinear( sampled_spins.t(), sampled_spins.t(), torch.unsqueeze(self.ising_tensor, 0) ).reshape(sampled_spins.shape[1]) - def __init_convergence_threshold(self, convergence_threshold: int) -> None: + def __check_convergence_threshold(self, convergence_threshold: int) -> None: + """ + Check that the provided convergence threshold is a positive integer. + + Parameters + ---------- + convergence_threshold : int + Convergence threshold that defines a convergence criterion for the agents. + + Raises + ------ + TypeError + If the convergence threshold is not an integer. + ValueError + If the convergence threshold is negative or bigger than 2**15 - 1 (32767). + """ if not isinstance(convergence_threshold, int): raise TypeError( "convergence_threshold should be an integer, " @@ -68,46 +94,92 @@ def __init_convergence_threshold(self, convergence_threshold: int) -> None: "convergence_threshold should be less than or equal to " f"{torch.iinfo(torch.int16).max}, received {convergence_threshold}." ) - self.convergence_threshold = convergence_threshold def update(self, sampled_spins: torch.Tensor) -> torch.Tensor: - """_summary_ + """ + Update the stability streaks and the stored spins of the + window with sampled spins from the Simulated Bifurcation + optimizer. When an agent converges, it is stored in the + window's memory and removed from the optimization process. + + Return a boolean tensor that indicates which agents still have not converged. Parameters ---------- sampled_spins : torch.Tensor - _description_ + Sampled spins provided by the optimizer. Returns ------- torch.Tensor - The agents that still have not converged. + The agents that still have not converged (as a boolean tensor). + """ + self.__update_stability_streaks(sampled_spins) + self.__update_progressbar(sampled_spins.shape[1]) + return self.__store_converged_spins(sampled_spins) + + def __update_stability_streaks(self, sampled_spins: torch.Tensor): + """ + Update the stability streaks of the window from the + sampled spins provided by the optimizer. + + Parameters + ---------- + sampled_spins : torch.Tensor + Sampled spins provided by the optimizer. """ current_agents = self.energies.shape[0] energies = self.__compute_energies(sampled_spins) stable_agents = torch.eq(energies, self.energies) self.energies = energies self.stability = torch.where( - stable_agents, self.stability + 1, torch.zeros(current_agents) + stable_agents, + self.stability + 1, + torch.zeros(current_agents, device=self.ising_tensor.device), ) + def __store_converged_spins(self, sampled_spins: torch.Tensor) -> torch.Tensor: + """ + Store the newly converged agents in the window's memory and updates the + utility tensors by removing data relative to converged agents. + + Return a boolean tensor that indicates which agents still have not converged. + + Parameters + ---------- + sampled_spins : torch.Tensor + Sampled spins provided by the optimizer. + + Returns + ------- + torch.Tensor + The agents that still have not converged (as a boolean tensor). + """ converged_agents = torch.eq(self.stability, self.convergence_threshold - 1) not_converged_agents = torch.logical_not(converged_agents) - self.stored_spins[ :, self.shifted_agents_indices[converged_agents] ] = sampled_spins[:, converged_agents] - self.shifted_agents_indices = self.shifted_agents_indices[not_converged_agents] self.energies = self.energies[not_converged_agents] self.stability = self.stability[not_converged_agents] - new_agents = self.energies.shape[0] - self.progress.update(current_agents - new_agents) return not_converged_agents + def __update_progressbar(self, previous_agents: int): + """ + Update the progressbar with the number of newly converged agents. + + Parameters + ---------- + previous_agents : int + Previous number of agents. + """ + new_agents = self.energies.shape[0] + self.progress.update(previous_agents - new_agents) + def get_stored_spins(self) -> torch.Tensor: """ - Returns the converged spins stored in the window. + Return the converged spins stored in the window. Returns ------- diff --git a/tests/optimizer/test_stop_window.py b/tests/optimizer/test_stop_window.py index 22d4b6a7..99185616 100644 --- a/tests/optimizer/test_stop_window.py +++ b/tests/optimizer/test_stop_window.py @@ -53,14 +53,25 @@ def test_wrong_convergence_threshold_value(): - with pytest.raises(TypeError): + with pytest.raises( + TypeError, match="convergence_threshold should be an integer, received 30.0." + ): # noinspection PyTypeChecker StopWindow(30.0, TENSOR, AGENTS, verbose=False) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="convergence_threshold should be a positive integer, received 0.", + ): StopWindow(0, TENSOR, AGENTS, verbose=False) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="convergence_threshold should be a positive integer, received -42.", + ): StopWindow(-42, TENSOR, AGENTS, verbose=False) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="convergence_threshold should be less than or equal to 32767, received 32768.", + ): StopWindow(2**15, TENSOR, AGENTS, verbose=False) From 38593116faa939084824225845f09a17cd14da01 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Tue, 2 Jan 2024 23:45:02 +0100 Subject: [PATCH 05/12] replaced stop window by convergence checker --- README.md | 4 +- docs/pages/general/background.rst | 13 ++- docs/pages/modules/optimizer.rst | 2 +- src/simulated_bifurcation/core/ising.py | 28 +++---- .../core/quadratic_polynomial.py | 84 ++++++++++--------- src/simulated_bifurcation/models/abc_model.py | 12 +-- .../optimizer/__init__.py | 2 +- ...{stop_window.py => convergence_checker.py} | 18 ++-- .../simulated_bifurcation_optimizer.py | 80 ++++++++++-------- .../simulated_bifurcation.py | 66 +++++++-------- tests/models/test_sequential_markowitz.py | 2 +- ..._window.py => test_convergence_checker.py} | 70 +++++++++------- tests/optimizer/test_optimizer.py | 12 +-- 13 files changed, 210 insertions(+), 183 deletions(-) rename src/simulated_bifurcation/optimizer/{stop_window.py => convergence_checker.py} (90%) rename tests/optimizer/{test_stop_window.py => test_convergence_checker.py} (51%) diff --git a/README.md b/README.md index 84697baf..644eb0d7 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ The Simulated Bifurcation algorithm stops after a certain number of iterations, At regular intervals, the energy of the agents is sampled and compared with its previous value to calculate their stability period. If an agent's stability period exceeds a convergence threshold, it is considered to have converged and its value is frozen. If all agents converge before the maximum number of iterations has been reached, the algorithm stops. - The sampling period and the convergence threshold are respectively set using the `sampling_period` and `convergence_threshold` parameters of the `minimize` and `maximize` functions. -- To use early stopping in the SB algorithm, set the `use_window` parameter to `True`. +- To use early stopping in the SB algorithm, set the `early_stopping` parameter to `True`. - If only some agents have converged when the maximum number of iterations is reached, the algorithm stops and only these agents are considered in the results. ```python @@ -247,7 +247,7 @@ sb.minimize( matrix, sampling_period=30, convergence_threshold=50, - use_window=True, + early_stopping=True, ) ``` diff --git a/docs/pages/general/background.rst b/docs/pages/general/background.rst index b6123589..a08ef99e 100644 --- a/docs/pages/general/background.rst +++ b/docs/pages/general/background.rst @@ -109,10 +109,15 @@ The Simulated Bifurcation algorithm stops after a certain number of iterations o computation timeout is reached. However, this implementation comes with the possibility to perform early stopping and save computation time by defining convergence conditions. -At regular intervals, the state of the spins is sampled and compared with its previous value to calculate -their stability period. If an agent's stability period exceeds a convergence threshold, it is considered -to have converged and its value is frozen. If all agents converge before the maximum number of iterations -has been reached, the algorithm stops. +At regular intervals (this interval being called a sampling period), the agents (spin vectors) are +sampled and compared with their previous state by comparing their Ising energy. If the energy is the +same, the stability period of the agent is increased. If an agent's stability period exceeds a +convergence threshold, it is considered to have converged and its state is frozen. If all agents converge +before the maximum number of iterations has been reached, the algorithm then stops earlier. + +The purpose of sampling the spins at regular intervals is to decorrelate them and make their stability more +informative about their convergence (because the evolution of the spins is *slow* it is expected that +most of the spins will not change from a time step to the following). Notes ~~~~~ diff --git a/docs/pages/modules/optimizer.rst b/docs/pages/modules/optimizer.rst index 038f44dc..a0e21f8a 100644 --- a/docs/pages/modules/optimizer.rst +++ b/docs/pages/modules/optimizer.rst @@ -12,5 +12,5 @@ Optimizer .. autoclass:: SymplecticIntegrator :members: -.. autoclass:: StopWindow +.. autoclass:: ConvergenceChecker :members: diff --git a/src/simulated_bifurcation/core/ising.py b/src/simulated_bifurcation/core/ising.py index 543a7e84..a4e78aec 100644 --- a/src/simulated_bifurcation/core/ising.py +++ b/src/simulated_bifurcation/core/ising.py @@ -247,7 +247,7 @@ def minimize( heated: bool = False, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -276,17 +276,17 @@ def minimize( verbose : bool, default=True Whether to display a progress bar to monitor the progress of the algorithm. - use_window : bool, default=True - Whether to use the window as a stopping criterion: an agent is - said to have converged if its energy has not changed over the - last `convergence_threshold` energy samplings (done every - `sampling_period` steps). - sampling_period : int, default=50 - Number of iterations between two consecutive energy samplings - by the window. - convergence_threshold : int, default=50 - Number of consecutive identical energy samplings considered as - a proof of convergence by the window. + early_stopping : bool, default=True, keyword-only + Whether to use early stopping or not, making agents' convergence a + stopping criterion. An agent is said to have converged if its energy + has not changed over the last `convergence_threshold` energy samplings + (done every `sampling_period` steps). + sampling_period : int, default=50, keyword-only + Number of iterations between two consecutive spins samplings used for + early stopping. + convergence_threshold : int, default=50, keyword-only + Number of consecutive identical energy samplings considered as a + proof of convergence of an agent. timeout : float | None, default=None Time in seconds after which the simulation is stopped. None means no timeout. @@ -308,7 +308,7 @@ def minimize( Warns ----- - If `use_window` is True and no agent has reached the convergence + If `early_stopping` is True and no agent has reached the convergence criterion defined by `sampling_period` and `convergence_threshold` within `max_steps` iterations, a warning is logged in the console. This is just an indication however; the returned vectors may still @@ -403,7 +403,7 @@ def minimize( convergence_threshold, ) tensor = self.as_simulated_bifurcation_tensor() - spins = optimizer.run_integrator(tensor, use_window) + spins = optimizer.run_integrator(tensor, early_stopping) if self.linear_term: self.computed_spins = spins[-1] * spins[:-1] else: diff --git a/src/simulated_bifurcation/core/quadratic_polynomial.py b/src/simulated_bifurcation/core/quadratic_polynomial.py index cbe34865..41e92eb0 100644 --- a/src/simulated_bifurcation/core/quadratic_polynomial.py +++ b/src/simulated_bifurcation/core/quadratic_polynomial.py @@ -318,7 +318,7 @@ def optimize( minimize: bool = True, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -344,13 +344,15 @@ def optimize( algorithm with a supplementary non-symplectic term to refine the model To stop the iterations of the symplectic integrator, a number of maximum - steps needs to be specified. However, a refined way to stop is also possible - using a window that checks that the spins have not changed among a set - number of previous steps. In practice, a every fixed number of steps - (called a sampling period) the current spins will be compared to the - previous ones. If they remain constant throughout a certain number of - consecutive samplings (called the convergence threshold), the spins are - considered to have bifurcated and the algorithm stops. + steps and/or a timeout need(s) to be specified. However, a refined way to stop + is also possible using a convergence checker that asserts that the energy + of the agents has not changed during a fixed number of steps. If so, the computation + stops earlier than expected. In practice, every fixed number of steps (called a + sampling period) the current spins will be compared to the previous + ones (energy-wise). If the energy remains constant throughout a certain number of + consecutive samplings (called the convergence threshold), the spins are considered + to have bifurcated andthe algorithm stops. These spaced samplings make it possible + to decorrelate the spins and make their stability more informative. Finally, it is possible to make several particle vectors at the same time (each one being called an agent). As the vectors are randomly @@ -364,7 +366,7 @@ def optimize( ---------- * convergence_threshold : int, optional - number of consecutive identical spin sampling considered as a proof + number of consecutive identical spins samplings considered as a proof of convergence (default is 50) sampling_period : int, optional number of time steps between two spin sampling (default is 50) @@ -373,9 +375,9 @@ def optimize( (default is 10000) agents : int, optional number of vectors to make evolve at the same time (default is 128) - use_window : bool, optional - indicates whether to use the window as a stopping criterion or not - (default is True) + early_stopping : bool, optional + indicates whether to use the early stopping or not, thus making agents' + convergence a stopping criterion (default is True) timeout : float | None, default=None Time in seconds after which the simulation is stopped. None means no timeout. @@ -410,7 +412,7 @@ def optimize( ballistic, heated, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -434,7 +436,7 @@ def minimize( heated: bool = False, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -460,13 +462,15 @@ def minimize( algorithm with a supplementary non-symplectic term to refine the model To stop the iterations of the symplectic integrator, a number of maximum - steps needs to be specified. However, a refined way to stop is also possible - using a window that checks that the spins have not changed among a set - number of previous steps. In practice, a every fixed number of steps - (called a sampling period) the current spins will be compared to the - previous ones. If they remain constant throughout a certain number of - consecutive samplings (called the convergence threshold), the spins are - considered to have bifurcated and the algorithm stops. + steps and/or a timeout need(s) to be specified. However, a refined way to stop + is also possible using a convergence checker that asserts that the energy + of the agents has not changed during a fixed number of steps. If so, the computation + stops earlier than expected. In practice, every fixed number of steps (called a + sampling period) the current spins will be compared to the previous + ones (energy-wise). If the energy remains constant throughout a certain number of + consecutive samplings (called the convergence threshold), the spins are considered + to have bifurcated andthe algorithm stops. These spaced samplings make it possible + to decorrelate the spins and make their stability more informative. Finally, it is possible to make several particle vectors at the same time (each one being called an agent). As the vectors are randomly @@ -480,7 +484,7 @@ def minimize( ---------- * convergence_threshold : int, optional - number of consecutive identical spin sampling considered as a proof + number of consecutive identical spins samplings considered as a proof of convergence (default is 50) sampling_period : int, optional number of time steps between two spin sampling (default is 50) @@ -489,9 +493,9 @@ def minimize( (default is 10000) agents : int, optional number of vectors to make evolve at the same time (default is 128) - use_window : bool, optional - indicates whether to use the window as a stopping criterion or not - (default is True) + early_stopping : bool, optional + indicates whether to use the early stopping or not, thus making agents' + convergence a stopping criterion (default is True) timeout : float | None, default=None Time in seconds after which the simulation is stopped. None means no timeout. @@ -522,7 +526,7 @@ def minimize( heated, True, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -538,7 +542,7 @@ def maximize( heated: bool = False, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -564,13 +568,15 @@ def maximize( algorithm with a supplementary non-symplectic term to refine the model To stop the iterations of the symplectic integrator, a number of maximum - steps needs to be specified. However, a refined way to stop is also possible - using a window that checks that the spins have not changed among a set - number of previous steps. In practice, a every fixed number of steps - (called a sampling period) the current spins will be compared to the - previous ones. If they remain constant throughout a certain number of - consecutive samplings (called the convergence threshold), the spins are - considered to have bifurcated and the algorithm stops. + steps and/or a timeout need(s) to be specified. However, a refined way to stop + is also possible using a convergence checker that asserts that the energy + of the agents has not changed during a fixed number of steps. If so, the computation + stops earlier than expected. In practice, every fixed number of steps (called a + sampling period) the current spins will be compared to the previous + ones (energy-wise). If the energy remains constant throughout a certain number of + consecutive samplings (called the convergence threshold), the spins are considered + to have bifurcated andthe algorithm stops. These spaced samplings make it possible + to decorrelate the spins and make their stability more informative. Finally, it is possible to make several particle vectors at the same time (each one being called an agent). As the vectors are randomly @@ -583,7 +589,7 @@ def maximize( Parameters ---------- convergence_threshold : int, optional - number of consecutive identical spin sampling considered as a proof + number of consecutive identical spins samplings considered as a proof of convergence (default is 50) sampling_period : int, optional number of time steps between two spin sampling (default is 50) @@ -592,9 +598,9 @@ def maximize( (default is 10000) agents : int, optional number of vectors to make evolve at the same time (default is 128) - use_window : bool, optional - indicates whether to use the window as a stopping criterion or not - (default is True) + early_stopping : bool, optional + indicates whether to use the early stopping or not, thus making agents' + convergence a stopping criterion (default is True) timeout : float | None, default=None Time in seconds after which the simulation is stopped. None means no timeout. @@ -625,7 +631,7 @@ def maximize( heated, False, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, diff --git a/src/simulated_bifurcation/models/abc_model.py b/src/simulated_bifurcation/models/abc_model.py index 690a7407..1f6e807c 100644 --- a/src/simulated_bifurcation/models/abc_model.py +++ b/src/simulated_bifurcation/models/abc_model.py @@ -31,7 +31,7 @@ def optimize( minimize: bool = True, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None @@ -45,7 +45,7 @@ def optimize( heated, minimize, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -60,7 +60,7 @@ def minimize( heated: bool = False, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None @@ -73,7 +73,7 @@ def minimize( heated, True, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -88,7 +88,7 @@ def maximize( heated: bool = False, verbose: bool = True, *, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None @@ -101,7 +101,7 @@ def maximize( heated, False, verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, diff --git a/src/simulated_bifurcation/optimizer/__init__.py b/src/simulated_bifurcation/optimizer/__init__.py index bcb5a3ce..d4b9fc11 100644 --- a/src/simulated_bifurcation/optimizer/__init__.py +++ b/src/simulated_bifurcation/optimizer/__init__.py @@ -13,11 +13,11 @@ optimization problems. """ +from .convergence_checker import ConvergenceChecker from .environment import get_env, reset_env, set_env from .simulated_bifurcation_engine import SimulatedBifurcationEngine from .simulated_bifurcation_optimizer import ( ConvergenceWarning, SimulatedBifurcationOptimizer, ) -from .stop_window import StopWindow from .symplectic_integrator import SymplecticIntegrator diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/convergence_checker.py similarity index 90% rename from src/simulated_bifurcation/optimizer/stop_window.py rename to src/simulated_bifurcation/optimizer/convergence_checker.py index 3cd1e69d..59417e87 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/convergence_checker.py @@ -2,7 +2,7 @@ from tqdm import tqdm -class StopWindow: +class ConvergenceChecker: """ Optimization tool to monitor agents bifurcation and convergence for the Simulated @@ -97,10 +97,9 @@ def __check_convergence_threshold(self, convergence_threshold: int) -> None: def update(self, sampled_spins: torch.Tensor) -> torch.Tensor: """ - Update the stability streaks and the stored spins of the - window with sampled spins from the Simulated Bifurcation - optimizer. When an agent converges, it is stored in the - window's memory and removed from the optimization process. + Update the stability streaks and the spins stored in the memory with sampled + spins from the Simulated Bifurcation optimizer. When an agent converges, it is + stored in the memory and removed from the optimization process. Return a boolean tensor that indicates which agents still have not converged. @@ -120,8 +119,7 @@ def update(self, sampled_spins: torch.Tensor) -> torch.Tensor: def __update_stability_streaks(self, sampled_spins: torch.Tensor): """ - Update the stability streaks of the window from the - sampled spins provided by the optimizer. + Update the stability streaks from the sampled spins provided by the optimizer. Parameters ---------- @@ -140,8 +138,8 @@ def __update_stability_streaks(self, sampled_spins: torch.Tensor): def __store_converged_spins(self, sampled_spins: torch.Tensor) -> torch.Tensor: """ - Store the newly converged agents in the window's memory and updates the - utility tensors by removing data relative to converged agents. + Store the newly converged agents in the memory and updates the utility tensors + by removing data relative to converged agents. Return a boolean tensor that indicates which agents still have not converged. @@ -179,7 +177,7 @@ def __update_progressbar(self, previous_agents: int): def get_stored_spins(self) -> torch.Tensor: """ - Return the converged spins stored in the window. + Return the converged spins stored in the memory. Returns ------- diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 54eb3003..d3292c1b 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -7,9 +7,9 @@ from numpy import minimum from tqdm import tqdm +from .convergence_checker import ConvergenceChecker from .environment import ENVIRONMENT from .simulated_bifurcation_engine import SimulatedBifurcationEngine -from .stop_window import StopWindow from .symplectic_integrator import SymplecticIntegrator LOGGER = logging.getLogger("simulated_bifurcation_optimizer") @@ -46,13 +46,15 @@ class SimulatedBifurcationOptimizer: algorithm with a supplementary non-symplectic term to refine the model To stop the iterations of the symplectic integrator, a number of maximum - steps needs to be specified. However, a refined way to stop is also possible - using a window that checks that the spins have not changed among a set - number of previous steps. In practice, a every fixed number of steps - (called a sampling period) the current spins will be compared to the - previous ones. If they remain constant throughout a certain number of - consecutive samplings (called the convergence threshold), the spins are - considered to have bifurcated and the algorithm stops. + steps and/or a timeout need(s) to be specified. However, a refined way to stop + is also possible using a convergence checker that asserts that the energy + of the agents has not changed during a fixed number of steps. If so, the computation + stops earlier than expected. In practice, every fixed number of steps (called a + sampling period) the current spins will be compared to the previous + ones (energy-wise). If the energy remains constant throughout a certain number of + consecutive samplings (called the convergence threshold), the spins are considered + to have bifurcated andthe algorithm stops. These spaced samplings make it possible + to decorrelate the spins and make their stability more informative. Finally, it is possible to make several particle vectors at the same time (each one being called an agent). As the vectors are randomly @@ -76,7 +78,7 @@ def __init__( ) -> None: # Optimizer setting self.engine = engine - self.window = None + self.convergence_checker = None self.symplectic_integrator = None self.heat_coefficient = ENVIRONMENT.heat_coefficient self.heated = engine.heated @@ -93,10 +95,10 @@ def __init__( self.max_steps = max_steps if max_steps is not None else float("inf") self.timeout = timeout if timeout is not None else float("inf") - def __reset(self, matrix: torch.Tensor, use_window: bool) -> None: + def __reset(self, matrix: torch.Tensor, early_stopping: bool) -> None: self.__init_progress_bars() self.__init_symplectic_integrator(matrix) - self.__init_window(matrix, use_window) + self.__init_convergence_checker(matrix, early_stopping) self.__init_quadratic_scale_parameter(matrix) self.run = True self.step = 0 @@ -126,12 +128,14 @@ def __init_quadratic_scale_parameter(self, matrix: torch.Tensor): 0.5 * (matrix.shape[0] - 1) ** 0.5 / (torch.sqrt(torch.sum(matrix**2))) ) - def __init_window(self, matrix: torch.Tensor, use_window: bool) -> None: - self.window = StopWindow( + def __init_convergence_checker( + self, matrix: torch.Tensor, early_stopping: bool + ) -> None: + self.convergence_checker = ConvergenceChecker( self.convergence_threshold, matrix, self.agents, - (self.verbose and use_window), + (self.verbose and early_stopping), ) def __init_symplectic_integrator(self, matrix: torch.Tensor) -> None: @@ -146,9 +150,9 @@ def __step_update(self) -> None: self.step += 1 self.iterations_progress.update() - def __check_stop(self, use_window: bool) -> None: - if use_window and self.__do_sampling: - stored_spins = self.window.get_stored_spins() + def __check_stop(self, early_stopping: bool) -> None: + if early_stopping and self.__do_sampling: + stored_spins = self.convergence_checker.get_stored_spins() all_agents_converged = torch.any(torch.eq(stored_spins, 0)).item() self.run = all_agents_converged if not self.run: @@ -178,12 +182,12 @@ def __do_sampling(self) -> bool: def __close_progress_bars(self): self.iterations_progress.close() self.time_progress.close() - self.window.progress.close() + self.convergence_checker.progress.close() def __symplectic_update( self, matrix: torch.Tensor, - use_window: bool, + early_stopping: bool, ) -> torch.Tensor: self.start_time = time() while self.run: @@ -206,12 +210,12 @@ def __symplectic_update( self.__heat(momentum_copy) self.__step_update() - if use_window and self.__do_sampling: + if early_stopping and self.__do_sampling: sampled_spins = self.symplectic_integrator.sample_spins() - not_converged_agents = self.window.update(sampled_spins) + not_converged_agents = self.convergence_checker.update(sampled_spins) self.__remove_converged_agents(not_converged_agents) - self.__check_stop(use_window) + self.__check_stop(early_stopping) sampled_spins = self.symplectic_integrator.sample_spins() return sampled_spins @@ -243,7 +247,9 @@ def __compute_symplectic_coefficients(self) -> Tuple[float, float, float]: def __pressure(self): return minimum(self.time_step * self.step * self.pressure_slope, 1.0) - def run_integrator(self, matrix: torch.Tensor, use_window: bool) -> torch.Tensor: + def run_integrator( + self, matrix: torch.Tensor, early_stopping: bool + ) -> torch.Tensor: """ Runs the Simulated Bifurcation (SB) algorithm. Given an input matrix, the SB algorithm aims at finding the groud state of the Ising model @@ -255,8 +261,8 @@ def run_integrator(self, matrix: torch.Tensor, use_window: bool) -> torch.Tensor ---------- matrix : torch.Tensor The matrix that defines the Ising model to optimize. - use_window : bool - Whether to use a stop window or not to perform early-stopping. + early_stopping : bool + Whether to perform early-stopping or not. Returns ------- @@ -271,37 +277,39 @@ def run_integrator(self, matrix: torch.Tensor, use_window: bool) -> torch.Tensor if ( self.max_steps == float("inf") and self.timeout == float("inf") - and not use_window + and not early_stopping ): raise ValueError("No stopping criterion provided.") - self.__reset(matrix, use_window) - spins = self.__symplectic_update(matrix, use_window) + self.__reset(matrix, early_stopping) + spins = self.__symplectic_update(matrix, early_stopping) self.__close_progress_bars() - return self.get_final_spins(spins, use_window) + return self.get_final_spins(spins, early_stopping) - def get_final_spins(self, spins: torch.Tensor, use_window: bool) -> torch.Tensor: + def get_final_spins( + self, spins: torch.Tensor, early_stopping: bool + ) -> torch.Tensor: """ Returns the final spins retrieved at the end of the Simulated Bifurcation (SB) algorithm. - If the stop window was used, it returns the bifurcated agents if any, + If the early stopping was used, it returns the converged agents if any, otherwise the actual final spins are returned. - If the stop window was not used, the final spins are returned. + If the early stopping was not used, the final spins are returned. Parameters ---------- spins : torch.Tensor The spins returned by the Simulated Bifurcation algorithm. - use_window : bool - Whether the stop window was used or not. + early_stopping : bool + Whether the early stopping was used or not. Returns ------- torch.Tensor """ - if use_window: - final_spins = self.window.get_stored_spins() + if early_stopping: + final_spins = self.convergence_checker.get_stored_spins() any_converged_agents = torch.any(torch.not_equal(final_spins, 0)).item() if not any_converged_agents: warnings.warn(ConvergenceWarning(), stacklevel=2) diff --git a/src/simulated_bifurcation/simulated_bifurcation.py b/src/simulated_bifurcation/simulated_bifurcation.py index 0e55e8e7..c8d90f64 100644 --- a/src/simulated_bifurcation/simulated_bifurcation.py +++ b/src/simulated_bifurcation/simulated_bifurcation.py @@ -168,7 +168,7 @@ def optimize( heated: bool = False, minimize: bool = True, verbose: bool = True, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -242,17 +242,17 @@ def optimize( verbose : bool, default=True, keyword-only Whether to display a progress bar to monitor the progress of the algorithm. - use_window : bool, default=True, keyword-only - Whether to use the window as a stopping criterion. An agent is said - to have converged if its energy has not changed over the - last `convergence_threshold` energy samplings + early_stopping : bool, default=True, keyword-only + Whether to use early stopping or not, making agents' convergence a + stopping criterion. An agent is said to have converged if its energy + has not changed over the last `convergence_threshold` energy samplings (done every `sampling_period` steps). sampling_period : int, default=50, keyword-only - Number of iterations between two consecutive energy samplings by - the window. + Number of iterations between two consecutive spins samplings used for + early stopping. convergence_threshold : int, default=50, keyword-only Number of consecutive identical energy samplings considered as a - proof of convergence by the window. + proof of convergence of an agent. timeout : float | None, default=None, keyword-only Time, in seconds, after which the simulation will be stopped. None means no timeout. @@ -284,8 +284,8 @@ def optimize( Warns ----- - Use of Stop Window - If `use_window` is True and no agent has reached the convergence + Use of early stopping + If `early_stopping` is True and no agent has reached the convergence criterion defined by `sampling_period` and `convergence_threshold` within `max_steps` iterations, a warning is logged in the console. This is just an indication however; the returned vectors may still be @@ -426,7 +426,7 @@ def optimize( heated=heated, minimize=minimize, verbose=verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -445,7 +445,7 @@ def minimize( ballistic: bool = False, heated: bool = False, verbose: bool = True, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -516,17 +516,17 @@ def minimize( verbose : bool, default=True, keyword-only Whether to display a progress bar to monitor the progress of the algorithm. - use_window : bool, default=True, keyword-only - Whether to use the window as a stopping criterion. An agent is said - to have converged if its energy has not changed over the - last `convergence_threshold` energy samplings + early_stopping : bool, default=True, keyword-only + Whether to use early stopping or not, making agents' convergence a + stopping criterion. An agent is said to have converged if its energy + has not changed over the last `convergence_threshold` energy samplings (done every `sampling_period` steps). sampling_period : int, default=50, keyword-only - Number of iterations between two consecutive energy samplings by - the window. + Number of iterations between two consecutive spins samplings used for + early stopping. convergence_threshold : int, default=50, keyword-only Number of consecutive identical energy samplings considered as a - proof of convergence by the window. + proof of convergence of an agent. timeout : float | None, default=None, keyword-only Time, in seconds, after which the simulation will be stopped. None means no timeout. @@ -558,8 +558,8 @@ def minimize( Warns ----- - Use of Stop Window - If `use_window` is True and no agent has reached the convergence + Use of early stopping + If `early_stopping` is True and no agent has reached the convergence criterion defined by `sampling_period` and `convergence_threshold` within `max_steps` iterations, a warning is logged in the console. This is just an indication however; the returned vectors may still be @@ -690,7 +690,7 @@ def minimize( heated=heated, minimize=True, verbose=verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, @@ -708,7 +708,7 @@ def maximize( ballistic: bool = False, heated: bool = False, verbose: bool = True, - use_window: bool = True, + early_stopping: bool = True, sampling_period: int = 50, convergence_threshold: int = 50, timeout: Optional[float] = None, @@ -779,17 +779,17 @@ def maximize( verbose : bool, default=True, keyword-only Whether to display a progress bar to monitor the progress of the algorithm. - use_window : bool, default=True, keyword-only - Whether to use the window as a stopping criterion. An agent is said - to have converged if its energy has not changed over the - last `convergence_threshold` energy samplings + early_stopping : bool, default=True, keyword-only + Whether to use early stopping or not, making agents' convergence a + stopping criterion. An agent is said to have converged if its energy + has not changed over the last `convergence_threshold` energy samplings (done every `sampling_period` steps). sampling_period : int, default=50, keyword-only - Number of iterations between two consecutive energy samplings by - the window. + Number of iterations between two consecutive spins samplings used for + early stopping. convergence_threshold : int, default=50, keyword-only Number of consecutive identical energy samplings considered as a - proof of convergence by the window. + proof of convergence of an agent. timeout : float | None, default=None, keyword-only Time, in seconds, after which the simulation will be stopped. None means no timeout. @@ -821,8 +821,8 @@ def maximize( Warns ----- - Use of Stop Window - If `use_window` is True and no agent has reached the convergence + Use of early stopping + If `early_stopping` is True and no agent has reached the convergence criterion defined by `sampling_period` and `convergence_threshold` within `max_steps` iterations, a warning is logged in the console. This is just an indication however; the returned vectors may still be @@ -953,7 +953,7 @@ def maximize( heated=heated, minimize=False, verbose=verbose, - use_window=use_window, + early_stopping=early_stopping, sampling_period=sampling_period, convergence_threshold=convergence_threshold, timeout=timeout, diff --git a/tests/models/test_sequential_markowitz.py b/tests/models/test_sequential_markowitz.py index 136df1a4..6f325ac9 100644 --- a/tests/models/test_sequential_markowitz.py +++ b/tests/models/test_sequential_markowitz.py @@ -84,7 +84,7 @@ def test_sequential_markowitz(): ) assert torch.equal(torch.tensor(-0.2), model[0]) - model.maximize(agents=128, use_window=False, verbose=False) + model.maximize(agents=128, early_stopping=False, verbose=False) assert (4, 2) == model.portfolio.shape assert torch.equal( torch.tensor([[0.0, 1.0], [0.0, 0.0], [1.0, 0.0], [1.0, 0.0]]), model.portfolio diff --git a/tests/optimizer/test_stop_window.py b/tests/optimizer/test_convergence_checker.py similarity index 51% rename from tests/optimizer/test_stop_window.py rename to tests/optimizer/test_convergence_checker.py index 99185616..79023ea9 100644 --- a/tests/optimizer/test_stop_window.py +++ b/tests/optimizer/test_convergence_checker.py @@ -1,7 +1,7 @@ import pytest import torch -from src.simulated_bifurcation.optimizer import StopWindow +from src.simulated_bifurcation.optimizer import ConvergenceChecker TENSOR = torch.tensor([[1.0, 0.5, -1.0], [0.5, 0.0, 1.0], [-1.0, 1.0, -2.0]]) CONVERGENCE_THRESHOLD = 3 @@ -57,22 +57,22 @@ def test_wrong_convergence_threshold_value(): TypeError, match="convergence_threshold should be an integer, received 30.0." ): # noinspection PyTypeChecker - StopWindow(30.0, TENSOR, AGENTS, verbose=False) + ConvergenceChecker(30.0, TENSOR, AGENTS, verbose=False) with pytest.raises( ValueError, match="convergence_threshold should be a positive integer, received 0.", ): - StopWindow(0, TENSOR, AGENTS, verbose=False) + ConvergenceChecker(0, TENSOR, AGENTS, verbose=False) with pytest.raises( ValueError, match="convergence_threshold should be a positive integer, received -42.", ): - StopWindow(-42, TENSOR, AGENTS, verbose=False) + ConvergenceChecker(-42, TENSOR, AGENTS, verbose=False) with pytest.raises( ValueError, match="convergence_threshold should be less than or equal to 32767, received 32768.", ): - StopWindow(2**15, TENSOR, AGENTS, verbose=False) + ConvergenceChecker(2**15, TENSOR, AGENTS, verbose=False) def test_use_scenario(): @@ -80,40 +80,48 @@ def test_use_scenario(): Ground state is degenerate: [-1, 1, -1] and [1, -1, 1] both reach the minimal energy value -6. - Test of the stop window's behavior on 2 agents: + Test of the convergence checker's behavior on 2 agents: - agent 1 converges to an optimal vector from step 3; - agent 2 oscillates in the optimal space from step 2. """ - window = StopWindow(CONVERGENCE_THRESHOLD, TENSOR, AGENTS, verbose=False) + convergence_checker = ConvergenceChecker( + CONVERGENCE_THRESHOLD, TENSOR, AGENTS, verbose=False + ) # Initial state - assert torch.equal(window.get_stored_spins(), torch.zeros(3, 2)) - assert torch.all(torch.isinf(window.energies)) - assert torch.equal(window.stability, torch.zeros(2)) + assert torch.equal(convergence_checker.get_stored_spins(), torch.zeros(3, 2)) + assert torch.all(torch.isinf(convergence_checker.energies)) + assert torch.equal(convergence_checker.stability, torch.zeros(2)) # First update - window.update(SCENARIO[0]) - assert torch.equal(window.energies, torch.tensor([2.0, 0.0])) - assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) - assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) + convergence_checker.update(SCENARIO[0]) + assert torch.equal(convergence_checker.energies, torch.tensor([2.0, 0.0])) + assert torch.equal(convergence_checker.get_stored_spins(), torch.zeros((3, 2))) + assert torch.equal( + convergence_checker.stability, torch.tensor([0, 0], dtype=torch.int16) + ) # Second update - window.update(SCENARIO[1]) - assert torch.equal(window.energies, torch.tensor([0.0, -6.0])) - assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) - assert torch.equal(window.stability, torch.tensor([0, 0], dtype=torch.int16)) + convergence_checker.update(SCENARIO[1]) + assert torch.equal(convergence_checker.energies, torch.tensor([0.0, -6.0])) + assert torch.equal(convergence_checker.get_stored_spins(), torch.zeros((3, 2))) + assert torch.equal( + convergence_checker.stability, torch.tensor([0, 0], dtype=torch.int16) + ) # Third update - window.update(SCENARIO[2]) - assert torch.equal(window.energies, torch.tensor([-6.0, -6.0])) - assert torch.equal(window.get_stored_spins(), torch.zeros((3, 2))) - assert torch.equal(window.stability, torch.tensor([0, 1], dtype=torch.int16)) + convergence_checker.update(SCENARIO[2]) + assert torch.equal(convergence_checker.energies, torch.tensor([-6.0, -6.0])) + assert torch.equal(convergence_checker.get_stored_spins(), torch.zeros((3, 2))) + assert torch.equal( + convergence_checker.stability, torch.tensor([0, 1], dtype=torch.int16) + ) # Fourth update - window.update(SCENARIO[3]) - assert torch.equal(window.energies, torch.tensor([-6.0])) + convergence_checker.update(SCENARIO[3]) + assert torch.equal(convergence_checker.energies, torch.tensor([-6.0])) assert torch.equal( - window.get_stored_spins(), + convergence_checker.get_stored_spins(), torch.tensor( [ [0, -1], @@ -123,13 +131,15 @@ def test_use_scenario(): dtype=torch.float32, ), ) - assert torch.equal(window.stability, torch.tensor([1], dtype=torch.float32)) + assert torch.equal( + convergence_checker.stability, torch.tensor([1], dtype=torch.float32) + ) # Fourth update - window.update(SCENARIO[4]) - assert torch.equal(window.energies, torch.tensor([])) + convergence_checker.update(SCENARIO[4]) + assert torch.equal(convergence_checker.energies, torch.tensor([])) assert torch.equal( - window.get_stored_spins(), + convergence_checker.get_stored_spins(), torch.tensor( [ [-1, -1], @@ -139,4 +149,4 @@ def test_use_scenario(): dtype=torch.float32, ), ) - assert torch.equal(window.stability, torch.tensor([])) + assert torch.equal(convergence_checker.stability, torch.tensor([])) diff --git a/tests/optimizer/test_optimizer.py b/tests/optimizer/test_optimizer.py index c798d760..f42c1744 100644 --- a/tests/optimizer/test_optimizer.py +++ b/tests/optimizer/test_optimizer.py @@ -27,7 +27,7 @@ def test_optimizer(): False, False, False, - use_window=False, + early_stopping=False, sampling_period=50, convergence_threshold=50, ) @@ -53,7 +53,7 @@ def test_optimizer_without_bifurcation(): False, False, False, - use_window=True, + early_stopping=True, sampling_period=50, convergence_threshold=50, ) @@ -70,7 +70,7 @@ def test_optimizer_without_bifurcation(): ) -def test_optimizer_with_window(): +def test_optimizer_with_convergence_checker(): torch.manual_seed(42) J = torch.tensor( [ @@ -88,7 +88,7 @@ def test_optimizer_with_window(): False, False, False, - use_window=True, + early_stopping=True, sampling_period=20, convergence_threshold=20, ) @@ -113,7 +113,7 @@ def test_optimizer_with_heating(): False, True, False, - use_window=False, + early_stopping=False, sampling_period=50, convergence_threshold=50, ) @@ -176,7 +176,7 @@ def test_timeout(): assert optimizer.simulation_time > 3.0 -def test_window(): +def test_convergence_checker(): torch.manual_seed(42) J = torch.tensor( [ From a21dcfec5f0f09e7afe37d1c14d37d10b12e5772 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet <63302082+bqth29@users.noreply.github.com> Date: Thu, 4 Jan 2024 09:45:12 +0100 Subject: [PATCH 06/12] Rename variable --- .../optimizer/simulated_bifurcation_optimizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 54eb3003..90509426 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -149,8 +149,8 @@ def __step_update(self) -> None: def __check_stop(self, use_window: bool) -> None: if use_window and self.__do_sampling: stored_spins = self.window.get_stored_spins() - all_agents_converged = torch.any(torch.eq(stored_spins, 0)).item() - self.run = all_agents_converged + some_agents_not_converged = torch.any(torch.eq(stored_spins, 0)).item() + self.run = some_agents_not_converged if not self.run: LOGGER.info("Optimizer stopped. Reason: all agents converged.") return From fd1523381108aeaa20fe0ff3e59c281d195a60fc Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Fri, 12 Jan 2024 19:23:49 +0100 Subject: [PATCH 07/12] package version in doc conf --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c96595b5..03e85c07 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ project = "Simulated Bifurcation" copyright = "2023, Romain Ageron, Thomas Bouquet and Lorenzo Pugliese" author = "Romain Ageron, Thomas Bouquet and Lorenzo Pugliese" -release = "2.0.1" +release = "1.3.0.dev0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration From 60c6880d8036fda9a8c22a9538e6d52ce34e706d Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Fri, 12 Jan 2024 19:31:52 +0100 Subject: [PATCH 08/12] non systematic reshape --- .../optimizer/simulated_bifurcation_optimizer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 90509426..3408b34e 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -209,7 +209,9 @@ def __symplectic_update( if use_window and self.__do_sampling: sampled_spins = self.symplectic_integrator.sample_spins() not_converged_agents = self.window.update(sampled_spins) - self.__remove_converged_agents(not_converged_agents) + # Only reshape the oscillators if some agents converged + if not torch.all(not_converged_agents).item(): + self.__remove_converged_agents(not_converged_agents) self.__check_stop(use_window) From dddf7f14a00c2ba7f1ad51f38570490110c271a4 Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Fri, 12 Jan 2024 19:32:54 +0100 Subject: [PATCH 09/12] sinc with stop window branch --- .../optimizer/simulated_bifurcation_optimizer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index d3292c1b..30a04e5c 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -213,7 +213,9 @@ def __symplectic_update( if early_stopping and self.__do_sampling: sampled_spins = self.symplectic_integrator.sample_spins() not_converged_agents = self.convergence_checker.update(sampled_spins) - self.__remove_converged_agents(not_converged_agents) + # Only reshape the oscillators if some agents converged + if not torch.all(not_converged_agents).item(): + self.__remove_converged_agents(not_converged_agents) self.__check_stop(early_stopping) From 99bbddb902ddebb4090c3cfecfd234e1f932f33a Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Thu, 8 Feb 2024 21:01:29 +0100 Subject: [PATCH 10/12] lint black 24.1.1 --- src/simulated_bifurcation/__init__.py | 1 - src/simulated_bifurcation/core/__init__.py | 1 - src/simulated_bifurcation/core/ising.py | 2 -- src/simulated_bifurcation/models/ising.py | 1 - src/simulated_bifurcation/models/markowitz.py | 2 -- .../models/number_partitioning.py | 1 - src/simulated_bifurcation/models/qubo.py | 1 - src/simulated_bifurcation/optimizer/__init__.py | 1 + .../optimizer/simulated_bifurcation_engine.py | 1 - .../optimizer/simulated_bifurcation_optimizer.py | 1 - src/simulated_bifurcation/optimizer/stop_window.py | 7 +++---- .../optimizer/symplectic_integrator.py | 1 - tests/core/test_quadratic_polynomial.py | 11 +---------- tests/polynomial/test_polynomial_map.py | 11 +---------- 14 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/simulated_bifurcation/__init__.py b/src/simulated_bifurcation/__init__.py index 1c01e114..7721b03b 100644 --- a/src/simulated_bifurcation/__init__.py +++ b/src/simulated_bifurcation/__init__.py @@ -175,7 +175,6 @@ """ - from . import models from .core import Ising, QuadraticPolynomial from .optimizer import ConvergenceWarning, get_env, reset_env, set_env diff --git a/src/simulated_bifurcation/core/__init__.py b/src/simulated_bifurcation/core/__init__.py index 247bf638..9b95ba25 100644 --- a/src/simulated_bifurcation/core/__init__.py +++ b/src/simulated_bifurcation/core/__init__.py @@ -22,7 +22,6 @@ """ - from .ising import Ising from .quadratic_polynomial import ( PolynomialLike, diff --git a/src/simulated_bifurcation/core/ising.py b/src/simulated_bifurcation/core/ising.py index 543a7e84..e5c3dd7d 100644 --- a/src/simulated_bifurcation/core/ising.py +++ b/src/simulated_bifurcation/core/ising.py @@ -18,7 +18,6 @@ """ - from typing import Optional, TypeVar, Union import torch @@ -31,7 +30,6 @@ class Ising: - """ Internal implementation of the Ising model. diff --git a/src/simulated_bifurcation/models/ising.py b/src/simulated_bifurcation/models/ising.py index 56f3e4ce..62399a34 100644 --- a/src/simulated_bifurcation/models/ising.py +++ b/src/simulated_bifurcation/models/ising.py @@ -7,7 +7,6 @@ class Ising(ABCModel): - """ Implementation of the Ising model. diff --git a/src/simulated_bifurcation/models/markowitz.py b/src/simulated_bifurcation/models/markowitz.py index 2fa3cf32..80425bc3 100644 --- a/src/simulated_bifurcation/models/markowitz.py +++ b/src/simulated_bifurcation/models/markowitz.py @@ -7,7 +7,6 @@ class SequentialMarkowitz(ABCModel): - """ Implementation of the Markowitz model for the integer trading trajectory optimization problem. @@ -154,7 +153,6 @@ def gains(self) -> float: class Markowitz(SequentialMarkowitz): - """ A representation of the Markowitz model for portfolio optimization. Portfolio only takes integer stocks. diff --git a/src/simulated_bifurcation/models/number_partitioning.py b/src/simulated_bifurcation/models/number_partitioning.py index 3ad3d1f2..d15e05cf 100644 --- a/src/simulated_bifurcation/models/number_partitioning.py +++ b/src/simulated_bifurcation/models/number_partitioning.py @@ -7,7 +7,6 @@ class NumberPartitioning(ABCModel): - """ A solver that separates a set of numbers into two subsets, the respective sums of which are as close as possible. diff --git a/src/simulated_bifurcation/models/qubo.py b/src/simulated_bifurcation/models/qubo.py index faa69f22..fe5a3aaa 100644 --- a/src/simulated_bifurcation/models/qubo.py +++ b/src/simulated_bifurcation/models/qubo.py @@ -7,7 +7,6 @@ class QUBO(ABCModel): - """ Quadratic Unconstrained Binary Optimization diff --git a/src/simulated_bifurcation/optimizer/__init__.py b/src/simulated_bifurcation/optimizer/__init__.py index bcb5a3ce..d3e4be92 100644 --- a/src/simulated_bifurcation/optimizer/__init__.py +++ b/src/simulated_bifurcation/optimizer/__init__.py @@ -13,6 +13,7 @@ optimization problems. """ + from .environment import get_env, reset_env, set_env from .simulated_bifurcation_engine import SimulatedBifurcationEngine from .simulated_bifurcation_optimizer import ( diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py index 1d2ab5e4..232860f7 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py @@ -4,7 +4,6 @@ class SimulatedBifurcationEngine(Enum): - """ Enum class that gathers the 4 variants of the Simulated Bifurcation algorithm: diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 3408b34e..03778cff 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -27,7 +27,6 @@ def __str__(self) -> str: class SimulatedBifurcationOptimizer: - """ The Simulated Bifurcation (SB) algorithm relies on Hamiltonian/quantum mechanics to find local minima of diff --git a/src/simulated_bifurcation/optimizer/stop_window.py b/src/simulated_bifurcation/optimizer/stop_window.py index 3cd1e69d..a6bfd3e7 100644 --- a/src/simulated_bifurcation/optimizer/stop_window.py +++ b/src/simulated_bifurcation/optimizer/stop_window.py @@ -3,7 +3,6 @@ class StopWindow: - """ Optimization tool to monitor agents bifurcation and convergence for the Simulated Bifurcation (SB) algorithm. Allows an early stopping of the iterations and saves @@ -157,9 +156,9 @@ def __store_converged_spins(self, sampled_spins: torch.Tensor) -> torch.Tensor: """ converged_agents = torch.eq(self.stability, self.convergence_threshold - 1) not_converged_agents = torch.logical_not(converged_agents) - self.stored_spins[ - :, self.shifted_agents_indices[converged_agents] - ] = sampled_spins[:, converged_agents] + self.stored_spins[:, self.shifted_agents_indices[converged_agents]] = ( + sampled_spins[:, converged_agents] + ) self.shifted_agents_indices = self.shifted_agents_indices[not_converged_agents] self.energies = self.energies[not_converged_agents] self.stability = self.stability[not_converged_agents] diff --git a/src/simulated_bifurcation/optimizer/symplectic_integrator.py b/src/simulated_bifurcation/optimizer/symplectic_integrator.py index 90e03849..c3b4e4c6 100644 --- a/src/simulated_bifurcation/optimizer/symplectic_integrator.py +++ b/src/simulated_bifurcation/optimizer/symplectic_integrator.py @@ -4,7 +4,6 @@ class SymplecticIntegrator: - """ Simulates the evolution of spins' momentum and position following the Hamiltonian quantum mechanics equations that drive the diff --git a/tests/core/test_quadratic_polynomial.py b/tests/core/test_quadratic_polynomial.py index 9c718dcf..193810f2 100644 --- a/tests/core/test_quadratic_polynomial.py +++ b/tests/core/test_quadratic_polynomial.py @@ -20,16 +20,7 @@ def test_build_polynomial_from_expression(): x, y, z = symbols("x y z") expression = poly( - x**2 - + 2 * y**2 - + 3 * z**2 - - 2 * x * y - - x * z - - 3 * y * z - - x - - 2 * y - + z - + 2 + x**2 + 2 * y**2 + 3 * z**2 - 2 * x * y - x * z - 3 * y * z - x - 2 * y + z + 2 ) # Valid definitions diff --git a/tests/polynomial/test_polynomial_map.py b/tests/polynomial/test_polynomial_map.py index 40b58190..bd29af51 100644 --- a/tests/polynomial/test_polynomial_map.py +++ b/tests/polynomial/test_polynomial_map.py @@ -167,16 +167,7 @@ def test_init_polynomial_map_with_inconsistent_dimension_raises_error(): def test_init_polynomial_map_from_expression(): x, y, z = symbols("x y z") expression = poly( - x**2 - + 2 * y**2 - + 3 * z**2 - - 2 * x * y - - x * z - - 3 * y * z - - x - - 2 * y - + z - + 2 + x**2 + 2 * y**2 + 3 * z**2 - 2 * x * y - x * z - 3 * y * z - x - 2 * y + z + 2 ) polynomial_map = PolynomialMap.from_expression(expression) assert_expected_polynomial_map(polynomial_map) From 64bfacea491620b45a0c352a556f81284d4b2a9a Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Thu, 8 Feb 2024 21:02:03 +0100 Subject: [PATCH 11/12] lint black 24.1.1 --- src/simulated_bifurcation/__init__.py | 1 - src/simulated_bifurcation/core/__init__.py | 1 - src/simulated_bifurcation/core/ising.py | 2 -- src/simulated_bifurcation/models/ising.py | 1 - src/simulated_bifurcation/models/markowitz.py | 2 -- .../models/number_partitioning.py | 1 - src/simulated_bifurcation/models/qubo.py | 1 - src/simulated_bifurcation/optimizer/__init__.py | 1 + .../optimizer/convergence_checker.py | 7 +++---- .../optimizer/simulated_bifurcation_engine.py | 1 - .../optimizer/simulated_bifurcation_optimizer.py | 1 - .../optimizer/symplectic_integrator.py | 1 - tests/core/test_quadratic_polynomial.py | 11 +---------- tests/polynomial/test_polynomial_map.py | 11 +---------- 14 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/simulated_bifurcation/__init__.py b/src/simulated_bifurcation/__init__.py index 1c01e114..7721b03b 100644 --- a/src/simulated_bifurcation/__init__.py +++ b/src/simulated_bifurcation/__init__.py @@ -175,7 +175,6 @@ """ - from . import models from .core import Ising, QuadraticPolynomial from .optimizer import ConvergenceWarning, get_env, reset_env, set_env diff --git a/src/simulated_bifurcation/core/__init__.py b/src/simulated_bifurcation/core/__init__.py index 247bf638..9b95ba25 100644 --- a/src/simulated_bifurcation/core/__init__.py +++ b/src/simulated_bifurcation/core/__init__.py @@ -22,7 +22,6 @@ """ - from .ising import Ising from .quadratic_polynomial import ( PolynomialLike, diff --git a/src/simulated_bifurcation/core/ising.py b/src/simulated_bifurcation/core/ising.py index a4e78aec..72f0d429 100644 --- a/src/simulated_bifurcation/core/ising.py +++ b/src/simulated_bifurcation/core/ising.py @@ -18,7 +18,6 @@ """ - from typing import Optional, TypeVar, Union import torch @@ -31,7 +30,6 @@ class Ising: - """ Internal implementation of the Ising model. diff --git a/src/simulated_bifurcation/models/ising.py b/src/simulated_bifurcation/models/ising.py index 56f3e4ce..62399a34 100644 --- a/src/simulated_bifurcation/models/ising.py +++ b/src/simulated_bifurcation/models/ising.py @@ -7,7 +7,6 @@ class Ising(ABCModel): - """ Implementation of the Ising model. diff --git a/src/simulated_bifurcation/models/markowitz.py b/src/simulated_bifurcation/models/markowitz.py index 2fa3cf32..80425bc3 100644 --- a/src/simulated_bifurcation/models/markowitz.py +++ b/src/simulated_bifurcation/models/markowitz.py @@ -7,7 +7,6 @@ class SequentialMarkowitz(ABCModel): - """ Implementation of the Markowitz model for the integer trading trajectory optimization problem. @@ -154,7 +153,6 @@ def gains(self) -> float: class Markowitz(SequentialMarkowitz): - """ A representation of the Markowitz model for portfolio optimization. Portfolio only takes integer stocks. diff --git a/src/simulated_bifurcation/models/number_partitioning.py b/src/simulated_bifurcation/models/number_partitioning.py index 3ad3d1f2..d15e05cf 100644 --- a/src/simulated_bifurcation/models/number_partitioning.py +++ b/src/simulated_bifurcation/models/number_partitioning.py @@ -7,7 +7,6 @@ class NumberPartitioning(ABCModel): - """ A solver that separates a set of numbers into two subsets, the respective sums of which are as close as possible. diff --git a/src/simulated_bifurcation/models/qubo.py b/src/simulated_bifurcation/models/qubo.py index faa69f22..fe5a3aaa 100644 --- a/src/simulated_bifurcation/models/qubo.py +++ b/src/simulated_bifurcation/models/qubo.py @@ -7,7 +7,6 @@ class QUBO(ABCModel): - """ Quadratic Unconstrained Binary Optimization diff --git a/src/simulated_bifurcation/optimizer/__init__.py b/src/simulated_bifurcation/optimizer/__init__.py index d4b9fc11..80f0753a 100644 --- a/src/simulated_bifurcation/optimizer/__init__.py +++ b/src/simulated_bifurcation/optimizer/__init__.py @@ -13,6 +13,7 @@ optimization problems. """ + from .convergence_checker import ConvergenceChecker from .environment import get_env, reset_env, set_env from .simulated_bifurcation_engine import SimulatedBifurcationEngine diff --git a/src/simulated_bifurcation/optimizer/convergence_checker.py b/src/simulated_bifurcation/optimizer/convergence_checker.py index 59417e87..d787076a 100644 --- a/src/simulated_bifurcation/optimizer/convergence_checker.py +++ b/src/simulated_bifurcation/optimizer/convergence_checker.py @@ -3,7 +3,6 @@ class ConvergenceChecker: - """ Optimization tool to monitor agents bifurcation and convergence for the Simulated Bifurcation (SB) algorithm. Allows an early stopping of the iterations and saves @@ -155,9 +154,9 @@ def __store_converged_spins(self, sampled_spins: torch.Tensor) -> torch.Tensor: """ converged_agents = torch.eq(self.stability, self.convergence_threshold - 1) not_converged_agents = torch.logical_not(converged_agents) - self.stored_spins[ - :, self.shifted_agents_indices[converged_agents] - ] = sampled_spins[:, converged_agents] + self.stored_spins[:, self.shifted_agents_indices[converged_agents]] = ( + sampled_spins[:, converged_agents] + ) self.shifted_agents_indices = self.shifted_agents_indices[not_converged_agents] self.energies = self.energies[not_converged_agents] self.stability = self.stability[not_converged_agents] diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py index 1d2ab5e4..232860f7 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_engine.py @@ -4,7 +4,6 @@ class SimulatedBifurcationEngine(Enum): - """ Enum class that gathers the 4 variants of the Simulated Bifurcation algorithm: diff --git a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py index 30a04e5c..5dac332e 100644 --- a/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py +++ b/src/simulated_bifurcation/optimizer/simulated_bifurcation_optimizer.py @@ -27,7 +27,6 @@ def __str__(self) -> str: class SimulatedBifurcationOptimizer: - """ The Simulated Bifurcation (SB) algorithm relies on Hamiltonian/quantum mechanics to find local minima of diff --git a/src/simulated_bifurcation/optimizer/symplectic_integrator.py b/src/simulated_bifurcation/optimizer/symplectic_integrator.py index 90e03849..c3b4e4c6 100644 --- a/src/simulated_bifurcation/optimizer/symplectic_integrator.py +++ b/src/simulated_bifurcation/optimizer/symplectic_integrator.py @@ -4,7 +4,6 @@ class SymplecticIntegrator: - """ Simulates the evolution of spins' momentum and position following the Hamiltonian quantum mechanics equations that drive the diff --git a/tests/core/test_quadratic_polynomial.py b/tests/core/test_quadratic_polynomial.py index 9c718dcf..193810f2 100644 --- a/tests/core/test_quadratic_polynomial.py +++ b/tests/core/test_quadratic_polynomial.py @@ -20,16 +20,7 @@ def test_build_polynomial_from_expression(): x, y, z = symbols("x y z") expression = poly( - x**2 - + 2 * y**2 - + 3 * z**2 - - 2 * x * y - - x * z - - 3 * y * z - - x - - 2 * y - + z - + 2 + x**2 + 2 * y**2 + 3 * z**2 - 2 * x * y - x * z - 3 * y * z - x - 2 * y + z + 2 ) # Valid definitions diff --git a/tests/polynomial/test_polynomial_map.py b/tests/polynomial/test_polynomial_map.py index 40b58190..bd29af51 100644 --- a/tests/polynomial/test_polynomial_map.py +++ b/tests/polynomial/test_polynomial_map.py @@ -167,16 +167,7 @@ def test_init_polynomial_map_with_inconsistent_dimension_raises_error(): def test_init_polynomial_map_from_expression(): x, y, z = symbols("x y z") expression = poly( - x**2 - + 2 * y**2 - + 3 * z**2 - - 2 * x * y - - x * z - - 3 * y * z - - x - - 2 * y - + z - + 2 + x**2 + 2 * y**2 + 3 * z**2 - 2 * x * y - x * z - 3 * y * z - x - 2 * y + z + 2 ) polynomial_map = PolynomialMap.from_expression(expression) assert_expected_polynomial_map(polynomial_map) From 2af52d91f9243c24e43153fe2511bf7de49d71df Mon Sep 17 00:00:00 2001 From: Thomas Bouquet Date: Sun, 29 Sep 2024 03:31:36 +0200 Subject: [PATCH 12/12] revert class definition --- src/simulated_bifurcation/optimizer/convergence_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulated_bifurcation/optimizer/convergence_checker.py b/src/simulated_bifurcation/optimizer/convergence_checker.py index 7cf04c48..d787076a 100644 --- a/src/simulated_bifurcation/optimizer/convergence_checker.py +++ b/src/simulated_bifurcation/optimizer/convergence_checker.py @@ -2,7 +2,7 @@ from tqdm import tqdm -class ConvergenceChecker(object): +class ConvergenceChecker: """ Optimization tool to monitor agents bifurcation and convergence for the Simulated Bifurcation (SB) algorithm. Allows an early stopping of the iterations and saves