From 48e5ff5015b0d1ed69c3689134355fc38f71fd1b Mon Sep 17 00:00:00 2001 From: Kenneth Jao Date: Wed, 29 Sep 2021 04:11:08 -0400 Subject: [PATCH] Changed adaptive stepping and non-saving Flow simulations no longer store frames --- docs/source/usage.rst | 8 +-- scripts/width_diagrams.py | 1 + squish/_squish/voronoi_dcel.pyx | 17 ------- squish/simulation.py | 86 +++++++++++++++------------------ squish/squish.py | 12 ++--- 5 files changed, 50 insertions(+), 74 deletions(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index a750b8c..e6f96b7 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -56,7 +56,7 @@ There are currently three simulation modes, and the configuration changes slight Flow """" -This mode simulates the relaxing of the objects to its equilibrium. The threshold is the sufficient condition where the gradient is sufficiently close to zero. Specifically, the simulation stops when the L1 norm of the gradient divided by the number of objects is less than the threshold. The `step-size` parameter only represents the *initial* step size. If ``accel`` is set to ``true``, then adaptive step size is used to make convergence faster. +This mode simulates the relaxing of the objects to its equilibrium. The threshold is the sufficient condition where the gradient is sufficiently close to zero. Specifically, the simulation stops when the L1 norm of the gradient divided by the number of objects is less than the threshold. The `step-size` parameter only represents the *initial* step size. If ``adaptive`` is set to ``true``, then adaptive step size is used to make convergence faster. .. code-block:: json @@ -68,7 +68,7 @@ This mode simulates the relaxing of the objects to its equilibrium. The threshol "mode": "flow", "step_size": 0.05, "threshold": 0.0001, - "accel": true + "adaptive": true }, ... } @@ -87,7 +87,7 @@ This mode searches for equilibrium until `eq_stop_count` equilibria are found. A "mode": "search", "step_size": 0.05, "threshold": 0.0001, - "accel": true, + "adaptive": true, "eq_stop_count": 100, "manifold_step_size": 0.1 }, @@ -109,7 +109,7 @@ This mode simulates the the change in the equilibrium as the width is decreased. "mode": "shrink", "step_size": 0.05, "threshold": 0.0001, - "accel": true, + "adaptive": true, "width_change": 1, "width_stop": 0.3 }, diff --git a/scripts/width_diagrams.py b/scripts/width_diagrams.py index 5ca8298..4aa0114 100644 --- a/scripts/width_diagrams.py +++ b/scripts/width_diagrams.py @@ -10,6 +10,7 @@ import squish.ordered as order from squish import Simulation, DomainParams from squish.common import OUTPUT_DIR + def order_process(domain: DomainParams) -> Tuple[float, float, float]: energies = [] configs = order.configurations(domain) diff --git a/squish/_squish/voronoi_dcel.pyx b/squish/_squish/voronoi_dcel.pyx index 5ec5fb7..885eac9 100644 --- a/squish/_squish/voronoi_dcel.pyx +++ b/squish/_squish/voronoi_dcel.pyx @@ -658,24 +658,7 @@ cdef class VoronoiContainer: ).gradient return (step/2)*(k1+k2), k1 - # k1 = self.gradient - # k2 = self.__class__(self.n, self.w, self.h, self.r, - # self.add_sites(step*k1/2) - # ).gradient - - # lower = step*(-k1+ 2*k2) - # k3 = self.__class__(self.n, self.w, self.h, self.r, - # self.add_sites(lower) - # ).gradient - - # higher = (step/6)*(k1+2*k2+k3) - - - #new_sites = self.add_sites(higher) - #error = higher - lower - - #return higher, k1 def hessian(self, d: float) -> np.ndarray: """ diff --git a/squish/simulation.py b/squish/simulation.py index 9b4d945..caafa6a 100644 --- a/squish/simulation.py +++ b/squish/simulation.py @@ -2,6 +2,7 @@ from __future__ import annotations from typing import Optional import pickle, numpy as np +from math import log10 from scipy.linalg import null_space from timeit import default_timer as timer @@ -149,18 +150,18 @@ class Flow(Simulation): frames (List[VoronoiContainer]): stores frames of the simulation. step_size (float): size fo step by for each iteration. thres (float): threshold for the stopping condition. - accel (bool): set to True if accelerated stepping is desired. + adaptive (bool): set to True if adaptive stepping is desired. """ - __slots__ = ['step_size', 'thres', 'accel'] + __slots__ = ['step_size', 'thres', 'adaptive'] attr_str = "flow" title_str = "Flow" def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, - accel: bool, name: Optional[str] = None) -> None: + adaptive: bool, name: Optional[str] = None) -> None: super().__init__(domain, energy, name=name) - self.step_size, self.thres, self.accel = step_size, thres, accel + self.step_size, self.thres, self.adaptive = step_size, thres, adaptive @property @@ -171,7 +172,7 @@ class Flow(Simulation): "energy": self.energy.attr_str, "step_size": self.step_size, "thres": self.thres, - "accel": self.accel + "adaptive": self.adaptive } return info @@ -181,49 +182,40 @@ class Flow(Simulation): if save: self.save(self.initial_data, True) if len(self) == 0: self.add_frame() - i, grad_norm = 0, float('inf') + i, stop = 0, False - trial = 2 - while grad_norm > self.thres: # Get to threshold. - if save: self.save(self.frame_data(i)) + while not stop: # Get to threshold. + if save: + self.save(self.frame_data(i)) + frame = self[i] + else: + frame = self[0] # Iterate and generate next frame using RK-2 start = timer() - change, grad = self[i].iterate(self.step_size) - new_frame = self.energy.mode(*self.domain, self[i].add_sites(change)) + change, grad = frame.iterate(self.step_size) + + new_frame = self.energy.mode(*self.domain, frame.add_sites(change)) grad_norm = np.linalg.norm(grad) end = timer() - if self.accel: - if new_frame.energy < self[i].energy: # If energy decreases. - if trial < 10: # Try increasing step size for 10 times. - factor = 1 + .1**trial + if self.adaptive: + error = change - grad*self.step_size + tol = 10**min(-3, -2+log10(grad_norm)) - test_frame = self.energy.mode(*self.domain, - self[i].add_sites(change*factor)) - # If increased step has less energy than original step. - if test_frame.energy < new_frame.energy: - self.step_size *= factor - trial = max(2, trial-1) - new_frame = test_frame - else: # Otherwise, increases trials, and use original. - trial += 1 - else: # Step size too large, decrease and reset trial counter. - trial = 2 - shrink_factor = 1.5 - new_frame = self.energy.mode(*self.domain, - self[i].add_sites(change/shrink_factor)) - self.step_size /= shrink_factor + self.step_size *= (tol/np.linalg.norm(error))**.5 - self.step_size = max(10e-6, self.step_size) + if not save: + del self.frames[0] self.frames.append(new_frame) + stop = grad_norm < self.thres i += 1 - if(log and i % log_steps == 0): - print(f'Iteration: {i:05} | Energy: {self[i].energy: .5f}' + \ - f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \ - f'Time: {end-start: .3f}', flush=True) + if (log and i % log_steps == 0) or stop: + print(f'Iteration: {i:05} | Energy: {frame.energy: .5f}' + \ + f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \ + f'Time: {end-start: .3f} |', flush=True) @@ -237,21 +229,21 @@ class Search(Simulation): frames (List[VoronoiContainer]): stores frames of the simulation. step_size (float): size fo step by for each iteration. thres (float): threshold for the stopping condition. - accel (bool): set to True if accelerated stepping is desired. + adaptive (bool): set to True if adaptive stepping is desired. kernel_step (float): size to step on manifold if nullity of hessian > 2. count (int): number of equilibria to find. """ - __slots__ = ['step_size', 'thres', 'accel', 'kernel_step', 'count'] + __slots__ = ['step_size', 'thres', 'adaptive', 'kernel_step', 'count'] attr_str = "search" title_str = "Search" def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, - accel: bool, kernel_step: float, count: int, + adaptive: bool, kernel_step: float, count: int, name: Optional[str] = None) -> None: super().__init__(domain, energy, name=name) - self.step_size, self.thres, self.accel = step_size, thres, accel + self.step_size, self.thres, self.adaptive = step_size, thres, adaptive self.kernel_step, self.count = kernel_step, count @@ -263,7 +255,7 @@ class Search(Simulation): "energy": self.energy.attr_str, "step_size": self.step_size, "thres": self.thres, - "accel": self.accel, + "adaptive": self.adaptive, "kernel_step": self.kernel_step, "count": self.count } @@ -282,7 +274,7 @@ class Search(Simulation): for i in range(self.count): # Get to equilibrium. - sim = Flow(self.domain, self.energy, self.step_size, self.thres, self.accel) + sim = Flow(self.domain, self.energy, self.step_size, self.thres, self.adaptive) sim.add_frame(new_sites) sim.run(False, log, log_steps) @@ -317,22 +309,22 @@ class Shrink(Simulation): frames (List[VoronoiContainer]): stores frames of the simulation. step_size (float): size fo step by for each iteration. thres (float): threshold for the stopping condition. - accel (bool): set to True if accelerated stepping is desired. + adaptive (bool): set to True if adaptive stepping is desired. delta (float): percent to change w each iteration. stop_width (float): percent at which to stop iterating. """ - __slots__ = ['step_size', 'thres', 'accel', 'delta', 'stop_width'] + __slots__ = ['step_size', 'thres', 'adaptive', 'delta', 'stop_width'] attr_str = "shrink" title_str = "Shrink" def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, - accel: bool, delta: float, stop_width: float, + adaptive: bool, delta: float, stop_width: float, name: Optional[str] = None) -> None: super().__init__(domain, energy, name=name) - self.step_size, self.thres, self.accel = step_size, thres, accel + self.step_size, self.thres, self.adaptive = step_size, thres, adaptive self.delta, self.stop_width = self.domain.w*delta/100, self.domain.w*stop_width @@ -344,7 +336,7 @@ class Shrink(Simulation): "energy": self.energy.attr_str, "step_size": self.step_size, "thres": self.thres, - "accel": self.accel, + "adaptive": self.adaptive, "delta": self.delta, "stop_width": self.stop_width } @@ -366,7 +358,7 @@ class Shrink(Simulation): while width >= self.stop_width: # Get to equilibrium. new_domain = DomainParams(self.domain.n, width, self.domain.h, self.domain.r) - sim = Flow(new_domain, self.energy, self.step_size, self.thres, self.accel) + sim = Flow(new_domain, self.energy, self.step_size, self.thres, self.adaptive) sim.add_frame(new_sites) sim.run(False, log, log_steps) new_sites = sim[-1].site_arr diff --git a/squish/squish.py b/squish/squish.py index ab84eff..7487891 100644 --- a/squish/squish.py +++ b/squish/squish.py @@ -96,28 +96,28 @@ def config_sim(args): else: points = np.asarray(dmn_params["points"]) - check_params(sim_params, ["mode", "step_size", "threshold", "save_sim", "accel"], { + check_params(sim_params, ["mode", "step_size", "threshold", "save_sim", "adaptive"], { "mode": ["flow", "search", "shrink"], "step_size": "positive", "threshold": "positive", }) - mode, step, thres, accel, save_sim = sim_params["mode"], sim_params["step_size"], \ - sim_params["threshold"], sim_params["accel"], \ + mode, step, thres, adaptive, save_sim = sim_params["mode"], sim_params["step_size"], \ + sim_params["threshold"], sim_params["adaptive"], \ sim_params["save_sim"] name = sim_params.get("name") if mode == "flow": - sim = Flow(domain, energy, step, thres, accel, name=name) + sim = Flow(domain, energy, step, thres, adaptive, name=name) elif mode == "search": check_params(sim_params, ["manifold_step_size", "eq_stop_count"], { "manifold_step_size": "positive", "eq_stop_count": "positive" }) - sim = Search(domain, energy, step, thres, accel, sim_params["manifold_step_size"], + sim = Search(domain, energy, step, thres, adaptive, sim_params["manifold_step_size"], sim_params["eq_stop_count"], name=name) elif mode == "shrink": check_params(sim_params, ["width_change", "width_stop"], { "width_change": "positive", "width_stop": "positive" }) - sim = Shrink(domain, energy, step, thres, accel, sim_params["width_change"], + sim = Shrink(domain, energy, step, thres, adaptive, sim_params["width_change"], sim_params["width_stop"], name=name) save_diagram = False