Reformatted using spaces and python-black.

This commit is contained in:
Kenneth Jao 2021-10-23 22:40:03 -04:00
parent b4d6b5dbd8
commit e7e7cefb56
12 changed files with 1621 additions and 1321 deletions

View File

@ -1,92 +0,0 @@
from __future__ import annotations
from typing import List, Tuple, Dict
import argparse, math, numpy as np, os, pickle
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
from multiprocessing import Pool, cpu_count
from pathlib import Path
import squish.ordered as order
from squish import Simulation, DomainParams
from squish.common import Energy, OUTPUT_DIR
def axis_settings(ax, widths):
ax.invert_xaxis()
ax.grid(zorder=0)
ax.set_xticks([round(w,2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation = 90)
plt.subplots_adjust(.07, .12, .97, .9)
def main():
# Loading arguments.
parser = argparse.ArgumentParser("Outputs ordered equilibria lowest eigenvalues.")
parser.add_argument('n_objects', metavar='N', type=int,
help="folder that contains simulation files, or cached data file.")
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False,
help="suppress all normal output")
args = parser.parse_args()
widths = np.round(np.linspace(3.0, 10.0, 141),2)
values = []
store_data = {}
for i, width in enumerate(widths):
domain = DomainParams(args.n_objects, width, 10, 4.0)
eig_vals = []
store_data[width] = {}
configs = order.configurations(domain)
for j, config in enumerate(configs):
if config == (1,0):
continue
points = order.sites(domain, config)
hess = Energy("radial-t").mode(*domain, points).hessian(10e-5)
eigs = np.sort(np.linalg.eig(hess)[0])[::-1]
store_data[width][config] = eigs
# zero_ind = np.where(np.isclose(eigs, 0))[0][0]
# if zero_ind == 0:
# eig_vals.append(eigs[2])
# else:
# eig_vals.append(eigs[zero_ind-1])
hashes = int(21*j/len(widths))
print(f'Generating at {width}, {i+1}/{len(widths)}... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {j+1}/{len(configs)} configs done.', flush=True, end='\r')
print(flush=True)
with open("coercivity_eigs.pkl", "wb") as f:
pickle.dump(store_data, f, pickle.HIGHEST_PROTOCOL)
return
fig, ax = plt.subplots(figsize=(12, 8))
plt.subplots_adjust(.07, .12, .97, .9)
ax.plot(widths, values)
ax.invert_xaxis()
ax.grid(zorder=0)
ax.set_xticks([round(w,2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation = 90)
fig.suptitle("Coercivity")
#ax.set_xlim([0, 5])
ax.legend()
ax.set_xlabel("Width")
ax.set_ylabel("Smallest positive eigenvalue")
fig.savefig(OUTPUT_DIR / "Coercivity.png")
print(f"Wrote to {OUTPUT_DIR / 'Coercivity.png'}")
if __name__ == '__main__':
os.environ["QT_LOGGING_RULES"] = "*=false"
try:
main()
except KeyboardInterrupt:
print("Program terminated by user.")

113
scripts/coercivity.py Normal file
View File

@ -0,0 +1,113 @@
from __future__ import annotations
from typing import List, Tuple, Dict
import argparse, math, numpy as np, os, pickle
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
from multiprocessing import Pool, cpu_count
from pathlib import Path
import squish.ordered as order
from squish import Simulation, DomainParams
from squish.common import Energy, OUTPUT_DIR
def axis_settings(ax, widths):
ax.invert_xaxis()
ax.grid(zorder=0)
ax.set_xticks([round(w, 2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation=90)
plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
def main():
# Loading arguments.
parser = argparse.ArgumentParser("Outputs ordered equilibria lowest eigenvalues.")
parser.add_argument(
"n_objects",
metavar="N",
type=int,
help="folder that contains simulation files, or cached data file.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args()
widths = np.round(np.linspace(3.0, 10.0, 141), 2)
values = []
with open("coercivity_eigs.pkl", "rb") as f:
store_data = pickle.load(f)
for width in widths:
eig_vals = []
for config, eigs in store_data[width].items():
zero_ind = np.where(np.isclose(eigs, 0, atol=1e-4))[0][0]
if zero_ind == 0:
eig_vals.append(eigs[2])
else:
eig_vals.append(eigs[0])
values.append(min(eig_vals))
# for i, width in enumerate(widths):
# domain = DomainParams(args.n_objects, width, 10, 4.0)
# eig_vals = []
# store_data[width] = {}
# configs = order.configurations(domain)
# for j, config in enumerate(configs):
# if config == (1,0):
# continue
# points = order.sites(domain, config)
# hess = Energy("radial-t").mode(*domain, points).hessian(10e-5)
# eigs = np.sort(np.linalg.eig(hess)[0])[::-1]
# store_data[width][config] = eigs
# zero_ind = np.where(np.isclose(eigs, 0))[0][0]
# if zero_ind == 0:
# eig_vals.append(eigs[2])
# else:
# eig_vals.append(eigs[0])
# hashes = int(21*j/len(widths))
# print(f'Generating at {width}, {i+1}/{len(widths)}... |{"#"*hashes}{" "*(20-hashes)}|' + \
# f' {j+1}/{len(configs)} configs done.', flush=True, end='\r')
# print(flush=True)
# with open("coercivity_eigs.pkl", "wb") as f:
# pickle.dump(store_data, f, pickle.HIGHEST_PROTOCOL)
# return
fig, ax = plt.subplots(figsize=(12, 8))
plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
ax.plot(widths, values)
ax.invert_xaxis()
ax.grid(zorder=0)
ax.set_xticks([round(w, 2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation=90)
fig.suptitle("Coercivity")
# ax.set_xlim([0, 5])
ax.set_xlabel("Width")
ax.set_ylabel("Eigenvalue")
fig.savefig(OUTPUT_DIR / "Coercivity.png")
print(f"Wrote to {OUTPUT_DIR / 'Coercivity.png'}")
if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false"
try:
main()
except KeyboardInterrupt:
print("Program terminated by user.")

View File

@ -9,27 +9,40 @@ from squish.common import OUTPUT_DIR
def main(): def main():
parser = argparse.ArgumentParser("Graphs convergence graphs for a collection of simulations.") parser = argparse.ArgumentParser(
parser.add_argument('sims_path', metavar='path/to/data', "Graphs convergence graphs for a collection of simulations."
help="folder that contains simulation files at various step sizes.") )
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, parser.add_argument(
help="suppress all normal output") "sims_path",
metavar="path/to/data",
help="folder that contains simulation files at various step sizes.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args() args = parser.parse_args()
data = {} data = {}
for file in Path(args.sims_path).iterdir(): for file in Path(args.sims_path).iterdir():
sim, frames = Simulation.load(file / "data.squish") sim, frames = Simulation.load(file)
step = sim.step_size step = sim.step_size
data[step] = {"times": [], "values": [], "diffs": []} data[step] = {"times": [], "values": [], "diffs": []}
for i, frame_info in enumerate(frames): for i, frame_info in enumerate(frames):
data[step]["times"].append(step*i) data[step]["times"].append(step * i)
data[step]["values"].append(np.linalg.norm(frame_info["arr"])) data[step]["values"].append(np.linalg.norm(frame_info["arr"]))
data[step]["diffs"].append(np.linalg.norm(all_info[-1]["arr"] - frame_info["arr"])) data[step]["diffs"].append(
np.linalg.norm(all_info[-1]["arr"] - frame_info["arr"])
)
fig, ax = plt.subplots(1, 2, figsize=(16, 8)) fig, ax = plt.subplots(1, 2, figsize=(16, 8))
plt.subplots_adjust(.07, .12, .97, .9) plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
for step, d in data.items(): for step, d in data.items():
ax[0].plot(d["times"], d["values"], label=step) ax[0].plot(d["times"], d["values"], label=step)
@ -50,7 +63,7 @@ def main():
print(f"Wrote to {OUTPUT_DIR / 'Equilibrium Convergence.png'}") print(f"Wrote to {OUTPUT_DIR / 'Equilibrium Convergence.png'}")
if __name__ == '__main__': if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false" os.environ["QT_LOGGING_RULES"] = "*=false"
try: try:
main() main()

View File

@ -10,77 +10,97 @@ from squish.common import OUTPUT_DIR
def main(): def main():
parser = argparse.ArgumentParser("Graphs average defects at N.") parser = argparse.ArgumentParser("Graphs average defects at N.")
parser.add_argument('sims_path', metavar='path/to/data', parser.add_argument(
help="folder that contains simulation files at various Ns.") "sims_path",
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, metavar="path/to/data",
help="suppress all normal output") help="folder that contains simulation files at various Ns.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args() args = parser.parse_args()
data = {} data = {}
files = list(Path(args.sims_path).iterdir())
for file in Path(args.sims_path).iterdir(): for i, file in enumerate(files):
sim, frames = Simulation.load(file / "data.squish") sim, frames = Simulation.load(file)
avg_defects = 0 avg_defects = 0
count = 0 count = 0
for frame in frames: for frame in frames:
if np.var(frame["stats"]["avg_radius"]) > 1e-8: if np.var(frame["stats"]["avg_radius"]) > 1e-8:
avg_defects += np.count_nonzero(frame["stats"]["site_edge_count"] != 6) avg_defects += np.count_nonzero(frame["stats"]["site_edge_count"] != 6)
count += 1 count += 1
avg_defects /= 1 if count == 0 else count
avg_defects /= (1 if count == 0 else count)
data[sim.domain.n] = avg_defects data[sim.domain.n] = avg_defects
hashes = int(21 * i / len(files))
print(
f'Processed N={sim.domain.n:03} |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(files)} simulations processed.",
flush=True,
end="\r",
)
print(flush=True)
data = sorted(data.items()) data = sorted(data.items())
ns, defects = np.array([x[0] for x in data]), np.array([x[1] for x in data]) ns, defects = np.array([x[0] for x in data]), np.array([x[1] for x in data])
corrected = [] corrected = []
for i, x in enumerate(defects): for i, x in enumerate(defects):
if x == 0: if x == 0:
corrected.append(defects[i+1]) corrected.append(defects[i + 1])
else: else:
corrected.append(x) corrected.append(x)
fig, ax = plt.subplots(1, 2, figsize=(16, 8)) fig, ax = plt.subplots(1, 2, figsize=(16, 8))
plt.subplots_adjust(.07, .12, .97, .9) plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
fig.suptitle("Defects at N") fig.suptitle("Defects vs. Number of Sites (N)")
m0, b0 = np.polyfit(ns, defects, 1) m0, b0 = np.polyfit(ns, defects, 1)
ax[0].plot(ns, defects) ax[0].plot(ns, defects)
ax[0].plot(ns, m0*ns+b0, label=f"Slope: {m0:.5f}") ax[0].plot(ns, m0 * ns + b0, label=f"Slope: {m0:.5f}")
ax[0].grid(zorder=0) ax[0].grid(zorder=0)
ax[0].legend() ax[0].legend()
ax[0].set_xlabel("N") ax[0].set_xlabel("N")
ax[0].set_ylabel("Average Defects") ax[0].set_ylabel("Average Defects")
x, y = np.log10(ns), np.log10(corrected) x, y = np.log(ns), np.log(corrected)
m, b = np.polyfit(x, y, 1) m, b = np.polyfit(x, y, 1)
x2, y2 = x[14:], np.log10(defects[14:]) x2, y2 = x[40:], np.log(defects[40:])
m2, b2 = np.polyfit(x2, y2, 1) m2, b2 = np.polyfit(x2, y2, 1)
ax[1].plot(x, y, linestyle='dotted', color='C0') ax[1].plot(x, y, linestyle="dotted", color="C0")
ax[1].plot(x, np.log10(defects)) ax[1].plot(x, np.log(defects))
ax[1].plot(x, m*x+b, label=f"All N: {m:.5f}") # ax[1].plot(x, m*x+b, label=f"All N: {m:.5f}")
ax[1].plot(x2, m2*x2+b2, label=f"N $\\geq$ 25: {m2:.5f}") ax[1].plot(x2, m2 * x2 + b2, label=f"N $\\geq$ 40: {m2:.5f}")
ax[1].grid(zorder=0) ax[1].grid(zorder=0)
ax[1].legend() ax[1].legend()
ax[1].set_xlabel("log10 N") ax[1].set_xticks(np.arange(3.75, 6.25, 0.25))
ax[1].set_ylabel("log10 Average Defects") ax[1].set_yticks(np.arange(0, 4.5, 0.5))
ax[1].set_xlim(3.75, 6)
ax[1].set_ylim(0, 4)
ax[1].set_xlabel("$\\ln$ N")
ax[1].set_ylabel("$\\ln$ Average Defects")
fig.savefig(OUTPUT_DIR / "DefectsN.png") fig.savefig(OUTPUT_DIR / "DefectsN.png")
print(f"Wrote to {OUTPUT_DIR / 'DefectsN.png'}") print(f"Wrote to {OUTPUT_DIR / 'DefectsN.png'}")
if __name__ == '__main__': if __name__ == "__main__":
os.environ["QT_log10GING_RULES"] = "*=false" os.environ["QT_log10GING_RULES"] = "*=false"
try: try:
main() main()

View File

@ -10,28 +10,32 @@ from squish.common import OUTPUT_DIR
def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]: def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]:
sim, frames = Simulation.load(file / 'data.squish') sim, frames = Simulation.load(file)
alls = [] alls = []
for frame_info in frames: for frame_info in frames:
alls.append([ alls.append(
[
frame_info["energy"], frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8, np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6) np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
]) ]
)
sim, frames = Simulation.load(file / 'data.squish') sim, frames = Simulation.load(file)
sim.frames = list(frames) sim.frames = list(frames)
counts = sim.get_distinct() counts = sim.get_distinct()
distincts = [] distincts = []
for j, frame_info in enumerate(sim.frames): for j, frame_info in enumerate(sim.frames):
distincts.append([ distincts.append(
[
frame_info["energy"], frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8, np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6), np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
counts[j] counts[j],
]) ]
)
return sim.domain.w, alls, distincts return sim.domain.w, alls, distincts
@ -39,16 +43,20 @@ def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]:
def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainParams]: def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainParams]:
data = {"all": {}, "distinct": {}} data = {"all": {}, "distinct": {}}
files = list(Path(filepath).iterdir()) files = list(Path(filepath).iterdir())
sim, frames = Simulation.load(files[0] / 'data.squish') sim, frames = Simulation.load(files[0])
with Pool(cpu_count()) as pool: with Pool(cpu_count()) as pool:
for i, res in enumerate(pool.imap_unordered(eq_file_process, files)): for i, res in enumerate(pool.imap_unordered(eq_file_process, files)):
data["all"][res[0]] = res[1] data["all"][res[0]] = res[1]
data["distinct"][res[0]] = res[2] data["distinct"][res[0]] = res[2]
hashes = int(21*i/len(files)) hashes = int(21 * i / len(files))
print(f'Loading simulations for N={sim.domain.n}... |{"#"*hashes}{" "*(20-hashes)}|' + \ print(
f' {i+1}/{len(files)} simulations loaded.', flush=True, end='\r') f'Loading simulations for N={sim.domain.n}... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(files)} simulations loaded.",
flush=True,
end="\r",
)
print(flush=True) print(flush=True)
widths = np.asarray(sorted(data["all"])) widths = np.asarray(sorted(data["all"]))
@ -59,10 +67,19 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
def main(): def main():
# Loading arguments. # Loading arguments.
parser = argparse.ArgumentParser("Outputs width search data into diagrams") parser = argparse.ArgumentParser("Outputs width search data into diagrams")
parser.add_argument('sims_path', metavar='path/to/data', parser.add_argument(
help="folder that contains simulation files of all searches for all N.") "sims_path",
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, metavar="path/to/data",
help="suppress all normal output") help="folder that contains simulation files of all searches for all N.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args() args = parser.parse_args()
@ -78,7 +95,9 @@ def main():
disorder_count = [] disorder_count = []
for width in widths: for width in widths:
equal_shape = list([c[1] for c in sim_data["all"][width]]) equal_shape = list([c[1] for c in sim_data["all"][width]])
disorder_count.append(100*equal_shape.count(False)/len(sim_data["all"][width])) disorder_count.append(
100 * equal_shape.count(False) / len(sim_data["all"][width])
)
disorder_dict[domain.n] = disorder_count disorder_dict[domain.n] = disorder_count
@ -88,20 +107,26 @@ def main():
# with open("testing.pkl", "wb") as f: # with open("testing.pkl", "wb") as f:
# pickle.dump(disorder_dict, f, pickle.HIGHEST_PROTOCOL) # pickle.dump(disorder_dict, f, pickle.HIGHEST_PROTOCOL)
disorder_arr = np.zeros((max_n-min_n+1, len(widths))) disorder_arr = np.zeros((max_n - min_n + 1, len(widths)))
for key, value in disorder_dict.items(): for key, value in disorder_dict.items():
disorder_arr[key-min_n] = np.asarray(value) disorder_arr[key - min_n] = np.asarray(value)
fig, ax = plt.subplots(figsize=(12, 8)) fig, ax = plt.subplots(figsize=(12, 8))
extent = [min(widths), max(widths), min_n, max_n+1] extent = [min(widths), max(widths), min_n, max_n + 1]
ax.imshow(disorder_arr, cmap='plasma', interpolation='nearest', aspect='auto', extent=extent) ax.imshow(
disorder_arr,
cmap="plasma",
interpolation="nearest",
aspect="auto",
extent=extent,
)
ax.invert_xaxis() ax.invert_xaxis()
ax.set_xticks([round(w,2) for w in widths[::-2]]) ax.set_xticks([round(w, 2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation = 90) ax.set_xticklabels(ax.get_xticks(), rotation=90)
ax.set_yticks(list(range(min_n, max_n+1))) ax.set_yticks(list(range(min_n, max_n + 1)))
plt.subplots_adjust(.07, .12, .97, .9) plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
ax.title.set_text(filepath) ax.title.set_text(filepath)
ax.set_xlabel("Width") ax.set_xlabel("Width")
@ -111,7 +136,7 @@ def main():
print(f"Wrote to {OUTPUT_DIR / filepath}.") print(f"Wrote to {OUTPUT_DIR / filepath}.")
if __name__ == '__main__': if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false" os.environ["QT_LOGGING_RULES"] = "*=false"
try: try:
main() main()

View File

@ -9,13 +9,27 @@ from squish.common import OUTPUT_DIR
def main(): def main():
parser = argparse.ArgumentParser("Graphs perturbation graphs for a collection of simulations.") parser = argparse.ArgumentParser(
parser.add_argument('sims_path', metavar='path/to/data', "Graphs perturbation graphs for a collection of simulations."
help="folder that contains simulations of perturbations from an equilibrium.") )
parser.add_argument('end_path', metavar='path/to/equilbrium', parser.add_argument(
help="NumPy binary (.npy) file that contains the equilibrium to compare to.") "sims_path",
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, metavar="path/to/data",
help="suppress all normal output") help="folder that contains simulations of perturbations from an equilibrium.",
)
parser.add_argument(
"end_path",
metavar="path/to/equilbrium",
help="NumPy binary (.npy) file that contains the equilibrium to compare to.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args() args = parser.parse_args()
@ -23,10 +37,10 @@ def main():
data = {} data = {}
for file in Path(args.sims_path).iterdir(): for file in Path(args.sims_path).iterdir():
k = float(file.name.split('k')[-1]) k = float(file.name.split("k")[-1])
delta = 10**k delta = 10 ** k
sim, frames = Simulation.load(file / 'data.squish') sim, frames = Simulation.load(file)
data[delta] = {"norm": [], "time": [], "k": k} data[delta] = {"norm": [], "time": [], "k": k}
for i, frame in enumerate(frames): for i, frame in enumerate(frames):
@ -36,24 +50,27 @@ def main():
data[delta]["time"].append(sim.step_size * i) data[delta]["time"].append(sim.step_size * i)
fig, ax = plt.subplots(figsize=(12, 8)) fig, ax = plt.subplots(figsize=(12, 8))
plt.subplots_adjust(.07, .12, .97, .9) plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
for delta in sorted(data): for delta in sorted(data):
ax.plot(np.log10(np.array(data[delta]["time"])+1), np.log10(data[delta]["norm"]), ax.plot(
label=f"k = {data[delta]['k']}") np.log10(np.array(data[delta]["time"]) + 1),
np.log10(data[delta]["norm"]),
label=f"k = {data[delta]['k']}",
)
fig.suptitle("Equilibrium Perturbations") fig.suptitle("Equilibrium Perturbations")
ax.grid(zorder=0) ax.grid(zorder=0)
#ax.set_xlim([0, 5]) # ax.set_xlim([0, 5])
ax.legend() ax.legend()
ax.set_xlabel("Log Time") ax.set_xlabel("$\\log_{10}$ Time")
ax.set_ylabel("Log L2 Norm of Difference") ax.set_ylabel("$\\log_{10}$ L2 Norm of Difference")
fig.savefig(OUTPUT_DIR / "Equilibrium Perturbations.png") fig.savefig(OUTPUT_DIR / "Equilibrium Perturbations.png")
print(f"Wrote to {OUTPUT_DIR / 'Equilibrium Perturbations.png'}") print(f"Wrote to {OUTPUT_DIR / 'Equilibrium Perturbations.png'}")
if __name__ == '__main__': if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false" os.environ["QT_LOGGING_RULES"] = "*=false"
try: try:
main() main()

View File

@ -15,15 +15,22 @@ def order_process(domain: DomainParams) -> Tuple[float, float, float]:
energies = [] energies = []
configs = order.configurations(domain) configs = order.configurations(domain)
for config in configs: for config in configs:
energies.append(2*domain.w*domain.h + \ energies.append(
2*math.pi*domain.n*(domain.r**2 - 2*domain.r*order.avg_radius(domain, config))) 2 * domain.w * domain.h
+ 2
* math.pi
* domain.n
* (domain.r ** 2 - 2 * domain.r * order.avg_radius(domain, config))
)
return domain.w, min(energies), max(energies) return domain.w, min(energies), max(energies)
def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict: def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict:
data = {} data = {}
domains = [DomainParams(orig_domain.n, w, orig_domain.h, orig_domain.r) for w in widths] domains = [
DomainParams(orig_domain.n, w, orig_domain.h, orig_domain.r) for w in widths
]
with Pool(cpu_count()) as pool: with Pool(cpu_count()) as pool:
mins, maxes = {}, {} mins, maxes = {}, {}
@ -31,9 +38,13 @@ def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict:
mins[res[0]] = res[1] mins[res[0]] = res[1]
maxes[res[0]] = res[2] maxes[res[0]] = res[2]
hashes = int(21*i/len(widths)) hashes = int(21 * i / len(widths))
print(f'Generating at width {res[0]:.02f}... |{"#"*hashes}{" "*(20-hashes)}|' + \ print(
f' {i+1}/{len(widths)} completed.', flush=True, end='\r') f'Generating at width {res[0]:.02f}... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(widths)} completed.",
flush=True,
end="\r",
)
print(flush=True) print(flush=True)
@ -44,28 +55,32 @@ def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict:
def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]: def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]:
sim, frames = Simulation.load(file / 'data.squish') sim, frames = Simulation.load(file)
alls = [] alls = []
for frame_info in frames: for frame_info in frames:
alls.append([ alls.append(
[
frame_info["energy"], frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8, np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6) np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
]) ]
)
sim, frames = Simulation.load(file / 'data.squish') sim, frames = Simulation.load(file)
sim.frames = list(frames) sim.frames = list(frames)
counts = sim.get_distinct() counts = sim.get_distinct()
distincts = [] distincts = []
for j, frame_info in enumerate(sim.frames): for j, frame_info in enumerate(sim.frames):
distincts.append([ distincts.append(
[
frame_info["energy"], frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8, np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6), np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
counts[j] counts[j],
]) ]
)
return sim.domain.w, alls, distincts return sim.domain.w, alls, distincts
@ -79,12 +94,16 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
data["all"][res[0]] = res[1] data["all"][res[0]] = res[1]
data["distinct"][res[0]] = res[2] data["distinct"][res[0]] = res[2]
hashes = int(21*i/len(files)) hashes = int(21 * i / len(files))
print(f'Loading simulations... |{"#"*hashes}{" "*(20-hashes)}|' + \ print(
f' {i+1}/{len(files)} simulations loaded.', flush=True, end='\r') f'Loading simulations... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(files)} simulations loaded.",
flush=True,
end="\r",
)
print(flush=True) print(flush=True)
sim, frames = Simulation.load(files[0] / 'data.squish') sim, frames = Simulation.load(files[0])
widths = np.asarray(sorted(data["all"])) widths = np.asarray(sorted(data["all"]))
domain = DomainParams(sim.domain.n, widths[-1], sim.domain.h, sim.domain.r) domain = DomainParams(sim.domain.n, widths[-1], sim.domain.h, sim.domain.r)
return data, widths, domain return data, widths, domain
@ -93,18 +112,27 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
def axis_settings(ax, widths): def axis_settings(ax, widths):
ax.invert_xaxis() ax.invert_xaxis()
ax.grid(zorder=0) ax.grid(zorder=0)
ax.set_xticks([round(w,2) for w in widths[::-2]]) ax.set_xticks([round(w, 2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation = 90) ax.set_xticklabels(ax.get_xticks(), rotation=90)
plt.subplots_adjust(.07, .12, .97, .9) plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
def main(): def main():
# Loading arguments. # Loading arguments.
parser = argparse.ArgumentParser("Outputs width search data into diagrams") parser = argparse.ArgumentParser("Outputs width search data into diagrams")
parser.add_argument('sims_path', metavar='path/to/data', parser.add_argument(
help="folder that contains simulation files, or cached data file.") "sims_path",
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, metavar="path/to/data",
help="suppress all normal output") help="folder that contains simulation files, or cached data file.",
)
parser.add_argument(
"-q",
"--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
args = parser.parse_args() args = parser.parse_args()
@ -121,19 +149,25 @@ def main():
all_disorder_count = [] all_disorder_count = []
for width in widths: for width in widths:
equal_shape = list([c[1] for c in data["all"][width]]) equal_shape = list([c[1] for c in data["all"][width]])
all_disorder_count.append(100*equal_shape.count(False)/len(data["all"][width])) all_disorder_count.append(
100 * equal_shape.count(False) / len(data["all"][width])
)
ax.plot(widths, all_disorder_count) ax.plot(widths, all_disorder_count)
axis_settings(ax, widths) axis_settings(ax, widths)
with open("N83-prob.txt", "w") as f:
f.write(", ".join([str(x) for x in widths]) + "\n")
f.write(", ".join([str(x) for x in all_disorder_count]))
ax.yaxis.set_major_formatter(mtick.PercentFormatter()) ax.yaxis.set_major_formatter(mtick.PercentFormatter())
ax.title.set_text(f"Probability of Disorder - N{domain.n}") ax.title.set_text(f"Probability of Disorder - N{domain.n}")
ax.set_xlabel("Width") ax.set_xlabel("Width")
ax.set_ylabel("Disordered Equilibria") ax.set_ylabel("Disordered Equilibria")
boa_y_min = round(min(all_disorder_count)/20)*20 - 5 boa_y_min = round(min(all_disorder_count) / 20) * 20 - 5
ax.set_yticks(np.arange(boa_y_min, 100.01, 2.5)) ax.set_yticks(np.arange(boa_y_min, 100.01, 2.5))
fig.savefig(fig_folder / "Probability of Disorder.png") fig.savefig(fig_folder / "Probability of Disorder.png")
# Density of States diagram. # Density of States diagram.
fig, ax = plt.subplots(figsize=(16, 8)) fig, ax = plt.subplots(figsize=(16, 8))
distinct_ordered, distinct_unordered = [], [] distinct_ordered, distinct_unordered = [], []
@ -143,21 +177,20 @@ def main():
distinct_unordered.append(equal_shape.count(False)) distinct_unordered.append(equal_shape.count(False))
ax2 = ax.twinx() ax2 = ax.twinx()
ax.plot(widths, distinct_unordered, label="Unordered Equilibria", color='C0') ax.plot(widths, distinct_unordered, label="Unordered Equilibria", color="C0")
ax2.plot(widths, distinct_ordered, label="Ordered Equilibria", color='C1') ax2.plot(widths, distinct_ordered, label="Ordered Equilibria", color="C1")
axis_settings(ax, widths) axis_settings(ax, widths)
ax.title.set_text(f"Density of States - N{domain.n}") ax.title.set_text(f"Density of States - N{domain.n}")
ax.set_xlabel("Width") ax.set_xlabel("Width")
ax.set_ylabel("Number of States (Disordered)", color='C0') ax.set_ylabel("Number of States (Disordered)", color="C0")
ax2.set_ylabel("Number of States (Ordered)", color='C1') ax2.set_ylabel("Number of States (Ordered)", color="C1")
dos_y_max_unorder = 1.05*max(distinct_unordered) dos_y_max_unorder = 1.05 * max(distinct_unordered)
dos_y_max_order = 1.05*max(distinct_ordered) dos_y_max_order = 1.05 * max(distinct_ordered)
ax.set_yticks(np.linspace(0, dos_y_max_unorder, 20).astype(int)) ax.set_yticks(np.linspace(0, dos_y_max_unorder, 20).astype(int))
#ax.set_yticks(np.arange(0, dos_y_max_unorder, round(dos_y_max_unorder/200, 1)*10)) # ax.set_yticks(np.arange(0, dos_y_max_unorder, round(dos_y_max_unorder/200, 1)*10))
ax2.set_yticks(np.arange(0, dos_y_max_order)) ax2.set_yticks(np.arange(0, dos_y_max_order))
fig.savefig(fig_folder / "Density Of States.png") fig.savefig(fig_folder / "Density Of States.png")
# Defect density diagram # Defect density diagram
@ -165,17 +198,19 @@ def main():
defects = [] defects = []
for width in widths: for width in widths:
defects.append(sum([c[2] for c in data["all"][width] if not c[1]])/len(data["all"][width])) defects.append(
sum([c[2] for c in data["all"][width] if not c[1]])
/ len(data["all"][width])
)
ax.plot(widths, defects) ax.plot(widths, defects)
axis_settings(ax, widths) axis_settings(ax, widths)
ax.title.set_text(f"Average Defects - N{domain.n}") ax.title.set_text(f"Average Defects - N{domain.n}")
ax.set_xlabel("Width") ax.set_xlabel("Width")
ax.set_ylabel("Defects") ax.set_ylabel("Defects")
ax.set_yticks(np.arange(0, 1+max(defects), 0.5)) ax.set_yticks(np.arange(0, 1 + max(defects), 0.5))
fig.savefig(fig_folder / "Defects.png") fig.savefig(fig_folder / "Defects.png")
# Bifurcation diagram # Bifurcation diagram
fig, ax = plt.subplots(figsize=(16, 8)) fig, ax = plt.subplots(figsize=(16, 8))
@ -200,19 +235,23 @@ def main():
max_unorder = np.asarray([max(width) for width in unordered_energies]) max_unorder = np.asarray([max(width) for width in unordered_energies])
offset = np.array(order_data["min"]) offset = np.array(order_data["min"])
#offset = np.array(min_order) # offset = np.array(min_order)
min_unorder_off = min_unorder - offset min_unorder_off = min_unorder - offset
max_unorder_off = max_unorder - offset max_unorder_off = max_unorder - offset
ax.plot(widths, min_order - offset, color='C1') ax.plot(widths, min_order - offset, color="C1")
#ax.plot(widths, max_order - offset, color='C1', linestyle='dotted') # ax.plot(widths, max_order - offset, color='C1', linestyle='dotted')
ax.plot(widths, min_unorder_off, color='C0') ax.plot(widths, min_unorder_off, color="C0")
ax.plot(widths, max_unorder_off, color='C0', linestyle='dotted') ax.plot(widths, max_unorder_off, color="C0", linestyle="dotted")
axis_settings(ax, widths) axis_settings(ax, widths)
with open("N83-od.txt", "w") as f:
f.write(", ".join([str(x) for x in widths]) + "\n")
f.write(", ".join([str(x) for x in min_unorder_off]) + "\n")
f.write(", ".join([str(x) for x in max_unorder_off]))
for i in null_unorder: for i in null_unorder:
ax.scatter(widths[i], 0, ax.scatter(widths[i], 0, marker="X", color="blue", s=50, zorder=4)
marker='X', color="blue", s=50, zorder=4)
# ax.scatter(widths[i], max_unorder[i] - offset[i], # ax.scatter(widths[i], max_unorder[i] - offset[i],
# marker='X', edgecolors="blue", facecolors='none', s=100, zorder=4) # marker='X', edgecolors="blue", facecolors='none', s=100, zorder=4)
@ -220,13 +259,16 @@ def main():
ax.set_xlabel("Width") ax.set_xlabel("Width")
ax.set_ylabel("Reduced Energy") ax.set_ylabel("Reduced Energy")
bif_y_max = np.max(np.abs(np.concatenate((min_unorder_off, max_unorder_off)))) bif_y_max = np.max(np.abs(np.concatenate((min_unorder_off, max_unorder_off))))
bif_top = np.arange(0, bif_y_max, round(bif_y_max/20, -math.floor(math.log10(bif_y_max/20)))) bif_top = np.arange(
0, bif_y_max, round(bif_y_max / 20, -math.floor(math.log10(bif_y_max / 20)))
)
ax.set_yticks(np.concatenate((-bif_top[1:][::-1], bif_top))) ax.set_yticks(np.concatenate((-bif_top[1:][::-1], bif_top)))
fig.savefig(fig_folder / "Bifurcation.png") fig.savefig(fig_folder / "Bifurcation.png")
print(f"Wrote to {fig_folder}.") print(f"Wrote to {fig_folder}.")
if __name__ == '__main__':
if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false" os.environ["QT_LOGGING_RULES"] = "*=false"
try: try:
main() main()

View File

@ -10,16 +10,20 @@ OUTPUT_DIR = Path("squish_output")
STR_TO_ENERGY = { STR_TO_ENERGY = {
"area": AreaEnergy, "area": AreaEnergy,
"radial-al": RadialALEnergy, "radial-al": RadialALEnergy,
"radial-t" : RadialTEnergy "radial-t": RadialTEnergy,
} }
def generate_filepath(sim: SimulationMode, fol: Union[str, Path], prec: int = 2) -> 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, prec), round(sim.domain.h, prec) width, height = round(sim.domain.w, prec), round(sim.domain.h, prec)
base_path = f"{fol}/{energy}{sim.title_str} - " + \ base_path = (
f"N{sim.domain.n} - {width:.{prec}f}x{height:.{prec}f}" 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)
@ -42,8 +46,7 @@ class DomainParams:
""" """
__slots__ = ['n', 'w', 'h', 'r', 'dim'] __slots__ = ["n", "w", "h", "r", "dim"]
def __init__(self, n: int, w: float, h: float, r: float) -> None: def __init__(self, n: int, w: float, h: float, r: float) -> None:
if n < 2: if n < 2:
@ -58,7 +61,6 @@ class DomainParams:
self.n, self.w, self.h, self.r = int(n), float(w), float(h), float(r) self.n, self.w, self.h, self.r = int(n), float(w), float(h), float(r)
self.dim = np.array([self.w, self.h]) self.dim = np.array([self.w, self.h])
def __iter__(self) -> Iterator: def __iter__(self) -> Iterator:
return iter((self.n, self.w, self.h, self.r)) return iter((self.n, self.w, self.h, self.r))
@ -74,26 +76,23 @@ class Energy:
""" """
__slots__ = ['mode'] __slots__ = ["mode"]
def __init__(self, mode: Union[str, VoronoiContainer]) -> None: def __init__(self, mode: Union[str, VoronoiContainer]) -> None:
if isinstance(mode, str): if isinstance(mode, str):
try: try:
self.mode = STR_TO_ENERGY[mode.lower()] self.mode = STR_TO_ENERGY[mode.lower()]
except KeyError: except KeyError:
raise ValueError(f"\'{mode}\' is not a valid energy!") raise ValueError(f"'{mode}' is not a valid energy!")
else: else:
if mode is not VoronoiContainer and issubclass(mode, VoronoiContainer): if mode is not VoronoiContainer and issubclass(mode, VoronoiContainer):
raise ValueError("Provided class is not a valid energy!") raise ValueError("Provided class is not a valid energy!")
self.mode = mode self.mode = mode
@property @property
def attr_str(self) -> str: def attr_str(self) -> str:
return self.mode.attr_str return self.mode.attr_str
@property @property
def title_str(self) -> str: def title_str(self) -> str:
return self.mode.title_str return self.mode.title_str

View File

@ -8,7 +8,10 @@ from multiprocessing import Pool, cpu_count
from .common import DomainParams, OUTPUT_DIR from .common import DomainParams, OUTPUT_DIR
SYMM = np.array([[0,0], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1], [0,-1], [1,-1]]) SYMM = np.array(
[[0, 0], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]
)
class SimData: class SimData:
"""Stores diagram information for a simulation. """Stores diagram information for a simulation.
@ -22,8 +25,7 @@ class SimData:
""" """
__slots__ = ['path', 'domains', 'energies', 'voronois', 'stats'] __slots__ = ["path", "domains", "energies", "voronois", "stats"]
def __init__(self, sim: Simulation) -> None: def __init__(self, sim: Simulation) -> None:
if sim is not None: if sim is not None:
@ -33,11 +35,9 @@ class SimData:
self.voronois = list([s.vor_data for s in sim]) self.voronois = list([s.vor_data for s in sim])
self.stats = list([s.stats 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: def slice(self, indices: List[int]) -> SimData:
new_data = SimData(None) new_data = SimData(None)
new_data.path = self.path new_data.path = self.path
@ -49,17 +49,24 @@ class SimData:
return new_data return new_data
def hist(
def hist(self, stat: str, i: int, bins: int = 10, bounds: Optional[Tuple[float, float]] = None, self,
cumul: bool = False, avg: bool = False) -> Tuple[numpy.ndarray, numpy.ndarray]: 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. """Generates a histogram from the selected data.
Arguments: Arguments:
stat (str): name of data to obtain stat (str): name of data to obtain
i (int): which frame to select from i (int): which frame to select from
bins (int): number of bins for the histogram. bins (int): number of bins for the histogram.
bounds (Optional[Tuple[float, float]]): upper and lower bounds of the histogram. bounds (Optional[Tuple[float, float]]): upper and lower bounds of the
this will automatically take the minimum and maximum value of not set. histogram. This will automatically take the minimum and maximum value
if not set.
cumul (bool): aggregates all data up to frame i if True. cumul (bool): aggregates all data up to frame i if True.
avg (bool): will average the data based on number of frames if True. avg (bool): will average the data based on number of frames if True.
@ -67,24 +74,27 @@ class SimData:
Tuple[numpy.ndarray, numpy.ndarray]: the histogram and its bins. Tuple[numpy.ndarray, numpy.ndarray]: the histogram and its bins.
""" """
if cumul: if cumul:
values = np.concatenate([f[stat] for f in self.stats[:(i+1)]]) values = np.concatenate([f[stat] for f in self.stats[: (i + 1)]])
else: else:
values = self.stats[i][stat] values = self.stats[i][stat]
if np.var(values) <= 1e-8: if np.var(values) <= 1e-8:
hist = np.zeros((bins,)) hist = np.zeros((bins,))
val = np.average(values) val = np.average(values)
hist[(bins+1) // 2 - 1] = len(values) hist[(bins + 1) // 2 - 1] = len(values)
bin_list = np.linspace(0, val, bins//2+1, endpoint=True) bin_list = np.linspace(0, val, bins // 2 + 1, endpoint=True)
bin_list = np.concatenate((bin_list, (bin_list+val)[1:])) bin_list = np.concatenate((bin_list, (bin_list + val)[1:]))
return hist, bin_list[not (bins%2):] return hist, bin_list[not (bins % 2) :]
hist, bin_edges = np.histogram(values, bins=bins, range=bounds) 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)]) bin_list = np.array(
[(bin_edges[i] + bin_edges[i + 1]) / 2 for i in range(len(bin_edges) - 1)]
)
if avg and cumul: if avg and cumul:
return hist / (i+1), bin_list return hist / (i + 1), bin_list
return hist, bin_list return hist, bin_list
@ -99,30 +109,30 @@ class Diagram:
""" """
__slots__ = ['sim', 'diagrams', 'cumulative'] __slots__ = ["sim", "diagrams", "cumulative"]
def __init__(
def __init__(self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = False) -> None: self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = False
) -> None:
self.sim = SimData(sim) 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, mode: str, fol: str, name: str = None) -> 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!")
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):
getattr(self, str(self.diagrams[0][0]) + '_plot')(frame, axes) getattr(self, str(self.diagrams[0][0]) + "_plot")(frame, axes)
else: else:
axes = np.atleast_2d(axes) axes = np.atleast_2d(axes)
it = np.nditer(self.diagrams, flags=["multi_index"]) it = np.nditer(self.diagrams, flags=["multi_index"])
for diagram in it: for diagram in it:
if diagram == "": if diagram == "":
continue continue
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: if name is None:
@ -134,33 +144,36 @@ class Diagram:
elif mode == "show": elif mode == "show":
plt.show() plt.show()
def voronoi_plot(self, i: int, ax: AxesSubplot) -> None: def voronoi_plot(self, i: int, ax: AxesSubplot) -> None:
domain = self.sim.domains[i] domain = self.sim.domains[i]
n, w, h = domain.n, domain.w, domain.h n, w, h = domain.n, domain.w, domain.h
scale = 1.5 scale = 1.5
area = n <= 60 area = n <= 60
voronoi_plot_2d(self.sim.voronois[i], ax, show_vertices=False, point_size = 7-n/100) voronoi_plot_2d(
ax.plot([-w, 2*w], [0, 0], 'r') self.sim.voronois[i], ax, show_vertices=False, point_size=7 - n / 100
ax.plot([-w, 2*w], [h, h], 'r') )
ax.plot([0,0], [-h, 2*h], 'r') ax.plot([-w, 2 * w], [0, 0], "r")
ax.plot([w, w], [-h, 2*h], 'r') ax.plot([-w, 2 * w], [h, h], "r")
ax.axis('equal') ax.plot([0, 0], [-h, 2 * h], "r")
ax.set_xlim([(1-scale)*w/2, (1+scale)*w/2]) ax.plot([w, w], [-h, 2 * h], "r")
ax.set_ylim([(1-scale)*h/2, (1+scale)*h/2]) ax.axis("equal")
ax.set_xlim([(1 - scale) * w / 2, (1 + scale) * w / 2])
ax.set_ylim([(1 - scale) * h / 2, (1 + scale) * h / 2])
ax.title.set_text("Voronoi Visualization") ax.title.set_text("Voronoi Visualization")
props = dict(boxstyle='round', facecolor='wheat', alpha=0.8) props = dict(boxstyle="round", facecolor="wheat", alpha=0.8)
defects = {5: {"x": [], "y": []}, 7: {"x": [], "y": []}} defects = {5: {"x": [], "y": []}, 7: {"x": [], "y": []}}
for j in range(n): for j in range(n):
for s in SYMM: for s in SYMM:
vec = (self.sim.voronois[i].points[j] + s*self.sim.domains[i].dim) vec = self.sim.voronois[i].points[j] + s * self.sim.domains[i].dim
if area: if area:
txt = ax.text(*vec, str(round(self.sim.stats[i]["site_areas"][j], 3))) txt = ax.text(
*vec, str(round(self.sim.stats[i]["site_areas"][j], 3))
)
txt.set_clip_on(True) txt.set_clip_on(True)
if self.sim.stats[i]["site_edge_count"][j] == 5: if self.sim.stats[i]["site_edge_count"][j] == 5:
@ -173,31 +186,35 @@ class Diagram:
ax.scatter(defects[5]["x"], defects[5]["y"], marker="p", color="red") ax.scatter(defects[5]["x"], defects[5]["y"], marker="p", color="red")
ax.scatter(defects[7]["x"], defects[7]["y"], marker="*", color="red") ax.scatter(defects[7]["x"], defects[7]["y"], marker="*", color="red")
ax.text(
ax.text(0.05, 0.95, f'Energy: {self.sim.energies[i]}', transform=ax.transAxes, fontsize=14, 0.05,
verticalalignment='top', bbox=props) 0.95,
f"Energy: {self.sim.energies[i]}",
transform=ax.transAxes,
fontsize=14,
verticalalignment="top",
bbox=props,
)
def energy_plot(self, i: int, ax: AxesSubplot) -> None: def energy_plot(self, i: int, ax: AxesSubplot) -> None:
ax.set_xlim([0, len(self.sim)]) ax.set_xlim([0, len(self.sim)])
energies = self.sim.energies[:(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")
ax.set_ylabel("Energy") ax.set_ylabel("Energy")
ax.grid() ax.grid()
def site_areas_plot(self, i: int, ax: AxesSubplot) -> None: def site_areas_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_areas", i, cumul=self.cumulative, avg=True) 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.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Site Areas') ax.title.set_text("Site Areas")
ax.set_xlabel("Area") ax.set_xlabel("Area")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
@ -207,24 +224,26 @@ class Diagram:
# if color != 'C0': # if color != 'C0':
# xtick.set_color(color) # xtick.set_color(color)
def site_edge_count_plot(self, i: int, ax: AxesSubplot) -> None: 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) 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.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Edges per Site') ax.title.set_text("Edges per Site")
ax.set_xlabel("Number of Edges") ax.set_xlabel("Number of Edges")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
ax.set_xticklabels([int(z) for z in x]) ax.set_xticklabels([int(z) for z in x])
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def site_isos_plot(self, i: int, ax: AxesSubplot) -> None: 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) 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.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Isoparametric Values') ax.title.set_text("Isoparametric Values")
ax.set_xlabel("Isoparametric Value") ax.set_xlabel("Isoparametric Value")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
@ -234,83 +253,84 @@ class Diagram:
# if color != 'C0': # if color != 'C0':
# xtick.set_color(color) # xtick.set_color(color)
def site_energies_plot(self, i: int, ax: AxesSubplot) -> None: def site_energies_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_energies", i, cumul=self.cumulative, avg=True) y, x = self.sim.hist("site_energies", i, cumul=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")
ax.set_xlabel("Energy") ax.set_xlabel("Energy")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
ax.ticklabel_format(useOffset=False) ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def avg_radius_plot(self, i: int, ax: AxesSubplot) -> None: def avg_radius_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("avg_radius", i, cumul=self.cumulative, avg=True) y, x = self.sim.hist("avg_radius", i, cumul=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")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
ax.ticklabel_format(useOffset=False) ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def isoparam_avg_plot(self, i: int, ax: AxesSubplot) -> None: def isoparam_avg_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("isoparam_avg", i, cumul=self.cumulative, avg=True) y, x = self.sim.hist("isoparam_avg", i, cumul=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")
ax.set_xlabel("Isoperimetric Average") ax.set_xlabel("Isoperimetric Average")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
ax.ticklabel_format(useOffset=False) ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def edge_lengths_plot(self, i: int, ax: AxesSubplot) -> None: def edge_lengths_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("edge_lengths", i, 30, cumul=self.cumulative, avg=True) 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.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Edge Lengths') ax.title.set_text("Edge Lengths")
ax.set_xlabel("Length") ax.set_xlabel("Length")
ax.set_ylabel("Average Occurances") ax.set_ylabel("Average Occurances")
ax.set_xticks(x) ax.set_xticks(x)
ax.set_xticklabels(ax.get_xticks(), rotation = 90) ax.set_xticklabels(ax.get_xticks(), rotation=90)
ax.xaxis.set_major_formatter(FormatStrFormatter('%.3f')) ax.xaxis.set_major_formatter(FormatStrFormatter("%.3f"))
#ax.ticklabel_format(useOffset=False) # ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.yaxis.set_major_locator(MaxNLocator(integer=True))
# for xtick, color in zip(ax.get_xticklabels(), lengths_bar[2]): # for xtick, color in zip(ax.get_xticklabels(), lengths_bar[2]):
# if color != 'C0': # if color != 'C0':
# xtick.set_color(color) # xtick.set_color(color)
def eigs_plot(self, i: int, ax: AxesSubplot) -> None: def eigs_plot(self, i: int, ax: AxesSubplot) -> None:
try: try:
eigs = self.sim.stats[i]["eigs"] eigs = self.sim.stats[i]["eigs"]
ax.plot(list(range(len(eigs))), eigs, marker='o', linestyle='dashed', color='C0') ax.plot(
ax.plot([0,len(eigs)], [0, 0], color="red") list(range(len(eigs))), eigs, marker="o", linestyle="dashed", color="C0"
)
ax.plot([0, len(eigs)], [0, 0], color="red")
except KeyError: except KeyError:
ax.text(0.5, 0.5, "Not Computed") ax.text(0.5, 0.5, "Not Computed")
ax.title.set_text('Hessian Eigenvalues') ax.title.set_text("Hessian Eigenvalues")
ax.set_xlabel("") ax.set_xlabel("")
ax.set_ylabel("Value") ax.set_ylabel("Value")
def render_frames(self, frames: List[int], fol: str = "frames") -> None:
def render_frames(self, frames: List[int], fol: str = 'frames') -> None:
OUTPUT_DIR.mkdir(exist_ok=True) OUTPUT_DIR.mkdir(exist_ok=True)
self.sim.path.mkdir(exist_ok=True) self.sim.path.mkdir(exist_ok=True)
(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()):
start, end = int(i*len(frames)/cpu_count()), int((i+1)*len(frames)/cpu_count()) start, end = (
int(i * len(frames) / cpu_count()),
int((i + 1) * len(frames) / cpu_count()),
)
new_dia = Diagram(None, self.diagrams, self.cumulative) new_dia = Diagram(None, self.diagrams, self.cumulative)
new_dia.sim = self.sim.slice(frames[start:end]) new_dia.sim = self.sim.slice(frames[start:end])
combo_list.append((new_dia, fol, start, len(frames[start:end]), len(frames))) combo_list.append(
(new_dia, fol, start, len(frames[start:end]), len(frames))
)
# Free up memory, since it's already duplicated to other cores. # Free up memory, since it's already duplicated to other cores.
self.sim = self.sim.slice([]) self.sim = self.sim.slice([])
@ -319,8 +339,7 @@ class Diagram:
pass pass
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"]:
@ -330,27 +349,31 @@ 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)-1, fps*time)).astype(int)) frames = list(
np.round(np.linspace(0, len(self.sim) - 1, fps * time)).astype(int)
)
self.render_frames(frames, 'temp') self.render_frames(frames, "temp")
path = self.sim.path / 'simulation.mp4' path = self.sim.path / "simulation.mp4"
print("Assembling MP4...", flush=True) print("Assembling MP4...", flush=True)
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i' + \ os.system(
f' \"{self.sim.path}/temp/img%05d.png\"' + \ 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 i in range(len(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, str, int, int, int]) -> None: def render_frame_range(combo: Tuple[Diagram, str, int, int, int]) -> None:
@ -358,6 +381,10 @@ def render_frame_range(combo: Tuple[Diagram, str, int, int, int]) -> None:
for i in range(length): for i in range(length):
self.generate_frame(i, "save", fol, f"img{i+offset:05}.png") 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' {i}/{num_frames} frames rendered.', flush=True, end='\r') f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i}/{num_frames} frames rendered.",
flush=True,
end="\r",
)

View File

@ -6,11 +6,12 @@ from math import gcd, sqrt, log, tan, atan, pi
Config = Tuple[int, int] Config = Tuple[int, int]
def configurations(domain: DomainParams) -> List[Config]: def configurations(domain: DomainParams) -> List[Config]:
n, w, h = domain.n, domain.w, domain.h n, w, h = domain.n, domain.w, domain.h
valid = [] valid = []
mults = np.arange(n) mults = np.arange(n)
configs = np.dstack((np.repeat(mults,n).T, np.tile(mults, n).T))[0][1:] configs = np.dstack((np.repeat(mults, n).T, np.tile(mults, n).T))[0][1:]
for i in range(len(configs)): for i in range(len(configs)):
eq_x = n if configs[i][0] == 0 else configs[i][0] eq_x = n if configs[i][0] == 0 else configs[i][0]
eq_y = n if configs[i][1] == 0 else configs[i][1] eq_y = n if configs[i][1] == 0 else configs[i][1]
@ -18,8 +19,13 @@ def configurations(domain: DomainParams) -> List[Config]:
if gcd(eq_x, eq_y) != 1: if gcd(eq_x, eq_y) != 1:
continue continue
vecs = configs[i]*np.dstack((w*mults, h*mults)).swapaxes(0,1)/n % domain.dim vecs = (
vmod2 = np.squeeze(np.matmul(vecs, vecs.transpose(0,2,1))) configs[i]
* np.dstack((w * mults, h * mults)).swapaxes(0, 1)
/ n
% domain.dim
)
vmod2 = np.squeeze(np.matmul(vecs, vecs.transpose(0, 2, 1)))
vmodv = np.squeeze(vecs).dot(vecs[1].T).T.flatten() vmodv = np.squeeze(vecs).dot(vecs[1].T).T.flatten()
if np.all(vmod2 >= vmodv): if np.all(vmod2 >= vmodv):
@ -28,20 +34,26 @@ def configurations(domain: DomainParams) -> List[Config]:
return valid return valid
def get_config_generators(domain: DomainParams, config: Config) -> Tuple[Config, Config]: def get_config_generators(
domain: DomainParams, config: Config
) -> Tuple[Config, Config]:
n, w, h = domain.n, domain.w, domain.h n, w, h = domain.n, domain.w, domain.h
q1 = sites(domain, config) q1 = sites(domain, config)
v = q1[1] v = q1[1]
# Sites to check can ignore 0*v and v itself. # Sites to check can ignore 0*v and v itself.
all_sites = np.concatenate((q1, q1-[w,0], q1-[w,h], q1-[0,h]))[2:] all_sites = np.concatenate((q1, q1 - [w, 0], q1 - [w, h], q1 - [0, h]))[2:]
# Checking 0 < ax + by < v*v to make the sites are within the region. # Checking 0 < ax + by < v*v to make the sites are within the region.
tol = 1e-3 tol = 1e-3
vdot = np.matmul(all_sites, v) vdot = np.matmul(all_sites, v)
in_box = all_sites[np.where((-tol <= vdot) & (vdot <= (v.dot(v)+tol)))[0]] in_box = all_sites[np.where((-tol <= vdot) & (vdot <= (v.dot(v) + tol)))[0]]
in_box = np.expand_dims(in_box, 0).swapaxes(0,1) # Used for the next step, getting site*site in_box = np.expand_dims(in_box, 0).swapaxes(
0, 1
) # Used for the next step, getting site*site
w = in_box[np.argmin(np.squeeze(np.matmul(in_box, in_box.transpose(0,2,1))))].flatten() w = in_box[
np.argmin(np.squeeze(np.matmul(in_box, in_box.transpose(0, 2, 1))))
].flatten()
return tuple(v), tuple(w) return tuple(v), tuple(w)
@ -49,7 +61,7 @@ def get_config_generators(domain: DomainParams, config: Config) -> Tuple[Config,
def sites(domain: DomainParams, config: Config) -> numpy.ndarray: def sites(domain: DomainParams, config: Config) -> numpy.ndarray:
n, w, h = domain.n, domain.w, domain.h n, w, h = domain.n, domain.w, domain.h
config, mults = np.array(config), np.arange(domain.n) config, mults = np.array(config), np.arange(domain.n)
return (config*np.dstack((w*mults, h*mults))[0]/n) % domain.dim return (config * np.dstack((w * mults, h * mults))[0] / n) % domain.dim
def area(domain: DomainParams, config: Config) -> float: def area(domain: DomainParams, config: Config) -> float:
@ -57,7 +69,11 @@ def area(domain: DomainParams, config: Config) -> float:
v, w = np.array(v), np.array(w) v, w = np.array(v), np.array(w)
c = circumcenter(v, w) c = circumcenter(v, w)
return mag(v)*mag(v/2 - c) + mag(w)*mag(w/2-c) + mag(v-w)*mag((v+w)/2-c) return (
mag(v) * mag(v / 2 - c)
+ mag(w) * mag(w / 2 - c)
+ mag(v - w) * mag((v + w) / 2 - c)
)
def avg_radius(domain: DomainParams, config: Config) -> float: def avg_radius(domain: DomainParams, config: Config) -> float:
@ -65,20 +81,23 @@ def avg_radius(domain: DomainParams, config: Config) -> float:
v, w = np.array(v), np.array(w) v, w = np.array(v), np.array(w)
c = circumcenter(v, w) c = circumcenter(v, w)
return 2*(avg_rp(mag(v), 2*mag(v/2 - c)) + avg_rp(mag(w), 2*mag(w/2-c)) + \ return 2 * (
avg_rp(mag(v-w),2*mag((v+w)/2-c))) avg_rp(mag(v), 2 * mag(v / 2 - c))
+ avg_rp(mag(w), 2 * mag(w / 2 - c))
+ avg_rp(mag(v - w), 2 * mag((v + w) / 2 - c))
)
def avg_rp(d: float, l: float) -> float: def avg_rp(d: float, l: float) -> float:
return (d/(4*pi))*log(tan(.5*(atan(l/d)+pi/2))**2) return (d / (4 * pi)) * log(tan(0.5 * (atan(l / d) + pi / 2)) ** 2)
def circumcenter(v: numpy.ndarray, w: numpy.ndarray) -> Config: def circumcenter(v: numpy.ndarray, w: numpy.ndarray) -> Config:
det = 1/(2*rot(v).dot(w)) det = 1 / (2 * rot(v).dot(w))
v2, w2 = v.dot(v), w.dot(w) v2, w2 = v.dot(v), w.dot(w)
c = np.empty((2,)) c = np.empty((2,))
c[0], c[1] = w[1]*v2 - v[1]*w2,-w[0]*v2 + v[0]*w2 c[0], c[1] = w[1] * v2 - v[1] * w2, -w[0] * v2 + v[0] * w2
return det*c return det * c
def rot(v: numpy.ndarray) -> numpy.ndarray: def rot(v: numpy.ndarray) -> numpy.ndarray:

View File

@ -21,10 +21,11 @@ class Simulation:
""" """
__slots__ = ['domain', 'energy', 'path', 'frames'] __slots__ = ["domain", "energy", "path", "frames"]
def __init__(self, domain: DomainParams, energy: Energy, \ def __init__(
name: Optional[str, int] = None) -> None: 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 = []
@ -35,18 +36,15 @@ class Simulation:
else: else:
self.path = OUTPUT_DIR / name self.path = OUTPUT_DIR / name
def __iter__(self) -> Iterator: def __iter__(self) -> Iterator:
return iter(self.frames) return iter(self.frames)
def __getitem__(self, key: int) -> Energy: def __getitem__(self, key: int) -> Energy:
return self.frames[key] return self.frames[key]
def __len__(self) -> int: def __len__(self) -> int:
return len(self.frames) return len(self.frames)
def add_frame(self, points: Optional[numpy.ndarray] = None) -> None: def add_frame(self, points: Optional[numpy.ndarray] = None) -> None:
if points is None: if points is None:
points = np.random.random_sample((self.domain.n, 2)) * self.domain.dim points = np.random.random_sample((self.domain.n, 2)) * self.domain.dim
@ -59,7 +57,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 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.
@ -67,7 +64,6 @@ class Simulation:
distinct_avg_radii, distinct_count, new_frames = [], [], [] distinct_avg_radii, distinct_count, new_frames = [], [], []
for frame in self.frames: for frame in self.frames:
try: try:
stats = frame.stats stats = frame.stats
@ -90,38 +86,35 @@ class Simulation:
self.frames = new_frames self.frames = new_frames
return distinct_count return distinct_count
def save(self, info: Dict, overwrite: bool = False) -> None: def save(self, info: Dict, overwrite: bool = False) -> None:
OUTPUT_DIR.mkdir(exist_ok=True) OUTPUT_DIR.mkdir(exist_ok=True)
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, 'wb' if overwrite else '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, True) 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))
def frame_data(self, index: int) -> None: def frame_data(self, index: int) -> None:
f = self[index] f = self[index]
info = { info = {
"arr": f.site_arr, "arr": f.site_arr,
"domain": (f.n, f.w, f.h, f.r), "domain": (f.n, f.w, f.h, f.r),
"energy": f.energy, "energy": f.energy,
"stats": f.stats "stats": f.stats,
} }
return info return info
@staticmethod @staticmethod
def load(path: str) -> Tuple[Simulation, Generator]: def load(path: str) -> Tuple[Simulation, Generator]:
path = Path(path) path = Path(path)
def frames() -> Dict: def frames() -> Dict:
with open(path / 'data.squish', 'rb') as infile: with open(path / "data.squish", "rb") as infile:
first = True first = True
while True: while True:
try: try:
@ -133,17 +126,18 @@ class Simulation:
except EOFError: except EOFError:
break break
with open(path / 'data.squish', 'rb') as infile: with open(path / "data.squish", "rb") as infile:
sim_info = pickle.load(infile) sim_info = pickle.load(infile)
domain = DomainParams(*sim_info["domain"]) domain = DomainParams(*sim_info["domain"])
energy = Energy(sim_info["energy"]) energy = Energy(sim_info["energy"])
sim = STR_TO_SIM[sim_info["mode"]](domain, energy, *list(sim_info.values())[3:]) sim = STR_TO_SIM[sim_info["mode"]](
domain, energy, *list(sim_info.values())[3:]
)
sim.path = path sim.path = path
return sim, frames() return sim, frames()
@staticmethod @staticmethod
def from_file(path: str) -> Simulation: def from_file(path: str) -> Simulation:
sim, frames = Simulation.load(path) sim, frames = Simulation.load(path)
@ -167,16 +161,22 @@ class Flow(Simulation):
""" """
__slots__ = ['step_size', 'thres', 'adaptive'] __slots__ = ["step_size", "thres", "adaptive"]
attr_str = "flow" attr_str = "flow"
title_str = "Flow" title_str = "Flow"
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, def __init__(
adaptive: bool, name: Optional[str] = None) -> None: self,
domain: DomainParams,
energy: Energy,
step_size: float,
thres: float,
adaptive: bool,
name: Optional[str] = None,
) -> None:
super().__init__(domain, energy, name=name) super().__init__(domain, energy, name=name)
self.step_size, self.thres, self.adaptive = step_size, thres, adaptive self.step_size, self.thres, self.adaptive = step_size, thres, adaptive
@property @property
def initial_data(self) -> Dict: def initial_data(self) -> Dict:
info = { info = {
@ -185,18 +185,25 @@ class Flow(Simulation):
"energy": self.energy.attr_str, "energy": self.energy.attr_str,
"step_size": self.step_size, "step_size": self.step_size,
"thres": self.thres, "thres": self.thres,
"adaptive": self.adaptive "adaptive": self.adaptive,
} }
return info return info
def run(
self,
save: bool,
log: bool,
log_steps: int,
new_sites: Optional[numpy.ndarray] = None,
) -> None:
if log:
print(f"Find - {self.domain}", flush=True)
if save and len(self) == 0:
self.save(self.initial_data, True)
if len(self) == 0:
self.add_frame(new_sites)
def run(self, save: bool, log: bool, log_steps: int, i, stop = len(self) - 1, False
new_sites: Optional[numpy.ndarray] = None) -> None:
if log: print(f"Find - {self.domain}", flush=True)
if save and len(self) == 0: self.save(self.initial_data, True)
if len(self) == 0: self.add_frame(new_sites)
i, stop = len(self)-1, False
while not stop: # Get to threshold. while not stop: # Get to threshold.
if save: if save:
@ -214,10 +221,10 @@ class Flow(Simulation):
end = timer() end = timer()
if self.adaptive: if self.adaptive:
error = change - grad*self.step_size error = change - grad * self.step_size
tol = 10**min(-3, -2+log10(grad_norm)) tol = 10 ** min(-3, -2 + log10(grad_norm))
self.step_size *= (tol/np.linalg.norm(error))**.5 self.step_size *= (tol / np.linalg.norm(error)) ** 0.5
if not save: if not save:
del self.frames[0] del self.frames[0]
@ -227,10 +234,12 @@ class Flow(Simulation):
i += 1 i += 1
if (log and i % log_steps == 0) or stop: if (log and i % log_steps == 0) or stop:
print(f'Iteration: {i:05} | Energy: {frame.energy: .5f}' + \ print(
f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \ f"Iteration: {i:05} | Energy: {frame.energy: .5f}"
f'Time: {end-start: .3f} |', flush=True) + f" | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | "
+ f"Time: {end-start: .3f} |",
flush=True,
)
class Search(Simulation): class Search(Simulation):
@ -249,18 +258,25 @@ class Search(Simulation):
""" """
__slots__ = ['step_size', 'thres', 'adaptive', 'kernel_step', 'count'] __slots__ = ["step_size", "thres", "adaptive", "kernel_step", "count"]
attr_str = "search" attr_str = "search"
title_str = "Search" title_str = "Search"
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, def __init__(
adaptive: bool, kernel_step: float, count: int, self,
name: Optional[str] = None) -> None: domain: DomainParams,
energy: Energy,
step_size: float,
thres: float,
adaptive: bool,
kernel_step: float,
count: int,
name: Optional[str] = None,
) -> None:
super().__init__(domain, energy, name=name) super().__init__(domain, energy, name=name)
self.step_size, self.thres, self.adaptive = step_size, thres, adaptive self.step_size, self.thres, self.adaptive = step_size, thres, adaptive
self.kernel_step, self.count = kernel_step, count self.kernel_step, self.count = kernel_step, count
@property @property
def initial_data(self) -> Dict: def initial_data(self) -> Dict:
info = { info = {
@ -271,40 +287,54 @@ class Search(Simulation):
"thres": self.thres, "thres": self.thres,
"adaptive": self.adaptive, "adaptive": self.adaptive,
"kernel_step": self.kernel_step, "kernel_step": self.kernel_step,
"count": self.count "count": self.count,
} }
return info return info
def run(
def run(self, save: bool, log: bool, log_steps: int, self,
new_sites: Optional[numpy.ndarray] = None) -> None: save: bool,
if log: print(f'Travel - {self.domain}', flush=True) log: bool,
if save and len(self) == 0: self.save(self.initial_data, True) log_steps: int,
new_sites: Optional[numpy.ndarray] = None,
) -> None:
if log:
print(f"Travel - {self.domain}", flush=True)
if save and len(self) == 0:
self.save(self.initial_data, True)
for i in range(len(self), self.count): for i in range(len(self), self.count):
# Get to equilibrium. # Get to equilibrium.
sim = Flow(self.domain, self.energy, self.step_size, self.thres, self.adaptive) sim = Flow(
self.domain, self.energy, self.step_size, self.thres, self.adaptive
)
sim.add_frame(new_sites) sim.add_frame(new_sites)
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(self.frame_data(i)) if save:
if log: print(f'Equilibrium: {i:04}\n', flush=True) self.save(self.frame_data(i))
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.
hess = self.frames[i].hessian(10e-5) hess = self.frames[i].hessian(10e-5)
eigs = np.sort(np.linalg.eig(hess)[0]) eigs = np.sort(np.linalg.eig(hess)[0])
self.frames[i].stats["eigs"] = eigs self.frames[i].stats["eigs"] = eigs
zero_eigs = np.count_nonzero(np.isclose(eigs, np.zeros((len(eigs),)), atol=1e-4)) zero_eigs = np.count_nonzero(
np.isclose(eigs, np.zeros((len(eigs),)), atol=1e-4)
)
if zero_eigs == 2: if zero_eigs == 2:
new_sites = None new_sites = None
else: else:
print("Warning: Nullity > 2. Expected if AreaEnergy.", flush=True) print("Warning: Nullity > 2. Expected if AreaEnergy.", flush=True)
ns = null_space(hess, 10e-4).T ns = null_space(hess, 10e-4).T
vec = ns[random.randint(0, len(ns)-1)].reshape((self.domain.n, 2)) # Random vector. vec = ns[random.randint(0, len(ns) - 1)].reshape(
new_sites = self.frames[i].add_sites(self.kernel_step*vec) (self.domain.n, 2)
) # Random vector.
new_sites = self.frames[i].add_sites(self.kernel_step * vec)
class Shrink(Simulation): class Shrink(Simulation):
@ -312,7 +342,7 @@ class Shrink(Simulation):
Attributes: Attributes:
domain (DomainParams): domain parameters for this simulation. domain (DomainParams): domain parameters for this simulation.
energy (Energy): energy being used for caluclations. energy (Energy): energy being used for calcu1lations.
path (Path): path to the location of where to store simulation files. path (Path): path to the location of where to store simulation files.
frames (List[VoronoiContainer]): stores frames of the simulation. frames (List[VoronoiContainer]): stores frames of the simulation.
step_size (float): size fo step by for each iteration. step_size (float): size fo step by for each iteration.
@ -323,18 +353,27 @@ class Shrink(Simulation):
""" """
__slots__ = ['step_size', 'thres', 'adaptive', 'delta', 'stop_width'] __slots__ = ["step_size", "thres", "adaptive", "delta", "stop_width"]
attr_str = "shrink" attr_str = "shrink"
title_str = "Shrink" title_str = "Shrink"
def __init__(
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float, self,
adaptive: bool, delta: float, stop_width: float, domain: DomainParams,
name: Optional[str] = None) -> None: energy: Energy,
step_size: float,
thres: float,
adaptive: bool,
delta: float,
stop_width: float,
name: Optional[str] = None,
) -> None:
super().__init__(domain, energy, name=name) super().__init__(domain, energy, name=name)
self.step_size, self.thres, self.adaptive = step_size, thres, adaptive self.step_size, self.thres, self.adaptive = step_size, thres, adaptive
self.delta, self.stop_width = self.domain.w*delta/100, self.domain.w*stop_width self.delta, self.stop_width = (
self.domain.w * delta / 100,
self.domain.w * stop_width,
)
@property @property
def initial_data(self) -> Dict: def initial_data(self) -> Dict:
@ -346,37 +385,45 @@ class Shrink(Simulation):
"thres": self.thres, "thres": self.thres,
"adaptive": self.adaptive, "adaptive": self.adaptive,
"delta": self.delta, "delta": self.delta,
"stop_width": self.stop_width "stop_width": self.stop_width,
} }
return info return info
def run(
def run(self, save: bool, log: bool, log_steps: int, self,
new_sites: Optional[numpy.ndarray] = None) -> None: save: bool,
if log: print(f'Shrink - {self.domain}', flush=True) log: bool,
if save and len(self) == 0: self.save(self.initial_data, True) log_steps: int,
new_sites: Optional[numpy.ndarray] = None,
) -> None:
if log:
print(f"Shrink - {self.domain}", flush=True)
if save and len(self) == 0:
self.save(self.initial_data, True)
width = self.domain.w if len(self.frames) == 0 else self.frames[-1].w width = self.domain.w if len(self.frames) == 0 else self.frames[-1].w
i = 0 i = 0
while width >= self.stop_width: while width >= self.stop_width:
# Get to equilibrium. # Get to equilibrium.
new_domain = DomainParams(self.domain.n, width, self.domain.h, self.domain.r) new_domain = DomainParams(
sim = Flow(new_domain, self.energy, self.step_size, self.thres, self.adaptive) self.domain.n, width, self.domain.h, self.domain.r
)
sim = Flow(
new_domain, self.energy, self.step_size, self.thres, self.adaptive
)
sim.add_frame(new_sites) sim.add_frame(new_sites)
sim.run(False, log, log_steps) sim.run(False, log, log_steps)
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(self.frame_data(i)) if save:
self.save(self.frame_data(i))
if log: print(f'Width: {width:.4f}\n') if log:
print(f"Width: {width:.4f}\n")
width -= self.delta width -= self.delta
i += 1 i += 1
STR_TO_SIM = { STR_TO_SIM = {"flow": Flow, "search": Search, "shrink": Shrink}
"flow": Flow,
"search": Search,
"shrink": Shrink
}

View File

@ -13,11 +13,12 @@ dia_presets = {
"energy": [["voronoi", "energy"]], "energy": [["voronoi", "energy"]],
"stats": [ "stats": [
["voronoi", "eigs", "site_edge_count"], ["voronoi", "eigs", "site_edge_count"],
["site_isos", "site_energies", "edge_lengths"] ["site_isos", "site_energies", "edge_lengths"],
], ],
"eigs": [["voronoi", "eigs"]] "eigs": [["voronoi", "eigs"]],
} }
def check_params(container: Dict, needed: List[str], valid: Dict) -> None: def check_params(container: Dict, needed: List[str], valid: Dict) -> None:
"""Checks container for the necessary items, and raises """Checks container for the necessary items, and raises
an error if the parameter is not found. an error if the parameter is not found.
@ -25,41 +26,65 @@ def check_params(container: Dict, needed: List[str], valid: Dict) -> None:
Args: Args:
container (Dict): contains the submitted parameters. container (Dict): contains the submitted parameters.
needed (List[str]): contains the needed paramters. needed (List[str]): contains the needed paramters.
valid: (Dict): if there are specific valid parameters, it will also check for these. valid: (Dict): if there are specific valid parameters, it will also check these.
""" """
for need in needed: for need in needed:
if need not in container: if need not in container:
raise ValueError(f"Parameter \'{need}\' is required.") raise ValueError(f"Parameter '{need}' is required.")
if need in valid: if need in valid:
if type(valid[need]) is list: if type(valid[need]) is list:
if container[need] not in valid[need]: if container[need] not in valid[need]:
raise ValueError(f"Parameter \'{need}\' must be one of these values: " + \ raise ValueError(
f"{str(valid[need])[1:-1]}.") f"Parameter '{need}' must be one of these values: "
+ f"{str(valid[need])[1:-1]}."
)
elif valid[need] == "positive": elif valid[need] == "positive":
if container[need] < 0: if container[need] < 0:
raise ValueError(f"Parameter \'{need}\' must be positive.") raise ValueError(f"Parameter '{need}' must be positive.")
def main(): def main():
# Loading configuration and settings. # Loading configuration and settings.
parser = argparse.ArgumentParser("Squish") parser = argparse.ArgumentParser("Squish")
parser.add_argument('sim_conf', metavar='/path/to/config.json', parser.add_argument(
help="configuration file for a simulation") "sim_conf",
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', default=False, metavar="/path/to/config.json",
help="suppress all normal output") help="configuration file for a simulation",
parser.add_argument('-l', '--log', dest='log_steps', default=50, type=int, )
help="number of iterations before logging") parser.add_argument(
parser.add_argument('-i', dest='input_file', metavar='/path/to/sim', "-q",
help="folder that contains outputted simulation files.", default=None) "--quiet",
dest="quiet",
action="store_true",
default=False,
help="suppress all normal output",
)
parser.add_argument(
"-l",
"--log",
dest="log_steps",
default=50,
type=int,
help="number of iterations before logging",
)
parser.add_argument(
"-i",
dest="input_file",
metavar="/path/to/sim",
help="folder that contains outputted simulation files.",
default=None,
)
parser.add_argument('--n_objects', dest='n', type=int, help="objects in domain") parser.add_argument("--n_objects", dest="n", type=int, help="objects in domain")
parser.add_argument('--width', dest='w', type=float, help="width of domain") parser.add_argument("--width", dest="w", type=float, help="width of domain")
parser.add_argument('--height', dest='h', type=float, help="height of domain") parser.add_argument("--height", dest="h", type=float, help="height of domain")
parser.add_argument('--natural_radius', dest='r', type=float, help="natural radius of object") parser.add_argument(
parser.add_argument('--energy', dest='energy', help="energy type of system") "--natural_radius", dest="r", type=float, help="natural radius of object"
)
parser.add_argument("--energy", dest="energy", help="energy type of system")
args = parser.parse_args() args = parser.parse_args()
do_sim(args, args.input_file) do_sim(args, args.input_file)
@ -69,38 +94,63 @@ def do_sim(args, file):
with open(args.sim_conf) as f: with open(args.sim_conf) as f:
params = json.load(f) params = json.load(f)
check_params(params, ["domain", "simulation"], {}) check_params(params, ["domain", "simulation"], {})
dmn_params, sim_params = params["domain"], params["simulation"] dmn_params, sim_params = params["domain"], params["simulation"]
overrides = {args.n: "n_objects", args.w: "width", args.h: "height", args.r: "natural_radius", overrides = {
args.energy: "energy"} args.n: "n_objects",
args.w: "width",
args.h: "height",
args.r: "natural_radius",
args.energy: "energy",
}
for arg, arg_name in overrides.items(): for arg, arg_name in overrides.items():
if arg is not None: if arg is not None:
dmn_params[arg_name] = arg dmn_params[arg_name] = arg
check_params(dmn_params, ["n_objects", "width", "height", "natural_radius", "energy"], { check_params(
"n_objects": "positive", "width": "positive", "height": "positive", dmn_params,
"natural_radius": "positive", "energy": ["area", "radial-al", "radial-t"] ["n_objects", "width", "height", "natural_radius", "energy"],
}) {
domain = DomainParams(dmn_params["n_objects"], dmn_params["width"], \ "n_objects": "positive",
dmn_params["height"], dmn_params["natural_radius"]) "width": "positive",
"height": "positive",
"natural_radius": "positive",
"energy": ["area", "radial-al", "radial-t"],
},
)
domain = DomainParams(
dmn_params["n_objects"],
dmn_params["width"],
dmn_params["height"],
dmn_params["natural_radius"],
)
energy = Energy(dmn_params["energy"]) energy = Energy(dmn_params["energy"])
points = None points = None
if "points" in dmn_params: if "points" in dmn_params:
if type(dmn_params["points"]) is str: if type(dmn_params["points"]) is str:
with open(Path(dmn_params["points"]), 'rb') as f: with open(Path(dmn_params["points"]), "rb") as f:
points = np.load(f) points = np.load(f)
else: else:
points = np.asarray(dmn_params["points"]) points = np.asarray(dmn_params["points"])
check_params(sim_params, ["mode", "step_size", "threshold", "save_sim", "adaptive"], { check_params(
"mode": ["flow", "search", "shrink"], "step_size": "positive", "threshold": "positive", sim_params,
}) ["mode", "step_size", "threshold", "save_sim", "adaptive"],
mode, step, thres, adaptive, save_sim = sim_params["mode"], sim_params["step_size"], \ {
sim_params["threshold"], sim_params["adaptive"], \ "mode": ["flow", "search", "shrink"],
sim_params["save_sim"] "step_size": "positive",
"threshold": "positive",
},
)
mode, step, thres, adaptive, save_sim = (
sim_params["mode"],
sim_params["step_size"],
sim_params["threshold"],
sim_params["adaptive"],
sim_params["save_sim"],
)
name = sim_params.get("name") name = sim_params.get("name")
@ -108,17 +158,37 @@ def do_sim(args, file):
if mode == "flow": if mode == "flow":
sim = Flow(domain, energy, step, thres, adaptive, name=name) sim = Flow(domain, energy, step, thres, adaptive, name=name)
elif mode == "search": elif mode == "search":
check_params(sim_params, ["manifold_step_size", "eq_stop_count"], { check_params(
"manifold_step_size": "positive", "eq_stop_count": "positive" sim_params,
}) ["manifold_step_size", "eq_stop_count"],
sim = Search(domain, energy, step, thres, adaptive, sim_params["manifold_step_size"], {"manifold_step_size": "positive", "eq_stop_count": "positive"},
sim_params["eq_stop_count"], name=name) )
sim = Search(
domain,
energy,
step,
thres,
adaptive,
sim_params["manifold_step_size"],
sim_params["eq_stop_count"],
name=name,
)
elif mode == "shrink": elif mode == "shrink":
check_params(sim_params, ["width_change", "width_stop"], { check_params(
"width_change": "positive", "width_stop": "positive" sim_params,
}) ["width_change", "width_stop"],
sim = Shrink(domain, energy, step, thres, adaptive, sim_params["width_change"], {"width_change": "positive", "width_stop": "positive"},
sim_params["width_stop"], name=name) )
sim = Shrink(
domain,
energy,
step,
thres,
adaptive,
sim_params["width_change"],
sim_params["width_stop"],
name=name,
)
else: else:
sim = Simulation.from_file(file) sim = Simulation.from_file(file)
@ -126,12 +196,12 @@ def do_sim(args, file):
if "diagram" in params: if "diagram" in params:
save_diagram = True save_diagram = True
dia_params = params["diagram"] dia_params = params["diagram"]
check_params(dia_params, ["filetype", "figures"], { check_params(dia_params, ["filetype", "figures"], {"filetype": ["img", "mp4"]})
"filetype": ["img", "mp4"]
})
if dia_params["filetype"] == "mp4": if dia_params["filetype"] == "mp4":
if which("ffmpeg") is None: if which("ffmpeg") is None:
raise ValueError("The program 'ffmpeg' needs to be installed on your system.") raise ValueError(
"The program 'ffmpeg' needs to be installed on your system."
)
if type(dia_params["figures"]) is str: if type(dia_params["figures"]) is str:
dia_params["figures"] = np.asarray(dia_presets[dia_params["figures"]]) dia_params["figures"] = np.asarray(dia_presets[dia_params["figures"]])