Changed diagram.py so that parallelization doesn't use excess memory, and other small tweaks

This commit is contained in:
Kenneth Jao 2021-09-27 16:28:10 -04:00
parent 475d306e3a
commit 4b472d2200
4 changed files with 52 additions and 31 deletions

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = squish name = squish
version = 0.1.1 version = 0.1.2
author = Kenneth Jao author = Kenneth Jao
author_email = ksjdragon@gmail.com author_email = ksjdragon@gmail.com
description = squish is Python program which perform simulations for the flow of 'soft' or 'compressible' objects under some energy in a periodic domain. description = squish is Python program which perform simulations for the flow of 'soft' or 'compressible' objects under some energy in a periodic domain.

View File

@ -15,11 +15,12 @@ STR_TO_ENERGY = {
} }
def generate_filepath(sim: SimulationMode, fol: Union[str, Path]) -> Path: def generate_filepath(sim: SimulationMode, fol: Union[str, Path], prec: int = 2) -> Path:
energy = sim.energy.title_str energy = sim.energy.title_str
width, height = round(sim.domain.w, 2), round(sim.domain.h, 2) width, height = round(sim.domain.w, 2), round(sim.domain.h, 2)
base_path = f"{fol}/{energy}{sim.title_str} - N{sim.domain.n} - {width:.2f}x{height:.2f}" base_path = f"{fol}/{energy}{sim.title_str} - " + \
f"N{sim.domain.n} - {width:.{prec}f}x{height:.{prec}f}"
i = 1 i = 1
real_path = Path(base_path) real_path = Path(base_path)

View File

@ -26,17 +26,30 @@ class SimData:
def __init__(self, sim: Simulation) -> None: def __init__(self, sim: Simulation) -> None:
self.path = sim.path if sim is not None:
self.domains = list([DomainParams(s.n, s.w, s.h, s.r) for s in sim]) self.path = sim.path
self.energies = list([s.energy for s in sim]) self.domains = list([DomainParams(s.n, s.w, s.h, s.r) for s in sim])
self.voronois = list([s.vor_data for s in sim]) self.energies = list([s.energy for s in sim])
self.stats = list([s.stats for s in sim]) self.voronois = list([s.vor_data for s in sim])
self.stats = list([s.stats for s in sim])
def __len__(self) -> int: def __len__(self) -> int:
return len(self.domains) return len(self.domains)
def slice(self, indices: List[int]) -> SimData:
new_data = SimData(None)
new_data.path = self.path
new_data.domains = list([self.domains[i] for i in indices])
new_data.energies = list([self.energies[i] for i in indices])
new_data.voronois = list([self.voronois[i] for i in indices])
new_data.stats = list([self.stats[i] for i in indices])
return new_data
def hist(self, stat: str, i: int, bins: int = 10, bounds: Optional[Tuple[float, float]] = None, def hist(self, stat: str, i: int, bins: int = 10, bounds: Optional[Tuple[float, float]] = None,
cumul: bool = False, avg: bool = False) -> Tuple[numpy.ndarray, numpy.ndarray]: cumul: bool = False, avg: bool = False) -> Tuple[numpy.ndarray, numpy.ndarray]:
"""Generates a histogram from the selected data. """Generates a histogram from the selected data.
@ -95,7 +108,7 @@ class Diagram:
self.cumulative = cumulative self.cumulative = cumulative
def generate_frame(self, frame: int, mode: str, fol: str) -> None: def generate_frame(self, frame: int, mode: str, fol: str, name: str = None) -> None:
if mode not in ["save", "open"]: if mode not in ["save", "open"]:
raise ValueError("Not a valid mode for diagrams!") raise ValueError("Not a valid mode for diagrams!")
@ -112,9 +125,11 @@ class Diagram:
getattr(self, str(diagram) + '_plot')(frame, axes[it.multi_index]) getattr(self, str(diagram) + '_plot')(frame, axes[it.multi_index])
plt.tight_layout() plt.tight_layout()
if name is None:
name = f"img{frame:05}.png"
if mode == "save": if mode == "save":
plt.savefig(self.sim.path / fol / f"img{frame:05}.png") plt.savefig(self.sim.path / fol / name)
plt.close(fig) plt.close(fig)
elif mode == "show": elif mode == "show":
plt.show() plt.show()
@ -290,9 +305,13 @@ class Diagram:
(self.sim.path / fol).mkdir(exist_ok=True) (self.sim.path / fol).mkdir(exist_ok=True)
combo_list = [] combo_list = []
for i in range(cpu_count()): for i in range(cpu_count()):
combo_list.append((self, frames[:int((i+1)*len(frames)/cpu_count())], start, end = int(i*len(frames)/cpu_count()), int((i+1)*len(frames)/cpu_count())
fol, len(frames))) new_dia = Diagram(None, self.diagrams, self.cumulative)
new_dia.sim = self.sim.slice(frames[start:end])
combo_list.append((new_dia, fol, start, len(frames[start:end]), len(frames)))
# Free up memory, since it's already duplicated to other cores.
self.sim = self.sim.slice([])
with Pool(cpu_count()) as pool: with Pool(cpu_count()) as pool:
for _ in pool.imap_unordered(render_frame_range, combo_list): for _ in pool.imap_unordered(render_frame_range, combo_list):
pass pass
@ -300,6 +319,7 @@ class Diagram:
print(flush=True) print(flush=True)
print(f'Wrote to \"{self.sim.path / fol}\".', flush=True) print(f'Wrote to \"{self.sim.path / fol}\".', flush=True)
def render_video(self, time: int, mode: str) -> None: def render_video(self, time: int, mode: str) -> None:
if mode not in ["use_all", "sample"]: if mode not in ["use_all", "sample"]:
raise ValueError("Not a valid mode for videos!") raise ValueError("Not a valid mode for videos!")
@ -308,12 +328,11 @@ class Diagram:
frames = list(range(len(self.sim))) frames = list(range(len(self.sim)))
elif mode == "sample": elif mode == "sample":
fps = 30 fps = 30
if len(self.sim) < fps*time : if len(self.sim) < fps*time:
frames = list(range(len(self.sim))) frames = list(range(len(self.sim)))
fps = len(self.sim)/time fps = len(self.sim)/time
else: else:
frames = list(np.round(np.linspace(0, len(self.sim), fps*time)).astype(int)) frames = list(np.round(np.linspace(0, len(self.sim)-1, fps*time)).astype(int))
print(frames)
self.render_frames(frames, 'temp') self.render_frames(frames, 'temp')
path = self.sim.path / 'simulation.mp4' path = self.sim.path / 'simulation.mp4'
@ -325,17 +344,17 @@ class Diagram:
f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"') f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"')
# Remove files. # Remove files.
for i in frames: for i in range(len(frames)):
os.remove(self.sim.path / f"temp/img{i:05}.png") os.remove(self.sim.path / f"temp/img{i:05}.png")
os.rmdir(self.sim.path / 'temp') os.rmdir(self.sim.path / 'temp')
print(f'Wrote to \"{path}\".', flush=True) print(f'Wrote to \"{path}\".', flush=True)
def render_frame_range(combo: Tuple[Diagram, List[int], str, int]) -> None: def render_frame_range(combo: Tuple[Diagram, str, int, int, int]) -> None:
self, frames, fol, num_frames = combo self, fol, offset, length, num_frames = combo
for frame in frames: for i in range(length):
self.generate_frame(frame, "save", fol) self.generate_frame(i, "save", fol, f"img{i+offset:05}.png")
i = len(list((self.sim.path / fol).iterdir())) i = len(list((self.sim.path / fol).iterdir()))
hashes = int(21*i/num_frames) hashes = int(21*i/num_frames)
print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \ print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \

