Made diagram.py work properly, and add multiprocessing to frame rendering.

This commit is contained in:
Kenneth Jao 2021-09-24 04:11:52 -04:00
parent 989ef93f29
commit 7e5e711533
7 changed files with 249 additions and 239 deletions

View File

@ -144,7 +144,7 @@ It's also possible to visualize the data, but this section is **optional**. It's
.. note::
Rendering as an `mp4` requires ``ffmpeg`` to be installed on your system.
While it's possible to customize the figures that are drawn and outputted, there are already a few preset modes: `animate`, `energy`, `stats`, and `eigs`, which outputs the simulation steps as a video, a video with the graph of energy, compiled statistics, and the eigenvalues, respectively.
While it's possible to customize the figures that are drawn and outputted, there are already a few preset modes: `animate`, `energy`, `stats`, and `eigs`, which outputs the simulation steps as a video, a video with the graph of energy, compiled statistics, and the eigenvalues, respectively. Lastly, you can optionally specify a time for the video, which is default 30 seconds.
.. code-block:: json
:force:
@ -154,6 +154,7 @@ While it's possible to customize the figures that are drawn and outputted, there
"diagram": {
"filetype": "mp4",
"figures": "animate",
"time": 30
}
}

View File

@ -1,10 +1,33 @@
alabaster==0.7.12
Babel==2.9.1
certifi==2021.5.30
charset-normalizer==2.0.6
cycler==0.10.0
Cython==0.29.24
kiwisolver==1.3.1
docutils==0.17.1
idna==3.2
imagesize==1.2.0
Jinja2==3.0.1
kiwisolver==1.3.2
MarkupSafe==2.0.1
matplotlib==3.4.3
numpy==1.21.2
Pillow==8.3.1
packaging==21.0
Pillow==8.3.2
Pygments==2.10.0
pyparsing==2.4.7
python-dateutil==2.8.2
pytz==2021.1
requests==2.26.0
scipy==1.7.1
six==1.16.0
snowballstemmer==2.1.0
Sphinx==4.2.0
sphinx-rtd-theme==1.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
-e git+git@github.com:ksjdragon/packsim.git@e2f25182310c1f9a950df55c0219165366466e9b#egg=squish
urllib3==1.26.6

View File

@ -1,2 +1,2 @@
from .common import DomainParams, Energy, Simulation
#from .simulation import Diagram as Diagram
from .diagram import Diagram

View File

