Made diagram.py work properly, and add multiprocessing to frame rendering.
This commit is contained in:
parent
989ef93f29
commit
7e5e711533
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
from .common import DomainParams, Energy, Simulation
|
||||
#from .simulation import Diagram as Diagram
|
||||
from .diagram import Diagram
|
||||
@ -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:
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user