View File

@ -21,12 +21,15 @@ class Simulation:
__slots__ = ['domain', 'energy', 'path', 'frames'] __slots__ = ['domain', 'energy', 'path', 'frames']
def __init__(self, domain: DomainParams, energy: Energy, name: Optional[str] = None) -> None: def __init__(self, domain: DomainParams, energy: Energy, \
name: Optional[str, int] = None) -> None:
self.domain, self.energy = domain, energy self.domain, self.energy = domain, energy
self.frames = [] self.frames = []
if name is None: if name is None:
self.path = generate_filepath(self, OUTPUT_DIR) self.path = generate_filepath(self, OUTPUT_DIR)
elif isinstance(name, int):
self.path = generate_filepath(self, OUTPUT_DIR, name)
else: else:
self.path = OUTPUT_DIR / name self.path = OUTPUT_DIR / name
@ -86,16 +89,16 @@ class Simulation:
return distinct_count return distinct_count
def save(self, info: Dict) -> None: def save(self, info: Dict, overwrite: bool = False) -> None:
self.path.mkdir(exist_ok=True) self.path.mkdir(exist_ok=True)
path = self.path / 'data.squish' path = self.path / 'data.squish'
with open(path, 'ab') as out: with open(path, 'wb' if overwrite else 'ab') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL) pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
def save_all(self) -> None: def save_all(self) -> None:
self.save(self.initial_data) self.save(self.initial_data, True)
for i in range(len(self.frames)): for i in range(len(self.frames)):
self.save(self.frame_data(i)) self.save(self.frame_data(i))
@ -175,7 +178,7 @@ class Flow(Simulation):
def run(self, save: bool, log: bool, log_steps: int) -> None: def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f"Find - {self.domain}", flush=True) if log: print(f"Find - {self.domain}", flush=True)
if save: self.save(self.initial_data) if save: self.save(self.initial_data, True)
if len(self) == 0: self.add_frame() if len(self) == 0: self.add_frame()
i, grad_norm = 0, float('inf') i, grad_norm = 0, float('inf')
@ -212,7 +215,7 @@ class Flow(Simulation):
self[i].add_sites(change/shrink_factor)) self[i].add_sites(change/shrink_factor))
self.step_size /= shrink_factor self.step_size /= shrink_factor
self.step_size = max(10e-4, self.step_size) self.step_size = max(10e-6, self.step_size)
self.frames.append(new_frame) self.frames.append(new_frame)
@ -269,7 +272,7 @@ class Search(Simulation):
def run(self, save: bool, log: bool, log_steps: int) -> None: def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f'Travel - {self.domain}', flush=True) if log: print(f'Travel - {self.domain}', flush=True)
if save: self.save(self.initial_data) if save: self.save(self.initial_data, True)
if len(self) != 0: if len(self) != 0:
new_sites = self[0].site_arr new_sites = self[0].site_arr
@ -350,7 +353,7 @@ class Shrink(Simulation):
def run(self, save: bool, log: bool, log_steps: int) -> None: def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f'Shrink - {self.domain}', flush=True) if log: print(f'Shrink - {self.domain}', flush=True)
if save: self.save(self.initial_data) if save: self.save(self.initial_data, True)
if len(self) != 0: if len(self) != 0:
new_sites = self[0].site_arr new_sites = self[0].site_arr
@ -381,5 +384,3 @@ STR_TO_SIM = {
"search": Search, "search": Search,
"shrink": Shrink "shrink": Shrink
} }
simulation = Simulation