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::
|
.. note::
|
||||||
Rendering as an `mp4` requires ``ffmpeg`` to be installed on your system.
|
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
|
.. code-block:: json
|
||||||
:force:
|
:force:
|
||||||
@ -154,6 +154,7 @@ While it's possible to customize the figures that are drawn and outputted, there
|
|||||||
"diagram": {
|
"diagram": {
|
||||||
"filetype": "mp4",
|
"filetype": "mp4",
|
||||||
"figures": "animate",
|
"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
|
cycler==0.10.0
|
||||||
Cython==0.29.24
|
docutils==0.17.1
|
||||||
kiwisolver==1.3.1
|
idna==3.2
|
||||||
|
imagesize==1.2.0
|
||||||
|
Jinja2==3.0.1
|
||||||
|
kiwisolver==1.3.2
|
||||||
|
MarkupSafe==2.0.1
|
||||||
matplotlib==3.4.3
|
matplotlib==3.4.3
|
||||||
numpy==1.21.2
|
numpy==1.21.2
|
||||||
Pillow==8.3.1
|
packaging==21.0
|
||||||
|
Pillow==8.3.2
|
||||||
|
Pygments==2.10.0
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
pytz==2021.1
|
||||||
|
requests==2.26.0
|
||||||
scipy==1.7.1
|
scipy==1.7.1
|
||||||
six==1.16.0
|
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 .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 pathlib import Path
|
||||||
from _squish import AreaEnergy, RadialALEnergy, RadialTEnergy
|
from _squish import AreaEnergy, RadialALEnergy, RadialTEnergy
|
||||||
|
|
||||||
|
OUTPUT_DIR = Path("squish_output")
|
||||||
|
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
STR_TO_ENERGY = {
|
STR_TO_ENERGY = {
|
||||||
"area": AreaEnergy,
|
"area": AreaEnergy,
|
||||||
"radial-al": RadialALEnergy,
|
"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
|
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} - N{sim.domain.n} - {width:.2f}x{height:.2f}"
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
if ext == "folder":
|
real_path = Path(base_path)
|
||||||
real_path = Path(base_path)
|
while real_path.is_dir():
|
||||||
while real_path.is_dir():
|
real_path = Path(f"{base_path}({i})")
|
||||||
real_path = Path(f"{base_path}({i})")
|
i += 1
|
||||||
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
|
|
||||||
|
|
||||||
return real_path
|
return real_path
|
||||||
|
|
||||||
@ -125,10 +122,15 @@ class Simulation:
|
|||||||
self.frames = []
|
self.frames = []
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
self.path = generate_filepath(self, "sim", "simulations")
|
self.path = generate_filepath(self, OUTPUT_DIR)
|
||||||
else:
|
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:
|
def __getitem__(self, key: int) -> Energy:
|
||||||
return self.frames[key]
|
return self.frames[key]
|
||||||
@ -151,49 +153,6 @@ class Simulation:
|
|||||||
self.frames.append(self.energy.mode(*self.domain, points % self.domain.dim))
|
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]:
|
def get_distinct(self) -> List[int]:
|
||||||
"""Gets the distinct configurations based on the average radii of the sites.
|
"""Gets the distinct configurations based on the average radii of the sites.
|
||||||
and returns the number of configurations for each distinct configuration.
|
and returns the number of configurations for each distinct configuration.
|
||||||
@ -218,7 +177,14 @@ class Simulation:
|
|||||||
return distinct_count
|
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]
|
f = self[index]
|
||||||
info = {
|
info = {
|
||||||
"arr": f.site_arr,
|
"arr": f.site_arr,
|
||||||
@ -226,9 +192,7 @@ class Simulation:
|
|||||||
"energy": f.attr_str,
|
"energy": f.attr_str,
|
||||||
"stats": f.stats
|
"stats": f.stats
|
||||||
}
|
}
|
||||||
|
return info
|
||||||
with open(self.path, 'ab') as out:
|
|
||||||
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
|
|
||||||
|
|
||||||
# all_info = []
|
# all_info = []
|
||||||
# for frame in self.frames:
|
# for frame in self.frames:
|
||||||
|
|||||||
@ -1,40 +1,104 @@
|
|||||||
from __future__ import annotations
|
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
|
from matplotlib.ticker import MaxNLocator, FormatStrFormatter
|
||||||
import os, math, random, time, pickle, scipy, numpy as np
|
from scipy.spatial import Voronoi, voronoi_plot_2d
|
||||||
from timeit import default_timer as timer
|
from multiprocessing import Pool, cpu_count
|
||||||
|
|
||||||
INT = np.int64
|
from .common import DomainParams
|
||||||
FLOAT = np.float64
|
|
||||||
|
|
||||||
SYMM = np.array([[1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1], [0,-1], [1,-1]])
|
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.
|
__slots__ = ['path', 'domains', 'energies', 'voronois', 'stats']
|
||||||
:param diagrams: [np.ndarray] selects which diagrams to show.
|
|
||||||
|
|
||||||
|
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']
|
__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.diagrams = np.atleast_2d(diagrams)
|
||||||
self.cumulative = cumulative
|
self.cumulative = cumulative
|
||||||
|
|
||||||
|
|
||||||
def generate_frame(self, frame: int):
|
def generate_frame(self, frame: int, mode: str, fol: str) -> None:
|
||||||
"""
|
if mode not in ["save", "open"]:
|
||||||
Generates one frame for the plot.
|
raise ValueError("Not a valid mode for diagrams!")
|
||||||
: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.
|
|
||||||
"""
|
|
||||||
shape = self.diagrams.shape
|
shape = self.diagrams.shape
|
||||||
fig, axes = plt.subplots(*shape, figsize=(shape[1]*8, shape[0]*8))
|
fig, axes = plt.subplots(*shape, figsize=(shape[1]*8, shape[0]*8))
|
||||||
if self.diagrams.shape == (1,1):
|
if self.diagrams.shape == (1,1):
|
||||||
@ -49,14 +113,20 @@ class Diagram():
|
|||||||
|
|
||||||
plt.tight_layout()
|
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
|
scale = 1.5
|
||||||
area = n <= 60
|
area = n <= 60
|
||||||
|
|
||||||
scipy.spatial.voronoi_plot_2d(self.sim[i].vor_data, ax, show_vertices=False,
|
voronoi_plot_2d(self.sim.voronois[i], ax, show_vertices=False, point_size = 7-n/100)
|
||||||
point_size = 7-n/100)
|
|
||||||
ax.plot([-w, 2*w], [0, 0], 'r')
|
ax.plot([-w, 2*w], [0, 0], 'r')
|
||||||
ax.plot([-w, 2*w], [h, h], 'r')
|
ax.plot([-w, 2*w], [h, h], 'r')
|
||||||
ax.plot([0,0], [-h, 2*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)
|
props = dict(boxstyle='round', facecolor='wheat', alpha=0.8)
|
||||||
|
|
||||||
# if area:
|
if area:
|
||||||
# global SYMM
|
global SYMM
|
||||||
# for site_index in range(n):
|
for j in range(n):
|
||||||
# for s in np.concatenate(([[0,0]], SYMM)):
|
for s in np.concatenate(([[0,0]], SYMM)):
|
||||||
# txt = ax.text(*(site.vec + s*self.sim[i].dim),
|
txt = ax.text(*(self.sim.voronois[i].points[j] + s*self.sim.domains[i].dim),
|
||||||
# str(round(site.cache("area"), 3)))
|
str(round(self.sim.stats[i]["site_areas"][j], 3)))
|
||||||
# txt.set_clip_on(True)
|
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)
|
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)])
|
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.plot(list(range(i+1)), energies)
|
||||||
ax.title.set_text('Energy vs. Time')
|
ax.title.set_text('Energy vs. Time')
|
||||||
max_value = round(self.sim[0].energy)
|
# max_value = round(self.sim[0].energy)
|
||||||
min_value = round(self.sim[-1].energy)
|
# min_value = round(self.sim[-1].energy)
|
||||||
#diff = max_value-min_value
|
#diff = max_value-min_value
|
||||||
#ax.set_yticks(np.arange(int(min_value-diff/5), int(max_value+diff/5), diff/25))
|
#ax.set_yticks(np.arange(int(min_value-diff/5), int(max_value+diff/5), diff/25))
|
||||||
ax.set_xlabel("Iterations")
|
ax.set_xlabel("Iterations")
|
||||||
@ -99,10 +165,8 @@ class Diagram():
|
|||||||
ax.grid()
|
ax.grid()
|
||||||
|
|
||||||
|
|
||||||
def site_areas_plot(self, i: int, ax):
|
def site_areas_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
|
y, x = self.sim.hist("site_areas", i, cumul=self.cumulative, avg=True)
|
||||||
y, x = self.sim.generate_bar_info("site_areas", i, self.cumulative,
|
|
||||||
avg=True, reg=regular_area)
|
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Site Areas')
|
ax.title.set_text('Site Areas')
|
||||||
@ -116,9 +180,8 @@ class Diagram():
|
|||||||
# xtick.set_color(color)
|
# xtick.set_color(color)
|
||||||
|
|
||||||
|
|
||||||
def site_edge_count_plot(self, i: int, ax):
|
def site_edge_count_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
y, x = self.sim.generate_bar_info("site_edge_count", i, self.cumulative,
|
y, x = self.sim.hist("site_edge_count", i, bounds=(1, 11), cumul=self.cumulative, avg=True)
|
||||||
bounds=(1, 11), avg=True)
|
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Edges per Site')
|
ax.title.set_text('Edges per Site')
|
||||||
@ -129,13 +192,8 @@ class Diagram():
|
|||||||
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
||||||
|
|
||||||
|
|
||||||
def site_isos_plot(self, i: int, ax):
|
def site_isos_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
|
y, x = self.sim.hist("site_isos", i, bounds=(0,1), cumul=self.cumulative, avg=True)
|
||||||
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)
|
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Isoparametric Values')
|
ax.title.set_text('Isoparametric Values')
|
||||||
@ -149,8 +207,8 @@ class Diagram():
|
|||||||
# xtick.set_color(color)
|
# xtick.set_color(color)
|
||||||
|
|
||||||
|
|
||||||
def site_energies_plot(self, i: int, ax):
|
def site_energies_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
y, x = self.sim.generate_bar_info("site_energies", i, self.cumulative, avg=True)
|
y, x = self.sim.hist("site_energies", i, self.cumulative, avg=True)
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Site Energies')
|
ax.title.set_text('Site Energies')
|
||||||
@ -161,8 +219,9 @@ class Diagram():
|
|||||||
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
||||||
|
|
||||||
|
|
||||||
def avg_radius_plot(self, i: int, ax):
|
def avg_radius_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
y, x = self.sim.generate_bar_info("avg_radius", i, self.cumulative, avg=True)
|
y, x = self.sim.hist("avg_radius", i, self.cumulative, avg=True)
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Site Average Radii')
|
ax.title.set_text('Site Average Radii')
|
||||||
ax.set_xlabel("Average Radius")
|
ax.set_xlabel("Average Radius")
|
||||||
@ -172,8 +231,8 @@ class Diagram():
|
|||||||
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
||||||
|
|
||||||
|
|
||||||
def isoparam_avg_plot(self, i: int, ax):
|
def isoparam_avg_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
y, x = self.sim.generate_bar_info("isoparam_avg", i, self.cumulative, avg=True)
|
y, x = self.sim.hist("isoparam_avg", i, self.cumulative, avg=True)
|
||||||
|
|
||||||
ax.bar(x,y, width=0.8*(x[1]-x[0]))
|
ax.bar(x,y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Site Isoperimetric Averages')
|
ax.title.set_text('Site Isoperimetric Averages')
|
||||||
@ -184,11 +243,8 @@ class Diagram():
|
|||||||
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
||||||
|
|
||||||
|
|
||||||
def edge_lengths_plot(self, i: int, ax):
|
def edge_lengths_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
regular_area = self.sim[i].w*self.sim[i].h/self.sim[i].n
|
y, x = self.sim.hist("edge_lengths", i, 30, cumul=self.cumulative, avg=True)
|
||||||
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)
|
|
||||||
|
|
||||||
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
ax.bar(x, y, width=0.8*(x[1]-x[0]))
|
||||||
ax.title.set_text('Edge Lengths')
|
ax.title.set_text('Edge Lengths')
|
||||||
@ -204,8 +260,8 @@ class Diagram():
|
|||||||
# xtick.set_color(color)
|
# xtick.set_color(color)
|
||||||
|
|
||||||
|
|
||||||
def eigs_plot(self, i: int, ax):
|
def eigs_plot(self, i: int, ax: AxesSubplot) -> None:
|
||||||
eigs = self.sim[i].stats["eigs"]
|
eigs = self.sim.stats[i]["eigs"]
|
||||||
ax.plot(list(range(len(eigs))), eigs, marker='o', linestyle='dashed', color='C0')
|
ax.plot(list(range(len(eigs))), eigs, marker='o', linestyle='dashed', color='C0')
|
||||||
ax.plot([0,len(eigs)], [0, 0], color="red")
|
ax.plot([0,len(eigs)], [0, 0], color="red")
|
||||||
ax.title.set_text('Hessian Eigenvalues')
|
ax.title.set_text('Hessian Eigenvalues')
|
||||||
@ -213,99 +269,64 @@ class Diagram():
|
|||||||
ax.set_ylabel("Value")
|
ax.set_ylabel("Value")
|
||||||
|
|
||||||
|
|
||||||
def render_static(self, i: int, j: int = None, filename = None):
|
def render_frames(self, frames: List[int], fol: str = 'frames') -> None:
|
||||||
"""
|
(self.sim.path / fol).mkdir(exist_ok=True)
|
||||||
Renders single frames.
|
combo_list = []
|
||||||
:param filename: [str] name of file.
|
print(cpu_count())
|
||||||
:param i: [int] index of frame to start rendering.
|
for i in range(cpu_count()):
|
||||||
:param j: [j] index of frame to stop rendering.
|
combo_list.append((self, frames[:int((i+1)*len(frames)/cpu_count())],
|
||||||
:param only: [bool] set to True to only render diagram.
|
fol, len(frames)))
|
||||||
"""
|
|
||||||
if j is None:
|
|
||||||
j = len(self.sim)-1
|
|
||||||
|
|
||||||
length = j+1-i
|
with Pool(cpu_count()) as pool:
|
||||||
if length == 1:
|
for _ in pool.imap_unordered(render_frame_range, combo_list):
|
||||||
if filename is None:
|
pass
|
||||||
path = gen_filepath(self.sim, "png")
|
|
||||||
else:
|
|
||||||
path = f'figures/{filename}.png'
|
|
||||||
|
|
||||||
self.generate_frame(i)
|
# for i, frame in enumerate(frames):
|
||||||
plt.savefig(path)
|
# self.generate_frame(frame, "save", fol)
|
||||||
plt.close()
|
# hashes = int(21*i/len(frames))
|
||||||
|
# print(f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|' + \
|
||||||
print(f'Wrote to \"{path}\"')
|
# f' {i+1}/{len(frames)} frames rendered.', flush=True, end='\r')
|
||||||
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()
|
|
||||||
|
|
||||||
print(flush=True)
|
print(flush=True)
|
||||||
|
|
||||||
|
|
||||||
if filename is None:
|
def render_video(self, time: int, mode: str) -> None:
|
||||||
path = gen_filepath(self.sim, "mp4")
|
if mode not in ["use_all", "sample"]:
|
||||||
else:
|
raise ValueError("Not a valid mode for videos!")
|
||||||
path = f'figures/{filename}.mp4'
|
|
||||||
|
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)
|
print("Assembling MP4...", flush=True)
|
||||||
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i figures/temp/img%03d.png' + \
|
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i' + \
|
||||||
f' -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -vf' + \
|
f' \"{self.sim.path}/temp/img%05d.png\"' + \
|
||||||
f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"')
|
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.
|
# Remove files.
|
||||||
for j in range(frames):
|
for i in frames:
|
||||||
os.remove(f'figures/temp/img{j:03}.png')
|
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)
|
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
|
self.step_size, self.thres, self.accel = step_size, thres, accel
|
||||||
|
|
||||||
|
|
||||||
def save_initial(self) -> None:
|
@property
|
||||||
|
def initial_data(self) -> Dict:
|
||||||
info = {
|
info = {
|
||||||
"mode": self.attr_str,
|
"mode": self.attr_str,
|
||||||
"step_size": self.step_size,
|
"step_size": self.step_size,
|
||||||
"thres": self.thres,
|
"thres": self.thres,
|
||||||
"accel": self.accel
|
"accel": self.accel
|
||||||
}
|
}
|
||||||
|
return info
|
||||||
with open(self.path, 'wb') as out:
|
|
||||||
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
|
|
||||||
print("Created simulation file at:", self.path, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
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_initial()
|
if save: self.save(self.initial_data)
|
||||||
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')
|
||||||
|
|
||||||
trial = 2
|
trial = 2
|
||||||
while grad_norm > self.thres: # Get to threshold.
|
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
|
# Iterate and generate next frame using RK-2
|
||||||
start = timer()
|
start = timer()
|
||||||
@ -124,7 +122,8 @@ class Search(Simulation):
|
|||||||
self.kernel_step, self.count = kernel_step, count
|
self.kernel_step, self.count = kernel_step, count
|
||||||
|
|
||||||
|
|
||||||
def save_initial(self) -> None:
|
@property
|
||||||
|
def initial_data(self) -> Dict:
|
||||||
info = {
|
info = {
|
||||||
"mode": self.attr_str,
|
"mode": self.attr_str,
|
||||||
"step_size": self.step_size,
|
"step_size": self.step_size,
|
||||||
@ -133,15 +132,12 @@ class Search(Simulation):
|
|||||||
"kernel_step": self.kernel_step,
|
"kernel_step": self.kernel_step,
|
||||||
"count": self.count
|
"count": self.count
|
||||||
}
|
}
|
||||||
|
return info
|
||||||
with open(self.path, 'wb') as out:
|
|
||||||
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
|
|
||||||
print("Created simulation file at:", self.path, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
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_initial()
|
if save: self.save(self.initial_data)
|
||||||
|
|
||||||
if len(self) != 0:
|
if len(self) != 0:
|
||||||
new_sites = self[0].site_arr
|
new_sites = self[0].site_arr
|
||||||
@ -156,7 +152,7 @@ class Search(Simulation):
|
|||||||
sim.run(False, log, log_steps)
|
sim.run(False, log, log_steps)
|
||||||
|
|
||||||
self.frames.append(sim[-1])
|
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)
|
if log: print(f'Equilibrium: {i:04}\n', flush=True)
|
||||||
|
|
||||||
# Get Hessian,and check nullity. If > 2, perturb.
|
# 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
|
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 = {
|
info = {
|
||||||
"mode": self.attr_str,
|
"mode": self.attr_str,
|
||||||
"step_size": self.step_size,
|
"step_size": self.step_size,
|
||||||
@ -214,15 +211,12 @@ class Shrink(Simulation):
|
|||||||
"kernel_step": self.kernel_step,
|
"kernel_step": self.kernel_step,
|
||||||
"count": self.count
|
"count": self.count
|
||||||
}
|
}
|
||||||
|
return info
|
||||||
with open(self.path, 'wb') as out:
|
|
||||||
pickle.dump(info, out, pickle.HIGHEST_PROTOCOL)
|
|
||||||
print("Created simulation file at:", self.path, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
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_initial()
|
if save: self.save(self.initial_data)
|
||||||
|
|
||||||
if len(self) != 0:
|
if len(self) != 0:
|
||||||
new_sites = self[0].site_arr
|
new_sites = self[0].site_arr
|
||||||
@ -239,7 +233,7 @@ class Shrink(Simulation):
|
|||||||
new_sites = sim[-1].site_arr
|
new_sites = sim[-1].site_arr
|
||||||
|
|
||||||
self.frames.append(sim[-1])
|
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')
|
if log: print(f'Width: {self.w:.4f}\n')
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from .common import DomainParams, Energy
|
from .common import DomainParams, Energy
|
||||||
from .simulation import Flow, Search, Shrink
|
from .simulation import Flow, Search, Shrink
|
||||||
|
from .diagram import Diagram
|
||||||
|
|
||||||
dia_presets = {
|
dia_presets = {
|
||||||
"animate": [["voronoi"]],
|
"animate": [["voronoi"]],
|
||||||
@ -138,15 +139,21 @@ def config_sim(args):
|
|||||||
else:
|
else:
|
||||||
dia_params["figures"] = np.asarray(dia_params["figures"])
|
dia_params["figures"] = np.asarray(dia_params["figures"])
|
||||||
|
|
||||||
|
if "time" not in dia_params:
|
||||||
|
dia_params["time"] = 30
|
||||||
|
|
||||||
sim.add_frame(points)
|
sim.add_frame(points)
|
||||||
sim.run(save_sim, not args.quiet, args.log_steps)
|
sim.run(save_sim, not args.quiet, args.log_steps)
|
||||||
|
|
||||||
if save_diagram:
|
if save_diagram:
|
||||||
diagram = Diagram(sim, dia_params["figures"])
|
diagram = Diagram(sim, dia_params["figures"])
|
||||||
if dia_params["filetype"] == "img":
|
if dia_params["filetype"] == "img":
|
||||||
diagram.render_static(0, filename=name)
|
diagram.render_frames()
|
||||||
elif dia_params["filetype"] == "mp4":
|
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):
|
def loaded_sim(args):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user