@ -4,6 +4,9 @@ import pickle, numpy as np
from pathlib import Path
from _squish import AreaEnergy, RadialALEnergy, RadialTEnergy
OUTPUT_DIR = Path("squish_output")
OUTPUT_DIR.mkdir(exist_ok=True)
STR_TO_ENERGY = {
"area": AreaEnergy,
"radial-al": RadialALEnergy,
@ -11,23 +14,17 @@ STR_TO_ENERGY = {
}
def generate_filepath(sim: SimulationMode, ext: str, fol: str) -> Path:
def generate_filepath(sim: SimulationMode, fol: Union[str, Path]) -> Path:
energy = sim.energy.title_str
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}"
i = 1
if ext == "folder":
real_path = Path(base_path)
while real_path.is_dir():
real_path = Path(f"{base_path}({i})")
i += 1
else:
real_path = Path(f"{base_path}.{ext}")
while real_path.is_file():
real_path = Path(f"{base_path}({i}).{ext}")
i += 1
real_path = Path(base_path)
while real_path.is_dir():
real_path = Path(f"{base_path}({i})")
i += 1
return real_path
@ -125,10 +122,15 @@ class Simulation:
self.frames = []
if name is None:
self.path = generate_filepath(self, "sim", "simulations")
self.path = generate_filepath(self, OUTPUT_DIR)
else:
self.path = Path(f"simulations/{name}.sim")
self.path = OUTPUT_DIR / name
self.path.mkdir()
def __iter__(self) -> Iterator:
return iter(self.frames)
def __getitem__(self, key: int) -> Energy:
return self.frames[key]
@ -151,49 +153,6 @@ class Simulation:
self.frames.append(self.energy.mode(*self.domain, points % self.domain.dim))
def generate_bar_info(self, stat: str, i: int, cumulative: bool, bins: int = 10,
bounds: Tuple[float] = None, avg: bool = False, reg = None) -> Tuple:
"""
Gets the bar info for matplotlib from the ith to jth frame.
:param stat: [str] name of statistic to obtain.
:param i: [int] frame to obtain
:param cumulative: [bool] Will obtain all stats up to the ith frame if True.
:param bins: [int] number of bins for the bar graph.
:param bound: [Tuple[float]] lower and upper bounds for the bins. If not set,
automatically take the min and max value.
:param avg: [bool] Averages the counts over the number of frames if True.
:param mark: If not None, set a specific marker.
:return: [Tuple] returns a tuple of labels, values, and colors.
"""
if cumulative:
values = np.concatenate([f.stats[stat] for f in self.frames[:(i+1)]])
else:
values = self.frames[i].stats[stat]
#bins = 9
if np.var(values) <= 1e-8:
hist = np.zeros((bins,))
val = np.average(values)
hist[(bins+1) // 2 - 1] = len(values)
bin_list = np.linspace(0, val, bins//2+1, endpoint=True)
bin_list = np.concatenate((bin_list, (bin_list+val)[1:]))
return hist, bin_list[not (bins%2):]
hist, bin_edges = np.histogram(values, bins=bins, range=bounds)
bin_list = [(bin_edges[i] + bin_edges[i+1])/2 for i in range(len(bin_edges)-1)]
if avg and cumulative:
return hist / (i+1), bin_list
return hist, bin_list
# colors = ["C0"]*bins
# if reg >= lb and reg <= ub:
# colors[int((reg-lb)*bins/diff)] = "C3"
# return (labels, count, colors)
def get_distinct(self) -> List[int]:
"""Gets the distinct configurations based on the average radii of the sites.
and returns the number of configurations for each distinct configuration.
@ -218,7 +177,14 @@ class Simulation:
return distinct_count
def save_frame(self, index: int) -> None:
def save(self, info: Dict) -> None:
path = self.path / 'data.squish'
with open(path, 'wb') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
def frame_data(self, index: int) -> None:
f = self[index]
info = {
"arr": f.site_arr,
@ -226,9 +192,7 @@ class Simulation:
"energy": f.attr_str,
"stats": f.stats
}
with open(self.path, 'ab') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
return info
# all_info = []
# for frame in self.frames:

View File

@ -1,40 +1,104 @@
from __future__ import annotations
from typing import Tuple, List
from typing import Tuple, List, Optional
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt, numpy as np, os
from matplotlib.ticker import MaxNLocator, FormatStrFormatter
import os, math, random, time, pickle, scipy, numpy as np
from timeit import default_timer as timer
from scipy.spatial import Voronoi, voronoi_plot_2d
from multiprocessing import Pool, cpu_count
INT = np.int64
FLOAT = np.float64
from .common import DomainParams
SYMM = np.array([[1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1], [0,-1], [1,-1]])
class SimData:
"""Stores diagram information for a simulation.
Attributes:
path (Path): path to output directory.
domains (List[DomainParams]): domain parameters from simulation frames,
energies (List[float]): energy from simulation frames.
voronois (List[Voronoi]): voronoi information from scipy from simulation frames.
stats (List[numpy.ndarray]): statistics from simulation frames.
class Diagram():
"""
Class for generating diagrams.
:param sim: [Simulation] Simulation class containing dynamics.
:param diagrams: [np.ndarray] selects which diagrams to show.
__slots__ = ['path', 'domains', 'energies', 'voronois', 'stats']
def __init__(self, sim: Simulation) -> None:
self.path = sim.path
self.domains = list([DomainParams(s.n, s.w, s.h, s.r) for s in sim])
self.energies = list([s.energy 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:
return len(self.domains)
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]:
"""Generates a histogram from the selected data.
Arguments:
stat (str): name of data to obtain
i (int): which frame to select from
bins (int): number of bins for the histogram.
bounds (Optional[Tuple[float, float]]): upper and lower bounds of the histogram.
this will automatically take the minimum and maximum value of not set.
cumul (bool): aggregates all data up to frame i if True.
avg (bool): will average the data based on number of frames if True.
Returns:
Tuple[numpy.ndarray, numpy.ndarray]: the histogram and its bins.
"""
if cumulative:
values = np.concatenate([f[stat] for f in self.stats[:(i+1)]])
else:
values = self.stats[i][stat]
if np.var(values) <= 1e-8:
hist = np.zeros((bins,))
val = np.average(values)
hist[(bins+1) // 2 - 1] = len(values)
bin_list = np.linspace(0, val, bins//2+1, endpoint=True)
bin_list = np.concatenate((bin_list, (bin_list+val)[1:]))
return hist, bin_list[not (bins%2):]
hist, bin_edges = np.histogram(values, bins=bins, range=bounds)
bin_list = np.array([(bin_edges[i] + bin_edges[i+1])/2 for i in range(len(bin_edges)-1)])
if avg and cumulative:
return hist / (i+1), bin_list
return hist, bin_list
class Diagram:
"""Class for generating diagrams.
Attributes:
sim (SimData): the simulation data that contains all the frames and information.
diagrams (numpy.ndarray): array that selects which diagrams to show.
cumulative (bool): selects whether or not graph statistics are cumulative.
"""
__slots__ = ['sim', 'diagrams', 'cumulative']
def __init__(self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = True):
self.sim = sim
def __init__(self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = False) -> None:
self.sim = SimData(sim)
self.diagrams = np.atleast_2d(diagrams)
self.cumulative = cumulative
def generate_frame(self, frame: int):
"""
Generates one frame for the plot.
:param frame: [int] frame index to draw.
:param scale: [float] how much of the domain to draw.
:param area: [bool] set to false to not label areas.
:param only: [bool] set to True to only render diagram.
"""
def generate_frame(self, frame: int, mode: str, fol: str) -> None:
if mode not in ["save", "open"]:
raise ValueError("Not a valid mode for diagrams!")
shape = self.diagrams.shape
fig, axes = plt.subplots(*shape, figsize=(shape[1]*8, shape[0]*8))
if self.diagrams.shape == (1,1):
@ -49,14 +113,20 @@ class Diagram():
plt.tight_layout()
if mode == "save":
plt.savefig(self.sim.path / fol / f"img{frame:05}.png")
plt.close(fig)
elif mode == "show":
plt.show()
def voronoi_plot(self, i: int, ax):
n,w,h = self.sim[i].n, self.sim[i].w, self.sim[i].h
def voronoi_plot(self, i: int, ax: AxesSubplot) -> None:
domain = self.sim.domains[i]
n,w,h = domain.n, domain.w, domain.h
scale = 1.5
area = n <= 60
scipy.spatial.voronoi_plot_2d(self.sim[i].vor_data, ax, show_vertices=False,
point_size = 7-n/100)
voronoi_plot_2d(self.sim.voronois[i], ax, show_vertices=False, point_size = 7-n/100)
ax.plot([-w, 2*w], [0, 0], 'r')
ax.plot([-w, 2*w], [h, h], 'r')
ax.plot([0,0], [-h, 2*h], 'r')
@ -68,30 +138,26 @@ class Diagram():
props = dict(boxstyle='round', facecolor='wheat', alpha=0.8)
# if area:
# global SYMM
# for site_index in range(n):
# for s in np.concatenate(([[0,0]], SYMM)):
# txt = ax.text(*(site.vec + s*self.sim[i].dim),
# str(round(site.cache("area"), 3)))
# txt.set_clip_on(True)
if area:
global SYMM
for j in range(n):
for s in np.concatenate(([[0,0]], SYMM)):
txt = ax.text(*(self.sim.voronois[i].points[j] + s*self.sim.domains[i].dim),
str(round(self.sim.stats[i]["site_areas"][j], 3)))
txt.set_clip_on(True)
ax.text(0.05, 0.95, f'Energy: {self.sim[i].energy}', transform=ax.transAxes, fontsize=14,
ax.text(0.05, 0.95, f'Energy: {self.sim.energies[i]}', transform=ax.transAxes, fontsize=14,
verticalalignment='top', bbox=props)
def energy_plot(self, i: int, ax):
def energy_plot(self, i: int, ax: AxesSubplot) -> None:
ax.set_xlim([0, len(self.sim)])
try:
ax.plot([0, len(self.sim)], [self.sim[i].minimum, self.sim[i].minimum], 'red')
except AttributeError:
pass
energies = [self.sim[j].energy for j in range(i+1)]
energies = self.sim.energies[:(i+1)]
ax.plot(list(range(i+1)), energies)
ax.title.set_text('Energy vs. Time')
max_value = round(self.sim[0].energy)
min_value = round(self.sim[-1].energy)
# max_value = round(self.sim[0].energy)
# min_value = round(self.sim[-1].energy)
#diff = max_value-min_value
#ax.set_yticks(np.arange(int(min_value-diff/5), int(max_value+diff/5), diff/25))
ax.set_xlabel("Iterations")
@ -99,10 +165,8 @@ class Diagram():
ax.grid()
def site_areas_plot(self, i: int, ax):
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
y, x = self.sim.generate_bar_info("site_areas", i, self.cumulative,
avg=True, reg=regular_area)
def site_areas_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_areas", i, cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Site Areas')
@ -116,9 +180,8 @@ class Diagram():
# xtick.set_color(color)
def site_edge_count_plot(self, i: int, ax):
y, x = self.sim.generate_bar_info("site_edge_count", i, self.cumulative,
bounds=(1, 11), avg=True)
def site_edge_count_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_edge_count", i, bounds=(1, 11), cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Edges per Site')
@ -129,13 +192,8 @@ class Diagram():
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def site_isos_plot(self, i: int, ax):
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
regular_edge = math.sqrt(2*regular_area/(3*math.sqrt(3)))
regular_isoparam = 4*math.pi*regular_area/(6*regular_edge)**2
y, x = self.sim.generate_bar_info("site_isos", i, self.cumulative, bounds=(0,1),
avg=True, reg=regular_isoparam)
def site_isos_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_isos", i, bounds=(0,1), cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Isoparametric Values')
@ -149,8 +207,8 @@ class Diagram():
# xtick.set_color(color)
def site_energies_plot(self, i: int, ax):
y, x = self.sim.generate_bar_info("site_energies", i, self.cumulative, avg=True)
def site_energies_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_energies", i, self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Site Energies')
@ -161,8 +219,9 @@ class Diagram():
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def avg_radius_plot(self, i: int, ax):
y, x = self.sim.generate_bar_info("avg_radius", i, self.cumulative, avg=True)
def avg_radius_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("avg_radius", i, self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Site Average Radii')
ax.set_xlabel("Average Radius")
@ -172,8 +231,8 @@ class Diagram():
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def isoparam_avg_plot(self, i: int, ax):
y, x = self.sim.generate_bar_info("isoparam_avg", i, self.cumulative, avg=True)
def isoparam_avg_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("isoparam_avg", i, self.cumulative, avg=True)
ax.bar(x,y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Site Isoperimetric Averages')
@ -184,11 +243,8 @@ class Diagram():
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def edge_lengths_plot(self, i: int, ax):
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
regular_edge = math.sqrt(2*regular_area/(3*math.sqrt(3)))
y, x = self.sim.generate_bar_info("edge_lengths", i, self.cumulative,
30, avg=True, reg=regular_edge)
def edge_lengths_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("edge_lengths", i, 30, cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8*(x[1]-x[0]))
ax.title.set_text('Edge Lengths')
@ -204,8 +260,8 @@ class Diagram():
# xtick.set_color(color)
def eigs_plot(self, i: int, ax):
eigs = self.sim[i].stats["eigs"]
def eigs_plot(self, i: int, ax: AxesSubplot) -> None:
eigs = self.sim.stats[i]["eigs"]
ax.plot(list(range(len(eigs))), eigs, marker='o', linestyle='dashed', color='C0')
ax.plot([0,len(eigs)], [0, 0], color="red")
ax.title.set_text('Hessian Eigenvalues')
@ -213,99 +269,64 @@ class Diagram():
ax.set_ylabel("Value")
def render_static(self, i: int, j: int = None, filename = None):
"""
Renders single frames.
:param filename: [str] name of file.
:param i: [int] index of frame to start rendering.
:param j: [j] index of frame to stop rendering.
:param only: [bool] set to True to only render diagram.
"""
if j is None:
j = len(self.sim)-1
def render_frames(self, frames: List[int], fol: str = 'frames') -> None:
(self.sim.path / fol).mkdir(exist_ok=True)
combo_list = []
print(cpu_count())
for i in range(cpu_count()):
combo_list.append((self, frames[:int((i+1)*len(frames)/cpu_count())],
fol, len(frames)))
length = j+1-i
if length == 1:
if filename is None:
path = gen_filepath(self.sim, "png")
else:
path = f'figures/{filename}.png'
with Pool(cpu_count()) as pool:
for _ in pool.imap_unordered(render_frame_range, combo_list):
pass
self.generate_frame(i)
plt.savefig(path)
plt.close()
print(f'Wrote to \"{path}\"')
else:
if filename is None:
path = gen_filepath(self.sim, "")
else:
path = f'figures/{filename}'
os.mkdir(path)
for frame in range(i, j+1):
self.generate_frame(frame)
hashes = int(21*i/(j+1))
print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {i+1}/{j+1} frames rendered.', flush=True, end='\r')
plt.savefig(f'{path}/img{frame:03}.png')
plt.close()
print(flush=True)
print(f'Wrote to folder \"{path}\"', flush=True)
def render_video(self, time = 30, fps = None, filename = None):
"""
Renders plot(s) into image.
:param scale: [float] how much of the domain to draw.
:param area: [bool] set to false to not label area.
:param filename: [str] name for static image.
:param fps: [float] fps for image.
:param only: [bool] set to True to only render diagram.
"""
if fps is None:
if type(self.sim) == Flow:
fps = min(len(self.sim)/time, 30)
else:
fps = 5
step = len(self.sim)/(fps*time) if fps == 30 else 1
# Iterate through desired frames.
try:
os.mkdir("figures/temp")
except FileExistsError:
pass
frames = min(len(self.sim), int(fps * time))
for j in range(frames):
self.generate_frame(int(j*step))
hashes = int(21*j/frames)
print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {j+1}/{frames} frames rendered.', flush=True, end='\r')
plt.savefig(f'figures/temp/img{j:03}.png')
plt.close()
# for i, frame in enumerate(frames):
# self.generate_frame(frame, "save", fol)
# hashes = int(21*i/len(frames))
# print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \
# f' {i+1}/{len(frames)} frames rendered.', flush=True, end='\r')
print(flush=True)
if filename is None:
path = gen_filepath(self.sim, "mp4")
else:
path = f'figures/{filename}.mp4'
def render_video(self, time: int, mode: str) -> None:
if mode not in ["use_all", "sample"]:
raise ValueError("Not a valid mode for videos!")
if mode == "use_all":
frames = list(range(len(self.sim)))
elif mode == "sample":
fps = 30
if len(self.sim) < fps*time :
frames = list(range(len(self.sim)))
fps = len(self.sim)/time
else:
frames = list(np.round(np.linspace(0, len(self.sim), fps*time)).astype(int))
print(frames)
self.render_frames(frames, 'temp')
path = self.sim.path / 'simulation.mp4'
# Convert to gif.
print("Assembling MP4...", flush=True)
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i figures/temp/img%03d.png' + \
f' -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -vf' + \
f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"')
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i' + \
f' \"{self.sim.path}/temp/img%05d.png\"' + \
f' -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -vf' + \
f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"')
# Remove files.
for j in range(frames):
os.remove(f'figures/temp/img{j:03}.png')
for i in frames:
os.remove(self.sim.path / f"temp/img{i:05}.png")
os.rmdir("figures/temp")
os.rmdir(self.sim.path / 'temp')
print(f'Wrote to \"{path}\".', flush=True)
def render_frame_range(combo: Tuple[Diagram, List[int], str, int]) -> None:
self, frames, fol, num_frames = combo
for frame in frames:
self.generate_frame(frame, "save", fol)
i = len(list((self.sim.path / fol).iterdir()))
hashes = int(21*i/num_frames)
print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {i}/{num_frames} frames rendered.', flush=True, end='\r')

View File

@ -32,29 +32,27 @@ class Flow(Simulation):
self.step_size, self.thres, self.accel = step_size, thres, accel
def save_initial(self) -> None:
@property
def initial_data(self) -> Dict:
info = {
"mode": self.attr_str,
"step_size": self.step_size,
"thres": self.thres,
"accel": self.accel
}
with open(self.path, 'wb') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
print("Created simulation file at:", self.path, flush=True)
return info
def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f"Find - {self.domain}", flush=True)
if save: self.save_initial()
if save: self.save(self.initial_data)
if len(self) == 0: self.add_frame()
i, grad_norm = 0, float('inf')
trial = 2
while grad_norm > self.thres: # Get to threshold.
if save: self.save_frame(i)
if save: self.save(self.frame_data(i))
# Iterate and generate next frame using RK-2
start = timer()
@ -124,7 +122,8 @@ class Search(Simulation):
self.kernel_step, self.count = kernel_step, count
def save_initial(self) -> None:
@property
def initial_data(self) -> Dict:
info = {
"mode": self.attr_str,
"step_size": self.step_size,
@ -133,15 +132,12 @@ class Search(Simulation):
"kernel_step": self.kernel_step,
"count": self.count
}
with open(self.path, 'wb') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
print("Created simulation file at:", self.path, flush=True)
return info
def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f'Travel - {self.domain}', flush=True)
if save: self.save_initial()
if save: self.save(self.initial_data)
if len(self) != 0:
new_sites = self[0].site_arr
@ -156,7 +152,7 @@ class Search(Simulation):
sim.run(False, log, log_steps)
self.frames.append(sim[-1])
if save: self.save_frame(i)
if save: self.save(self.frame_data(i))
if log: print(f'Equilibrium: {i:04}\n', flush=True)
# Get Hessian,and check nullity. If > 2, perturb.
@ -205,7 +201,8 @@ class Shrink(Simulation):
self.delta, self.stop_width = self.domain.w*delta, self.domain.w*stop_width
def save_initial(self) -> None:
@property
def initial_data(self) -> Dict:
info = {
"mode": self.attr_str,
"step_size": self.step_size,
@ -214,15 +211,12 @@ class Shrink(Simulation):
"kernel_step": self.kernel_step,
"count": self.count
}
with open(self.path, 'wb') as out:
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
print("Created simulation file at:", self.path, flush=True)
return info
def run(self, save: bool, log: bool, log_steps: int) -> None:
if log: print(f'Shrink - {self.domain}', flush=True)
if save: self.save_initial()
if save: self.save(self.initial_data)
if len(self) != 0:
new_sites = self[0].site_arr
@ -239,7 +233,7 @@ class Shrink(Simulation):
new_sites = sim[-1].site_arr
self.frames.append(sim[-1])
if save: self.save_frame(i)
if save: self.save(self.frame_data(i))
if log: print(f'Width: {self.w:.4f}\n')

View File

@ -6,6 +6,7 @@ from pathlib import Path
from .common import DomainParams, Energy
from .simulation import Flow, Search, Shrink
from .diagram import Diagram
dia_presets = {
"animate": [["voronoi"]],
@ -138,15 +139,21 @@ def config_sim(args):
else:
dia_params["figures"] = np.asarray(dia_params["figures"])
if "time" not in dia_params:
dia_params["time"] = 30
sim.add_frame(points)
sim.run(save_sim, not args.quiet, args.log_steps)
if save_diagram:
diagram = Diagram(sim, dia_params["figures"])
if dia_params["filetype"] == "img":
diagram.render_static(0, filename=name)
diagram.render_frames()
elif dia_params["filetype"] == "mp4":
diagram.render_video(filename=name)
if mode == "flow":
diagram.render_video(dia_params["time"], "sample")
else:
diagram.render_video(dia_params["time"], "use_all")
def loaded_sim(args):